diff --git a/.gitignore b/.gitignore index f7b04e7..9a5cee3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,6 @@ samples/ .vscode/ # publish -iotc.egg-info +azure_iotcentral_device_client.egg-info build dist \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 00efda1..7001670 100755 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,2 @@ -include src/iotc/baltimore.pem \ No newline at end of file +include src/iotc/baltimore.pem +include assets/* \ No newline at end of file diff --git a/publish.sh b/publish.sh index 443336e..903b800 100755 --- a/publish.sh +++ b/publish.sh @@ -1,12 +1,15 @@ #!/bin/bash +shopt -s expand_aliases +source ~/.bashrc rm -rf build/ dist/ src/azure/iotcentral/device/client/iotc_device.egg-info src/azure/iotcentral/device/client/_pycache_ src/azure/iotcentral/device/client/_init_.pyc TEST="" if [[ $1 == 'test' ]]; then - TEST="-r testpypi" + TEST="--repository testpypi" fi -python2 setup.py sdist bdist_wheel + +#python2 setup.py sdist bdist_wheel python3 setup.py sdist bdist_wheel -twine upload dist/* $TEST \ No newline at end of file +python3 -m twine upload $TEST dist/* \ No newline at end of file diff --git a/samples/py2.py b/samples/py2.py index edbe113..b8375ca 100644 --- a/samples/py2.py +++ b/samples/py2.py @@ -7,9 +7,12 @@ from azure.iotcentral.device.client import IoTCClient, IOTCConnectType, IOTCLogL -deviceId = "nuovodev" -scopeId = "0ne00052362" -key = '68p6zEjwVNB6L/Dz8Wkz4VhaTrYqkndPrB0uJbWr2Hc/AmB+Qxz/eJJ9MIhLZFJ6hC0RmHMgfaYBkNTq84OCNQ==' +deviceId = "" +scopeId = "" +key = '' + +# optional model Id for auto-provisioning +modelId= '' def onProps(propName, propValue): @@ -25,12 +28,11 @@ def onCommands(command, ack): # see iotc.Device documentation above for x509 argument sample iotc = IoTCClient(deviceId, scopeId, IOTCConnectType.IOTC_CONNECT_SYMM_KEY, key) -iotc.setModelId('c318d580-39fc-4aca-b995-843719821049/1.5.0') +iotc.setModelId(modelId) iotc.setLogLevel(IOTCLogLevel.IOTC_LOGGING_ALL) iotc.on(IOTCEvents.IOTC_PROPERTIES, onProps) # iotc.on(IOTCEvents.IOTC_COMMAND, onCommands) -# iotc.setQosLevel(IOTQosLevel.IOTC_QOS_AT_MOST_ONCE) def main(): diff --git a/samples/py3.py b/samples/py3.py index d47673a..23f32c5 100644 --- a/samples/py3.py +++ b/samples/py3.py @@ -1,16 +1,18 @@ import sys sys.path.insert(0, 'src') -import time import asyncio from random import randint from azure.iotcentral.device.client.aio import IoTCClient, IOTCConnectType, IOTCLogLevel, IOTCEvents -deviceId = "nuovodev" -scopeId = "0ne00052362" -key = '68p6zEjwVNB6L/Dz8Wkz4VhaTrYqkndPrB0uJbWr2Hc/AmB+Qxz/eJJ9MIhLZFJ6hC0RmHMgfaYBkNTq84OCNQ==' +deviceId = "" +scopeId = "" +key = '' + +# optional model Id for auto-provisioning +modelId= '' async def onProps(propName, propValue): @@ -23,10 +25,10 @@ async def onCommands(command, ack): await ack(command.name, 'Command received', command.request_id) -# see iotc.Device documentation above for x509 argument sample +# change connect type to reflect the used key (device or group) iotc = IoTCClient(deviceId, scopeId, IOTCConnectType.IOTC_CONNECT_SYMM_KEY, key) -iotc.setModelId('c318d580-39fc-4aca-b995-843719821049/1.5.0') +iotc.setModelId(modelId) iotc.setLogLevel(IOTCLogLevel.IOTC_LOGGING_ALL) iotc.on(IOTCEvents.IOTC_PROPERTIES, onProps) iotc.on(IOTCEvents.IOTC_COMMAND, onCommands) @@ -42,7 +44,7 @@ async def main(): 'accelerometerY': str(randint(20, 45)), "accelerometerZ": str(randint(20, 45)) }) - time.sleep(3) + await asyncio.sleep(3) asyncio.run(main()) diff --git a/setup.py b/setup.py index 1baf3a1..1859587 100755 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ import setuptools import sys sys.path.insert(0, 'src') -from iotc import __version__, __name__ +from azure.iotcentral.device.client import __version__, __name__ with open("README.md", "r") as fh: long_description = fh.read() @@ -15,7 +15,7 @@ setuptools.setup( description="Azure IoT Central device client for Python", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/lucadruda/azure-iotcentral-device-client", + url="https://github.com/lucadruda/iotc-python-device-client", packages=setuptools.find_packages('src'), package_dir={'': 'src'}, license="MIT", @@ -27,10 +27,8 @@ setuptools.setup( 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy' + 'Programming Language :: Python :: 3.8', ], include_package_data=True, - install_requires=["paho-mqtt", "httplib2"] + install_requires=["azure-iot-device"] ) diff --git a/src/azure/iotcentral/device/client/__init__.py b/src/azure/iotcentral/device/client/__init__.py index 99724e5..b2b4aba 100644 --- a/src/azure/iotcentral/device/client/__init__.py +++ b/src/azure/iotcentral/device/client/__init__.py @@ -9,7 +9,7 @@ from azure.iot.device import ProvisioningDeviceClient from azure.iot.device import Message, MethodResponse from datetime import datetime -__version__ = "0.0.1-beta.2" +__version__ = "0.2.0-beta.3" __name__ = "azure-iotcentral-device-client" @@ -57,11 +57,6 @@ class IOTCConnectType: IOTC_CONNECT_DEVICE_KEY = 3 -class IOTCProtocol: - IOTC_PROTOCOL_MQTT = 1 - IOTC_PROTOCOL_AMQP = 2 - IOTC_PROTOCOL_HTTP = 4 - class IOTCLogLevel: IOTC_LOGGING_DISABLED = 1 @@ -116,7 +111,6 @@ class IoTCClient: self._credType = credType self._keyORCert = keyOrCert self._modelId = None - self._protocol = IOTCProtocol.IOTC_PROTOCOL_MQTT self._connected = False self._events = {} self._propThread = None @@ -138,12 +132,6 @@ class IoTCClient: 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): """ diff --git a/src/azure/iotcentral/device/client/aio/__init__.py b/src/azure/iotcentral/device/client/aio/__init__.py index 66a9b4b..43fde2c 100644 --- a/src/azure/iotcentral/device/client/aio/__init__.py +++ b/src/azure/iotcentral/device/client/aio/__init__.py @@ -5,7 +5,7 @@ from azure.iot.device.aio import ProvisioningDeviceClient from azure.iot.device import Message, MethodResponse from datetime import datetime -__version__ = "0.0.1-beta.2" +__version__ = "0.2.0-beta.3" __name__ = "azure-iotcentral-device-client" @@ -53,11 +53,6 @@ class IOTCConnectType: IOTC_CONNECT_DEVICE_KEY = 3 -class IOTCProtocol: - IOTC_PROTOCOL_MQTT = 1 - IOTC_PROTOCOL_AMQP = 2 - IOTC_PROTOCOL_HTTP = 4 - class IOTCLogLevel: IOTC_LOGGING_DISABLED = 1 @@ -112,7 +107,6 @@ class IoTCClient: self._credType = credType self._keyORCert = keyOrCert self._modelId = None - self._protocol = IOTCProtocol.IOTC_PROTOCOL_MQTT self._connected = False self._events = {} # self._threads = None @@ -125,24 +119,44 @@ 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): - 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 @@ -216,16 +230,31 @@ class IoTCClient: callback() async def sendProperty(self, payload, callback=None): + """ + Send a property message + :param dict payload: The properties payload. Can contain multiple properties in the form {'':{'value':''}} + :param function callback: Function executed after successfull dispatch + """ self._logger.debug('Sending property {}'.format(json.dumps(payload))) await self._deviceClient.patch_twin_reported_properties(payload) if callback is not None: callback() async 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 {'':,...,'':} + :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)) await self._sendMessage(json.dumps(payload), properties, callback) async 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( @@ -269,8 +298,7 @@ class IoTCClient: # setup listeners self._propThread = asyncio.create_task(self._onProperties()) - await self._propThread - #self._cmdThread = asyncio.create_task(self._onCommands()) + self._cmdThread = asyncio.create_task(self._onCommands()) # self._threads = await asyncio.gather( # self._onProperties(), # self._onCommands() diff --git a/test/v3/test_basic3.py b/test/v3/test_basic3.py index 98dd877..bd834ec 100644 --- a/test/v3/test_basic3.py +++ b/test/v3/test_basic3.py @@ -66,9 +66,7 @@ def async_return(result): async def stopThreads(iotc): iotc._propThread.cancel() - with suppress(asyncio.CancelledError): - return True - #iotc._cmdThread.cancel() + iotc._cmdThread.cancel() @pytest.mark.asyncio @@ -91,7 +89,7 @@ async def test_deviceKeyGeneration(mocker): iotc = init(mocker) await iotc.connect() assert iotc._keyORCert == deviceKey - stopThreads(iotc) + await stopThreads(iotc) @pytest.mark.asyncio @@ -99,14 +97,33 @@ async def test_hubConnectionString(mocker): iotc = init(mocker) await iotc.connect() assert iotc._hubCString == expectedHub - stopThreads(iotc) + await 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) + + mocker.patch.object(iotc,'sendProperty',return_value=True) + iotc.on(IOTCEvents.IOTC_PROPERTIES,onProps) + await iotc.connect() + try: + await iotc._propThread + await iotc._cmdThread + except asyncio.CancelledError: + pass + + + +@pytest.mark.asyncio +async def test_onproperties_after(mocker): + iotc = init(mocker) + async def onProps(propname,propvalue): assert propname == 'prop1' assert propvalue == 40 @@ -114,64 +131,62 @@ async def test_onproperties_before(mocker): 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) + + try: + await iotc._propThread + except asyncio.CancelledError: + pass @pytest.mark.asyncio async def test_onCommands_before(mocker): - onCmds=mock.Mock() + iotc = init(mocker) - def mockedAck(): - print('Callback called') + async def onCmds(command,ack): + ret=ack() + assert ret=='mocked' + await stopThreads(iotc) return True - iotc = init(mocker) + + def mockedAck(): + return 'mocked' + mocker.patch.object(iotc,'_cmdAck',mockedAck) iotc.on(IOTCEvents.IOTC_COMMAND,onCmds) - asyncio.run(iotc.connect()) - onCmds.assert_called_with(methodRequest,mockedAck) - stopThreads(iotc) + await iotc.connect() + try: + await iotc._cmdThread + except asyncio.CancelledError: + pass @pytest.mark.asyncio async def test_onCommands_after(mocker): - onCmds=mock.Mock() + iotc = init(mocker) - def mockedAck(): - print('Callback called') + async def onCmds(command,ack): + ret=ack() + assert ret=='mocked' + await stopThreads(iotc) return True - iotc = init(mocker) + + def mockedAck(): + return 'mocked' + mocker.patch.object(iotc,'_cmdAck',mockedAck) - asyncio.run(iotc.connect()) + + await 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) - + try: + await iotc._cmdThread + except asyncio.CancelledError: + pass