Search code examples
pythonjsonutf-8azure-iot-hubazure-digital-twins

Sending payload to IoT Hub for using in Azure Digital Twin using an Azure Function


Apologies for any incorrect formatting, long time since I posted anything on stack overflow.

I'm looking to send a json payload of data to Azure IoT Hub which I am then going to process using an Azure Function App to display real-time telemetry data in Azure Digital Twin.

I'm able to post the payload to IoT Hub and view it using the explorer fine, however my function is unable to take this and display this telemetry data in Azure Digital Twin. From Googling I've found that the json file needs to be utf-8 encrypted and set to application/json, which I think might be the problem with my current attempt at fixing this.

I've included a snipped of the log stream from my azure function app below, as shown the "body" part of the message is scrambled which is why I think it may be an issue in how the payload is encoded:

"iothub-message-source":"Telemetry"},"body":"eyJwb3dlciI6ICIxLjciLCAid2luZF9zcGVlZCI6ICIxLjciLCAid2luZF9kaXJlY3Rpb24iOiAiMS43In0="} 2023-01-27T13:39:05Z [Error] Error in ingest function: Cannot access child value on Newtonsoft.Json.Linq.JValue.

My current test code is below for sending payloads to IoT Hub, with the potential issue being that I'm not encoding the payload properly.

import datetime, requests 
import json

deviceID = "JanTestDT"
IoTHubName = "IoTJanTest"
iotHubAPIVer = "2018-04-01"
iotHubRestURI = "https://" + IoTHubName + ".azure-devices.net/devices/" + deviceID +     "/messages/events?api-version=" + iotHubAPIVer
SASToken = 'SharedAccessSignature'

Headers = {}
Headers['Authorization'] = SASToken
Headers['Content-Type'] = "application/json"
Headers['charset'] = "utf-8"

datetime =  datetime.datetime.now()
payload = {
'power': "1.7",
'wind_speed': "1.7",
'wind_direction': "1.7"
}

payload2 = json.dumps(payload, ensure_ascii = False).encode("utf8")

resp = requests.post(iotHubRestURI, data=payload2, headers=Headers)

I've attempted to encode the payload correctly in several different ways including utf-8 within request.post, however this produces an error that a dict cannot be encoded or still has the body encrypted within the Function App log stream unable to decipher it.

Thanks for any help and/or guidance that can be provided on this - happy to elaborate further on anything that is not clear.


Solution

  • is there any particular reason why you want to use Azure IoT Hub Rest API end point instead of using Python SDK? Also, even though you see the values in JSON format when viewed through Azure IoT Explorer, the message format when viewed through a storage end point such as blob reveals a different format as you pointed.

    I haven't tested the Python code with REST API, but I have a Python SDK that worked for me. Please refer the code sample below

    import os
    import random
    import time
    from datetime import date, datetime
    from json import dumps
    from azure.iot.device import IoTHubDeviceClient, Message
    
    
    def json_serial(obj):
        """JSON serializer for objects not serializable by default json code"""
    
        if isinstance(obj, (datetime, date)):
            return obj.isoformat()
        raise TypeError("Type %s not serializable" % type(obj))
    
    
    CONNECTION_STRING = "<AzureIoTHubDevicePrimaryConnectionString>"
    TEMPERATURE = 45.0
    HUMIDITY = 60
    MSG_TXT = '{{"temperature": {temperature},"humidity": {humidity}, "timesent": {timesent}}}'
    
    
    def run_telemetry_sample(client):
        print("IoT Hub device sending periodic messages")
    
        client.connect()
    
        while True:
            temperature = TEMPERATURE + (random.random() * 15)
            humidity = HUMIDITY + (random.random() * 20)
            x = datetime.now().isoformat()
            timesent = dumps(datetime.now(), default=json_serial)
            msg_txt_formatted = MSG_TXT.format(
                temperature=temperature, humidity=humidity, timesent=timesent)
            message = Message(msg_txt_formatted, content_encoding="utf-8", content_type="application/json")
            
            print("Sending message: {}".format(message))
            client.send_message(message)
            print("Message successfully sent")
            time.sleep(10)
    
    
    def main():
        print("IoT Hub Quickstart #1 - Simulated device")
        print("Press Ctrl-C to exit")
    
        client = IoTHubDeviceClient.create_from_connection_string(CONNECTION_STRING)
    
        try:
            run_telemetry_sample(client)
        except KeyboardInterrupt:
            print("IoTHubClient sample stopped by user")
        finally:
            print("Shutting down IoTHubClient")
            client.shutdown()
    
    
    if __name__ == '__main__':
        main()
    

    You can edit the MSG_TXT variable in the code to match the payload format and pass the values. Note that the SDK uses Message class from Azure IoT Device library which has an overload for content type and content encoding. Here is how I have passed the overloads in the code message = Message(msg_txt_formatted, content_encoding="utf-8", content_type="application/json")

    I have validated the message by routing to a Blob Storage container and could see the telemetry data in the JSON format. Please refer below image screenshot referring the data captured at end point.

    enter image description here

    Hope this helps!