前面几节介绍了Mqtt协议以及用ESP8266模块作为客户端连接Mqtt服务器并实现数据的发布和订阅。这一节我们就正式的开始制作第一个物联网终端,基于Arduino和ESP8266模块。之所以选择了Arduino是因为它的开发比较方便,并且后期直接可以自己用Atmega芯片搭建最小系统来实现终端的产品化。
以下就是基于Arudino和ESP8266的自动数据采集和上传终端,前期为了调试数据的需要,我直接使用了Arduino mega2560,因为它有多个串口可以使用,当然Arduino UNO也完全没问题,但是由于只有一个硬串口,而软串口支持不了115200波特率,所以ESP模块只能接在硬串口上了,这就导致前期用UNO来开发就会很麻烦,每次改程序就要拔掉ESP模块。所以直接用了2560开发就方便多了。
硬件的连接非常的简单,我这里将ESP模块接到了串口1上,所以接线为:
Esp8266 Arduino Mega2560
Vcc ------------- 3V3
CH_PD ------------ 3V3
GND ------------- GND
URXD ------------- TXD1
UTXD ------------- RXD1
我上传的数据为模拟口A0采集到的模拟电压值,你可以接一个Lm35温度传感器上去,不过我这里仅仅为了测试Mqtt数据传输,就不接了,所以每次测量出来的数据都是随机的。
下面主要就是程序了,思路也很简单,就是完全模拟上一节时手动输入AT指令和Mqtt指令,只不过交由程序来自动实现罢了,好了,直接看程序吧,具体细节我也直接写在程序的注释里了。
//注意,本程序需要Arduino mega2560
//下面填入你自己的IP地址
String tcplinkstr = "AT+CIPSTART=\"TCP\",\"10.0.0.252\",1883";
//你的wifi名称
String wifiname = "******";
//wifi密码
String wifipsd = "******";
String wifilinkstr, tempstr,constr = "";
//mqttlink为mqtt连接指令,参考之前的内容
unsigned char mqttlink[27] = {0x10, 0x19, 0x00, 0x06, 0x4d, 0x51, 0x49, 0x73, 0x64, 0x70, 0x03, 0xc2, 0x01, 0x2c, 0x00, 0x05, 0x4d, 0x61, 0x6b, 0x65, 0x72, 0x00, 0x01, 0x72, 0x00, 0x01, 0x72};
//mqttsubscribe为mqtt数据发布指令,同样参考之前的,这里发送的数据为"t:****",采集到数据后替换掉*
unsigned char mqttpublish[13] = {0x32, 0x0b, 0x00, 0x01, 0x72, 0x00, 0x01, 0x74, 0x3a, 0x2a, 0x2a, 0x2a, 0x2a};
unsigned char mqttheart[2] = {0xc0, 0x00};
unsigned char mqttdata[4];
int mi = 0, sndi = 0, mode = 0, at = 0, espok = 0, temp,rtn;
long timenow, timebefor = 0, tm;
void setup() {
//连接电脑串口,用于程序编写时观察程序的运行
Serial.begin(9600);
//用于连接esp8266
Serial1.begin(115200);
//用于连接wifi的AT指令
wifilinkstr = "AT+CWJAP=\"";
wifilinkstr += wifiname;
wifilinkstr += "\",\"";
wifilinkstr += wifipsd;
wifilinkstr += "\"";
//通电后等待一段时间让esp8266启动完毕
delay(5000);
//一通电就发送网络状态查询,因为esp8266会自动记录wifi,所以可能一开机就连接上了wifi
Serial1.println("AT+CIPSTATUS");
}
//*******以下为loop主程序,主要用mode来判断程序此刻的功能*********
void loop() {
//mode=0时为连接tcp服务器状态,此时为AT指令阶段,esp8266返回的都是字符串
if (mode == 0) {
//读取来自串口1的数据,来自esp8266,最后存在constr字符串中
while (Serial1.available())
{
constr += char(Serial1.read());
delay(2);
}
//如果constr字符串有数据了,则运行tcplink程序
if (constr.length() > 0)
{
tcplink();
}
}
//已经进入透传模式可以发送数据
else if (mode == 1) {
//当接收mqtt服务器返回的数据时是16进制
while (Serial1.available())
{
//将读取的16进制数据保存到mqttdata数组中
mqttdata[mi] = byte(Serial1.read());
mi++;
delay(2);
}
mqttsend();
}
}
//************tcplink程序,主要用于esp8266AT模式时与模块的互动******
void tcplink() {
if (espok == 0)
{
//用于相应网络连接信息返回值,2、4说明wifi已经连上
if (constr.indexOf("STATUS:2") || constr.indexOf("STATUS:4"))
{
Serial.println("STATUS:2、4");
delay(500);
//直接让at=4,跳过连接wifi的AT指令
at = 4;
//有返回说明esp8266模块没问题
espok = 1;
//进入AT发送指令程序,由at值来决定发哪句
esp8266at(at);
}
//5说明WiFi还没有连上,需要从头开始AT指令
else if (constr.indexOf("STATUS:5"))
{
Serial.println("STATUS:5");
delay(500);
at = 1;
espok = 1;
esp8266at(at);
}
//3说明TCP已经连上,直接进入透传模式即可
else if (constr.indexOf("STATUS:3"))
{
Serial.println("STATUS:3");
delay(500);
at = 7;
espok = 1;
esp8266at(at);
}
}
else
{
//如果模块返回OK,则继续下一条AT指令
if (at < 6 && constr.indexOf("OK"))
{
Serial.println("OK");
//收到返回后到下一条需要延时一段时间,不然esp模块会反应不过来
delay(500);
at++;
//执行AT指令
esp8266at(at);
}
//当AT指令到第6条也就是连接TCP服务器后,返回CONNECT说明连接成功
else if (at == 6 && constr.indexOf("CONNECT"))
{
Serial.println("CONNECT");
delay(500);
at++;
esp8266at(at);
}
//当AT指令到第6条也就是连接TCP服务器后,返回CLOSED说明连接不成功,at不增加,会再次连接
else if (at == 6 && constr.indexOf("CLOSED"))
{
Serial.println("CLOSED");
delay(500);
esp8266at(at);
}
//连接成功后收到">"说明进入了透传模式
else if (at == 7 && constr.indexOf(">"))
{
Serial.println("TCPlinked!");
delay(1000);
//模式mode=1,进入了mqtt数据发送模式
mode = 1;
}
}
constr="";//每次处理完数据都要清空
}
//***********************mqtt数据发送**********
void mqttsend() {
//用于计时,从而实现每隔一定时间发送一个数据
timenow = millis();
tm = timenow - timebefor;
temp = analogRead(A0); //采集模拟口A0的数据
tempstr = String(temp); //将采集到的数据变成字符串
int mqtti = tempstr.length(); //读取字符串长度
String mqttch;
//下面的程序用来将采集到的数据添加到mqtt发送的16进制数组中
for (int j = 0; j < mqtti; j++)
{
mqttch = tempstr.substring(j, j + 1);
//将字符转变为ascii码的16进制
mqttpublish[j + 9] = chartobyte(mqttch);
}
for (int j = mqtti; j < 4; j++)
{
//为了mqtt数据发送方便,这里控制发送的字符数每次都相同,后面非数字部分用“*”来填充
mqttpublish[j + 9] = 0x2a;
}
//如果是刚进入透传模式
if (sndi == 0)
{
Serial.print("send mqttlink");
//发送mqtt的连接指令,由于是16进制发送,这里需要用到write
Serial1.write(mqttlink, 27);
sndi = 1;
}
if (tm > 10000) //每次计时到10秒时
{
Serial1.write(mqttheart, 2); //发送心跳指令
timebefor = millis();
}
if (mi > 0) //如果收到了mqtt服务器返回的数据
{
mi = 0;
//方便起见,只判断返回数据的第一位
if (mqttdata[0] == 0x20 || mqttdata[0] == 0xd0)
{
Serial1.write(mqttpublish, 13); //发布数据
}
//由于用了QoS=01,所以mqtt服务器会返回数据
else if (mqttdata[0] == 0x40)
{
timebefor = millis(); //收到数据后重新计时
}
}
}
//*****************esp8266AT指令**********************
void esp8266at(int eat)
{
switch (eat) {
case 0: {
Serial1.println("AT"); //测试模块是否正常
} break;
case 1: {
Serial1.println("AT+CWMODE=1"); //设置模块模式
} break;
case 2: {
Serial1.println(wifilinkstr); //连接WIFI路由器
} break;
case 3: {
Serial1.println("AT"); //查询
} break;
case 4: {
Serial1.println("AT+CIPMUX=0"); //设置单连接
} break;
case 5: {
Serial1.println("AT+CIPMODE=1"); //设置传输模式为透传
} break;
case 6: {
Serial1.println(tcplinkstr); //连接tcp服务器
} break;
case 7: {
Serial1.println("AT+CIPSEND"); //进入tcp透传模式
} break;
}
}
//*******************字符转换为asscii码,用于mqtt数据发送*****
unsigned char chartobyte(String chr) {
if (chr == "#")
return 0x23;
else if (chr == ":")
return 0x3a;
else if (chr == "$")
return 0x24;
else if (chr == "0")
return 0x30;
else if (chr == "1")
return 0x31;
else if (chr == "2")
return 0x32;
else if (chr == "3")
return 0x33;
else if (chr == "4")
return 0x34;
else if (chr == "5")
return 0x35;
else if (chr == "6")
return 0x36;
else if (chr == "7")
return 0x37;
else if (chr == "8")
return 0x38;
else if (chr == "9")
return 0x39;
else if (chr == "@")
return 0x40;
}
将程序写入Arduino,然后打开emqx的websocket页面,连接上后订阅一个主题为“r“的消息,然后将Arduino断电再通电,就可以在页面上观察来自Arduino的消息了,当然你也可以在手机端订阅,同样可以收到消息。
由于没有使用任何的库函数,所以程序看起来有点长,不过各位同学正好可以了解清楚程序运行的全部过程。如果你使用了Arduino UNO模块的话,可以将程序中的Serial1全部改成Serial就可以了,当然还得把原来的Serial全部删除。
好了,这就是Arduino和ESP8266通过Mqtt服务器自动上传数据的终端示例,你可以接入自己的传感器,上传自己的数据。
现在虽然有了上传数据的终端,可是数据并没有保存起来。并且需要开启一台电脑来作为Mqtt服务器,也有点浪费,下一节我会来介绍如何使用一台树莓派来作为Mqtt服务器,再之后我们用Python编写一个小程序来将Arduino上传的数据存储到Mysql数据库里面,这样一个自动采集数据的物联网项目才能算完整!