玩转 ESP32 + Arduino (二十二) SIM800L上传数据到阿里IOT(温湿度和LBS)(NTP对时)

思路同上一篇: 改为连接阿里云平台, 增加了平台对时功能和定位功能
思路同上一篇:
本例程实现了以下功能:

  1. 通过SIM800L连接网络
  2. 连接阿里IOT平台()
  3. 调用SHT20的库采集温湿度
  4. 将温湿度信息以及LBS定位信息上传至IOT
  5. 与阿里云IOT平台进行对时
  6. 每5分钟采集一次, 采集完成就休眠
#include <Arduino.h>
#include "PubSubClient.h"
#include "aliyun_mqtt.h"
#include "ArduinoJson.h"
#include "uFire_SHT20.h"

/*-------------------------------SIM800L 硬件定义----------------------------------*/
#define MODEM_RST 5       //SIM800L复位引脚接在GPIO5
#define MODEM_PWRKEY 4    //SIM800L开关机引脚接在GPIO4
#define MODEM_POWER_ON 23 //SIM800L电源引脚接在GPIO23
#define MODEM_TX 27       //SIM800L串口TX引脚接在GPIO27
#define MODEM_RX 26       //SIM800L串口RX引脚接在GPIO26

/*-------------------------------其他硬件定义-------------------------------------*/
uFire_SHT20 sht20;
#define SerialMon Serial //调试串口为UART0
#define SerialAT Serial1 //AT串口为UART1

/*-------------------------------公共变量,参数定义-------------------------------------*/
float currentTemp, currentHumi; //温湿度
bool tempAndHumi_Ready = false; //温湿度采集成功标志位
bool timeNTPdone = false;

//以下参数需要休眠记忆
RTC_DATA_ATTR time_t lastNTP_timestamp;       //上次对时的时间戳
RTC_DATA_ATTR int postMsgId = 0;          //记录已经post了多少条
RTC_DATA_ATTR float locationE, locationN; //地理位置,经度纬度
RTC_DATA_ATTR tm *timeNow;                //当前时间

/*-------------------------------Modem相关定义-------------------------------------*/
#define TINY_GSM_MODEM_SIM800 // Modem is SIM800
//引入TinyGSM库. 在引入之前要定义好TINY_GSM_MODEM_SIM800,让它知道我们用的模块型号
#include <TinyGsmClient.h>
// 创建一个关联到SerialAT的SIM800L模型
TinyGsm modem(SerialAT);
// 创建一个GSM型的网络客户端
TinyGsmClient gsmclient(modem);
PubSubClient mqttClient(gsmclient);
// Your GPRS credentials (leave empty, if missing)
const char apn[] = "CMNET"; // Your APN
const char gprsUser[] = ""; // User
const char gprsPass[] = ""; // Password
const char simPIN[] = "";   // SIM card PIN code, if any

/*-------------------------------云平台相关定义-------------------------------------*/
#define PRODUCT_KEY "a1AYa96sZMJ"                        //产品ID
#define DEVICE_NAME "EspTempAndHumi_D001"                //设备名
#define DEVICE_SECRET "a23249cb179feee41ca2f8f38525113d" //设备key
//鉴权信息
#define mqtt_password "version=2018-10-31&res=products%2F370098%2Fdevices%2Fesp_device001&et=4092512761&method=md5&sign=MUV%2BKFLzv81a4Bw6BDrChQ%3D%3D"

//设备下发命令的set主题
#define ALINK_TOPIC_PROP_SET "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/service/property/set"
//设备上传数据的post主题
#define ALINK_TOPIC_PROP_POST "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post"
//设备post上传数据要用到一个json字符串, 这个是拼接postJson用到的一个字符串
#define ALINK_METHOD_PROP_POST "thing.event.property.post"
//这是post上传数据使用的模板
#define ALINK_BODY_FORMAT "{\"id\":\"%u\",\"version\":\"1.0\",\"method\":\"%s\",\"params\":%s}"
//服务器时间同步主题
#define ALINK_TOPIC_NTP_REQ "/ext/ntp/" PRODUCT_KEY "/" DEVICE_NAME "/request"
#define ALINK_TOPIC_NTP_RSP "/ext/ntp/" PRODUCT_KEY "/" DEVICE_NAME "/response"

