手把手教你做一个天猫精灵(三)
上一章展示了如何将智能终端部署到树莓派中,从而实现按钮唤醒智能终端的功能。本章将介绍如何通过智能终端控制硬件。
硬件准备
- ESP8266 WiFi模块 (NodeMCU板载ESP-12F芯片)
- 数据线(microUSB-USB接口)、按钮、发光二极管、面包板和杜邦线等
环境搭建
ESP8266 WiFi模块和开发环境
ESP8266 WiFi模块是乐鑫信息科技开发的一块WiFi模块,它可以将硬件连入WiFi进行通信,也是物联网智能家居中必不可少的模块。初次使用ESP8266 WiFi模块需要烧录固件,我烧录的是Node固件(可以运行Lua脚本),另外还有AT固件。网上有相应的资源教程可以使用。
编写硬件程序除了需要编译器还需要下载器,一般的流程是编写程序然后编译成hex文件,最后下载到硬件中使用。考虑到这部分流程比较复杂我选择了使用Arduino IDE作为开发环境,这样可以节省很多时间。
首先,将烧录好固件的WiFi模块连接电脑,一般驱动是免安装的。这时候右击“此电脑”,弹出菜单选择“管理”,点击侧边栏“设备管理器”,找到“端口(COM和LPT)”,点开后记住使用的串行端口号,比如COM5,如图所示:
提醒:如果找不到对应的端口可能是数据线不对,因为还一种microUSB-USB接口的数据线是充电线,那个是不能传数据的。也有可能电脑没有CH340驱动程序,需要安装一下。
然后打开Arduino IDE,打开“文件”-“首选项”,将下面这行输入到“附加开发板管理器网址”栏中,点击“好”:
http://arduino.esp8266.com/stable/package_esp8266com_index.json
接着打开“工具”-“开发板”-“开发板管理器”,搜索ESP8266的开发板并安装。然后开发版选择“NodeMCU 1.0 (ESP-12E module)”,端口选择之前管理里显示的端口号,比如“COM5”。这样就能在Arduino IDE编写WiFi模块的相关程序了。
MQTT服务器搭建
在第一章就讲过,物联网内是通过MQTT协议传输信息的,它是一种消息协议。和传统的消息协议不同,它有 服务质量(QoS) 和 遗言机制(Last Will) 两大特性。
-
服务质量(QoS):类似Kafka的重传机制,它也是解决消息可靠传输的思想。它将消息传输分为三种质量等级“至多一次”、“至少一次”和“只有一次”,这种区分就非常适合在不可靠的传输环境下使用。
-
遗言机制(Last Will):在客户端断开后,该客户端会发送一条遗言消息给订阅这个Topic的所有客户端,这也在不稳定环境下非常有用。
为了快速部署,我们可以直接使用开源的emqx服务器作为MQTT服务器,点击这里下载。下载后解压运行就能在浏览器中输入127.0.0.1:18083访问了。第一次登陆的默认账号是admin,密码是public。登陆进去以后便要求改密码。
然后为我们的硬件申请一个emqx账号方便以后使用,我设置的账号和密码都是“esp8266”。最后,运行智能终端,你会在主页上看到有客户端订阅了一个Topic,这是fubuki-iot自带的监听Topic——“self/#”。如图所示:
控制硬件程序设计
和天猫精灵一样,fubuki-iot自带了一些内置的语义模型控制硬件,下面就利用其自带的light语义模型控制卧室、客厅和餐厅的灯。
程序编写
首先下载第三方库文件,选择“工具”-“管理库...”,下载PubSubClient这个库,如图所示:
然后定义一个WiFiClient
和PubSubClient
,并用WiFiClient
初始化它,WiFiClient
是用于连接无线网,而PubSubClient
用于连接MQTT服务器,如下所示:
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
WiFiClient espClient;
PubSubClient client(espClient);
在Arduino IDE中有两个基本函数,setup
函数和loop
函数,顾名思义就是一个用来初始化,另一个循环调用。因此,在setup
函数中,我们需要初始化四件事情:
- 初始化引脚用来点亮LED灯,以模拟卧室、客厅和餐厅的灯
- 初始化串口打印日志
- 初始化WiFi连接到网络
- 初始化PubSubClient连接到MQTT服务器
//1. 初始化控制三个灯的引脚
pinMode(BEDROOM_PIN, OUTPUT);
pinMode(LIVINGROOM_PIN, OUTPUT);
pinMode(DINNINGROOM_PIN, OUTPUT);
//2. 初始化串口,用于打印日志。ESP8266的波特率一般为115200
Serial.begin(115200);
//3. 初始化WiFi Client,WIFI_SSID是WiFi名,WIFI_PASSWD是密钥
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWD);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.println("WiFi Connecting ...");
}
Serial.println("Connected to AP");
//4. 初始化PubSubClient,MQTT_BROKER是MQTT服务器地址,在PC上是127.0.0.1,但是这里是局域网地址,由路由器分配,可以在路由器管理页面查看
//MQTT_PORT是端口号,默认1883
client.setServer(MQTT_BROKER, MQTT_PORT);
client.setCallback(callback); // 回调函数指针,用来处理MQTT消息
while (!client.connected()) {
String client_id = "esp8266-client-";
client_id += String(WiFi.macAddress());
Serial.printf("The client %s connects to the mqtt broker\n", client_id.c_str());
if (client.connect(client_id.c_str(), "esp8266", "esp8266")) { // esp8266是前文创建的登陆账号和密码
Serial.println("Public emqx mqtt broker connected");
} else {
Serial.print("failed with state ");
Serial.print(client.state());
delay(2000);
}
}
client.subscribe("default/light"); // 订阅fubuki-iot的topic,内置的topic是default/light
上面涉及到一个callback
的函数指针,这个函数是用来处理订阅的消息的,比如说,如果收到指令是卧室,则点亮卧室的灯。具体如下:
#include <ArduinoJson.h> // 需要用到这个包反序列化
void callback(char *topic, byte *payload, unsigned int length) // 指定的函数签名
{
Serial.print("Message arrived in topic: ");
Serial.println(topic);
Serial.print("Message:");
for (int i = 0; i < length; i++) {
Serial.println((char) payload[i]);
}
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, payload);
if (error){
return;
}
const char* pos = doc["position"];
if (!strcmp(pos, "bedroom")) {
Serial.println("bedroom");
digitalWrite(BEDROOM_PIN, HIGH);
digitalWrite(LIVINGROOM_PIN, LOW);
digitalWrite(DINNINGROOM_PIN, LOW);
} else if (!strcmp(pos, "livingroom")) {
Serial.println("livingroom");
digitalWrite(BEDROOM_PIN, LOW);
digitalWrite(LIVINGROOM_PIN, HIGH);
digitalWrite(DINNINGROOM_PIN, LOW);
} else {
Serial.println("dinningroom");
digitalWrite(BEDROOM_PIN, LOW);
digitalWrite(LIVINGROOM_PIN, LOW);
digitalWrite(DINNINGROOM_PIN, HIGH);
}
}
点击Arduino IDE左上角“√”可以编译文件,点击“→”可以下载到ESP8266 WiFi模块中。
模块搭建
搭建很简单,只需要选三个引脚接三个LED灯就行,我采用的是共阴极接法(事实上代码也是这样写的)。
首先,我用的ESP8266 WiFi模块的引脚图如下:
然后共阴极就是这样:
现在,运行智能终端,对着说“打开卧室灯”红色LED灯就会亮,“打开客厅灯”黄色灯就会亮,而且打开Arduino IDE右上角的“放大镜”查看串口输出也可以看到对应的日志。
硬件推送程序设计
同样,fubuki-iot还可以接受硬件的消息推送,这部分功能天猫精灵还没支持。现在,我们用按钮结合内置的语义模型模拟一些硬件推送的功能。
程序编写
和上面一样,初始化仍需要初始引脚、串口、WiFiClient和PubSubClient,但不同的是这次不需要设置回调函数,而且只需要一个引脚作为输入。
然后在loop
函数中,我们要监听引脚状态,当为高电平的时候就发送MQTT消息给服务器。
int state = digitalRead(BUTTON_PIN);
if (state){
client.publish("self/button", "{\"topic\":\"self/button\",\"device\":\"button\",\"verbose\":\"false\",\"message\":\"有人按下了按钮\"}");
}
模块搭建
因为开关控制发送MQTT请求,只要将开关和电源串联即可:
现在启动智能终端,按下按钮就可以听到语音“有人按下了按钮”。
两个程序的完整代码
最后给出两端程序完整代码
控制硬件:
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <string.h>
const char* WIFI_SSID = "你的WiFi名称";
const char* WIFI_PASSWD = "WiFi密钥";
const char* MQTT_BROKER = "MQTT服务器IP";
const int MQTT_PORT = 1883;
const int BEDROOM_PIN = D0;
const int LIVINGROOM_PIN = D1;
const int DINNINGROOM_PIN = D2;
WiFiClient espClient;
PubSubClient client(espClient);
void setup() {
pinMode(BEDROOM_PIN, OUTPUT);
pinMode(LIVINGROOM_PIN, OUTPUT);
pinMode(DINNINGROOM_PIN, OUTPUT);
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWD);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.println("WiFi Connecting ...");
}
Serial.println("Connected to AP");
client.setServer(MQTT_BROKER, MQTT_PORT);
client.setCallback(callback);
while (!client.connected()) {
String client_id = "esp8266-client-";
client_id += String(WiFi.macAddress());
Serial.printf("The client %s connects to the mqtt broker\n", client_id.c_str());
if (client.connect(client_id.c_str(), "esp8266", "esp8266")) {
Serial.println("Public emqx mqtt broker connected");
} else {
Serial.print("failed with state ");
Serial.print(client.state());
delay(2000);
}
}
client.subscribe("default/light");
}
void callback(char *topic, byte *payload, unsigned int length) {
Serial.print("Message arrived in topic: ");
Serial.println(topic);
Serial.print("Message:");
for (int i = 0; i < length; i++) {
Serial.println((char) payload[i]);
}
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, payload);
if (error){
return;
}
const char* pos = doc["position"];
if (!strcmp(pos, "bedroom")) {
Serial.println("bedroom");
digitalWrite(BEDROOM_PIN, HIGH);
digitalWrite(LIVINGROOM_PIN, LOW);
digitalWrite(DINNINGROOM_PIN, LOW);
} else if (!strcmp(pos, "livingroom")) {
Serial.println("livingroom");
digitalWrite(BEDROOM_PIN, LOW);
digitalWrite(LIVINGROOM_PIN, HIGH);
digitalWrite(DINNINGROOM_PIN, LOW);
} else {
Serial.println("dinningroom");
digitalWrite(BEDROOM_PIN, LOW);
digitalWrite(LIVINGROOM_PIN, LOW);
digitalWrite(DINNINGROOM_PIN, HIGH);
}
}
void loop() {
client.loop();
}
硬件推送:
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <string.h>
const char* WIFI_SSID = "你的WiFi名称";
const char* WIFI_PASSWD = "WiFi密钥";
const char* MQTT_BROKER = "MQTT服务器IP";
const int MQTT_PORT = 1883;
const int BUTTON_PIN = D0;
WiFiClient espClient;
PubSubClient client(espClient);
void setup() {
pinMode(BUTTON_PIN, INPUT);
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWD);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.println("WiFi Connecting ...");
}
Serial.println("Connected to AP");
client.setServer(MQTT_BROKER, MQTT_PORT);
while (!client.connected()) {
String client_id = "esp8266-client-";
client_id += String(WiFi.macAddress());
Serial.printf("The client %s connects to the mqtt broker\n", client_id.c_str());
if (client.connect(client_id.c_str(), "esp8266", "esp8266")) {
Serial.println("Public emqx mqtt broker connected");
} else {
Serial.print("failed with state ");
Serial.print(client.state());
delay(2000);
}
}
}
void loop() {
int state = digitalRead(BUTTON_PIN);
if (state){
client.publish("self/button", "{\"topic\":\"self/button\",\"device\":\"button\",\"verbose\":\"false\",\"message\":\"有人按下了按钮\"}");
}
}
本章重点讲了如何利用内置的语义模型控制硬件,重点在硬件上。这方面资料在网上也俯拾皆是,并没有创新的成分。下一章将回到智能终端上,去自定义一个控制硬件的语义模型。