├── CHANGES.txt ├── LICENSE.txt ├── README.md ├── examples └── esp8266 │ └── aliyun.ino ├── keywords.txt ├── library.json ├── library.properties ├── src ├── AliyunIoTSDK.cpp └── AliyunIoTSDK.h └── 如何使用 arduino-aliyun-iot-sdk 2 行代码接入物联网平台.md /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 2 | 0.1 3 | * 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2015 Nicholas O'Leary 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino TopLevel Client for aliyun IoT Platform 2 | 3 | `AliyunIoTSDK` 可以帮助你快速连接阿里云 IoT 平台,通过和阿里云物联网开发平台配合,可快速实现各种硬件应用,包括了很上层的封装,无需自己解析数据体,绑定事件即可,在 esp8266 平台充分测试(NodeMCU 1.0) 4 | 5 | ## update 6 | 7 | - v0.2 增加属性发送 buffer,5秒一次或者10条buffer满,才会一起发送数据,节省请求次数 8 | - v0.1 上线 9 | 10 | ## 依赖项 11 | - Arduino需要安装 ArduinoJson,Crypto,PubSubClient库 12 | - Esp8266 需要在Arduino中安装 [ESP8266库](https://github.com/esp8266/Arduino) 13 | 14 | ## Usage 使用示例 15 | 16 | ```c++ 17 | // 引入 wifi 模块,并实例化,不同的芯片这里的依赖可能不同 18 | #include 19 | static WiFiClient espClient; 20 | 21 | // 引入阿里云 IoT SDK 22 | #include 23 | 24 | // 设置产品和设备的信息,从阿里云设备信息里查看 25 | #define PRODUCT_KEY "xxx" 26 | #define DEVICE_NAME "Device_D" 27 | #define DEVICE_SECRET "xxxxxxxxxxxxxx" 28 | #define REGION_ID "cn-shanghai" 29 | 30 | // 设置 wifi 信息 31 | #define WIFI_SSID "xxxxx" 32 | #define WIFI_PASSWD "xxxxx" 33 | 34 | void setup() 35 | { 36 | Serial.begin(115200); 37 | 38 | // 初始化 wifi 39 | wifiInit(WIFI_SSID, WIFI_PASSWD); 40 | 41 | // 初始化 iot,需传入 wifi 的 client,和设备产品信息 42 | AliyunIoTSDK::begin(espClient, PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET, REGION_ID); 43 | 44 | // 绑定一个设备属性回调,当远程修改此属性,会触发 powerCallback 45 | // PowerSwitch 是在设备产品中定义的物联网模型的 id 46 | AliyunIoTSDK::bindData("PowerSwitch", powerCallback); 47 | 48 | // 发送一个数据到云平台,LightLuminance 是在设备产品中定义的物联网模型的 id 49 | AliyunIoTSDK::send("LightLuminance", 100); 50 | } 51 | 52 | void loop() 53 | { 54 | AliyunIoTSDK::loop(); 55 | } 56 | 57 | // 初始化 wifi 连接 58 | void wifiInit(const char *ssid, const char *passphrase) 59 | { 60 | WiFi.mode(WIFI_STA); 61 | WiFi.begin(ssid, passphrase); 62 | while (WiFi.status() != WL_CONNECTED) 63 | { 64 | delay(1000); 65 | Serial.println("WiFi not Connect"); 66 | } 67 | Serial.println("Connected to AP"); 68 | } 69 | 70 | // 电源属性修改的回调函数 71 | void powerCallback(JsonVariant p) 72 | { 73 | int PowerSwitch = p["PowerSwitch"]; 74 | if (PowerSwitch == 1) 75 | { 76 | // 启动设备 77 | } 78 | } 79 | ``` 80 | 81 | ## API 可用方法 82 | 83 | ```c++ 84 | // 在主程序 loop 中调用,检查连接和定时发送信息 85 | static void loop(); 86 | 87 | /** 88 | * 初始化程序 89 | * @param ssid wifi名 90 | * @param passphrase wifi密码 91 | */ 92 | static void begin(Client &espClient, 93 | const char *_productKey, 94 | const char *_deviceName, 95 | const char *_deviceSecret, 96 | const char *_region); 97 | 98 | /** 99 | * 发送数据 100 | * @param param 字符串形式的json 数据,例如 {"${key}":"${value}"} 101 | */ 102 | static void send(const char *param); 103 | /** 104 | * 发送 float 格式数据 105 | * @param key 数据的 key 106 | * @param number 数据的值 107 | */ 108 | static void send(char *key, float number); 109 | /** 110 | * 发送 int 格式数据 111 | * @param key 数据的 key 112 | * @param number 数据的值 113 | */ 114 | static void send(char *key, int number); 115 | /** 116 | * 发送 double 格式数据 117 | * @param key 数据的 key 118 | * @param number 数据的值 119 | */ 120 | static void send(char *key, double number); 121 | /** 122 | * 发送 string 格式数据 123 | * @param key 数据的 key 124 | * @param text 数据的值 125 | */ 126 | static void send(char *key, char *text); 127 | 128 | /** 129 | * 发送事件到云平台(附带数据) 130 | * @param eventId 事件名,在阿里云物模型中定义好的 131 | * @param param 字符串形式的json 数据,例如 {"${key}":"${value}"} 132 | */ 133 | static void sendEvent(const char *eventId, const char *param); 134 | /** 135 | * 发送事件到云平台(空数据) 136 | * @param eventId 事件名,在阿里云物模型中定义好的 137 | */ 138 | static void sendEvent(const char *eventId); 139 | 140 | /** 141 | * 绑定回调,所有云服务下发的数据都会进回调 142 | */ 143 | // static void bind(MQTT_CALLBACK_SIGNATURE); 144 | 145 | /** 146 | * 绑定事件回调,云服务下发的特定事件会进入回调 147 | * @param eventId 事件名 148 | */ 149 | // static void bindEvent(const char * eventId, MQTT_CALLBACK_SIGNATURE); 150 | /** 151 | * 绑定属性回调,云服务下发的数据包含此 key 会进入回调,用于监听特定数据的下发 152 | * @param key 物模型的key 153 | */ 154 | static int bindData(char *key, poniter_fun fp); 155 | /** 156 | * 卸载某个 key 的所有回调(慎用) 157 | * @param key 物模型的key 158 | */ 159 | static int unbindData(char *key); 160 | ``` 161 | 162 | ## Examples 示例 163 | 164 | buiding... 165 | 166 | ## Limitations 使用限制和说明 167 | 168 | - 本库不包含 wifi 连接的代码,需先建立连接,然后将 client 传入 169 | - 依赖 PubSubClient ,在使用前,请务必修改 PubSubClient 的连接参数,否则无法使用 170 | - PubSubClient 中的 MQTT_MAX_PACKET_SIZE 修改为 1024 171 | - PubSubClient 中的 MQTT_KEEPALIVE 修改为 60 172 | - 掉线后会一直尝试重新连接,可能会触发阿里云的一些限流规则(已经做了规避),并且会导致挤掉其他同设备 ID 的设备 173 | - 默认 5000ms 检测一次连接状态,可以通过 CHECK_INTERVAL 修改此值 174 | 175 | 176 | ## Compatible Hardware 适用硬件 177 | 178 | 本 SDK 基于 PubSubClient 底层库开发,兼容列表与 PubSubClient 相同。 179 | 180 | The library uses the Arduino Ethernet Client api for interacting with the underlying network hardware. This means it Just Works with a growing number of boards and shields, including: 181 | 182 | - Arduino Ethernet 183 | - Arduino Ethernet Shield 184 | - Arduino YUN – use the included YunClient in place of EthernetClient, and be sure to do a Bridge.begin() first 185 | - Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield, enable the MQTT_MAX_TRANSFER_SIZE define in PubSubClient.h. 186 | - Sparkfun WiFly Shield – library 187 | - TI CC3000 WiFi - library 188 | - Intel Galileo/Edison 189 | - ESP8266 190 | - ESP32 191 | 192 | The library cannot currently be used with hardware based on the ENC28J60 chip – such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an alternative library available. 193 | 194 | ## License 195 | 196 | This code is released under the MIT License. 197 | -------------------------------------------------------------------------------- /examples/esp8266/aliyun.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static WiFiClient espClient; 4 | #include 5 | 6 | #include 7 | AliyunIoTSDK iot; 8 | 9 | #define PRODUCT_KEY "xxxxx" 10 | #define DEVICE_NAME "Device_D" 11 | #define DEVICE_SECRET "xxxxxxxxxxx" 12 | #define REGION_ID "cn-shanghai" 13 | 14 | #define WIFI_SSID "xxxxxx" 15 | #define WIFI_PASSWD "xxxxxxxx" 16 | 17 | 18 | void wifiInit(const char *ssid, const char *passphrase) 19 | { 20 | WiFi.mode(WIFI_STA); 21 | WiFi.begin(ssid, passphrase); 22 | WiFi.setAutoConnect (true); 23 | WiFi.setAutoReconnect (true); 24 | while (WiFi.status() != WL_CONNECTED) 25 | { 26 | delay(1000); 27 | Serial.println("WiFi not Connect"); 28 | } 29 | Serial.println("Connected to AP"); 30 | } 31 | 32 | void setup() 33 | { 34 | Serial.begin(115200); 35 | 36 | wifiInit(WIFI_SSID, WIFI_PASSWD); 37 | 38 | AliyunIoTSDK::begin(espClient, PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET, REGION_ID); 39 | 40 | // 绑定属性回调 41 | AliyunIoTSDK::bindData("PowerSwitch", powerCallback); 42 | 43 | // 操作用户自定义Topic 44 | AliyunIoTSDK::subscribeUser("/get", callback); 45 | 46 | /* 47 | 下面为操作其他各类topic方法,注意替换自己的参数 48 | AliyunIoTSDK::subscribe("/sys/${ProductKey}/${deviceName}/thing/service/${tsl.service.identifier}", callback); 49 | AliyunIoTSDK::publish("/sys/${ProductKey}/${deviceName}/thing/service/${tsl.service.identifier}_reply", callback); 50 | */ 51 | } 52 | 53 | unsigned long lastMsMain = 0; 54 | void loop() 55 | { 56 | AliyunIoTSDK::loop(); 57 | if (millis() - lastMsMain >= 5000) 58 | { 59 | lastMsMain = millis(); 60 | // 发送事件到阿里云平台 61 | AliyunIoTSDK::sendEvent("xxx"); 62 | // 发送模型属性到阿里云平台 63 | AliyunIoTSDK::send("CurrentTemperature", 30); 64 | } 65 | } 66 | 67 | void callback(JsonVariant p) 68 | { 69 | Serial.println("custom topic callback"); 70 | serializeJsonPretty(p, Serial); 71 | Serial.println(); 72 | } 73 | 74 | void powerCallback(JsonVariant p) 75 | { 76 | int PowerSwitch = p["PowerSwitch"]; 77 | if (PowerSwitch == 1) 78 | { 79 | // 80 | } 81 | else 82 | { 83 | // 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For AliyunIoTSDK 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | AliyunIoTSDK KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | begin KEYWORD2 16 | send KEYWORD2 17 | sendEvent KEYWORD2 18 | bindData KEYWORD2 19 | unbindData KEYWORD2 20 | 21 | ####################################### 22 | # Constants (LITERAL1) 23 | ####################################### 24 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AliyunIoTSDK", 3 | "keywords": "ethernet, aliyun, mqtt, m2m, iot", 4 | "description": "A toplevel client library for aliyun MQTT messaging. 此库可以帮助你快速连接阿里云 IoT 平台,通过和阿里云物联网开发平台配合,可快速实现各种硬件应用,包括了很上层的封装,无需自己解析数据体,绑定事件即可。", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/0xYootou/arduino-aliyun-iot-sdk" 8 | }, 9 | "version": "0.4", 10 | "examples": "examples/*/*.ino", 11 | "frameworks": "arduino", 12 | "platforms": ["espressif8266", "espressif32"], 13 | "dependencies": [ 14 | { 15 | "name": "PubSubClient", 16 | "platforms": ["espressif8266", "espressif32"] 17 | }, 18 | { 19 | "name": "ArduinoJson" 20 | }, 21 | { 22 | "name": "Crypto" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=AliyunIoTSDK 2 | version=0.4 3 | author=yutou 4 | maintainer=yutou 5 | sentence=A toplevel client library for aliyun MQTT messaging. 6 | paragraph=此库可以帮助你快速连接阿里云 IoT 平台,通过和阿里云物联网开发平台配合,可快速实现各种硬件应用,包括了很上层的封装,无需自己解析数据体,绑定事件即可。 7 | category=Communication 8 | url=https://github.com/0xYootou/arduino-aliyun-iot-sdk 9 | architectures=* 10 | depends=ArduinoJson, Crypto, PubSubClient 11 | -------------------------------------------------------------------------------- /src/AliyunIoTSDK.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "AliyunIoTSDK.h" 3 | #include 4 | #include 5 | 6 | #define CHECK_INTERVAL 10000 7 | #define MESSAGE_BUFFER_SIZE 10 8 | #define RETRY_CRASH_COUNT 5 9 | 10 | static const char *deviceName = NULL; 11 | static const char *productKey = NULL; 12 | static const char *deviceSecret = NULL; 13 | static const char *region = NULL; 14 | 15 | struct DeviceProperty 16 | { 17 | String key; 18 | String value; 19 | }; 20 | 21 | DeviceProperty PropertyMessageBuffer[MESSAGE_BUFFER_SIZE]; 22 | 23 | #define MQTT_PORT 1883 24 | 25 | #define SHA256HMAC_SIZE 32 26 | #define DATA_CALLBACK_SIZE 20 27 | 28 | #define ALINK_BODY_FORMAT "{\"id\":\"123\",\"version\":\"1.0\",\"method\":\"thing.event.property.post\",\"params\":%s}" 29 | #define ALINK_EVENT_BODY_FORMAT "{\"id\": \"123\",\"version\": \"1.0\",\"params\": %s,\"method\": \"thing.event.%s.post\"}" 30 | 31 | static unsigned long lastMs = 0; 32 | static int retry_count = 0; 33 | 34 | static PubSubClient *client = NULL; 35 | 36 | char AliyunIoTSDK::clientId[256] = ""; 37 | char AliyunIoTSDK::mqttUsername[100] = ""; 38 | char AliyunIoTSDK::mqttPwd[256] = ""; 39 | char AliyunIoTSDK::domain[150] = ""; 40 | 41 | char AliyunIoTSDK::ALINK_TOPIC_PROP_POST[150] = ""; 42 | char AliyunIoTSDK::ALINK_TOPIC_PROP_SET[150] = ""; 43 | char AliyunIoTSDK::ALINK_TOPIC_EVENT[150] = ""; 44 | char AliyunIoTSDK::ALINK_TOPIC_USER[150] = ""; 45 | 46 | static String hmac256(const String &signcontent, const String &ds) 47 | { 48 | byte hashCode[SHA256HMAC_SIZE]; 49 | SHA256 sha256; 50 | 51 | const char *key = ds.c_str(); 52 | size_t keySize = ds.length(); 53 | 54 | sha256.resetHMAC(key, keySize); 55 | sha256.update((const byte *)signcontent.c_str(), signcontent.length()); 56 | sha256.finalizeHMAC(key, keySize, hashCode, sizeof(hashCode)); 57 | 58 | String sign = ""; 59 | for (byte i = 0; i < SHA256HMAC_SIZE; ++i) 60 | { 61 | sign += "0123456789ABCDEF"[hashCode[i] >> 4]; 62 | sign += "0123456789ABCDEF"[hashCode[i] & 0xf]; 63 | } 64 | 65 | return sign; 66 | } 67 | 68 | static void parmPass(JsonVariant parm) 69 | { 70 | // const char *method = parm["method"]; 71 | for (int i = 0; i < DATA_CALLBACK_SIZE; i++) 72 | { 73 | if (poniter_array[i].key) 74 | { 75 | bool hasKey = parm["params"].containsKey(poniter_array[i].key); 76 | if (hasKey) 77 | { 78 | poniter_array[i].fp(parm["params"]); 79 | } 80 | } 81 | } 82 | } 83 | // 所有云服务的回调都会首先进入这里,例如属性下发 84 | static void callback(char *topic, byte *payload, unsigned int length) 85 | { 86 | Serial.print("Message arrived ["); 87 | Serial.print(topic); 88 | Serial.print("] "); 89 | payload[length] = '\0'; 90 | Serial.println((char *)payload); 91 | 92 | StaticJsonDocument<200> doc; 93 | DeserializationError error = deserializeJson(doc, payload); //反序列化JSON数据 94 | 95 | if (strstr(topic, AliyunIoTSDK::ALINK_TOPIC_PROP_SET)) 96 | { 97 | if (!error) //检查反序列化是否成功 98 | { 99 | parmPass(doc.as()); //将参数传递后打印输出 100 | } 101 | }else if(strstr(topic, AliyunIoTSDK::ALINK_TOPIC_USER)) 102 | { 103 | // 自定义订阅回调 104 | for (int i = 0; i < DATA_CALLBACK_SIZE; i++) 105 | { 106 | if (poniter_array[i].key) 107 | { 108 | if(strcmp(topic, poniter_array[i].key) == 0) 109 | poniter_array[i].fp(doc.as()); 110 | } 111 | } 112 | } 113 | else 114 | { 115 | for (int i = 0; i < DATA_CALLBACK_SIZE; i++) 116 | { 117 | if (poniter_array[i].key && strcmp(topic, poniter_array[i].key) == 0) 118 | { 119 | poniter_array[i].fp(doc.as()); 120 | } 121 | } 122 | } 123 | } 124 | 125 | static bool mqttConnecting = false; 126 | void(* resetFunc) (void) = 0; //制造重启命令 127 | void AliyunIoTSDK::mqttCheckConnect() 128 | { 129 | if (client != NULL && !mqttConnecting) 130 | { 131 | if (!client->connected()) 132 | { 133 | client->disconnect(); 134 | Serial.println("Connecting to MQTT Server ..."); 135 | mqttConnecting = true; 136 | if (client->connect(clientId, mqttUsername, mqttPwd)) 137 | { 138 | Serial.println("MQTT Connected!"); 139 | } 140 | else 141 | { 142 | Serial.print("MQTT Connect err:"); 143 | Serial.println(client->state()); 144 | retry_count++; 145 | if(retry_count > RETRY_CRASH_COUNT){ 146 | resetFunc(); 147 | } 148 | } 149 | mqttConnecting = false; 150 | } 151 | else 152 | { 153 | Serial.println("state is connected"); 154 | retry_count = 0; 155 | } 156 | } 157 | } 158 | 159 | void AliyunIoTSDK::begin(Client &espClient, 160 | const char *_productKey, 161 | const char *_deviceName, 162 | const char *_deviceSecret, 163 | const char *_region) 164 | { 165 | 166 | client = new PubSubClient(espClient); 167 | client->setBufferSize(1024); 168 | client->setKeepAlive(60); 169 | productKey = _productKey; 170 | deviceName = _deviceName; 171 | deviceSecret = _deviceSecret; 172 | region = _region; 173 | long times = millis(); 174 | String timestamp = String(times); 175 | 176 | sprintf(clientId, "%s|securemode=3,signmethod=hmacsha256,timestamp=%s|", deviceName, timestamp.c_str()); 177 | 178 | String signcontent = "clientId"; 179 | signcontent += deviceName; 180 | signcontent += "deviceName"; 181 | signcontent += deviceName; 182 | signcontent += "productKey"; 183 | signcontent += productKey; 184 | signcontent += "timestamp"; 185 | signcontent += timestamp; 186 | 187 | String pwd = hmac256(signcontent, deviceSecret); 188 | 189 | strcpy(mqttPwd, pwd.c_str()); 190 | 191 | sprintf(mqttUsername, "%s&%s", deviceName, productKey); 192 | sprintf(ALINK_TOPIC_PROP_POST, "/sys/%s/%s/thing/event/property/post", productKey, deviceName); 193 | sprintf(ALINK_TOPIC_PROP_SET, "/sys/%s/%s/thing/service/property/set", productKey, deviceName); 194 | sprintf(ALINK_TOPIC_EVENT, "/sys/%s/%s/thing/event", productKey, deviceName); 195 | sprintf(ALINK_TOPIC_USER, "/%s/%s/user", productKey, deviceName); 196 | 197 | sprintf(domain, "%s.iot-as-mqtt.%s.aliyuncs.com", productKey, region); 198 | client->setServer(domain, MQTT_PORT); /* 连接WiFi之后,连接MQTT服务器 */ 199 | client->setCallback(callback); 200 | 201 | mqttCheckConnect(); 202 | } 203 | 204 | void AliyunIoTSDK::loop() 205 | { 206 | client->loop(); 207 | if (millis() - lastMs >= CHECK_INTERVAL) 208 | { 209 | lastMs = millis(); 210 | mqttCheckConnect(); 211 | messageBufferCheck(); 212 | } 213 | } 214 | 215 | void AliyunIoTSDK::sendEvent(const char *eventId, const char *param) 216 | { 217 | char topicKey[156]; 218 | sprintf(topicKey, "%s/%s/post", ALINK_TOPIC_EVENT, eventId); 219 | char jsonBuf[1024]; 220 | sprintf(jsonBuf, ALINK_EVENT_BODY_FORMAT, param, eventId); 221 | Serial.println(jsonBuf); 222 | boolean d = client->publish(topicKey, jsonBuf); 223 | Serial.print("publish:0 成功:"); 224 | Serial.println(d); 225 | } 226 | void AliyunIoTSDK::sendEvent(const char *eventId) 227 | { 228 | sendEvent(eventId, "{}"); 229 | } 230 | unsigned long lastSendMS = 0; 231 | 232 | // 检查是否有数据需要发送 233 | void AliyunIoTSDK::messageBufferCheck() 234 | { 235 | int bufferSize = 0; 236 | for (int i = 0; i < MESSAGE_BUFFER_SIZE; i++) 237 | { 238 | if (PropertyMessageBuffer[i].key.length() > 0) 239 | { 240 | bufferSize++; 241 | } 242 | } 243 | // Serial.println("bufferSize:"); 244 | // Serial.println(bufferSize); 245 | if (bufferSize > 0) 246 | { 247 | if (bufferSize >= MESSAGE_BUFFER_SIZE) 248 | { 249 | sendBuffer(); 250 | } 251 | else 252 | { 253 | unsigned long nowMS = millis(); 254 | // 3s 发送一次数据 255 | if (nowMS - lastSendMS > 5000) 256 | { 257 | sendBuffer(); 258 | lastSendMS = nowMS; 259 | } 260 | } 261 | } 262 | } 263 | 264 | // 发送 buffer 数据 265 | void AliyunIoTSDK::sendBuffer() 266 | { 267 | int i; 268 | String buffer; 269 | for (i = 0; i < MESSAGE_BUFFER_SIZE; i++) 270 | { 271 | if (PropertyMessageBuffer[i].key.length() > 0) 272 | { 273 | buffer += "\"" + PropertyMessageBuffer[i].key + "\":" + PropertyMessageBuffer[i].value + ","; 274 | PropertyMessageBuffer[i].key = ""; 275 | PropertyMessageBuffer[i].value = ""; 276 | } 277 | } 278 | 279 | buffer = "{" + buffer.substring(0, buffer.length() - 1) + "}"; 280 | send(buffer.c_str()); 281 | } 282 | 283 | void addMessageToBuffer(char *key, String value) 284 | { 285 | int i; 286 | for (i = 0; i < MESSAGE_BUFFER_SIZE; i++) 287 | { 288 | if (PropertyMessageBuffer[i].key.length() == 0) 289 | { 290 | PropertyMessageBuffer[i].key = key; 291 | PropertyMessageBuffer[i].value = value; 292 | break; 293 | } 294 | } 295 | } 296 | void AliyunIoTSDK::send(const char *param) 297 | { 298 | 299 | char jsonBuf[1024]; 300 | sprintf(jsonBuf, ALINK_BODY_FORMAT, param); 301 | Serial.println(jsonBuf); 302 | boolean d = client->publish(ALINK_TOPIC_PROP_POST, jsonBuf); 303 | Serial.print("publish:0 成功:"); 304 | Serial.println(d); 305 | } 306 | void AliyunIoTSDK::send(char *key, float number) 307 | { 308 | addMessageToBuffer(key, String(number)); 309 | messageBufferCheck(); 310 | } 311 | void AliyunIoTSDK::send(char *key, int number) 312 | { 313 | addMessageToBuffer(key, String(number)); 314 | messageBufferCheck(); 315 | } 316 | void AliyunIoTSDK::send(char *key, double number) 317 | { 318 | addMessageToBuffer(key, String(number)); 319 | messageBufferCheck(); 320 | } 321 | 322 | void AliyunIoTSDK::send(char *key, char *text) 323 | { 324 | addMessageToBuffer(key, "\"" + String(text) + "\""); 325 | messageBufferCheck(); 326 | } 327 | 328 | int AliyunIoTSDK::bindData(char *key, poniter_fun fp) 329 | { 330 | int i; 331 | for (i = 0; i < DATA_CALLBACK_SIZE; i++) 332 | { 333 | if (!poniter_array[i].fp) 334 | { 335 | poniter_array[i].key = key; 336 | poniter_array[i].fp = fp; 337 | return 0; 338 | } 339 | } 340 | return -1; 341 | } 342 | 343 | int AliyunIoTSDK::unbindData(char *key) 344 | { 345 | int i; 346 | for (i = 0; i < DATA_CALLBACK_SIZE; i++) 347 | { 348 | if (!strcmp(poniter_array[i].key, key)) 349 | { 350 | poniter_array[i].key = NULL; 351 | poniter_array[i].fp = NULL; 352 | return 0; 353 | } 354 | } 355 | return -1; 356 | } 357 | 358 | 359 | boolean AliyunIoTSDK::publish(const char *topic, const char *payload, bool retained){ 360 | return client->publish(topic, payload, retained); 361 | } 362 | 363 | boolean AliyunIoTSDK::publish(const char *topic, const char *payload){ 364 | return client->publish(topic, payload); 365 | } 366 | 367 | boolean AliyunIoTSDK::publishUser(const char *topicSuffix, const char *payload){ 368 | char topic[150]; 369 | strcpy(topic, ALINK_TOPIC_USER); 370 | return AliyunIoTSDK::publish(strcat(topic, topicSuffix), payload); 371 | } 372 | 373 | boolean AliyunIoTSDK::subscribeUser(const char *topicSuffix, poniter_fun fp){ 374 | char *topic = new char[150]; 375 | strcpy(topic, ALINK_TOPIC_USER); 376 | return AliyunIoTSDK::subscribe(strcat(topic, topicSuffix), fp); 377 | } 378 | 379 | boolean AliyunIoTSDK::unsubscribeUser(char *topicSuffix){ 380 | char *topic = new char[150]; 381 | strcpy(topic, ALINK_TOPIC_USER); 382 | return AliyunIoTSDK::unsubscribe(strcat(topic, topicSuffix)); 383 | } 384 | 385 | boolean AliyunIoTSDK::subscribe(char* topic, uint8_t qos, poniter_fun fp){ 386 | boolean ret = false; 387 | if(client->subscribe(topic, qos)){ 388 | ret = true; 389 | bindData(topic, fp); 390 | Serial.print("subcribe: "); 391 | Serial.println(topic); 392 | } 393 | return ret; 394 | } 395 | 396 | boolean AliyunIoTSDK::subscribe(char* topic, poniter_fun fp){ 397 | return subscribe(topic, 0, fp); 398 | } 399 | 400 | boolean AliyunIoTSDK::unsubscribe(char* topic){ 401 | boolean ret = false; 402 | if(client->unsubscribe(topic)){ 403 | ret = true; 404 | unbindData(topic); 405 | Serial.print("unsubcribe: "); 406 | Serial.println(topic); 407 | } 408 | return ret; 409 | } 410 | -------------------------------------------------------------------------------- /src/AliyunIoTSDK.h: -------------------------------------------------------------------------------- 1 | #ifndef ALIYUN_IOT_SDK_H 2 | #define ALIYUN_IOT_SDK_H 3 | #include 4 | #include 5 | #include "Client.h" 6 | 7 | typedef void (*poniter_fun)(JsonVariant ele); //定义一个函数指针 8 | 9 | typedef struct poniter_desc 10 | { 11 | char *key; 12 | poniter_fun fp; 13 | } poniter_desc, *p_poniter_desc; 14 | 15 | // 最多绑定20个回调 16 | static poniter_desc poniter_array[20]; 17 | static p_poniter_desc p_poniter_array; 18 | 19 | class AliyunIoTSDK 20 | { 21 | private: 22 | // mqtt 链接信息,动态生成的 23 | static char mqttPwd[256]; 24 | static char clientId[256]; 25 | static char mqttUsername[100]; 26 | static char domain[150]; 27 | 28 | // 定时检查 mqtt 链接 29 | static void mqttCheckConnect(); 30 | 31 | static void messageBufferCheck(); 32 | static void sendBuffer(); 33 | public: 34 | 35 | // 标记一些 topic 模板 36 | static char ALINK_TOPIC_PROP_POST[150]; 37 | static char ALINK_TOPIC_PROP_SET[150]; 38 | static char ALINK_TOPIC_EVENT[150]; 39 | static char ALINK_TOPIC_USER[150]; 40 | 41 | // 在主程序 loop 中调用,检查连接和定时发送信息 42 | static void loop(); 43 | 44 | /** 45 | * 初始化程序 46 | * @param ssid wifi名 47 | * @param passphrase wifi密码 48 | */ 49 | static void begin(Client &espClient, 50 | const char *_productKey, 51 | const char *_deviceName, 52 | const char *_deviceSecret, 53 | const char *_region); 54 | 55 | /** 56 | * 发送数据 57 | * @param param 字符串形式的json 数据,例如 {"${key}":"${value}"} 58 | */ 59 | static void send(const char *param); 60 | /** 61 | * 发送 float 格式数据 62 | * @param key 数据的 key 63 | * @param number 数据的值 64 | */ 65 | static void send(char *key, float number); 66 | /** 67 | * 发送 int 格式数据 68 | * @param key 数据的 key 69 | * @param number 数据的值 70 | */ 71 | static void send(char *key, int number); 72 | /** 73 | * 发送 double 格式数据 74 | * @param key 数据的 key 75 | * @param number 数据的值 76 | */ 77 | static void send(char *key, double number); 78 | /** 79 | * 发送 string 格式数据 80 | * @param key 数据的 key 81 | * @param text 数据的值 82 | */ 83 | static void send(char *key, char *text); 84 | 85 | /** 86 | * 发送事件到云平台(附带数据) 87 | * @param eventId 事件名,在阿里云物模型中定义好的 88 | * @param param 字符串形式的json 数据,例如 {"${key}":"${value}"} 89 | */ 90 | static void sendEvent(const char *eventId, const char *param); 91 | /** 92 | * 发送事件到云平台(空数据) 93 | * @param eventId 事件名,在阿里云物模型中定义好的 94 | */ 95 | static void sendEvent(const char *eventId); 96 | 97 | /** 98 | * 绑定回调,所有云服务下发的数据都会进回调 99 | */ 100 | // static void bind(MQTT_CALLBACK_SIGNATURE); 101 | 102 | /** 103 | * 绑定事件回调,云服务下发的特定事件会进入回调 104 | * @param eventId 事件名 105 | */ 106 | // static void bindEvent(const char * eventId, MQTT_CALLBACK_SIGNATURE); 107 | /** 108 | * 绑定属性回调,云服务下发的数据包含此 key 会进入回调,用于监听特定数据的下发 109 | * @param key 物模型的key 110 | */ 111 | static int bindData(char *key, poniter_fun fp); 112 | /** 113 | * 卸载某个 key 的所有回调(慎用) 114 | * @param key 物模型的key 115 | */ 116 | static int unbindData(char *key); 117 | 118 | /** 119 | * 发布topic 120 | * @param topic 要发布的topic 121 | * @param payload 要发布的消息体 122 | * @param retained 是否需要开启保留 123 | */ 124 | static boolean publish(const char *topic, const char *payload, bool retained); 125 | 126 | static boolean publish(const char *topic, const char *payload); 127 | 128 | /** 129 | * 发布阿里云用户自定义Topic类 130 | * @param topicSuffix topic后缀,即user后面的部分 131 | * @param payload 发布的消息 132 | */ 133 | static boolean publishUser(const char *topicSuffix, const char *payload); 134 | 135 | /** 136 | * 订阅阿里云用户自定义Topic类 137 | * @param topicSuffix topic后缀,即user后面的部分 138 | * @param fp 回调函数 139 | */ 140 | static boolean subscribeUser(const char *topicSuffix, poniter_fun fp); 141 | 142 | /** 143 | * 取消订阅阿里云用户自定义Topic类 144 | * @param topicSuffix topic后缀,即user后面的部分 145 | */ 146 | static boolean unsubscribeUser(char *topicSuffix); 147 | 148 | /** 149 | * 订阅topic 150 | * @param topic 订阅的topic 151 | * @param qos 152 | * @param fp 回调函数 153 | */ 154 | static boolean subscribe(char* topic, uint8_t qos, poniter_fun fp); 155 | 156 | /** 157 | * 订阅topic 158 | * @param topic 订阅的topic 159 | * @param fp 回调函数 160 | */ 161 | static boolean subscribe(char* topic, poniter_fun fp); 162 | 163 | /** 164 | * 取消订阅指定topic 165 | * @param topic 订阅的topic 166 | */ 167 | static boolean unsubscribe(char* topic); 168 | 169 | 170 | }; 171 | #endif 172 | -------------------------------------------------------------------------------- /如何使用 arduino-aliyun-iot-sdk 2 行代码接入物联网平台.md: -------------------------------------------------------------------------------- 1 | > 文中提到的 AliyunIoTSDK 这个 arduino 库,可以在 arduino 库商店里搜索到(搜索 AliyunIoTSDK),但是版本可能不一定是最新的,也可以手动把 github 上的项目 clone 下来,放到 arduino 的 library 库下,保证功能是最新的。 2 | 3 | > github 地址:[https://github.com/xinyu198736/arduino-aliyun-iot-sdk](https://github.com/xinyu198736/arduino-aliyun-iot-sdk) 4 | 5 | > 有问题,欢迎提 issues 或 PR 共建,我对 c++ 不是特别熟悉,写出来的代码可能不一定是最合理的。 6 | 7 | ## 阿里云物联网平台简介 8 | 9 | 阿里云物联网平台提供接入物联网场景的一整套基础设施,强大的在线配置能力,基本可以实现无代码开发接入各类应用,实现大规模的物联网应用场景。 10 | 11 | 核心能力模块包括: 12 | 13 | - 产品&设备创建和管理 14 | - 服务可视化开发 15 | - 控制界面可视化开发 16 | - 数据分析和监控运维等辅助设施 17 | 18 | 基于以上功能模块,可以快速在阿里云定义设备模型和事件等信息,然后快速创建和接入设备,再利用可视化的开发平台,可以零代码实现非常强大的功能。 19 | 20 | 不过,今天我们要介绍一个比较特殊的接入场景,使用嵌入式系统快速接入阿里云物联网平台,这里以 esp8266 为例。 21 | 22 | > esp8266 是一款集成网络模块的嵌入式芯片,自带 32 位处理器,各种外设接口,wifi 模块等,可以使用 arduino 平台进行编程,兼容大量 arduino 库。 23 | 24 | 25 | 本文就是以 arduino 编程方式,使用的 esp8266 开发板为 NodeMCU 1.0 ,淘宝成本仅为 8~10 元。 26 | 27 | 28 | ## 场景 29 | 30 | 本次我们想利用 ESP8266 芯片实现一个简单的可远程控制的空调控制器,具备以下功能: 31 | 32 | - 批量部署,可通过云平台集中管理和远程控制空调开关、温度、模式等 33 | - 芯片可感知环境温度、亮度,并上报物联网平台 34 | - 芯片可远程触发事件,上报物联网平台 35 | - 成本尽量低,不超过 30 元 36 | 37 | 首先,我们进行硬件选型,经过功能评估,选用以下硬件: 38 | 39 | - nodemcu 核心板(基于 esp8266) 10.89元/个 40 | - DS18B20模块 温度传感 3.18元/个 41 | - GY-30  光照传感 4.38元/个 42 | - 红外发射板 3.5元/个 43 | - 杜邦线母对母 1.45元/串 44 | - 电源插头  1.4元/个 45 | - 数据线 1.8元/个 46 | - 按钮开关 0.75元/个 47 | 48 | 49 | ## 开始接入 50 | 51 | 具体的连线,我们这里不展开,我们主要探讨如何使用 arduino 快速接入物联网平台。 52 | 53 | 第一步,在物联网创建产品,建立物模型,并创建设备,获取设备配置信息。 54 | 55 | ![image.png](https://blog.souche.com/content/images/2019/09/1568620395927-b7090d59-5958-4050-a0e7-7205faafb455.png) 56 | 创建产品,并定义物模型 57 | 58 | ![image.png](https://blog.souche.com/content/images/2019/09/1568620350930-270bbfa3-22b8-4711-b6b8-ff6f38a8d49a.png) 59 | 创建设备,与产品绑定,获取设备鉴权信息
60 |
第二步,用 arduino 连接 nodemcu 开发板,开始硬件端的开发。
61 | ![IMG_2546.JPG](https://blog.souche.com/content/images/2019/09/1568642438839-a595c156-3afb-4ef8-8213-d67433be474a.jpeg) 62 | 具体接线可以自己研究,这里只是个展示,哈哈
63 | 64 | 65 | ## 硬件端开发 66 | 这里不讨论如何使用 arduino 进行 esp8266 芯片开发,直接进入代码环节,相关基础知识可以百度一下。 67 | 68 | 首先,亮相一个我自己封装的 aliyun iot 上层 SDK( [arduino-aliyun-iot-sdk](https://github.com/xinyu198736/arduino-aliyun-iot-sdk) ),底层连接基于 PubSubClient 库,并且对模型操作做了一些上层封装,可以方便的发送数据和订阅远程指令。 69 | ```cpp 70 | // 引入 wifi 模块,并实例化,不同的芯片这里的依赖可能不同 71 | #include 72 | static WiFiClient espClient; 73 | 74 | // 引入阿里云 IoT SDK 75 | #include 76 | 77 | // 设置产品和设备的信息,从阿里云设备信息里查看 78 | #define PRODUCT_KEY "xxx" 79 | #define DEVICE_NAME "Device_D" 80 | #define DEVICE_SECRET "xxxxxxxxxxxxxx" 81 | #define REGION_ID "cn-shanghai" 82 | 83 | // 设置 wifi 信息 84 | #define WIFI_SSID "xxxxx" 85 | #define WIFI_PASSWD "xxxxx" 86 | 87 | void setup() 88 | { 89 | Serial.begin(115200); 90 | 91 | // 初始化 wifi 92 | wifiInit(WIFI_SSID, WIFI_PASSWD); 93 | 94 | // 初始化 iot,需传入 wifi 的 client,和设备产品信息 95 | AliyunIoTSDK::begin(espClient, PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET, REGION_ID); 96 | 97 | // 绑定一个设备属性回调,当远程修改此属性,会触发 powerCallback 98 | // PowerSwitch 是在设备产品中定义的物联网模型的 id 99 | AliyunIoTSDK::bindData("PowerSwitch", powerCallback); 100 | 101 | // 发送一个数据到云平台,LightLuminance 是在设备产品中定义的物联网模型的 id 102 | AliyunIoTSDK::send("LightLuminance", 100); 103 | } 104 | 105 | void loop() 106 | { 107 | AliyunIoTSDK::loop(); 108 | } 109 | 110 | // 初始化 wifi 连接 111 | void wifiInit(const char *ssid, const char *passphrase) 112 | { 113 | WiFi.mode(WIFI_STA); 114 | WiFi.begin(ssid, passphrase); 115 | while (WiFi.status() != WL_CONNECTED) 116 | { 117 | delay(1000); 118 | Serial.println("WiFi not Connect"); 119 | } 120 | Serial.println("Connected to AP"); 121 | } 122 | 123 | // 电源属性修改的回调函数 124 | void powerCallback(JsonVariant p) 125 | { 126 | int PowerSwitch = p["PowerSwitch"]; 127 | if (PowerSwitch == 1) 128 | { 129 | // 启动设备 130 | } 131 | } 132 | ``` 133 | 134 | 核心代码只有三句: 135 | 136 | ```cpp 137 | // 初始化 iot,需传入 wifi 的 client,和设备产品信息 138 | AliyunIoTSDK::begin(espClient, PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET, REGION_ID); 139 | 140 | // 绑定一个设备属性回调,当远程修改此属性,会触发 powerCallback 141 | // PowerSwitch 是在设备产品中定义的物联网模型的 id 142 | AliyunIoTSDK::bindData("PowerSwitch", powerCallback); 143 | 144 | // 发送一个数据到云平台,LightLuminance 是在设备产品中定义的物联网模型的 id 145 | AliyunIoTSDK::send("LightLuminance", 100); 146 | ``` 147 | 148 | 注意这里的数据绑定和数据发送,是非常有用和方便的,不需要自己去写做匹配的代码,也不需要自己组装数据,并且会自动合并数据,节约上报的频率。 149 | 150 | 将设备配置信息配置进代码,烧录到板子,运行即可,是不是炒鸡方便呢。 151 | 152 | 注意事项: 153 | 154 | - 阿里云对客户端的链接参数有一些限制,需要修改  PubSubClient 的连接参数,否则无法使用 155 | - PubSubClient 中的 MQTT_MAX_PACKET_SIZE 修改为 1024 156 | - PubSubClient 中的 MQTT_KEEPALIVE 修改为 60 157 | - 默认 5000ms 检测一次连接状态,可以通过 AliyunIoTSDK.cpp 中的 CHECK_INTERVAL 修改此值 158 | - 掉线后会一直尝试重新连接,可能会触发阿里云的一些限流规则(已经做了规避),并且会导致挤掉其他同设备 ID 的设备 159 | 160 | ### 资源 161 | 目前 AliyunIoTSDK 这个 arduino 库,可以在 arduino 库商店里搜索到(搜索 AliyunIoTSDK),但是版本可能不一定是最新的,可以手动把 github 上的项目 clone 下来,放到 arduino 的 library 库下,保证功能是最新的。 162 | 163 | github 地址:[https://github.com/xinyu198736/arduino-aliyun-iot-sdk](https://github.com/xinyu198736/arduino-aliyun-iot-sdk) 164 | 165 | 有问题,欢迎提 issues 或 PR 共建,我对 c++ 不是特别熟悉,写出来的代码可能不一定是最合理的。 166 | 167 | 168 | ## 可视化开发界面和服务 169 | 完成了设备的连接之后,已经迈出了第一步,如果你想要自己开发一个界面,可视化的控制空调,或者想利用一些远程设备的数据触发一些自定义的规则和逻辑(例如如果某个数据超标,报警到钉钉群等),用阿里云的物联网平台的话,这些都不需要自己去开发,直接使用物联网平台的“开发服务”拖拖拽拽即可完成。 170 | 171 | 172 | ### 界面开发 173 | 界面开发的核心是 拖动布局、数据源绑定、交互绑定、自定义变量等。 174 |
175 | ![image.png](https://blog.souche.com/content/images/2019/09/1568630094545-d440e173-23f0-4d63-81e7-00d6b6ce8811.png) 176 | 布局界面,右侧可以设置各种控件属性 177 |
178 | ![image.png](https://blog.souche.com/content/images/2019/09/1568630244900-18ee8b99-1e80-409a-8482-981fd596d095.png) 179 | 数据源绑定,可以方便的和产品模型数据绑定 180 |
181 | ![image.png](https://blog.souche.com/content/images/2019/09/1568630294665-d5cf7a85-ad06-491c-a7bf-65f6439450f8.png) 182 | 交互绑定,可以与服务进行绑定,触发某个服务,并传入设备属性
183 |
184 | ![image.png](https://blog.souche.com/content/images/2019/09/1568642695402-cc25fedc-8c3f-4e32-90a9-89c0eec2f712.png) 185 | 控件可以给自定义变量赋值,其他组件可以与变量绑定 186 | 187 | 使用上述的功能,加上各种设备绑定功能,基本可以完成一个很复杂的界面的开发,但是如果你需要通过交互触发远程指令,或者监听远程的数据做一些判断逻辑,光有界面是不够的。 188 | 189 | 190 | ### 服务开发 191 | 上面提到的逻辑开发,就需要使用“服务开发”了,服务开发的触发点主要是两类:设备触发、界面交互触发。 192 | 193 | ![image.png](https://blog.souche.com/content/images/2019/09/1568643025480-f10c038d-8a9b-44a1-b270-51625b56a137.png) 194 | 设备触发的服务,中间的路径选择是用来做条件判断分流的,最右侧是触发通知逻辑节点
195 |
196 | ![image.png](https://blog.souche.com/content/images/2019/09/1568643229960-62754bc2-4ddf-4724-ba99-ed81459f9bc2.png) 197 | 一个温度控制服务,通过界面控件触发,最终生成设备控制逻辑
198 | 199 | 200 | ## 结语 201 | 本文不是 arduino 教学,也不是 esp8266 教学,更不是硬件教学,所以没有展开细节,需要读者具备一定的基础,请各位谅解,如果对 arduino 开发感兴趣,可以加我微信交流(微信号:mier963,注明 arduino)。 202 | 203 | 最后展示一下成果:
204 | ![IMG_7254.JPG](https://blog.souche.com/content/images/2019/09/1568643588452-48f528a4-29e2-41f9-99bb-0a004600bd8a.jpeg) 205 | 一个成品,包含四个传感器模块,包括一个硬件按钮
206 |
207 | ![image.png](https://blog.souche.com/content/images/2019/09/1568643652167-2f7d14b9-77d4-4a36-8172-ad7e9b9bc2ae.png) 208 | 空调远程控制界面
209 |
另外,还可以给这个控制器加入一些很有意思的功能,例如定点关闭所有设备,根据环境亮度开关空调等。 210 | 211 | --------------------------------------------------------------------------------