Ryan
Ryan

Reputation: 13

Has anyone figured out how to connect to tuya MQTT standard protocol on an esp32?

I'm trying to connect to the Tuya IoT Developer platform using https://developer.tuya.com/en/docs/iot/MQTT-protocol?id=Kb65nphxrj8f1 as a reference.
I've created a product in the developer portal already but can't seem to get my esp32 to connect to the Tuya MQTT servers.

Here's what I've written

#include <Seeed_mbedtls.h>

#define KEY_SIZE 32  // 256 bits for HMAC-SHA256
#define OUTPUT_SIZE 32 // SHA-256 outputs 32 bytes

// Your 256-bit secret key (32 bytes)
String str = "DeviceSecret";
const uint8_t* deviceSecret = (const uint8_t*)str.c_str();

#include <WiFi.h>
WiFiClient wifiClient;

#include <time.h>     // include time library

// (utc+) TZ in hours

long timezone = 0; 

byte daysavetime = 0;

#include <PubSubClient.h>
PubSubClient client(wifiClient);

// Set up wifi
#define SSID "SSID"
#define WIFI_PWD "pwd"


// MQTT set up

//MQTT server
#define MQTT_BROKER "m1.tuyacn.com"
#define MQTT_PORT (8883)


String compute_hmac_sha256(const uint8_t *key, size_t key_len, const uint8_t *input, size_t input_len) {
    mbedtls_md_context_t ctx;
    const mbedtls_md_info_t *md_info;
    uint8_t hmac_output[32]; // SHA-256 outputs 32 bytes

    mbedtls_md_init(&ctx);
    md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);

    if (md_info == NULL) {
        Serial.println("Failed to get MD info for SHA256");
        mbedtls_md_free(&ctx);
        return String();
    }

    if (mbedtls_md_setup(&ctx, md_info, 1) != 0) {
        Serial.println("Failed to setup MD context");
        mbedtls_md_free(&ctx);
        return String();
    }

    if (mbedtls_md_hmac_starts(&ctx, key, key_len) != 0) {
        Serial.println("Failed to start HMAC");
        mbedtls_md_free(&ctx);
        return String();
    }

    if (mbedtls_md_hmac_update(&ctx, input, input_len) != 0) {
        Serial.println("Failed to update HMAC");
        mbedtls_md_free(&ctx);
        return String();
    }

    if (mbedtls_md_hmac_finish(&ctx, hmac_output) != 0) {
        Serial.println("Failed to finish HMAC");
        mbedtls_md_free(&ctx);
        return String();
    }

    mbedtls_md_free(&ctx);

    // Convert the HMAC output to a hexadecimal string
    String hmac_str;
    for (int i = 0; i < sizeof(hmac_output); i++) {
        char buf[3];
        sprintf(buf, "%02x", hmac_output[i]);
        hmac_str += buf;
    }

    return hmac_str;
}

// the setup function runs once when you press reset or power the board
void setup() {
   Serial.begin(9600);
    Serial.print("Connecting to " SSID);
  WiFi.begin(SSID, WIFI_PWD); // attempt to connect to an existing wifi
  //Wait for wifi to connect
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(" . ");
    }
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  //Time Server Set up
  Serial.println("Contacting Time Server");
  configTime(3600*timezone, daysavetime*3600, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org");
  delay(1000);

  time_t now;
  time(&now); 

  //MQTT Credentials set up
  String MQTT_USERNAME = "deviceID|signMethod=hmacSha256,timestamp=" + String(now) +",secureMode=1,accessType=1";

      // Your original const char* message
    const char *message = "deviceId=deviceID,timestamp=,secureMode=1,accessType=1";

    // The String you want to insert
    String additional = String(now);

    // Convert the original message to a String
    String messageString = String(message);

    // Insert the additional String at the 43rd position
    size_t insertPos = 42; // Position to insert
    messageString = messageString.substring(0, insertPos) + additional + messageString.substring(insertPos);


    // Convert the modified message back to const char*
    const char* final_message = messageString.c_str();



  // Compute HMAC-SHA256
  String MQTT_PWD = compute_hmac_sha256(deviceSecret, KEY_SIZE, (const uint8_t *)final_message, strlen(final_message));
  Serial.println(now);

  Serial.print("MQTT_PWD: ");
  Serial.println(MQTT_PWD);

 Serial.println("init successfull");
 String tuyalink = "tuyalink_DeviceId";
    // Handle MQTT connection.
  client.setServer(MQTT_BROKER, MQTT_PORT); // set broker settings
  while (! client.connected()) { // check connected status
    if ( client.connect(tuyalink.c_str(),MQTT_USERNAME.c_str(),MQTT_PWD.c_str())){ // connect with random id
      Serial.println("MQTT connected."); // report success
      //client.subscribe("Test/ra542");
    } else {
      Serial. printf (" failed , rc=%d try again in 5 seconds", client.state ()); // report error
      delay(5000); // wait 5 seconds
    }
  }



}

