Skip to content

Commit c384d42

Browse files
author
Jim Bennett
committedApr 11, 2020
Copying in code from the external repo
1 parent 74d5688 commit c384d42

23 files changed

+999
-344
lines changed
 

‎.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ bundles
88
*.DS_Store
99
.eggs
1010
dist
11-
**/*.egg-info
11+
**/*.egg-info
12+
.vscode/settings.json

‎.pylintrc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ indent-after-paren=4
217217
indent-string=' '
218218

219219
# Maximum number of characters on a single line.
220-
max-line-length=100
220+
max-line-length=140
221221

222222
# Maximum number of lines in a module
223223
max-module-lines=1000
@@ -395,7 +395,7 @@ valid-metaclass-classmethod-first-arg=mcs
395395
[DESIGN]
396396

397397
# Maximum number of arguments for function / method
398-
max-args=5
398+
max-args=6
399399

400400
# Maximum number of attributes for a class (see R0902).
401401
# max-attributes=7

‎adafruit_azureiot.py

Lines changed: 0 additions & 240 deletions
This file was deleted.

‎adafruit_azureiot/__init__.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2019 Jim Bennett
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
"""
23+
`adafruit_azureiot`
24+
================================================================================
25+
26+
Microsoft Azure IoT for CircuitPython
27+
28+
* Author(s): Jim Bennett, Elena Horton
29+
30+
Implementation Notes
31+
--------------------
32+
33+
**Software and Dependencies:**
34+
35+
* Adafruit CircuitPython firmware for the supported boards:
36+
https://github.com/adafruit/circuitpython/releases
37+
38+
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
39+
* Adafruit's ESP32SPI library: https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI
40+
* Community HMAC library: https://github.com/jimbobbennett/CircuitPython_HMAC
41+
* Community base64 library: https://github.com/jimbobbennett/CircuitPython_Base64
42+
* Community Parse library: https://github.com/jimbobbennett/CircuitPython_Parse
43+
"""
44+
45+
__version__ = "0.0.0-auto.0"
46+
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_AzureIoT.git"

