From de601a55ade866363826b5c06325d8f25147685e Mon Sep 17 00:00:00 2001 From: Akash Sharma Date: Thu, 29 Dec 2022 13:19:42 -0600 Subject: [PATCH 01/29] 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/29] 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/29] 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/29] 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/29] 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/29] '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/29] 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/29] 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/29] 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/29] 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 From 9c6a027de1cc36f6e45ccffef5e60462086fb932 Mon Sep 17 00:00:00 2001 From: skysharma Date: Tue, 6 Jun 2023 12:47:15 -0500 Subject: [PATCH 11/29] setup.py; ver 2.0.2 -> 2.0.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 06f2af21..ec786744 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ name = "clearblade-cloud-iot" description = "Cloud IoT API client library" -version = "2.0.2" +version = "2.0.3" release_status = "Development Status :: 5 - Production/Stable" dependencies = ["httpx", "proto-plus"] From 02a876c66e0583f049c85a270b15809d59e0ce73 Mon Sep 17 00:00:00 2001 From: jslavin-clearblade <116581763+jslavin-clearblade@users.noreply.github.com> Date: Wed, 14 Jun 2023 13:33:26 -0400 Subject: [PATCH 12/29] Update README.rst Improved grammar and readability. Please merge if I didn't make any semantic changes. --- README.rst | 69 +++++++++++++++++------------------------------------- 1 file changed, 21 insertions(+), 48 deletions(-) diff --git a/README.rst b/README.rst index 5cf93962..3c3ace27 100644 --- a/README.rst +++ b/README.rst @@ -1,49 +1,23 @@ -.. 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 +Python Client for ClearBlade IoT Core API ================================================================ -Quick Start +Quick start ----------- -In order to use this library, you first need to go through the following steps: +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**, pointing 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 ~~~~~~~~~~~~ -Install this library in a `virtualenv`_ using pip. `virtualenv`_ is a tool to -create isolated Python environments. The basic problem it addresses is one of -dependencies and versions, and indirectly permissions. +Install this library in a `virtualenv`_ using pip. `virtualenv`_ is a tool to create isolated Python environments. It addresses dependencies and versions and, indirectly, permissions. -With `virtualenv`_, it's possible to install this library without needing system -install permissions, and without clashing with the installed system -dependencies. +With `virtualenv`_, it's possible to install this library without system install permissions and clashing with the installed system dependencies. .. _`virtualenv`: https://virtualenv.pypa.io/en/latest/ @@ -51,10 +25,10 @@ dependencies. Code samples and snippets ~~~~~~~~~~~~~~~~~~~~~~~~~ -Code samples and snippets live in the `samples/clearblade` folder. +Code samples and snippets live in the samples/clearblade folder. -Supported Python Versions +Supported Python versions ^^^^^^^^^^^^^^^^^^^^^^^^^ Our client libraries are compatible with all current `active`_ and `maintenance`_ versions of Python. @@ -64,12 +38,11 @@ Python >= 3.7 .. _active: https://devguide.python.org/devcycle/#in-development-main-branch .. _maintenance: https://devguide.python.org/devcycle/#maintenance-branches -Unsupported Python Versions +Unsupported Python versions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Python <= 3.6 -If you are using an `end-of-life`_ -version of Python, we recommend that you update as soon as possible to an actively supported version. +If you are using an `end-of-life`_ version of Python, we recommend you update it to an actively supported version as soon as possible. .. _end-of-life: https://devguide.python.org/devcycle/#end-of-life-branches @@ -92,19 +65,19 @@ Windows virtualenv \Scripts\activate -Next Steps +Next steps ~~~~~~~~~~ -- clone the github repository. +- Clone the GitHub repository. -- and execute the setup.py file like , python setup.py install. +- 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. +- Everything else should work if you change your imports from google.cloud to clearblade.cloud. Note about types of times and binaryData ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- By default the following parameters are returned as the shown types: +- 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** @@ -116,20 +89,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. +- If this environment variable is not set, or is set to any unexpected values, then the default types listed previously are used. -Note about running from source instead of PyPi (pip) module: +Note about running from the source instead of the PyPi (pip) module: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- To temporarily use the source code in this repo. instead of the installed PyPi (pip) module do the following: +- 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 **. +2. Check out 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: +4. Precede that line with **import sys** and **sys.path.insert(0, )**. The path must end with python-iot. 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 + from clearblade.cloud import iot_v1 From 820a1dbc106a15c8b5893dade317fff3b3c1b39a Mon Sep 17 00:00:00 2001 From: ronak-ingress <114172748+ronak-ingress@users.noreply.github.com> Date: Fri, 16 Jun 2023 21:13:42 +0530 Subject: [PATCH 13/29] Iot 1012 syskey token env vars (#35) * updated device class and enums (#19) * updated send_command function to base64 encode payload (#18) * Changed 'cloud_ack_time' to 'cloud_update_time' (#20) --------- Co-authored-by: rajasd27 Co-authored-by: Akash Sharma --- README.rst | 5 ++ clearblade/cloud/iot_v1/config_manager.py | 72 ++++++++++++++--------- clearblade/cloud/iot_v1/device_types.py | 2 +- clearblade/cloud/iot_v1/devices.py | 2 +- 4 files changed, 50 insertions(+), 31 deletions(-) diff --git a/README.rst b/README.rst index 5cf93962..50bf0a61 100644 --- a/README.rst +++ b/README.rst @@ -118,6 +118,11 @@ Note about types of times and binaryData - If this environment variable is not set, or is set to any unexpeced values, then the default types listed previously are used. +- By default calls to some SDK functions cause a REST request to be sent to acquire the Registry API Keys found on the IoTCore UI Registry Details page. Those keys are cached for subsequent operations in order to improve performance. However these caches do not persist if the application is stopped and restarted as would be the case with typical serverless functions (e.g. Google Cloud Functions, AWS Lambda etc.). In order to improve the performance of those functions, the REST call can be prevented by passing the API Keys as environment variables: +1. **REGISTRY_URL**: **string** +2. **REGISTRY_SYSKEY**: **string** +3. **REGISTRY_TOKEN**: **string** + 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: diff --git a/clearblade/cloud/iot_v1/config_manager.py b/clearblade/cloud/iot_v1/config_manager.py index fb8cd559..4ac94e1b 100644 --- a/clearblade/cloud/iot_v1/config_manager.py +++ b/clearblade/cloud/iot_v1/config_manager.py @@ -82,6 +82,11 @@ def _set_admin_clearblade_config(self): self._admin_config = ClearBladeConfig(system_key=system_key, auth_token=auth_token, api_url=api_url, project=project) + def _create_regional_config_from_env(self, systemKey:str = None, serviceAccountToken:str = None, url:str = None, region:str = None)-> ClearBladeConfig : + return ClearBladeConfig(system_key=systemKey, auth_token=serviceAccountToken, api_url=url, + region=region, project=self._admin_config.project) + + def _create_regional_config(self, regional_json: json = None)-> ClearBladeConfig : system_key = regional_json['systemKey'] auth_token = regional_json['serviceAccountToken'] @@ -102,20 +107,25 @@ def _set_regional_config(self, region:str = None, registry:str = None): if not region or not registry: raise Exception("Either location or registry name is not provided") - - sync_client = SyncClient(clearblade_config=self._admin_config) - request_body = {'region':region,'registry':registry, 'project':self._admin_config.project} - response = sync_client.post(api_name="getRegistryCredentials", is_webhook_folder=False, - request_body=request_body) - - if response.status_code != 200: - raise Exception( - f"\n\nRegistry Information not found! Please check if the given registry exists\nProject: {self._admin_config.project}\nRegistry: {registry}\nRegion: {region}" - ) - - response_json = response.json() - response_json['region'] = region - self._regional_config = self._create_regional_config(regional_json=response_json) + + system_key = os.environ.get("REGISTRY_SYSKEY") + registry_token = os.environ.get("REGISTRY_TOKEN") + url = os.environ.get("REGISTRY_URL") + if (system_key and registry_token and url) : + self._regional_config = self._create_regional_config_from_env(systemKey=system_key , serviceAccountToken = registry_token, url = url, region = region) + else : + request_body = {'region':region,'registry':registry, 'project':self._admin_config.project} + sync_client = SyncClient(clearblade_config=self._admin_config) + response = sync_client.post(api_name="getRegistryCredentials", is_webhook_folder=False, + request_body=request_body) + if response.status_code != 200: + raise Exception( + f"\n\nRegistry Information not found! Please check if the given registry exists\nProject: {self._admin_config.project}\nRegistry: {registry}\nRegion: {region}" + ) + response_json = response.json() + response_json['region'] = region + self._regional_config = self._create_regional_config(regional_json=response_json) + async def _set_regional_config_async(self, region:str = None, registry:str = None): @@ -126,21 +136,25 @@ async def _set_regional_config_async(self, region:str = None, registry:str = Non if not registry: registry = self.registry_name - - async_client = AsyncClient(clearblade_config=self._admin_config) - request_body = {'region':region,'registry':registry, 'project':self._admin_config.project} - response = await async_client.post(api_name="getRegistryCredentials", - is_webhook_folder=False, - request_body=request_body) - - if response.status_code != 200: - raise Exception( - f"\n\nRegistry Information not found! Please check if the given registry exists\nProject: {self._admin_config.project}\nRegistry: {registry}\nRegion: {region}" - ) - - response_json = response.json() - response_json['region'] = region - self._regional_config = self._create_regional_config(regional_json=response_json) + system_key = os.environ.get("REGISTRY_SYSKEY") + registry_token = os.environ.get("REGISTRY_TOKEN") + url = os.environ.get("REGISTRY_URL") + if (system_key and registry_token) : + self._regional_config = self._create_regional_config_from_env(systemKey=system_key , serviceAccountToken = registry_token, url = url, region = region) + else : + async_client = AsyncClient(clearblade_config=self._admin_config) + request_body = {'region':region,'registry':registry, 'project':self._admin_config.project} + response = await async_client.post(api_name="getRegistryCredentials", + is_webhook_folder=False, + request_body=request_body) + + if response.status_code != 200: + raise Exception( + f"\n\nRegistry Information not found! Please check if the given registry exists\nProject: {self._admin_config.project}\nRegistry: {registry}\nRegion: {region}" + ) + response_json = response.json() + response_json['region'] = region + self._regional_config = self._create_regional_config(regional_json=response_json) @property diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index dd21121a..a63d3524 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -634,4 +634,4 @@ def from_json(devices_list_json): if 'nextPageToken' in devices_list_json: next_page_token = devices_list_json['nextPageToken'] - return ListDevicesResponse(devices=devices, next_page_token=next_page_token) + return ListDevicesResponse(devices=devices, next_page_token=next_page_token) \ No newline at end of file diff --git a/clearblade/cloud/iot_v1/devices.py b/clearblade/cloud/iot_v1/devices.py index 97633860..244492e9 100644 --- a/clearblade/cloud/iot_v1/devices.py +++ b/clearblade/cloud/iot_v1/devices.py @@ -490,4 +490,4 @@ async def config_versions_list_async(self, if response.status_code == 200: return ListDeviceConfigVersionsResponse.from_json(response.json()) - return response + return response \ No newline at end of file From e075ea3372c516373055cdf2027c6dc274a686fa Mon Sep 17 00:00:00 2001 From: akash-sharma-ext-clearblade Date: Mon, 19 Jun 2023 04:46:51 -0700 Subject: [PATCH 14/29] Develop (#37) * ListDevicesRequest >> _prepare_params_for_list: (#16) modified to handle gatewayListOptions following NodeJS SDK * updated device class and enums (#19) * updated send_command function to base64 encode payload (#18) * Changed 'cloud_ack_time' to 'cloud_update_time' (#20) * 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 * '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 * 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. * 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 * Added name attribute to Device class. * Added code to populate the name attribute on the device object. * Iot 1012 syskey token env vars (#35) * updated device class and enums (#19) * updated send_command function to base64 encode payload (#18) * Changed 'cloud_ack_time' to 'cloud_update_time' (#20) --------- Co-authored-by: rajasd27 Co-authored-by: Akash Sharma --------- Co-authored-by: rajasd27 Co-authored-by: Jim Bouquet Co-authored-by: ronak-ingress <114172748+ronak-ingress@users.noreply.github.com> --- README.rst | 6 ++ clearblade/cloud/iot_v1/config_manager.py | 72 ++++++++++++++--------- clearblade/cloud/iot_v1/device_types.py | 2 +- clearblade/cloud/iot_v1/devices.py | 2 +- 4 files changed, 51 insertions(+), 31 deletions(-) diff --git a/README.rst b/README.rst index 3c3ace27..8acd3e93 100644 --- a/README.rst +++ b/README.rst @@ -91,6 +91,12 @@ Note about types of times and binaryData - If this environment variable is not set, or is set to any unexpected values, then the default types listed previously are used. +- By default calls to some SDK functions cause a REST request to be sent to acquire the Registry API Keys found on the IoTCore UI Registry Details page. Those keys are cached for subsequent operations in order to improve performance. However these caches do not persist if the application is stopped and restarted as would be the case with typical serverless functions (e.g. Google Cloud Functions, AWS Lambda etc.). In order to improve the performance of those functions, the REST call can be prevented by passing the API Keys as environment variables: +1. **REGISTRY_URL**: **string** +2. **REGISTRY_SYSKEY**: **string** +3. **REGISTRY_TOKEN**: **string** + +Note about running from source instead of PyPi (pip) module: Note about running from the source instead of the PyPi (pip) module: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - To temporarily use the source code in this repo instead of the installed PyPi (pip) module, do the following: diff --git a/clearblade/cloud/iot_v1/config_manager.py b/clearblade/cloud/iot_v1/config_manager.py index fb8cd559..4ac94e1b 100644 --- a/clearblade/cloud/iot_v1/config_manager.py +++ b/clearblade/cloud/iot_v1/config_manager.py @@ -82,6 +82,11 @@ def _set_admin_clearblade_config(self): self._admin_config = ClearBladeConfig(system_key=system_key, auth_token=auth_token, api_url=api_url, project=project) + def _create_regional_config_from_env(self, systemKey:str = None, serviceAccountToken:str = None, url:str = None, region:str = None)-> ClearBladeConfig : + return ClearBladeConfig(system_key=systemKey, auth_token=serviceAccountToken, api_url=url, + region=region, project=self._admin_config.project) + + def _create_regional_config(self, regional_json: json = None)-> ClearBladeConfig : system_key = regional_json['systemKey'] auth_token = regional_json['serviceAccountToken'] @@ -102,20 +107,25 @@ def _set_regional_config(self, region:str = None, registry:str = None): if not region or not registry: raise Exception("Either location or registry name is not provided") - - sync_client = SyncClient(clearblade_config=self._admin_config) - request_body = {'region':region,'registry':registry, 'project':self._admin_config.project} - response = sync_client.post(api_name="getRegistryCredentials", is_webhook_folder=False, - request_body=request_body) - - if response.status_code != 200: - raise Exception( - f"\n\nRegistry Information not found! Please check if the given registry exists\nProject: {self._admin_config.project}\nRegistry: {registry}\nRegion: {region}" - ) - - response_json = response.json() - response_json['region'] = region - self._regional_config = self._create_regional_config(regional_json=response_json) + + system_key = os.environ.get("REGISTRY_SYSKEY") + registry_token = os.environ.get("REGISTRY_TOKEN") + url = os.environ.get("REGISTRY_URL") + if (system_key and registry_token and url) : + self._regional_config = self._create_regional_config_from_env(systemKey=system_key , serviceAccountToken = registry_token, url = url, region = region) + else : + request_body = {'region':region,'registry':registry, 'project':self._admin_config.project} + sync_client = SyncClient(clearblade_config=self._admin_config) + response = sync_client.post(api_name="getRegistryCredentials", is_webhook_folder=False, + request_body=request_body) + if response.status_code != 200: + raise Exception( + f"\n\nRegistry Information not found! Please check if the given registry exists\nProject: {self._admin_config.project}\nRegistry: {registry}\nRegion: {region}" + ) + response_json = response.json() + response_json['region'] = region + self._regional_config = self._create_regional_config(regional_json=response_json) + async def _set_regional_config_async(self, region:str = None, registry:str = None): @@ -126,21 +136,25 @@ async def _set_regional_config_async(self, region:str = None, registry:str = Non if not registry: registry = self.registry_name - - async_client = AsyncClient(clearblade_config=self._admin_config) - request_body = {'region':region,'registry':registry, 'project':self._admin_config.project} - response = await async_client.post(api_name="getRegistryCredentials", - is_webhook_folder=False, - request_body=request_body) - - if response.status_code != 200: - raise Exception( - f"\n\nRegistry Information not found! Please check if the given registry exists\nProject: {self._admin_config.project}\nRegistry: {registry}\nRegion: {region}" - ) - - response_json = response.json() - response_json['region'] = region - self._regional_config = self._create_regional_config(regional_json=response_json) + system_key = os.environ.get("REGISTRY_SYSKEY") + registry_token = os.environ.get("REGISTRY_TOKEN") + url = os.environ.get("REGISTRY_URL") + if (system_key and registry_token) : + self._regional_config = self._create_regional_config_from_env(systemKey=system_key , serviceAccountToken = registry_token, url = url, region = region) + else : + async_client = AsyncClient(clearblade_config=self._admin_config) + request_body = {'region':region,'registry':registry, 'project':self._admin_config.project} + response = await async_client.post(api_name="getRegistryCredentials", + is_webhook_folder=False, + request_body=request_body) + + if response.status_code != 200: + raise Exception( + f"\n\nRegistry Information not found! Please check if the given registry exists\nProject: {self._admin_config.project}\nRegistry: {registry}\nRegion: {region}" + ) + response_json = response.json() + response_json['region'] = region + self._regional_config = self._create_regional_config(regional_json=response_json) @property diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index dd21121a..a63d3524 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -634,4 +634,4 @@ def from_json(devices_list_json): if 'nextPageToken' in devices_list_json: next_page_token = devices_list_json['nextPageToken'] - return ListDevicesResponse(devices=devices, next_page_token=next_page_token) + return ListDevicesResponse(devices=devices, next_page_token=next_page_token) \ No newline at end of file diff --git a/clearblade/cloud/iot_v1/devices.py b/clearblade/cloud/iot_v1/devices.py index 97633860..244492e9 100644 --- a/clearblade/cloud/iot_v1/devices.py +++ b/clearblade/cloud/iot_v1/devices.py @@ -490,4 +490,4 @@ async def config_versions_list_async(self, if response.status_code == 200: return ListDeviceConfigVersionsResponse.from_json(response.json()) - return response + return response \ No newline at end of file From f5d340010015dbf38a1ccaf9bbd0629699e9090f Mon Sep 17 00:00:00 2001 From: skysharma Date: Mon, 19 Jun 2023 07:01:35 -0500 Subject: [PATCH 15/29] README formatting. --- README.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 8acd3e93..a6eb0b18 100644 --- a/README.rst +++ b/README.rst @@ -91,14 +91,18 @@ Note about types of times and binaryData - If this environment variable is not set, or is set to any unexpected values, then the default types listed previously are used. +Note about performance: +~~~~~~~~~~~~~~~~~~~~~~~ + - By default calls to some SDK functions cause a REST request to be sent to acquire the Registry API Keys found on the IoTCore UI Registry Details page. Those keys are cached for subsequent operations in order to improve performance. However these caches do not persist if the application is stopped and restarted as would be the case with typical serverless functions (e.g. Google Cloud Functions, AWS Lambda etc.). In order to improve the performance of those functions, the REST call can be prevented by passing the API Keys as environment variables: + 1. **REGISTRY_URL**: **string** 2. **REGISTRY_SYSKEY**: **string** 3. **REGISTRY_TOKEN**: **string** -Note about running from source instead of PyPi (pip) module: Note about running from the source instead of the 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. From 2d4a9eebcf69cb4ee0ff2826cf37ff8db5a694e7 Mon Sep 17 00:00:00 2001 From: skysharma Date: Mon, 19 Jun 2023 07:03:59 -0500 Subject: [PATCH 16/29] setup.py; 2.0.3 -> 2.0.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ec786744..f1454a73 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ name = "clearblade-cloud-iot" description = "Cloud IoT API client library" -version = "2.0.3" +version = "2.0.4" release_status = "Development Status :: 5 - Production/Stable" dependencies = ["httpx", "proto-plus"] From d561ace04ea0965e29d781678ba48a1fbce0afb0 Mon Sep 17 00:00:00 2001 From: jslavin-clearblade <116581763+jslavin-clearblade@users.noreply.github.com> Date: Tue, 27 Jun 2023 12:44:02 -0400 Subject: [PATCH 17/29] Update UPGRADING.md --- UPGRADING.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 1b711d5a..74ba8d77 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -42,7 +42,7 @@ limitations under the License. --> # 2.0.0 Migration Guide -The 2.0 release of the `clearblade-cloud-iot` client is a significant upgrade based on addition of two new classes in **iot_v1**: +The 2.0 release of the `clearblade-cloud-iot` client is a significant upgrade based on the addition of two new classes in **iot_v1**: - **DeviceCredential** - **PublicKeyCredential** @@ -52,7 +52,7 @@ The release also includes enhancements to these classes already present in **iot - **DeviceConfig** - **DeviceState** -The version was made with the intent of minimizing required code changes. **However these changes should be considrered Breaking changes**. +The version was made with the intent of minimizing the required code changes. **However, these changes should be considered breaking changes**. # @@ -64,13 +64,13 @@ The version was made with the intent of minimizing required code changes. **Howe **After**: device.credentials is of type **[DeviceCredential]** (i.e. list of objects of class DeviceCredential). - The **DeviceCredential** class has these features for usability: + The **DeviceCredential** class has these usability features: - - A **get** method that mimics the **get** method of a dict. + - A **get** method that mimics the dict **get** method. - Allows accessing attributes using dot notation OR square-brackets. - - Supports camel-case as well as snake-case for accessing attributes: + - Supports camel-case and snake-case for accessing attributes: - e.g. All these are valid for retrieving the public key: + e.g., All these are valid for retrieving the public key: - **public_key = device.credentials[0]['publicKey']** - **public_key = device.credentials[0]['public_key']** @@ -81,7 +81,7 @@ The version was made with the intent of minimizing required code changes. **Howe # -2. This refers to pub_key mentioned in the previous section. +2. This refers to pub_key mentioned in the previous section: **Before**: public_key was of type **dict**. @@ -89,12 +89,12 @@ The version was made with the intent of minimizing required code changes. **Howe **After**: public_key is an object of class **PublicKeyCredential**. - The **PublicKeyCredential** class has these features for usability: + The **PublicKeyCredential** class has these usability features: - - A **get** method that mimics the **get** method of a dict. + - A **get** method that mimics the dict **get** method. - Allows accessing attributes using dot notation OR square-brackets. - e.g. All these are valid for retrieving the public key format: + e.g., All these are valid for retrieving the public key format: - **format = public_key['format']** - **format = public_key.get('format')** @@ -110,13 +110,13 @@ The version was made with the intent of minimizing required code changes. **Howe **After**: dev_config is an object of class **DeviceConfig**. - The **DeviceConfig** class has these features for usability: + The **DeviceConfig** class has these usability features: - - A **get** method that mimics the **get** method of a dict. + - A **get** method that mimics the dict **get** method. - Allows accessing attributes using dot notation OR square-brackets. - - Supports camel-case as well as snake-case for accessing attributes: + - Supports camel-case and snake-case for accessing attributes: - e.g. All these are valid for retrieving the cloud_update_time: + e.g., All these are valid for retrieving the cloud_update_time: - **cloud_update_time = device.credentials[0]['cloudUpdateTime']** - **cloud_update_time = device.credentials[0]['cloud_update_time']** @@ -135,13 +135,13 @@ The version was made with the intent of minimizing required code changes. **Howe **After**: dev_state is an object of class **DeviceState**. - The **DeviceState** class has these features for usability: + The **DeviceState** class has these usability features: - - A **get** method that mimics the **get** method of a dict. + - A **get** method that mimics the dict **get** method. - Allows accessing attributes using dot notation OR square-brackets. - - Supports camel-case as well as snake-case for accessing attributes: + - Supports camel-case and snake-case for accessing attributes: - e.g. All these are valid for retrieving the binary_data: + e.g., All these are valid for retrieving the binary_data: - **binary_data = device.credentials[0]['binaryData']** - **binary_data = device.credentials[0]['binary_data']** From 773bbeb5cdc20af58db3e0011f82bec2b946be38 Mon Sep 17 00:00:00 2001 From: jslavin-clearblade <116581763+jslavin-clearblade@users.noreply.github.com> Date: Tue, 19 Sep 2023 15:38:42 -0400 Subject: [PATCH 18/29] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a6eb0b18..8adb4569 100644 --- a/README.rst +++ b/README.rst @@ -94,7 +94,7 @@ Note about types of times and binaryData Note about performance: ~~~~~~~~~~~~~~~~~~~~~~~ -- By default calls to some SDK functions cause a REST request to be sent to acquire the Registry API Keys found on the IoTCore UI Registry Details page. Those keys are cached for subsequent operations in order to improve performance. However these caches do not persist if the application is stopped and restarted as would be the case with typical serverless functions (e.g. Google Cloud Functions, AWS Lambda etc.). In order to improve the performance of those functions, the REST call can be prevented by passing the API Keys as environment variables: +- By default, calls to some SDK functions cause a REST request to be sent to acquire the registry API keys found on the IoTCore UI Registry Details page. Those keys are cached for subsequent operations to improve performance. However, these caches do not persist if the application is stopped and restarted, as would be the case with typical serverless functions (e.g., Google Cloud Functions, AWS Lambda, etc.). To improve those functions' performance, the REST call can be prevented by passing the API keys as environment variables: 1. **REGISTRY_URL**: **string** 2. **REGISTRY_SYSKEY**: **string** From 17fe93da680be5800036d822675a40a37c368f43 Mon Sep 17 00:00:00 2001 From: akash-sharma-ext-clearblade Date: Tue, 10 Oct 2023 12:22:00 -0500 Subject: [PATCH 19/29] Default log_level: NONE-> LOG_LEVEL_UNSPECIFIED (#41) --- clearblade/cloud/iot_v1/device_types.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index a63d3524..f686ffcd 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -73,7 +73,9 @@ def __init__(self, id: str, num_id: str = None, 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 = LogLevel.NONE, meta_data: dict = {}, gateway_config : dict = {"gatewayType": GatewayType.NON_GATEWAY}) -> None: + log_level: str = LogLevel.LOG_LEVEL_UNSPECIFIED, + meta_data: dict = {}, + gateway_config: dict = {"gatewayType": GatewayType.NON_GATEWAY}) -> None: self._id = id self._name = '' From dc87b2b48c60c9c3614a122228a38f4d76f4f4a1 Mon Sep 17 00:00:00 2001 From: skysharma Date: Tue, 10 Oct 2023 12:37:03 -0500 Subject: [PATCH 20/29] setup.py; 2.0.4 -> 2.0.5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f1454a73..c788fdca 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ name = "clearblade-cloud-iot" description = "Cloud IoT API client library" -version = "2.0.4" +version = "2.0.5" release_status = "Development Status :: 5 - Production/Stable" dependencies = ["httpx", "proto-plus"] From 09eefb3339ac1955135e40476806034374ae1fe8 Mon Sep 17 00:00:00 2001 From: skysharma Date: Wed, 11 Oct 2023 13:51:58 -0500 Subject: [PATCH 21/29] setup.py; chng author_email to info@clearblade.com --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f1454a73..98f3f949 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ description=description, long_description=readme, author="Clearblade", - author_email="googleapis-packages@oogle.com", + author_email="info@clearblade.com", license="Apache 2.0", url="https://github.com/clearblade/python-iot", classifiers=[ From b99540e14c67eb489ee366ca134541b076a54a40 Mon Sep 17 00:00:00 2001 From: jslavin-clearblade <116581763+jslavin-clearblade@users.noreply.github.com> Date: Mon, 12 Feb 2024 13:00:40 -0500 Subject: [PATCH 22/29] Update README.rst --- README.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 8adb4569..4a069bde 100644 --- a/README.rst +++ b/README.rst @@ -8,9 +8,11 @@ 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**, pointing to your ClearBlade service account JSON file. +2. Set an environment variable **CLEARBLADE_CONFIGURATION**, pointing to your ClearBlade service account JSON file. Look `here`_ for how to create a service account and download a credentials 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. +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. + +.. _`here`: https://clearblade.atlassian.net/wiki/spaces/IC/pages/2240675843/Add+service+accounts+to+a+project Installation ~~~~~~~~~~~~ From 39d064aa74959e07800bcbfe47b6059d9b083bd1 Mon Sep 17 00:00:00 2001 From: jslavin-clearblade <116581763+jslavin-clearblade@users.noreply.github.com> Date: Mon, 20 May 2024 12:56:54 -0400 Subject: [PATCH 23/29] Update README.rst --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 4a069bde..0fb1937f 100644 --- a/README.rst +++ b/README.rst @@ -3,6 +3,7 @@ Python Client for ClearBlade IoT Core API Quick start ----------- +**Note: This SDK is for use with ClearBlade IoT Core and NOT ClearBlade IoT Enterprise. The Python SDK for ClearBlade IoT Core can be found here: https://github.com/ClearBlade/python-iot.** To use this library, you first need to go through the following steps: From 9456e8c931dc10caf39a4729267669749c35616b Mon Sep 17 00:00:00 2001 From: jslavin-clearblade <116581763+jslavin-clearblade@users.noreply.github.com> Date: Mon, 20 May 2024 12:57:59 -0400 Subject: [PATCH 24/29] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0fb1937f..a7753b18 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Python Client for ClearBlade IoT Core API Quick start ----------- -**Note: This SDK is for use with ClearBlade IoT Core and NOT ClearBlade IoT Enterprise. The Python SDK for ClearBlade IoT Core can be found here: https://github.com/ClearBlade/python-iot.** +**Note: This SDK is for use with ClearBlade IoT Core and NOT ClearBlade IoT Enterprise. The Python SDK for ClearBlade IoT Enterprise can be found here: https://github.com/ClearBlade/ClearBlade-Python-SDK/.** To use this library, you first need to go through the following steps: From 30a0caf9276be85024bc45e6fa8a7ac1c7824de1 Mon Sep 17 00:00:00 2001 From: ronak-ingress <114172748+ronak-ingress@users.noreply.github.com> Date: Mon, 21 Jul 2025 19:36:17 +0530 Subject: [PATCH 25/29] Update device_manager.py (#42) removed Maximum IDs: 10,000. --- google/cloud/iot_v1/types/device_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google/cloud/iot_v1/types/device_manager.py b/google/cloud/iot_v1/types/device_manager.py index 93d6622d..8b8b19f5 100644 --- a/google/cloud/iot_v1/types/device_manager.py +++ b/google/cloud/iot_v1/types/device_manager.py @@ -318,11 +318,11 @@ class ListDevicesRequest(proto.Message): ``projects/my-project/locations/us-central1/registries/my-registry``. device_num_ids (Sequence[int]): A list of device numeric IDs. If empty, this - field is ignored. Maximum IDs: 10,000. + field is ignored. device_ids (Sequence[str]): A list of device string IDs. For example, ``['device0', 'device12']``. If empty, this field is - ignored. Maximum IDs: 10,000 + ignored. field_mask (google.protobuf.field_mask_pb2.FieldMask): The fields of the ``Device`` resource to be returned in the response. The fields ``id`` and ``num_id`` are always From 4f93c02f4eaff3b389c14d2207e3b3edf06fa23c Mon Sep 17 00:00:00 2001 From: sky-sharma Date: Mon, 21 Jul 2025 08:01:33 -0700 Subject: [PATCH 26/29] Implementing, using new FieldMask class (#39) (#43) --- clearblade/cloud/iot_v1/device_types.py | 4 ++-- clearblade/cloud/iot_v1/resources.py | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/clearblade/cloud/iot_v1/device_types.py b/clearblade/cloud/iot_v1/device_types.py index f686ffcd..0b7d6cdd 100644 --- a/clearblade/cloud/iot_v1/device_types.py +++ b/clearblade/cloud/iot_v1/device_types.py @@ -43,7 +43,7 @@ """ from typing import List -from .resources import GatewayType, LogLevel, PublicKeyFormat, PublicKeyCredential, DeviceCredential +from .resources import GatewayType, LogLevel, PublicKeyFormat, PublicKeyCredential, DeviceCredential, FieldMask from .utils import get_value import os from proto.datetime_helpers import DatetimeWithNanoseconds @@ -591,7 +591,7 @@ def _prepare_params_for_list(self): if self.device_ids: params['deviceIds'] = self.device_ids if self.field_mask: - params['fieldMask'] = self.field_mask + params['fieldMask'] = FieldMask.convert_fieldmask_for_list(self.field_mask) if self.gateway_list_options : if 'associationsDeviceId' in self.gateway_list_options: params['gatewayListOptions.associationsDeviceId'] = self.gateway_list_options['associationsDeviceId'] diff --git a/clearblade/cloud/iot_v1/resources.py b/clearblade/cloud/iot_v1/resources.py index b933a701..0d37e590 100644 --- a/clearblade/cloud/iot_v1/resources.py +++ b/clearblade/cloud/iot_v1/resources.py @@ -168,4 +168,22 @@ def convert_credentials_for_create_update(cls, credentials): if updateDeviceCredential: credentials[index] = credential - return credentials \ No newline at end of file + return credentials + +class FieldMask(): + def __init__(self, paths: [str]): + self.paths = paths + + def __getitem__(self, arg): + return getattr(self, arg) + + def get(self, arg): + return getattr(self, arg) + + @classmethod + def convert_fieldmask_for_list(cls, field_mask): + if (isinstance(field_mask, FieldMask)): + field_mask = field_mask.__dict__ + if 'paths' in field_mask: + field_mask = field_mask['paths'] + return field_mask From d0e17e9137e7e452d4f4709851afb9c7b44b577c Mon Sep 17 00:00:00 2001 From: sky-sharma Date: Mon, 21 Jul 2025 16:20:51 -0700 Subject: [PATCH 27/29] setup.py; 2.0.5 -> 2.0.6 (#44) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 97e73031..e31e60ff 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ name = "clearblade-cloud-iot" description = "Cloud IoT API client library" -version = "2.0.5" +version = "2.0.6" release_status = "Development Status :: 5 - Production/Stable" dependencies = ["httpx", "proto-plus"] From a431572d7ac49dced1ca03b758b780a24201c1cb Mon Sep 17 00:00:00 2001 From: sky-sharma Date: Tue, 23 Sep 2025 15:52:04 -0700 Subject: [PATCH 28/29] Correct broken docs links and improve README (#45) * Tried to clarify differences between IoT Core and IoT Enterprise; corrected docs links * Formatting * Formatting * Formatting * Formatting * Formatting * Formatting * Formatting * Formatting * Formatting * Added link to explain IoT Core Standard vs Enterprise * Improved link explaining differences between IoT Core Standard & Enterprise * setup.py; ver 2.0.6 -> 2.0.7 --------- Co-authored-by: sky-sharma --- README.rst | 25 +++++++++++++++++++++---- setup.py | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index a7753b18..3e95a9cd 100644 --- a/README.rst +++ b/README.rst @@ -3,17 +3,34 @@ Python Client for ClearBlade IoT Core API Quick start ----------- -**Note: This SDK is for use with ClearBlade IoT Core and NOT ClearBlade IoT Enterprise. The Python SDK for ClearBlade IoT Enterprise can be found here: https://github.com/ClearBlade/ClearBlade-Python-SDK/.** +**Notes** -To use this library, you first need to go through the following steps: +1. This SDK is for use with ClearBlade IoT Core Standard and ClearBlade IoT Core Enterprise. + +2. ClearBlade IoT **Core** Enterprise is DIFFERENT from ClearBlade IoT Enterprise!!! + +3. If you are using ClearBlade but not using ClearBlade IoT Core (Standard or Enterprise), then you need a different SDK that can be found `here `_. + +4. To understand how IoT Core Enterprise differs from Standard, look `here `_. + + + +To use this SDK, 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**, pointing to your ClearBlade service account JSON file. Look `here`_ for how to create a service account and download a credentials JSON file. +2. Set an environment variable **CLEARBLADE_CONFIGURATION**, pointing to your ClearBlade service account JSON file (see below). 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. -.. _`here`: https://clearblade.atlassian.net/wiki/spaces/IC/pages/2240675843/Add+service+accounts+to+a+project + +To create a service account and download a credentials JSON file look at the appropriate link below: + +`ClearBlade IoT Core Standard `_ + +`ClearBlade IoT Core Enterprise `_ + + Installation ~~~~~~~~~~~~ diff --git a/setup.py b/setup.py index e31e60ff..56ba1951 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ name = "clearblade-cloud-iot" description = "Cloud IoT API client library" -version = "2.0.6" +version = "2.0.7" release_status = "Development Status :: 5 - Production/Stable" dependencies = ["httpx", "proto-plus"] From c64c1dc60326a07099e12f721dd2ffc364ddb870 Mon Sep 17 00:00:00 2001 From: sky-sharma Date: Wed, 24 Sep 2025 05:05:56 -0700 Subject: [PATCH 29/29] README different link format (#46) Co-authored-by: sky-sharma --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 3e95a9cd..372f5a91 100644 --- a/README.rst +++ b/README.rst @@ -9,9 +9,9 @@ Quick start 2. ClearBlade IoT **Core** Enterprise is DIFFERENT from ClearBlade IoT Enterprise!!! -3. If you are using ClearBlade but not using ClearBlade IoT Core (Standard or Enterprise), then you need a different SDK that can be found `here `_. +3. If you are using ClearBlade but not using ClearBlade IoT Core (Standard or Enterprise), then you need this different SDK: ``_. -4. To understand how IoT Core Enterprise differs from Standard, look `here `_. +4. To understand how IoT Core Enterprise differs from Standard, look here: ``_.