物联网云平台了解
1、物联网云平台:接收设备上报的数据、向设备下发数据、对数据进行转发/分析/计算/显示、管理设备等。
2、常见的物联网云平台一般有:
①:私有物联网云平台:假设某瓜农,为瓜棚装上了物联网温湿计,温湿度数据通过网络发送某台主机。这台主机运行特定的程序,作用是记录并分析瓜棚的温湿度。那么,这台主机就是只为一个客户服务的物联网服务器。
②:通用物联网云平台:专业机构搭建开发的物联网服务平台,提供免费/收费的物联网云平台服务面向的场合是:大量客户、大量设备、大量数据。
3、相比于传统开发使用通用物联网平台提供的服务有什么优势
- 传统开发:
1.需要搭建基础设施、寻找并联合嵌入式开发人员与云端开发人员。开发工作量大、效率低。
2.自行实现扩展性架构,极难做到从设备粒度调度服务器、负载均衡等基础设施。
3.需要额外开发、部署各种安全措施,保障设备数据安全是个极大挑战。
4.自行发现宕机并完成迁移,迁移时服务会中断。稳定性无法保障。 - 通用物联网平台的开发
1.提供设备端SDK,快速连接设备上云,效率高。同时支持全球设备接入、异构网络设备接入、多环境下设备接入、多协议设备接入。
2.具有亿级设备的长连接能力、百万级并发的能力,架构支撑水平性扩展。
3.提供多重防护保障设备云端安全,并且可以确保设备认证保障设备安全与唯一性,传输加密保障数据不被篡改,云盾护航、权限校验保障云端安全。服务可用性高达99.9%。单点故障,自动迁移。
创建云端设备(乐鑫云)
1、乐鑫云平台(建议大家选择前一个)
- iot.espressif.cn(时区: 中国北京时间)
- iot.espressif.com(时区:格林尼治标准时间)
2、设备
- 云下设备:真实存在的设备或者是模拟器模拟的设备(ESP8266模组、网络调试助手等)
- 云端设备:在云平台中,与云下设备对应的虚拟设备(在云平台中可以创建设备,但是我创建的都是云端设备,但是每个云端设备都需要对应一个云下设备。
注意:当我们创建云端设备的时候,正常情况下,需要有云下设备来对应我们创建的云端设备。
3、操作 - 首先我们打开乐鑫云物联网平台,我们需要先登录,之后才可以创建云端设备,在设备开发页面点击创建。
- 可以由以下方式快捷登录,也可以注册。
- 之后创建云端设备,在设备开发页面点击创建。
- 填写好产品的名称,类型即可点击创建。注意:最好选择公开设备。
- 点击创建之后出现以下界面,说明我们的云端设备创建成功。可以看到设备的ID,设备系列,设备名,产品ID,产品系列,产品名。注意:当我们创建云端设备的时候,这个设备会被分配唯一的设备密钥,只有通过这个设备密钥我们的云下设备才可以和我们的云端设备建立联系,也就是说我们的云下设备才可以连接到我们的物联网云平台。
- 我们可以下载设备密钥。
- 如图显示,我们的设备未激活,因为我们创建的是云端设备,还没有云下设备和我们的云端设备建立连接,所以我们这里显示的是未激活。
关于数据模型,触发器,定时器,RPC请求等可以参考API文档的详细说明。
API文档:https://iot.espressif.cn/#/api-zh-cn/
与乐鑫云平台进行交互
1、请求
- 目前支持 Http Rest 和 Socket 接口,不是严格的遵循 Rest 规范,比如在 POST
创建行为之后,会响应新创建的对象,为的是方便开发者获取创建对象;没有 DELETE 方法,使用 POST
/url?method=DELETE 代替。 - 所有的 Http API 都通过
https://iot.espressif.cn
这里强烈推荐使用 https 协议而不是
http。 - Socket 通过
tcp://iot.espressif.cn:8000
。 - 加密 Socket 通过
tcp://iot.espressif.cn:8443
。
2、参数
- 目前参数通过三种方式传递:
Url: 参数包含在 url 里面,比如 /v1/products/:product_id/。 :product_id 代表一个可变的参数
Header: 比如认证 Token 的传递:
Authorization: token 6d9542d12d17e69c2d4260f09ee5a72464565e22
Get: 一些分页参数,或者 id:?offset=0&row_count=100
Post: 大部分信息的传递,json字符串:
{
"nonce": 10086,
"path": "/v1/device/",
"method": "GET",
"meta": {
"Authorization": "token 000..."
},
"get": {},
"post": {},
"body": {},
}
- 使用标签表示需要的参数,比如:
require userauth
(需要 email/password 验证)require userkey
(需要 userkey)require devicekey require master devicekey require owner devicekey
(需要 devicekey)require key
(需要 userkey or devicekey)
- 一些常用的参数,比如:
offset, row_count: 用于分页函数,offset=100&row_count=100 偏移值和行数,对应 mysql 语法的offset
,row_count
start, end: 对于时间序列的数据,start=2014-05-22 16:16:04&end=2014-06-22 16:16:04 - 请求一段时间范围 [start, end) 的数据
deliver_to_device: true/false,当设备使用 socket 连接,thirdparty-device-key 的请求可以使用 deliver_to_device=true 参数把请求传递到设备,主要是为了反向控制使用,api 使用见 获取设备单个数据点、创建设备单个数据点
3、响应
- 常规的响应:
{"status": 200, "result": "success if status == 20x or failed", "message": "here is the message"}
- 带有元信息的响应:
{"status": 200, "device": {...}}
- 响应状态(“status”: 200),尽可能对应 http status code 的相关含义,一般有以下几种状态:
200: 正常,可能附带数据
400: 请求参数或者格式不对
403: 没有相关的权限
404: 资源没有找到
500: 内部数据出现错误
4、API(这些在API文档里有详细说明:https://iot.espressif.cn/#/api-zh-cn/
PLAIN方式激活NC请求:
echo '{"path": "/v1/device/activate/", "method": "POST", "meta": {"Authorization": "token HERE_IS_THE_MASTER_DEVICE_KEY"}, "body": {"encrypt_method": "PLAIN", "bssid": ":bssid", "token": ":token"}}'|nc iot.espressif.cn 8000
{"Authorization": "token HERE_IS_THE_MASTER_DEVICE_KEY"}
这里需要我们输入密钥,是云下设备向乐鑫云平台表明身份用的。
"bssid"
这里需要我们填写云下设备的MARK地址
"token"
这个是一个随机值,加密使用。
云下设备可以向乐鑫云平台发送以下的JSON字符串,来和服务器保持心跳,也需要填写设备密钥。
【心跳】:当网络连接(TCP)建立后,如果[云下设备]长时间未和[物联网云平台(服务器)]进行通信,服务器将会断开此网络连接(TCP)。但是,很多情况下,云下设备比较长时间才会发一次数据。为了不让云平台断开与云下设备的网络连接(TCP),云下设备]每隔一定时间,就要向云平台发送消息。
echo '{"path": "/v1/ping/", "method": "GET", "meta": {"Authorization": "token HERE_IS_THE_DEVICE_KEY"}}'|nc iot.espressif.cn 8000
【注意】:云下设备在与乐鑫云平台建立网络连接后,只要按照[乐鑫云平台]规定好的数据格式,来收发网络数据,云下设备就能与乐鑫云平台进行交互,实现[设备接入物联网云平台。
例程
我们用网络调试助手做为TCPclient连接到乐鑫云平台。首先设置乐鑫云平台的域名,端口号是8000,点击连接。可以看到TCP连接已成功建立
接下来我们需要使用网络调试助手向乐鑫云平台发送云端设备激活的API
" nonce": 66," path": “/v1/device/activate/” “method”: “POST”, “body”: {" encrypt method":”PL AIN", “token”:“ ”,”bssid"." 66:66:66:66:66:66"," rom_ version":“V1.0”}, “meta”: {" Authorization":”token 4c4192c3095be4a06e30840f5feec945ac106bd4"}
程序会根据我们Flash的大小来决定系统扇区的起始位置。如果是图中所选的大小则是32M_512_512,设备密钥文件烧录到【0X7D】上。
首先是user_init函数,其中有user_esp_platform_init();
此函数,读取0X7D扇区的数据,其中有设备密钥等参数。
void ICACHE_FLASH_ATTR user_esp_platform_init(void)
{
// 获取版本信息
//--------------------------------------------------------------------------
os_sprintf(iot_version,"%s%d.%d.%dt%d(%s)",VERSION_TYPE,IOT_VERSION_MAJOR,\
IOT_VERSION_MINOR,IOT_VERSION_REVISION,device_type,UPGRADE_FALG);
os_printf("IOT VERSION = %s\n",iot_version);
//--------------------------------------------------------------------------------------------------------------------
system_param_load(priv_param_start_sec+1,0,&esp_param,sizeof(esp_param)); // 读取【0x7D(0x7C+1)扇区】的数据(KEY_BIN)
os_printf("esp_param.devkey = %s\n",esp_param.devkey); // 串口打印【devkey】
os_printf("esp_param.token = %s\n",esp_param.token); // 串口打印【token】
os_printf("esp_param.pad = %s\n",esp_param.pad); // 串口打印【pad】
os_printf("esp_param.activeflag = %d\n",esp_param.activeflag); // 串口打印【activeflag】
// while(1) system_soft_wdt_feed(); // 【技小新添加】
// ESP8266复位后,执行复位查询
//-------------------------------------------------------------------------
struct rst_info *rtc_info = system_get_rst_info(); // 获取当前的启动信息
os_printf("reset reason: %x\n", rtc_info->reason); // 打印复位原因
// 判断复位原因
//--------------------------------------------------------------------
if (rtc_info->reason == REASON_WDT_RST || // 看门狗复位
rtc_info->reason == REASON_EXCEPTION_RST || // 异常复位
rtc_info->reason == REASON_SOFT_WDT_RST) // 软件看门狗复位
{
if (rtc_info->reason == REASON_EXCEPTION_RST)
{
os_printf("Fatal exception (%d):\n", rtc_info->exccause);
}
os_printf("epc1=0x%08x, epc2=0x%08x, epc3=0x%08x, excvaddr=0x%08x, depc=0x%08x\n",
rtc_info->epc1, rtc_info->epc2, rtc_info->epc3, rtc_info->excvaddr, rtc_info->depc);
}
// 保存之前的IP地址
//-------------------------------------------------------------------------
/***add by tzx for saving ip_info to avoid dhcp_client start****/
struct dhcp_client_info dhcp_info;
struct ip_info sta_info;
system_rtc_mem_read(64,&dhcp_info,sizeof(struct dhcp_client_info)); // 读取RTC memory中的数据
// 判断之前是否保存为1
//-----------------------------------------------------------------
if(dhcp_info.flag == 0x01 )
{
if (true == wifi_station_dhcpc_status()) // STA_DHCP启动
{
wifi_station_dhcpc_stop(); // STA_DHCP停止
}
sta_info.ip = dhcp_info.ip_addr; // 重新设为之前的IP地址
sta_info.gw = dhcp_info.gw;
sta_info.netmask = dhcp_info.netmask;
if ( true != wifi_set_ip_info(STATION_IF,&sta_info)) // 设置STA的IP地址
{ os_printf("set default ip wrong\n"); }
}
os_memset(&dhcp_info,0,sizeof(struct dhcp_client_info)); // dhcp_info清0
system_rtc_mem_write(64,&dhcp_info,sizeof(struct rst_info));// RTC_mem清0
#if AP_CACHE
wifi_station_ap_number_set(AP_CACHE_NUMBER); // AP信息缓存(5个)
#endif
#if 0
{
char sofap_mac[6] = {0x16, 0x34, 0x56, 0x78, 0x90, 0xab};
char sta_mac[6] = {0x12, 0x34, 0x56, 0x78, 0x90, 0xab};
struct ip_info info;
wifi_set_macaddr(SOFTAP_IF, sofap_mac);
wifi_set_macaddr(STATION_IF, sta_mac);
IP4_ADDR(&info.ip, 192, 168, 3, 200);
IP4_ADDR(&info.gw, 192, 168, 3, 1);
IP4_ADDR(&info.netmask, 255, 255, 255, 0);
wifi_set_ip_info(STATION_IF, &info);
IP4_ADDR(&info.ip, 10, 10, 10, 1);
IP4_ADDR(&info.gw, 10, 10, 10, 1);
IP4_ADDR(&info.netmask, 255, 255, 255, 0);
wifi_set_ip_info(SOFTAP_IF, &info);
}
#endif
// esp_param.activeflag ==【0】:云端设备未激活【初始值==0xFF】
// esp_param.activeflag ==【1】:云端设备已激活
//------------------------------------------------------------------
if (esp_param.activeflag != 1) // 【云端设备】未激活
{
#ifdef SOFTAP_ENCRYPT
struct softap_config config;
char password[33];
char macaddr[6];
wifi_softap_get_config(&config);
wifi_get_macaddr(SOFTAP_IF, macaddr);
os_memset(config.password, 0, sizeof(config.password));
os_sprintf(password, MACSTR "_%s", MAC2STR(macaddr), PASSWORD);
os_memcpy(config.password, password, os_strlen(password));
config.authmode = AUTH_WPA_WPA2_PSK;
wifi_softap_set_config(&config);
#endif
wifi_set_opmode(STATIONAP_MODE); // 设为AP+STA模式【开启AP模式使为了使用APP来向ESP8266发送路由器WIFI的SSID、PASS】
}
#if PLUG_DEVICE
user_plug_init(); // 插座初始化
#elif LIGHT_DEVICE
user_light_init(); // 灯光初始化(PWM)
#elif SENSOR_DEVICE
user_sensor_init(esp_param.activeflag); // 传感器初始化
#endif
// 判断ESP8266是否为SoftAP模式
//-------------------------------------------------------------------------------------------
if (wifi_get_opmode() != SOFTAP_MODE) // 不是SoftAP模式
{
// 设置定时器(定时查询ESP8266的IP情况)
//----------------------------------------
os_timer_disarm(&client_timer);
os_timer_setfn(&client_timer, (os_timer_func_t *)user_esp_platform_check_ip,(void *)1); // 参数3=1:表示当前是刚复位状态
os_timer_arm(&client_timer, 100, 0); // 使能毫秒定时器(100Ms定时)
}
}
之后连接wifi,如果成功连接到wifi得话。
,进行TCP连接设置,并进行DNS域名解析,如果域名解析成功的话,那么就将ESP8266做为TCPclient连接到TCPServer也就是乐鑫云平台。当TCP连接成功之后,我们向乐鑫云平台发送数据包。SP8266会根据云端设备是否激活来选择向云端设备发送设备激活的字符串还是核实身份的JSON字符串。
void ICACHE_FLASH_ATTR user_esp_platform_check_ip(uint8 reset_flag)
{
struct ip_info ipconfig; // IP信息结构体
os_timer_disarm(&client_timer); // 暂时关闭Client定时器
wifi_get_ip_info(STATION_IF, &ipconfig); // 获取STA模式下的IP地址
// ESP8266获取到IP地址
//---------------------------------------------------------------------------
if (wifi_station_get_connect_status()==STATION_GOT_IP && ipconfig.ip.addr!=0)
{
#if (PLUG_DEVICE || SENSOR_DEVICE)
user_link_led_timer_init();
#endif
//--------------------------------------
#if LIGHT_DEVICE
user_mdns_conf(); // mDNS初始化
#endif
//--------------------------------------
// TCP连接设置
//------------------------------------------------------------------
user_conn.proto.tcp = &user_tcp; // 开辟内存
user_conn.type = ESPCONN_TCP; // 设为TCP连接
user_conn.state = ESPCONN_NONE;
device_status = DEVICE_CONNECTING; // 设备(8266)状态设为【正在连接】
if (reset_flag) // 判断是否是刚复位
{ device_recon_count = 0;} // 系统复位后,ESP8266_TCP重连计数=0(reset_flag==1表示为系统复位)
#if (PLUG_DEVICE || LIGHT_DEVICE)
os_timer_disarm(&beacon_timer); // 设置心跳定时器定时器
os_timer_setfn(&beacon_timer, (os_timer_func_t *)user_esp_platform_sent_beacon, &user_conn);
#endif
#ifdef USE_DNS
user_esp_platform_start_dns(&user_conn); // DNS设置
#else
const char esp_server_ip[4] = {114, 215, 177, 97};
os_memcpy(user_conn.proto.tcp->remote_ip, esp_server_ip, 4);
user_conn.proto.tcp->local_port = espconn_port();
#ifdef CLIENT_SSL_ENABLE
user_conn.proto.tcp->remote_port = 8443;
#else
user_conn.proto.tcp->remote_port = 8000;
#endif
espconn_regist_connectcb(&user_conn, user_esp_platform_connect_cb);
espconn_regist_reconcb(&user_conn, user_esp_platform_recon_cb);
user_esp_platform_connect(&user_conn);
#endif
}
// 未获取到IP地址
//------------------------------------------------------------------------------------------
else
{
/* if there are wrong while connecting to some AP, then reset mode */
if ((wifi_station_get_connect_status() == STATION_WRONG_PASSWORD || // [密码错误]、[未发现AP]、[连接失败]
wifi_station_get_connect_status() == STATION_NO_AP_FOUND ||
wifi_station_get_connect_status() == STATION_CONNECT_FAIL))
{
user_esp_platform_reset_mode(); // ESP8266设为AP+STA模式,并且5秒切换WIFI
}
else
{
os_timer_setfn(&client_timer, (os_timer_func_t *)user_esp_platform_check_ip, NULL); // 继续查询ESP8266的IP获取情况
os_timer_arm(&client_timer, 100, 0);
}
}
}
当ESP8266接收到乐鑫云平台发送的请求,当发送的是LED_ON时,ESP8266会将LED点亮,当请求内容是LED_OF时,ESP8266会将LED熄灭。
if( ((char *)os_strstr(pbuffer, "{\"deliver_to_device\": true, \"get\": {\"action\": \"LED_ON\"}")) != NULL )
{
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4);
GPIO_OUTPUT_SET(GPIO_ID_PIN(4),0); // LED亮
}
else if( ((char *)os_strstr(pbuffer, "{\"deliver_to_device\": true, \"get\": {\"action\": \"LED_OFF\"}")) != NULL )
{
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4);
GPIO_OUTPUT_SET(GPIO_ID_PIN(4),1); // LED灭
}
现象
首先我们编译程序,下载程序,我们还需要将我们的密钥烧录到ESP8266的0x7d000中。
未完待续。。。。。。
参考链接
https://iot.espressif.cn/#/api-zh-cn/