diff --git a/.gitignore b/.gitignore index 003f1cc..62c10db 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,10 @@ exp.py # publish *.egg-info build -dist \ No newline at end of file +dist + +# virtualenv +Include/ +Lib/ +Scripts/ +pyvenv.cfg \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..95605e9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +azure-iot-device \ No newline at end of file diff --git a/samples/py3.py b/samples/py3.py index 71eb588..06eaa4a 100644 --- a/samples/py3.py +++ b/samples/py3.py @@ -6,41 +6,42 @@ import sys from random import randint config = configparser.ConfigParser() -config.read(os.path.join(os.path.dirname(__file__),'samples.ini')) +config.read(os.path.join(os.path.dirname(__file__), "samples.ini")) -if config['DEFAULT'].getboolean('Local'): - sys.path.insert(0, 'src') +if config["DEFAULT"].getboolean("Local"): + sys.path.insert(0, "src") from iotc import IOTCConnectType, IOTCLogLevel, IOTCEvents from iotc.aio import IoTCClient -device_id = config['DEVICE_M3']['DeviceId'] -scope_id = config['DEVICE_M3']['ScopeId'] -key = config['DEVICE_M3']['DeviceKey'] +device_id = config["DEVICE_M3"]["DeviceId"] +scope_id = config["DEVICE_M3"]["ScopeId"] +key = config["DEVICE_M3"]["DeviceKey"] # optional model Id for auto-provisioning try: - model_id = config['DEVICE_M3']['ModelId'] + model_id = config["DEVICE_M3"]["ModelId"] except: model_id = None -async def on_props(propName, propValue): - print(propValue) +async def on_props(property_name, property_value, component_name): + print("Received {}:{}".format(property_name, property_value)) return True async def on_commands(command, ack): print(command.name) - await ack(command.name, 'Command received', command.request_id) + await ack(command.name, "Command received", command.request_id) -async def on_enqueued_commands(command_name,command_data): + +async def on_enqueued_commands(command_name, command_data): print(command_name) print(command_data) + # change connect type to reflect the used key (device or group) -client = IoTCClient(device_id, scope_id, - IOTCConnectType.IOTC_CONNECT_SYMM_KEY, key) +client = IoTCClient(device_id, scope_id, IOTCConnectType.IOTC_CONNECT_DEVICE_KEY, key) if model_id != None: client.set_model_id(model_id) @@ -54,13 +55,18 @@ client.on(IOTCEvents.IOTC_ENQUEUED_COMMAND, on_enqueued_commands) async def main(): await client.connect() + await client.send_property({"writeableProp": 50}) while client.is_connected(): - await client.send_telemetry({ - 'accelerometerX': str(randint(20, 45)), - 'accelerometerY': str(randint(20, 45)), - "accelerometerZ": str(randint(20, 45)) - }) + await client.send_telemetry( + { + "acceleration": { + "x": str(randint(20, 45)), + "y": str(randint(20, 45)), + "z": str(randint(20, 45)), + } + } + ) await asyncio.sleep(3) - + asyncio.run(main()) diff --git a/setup.py b/setup.py index 9cbed7f..0b57759 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ import sys with open("README.md", "r") as fh: long_description = fh.read() -version = "1.0.4" +version = "1.0.5" setuptools.setup( name='iotc', diff --git a/src/iotc/__init__.py b/src/iotc/__init__.py index 5632149..5b35709 100644 --- a/src/iotc/__init__.py +++ b/src/iotc/__init__.py @@ -1,4 +1,3 @@ - import sys import threading import time @@ -12,9 +11,11 @@ from datetime import datetime __version__ = pkg_resources.get_distribution("iotc").version if sys.version_info[0] < 3: import urllib + quote = urllib.quote_plus else: import urllib.parse + quote = urllib.parse.quote_plus try: @@ -48,7 +49,6 @@ except ImportError: sys.exit() - class IOTCConnectType: IOTC_CONNECT_SYMM_KEY = 1 IOTC_CONNECT_X509_CERT = 2 @@ -78,8 +78,8 @@ class IOTCMessageStatus: class IOTCEvents: - IOTC_COMMAND = 2, - IOTC_PROPERTIES = 4, + IOTC_COMMAND = (2,) + IOTC_PROPERTIES = (4,) IOTC_ENQUEUED_COMMAND = 8 @@ -113,8 +113,8 @@ class AbstractClient: self._prop_thread = None self._cmd_thread = None self._enqueued_cmd_thread = None - self._content_type='application%2Fjson' - self._content_encoding='utf-8' + self._content_type = "application%2Fjson" + self._content_encoding = "utf-8" self._global_endpoint = "global.azure-devices-provisioning.net" def is_connected(self): @@ -124,7 +124,9 @@ class AbstractClient: :rtype: bool """ if not self._device_client: - print("ERROR: A connection was never attempted. You need to first call connect() before querying the connection state") + print( + "ERROR: A connection was never attempted. You need to first call connect() before querying the connection state" + ) else: return self._device_client.connected @@ -149,14 +151,14 @@ class AbstractClient: """ self._logger.set_log_level(log_level) - def set_content_type(self,content_type): + def set_content_type(self, content_type): self._content_type = quote(content_type) - def set_content_encoding(self,content_encoding): + def set_content_encoding(self, content_encoding): self._content_encoding = content_encoding - def _prepare_message(self,payload,properties): - msg = Message(payload,uuid.uuid4(),self._content_encoding,self._content_type) + def _prepare_message(self, payload, properties): + msg = Message(payload, uuid.uuid4(), self._content_encoding, self._content_type) if bool(properties): for prop in properties: msg.custom_properties[prop] = properties[prop] @@ -171,99 +173,152 @@ class AbstractClient: self._events[eventname] = callback return 0 -class IoTCClient(AbstractClient): + +class IoTCClient(AbstractClient): def __init__(self, device_id, scope_id, cred_type, key_or_cert, logger=None): - AbstractClient.__init__(self,device_id, scope_id, cred_type, key_or_cert) + AbstractClient.__init__(self, device_id, scope_id, cred_type, key_or_cert) if logger is None: self._logger = ConsoleLogger(IOTCLogLevel.IOTC_LOGGING_API_ONLY) else: - if hasattr(logger, "info") and hasattr(logger, "debug") and hasattr(logger, "set_log_level"): + if ( + hasattr(logger, "info") + and hasattr(logger, "debug") + and hasattr(logger, "set_log_level") + ): self._logger = logger else: - print("ERROR: Logger object has unsupported format. It must implement the following functions\n\ - info(message);\ndebug(message);\nset_log_level(message);") + print( + "ERROR: Logger object has unsupported format. It must implement the following functions\n\ + info(message);\ndebug(message);\nset_log_level(message);" + ) sys.exit() + def _handle_property_ack( + self, + callback, + property_name, + property_value, + property_version, + component_name=None, + ): + ret = callback(property_name, property_value, component_name) + if ret: + if component_name is not None: + self._logger.debug("Acknowledging {}".format(property_name)) + self.send_property( + { + "{}".format(component_name): { + "{}".format(property_name): { + "ac": 200, + "ad": "Property received", + "av": property_version, + "value": property_value, + } + } + } + ) + else: + self._logger.debug("Acknowledging {}".format(property_name)) + self.send_property( + { + "{}".format(property_name): { + "ac": 200, + "ad": "Property received", + "av": property_version, + "value": property_value, + } + } + ) + else: + self._logger.debug('Property "{}" unsuccessfully processed'.format(property_name)) + def _on_properties(self): - self._logger.debug('Setup properties listener') + self._logger.debug("Setup properties listener") while True: try: prop_cb = self._events[IOTCEvents.IOTC_PROPERTIES] except KeyError: - self._logger.debug('Properties callback not found') + self._logger.debug("Properties callback not found") time.sleep(10) continue patch = self._device_client.receive_twin_desired_properties_patch() - self._logger.debug( - '\nReceived desired properties. {}\n'.format(patch)) + self._logger.debug("\nReceived desired properties. {}\n".format(patch)) for prop in patch: - if prop == '$version': + is_component = False + if prop == "$version": continue - ret = prop_cb(prop, patch[prop]['value']) - if ret: - self._logger.debug('Acknowledging {}'.format(prop)) - self.send_property({ - '{}'.format(prop): { - "ac": 200, - "ad": 'Property received', - "av": patch['$version'], - "value": patch[prop]["value"] - } - }) + # check if component + try: + is_component = patch[prop]["__t"] + del patch[prop]["__t"] + except KeyError: + pass + + if is_component: + for component_prop in patch[prop]: + self._logger.debug( + 'In component "{}" for property "{}"'.format( + prop, component_prop + ) + ) + self._handle_property_ack( + prop_cb, + component_prop, + patch[prop][component_prop]["value"], + patch["$version"], + prop, + ) else: - self._logger.debug( - 'Property "{}" unsuccessfully processed'.format(prop)) + self._handle_property_ack( + prop_cb, prop, patch[prop]["value"], patch["$version"] + ) def _cmd_ack(self, name, value, request_id): - self.send_property({ - '{}'.format(name): { - 'value': value, - 'requestId': request_id - } - }) + self.send_property( + {"{}".format(name): {"value": value, "requestId": request_id}} + ) def _on_commands(self): - self._logger.debug('Setup commands listener') + self._logger.debug("Setup commands listener") while True: try: cmd_cb = self._events[IOTCEvents.IOTC_COMMAND] except KeyError: - self._logger.debug('Commands callback not found') + self._logger.debug("Commands callback not found") time.sleep(10) continue # Wait for unknown method calls method_request = self._device_client.receive_method_request() - self._logger.debug( - 'Received command {}'.format(method_request.name)) - self._device_client.send_method_response(MethodResponse.create_from_method_request( - method_request, 200, { - 'result': True, 'data': 'Command received'} - )) + self._logger.debug("Received command {}".format(method_request.name)) + self._device_client.send_method_response( + MethodResponse.create_from_method_request( + method_request, 200, {"result": True, "data": "Command received"} + ) + ) cmd_cb(method_request, self._cmd_ack) def _on_enqueued_commands(self): - self._logger.debug('Setup enqueued commands listener') + self._logger.debug("Setup enqueued commands listener") while True: try: enqueued_cmd_cb = self._events[IOTCEvents.IOTC_ENQUEUED_COMMAND] except KeyError: - self._logger.debug('Enqueued commands callback not found') + self._logger.debug("Enqueued commands callback not found") time.sleep(10) continue # Wait for unknown method calls c2d = self._device_client.receive_message() - c2d_name=c2d.custom_properties['method-name'].split(':')[1] - self._logger.debug( - 'Received enqueued command {}'.format(c2d_name)) - enqueued_cmd_cb(c2d_name,c2d.data) + c2d_name = c2d.custom_properties["method-name"].split(":")[1] + self._logger.debug("Received enqueued command {}".format(c2d_name)) + enqueued_cmd_cb(c2d_name, c2d.data) def _send_message(self, payload, properties): - msg = self._prepare_message(payload,properties) + msg = self._prepare_message(payload, properties) self._device_client.send_message(msg) def send_property(self, payload): @@ -271,7 +326,7 @@ class IoTCClient(AbstractClient): Send a property message :param dict payload: The properties payload. Can contain multiple properties in the form {'':{'value':''}} """ - self._logger.debug('Sending property {}'.format(json.dumps(payload))) + self._logger.debug("Sending property {}".format(json.dumps(payload))) self._device_client.patch_twin_reported_properties(payload) def send_telemetry(self, payload, properties=None): @@ -280,7 +335,7 @@ class IoTCClient(AbstractClient): :param dict payload: The telemetry payload. Can contain multiple telemetry fields in the form {'':,...,'':} :param dict optional properties: An object with custom properties to add to the message. """ - self._logger.info('Sending telemetry message: {}'.format(payload)) + self._logger.info("Sending telemetry message: {}".format(payload)) self._send_message(json.dumps(payload), properties) def connect(self): @@ -288,58 +343,84 @@ class IoTCClient(AbstractClient): Connects the device. :raises exception: If connection fails """ - if self._cred_type in (IOTCConnectType.IOTC_CONNECT_DEVICE_KEY, IOTCConnectType.IOTC_CONNECT_SYMM_KEY): + if self._cred_type in ( + IOTCConnectType.IOTC_CONNECT_DEVICE_KEY, + IOTCConnectType.IOTC_CONNECT_SYMM_KEY, + ): if self._cred_type == IOTCConnectType.IOTC_CONNECT_SYMM_KEY: self._key_or_cert = self._compute_derived_symmetric_key( - self._key_or_cert, self._device_id) - self._logger.debug('Device key: {}'.format(self._key_or_cert)) + self._key_or_cert, self._device_id + ) + self._logger.debug("Device key: {}".format(self._key_or_cert)) - self._provisioning_client = ProvisioningDeviceClient.create_from_symmetric_key( - self._global_endpoint, self._device_id, self._scope_id, self._key_or_cert) + self._provisioning_client = ( + ProvisioningDeviceClient.create_from_symmetric_key( + self._global_endpoint, + self._device_id, + self._scope_id, + self._key_or_cert, + ) + ) else: - self._key_file = self._key_or_cert['key_file'] - self._cert_file = self._key_or_cert['cert_file'] + self._key_file = self._key_or_cert["key_file"] + self._cert_file = self._key_or_cert["cert_file"] try: - self._cert_phrase = self._key_or_cert['cert_phrase'] + self._cert_phrase = self._key_or_cert["cert_phrase"] x509 = X509(self._cert_file, self._key_file, self._cert_phrase) except: self._logger.debug( - 'No passphrase available for certificate. Trying without it') + "No passphrase available for certificate. Trying without it" + ) x509 = X509(self._cert_file, self._key_file) # Certificate provisioning - self._provisioning_client = ProvisioningDeviceClient.create_from_x509_certificate( - provisioning_host=self._global_endpoint, registration_id=self._device_id, id_scope=self._scope_id, x509=x509) + self._provisioning_client = ( + ProvisioningDeviceClient.create_from_x509_certificate( + provisioning_host=self._global_endpoint, + registration_id=self._device_id, + id_scope=self._scope_id, + x509=x509, + ) + ) if self._model_id: self._provisioning_client.provisioning_payload = { - 'iotcModelId': self._model_id} + "iotcModelId": self._model_id + } try: registration_result = self._provisioning_client.register() assigned_hub = registration_result.registration_state.assigned_hub self._logger.debug(assigned_hub) - self._hub_conn_string = 'HostName={};DeviceId={};SharedAccessKey={}'.format( - assigned_hub, self._device_id, self._key_or_cert) + self._hub_conn_string = "HostName={};DeviceId={};SharedAccessKey={}".format( + assigned_hub, self._device_id, self._key_or_cert + ) self._logger.debug( - 'IoTHub Connection string: {}'.format(self._hub_conn_string)) + "IoTHub Connection string: {}".format(self._hub_conn_string) + ) - if self._cred_type in (IOTCConnectType.IOTC_CONNECT_DEVICE_KEY, IOTCConnectType.IOTC_CONNECT_SYMM_KEY): + if self._cred_type in ( + IOTCConnectType.IOTC_CONNECT_DEVICE_KEY, + IOTCConnectType.IOTC_CONNECT_SYMM_KEY, + ): self._device_client = IoTHubDeviceClient.create_from_connection_string( - self._hub_conn_string) + self._hub_conn_string + ) else: self._device_client = IoTHubDeviceClient.create_from_x509_certificate( - x509=x509, hostname=assigned_hub, device_id=registration_result.registration_state.device_id) + x509=x509, + hostname=assigned_hub, + device_id=registration_result.registration_state.device_id, + ) except: t, v, tb = sys.exc_info() - self._logger.info( - 'ERROR: Failed to get device provisioning information') + self._logger.info("ERROR: Failed to get device provisioning information") raise t(v) # Connect to iothub try: self._device_client.connect() - self._logger.debug('Device connected') + self._logger.debug("Device connected") except: t, v, tb = sys.exc_info() - self._logger.info('ERROR: Failed to connect to Hub') + self._logger.info("ERROR: Failed to connect to Hub") raise t(v) # setup listeners @@ -361,8 +442,11 @@ class IoTCClient(AbstractClient): try: secret = base64.b64decode(secret) except: - self._logger.debug( - "ERROR: broken base64 secret => `" + secret + "`") + self._logger.debug("ERROR: broken base64 secret => `" + secret + "`") sys.exit() - return base64.b64encode(hmac.new(secret, msg=reg_id.encode('utf8'), digestmod=hashlib.sha256).digest()).decode('utf-8') + return base64.b64encode( + hmac.new( + secret, msg=reg_id.encode("utf8"), digestmod=hashlib.sha256 + ).digest() + ).decode("utf-8") diff --git a/src/iotc/aio/__init__.py b/src/iotc/aio/__init__.py index 7b7aa7a..e034a62 100644 --- a/src/iotc/aio/__init__.py +++ b/src/iotc/aio/__init__.py @@ -1,7 +1,7 @@ import sys import asyncio import pkg_resources -from .. import AbstractClient,IOTCLogLevel, IOTCEvents, IOTCConnectType +from .. import AbstractClient, IOTCLogLevel, IOTCEvents, IOTCConnectType from azure.iot.device import X509, MethodResponse, Message from azure.iot.device.aio import IoTHubDeviceClient, ProvisioningDeviceClient @@ -58,98 +58,151 @@ class ConsoleLogger: class IoTCClient(AbstractClient): - def __init__(self, device_id, scope_id, cred_type, key_or_cert, logger=None): - AbstractClient.__init__(self,device_id, scope_id, cred_type, key_or_cert) + AbstractClient.__init__(self, device_id, scope_id, cred_type, key_or_cert) if logger is None: self._logger = ConsoleLogger(IOTCLogLevel.IOTC_LOGGING_API_ONLY) else: - if hasattr(logger, "info") and hasattr(logger, "debug") and hasattr(logger, "set_log_level"): + if ( + hasattr(logger, "info") + and hasattr(logger, "debug") + and hasattr(logger, "set_log_level") + ): self._logger = logger else: - print("ERROR: Logger object has unsupported format. It must implement the following functions\n\ - info(message);\ndebug(message);\nset_log_level(message);") + print( + "ERROR: Logger object has unsupported format. It must implement the following functions\n\ + info(message);\ndebug(message);\nset_log_level(message);" + ) sys.exit() + async def _handle_property_ack( + self, + callback, + property_name, + property_value, + property_version, + component_name=None, + ): + ret = await callback(property_name, property_value, component_name) + if ret: + if component_name is not None: + await self._logger.debug("Acknowledging {}".format(property_name)) + await self.send_property( + { + "{}".format(component_name): { + "{}".format(property_name): { + "ac": 200, + "ad": "Property received", + "av": property_version, + "value": property_value, + } + } + } + ) + else: + await self._logger.debug("Acknowledging {}".format(property_name)) + await self.send_property( + { + "{}".format(property_name): { + "ac": 200, + "ad": "Property received", + "av": property_version, + "value": property_value, + } + } + ) + else: + await self._logger.debug( + 'Property "{}" unsuccessfully processed'.format(property_name) + ) + async def _on_properties(self): - await self._logger.debug('Setup properties listener') + await self._logger.debug("Setup properties listener") while True: try: prop_cb = self._events[IOTCEvents.IOTC_PROPERTIES] except KeyError: - await self._logger.debug('Properties callback not found') + await self._logger.debug("Properties callback not found") await asyncio.sleep(10) continue patch = await self._device_client.receive_twin_desired_properties_patch() - await self._logger.debug('Received desired properties. {}'.format(patch)) + await self._logger.debug("Received desired properties. {}".format(patch)) for prop in patch: - if prop == '$version': + is_component = False + if prop == "$version": continue - ret = await prop_cb(prop, patch[prop]['value']) - if ret: - await self._logger.debug('Acknowledging {}'.format(prop)) - await self.send_property({ - '{}'.format(prop): { - "ac": 200, - "ad": 'Property received', - "av": patch['$version'], - "value": patch[prop]["value"]} - }) + # check if component + try: + is_component = patch[prop]["__t"] + del patch[prop]["__t"] + except KeyError: + pass + + if is_component: + for component_prop in patch[prop]: + await self._logger.debug( + 'In component "{}" for property "{}"'.format( + prop, component_prop + ) + ) + await self._handle_property_ack( + prop_cb, + component_prop, + patch[prop][component_prop]["value"], + patch["$version"], + prop, + ) else: - await self._logger.debug( - 'Property "{}" unsuccessfully processed'.format(prop)) + await self._handle_property_ack( + prop_cb, prop, patch[prop]["value"], patch["$version"] + ) async def _cmd_ack(self, name, value, requestId): - await self.send_property({ - '{}'.format(name): { - 'value': value, - 'requestId': requestId - } - }) + await self.send_property( + {"{}".format(name): {"value": value, "requestId": requestId}} + ) async def _on_commands(self): - await self._logger.debug('Setup commands listener') + await self._logger.debug("Setup commands listener") while True: try: cmd_cb = self._events[IOTCEvents.IOTC_COMMAND] except KeyError: - await self._logger.debug('Commands callback not found') + await self._logger.debug("Commands callback not found") await asyncio.sleep(10) continue # Wait for unknown method calls - method_request = ( - await self._device_client.receive_method_request() + method_request = await self._device_client.receive_method_request() + await self._logger.debug("Received command {}".format(method_request.name)) + await self._device_client.send_method_response( + MethodResponse.create_from_method_request( + method_request, 200, {"result": True, "data": "Command received"} + ) ) - await self._logger.debug( - 'Received command {}'.format(method_request.name)) - await self._device_client.send_method_response(MethodResponse.create_from_method_request( - method_request, 200, { - 'result': True, 'data': 'Command received'} - )) await cmd_cb(method_request, self._cmd_ack) async def _on_enqueued_commands(self): - await self._logger.debug('Setup enqueued commands listener') + await self._logger.debug("Setup enqueued commands listener") while True: try: enqueued_cmd_cb = self._events[IOTCEvents.IOTC_ENQUEUED_COMMAND] except KeyError: - await self._logger.debug('Enqueued commands callback not found') + await self._logger.debug("Enqueued commands callback not found") await asyncio.sleep(10) continue # Wait for unknown method calls c2d = await self._device_client.receive_message() - c2d_name=c2d.custom_properties['method-name'].split(':')[1] - await self._logger.debug( - 'Received enqueued command {}'.format(c2d_name)) - await enqueued_cmd_cb(c2d_name,c2d.data) + c2d_name = c2d.custom_properties["method-name"].split(":")[1] + await self._logger.debug("Received enqueued command {}".format(c2d_name)) + await enqueued_cmd_cb(c2d_name, c2d.data) async def _send_message(self, payload, properties): - msg = self._prepare_message(payload,properties) + msg = self._prepare_message(payload, properties) await self._device_client.send_message(msg) async def send_property(self, payload): @@ -157,7 +210,7 @@ class IoTCClient(AbstractClient): Send a property message :param dict payload: The properties payload. Can contain multiple properties in the form {'':{'value':''}} """ - await self._logger.debug('Sending property {}'.format(json.dumps(payload))) + await self._logger.debug("Sending property {}".format(json.dumps(payload))) await self._device_client.patch_twin_reported_properties(payload) async def send_telemetry(self, payload, properties=None): @@ -166,7 +219,7 @@ class IoTCClient(AbstractClient): :param dict payload: The telemetry payload. Can contain multiple telemetry fields in the form {'':,...,'':} :param dict optional properties: An object with custom properties to add to the message. """ - await self._logger.info('Sending telemetry message: {}'.format(payload)) + await self._logger.info("Sending telemetry message: {}".format(payload)) await self._send_message(json.dumps(payload), properties) async def connect(self): @@ -174,58 +227,88 @@ class IoTCClient(AbstractClient): Connects the device. :raises exception: If connection fails """ - if self._cred_type in (IOTCConnectType.IOTC_CONNECT_DEVICE_KEY, IOTCConnectType.IOTC_CONNECT_SYMM_KEY): + if self._cred_type in ( + IOTCConnectType.IOTC_CONNECT_DEVICE_KEY, + IOTCConnectType.IOTC_CONNECT_SYMM_KEY, + ): if self._cred_type == IOTCConnectType.IOTC_CONNECT_SYMM_KEY: self._key_or_cert = await self._compute_derived_symmetric_key( - self._key_or_cert, self._device_id) + self._key_or_cert, self._device_id + ) - await self._logger.debug('Device key: {}'.format(self._key_or_cert)) + await self._logger.debug("Device key: {}".format(self._key_or_cert)) - self._provisioning_client = ProvisioningDeviceClient.create_from_symmetric_key( - self._global_endpoint, self._device_id, self._scope_id, self._key_or_cert) + self._provisioning_client = ( + ProvisioningDeviceClient.create_from_symmetric_key( + self._global_endpoint, + self._device_id, + self._scope_id, + self._key_or_cert, + ) + ) else: - self._key_file = self._key_or_cert['key_file'] - self._cert_file = self._key_or_cert['cert_file'] + self._key_file = self._key_or_cert["key_file"] + self._cert_file = self._key_or_cert["cert_file"] try: - self._cert_phrase = self._key_or_cert['cert_phrase'] + self._cert_phrase = self._key_or_cert["cert_phrase"] x509 = X509(self._cert_file, self._key_file, self._cert_phrase) except: await self._logger.debug( - 'No passphrase available for certificate. Trying without it') + "No passphrase available for certificate. Trying without it" + ) x509 = X509(self._cert_file, self._key_file) # Certificate provisioning - self._provisioning_client = ProvisioningDeviceClient.create_from_x509_certificate( - provisioning_host=self._global_endpoint, registration_id=self._device_id, id_scope=self._scope_id, x509=x509) + self._provisioning_client = ( + ProvisioningDeviceClient.create_from_x509_certificate( + provisioning_host=self._global_endpoint, + registration_id=self._device_id, + id_scope=self._scope_id, + x509=x509, + ) + ) if self._model_id: print("Provision model Id") self._provisioning_client.provisioning_payload = { - 'iotcModelId': self._model_id} + "iotcModelId": self._model_id + } try: registration_result = await self._provisioning_client.register() assigned_hub = registration_result.registration_state.assigned_hub await self._logger.debug(assigned_hub) - self._hub_conn_string = 'HostName={};DeviceId={};SharedAccessKey={}'.format( - assigned_hub, self._device_id, self._key_or_cert) + self._hub_conn_string = "HostName={};DeviceId={};SharedAccessKey={}".format( + assigned_hub, self._device_id, self._key_or_cert + ) await self._logger.debug( - 'IoTHub Connection string: {}'.format(self._hub_conn_string)) + "IoTHub Connection string: {}".format(self._hub_conn_string) + ) - if self._cred_type in (IOTCConnectType.IOTC_CONNECT_DEVICE_KEY, IOTCConnectType.IOTC_CONNECT_SYMM_KEY): + if self._cred_type in ( + IOTCConnectType.IOTC_CONNECT_DEVICE_KEY, + IOTCConnectType.IOTC_CONNECT_SYMM_KEY, + ): self._device_client = IoTHubDeviceClient.create_from_connection_string( - self._hub_conn_string) + self._hub_conn_string + ) else: self._device_client = IoTHubDeviceClient.create_from_x509_certificate( - x509=x509, hostname=assigned_hub, device_id=registration_result.registration_state.device_id) + x509=x509, + hostname=assigned_hub, + device_id=registration_result.registration_state.device_id, + ) except: await self._logger.info( - 'ERROR: Failed to get device provisioning information') + "ERROR: Failed to get device provisioning information" + ) sys.exit() # Connect to iothub try: await self._device_client.connect() - await self._logger.debug('Device connected') + await self._logger.debug("Device connected") + self._twin = await self._device_client.get_twin() + await self._logger.debug("Current twin: {}".format(self._twin)) except: - await self._logger.info('ERROR: Failed to connect to Hub') + await self._logger.info("ERROR: Failed to connect to Hub") sys.exit() # setup listeners @@ -243,8 +326,11 @@ class IoTCClient(AbstractClient): try: secret = base64.b64decode(secret) except: - await self._logger.debug( - "ERROR: broken base64 secret => `" + secret + "`") + await self._logger.debug("ERROR: broken base64 secret => `" + secret + "`") sys.exit() - return base64.b64encode(hmac.new(secret, msg=reg_id.encode('utf8'), digestmod=hashlib.sha256).digest()).decode('utf-8') + return base64.b64encode( + hmac.new( + secret, msg=reg_id.encode("utf8"), digestmod=hashlib.sha256 + ).digest() + ).decode("utf-8") diff --git a/test/v2/test_basic2.py b/test/v2/test_basic2.py index ac88b76..dcbc152 100644 --- a/test/v2/test_basic2.py +++ b/test/v2/test_basic2.py @@ -103,6 +103,9 @@ class NewDeviceClient(): def patch_twin_reported_properties(self, payload): return True + def get_twin(self): + return 'Twin' + def init(mocker): client = IoTCClient(device_id, scopeId, @@ -139,7 +142,7 @@ def test_onproperties_before(mocker): mocker.patch.object(client, 'send_property', mock.Mock()) client.on(IOTCEvents.IOTC_PROPERTIES, on_props) client.connect() - on_props.assert_called_with('prop1', 40) + on_props.assert_called_with('prop1', 40,None) def test_onproperties_after(mocker): @@ -151,7 +154,7 @@ def test_onproperties_after(mocker): client.on(IOTCEvents.IOTC_PROPERTIES, on_props) # give at least 10 seconds for the new listener to be recognized. assign the listener after connection is discouraged time.sleep(11) - on_props.assert_called_with('prop1', 40) + on_props.assert_called_with('prop1', 40,None) def test_onCommands_before(mocker): diff --git a/test/v3/test_basic3.py b/test/v3/test_basic3.py index c222996..3513b4e 100644 --- a/test/v3/test_basic3.py +++ b/test/v3/test_basic3.py @@ -9,60 +9,61 @@ import sys from contextlib import suppress from azure.iot.device.provisioning.models import RegistrationResult -from azure.iot.device.iothub.models import MethodRequest,Message +from azure.iot.device.iothub.models import MethodRequest, Message config = configparser.ConfigParser() -config.read(os.path.join(os.path.dirname(__file__), '../tests.ini')) +config.read(os.path.join(os.path.dirname(__file__), "../tests.ini")) -if config['TESTS'].getboolean('Local'): - sys.path.insert(0, 'src') +if config["TESTS"].getboolean("Local"): + sys.path.insert(0, "src") from iotc import IOTCConnectType, IOTCLogLevel, IOTCEvents from iotc.aio import IoTCClient try: - groupKey = config['TESTS']['GroupKey'] + groupKey = config["TESTS"]["GroupKey"] except: - groupKey='groupKey' + groupKey = "groupKey" try: - deviceKey = config['TESTS']['DeviceKey'] + deviceKey = config["TESTS"]["DeviceKey"] except: - deviceKey='kPufjjN/EMoyKcNiAXvlTz8H61mlhSnmvoF6dxhnysA=' + deviceKey = "kPufjjN/EMoyKcNiAXvlTz8H61mlhSnmvoF6dxhnysA=" try: - device_id = config['TESTS']['DeviceId'] + device_id = config["TESTS"]["DeviceId"] except: - device_id='device_id' + device_id = "device_id" try: - scopeId = config['TESTS']['ScopeId'] + scopeId = config["TESTS"]["ScopeId"] except: - scopeId='scopeId' + scopeId = "scopeId" try: - assignedHub = config['TESTS']['AssignedHub'] + assignedHub = config["TESTS"]["AssignedHub"] except: - assignedHub='assignedHub' + assignedHub = "assignedHub" try: - expectedHub = config['TESTS']['ExpectedHub'] + expectedHub = config["TESTS"]["ExpectedHub"] except: - expectedHub='HostName=assignedHub;DeviceId=device_id;SharedAccessKey=kPufjjN/EMoyKcNiAXvlTz8H61mlhSnmvoF6dxhnysA=' + expectedHub = "HostName=assignedHub;DeviceId=device_id;SharedAccessKey=kPufjjN/EMoyKcNiAXvlTz8H61mlhSnmvoF6dxhnysA=" -propPayload = {'prop1': {'value': 40}, '$version': 5} +propPayload = {"prop1": {"value": 40}, "$version": 5} -cmdRequestId = 'abcdef' -cmdName = 'command1' -cmdPayload = 'payload' +cmdRequestId = "abcdef" +cmdName = "command1" +cmdPayload = "payload" methodRequest = MethodRequest(cmdRequestId, cmdName, cmdPayload) -enqueued_method_name='test:enqueued' +enqueued_method_name = "test:enqueued" -enqueued_message = Message('test_enqueued') -enqueued_message.custom_properties['method-name']=enqueued_method_name +enqueued_message = Message("test_enqueued") +enqueued_message.custom_properties["method-name"] = enqueued_method_name -class NewRegState(): + +class NewRegState: def __init__(self): self.assigned_hub = assignedHub @@ -70,13 +71,13 @@ class NewRegState(): return self.assigned_hub -class NewProvClient(): +class NewProvClient: async def register(self): - reg = RegistrationResult('3o375i827i852', 'assigned', NewRegState()) + reg = RegistrationResult("3o375i827i852", "assigned", NewRegState()) return reg -class NewDeviceClient(): +class NewDeviceClient: async def connect(self): return True @@ -98,6 +99,9 @@ class NewDeviceClient(): async def patch_twin_reported_properties(self, payload): return True + async def get_twin(self): + return "Twin" + def async_return(result): f = asyncio.Future() @@ -113,20 +117,24 @@ async def stop_threads(client): @pytest.mark.asyncio def init(mocker): - client = IoTCClient(device_id, scopeId, - IOTCConnectType.IOTC_CONNECT_SYMM_KEY, groupKey) - mocker.patch('iotc.aio.ProvisioningDeviceClient.create_from_symmetric_key', - return_value=NewProvClient()) - mocker.patch('iotc.aio.IoTHubDeviceClient.create_from_connection_string', - return_value=NewDeviceClient()) + client = IoTCClient( + device_id, scopeId, IOTCConnectType.IOTC_CONNECT_SYMM_KEY, groupKey + ) + mocker.patch( + "iotc.aio.ProvisioningDeviceClient.create_from_symmetric_key", + return_value=NewProvClient(), + ) + mocker.patch( + "iotc.aio.IoTHubDeviceClient.create_from_connection_string", + return_value=NewDeviceClient(), + ) return client @pytest.mark.asyncio async def test_computeKey(mocker): client = init(mocker) - key = await client._compute_derived_symmetric_key( - groupKey, device_id) + key = await client._compute_derived_symmetric_key(groupKey, device_id) assert key == deviceKey @@ -150,13 +158,14 @@ async def test_hubConnectionString(mocker): async def test_onproperties_before(mocker): client = init(mocker) - async def onProps(propname, propvalue): - assert propname == 'prop1' + async def on_props(propname, propvalue,component_name): + assert propname == "prop1" assert propvalue == 40 + assert component_name == None await stop_threads(client) - mocker.patch.object(client, 'send_property', return_value=True) - client.on(IOTCEvents.IOTC_PROPERTIES, onProps) + mocker.patch.object(client, "send_property", return_value=True) + client.on(IOTCEvents.IOTC_PROPERTIES, on_props) await client.connect() try: await client._prop_thread @@ -169,15 +178,16 @@ async def test_onproperties_before(mocker): async def test_onproperties_after(mocker): client = init(mocker) - async def onProps(propname, propvalue): - assert propname == 'prop1' + async def on_props(propname, propvalue, component_name): + assert propname == "prop1" assert propvalue == 40 + assert component_name == None await stop_threads(client) return True - mocker.patch.object(client, 'send_property', return_value=True) + mocker.patch.object(client, "send_property", return_value=True) await client.connect() - client.on(IOTCEvents.IOTC_PROPERTIES, onProps) + client.on(IOTCEvents.IOTC_PROPERTIES, on_props) try: await client._prop_thread @@ -192,14 +202,14 @@ async def test_on_commands_before(mocker): async def onCmds(command, ack): ret = ack() - assert ret == 'mocked' + assert ret == "mocked" await stop_threads(client) return True def mockedAck(): - return 'mocked' + return "mocked" - mocker.patch.object(client, '_cmd_ack', mockedAck) + mocker.patch.object(client, "_cmd_ack", mockedAck) client.on(IOTCEvents.IOTC_COMMAND, onCmds) await client.connect() @@ -216,14 +226,14 @@ async def test_on_commands_after(mocker): async def onCmds(command, ack): ret = ack() - assert ret == 'mocked' + assert ret == "mocked" await stop_threads(client) return True def mockedAck(): - return 'mocked' + return "mocked" - mocker.patch.object(client, '_cmd_ack', mockedAck) + mocker.patch.object(client, "_cmd_ack", mockedAck) await client.connect() client.on(IOTCEvents.IOTC_COMMAND, onCmds) @@ -233,17 +243,17 @@ async def test_on_commands_after(mocker): except asyncio.CancelledError: pass + @pytest.mark.asyncio async def test_on_enqueued_commands_before(mocker): client = init(mocker) - async def on_enqs(command_name,command_data): - assert command_name == enqueued_method_name.split(':')[1] + async def on_enqs(command_name, command_data): + assert command_name == enqueued_method_name.split(":")[1] await stop_threads(client) return True - client.on(IOTCEvents.IOTC_ENQUEUED_COMMAND, on_enqs) await client.connect() try: @@ -257,12 +267,11 @@ async def test_on_enqueued_commands_after(mocker): client = init(mocker) - async def on_enqs(command_name,command_data): - assert command_name == enqueued_method_name.split(':')[1] + async def on_enqs(command_name, command_data): + assert command_name == enqueued_method_name.split(":")[1] await stop_threads(client) return True - await client.connect() client.on(IOTCEvents.IOTC_ENQUEUED_COMMAND, on_enqs) try: