I'm trying to write a python program that updates all device-twins of a given IoT-hub, only by having the Hub's connection string. The code given here: Get started with device twins (Python) is not up-to-date and crashes. Does anyone have a successful experience with this kind of job?

- 196
- 1
- 12
-
Take a look at IoT Hub Jobs. Does exactly what you are looking for: targeting many devices for a certain operation such as twin updates https://learn.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-jobs#jobs-to-update-device-twin-properties – silent Oct 31 '19 at 15:18
2 Answers
In addition to @silent comment, the Azure IoT Hub has built-in a special job for Import and Export IoT Hub device identities in bulk.
This feature allows to update a device twin included also a reported properties. The bulk job is submitted to the service by REST API Create Import Export Job.
The bulk job is described in the blob file for each device line by line, the following line is an example for updateTwin on device1:
{ "id":"device1", "importMode":"updateTwin", "status":"enabled", "tags":{},"properties":{"desired":{ "key1":12},"reported":{ "key2":null, "key3":"abcd"} }}
the following code snippet shows how can be invoked this REST service for updating all device twins described in the inputBlobName:
import requests
import json
import time
import urllib
import hmac
import hashlib
import base64
# Example of the blob content for one device
# { "id":"device1", "importMode":"updateTwin", "status":"enabled", "tags":{},"properties":{"desired":{ "key1":12},"reported":{ "key2":null, "key3":"abcd" } } }
# IoT Hub and Blob Storage
iothub_connection_string = "<IoTHubConnectionString>"
blobContainerUri = "<blobContainerUriSaS>"
inputBlobName = "<inputBlobName>"
def get_sas_token(resource_uri, sas_name, sas_value):
sas = base64.b64decode(sas_value.encode('utf-8'))
expiry = str(int(time.time() + 10000))
string_to_sign = (resource_uri + '\n' + expiry).encode('utf-8')
signed_hmac_sha256 = hmac.HMAC(sas, string_to_sign, hashlib.sha256)
signature = urllib.parse.quote(base64.b64encode(signed_hmac_sha256.digest()))
return "SharedAccessSignature sr={}&sig={}&se={}&skn={}".format(resource_uri, signature, expiry, sas_name)
# iothub_sas_token from connection string
cs = dict(map(lambda x: x.split('=',1), iothub_connection_string.split(';')))
iothub_namespace = cs["HostName"].split('.')[0]
sas_token = get_sas_token(cs["HostName"], cs["SharedAccessKeyName"], cs["SharedAccessKey"])
# REST API see doc: https://learn.microsoft.com/en-us/rest/api/iothub/digitaltwinmodel/service/createimportexportjob
uri = "https://{}.azure-devices.net/jobs/create?api-version=2018-06-30".format(iothub_namespace)
headers = { 'Authorization':sas_token, 'Content-Type':'application/json;charset=utf-8' }
payload = { "inputBlobContainerUri": blobContainerUri, "outputBlobContainerUri": blobContainerUri, "inputBlobName": inputBlobName, "type":"import" }
res = requests.post(uri, data = json.dumps(payload), headers = headers)
print(res)
# check if the job has been accepted
if res.status_code != 200:
quit()
# check the job status progress
jobId = json.loads(res.content)["jobId"]
uri = "https://{}.azure-devices.net/jobs/{}?api-version=2018-06-30".format(iothub_namespace, jobId);
while(True):
time.sleep(2)
res = requests.get(uri, data = None, headers = headers)
if res.status_code == 200 and json.loads(res.content)["status"] == "running":
print(".", end="", flush=True)
else:
break
print("Done")
For creating a blob container, generating its sas uri address, etc. can be used a Microsoft Azure Storage Explorer.

- 7,925
- 1
- 8
- 21
-
anyway to get this sas token in php, if already implemented, the one on the azure site doesn't work. – naman1994 Nov 22 '19 at 04:06
With @Roman Kiss help, I used the create job API request - which not requires using storage.
JOB_ID = {INSERT_JOB_ID}
CONNECTION_STRING = {INSERT_CONNECTION_STRING}
IOT_HUB_NAME = {INSERT_IOT_HUB_NAME}
def create_device_twin():
return {"jobId": JOB_ID, "type": "scheduleUpdateTwin", "updateTwin": {"properties": {
"desired": {INSERT_YOUR_DESIRED_PROERTIES}
}, "etag": "*"}
}
def get_sas_token():
cs = dict(map(lambda x: x.split('=', 1), CONNECTION_STRING.split(';')))
resource_uri = cs["HostName"]
sas_name = cs["SharedAccessKeyName"]
sas_value = cs["SharedAccessKey"]
sas = base64.b64decode(sas_value.encode('utf-8'))
expiry = str(int(time.time() + 10000))
string_to_sign = (resource_uri + '\n' + expiry).encode('utf-8')
signed_hmac_sha256 = hmac.HMAC(sas, string_to_sign, hashlib.sha256)
signature = urllib.parse.quote(base64.b64encode(signed_hmac_sha256.digest()))
return "SharedAccessSignature sr={}&sig={}&se={}&skn={}".format(resource_uri, signature, expiry, sas_name)
if __name__ == '__main__':
uri = "https://{}.azure-devices.net/jobs/v2/{}?api-version=2018-06-30".format(IOT_HUB_NAME, JOB_ID)
sas_token = get_sas_token()
headers = {'Authorization': sas_token, 'Content-Type': 'application/json;charset=utf-8'}
res = requests.put(uri, headers=headers, data=json.dumps(create_device_twin()))

- 196
- 1
- 12
-
The **bulk job** is suitable for case when each device needs to be updated by different values. Your needs probably is not this case, when the job is using the same device state (desired properties) for multiple devices. In the case of the *device configuration*, the IoT Hub offers a built-in a special feature such as an implementation of the device twins to configure devices https://learn.microsoft.com/en-us/azure/iot-hub/iot-hub-automatic-device-management#implement-device-twins-to-configure-devices – Roman Kiss Nov 05 '19 at 22:02
-
so, in other words, using the REST service - *Create Or Update Configuration* https://learn.microsoft.com/en-us/rest/api/iothub/service/createorupdateconfiguration can be a better choice for your needs. – Roman Kiss Nov 05 '19 at 22:03