ESP8266/32通过 Arduino连接 Tuyalink
起因最近手上有一些闲置的ESP8266/32模组,在网上查找各种设备上云远程控制的方法,发现了涂鸦的Tuyalink接入方式(之前使用过涂鸦产品,有联动控制和消息推送)但是涂鸦官方没有提供Tuyalink Arduino库,所以只能自己动手啦,在使用Arduino开发接入整体还是非常方便高效的,但是也踩了几个坑,所以这里整理笔记记录下,方便感兴趣的同学参考。至于为什么使用Arduino只能说DDDD(懂得都懂)YYDS,简单方便,有各种开源库,也可以通过看别人写的库学习编程技巧,真是何乐而不为呢。
什么是Tuyalink?
TuyaLink协议是涂鸦 平台面向物联网开发领域设计的一种数据交换规范,数据格式为 JSON,主要用于设备端和涂鸦 IoT 开发平台的双向通信,更便捷地实现了设备端和平台之间的业务数据交互。
主要用于使用非涂鸦模组接入涂鸦平台,使用MQTT 3.1.1协议接入,是生态设备接入涂鸦的一种方式。 (下面引用涂鸦官网对Tuyalink的接入流程图,个人觉得流程很清晰所以这里引用一下啦)
简单说就是Tuyalink就是涂鸦提供了一个MQTT服务器让设备可以连接到涂鸦云服务,然后通过手机或者微信小程序远程控制。
Tuyalink支持哪些功能
- 远程控制
- OTA(远程升级)
- 管理子设备——类似网关功能
- 文件上传下载服务
- …这里就不一 一列举了,感兴趣可以直接到涂鸦开发者平台查看。
这里提供一个涂鸦官网介绍链接:https://developer.tuya.com/cn/docs/iot/device-connection?id=Kb46bqq71kwtd
进入正题——如何连接到Tuyalink服务器?
端口和域名
涂鸦 IoT 开发平台支持全球多个区域的设备接入,故需要根据设备实际使用的区域,来选择对应的接入点。全球 6 大区 MQTT 接入点可自行查看。这里主要测试中国区接入。
区域 | MQTT 接入域名 | 端口 |
---|---|---|
中国数据中心 | m1.tuyacn.com | 8883 |
获取接入授权码
Tuyalink采用一机一密认证是预先为每个设备烧录其唯一的设备证书(即 ProductID、DeviceID 和 DeviceSecret)。当设备与云平台建立连接时,云平台对其携带的设备证书信息进行加密计算,最后使用 username/password 的方式认证。获取授权码方法见下图,需要先在涂鸦平台上创建生态设备产品,然后领取获取购买授权。
小提示:每个账号可以免费获取2个接入授权码哦
安装依赖
- 安装 ESP8266 开发板。 点击工具 -> 开发板 -> 开发板管理。搜索 ESP8266,点击安装。*注:这里最好安装最新的版本,我安装的是3.1.2,我测试过2.7.1版本MQTT连接会失败。
- 安装 PubSub client 库。 点击项目 -> 加载库 -> 管理库…。搜索 PubSubClient,安装 PubSubClient by Nick O’Leary。
- 安装 Crypto 库,后面通过SHA256计算连接MQTT的密码需要使用
这条分割线之后即认为你已经准备好了Arduino开发环境了
代码
导入必要的库
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <time.h>
#include <SHA256.h>
定义必要的变量保存参数
#define SHA256HMAC_SIZE 32
// WiFi credentials
const char *wifi_ssid = "填入自己的WiFi名"; // Replace with your WiFi name
const char *wifi_password = "密码"; // Replace with your WiFi password
// MQTT Broker settings
const int mqtt_port = 8883; // MQTT port (TLS)
const char *mqtt_broker = "m1.tuyacn.com"; // EMQX broker endpoint
const char *mqtt_topic = "tylink/%s/thing/model/get"; // MQTT topic
//需要使用到NTP时间服务获取unix时间戳用于连接MQTT使用
// NTP Server settings
const char *ntp_server = "pool.ntp.org"; // Default NTP server
// const char* ntp_server = "cn.pool.ntp.org"; // Recommended NTP server for users in China
const long gmt_offset_sec = 0; // GMT offset in seconds (adjust for your time zone)
const int daylight_offset_sec = 0; // Daylight saving time offset in seconds
定义Tuyalink连接参数
//productId、deviceId、deviceSecret是涂鸦平台获取到授权码信息
const char productId[] = "rwosj58aaqjk **** ";
const char deviceId[] = "6c95875d0f5ba69607 **** ";
const char deviceSecret[] = " ******************* ";
char clientID[50] ;
char username[100];
char password[96] ;
证书
因为Tuyalink连接MQTT服务器使用TLS加密连接,所以需要导入服务器证书,这里的证书可以在官方提供IoT Core SDK(C)中找到。如果不想研究源码的这里可以直接复制下面的代码即可。
// SSL certificate for MQTT broker
static const char ca_cert[]
{
"-----BEGIN CERTIFICATE-----\n"
"MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx\n"
"EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT\n"
"EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp\n"
"ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz\n"
"NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH\n"
"EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE\n"
"AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw\n"
"DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD\n"
"E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH\n"
"/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy\n"
"DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh\n"
"GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR\n"
"tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA\n"
"AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE\n"
"FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX\n"
"WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu\n"
"9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr\n"
"gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo\n"
"2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO\n"
"LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI\n"
"4uJEvlz36hz1\n"
"-----END CERTIFICATE-----\n"
};
const char iot_dns_cert_der[] = {
"-----BEGIN CERTIFICATE-----\n"
"MIICGDCCAb2gAwIBAgIRAI4kVSI/DR6TlRqvv0C7A4EwCgYIKoZIzj0EAwIwNTEdMBsGA1UECgwU\n"
"U2luYmF5IEdyb3VwIExpbWl0ZWQxFDASBgNVBAMMC0Nsb3VkIFJDQSAyMCAXDTIyMDUzMTE2MDAw\n"
"MFoYDzIwNzIwNjMwMTU1OTU5WjA1MR0wGwYDVQQKDBRTaW5iYXkgR3JvdXAgTGltaXRlZDEUMBIG\n"
"A1UEAwwLQ2xvdWQgUkNBIDIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATqjfuzyXh8P0MuuWrH\n"
"PUSoOp9OqsSHnCvDL18EK/Wfo1MOaQoIAy82zaC+ggjQph0AwCICTfzauMr0AUKw28Vko4GrMIGo\n"
"MA4GA1UdDwEB/wQEAwIBBjBFBgNVHSUEPjA8BggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMD\n"
"BggrBgEFBQcDCAYIKwYBBQUHAwQGCCsGAQUFBwMJMA8GA1UdEwQIMAYBAf8CAQEwHwYDVR0jBBgw\n"
"FoAUjW5pdbOF5Bmvn+MrD+yG6tcJ7yowHQYDVR0OBBYEFI1uaXWzheQZr5/jKw/shurXCe8qMAoG\n"
"CCqGSM49BAMCA0kAMEYCIQDaNnFTr66LnhYY+55C234I7MWBveU3RLg5pcVzb5EYUAIhAJN4+4go\n"
"F3rrb03/o2AsmPMLLZ+UjTjeCXrTXUyxBt2N\n"
"-----END CERTIFICATE-----\n"};
计算连接密钥
不得不说涂鸦还是很注重数据安全的,这里MQTT连接服务器的用户名和密码还需要通过前面获取的授权码通过hmacSha256加密算法计算得到。计算格式这里也从官方文档搬运到这里了,方便感兴趣的同学查看。
参数名称 | 参数说明 | 示例 |
---|---|---|
user name | D e v i c e I D I s i g n M e t h o d = h m a c S h a 256 , t i m e s t a m p = {DeviceID} I signMethod=hmacSha256,timestamp= DeviceIDIsignMethod=hmacSha256,timestamp={当前 10 位时间戳},secureMode=1,accessType=1; | 例如:6c828cba434ff40c074wF2 I signMethod=hmacSha256,timestamp=1607837283,secureMode=1,accessType=1 |
password | hmacSha256(content, DeviceSecret), content 的格式:deviceId= D e v i c e I D , t i m e s t a m p = {DeviceID},timestamp= DeviceID,timestamp={当前 10 位时间戳},secureMode=1,accessType=1 需要按照 deviceId,timestamp,secureMode,accessType 这个顺序组装明文内容。 64 位字符的 16 进制数,不足 64位时前面需要补零。 | content 例如:“deviceId=6c828cba434ff40c074wF2,timestamp=1607635284,secureMode=1,accessType=1” DeviceSecret 例如:ffad8eb66ae8c717 password 例如:9088f1608df4744e2a933ff905ffdde58dc7213510f25ad786a89896a5ea1104 |
static String hmac256(const String& signcontent, const String& ds) {
byte hashCode[SHA256HMAC_SIZE];
SHA256 sha256;
const char* key = ds.c_str();
size_t keySize = ds.length();
sha256.resetHMAC(key, keySize);
sha256.update((const byte*)signcontent.c_str(), signcontent.length());
sha256.finalizeHMAC(key, keySize, hashCode, sizeof(hashCode));
String sign = "";
for (byte i = 0; i < SHA256HMAC_SIZE; ++i) {
sprintf(password + 2 * i, "%02x", hashCode[i]);
//下面两行功能和sprintf功能一样,只不过是string类型存储的
sign += "0123456789ABCDEF"[hashCode[i] >> 4];
sign += "0123456789ABCDEF"[hashCode[i] & 0xf];
}
return sign;
}
static int tuya_mqtt_auth_signature_calculate(const char* deviceId, const char* deviceSecret) {
if (NULL == deviceId || NULL == deviceSecret) {
return -1;
}
uint32_t timestamp = time(nullptr);
/* client ID */
sprintf(username, "%s|signMethod=hmacSha256,timestamp=%d,secureMode=1,accessType=1", deviceId, timestamp);
Serial.print("username:");
Serial.println(username);
/* username */
sprintf(clientID, "tuyalink_%s", deviceId);
Serial.print("clientID:");
Serial.println(clientID);
/* password */
int i = 0;
char passward_stuff[255];
size_t slen = sprintf(passward_stuff, "deviceId=%s,timestamp=%d,secureMode=1,accessType=1", deviceId, timestamp);
hmac256(passward_stuff, deviceSecret);
Serial.print("password:");
Serial.println(password);
return 0;
}
TLS连接到MQTT
void connectToWiFi() {
WiFi.begin(wifi_ssid, wifi_password);
Serial.println("Connecting to WiFi ");
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("Connected to WiFi Success !");
}
void syncTime() {
configTime(gmt_offset_sec, daylight_offset_sec, ntp_server);
Serial.print("Waiting for NTP time sync: ");
while (time(nullptr) < 8 * 3600 * 2) {
delay(1000);
Serial.print(".");
}
Serial.println("Time synchronized");
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
Serial.print("Current time: ");
Serial.println(asctime(&timeinfo));
} else {
Serial.println("Failed to obtain local time");
}
}
void connectToMQTT() {
BearSSL::X509List cert(ca_cert);
espClient.setTrustAnchors(&cert);
// espClient.setFingerprint(fingerprint);
while (!mqtt_client.connected()) {
tuya_mqtt_auth_signature_calculate(deviceId, deviceSecret);
Serial.printf("Connecting to MQTT Broker as %s.....\n", clientID);
if (mqtt_client.connect(clientID, username, password)) {
Serial.println("Connected to MQTT broker");
char auto_subscribe_topic[64];
sprintf(auto_subscribe_topic, "tylink/%s/channel/downlink/auto_subscribe", deviceId);
mqtt_client.subscribe(auto_subscribe_topic);
sprintf(auto_subscribe_topic,mqtt_topic,deviceId);
// Publish message upon successful connection
mqtt_client.publish(auto_subscribe_topic, "{\"data\":{\"format\":\"simple\"}}");
} else {
char err_buf[128];
espClient.getLastSSLError(err_buf, sizeof(err_buf));
Serial.print("Failed to connect to MQTT broker, rc=");
Serial.println(mqtt_client.state());
Serial.print("SSL error: ");
Serial.println(err_buf);
delay(5000);
}
}
}
void mqttCallback(char *topic, byte *payload, unsigned int length) {
Serial.print("Message received on topic: ");
Serial.print(topic);
Serial.print("]: ");
for (int i = 0; i < length; i++) {
Serial.print((char) payload[i]);
}
Serial.println();
}
void setup() {
Serial.begin(115200);
connectToWiFi();
syncTime(); // X.509 validation requires synchronization time
mqtt_client.setServer(mqtt_broker, mqtt_port);
mqtt_client.setCallback(mqttCallback);
connectToMQTT();
}
void loop() {
if (!mqtt_client.connected()) {
connectToMQTT();
}
mqtt_client.loop();
}
到了这里连接到Tuyalink的步骤已经全部讲完了
使用涂鸦App通过扫码绑定到设备就能远程控制啦——这里App的使用操作就不做赘述,有疑问的同学可以到涂鸦官网咨询。
运行截图
剩下的事情就是需要将下发的JSON内容解析通过8266执行即可,数据解析如果有时间后续我在另一个文章做记录,感谢大家的观看。