从头开始做一个智能家居设备: 硬件代码
前言
前面的几篇博客都已经把准备工作做的明明白白了~,接下来我们正式的进入我们的硬件代码编写部分.当然了在一切开始之前,我们先来说一下整体的MQTT逻辑结构部分,示意图如下所示.
首先是上线部分,这里主要是分为两种情况,一,用户终端已经不管在线与否,硬件上线都必须发布设备上线消息,里面包含设备的ID,设备的名称以及设备的功能管理等;二,当前用户上线,会发布一个终端设备上线信息,这时候所有的在线硬件设备都需要发送一遍设备信息,用户终端根据反馈回来的设备信息更新UI.
然后就是下线逻辑,用户终端下线不需要通知硬件,但是当硬件设备下线的时候,需要去通过遗嘱消息通知用户终端,用户终端根据其信息更新UI界面.
硬件发送温湿度消息逻辑比较简单只需要发送温湿度消息给终端即可.用户终端根据发送过来的消息更新UI界面.
用户终端发送控制硬件消息则和上面的有所不同.因为当硬件接受到控制消息做出对应改变的时候,终端需要根据设备当前的状态来显示UI,所以硬件设备还需要发送一个反馈消息给用户终端,告诉用户终端当前设备的状态.用户终端再根据这个状态修改对应的UI界面即可.
关于MQTT这部分主体功能官方也已经帮助我们编写好了,我们只需要做的是做一些定制化的工作.这篇博客主要我们主要聊一下如何定制官方Demo.
当然了,如果有任何SDK的问题,或者方法的不明白,可以直接去看 ESP8266 NONOS SDK API.官方已经写的很详细了.如果还是整不明白,直接上视频吧,这个视频讲的很详细了已经. 还是建议去看一遍视频,因为我觉得这个视频确实可以给个五分,真是太详细了~ 基于MQTT 的物联网开发
上手准备
在一切开始之前,我们首先要掌握以下的主要内容.这些在上面的B站视频中都是有的,所以大家自行去查看就OK了.
说一下各自的工作. C语言的掌握这是必须的,因为我们编写的代码就需要使用的C语言.所以这方面是必须的.引脚的GPIO功能主要是用来控制外部的输入的,例如控制LED灯,控制继电器等等.C_JSON库主要是用来做服务器Json数据的解析以及构造Json字符串的.MQTT协议这个就不过多的叙述了,前面的已经写了一篇从头开始做一个智能家居设备:MQTT协议及使用,可以参考前面博客或者参考视频都可以.
项目整合
依然是下载2.2.0版本的NONOS SDK,因为官方中写好了MQTT协议的封装,所以我们直接拿来用就好,终端cd到想要放置的文件,直接一顿指令就完事了~ 指令如下所示.当然了,如果网速过慢,我们可以直接去github去下载对应的版本,传送门如下所示.
NONOS SDK传送门
#下载项目
wget http://wiki.ai-thinker.com/_media/esp8266/sdk/esp8266_nonos_sdk-2.2.0.zip
unzip esp8266_nonos_sdk-2.2.0.zip
cd ESP8266_NONOS_SDK-2.2.0
#将 driver_lib 和 examples/esp_mqtt_proj 下的文件拷贝到 app目录下
mv driver_lib app
cp -rf examples/esp_mqtt_proj/* app/
rm -rf examples
rm -rf third_party
在上面的一顿操作之后,我们还需要连接两个库.分别是libc.a和libhal.a,指令如下所示.
wget -O lib/libc.a https://github.com/esp8266/esp8266-wiki/raw/master/libs/libc.a
wget -O lib/libhal.a https://github.com/esp8266/esp8266-wiki/raw/master/libs/libhal.a
项目整合完成之后,我们接下来开始定制我们的代码部分.
代码定制
在了解代码定制之前,我们需要确定我们都需要定制那些文件.分别是app/user目录下的用户主文件user_main.c,app/include目录下的配置文件mqtt_config.h,app/modules目录下的配置结构体文件config.h.
首先,由于百度云的连接地址和连接密码可能比较长,所以需要修改配置结构体文件config.h的对应成员属性的长度.当然了,这里我都进行了长度的增加,如下所示.
typedef struct{
uint32_t cfg_holder;
uint8_t device_id[256];
uint8_t sta_ssid[256];
uint8_t sta_pwd[256];
uint32_t sta_type;
uint8_t mqtt_host[256];
uint32_t mqtt_port;
uint8_t mqtt_user[256];
uint8_t mqtt_pass[256];
uint32_t mqtt_keepalive;
uint8_t security;
} SYSCFG;
接着我们需要修改配置文件mqtt_config.h的内容,我们在从头开始做一个智能家居设备:MQTT协议及使用这篇博客不是已经做好了信息的准备了吗?我们如实填写即可.整体代码如下所示.
/*IMPORTANT: the following configuration maybe need modified*/
/***********************************************************************************************************************/
#define CFG_HOLDER 0x00FF55A8 /* Change this value to load default configurations */
/*DEFAULT CONFIGURATIONS*/
#define MQTT_HOST "tcp连接地址" // the IP address or domain name of your MQTT server or MQTT broker ,such as "mqtt.yourdomain.com"
#define MQTT_PORT 1883 // the listening port of your MQTT server or MQTT broker
#define MQTT_CLIENT_ID "设备ID,可自行定义,但是需要保证唯一" // the ID of yourself, any string is OK,client would use this ID register itself to MQTT server
#define MQTT_USER "用户账号" // your MQTT login name, if MQTT server allow anonymous login,any string is OK, otherwise, please input valid login name which you had registered
#define MQTT_PASS "MQTT身份密钥" // you MQTT login password, same as above
#define STA_SSID "WIFI账号" // your AP/router SSID to config your device networking
#define STA_PASS "WIFI密码" // your AP/router password
#define DEFAULT_SECURITY NO_TLS // very important: you must config DEFAULT_SECURITY for SSL/TLS
#define CA_CERT_FLASH_ADDRESS 0x77 // CA certificate address in flash to read, 0x77 means address 0x77000
#define CLIENT_CERT_FLASH_ADDRESS 0x78 // client certificate and private key address in flash to read, 0x78 means address 0x78000
/***********************************************************************************************************************/
PS : 这里唯一要注意的就是 CFG_HOLDER 这个值,有很多同学可能后期会对其他WIFI信息做修改,那么这个值就一定要修改,如果这个值没有发生改变,这些配置信息是不会重写到硬件的Flash中的~
在user_main.c中,我们主要做了MQTT相关操作,我们还是从user_init函数开始看.在user_init中主要是做了Wif连接和MQTT连接准备.,设定MQTT相关回调函数.代码如下所示.
MQTT_InitConnection(&mqttClient, sysCfg.mqtt_host, sysCfg.mqtt_port, sysCfg.security);
MQTT_InitClient(&mqttClient, sysCfg.device_id, sysCfg.mqtt_user, sysCfg.mqtt_pass, sysCfg.mqtt_keepalive, 1);
Set_Will_Message();
MQTT_OnConnected(&mqttClient, mqttConnectedCb);
MQTT_OnDisconnected(&mqttClient, mqttDisconnectedCb);
MQTT_OnPublished(&mqttClient, mqttPublishedCb);
MQTT_OnData(&mqttClient, mqttDataCb);
WIFI_Connect(sysCfg.sta_ssid, sysCfg.sta_pwd, wifiConnectCb);
在WIFI连接函数wifiConnectCb中,我们初始化了一个定时器,获取当前的网络时间,判断网络是否可用.如果网络可用,那么就进行MQTT连接操作函数MQTT_Connect,整体代码如下所示.
void sntpfn()
{
u32_t ts = 0;
ts = sntp_get_current_timestamp();
os_printf("current time : %s\n", sntp_get_real_time(ts));
if (ts == 0) {
//os_printf("did not get a valid time from sntp server\n");
} else {
os_timer_disarm(&sntp_timer);
MQTT_Connect(&mqttClient);
}
}
void wifiConnectCb(uint8_t status)
{
if(status == STATION_GOT_IP){
sntp_setservername(0, "pool.ntp.org"); // set sntp server after got ip address
sntp_setservername(1, "ntp.sjtu.edu.cn"); // set sntp server after got ip address
sntp_init();
os_timer_disarm(&sntp_timer);
os_timer_setfn(&sntp_timer, (os_timer_func_t *)sntpfn, NULL);
os_timer_arm(&sntp_timer, 1000, 1);//1s
} else {
MQTT_Disconnect(&mqttClient);
}
}
当MQTT连接成功的时候,订阅遗嘱,指令,设备三个主题。并且在设备主题中发布设备信息。初始化D3引脚为温湿度数据接收引脚,并且初始化一个3秒一次的发布温湿度消息的定时器。整体代码如下所示。
void mqttConnectedCb(uint32_t *args)
{
MQTT_Client* client = (MQTT_Client*)args;
INFO("MQTT: Connected\r\n");
//监听遗嘱主题和指令主题
MQTT_Subscribe(client, WillTopic, 0);
MQTT_Subscribe(client, OrderTopic, 0);
MQTT_Subscribe(client, ClientTopic, 0);
//发送设备信息
Send_ESP8266_Info_Message();
sensor.pin = 3;
sensor.type = DHT11;
if (DHTInit(&sensor)) {
os_printf("DHT11 #0 init on Pin3 \r\n");
} else {
os_printf("Error init DHT11 #0 on on Pin3\r\n");
}
//启动定时发布消息任务
OS_Send_Time_Init(3000,1);
}
如果连接失败,我们就尝试重新连接,从WIFI连接开始重新尝试。整体代码如下所示。
void mqttDisconnectedCb(uint32_t *args)
{
MQTT_Client* client = (MQTT_Client*)args;
INFO("MQTT: Disconnected\r\n");
//判断是否是连接失败,如果是,那么重新连接.
MQTT_Disconnect(&mqttClient);
os_timer_disarm(&OS_Send_time);
WIFI_Connect(sysCfg.sta_ssid, sysCfg.sta_pwd, wifiConnectCb);
}
在mqttDataCb回调方法中进行接受数据相关操作.其实主要有两个操作一个是用户指令,一个是设备消息。我们先来说设备消息,我们要判断当前设备消息是否是用户终端发布的,如果是,我们是需要发布硬件的设备情况的消息的。整体代码如下所示。
if(os_strncmp(topicBuf,"Client",topic_len) == 0) {
cJSON * json = cJSON_Parse(dataBuf);
cJSON * data = cJSON_GetObjectItem(json,"data");
cJSON * clientType = cJSON_GetObjectItem(data,"clientType");
//如果是手机设备发送过来的设备信息,那么我们需要返回一个自己的信息
if (clientType->valueint == 0) {
Send_ESP8266_Info_Message();
}
}
如果是用户指令消息,在对对应的引脚进行高低压转换之后,我们需要进行在数据主题(Data)中进行引脚状态的反馈。整体代码如下所示。
if(os_strncmp(topicBuf,"Order",topic_len) == 0) {
cJSON * json = cJSON_Parse(dataBuf);
cJSON * data = cJSON_GetObjectItem(json,"data");
cJSON * clientID = cJSON_GetObjectItem(data,"clientID");
cJSON * switchID = cJSON_GetObjectItem(data,"switchID");
cJSON * isOn = cJSON_GetObjectItem(data,"isOn");
//查看是否是自己的设备
if(os_strncmp(clientID->valuestring,MQTT_CLIENT_ID,topic_len) == 0) {
if (isOn->valueint == 0) {
GPIO4_State = 0;
GPIO_OUTPUT_SET(GPIO_ID_PIN(4),0);
Send_Response_Info_Message(0);
} else {
GPIO4_State = 1;
GPIO_OUTPUT_SET(GPIO_ID_PIN(4),1);
Send_Response_Info_Message(1);
}
}
}
说道这里,大部分的逻辑代码部分都已经说完了,是不是很简单呢?像我写的user_main.c的其他函数大部分是用来发送消息,组装Json数据的。大家可以自行查看。
结语
写到这里硬件代码部分基本已经是结束了,如果有任何问题,欢迎在评论区指导批评,谢谢各位看官大佬查阅本文章。最后附上Demo传送门。