Search code examples
azuremqttazure-iot-hubarduino-esp8266azure-management-api

Unable to subscribe to Azure Device Twins using MQTT and ESP8266


I have been working on the Azure Device Twins for the past month. The main goal is to subscribe to three device twin paths so that any change of the desired property results in a notification shown on the serial monitor and the desired property is parsed and given as a reported property. I am running the code on a NodeMCU and the issue I am facing is that the device connects to the IoTHub and publishes data but any changes in the device twin are not shown or parsed. The Device Twin works when api-version=2016-11-14 is added to the MQTT Username, as soon as I add the api-version in the username it gives the error failed rc=-1. The code I am using is given below

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <WiFiClientSecure.h>
const char mqtt_client[] = "test-device1";
const char mqtt_server[] = "HyperNet-IoTHub-Azure-Sphere.azure-devices.net";
const int mqtt_port = 8883;
const char mqtt_user[] = "HyperNet-IoTHub-Azure-Sphere.azure-devices.net/test-device1/api-version=2016-11-14";


void callback(char* topic, byte* payload, unsigned int length);

WiFiClientSecure WiFiclient;
PubSubClient client(WiFiclient);
void callback(char* topic, byte* payload, unsigned int length);
void parseDesiredProperties(byte* payload);

unsigned long previousMillis = 0;
long interval = 5000;
void reconnect();

void setup(){
  Serial.begin(9600);
  delay(250);
  client.setServer(mqtt_server, 8883);
  client.setCallback(callback);
  WiFi.disconnect();
  delay(100);
  WiFi.mode(WIFI_STA);
  Serial.println("Connecting To Wi-Fi: " + String(ssid));
  WiFi.begin(ssid,pass);
  while(WiFi.status() !=WL_CONNECTED){
    delay(500);
    Serial.print("..");
  }
  Serial.println(" ");
  Serial.println("Wi-Fi Connected");
}

void loop(){
  if(!client.connected()){
    reconnect();
  }
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis >= interval){
    previousMillis = currentMillis;
    float value1 = 12.3;
    float value2 = 13.6;
    String postData = "{\"testvalue1\":" + String(value1) + ",\"testvalue2\":" + String(value2) +"}";
    char postBuffer[postData.length()+1];
    postData.toCharArray(postBuffer, postData.length()+1);
    Serial.println(postBuffer);
    client.publish("devices/test-device1/messages/events/", postBuffer);
  }

  client.loop();

}

void reconnect(){
  WiFiclient.setInsecure();
  while(!client.connected()){
    Serial.println("Attempting MQTT Connection");
    if (client.connect(mqtt_client, mqtt_user, mqtt_pass)) {
      Serial.println("connected");
      client.subscribe("devices/test-device1/messages/devicebound/#");
      // subscribe to operation responses
      client.subscribe("$iothub/twin/res/#");
      // subscribe to desired property updates
      client.subscribe("$iothub/twin/PATCH/properties/desired/#");
    } 
    else {
     Serial.print("failed, rc=");
     Serial.print(client.state());
     Serial.println(" try again in 5 seconds");
     delay(5000);
    }  
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("MQTT message arrived on topic: ");
  Serial.println(topic);
  if (String(topic).startsWith("$iothub/twin/PATCH/properties/desired")) {
    parseDesiredProperties(payload);  
  }   
}

void parseDesiredProperties(byte* payload) {

  JsonObject& root = jsonDesiredProperties.parseObject(payload);
  if(root.success()) {
    Serial.println("Parsed desired properties");
    int newMillis=root["reportInterval"];
    if(newMillis > 2999 && newMillis < 120001) {
      interval = newMillis;
      String postProperty = "{\"reportInterval\":" + String(newMillis) + "}";
      char postBuffer[postProperty.length()+1];
      postProperty.toCharArray(postBuffer, postProperty.length()+1);
      client.publish("$iothub/twin/PATCH/properties/reported/?$rid=1", postBuffer);
      Serial.print("Set new interval to: ");
      Serial.println(newMillis);
    }
   } 
   else {
    Serial.println("Could not parse desired properties");
   }
}

Solution

  • the code snippet you posted seems to be incomplete. It was missing the jsonDesiredProperties for instance. You might have left some other things out, but one important thing missing is the certificate fingerprint. IoT Hub only accepts secure MQTT connections, and to do that, you need to include the SHA1 fingerprint of Microsoft's CA cert.

    You can download the certificate and put it in a file and retrieve the fingerprint with openssl:

    openssl x509 -noout -fingerprint -sha1 -inform pem -in [certificate-file.crt]
    

    Your setup method can then include the fingerprint:

    static const char *fingerprint PROGMEM = "D4 DE 20 D0 5E 66 FC 53 FE 1A 50 88 2C 78 DB 28 52 CA E4 74";
    
        void setup(){
              Serial.begin(9600);
              delay(250);
              WiFiclient.setFingerprint(fingerprint);
            
              client.setServer(mqtt_server, 8883);
              client.setCallback(callback);
              WiFi.disconnect();
              delay(100);
              WiFi.mode(WIFI_STA);
              Serial.println("Connecting To Wi-Fi");
              WiFi.begin("<REDACTED>","<REDACTED>");
              while(WiFi.status() !=WL_CONNECTED){
                delay(500);
                Serial.print("..");
              }
              Serial.println(" ");
              Serial.println("Wi-Fi Connected");
            }
    

    Another change I made is the API version you included in your MQTT username. You can use ?api-version=2018-06-30 rather than api-version=2016-11-14.

    I tested these changes on my NodeMCU, and updating the reported twin properties is working just fine! Let me know if this works for you!