iotconnect-lib 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. iotconnect_lib-1.0.0/LICENSE.md +20 -0
  2. iotconnect_lib-1.0.0/PKG-INFO +71 -0
  3. iotconnect_lib-1.0.0/README.md +41 -0
  4. iotconnect_lib-1.0.0/pyproject.toml +63 -0
  5. iotconnect_lib-1.0.0/setup.cfg +4 -0
  6. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/__init__.py +1 -0
  7. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/config.py +37 -0
  8. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/dra.py +157 -0
  9. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/error.py +18 -0
  10. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/mqtt.py +317 -0
  11. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/protocol/__init__.py +0 -0
  12. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/protocol/c2d.py +40 -0
  13. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/protocol/d2c.py +37 -0
  14. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/protocol/discovery.py +25 -0
  15. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/protocol/files.py +21 -0
  16. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/protocol/identity.py +79 -0
  17. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/util.py +142 -0
  18. iotconnect_lib-1.0.0/src/avnet/iotconnect/sdk/sdklib/version.py +0 -0
  19. iotconnect_lib-1.0.0/src/iotconnect_lib.egg-info/PKG-INFO +71 -0
  20. iotconnect_lib-1.0.0/src/iotconnect_lib.egg-info/SOURCES.txt +24 -0
  21. iotconnect_lib-1.0.0/src/iotconnect_lib.egg-info/dependency_links.txt +1 -0
  22. iotconnect_lib-1.0.0/src/iotconnect_lib.egg-info/requires.txt +4 -0
  23. iotconnect_lib-1.0.0/src/iotconnect_lib.egg-info/top_level.txt +1 -0
  24. iotconnect_lib-1.0.0/tests/test_c2d.py +89 -0
  25. iotconnect_lib-1.0.0/tests/test_discovery.py +56 -0
  26. iotconnect_lib-1.0.0/tests/test_telemetry.py +76 -0
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2020-2024 Avnet
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: iotconnect-lib
3
+ Version: 1.0.0
4
+ Summary: Avnet IoTConnect SDK Library
5
+ Author-email: Nik Markovic <nikola.markovic@avnet.com>
6
+ Project-URL: Homepage, https://github.com/avnet-iotconnect/iotc-python-lib
7
+ Keywords: IoTconnect,AWS,IoTCore,Azure,IoTHub,Greengrass
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Natural Language :: English
12
+ Classifier: Operating System :: MacOS :: MacOS X
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Operating System :: POSIX
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Communications
22
+ Classifier: Topic :: Internet
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE.md
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest; extra == "test"
28
+ Requires-Dist: pytest-cov; extra == "test"
29
+ Dynamic: license-file
30
+
31
+
32
+ > This document is reformatted to better viewing as a standalone document.
33
+ > We recommend visiting this [GitHub v1.0.0 link](https://github.com/avnet-iotconnect/iotc-python-lib/blob/v1.0.0/) for best experience.
34
+
35
+ # Introduction
36
+ This project is the library that abstracts the /IOTCONNECT protocols for the SDKs like the
37
+ [/IOTCONNECT Python Lite SDK](https://github.com/avnet-iotconnect/iotc-python-lite-sdk)
38
+ and the [/IOTCONNECT Greengrass SDK](https://github.com/avnet-iotconnect/iotc-python-greengrass-sdk).
39
+
40
+ This library should generally not be used in other projects as the SDKs should be able
41
+ to provide all the functionality that you would need in your python applications.
42
+
43
+ # Features
44
+
45
+ The library provides common code for interacting with /IOTCONNECT MQTT and HTTP device connectivity services:
46
+ * Format Telemetry (Reporting) messages
47
+ * Provide events for OTA and Command processing
48
+ * Streamline OTA and Command acknowledgements
49
+ * Obtain Discovery and Identity information
50
+
51
+ # Using The Library
52
+
53
+ To use this library in your project, ensure that your pyton project depends on iotconnect-lib.
54
+ Use a fixed major version dependency (E.g. "iotconnect-lib<2.0.0".) to
55
+ avoid potential major version breaking your application calls.
56
+
57
+ The best way to learn how to use this library is to examine the unit test usage examples
58
+ in the [tests](https://github.com/avnet-iotconnect/iotc-python-lib/blob/v1.0.0/tests) directory or use the SDK implementations listed above in this document
59
+ for reference.
60
+
61
+ # Testing
62
+
63
+ Regression tests should be run with every release using pytest.
64
+
65
+ See [tests/TESTS_CONFIGURATION.md](https://github.com/avnet-iotconnect/iotc-python-lib/blob/v1.0.0/tests/TESTS_CONFIGURATION.md) for more details.
66
+
67
+ # Licensing
68
+
69
+ This python package is distributed under the [MIT License](https://github.com/avnet-iotconnect/iotc-python-lib/blob/v1.0.0/LICENSE.md).
70
+
71
+
@@ -0,0 +1,41 @@
1
+
2
+ > This document is reformatted to better viewing as a standalone document.
3
+ > We recommend visiting this [GitHub v1.0.0 link](https://github.com/avnet-iotconnect/iotc-python-lib/blob/v1.0.0/) for best experience.
4
+
5
+ # Introduction
6
+ This project is the library that abstracts the /IOTCONNECT protocols for the SDKs like the
7
+ [/IOTCONNECT Python Lite SDK](https://github.com/avnet-iotconnect/iotc-python-lite-sdk)
8
+ and the [/IOTCONNECT Greengrass SDK](https://github.com/avnet-iotconnect/iotc-python-greengrass-sdk).
9
+
10
+ This library should generally not be used in other projects as the SDKs should be able
11
+ to provide all the functionality that you would need in your python applications.
12
+
13
+ # Features
14
+
15
+ The library provides common code for interacting with /IOTCONNECT MQTT and HTTP device connectivity services:
16
+ * Format Telemetry (Reporting) messages
17
+ * Provide events for OTA and Command processing
18
+ * Streamline OTA and Command acknowledgements
19
+ * Obtain Discovery and Identity information
20
+
21
+ # Using The Library
22
+
23
+ To use this library in your project, ensure that your pyton project depends on iotconnect-lib.
24
+ Use a fixed major version dependency (E.g. "iotconnect-lib<2.0.0".) to
25
+ avoid potential major version breaking your application calls.
26
+
27
+ The best way to learn how to use this library is to examine the unit test usage examples
28
+ in the [tests](https://github.com/avnet-iotconnect/iotc-python-lib/blob/v1.0.0/tests) directory or use the SDK implementations listed above in this document
29
+ for reference.
30
+
31
+ # Testing
32
+
33
+ Regression tests should be run with every release using pytest.
34
+
35
+ See [tests/TESTS_CONFIGURATION.md](https://github.com/avnet-iotconnect/iotc-python-lib/blob/v1.0.0/tests/TESTS_CONFIGURATION.md) for more details.
36
+
37
+ # Licensing
38
+
39
+ This python package is distributed under the [MIT License](https://github.com/avnet-iotconnect/iotc-python-lib/blob/v1.0.0/LICENSE.md).
40
+
41
+
@@ -0,0 +1,63 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "iotconnect-lib"
7
+ dynamic = ["version"]
8
+ description = "Avnet IoTConnect SDK Library"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ authors = [
12
+ { name = "Nik Markovic", email = "nikola.markovic@avnet.com" },
13
+ ]
14
+
15
+ keywords = [
16
+ "IoTconnect",
17
+ "AWS",
18
+ "IoTCore",
19
+ "Azure",
20
+ "IoTHub",
21
+ "Greengrass"
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 3 - Alpha",
25
+ "Intended Audience :: Developers",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Natural Language :: English",
28
+ "Operating System :: MacOS :: MacOS X",
29
+ "Operating System :: Microsoft :: Windows",
30
+ "Operating System :: POSIX",
31
+ "Programming Language :: Python",
32
+ "Programming Language :: Python :: 3",
33
+ "Programming Language :: Python :: 3.9",
34
+ "Programming Language :: Python :: 3.10",
35
+ "Programming Language :: Python :: 3.11",
36
+ "Programming Language :: Python :: 3.12",
37
+ "Topic :: Communications",
38
+ "Topic :: Internet",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ test = ["pytest", "pytest-cov"]
43
+
44
+
45
+ [project.urls]
46
+ Homepage = "https://github.com/avnet-iotconnect/iotc-python-lib"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.setuptools.dynamic]
52
+ version = {attr = "avnet.iotconnect.sdk.sdklib.__version__"}
53
+
54
+ [tool.coverage.run]
55
+ source = ["src"] # Basic source directory
56
+ omit = [
57
+ "*/tests/*",
58
+ "*/__pycache__/*"
59
+ ]
60
+
61
+ [tool.pytest.ini_options]
62
+ testpaths = ["tests"]
63
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = '1.0.0'
@@ -0,0 +1,37 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024 Avnet
3
+ # Authors: Nikola Markovic <nikola.markovic@avnet.com> et al.
4
+
5
+ from .error import DeviceConfigError
6
+
7
+
8
+
9
+ class DeviceProperties:
10
+ """
11
+ This class represents the /IOTCONNECT device properties
12
+ like device Unique ID (DUID) and account properties lke CPID, Environment etc.
13
+ """
14
+
15
+ def __init__(self, duid: str, cpid: str, env: str, platform: str):
16
+ """
17
+ :param platform: The IoTconnect IoT platform - Either "aws" for AWS IoTCore or "az" for Azure IoTHub
18
+ :param env: Your account environment. You can locate this in you IoTConnect web UI at Settings -> Key Value
19
+ :param cpid: Your account CPID (Company ID). You can locate this in you IoTConnect web UI at Settings -> Key Value
20
+ :param duid: Your Device Unique ID
21
+ """
22
+
23
+ self.duid = duid
24
+ self.cpid = cpid
25
+ self.env = env
26
+ self.platform = platform
27
+
28
+ def validate(self):
29
+ """ Format validation in cases where custom topic configuration may be needed """
30
+ if self.duid is None or len(self.duid) < 2:
31
+ raise DeviceConfigError('DeviceProperties: Device Unique ID (DUID) is missing')
32
+ if self.cpid is None or len(self.cpid) < 2:
33
+ raise DeviceConfigError('DeviceProperties: CPID value is missing')
34
+ if self.env is None or len(self.env) < 2:
35
+ raise DeviceConfigError('DeviceProperties: Environment value is missing')
36
+ if self.platform not in ("aws", "az"):
37
+ raise DeviceConfigError('DeviceProperties: Platform must be "aws" or "az"')
@@ -0,0 +1,157 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024 Avnet
3
+ # Authors: Nikola Markovic <nikola.markovic@avnet.com> et al.
4
+
5
+ import json
6
+ import urllib.parse
7
+ import urllib.request
8
+ from typing import Final, Union, Optional
9
+ from urllib.error import HTTPError, URLError
10
+
11
+ from avnet.iotconnect.sdk.sdklib.config import DeviceProperties
12
+ from avnet.iotconnect.sdk.sdklib.error import DeviceConfigError
13
+ from avnet.iotconnect.sdk.sdklib.protocol.discovery import IotcDiscoveryResponseJson
14
+ from avnet.iotconnect.sdk.sdklib.protocol.identity import ProtocolIdentityPJson, ProtocolMetaJson, ProtocolIdentityResponseJson
15
+ from avnet.iotconnect.sdk.sdklib.util import deserialize_dataclass
16
+
17
+
18
+ class DeviceIdentityData:
19
+ def __init__(self, mqtt: ProtocolIdentityPJson, metadata: ProtocolMetaJson):
20
+ self.host = mqtt.h
21
+ self.client_id = mqtt.id
22
+ self.username = mqtt.un
23
+ self.topics = mqtt.topics
24
+
25
+ self.pf = metadata.pf
26
+ self.is_edge_device = metadata.edge
27
+ self.is_gateway_device = metadata.gtw
28
+ self.protocol_version = str(metadata.v)
29
+
30
+ class DraDiscoveryUrl:
31
+ method: str = "GET" # To clarify that get should be used to parse the response
32
+ API_URL_FORMAT: Final[str] = "https://discovery.iotconnect.io/api/v2.1/dsdk/cpId/%s/env/%s?pf=%s"
33
+
34
+ def __init__(self, config: DeviceProperties):
35
+ self.config = config
36
+
37
+ def get_api_url(self) -> str:
38
+ return DraDiscoveryUrl.API_URL_FORMAT % (
39
+ urllib.parse.quote(self.config.cpid, safe=''),
40
+ urllib.parse.quote(self.config.env, safe=''),
41
+ urllib.parse.quote(self.config.platform, safe='')
42
+ )
43
+
44
+
45
+ class DraIdentityUrl:
46
+ UID_API_URL_FORMAT: Final[str] = "%s/uid/%s"
47
+
48
+ def __init__(self, base_url):
49
+ self.base_url = base_url
50
+
51
+ method: str = "GET" # To clarify that get should be used to parse the response
52
+
53
+ def get_uid_api_url(self, config: DeviceProperties) -> str:
54
+ return DraIdentityUrl.UID_API_URL_FORMAT % (
55
+ self.base_url,
56
+ urllib.parse.quote(config.duid, safe='')
57
+ )
58
+
59
+ def _validate_identity_response(self, ird: ProtocolIdentityResponseJson):
60
+ # TODO: validate and throw DeviceConfigError
61
+ pass
62
+
63
+
64
+ class DraDeviceInfoParser:
65
+ EC_RESPONSE_MAPPING = [
66
+ "OK – No Error",
67
+ "Device not found. Device is not whitelisted to platform.",
68
+ "Device is not active.",
69
+ "Un-Associated. Device has not any template associated with it.",
70
+ "Device is not acquired. Device is created but it is in release state.",
71
+ "Device is disabled. It’s disabled from broker by Platform Admin",
72
+ "Company not found as SID is not valid",
73
+ "Subscription is expired.",
74
+ "Connection Not Allowed.",
75
+ "Invalid Bootstrap Certificate.",
76
+ "Invalid Operational Certificate."
77
+ ]
78
+
79
+ @classmethod
80
+ def _parsing_common(cls, what: str, rd: Union[IotcDiscoveryResponseJson, ProtocolIdentityResponseJson]):
81
+ """ Helper to parse either discovery or identity response common error fields """
82
+
83
+ ec_message = 'not available'
84
+ has_error = False
85
+ if rd.d is not None:
86
+ if rd.d.ec != 0:
87
+ has_error = True
88
+ if rd.d.ec <= len(cls.EC_RESPONSE_MAPPING):
89
+ ec_message = 'ec=%d (%s)' % (rd.d.ec, cls.EC_RESPONSE_MAPPING[rd.d.ec])
90
+ else:
91
+ ec_message = 'ec==%d' % rd.d.ec
92
+ else:
93
+ has_error = True
94
+
95
+ if rd.status != 200:
96
+ has_error = True
97
+
98
+ if has_error:
99
+ raise DeviceConfigError(
100
+ '%s failed. Error: "%s" status=%d message=%s' % (
101
+ what,
102
+ ec_message,
103
+ rd.status,
104
+ rd.message or "(message not available)"
105
+ )
106
+ )
107
+
108
+ @classmethod
109
+ def parse_discovery_response(cls, discovery_response: str) -> str:
110
+ """ Parses discovery response JSON and Returns base URL or raises DeviceConfigError """
111
+
112
+ drd: IotcDiscoveryResponseJson
113
+ try:
114
+ drd = deserialize_dataclass(IotcDiscoveryResponseJson, json.loads(discovery_response))
115
+ except json.JSONDecodeError as json_error:
116
+ raise DeviceConfigError("Discovery JSON Parsing Error: %s" % str(json_error))
117
+ cls._parsing_common("Discovery", drd)
118
+
119
+ if drd.d.bu is None:
120
+ raise DeviceConfigError("Discovery response is missing base URL")
121
+
122
+ return drd.d.bu
123
+
124
+ @classmethod
125
+ def parse_identity_response(cls, identity_response: str) -> DeviceIdentityData:
126
+ ird: ProtocolIdentityResponseJson
127
+ try:
128
+ ird = deserialize_dataclass(ProtocolIdentityResponseJson, json.loads(identity_response))
129
+ except json.JSONDecodeError as json_error:
130
+ raise DeviceConfigError("Identity JSON Parsing Error: %s" % str(json_error))
131
+ cls._parsing_common("Identity", ird)
132
+
133
+ return DeviceIdentityData(ird.d.p, ird.d.meta)
134
+
135
+ class DeviceRestApi:
136
+ def __init__(self, config: DeviceProperties, verbose: Optional[bool] = False):
137
+ self.config = config
138
+ self.verbose = verbose
139
+
140
+ def get_identity_data(self) -> DeviceIdentityData:
141
+ try:
142
+ if self.verbose:
143
+ print("Requesting Discovery Data %s..." % DraDiscoveryUrl(self.config).get_api_url())
144
+ resp = urllib.request.urlopen(urllib.request.Request(DraDiscoveryUrl(self.config).get_api_url()))
145
+ discovery_base_url = DraDeviceInfoParser.parse_discovery_response(resp.read())
146
+
147
+ if self.verbose:
148
+ print("Requesting Identity Data %s..." % DraIdentityUrl(discovery_base_url).get_uid_api_url(self.config))
149
+ resp = urllib.request.urlopen(DraIdentityUrl(discovery_base_url).get_uid_api_url(self.config))
150
+ identity_response = DraDeviceInfoParser.parse_identity_response(resp.read())
151
+ return identity_response
152
+
153
+ except HTTPError as http_error:
154
+ raise DeviceConfigError(http_error)
155
+
156
+ except URLError as url_error:
157
+ raise DeviceConfigError(str(url_error))
@@ -0,0 +1,18 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (C) 2024 Avnet
3
+ # Authors: Nikola Markovic <nikola.markovic@avnet.com> et al.
4
+
5
+ class DeviceConfigError(RuntimeError):
6
+ def __init__(self, message: str):
7
+ self.msg = message
8
+ super().__init__(message)
9
+
10
+ class ClientError(RuntimeError):
11
+ def __init__(self, message: str):
12
+ self.msg = message
13
+ super().__init__(message)
14
+
15
+ class C2DDecodeError(RuntimeError):
16
+ def __init__(self, message: str):
17
+ self.msg = message
18
+ super().__init__(message)