智能家居入门DIY——【六、使用OneNet后台处理数据】
OneNet使用起来要比lewei50复杂一些,它没有前台需要自己开发。命令下发也和之前介绍的lewei50有一些区别,这里着重介绍一下使用MQTT协议来进行通讯。
一、准备
1、Esp8266开发板(+烧写器或者直接用WeMos D1 Wifi之类的就不要烧写器了)
2、Arduino IDE for VS
3、OneNet注册
二、OneNet配置
OneNet整体结构上与lewei50不同,它从产品开始下面可以有若干按APIKEY组织的设备群组(实名认证与否决定了能用多少产品和设备),每个设备下面可以有若干传感器和控制器(测试过程未发现个数限制)。而其中控制器状态是和传感器一起上传的,服务器下发的命令以Qos0进行,即不接收回复。更多的内容就请参考开发文档吧。
1、进入开发者中心创建产品
接入方式选择公开协议,设备接入协议选择MQTT。创建完成后,会得到一个APIKEY,这个APIKEY可以用于访问产品下的任何一个设备。当然,也可以创建一个新的APIKEY,然后设置该APIKEY关联的设备。
2、创建设备
其中设备名称和鉴权信息都是自定义,鉴权信息与设备ID是通用的,如果想要用MAC进行绑定那这里鉴权信息就填写设备的MAC地址,我就随便填了一个。当然这是与前台相关的内容,不在此讨论。
3、创建数据流模板
数据流名称最好简明,因为需要ESP8266处理的字符很多,而其资源也不是十分充裕。需要主意的是,每一个数据流就是一个传感器或控制器。即控制器也在这里创建。创建完成之后就可以把数据流和产品关联起来,为了直观观察,我们再在“应用管理”里面创建一个简单的应用:
选择这个开关控件,然后在右面除了关联设备数据流之外,设置一下开关开值和开关关值:
其默认值开为1,关为0。这里说明一下该参数在服务器下发命令时如何被解释(以图中的01和00为例):
把每个数字解释为1字节进行下发,所以上面00被接收到的是{0,0}这样的,它与0完全不同。而右面的按钮我设置的开关之分别为11、10,所以在后面的代码中前一字节被作为分组标志,后一字节被作为开关值。
三、编码
1、IDE和配置
与前一篇一样,这里使用Arduino IDE(实际上我用的是for vs,操作差不多,有代码提示而已)来进行编写。开发板选择NodeMCU1.0(ESP-12E Module),而不论使用的是WeMos D1 Wifi还是其他8针、IO全引出啥的(估计魔改串口4针的是玩不了的)。其他配置如下:
2、层次逻辑
a、Esp8266wifi.h负责连接WIFI
b、pubsubclient.h负责MQTT通讯(可变头和载体需要自定义,该库只负责Fixed header)
c、ArduinoJson.h负责构建载体中数据部分,具体格式请参考OneNet MQTT开发文档
d、eeprom.h负责将配置写到esp8266模块中和读取,这个的写操作与arduino开发板是不同的
3、主要代码示例
这里主要说一下MQTT部分,其他部分不再赘述。
a、pubsubclient总述:
WiFiClient wClient;
PubSubClient mClient(mServerAddress,mServerPort, mCallback,wClient);
其中,mCallback是一个回调函数,该函数用于处理服务器发来的消息,其签名如下:
void mCallback(char* topic, byte* payload, unsigned int length)
其中topic是话题,包含了topicName,Payload是实际下发的内容,例如上面的水泵开关开则有2字节:0 1。length指payload字节数。要处理的服务器数据就在该函数处理就可以了,但是最好别太长,否则影响数据的响应速度。
b、连接MQTT服务器
bool ConnectServer() { int waitConst = 0; if (!mClient.connected()) { //id="设备id",user="产品id",pass="APIkey" mClient.connect(DeviceID, ProductID, APIKey); while (!mClient.connected() && waitConst<20) { delay(100); waitConst++; } Serial.println((String)"ConnectServer " + mClient.connected()); } return mClient.connected();// mClient.state() == MQTT_CONNECTED; }
在Onenet开发者页面的说明后面有一段问答,其中明确说明了上面的参数如何传递,上面注释写的非常清楚了;当然也可以用前面提到的设备鉴权信息代替APIKEY字段作为password传入。
c、发布话题
bool PubTopic(String TopicName,String Json) { memset(msg_buf, 0, MQTT_MAX_PACKET_SIZE); //清理MQTT消息缓存 //设置可变头 msg_buf[0] = char(0x03); //包数据格式标志 msg_buf[1] = char(Json.length() >> 8); //高位在后 msg_buf[2] = char(Json.length() & 0xff); //低位在前 //设置数据 memcpy(msg_buf + 3, Json.c_str(), Json.length()); mClient.publish(TopicName.c_str(), (uint8_t*)msg_buf, Json.length() + 3); //设置发送长度以免中间的00使得自动判定长度出错。 }
可变头部分指明了使用哪种数据包格式,设置了Json数据长度。这一部分有若干种数据格式,具体参考开发文档,上面代码中使用的type3格式在说明文档中的说明如下:
数据类型3(type == 3)格式说明:
Byte 1 |
数据点类型指示:type=3 // JSON格式2字符串 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
Byte 2 |
//指示后面字符串长度 固定两字节长度高位字节,值为0x00 |
|
|
|
|
|
|
|
|
Byte 3 |
固定两字节长度低位字节,值为0x46 |
|
|
|
|
|
|
|
|
Byte 4 |
通用格式: { “datastream_id1”:”value1”, “datastream_id2”:”value2”, … }
示例: {“temperature”:22.5,”humidity”:”95.2%”} |
|
|
|
|
|
|
|
|
… … … … |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
||
Byte n |
|
|
|
|
|
|
|
|
看的时候无视这没头没尾的0x00、0x46吧。参考代码上面的代码就可以了。其中Json的构建在上一篇中已经说得非常清楚、十分深入了,完成这个代码构建是基础中的基础了,这里不再说明。
四、ESP8266的EEPROM
关于这个问题是看乐鑫的开发文档还是怎么的才能解决说起来没意义,上大家最喜欢的积木:
void SaveConfig(int cfgidx, String val) { //esp8266的EEPROM与Areduino的使用不同 //1、申请操作缓存 EEPROM.begin(eBlockSize); //2、写入数据 for (int i = 0; i < eBlockSize; i++) { EEPROM.write(cfgidx*eBlockSize + i, val[i]); } //3、保存数据更改 EEPROM.commit(); }
好了,这篇就介绍到这里,也不把OneNet上面创建的这个设备的应用嵌入到这里了,愿意做的自己做一下就可以看到效果了。我这现在就几块开发板,USB已经插了一堆堆各种东西,不可能长期保持这东西在线的。
五、网络调试工具
这个不像之前的一些WIFI应用调试起来那么简单,有时候代码看着貌似都没问题就是不过,而服务器端没有提供底层的数据调试功能,一般的SOCKET调试工具也不提供MQTT服务器,自己架设一个也需要大篇幅的工作。所以具体WIFI发送了什么还是要一些其他工具。首先,得让数据从电脑上过去我们才有机会在电脑上下个钩子把数据呈现出来,于是我安了一个随身WIFI,设置好之后,让ESP8266连接它,至于用什么WIFI软件无所谓,根本别指望他们提供底层数据,也没指望去勾它们,那太麻烦了。开发一个新的东西也得不偿失,还是用专业工具吧,祭出神器winpcap就解决一切了。使用winpcap的网络封包工具很多,其中做的好的也不少,Ethereal就是其中一个,现在可能叫Wireshark。