制作一朵可以随着天气变化的智能“云”
首先简单介绍下将会使用到一个物联网链接协议MQTT
MQTT是Message Queuing Telemetry Transport 的一个简称.是IBM开发的一个即时性的一个通信协议.因为是采用轻量级的发布和订阅消息的传输机制被广泛的适用于物联网的设备上.使其设备对设备之间的短消息通信简单.MQTT是使用TCP/IP提供基本网络服务.而MQTT-SN呢是基于UDP or Non-ip协议的.因为两者适用于不同的环境.所以我已更加常见的前者作为举例介绍。
在MQTT协议中分为发布者,代理器(服务器)和订阅者,三种角色。通过创立主题实现将消息从发布者通过代理器传达到每一位订阅者上。发布者将主题和主题对应的内容送至代理器,然后由代理器将该消息转发至每一个订阅了该主题的订阅者。如图所示
发布者
其发布者的作用主要是将信息比如湿度传给服代理器,然后让代理器发送给每一位订阅如湿度主题的该主题的订阅者推送温度这条主题的消息,当然发布者也可以订阅其他主题!
代理器
主要的作用充当一个server接收发布者发布的应用信息,然后将应用信息转发给符合条件的订阅者客户端。
订阅者
订阅者首先向服务器订阅相关的主题,例如如果订阅是加湿器,那么我这个加湿器会先向代理器订阅湿度这个主题,当传感器(发布者)采集到了湿度数据,然后发布者将这个主题和主题内容发送给代理器,然后代理器会将数据传输至每一个订阅该主题的订阅者就是加湿器上。
首先讲下这个服务器上的QOS,这个是为了不同的使用场景提供的三种质量的服务:
1:至多一次,这个服务是可能会出现丢包的现象,在实时性要求不高的场景中适用的,例如现在的环境,传感器给让代理者发送湿度信息,这个数据包丢了就丢了,可以等下一个一包再过来。
2:至少一次,这个为了保证数据包能够正常的到达目的地所采用的一种服务,例如适用于门禁报警器之类的环境,在这种环境中信息可能会重复,发送者在指定时间内没有收到PUBACK的报文,数据内容会被重新发送。
3:正好一次,这种环境是保证数据包不重复并且能够到达目的的一种服务, 适用于计费之类的系统环境,在这种环境中当一条信息被发送出去,代理器会保存这个信息的id,并且会使用任何长连接,坚持将信息推送给订阅者,如果订阅者收到信息会发送一个PUREL给代理器清楚保存的信息的id。
然后来讲讲如何使用通配符订阅多个主题
/:用来表示层次,比如a/b,a/b/c。
#:表示匹配>=0个层次,比如a/#就匹配a/,a/b,a/b/c。
单独的一个#表示匹配所有。 不允许 a#和a/#/c。
+:表示匹配一个层次,例如a/+匹配a/b,a/c,不匹配a/b/c。
单独的一个+是允许的,a+不允许,a/+/b不允许
首先我们现在来说下发布者,温湿度传感器这边,这里使用的是 通过wifi连接优酷路由器通过MQTT协议将温湿度等信息传到优酷代理器器上,然后由代理进行转发,将相互的数据传给加湿器或者手机,原图是使用的普通的温度传感器,后面是换成了DHT11,然后显示屏是用的SSD1306驱动的OLED显示屏,这里天气和PM2.5使用的是外网的天气API接口这里就不过多的讲了。
然后通过Nodemcu读取DHT11传感器的数据,每隔两秒用MQTT协议发布两个温度,湿度两个主题和相关数据。
void loop() {
if (!mqclient.connected()) {
reconnect();
}
mqclient.loop();
long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
float h = dht.readHumidity();
float t = dht.readTemperature();
float f = dht.readTemperature(true);
float hi = dht.computeHeatIndex(t, h, false);
Serial.print("Humidity: ");
Serial.print(h);
Serial.print(" %\t");
Serial.print("Temperature: ");
Serial.println(t);
mqclient.publish("temperature", String(t).c_str());
mqclient.publish("Humidity", String(h).c_str());
}
}
这时候我们抓下数据包看下传感器发送的MQTT的协议头。
然后发布者这边没有问题之后我们开始搭建代理器,可以直接用vps搭建一个,但因为其中包含很多敏感的一些数据所以我是选择将数据保存在内网本地,但是可以通过外网来控制一些设置,所以我们是选择在路由器上搭建一个这些服务,因为代理器和传感器都需要二十四小时的不间断进行一些数据的交互,如果是在内网额外搭建一个服务器对于我们这种小型的智能家居的环境中显然是不现实的,所以我们选择用路由器作为网管并且担任代理器的任务在适合不过了,第一是在数据交互中减少了非常多的不必要的一个链路,第二是无成本。这里我用的是优酷路由器,理由很简单带8G sd卡的跑openwrt的廉价路由。然后优库的路由作为二级路由负责传感器的数据网络,本来是打算在通过一级路由的端口映射将80,1883(MQTT)端口映射到二级路由上,这样我们在外面时候可以通过1883端口与内网的模块相连控制一些设备之类的,然后80是为了跟微信对联,这样我们控制而内网设备的时候就方便很多了,结果发现我用的是联通的宽带会封闭80端口的,但是我们可以通过HTTPS(433)绕过这个限制,微信这个只支持80,433这两个端口。
然后我们拿到路由器后通过漏洞拿到优酷路由器的root shell搭建这个server,拿到shell 的第一步就是修改相关的配置文件将默认跑着的挖矿程序关掉,然后更新下opkg的源
vi /etc/opkg.conf
然后将内容修改为
dest root / #dest ram /tmp/youku/tmp #dest bin /tmp/youku/pkg #dest iku /tmp/youku/pkg #dest pkg /tmp/youku/pkg dest usb /mount lists_dir ext /tmp/youku/pkg/opkg-lists option overlay_root /tmp/youku/pkg/overlay src/gz barrier_breaker_base http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/base src/gz barrier_breaker_luci http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/luci src/gz barrier_breaker_management http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/management src/gz barrier_breaker_oldpackages http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/oldpackage src/gz barrier_breaker_packages http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/packages src/gz barrier_breaker_routing http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/routing src/gz barrier_breaker_telephony http://downloads.openwrt.org/barrier_breaker/14.07/ramips/mt7620a/packages/telephony
然后保存,使用
opkg update
更新软件源,更新好后就可以安装我们环境中所需要的一个环境。如mysql,php,mosquitto之类的。
opkg install mosquitto mosquitto-client libmosquitto shadow-useradd
然后mosquitto默认以mosquitto启动服务,我们可以创建这个用户或者是修改配置文件
useradd mosquitto
我们先来看下这个程序的帮助
Usage: mosquitto [-c config_file] [-d] [-h] [-p port] -c : 选择配置文件 -d : 后台静默执行 -h : 查看帮助 -p : 选择服务监听的端口,配置文件中默认是1883,所以不建议与-c一起使用 -v : 日志级调试输出,简单的话就是打印更多的信息
然后我们启动代理服务
mosquitto -v -c /etc/mosquitto/mosquitto.conf
熟悉之后将“mosquitto -c /etc/mosquitto/mosquitto.conf -d”写入/etc/rc.local使其能开机自动运行
然后我们可以看到下面这些信息
1478963435: mosquitto version 1.3.5 (build date 2015-06-14 11:41:32+0200) starting 1478963435: Config loaded from /etc/mosquitto/mosquitto.conf. 1478963435: Opening ipv6 listen socket on port 1883. 1478963435: Opening ipv4 listen socket on port 1883. 1478963436: New connection from 192.168.11.247 on port 1883. 1478963436: New client connected from 192.168.11.247 as ESP8266Client (c1, k15). 1478963436: Sending CONNACK to ESP8266Client (0) 1478963436: Received PUBLISH from ESP8266Client (d0, q0, r0, m0, 'temperature', ... (11 bytes)) 1478963436: Received SUBSCRIBE from ESP8266Client 1478963436: inTopic1 (QoS 0) 1478963436: ESP8266Client 0 inTopic1 1478963436: Sending SUBACK to ESP8266Client 1478963436: Received SUBSCRIBE from ESP8266Client 1478963436: inTopic2 (QoS 0) 1478963436: ESP8266Client 0 inTopic2 1478963436: Sending SUBACK to ESP8266Client 1478963436: Received SUBSCRIBE from ESP8266Client 1478963436: inTopic3 (QoS 0) 1478963436: ESP8266Client 0 inTopic3 1478963436: Sending SUBACK to ESP8266Client 1478963436: Received PUBLISH from ESP8266Client (d0, q0, r0, m0, 'temperature', ... (5 bytes)) 1478963436: Received PUBLISH from ESP8266Client (d0, q0, r0, m0, 'Humidity', ... (5 bytes)) 1478963438: Received PUBLISH from ESP8266Client (d0, q0, r0, m0, 'temperature', ... (5 bytes)) 1478963438: Received PUBLISH from ESP8266Client (d0, q0, r0, m0, 'Humidity', ... (5 bytes))
然后我们通过优酷路由器订阅“温度”,“湿度”这两个主题,看下传感器发来的数据。
mosquitto_sub -t "Humidity" & mosquitto_sub -t "temperature"
现在温度19℃,湿度51%没毛病。
然后我们试下用手机订阅主题数据。这里我使用的是MYMQTT的APP做测试,然后手机连上一级路由的wifi,填入优酷路由的ip,端口默认不用改,这里说下如果是要在外网环境下的话最好在一级路由做动态域名绑定,然后端口映射将相关端口映射到优酷路由就可以了。
然后看下右上角的绿条就代表连接上优酷路由了,然后我们订阅相关主题。
这时候手机能够正常接收到传感器的数据,我们后面是要通过手机控制加湿器的灯和开关的。然后我们通过手机往“light”主题发送一条内容为“RED”数据来控制加湿器的灯的颜色。
然后我们用优酷路由器模拟加湿器上的模块接受数据。
弄完代理器,然后我们就要搭建订阅者这边了,就是拓扑图中的加湿器,我的加湿器长这个样子,有加湿和七彩灯的功能,三个按键,分别是灯的颜色控制,开关,和加湿强度。
首先我们拆开这个加湿器,可以看到内部空间非常大,找到按键的排线,然后用万用表测一下这个按钮的电路情况。
然后直接将相关的信号线接MCU的IO共地,忘了介绍下这个控制器了,这里我们使用Nodemcu搭载esp8266芯片的无线模组,通过WIFI链接优酷路由器的wifi进行数据传输,这里说下esp8266是可以有softap模式的,意思就是两个Nodemcu可以一个当AP另外一个当wificlien进行通讯的不需要额外买路由器!回到正题,加湿器使用的是24V供电而我们的nodemcu则需要5V,就是要么通过降压模块将24V将至5V,要么就是加多一个小电池进去。
然后我们在Nodemcu写相关的程序,下面为部分代码,首先初始化网络这里不细说,然后连接MQTT代理器,然后订阅“Humidity”湿度主题,和“Light”灯光主题,先说下这个湿度主题,这个湿度主题由另外一个Nodemcu发布,然后加湿器这边的模块接受这个湿度传感的数据,当室内的湿度到底一定湿度的时候自动打开加湿器进行加湿,然后Light这个主题呢是手机通过mymqtt这个app进行发布,用于控制这个加湿器的灯光用的!
void reconnect() { while (!mqclient.connected()) { Serial.print("Attempting MQTT connection..."); if (mqclient.connect("Humidifier")) {// 设置用户名 Serial.println("connected");//串口调试使用 mqclient.subscribe("Humidity");//订阅“Humidity”主题 mqclient.subscribe("Light");//订阅“Light”主题 } else { Serial.print("failed, rc=");//串口调试使用 Serial.print(mqclient.state()); Serial.println(" try again in 5 seconds");//连接失败,五秒后重链 delay(5000); } } }
设置MQTT回调函数,当湿度达到一定程度时候打开加湿器,然后根据传入的Light的内容,给接加湿器按钮的信号低电平的信号模拟按钮信号控制加湿器就可以了,可能有人会问了传感器放入这个加湿器内部让加湿器判断湿度在打开开关,如果这样测出来的数据因为放入外壳的原因是不准的!然后我们把程序烧录到Nodemcu后就可以将Nodemcu的IO脚连好按钮的信号线,然后和电池一起放入加湿器,拧上螺丝就可以了。
void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); if(topic=="Humidity"){ HumiditySwitch(payload,length); }else if (topic=="Light"){ LightSwitch(payload,length); } for (int i = 0; i < length; i++) { Serial.print((char)payload[i]); } Serial.println(); }
然后我们可以通过在加湿器附近粘些蚕丝之类的装饰物然这个加湿器看起像一朵云,这里我买的是婚庆用的装饰品,挺不错的十多块钱一大袋还包邮。其实淘宝也有卖之类的遥控云,但是贵点的飞起。
并且我们可以通过外部的天气api来看下室外天气的状态,并且用灯光的方式表现出外面的空气情况,不过这要是在帝都的会恐怕这朵云会持续为深红色。
void getweather(){ HTTPClient http; http.begin("api.heweather.com",80,"/x3/weather?cityid=CN101010300&key=XXXXXXX"); int httpCode = http.GET(); if(httpCode > 0) { if(httpCode == HTTP_CODE_OK) { payload = http.getString(); } }else { Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); char * data=(char*)payload.c_str(); Serial.println(data); aJsonObject * msg = aJson.parse(data); //unsigned char * udata ="HeWeather data service 3.0"; aJsonObject * amsg = aJson.getObjectItem(msg, "HeWeather data service 3.0"); aJsonObject * weatherinfo = aJson.getArrayItem(amsg,0); aJsonObject * aqi = aJson.getObjectItem(weatherinfo, "aqi"); aJsonObject * cityi = aJson.getObjectItem(aqi, "city"); aJsonObject * pm25 = aJson.getObjectItem(cityi, "pm25"); Serial.println(pm25->valuestring); aJsonObject * daily_forecast = aJson.getObjectItem(weatherinfo, "daily_forecast"); aJsonObject * forecast = aJson.getArrayItem(daily_forecast,0); aJsonObject * tmp = aJson.getObjectItem(forecast, "tmp"); aJsonObject * dmax = aJson.getObjectItem(tmp, "max"); aJsonObject * dmin = aJson.getObjectItem(tmp, "min"); Serial.print("max:"); Serial.print(dmax->valuestring); Serial.print("min:"); Serial.println(dmin->valuestring); }
然后在配合这个加湿器的出雾气效果和香薰效果在用线吊起来,简直了,但是需要注意出雾口和进风口不要堵上了。
然后一朵可以根据室内湿度智能加湿还能远程控制并且能够通过灯光颜色观察室外天气的智能加湿器,不,智能云就酱紫DIY出来啦,不过我最初只是想搭一个智能家居环境来着,代码的话我是建议自己按照例程写,代码还有部分需要修改的,然后环境搭好了,如果后面有空的话会更新安全篇,像这个环境中根本没有认证,加密之类的。和之前出现过或者可能会出现的一些安全问题,漏洞的关键点进行分析。
发表评论