This commit is contained in:
Luca Druda 2020-04-28 14:42:03 +02:00
Родитель a14050d8a8
Коммит c6b9ce4582
15 изменённых файлов: 575 добавлений и 929 удалений

2
.gitignore поставляемый
Просмотреть файл

@ -5,6 +5,8 @@ exp.py
*.pyc
*.pyo
samples/
# VSCODE
.vscode/

481
README.md
Просмотреть файл

@ -1,386 +1,137 @@
## iotc - Azure IoT Central - Python (light) device SDK Documentation
# Microsoft Azure IoTCentral SDK for Python
### Prerequisites
[![Join the chat at https://gitter.im/iotdisc/community](https://badges.gitter.im/iotdisc.svg)](https://gitter.im/iotdisc/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Licensed under the MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/lucadruda/iotc-python-device-client/blob/master/LICENSE)
Python 2.7+ or Python 3.4+ or Micropython 1.9+
*Runtime dependencies vary per platform*
## Prerequisites
+ Python 2.7+ or Python 3.7+ (recommended)
### Install
Python 2/3
```
pip install iotc
```
### Common Concepts
- API calls should return `0` on success and `error code` otherwise.
- External API naming convention follows `lowerCamelCase` for `Device` class members
- Asyncronous commands must be acknowledge as any other sync commands but execution updates should be sent through the `sendCommand` function
### Usage
## Installing `azure-iotcentral-device-client`
```
import iotc
device = iotc.Device(scopeId, keyORCert, deviceId, credType)
pip install azure-iotcentral-device-client
```
- *scopeId* : Azure IoT DPS Scope Id
- *keyORcert* : Group or device symmetric key or x509 Certificate
- *deviceId* : Device Id
- *credType* : `IOTConnectType.IOTC_CONNECT_SYMM_KEY`,`IOTConnectType.IOTC_CONNECT_DEVICE_KEY` or `IOTConnectType.IOTC_CONNECT_X509_CERT`
## Importing the module
Sync client (Python 2.7+ and 3.7+) can be imported in this way:
`keyORcert` for `X509` certificate:
```
credType = IOTConnectType.IOTC_CONNECT_X509_CERT
keyORcert = {
"keyfile": "/src/python/test/device.key.pem",
"certfile": "/src/python/test/device.cert.pem"
from azure.iotcentral.device.client import IoTCClient
```
Async client (with asyncio for Python 3.7+ only) can be imported like this:
```
from azure.iotcentral.device.client.aio import IoTCClient
```
## Connecting
#### X509
```
const iotCentral = require('azure-iotcentral-device-client');
const scopeId = '';
const deviceId = '';
const passphrase = ''; //optional
const cert = {
cert: "public cert"
key: "private key",
passphrase: "passphrase"
}
const iotc = new iotCentral.IoTCClient(deviceId, scopeId, 'X509_CERT', cert);
```
`keyORcert` for `SAS` token:
#### SAS
```
credType = IOTConnectType.IOTC_CONNECT_SYMM_KEY
keyORcert = "xxxxxxxxxxxxxxx........"
scopeId = 'scopeID';
deviceId = 'deviceID';
sasKey = 'masterKey'; # or use device key directly
iotc = IoTCClient(deviceId, scopeId,
IOTCConnectType.IOTC_CONNECT_SYMM_KEY, sasKey)
```
IOTCConnectType enum can be imported from the same module of IoTCClient
#### setLogLevel
set logging level
### Connect
Sync
```
device.setLogLevel(logLevel)
```
*logLevel* : (default value is `IOTC_LOGGING_DISABLED`)
```
class IOTLogLevel:
IOTC_LOGGING_DISABLED = 1
IOTC_LOGGING_API_ONLY = 2
IOTC_LOGGING_ALL = 16
```
*i.e.* => `device.setLogLevel(IOTLogLevel.IOTC_LOGGING_API_ONLY)`
#### setExitOnError
enable/disable application termination on mqtt later exceptions. (default false)
```
device.setExitOnError(isEnabled)
```
*i.e.* => `device.setExitOnError(True)`
#### setModelData
set the device model data (if any)
```
device.setModelData(modelJSON)
```
*modelJSON* : Device model definition.
*i.e.* => `device.setModelData({"__iot:interfaces": {"CapabilityModelId": "<PUT_MODEL_ID_HERE>"}})`
#### setInterfaces
set the interfaces exposed by the device. If interfaces are not declared, related properties and commands will not be received by the device and telemetry will have no effect
```
device.setInterfaces(interfaceJSON)
```
*interfacesJSON* : Interfaces object
*i.e.* => `device.setInterfaces({"[PUT_INTERFACE_1_NAME_HERE]": "[PUT_INTERFACE_1_ID_HERE]", ... ,"[PUT_INTERFACE_N_NAME_HERE]": "[PUT_INTERFACE_N_ID_HERE]"})`
#### setTokenExpiration
set the token expiration timeout. default is 21600 seconds (6 hours)
```
device.setTokenExpiration(totalSeconds)
```
*totalSeconds* : timeout in seconds.
*i.e.* => `device.setTokenExpiration(600)`
#### setServiceHost
set the service endpoint URL
```
device.setServiceHost(url)
```
*url* : URL for service endpoint. (default value is `global.azure-devices-provisioning.net`)
*call this before connect*
#### setQosLevel
Set the MQTT Quality of Service (QoS) level desired for all MQTT publish calls
```
device.setQosLevel(qosLevel)
```
*qosLevel* : (default value is `IOTC_QOS_AT_MOST_ONCE`)
```
class IOTQosLevel:
IOTC_QOS_AT_MOST_ONCE = 0
IOTC_QOS_AT_LEAST_ONCE = 1
```
Note: IOTC_QOS_AT_LEAST_ONCE will have slower performance than IOTC_QOS_AT_MOST_ONCE as the MQTT client must store the value for possible replay and also wait for an acknowledgement from the IoT Hub that the MQTT message has been received. Think of IOTC_QOS_AT_MOST_ONCE as "fire and forget" vs. IOTC_QOS_AT_LEAST_ONCE as "guaranteed delivery". As the developer you should consider the importance of 100% data delivery vs. increased connection time and data traffic over the data connection.
*call this before connect*
#### connect
connect device client `# blocking`. Raises `ConnectionStatus` event.
```
device.connect()
```
or
```
device.connect(hostName)
```
*i.e.* => `device.connect()`
#### sendTelemetry
send telemetry
```
device.sendTelemetry(payload, [[optional system properties]])
```
*payload* : A text payload.
*i.e.* => `device.sendTelemetry('{ "temperature": 15 }')`
You may also set system properties for the telemetry message. See also [iothub message format](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-messages-construct)
*i.e.* => `device.sendTelemetry('{ "temperature":22 }', {"iothub-creation-time-utc": time.time()})`
#### sendState
send device state
```
device.sendState(payload)
```
*payload* : A text payload.
*i.e.* => `device.sendState('{ "status": "WARNING"}')`
#### sendProperty
send reported property
```
device.sendProperty(payload)
```
*payload* : A text payload.
*i.e.* => `device.sendProperty('{"countdown":{"value": %d}}')`
#### sendCommandUpdate
send execution updates for asyncronous messages
```
device.sendCommandUpdate(interface_name, command_name, request_id, status_code, update_message)
```
*interface_name* : Command interface name.
*command_name* : Command name.
*request_id* : Request Id for the command. This must be the same value received for the command in on("Command"). Can be obtain by calling _getPayload()["requestId"]_ of the callback object (see example)
*status_code* : Update status code.
*update_message* : A text message.
*i.e.* => `device.sendCommandUpdate("commands", "reboot", "a4795148-155f-4d75-92bf-3536bc0cbcee", 200, "Progress")`
`
#### doNext
let framework do the partial MQTT work.
```
device.doNext()
```
#### isConnected
returns whether the connection was established or not.
```
device.isConnected()
```
*i.e.* => `device.isConnected()`
#### disconnect
disconnect device client
```
device.disconnect()
```
*i.e.* => `device.disconnect()`
#### getDeviceProperties
pulls latest twin data (device properties). Raises `PropertiesUpdated` event.
```
device.getDeviceProperties()
```
*i.e.* => `device.getDeviceProperties()`
#### getHostName
returns the iothub hostname cached during the initial connection.
```
device.getHostName()
```
*i.e.* => `device.getDeviceHostName()`
#### on
set event callback to listen events
- `ConnectionStatus` : connection status has changed
- `MessageSent` : message was sent
- `Command` : a command received from Azure IoT Central
- `PropertiesUpdated` : device properties were updated
i.e.
```
def onconnect(info):
if info.getStatusCode() == 0:
print("connected!")
device.on("ConnectionStatus", onconnect)
```
```
def onmessagesent(info):
print("message sent -> " + info.getPayload())
device.on("MessageSent", onmessagesent)
```
```
def oncommand(info):
print("command name:", info.getTag())
print("command args: ", info.getPayload())
device.on("Command", oncommand)
```
```
def onpropertiesupdated(info):
print("property name:", info.getTag())
print("property value: ", info.getPayload())
device.on("Updated", onpropertiesupdated)
```
#### callback info class
`iotc` callbacks have a single argument derived from `IOTCallbackInfo`.
Using this interface, you can get the callback details and respond back when it's necessary.
public members of `IOTCallbackInfo` are;
`getResponseCode()` : get response code or `None`
`getResponseMessage()` : get response message or `None`
`setResponse(responseCode, responseMessage)` : set callback response
*i.e.* => `info.setResponse(200, 'completed')`
`getClient()` : get active `device` client
`getEventName()` : get the name of the event
`getPayload()` : get the payload or `None`
`getTag()` : get the tag or `None`
`getStatusCode()` : get callback status code
#### sample app
```
import iotc
from iotc import IOTConnectType, IOTLogLevel, IOTQosLevel
from random import randint
deviceId = "DEVICE_ID"
scopeId = "SCOPE_ID"
key = "SYMM_KEY"
# see iotc.Device documentation above for x509 argument sample
iotc = iotc.Device(scopeId, key, deviceId,
IOTConnectType.IOTC_CONNECT_SYMM_KEY)
iotc.setLogLevel(IOTLogLevel.IOTC_LOGGING_ALL)
iotc.setQosLevel(IOTQosLevel.IOTC_QOS_AT_MOST_ONCE)
gCanSend = False
gCounter = 0
def onconnect(info):
global gCanSend
print("- [onconnect] => status:" + str(info.getStatusCode()))
if info.getStatusCode() == 0:
if iotc.isConnected():
gCanSend = True
def onmessagesent(info):
print("\t- [onmessagesent] => " + str(info.getPayload()))
def oncommand(info):
interface_name = info.getInterface()
command_name = info.getTag()
request_id = None
try:
command_value = info.getPayload()["value"]
request_id = info.getPayload()["requestId"]
except:
command_value = info.getPayload()
resp = "Received"
info.setResponse(200,resp)
def onpropertiesupdated(info):
print("property name:", info.getTag())
print("property value: ", info.getPayload())
iotc.on("ConnectionStatus", onconnect)
iotc.on("MessageSent", onmessagesent)
iotc.on("Command", oncommand)
iotc.on("Properties", onpropertiesupdated)
iotc.setModelData(
{"__iot:interfaces": {"CapabilityModelId": "MODEL_ID"}})
# Put interfaces for the specified model
iotc.setInterfaces({"IFNAME": "IFID"})
iotc.connect()
```
Async
```
await iotc.connect()
```
After successfull connection, IOTC context is available for further commands.
registered = False
### Send telemetry
e.g. Send telemetry every 3 seconds
```
while iotc.isConnected():
iotc.doNext() # do the async work needed to be done for MQTT
if gCanSend == True:
if gCounter % 20 == 0:
gCounter = 0
iotc.sendTelemetry({"temperature": str(randint(20, 45))}, {
"$.ifid": 'IFID',
"$.ifname": 'IFNAME',
"$.schema": 'temperature'})
iotc.sendTelemetry({"pressure": str(randint(20, 45))}, {
"$.ifid": 'IFID',
"$.ifname": 'IFNAME',
"$.schema": 'pressure'})
await iotc.sendTelemetry({
'accelerometerX': str(randint(20, 45)),
'accelerometerY': str(randint(20, 45)),
"accelerometerZ": str(randint(20, 45))
})
time.sleep(3)
```
An optional *properties* object can be included in the send methods, to specify additional properties for the message (e.g. timestamp, content-type etc... ).
Properties can be custom or part of the reserved ones (see list [here](https://github.com/Azure/azure-iot-sdk-csharp/blob/master/iothub/device/src/MessageSystemPropertyNames.cs#L36)).
gCounter += 1
```
### Send property update
```
iotc.sendProperty({'fieldName':'fieldValue'});
```
### Listen to properties update
```
iotc.on(IOTCEvents.IOTC_PROPERTIES, callback);
```
To provide setting sync aknowledgement, the callback must reply **True** if the new value has been applied or **False** otherwise
```
async def onProps(propName, propValue):
print(propValue)
return True
iotc.on(IOTCEvents.IOTC_PROPERTIES, onProps);
```
### Listen to commands
```
iotc.on(IOTCEvents.IOTC_COMMAND, callback)
```
To provide feedbacks for the command like execution result or progress, the client can call the **ack** function available in the callback.
The function accepts 3 arguments: command name, a custom response message and the request id for which the ack applies.
```
async def onCommands(command, ack):
print(command.name)
await ack(command.name, 'Command received', command.request_id)
```
## One-touch device provisioning and approval
A device can send custom data during provision process: if a device is aware of its IoT Central template Id, then it can be automatically provisioned.
### How to set IoTC template ID in your device
Template Id can be found in the device explorer page of IoTCentral
![Img](assets/modelId.jpg)
Then call this method before connect():
```
iotc.setModelId('<modelId>');
```
### Manual approval (default)
By default device auto-approval in IoT Central is disabled, which means that administrator needs to approve the device registration to complete the provisioning process.
This can be done from explorer page after selecting the device
![Img](assets/manual_approval.jpg)
### Automatic approval
To change default behavior, administrator can enable device auto-approval from Device Connection page under the Administration section.
With automatic approval a device can be provisioned without any manual action and can start sending/receiving data after status changes to "Provisioned"
![Img](assets/auto_approval.jpg)

Двоичные данные
assets/auto_approval.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 82 KiB

Двоичные данные
assets/manual_approval.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 135 KiB

Двоичные данные
assets/modelId.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 124 KiB

Просмотреть файл

@ -3,6 +3,7 @@ __path__ = __import__("pkgutil").extend_path(__path__, __name__)
import sys
import threading
import time
from azure.iot.device import IoTHubDeviceClient
from azure.iot.device import ProvisioningDeviceClient
from azure.iot.device import Message, MethodResponse
@ -118,6 +119,8 @@ class IoTCClient:
self._protocol = IOTCProtocol.IOTC_PROTOCOL_MQTT
self._connected = False
self._events = {}
self._propThread = None
self._cmdThread = None
self._globalEndpoint = "global.azure-devices-provisioning.net"
if logger is None:
self._logger = ConsoleLogger(IOTCLogLevel.IOTC_LOGGING_API_ONLY)
@ -125,54 +128,83 @@ class IoTCClient:
self._logger = logger
def isConnected(self):
"""
Check if device is connected to IoTCentral
:returns: Connection state
:rtype: bool
"""
if self._connected:
return True
else:
return False
def setProtocol(self, protocol):
"""
Set the connection protocol to be used.
:param IOTCProtocol protocol: One protocol between MQTT, AMQP and HTTPS (default MQTT)
"""
self._protocol = protocol
def setGlobalEndpoint(self, endpoint):
"""
Set the device provisioning endpoint.
:param str endpoint: Custom device provisioning endpoint. Default ('global.azure-devices-provisioning.net')
"""
self._globalEndpoint = endpoint
def setModelId(self, modelId):
"""
Set the model Id for the device to be associated
:param str modelId: Id for an existing model in the IoTCentral app
"""
self._modelId = modelId
def setLogLevel(self, logLevel):
"""
Set the logging level
:param IOTCLogLevel: Logging level. Available options are: ALL, API_ONLY, DISABLE
"""
self._logger.setLogLevel(logLevel)
def on(self, eventname, callback):
"""
Set a listener for a specific event
:param IOTCEvents eventname: Supported events: IOTC_PROPERTIES, IOTC_COMMANDS
:param function callback: Function executed when the specified event occurs
"""
self._events[eventname] = callback
return 0
def _onProperties(self):
self._logger.debug('Setup properties listener')
while True:
propCb = self._events[IOTCEvents.IOTC_PROPERTIES]
try:
propCb = self._events[IOTCEvents.IOTC_PROPERTIES]
except KeyError:
self._logger.debug('Properties callback not found')
time.sleep(10)
continue
patch = self._deviceClient.receive_twin_desired_properties_patch()
self._logger.debug('\nReceived desired properties. {}\n'.format(patch))
for prop in patch:
if prop == '$version':
continue
if propCb:
for prop in patch:
if prop == '$version':
continue
ret = propCb(prop, patch[prop]['value'])
if ret:
self._logger.debug('Acknowledging {}'.format(prop))
self.sendProperty({
'{}'.format(prop): {
"value": patch[prop]["value"],
'status': 'completed',
'desiredVersion': patch['$version'],
'message': 'Property received'}
})
else:
self._logger.debug(
'Property "{}" unsuccessfully processed'.format(prop))
else:
self._logger.debug('Callback not found')
ret = propCb(prop, patch[prop]['value'])
if ret:
self._logger.debug('Acknowledging {}'.format(prop))
self.sendProperty({
'{}'.format(prop): {
"value": patch[prop]["value"],
'status': 'completed',
'desiredVersion': patch['$version'],
'message': 'Property received'}
})
else:
self._logger.debug(
'Property "{}" unsuccessfully processed'.format(prop))
def _cmdAck(self,name, value, requestId):
self.sendProperty({
@ -184,8 +216,13 @@ class IoTCClient:
def _onCommands(self):
self._logger.debug('Setup commands listener')
while True:
try:
cmdCb = self._events[IOTCEvents.IOTC_COMMAND]
except KeyError:
self._logger.debug('Commands callback not found')
time.sleep(10)
continue
# Wait for unknown method calls
method_request = self._deviceClient.receive_method_request()
self._logger.debug(
@ -194,11 +231,8 @@ class IoTCClient:
method_request, 200, {
'result': True, 'data': 'Command received'}
))
try:
cmdCb = self._events[IOTCEvents.IOTC_COMMAND]
cmdCb(method_request)
except KeyError:
self._logger.debug('Callback not found')
cmdCb(method_request,self._cmdAck)
def _sendMessage(self, payload, properties, callback=None):
msg = Message(payload)
@ -211,23 +245,38 @@ class IoTCClient:
callback()
def sendProperty(self, payload, callback=None):
"""
Send a property message
:param dict payload: The properties payload. Can contain multiple properties in the form {'<propName>':{'value':'<propValue>'}}
:param function callback: Function executed after successfull dispatch
"""
self._logger.debug('Sending property {}'.format(json.dumps(payload)))
self._deviceClient.patch_twin_reported_properties(payload)
if callback is not None:
callback()
def sendTelemetry(self, payload, properties=None, callback=None):
"""
Send a telemetry message
:param dict payload: The telemetry payload. Can contain multiple telemetry fields in the form {'<fieldName1>':<fieldValue1>,...,'<fieldNameN>':<fieldValueN>}
:param dict optional properties: An object with custom properties to add to the message.
:param function callback: Function executed after successfull dispatch
"""
self._logger.info('Sending telemetry message: {}'.format(payload))
self._sendMessage(json.dumps(payload), properties, callback)
def connect(self):
"""
Connects the device.
:raises exception: If connection fails
"""
if self._credType in (IOTCConnectType.IOTC_CONNECT_DEVICE_KEY, IOTCConnectType.IOTC_CONNECT_SYMM_KEY):
if self._credType == IOTCConnectType.IOTC_CONNECT_SYMM_KEY:
self._keyORCert = self._computeDerivedSymmetricKey(
self._keyORCert, self._deviceId)
self._logger.debug('Device key: {}'.format(self._keyORCert))
# self._keyORCert = devKey
self._provisioningClient = ProvisioningDeviceClient.create_from_symmetric_key(
self._provisioningClient = ProvisioningDeviceClient.create_from_symmetric_key(
self._globalEndpoint, self._deviceId, self._scopeId, self._keyORCert)
else:
self._keyfile = self._keyORCert["keyfile"]
@ -249,27 +298,29 @@ class IoTCClient:
self._deviceClient = IoTHubDeviceClient.create_from_connection_string(
self._hubCString)
except:
t, v, tb = sys.exc_info()
self._logger.info(
'ERROR: Failed to get device provisioning information')
sys.exit()
raise t(v)
# Connect to iothub
try:
self._deviceClient.connect()
self._connected = True
self._logger.debug('Device connected')
except:
t, v, tb = sys.exc_info()
self._logger.info('ERROR: Failed to connect to Hub')
sys.exit()
raise t(v)
# setup listeners
listen_thread_props = threading.Thread(target=self._onProperties)
listen_thread_props.daemon = True
listen_thread_props.start()
self._propThread = threading.Thread(target=self._onProperties)
self._propThread.daemon = True
self._propThread.start()
listen_thread_commands = threading.Thread(target=self._onCommands)
listen_thread_commands.daemon = True
listen_thread_commands.start()
self._cmdThread = threading.Thread(target=self._onCommands)
self._cmdThread.daemon = True
self._cmdThread.start()
def _computeDerivedSymmetricKey(self, secret, regId):
# pylint: disable=no-member

Просмотреть файл

@ -111,9 +111,13 @@ class IoTCClient:
self._scopeId = scopeId
self._credType = credType
self._keyORCert = keyOrCert
self._modelId = None
self._protocol = IOTCProtocol.IOTC_PROTOCOL_MQTT
self._connected = False
self._events = {}
# self._threads = None
self._cmdThread = None
self._propThread = None
self._globalEndpoint = "global.azure-devices-provisioning.net"
if logger is None:
self._logger = ConsoleLogger(IOTCLogLevel.IOTC_LOGGING_API_ONLY)
@ -145,41 +149,51 @@ class IoTCClient:
async def _onProperties(self):
self._logger.debug('Setup properties listener')
while True:
propCb = self._events[IOTCEvents.IOTC_PROPERTIES]
try:
propCb = self._events[IOTCEvents.IOTC_PROPERTIES]
except KeyError:
self._logger.debug('Properties callback not found')
await asyncio.sleep(10)
continue
patch = await self._deviceClient.receive_twin_desired_properties_patch()
self._logger.debug('Received desired properties. {}'.format(patch))
if propCb:
for prop in patch:
if prop == '$version':
continue
for prop in patch:
if prop == '$version':
continue
ret = await propCb(prop, patch[prop]['value'])
if ret:
self._logger.debug('Acknowledging {}'.format(prop))
await self.sendProperty({
'{}'.format(prop): {
"value": patch[prop]["value"],
'status': 'completed',
'desiredVersion': patch['$version'],
'message': 'Property received'}
})
else:
self._logger.debug(
'Property "{}" unsuccessfully processed'.format(prop))
ret = await propCb(prop, patch[prop]['value'])
if ret:
self._logger.debug('Acknowledging {}'.format(prop))
await self.sendProperty({
'{}'.format(prop): {
"value": patch[prop]["value"],
'status': 'completed',
'desiredVersion': patch['$version'],
'message': 'Property received'}
})
else:
self._logger.debug(
'Property "{}" unsuccessfully processed'.format(prop))
async def _cmdAck(self,name, value, requestId):
await self.sendProperty({
'{}'.format(name): {
'value': value,
'requestId': requestId
}
})
async def _onCommands(self):
self._logger.debug('Setup commands listener')
async def cmdAck(name, value, requestId):
await self.sendProperty({
'{}'.format(name): {
'value': value,
'requestId': requestId
}
})
while True:
cmdCb = self._events[IOTCEvents.IOTC_COMMAND]
try:
cmdCb = self._events[IOTCEvents.IOTC_COMMAND]
except KeyError:
self._logger.debug('Commands callback not found')
await asyncio.sleep(10)
continue
# Wait for unknown method calls
method_request = await self._deviceClient.receive_method_request()
self._logger.debug(
@ -188,9 +202,8 @@ class IoTCClient:
method_request, 200, {
'result': True, 'data': 'Command received'}
))
await cmdCb(method_request, self._cmdAck)
if cmdCb:
await cmdCb(method_request, cmdAck)
async def _sendMessage(self, payload, properties, callback= None):
msg = Message(payload)
@ -216,10 +229,11 @@ class IoTCClient:
if self._credType in (IOTCConnectType.IOTC_CONNECT_DEVICE_KEY, IOTCConnectType.IOTC_CONNECT_SYMM_KEY):
if self._credType == IOTCConnectType.IOTC_CONNECT_SYMM_KEY:
self._keyORCert = self._computeDerivedSymmetricKey(
self._keyORCert, self._deviceId).decode('UTF-8')
# self._logger.debug('Device key: {}'.format(devKey))
# self._keyORCert = devKey
self._provisioningClient = ProvisioningDeviceClient.create_from_symmetric_key(
self._keyORCert, self._deviceId)
self._logger.debug('Device key: {}'.format(self._keyORCert))
self._provisioningClient = ProvisioningDeviceClient.create_from_symmetric_key(
self._globalEndpoint, self._deviceId, self._scopeId, self._keyORCert)
else:
self._keyfile = self._keyORCert["keyfile"]
@ -254,8 +268,13 @@ class IoTCClient:
sys.exit()
# setup listeners
asyncio.create_task(self._onProperties())
asyncio.create_task(self._onCommands())
self._propThread = asyncio.create_task(self._onProperties())
await self._propThread
#self._cmdThread = asyncio.create_task(self._onCommands())
# self._threads = await asyncio.gather(
# self._onProperties(),
# self._onCommands()
# )
def _computeDerivedSymmetricKey(self, secret, regId):
# pylint: disable=no-member
@ -268,6 +287,6 @@ class IoTCClient:
sys.exit()
if gIsMicroPython == False:
return base64.b64encode(hmac.new(secret, msg=regId.encode('utf8'), digestmod=hashlib.sha256).digest())
return base64.b64encode(hmac.new(secret, msg=regId.encode('utf8'), digestmod=hashlib.sha256).digest()).decode('utf-8')
else:
return base64.b64encode(hmac.new(secret, msg=regId.encode('utf8'), digestmod=hashlib._sha256.sha256).digest())

Просмотреть файл

@ -1,98 +0,0 @@
import sys
sys.path.insert(0, 'src')
import pytest
import mock
from unittest.mock import ANY
from azure.iot.device.provisioning.models import RegistrationResult
from azure.iot.device.iothub.models import MethodRequest
from azure.iotcentral.device.client import IoTCClient, IOTCConnectType, IOTCLogLevel, IOTCEvents
groupKey='68p6zEjwVNB6L/Dz8Wkz4VhaTrYqkndPrB0uJbWr2Hc/AmB+Qxz/eJJ9MIhLZFJ6hC0RmHMgfaYBkNTq84OCNQ=='
deviceKey='Jdj7TBhH5RXCD+24bT5PTGf0NwdDbDvsI+rniK2AUHk='
deviceId='nuovodev'
scopeId='0ne00052362'
assignedHub='iotc-632b1fc0-6e52-45d5-a37f-0daf6838f515.azure-devices.net'
expectedHub='HostName=iotc-632b1fc0-6e52-45d5-a37f-0daf6838f515.azure-devices.net;DeviceId=nuovodev;SharedAccessKey=Jdj7TBhH5RXCD+24bT5PTGf0NwdDbDvsI+rniK2AUHk='
class NewRegState():
def __init__(self):
self.assigned_hub=assignedHub
def assigned_hub(self):
return self.assigned_hub
class NewProvClient():
def register(self):
reg=RegistrationResult('3o375i827i852','assigned',NewRegState())
return reg
def init():
return IoTCClient(deviceId, scopeId,
IOTCConnectType.IOTC_CONNECT_SYMM_KEY, groupKey)
def test_computeKey():
iotc=init()
assert iotc._computeDerivedSymmetricKey(groupKey,deviceId) == deviceKey
# @mock.patch('azure.iotcentral.device.client.IoTHubDeviceClient.connect',return_value=True)
# @mock.patch('azure.iotcentral.device.client.ProvisioningDeviceClient.create_from_symmetric_key',return_value=NewProvClient())
# def test_notConnectedDefault(ioTHubDeviceClient,provisioningDeviceClient):
# provisioningDeviceClient.create_from_symmetric_key.return_value=provisioningDeviceClient
# iotc = init()
# iotc.connect()
# assert iotc.isConnected() == False
@mock.patch('azure.iotcentral.device.client.IoTHubDeviceClient.connect',return_value=True)
@mock.patch('azure.iotcentral.device.client.ProvisioningDeviceClient.create_from_symmetric_key',return_value=NewProvClient())
def test_deviceKeyGeneration(ioTHubDeviceClient,provisioningDeviceClient):
iotc = init()
iotc.connect()
assert iotc._keyORCert == deviceKey
@mock.patch('azure.iotcentral.device.client.IoTHubDeviceClient.connect',return_value=True)
@mock.patch('azure.iotcentral.device.client.ProvisioningDeviceClient.create_from_symmetric_key',return_value=NewProvClient())
def test_hubConnectionString(ioTHubDeviceClient,provisioningDeviceClient):
iotc = init()
iotc.connect()
assert iotc._hubCString == expectedHub
def test_onproperties(mocker):
propPayload={'prop1':{'value':40},'$version':5}
mocker.patch('azure.iotcentral.device.client.IoTHubDeviceClient.connect',return_value=True)
mocker.patch('azure.iotcentral.device.client.IoTHubDeviceClient.receive_twin_desired_properties_patch',return_value=propPayload)
mocker.patch('azure.iotcentral.device.client.ProvisioningDeviceClient.create_from_symmetric_key',return_value=NewProvClient())
onProps=mock.Mock()
iotc = init()
iotc.on(IOTCEvents.IOTC_PROPERTIES,onProps)
iotc.connect()
onProps.assert_called_with('prop1',40)
def test_onCommands(mocker):
cmdRequestId='abcdef'
cmdName='command1'
cmdPayload='payload'
methodRequest=MethodRequest(cmdRequestId,cmdName,cmdPayload)
mocker.patch('azure.iotcentral.device.client.IoTHubDeviceClient.connect',return_value=True)
mocker.patch('azure.iotcentral.device.client.IoTHubDeviceClient.receive_method_request',return_value=methodRequest)
mocker.patch('azure.iotcentral.device.client.ProvisioningDeviceClient.create_from_symmetric_key',return_value=NewProvClient())
onCmds=mock.Mock()
iotc = init()
iotc.on(IOTCEvents.IOTC_COMMAND,onCmds)
iotc.connect()
onCmds.assert_called_with(methodRequest)

Просмотреть файл

@ -1,5 +0,0 @@
import pytest
@pytest.fixture
def provision_register():
return True

Просмотреть файл

@ -1,127 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
# Licensed under the MIT license.
import sys
import time
import hashlib
import hmac
import base64
file_path = __file__[:len(__file__) - len("cacheHost.py")]
# Update /usr/local/lib/python2.7/site-packages/iotc/__init__.py ?
if 'dont_write_bytecode' in dir(sys):
import os
sys.dont_write_bytecode = True
gIsMicroPython = False
else: # micropython
gIsMicroPython = True
file_path = file_path[:len(file_path) - 1] if file_path[len(file_path) - 1:] == "b" else file_path
sys.path.append(file_path + "../src")
import iotc
from iotc import IOTConnectType, IOTLogLevel
import json
pytest_run = False
def test_LOG_IOTC():
global pytest_run
pytest_run = True
assert iotc.LOG_IOTC("LOGME") == 0
def CALLBACK_(info):
if info.getPayload() == "{\"number\":1}":
if info.getTag() == "TAG":
if info.getStatusCode() == 0:
if info.getEventName() == "TEST":
info.setResponse(200, "DONE")
return 0
return 1
def test_MAKE_CALLBACK():
client = { "_events" : { "TEST" : CALLBACK_ } }
ret = iotc.MAKE_CALLBACK(client, "TEST", "{\"number\":1}", "TAG", 0)
assert ret != 0
assert ret.getResponseCode() == 200
assert ret.getResponseMessage() == "DONE"
def test_quote():
assert iotc._quote("abc+\\0123\"?%456@def", '~()*!.') == "abc%2B%5C0123%22%3F%25456%40def"
with open(file_path + "config.json", "r") as fh:
configText = fh.read()
config = json.loads(configText)
assert config["scopeId"] != None and config["masterKey"] != None and config["hostName"] != None
testCounter = 0
eventRaised = False
device = None
def compute_key(secret, regId):
global gIsMicroPython
try:
secret = base64.b64decode(secret)
except:
print("ERROR: broken base64 secret => `" + secret + "`")
sys.exit()
if gIsMicroPython == False:
return base64.b64encode(hmac.new(secret, msg=regId.encode('utf8'), digestmod=hashlib.sha256).digest())
else:
return base64.b64encode(hmac.new(secret, msg=regId.encode('utf8'), digestmod=hashlib._sha256.sha256).digest())
def test_lifetime():
global testCounter
global device
global eventRaised
if config["TEST_ID"] == 2:
keyORcert = config["cert"]
else:
keyORcert = compute_key(config["masterKey"], "dev1")
device = iotc.Device(config["scopeId"], keyORcert, "dev1", config["TEST_ID"]) # 1 / 2 (symm / x509)
if "modelData" in config:
assert device.setModelData(config["modelData"]) == 0
device.setExitOnError(True)
hostName = None
def onconnect(info):
global testCounter
global eventRaised
print "PASS = " + str(testCounter)
if testCounter < 2:
eventRaised = True
testCounter = testCounter + 1
assert device.on("ConnectionStatus", onconnect) == 0
# assert device.setLogLevel(IOTLogLevel.IOTC_LOGGING_ALL) == 0
assert device.setDPSEndpoint(config["hostName"]) == 0
assert device.connect() == 0
hostName = device.getHostName()
showCommandWarning = False
MAX_EXPECTED_TEST_COUNTER = 3
while testCounter < MAX_EXPECTED_TEST_COUNTER:
if eventRaised == True:
eventRaised = False
if testCounter == 1:
print "DISCONNECTS"
assert device.disconnect() == 0
else:
print "CONNECTS"
device = iotc.Device(config["scopeId"], keyORcert, "dev1", config["TEST_ID"]) # 1 / 2 (symm / x509)
assert device.on("ConnectionStatus", onconnect) == 0
# assert device.setLogLevel(IOTLogLevel.IOTC_LOGGING_ALL) == 0
assert device.setDPSEndpoint(config["hostName"]) == 0
device.connect(hostName)
device.doNext()
assert device.disconnect() == 0
if pytest_run == False:
test_LOG_IOTC()
test_MAKE_CALLBACK()
test_quote()
test_lifetime()

Просмотреть файл

@ -1,42 +0,0 @@
import sys
sys.path.insert(0, 'src')
from azure.iotcentral.device.client import IoTCClient, IOTCConnectType, IOTCLogLevel, IOTCEvents
groupKey='68p6zEjwVNB6L/Dz8Wkz4VhaTrYqkndPrB0uJbWr2Hc/AmB+Qxz/eJJ9MIhLZFJ6hC0RmHMgfaYBkNTq84OCNQ=='
deviceKey='Jdj7TBhH5RXCD+24bT5PTGf0NwdDbDvsI+rniK2AUHk='
deviceId='nuovodev'
scopeId='0ne00052362'
iotc = IoTCClient(deviceId, scopeId,
IOTCConnectType.IOTC_CONNECT_SYMM_KEY, groupKey)
def test_properties():
propName='prop1'
propValue='val1'
def onProps(name,value):
assert propName == name
assert propValue == value
iotc.on(IOTCEvents.IOTC_PROPERTIES,onProps)
iotc._events[IOTCEvents.IOTC_PROPERTIES](propName,propValue)
def test_commands():
commandName='cmd1'
commandValue='val1'
def onCommand(name,value):
assert commandName == name
assert commandValue == value
iotc.on(IOTCEvents.IOTC_COMMAND,onCommand)
iotc._events[IOTCEvents.IOTC_COMMAND](commandName,commandValue)

Просмотреть файл

@ -1,138 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
# Licensed under the MIT license.
import sys
import time
import hashlib
import hmac
import base64
file_path = __file__[:len(__file__) - len("multiDevice.py")]
# Update /usr/local/lib/python2.7/site-packages/iotc/__init__.py ?
if 'dont_write_bytecode' in dir(sys):
import os
sys.dont_write_bytecode = True
gIsMicroPython = False
else: # micropython
gIsMicroPython = True
file_path = file_path[:len(file_path) - 1] if file_path[len(file_path) - 1:] == "b" else file_path
sys.path.append(file_path + "../src")
import iotc
from iotc import IOTConnectType, IOTLogLevel
import json
pytest_run = False
def test_LOG_IOTC():
global pytest_run
pytest_run = True
assert iotc.LOG_IOTC("LOGME") == 0
def CALLBACK_(info):
if info.getPayload() == "{\"number\":1}":
if info.getTag() == "TAG":
if info.getStatusCode() == 0:
if info.getEventName() == "TEST":
info.setResponse(200, "DONE")
return 0
return 1
def test_MAKE_CALLBACK():
client = { "_events" : { "TEST" : CALLBACK_ } }
ret = iotc.MAKE_CALLBACK(client, "TEST", "{\"number\":1}", "TAG", 0)
assert ret != 0
assert ret.getResponseCode() == 200
assert ret.getResponseMessage() == "DONE"
def test_quote():
assert iotc._quote("abc+\\0123\"?%456@def", '~()*!.') == "abc%2B%5C0123%22%3F%25456%40def"
with open(file_path + "config.json", "r") as fh:
configText = fh.read()
config = json.loads(configText)
assert config["scopeId"] != None and config["masterKey"] != None and config["hostName"] != None
testCounter = 0
devices = []
def compute_key(secret, regId):
global gIsMicroPython
try:
secret = base64.b64decode(secret)
except:
print("ERROR: broken base64 secret => `" + secret + "`")
sys.exit()
if gIsMicroPython == False:
return base64.b64encode(hmac.new(secret, msg=regId.encode('utf8'), digestmod=hashlib.sha256).digest())
else:
return base64.b64encode(hmac.new(secret, msg=regId.encode('utf8'), digestmod=hashlib._sha256.sha256).digest())
def test_lifetime(id):
deviceId = "dev" + str(id + 1)
devices.append(iotc.Device(config["scopeId"], compute_key(config["masterKey"], deviceId), deviceId, config["TEST_ID"])) # 1 / 2 (symm / x509)
if "modelData" in config:
assert devices[id].setModelData(config["modelData"]) == 0
devices[id].setExitOnError(True)
def onconnect(info):
global testCounter
if devices[id].isConnected():
assert info.getStatusCode() == 0
testCounter += 1
assert devices[id].sendTelemetry("{\"temp\":22}", {"iothub-creation-time-utc": time.time()}) == 0
testCounter += 1
assert devices[id].sendProperty("{\"dieNumber\":3}") == 0
else:
print ("- ", "done", "device", deviceId)
def onmessagesent(info):
global testCounter
print ("onmessagesent", deviceId, info.getTag(), info.getPayload())
testCounter += 1
def oncommand(info):
global testCounter
print("oncommand", deviceId, info.getTag(), info.getPayload())
assert info.getTag() == "echo"
testCounter += 1
allSettings = {}
def onsettingsupdated(info):
global testCounter
testCounter += 1
assert '$version' in json.loads(info.getPayload()) # each setting has a version
assert not info.getTag() + str(id) in allSettings # no double event
allSettings[info.getTag() + str(id)] = True
print("onsettingsupdated", deviceId, info.getTag(), info.getPayload())
assert devices[id].on("ConnectionStatus", onconnect) == 0
assert devices[id].on("MessageSent", onmessagesent) == 0
assert devices[id].on("Command", oncommand) == 0
assert devices[id].on("SettingsUpdated", onsettingsupdated) == 0
assert devices[id].setLogLevel(IOTLogLevel.IOTC_LOGGING_ALL) == 0
assert devices[id].setDPSEndpoint(config["hostName"]) == 0
assert devices[id].connect() == 0
if pytest_run == False:
test_LOG_IOTC()
test_MAKE_CALLBACK()
test_quote()
for i in range(3):
test_lifetime(i)
while testCounter < 30:
for i in range(3):
assert devices[i].isConnected()
devices[i].doNext()
for i in range(3):
assert devices[i].disconnect() == 0

Просмотреть файл

@ -1,82 +0,0 @@
import os
import sys
file_path = __file__[:len(__file__) - len("basics.py")]
file_path = file_path[:len(file_path) - 1] if file_path[len(file_path) - 1:] == "b" else file_path
sys.path.append(os.path.join(file_path, "..", "src"))
import iotc
from iotc import IOTConnectType, IOTLogLevel, IOTQosLevel
from datetime import datetime, timezone
from threading import Timer, Thread, Event
class PT():
def __init__(self, t, hFunction):
self.t = t
self.hFunction = hFunction
self.thread = Timer(self.t, self.handle_function)
def handle_function(self):
self.hFunction()
self.thread = Timer(self.t, self.handle_function)
self.thread.start()
def start(self):
self.thread.start()
deviceId = "<Add device Id here>"
scopeId = "<Add scope Id here>"
deviceKey = "<Add device Key here>"
# see iotc.Device documentation above for x509 argument sample
iotc = iotc.Device(scopeId, deviceKey, deviceId, IOTConnectType.IOTC_CONNECT_SYMM_KEY)
iotc.setLogLevel(IOTLogLevel.IOTC_LOGGING_API_ONLY)
iotc.setTokenExpiration(21600 * 12)
# set QoS level to guarantee delivery at least once
iotc.setQosLevel(IOTQosLevel.IOTC_QOS_AT_LEAST_ONCE)
gCanSend = False
sent = 0
confirmed = 0
enableLogging = False
def log(s):
if enableLogging:
f=open("python-iotc.txt","a+")
f.write(datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S') + " - " + s + "\n")
f.close()
def onconnect(info):
global gCanSend
log("[onconnect] => status:" + str(info.getStatusCode()))
if info.getStatusCode() == 0:
if iotc.isConnected():
gCanSend = True
def onmessagesent(info):
global confirmed
confirmed += 1
log("[onmessagesent] => " + str(info.getPayload()))
def oncommand(info):
log("command name:", info.getTag())
log("command value: ", info.getPayload())
def onsettingsupdated(info):
log("setting name:", info.getTag())
log("setting value: ", info.getPayload())
def send():
global sent
sent += 1
print("Sending telemetry.. sent:{}, confirmed: {}".format(sent, confirmed))
iotc.sendTelemetry("{\"index\": " + str(sent) + "}")
iotc.on("ConnectionStatus", onconnect)
iotc.on("MessageSent", onmessagesent)
iotc.on("Command", oncommand)
iotc.on("SettingsUpdated", onsettingsupdated)
iotc.connect()
t = PT(5, send)
t.start()

138
test/v2/test_basic2.py Normal file
Просмотреть файл

@ -0,0 +1,138 @@
import sys
sys.path.insert(0, 'src')
import pytest
import mock
import time
from azure.iot.device.provisioning.models import RegistrationResult
from azure.iot.device.iothub.models import MethodRequest
from azure.iotcentral.device.client import IoTCClient, IOTCConnectType, IOTCLogLevel, IOTCEvents
groupKey='68p6zEjwVNB6L/Dz8Wkz4VhaTrYqkndPrB0uJbWr2Hc/AmB+Qxz/eJJ9MIhLZFJ6hC0RmHMgfaYBkNTq84OCNQ=='
deviceKey='Jdj7TBhH5RXCD+24bT5PTGf0NwdDbDvsI+rniK2AUHk='
deviceId='nuovodev'
scopeId='0ne00052362'
assignedHub='iotc-632b1fc0-6e52-45d5-a37f-0daf6838f515.azure-devices.net'
expectedHub='HostName=iotc-632b1fc0-6e52-45d5-a37f-0daf6838f515.azure-devices.net;DeviceId=nuovodev;SharedAccessKey=Jdj7TBhH5RXCD+24bT5PTGf0NwdDbDvsI+rniK2AUHk='
propPayload={'prop1':{'value':40},'$version':5}
cmdRequestId='abcdef'
cmdName='command1'
cmdPayload='payload'
methodRequest=MethodRequest(cmdRequestId,cmdName,cmdPayload)
class NewRegState():
def __init__(self):
self.assigned_hub=assignedHub
def assigned_hub(self):
return self.assigned_hub
class NewProvClient():
def register(self):
reg=RegistrationResult('3o375i827i852','assigned',NewRegState())
return reg
class NewDeviceClient():
def connect(self):
return True
def receive_twin_desired_properties_patch(self):
return propPayload
def receive_method_request(self):
return methodRequest
def send_method_response(self,payload):
return True
def patch_twin_reported_properties(self,payload):
return True
def init(mocker):
iotc=IoTCClient(deviceId, scopeId,
IOTCConnectType.IOTC_CONNECT_SYMM_KEY, groupKey)
mocker.patch('azure.iotcentral.device.client.ProvisioningDeviceClient.create_from_symmetric_key',return_value=NewProvClient())
mocker.patch('azure.iotcentral.device.client.IoTHubDeviceClient.create_from_connection_string',return_value=NewDeviceClient())
return iotc
def test_computeKey(mocker):
iotc=init(mocker)
assert iotc._computeDerivedSymmetricKey(groupKey,deviceId) == deviceKey
def test_deviceKeyGeneration(mocker):
iotc = init(mocker)
iotc.connect()
assert iotc._keyORCert == deviceKey
def test_hubConnectionString(mocker):
iotc = init(mocker)
iotc.connect()
assert iotc._hubCString == expectedHub
def test_onproperties_before(mocker):
onProps=mock.Mock()
iotc = init(mocker)
mocker.patch.object(iotc,'sendProperty',mock.Mock())
iotc.on(IOTCEvents.IOTC_PROPERTIES,onProps)
iotc.connect()
onProps.assert_called_with('prop1',40)
def test_onproperties_after(mocker):
onProps=mock.Mock()
iotc = init(mocker)
mocker.patch.object(iotc,'sendProperty',mock.Mock())
iotc.connect()
iotc.on(IOTCEvents.IOTC_PROPERTIES,onProps)
# give at least 10 seconds for the new listener to be recognized. assign the listener after connection is discouraged
time.sleep(11)
onProps.assert_called_with('prop1',40)
def test_onCommands_before(mocker):
onCmds=mock.Mock()
def mockedAck():
print('Callback called')
return True
iotc = init(mocker)
mocker.patch.object(iotc,'_cmdAck',mockedAck)
iotc.on(IOTCEvents.IOTC_COMMAND,onCmds)
iotc.connect()
onCmds.assert_called_with(methodRequest,mockedAck)
def test_onCommands_after(mocker):
onCmds=mock.Mock()
def mockedAck():
print('Callback called')
return True
iotc = init(mocker)
mocker.patch.object(iotc,'_cmdAck',mockedAck)
iotc.connect()
iotc.on(IOTCEvents.IOTC_COMMAND,onCmds)
# give at least 10 seconds for the new listener to be recognized. assign the listener after connection is discouraged
time.sleep(11)
onCmds.assert_called_with(methodRequest,mockedAck)

177
test/v3/test_basic3.py Normal file
Просмотреть файл

@ -0,0 +1,177 @@
import sys
sys.path.insert(0, 'src')
import pytest
import mock
import time
import asyncio
from contextlib import suppress
from azure.iot.device.provisioning.models import RegistrationResult
from azure.iot.device.iothub.models import MethodRequest
from azure.iotcentral.device.client.aio import IoTCClient, IOTCConnectType, IOTCLogLevel, IOTCEvents
groupKey='68p6zEjwVNB6L/Dz8Wkz4VhaTrYqkndPrB0uJbWr2Hc/AmB+Qxz/eJJ9MIhLZFJ6hC0RmHMgfaYBkNTq84OCNQ=='
deviceKey='Jdj7TBhH5RXCD+24bT5PTGf0NwdDbDvsI+rniK2AUHk='
deviceId='nuovodev'
scopeId='0ne00052362'
assignedHub='iotc-632b1fc0-6e52-45d5-a37f-0daf6838f515.azure-devices.net'
expectedHub='HostName=iotc-632b1fc0-6e52-45d5-a37f-0daf6838f515.azure-devices.net;DeviceId=nuovodev;SharedAccessKey=Jdj7TBhH5RXCD+24bT5PTGf0NwdDbDvsI+rniK2AUHk='
propPayload={'prop1':{'value':40},'$version':5}
cmdRequestId='abcdef'
cmdName='command1'
cmdPayload='payload'
methodRequest=MethodRequest(cmdRequestId,cmdName,cmdPayload)
class NewRegState():
def __init__(self):
self.assigned_hub=assignedHub
def assigned_hub(self):
return self.assigned_hub
class NewProvClient():
async def register(self):
reg=RegistrationResult('3o375i827i852','assigned',NewRegState())
return reg
class NewDeviceClient():
async def connect(self):
return True
async def receive_twin_desired_properties_patch(self):
await asyncio.sleep(3)
return propPayload
async def receive_method_request(self):
await asyncio.sleep(3)
return methodRequest
async def send_method_response(self,payload):
return True
async def patch_twin_reported_properties(self,payload):
return True
def async_return(result):
f = asyncio.Future()
f.set_result(result)
return f
async def stopThreads(iotc):
iotc._propThread.cancel()
with suppress(asyncio.CancelledError):
return True
#iotc._cmdThread.cancel()
@pytest.mark.asyncio
def init(mocker):
iotc=IoTCClient(deviceId, scopeId,
IOTCConnectType.IOTC_CONNECT_SYMM_KEY, groupKey)
mocker.patch('azure.iotcentral.device.client.aio.ProvisioningDeviceClient.create_from_symmetric_key',return_value=NewProvClient())
mocker.patch('azure.iotcentral.device.client.aio.IoTHubDeviceClient.create_from_connection_string',return_value=NewDeviceClient())
return iotc
@pytest.mark.asyncio
async def test_computeKey(mocker):
iotc=init(mocker)
assert iotc._computeDerivedSymmetricKey(groupKey,deviceId) == deviceKey
@pytest.mark.asyncio
async def test_deviceKeyGeneration(mocker):
iotc = init(mocker)
await iotc.connect()
assert iotc._keyORCert == deviceKey
stopThreads(iotc)
@pytest.mark.asyncio
async def test_hubConnectionString(mocker):
iotc = init(mocker)
await iotc.connect()
assert iotc._hubCString == expectedHub
stopThreads(iotc)
@pytest.mark.asyncio
async def test_onproperties_before(mocker):
iotc = init(mocker)
async def onProps(propname,propvalue):
assert propname == 'prop1'
assert propvalue == 40
await stopThreads(iotc)
return True
mocker.patch.object(iotc,'sendProperty',return_value=True)
iotc.on(IOTCEvents.IOTC_PROPERTIES,onProps)
await iotc.connect()
@pytest.mark.asyncio
async def test_onproperties_after(mocker):
mock_async=mock.Mock()
async def onProps(propname,propvalue):
return True
mock_async.return_value=await onProps('prop1',40)
iotc = init(mocker)
mocker.patch.object(iotc,'sendProperty',return_value=True)
asyncio.run(iotc.connect())
iotc.on(IOTCEvents.IOTC_PROPERTIES,onProps)
# give at least 10 seconds for the new listener to be recognized. assign the listener after connection is discouraged
time.sleep(11)
mock_async.assert_called_with('prop1',40)
stopThreads(iotc)
@pytest.mark.asyncio
async def test_onCommands_before(mocker):
onCmds=mock.Mock()
def mockedAck():
print('Callback called')
return True
iotc = init(mocker)
mocker.patch.object(iotc,'_cmdAck',mockedAck)
iotc.on(IOTCEvents.IOTC_COMMAND,onCmds)
asyncio.run(iotc.connect())
onCmds.assert_called_with(methodRequest,mockedAck)
stopThreads(iotc)
@pytest.mark.asyncio
async def test_onCommands_after(mocker):
onCmds=mock.Mock()
def mockedAck():
print('Callback called')
return True
iotc = init(mocker)
mocker.patch.object(iotc,'_cmdAck',mockedAck)
asyncio.run(iotc.connect())
iotc.on(IOTCEvents.IOTC_COMMAND,onCmds)
# give at least 10 seconds for the new listener to be recognized. assign the listener after connection is discouraged
time.sleep(11)
onCmds.assert_called_with(methodRequest,mockedAck)
stopThreads(iotc)