diff --git a/.gitignore b/.gitignore index dcea98b..829ba63 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__ dist *egg-info +MANIFEST # Micropython boot.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b220348 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 iot-for-all + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 10b5086..f70807f 100644 --- a/README.md +++ b/README.md @@ -1 +1,176 @@ -Work in progress... \ No newline at end of file +# Microsoft Azure IoTCentral SDK for MicroPython + +[![Join the chat at https://gitter.im/iotdisc/community](https://badges.gitter.im/iotdisc.svg)](https://gitter.im/iotdisc/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Licensed under the MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/iot-for-all/iotc-micropython-client/blob/master/LICENSE) +[![PyPI version](https://badge.fury.io/py/micropython-iotc.svg)](https://badge.fury.io/py/micropython-iotc) + +### An Azure IoT Central device client library for Micropython. +This repository contains code for the Azure IoT Central SDK for Micropython. This enables micropython developers to easily create device solutions that semealessly connect to Azure IoT Central applications. +It can run on various boards with some tweaks for low-memory devices. + + +## Prerequisites ++ Micropython 1.12+ (recommended) + +## Import ``iotc`` +In most of the micropython capable boards, is sufficient to import library or install it if missing through upip. + +```py +try: + import iotc +except: + import upip + upip.install('micropython-iotc') + import iotc +``` + +The same commands apply when running through Micropython REPL. + +> **NOTE:** for low-end devices like the **ESP8266**, importing as external module can cause out-of-memory exception during execution because of the limited amount of heap space. +For this kind of boards, putting the library on flash memory as a frozen module might be the only available option. +
+
+Details on how to build a custom firmware for specific boards with frozen modules can be found on official micropython [github repo](https://github.com/micropython/micropython) and [website](http://docs.micropython.org/en/latest/) + + +## Samples +Check out the [sample repository](samples) for example code showing how the SDK can be used in the various scenarios: + + +## Connecting +Currently only connection through Shared Access Keys is supported. +You can use both device keys or group keys. + +### Init +```py +from iotc import IoTCConnectType +id_scope = 'scopeID' +device_id = 'device_id' +sasKey = 'masterKey' # or use device key directly +conn_type=IoTCConnectType.SYMM_KEY # or use DEVICE_KEY if working with device keys +client = IoTCClient(id_scope, device_id, conn_type, sasKey) +``` + +You can pass a logger instance to have your custom log implementation. (see [#Logging](#logging)) + +e.g. + +```py +from iotc import ConsoleLogger,IoTCLogLevel +logger = ConsoleLogger(IoTCLogLevel.ALL) +client = IoTCClient(id_scope, device_id, conn_type, sasKey, logger) +``` + +### Connect + +```py +client.connect() +``` +After successfull connection, IOTC context is available for further commands. + +## Operations + +### Send telemetry + +```py +client.send_telemetry(payload,properties=None) +``` + +e.g. Send telemetry every 3 seconds +```py +while client.is_connected(): + print('Sending telemetry') + client.send_telemetry({'temperature':randint(0,20),'pressure':randint(0,20),'acceleration':{'x':randint(0,20),'y':randint(0,20)}}) + sleep(3) +``` +An optional *properties* object can be included in the send methods, to specify additional properties for the message (e.g. timestamp,etc... ). +Properties can be custom or part of the reserved ones (see list [here](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/iothub/device/src/MessageSystemPropertyNames.cs#L36)). + +> **NOTE:** Payload content type and encoding are set by default to 'application/json' and 'utf-8'. Alternative values can be set using these functions:
+_iotc.set_content_type(content_type)_ # .e.g 'text/plain' +_iotc.set_content_encoding(content_encoding)_ # .e.g 'ascii' + +### Send property update +```py +client.send_property({'fieldName':'fieldValue'}) +``` + +### Listen to properties update +Subscribe to properties update event before calling _connect()_: +```py +client.on(IoTCEvents.PROPERTIES, callback) +``` +To provide property sync aknowledgement, the callback must return the +property value if has been successfully applied or nothing. + +e.g. +```py +def on_props(prop_name, prop_value): + if prop_value>10: + # process property + return prop_value + +client.on(IoTCEvents.PROPERTIES, on_props) +``` + +### Listen to commands +Subscribe to command events before calling _connect()_: +```py +client.on(IoTCEvents.COMMANDS, callback) +``` +To provide feedbacks for the command like execution result or progress, the client can call the **ack** function available in the callback. + +The function accepts 2 arguments: the command instance and a custom response message. +```py +def on_commands(command, ack): + print(command.name) + ack(command, 'Command received') + +client.on(IoTCEvents.COMMANDS, on_commands) +``` + +## Logging + +The default log prints to serial console operations status and errors. +This is the _API_ONLY_ logging level. +The function __set_log_level()__ can be used to change options or disable logs. It accepts a _IoTCLogLevel_ value among the following: + +- IoTCLogLevel.DISABLED (log disabled) +- IoTCLogLevel.API_ONLY (information and errors, default) +- IoTCLogLevel.ALL (all messages, debug and underlying errors) + +The device client also accepts an optional Logger instance to redirect logs to other targets than console. +The custom class must implement three methods: + +- info(message) +- debug(message) +- set_log_level(message); + +## One-touch device provisioning and approval +A device can send custom data during provision process: if a device is aware of its IoT Central template Id, then it can be automatically provisioned. + +### How to set IoTC template ID in your device +Template Id can be found in the device explorer page of IoTCentral +![Img](assets/modelid_1.png) +![Img](assets/modelid_2.png) + +Then call this method before connect(): + +```py +client.set_model_id(''); +``` + +### Manual approval (default) +By default device auto-approval in IoT Central is disabled, which means that administrator needs to approve the device registration to complete the provisioning process. +This can be done from explorer page after selecting the device +![Img](assets/manual_approval.jpg) + + +### Automatic approval +To change default behavior, administrator can enable device auto-approval from Device Connection page under the Administration section. +With automatic approval a device can be provisioned without any manual action and can start sending/receiving data after status changes to "Provisioned" + +![Img](assets/auto_approval.jpg) + +## License +This samples is licensed with the MIT license. For more information, see [LICENSE](./LICENSE) \ No newline at end of file diff --git a/assets/auto_approval.jpg b/assets/auto_approval.jpg new file mode 100644 index 0000000..4926db5 Binary files /dev/null and b/assets/auto_approval.jpg differ diff --git a/assets/manual_approval.jpg b/assets/manual_approval.jpg new file mode 100644 index 0000000..5328b76 Binary files /dev/null and b/assets/manual_approval.jpg differ diff --git a/assets/modelid_1.png b/assets/modelid_1.png new file mode 100644 index 0000000..447cd05 Binary files /dev/null and b/assets/modelid_1.png differ diff --git a/assets/modelid_2.png b/assets/modelid_2.png new file mode 100644 index 0000000..6ccb0e7 Binary files /dev/null and b/assets/modelid_2.png differ diff --git a/iotc/__init__.py b/iotc/__init__.py index e69de29..dca23eb 100644 --- a/iotc/__init__.py +++ b/iotc/__init__.py @@ -0,0 +1,218 @@ +from iotc.constants import * +from iotc.provision import ProvisioningClient +import ure +import json +from utime import time,sleep +import gc +try: + from umqtt.robust import MQTTClient +except: + print('Mqtt library not found. Installing...') + import upip + upip.install('micropython-umqtt.robust') + from umqtt.robust import MQTTClient +gc.collect() +class Command(): + def __init__(self, cmd_name, request_id): + self._cmd_name = cmd_name + self._request_id = request_id + + @property + def name(self): + return self._cmd_name + @property + def payload(self): + return self._payload + + @payload.setter + def payload(self,value): + self._payload=value + + @property + def request_id(self): + return self._request_id +class IoTCClient(): + def __init__(self, id_scope, device_id, credentials_type: IoTCConnectType, credentials, logger=None): + self._device_id = device_id + self._id_scope = id_scope + self._credentials_type = credentials_type + self._content_type = 'application%2Fjson' + self._content_encoding = 'utf-8' + self._connected = False + self._credentials = credentials + self._events = {} + self._model_id = None + if logger is not None: + self._logger = logger + else: + self._logger = ConsoleLogger(IoTCLogLevel.API_ONLY) + self._twin_request_id = time() + + def set_content_type(self, content_type): + self._content_type = encode_uri_component(content_type) + + def set_content_encoding(self, content_encoding): + self._content_encoding = content_encoding + + def set_log_level(self,log_level:IoTCLogLevel): + self._logger.set_log_level(log_level) + + def _on_message(self, topic, message): + topic = topic.decode('utf-8') + if topic == HubTopics.TWIN_RES.format(200, self._twin_request_id): + self._logger.info('Received twin: {}'.format(message)) + + if topic.startswith(HubTopics.PROPERTIES): + # desired properties + self._logger.info( + 'Received desired property message: {}'.format(message)) + message = json.loads(message.decode('utf-8')) + self.on_properties_update(message) + + elif topic.startswith(HubTopics.COMMANDS): + # commands + self._logger.info( + 'Received command {} with message: {}'.format(topic, message)) + match = self._commands_regex.match(topic) + if match is not None: + if all(m is not None for m in [match.group(1), match.group(2)]): + command_name = match.group(1) + command_req = match.group(2) + command = Command(command_name, command_req) + if message is not None: + command.payload = message + self._on_commands(command) + + elif topic.startswith(HubTopics.ENQUEUED_COMMANDS.format(self._device_id)): + params = topic.split( + "devices/{}/messages/devicebound/".format(self._device_id), 1)[1].split('&') + for param in params: + p = param.split('=') + if p[0] == "method-name": + command_name = p[1].split("Commands%3A")[1] + + self._logger.info( + 'Received enqueued command {} with message: {}'.format(command_name, message)) + command = Command(command_name, None) + if message is not None: + command.payload = message + self._on_enqueued_commands(command) + + def connect(self): + prov = ProvisioningClient( + self._id_scope, self._device_id, self._credentials_type,self._credentials,self._logger,self._model_id) + creds = prov.register() + self._mqtt_client = MQTTClient(self._device_id, creds.host, 8883, creds.user.encode( + 'utf-8'), creds.password.encode('utf-8'), ssl=True, keepalive=60) + self._commands_regex = ure.compile( + '\$iothub\/methods\/POST\/(.+)\/\?\$rid=(.+)') + self._mqtt_client.connect(False) + self._connected = True + self._logger.info('Device connected!') + self._mqtt_client.set_callback(self._on_message) + self._mqtt_client.subscribe(HubTopics.TWIN) + self._mqtt_client.subscribe('{}/#'.format(HubTopics.PROPERTIES)) + self._mqtt_client.subscribe('{}/#'.format(HubTopics.COMMANDS)) + self._mqtt_client.subscribe( + '{}/#'.format(HubTopics.ENQUEUED_COMMANDS.format(self._device_id))) + + self._logger.debug(self._twin_request_id) + self._mqtt_client.publish( + HubTopics.TWIN_REQ.format(self._twin_request_id).encode('utf-8'), '{{}}') + + def is_connected(self): + if self._connected == True: + return True + return False + + def set_model_id(self, model): + self._model_id = model + + def send_property(self, payload): + self._logger.debug('Sending properties {}'.format(json.dumps(payload))) + self._mqtt_client.publish( + HubTopics.PROP_REPORT.format(time()).encode('utf-8'), json.dumps(payload)) + + def send_telemetry(self, payload, properties=None): + topic = 'devices/{}/messages/events/?$.ct={}&$.ce={}'.format( + self._device_id, self._content_type, self._content_encoding) + if properties is not None: + for prop in properties: + topic += '{}={}&'.format(encode_uri_component(prop), + encode_uri_component(properties[prop])) + + topic = topic[:-1] + self._mqtt_client.publish(topic.encode( + 'utf-8'), json.dumps(payload).encode('utf-8')) + + def on(self, event, callback): + self._events[event] = callback + + def listen(self): + if not self.is_connected(): + return + self._mqtt_client.ping() + self._mqtt_client.wait_msg() + sleep(1) + + def on_properties_update(self, patch): + try: + prop_cb = self._events[IoTCEvents.PROPERTIES] + except: + return + + for prop in patch: + if prop == '$version': + continue + ret = prop_cb(prop, patch[prop]['value']) + if ret: + self._logger.debug('Acknowledging {}'.format(prop)) + self.send_property({ + '{}'.format(prop): { + "value": patch[prop]["value"], + 'status': 'completed', + 'desiredVersion': patch['$version'], + 'message': 'Property received'} + }) + else: + self._logger.debug( + 'Property "{}" unsuccessfully processed'.format(prop)) + + def _cmd_resp(self, command: Command, value): + self._logger.debug( + 'Responding to command "{}" request'.format(command.name)) + self.send_property({ + '{}'.format(command.name): { + 'value': value, + 'requestId': command.request_id + } + }) + + def _cmd_ack(self, command: Command): + self._logger.debug('Acknowledging command {}'.format(command.name)) + self._mqtt_client.publish( + '$iothub/methods/res/{}/?$rid={}'.format(200, command.request_id).encode('utf-8'), '') + + def _on_commands(self, command: Command): + try: + cmd_cb = self._events[IoTCEvents.COMMANDS] + except KeyError: + return + + self._logger.debug( + 'Received command {}'.format(command.name)) + self._cmd_ack(command) + + cmd_cb(command, self._cmd_resp) + + def _on_enqueued_commands(self, command: Command): + try: + cmd_cb = self._events[IoTCEvents.ENQUEUED_COMMANDS] + except KeyError: + return + + self._logger.debug( + 'Received enqueued command {}'.format(command.name)) + self._cmd_ack(command) + + cmd_cb(command) diff --git a/iotc/constants.py b/iotc/constants.py new file mode 100644 index 0000000..bebf40e --- /dev/null +++ b/iotc/constants.py @@ -0,0 +1,63 @@ +class IoTCLogLevel: + DISABLED = 1 + API_ONLY = 2 + ALL = 3 + +class IoTCConnectType: + SYMM_KEY = 1 + DEVICE_KEY = 2 + +class IoTCEvents: + PROPERTIES = 1 + COMMANDS = 2 + ENQUEUED_COMMANDS = 3 + +class HubTopics: + TWIN = '$iothub/twin/res/#' + TWIN_REQ = '$iothub/twin/GET/?$rid={}' + TWIN_RES = '$iothub/twin/res/{}/?$rid={}' + PROPERTIES = '$iothub/twin/PATCH/properties/desired' + PROP_REPORT = '$iothub/twin/PATCH/properties/reported/?$rid={}' + COMMANDS = '$iothub/methods/POST' + ENQUEUED_COMMANDS = 'devices/{}/messages/devicebound' + +class ConsoleLogger: + def __init__(self, log_level=IoTCLogLevel.API_ONLY): + self._log_level = log_level + + def _log(self, message): + print(message) + + def info(self, message): + if self._log_level != IoTCLogLevel.DISABLED: + self._log(message) + + def debug(self, message): + if self._log_level == IoTCLogLevel.ALL: + self._log(message) + + def set_log_level(self, log_level): + self._log_level = log_level + +unsafe = { + '?': '%3F', + ' ': '%20', + '$': '%24', + '%': '%25', + '&': '%26', + "\'": '%27', + '/': '%2F', + ':': '%3A', + ';': '%3B', + '+': '%2B', + '=': '%3D', + '@': '%40' +} + +def encode_uri_component(string): + ret = '' + for char in string: + if char in unsafe: + char = unsafe[char] + ret = '{}{}'.format(ret, char) + return ret \ No newline at end of file diff --git a/iotc/device.py b/iotc/device.py deleted file mode 100644 index f4526c6..0000000 --- a/iotc/device.py +++ /dev/null @@ -1,250 +0,0 @@ -import gc -gc.collect() -from utime import time,sleep -gc.collect() -import json -gc.collect() -try: - from umqtt.robust import MQTTClient - gc.collect() -except: - print('Mqtt library not found. Installing...') - import upip - upip.install('micropython-umqtt.robust') - from umqtt.robust import MQTTClient - -unsafe = { - '?': '%3F', - ' ': '%20', - '$': '%24', - '%': '%25', - '&': '%26', - "\'": '%27', - '/': '%2F', - ':': '%3A', - ';': '%3B', - '+': '%2B', - '=': '%3D', - '@': '%40' -} - -def encode_uri_component(string): - ret = '' - for char in string: - if char in unsafe: - char = unsafe[char] - ret = '{}{}'.format(ret, char) - return ret - -class Command(): - def __init__(self, cmd_name, request_id): - self._cmd_name = cmd_name - self._request_id = request_id - - @property - def name(self): - return self._cmd_name - @property - def payload(self): - return self._payload - - @payload.setter - def payload(self,value): - self._payload=value - - @property - def request_id(self): - return self._request_id - -class IoTCEvents: - PROPERTIES = 1 - COMMANDS = 2 - ENQUEUED_COMMANDS = 3 - -class HubTopics: - TWIN = '$iothub/twin/res/#' - TWIN_REQ = '$iothub/twin/GET/?$rid={}' - TWIN_RES = '$iothub/twin/res/{}/?$rid={}' - PROPERTIES = '$iothub/twin/PATCH/properties/desired' - PROP_REPORT = '$iothub/twin/PATCH/properties/reported/?$rid={}' - COMMANDS = '$iothub/methods/POST' - ENQUEUED_COMMANDS = 'devices/{}/messages/devicebound' - -class DeviceClient(): - def __init__(self,device_id, credentials, logger): - # clean up modules - try: - import sys - del sys.modules['iotc.provision'] - del sys.modules['iotc'] - except: - pass - self._device_id=device_id - self._content_type='application%2Fjson' - self._content_encoding='utf-8' - self._connected=False - self._events = {} - self._logger = logger - self._twin_request_id = time() - self._mqtt_client = MQTTClient(self._device_id,credentials.host, 8883, credentials.user.encode( - 'utf-8'), credentials.password.encode('utf-8'), ssl=True,keepalive=60) - - - import ure - gc.collect() - self._commands_regex=ure.compile('\$iothub\/methods\/POST\/(.+)\/\?\$rid=(.+)') - - - def set_content_type(self,content_type): - self._content_type = encode_uri_component(content_type) - - def set_content_encoding(self,content_encoding): - self._content_encoding = content_encoding - - - def _on_message(self, topic, message): - topic = topic.decode('utf-8') - if topic == HubTopics.TWIN_RES.format(200, self._twin_request_id): - self._logger.info('Received twin: {}'.format(message)) - - if topic.startswith(HubTopics.PROPERTIES): - # desired properties - self._logger.info( - 'Received desired property message: {}'.format(message)) - message = json.loads(message.decode('utf-8')) - self.on_properties_update(message) - - elif topic.startswith(HubTopics.COMMANDS): - # commands - self._logger.info('Received command {} with message: {}'.format(topic,message)) - match=self._commands_regex.match(topic) - if match is not None: - if all(m is not None for m in [match.group(1),match.group(2)]): - command_name=match.group(1) - command_req=match.group(2) - command=Command(command_name,command_req) - if message is not None: - command.payload=message - self._on_commands(command) - - elif topic.startswith(HubTopics.ENQUEUED_COMMANDS.format(self._device_id)): - params=topic.split("devices/{}/messages/devicebound/".format(self._device_id),1)[1].split('&') - for param in params: - p=param.split('=') - if p[0] == "method-name": - command_name=p[1].split("Commands%3A")[1] - - self._logger.info('Received enqueued command {} with message: {}'.format(command_name,message)) - command=Command(command_name,None) - if message is not None: - command.payload=message - self._on_enqueued_commands(command) - - - - def connect(self): - self._mqtt_client.connect(False) - self._connected = True - self._logger.info('Device connected!') - self._mqtt_client.set_callback(self._on_message) - self._mqtt_client.subscribe(HubTopics.TWIN) - self._mqtt_client.subscribe('{}/#'.format(HubTopics.PROPERTIES)) - self._mqtt_client.subscribe('{}/#'.format(HubTopics.COMMANDS)) - self._mqtt_client.subscribe( - '{}/#'.format(HubTopics.ENQUEUED_COMMANDS.format(self._device_id))) - - self._logger.debug(self._twin_request_id) - self._mqtt_client.publish( - HubTopics.TWIN_REQ.format(self._twin_request_id).encode('utf-8'), '{{}}') - - def is_connected(self): - if self._connected == True: - return True - return False - - def set_model_id(self,model): - self._model_id=model - - def send_property(self, payload): - self._logger.debug('Sending properties {}'.format(json.dumps(payload))) - self._mqtt_client.publish( - HubTopics.PROP_REPORT.format(time()).encode('utf-8'), json.dumps(payload)) - - def send_telemetry(self,payload,properties=None): - topic = 'devices/{}/messages/events/?$.ct={}&$.ce={}'.format(self._device_id,self._content_type,self._content_encoding) - if properties is not None: - for prop in properties: - topic+='{}={}&'.format(encode_uri_component(prop),encode_uri_component(properties[prop])) - - topic=topic[:-1] - self._mqtt_client.publish(topic.encode('utf-8'),json.dumps(payload).encode('utf-8')) - - def on(self, event, callback): - self._events[event] = callback - - def listen(self): - if not self.is_connected(): - return - self._mqtt_client.ping() - self._mqtt_client.wait_msg() - sleep(1) - - def on_properties_update(self, patch): - try: - prop_cb = self._events[IoTCEvents.PROPERTIES] - except: - return - - for prop in patch: - if prop == '$version': - continue - ret = prop_cb(prop, patch[prop]['value']) - if ret: - self._logger.debug('Acknowledging {}'.format(prop)) - self.send_property({ - '{}'.format(prop): { - "value": patch[prop]["value"], - 'status': 'completed', - 'desiredVersion': patch['$version'], - 'message': 'Property received'} - }) - else: - self._logger.debug( - 'Property "{}" unsuccessfully processed'.format(prop)) - - def _cmd_resp(self, command:Command,value): - self._logger.debug('Responding to command "{}" request'.format(command.name)) - self.send_property({ - '{}'.format(command.name): { - 'value': value, - 'requestId': command.request_id - } - }) - - def _cmd_ack(self,command:Command): - self._logger.debug('Acknowledging command {}'.format(command.name)) - self._mqtt_client.publish('$iothub/methods/res/{}/?$rid={}'.format(200,command.request_id).encode('utf-8'),'') - - def _on_commands(self,command:Command): - try: - cmd_cb = self._events[IoTCEvents.COMMANDS] - except KeyError: - return - - self._logger.debug( - 'Received command {}'.format(command.name)) - self._cmd_ack(command) - - cmd_cb(command, self._cmd_resp) - - def _on_enqueued_commands(self,command:Command): - try: - cmd_cb = self._events[IoTCEvents.ENQUEUED_COMMANDS] - except KeyError: - return - - self._logger.debug( - 'Received enqueued command {}'.format(command.name)) - self._cmd_ack(command) - - cmd_cb(command) diff --git a/iotc/logger.py b/iotc/logger.py deleted file mode 100644 index 0824d1f..0000000 --- a/iotc/logger.py +++ /dev/null @@ -1,23 +0,0 @@ -class IoTCLogLevel: - DISABLED = 1 - API_ONLY = 2 - ALL = 3 - - -class ConsoleLogger: - def __init__(self, log_level=IoTCLogLevel.API_ONLY): - self._log_level = log_level - - def _log(self, message): - print(message) - - def info(self, message): - if self._log_level != IoTCLogLevel.DISABLED: - self._log(message) - - def debug(self, message): - if self._log_level == IoTCLogLevel.ALL: - self._log(message) - - def set_log_level(self, log_level): - self._log_level = log_level diff --git a/iotc/provision.py b/iotc/provision.py index e1e4f89..efebc67 100644 --- a/iotc/provision.py +++ b/iotc/provision.py @@ -1,51 +1,27 @@ import sys import gc - +gc.collect() +import json +from iotc.constants import IoTCConnectType,encode_uri_component,ConsoleLogger,IoTCLogLevel +import ubinascii +import hashlib +from iotc.hmac import new as hmac +gc.collect() try: from utime import time, sleep - gc.collect() except: print('ERROR: missing dependency `utime`') sys.exit(1) +gc.collect() + try: import urequests - gc.collect() except: import upip upip.install('micropython-urequests') import urequests - -import json - -unsafe = { - '?': '%3F', - ' ': '%20', - '$': '%24', - '%': '%25', - '&': '%26', - "\'": '%27', - '/': '%2F', - ':': '%3A', - ';': '%3B', - '+': '%2B', - '=': '%3D', - '@': '%40' -} - -def encode_uri_component(string): - ret = '' - for char in string: - if char in unsafe: - char = unsafe[char] - ret = '{}{}'.format(ret, char) - return ret - gc.collect() -class IoTCConnectType: - SYMM_KEY = 1 - DEVICE_KEY = 2 - x509_CERT = 3 class Credentials: @@ -78,7 +54,10 @@ class ProvisioningClient(): self._registration_id = registration_id self._credentials_type = credentials_type self._api_version = '2019-01-15' - self._logger = logger + if logger is not None: + self._logger=logger + else: + self._logger=ConsoleLogger(IoTCLogLevel.DISABLED) if model_id is not None: self._model_id = model_id @@ -176,9 +155,6 @@ class ProvisioningClient(): return None def _compute_key(self, key, payload): - import ubinascii - import hashlib - from iotc.hmac import new as hmac try: secret = ubinascii.a2b_base64(key) except: