littlebutt

导航

手把手教你做一个天猫精灵(三)

上一章展示了如何将智能终端部署到树莓派中,从而实现按钮唤醒智能终端的功能。本章将介绍如何通过智能终端控制硬件。

硬件准备

  • ESP8266 WiFi模块 (NodeMCU板载ESP-12F芯片)

ESP8266模块

  • 数据线(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/#”。如图所示:

emqx主页

控制硬件程序设计

和天猫精灵一样,fubuki-iot自带了一些内置的语义模型控制硬件,下面就利用其自带的light语义模型控制卧室、客厅和餐厅的灯。

程序编写

首先下载第三方库文件,选择“工具”-“管理库...”,下载PubSubClient这个库,如图所示:

PubSubClient

然后定义一个WiFiClientPubSubClient,并用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模块的引脚图如下:

NodeMCU

然后共阴极就是这样:

电路图

实物图

现在,运行智能终端,对着说“打开卧室灯”红色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\":\"有人按下了按钮\"}");
  }

}

本章重点讲了如何利用内置的语义模型控制硬件,重点在硬件上。这方面资料在网上也俯拾皆是,并没有创新的成分。下一章将回到智能终端上,去自定义一个控制硬件的语义模型。

posted on 2022-10-02 19:13  小奶球  阅读(802)  评论(1编辑  收藏  举报