从零开始开发物联网项目(6)——Arduino和ESP8266自动数据上传终端

前面几节介绍了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数据库里面,这样一个自动采集数据的物联网项目才能算完整!

你可能感兴趣的:(物联网)