我是一个学软件的小白,想找一个最简单的物联网案例跟着做,但是从来没有找到过。最后,我摸索了一下,打算自己写一篇文章。关于物联网的详细研究,你可以看到太极制造商,但你仍然需要提到官方文档总是比大多数博客更有用。
基本框架
mqtt服务器作为新闻中转,uni-app(vue)通过mqtt协议将服务器连接到另一端esp01s也连接mqtt服务器,然后可以传输数据。 这种通信方式主要用于控制指令的传输和实时数据显示,如传感器数据mqtt直接显示到前端,但持久性需要构建后端和mqtt服务器连接,然后将信息存储到数据库中,mqtt服务器不提供持久服务。
为什么使用MQTT
mqtt协议仍然基于应用层协议TCP/IP的。
mqtt服务器的基本工作原理是订阅和发布消息,mqtt服务器被称为服务器,其他设备连接到服务器被称为客户端。uni-app前端和 esp01s连接到服务器后称为客户端。 客户端和客户端之间的信息传输依赖于订阅相同的主题,类似于一个主题qq小组,订阅相当于加入小组,发布相当于在小组中发送信息,所以只要你订阅相同的主题,你就可以收到其他设备在主题下发布的信息。当然,一个设备可以订阅多个主题,通常在手机前端订阅多个设备的信息。主题的名字是自己取的,只要有人订阅了这个名字,这个主题就存在了。
若订阅主题设备意外断线,mqtt服务器将在服务器端保存其他设备发布的数据。当设备重新连接到服务器时,数据将继续发送给设备,相当于您在线查看组的历史记录。 所以, 这可以在很大程度上保证数据在网络恶劣环境下的稳定交付,两个客户端不需要同时在线交流,相当于发送微信可以继续做自己的事情。非常适合物联网设备的数据通信。
战前准备
:esp01s、配套继电器。 :灯、电池、杜邦线。(如果你有自己的电源、电线或电线。PCB板、目标设备) :小螺丝刀(用于拧松继电器上的接线螺钉)
esp01s
esp01s是esp8266系列wifi芯片与系列中的其他型号相同,外部模块相对简单。 esp8266芯片来自国内企业乐信科技,建议从乐信或安信可购买。
其他设备
要让模块在通电的时候做我们希望让它做的事需要对其进行编程,所以需要一个烧录器。 配套继电器 电池(电压需要大于继电器的标准电压才能工作) 杜邦线(线两头有细金属插头接口,分公母,其实在找到这线之前我甚至不知道这线叫什么名字) LED灯(本来我搜的是发光二极管,但是这个引脚看起来挺舒服的,可以直接插杜邦线)
操作思路
- 安装EMQX(mqtt服务器)和MQTT X(mqtt计算机客户端)
- 安装Hbuilder,创建uni-app项目(使用Vue框架的)
- Vue连接EMQX
- 安装Arduino(编辑器用于编写硬件)
- esp01s连接EMQX
- 连接硬件测试
安装EMQX、MQTT X
EMQX是一个mqtt您可以将当地安装服务器linux系统运行,但我估计大多数人使用它windows,没有其他闲置的linux,除非你买个云服务器。 因此,他们非常体贴地提供免费云EMQX,您可以通过连接他们现成的服务器直接中转信息。 如果打算下载到本地安装,可以看官方文件:快速开始 | EMQX文档 我有一个树莓派,所以我把它安装在我的树莓派上。
MQTT X可直接在电脑上运行的客户端程序,可用于测试发送和接收mqtt消息。 官网地址:MQTT X:优雅的跨平台 MQTT 5.0 桌面客户端工具
使用本地EMQX
EMQX文件中提到过Dashboard该系统提供的可视化操作界面。默认情况下,服务器将在运行后自动启动Dashboard。 我们可以通过设备IP访问此操作界面时,用户名默认admin,密码默认public。 默认英语,可以在设置中转中文。 工具里的Websocket界面默认显示您的服务器地址(192.168.1.109是我的树莓派地址) 不过是ws开头的 ws://192.168.1.109:8083/mqtt 如果是mqtt开头是 mqtt://192.168.1.109:1883 除了我的IP其他地址是系统默认的。
使用MQTT X
官方文档将引导您创建一个新的连接。点击侧栏的第二个加号,然后默认显示服务器地址是他们提供的免费在线服务器。 填己填名字就行了。 Client ID用于让服务器识别您的客户端,在这里随机生成,无需管理。 地址头有mqtt和ws两种,mqtts和wss是加密传输。(有些前端,比如微信小程序,官方要求加密传输,所以一定要加s,但是官方提供的免费服务器只提供非加密传输,所以建议用微信小程序自己搭服务器。 暂时不填写用户名和密码,默认免密登录服务器,然后有实际需要。 这里的名字叫我remote_test连接免费远程服务器。然后点击添加订阅添加订阅。 建议不要订阅默认testtopic/#主题名,因为用的人很多,你会收到别人在这里发的内容,不适合我们自己测试。 是不是特别像聊天室?…testtopic/后跟#意思是只要testtopic开头的都可以接收。 #只用于订阅时,必须指出你发布的主题,而不是范围,比如我写的testtopic/down要发布的主题。 下面的Json格式数据是要发送的内容,Payload可以选择数据格式,我用的是Json。 Qos是发送后对方如果无响应的重发策略,暂时不用管。
安装Hbuilder
使用uni-app因为它可以直接在手机上运行,毕竟,手机通常用于控制开关。 使用Hbuilder因为它可以快速生成一个uni-app作为一个编辑器,项目感觉不如VScode。
Hbuilder官网:HbuilderX-高效极客技能 创建uni-app项目可以看到官方文件的快速开始:uni-app官网
Vue连接mqtt服务器
需要先在Hbuilder引入底部的终端mqtt。目前已经有4.0.0版本,但可能有小问题。
npm install mqtt@3.0.0
我只是在这里放一下js部分连接脚本。 您可以在发布和接收数据后设计前端界面样式和逻辑。 这里我的state变量来自vuex。 与硬件端代码有一些对应的地方,建议放在一起。
<script scoped> export default {
data() {
return {
//mqttHost: "192.168.1.109" // 自己的服务器地址 mqttHost: "broker.emqx.io", // 官方服务器地址 mqttPort: 8083,
mqttPath: "/mqtt",
client: {
} // 代表我的客户端
}
},
onLoad() {
},
// 注意在OnShow()中连接
onShow() {
this.mqttConnect()
},
methods: {
mqttConnect() {
var that = this
// 连接服务器
// 在其他地方你可能会看到引入connect的方式,但是那样在APP端可能无法连接服务器
var mqtt = require('mqtt/dist/mqtt.js')
// #ifdef APP-PLUS
var pinf = plus.push.getClientInfo(); // 获取终端标识
var clientId = pinf.clientid; //客户端标识
const options = {
port: that.mqttPort,
clientId,
};
that.client = mqtt.connect("wx://" + that.mqttHost + that.mqttPath, options);
//#endif
// #ifdef H5
const options = {
port: that.mqttPort,
};
that.client = mqtt.connect("ws://" + that.mqttHost + that.mqttPath, options);
//#endif
// 如果连接成功执行函数
that.client.on('connect', function(){
console.log('connected')
// 订阅主题并执行函数
that.client.subscribe('testtopic/up/#', function(err){
if(!err){
console.log('get topic')
}
})
})
// 我设定设备会每隔一段时间发布一次自己的状态
// 如果监听到主题执行函数
that.client.on('message', function(topic,message){
console.log(topic)
// 我设计的主题末尾是设备的mac地址,这里是将mac地址赋值给id
let id = ''
id = topic.substr(topic.lastIndexOf("/") + 1)
let data = {
}
data = JSON.parse(message)
let set = {
"id": id,
"state": data.state
}
// 将前端该id的设备状态设为获取到的状态
that.$store.dispatch('setState', set)
})
},
// 点击灯的开关按钮
clickButton(item) {
var that = this
// 如果此时是开着的(state为true)
if(item.state){
// 发布关闭消息
that.client.publish('testtopic/down/'+item.id,'{"switch": 0}',function(err){
if(!err){
console.log(item.name+' off')
let set = {
"id": item.id,
"state": false
}
// 由于我设定设备会间隔一段时间发布一遍状态
// 那么我们点击以后前端按钮不一定马上会做出反应
// 所以我们不应该等待设备发状态再更改前端按钮的状态
// 应该直接前端先改了,如果设备改状态失败,再通过设备发布的状态改回去
that.$store.dispatch('setState', set)
}
})
}else{
// 发布开启消息
that.client.publish('testtopic/down/'+item.id,'{"switch": 1}',function(err){
if(!err){
console.log(item.name+' on')
let set = {
"id": item.id,
"state": true
}
that.$store.dispatch('setState', set)
}
})
}
},
}
}
</script>
安装Arduino
用于编写c语言硬件程序的编辑器,配合烧录器将程序传入硬件。 下载地址:Software | Arduino
基本配置
打开 文件 > 首选项 像这种项目文件不建议放c盘。 输出模式为编译。 管理器网址直接填这个 http://arduino.esp8266.com/stable/package_esp8266com_index.json
确认后关了重启。
esp8266开发板配置
打开 工具 > 开发板 > 开发板管理器… 搜索esp8266并安装。 选择开发板:NodeMCU 1.0,与esp01s通用。
ESP01s连接mqtt服务器
我是看这个了解了大概框架再改的:太极创客 | MQTT基础篇
Arduino引入mqtt相关库
使用mqtt需要先引入它的包。 打开 工具 > 管理库 搜索PubSubClient,可能需要往下翻一下。
Arduino引入Json操作相关库
由于我是以Json格式的数据发布的,所以在esp01s接收到以后需要会处理Json数据。
Aduino源码
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Ticker.h>
#include <ArduinoJson.h>
// 设置wifi接入信息(请根据您的WiFi信息进行修改)
const char* ssid = "This is not 516";
const char* password = "zz888666";
const char* mqttServer = "192.168.1.109"; //可替换官方提供的域名:broker.emqx.io
// 如以上MQTT服务器无法正常连接,请前往以下页面寻找解决方案
// http://www.taichi-maker.com/public-mqtt-broker/
Ticker ticker;
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
int count; // Ticker计数用变量
int pin = 0; // 设置一个变量代表控制继电器开关的GPIO
// 通电初始化函数
void setup() {
Serial.begin(9600); // 波特率
//pinMode(LED_BUILTIN, OUTPUT); // 这里换成变量
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW); // GPIO0 低电平,吸合继电器,作为初始状态
// Ticker定时对象
ticker.attach(1, tickerCount);
//设置ESP8266工作模式为无线终端模式
WiFi.mode(WIFI_STA);
// 连接WiFi
connectWifi();
// 设置MQTT服务器和端口号
mqttClient.setServer(mqttServer, 1883);
mqttClient.setCallback(receiveCallback);
// 连接MQTT服务器
connectMQTTServer();
}
// 状态持续函数,一直重复执行
void loop() {
if (mqttClient.connected()) {
// 如果开发板成功连接服务器
// 每隔3秒钟发布一次信息,一般用于状态报告
if (count >= 3){
pubMQTTmsg();
count = 0;
}
// 保持心跳
mqttClient.loop();
} else {
// 如果开发板未能成功连接服务器
connectMQTTServer(); // 则尝试连接服务器
}
}
void tickerCount(){
count++;
}
// 连接MQTT服务器
void connectMQTTServer(){
// 根据ESP8266的MAC地址生成客户端ID(避免与其它ESP8266的客户端ID重名)
String clientId = "esp8266-" + WiFi.macAddress();
// 连接MQTT服务器
if (mqttClient.connect(clientId.c_str())) {
Serial.println("MQTT Server Connected.");
Serial.print("Server Address: ");
Serial.println(mqttServer);
Serial.print("ClientId: ");
Serial.println(clientId);
subscribeTopic(); // 订阅指定主题
} else {
Serial.print("MQTT Server Connect Failed. Client State:");
Serial.println(mqttClient.state());
delay(3000);
}
}
// 发布信息
void pubMQTTmsg(){
// 建立发布主题。
String topicString = "testtopic/up/"+WiFi.macAddress(); // 上行主题,设备到控制端
char publishTopic[topicString.length() + 1];
strcpy(publishTopic, topicString.c_str());
// 定时向服务器主题发布当前GPIO0引脚状态
String state;
if(digitalRead(0)){
state = "{\"state\": false}"; // 高电平断开继电器
} else {
state = "{\"state\": true}"; // 低电平吸合继电器
}
// 打包为JSON,但是我发现message变成了null,所以放弃了,直接上面写JSON...
// DynamicJsonDocument doc(1024);
// JsonObject obj = doc.as<JsonObject>();
// obj["state"] = state;
// String message;
// serializeJson(doc, message);
char publishMsg[state.length() + 1];
strcpy(publishMsg, state.c_str());
// 实现ESP8266向主题发布信息
if(mqttClient.publish(publishTopic, publishMsg)){
Serial.print("Publish Topic: ");Serial.println(publishTopic);
Serial.print("Publish message: ");Serial.println(publishMsg);
} else {
Serial.println("Message Publish Failed.");
}
}
// 订阅指定主题
void subscribeTopic(){
// 建立订阅主题。
String topicString = "testtopic/down/"+WiFi.macAddress(); // 下行主题,控制端到设备
char subTopic[topicString.length() + 1];
strcpy(subTopic, topicString.c_str());
// 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
if(mqttClient.subscribe(subTopic)){
Serial.print("Subscribe Topic: ");
Serial.println(subTopic);
} else {
Serial.print("Subscribe Fail...");
}
}
// 收到信息后的回调函数
void receiveCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message Received [");
Serial.print(topic);
Serial.println("] ");
// 将获取到的pyte类payload消息转换为String类型的data
String data = "";
for (int i = 0; i < length; i++) {
data = data + (char)payload[i];
}
Serial.println(data);
// 一般我们会发布JSON格式的消息,所以就需要解析获取JSON中的数据
// 声明你的json数据中存在几个对象,如果有list列表,需要+ JSON_ARRAY_SIZE(1),这里我的json只有{"switch":1}
const size_t capacity = JSON_OBJECT_SIZE(1) + 60;
StaticJsonDocument<capacity> jsonBuffer;
// Parse JSON object解析 JSON
deserializeJson(jsonBuffer, data);
JsonObject object = jsonBuffer.as<JsonObject>();
Serial.println(object["switch"].as<String>());
// Serial.println("");
// Serial.print("Message Length(Bytes) ");
// Serial.println(length);
if (object["switch"].as<String>() == "1") {
// 如果收到的信息为"1",注意双引号为String,单引号为char
//digitalWrite(LED_BUILTIN, LOW); // 则点亮LED。
digitalWrite(pin, LOW); // GPIO0 低电平,吸合继电器
} else {
//digitalWrite(LED_BUILTIN, HIGH); // 否则熄灭LED。
digitalWrite(pin, HIGH); // GPIO0 高电平,断开继电器
}
}
// ESP8266连接wifi
void connectWifi(){
WiFi.begin(ssid, password);
//等待WiFi连接,成功连接后输出成功信息
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
Serial.println("");
}
上传到硬件
这个时候你需要将esp01s插在烧录器上,再把烧录器插到电脑上。 查看你的串口是否识别到。 如果有就可以点击左上角箭头上传到硬件。
源码中的println函数会将数据输出到串口监视器。
上传成功后就会开始println输出,并且发布自己开关的状态到mqtt服务器。
此时你可以在客户端看到硬件发布的内容。
然后你可以测试用客户端发送关闭消息。此时它定时发布的GPIO 0引脚就变成高电平。
硬件演示
把esp01s插到继电器上就能用来控制开合了。
继电器背面标了接口符号。VCC:电源,GND:接地。 GPIO 0为低电平时(开):COM与NO闭合。 GPIO 0为高电平时(关):COM与NC闭合。
然后我另外从电池接了线出来给LED灯供电来模拟开关灯。
关于mqtt主题
可能会有人和我一样奇怪为什么上行下行的数据要用两个主题,不是一个主题两边都能发数据吗。 于是我自己试了一下发布和订阅同一个主题,在MQTT X客户端上我们可以看到如果在自己已经订阅的主题中发布信息,会同时在该主题中收到自己发布的信息。 那么如果两边用同一个主题交换消息,两边就都需要识别每一个消息是对方发的还是自己发的,识别方法可能就是在Payload中多写一个数据标识是谁发的。这明显会增加很多的判断成本。 按照这样一个思路,一个主题最好的使用方法应该是一个发布者对应多个订阅者或者一个订阅者对应多个发布者。 但是同为客户端,也分为手机控制端和物联网IoT设备。 那么在不考虑IoT设备之间互相订阅的前提下,一个IoT设备,应该都会有一个主题是自己独占用来发布的,多个控制端可以订阅这个主题,同时有一个主题是独占用来接收控制信息的,多个控制端可以发布信息到这个主题。而一个控制端,与每一个IoT设备之间都会有一个发布主题和订阅主题,不存在自己独占的发布或订阅主题。