以前一直想着做一个智能家居的监控系统,说没时间是虚伪了,舍不得钱买服务器是真的…,后来想过内网穿透,使用树莓派或者pc本机跑服务程序,感觉太麻烦也一直没搞,所以人嘛,不想搞事情可以找一千个、一万个借口。
那现在为什么做了呢???
我这么抠,肯定是因为钱咯,难道是接了个外包的项目做?那肯定不是!
原因是经朋友推荐买了个国外的服务器做代理,上上网(你懂的),一个月才18元!虽然配置有点低吧,对我来说足够了。
我就想,钱都交了,只是用于上网,那不就太浪费了,为何不多装几个服务程序呢?
本着节省的精神,回想之前的智能家居理想,于是说干就干!…然后半夜3点才睡觉,第二天早上9点起床就写文章!
看来看去,最后选择了比较简单的 MQTT 协议,服务程序选的mosquitto。
MQTT 是一个基于发布/订阅模式的消息传输协议。它具有轻量级、开放、简单,易于实现,通信带宽要求低等特点。这些特点使得它对机器与机器的通信(M2M)以及物联网应用(IoT)来说是很好的选择。
Mosquitto是一个开源消息代理,实现了MQTT协议版本3.1和3.1.1。Mosquitto轻量,适用于低功耗单板计算机到完整服务器的所有设备。Mosquitto项目还提供了用于实现MQTT客户端的C库以及非常受欢迎的mosquitto_pub和mosquitto_sub命令行MQTT客户端。
记住上面说的MQTT 协议 和 mosquitto 服务程序。
最后,忽然想到,只要能连接代理服务器,这样岂不是在全球任何地方都能控制家里的电器!!!
想要实现数据的远程监控,要有三部分,终端、服务端、客户端。
需要对终端(esp8266)进行监控,esp8266端不仅需要发送数据,还需要接收(订阅)数据,比如手机控制家里的电灯。
数据上传(publish): 不断采集各种传感器数据集,然后使用MQTT协议按一定时间间隔publish到服务端。
数据订阅(subscribe): 在publish数据的间隔期间,这个时间全部用于MQTT监听数据。
我写的这个软件还不够智能,其中传感器的类型、个数在设计的时候必须确定好,或者空留出位置。如果后面加了新的传感器,将需要修改源代码。
后续可以的话,重新设计一下esp8266的软件,然后再写一个上位机用于设定esp8266传感器的接口和类型。不知道这样的方案有没有开源的代码了,我猜可能有,毕竟程序员都是聪明人!谁还没有个脑子,哈哈哈哈哈…
mqtt菜鸟教程:https://www.runoob.com/w3cnote/mqtt-intro.html
MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。
下面,我们将数据主要发布者称为采集终端,主要订阅者称为客户端,代理称为服务端,分别介绍。
采集终端,也就是传感器数据采集端、输出控制端,即 单片机端。MQTT协议是基于TCP传输的,所以要求单片机+网络模块(W5500)+网线连接?不不不,使用esp32、esp8266 wifi连接、无线传输,不香吗?我这esp32、8266都有,虽然esp32性能要强一些,但是之前esp8266有硬件连接,而且刚好可以用上,所以使用8266了,毕竟编程上两者区别不大。
这里说明一下,我是使用arduino IDE编程,是可以直接下载MQTT库的(不然软件开发也不会那么快)
arduino IDE 下载MQTT库,点击菜单栏的 工具->管理库选项,弹出下面弹窗,按标记内容操作下载。
说明,需要“科学上网”下载才会无阻碍,否则可能会下载无响应。
硬件连接图:
占位!!!
服务器端,我安装的是CentOS 7,服务程序是mosquitto,mosquitto也可以说是代理程序,它帮你将消息由发布端(published)转发到订阅端(subsription)。
CentOS 7安装mosquitto: 直接使用命令:yum install mosquitto
或者参考链接:https://blog.csdn.net/qq_34301871/article/details/93617204
mosquitto使用教程:https://www.cnblogs.com/chen1-kerr/p/7258487.html
mosquitto开机自启动:https://blog.csdn.net/u011267225/article/details/82387351
客户端就是监控采集终端数据的,客户端可以是手机、电脑,或者其他联网设备,也可以是esp8266,用于展示家居状态数据或者获取设定的状态,不过我们一般客户端只讲PC端,下面介绍各平台的客户端软件。
因为比较简单,下面的软件就不上使用教程了,自己摸索,这就是该死的技术人的乐趣。
Android软件: 我用的是MQTT Dash(科学上网,google商店下载的,不得不说,里面好用的软件还是非常多的,而且免费无广告)
上图中的所有控件都是可以编辑自定义的,比如开灯是绿色,关灯设为灰色,检测到火灾烟雾显示红色等等。
windows软件: MQTTX
windows端目前没有找到可以自定义编辑的图形化界面监控软件(如上面的安卓一样)。
下面的软件只能用于程序开发阶段的调试使用,毕竟谁没事用这种界面看数据。
其实在我的系统设计中,想的是使用qt写一个通用的可编辑的图像化客户端软件,就像下图:
可编辑背景图,然后可添加各种终端并可设置终端图片,移动位置、设置终端控件大小等等功能。
但是奈何工作量不小,平时要工作,下班要玩游戏,没有动力啊。(就是懒)
其他平台客户端
其他平台如linux,没有使用过。不过MQTTX支持很多平台,可以下载对应的软件。
开发的时候使用windows的MQTTX软件调试挺好的,当系统差不多了,可以使用手机调试,毕竟手机携带方便。
代码内容如下:
1、wifi连接,只有连接成功才会继续执行。
2、初始化各种传感器、和执行部件,如温湿度、超声波、led等。
3、设置MQTT订阅内容(依据 话题-Topic ),用于监听客户端发过来的指令。
4、循环监听、发送数据。
esp8266端代码(arduino IDE开发):
#include
#include
#include
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
//====================================================================================
#if 1 //1:使用串口打印输出 0:禁止串口输出
#define coutInit(x) Serial.begin(x)
#define cout Serial.print
#define coutln Serial.println
#else
#define coutInit(x)
#define cout(x)
#define coutln(x)
#endif
//====================================================================================
#define WLAN_SSID "差点让你知道的wifi名字了"
#define WLAN_PASS "差点让你知道的wifi密码了"
//====================================================================================
WiFiClient client;
#define AIO_SERVER "mosquitto服务端地址,可以是ip地址"
#define AIO_SERVERPORT 1883 // use 8883 for SSL
#define AIO_USERNAME "mosquitto的用户名,不会就去看mosquitto使用教程"
#define AIO_KEY "mosquitto的用户密码"
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);
Adafruit_MQTT_Publish updataCount = Adafruit_MQTT_Publish(&mqtt,"esp_updataCount",0);
Adafruit_MQTT_Subscribe onoffbutton = Adafruit_MQTT_Subscribe(&mqtt,"esp_Rx_1",0);
Adafruit_MQTT_Publish pub_temp = Adafruit_MQTT_Publish(&mqtt,"esp_temp",0);
Adafruit_MQTT_Publish pub_hum = Adafruit_MQTT_Publish(&mqtt,"esp_hum",0);
Adafruit_MQTT_Publish pub_distance = Adafruit_MQTT_Publish(&mqtt,"esp_distance",0);
Adafruit_MQTT_Subscribe sub_led = Adafruit_MQTT_Subscribe(&mqtt,"esp_led",0);
//====================================================================================
void MQTT_connect(int reconnect = 0) //失联情况下会自动重新连接
{
int8_t ret;
if(!reconnect)
{
if (mqtt.connected())
return;
}
cout("Connecting to MQTT... ");
uint8_t retries = 20; //自动重连20次,20次后连不上进入死循环,等待看门狗重启
while ((ret = mqtt.connect()) != 0) // connect will return 0 for connected
{
coutln(mqtt.connectErrorString(ret));
coutln("Retrying MQTT connection in 5 seconds...");
mqtt.disconnect();
delay(5000);
retries--;
if (retries == 0)
{
while (1);
}
}
coutln("MQTT Connected!");
}
//====================================================================================
#define dhtPin 2
DHTesp dht;
float dht_temp;
float dht_hum;
void dht11Init()
{
dht.setup(dhtPin, DHTesp::DHT11);
}
void getDHT11Data()
{
TempAndHumidity dhtValues = dht.getTempAndHumidity();
dht_temp = dhtValues.temperature;
dht_hum = dhtValues.humidity;
}
//=======================================================================
#define trigPin 12
#define echoPin 13
void distanceInit()
{
digitalWrite(trigPin, LOW);
delayMicroseconds(1000);
digitalWrite(trigPin, HIGH);
pinMode(trigPin, OUTPUT); // Sets the trigPin as an Output
pinMode(echoPin, INPUT); // Sets the echoPin as an Input
}
float getDistance()
{
digitalWrite(trigPin, LOW); // Clears the trigPin
delayMicroseconds(2);
digitalWrite(trigPin, HIGH); // Sets the trigPin on HIGH state for 10 micro seconds
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
long duration = pulseIn(echoPin, HIGH); // Reads the echoPin, returns the sound wave travel time in microseconds
float distance = duration*0.034/2; // Calculating the distance
return distance;
}
//=======================================================================
#define ledPIN 4 //定义LED点阵 IO
void RGB_LED_Init(void)
{
pinMode(ledPIN, OUTPUT);
}
void openLED()
{
digitalWrite(ledPIN, HIGH);
}
void closeLED()
{
digitalWrite(ledPIN, LOW);
}
//=======================================================================
void ledCallback(double led)
{
cout(F("Get LED: "));
coutln(led);
if(led > 0.5)
openLED();
else
closeLED();
}
void stringCallback(char *data, uint16_t len) {
cout("Hey we're in a onoff callback, the button value is: ");
coutln(data);
}
void setup() {
coutInit(115200);
delay(20);
//===== 连接wifi =====
cout("\nConnecting to ");
coutln(WLAN_SSID);
WiFi.begin(WLAN_SSID, WLAN_PASS);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
cout(".");
}
coutln();
cout("WiFi connected,IP address: ");
coutln(WiFi.localIP());
//===== 初始化各种传感器 =====
coutln("DHT11 initialization...");
dht11Init();
coutln("Ultrasonic initialization...");
distanceInit();
coutln("LED initialization...");
RGB_LED_Init();
//===== 设置subscribe =====
//mqtt.subscribe(&onoffbutton);
sub_led.setCallback(ledCallback);
mqtt.subscribe(&sub_led);
}
void loop()
{
MQTT_connect();
mqtt.processPackets(5000);
int succes_count = 0; //发送成功计数,用于判断连接状态,MQTT_connect函数短时间无法判断。
//===== 发送计数 =====
static unsigned int runCount;
cout(F("Sending updataCount val ")); cout(runCount); cout("...");
if (! updataCount.publish(runCount++))
{
coutln(F("Failed"));
}
else
{
coutln(F("OK!"));
succes_count ++;
}
//===== 发送温度、湿度(DHT11) =====
getDHT11Data(); //获取温湿度
cout(F("Sending DHT11 - temp:"));
cout(dht_temp);cout("...");
if (! pub_temp.publish(dht_temp)) //发送温度
{
coutln(F("Failed"));
}
else
{
coutln(F("OK!"));
succes_count ++;
}
cout(F("Sending DHT11 - hum:"));
cout(dht_hum);cout("...");
if (! pub_hum.publish(dht_hum)) //发送湿度
{
coutln(F("Failed"));
}
else
{
coutln(F("OK!"));
succes_count ++;
}
//===== 发送距离 =====
float distance = getDistance(); //获取距离
cout(F("Sending distance:"));
cout(distance);cout("...");
if (! pub_distance.publish(distance))
{
coutln(F("Failed"));
}
else
{
coutln(F("OK!"));
succes_count ++;
}
coutln(F("==========================="));
//===== 发送失败处理方式 =====
static int sendFaildCount = 0; //连续全部发送失败次数
if(succes_count == 0) //表示全部发送失败
{
sendFaildCount ++;
if(sendFaildCount >= 3)
{
MQTT_connect(1); //强制重连
sendFaildCount = 0;
}
}
else
sendFaildCount = 0;
}