404
- -Page not found
- - -diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..ff80994b Binary files /dev/null and b/.DS_Store differ diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index f0ec8c83..00000000 --- a/.coveragerc +++ /dev/null @@ -1,3 +0,0 @@ -[run] -branch = True -source = src/wiotp/sdk \ No newline at end of file diff --git a/.coveralls.yml b/.coveralls.yml deleted file mode 100644 index ea06f7a6..00000000 --- a/.coveralls.yml +++ /dev/null @@ -1,2 +0,0 @@ -service_name: travis-ci -parallel: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..53b8d72f --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,25 @@ +name: Build Documentation + +on: + push: + branches: [ '**' ] + tags-ignore: [ '**' ] + +jobs: + deploy-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.3.1 + + - name: Install and Build + run: | + python -m pip install -q mkdocs + mkdocs build --verbose --clean --strict + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@4.1.7 + if: github.ref == 'refs/heads/master' + with: + branch: gh-pages + folder: site diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 00000000..744c535b --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,61 @@ +name: Python Package + +on: + push: + branches: [ "*" ] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11"] + primary-config: ["true", "false"] + # There may be a better way to do this, but it was the first way I found to set a variable for specific matrix entry + exclude: + - python-version: "3.9" + primary-config: "true" + - python-version: "3.10" + primary-config: "true" + - python-version: "3.11" + primary-config: "false" + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install + run: | + python -m pip install --upgrade pip + python -m pip install .[dev] flake8 + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 src --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest (only on Python 3.11 to avoid concurrency issues) + if: ${{ (github.ref == 'refs/heads/master') && (matrix.primary-config == 'true') }} + env: + ONE_JOB_ONLY_TESTS: ${{ matrix.primary-config }} + WIOTP_API_KEY: ${{ secrets.WIOTP_API_KEY }} + WIOTP_API_TOKEN: ${{ secrets.WIOTP_API_TOKEN }} + WIOTP_ORG_ID: ${{ secrets.WIOTP_ORG_ID }} + CLOUDANT_HOST: ${{ secrets.CLOUDANT_HOST }} + CLOUDANT_PORT: ${{ secrets.CLOUDANT_PORT }} + CLOUDANT_USERNAME: ${{ secrets.CLOUDANT_USERNAME }} + CLOUDANT_PASSWORD: ${{ secrets.CLOUDANT_PASSWORD }} + EVENTSTREAMS_API_KEY: ${{ secrets.EVENTSTREAMS_API_KEY }} + EVENTSTREAMS_ADMIN_URL: ${{ secrets.EVENTSTREAMS_ADMIN_URL }} + EVENTSTREAMS_BROKER1: ${{ secrets.EVENTSTREAMS_BROKER1 }} + EVENTSTREAMS_BROKER2: ${{ secrets.EVENTSTREAMS_BROKER2 }} + EVENTSTREAMS_BROKER3: ${{ secrets.EVENTSTREAMS_BROKER3 }} + EVENTSTREAMS_BROKER4: ${{ secrets.EVENTSTREAMS_BROKER4 }} + EVENTSTREAMS_BROKER5: ${{ secrets.EVENTSTREAMS_BROKER5 }} + EVENTSTREAMS_BROKER6: ${{ secrets.EVENTSTREAMS_BROKER6 }} + EVENTSTREAMS_USER: ${{ secrets.EVENTSTREAMS_USER }} + EVENTSTREAMS_PASSWORD: ${{ secrets.EVENTSTREAMS_PASSWORD }} + run: | + pytest diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 00000000..b23aa568 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,29 @@ +name: Upload Python Package + +on: + release: + types: [ published ] + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Install + run: | + python -m pip install --upgrade pip + pip install .[dev] + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore index c7b97e4a..adff9ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ samples/deviceFactory/bin samples/*.exe samples/rbac-config.yaml test/.DS_Store +/venv +pandoc-*-amd64.deb +README.rst diff --git a/.tox.ini.swp b/.tox.ini.swp deleted file mode 100644 index 64a554f0..00000000 Binary files a/.tox.ini.swp and /dev/null differ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2ed7ec26..00000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -group: travis_latest - -language: python - -services: - - docker - -cache: pip - -matrix: - include: - - python: "2.7" - - python: "3.5" - - python: "3.6" - - python: "3.7" - dist: xenial # required for Python >= 3.7 - env: - - BUILD_DOCKER_IMAGES=true - -install: - - pip install tox-travis coveralls pyyaml - - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - -script: - - tox - - ./buildDockerImages.sh - - -after_success: - # Only upload coverage report from pythonn 3.7 test run if BUILD_DOCKER_IMAGES is not null and not empty - - | - if [ -n "${BUILD_DOCKER_IMAGES}" ]; then - coveralls - fi - -notifications: - webhooks: https://coveralls.io/webhook diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..9a178351 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +.PHONY: docs install test build clean + +venv: + python3 -m venv venv + +clean: + rm -rf venv + +install: venv + . venv/bin/activate && python -m pip install --editable .[dev] + +test: venv + . venv/bin/activate && pytest + +build: venv + rm README.rst + . venv/bin/activate && python -m build + +docs: + cd docs && make html diff --git a/README.md b/README.md index a30371c3..2a554a52 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,36 @@ -# Python for IBM Watson IoT Platform +Python for IBM Watson IoT Platform +=============================================================================== -[](https://travis-ci.org/ibm-watson-iot/iot-python) -[](https://coveralls.io/github/ibm-watson-iot/iot-python?branch=master) [](https://github.com/ibm-watson-iot/iot-python/issues) [](https://github.com/ibm-watson-iot/iot-python/blob/master/LICENSE) [](https://pypi.org/project/wiotp-sdk/) + [](https://pepy.tech/project/ibmiotf) [](https://pepy.tech/project/wiotp-sdk) [](https://github.com/ambv/black) -Python module for interacting with the [IBM Watson IoT Platform](https://internetofthings.ibmcloud.com). +Python module for interacting with **Maximo IoT** and **[IBM Watson IoT Platform](https://internetofthings.ibmcloud.com)** -- [Python 3.7](https://www.python.org/downloads/release/python-373/) (recommended) -- [Python 2.7](https://www.python.org/downloads/release/python-2716/) +- Python 3.11 +- Python 3.10 +- Python 3.9 -Note: Support for MQTT with TLS requires at least Python v2.7.9 and openssl v1.0.1 +Product Withdrawal Notice +------------------------------------------------------------------------------- +Per the September 8, 2020 [announcement](https://www-01.ibm.com/common/ssi/cgi-bin/ssialias?subtype=ca&infotype=an&appname=iSource&supplier=897&letternum=ENUS920-136#rprodnx) IBM Watson IoT Platform (5900-A0N) has been withdrawn from marketing effective **December 9, 2020**. As a result, updates to this project will be limited. -## Dependencies +Dependencies +------------------------------------------------------------------------------- - [paho-mqtt](https://pypi.python.org/pypi/paho-mqtt) - [iso8601](https://pypi.python.org/pypi/iso8601) - [pytz](https://pypi.python.org/pypi/pytz) - [requests](https://pypi.python.org/pypi/requests) -## Installation - +Installation +------------------------------------------------------------------------------- Install the [latest version](https://pypi.org/project/wiotp-sdk/) of the library with pip ``` @@ -34,39 +38,30 @@ Install the [latest version](https://pypi.org/project/wiotp-sdk/) of the library ``` -## Uninstall - +Uninstall +------------------------------------------------------------------------------- Uninstalling the module is simple. ``` # pip uninstall wiotp-sdk ``` -## Legacy ibmiotf Module - -Version `0.4.0` of the old [ibmiotf](https://pypi.python.org/pypi/ibmiotf) pre-release is still available, if you do not wish to upgrade to the new version, we have no plans to remove this from pypi at this time, however it will not be getting any updates. - - -## Documentation +Documentation +------------------------------------------------------------------------------- https://ibm-watson-iot.github.io/iot-python/ -## Supported Features - +Supported Features +------------------------------------------------------------------------------- - **Device Connectivity**: Connect your device(s) to Watson IoT Platform with ease using this library - **Gateway Connectivity**: Connect your gateway(s) to Watson IoT Platform with ease using this library - **Application connectivity**: Connect your application(s) to Watson IoT Platform with ease using this library - **Watson IoT API**: Support for the interacting with the Watson IoT Platform through REST APIs -- **SSL/TLS**: By default, this library connects your devices, gateways and applications securely to Watson IoT Platform registered service. Ports `8883` (default) and `443` support secure connections using TLS with the MQTT and HTTP protocol. Support for MQTT with TLS requires at least Python v2.7.9 or v3.4, and openssl v1.0.1 +- **SSL/TLS**: By default, this library connects your devices, gateways and applications securely to Watson IoT Platform registered service. Ports `8883` (default) and `443` support secure connections using TLS with the MQTT and HTTP protocol. Support for MQTT with TLS requires at least Python v2.7.9 or v3.5, and openssl v1.0.1 - **Device Management for Device**: Connects your device(s) as managed device(s) to Watson IoT Platform. - **Device Management for Gateway**: Connects your gateway(s) as managed device(s) to Watson IoT Platform. - **Device Management Extensions**: Provides support for custom device management actions. - **Scalable Applications**: Supports load balancing of MQTT subscriptions over multiple application instances. - **Auto Reconnect**: All clients support automatic reconnect to the Platform in the event of a network interruption. - **Websockets**: Support device/gateway/application connectivity to Watson IoT Platform using WebSocket - - -## Unsupported Features -- **Client side Certificate based authentication**: [Client side Certificate based authentication](https://console.ng.bluemix.net/docs/services/IoT/reference/security/RM_security.html)n - diff --git a/docs/404.html b/docs/404.html deleted file mode 100644 index c8a12865..00000000 --- a/docs/404.html +++ /dev/null @@ -1,243 +0,0 @@ - - - -
- - - - - - -Page not found
- - -Service bindings can be established with Cloudant and EventStreams, service bindings are required to allow the configuration of data store connectors:
-import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-serviceBinding = {
- "name": "test-cloudant",
- "description": "Test Cloudant instance",
- "type": "cloudant",
- "credentials": {
- "host": "hostname",
- "port": 443,
- "username": "username",
- "password": "password
- }
-}
-
-cloudantService = appClient.serviceBindings.create(serviceBinding)
-
-serviceBinding = {
- "name": "test-eventstreams",
- "description": "Test EventStreams instance",
- "type": "eventstreams",
- "credentials": {
- "api_key": "myapikey",
- "user": "myusername,
- "password": "mypassword",
- "kafka_admin_url": "myurl",
- "kafka_brokers_sasl": [ "broker1", "broker2", "broker3", "broker4", "broker5" ]
- }
-}
-
-eventstreamsService = appClient.serviceBindings.create(serviceBinding)
-
-
-Finding service bindings
-import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-# Iterate through all service bindings
-for s in appClient.serviceBindings:
- print(s.name)
- print(" - " + s.description)
- print(" - " + s.type)
- print()
-
-print()
-
-# Iterate through service bindings of type "cloudant"
-for s in appClient.serviceBindings.find(typeFilter="cloudant"):
- print(s.name)
- print(" - " + s.description)
- print(" - " + s.type)
- print()
-
-
- Data store connectors can only be configured after you have set up one or more service bindings:
-import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-serviceBinding = {
- "name": "test-cloudant", "description": "Test Cloudant instance", "type": "cloudant",
- "credentials": { "host": "hostname", "port": 443, "username": "username", "password": "password" }
-}
-
-cloudantService = appClient.serviceBindings.create(serviceBinding)
-
-# Create the connector
-connector = self.appClient.dsc.create(
- name="connector1", type="cloudant", serviceId=cloudantService.id, timezone="UTC",
- description="A test connector", enabled=True
-)
-
-# Create a destination under the connector
-destination1 = connector.destinations.create(name="all-data", bucketInterval="DAY")
-
-# Create a rule under the connector, that routes all events to the destination
-rule1 = connector.rules.createEventRule(
- name="allevents", destinationName=destination1.name, description="Send all events",
- enabled=True, typeId="*", eventId="*"
-)
-# Create a second rule under the connector, that routes all state to the same destination
-rule2 = connector.rules.createStateRule(
- name="allstate", destinationName=destination1.name, description="Send all state",
- enabled=True, logicalInterfaceId="*"
-)
-
-
-import wiotp.sdk.application
-from wiotp.sdk.api.services import EventStreamsServiceBindingCredentials, EventStreamsServiceBindingCreateRequest
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-serviceBinding = {
- "name": "test-eventstreams",
- "description": "Test EventStreams instance",
- "type": "eventstreams",
- "credentials": {
- "api_key": "EVENTSTREAMS_API_KEY",
- "user": "EVENTSTREAMS_USER",
- "password": "EVENTSTREAMS_PASSWORD",
- "kafka_admin_url": "EVENTSTREAMS_ADMIN_URL",
- "kafka_brokers_sasl": [
- "EVENTSTREAMS_BROKER1",
- "EVENTSTREAMS_BROKER2",
- ],
- },
-}
-
-eventStreamsService = appClient.serviceBindings.create(serviceBinding)
-
-# Create the connector
-connector = self.appClient.dsc.create(
- name="connectorES", type="eventstreams", serviceId=eventStreamsService.id, timezone="UTC",
- description="A test event streams connector", enabled=True
-)
-
-# Create a destination under the connector
-destination1 = connector.destinations.create(name="all-data", partitions=3)
-
-# Create a rule under the connector, that routes all events to the destination
-rule1 = connector.rules.createEventRule(
- name="allevents", destinationName=destination1.name, description="Send all events",
- enabled=True, typeId="*", eventId="*"
-)
-# Create a second rule under the connector, that routes all state to the same destination
-rule2 = connector.rules.createStateRule(
- name="allstate", destinationName=destination1.name, description="Send all state",
- enabled=True, logicalInterfaceId="*"
-)
-
-
-import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-credentials = {
- "hostname": "DB2_HOST", "port": "DB2_PORT", "username": "DB2_USERNAME",
- "password": "DB2_PASSWORD", "https_url": "DB2_HTTPS_URL", "ssldsn": "DB2_SSL_DSN",
- "host": "DB2_HOST", "uri": "DB2_URI", "db": "DB2_DB", "ssljdbcurl": "DB2_SSLJDCURL",
- "jdbcurl": "DB2_JDBCURL",
-}
-
-serviceBinding = {
- "name": "test-db2", "description": "Test DB2 instance", "type": "db2",
- "credentials": credentials,
-}
-
-db2Service = appClient.serviceBindings.create(serviceBinding)
-
-# Create the connector
-connector = self.appClient.dsc.create(
- name="connectorDB2", type="db2", serviceId=db2Service.id, timezone="UTC",
- description="A test connector", enabled=True
-)
-
-# Create a destination under the connector
-destination1 = connector.destinations.create(name="all-data", bucketInterval="DAY")
-
-# Create a rule under the connector, that routes all state to the same destination
-# We can only forward state to a db2 connector, not the raw events
-rule = connector.rules.createStateRule(
- name="allstate", destinationName=destination1.name, description="Send all state",
- enabled=True, logicalInterfaceId="*"
-)
-
-
-Tip
-You can have multiple connectors (up to 10, depending on your service plan) of mixed types (Cloudant or EventStreams). This means 10 combined, not 10 of each.
-import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-# Create the connector
-serviceId = "xxx"
-connector = appClient.dsc.create(
- name="test-connector-cloudant", serviceId=serviceId, timezone="UTC", description="A test connector", enabled=True
-)
-
-print(" - " + connector.name)
-print(" - " + connector.connectorType)
-print(" - Enabled: " + connector.enabled)
-
-
-Each connector can have multiple destinations defined (up to 100 depending on your service plan)
-Tip
-Destinations are immutable. If you want to change where you send events to:
-import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-connectorId = "xxx"
-connector = appClient.dsc[connectorId]
-
-# Create a destination under the connector
-destination1 = connector.destinations.create(name=destinationName, bucketInterval="DAY")
-
-
-
-Forwarding rules configure what kind of data and the scope of the data that is sent to a destination. Each connector can have multiple forwarding rules defined (up to 100 depending on your service plan)
-Tip
-Each forwarding rule can only route to a single destination, but multiple rules can reference the same destination
-import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-# Note: This code assumes a destination named "all-data" has already been created under this connector
-connectorId = "xxx"
-connector = appClient.dsc[connectorId]
-
-# Create a rule under the connector, that routes all events to the destination
-rule1 = connector.rules.createEventRule(
- name="allevents",
- destinationName="all-data",
- description="Send all events",
- enabled=True,
- typeId="*",
- eventId="*"
-)
-
-# Create a second rule under the connector, that routes all state to the same destination
-rule2 = createdConnector.rules.createStateRule(
- name="allstate",
- destinationName="all-data",
- description="Send all state",
- enabled=True,
- logicalInterfaceId="*"
-)
-
-
- Last Event Cache is an optional feature in Watson IoT Platform, which when enabled allows the caching of the last event sent for each eventId by each registered device. By default this feature is disabled, to use this feature you must enable it from your dashboard at https://MYORGID.internetofthings.ibmcloud.com/dashboard/settings
.
The lec.get(device, eventId)
method allows you to retrieve the last event isntance of a specific eventId sent by a device. The method supports multiple ways to identify the device, as demonstrated below.
import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-eventId = "test1"
-
-# Get the last event using a python dictionary to define the device
-device = {"typeId": "myType", "deviceId": "myDevice"}
-lastEvent = appClient.lec.get(device, eventId)
-
-# Get the last event using a DeviceUid support class to define the device
-import wiotp.sdk.api.registry.devices.DeviceUid
-device = DeviceUid(typeId: "myType", deviceId: "myDevice")
-lastEvent = appClient.lec.get(device, eventId)
-
-# Get the last event using the result of a lookup from the device registry
-try:
- device = client.registry.devicetypes["myType"].devices["myDevice"]
- lastEvent = appClient.lec.get(device, eventId)
-except KeyError as e:
- print("Device does not exist %s" % (e))
-
-
-The lec.getAll(device)
method returns a list of the last cached event for all eventIds for a single device. As with the lec.get()
method, this supports multiple ways to define the device.
import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-# Get the last events using a python dictionary to define the device
-device = {"typeId": "myType", "deviceId": "myDevice"}
-lastEvents = appClient.lec.getAll(device)
-
-# Get the last events using a DeviceUid support class to define the device
-import wiotp.sdk.api.registry.devices.DeviceUid
-device = DeviceUid(typeId: "myType", deviceId: "myDevice")
-lastEvents = appClient.lec.getAll(device)
-
-# Get the last events using the result of a lookup from the device registry
-try:
- device = client.registry.devicetypes["myType"].devices["myDevice"]
- lastEvents = appClient.lec.getAll(device)
-except KeyError as e:
- print("Device does not exist %s" % (e))
-
-
-The wiotp.sdk.api.lec.LastEvent
class extends defaultdict allowing you to treat the reponse as a simple Python dictionary if you so choose, however it's designed to make it easy to interact with the Last Event Cache API results by providing convenient properties representing the data available from the API:
eventId
The eventId of the cached eventtypeId
The typeId of the device that sent the cached eventdeviceId
The devieId of the device that sent the cached eventformat
The format that the cached event was sent usingtimestamp
The date and time when the event was cached. This is not the time the event was published by the device. Events are cached in batches, so although this will usually be a good approximation of the time the event was sent, you can not rely on this timestamp representing the precise time the event was sent, only the time that it reached the cache.payload
The base64 encoded message content (payload) of the cached event. The payload is base64 encoded to allow for any content-type to be safely cached and retrieved. use the information in the format to determine how to handle the payload correctly.import wiotp.sdk.application
-import base64
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-# Get the last events using a python dictionary to define the device
-device = {"typeId": "myType", "deviceId": "myDevice"}
-lastEvents = appClient.lec.getAll(device)
-
-for event in lastEvents:
- print("Event from device: %s:%s" % (event.typeId, event.deviceId))
- print("- Event ID: %s " % (event.eventId))
- print("- Format: %s" % (event.format))
- print("- Cached at: %s" % (event.timestamp.isoformat()))
-
- # The payload is always returned base64 encoded by the API
- print("- Payload (base64 encoded): %s" % (event.payload))
-
- # Depending on the content of the message this may not be a good idea (e.g. if it was originally binary data)
- print("- Payload (decoded): %s" % (base64.b64decode(event.payload).decode('utf-8')))
-
-
- Every device in your organization is uniquely identifiable by the combination of it's typeId
and deviceId
. Two devices of different types can have the same deviceId
. It helps to think of typeId
as a model number and deviceId
as a serial number. Two seperate types may independently have the same serial number pattern (e.g. simple sequential numbering), but because identity in WIoTP is a combination of typeId
and deviceId
this does not cause a clash.
Globally, your organization ID is applied to the deviceUID creating a globally unique identifier for every device connected to Watson IoT Platform.
-myDeviceType
myDevice
myDeviceType:myDevice
myOrg:myDeviceType:myDevice
The wiotp.sdk.registry.devices.DeviceUID
class provides a structure to encapsulate a device unique ID. You can use it interchangeably throughout the SDK with a simple Python dictionary. It's a code style choice.
-from pprint import pprint
-import wiotp.sdk.application
-import wiotp.sdk.api.registry.devices.DeviceUid as DeviceUid
-
-deviceIdAsClass = DeviceUid(typeId="myDeviceType", deviceId="myDevice")
-deviceIdAsDict = {"typeId": "myDeviceType", "deviceId": "myDevice"}
-
-# All three output "myDeviceType:myDevice"
-print(deviceIdAsClass.typeId + ":" + deviceIdAsClass.deviceId)
-print(deviceIdAsClass)
-print(deviceIdAsDict["typeId"] + ":" + deviceIdAsDict["deviceId"])
-
-# Outputs {"typeId": "myDeviceType", "deviceId": "myDevice"}
-pprint(deviceIdAsClass)
-
-# These two calls are identical, the same applies anywhere that a
-# deviceUid is needed, it is your choice which style you prefer
-# in your code.
-appClient.registry.devices.delete(deviceIdAsClass)
-appClient.registry.devices.delete(deviceIdAsDict)
-
-
-wiotp.sdk.api.regsitry.devices.DeviceCreateRequest
exists to aid in the registration of new devices. It's use is entirely optional, and you can use a standard python dictionary instead if you prefer. To create an instance of this class provide the following named parameters:
typeId
RequireddeviceId
RequiredauthToken
Optional, if you do not specify a token on creation one will be generated automatically for you. If you take a generated toekn, you must capture the token from the device registration response as it can not be retrieved later.deviceInfo
Optional. A python dictionary, or a DeviceInfo
instancelocation
Optionalmetadata
Optional dictionary containing freeform metadataimport uuid
-import wiotp.sdk.application
-import wiotp.sdk.api.registry.devices.DeviceCreateRequest as DeviceCreateRequest
-import wiotp.sdk.api.registry.devices.DeviceInfo as DeviceInfo
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-# Device registration using the helper classes
-deviceToRegister1 = DeviceCreateRequest(
- typeId="myDeviceType",
- deviceId=str(uuid.uuid4()),
- authToken="NotVerySecretPassw0rd",
- deviceInfo=DeviceInfo(serialNumber="123", descriptiveLocation="Floor 3, Room 2")
-)
-
-appClient.registry.devices.create(deviceToRegister1)
-
-# Device registration using simple Python dictionaries
-deviceToRegister2 = {
- "typeId": "myDeviceType",
- "deviceId": str(uuid.uuid4()),
- "authToken": "NotVerySecretPassw0rd",
- "deviceInfo": {
- "serialNumber": "123",
- "descriptiveLocation": "Floor 3, Room 2"
- }
-}
-
-appClient.registry.devices.create(deviceToRegister2)
-
-
-registry.devices.create()
also allows you to pass a list of devices to perform bulk device registration.
import uuid
-import wiotp.sdk.application
-import wiotp.sdk.api.registry.devices.DeviceCreateRequest as DeviceCreateRequest
-
-# Register 100 devices with random UUIDs as deviceIds
-devicesToRegister = []
-for i in range(100)
- dReq = DeviceCreateRequest(typeId="myDeviceType", deviceId=str(uuid.uuid4())
- devicesToRegister.append(dReq)
-
-registrationResult = appClient.registry.devices.create(devicesToRegister)
-
-
-
-registry.devicetypes
provides a dictionary-like interface to simplify working with device types in your application:
registry.devicetypes[typeId]
for deviceType in registry.devicetypes
del registry.devicetypes[typeId]
if typeId in appClient.registry.devicetypes
wiotp.sdk.api.registry.types.DeviceType
provides a number of properties and functions to simplify application development when working with device types:
id
The ID for the device typedescription
The description string of the device type (None
if no description is available)classId
Identifies the device type as either a normal or gateway class of devicemetadata
The metadata stored for the device type (None
if no metadata is available)json()
Returns the underlying JSON response body obtained from the APIimport wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-typeId = "myDeviceType"
-
-try:
- deviceType = appClient.registry.devicetypes[typeId]
- print("- %s\n :: %s" % (deviceType.id, deviceType.description))
-except KeyError:
- Print("Device type does not exist")
-
-
-
-import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-typeId = "myDeviceType"
-
-# Check for the existence of a specific device type
-if typeId in appClient.registry.devicetypes:
- print("Device type %s exists" % (typeId))
-
- # Delete the device type
- del appClient.registry.devicetypes[typeId]
-
-
-import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-# Display all device types and their descriptions
-for deviceType in appClient.registry.devicetypes:
- print("- %s\n :: %s" % (deviceType.id, deviceType.description))
-
-
-The registry.devicetypes.create(deviceType)
method accepts a dictionary object containing the data for the device type to be created:
id
Required.description
Optional description of the device typemetadata
Optional freeform metadata about the device typedeviceInfo
Optional structured metadata about the device type (see wiotp.sdk.api.registry.devices.DeviceInfo
)import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-typeId = "myDeviceType"
-
-# Retrieve a devicetype from the registry, if the devicetype doesn't exist create it
-deviceType = None
-try:
- deviceType = appClient.registry.devicetypes[typeId]
-except KeyError as ke:
- print("Device Type %s did not exist, creating it now ..." % (typeId) )
- deviceType = appClient.registry.devicetypes.create({"typeId": typeId, "description": "I just created this using the WIoTP Python SDK"})
-
-print(deviceType)
-
-
-The registry.devicetypes.update(typeId, description, deviceInfo, metadata)
method enabled updates to existing device types.
typeId
indicates the device type to be updateddescription
, deviceInfo
, & metadata
provide the content for the updateimport wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-typeId = "myDeviceType"
-
-# Display the original data
-deviceType = appClient.registry.devicetypes[typeId]
-print("- %s\n :: %s" % (deviceType.id, deviceType.description))
-
-# Update the device type and capture the updated information
-deviceType = appClient.registry.devicetypes.update(typeId, description="This is an updated description")
-print("- %s\n :: %s" % (deviceType.id, deviceType.description))
-
-
- The serviceStatus()
method provides a simple way to query the health of IBM Watston IoT Platform in your region. The response is a Python dictionary that firstly, tells you the region your service instance is running in (e.g. us
, uk
, de
), and below that will provide a simple green
, orange
, red
overview of the health of the platform broken down by different capabilities:
dashboard
Availability of the dashboardmessaging
Availability of the core messaging service (for events, commands, device management protocol etc)thirdParty
Availability of the third party connector service (for ARM)The three status' represent:
-green
No known issues currentlyorange
Degraded performance, by service is availablered
Service outage, or performance significantly impacted as to be unusableThe wiotp.sdk.api.status.ServiceStatusResult
class extends defaultdict allowing you to treat the reponse as a simple Python dictionary that contains the json response body of the API call made under the covers if you so choose, however it's designed to make it easy to interact with the API results by providing convenient properties representing the data available from the API:
region
The Watson IoT Platform region where your organization runs.messaging
The status of core messaging services in your region.dashboard
The availability of the web dashboard (UI) in your region.thirdParty
The status of third party connector service (for ARM) in your region.import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-status = appClient.serviceStatus()
-
-# If you don't know what region you are in you can look it up from the status
-region = status.region
-
-print("Messaging is %s" % (status.messaging))
-print("Dashboard is %s" % (status.dashboard))
-
-
- The usage.dataTransfer(start, end, detail)
method allows you to retrieve data regarding the volume of data transfer to Watson IoT Platfrom in your organization.
By setting detail=False
(or omitting the parameter entirely) the response will provide a month by month breakdown of your data transfer:
import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-dataTransfer = appClient.usage.dataTransfer(datetime.today() - timedelta(days=10), datetime.today())
-
-print("Time period = %s to %s" % (dataTransfer.start.isoformat(), dataTransfer.end.isoformat()))
-print("- Average usage = %s" % (dataTransfer.average))
-print("- Total usage = %s" % (dataTransfer.total))
-
-
-In setting detail=True
the response will additionally provide a day by day breakdown of data transfer.
import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseEnvVars()
-appClient = wiotp.sdk.application.ApplicationClient(options)
-
-dataTransfer = appClient.usage.dataTransfer(datetime.today() - timedelta(days=10), datetime.today(), True)
-
-print("Time period = %s to %s" % (dataTransfer.start.isoformat(), dataTransfer.end.isoformat()))
-print("- Average usage = %s bytes" % (dataTransfer.average))
-print("- Total usage = %s bytes" % (dataTransfer.total))
-
-for day in dataTransfer.days:
- print(" * Usage on %s = %s bytes" % (day.date.isoformat(), day.total) )
-
-
-
-The wiotp.sdk.api.usage.DataTransferSummary
and wiotp.sdk.api.usage.DayDataTransfer
classes provide an easy way to work with the data that is returned by the usage API. wiotp.sdk.api.usage.DataTransferSummary
is the top level container and provides the following properties:
start
The start date for the summary report (datetime.date
)end
The end date for the summary report (datetime.date
)average
The average usage across the summary periodtotal
The total usage across the summary perioddays
If detail=True
then this will be a list of DayDataTransfer
objectsThe wiotp.sdk.api.usage.DayDataTransfer
allows you to work with the day by day breakdown of data usage, exposing the following properties:
date
The day that usage is being reported for (datetime.date
)total
The total usage on this dayApplication configuration can be broken down into required and optional configuration:
-identity.appId
Your unique application ID.auth.key
An API key authentication token to securely connect your application to Watson IoT Platform.auth.token
The authentication token for the API key you are using.options.domain
A boolean value indicating which Watson IoT Platform domain to connect to (e.g. if you have a dedicated platform instance). Defaults to internetofthings.ibmcloud.com
options.logLevel
Controls the level of logging in the client, can be set to error
, warning
, info
, or debug
. Defaults to info
.options.http.verify
Allows HTTP certificate verification to be disabled if set to False
. You are strongly discouraged from using this in any production usage, this primarily exists as a development aid. Defaults to True
options.mqtt.instanceId
Optional instance ID, use if you wish create a multi-instance application which will loadbalance incoming messages.options.mqtt.port
A integer value defining the MQTT port. Defaults to auto-negotiation.options.mqtt.transport
The transport to use for MQTT connectivity - tcp
or websockets
.options.mqtt.cleanStart
A boolean value indicating whether to discard any previous state when reconnecting to the service. Defaults to False
.options.mqtt.sessionExpiry
When cleanStart is disabled, defines the maximum age of the previous session (in seconds). Defaults to False
.options.mqtt.keepAlive
Control the frequency of MQTT keep alive packets (in seconds). Details to 60
.options.mqtt.caFile
A String value indicating the path to a CA file (in pem format) to use in verifying the server certificate. Defaults to messaging.pem
inside this module.The config parameter when constructing an instance of wiotp.sdk.application.ApplicationClient
expects to be passed a dictionary containing this configuration:
myConfig = {
- "identity": {
- "appId": "app1"
- }.
- "auth" {
- "key": "orgid-h798S783DK"
- "token": "Ab$76s)asj8_s5"
- },
- "options": {
- "domain": "internetofthings.ibmcloud.com",
- "logLevel": "error|warning|info|debug",
- "http": {
- "verify": True|False
- },
- "mqtt": {
- "instanceId": "instance1",
- "port": 8883,
- "transport": "tcp|websockets",
- "cleanStart": True|False,
- "sessionExpiry": 3600,
- "keepAlive": 60,
- "caFile": "/path/to/certificateAuthorityFile.pem"
- }
- }
-}
-client = wiotp.sdk.application.ApplicationClient(config=myConfig, logHandlers=None)
-
-
-In most cases you will not manually build the config
dictionary. Two helper methods are provided to make configuration simple:
wiotp.sdk.application.parseConfigFile()
allows one to easily pass in application configuration from environment variables.
import wiotp.sdk.application
-
-myConfig = wiotp.sdk.application.parseConfigFile("application.yaml")
-client = wiotp.sdk.application.ApplicationClient(config=myConfig, logHandlers=None)
-
-
-identity:
- appId: app1
-auth:
- key: orgid-h798S783DK
- token: Ab$76s)asj8_s5
-
-
-This file defines all optional configuration parameters.
-identity:
- appId: app1
-auth:
- key: orgid-h798S783DK
- token: Ab$76s)asj8_s5
-options:
- domain: internetofthings.ibmcloud.com
- logLevel: debug
- http:
- verify: True
- mqtt:
- instanceId: instance1
- port: 8883
- transport: tcp
- cleanStart: true
- sessionExpiry: 7200
- keepAlive: 120
- caFile: /path/to/certificateAuthorityFile.pem
-
-
-wiotp.sdk.application.parseEnvVars()
allows one to easily pass in device configuration from environment variables.
import wiotp.sdk.application
-
-myConfig = wiotp.sdk.application.parseEnvVars()
-client = wiotp.sdk.application.ApplicationClient(config=myConfig, logHandlers=None)
-
-
-WIOTP_IDENTITY_APPID
WIOTP_AUTH_TOKEN
WIOTP_AUTH_KEY
WIOTP_OPTIONS_DOMAIN
WIOTP_OPTIONS_LOGLEVEL
WIOTP_OPTIONS_HTTP_VERIFY
WIOTP_OPTIONS_MQTT_INSTANCEID
WIOTP_OPTIONS_MQTT_PORT
WIOTP_OPTIONS_MQTT_TRANSPORT
WIOTP_OPTIONS_MQTT_CAFILE
WIOTP_OPTIONS_MQTT_CLEANSTART
WIOTP_OPTIONS_MQTT_SESSIONEXPIRY
WIOTP_OPTIONS_MQTT_KEEPALIVE
The wiotp.sdk.application
package contains the following:
The client implementations:
-wiotp.sdk.application.ApplicationClient
Support classes for working with the data model:
-wiotp.sdk.application.Command
wiotp.sdk.application.Event
wiotp.sdk.application.Status
Support methods for handling device configuration:
-wiotp.sdk.application.parseConfigFile
wiotp.sdk.application.parseEnvVars
Application configuration is passed to the client via the config
parameter when you create the client instance. See the configure applications section for full details of all available options, and the built-in support for YAML file and environment variable sourced configuration.
myConfig = {
- "auth" {
- "key": "a-org1id-y67si9et"
- "token": "Ab$76s)asj8_s5"
- }
-}
-client = wiotp.sdk.application.ApplicationClient(config=myConfig)
-
-
-connect()
& disconnect()
methods are used to manage the MQTT connection to IBM Watson IoT Platform that allows the application to
-handle commands and device events.
Applications have three flavours of real-time data to work with once they have established an MQTT connection, for more information on each of these subjects see the relavent section of the documentation:
- - -Applications can publish commands to connected devices.
-import wiotp.sdk.application
-
-options = wiotp.sdk.application.ParseConfigFile("app.yaml")
-client = wiotp.sdk.application.ApplicationClient(options)
-
-client.connect()
-commandData={'rebootDelay' : 50}
-client.publishCommand(myDeviceType, myDeviceId, "reboot", "json", commandData)
-
-
-An application can subscribe to commands sent to devices to monitor the command channel, the application must explicitly subscribe to any commands it wishes to monitor.
-To process specific commands, you need to register a command callback method.
-def myCommandCallback(cmd):
- print("Command received: %s" % cmd.data)
-
-client.commandCallback = myCommandCallback
-
-
-The messages are returned as an instance of the Command
class with the following attributes:
commandId
: Identifies the commandformat
: Format that the command was encoded in, for example json
data
: Data for the payload converted to a Python dict by an impleentation of MessageCodec
timestamp
: Date and time that the event was recieved (as datetime.datetime
object)If a command is recieved in an unknown format or if a device does not recognize the format, the device library raises wiotp.sdk.MissingMessageDecoderException
.
Events are the mechanism by which devices publish data to the Watson IoT Platform. The device controls the content of the event and assigns a name for each event that it sends. Depending on the permissions set in the API key that your application connects with your application will have the ability to publish events as if they originated from any registered device.
-As with devices, events can be published with any of the three quality of service (QoS) levels that are defined by the MQTT protocol. By default, events are published with a QoS level of 0.
-publishEvent()
takes up to 5 arguments:
event
Name of this eventmsgFormat
Format of the data for this eventdata
Data for this eventqos
MQTT quality of service level to use (0
, 1
, or 2
)on_publish
A function that will be called when receipt of the publication is confirmed.import wiotp.sdk.application
-
-options = wiotp.sdk.application.ParseConfigFile("app.yaml")
-client = wiotp.sdk.application.ApplicationClient(options)
-
-client.connect()
-myData={'name' : 'foo', 'cpu' : 60, 'mem' : 50}
-client.publishEvent(myDeviceType, myDeviceId, "status", "json", myData)
-
-
-Callback and QoS
-The use of the optional on_publish
function has different implications depending on the level of qos used to publish the event:
def eventPublishCallback():
- print("Device Publish Event done!!!")
-
-client.publishEvent(typeId="foo", deviceId="bar", eventId="status", msgFormat="json", data=myData, qos=0, onPublish=eventPublishCallback)
-
-
-subscribeToDeviceEvents()
allows the application to recieve real-time device events as they are published. With no parameters provided the method would subscribe the application to all events from all connected devices. In most use cases this is not what you want to do. Use the optional typeId
, deviceId
, eventId
, and msgFormat
parameters to control the scope of the subscription.
A single client can support multiple subscriptions. The following code samples show how you can use deviceType, deviceId, event, and msgFormat parameters to define the scope of a subscription:
-import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseConfigFile("app.yaml")
-client = wiotp.sdk.application.ApplicationClient(options)
-
-client.connect()
-
-# Subscribing to all events from all devices
-client.subscribeToDeviceEvents()
-
-# Subscribing to all events from all devices of a specific type
-client.subscribeToDeviceEvents(typeId=myDeviceType)
-
-# Subscribing to a specific event from all devices
-client.subscribeToDeviceEvents(eventId=myEvent)
-
-# Subscribing to a specific event from two or more different devices
-client.subscribeToDeviceEvents(typeId=myDeviceType, deviceId=myDeviceId, eventId=myEvent)
-client.subscribeToDeviceEvents(typeId=myOtherDeviceType, eventId=myEvent)
-
-# Subscribing to all events that are published in JSON format
-client.subscribeToDeviceEvents(msgFormat="json")
-
-
-To process the events that are received by your subscriptions, you need to register an event callback method. The messages are returned as an instance of the Event class:
-event.eventId
Typically used to group specific events, for example "status", "warning" and "data".event.typeId
Identifies the device type. Typically, the deviceType is a grouping for devices that perform a specific task, for example "weatherballoon".event.deviceId
Represents the ID of the device. Typically, for a given device type, the deviceId is a unique identifier of that device, for example a serial number or MAC address.event.device
Uniquely identifies the device across all types of devices in the organizationevent.format
The format can be any string, for example JSON.event.data
The data for the message payload.event.timestamp
The date and time of the eventimport wiotp.sdk.application
-
-options = wiotp.sdk.application.parseConfigFile("app.yaml")
-client = wiotp.sdk.application.ApplicationClient(options)
-
-def myEventCallback(event):
- str = "%s event '%s' received from device [%s]: %s"
- print(str % (event.format, event.eventId, event.device, json.dumps(event.data)))
-
-client.connect()
-client.deviceEventCallback = myEventCallback
-client.subscribeToDeviceEvents()
-
-
- subscribeToDeviceStatus()
allows the application to recieve real-time notification when devices connect and disconnect from the service. With no parameters provided the method would subscribe to notifications for all devies. Use the typeId
and deviceId
parameters to control the scope of the subscription. A single client can support multiple subscriptions.
import wiotp.sdk.application
-
-options = wiotp.sdk.application.parseConfigFile("app.conf")
-client = wiotp.sdk.application.ApplicationClient(options)
-
-client.connect()
-
-# Subscribing to status updates for all devices
-client.subscribeToDeviceStatus()
-
-# Subscribing to status updates for all devices of a specific type
-client.subscribeToDeviceStatus(typeId=myDeviceType)
-
-# Subscribing to status updates for two different devices
-client.subscribeToDeviceStatus(typeId=myDeviceType, deviceId=myDeviceId)
-client.subscribeToDeviceStatus(typeId=myOtherDeviceType, deviceId=myOtherDeviceId)
-
-
-To process the status updates that are received by your subscriptions, you need to register an event callback method. The messages are returned as an instance of the Status
class. There are two types of status events, Connect events and Disconnect events. All status events include the following properties:
clientAddr
protocol
clientId
user
time
action
connectTime
port
The action
property determines whether a status event is of type Connect or Disconnect. Disconnect status events include the following additional properties:
writeMsg
readMsg
reason
readBytes
writeBytes
import wiotp.sdk.application
-
-options = wiotp.sdk.application.ParseConfigFile(configFilePath)
-client = wiotp.sdk.application.ApplicationClient(options)
-
-def myStatusCallback(status):
-
- if status.action == "Disconnect":
- str = "%s - device %s - %s (%s)"
- print(str % (status.time.isoformat(), status.device, status.action, status.reason))
- else:
- print("%s - %s - %s" % (status.time.isoformat(), status.device, status.action))
-
-client.connect()
-client.deviceStatusCallback = myStatusCallback
-client.subscribeToDeviceStstus()
-
-
- The Internet is made up of "things", the most important concept to get to terms with when working with Watson IoT Platform is the idea of applications, devices, & gateways as three distinct classes of "thing" in your Internet of Things solution.
-Getting your physical device model right is essential to building a solution that will allow you to take advantage of all the advanced capabilities of Watson IoT.
-Applications are the most powerful class of thing in Watson IoT Platform.
-Tip
-Applications are able to function as a gateway into the service, but should only be used as such when you view the gateway as an abstract entity in your solution rather than something physical to be managed on-site. If you assoicate the central point of contact with the platform as a specific piece of hardware it should be implemented as a gateway.
-Warning
-Applications capabilities vary wildly depending on the permissions granted to the application by the API key that it uses to connect. It is important to align the role granted to the API key used by the application to the capabilities of the application.
-Devices are things that send data into the service (directly, or indirectly), and respond to commands directed at them.
-Tip
-Devices in Watson IoT Platform are intended to mirror the physical deployment of hardware that will generate IoT data, regardless of whether it directly connects to the internet.
-Warning
-If you deploy 6 pieces of hardware each with seperate firmware, software, etc avoid the temptation to think that tracking these as individual devices has no value. Merging them into an "abstract device" representing all 6 when you register your physical device model in Watson IoT will make it more difficult to use advanced features of the platform as you explore Watson IoT Platform's advanced capabilities for device and data management.
-Gateways are things that send data into the service, respond to commands, are able to send data from other devices, and relay commands to other devices.
-Tip
-Use gateways when you are developing a solution where multiple physical devices exist that will not each directly communicate with Watson IoT Platform, but instead will report to a local device, which serves as a central contact point to the service.
-By default, the client library support encoding and decoding events and commands as json
messages. To add support
-for your own custom message formats you can create and register implementations of wiotp.sdk.MessageCodec
. MessageCodecs work for both commands and events. To Implement a MessageCodec you must support two static class methods:
The job of the encode(data, timestamp)
method is to take data
(any python object) and optionally a timestamp
(a datetime.datetime
object) and
-return a String representation of the message ready to be sent over MQTT.
The job of decode(message)
is to decode an incoming MQTT message and return an instance of ibmiotf.Message
import yaml
-import wiotp.sdk.device
-import wiotp.sdk.Message
-import wiotp.sdk.MessageCodec
-
-class YamlCodec(ibmiotf.MessageCodec):
-
- @staticmethod
- def encode(data=None, timestamp=None):
- return yaml.dumps(data)
-
- @staticmethod
- def decode(message):
- try:
- data = yaml.loads(message.payload.decode("utf-8"))
- except ValueError as e:
- raise InvalidEventException("Unable to parse YAML. payload=\"%s\" error=%s" % (message.payload, str(e)))
-
- timestamp = datetime.now(pytz.timezone('UTC'))
-
- return wiotp.sdk.Message(data, timestamp)
-
-myConfig = ibmiotf.device.ParseConfigFile("device.yaml")
-client = ibmiotf.device.Client(config=myConfig, logHandlers=None)
-client.setMessageCodec("yaml", YamlCodec)
-myData = { 'hello' : 'world', 'x' : 100}
-
-# Publish the same event, in both json and yaml formats:
-client.publishEvent("status", "json", myData)
-client.publishEvent("status", "yaml", myData)
-
-
-If you want to lookup which encoder is set for a specific message format use the getMessageEncoderModule(msgFormt)
. If an event is sent/received in an unknown format or if a client does not recognize the format, the client library will raise wiotp.sdk.MissingMessageEncoderException
or wiotp.sdk.MissingMessageDecoderException
.
Device configuration can be broken down into required and optional configuration:
-identity.orgId
Your organization ID.identity.typeId
The type of the device. Think of the device type is analagous to a model number.identity.deviceId
A unique ID to identify a device. Think of the device id as analagous to a serial number.auth.token
An authentication token to securely connect your device to Watson IoT Platform.options.domain
A boolean value indicating which Watson IoT Platform domain to connect to (e.g. if you have a dedicated platform instance). Defaults to internetofthings.ibmcloud.com
options.logLevel
Controls the level of logging in the client, can be set to error
, warning
, info
, or debug
. Defaults to info
.options.mqtt.port
A integer value defining the MQTT port. Defaults to auto-negotiation.options.mqtt.transport
The transport to use for MQTT connectivity - tcp
or websockets
.options.mqtt.cleanStart
A boolean value indicating whether to discard any previous state when reconnecting to the service. Defaults to False
.options.mqtt.sessionExpiry
When cleanStart is disabled, defines the maximum age of the previous session (in seconds). Defaults to False
.options.mqtt.keepAlive
Control the frequency of MQTT keep alive packets (in seconds). Details to 60
.options.mqtt.caFile
A String value indicating the path to a CA file (in pem format) to use in verifying the server certificate. Defaults to messaging.pem
inside this module.The config parameter when constructing an instance of wiotp.sdk.device.DeviceClient
expects to be passed a dictionary containing this configuration:
myConfig = {
- "identity": {
- "orgId": "org1id",
- "typeId": "raspberry-pi-3"
- "deviceId": "00ef08ac05"
- }.
- "auth" {
- "token": "Ab$76s)asj8_s5"
- },
- "options": {
- "domain": "internetofthings.ibmcloud.com",
- "logLevel": "error|warning|info|debug",
- "mqtt": {
- "port": 8883,
- "transport": "tcp|websockets",
- "cleanStart": True|False,
- "sessionExpiry": 3600,
- "keepAlive": 60,
- "caFile": "/path/to/certificateAuthorityFile.pem"
- }
- }
-}
-client = wiotp.sdk.device.DeviceClient(config=myConfig, logHandlers=None)
-
-
-In most cases you will not manually build the config
dictionary. Two helper methods are provided to make configuration simple:
wiotp.sdk.device.parseConfigFile()
allows one to easily pass in device configuration from environment variables.
import wiotp.sdk.device
-
-myConfig = wiotp.sdk.device.parseConfigFile("device.yaml")
-client = wiotp.sdk.device.DeviceClient(config=myConfig, logHandlers=None)
-
-
-identity:
- orgId: org1id
- typeId: raspberry-pi
- deviceId: 00ef08ac05
-auth:
- token: Ab$76s)asj8_s5
-
-
-This file defines all optional configuration parameters.
-identity:
- orgId: org1id
- typeId: raspberry-pi
- deviceId: 00ef08ac05
-auth:
- token: Ab$76s)asj8_s5
-options:
- domain: internetofthings.ibmcloud.com
- logLevel: debug
- mqtt:
- port: 8883
- transport: tcp
- cleanStart: true
- sessionExpiry: 7200
- keepAlive: 120
- caFile: /path/to/certificateAuthorityFile.pem
-
-
-wiotp.sdk.device.parseEnvVars()
allows one to easily pass in device configuration from environment variables.
import wiotp.sdk.device
-
-myConfig = wiotp.sdk.device.parseEnvVars()
-client = wiotp.sdk.device.DeviceClient(config=myConfig, logHandlers=None)
-
-
-WIOTP_IDENTITY_ORGID
WIOTP_IDENTITY_TYPEID
WIOTP_IDENTITY_DEVICEID
WIOTP_AUTH_TOKEN
WIOTP_OPTIONS_DOMAIN
WIOTP_OPTIONS_LOGLEVEL
WIOTP_OPTIONS_MQTT_PORT
WIOTP_OPTIONS_MQTT_TRANSPORT
WIOTP_OPTIONS_MQTT_CAFILE
WIOTP_OPTIONS_MQTT_CLEANSTART
WIOTP_OPTIONS_MQTT_SESSIONEXPIRY
WIOTP_OPTIONS_MQTT_KEEPALIVE
The wiotp.sdk.device
package contains the following:
Two client implementations:
-wiotp.sdk.device.DeviceClient
wiotp.sdk.device.ManagedDeviceClient
Support classes for working with the data model:
-wiotp.sdk.device.Command
wiotp.sdk.device.DeviceInfo
wiotp.sdk.device.DeviceFirmware
Support methods for handling device configuration:
-wiotp.sdk.device.parseConfigFile
wiotp.sdk.device.parseEnvVars
Device configuration is passed to the client via the config
parameter when you create the client instance. See the configure devices section for full details of all available options, and the built-in support for YAML file and environment variable sourced configuration.
myConfig = {
- "identity": {
- "orgId": "org1id",
- "typeId": "raspberry-pi-3"
- "deviceId": "00ef08ac05"
- }.
- "auth" {
- "token": "Ab$76s)asj8_s5"
- }
-}
-client = wiotp.sdk.device.DeviceClient(config=myConfig)
-
-
-connect()
& disconnect()
methods are used to manage the MQTT connection to IBM Watson IoT Platform that allows the device to
-handle commands and publish events.
Events are the mechanism by which devices publish data to the Watson IoT Platform. The device -controls the content of the event and assigns a name for each event that it sends.
-When an event is received by Watson IoT Platform, the credentials of the received event identify -the sending device, which means that a device cannot impersonate another device.
-Events can be published with any of the three quality of service (QoS) levels that are defined -by the MQTT protocol. By default, events are published with a QoS level of 0.
-publishEvent()
takes up to 5 arguments:
eventId
Name of this eventmsgFormat
Format of the data for this eventdata
Data for this eventqos
MQTT quality of service level to use (0
, 1
, or 2
)onPublish
A function that will be called when receipt of the publication is confirmed.Callback and QoS
-The use of the optional onPublish
function has different implications depending
-on the level of qos used to publish the event:
def eventPublishCallback():
- print("Device Publish Event done!!!")
-
-client.publishEvent(eventId="status", msgFormat="json", data=myData, qos=0, onPublish=eventPublishCallback)
-
-
-When the device client connects, it automatically subscribes to any command that is specified for -this device. To process specific commands, you need to register a command callback method.
-def myCommandCallback(cmd):
- print("Command received: %s" % cmd.data)
-
-client.commandCallback = myCommandCallback
-
-
-The messages are returned as an instance of the Command
class with the following attributes:
commandId
: Identifies the commandformat
: Format that the command was encoded in, for example json
data
: Data for the payload converted to a Python dict by an impleentation of MessageCodec
timestamp
: Date and time that the event was recieved (as datetime.datetime
object)If a command is recieved in an unknown format or if a device does not recognize the format, the device
-library raises wiotp.sdk.MissingMessageDecoderException
.
import wiotp.sdk.device
-
-def myCommandCallback(cmd):
- print("Command received: %s" % cmd.data)
-
-# Configure
-myConfig = wiotp.sdk.device.parseConfigFile("device.yaml")
-client = wiotp.sdk.device.DeviceClient(config=myConfig, logHandlers=None)
-client.commandCallback = myCommandCallback
-
-# Connect
-client.connect()
-
-# Send Data
-myData={'name' : 'foo', 'cpu' : 60, 'mem' : 50}
-client.publishEvent(eventId="status", msgFormat="json", data=myData, qos=0, onPublish=None)
-
-# Disconnect
-client.disconnect()
-
-
- Sorry, this documentation is still a work in progress.
- -Exception classes in the SDK are common across the three packages (application, device, gateway). Below is a summary of the custom exception classes that are used in this SDK. All classes extend the base Exception
class, in the majority of cases you should be able to develop code without needing to worry about these classes, however their presence allows for more sophisticated error handling in more complex programs.
wiotp.sdk.ConnectionException
is a generic Connection exception. More details about the exception are available in the reason
property of the thrown exception.
Raised By:
-wiotp.sdk.UnsupportedAuthenticationMethod
is a specific type of wiotp.sdk.ConnectionException
, thrown when the authentication method specified is not supported. More details about the exception are available in the reason
property of the thrown exception.
Raised By:
-wiotp.sdk.ConfigurationException
is thrown when the configuration passed into an application, device, or gateway client is missing required properties, or has one or more invalid values defined. More details about the exception are available in the reason
property of the thrown exception.
Raised By:
-wiotp.sdk.InvalidEventException
is thrown when an Event
object can not be constructed by a MessageCodec
. More details about the exception are available in the reason
property of the thrown exception.
Raised By:
-wiotp.sdk.MissingMessageDecoderException
is thrown when there is no message decoder defined for the message format being processed. The specific format that cuased the problem can be found from the format
property of the thrown exception.
Raised By:
-wiotp.sdk.MissingMessageEncoderException
is thrown when there is no message encoder defined for the message format being processed. The specific format that cuased the problem can be found from the format
property of the thrown exception.
Raised By:
-wiotp.sdk.ApiException
is thrown when any API call unexpectedly fails. The thrown exception has a number of properties available to aid in debug:
response
Full details of the underlying API call that failed. This will be an instance of requests.Response
.body
The reponse body, if a reponse body was returned. Otherwise None
.message
The specific error message (in English) returned by IBM Watson IoT Platform. e.g. CUDRS0007E: The request was not valid. Review the constraint violations provided.
exception
The Exception code and properties for the error message, allowing clients to support error translation.id
The exception ID of the error (if available), e.g. CUDRS0007E
violations
If the error is due to a malformed request, this will contain the list of reasons why the request was rejected.Raised By:
-Gateway configuration can be broken down into required and optional configuration:
-identity.orgId
Your organization ID.identity.typeId
The type of the device. Think of the device type is analagous to a model number.identity.deviceId
A unique ID to identify a device. Think of the device id as analagous to a serial number.auth.token
An authentication token to securely connect your device to Watson IoT Platform.options.domain
A boolean value indicating which Watson IoT Platform domain to connect to (e.g. if you have a dedicated platform instance). Defaults to internetofthings.ibmcloud.com
options.logLevel
Controls the level of logging in the client, can be set to error
, warning
, info
, or debug
. Defaults to info
.options.mqtt.port
A integer value defining the MQTT port. Defaults to auto-negotiation.options.mqtt.transport
The transport to use for MQTT connectivity - tcp
or websockets
.options.mqtt.cleanStart
A boolean value indicating whether to discard any previous state when reconnecting to the service. Defaults to False
.options.mqtt.sessionExpiry
When cleanStart is disabled, defines the maximum age of the previous session (in seconds). Defaults to False
.options.mqtt.keepAlive
Control the frequency of MQTT keep alive packets (in seconds). Details to 60
.options.mqtt.caFile
A String value indicating the path to a CA file (in pem format) to use in verifying the server certificate. Defaults to messaging.pem
inside this module.The config parameter when constructing an instance of wiotp.sdk.gateway.GatewayClient
expects to be passed a dictionary containing this configuration:
myConfig = {
- "identity": {
- "orgId": "org1id",
- "typeId": "raspberry-pi-3"
- "deviceId": "00ef08ac05"
- }.
- "auth" {
- "token": "Ab$76s)asj8_s5"
- },
- "options": {
- "domain": "internetofthings.ibmcloud.com",
- "logLevel": "error|warning|info|debug",
- "mqtt": {
- "port": 8883,
- "transport": "tcp|websockets",
- "cleanStart": True|False,
- "sessionExpiry": 3600,
- "keepAlive": 60,
- "caFile": "/path/to/certificateAuthorityFile.pem"
- }
- }
-}
-client = wiotp.sdk.gateway.GatewayClient(config=myConfig, logHandlers=None)
-
-
-In most cases you will not manually build the config
dictionary. Two helper methods are provided to make configuration simple:
wiotp.sdk.gateway.parseConfigFile()
allows one to easily pass in gateway configuration from environment variables.
import wiotp.gateway.sdk
-
-myConfig = wiotp.sdk.device.parseConfigFile("gateway.yaml")
-client = ibmiotf.gateway.Client(config=myConfig, logHandlers=None)
-
-
-identity:
- orgId: org1id
- typeId: raspberry-pi
- deviceId: 00ef08ac05
-auth:
- token: Ab$76s)asj8_s5
-
-
-This file defines all optional configuration parameters.
-identity:
- orgId: org1id
- typeId: raspberry-pi
- deviceId: 00ef08ac05
-auth:
- token: Ab$76s)asj8_s5
-options:
- domain: internetofthings.ibmcloud.com
- logLevel: debug
- mqtt:
- port: 8883
- transport: tcp
- cleanStart: true
- sessionExpiry: 7200
- keepAlive: 120
- caFile: /path/to/certificateAuthorityFile.pem
-
-
-wiotp.sdk.gateway.parseEnvVars()
allows one to easily pass in gateway configuration from environment variables.
import wiotp.sdk.gateway
-
-myConfig = wiotp.sdk.gateway.parseEnvVars()
-client = wiopt.sdk.gateway.Client(config=myConfig, logHandlers=None)
-
-
-WIOTP_IDENTITY_ORGID
WIOTP_IDENTITY_TYPEID
WIOTP_IDENTITY_DEVICEID
WIOTP_AUTH_TOKEN
WIOTP_OPTIONS_DOMAIN
WIOTP_OPTIONS_LOGLEVEL
WIOTP_OPTIONS_MQTT_PORT
WIOTP_OPTIONS_MQTT_TRANSPORT
WIOTP_OPTIONS_MQTT_CAFILE
WIOTP_OPTIONS_MQTT_CLEANSTART
WIOTP_OPTIONS_MQTT_SESSIONEXPIRY
WIOTP_OPTIONS_MQTT_KEEPALIVE
The wiotp.sdk.gateway
package contains the following:
Two client implementations:
-wiotp.sdk.gateway.GatewayClient
wiotp.sdk.gateway.ManagedGatewayClient
Support classes for working with the data model:
-wiotp.sdk.gateway.Command
wiotp.sdk.gateway.Notification
wiotp.sdk.gateway.DeviceInfo
wiotp.sdk.gateway.DeviceFirmware
Support methods for handling device configuration:
-wiotp.sdk.gateway.parseConfigFile
wiotp.sdk.gateway.parseEnvVars
Gateway configuration is passed to the client via the config
parameter when you create the client instance. See the configure gateways section for full details of all available options, and the built-in support for YAML file and environment variable sourced configuration.
myConfig = {
- "identity": {
- "orgId": "org1id",
- "typeId": "raspberry-pi-3"
- "deviceId": "00ef08ac05"
- }.
- "auth" {
- "token": "Ab$76s)asj8_s5"
- }
-}
-client = wiotp.sdk.gateway.GatewayClient(config=myConfig)
-
-
-connect()
& disconnect()
methods are used to manage the MQTT connection to IBM Watson IoT Platform that allows the gateway to
-handle commands and publish events.
Events are the mechanism by which devices & gateway publish data to the Watson IoT Platform. The gateway -controls the content of the event and assigns a name for each event that it sends.
-Events can be published with any of the three quality of service (QoS) levels that are defined -by the MQTT protocol. By default, events are published with a QoS level of 0.
-publishEvent()
takes up to 5 arguments and submits an event from the gateway itself:
eventId
Name of this eventmsgFormat
Format of the data for this eventdata
Data for this eventqos
MQTT quality of service level to use (0
, 1
, or 2
)onPublish
A function that will be called when receipt of the publication is confirmed.publishDeviceEvent()
takes up to 7 arguments and submits an event from a device connected to the gateway, rather than the gateway itself:
typeId
Type ID of the device connected to this gateway that the event belongs todeviceId
Device ID of the device connected to this gateway that the event belongs toeventId
Name of this eventmsgFormat
Format of the data for this eventdata
Data for this eventqos
MQTT quality of service level to use (0
, 1
, or 2
)onPublish
A function that will be called when receipt of the publication is confirmed.Callback and QoS
-The use of the optional onPublish
function has different implications depending
-on the level of qos used to publish the event:
def eventPublishCallback():
- print("Device Publish Event done!!!")
-
-client.publishEvent(eventId="status", msgFormat="json", data=myData, qos=0, onPublish=eventPublishCallback)
-
-
-Unlike devices, When the gateway client connects, it does not automatically subscribes to any commands. To -process specific commands, you need to explicitly subscribe as well as registering a command callback method.
-The messages are returned as an instance of the Command
class with the following attributes:
typeId
: Identifies the typeId of the device the command is directed ateventId
: Identifies the deviceId of the device the command is directed atcommandId
: Identifies the commandIdformat
: Format that the command was encoded in, for example json
data
: Data for the payload converted to a Python dict by an impleentation of MessageCodec
timestamp
: Date and time that the event was recieved (as datetime.datetime
object)If a command is recieved in an unknown format or if the gateway does not recognize the format, the gateway
-library raises wiotp.sdk.MissingMessageDecoderException
.
import wiotp.sdk.gateway
-
-def myCommandCallback(cmd):
- print("Command received for %s:%s: %s" % (cmd.typeId, cmd.deviceId, cmd.data))
-
-# Configure
-myConfig = wiotp.sdk.gateway.parseConfigFile("gateway.yaml")
-client = wiotp.sdk.gateway.GatewayClient(config=myConfig, logHandlers=None)
-client.commandCallback = myCommandCallback
-
-# Connect
-client.connect()
-
-# Send data on behalf of the gateway itself
-myData={'name' : 'foo', 'cpu' : 60, 'mem' : 50}
-client.publishEvent(eventId="status", msgFormat="json", data=myData, qos=0, onPublish=None)
-
-# Send data on behalf of a device connected to this gateway
-aDeviceData={'name' : 'foo', 'cpu' : 60, 'mem' : 50}
-client.publishEvent(eventId="status", msgFormat="json", data=aDeviceData, qos=0, onPublish=None)
-
-# Disconnect
-client.disconnect()
-
-
- Sorry, this documentation is still a work in progress.
- -Python module for interacting with the IBM Watson IoT Platform.
-Note
-Support for MQTT with TLS requires at least Python v2.7.9 or v3.4, and openssl v1.0.1
-Documentation for this SDK can be broken down into 4 distinct areas:
-Additional documentation for the library is available in IBM Cloud, but it's a "little" out of date in places:
- -Install the latest version of the library with pip
-# pip install wiotp-sdk
-
-
-Uninstalling the module is simple.
-# pip uninstall wiotp-sdk
-
-
- MQTT is the primary protocol that devices and applications use to communicate with IBM Watson IoT Platform. MQTT is a publish and subscribe messaging transport protocol that is designed for the efficient exchange of real-time data between sensor and mobile devices. MQTT support is available over TCP/IP and websockets, in your configuration file specify options.mqtt.transport=tcp
or options.mqtt.transport=websockets
Watson IoT Platform provides limited support for the retained messages feature of MQTT messaging. If the retained message flag is set to true in an MQTT message that is sent from a device, gateway, or application to Watson IoT Platform, the message is handled as an unretained message.
-The MQTT protocol provides three qualities of service for delivering messages between clients and servers: "at most once", "at least once", and "exactly once". While you can send events and commands by using any quality of service level, you must carefully consider what the right service level is for your needs. Quality of service level '2' is not always a better option than level '0'.
-The "at most once" quality of service level (QoS0) is the fastest mode of transfer and is sometimes called "fire and forget". The message is delivered at most once, or it might not be delivered at all. Delivery across the network is not acknowledged, and the message is not stored. The message might be lost if the client is disconnected, or if the server fails.
-The MQTT protocol does not require servers to forward publications at quality of service level '0' to a client. If the client is disconnected at the time the server receives the publication, the publication might be discarded, depending on the server implementation.
-Tip
-When sending real-time data on an interval, use quality of service level 0. If a single message goes missing, it does not really matter because another message that contains newer data will be sent shortly afterward.
-In this scenario, the extra cost of using a higher quality of service does not result in any tangible benefit.
-With quality of service level 1 (QoS1), the message is always delivered at least once. If a failure occurs before an acknowledgment is received by the sender, a message can be delivered multiple times. The message must be stored locally at the sender until the sender receives confirmation that the message was published by the receiver. The message is stored in case the message must be sent again.
-The "exactly once" quality of service level 2 (QoS2) is the safest, but slowest mode of transfer. The message is always delivered exactly once and must also be stored locally at the sender, until the sender receives confirmation that the message was published by the receiver. The message is stored in case the message must be sent again. With quality of service level 2, a more sophisticated handshaking and acknowledgment sequence is used than for level 1 to ensure that messages are not duplicated.
-Tip
-When sending commands, if you want confirmation that only the specified command will be actioned, and that it will be actioned once only, use the quality of service level 2.
-This is an example of when the additional overheads of level 2 can be advantageous over other levels.
-Each subscription from either a device or application is allocated a buffer of 5000 messages. The buffer allows for any application or device to fall behind the live data it is processing, and to also build up a backlog of up to 5000 pending messages for each subscription it has made. When the buffer is full, the oldest messages are discarded when a new message is received.
-Use the MQTT clean session option to access the subscription buffer. When clean session is set to false, the subscriber receives messages from the buffer. When clean session is set to true, the buffer is reset.
-Warning
-The subscription buffer limit applies regardless of the quality of service setting that is used. It is possible that a message that is sent at level 1 or 2 might not be delivered to an application that is unable to keep up with the messages rate for the subscription that it has made.
-IBM Watson IoT Platform supports sending and receiving messages in any format. MQTT is data-agnostic so it's possible to send images, text in any encoding, encrypted data, or raw data in binary format.
-The maximum payload size on Watson IoT Platform is 131072 bytes.
-Warning
-Messages with a payload that is greater than the limit are rejected. The connecting client is also disconnected, and a message appears in the diagnostic logs, as outlined in the following device message example: Closed connection from x.x.x.x. The message size is too large for this endpoint.
The MQTT keep alive interval, which is measured in seconds, defines the maximum time that can pass without communication between the client and broker.
-The MQTT client must ensure that, in the absence of any other communication with the broker, a PINGREQ
packet is sent. The keep alive interval allows both the client and the broker to detect that the network failed, resulting in a broken connection, without needing to wait for the TCP/IP timeout period to be reached.
Warning
-If your application utilizes a shared subscription, the keep alive interval value can be set only to between 1 and 3600 seconds.
-If a value of 0 or a value that is greater than 3600 is requested, Watson IoT Platform sets the keep alive interval to 3600 seconds.
-