前言:
从接触ESP8266到现在有一段时间了,也感受到ESP8266的强大,其高性价比给极客者们带来了极大的福音。之前用ESP8266改装了一部遥控车,使其能用手机控制,手机app是用易安卓编制的,遥控车视频挂到b站了,视频地址:
https://www.bilibili.com/video/av20141857?from=search&seid=10781143291446850224
这次是打算用Arduino+ESP8266来实现智能配网以及通过EDP协议上传温度、湿度和光照强度等数据到OneNET平台,也可以用EDP下发命令来实现远程控制。
所需材料:
Arduino_pro_mini 1
ESP8266-12F 1
USB-TTL串口下载模块 1
DHT11温湿度传感器 1
面包板及杜板线 若干
光敏电阻 1
电阻及发光二极管等 若干
智能配网(Smart Config)
SmartConfig的过程:
1、wifi模块通电,没有可用的wifi, 进入混杂模式,开始监(和谐)听覆盖范围内所有WiFi数据帧;
2、手机APP或微信端发送包含WIFI用户名和WIFI密码的UDP广播包或者组播包;
3、智能终端的WIFI芯片可以接收到该UDP包,只要知道UDP的组织形式,就可以通过接收到的UDP包解密出WIFI用户名和密码;
4、然后智能硬件配置收到的WIFI用户名和密码到指定的WIFI AP(路由器)上。
ESP8266可通过AT指令进入智能配网模式,我所用的AT固件是安信可推出的固件,可在安信可官网里 http://wiki.ai-thinker.com/esp8266/sdk
下载。配网的AT指令可参考安信可官网 http://wiki.ai-thinker.com/esp8266/examples/at_demo
里的步骤。通过将AT指令写在pro mini内,再通过外部按钮按下进入配网。
pro mini通过软串口跟ESP8266通信,而ESP8266用的是3.3V电源,pro mini用的是5V电源,所以串口必须通过电平转换才行,由于身边暂时没有电平转换模块,所以只能通过分压来实现。经过实测,ESP8266跟pro mini软串口通信用115200波特率
会出现乱码,所以只能用9600波特率。
分压和配网按钮接线图如下所示:
程序如下:
#include //使用软串口的库文件
#define configkey 2 //配网按钮,按住时候配网
#define configRED 7 //智能配网失败红灯亮起
#define configGREEN 8 //智能配网成功绿灯亮起
#define TcpLED 12 //OneNET的TCP连接灯
#define EdpLED 6 //OneNET的EDP连接灯
bool link; //判断智能配网是否成功
bool keyOK=1; //判断配网按钮是否可用
SoftwareSerial mySerial(10, 9); // RX, TX
void setup() {
mySerial.begin(9600);
pinMode(configkey,INPUT); //设置按键引脚为输入
pinMode(configRED,OUTPUT); //没连上无线网
pinMode(configGREEN,OUTPUT); //连上无线网
pinMode(TcpLED,OUTPUT); //连上OneNET的TCP
pinMode(EdpLED,OUTPUT); //连上OneNET的EDP
}
void loop() {
int buttonState = digitalRead(configkey);
if(buttonState==0 && keyOK==1){ //按钮被按下时
mySerial.print("+++");
delay(100);
mySerial.print("+++");
delay(100);
digitalWrite(TcpLED, HIGH);
analogWrite(EdpLED,255);
digitalWrite(configGREEN, HIGH);
digitalWrite(configRED, HIGH);
while (!doCmdOk("AT+CWSTARTSMART=3", "OK",5)); //启动智能配网
t1=0;
Serial.println("Smartconfig Start");
digitalWrite(testLED, HIGH);
while (!doConfigOK("smartconfig connected wifi"));
t1=0;
if(link==1){
Serial.println("do Config OK");
digitalWrite(configGREEN, LOW);
digitalWrite(testLED, LOW);
while (!doCmdOk("AT+CIPSTART=\"TCP\",\"183.230.40.39\",876","CONNECT",5)); //OneNET的TCP透传
t1=0;
digitalWrite(TcpLED, LOW); //tcp连接指示灯亮
Serial.println("TCP connect OK");
while (!doCmdOk("AT+CIPMODE=1", "OK",5)); //透传模式
t1=0;
while (!doCmdOk("AT+CIPSEND", ">",5)); //开始发送
t1=0;
link=1;
edp_connect=0;
keyOK=0;
Serial.println("Start send");
} else {
Serial.println("do Config ERROR");
digitalWrite(configRED, LOW);
digitalWrite(testLED, LOW);
}
while (!doCmdOk("AT+CWSTOPSMART", "OK",5)); //不管ESP8266有没有连上wifi,都要释放内存
t1=0;
}
等待串口命令结果函数
booldoCmdOk(String data, char keyword[], int timeout){ //给ESP8266发送AT指令,在次数timeout范围内回应正确就返回TURE
bool result = false;
if (data != ""){
mySerial.println(data); //发送AT指令
Serial.print("SEND: ");
Serial.println(data);
}
while (!mySerial.available()); // 等待模块回复
delay(200);
if (mySerial.find(keyword)){ //返回值判断
Serial.println("do cmd OK");
response=1;
result = true;
} else {
Serial.println("do cmd ERROR");
result = false;
}
while(mySerial.read()>= 0){} //清空串口接收缓存
delay(800); //指令时间间隔
t1++;
if(t1>=timeout){ //超过设定时间,跳出循环,并返回回应判断response的值
result = true;
response=0;
}
return result;
}
配网时等待串口回复函数
booldoConfigOK(char keyword1[]){ //开启智能配网
bool result1 = false;
linktime=0;
while (!mySerial.available()){ // 等待模块回复,10秒内要配网完成
t1++;
delay(1000);
if(t1 >= 10){
linktime=1;
link=0;
result1 = true;
Serial.println("Config timeout");
break; //超过时间串口没收到信号就跳出
}
}
if(linktime==0){
delay(200);
if (mySerial.find(keyword1)){ //返回值判断
link=1;
result1 = true;
} else {
link=0;
result1 = false;
}
while(mySerial.read()>= 0){} //清空串口接收缓存
delay(500); //指令时间间隔
}
return result1;
}
EDP连接
通过OneNET官网的库文件edp.c,可以很简单的连接到OneNET平台。当成功连接到平台时,会收到0x20 0x02 0x00 0x00,可以用这个信号来判断是否连接上。
if(link==1){ //当wifi连接上后,执行下面程序
if (!edp_connect){
while(mySerial.read()>= 0){} //清空串口接收缓存
packetSend(packetConnect(ID, KEY)); //发送EPD连接包
while (!mySerial.available()); //等待EDP连接应答
delay(200);
if ((tmp = mySerial.readBytes(rcv_pkt.data, sizeof(rcv_pkt.data))) > 0 ){
rcvDebug(rcv_pkt.data, tmp);
if (rcv_pkt.data[0] == 0x20 && rcv_pkt.data[1] == 0x02 && rcv_pkt.data[2] == 0x00 && rcv_pkt.data[3] == 0x00)
{
edp_connect = 1;
analogWrite(EdpLED,0);
Serial.println("EDP connected.");
}
else
Serial.println("EDP connect error.");
}
packetClear(&rcv_pkt);
}
}
光敏电阻
光敏电阻通过分压,并通过模拟口A0输入到pro mini,接线电路图如下:
通过分压后,读出的值在0~1024之间,数值越大,说明光线约弱,有点不太直观,于是我用个简单的函数将数值控制在0~100之间,并且数值越大,光线约强。
#define lightread A0 //光敏电阻读取的数值
ld_x=analogRead(lightread); //光敏电阻接收的信号,信号是模拟值
ld=(4490-4*ld_x)/49;
温湿度传感器
温湿度传感器DHT11接线如下图所示:
#include //使用DHT11的库文件
#define DHT11PIN A1 //DHT11温湿度模块数值
int wd; //温度
int sd; //湿度
int chk = DHT11.read(DHT11PIN); //读DHT11
wd = (float)DHT11.temperature; //获取温度
sd = (float)DHT11.humidity; //获取湿度
上传数据到OneNET平台
if (edp_connect && trigger){
Serial.print("temperature : "); Serial.println(wd); //串口打印温度
Serial.print("humidity : "); Serial.println(sd); //串口打印湿度
Serial.print("LigthRead_x : "); Serial.println(ld_x); //串口打印亮度(读值)
Serial.print("LigthRead_y : "); Serial.println(ld); //串口打印亮度(计算值)
Serial.println(" ");
sprintf(wd1,"%d",wd); //int型转换char型
sprintf(sd1,"%d",sd); //int型转换char型
sprintf(ld1,"%d",ld); //int型转换char型
packetSend(packetDataSaveTrans(NULL, "tem", wd1)); //发送的数据必须为字符串
delay(100);
packetSend(packetDataSaveTrans(NULL, "hum", sd1));
delay(100);
packetSend(packetDataSaveTrans(NULL, "light", ld1));
delay(2000);
}
EDP下发控制命令
在OneNET平台开发者中心创建应用,在元件库里拉出一个开关按钮。按钮的EDP命令内容填datastr:{V}
接收EDP命令并解析的程序:
while(mySerial.available()){
readEdpPkt(&rcv_pkt);
if (isEdpPkt(&rcv_pkt)){
pkt_type = rcv_pkt.data[0];
switch (pkt_type){
case CMDREQ:
char edp_command[50];
char edp_cmd_id[40];
long id_len, cmd_len, rm_len;
char datastr[20];
char val[10];
memset(edp_command, 0,sizeof(edp_command)); //置0
memset(edp_cmd_id, 0,sizeof(edp_cmd_id)); //置0
edpCommandReqParse(&rcv_pkt,edp_cmd_id, edp_command, &rm_len, &id_len, &cmd_len); //按照EDP命令请求协议,解析数据
Serial.print("rm_len: ");
Serial.println(rm_len, DEC);
delay(10);
Serial.print("id_len: ");
Serial.println(id_len, DEC);
delay(10);
Serial.print("cmd_len: ");
Serial.println(cmd_len, DEC);
delay(10);
Serial.print("id: ");
Serial.println(edp_cmd_id);
delay(10);
Serial.print("cmd: ");
Serial.println(edp_command);
sscanf(edp_command,"%[^:]:%s", datastr, val);
if (atoi(val) == 1){ //atoi(),int转换为char,把一个整数转换为字符串。
Serial.println("LEDon"); // 使Led亮
digitalWrite(testLED, HIGH);
}
else if(atoi(val) == 0){
Serial.println("LEDoff"); // 使Led灭
digitalWrite(testLED, LOW);
}
packetSend(packetDataSaveTrans(NULL,datastr,val)); //将新数据值上传至数据流
break;
default:
Serial.print("unknown type:");
Serial.println(pkt_type, HEX);
break;
}
}
}
最后的接线图:
电脑端界面: