From de601a55ade866363826b5c06325d8f25147685e Mon Sep 17 00:00:00 2001 From: Akash Sharma Date: Thu, 29 Dec 2022 13:19:42 -0600 Subject: [PATCH 01/10] ListDevicesRequest >> _prepare_params_for_list: (#16) modified to handle gatewayListOptions following NodeJS SDK --- clearblade/cloud/iot_v1/device_types.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index 1bdfdcb2..528aa652 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -398,7 +398,12 @@ def _prepare_params_for_list(self): if self.field_mask: params['fieldMask'] = self.field_mask if self.gateway_list_options : - params['gatewayListOptions'] = self.gateway_list_options + if 'associationsDeviceId' in self.gateway_list_options: + params['gatewayListOptions.associationsDeviceId'] = self.gateway_list_options['associationsDeviceId'] + if 'associationsGatewayId' in self.gateway_list_options: + params['gatewayListOptions.associationsGatewayId'] = self.gateway_list_options['associationsGatewayId'] + if 'gatewayType' in self.gateway_list_options: + params['gatewayListOptions.gatewayType'] = self.gateway_list_options['gatewayType'] if self.page_token: params['pageToken'] = self.page_token From 8775b04c6457cdc41ab432a495e7c72988861075 Mon Sep 17 00:00:00 2001 From: rajasd27 Date: Mon, 9 Jan 2023 12:52:21 -0600 Subject: [PATCH 02/10] updated device class and enums (#19) --- clearblade/cloud/iot_v1/__init__.py | 10 +++- clearblade/cloud/iot_v1/device_types.py | 49 +++++++++++++------ clearblade/cloud/iot_v1/devices.py | 4 +- clearblade/cloud/iot_v1/registry.py | 2 +- clearblade/cloud/iot_v1/registry_types.py | 7 +-- clearblade/cloud/iot_v1/resources.py | 44 ++++++++--------- samples/clearblade/create_device_async.py | 2 +- .../create_device_registry_async.py | 8 +-- .../clearblade/create_device_registry_sync.py | 7 ++- samples/clearblade/create_device_sync.py | 6 ++- .../clearblade/create_device_sync_es256.py | 3 +- .../clearblade/create_device_sync_rs256.py | 8 +-- samples/clearblade/update_device_async.py | 2 +- samples/clearblade/update_device_sync.py | 4 +- 14 files changed, 93 insertions(+), 63 deletions(-) diff --git a/clearblade/cloud/iot_v1/__init__.py b/clearblade/cloud/iot_v1/__init__.py index 3b736104..86561049 100644 --- a/clearblade/cloud/iot_v1/__init__.py +++ b/clearblade/cloud/iot_v1/__init__.py @@ -34,4 +34,12 @@ "ListDeviceRegistryPager", "ListDeviceRegistriesAsyncPager", "ListDevicesPager", - "ListDevicesAsyncPager") + "ListDevicesAsyncPager", + "MqttState", + "HtttState", + "LogLevel", + "GatewayType", + "GatewayAuthMethod", + "PublicKeyCertificateFormat", + "PublicKeyFormat" + ) diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index 528aa652..94ac4ed4 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -1,5 +1,5 @@ from typing import List - +from .resources import GatewayType, LogLevel from .utils import get_value @@ -9,17 +9,16 @@ class Device(): """ # TODO: find a better way to construct the Device object. I dont like so much parameter in a constructor - def __init__(self, id: str = None, name: str = None, num_id: str = None, + def __init__(self, id: str, num_id: str = None, credentials: list = [], last_heartbeat_time: str = None, last_event_time: str = None, last_state_time: str = None, last_config_ack_time: str = None, last_config_send_time: str = None, blocked: bool = False, - last_error_time: str = None, last_error_status_code: dict = {"code":None, "message":""}, + last_error_time: str = None, last_error_status_code: dict = None, config: dict = {"cloudUpdateTime":None, "version":""} , state: dict = {"updateTime":None, "binaryData":None}, - log_level: str = "NONE", meta_data: dict = {}, gateway_config : dict = {}) -> None: + log_level: str = LogLevel.NONE, meta_data: dict = {}, gateway_config : dict = {"gatewayType": GatewayType.NON_GATEWAY}) -> None: self._id = id - self._name = name self._num_id = num_id self._credentials = credentials self._last_heartbeat_time = last_heartbeat_time @@ -38,7 +37,7 @@ def __init__(self, id: str = None, name: str = None, num_id: str = None, @staticmethod def from_json(json): - return Device(id=json['id'], name=json['name'], num_id=json['numId'], + return Device(id=json['id'], num_id=json['numId'], credentials=json['credentials'], last_heartbeat_time=json['lastHeartbeatTime'], last_event_time=json['lastEventTime'], last_state_time=json['lastStateTime'], last_config_ack_time=json['lastConfigAckTime'], last_config_send_time=json['lastConfigSendTime'], @@ -51,10 +50,6 @@ def from_json(json): def id(self): return self._id - @property - def name(self): - return self._name - @property def num_id(self): return self._num_id @@ -63,6 +58,10 @@ def num_id(self): def credentials(self): return self._credentials + @credentials.setter + def credentials(self, credentials): + self._credentials = credentials + @property def last_error_status(self): return self._last_error_status_code @@ -78,15 +77,27 @@ def state(self): @property def log_level(self): return self._log_level + + @log_level.setter + def log_level(self, log_level): + self._log_level = log_level @property def meta_data(self): return self._meta_data + @meta_data.setter + def meta_data(self, meta_data): + self._meta_data = meta_data + @property def gateway_config(self): return self._gateway_config + @gateway_config.setter + def gateway_config(self, gateway_config): + self._gateway_config = gateway_config + @property def log_level(self): return self._log_level @@ -94,18 +105,26 @@ def log_level(self): @property def last_heartbeat_time(self): return self._last_heartbeat_time + + @property + def blocked(self): + return self._blocked + + @blocked.setter + def blocked(self, blocked): + self._blocked = blocked # classes to mock googles request & response class DeviceState(): - def __init__(self, updated_time: str = None, binary_data:str = None) -> None: - self._updated_time = updated_time + def __init__(self, update_time: str = None, binary_data:str = None) -> None: + self._update_time = update_time self._binary_data = binary_data @property - def updated_time(self): - return self._updated_time + def update_time(self): + return self._update_time @property def binary_data(self): @@ -113,7 +132,7 @@ def binary_data(self): @staticmethod def from_json(response_json): - return DeviceState(updated_time=get_value(response_json, 'updateTime'), + return DeviceState(update_time=get_value(response_json, 'updateTime'), binary_data=get_value(response_json, 'binaryData')) diff --git a/clearblade/cloud/iot_v1/devices.py b/clearblade/cloud/iot_v1/devices.py index 20d62c1d..b79e8407 100644 --- a/clearblade/cloud/iot_v1/devices.py +++ b/clearblade/cloud/iot_v1/devices.py @@ -30,10 +30,10 @@ def _prepare_for_send_command(self, return params,body def _create_device_body(self, device: Device) : - return {'id':device.name, 'name':device.name, + return {'id':device.id, 'credentials':device.credentials, 'lastErrorStatus':device.last_error_status, 'config':device.config, 'state':device.state, - 'loglevel':device.log_level, 'metadata':device.meta_data, + 'logLevel':device.log_level, 'metadata':device.meta_data, 'gatewayConfig':device.gateway_config} def _create_device_from_response(self, json_response) -> Device : diff --git a/clearblade/cloud/iot_v1/registry.py b/clearblade/cloud/iot_v1/registry.py index b8a869fc..900defbb 100644 --- a/clearblade/cloud/iot_v1/registry.py +++ b/clearblade/cloud/iot_v1/registry.py @@ -21,7 +21,7 @@ def _create_registry_body(self, registry: DeviceRegistry) : if registry.event_notification_configs: registry_json['eventNotificationConfigs']=registry.event_notification_configs if registry.log_level: - registry_json['loglevel']=registry.log_level + registry_json['logLevel']=registry.log_level return registry_json def _prepare_params_for_registry_list(self, request:ListDeviceRegistriesRequest): diff --git a/clearblade/cloud/iot_v1/registry_types.py b/clearblade/cloud/iot_v1/registry_types.py index 979e11d4..1e65a278 100644 --- a/clearblade/cloud/iot_v1/registry_types.py +++ b/clearblade/cloud/iot_v1/registry_types.py @@ -1,4 +1,5 @@ from .utils import get_value +from .resources import HttpState, MqttState, LogLevel class EventNotificationConfig: def __init__(self, pub_sub_topic_name, subfolder_matches=None) -> None: @@ -17,9 +18,9 @@ class DeviceRegistry: def __init__(self, id:str = None, name:str = None, eventNotificationConfigs:list = [], stateNotificationConfig:dict = {'pubsubTopicName': ''}, - mqttConfig:dict = {'mqttEnabledState':'MQTT_ENABLED'}, - httpConfig:dict = {'httpEnabledState':'HTTP_ENABLED'}, - logLevel:str = None, credentials:list = []) -> None: + mqttConfig:dict = {'mqttEnabledState': MqttState.MQTT_ENABLED}, + httpConfig:dict = {'httpEnabledState': HttpState.HTTP_ENABLED}, + logLevel:str = LogLevel.NONE, credentials:list = []) -> None: self._id = id self._name = name self._event_notification_configs = eventNotificationConfigs diff --git a/clearblade/cloud/iot_v1/resources.py b/clearblade/cloud/iot_v1/resources.py index 56ee03e1..0989f754 100644 --- a/clearblade/cloud/iot_v1/resources.py +++ b/clearblade/cloud/iot_v1/resources.py @@ -3,21 +3,21 @@ class MqttState(): r"""Indicates whether an MQTT connection is enabled or disabled. See the field description for details. """ - MQTT_STATE_UNSPECIFIED = 0 - MQTT_ENABLED = 1 - MQTT_DISABLED = 2 + MQTT_STATE_UNSPECIFIED = "MQTT_STATE_UNSPECIFIED" + MQTT_ENABLED = "MQTT_ENABLED" + MQTT_DISABLED = "MQTT_DISABLED" class HttpState(): r"""Indicates whether DeviceService (HTTP) is enabled or disabled for the registry. See the field description for details. """ - HTTP_STATE_UNSPECIFIED = 0 - HTTP_ENABLED = 1 - HTTP_DISABLED = 2 + HTTP_STATE_UNSPECIFIED = "HTTP_STATE_UNSPECIFIED" + HTTP_ENABLED = "HTTP_ENABLED" + HTTP_DISABLED = "HTTP_DISABLED" -class LogLevel(): +class LogLevel: r"""**Beta Feature** The logging verbosity for device activity. Specifies which events @@ -25,18 +25,18 @@ class LogLevel(): only events that terminate in errors will be logged. LogLevel is inclusive; enabling INFO logging will also enable ERROR logging. """ - LOG_LEVEL_UNSPECIFIED = 0 - NONE = 10 - ERROR = 20 - INFO = 30 - DEBUG = 40 + LOG_LEVEL_UNSPECIFIED = "LOG_LEVEL_UNSPECIFIED" + NONE = "NONE" + ERROR = "ERROR" + INFO = "INFO" + DEBUG = "DEBUG" -class GatewayType(): +class GatewayType: r"""Gateway type.""" - GATEWAY_TYPE_UNSPECIFIED = 0 - GATEWAY = 1 - NON_GATEWAY = 2 + GATEWAY_TYPE_UNSPECIFIED = "GATEWAY_TYPE_UNSPECIFIED" + GATEWAY = "GATEWAY" + NON_GATEWAY = "NON_GATEWAY" class GatewayAuthMethod(): @@ -44,16 +44,16 @@ class GatewayAuthMethod(): determines how Cloud IoT Core authorizes/authenticate devices to access the gateway. """ - GATEWAY_AUTH_METHOD_UNSPECIFIED = 0 - ASSOCIATION_ONLY = 1 - DEVICE_AUTH_TOKEN_ONLY = 2 - ASSOCIATION_AND_DEVICE_AUTH_TOKEN = 3 + GATEWAY_AUTH_METHOD_UNSPECIFIED = "GATEWAY_AUTH_METHOD_UNSPECIFIED" + ASSOCIATION_ONLY = "ASSOCIATION_ONLY" + DEVICE_AUTH_TOKEN_ONLY = "DEVICE_AUTH_TOKEN_ONLY" + ASSOCIATION_AND_DEVICE_AUTH_TOKEN = "ASSOCIATION_AND_DEVICE_AUTH_TOKEN" class PublicKeyCertificateFormat(): r"""The supported formats for the public key.""" - UNSPECIFIED_PUBLIC_KEY_CERTIFICATE_FORMAT = 0 - X509_CERTIFICATE_PEM = 1 + UNSPECIFIED_PUBLIC_KEY_CERTIFICATE_FORMAT = "UNSPECIFIED_PUBLIC_KEY_CERTIFICATE_FORMAT" + X509_CERTIFICATE_PEM = "X509_CERTIFICATE_PEM" class PublicKeyFormat: diff --git a/samples/clearblade/create_device_async.py b/samples/clearblade/create_device_async.py index 730eac54..3ddc4a82 100644 --- a/samples/clearblade/create_device_async.py +++ b/samples/clearblade/create_device_async.py @@ -12,7 +12,7 @@ async def sample_create_device_async(): "asia-east1", "test-asia-east1") - device = iot_v1.Device(id="Python_SDK", name="Python_SDK") + device = iot_v1.Device(id="Python_SDK") request = iot_v1.CreateDeviceRequest(parent=parent, device=device) response = await async_client.create_device(request) diff --git a/samples/clearblade/create_device_registry_async.py b/samples/clearblade/create_device_registry_async.py index 7c353319..2b08b6f1 100644 --- a/samples/clearblade/create_device_registry_async.py +++ b/samples/clearblade/create_device_registry_async.py @@ -8,10 +8,10 @@ async def sample_create_device_registry(): # Create a client client = iot_v1.DeviceManagerAsyncClient() - registry = iot_v1.DeviceRegistry(id='test-registry', name='test-registry', - mqttConfig={'mqttEnabledState':'MQTT_ENABLED'}, - httpConfig={'httpEnabledState':'HTTP_ENABLED'}, - logLevel='ERROR', + registry = iot_v1.DeviceRegistry(id='rajas-dummy-registry', + mqttConfig={'mqttEnabledState':iot_v1.resources.MqttState.MQTT_ENABLED}, + httpConfig={'httpEnabledState':iot_v1.resources.HttpState.HTTP_ENABLED}, + logLevel=iot_v1.resources.LogLevel.ERROR, eventNotificationConfigs=[{'pubsubTopicName':'projects/ingressdevelopmentenv/topics/deleting'}] ) diff --git a/samples/clearblade/create_device_registry_sync.py b/samples/clearblade/create_device_registry_sync.py index 5f2981d1..d3b361f5 100644 --- a/samples/clearblade/create_device_registry_sync.py +++ b/samples/clearblade/create_device_registry_sync.py @@ -9,10 +9,9 @@ def sample_create_device_registry(): registry = iot_v1.DeviceRegistry( id='test-registry', - name='test-registry', - mqttConfig={'mqttEnabledState':'MQTT_ENABLED'}, - httpConfig={'httpEnabledState':'HTTP_ENABLED'}, - logLevel='ERROR', + mqttConfig={'mqttEnabledState':iot_v1.MqttState.MQTT_ENABLED}, + httpConfig={'httpEnabledState':iot_v1.HttpState.HTTP_ENABLED}, + logLevel=iot_v1.LogLevel.NONE, eventNotificationConfigs=[{'pubsubTopicName':'projects/api-project-320446546234/topics/deleting'}] ) diff --git a/samples/clearblade/create_device_sync.py b/samples/clearblade/create_device_sync.py index f5725e0a..e8fe6587 100644 --- a/samples/clearblade/create_device_sync.py +++ b/samples/clearblade/create_device_sync.py @@ -10,7 +10,11 @@ def sample_create_device(): "us-central1", "test-registry") - device = iot_v1.Device(id="Python_11", name="Python_11") + device = iot_v1.Device( + id="Python_12", + gateway_config={"gatewayType": iot_v1.GatewayType.NON_GATEWAY}, + log_level=iot_v1.LogLevel.ERROR) + request = iot_v1.CreateDeviceRequest(parent=parent, device=device) response = client.create_device(request) diff --git a/samples/clearblade/create_device_sync_es256.py b/samples/clearblade/create_device_sync_es256.py index 9efd4975..ed5ff357 100644 --- a/samples/clearblade/create_device_sync_es256.py +++ b/samples/clearblade/create_device_sync_es256.py @@ -18,11 +18,10 @@ def create_device_in_dev_iot(name, keyFile): device = iot_v1.Device( id="my_test_device", - name=name, credentials=[ { "publicKey": { - "format": "ES256_PEM", + "format": iot_v1.PublicKeyFormat.ES256_PEM, "key": public_key, } }]) diff --git a/samples/clearblade/create_device_sync_rs256.py b/samples/clearblade/create_device_sync_rs256.py index f74fc194..306a29e0 100644 --- a/samples/clearblade/create_device_sync_rs256.py +++ b/samples/clearblade/create_device_sync_rs256.py @@ -10,15 +10,14 @@ def create_device_in_dev_iot(name, keyFile): parent = client.registry_path( "api-project-320446546234", - "asia-east1", - "test-asia-east1") + "us-central1", + "test-registry") with io.open(keyFile) as f: public_key = f.read() device = iot_v1.Device( - id="python_sdk_device_dummy", - name=name, + id=name, credentials=[ { "publicKey": { @@ -32,4 +31,5 @@ def create_device_in_dev_iot(name, keyFile): device_name = "python_sdk_device_dummy" key_path = "../api-client/manager/resources/ec_public.pem" +os.environ["CLEARBLADE_CONFIGURATION"] = "/Users/rajas/Downloads/test-credentials.json" create_device_in_dev_iot(device_name, key_path) diff --git a/samples/clearblade/update_device_async.py b/samples/clearblade/update_device_async.py index f19c1dc1..ba340abc 100644 --- a/samples/clearblade/update_device_async.py +++ b/samples/clearblade/update_device_async.py @@ -12,7 +12,7 @@ async def sample_update_device_async(): "us-central1", "test-registry") - device = iot_v1.Device(id="test-dev-1", blocked=True, log_level='NONE') + device = iot_v1.Device(id="test-dev-1", blocked=True, log_level=iot_v1.LogLevel.ERROR) request = iot_v1.UpdateDeviceRequest( parent=registry_path, diff --git a/samples/clearblade/update_device_sync.py b/samples/clearblade/update_device_sync.py index 2018d3c8..2816a536 100644 --- a/samples/clearblade/update_device_sync.py +++ b/samples/clearblade/update_device_sync.py @@ -11,7 +11,7 @@ def sample_update_device(): "us-central1", "test-registry") - device = iot_v1.Device(id="test-dev-1", blocked=True, log_level='NONE') + device = iot_v1.Device(id="python_11", blocked=True, log_level=iot_v1.LogLevel.ERROR) request = iot_v1.UpdateDeviceRequest( parent=registry_path, @@ -24,5 +24,5 @@ def sample_update_device(): print(response) -os.environ["CLEARBLADE_CONFIGURATION"] = "/Users/DummyUser/Downloads/test-credentials.json" +os.environ["CLEARBLADE_CONFIGURATION"] = "/Users/rajas/Downloads/test-credentials.json" sample_update_device() From 4d25b0f28e538e322e241a4f2c9994b5aeb408f7 Mon Sep 17 00:00:00 2001 From: rajasd27 Date: Mon, 9 Jan 2023 12:52:36 -0600 Subject: [PATCH 03/10] updated send_command function to base64 encode payload (#18) --- clearblade/cloud/iot_v1/devices.py | 3 ++- samples/clearblade/send_command_to_device.py | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/clearblade/cloud/iot_v1/devices.py b/clearblade/cloud/iot_v1/devices.py index b79e8407..75a4242b 100644 --- a/clearblade/cloud/iot_v1/devices.py +++ b/clearblade/cloud/iot_v1/devices.py @@ -2,6 +2,7 @@ from .device_types import * from .http_client import AsyncClient, SyncClient from .pagers import ListDevicesAsyncPager, ListDevicesPager +import base64 class ClearBladeDeviceManager(): @@ -25,7 +26,7 @@ def _prepare_for_send_command(self, if request is None: request = SendCommandToDeviceRequest(name, binary_data, subfolder) params = {'name':request.parent,'method':'sendCommandToDevice'} - body = {'binaryData':request.binary_data.decode("utf-8")} + body = {'binaryData':base64.b64encode(request.binary_data).decode("utf-8")} return params,body diff --git a/samples/clearblade/send_command_to_device.py b/samples/clearblade/send_command_to_device.py index 80186840..5f08bc97 100644 --- a/samples/clearblade/send_command_to_device.py +++ b/samples/clearblade/send_command_to_device.py @@ -1,3 +1,4 @@ +import json import os from clearblade.cloud import iot_v1 @@ -6,13 +7,20 @@ def sample_send_command_to_device(): client = iot_v1.DeviceManagerClient() + data = json.dumps({ + "device_code": "1", + "command": "test", + "command_option": "0", + "seq_no": 10 + }).encode("utf-8") + device_path = client.device_path( "api-project-320446546234", "us-central1", "test-registry", "test-dev-1") - request = iot_v1.SendCommandToDeviceRequest(name=device_path, binary_data=b"QUJD") + request = iot_v1.SendCommandToDeviceRequest(name=device_path, binary_data=data) response = client.send_command_to_device(request) print(response) From bb2915dbb3b005d8ed758cccf473e5f3fc97499d Mon Sep 17 00:00:00 2001 From: Akash Sharma Date: Tue, 10 Jan 2023 04:38:14 -0800 Subject: [PATCH 04/10] Changed 'cloud_ack_time' to 'cloud_update_time' (#20) --- clearblade/cloud/iot_v1/developer_tests.py | 2 +- clearblade/cloud/iot_v1/device_types.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clearblade/cloud/iot_v1/developer_tests.py b/clearblade/cloud/iot_v1/developer_tests.py index 872a83ff..cb5daa34 100644 --- a/clearblade/cloud/iot_v1/developer_tests.py +++ b/clearblade/cloud/iot_v1/developer_tests.py @@ -109,7 +109,7 @@ def test_get_device_configVersions(): device_configs = response.device_configs for device_config in device_configs: print("Device version = {} Device Ack Time {} \n".format(device_config.version, - device_config.cloud_ack_time)) + device_config.cloud_update_time)) async def test_get_device_configVersions_async(): async_client = DeviceManagerAsyncClient() diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index 94ac4ed4..e0667ed0 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -193,12 +193,12 @@ def binary_data(self): class DeviceConfig(Request): def __init__(self, name, version, - cloud_ack_time, + cloud_update_time, device_ack_time, binary_data) -> None: super().__init__(name) self._version = version - self._cloud_ack_time = cloud_ack_time + self._cloud_update_time = cloud_update_time self._device_ack_time = device_ack_time self._binary_data = binary_data @@ -207,8 +207,8 @@ def version(self): return self._version @property - def cloud_ack_time(self): - return self._cloud_ack_time + def cloud_update_time(self): + return self._cloud_update_time @property def device_ack_time(self): @@ -222,7 +222,7 @@ def binary_data(self): def from_json(json): return DeviceConfig(name='', version=get_value(json, 'version'), - cloud_ack_time=get_value(json,'cloudUpdateTime'), + cloud_update_time=get_value(json,'cloudUpdateTime'), device_ack_time=get_value(json, 'deviceAckTime'), binary_data=get_value(json,'binaryData')) From 76c48bcf6d720355a0e5c40d4edc68512d5b2d20 Mon Sep 17 00:00:00 2001 From: akash-sharma-ext-clearblade Date: Tue, 28 Feb 2023 08:27:19 -0800 Subject: [PATCH 05/10] Iot 918 conform to google format times binary data (#24) * 1. imports incl. google.api_core.datetime_helpers 2. Added logic to def from_json in class DeviceState and DeviceConfig to convert times to DateTimeWithNanoseconds and binaryData to bytes if env. vars set * 1. Changed Device.from_json to support GCP types for time, binaryData. 2. Changed DeviceState.from_json to handle blank binaryData 3. Changed DeviceConfig.from_json to handle blank binaryData * Removed extra 'import base64' * Added note about types of times, binaryData * Cleaned up formatting * Further cleanup * Added copyright info. at top of files * Not to 'pip install google-api-core' * Removed instructions for installing datetime_helpers module. Also now changed 'google.api_core' to 'proto' * Added "proto.datetime_helpers" to dependencies * Changed 'google.api_core' to 'proto.' * Removed call to set 'convert_binarydata_to_bytes' in Devices.from_json * Changed README to reflect decision on ONE env. var * Changed module to import to proto-plus * Two env. vars -> BINARYDATA_AND_TIME_GOOGLE_FORMAT * Removed a '.' * Format improvements for README * Added note about new env. var in 'Quick Start' sec --- README.rst | 41 ++++++- clearblade/cloud/iot_v1/device_types.py | 141 +++++++++++++++++++++--- clearblade/cloud/iot_v1/devices.py | 31 +++++- setup.py | 2 +- 4 files changed, 197 insertions(+), 18 deletions(-) diff --git a/README.rst b/README.rst index 0480a6f8..9e7a1b2a 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,24 @@ +.. Copyright 2023 ClearBlade Inc. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Copyright 2022 Google LLC + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + Python Client for ClearBlade Internet of Things (IoT) Core API ================================================================ @@ -9,8 +30,9 @@ In order to use this library, you first need to go through the following steps: 1. Install pip package - ```pip install clearblade-cloud-iot``` -2. Set an environment variable CLEARBLADE_CONFIGURATION which should point to your clearblade service account json file. +2. Set an environment variable **CLEARBLADE_CONFIGURATION** which should point to your clearblade service account json file. +3. Optionally set an environment variable **BINARYDATA_AND_TIME_GOOGLE_FORMAT** to True. Look at **Note about types of times and binaryData** below for details. Installation ~~~~~~~~~~~~ @@ -78,3 +100,20 @@ Next Steps - and execute the setup.py file like , python setup.py install. - mostly if you change you imports from from google.cloud to clearblade.cloud everything else should work. + +Note about types of times and binaryData +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- By default the following parameters are returned as the shown types: + +1. All time parameters (e.g. **cloudUpdateTime**, **deviceAckTime**, **updateTime**): **RFC3339** strings (e.g. "2023-01-12T23:38:07.732Z") +2. **CONFIG binaryData**: **base64-encoded string** +3. **STATE binaryData**: **NON-base64-encoded string** + + +- To return these parameters using the same types used by the **Google IoTCore Python SDK**, set environment variable **BINARYDATA_AND_TIME_GOOGLE_FORMAT** to **True** (case-insensitive string). This will ensure the following parameters are returned as the shown types: + +1. All times: **DatetimeWithNanoseconds** (defined in the **proto.datetime_helpers** module) +2. All **binaryData** (CONFIG, STATE etc.): **BYTE ARRAYS** + +- If this environment variable is not set, or is set to any unexpeced values, then the default types listed previously are used. \ No newline at end of file diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index e0667ed0..71832484 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -1,7 +1,38 @@ +# -*- coding: utf-8 -*- +# Copyright 2023 ClearBlade Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# from typing import List from .resources import GatewayType, LogLevel from .utils import get_value - +import os +from proto.datetime_helpers import DatetimeWithNanoseconds +import base64 class Device(): """ @@ -37,14 +68,57 @@ def __init__(self, id: str, num_id: str = None, @staticmethod def from_json(json): - return Device(id=json['id'], num_id=json['numId'], - credentials=json['credentials'], last_heartbeat_time=json['lastHeartbeatTime'], - last_event_time=json['lastEventTime'], last_state_time=json['lastStateTime'], - last_config_ack_time=json['lastConfigAckTime'], last_config_send_time=json['lastConfigSendTime'], - blocked=json['blocked'], last_error_time=json['lastErrorTime'], - last_error_status_code=json['lastErrorStatus'], config=json['config'], - state=json['state'], log_level=json['logLevel'], meta_data=json['metadata'], - gateway_config=json['gatewayConfig']) + lastHeartbeatTimeFromJson = get_value(json,'lastHeartbeatTime') + lastEventTimeFromJson = get_value(json, 'lastEventTime') + lastStateTimeFromJson = get_value(json, 'lastStateTime') + lastConfigAckTimeFromJson = get_value(json, 'lastConfigAckTime') + lastConfigSendTimeFromJson = get_value(json, 'lastConfigSendTime') + lastErrorTimeFromJson = get_value(json, 'lastErrorTime') + + convert_times_to_datetime_with_nanoseconds = (False if os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT") == None else os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT").lower() == "true") + if convert_times_to_datetime_with_nanoseconds: + last_heartbeat_time = None if lastHeartbeatTimeFromJson in [None, ""] else DatetimeWithNanoseconds.from_rfc3339(lastHeartbeatTimeFromJson) + last_event_time = None if lastEventTimeFromJson in [None, ""] else DatetimeWithNanoseconds.from_rfc3339(lastEventTimeFromJson) + last_state_time = None if lastStateTimeFromJson in [None, ""] else DatetimeWithNanoseconds.from_rfc3339(lastStateTimeFromJson) + last_config_ack_time = None if lastConfigAckTimeFromJson in [None, ""] else DatetimeWithNanoseconds.from_rfc3339(lastConfigAckTimeFromJson) + last_config_send_time = None if lastConfigSendTimeFromJson in [None, ""] else DatetimeWithNanoseconds.from_rfc3339(lastConfigSendTimeFromJson) + last_error_time = None if lastErrorTimeFromJson in [None, ""] else DatetimeWithNanoseconds.from_rfc3339(lastErrorTimeFromJson) + else: + last_heartbeat_time = lastHeartbeatTimeFromJson + last_event_time = lastEventTimeFromJson + last_state_time = lastStateTimeFromJson + last_config_ack_time = lastConfigAckTimeFromJson + last_config_send_time = lastConfigSendTimeFromJson + last_error_time = lastErrorTimeFromJson + + deviceConfig = DeviceConfig.from_json(get_value(json, 'config')) + config = { "version": deviceConfig.version, "cloudUpdateTime": deviceConfig.cloud_update_time } + if (deviceConfig.binary_data not in [None, ""]): + config["binaryData"] = deviceConfig.binary_data + if (deviceConfig.device_ack_time not in [None, ""]): + config["deviceAckTime"] = deviceConfig.device_ack_time + + deviceState = DeviceState.from_json(get_value(json, 'state')) + state = { "updateTime": deviceState.update_time, "binaryData": deviceState.binary_data } + + return Device( + id=get_value(json, 'id'), + num_id=get_value(json, 'numId'), + credentials=get_value(json, 'credentials'), + last_heartbeat_time=last_heartbeat_time, + last_event_time=last_event_time, + last_state_time=last_state_time, + last_config_ack_time=last_config_ack_time, + last_config_send_time=last_config_send_time, + last_error_time=last_error_time, + blocked=get_value(json, 'blocked'), + last_error_status_code=get_value(json, 'lastErrorStatus'), + config=config, + state=state, + log_level=get_value(json, 'logLevel'), + meta_data=get_value(json, 'metadata'), + gateway_config=get_value(json, 'gatewayConfig') + ) @property def id(self): @@ -132,8 +206,26 @@ def binary_data(self): @staticmethod def from_json(response_json): - return DeviceState(update_time=get_value(response_json, 'updateTime'), - binary_data=get_value(response_json, 'binaryData')) + updateTimeFromJson = get_value(response_json, 'updateTime') + binaryDataFromJson = get_value(response_json, 'binaryData') + + convert_times_to_datetime_with_nanoseconds = (False if os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT") == None else os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT").lower() == "true") + if convert_times_to_datetime_with_nanoseconds: + update_time = None if updateTimeFromJson in [None, ""] else DatetimeWithNanoseconds.from_rfc3339(updateTimeFromJson) + else: + update_time = updateTimeFromJson + + if (binaryDataFromJson not in [None, ""]): + convert_binarydata_to_bytes = (False if os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT") == None else os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT").lower() == "true") + if convert_binarydata_to_bytes: + binary_data = binaryDataFromJson.encode('utf-8') + else: + binary_data = binaryDataFromJson + else: + binary_data = binaryDataFromJson + + return DeviceState(update_time=update_time, + binary_data=binary_data) class Request(): @@ -220,11 +312,32 @@ def binary_data(self): @staticmethod def from_json(json): + cloudUpdateTimeFromJson = get_value(json,'cloudUpdateTime') + deviceAckTimeFromJson = get_value(json, 'deviceAckTime') + binaryDataFromJson = get_value(json,'binaryData') + + convert_times_to_datetime_with_nanoseconds = (False if os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT") == None else os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT").lower() == "true") + if convert_times_to_datetime_with_nanoseconds: + cloud_update_time = None if cloudUpdateTimeFromJson in [None, ""] else DatetimeWithNanoseconds.from_rfc3339(cloudUpdateTimeFromJson) + device_ack_time = None if deviceAckTimeFromJson in [None, ""] else DatetimeWithNanoseconds.from_rfc3339(deviceAckTimeFromJson) + else: + cloud_update_time = cloudUpdateTimeFromJson + device_ack_time = deviceAckTimeFromJson + + if binaryDataFromJson not in [None, ""]: + convert_binarydata_to_bytes = (False if os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT") == None else os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT").lower() == "true") + if convert_binarydata_to_bytes: + binary_data = base64.b64decode(binaryDataFromJson.encode('utf-8')) + else: + binary_data = binaryDataFromJson + else: + binary_data = binaryDataFromJson + return DeviceConfig(name='', version=get_value(json, 'version'), - cloud_update_time=get_value(json,'cloudUpdateTime'), - device_ack_time=get_value(json, 'deviceAckTime'), - binary_data=get_value(json,'binaryData')) + cloud_update_time=cloud_update_time, + device_ack_time=device_ack_time, + binary_data=binary_data) class DeleteDeviceRequest(Request): diff --git a/clearblade/cloud/iot_v1/devices.py b/clearblade/cloud/iot_v1/devices.py index 44fc9a9c..42ebbe4a 100644 --- a/clearblade/cloud/iot_v1/devices.py +++ b/clearblade/cloud/iot_v1/devices.py @@ -1,5 +1,32 @@ -import base64 - +# -*- coding: utf-8 -*- +# Copyright 2023 ClearBlade Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# from .config_manager import ClearBladeConfigManager from .device_types import * from .http_client import AsyncClient, SyncClient diff --git a/setup.py b/setup.py index 0fac11d2..4bf8c8da 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ description = "Cloud IoT API client library" version = "1.0.5" release_status = "Development Status :: 5 - Production/Stable" -dependencies = ["httpx"] +dependencies = ["httpx", "proto-plus"] package_root = os.path.abspath(os.path.dirname(__file__)) From 926b8ef0c8a38e66002d9ab5602774df35804bed Mon Sep 17 00:00:00 2001 From: akash-sharma-ext-clearblade Date: Fri, 24 Mar 2023 14:12:20 -0700 Subject: [PATCH 06/10] 'config', 'state' None? (i.e. not in fieldMask?) (#26) * 'config', 'state' None? (i.e. not in fieldMask?) * Added new @property's to address DESK-2174 --- clearblade/cloud/iot_v1/device_types.py | 46 ++++++++++++++++++++----- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index 71832484..a24218a3 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -74,6 +74,8 @@ def from_json(json): lastConfigAckTimeFromJson = get_value(json, 'lastConfigAckTime') lastConfigSendTimeFromJson = get_value(json, 'lastConfigSendTime') lastErrorTimeFromJson = get_value(json, 'lastErrorTime') + configFromJson = get_value(json, 'config') + stateFromJson = get_value(json, 'state') convert_times_to_datetime_with_nanoseconds = (False if os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT") == None else os.environ.get("BINARYDATA_AND_TIME_GOOGLE_FORMAT").lower() == "true") if convert_times_to_datetime_with_nanoseconds: @@ -91,15 +93,21 @@ def from_json(json): last_config_send_time = lastConfigSendTimeFromJson last_error_time = lastErrorTimeFromJson - deviceConfig = DeviceConfig.from_json(get_value(json, 'config')) - config = { "version": deviceConfig.version, "cloudUpdateTime": deviceConfig.cloud_update_time } - if (deviceConfig.binary_data not in [None, ""]): - config["binaryData"] = deviceConfig.binary_data - if (deviceConfig.device_ack_time not in [None, ""]): - config["deviceAckTime"] = deviceConfig.device_ack_time - - deviceState = DeviceState.from_json(get_value(json, 'state')) - state = { "updateTime": deviceState.update_time, "binaryData": deviceState.binary_data } + if (configFromJson): + deviceConfig = DeviceConfig.from_json(configFromJson) + config = { "version": deviceConfig.version, "cloudUpdateTime": deviceConfig.cloud_update_time } + if (deviceConfig.binary_data not in [None, ""]): + config["binaryData"] = deviceConfig.binary_data + if (deviceConfig.device_ack_time not in [None, ""]): + config["deviceAckTime"] = deviceConfig.device_ack_time + else: + config = configFromJson + + if (stateFromJson): + deviceState = DeviceState.from_json(stateFromJson) + state = { "updateTime": deviceState.update_time, "binaryData": deviceState.binary_data } + else: + state = stateFromJson return Device( id=get_value(json, 'id'), @@ -180,6 +188,26 @@ def log_level(self): def last_heartbeat_time(self): return self._last_heartbeat_time + @property + def last_event_time(self): + return self._last_event_time + + @property + def last_state_time(self): + return self._last_state_time + + @property + def last_config_ack_time(self): + return self._last_config_ack_time + + @property + def last_config_send_time(self): + return self._last_config_send_time + + @property + def last_error_time(self): + return self._last_error_time + @property def blocked(self): return self._blocked From 200188b4ed395bdcdafca50710be04385a9d2766 Mon Sep 17 00:00:00 2001 From: akash-sharma-ext-clearblade Date: Mon, 8 May 2023 07:30:49 -0700 Subject: [PATCH 07/10] Iot 994 add credential classes (#28) * Made PublicKeyFormat a child class of Enum * 1. import PublicKeyFormat from .resources 2. New dodict class for providing dot notation for dicts when read. 3. Call to dodict in credentials getter. 4. New functions for converting PublicKeyFormat to/from string. 5. Calls to PublicKeyFormat conversion functions. * Added note about how to run from source * Formatting * More formatting * Formatting * Formatting * Clearer info. on running from source * 1. Import PublicKeyCredential & DeviceCredential 2. Remove dotdict class. 3. Remove convertCredentialsFormatToString. 4. Set self._credentials=convertCredentialsFormatsToString(credentials) 5. Simplified Device class' credentials getter. 6. Changed Device class attr from 'credentials' to '_credentials' * Added code to _create_device_body to: 1. Convert DeviceCredential and PublicKeyCredential objects to dicts 2. Convert PublicKeyFormat class to string This is prior to creating device in registry. * Add PublicKeyCredential & DeviceCredential classes * Changed PublicKeyCredential constructor params: publicKeyFormat -> format publicKey -> key * 1. bug: DeviceCredential constructor calls PublicKeyCredential constructor where params were reversed. 2. Added classmethod convert_credentials_for_create_update. * 1. Removed unnecessary var from convertCredentialsFormatToString. 2. Calling convert_credentials_for_create_update in _prepare_params_body_for_update fcn * 1. Import DeviceCredential 2. Remove code for converting credentials from _create_device_body and call DeviceCredential.convert_credentials_for_create_update instead * 1. Removed the config and state conversion code from class Device. 2. In Device.from_json returned config as DeviceConfig.from_json() and state as DeviceState.from_json() 3. In DeviceState replace attr. self._update_time w/ self.updateTime and self._binary_data w/ self.binaryData 4. Added __getitem__ and get functions. * In classes PublicKeyCredential & DeviceCredential added function "get" which does the same thing as __getitem__ * Document changes from last version in UPGRADING.md * Turned ClearBlade and Google License info. into comments * Formatting changes * In 'convert_credentials_for_create_update' check for expirationTime. If it is datetime convert to ISO string * Formatting * Formatting * Formatting * Formatting * Formatting * Prefixed license info. * bug: if expirationTime type 'datetime', isoformat won't work: 'Z' suffix is left off and is needed by CB platform. Resolved by replacing isoformat with strftime. --- README.rst | 18 +- UPGRADING.md | 232 ++++++++++----------- clearblade/cloud/iot_v1/__init__.py | 44 ++++ clearblade/cloud/iot_v1/client.py | 44 ++++ clearblade/cloud/iot_v1/config.py | 44 ++++ clearblade/cloud/iot_v1/config_manager.py | 44 ++++ clearblade/cloud/iot_v1/developer_tests.py | 44 ++++ clearblade/cloud/iot_v1/device_types.py | 144 +++++++------ clearblade/cloud/iot_v1/devices.py | 77 ++++--- clearblade/cloud/iot_v1/http_client.py | 44 ++++ clearblade/cloud/iot_v1/pagers.py | 44 ++++ clearblade/cloud/iot_v1/registry.py | 44 ++++ clearblade/cloud/iot_v1/registry_types.py | 44 ++++ clearblade/cloud/iot_v1/resources.py | 108 +++++++++- clearblade/cloud/iot_v1/utils.py | 44 ++++ 15 files changed, 804 insertions(+), 215 deletions(-) diff --git a/README.rst b/README.rst index 9e7a1b2a..5cf93962 100644 --- a/README.rst +++ b/README.rst @@ -116,4 +116,20 @@ Note about types of times and binaryData 1. All times: **DatetimeWithNanoseconds** (defined in the **proto.datetime_helpers** module) 2. All **binaryData** (CONFIG, STATE etc.): **BYTE ARRAYS** -- If this environment variable is not set, or is set to any unexpeced values, then the default types listed previously are used. \ No newline at end of file +- If this environment variable is not set, or is set to any unexpeced values, then the default types listed previously are used. + +Note about running from source instead of PyPi (pip) module: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +- To temporarily use the source code in this repo. instead of the installed PyPi (pip) module do the following: + +1. Clone this repo. +2. Checkout the desired branch using **git checkout **. +3. In your code find where **clearblade** or **clearblade.cloud** is being imported. +4. Precede that line with **import sys** and **sys.path.insert(0, )**. The path must end with "python-iot". So for example: + +.. code-block:: console + + import sys + sys.path.insert(0, "path/to/python-iot") + + from clearblade.cloud import iot_v1 \ No newline at end of file diff --git a/UPGRADING.md b/UPGRADING.md index 7815d5f2..1b711d5a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,157 +1,151 @@ -# 2.0.0 Migration Guide - -The 2.0 release of the `google-cloud-iot` client is a significant upgrade based on a [next-gen code generator](https://github.com/googleapis/gapic-generator-python), and includes substantial interface changes. Existing code written for earlier versions of this library will likely require updates to use this version. This document describes the changes that have been made, and what you need to do to update your usage. - -If you experience issues or have questions, please file an [issue](https://github.com/googleapis/python-iot/issues). - -## Supported Python Versions - -> **WARNING**: Breaking change - -The 2.0.0 release requires Python 3.6+. - + -## Method Calls - -> **WARNING**: Breaking change - -Methods expect request objects. We provide a script that will convert most common use cases. - -* Install the library with `libcst`. - -```py -python3 -m pip install google-cloud-iot[libcst] -``` - -* The script `fixup_iot_v1_keywords.py` is shipped with the library. It expects -an input directory (with the code to convert) and an empty destination directory. +# 2.0.0 Migration Guide -```sh -$ fixup_iot_v1_keywords.py --input-directory .samples/ --output-directory samples/ -``` +The 2.0 release of the `clearblade-cloud-iot` client is a significant upgrade based on addition of two new classes in **iot_v1**: -**Before:** -```py -from google.cloud import iot_v1 +- **DeviceCredential** +- **PublicKeyCredential** -client = iot_v1.DeviceManagerClient() +The release also includes enhancements to these classes already present in **iot_v1**: -registry = client.get_device_registry("registry_name") -``` +- **DeviceConfig** +- **DeviceState** +The version was made with the intent of minimizing required code changes. **However these changes should be considrered Breaking changes**. -**After:** -```py -from google.cloud import iot_v1 +# -client = iot_v1.DeviceManagerClient() +1. If **device** is an object of class **Device**. -registry = client.get_device_registry(request={'name': "registry_name"}) -``` + **Before**: + device.credentials is of type **[dict]** (i.e. list of dicts). -### More Details + **After**: + device.credentials is of type **[DeviceCredential]** (i.e. list of objects of class DeviceCredential). -In `google-cloud-iot<2.0.0`, parameters required by the API were positional parameters and optional parameters were keyword parameters. + The **DeviceCredential** class has these features for usability: -**Before:** -```py - def create_device( - self, - parent, - device, - retry=google.api_core.gapic_v1.method.DEFAULT, - timeout=google.api_core.gapic_v1.method.DEFAULT, - metadata=None, - ): -``` + - A **get** method that mimics the **get** method of a dict. + - Allows accessing attributes using dot notation OR square-brackets. + - Supports camel-case as well as snake-case for accessing attributes: -In the 2.0.0 release, all methods have a single positional parameter `request`. Method docstrings indicate whether a parameter is required or optional. + e.g. All these are valid for retrieving the public key: -Some methods have additional keyword only parameters. The available parameters depend on the `google.api.method_signature` annotation specified by the API producer. + - **public_key = device.credentials[0]['publicKey']** + - **public_key = device.credentials[0]['public_key']** + - **public_key = device.credentials[0].get('publicKey')** + - **public_key = device.credentials[0].get('public_key')** + - **public_key = device.credentials[0].publicKey** + - **public_key = device.credentials[0].public_key** +# -**After:** -```py - def create_device( - self, - request: device_manager.CreateDeviceRequest = None, - *, - parent: str = None, - device: resources.Device = None, - retry: retries.Retry = gapic_v1.method.DEFAULT, - timeout: float = None, - metadata: Sequence[Tuple[str, str]] = (), - ) -> resources.Device: -``` +2. This refers to pub_key mentioned in the previous section. -> **NOTE:** The `request` parameter and flattened keyword parameters for the API are mutually exclusive. -> Passing both will result in an error. + **Before**: + public_key was of type **dict**. + **After**: + public_key is an object of class **PublicKeyCredential**. -Both of these calls are valid: + The **PublicKeyCredential** class has these features for usability: -```py -response = client.create_device( - request={ - "parent": parent, - "device": device, - } -) -``` + - A **get** method that mimics the **get** method of a dict. + - Allows accessing attributes using dot notation OR square-brackets. -```py -response = client.create_device( - parent=parent, - device=device, -) -``` + e.g. All these are valid for retrieving the public key format: -This call is invalid because it mixes `request` with a keyword argument `device`. Executing this code -will result in an error. + - **format = public_key['format']** + - **format = public_key.get('format')** + - **format = public_key.format** -```py -response = client.create_device( - request={ - "parent": parent, - }, - device=device -) -``` +# +3. This section refers to **dev_config** which holds device config. + **Before**: + dev_config is of type **dict**. -## Enums and Types + **After**: + dev_config is an object of class **DeviceConfig**. + The **DeviceConfig** class has these features for usability: -> **WARNING**: Breaking change + - A **get** method that mimics the **get** method of a dict. + - Allows accessing attributes using dot notation OR square-brackets. + - Supports camel-case as well as snake-case for accessing attributes: -The submodules `enums` and `types` have been removed. + e.g. All these are valid for retrieving the cloud_update_time: -**Before:** -```py -from google.cloud import iot_v1 + - **cloud_update_time = device.credentials[0]['cloudUpdateTime']** + - **cloud_update_time = device.credentials[0]['cloud_update_time']** + - **cloud_update_time = device.credentials[0].get('cloudUpdateTime')** + - **cloud_update_time = device.credentials[0].get('cloud_update_time')** + - **cloud_update_time = device.credentials[0].cloudUpdateTime** + - **cloud_update_time = device.credentials[0].cloud_update_time** -gateway_type = iot_v1.enums.GatewayType.GATEWAY -device = iot_v1.types.Device(name="name") -``` +# +4. This section refers to **dev_state** which contains device state. -**After:** -```py -from google.cloud import iot_v1 + **Before**: + dev_state is of type **dict**. -gateway_type = iot_v1.GatewayType.GATEWAY -device = iot_v1.Device(name="name") -``` + **After**: + dev_state is an object of class **DeviceState**. -## Location Path Helper Method + The **DeviceState** class has these features for usability: -Location path helper method has been removed. Please construct -the path manually. + - A **get** method that mimics the **get** method of a dict. + - Allows accessing attributes using dot notation OR square-brackets. + - Supports camel-case as well as snake-case for accessing attributes: -```py -project = 'my-project' -location = 'location' + e.g. All these are valid for retrieving the binary_data: -location_path = f'projects/{project}/locations/{location}' -``` + - **binary_data = device.credentials[0]['binaryData']** + - **binary_data = device.credentials[0]['binary_data']** + - **binary_data = device.credentials[0].get('binaryData')** + - **binary_data = device.credentials[0].get('binary_data')** + - **binary_data = device.credentials[0].binaryData** + - **binary_data = device.credentials[0].binary_data** diff --git a/clearblade/cloud/iot_v1/__init__.py b/clearblade/cloud/iot_v1/__init__.py index 86561049..fd1f559c 100644 --- a/clearblade/cloud/iot_v1/__init__.py +++ b/clearblade/cloud/iot_v1/__init__.py @@ -1,3 +1,47 @@ +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + from .client import DeviceManagerClient, DeviceManagerAsyncClient from .device_types import * from .registry_types import * diff --git a/clearblade/cloud/iot_v1/client.py b/clearblade/cloud/iot_v1/client.py index f81d27f2..20303f21 100644 --- a/clearblade/cloud/iot_v1/client.py +++ b/clearblade/cloud/iot_v1/client.py @@ -1,3 +1,47 @@ +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + from .device_types import * from .devices import * from .registry import * diff --git a/clearblade/cloud/iot_v1/config.py b/clearblade/cloud/iot_v1/config.py index cf0ea130..d3ec7d40 100644 --- a/clearblade/cloud/iot_v1/config.py +++ b/clearblade/cloud/iot_v1/config.py @@ -1,3 +1,47 @@ +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + class ClearBladeConfig: def __init__(self, system_key:str = None, auth_token:str = None, diff --git a/clearblade/cloud/iot_v1/config_manager.py b/clearblade/cloud/iot_v1/config_manager.py index 738f18aa..fb8cd559 100644 --- a/clearblade/cloud/iot_v1/config_manager.py +++ b/clearblade/cloud/iot_v1/config_manager.py @@ -1,3 +1,47 @@ +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + import json import os diff --git a/clearblade/cloud/iot_v1/developer_tests.py b/clearblade/cloud/iot_v1/developer_tests.py index cb5daa34..c12d152f 100644 --- a/clearblade/cloud/iot_v1/developer_tests.py +++ b/clearblade/cloud/iot_v1/developer_tests.py @@ -1,3 +1,47 @@ +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + from client import DeviceManagerClient, DeviceManagerAsyncClient from device_types import * from registry import * diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index a24218a3..9e109bd9 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -1,39 +1,62 @@ -# -*- coding: utf-8 -*- -# Copyright 2023 ClearBlade Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + from typing import List -from .resources import GatewayType, LogLevel +from .resources import GatewayType, LogLevel, PublicKeyFormat, PublicKeyCredential, DeviceCredential from .utils import get_value import os from proto.datetime_helpers import DatetimeWithNanoseconds import base64 +def convertCredentialsFormatsFromString(credentials): + # Converts public Key Format from string to object of class PublicKeyFormat + for index, credential in enumerate(credentials): + if 'publicKey' in credential: + credential['publicKey']['format'] = PublicKeyFormat(credential['publicKey']['format']) + credentials[index] = DeviceCredential(credential) + return credentials + class Device(): """ Data class for Clearblade Device @@ -93,26 +116,10 @@ def from_json(json): last_config_send_time = lastConfigSendTimeFromJson last_error_time = lastErrorTimeFromJson - if (configFromJson): - deviceConfig = DeviceConfig.from_json(configFromJson) - config = { "version": deviceConfig.version, "cloudUpdateTime": deviceConfig.cloud_update_time } - if (deviceConfig.binary_data not in [None, ""]): - config["binaryData"] = deviceConfig.binary_data - if (deviceConfig.device_ack_time not in [None, ""]): - config["deviceAckTime"] = deviceConfig.device_ack_time - else: - config = configFromJson - - if (stateFromJson): - deviceState = DeviceState.from_json(stateFromJson) - state = { "updateTime": deviceState.update_time, "binaryData": deviceState.binary_data } - else: - state = stateFromJson - return Device( id=get_value(json, 'id'), num_id=get_value(json, 'numId'), - credentials=get_value(json, 'credentials'), + credentials=convertCredentialsFormatsFromString(get_value(json, 'credentials')), last_heartbeat_time=last_heartbeat_time, last_event_time=last_event_time, last_state_time=last_state_time, @@ -121,8 +128,8 @@ def from_json(json): last_error_time=last_error_time, blocked=get_value(json, 'blocked'), last_error_status_code=get_value(json, 'lastErrorStatus'), - config=config, - state=state, + config=DeviceConfig.from_json(configFromJson), + state=DeviceState.from_json(stateFromJson), log_level=get_value(json, 'logLevel'), meta_data=get_value(json, 'metadata'), gateway_config=get_value(json, 'gatewayConfig') @@ -221,16 +228,22 @@ def blocked(self, blocked): class DeviceState(): def __init__(self, update_time: str = None, binary_data:str = None) -> None: - self._update_time = update_time - self._binary_data = binary_data + self.updateTime = update_time + self.binaryData = binary_data + + def __getitem__(self, arg): + return getattr(self, arg) + + def get(self, arg): + return getattr(self, arg) @property def update_time(self): - return self._update_time + return self.updateTime @property def binary_data(self): - return self._binary_data + return self.binaryData @staticmethod def from_json(response_json): @@ -254,8 +267,7 @@ def from_json(response_json): return DeviceState(update_time=update_time, binary_data=binary_data) - - + class Request(): def __init__(self, parent) -> None: self._parent = parent @@ -318,9 +330,15 @@ def __init__(self, name, binary_data) -> None: super().__init__(name) self._version = version - self._cloud_update_time = cloud_update_time - self._device_ack_time = device_ack_time - self._binary_data = binary_data + self.cloudUpdateTime = cloud_update_time + self.deviceAckTime = device_ack_time + self.binaryData = binary_data + + def __getitem__(self, arg): + return getattr(self, arg) + + def get(self, arg): + return getattr(self, arg) @property def version(self): @@ -328,15 +346,15 @@ def version(self): @property def cloud_update_time(self): - return self._cloud_update_time + return self.cloudUpdateTime @property def device_ack_time(self): - return self._device_ack_time + return self.deviceAckTime @property def binary_data(self): - return self._binary_data + return self.binaryData @staticmethod def from_json(json): @@ -493,8 +511,8 @@ def _prepare_params_body_for_update(self): body['metadata'] = self._device.meta_data if self._device._blocked is not None: body['blocked'] = self._device._blocked - if self._device.credentials is not None: - body['credentials'] = self._device.credentials + if self._device._credentials is not None: + body['credentials'] = DeviceCredential.convert_credentials_for_create_update(self._device._credentials) return params, body diff --git a/clearblade/cloud/iot_v1/devices.py b/clearblade/cloud/iot_v1/devices.py index 42ebbe4a..97633860 100644 --- a/clearblade/cloud/iot_v1/devices.py +++ b/clearblade/cloud/iot_v1/devices.py @@ -1,38 +1,53 @@ -# -*- coding: utf-8 -*- -# Copyright 2023 ClearBlade Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + from .config_manager import ClearBladeConfigManager from .device_types import * from .http_client import AsyncClient, SyncClient from .pagers import ListDevicesAsyncPager, ListDevicesPager import base64 - +from .resources import DeviceCredential class ClearBladeDeviceManager(): @@ -61,7 +76,7 @@ def _prepare_for_send_command(self, def _create_device_body(self, device: Device) : return {'id':device.id, - 'credentials':device.credentials, + 'credentials':DeviceCredential.convert_credentials_for_create_update(device.credentials), 'config':device.config, 'blocked': device.blocked, 'logLevel':device.log_level, 'metadata':device.meta_data, diff --git a/clearblade/cloud/iot_v1/http_client.py b/clearblade/cloud/iot_v1/http_client.py index 182c0c70..ddb002fd 100644 --- a/clearblade/cloud/iot_v1/http_client.py +++ b/clearblade/cloud/iot_v1/http_client.py @@ -1,3 +1,47 @@ +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + import json import httpx from .config import * diff --git a/clearblade/cloud/iot_v1/pagers.py b/clearblade/cloud/iot_v1/pagers.py index 1ba3bd29..622768c2 100644 --- a/clearblade/cloud/iot_v1/pagers.py +++ b/clearblade/cloud/iot_v1/pagers.py @@ -1,3 +1,47 @@ +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + from .device_types import Device, ListDevicesResponse, ListDevicesRequest from .registry_types import DeviceRegistry, ListDeviceRegistriesRequest, ListDeviceRegistriesResponse from typing import Any, Awaitable, AsyncIterator, Callable diff --git a/clearblade/cloud/iot_v1/registry.py b/clearblade/cloud/iot_v1/registry.py index 900defbb..972215c1 100644 --- a/clearblade/cloud/iot_v1/registry.py +++ b/clearblade/cloud/iot_v1/registry.py @@ -1,3 +1,47 @@ +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + from .config_manager import ClearBladeConfigManager from .http_client import AsyncClient, SyncClient from .pagers import ListDeviceRegistriesAsyncPager, ListDeviceRegistryPager diff --git a/clearblade/cloud/iot_v1/registry_types.py b/clearblade/cloud/iot_v1/registry_types.py index 1e65a278..01bbd51f 100644 --- a/clearblade/cloud/iot_v1/registry_types.py +++ b/clearblade/cloud/iot_v1/registry_types.py @@ -1,3 +1,47 @@ +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + from .utils import get_value from .resources import HttpState, MqttState, LogLevel diff --git a/clearblade/cloud/iot_v1/resources.py b/clearblade/cloud/iot_v1/resources.py index 0989f754..1246819b 100644 --- a/clearblade/cloud/iot_v1/resources.py +++ b/clearblade/cloud/iot_v1/resources.py @@ -1,3 +1,49 @@ +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from enum import Enum +from datetime import datetime class MqttState(): r"""Indicates whether an MQTT connection is enabled or disabled. @@ -56,10 +102,70 @@ class PublicKeyCertificateFormat(): X509_CERTIFICATE_PEM = "X509_CERTIFICATE_PEM" -class PublicKeyFormat: +class PublicKeyFormat(Enum): r"""The supported formats for the public key.""" UNSPECIFIED_PUBLIC_KEY_FORMAT = "UNSPECIFIED_PUBLIC_KEY_FORMAT" RSA_PEM = "RSA_PEM" RSA_X509_PEM = "RSA_X509_PEM" ES256_PEM = "ES256_PEM" ES256_X509_PEM = "ES256_X509_PEM" + +class PublicKeyCredential(): + def __init__(self, format: PublicKeyFormat, key: bytes): + self.format = format + self.key = key + + def __getitem__(self, arg): + return getattr(self, arg) + + def get(self, arg): + return getattr(self, arg) + + +class DeviceCredential(): + def __init__(self, public_key, expiration_time=''): + if isinstance(public_key, dict): + self.publicKey = PublicKeyCredential(public_key['publicKey']['format'], public_key['publicKey']['key']) + else: + self.publicKey = public_key + self.expirationTime = expiration_time + + def __getitem__(self, arg): + return getattr(self, arg) + + def get(self, arg): + return getattr(self, arg) + + @property + def public_key(self): + return self.publicKey + + @property + def expiration_time(self): + return self.expirationTime + + @classmethod + def convert_credentials_for_create_update(cls, credentials): + for index, credential in enumerate(credentials): + # Convert credential to dict if it is not + updateDeviceCredential = False + if (isinstance(credential, DeviceCredential)): + credential = credential.__dict__ + updateDeviceCredential = True + + if 'publicKey' in credential: + if (isinstance(credential['publicKey'], PublicKeyCredential)): + credential['publicKey'] = credential['publicKey'].__dict__ + # Convert PublicKeyFormat to string + credential['publicKey']['format'] = PublicKeyFormat(credential['publicKey']['format']).value + updateDeviceCredential = True + + if 'expirationTime' in credential: + if (isinstance(credential['expirationTime'], datetime)): + credential['expirationTime'] = credential['expirationTime'].strftime('%Y-%m-%dT%H:%M:%SZ') + updateDeviceCredential = True + + if updateDeviceCredential: + credentials[index] = credential + + return credentials \ No newline at end of file diff --git a/clearblade/cloud/iot_v1/utils.py b/clearblade/cloud/iot_v1/utils.py index 9b3b0de6..72c7d938 100644 --- a/clearblade/cloud/iot_v1/utils.py +++ b/clearblade/cloud/iot_v1/utils.py @@ -1,3 +1,47 @@ +""" +"Copyright 2023 ClearBlade Inc." + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2023 ClearBlade Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Copyright 2018 Google LLC +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + from typing import Any def find_project_region_registry_from_parent(parent): From aac94ea0a0521389da93787868a6aabdd8b50834 Mon Sep 17 00:00:00 2001 From: akash-sharma-ext-clearblade Date: Fri, 26 May 2023 07:26:07 -0700 Subject: [PATCH 08/10] bug: def get_value; if json_data None throws err (#30) * bug: def get_value; if json_data None throws err * In def convertCredentials... added check for None --- clearblade/cloud/iot_v1/device_types.py | 11 ++++++----- clearblade/cloud/iot_v1/utils.py | 5 +++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index 17ec961e..f2be17d7 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -50,11 +50,12 @@ import base64 def convertCredentialsFormatsFromString(credentials): - # Converts public Key Format from string to object of class PublicKeyFormat - for index, credential in enumerate(credentials): - if 'publicKey' in credential: - credential['publicKey']['format'] = PublicKeyFormat(credential['publicKey']['format']) - credentials[index] = DeviceCredential(credential['publicKey'], credential['expirationTime']) + if credentials is not None: + # Converts public Key Format from string to object of class PublicKeyFormat + for index, credential in enumerate(credentials): + if 'publicKey' in credential: + credential['publicKey']['format'] = PublicKeyFormat(credential['publicKey']['format']) + credentials[index] = DeviceCredential(credential['publicKey'], credential['expirationTime']) return credentials class Device(): diff --git a/clearblade/cloud/iot_v1/utils.py b/clearblade/cloud/iot_v1/utils.py index 72c7d938..b0068a46 100644 --- a/clearblade/cloud/iot_v1/utils.py +++ b/clearblade/cloud/iot_v1/utils.py @@ -61,8 +61,9 @@ def find_project_region_registry_from_parent(parent): return project_region_registry_dict def get_value(json_data, key): - if key in json_data: - return json_data[key] + if json_data is not None: + if key in json_data: + return json_data[key] return None class SingletonMetaClass(type): From 436b4ef893c2953ac65611ecafa936346b33be97 Mon Sep 17 00:00:00 2001 From: Jim Bouquet Date: Tue, 6 Jun 2023 10:29:07 -0500 Subject: [PATCH 09/10] Added name attribute to Device class. --- clearblade/cloud/iot_v1/device_types.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index f2be17d7..38ba65db 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -63,6 +63,8 @@ class Device(): Data class for Clearblade Device """ # TODO: find a better way to construct the Device object. I dont like so much parameter in a constructor + # From google SDK docs: The field ``name`` must be empty. The server generates ``name`` from the device + # registry ``id`` and the ``parent`` field. def __init__(self, id: str, num_id: str = None, credentials: list = [], last_heartbeat_time: str = None, last_event_time: str = None, @@ -74,6 +76,7 @@ def __init__(self, id: str, num_id: str = None, log_level: str = LogLevel.NONE, meta_data: dict = {}, gateway_config : dict = {"gatewayType": GatewayType.NON_GATEWAY}) -> None: self._id = id + self._name = '' self._num_id = num_id self._credentials = credentials self._last_heartbeat_time = last_heartbeat_time @@ -140,6 +143,10 @@ def from_json(json): def id(self): return self._id + @property + def name(self): + return self._name + @property def num_id(self): return self._num_id From b3098df23ab94edc0987c8451b3f6dcea660c18a Mon Sep 17 00:00:00 2001 From: Jim Bouquet Date: Tue, 6 Jun 2023 12:26:59 -0500 Subject: [PATCH 10/10] Added code to populate the name attribute on the device object. --- clearblade/cloud/iot_v1/device_types.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index 38ba65db..dd21121a 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -120,7 +120,7 @@ def from_json(json): last_config_send_time = lastConfigSendTimeFromJson last_error_time = lastErrorTimeFromJson - return Device( + theDevice = Device( id=get_value(json, 'id'), num_id=get_value(json, 'numId'), credentials=convertCredentialsFormatsFromString(get_value(json, 'credentials')), @@ -139,6 +139,13 @@ def from_json(json): gateway_config=get_value(json, 'gatewayConfig') ) + #Since _name is a private attribute, we have to populate it like this + #because we don't allow "name" to be passed in the constructor + + theDevice._name=get_value(json, 'name') + + return theDevice + @property def id(self): return self._id