最终效果
基于NodeMCU的物联网电灯控制系统设计
上图展现了小程序关灯过程的数据传输过程:用户下达关灯指令→小程序下发关灯指令→MQTT服务器接收关灯指令→下位机接收与处理关灯指令。
项目介绍
该项目是“物联网实验室监测控制系统设计(仿智能家居)”项目中的“家电控制设计”中的“电灯控制”子项目,最前者还包括“物联网设计”、“环境监测设计”、“门禁系统设计计”和“小程序设计”等内容。本文只介绍“电灯控制”部分。
项目功能实现的大致思路为:单片机先识别出当前处在的控制模式:为自动模式时,单片机采集光照传感器传来的光照强度数据,以此来控制灯的开闭;为手动模式时,用户的语音指令和小程序上的开关是一对双开关。单片机会识别这对双开关的最后一次命令,以此来控制灯的开闭。为自动模式或语音控制时,单片机会将灯的状态实时更新到MQTT服务器上,使小程序上的开关状态得到及时更新。控制模式通过语音指令切换。
硬件设计
接线
ESP-12F | GY-302 | LU_ASR01 | LED |
3v3 | VCC | 5V | |
GND | GND | G | |
D1 | SCL | ||
D2 | SDA | ||
D7 | + | ||
D8 | - | ||
RX | TX |
PCB设计
此电路板仅是为了代替杜邦线而已,上面只有引脚排座,而没有任何电子元件。
成本
ESP-12F | GY-302 | LU_ASR01 | LED |
27.9 | 7.4 | 47.5 | 2.5 |
其中共需85元左右来购买该项目所需的模块。此外还需1根数据线、焊接工具(电烙铁、锡丝、引脚排座)、PCB打板或若干杜邦线。
软件设计
LU-ASR01
该功能实现的原理及流程可参考:Arduino中借助LU-ASR01实现语音识别-CSDN博客
使用LU-ASR01语音识别开发板的语音识别功能。当语音识别模块识别到特定语音时,通过串口输出特定字符。此时,NodeMCU的串口会接收到这个特定字符,从而得知用户说出的特定语音。待识别语音、输出字符和输出语音之间的对应关系如下表所示:
待识别语音 | 输出字符 | 输出语音 |
自动模式 | a | 已进入自动模式 |
手动模式 | m | 已进入手动模式 |
开灯 | 1 | 已开灯 |
关灯 | 0 | 已关灯 |
代码
天问Block IDE中的程序如下:
ESP-12F
本次的开发环境为Arduino IDE,开发板型号为NodeMCU 0.9 (ESP-12 Module)。
本系统软件部分的流程如下图所示。初始化完成之后,系统默认进入自动模式(60LX为实验室环境昏暗与明亮的分界值)。之后会根据语音、小程序或光照值数据控制灯状态。
连接WiFi以及与MQTT服务器双向通信,可参考:利用ESP-01S中继实现STM32F103C8T6与MQTT服务器的串口双向通信_stm32串口接收esp01s数据-CSDN博客
获取光照强度,可参考:Arduino中使用GY-302测量环境中的光照强度-CSDN博客
解析JSON数据,可参考:Arduino中解析JSON数据-CSDN博客
控制LED,可参考:未完待续
代码
Arduino IDE中的程序如下:
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <Arduino.h>
#include <Wire.h> //IIC
#include <math.h>
// 设置wifi接入信息和MQTT服务器
const char* wifiname = "DOILMSBOIOT";
const char* password = "doilmsboiot";
const char* mqttServer = "broker.emqx.io";
bool receive_MQTT_message_flag = false; //1表示收到来自MQTT的信息但还未处理,0表示未收到来自MQTT的信息或已处理
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
// 待解析的json文件
String json = "{\"lamp\":0}";
// 创建DynamicJsonDocument对象
const size_t capacity = JSON_OBJECT_SIZE(1) + 24 ; //1表示待解析的JSON对象中有1对数据,24为解析过程中需要的额外空间,可在此网站计算 https://arduinojson.org/v6/assistant/#/step1
DynamicJsonDocument doc(capacity);
bool lamp_Bool_MQTT = false ; // 解析出的来自MQTT的灯的开关状态
int BH1750address = 0x23; //GY-302-BH1750的iic通讯地址
byte buff[2]; //用来储存GY-302-BH1750的iic传来的原始数据
long int light = 0; //光照值
char serial_information = '\0'; //从串口获得的信息
bool auto_flag = true; //是否为自动模式,即用光照传感器的值来决定灯的开关状态。它的反面为手动模式,可通过串口(语音)和MQTT(小程序)控制
void setup()
{
Serial.begin(9600);
WiFi.mode(WIFI_STA); //设置ESP8266工作模式为无线终端模式
connectWifi(); // 连接WiFi
mqttClient.setServer(mqttServer, 1883); // 设置MQTT服务器和端口号
mqttClient.setCallback(receiveCallback); // 设置MQTT订阅回调函数
connectMQTTserver(); // 连接MQTT服务器
Wire.begin(); //iic启动
led_initial();
}
void loop()
{
if (mqttClient.connected()) // 如果开发板成功连接服务器
{
mqttClient.loop(); // 处理信息(收到信息后的回调函数)以及心跳
}
else // 如果开发板未能成功连接服务器
{
connectMQTTserver(); // 则尝试连接服务器并订阅主题
}
if (receive_MQTT_message_flag == 1) //收到来自MQTT的信息但还未处理
{
deserializeJson(doc, json); // 反序列化数据
// 解析收到的数据信息
lamp_Bool_MQTT = doc["lamp"].as<bool>();
}
BH1750_Init(BH1750address);
if (BH1750_Read(BH1750address) == 2) //如果光照模块有数据传来
{
light = ((buff[0] << 8) | buff[1]) / 1.2;
Serial.print(light,DEC);
Serial.println(" lx");
}
if (Serial.available() > 0) //如果串口有数据传来
{
serial_information = Serial.read();
Serial.print("接收到的串口数据为:");
Serial.println(serial_information);
}
if(serial_information == 'm') //manual手动模式
{
auto_flag = false;
serial_information = '\0';
}
if(serial_information == 'a') //auto自动模式
{
auto_flag = true;
serial_information = '\0';
}
if(auto_flag == true) //如果为自动模式
{
if(light < 60) //光线弱
{
turn_on_light(); //开灯
pubMQTTmsg(true);
}
else //光线强
{
turn_off_light(); //关灯
pubMQTTmsg(false);
}
}
else //如果为手动模式,即可通过串口(语音)和MQTT(小程序)控制
{
if(serial_information == '1') //收到的串口(语音)指令为开灯,且尚未处理此信息
{
turn_on_light();
pubMQTTmsg(true);
serial_information = '\0';
}
if(serial_information == '0') //收到的串口(语音)指令为关灯,且尚未处理此信息
{
turn_off_light();
pubMQTTmsg(false);
serial_information = '\0';
}
if(receive_MQTT_message_flag == true && lamp_Bool_MQTT == true) //收到的MQTT(小程序)指令为开灯,且尚未处理此信息
{
turn_on_light();
receive_MQTT_message_flag = false;
}
if(receive_MQTT_message_flag == true && lamp_Bool_MQTT == false) //收到的MQTT(小程序)指令为关灯,且尚未处理此信息
{
turn_off_light();
receive_MQTT_message_flag = false;
}
}
delay(200);
}
int BH1750_Read(int address)
{
int i = 0;
Wire.beginTransmission(address);
Wire.requestFrom(address, 2);
while (Wire.available())
{
buff[i] = Wire.read(); // receive one byte
i++;
}
Wire.endTransmission();
return i;
}
void BH1750_Init(int address)
{
Wire.beginTransmission(address);
Wire.write(0x10);
Wire.endTransmission();
}
// 连接MQTT服务器并订阅主题
void connectMQTTserver()
{
// 根据ESP8266的MAC地址生成客户端ID(避免与其它ESP8266的客户端ID重名)
String clientId = "esp8266-" + WiFi.macAddress();
if (mqttClient.connect(clientId.c_str())) //如果成功连接MQTT服务器
{
Serial.print("MQTT Server Has Connected. ");
Serial.print("Server Address: ");
Serial.println(mqttServer);
Serial.print("ClientId: ");
Serial.println(clientId);
subscribeTopic(); // 订阅指定主题
}
else
{
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state());
delay(3000);
}
}
// 收到信息后的回调函数
void receiveCallback(char* topic, byte* payload, unsigned int length)
{
Serial.print("Message with the topic of [ ");
Serial.print(topic);
Serial.println(" ] has been received.");
Serial.print("Content: ");
for (int i = 0; i < length; i++)
{
Serial.print((char)payload[i]);
json[i] = (char)payload[i]; //将收到的信息赋给json,以便后续解析和发射信号
}
Serial.println("");
receive_MQTT_message_flag = 1; //表示收到来自MQTT的信息但还未处理
Serial.print("Message Length (Bytes) : ");
Serial.println(length);
Serial.println(" ");
}
void pubMQTTmsg(bool led_status) //向MQTT发布消息
{
String topicString = "deviceControl2"; //发布信息的主题
char publishTopic[topicString.length() + 1];
strcpy(publishTopic, topicString.c_str());
String messageString = led_status ? "{\"lamp\":true}" : "{\"lamp\":false}"; //发布信息的内容
char publishMsg[messageString.length() + 1];
strcpy(publishMsg, messageString.c_str());
if(mqttClient.publish(publishTopic, publishMsg, true)) //如果成功发布信息;publish函数第三个参数用于设置保留信息
{
Serial.print("Publish Topic: ");Serial.println(publishTopic);
Serial.print("Publish Retained message: ");Serial.println(publishMsg);
Serial.println("");
}
else //如果未能成功发布信息
{
Serial.println("Message Publish Failed.");
}
}
// 订阅指定主题
void subscribeTopic()
{
String topicString = "deviceControl3/lamp"; // 订阅的主题名称
char subTopic[topicString.length() + 1];
strcpy(subTopic, topicString.c_str());
if(mqttClient.subscribe(subTopic)) //如果成功订阅主题
{
Serial.print("Subscrib Topic: ");
Serial.println(subTopic);
Serial.println("");
} else
{
Serial.print("订阅主题失败");
}
}
// ESP8266连接wifi
void connectWifi()
{
WiFi.begin(wifiname, password);
Serial.println("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) //等待WiFi连接,当wifi未连接时,持续输出".";成功连接后输出连接成功信息
{
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
}
void led_initial() //灯初始化
{
pinMode(D7, OUTPUT);
pinMode(D8, OUTPUT);
}
void turn_on_light() //开灯
{
digitalWrite(D7, HIGH);
digitalWrite(D8, LOW);
}
void turn_off_light() //关灯
{
digitalWrite(D7, LOW);
digitalWrite(D8, HIGH);
}