‎adafruit_azureiot/constants.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""This file is for maintaining constants that could be changed or added to over time for different scenarios
2+
"""
3+
4+
DPS_API_VERSION = "2018-11-01"
5+
IOTC_API_VERSION = "2016-11-14"
6+
DPS_END_POINT = "global.azure-devices-provisioning.net"
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
"""
2+
Device Registration
3+
=====================
4+
5+
Handles registration of IoT Central devices, and gets the hostname to use when connecting
6+
to IoT Central over MQTT
7+
"""
8+
9+
import gc
10+
import json
11+
import time
12+
import circuitpython_base64 as base64
13+
import circuitpython_hmac as hmac
14+
import circuitpython_parse as parse
15+
from adafruit_esp32spi.adafruit_esp32spi_wifimanager import ESPSPI_WiFiManager
16+
import adafruit_logging as logging
17+
from adafruit_logging import Logger
18+
import adafruit_hashlib as hashlib
19+
from . import constants
20+
21+
22+
AZURE_HTTP_ERROR_CODES = [400, 401, 404, 403, 412, 429, 500] # Azure HTTP Status Codes
23+
24+
25+
class DeviceRegistrationError(Exception):
26+
"""
27+
An error from the device registration
28+
"""
29+
30+
def __init__(self, message):
31+
super(DeviceRegistrationError, self).__init__(message)
32+
self.message = message
33+
34+
35+
class DeviceRegistration:
36+
"""
37+
Handles registration of IoT Central devices, and gets the hostname to use when connecting
38+
to IoT Central over MQTT
39+
"""
40+
41+
_dps_endpoint = constants.DPS_END_POINT
42+
_dps_api_version = constants.DPS_API_VERSION
43+
_loop_interval = 2
44+
45+
@staticmethod
46+
def _parse_http_status(status_code, status_reason):
47+
"""Parses status code, throws error based on Azure IoT Common Error Codes.
48+
:param int status_code: HTTP status code.
49+
:param str status_reason: Description of HTTP status.
50+
"""
51+
for error in AZURE_HTTP_ERROR_CODES:
52+
if error == status_code:
53+
raise TypeError("Error {0}: {1}".format(status_code, status_reason))
54+
55+
def __init__(self, wifi_manager: ESPSPI_WiFiManager, id_scope: str, device_id: str, key: str, logger: Logger = None):
56+
"""Creates an instance of the device registration
57+
:param wifi_manager: WiFiManager object from ESPSPI_WiFiManager.
58+
:param str id_scope: The ID scope of the device to register
59+
:param str device_id: The device ID of the device to register
60+
:param str key: The primary or secondary key of the device to register
61+
:param adafruit_logging.Logger key: The primary or secondary key of the device to register
62+
"""
63+
wifi_type = str(type(wifi_manager))
64+
if "ESPSPI_WiFiManager" not in wifi_type:
65+
raise TypeError("This library requires a WiFiManager object.")
66+
67+
self._wifi_manager = wifi_manager
68+
self._id_scope = id_scope
69+
self._device_id = device_id
70+
self._key = key
71+
self._logger = logger if logger is not None else logging.getLogger("log")
72+
73+
@staticmethod
74+
def compute_derived_symmetric_key(secret, reg_id):
75+
"""Computes a derived symmetric key from a secret and a message
76+
"""
77+
secret = base64.b64decode(secret)
78+
return base64.b64encode(hmac.new(secret, msg=reg_id.encode("utf8"), digestmod=hashlib.sha256).digest())
79+
80+
def _loop_assign(self, operation_id, headers) -> str:
81+
uri = "https://%s/%s/registrations/%s/operations/%s?api-version=%s" % (
82+
self._dps_endpoint,
83+
self._id_scope,
84+
self._device_id,
85+
operation_id,
86+
self._dps_api_version,
87+
)
88+
self._logger.info("- iotc :: _loop_assign :: " + uri)
89+
target = parse.urlparse(uri)
90+
91+
response = self.__run_get_request_with_retry(target.geturl(), headers)
92+
93+
try:
94+
data = response.json()
95+
except Exception as error:
96+
err = "ERROR: " + str(error) + " => " + str(response)
97+
self._logger.error(err)
98+
raise DeviceRegistrationError(err)
99+
100+
loop_try = 0
101+
102+
if data is not None and "status" in data:
103+
if data["status"] == "assigning":
104+
time.sleep(self._loop_interval)
105+
if loop_try < 20:
106+
loop_try = loop_try + 1
107+
return self._loop_assign(operation_id, headers)
108+
109+
err = "ERROR: Unable to provision the device."
110+
self._logger.error(err)
111+
raise DeviceRegistrationError(err)
112+
113+
if data["status"] == "assigned":
114+
state = data["registrationState"]
115+
return state["assignedHub"]
116+
else:
117+
data = str(data)
118+
119+
err = "DPS L => " + str(data)
120+
self._logger.error(err)
121+
raise DeviceRegistrationError(err)
122+
123+
def __run_put_request_with_retry(self, url, body, headers):
124+
retry = 0
125+
response = None
126+
127+
while True:
128+
gc.collect()
129+
try:
130+
self._logger.debug("Trying to send...")
131+
response = self._wifi_manager.put(url, json=body, headers=headers)
132+
self._logger.debug("Sent!")
133+
break
134+
except RuntimeError as runtime_error:
135+
self._logger.info("Could not send data, retrying after 0.5 seconds: " + str(runtime_error))
136+
retry = retry + 1
137+
138+
if retry >= 10:
139+
self._logger.error("Failed to send data")
140+
raise
141+
142+
time.sleep(0.5)
143+
continue
144+
145+
gc.collect()
146+
return response
147+
148+
def __run_get_request_with_retry(self, url, headers):
149+
retry = 0
150+
response = None
151+
152+
while True:
153+
gc.collect()
154+
try:
155+
self._logger.debug("Trying to send...")
156+
response = self._wifi_manager.get(url, headers=headers)
157+
self._logger.debug("Sent!")
158+
break
159+
except RuntimeError as runtime_error:
160+
self._logger.info("Could not send data, retrying after 0.5 seconds: " + str(runtime_error))
161+
retry = retry + 1
162+
163+
if retry >= 10:
164+
self._logger.error("Failed to send data")
165+
raise
166+
167+
time.sleep(0.5)
168+
continue
169+
170+
gc.collect()
171+
return response
172+
173+
def register_device(self, expiry: int) -> str:
174+
"""
175+
Registers the device with the IoT Central device registration service.
176+
Returns the hostname of the IoT hub to use over MQTT
177+
:param str expiry: The expiry time
178+
"""
179+
# pylint: disable=c0103
180+
sr = self._id_scope + "%2Fregistrations%2F" + self._device_id
181+
sig_no_encode = DeviceRegistration.compute_derived_symmetric_key(self._key, sr + "\n" + str(expiry))
182+
sig_encoded = parse.quote(sig_no_encode, "~()*!.'")
183+
auth_string = "SharedAccessSignature sr=" + sr + "&sig=" + sig_encoded + "&se=" + str(expiry) + "&skn=registration"
184+
185+
headers = {
186+
"content-type": "application/json; charset=utf-8",
187+
"user-agent": "iot-central-client/1.0",
188+
"Accept": "*/*",
189+
}
190+
191+
if auth_string is not None:
192+
headers["authorization"] = auth_string
193+
194+
body = {"registrationId": self._device_id}
195+
196+
uri = "https://%s/%s/registrations/%s/register?api-version=%s" % (
197+
self._dps_endpoint,
198+
self._id_scope,
199+
self._device_id,
200+
self._dps_api_version,
201+
)
202+
target = parse.urlparse(uri)
203+
204+
self._logger.info("Connecting...")
205+
self._logger.info("URL: " + target.geturl())
206+
self._logger.info("body: " + json.dumps(body))
207+
print("headers: " + json.dumps(headers))
208+
209+
response = self.__run_put_request_with_retry(target.geturl(), body, headers)
210+
211+
data = None
212+
try:
213+
data = response.json()
214+
except Exception as e:
215+
err = "ERROR: non JSON is received from " + self._dps_endpoint + " => " + str(response) + " .. message : " + str(e)
216+
self._logger.error(err)
217+
raise DeviceRegistrationError(err)
218+
219+
if "errorCode" in data:
220+
err = "DPS => " + str(data)
221+
self._logger.error(err)
222+
raise DeviceRegistrationError(err)
223+
224+
time.sleep(1)
225+
return self._loop_assign(data["operationId"], headers)

‎adafruit_azureiot/iot_error.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""
2+
An error from the IoT service
3+
"""
4+
5+
6+
class IoTError(Exception):
7+
"""
8+
An error from the IoT service
9+
"""
10+
11+
def __init__(self, message):
12+
super(IoTError, self).__init__(message)
13+
self.message = message

