add docs
This commit is contained in:
Родитель
a14050d8a8
Коммит
c6b9ce4582
|
@ -5,6 +5,8 @@ exp.py
|
|||
*.pyc
|
||||
*.pyo
|
||||
|
||||
samples/
|
||||
|
||||
# VSCODE
|
||||
.vscode/
|
||||
|
||||
|
|
481
README.md
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)
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 82 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 135 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 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()
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
||||
|
Загрузка…
Ссылка в новой задаче