// the loop function runs over and over again forever
void loop() {
Serial.println("Hello World!");
delay(5000);

            
}

In my final code I've replaced the deviceId and DeviceSecret with the credentials I've gotten from tuya

On Connecting to the MQTT Server I get

failed , rc=-4 try again in 5 seconds failed

According to https://pubsubclient.knolleary.net/api#state rc -4 means the server didn't respond within the keepalive time.

I've tried increasing the keep alive time to 60s but that doesn't change anything

  uint16_t keepAlive = 60000;
  client.setKeepAlive(keepAlive);

When Testing with test.mosquitto.org and port 1883 without credentials it seems to work fine.
I'm not sure where I'm going wrong here. Any ideas/pointers would be greatly appreciated

Upvotes: 0

Views: 821

Answers (3)

Danny.tsai
Danny.tsai

Reputation: 11

I successfully connected tuya on ESP8266, first of all, you can follow this step to check your code.

My environment is using Arduino IDE with ESP8266, without arduino chip, use third-party libraries for MQTT, SHA256 calculation, not the Tuya iot sdk.

My code is based on this tutorial: https://blog.csdn.net/weixin_42172619/article/details/137993680

There are some points to consider.

  1. CA cert

You can use the Tuya iot SDK header file directly, don't change any format. examples\custom_protocol_basic_demo\tuya_cacert.h

BearSSL::X509List *certList;
certList = new BearSSL::X509List(tuya_cacert_pem);
espClient.setTrustAnchors(certList);

Note: I think setCACert() is deprecated, but setTrustAnchors() can be used instead.

  1. X509 Certification time

If you don't set the time, you may get failed, rc=-2 error,timestamp can use the same timestamp as Tuya username.

espClient.setX509Time(timestamp);
  1. hmacSha256 result:

https://developer.tuya.com/en/docs/iot/MQTT-protocol?id=Kb65nphxrj8f1

I believe it can be verified from the official tutorials that you can get the same results using the example content and DeviceSecret, and result is 9088f16***6a5ea1104(Same as tutorial)

Upvotes: 1

Hashim Taher
Hashim Taher

Reputation: 1

You must connect to the Tuya server in a secure connection

According to Tuya documentation insert link description here

#include <WiFiClientSecure.h>

WiFiClientSecure wifiClient;

And add the root certificate before connecting to the server

wifiClient.setCACert(Tuya_CERT_CA);

Upvotes: 0

hcheung
hcheung

Reputation: 4034

configTime(3600timezone, daysavetime3600, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org"); delay(1000);

The access to an NTP server with configTime() take time, it could varies from a few ms to a minute. So the delay(1000) isn't adequate, and if it failed, the rest of the code of trying to insert the timestamp would insert the incorrect data.

Here is a suggestion for ensuring the configTime get the correct timestamp.

  int timeOut = 0;
  configTime(3600*timezone, daysavetime*3600, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org");
  while (time(nullptr) < 1716086610) { // 1716086610 is just a valid timestamp in recent past
      delay(1000);
  };

  time_t now = time(NULL);

Update

Further to your comment, if you are using PORT 8883, you probably need a secure TCP connection. I don't have tuya account, but I test the following code on test.mosquitto.org:port 8883 with the following code that you might want to adopt it for your application.

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <time.h>

// Set up wifi
#define SSID "wifi_ssid"
#define WIFI_PWD "wifi_password"

// MQTT set up
#define MQTT_BROKER "test.mosquitto.org"
#define MQTT_PORT 8883

WiFiClientSecure wifiClient;
PubSubClient client(wifiClient);

void setup() {
  Serial.begin(115200);
  Serial.print("Connecting to WiFi");
  WiFi.begin(SSID, WIFI_PWD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected");

  //Time Server Set up
  Serial.println("Contacting Time Server");
  configTime(3600*timezone, daysavetime*3600, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org");
  while (time(nullptr) < 1716086610) { // 1716086610 is just a valid timestamp in recent past
      delay(1000);
  };

  time_t now = time(NULL);
  
  Serial.println("Contacting to MQTT broker");
  wifiClient.setInsecure(); // not to validate server certificate
  client.setServer(MQTT_BROKER, MQTT_PORT); // set broker settings
  while (! client.connected()) { // check connected status
    if (client.connect("my_mqtt_client") {
      Serial.println("MQTT connected."); // report success
    } else {
      Serial. printf (" failed , rc=%d try again in 5 seconds", client.state ()); // report error
      delay(5000); // wait 5 seconds
    }
  }

}

Upvotes: 0

Related Questions