‎adafruit_azureiot/iot_mqtt.py

Lines changed: 403 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""Connectivity to Azure IoT Central
2+
"""
3+
4+
import json
5+
import time
6+
from adafruit_esp32spi.adafruit_esp32spi_wifimanager import ESPSPI_WiFiManager
7+
from device_registration import DeviceRegistration
8+
from iot_error import IoTError
9+
from iot_mqtt import IoTMQTT, IoTMQTTCallback, IoTResponse
10+
import adafruit_logging as logging
11+
12+
13+
class IoTCentralDevice(IoTMQTTCallback):
14+
"""A device client for the Azure IoT Central service
15+
"""
16+
17+
def connection_status_change(self, connected: bool) -> None:
18+
"""Called when the connection status changes
19+
"""
20+
if self.on_connection_status_changed is not None:
21+
# pylint: disable=E1102
22+
self.on_connection_status_changed(connected)
23+
24+
# pylint: disable=W0613, R0201
25+
def direct_method_called(self, method_name: str, data) -> IoTResponse:
26+
"""Called when a direct method is invoked
27+
"""
28+
if self.on_command_executed is not None:
29+
# pylint: disable=E1102
30+
return self.on_command_executed(method_name, data)
31+
32+
raise IoTError("on_command_executed not set")
33+
34+
def device_twin_desired_updated(self, desired_property_name: str, desired_property_value, desired_version: int) -> None:
35+
"""Called when the device twin is updated
36+
"""
37+
if self.on_property_changed is not None:
38+
# pylint: disable=E1102
39+
self.on_property_changed(desired_property_name, desired_property_value, desired_version)
40+
41+
# when a desired property changes, update the reported to match to keep them in sync
42+
self.send_property(desired_property_name, desired_property_value)
43+
44+
def device_twin_reported_updated(self, reported_property_name: str, reported_property_value, reported_version: int) -> None:
45+
"""Called when the device twin is updated
46+
"""
47+
if self.on_property_changed is not None:
48+
# pylint: disable=E1102
49+
self.on_property_changed(reported_property_name, reported_property_value, reported_version)
50+
51+
# pylint: disable=R0913
52+
def __init__(
53+
self, wifi_manager: ESPSPI_WiFiManager, id_scope: str, device_id: str, key: str, token_expires: int = 21600, logger: logging = None
54+
):
55+
super(IoTCentralDevice, self).__init__()
56+
self._wifi_manager = wifi_manager
57+
self._id_scope = id_scope
58+
self._device_id = device_id
59+
self._key = key
60+
self._token_expires = token_expires
61+
self._logger = logger
62+
self._device_registration = None
63+
self._mqtt = None
64+
65+
self.on_connection_status_changed = None
66+
self.on_command_executed = None
67+
self.on_property_changed = None
68+
69+
def connect(self):
70+
"""Connects to Azure IoT Central
71+
"""
72+
self._device_registration = DeviceRegistration(self._wifi_manager, self._id_scope, self._device_id, self._key, self._logger)
73+
74+
token_expiry = int(time.time() + self._token_expires)
75+
hostname = self._device_registration.register_device(token_expiry)
76+
self._mqtt = IoTMQTT(self, hostname, self._device_id, self._key, self._token_expires, self._logger)
77+
78+
self._mqtt.connect()
79+
80+
def disconnect(self):
81+
"""Disconnects from the MQTT broker
82+
"""
83+
if self._mqtt is None:
84+
raise IoTError("You are not connected to IoT Central")
85+
86+
self._mqtt.disconnect()
87+
88+
def is_connected(self) -> bool:
89+
"""Gets if there is an open connection to the MQTT broker
90+
"""
91+
if self._mqtt is not None:
92+
return self._mqtt.is_connected()
93+
94+
return False
95+
96+
def loop(self):
97+
"""Listens for MQTT messages
98+
"""
99+
if self._mqtt is None:
100+
raise IoTError("You are not connected to IoT Central")
101+
102+
self._mqtt.loop()
103+
104+
def send_property(self, property_name, data):
105+
"""Updates the value of a writable property
106+
"""
107+
if self._mqtt is None:
108+
raise IoTError("You are not connected to IoT Central")
109+
110+
patch_json = {property_name: data}
111+
patch = json.dumps(patch_json)
112+
self._mqtt.send_twin_patch(patch)
113+
114+
def send_telemetry(self, data):
115+
"""Sends telemetry to the IoT Central app
116+
"""
117+
if self._mqtt is None:
118+
raise IoTError("You are not connected to IoT Central")
119+
120+
if isinstance(data, dict):
121+
data = json.dumps(data)
122+
123+
self._mqtt.send_device_to_cloud_message(data)

‎adafruit_azureiot/iothub_device.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
"""Connectivity to Azure IoT Hub
2+
"""
3+
4+
import json
5+
from iot_error import IoTError
6+
from iot_mqtt import IoTMQTT, IoTMQTTCallback, IoTResponse
7+
import adafruit_logging as logging
8+
9+
10+
def _validate_keys(connection_string_parts):
11+
"""Raise ValueError if incorrect combination of keys
12+
"""
13+
host_name = connection_string_parts.get(HOST_NAME)
14+
shared_access_key_name = connection_string_parts.get(SHARED_ACCESS_KEY_NAME)
15+
shared_access_key = connection_string_parts.get(SHARED_ACCESS_KEY)
16+
device_id = connection_string_parts.get(DEVICE_ID)
17+
18+
if host_name and device_id and shared_access_key:
19+
pass
20+
elif host_name and shared_access_key and shared_access_key_name:
21+
pass
22+
else:
23+
raise ValueError("Invalid Connection String - Incomplete")
24+
25+
26+
DELIMITER = ";"
27+
VALUE_SEPARATOR = "="
28+
29+
HOST_NAME = "HostName"
30+
SHARED_ACCESS_KEY_NAME = "SharedAccessKeyName"
31+
SHARED_ACCESS_KEY = "SharedAccessKey"
32+
SHARED_ACCESS_SIGNATURE = "SharedAccessSignature"
33+
DEVICE_ID = "DeviceId"
34+
MODULE_ID = "ModuleId"
35+
GATEWAY_HOST_NAME = "GatewayHostName"
36+
37+
VALID_KEYS = [
38+
HOST_NAME,
39+
SHARED_ACCESS_KEY_NAME,
40+
SHARED_ACCESS_KEY,
41+
SHARED_ACCESS_SIGNATURE,
42+
DEVICE_ID,
43+
MODULE_ID,
44+
GATEWAY_HOST_NAME,
45+
]
46+
47+
48+
class IoTHubDevice(IoTMQTTCallback):
49+
"""A device client for the Azure IoT Hub service
50+
"""
51+
52+
def connection_status_change(self, connected: bool) -> None:
53+
"""Called when the connection status changes
54+
"""
55+
if self.on_connection_status_changed is not None:
56+
# pylint: disable=E1102
57+
self.on_connection_status_changed(connected)
58+
59+
# pylint: disable=W0613, R0201
60+
def direct_method_called(self, method_name: str, data) -> IoTResponse:
61+
"""Called when a direct method is invoked
62+
"""
63+
if self.on_direct_method_called is not None:
64+
# pylint: disable=E1102
65+
return self.on_direct_method_called(method_name, data)
66+
67+
raise IoTError("on_direct_method_called not set")
68+
69+
# pylint: disable=C0103
70+
def cloud_to_device_message_received(self, body: str, properties: dict):
71+
"""Called when a cloud to device message is received
72+
"""
73+
if self.on_cloud_to_device_message_received is not None:
74+
# pylint: disable=E1102
75+
self.on_cloud_to_device_message_received(body, properties)
76+
77+
def device_twin_desired_updated(self, desired_property_name: str, desired_property_value, desired_version: int) -> None:
78+
"""Called when the device twin is updated
79+
"""
80+
if self.on_device_twin_desired_updated is not None:
81+
# pylint: disable=E1102
82+
self.on_device_twin_desired_updated(desired_property_name, desired_property_value, desired_version)
83+
84+
def device_twin_reported_updated(self, reported_property_name: str, reported_property_value, reported_version: int) -> None:
85+
"""Called when the device twin is updated
86+
"""
87+
if self.on_device_twin_reported_updated is not None:
88+
# pylint: disable=E1102
89+
self.on_device_twin_reported_updated(reported_property_name, reported_property_value, reported_version)
90+
91+
def __init__(self, device_connection_string: str, token_expires: int = 21600, logger: logging = None):
92+
self._token_expires = token_expires
93+
self._logger = logger if logger is not None else logging.getLogger("log")
94+
95+
connection_string_values = {}
96+
97+
try:
98+
cs_args = device_connection_string.split(DELIMITER)
99+
connection_string_values = dict(arg.split(VALUE_SEPARATOR, 1) for arg in cs_args)
100+
except (ValueError, AttributeError):
101+
raise ValueError("Connection string is required and should not be empty or blank and must be supplied as a string")
102+
103+
if len(cs_args) != len(connection_string_values):
104+
raise ValueError("Invalid Connection String - Unable to parse")
105+
106+
_validate_keys(connection_string_values)
107+
108+
self._hostname = connection_string_values[HOST_NAME]
109+
self._device_id = connection_string_values[DEVICE_ID]
110+
self._shared_access_key = connection_string_values[SHARED_ACCESS_KEY]
111+
112+
self._logger.debug("Hostname: " + self._hostname)
113+
self._logger.debug("Device Id: " + self._device_id)
114+
self._logger.debug("Shared Access Key: " + self._shared_access_key)
115+
116+
self.on_connection_status_changed = None
117+
self.on_direct_method_called = None
118+
self.on_cloud_to_device_message_received = None
119+
self.on_device_twin_desired_updated = None
120+
self.on_device_twin_reported_updated = None
121+
122+
self._mqtt = None
123+
124+
def connect(self):
125+
"""Connects to Azure IoT Central
126+
"""
127+
self._mqtt = IoTMQTT(self, self._hostname, self._device_id, self._shared_access_key, self._token_expires, self._logger)
128+
self._mqtt.connect()
129+
130+
def disconnect(self):
131+
"""Disconnects from the MQTT broker
132+
"""
133+
if self._mqtt is None:
134+
raise IoTError("You are not connected to IoT Central")
135+
136+
self._mqtt.disconnect()
137+
138+
def is_connected(self) -> bool:
139+
"""Gets if there is an open connection to the MQTT broker
140+
"""
141+
if self._mqtt is not None:
142+
return self._mqtt.is_connected()
143+
144+
return False
145+
146+
def loop(self):
147+
"""Listens for MQTT messages
148+
"""
149+
if self._mqtt is None:
150+
raise IoTError("You are not connected to IoT Central")
151+
152+
self._mqtt.loop()
153+
154+
def send_device_to_cloud_message(self, message, system_properties=None):
155+
"""Sends a device to cloud message to the IoT Hub
156+
"""
157+
if self._mqtt is None:
158+
raise IoTError("You are not connected to IoT Central")
159+
160+
self._mqtt.send_device_to_cloud_message(message, system_properties)
161+
162+
def update_twin(self, patch):
163+
"""Updates the reported properties in the devices device twin
164+
"""
165+
if self._mqtt is None:
166+
raise IoTError("You are not connected to IoT Central")
167+
168+
if isinstance(patch, dict):
169+
patch = json.dumps(patch)
170+
171+
self._mqtt.send_twin_patch(patch)

‎docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# Uncomment the below if you use native CircuitPython modules such as
2222
# digitalio, micropython and busio. List the modules you use. Without it, the
2323
# autodoc module docs will fail to generate with a warning.
24-
# autodoc_mock_imports = ["digitalio", "busio"]
24+
autodoc_mock_imports = ["adafruit_loging"]
2525

2626

2727
intersphinx_mapping = {

‎examples/azureiot_device_management.py

Lines changed: 0 additions & 44 deletions
This file was deleted.

‎examples/azureiot_devicetwin.py

Lines changed: 0 additions & 53 deletions
This file was deleted.

‎examples/iotcentral_commands.py

Whitespace-only changes.

‎examples/iotcentral_properties.py

Whitespace-only changes.
File renamed without changes.

‎examples/iotcentral_telemetry.py

Whitespace-only changes.

‎examples/iothub_directmethods.py

Whitespace-only changes.

‎examples/iothub_messages.py

Whitespace-only changes.

‎examples/iothub_simpletest.py

Whitespace-only changes.

‎examples/iothub_twin_operations.py

Whitespace-only changes.

‎requirements.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
Adafruit-Blinka
2-
Adafruit_CircuitPython_ESP32SPI
2+
Adafruit_CircuitPython_ESP32SPI
3+
Adafruit-CircuitPython-miniMQTT
4+
CircuitPython-HMAC
5+
CircuitPython-Base64
6+
CircuitPython-Parse

‎setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
# Author details
3030
author="Adafruit Industries",
3131
author_email="circuitpython@adafruit.com",
32-
install_requires=["Adafruit-Blinka", "Adafruit_CircuitPython_ESP32SPI"],
32+
install_requires=["Adafruit-Blinka", "Adafruit_CircuitPython_ESP32SPI", "Adafruit-CircuitPython-miniMQTT", "CircuitPython-HMAC", "CircuitPython-Base64", "CircuitPython-Parse"],
3333
# Choose your license
3434
license="MIT",
3535
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
@@ -44,7 +44,7 @@
4444
"Programming Language :: Python :: 3.5",
4545
],
4646
# What does your project relate to?
47-
keywords="adafruit blinka circuitpython micropython azureiot azure, iot, device, services",
47+
keywords="adafruit blinka circuitpython micropython azureiot azure iot device services, iothub, iotcentral",
4848
# You can just specify the packages manually here if your project is
4949
# simple. Or you can use find_packages().
5050
# TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER,

0 commit comments

Comments
 (0)
Please sign in to comment.