/*-------------------------------初始化SIM800L-------------------------------------*/
void setupModem()
{
  pinMode(MODEM_POWER_ON, OUTPUT); //电源引脚
  pinMode(MODEM_PWRKEY, OUTPUT);   //开关机键引脚

  // 先打开SIM800L的电源
  digitalWrite(MODEM_POWER_ON, HIGH);
  //根据手册要求拉下PWRKEY 1秒钟以上 可以开机
  digitalWrite(MODEM_PWRKEY, HIGH);
  delay(100);
  digitalWrite(MODEM_PWRKEY, LOW);
  delay(1000);
  digitalWrite(MODEM_PWRKEY, HIGH);
  SerialMon.println("Initializing modem...");
  modem.init(); //开机后modem初始化一下
}
/*-------------------------------SIM800L连接GPRS-------------------------------------*/
void modemToGPRS()
{
  //连接网络
  SerialMon.print("Waiting for network...");
  while (!modem.waitForNetwork(240000L))
  {
    SerialMon.print(".");
    delay(500);
  }
  SerialMon.println(" OK");
  //连接GPRS接入点
  SerialMon.print(F("Connecting to APN: "));
  SerialMon.print(apn);
  while (!modem.gprsConnect(apn, gprsUser, gprsPass))
  {
    SerialMon.print(".");
    delay(10000);
  }
  SerialMon.println(" OK");
}
/*-------------------------------获取温湿度-------------------------------------*/
void getTempAndHumi()
{
  float _currentTemp = sht20.temperature();
  float _currentHumi = sht20.humidity();
  if (_currentTemp > -50 && _currentTemp < 80)
  {
    currentTemp = _currentTemp;
    currentHumi = _currentHumi;
    tempAndHumi_Ready = true;
    /* code */
  }
  else
  {
    Serial.println("此处写温湿度采集失败的处理函数");
  }
}
/*-------------------------------获取位置信息-------------------------------------*/
void getLBSLocation()
{
  String locationStr, locationStrX, locationStrY;
  locationStr = modem.getGsmLocation();
  if (locationStr.length() > 15)
  {
    int finddou;
    finddou = locationStr.indexOf(',');
    locationStrX = locationStr.substring(0, finddou);
    locationStrY = locationStr.substring(finddou + 1, locationStr.length());
    Serial.println(locationStr);
    Serial.println(finddou);
    Serial.println(locationStrX);
    Serial.println(locationStrY);
    if (locationStrX.toFloat() > 1)
    {
      locationE = locationStrX.toFloat();
      locationN = locationStrY.toFloat();
    }
  }
}
/*-------------------------------获取NTP信息-------------------------------------*/
void mqttPublish_ntpTimeRequest()
{
  if (mqttClient.connected())
  {
    //先拼接出json字符串
    char jsonBuf[178] = "{\"deviceSendTime\":\"1571724098000\"}";
    //再从mqtt客户端中发布post消息
    if (mqttClient.publish(ALINK_TOPIC_NTP_REQ, jsonBuf))
    {
      Serial.print("NTP message to cloud: ");
      Serial.println(jsonBuf);
    }
    else
    {
      Serial.println("Publish NTP message to cloud failed!");
    }
  }
}

/*-------------------------------向云平台断线重连-------------------------------------*/
void clientReconnect()
{
  while (!mqttClient.connected()) //再重连客户端
  {
    Serial.print("net connected :");
    Serial.println(modem.isGprsConnected());
    Serial.println("reconnect MQTT...");
    if (connectAliyunMQTT(mqttClient, PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET))
    {
      Serial.println("connected");
    }
    else
    {
      Serial.println("failed");
      Serial.println(mqttClient.state());
      Serial.println("try again in 5 sec");
      delay(5000);
    }
  }
}
/*-------------------------------向云平台发送温湿度数据-------------------------------*/
void sendTempAndHumi()
{
  if (mqttClient.connected())
  {
    if (!tempAndHumi_Ready)
      return; //如果没有测好温湿度, 就不用上传了
    //先拼接出json字符串
    char param[182];
    char jsonBuf[278];
    sprintf(param, "{\"CurrentHumidity\":%.2f,\"CurrentTemperature\":%.2f,\"GeoLocation\":{\"value\":{\"Longitude\":%.2f,\"Latitude\":%.2f,\"Altitude\":150,\"CoordinateSystem\":2}}}",
            currentHumi,
            currentTemp,
            locationE,
            locationN); //我们把要上传的数据写在param里
    postMsgId += 1;
    sprintf(jsonBuf, ALINK_BODY_FORMAT, postMsgId, ALINK_METHOD_PROP_POST, param);
    //再从mqtt客户端中发布post消息
    if (mqttClient.publish(ALINK_TOPIC_PROP_POST, jsonBuf))
    {
      Serial.print("Post message to cloud: ");
      Serial.println(jsonBuf);
    }
    else
    {
      Serial.println("Publish message to cloud failed!");
    }
    tempAndHumi_Ready = false;
  }
}

/*-------------------------------云平台回调-------------------------------*/
void callback(char *topic, byte *payload, unsigned int length)
{
  //如果收到的主题里包含字符串ALINK_TOPIC_PROP_SET(也就是收到"/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/service/property/set"主题)
  if (strstr(topic, ALINK_TOPIC_PROP_SET))
  {
    Serial.println("rev a topic:");
    Serial.println(topic);
    Serial.println("the payload is:");
    payload[length] = '\0'; //为payload添加一个结束附,防止Serial.println()读过了
    Serial.println((char *)payload);
    //接下来是收到的json字符串的解析
    DynamicJsonDocument doc(100);
    DeserializationError error = deserializeJson(doc, payload);
    if (error)
    {
      Serial.println("parse json failed");
      return;
    }
    JsonObject setAlinkMsgObj = doc.as<JsonObject>();
    serializeJsonPretty(setAlinkMsgObj, Serial);
    Serial.println();
    //接下来就可以解析set里的命令了
  }
  //如果收到的主题里包含字符串ALINK_TOPIC_NTP_RSP(也就是收到"/ext/ntp/" PRODUCT_KEY "/" DEVICE_NAME "/response")
  if (strstr(topic, ALINK_TOPIC_NTP_RSP))
  {
    Serial.println("rev a topic:");
    Serial.println(topic);
    Serial.println("the payload is:");
    payload[length] = '\0'; //为payload添加一个结束附,防止Serial.println()读过了
    Serial.println((char *)payload);

    DynamicJsonDocument timDoc(100);
    DeserializationError error = deserializeJson(timDoc, payload);
    if (error)
    {
      Serial.println("parse timDoc json failed");
      return;
    }
    JsonObject setAlinkMsgObj = timDoc.as<JsonObject>();
    serializeJsonPretty(setAlinkMsgObj, Serial);
    String str = setAlinkMsgObj["serverRecvTime"];
    //这个时间字符串是以毫秒为单位的, 用toInt方法可能会溢出,所以需要裁减
    str = str.substring(0, 10);
    long stamp = str.toInt() + 8 * 60 * 60; //我们在东八区,所以给时间戳加了八个小时的偏移
    time_t timesmap = stamp;
    timeNow = localtime(&timesmap);
    Serial.printf("timeNow: %d-%d-%d %d:%d",
                  timeNow->tm_year + 1900, //由于格林威治时间从1900年开始,所以加1900
                  timeNow->tm_mon + 1,     //格林威治时间月份是0~11,所以+1
                  timeNow->tm_mday,        //mday的意思是本月第几天
                  timeNow->tm_hour,
                  timeNow->tm_min);
    timeNTPdone = true;
  }
}

void setup()
{
  Wire.begin();
  sht20.begin();
  SerialMon.begin(115200);                                //初始化调试串口
  SerialAT.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX); //初始化AT串口

  setupModem();  //SIM800L物理开机
  modemToGPRS(); //modem连接GPRS
  Serial.println("get LBS location:");
  getLBSLocation(); //采集位置信息
  //连接OneNet并上传数据
  Serial.println("connecting to ALI IOT...");
  if (connectAliyunMQTT(mqttClient, PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET))
  {
    Serial.println("MQTT connected!");
  };
  mqttClient.setCallback(callback); //绑定收到set主题时的回调(命令下发回调)
  getTempAndHumi();                 //采集温湿度数据
  sendTempAndHumi();                //发布一次温湿度位置消息
  Serial.println("now NTPing");
  timeNTPdone = false;
  mqttPublish_ntpTimeRequest();
  Serial.println("now looping");
  while (!timeNTPdone)
  {
    mqttClient.loop();
  }
  delay(500);
  //进入睡眠
  esp_sleep_enable_timer_wakeup(300000000);
  Serial.println("now sleep");
  esp_deep_sleep_start();
}

void loop()
{
}
/*-------------------------------获取定位信息LSB-------------------------------------*/
void getLSB()
{
  String locationStr, locationStrX, locationStrY;
  int finddou;
  locationStr = modem.getGsmLocation();
  finddou = locationStr.indexOf(',');
  locationStrX = locationStr.substring(0, finddou);
  locationStrY = locationStr.substring(finddou+1, locationStr.length());
  Serial.println(locationStr);
  Serial.println(finddou);
  Serial.println(locationStrX);
  Serial.println(locationStrY);
}

经测试, 上传正常

这是阿里云studio的画面

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341