Python API Reference for Oracle Internet of Things Cloud Service Client Software Library.

The Python Client Library API for Oracle Internet of Things (IoT) Cloud Service allows the developer to create applications that can interface to the Oracle IoT Cloud Service.

API Reference

The Reference documentation provides API-level documentation.

Overview

Device Client Library User Guide

The Python device client libraries simplify working with the Oracle IoT Cloud Service. These client libraries are a higher-level abstraction of messages and REST APIs. Device clients are designed to make it easy to expose device functionality to the Oracle IoT Cloud Service.

The true state of a device is within the device itself (whether the light is on or off). A “virtual” device object is contained within the cloud. The device clients can represent the last-known state of that device, and allow enterprise clients to send commands and set attributes of the device model (e.g., “turn the light off”).

Configuration

The client must have a runtime configuration in order to communicate with the cloud service. This runtime configuration includes the IoT Cloud Service host, the identifier of the device the client represents, and the shared secret of the device.

The configuration is created by registering a device with the Oracle IoT Cloud Service and downloading the provisioning file. The downloaded file is encrypted and used when running the client application.

The library defines a set of tunable parameters that affects the library behavior dealing with messages, network, and other library features. See config

Device Models

A device model is a predefined specification of the attributes, actions, formats and resources of a device that can be accessed by the client library. The attributes of a device model represent the basic variables that the device supports, such as temperature, humidity, flow rate, valve position, and so forth. Actions are similar to attributes, but are invocable state rather than readable state. For example, “reset”. An action can have one or zero arguments. The formats of a device model define the structure of a message payload. A format describes the message attributes by specifying the attribute names, types, optionality, and default values. The resources of a device model define additional, ad-hoc REST resources which provide richer REST support than what is possible with attributes and formats.

The client library has explicit API for obtaining a device model. See iotcs.client.Client.getDeviceModel()

By default the API above always uses the network to retrieve a given device model. This behavior can be changed to cache device models to a local file store by setting a property in the configuration module config config.ini file. Set the value of the property device_model_store in the section device_model, to an existing, local file directory. Each model will be stored in its own file using a pattern of dm-{URL encoded device model URN}.json, for example dm-urn%3Acom%3Aoracle%3Aiot%3Adevice%3Ahumidity_sensor.json. Statically provisioning a local file directory with device models avoids network traffic; however, the local model must be identical to the model on the server. Dynamic provisioning, in which the client library retrieves the model from the server, ensures correctness of the model, but at the expense of network traffic. Dynamic provisioning is recommended.

Beware that when using a local store of device models, a local model will not get updated if the model on the server is changed.

Device Policies

A policy is a set of rules and constraints that can be associated with a device client (see below) to control its basic data transformation and transfer behavior. Device policies are automatically loaded, if they have been configured for the device, and there is no direct API in the library for manipulating the policies. The policies are applied when a value is offered to a VirtualDevice. See VirtualDevice.offer()

Device Client

There is an API for getting and setting values on a physical device, represented by a VirtualDevice. A device client application creates a DirectlyConnectedDevice or a GatewayDevice. The application uses the Oracle IoT Client API to create instances of a “virtual device” VirtualDevice which provides access to the device, to get and set values. Further detail is available in the Client documentation.

Prerequisites

  • Register your device application with the Cloud Service.
  • Download the provisioning file after registration. Record the passphrase used to encrypt the provisioning file for later use.
  • Optionally provision the device model.

Loggers

The available loggers

  • iotcs.activation.ActivationManager
  • iotcs.client.attributes.VirtualDeviceAttributeImpl
  • iotcs.client.DeviceModelFactory
  • iotcs.client.device.AlertMessageImpl
  • iotcs.client.device.DataImpl
  • iotcs.client.device.DirectlyConnectedDevice
  • iotcs.client.device.StorageObjectImpl._ProgressCallback
  • iotcs.client.device.VirtualDeviceImpl.ErrorCallbackBridge
  • iotcs.client.device.VirtualDeviceImpl.errorNotifyThread
  • iotcs.device.impl.VirtualDeviceImpl
  • iotcs.device.impl.MessageDispatcherImpl
  • iotcs.device.impl.MessageDispatcherImpl.Dispatcher
  • iotcs.device.impl.MessageDispatcherImpl.Receiver
  • iotcs.device.impl.MessageDispatcherImpl.Transmitter
  • iotcs.device.impl.SendReceiveImpl
  • iotcs.diagnostics.Diagnostics
  • iotcs.diagnostics.TestConnectivity.TestConnectivityThread
  • iotcs.http.HttpSecureConnection
  • iotcs.http.HttpSendReceiveImpl
  • iotcs.message.DataMessage
  • iotcs.messaging.client.storage.StorageConnectionBase
  • iotcs.messaging.client.device.persistence
  • iotcs.messaging.client.device.persistence.BatchByPersistence
  • iotcs.messaging.client.device.persistence.MessagePersistence
  • iotcs.messaging.client.device.DirectlyConnectedDeviceImpl
  • iotcs.messaging.client.device.util.RequestDispatcher
  • iotcs.messaging.client.device.DeviceAnalogImpl
  • iotcs.messaging.client.device.MessagingPolicy
  • iotcs.messaging.client.device.ScheduledPolicyData
  • iotcs.messaging.client.device.TimedPolicyThread
  • iotcs.messaging.client.device.storage.StorageDispatcherImpl.ContentTransmitter
  • iotcs.shared.RequestBuffer

Directly Connected Device Client Quick Start

The following steps must be performed by a device client application to allow a device to be monitored and controlled.

  1. Initialize a device client DirectlyConnectedDevice, ensure that the configurationFilePath is not pointing to a read only file as this might be modified during activation

    from iotcs.client.DirectlyConnectedDevice import DirectylyConnectedDevice
    ...
    dcd = DirectlyConnectedDevice(configurationFilePath, passphrase)
    
  2. Activate the device, where deviceModelUrn is the urn identifying the device model, for example urn:com:oracle:iot:device:humidity_sensor.

    if not dcd.isActivated():
        dcd.activate([deviceModelUrn])
    
  3. Obtain the device model DeviceModel where deviceModelUrn is the urn identifying the device model, for example urn:com:oracle:iot:device:humidity_sensor.

    # The type returned by ``getDeviceModel``
    from iotcs.client.DeviceModel import DeviceModel
    ...
    deviceModel = dcd.getDeviceModel(deviceModelUrn)
    
  4. Create a virtual device VirtualDevice implementing the device model

    # The type returned by "createVirtualDevice"
    from iotcs.client.device.VirtualDevice import VirtualDevice
    ...
    virtualDevice = dcd.createVirtualDevice(dcd.getEndpointId(), deviceModel)
    
  5. Listen for control operations using callbacks ChangeCallback, Callable, from an enterprise client. Also implement an ErrorCallback

    # The VirtualDevice type
    from iotcs.client.VirtualDevice import VirtualDevice
    
    # Implement a ChangeCallback handler
    class OnChangeCallback(VirtualDevice.ChangeCallback):
        __slots__ = (
            'realdevice'
        )
        __init__(self, realdevice):
            super(OnChangeCallback, self).__init__()
            #
            # provide any custom initialization or context to
            # reference when the "onChange" method is called
            #
            self.realdevice = realdevice
    
        def onChange(self, event):
            # Get the virtual device if needed
            virtualdevice = event.getVirtualDevice()
            namedvalue = event.getNamedValue()
            while namedValue is not None:
                attribute = namedvalue.getName()
                value = namedvalue.getValue()
                #
                # update the device with the new value
                #
                # handle multiple attribute updates
                self.realdevice[attribute] = value
                namedvalue = namedValue.next()
    
    # set the callback for any changes from the server to this virtualobject
    virtualdevice.setOnChange(OnChangeCallback())
    
    # Implement a Callable handler for a "reset" action
    class Reset(VirtualDevice.Callable):
        __slots__ = (
            'realdevice'
        )
        __init__(self, realdevice):
            super(Rest, self).__init__()
            #
            # provide any custom initialization or context to
            # reference when the "call"  method is called
            #
            self.realdevice = realdevice
    
        def call(self):
            #
            # perform the reset action
            #
            self.realdevice.reset()
    
    # Set a handler for a callable action. For example, the device model
    # might have a 'reset' action. In this example, the action does not
    # require any input data.
    virtualdevice.setCallable("reset", Reset(theactualdevice))
    
    # Implement a Callable handler for a "calibrate" action
    class Calibrate(VirtualDevice.Callable):
        __slots__ = (
            'realdevice'
        )
        __init__(self, realdevice):
            super(Calibrate, self).__init__()
            #
            # provide any custom initialization or context to
            # reference when the "call"  method is called
            #
            self.realdevice = realdevice
    
        def call(self, zerooffset):
            #
            # calibrate the device to 'zerooffset'
            #
            self.realdevice.calibrate(zerooffset)
    
    # Set a handler for a callable action. For example, the device model
    # might have a 'calibrate' action. In this example, the action
    # requires numeric input data.
    virtualdevice.setCallable("calibrate", Calibrate(theactualdevice))
    
    # Implement an ErrorCallback handler
    class OnErrorCallback(VirtualDevice.ErrorCallback):
        __slots__ = (
            'realdevice'
        )
        __init__(self, realdevice):
            super(OnErrorCallback, self).__init__()
            #
            # provide any custom initialization or context to
            # reference when the "call"  method is called
            #
            self.realdevice = realdevice
    
        def onError(self, errorevent):
            # log the error
            logging.getLogger("onError").warn(errorevent.getMessage())
            # Get the virtual device if needed
            virtualdevice = event.getVirtualDevice()
            namedvalue = event.getNamedValue()
            while namedValue is not None:
                attribute = namedvalue.getName()
                value = namedvalue.getValue()
                #
                # do anything to the real device if necessary
                #
                # handle multiple attribute updates
                namedvalue = namedValue.next()
    
            self.realdevice.reboot()
    
    #  set a callback to receive errors from sending data to the server
    virtualdevice.setOnError(OnErrorCallback())
    
  6. Update the virtual device when the data on the physical device changes

    # set a value for a particular attribute in this virtual device
    virtualdevice.set("attribute", newValue);
    
    // or set multiple attributes in a batch operation
    virtualdevice.update()
        .set("attribute1", value1)
        .set("attribute2", value2)
        .finish()
    
  7. Dispose of the device client

    dcd.close()
    

Gateway Device Client Quick Start

The following steps must be performed by a gateway device client application to allow devices to be monitored and controlled.

  1. Initialize a gateway device client GatewayDevice, ensure that the configurationFilePath is not pointing to a read only file as this might be modified during activation

    from iotcs.client.device.GatewayDevice import GatewayDevice
    ...
    gw = GatewayDevice(configurationFilePath, passphrase)
    
  2. Activate the gateway device

    if not gw.isActivated():
        gw.activate()
    
  3. Obtain the device model DeviceModel where deviceModelUrn is the urn identifying the device model, for example urn:com:oracle:iot:device:humidity_sensor.

    # the type returned by "getDeviceModel"
    from iotcs.client.DeviceModel import DeviceModel
    ...
    deviceModel = gw.getDeviceModel(deviceModelUrn):
    
  4. Register indirectly-connected devices

    # create meta-data with the indirectly-connected device's
    # manufacturer, model, and serial number
    metaData = dict()
    metaData[GatewayDevice.MANUFACTURER] =, "A Manufacturer"
    metaData[GatewayDevice.MODEL_NUMBER] = "MN-xxxx-xxxx"
    metaData[GatewayDevice.SERIAL_NUMBER] = "SN-yyyyyyyy"
    # add any vendor-specific meta-data to the metaData dictionary
    # register it
    deviceId = gw.registerDevice(hardwareId, metaData, deviceModelUrn)
    
  5. Create virtual devices VirtualDevice implementing the device model of the indirectly connected devices.

    # the type returned by "createVirtualDevice"
    from iotcs.client.device.VirtualDevice import VirtualDevice
    ...
    virtualdevice = gw.createVirtualDevice(deviceId, deviceModel)
    
  6. Listen for control operations using callbacks ChangeCallback, Callable, from an enterprise client. Also implement an ErrorCallback

    # the VirtualDevice type
    from iotcs.client.VirtualDevice import VirtualDevice
    ...
    
    # Implement a ChangeCallback handler
    class OnChangeCallback(VirtualDevice.ChangeCallback):
        __slots__ = (
            'realdevice'
        )
        __init__(self, realdevice):
            super(OnChangeCallback, self).__init__()
            #
            # provide any custom initialization or context to
            # reference when the "onChange" method is called
            #
            self.realdevice = realdevice
    
        def onChange(self, event):
            # Get the virtual device if needed
            virtualdevice = event.getVirtualDevice()
            namedvalue = event.getNamedValue()
            while namedValue is not None:
                attribute = namedvalue.getName()
                value = namedvalue.getValue()
                #
                # update the device with the new value
                #
                # handle multiple attribute updates
                self.realdevice[attribute] = value
                namedvalue = namedValue.next()
    
    # set the callback for any changes from the server to this virtualobject
    virtualdevice.setOnChange(OnChangeCallback())
    
    # Implement a Callable handler for a "reset" action
    class Reset(VirtualDevice.Callable):
        __slots__ = (
            'realdevice'
        )
        __init__(self, realdevice):
            super(Rest, self).__init__()
            #
            # provide any custom initialization or context to
            # reference when the "call"  method is called
            #
            self.realdevice = realdevice
    
        def call(self):
            #
            # perform the reset action
            #
            self.realdevice.reset()
    
    # Set a handler for a callable action. For example, the device model
    # might have a 'reset' action. In this example, the action does not
    # require any input data.
    virtualdevice.setCallable("reset", Reset(theactualdevice))
    
    # Implement a Callable handler for "calibrate" action
    class Calibrate(VirtualDevice.Callable):
        __slots__ = (
            'realdevice'
        )
        __init__(self, realdevice):
            super(Calibrate, self).__init__()
            #
            # provide any custom initialization or context to
            # reference when the "call"  method is called
            #
            self.realdevice = realdevice
    
        def call(self, zerooffset):
            #
            # calibrate the device to 'zerooffset'
            #
            self.realdevice.calibrate(zerooffset)
    
    # Set a handler for a callable action. For example, the device model
    # might have a 'calibrate' action. In this example, the action
    # requires numeric input data.
    virtualdevice.setCallable("calibrate", Calibrate(theactualdevice))
    
    # Implement an ErrorCallback handler for
    class OnErrorCallback(VirtualDevice.ErrorCallback):
        __slots__ = (
            'realdevice'
        )
        __init__(self, realdevice):
            super(OnErrorCallback, self).__init__()
            #
            # provide any custom initialization or context to
            # reference when the "call"  method is called
            #
            self.realdevice = realdevice
    
        def onError(self, errorevent):
            # log the error
            logging.getLogger("onError").warn(errorevent.getMessage())
            # Get the virtual device if needed
            virtualdevice = event.getVirtualDevice()
            namedvalue = event.getNamedValue()
            while namedValue is not None:
                attribute = namedvalue.getName()
                value = namedvalue.getValue()
                #
                # do anything to the real device if necessary
                #
                # handle multiple attribute updates
                namedvalue = namedValue.next()
    
            self.realdevice.reboot()
    
    #  set a callback to receive errors from sending data to the server
    virtualdevice.setOnError(OnErrorCallback())
    
  7. Update the virtual device when the data on the physical device changes

    # set a value for a particular attribute in this virtual device
    virtualDevice.set("attribute", newValue);
    
    # or set multiple attributes in a batch operation
    virtualDevice.update()
        .set("attribute1", value1)
        .set("attribute2", value2)
        .finish();
    
  8. Dispose of the virtual device

    virtualdevice.close()
    
  9. Dispose of the gateway device client

    gw.close()
    

Advanced Library Usage

Storage Cloud Quick Start

This shows how to use a virtual device attribute to upload content to, or download content from, the Oracle Storage Cloud Service.

To upload or download content from a virtual device, there must be an attribute, field, or action in the device model with type URI. For the Oracle Storage Cloud Service, the URI type corresponds to the class oracle.iot.client.StorageObject. When the attribute, field, or action of type URI is set to a StorageObject instance, the content is automatically synchronized with the Storage Cloud Service.

Uploading content

An instance of oracle.iot.client.StorageObject is first needed to upload a file from a device client or from an enterprise client. The StorageObject is created using the createStorageObject API in oracle.iot.client.Client, which is the base class for oracle.iot.client.enterprise.EnterpriseClient, oracle.iot.client.device.DirectlyConnectedDevice, and oracle.iot.client.device.GatewayDevice. The StorageObject names the object in storage, and provides the mime-type of the content. To set the input file, the oracle.iot.client.StorageObject API setInputPath(String) is used.

This example shows the typical use case from a DirectlyConnectedDevice. But the code for a GatewayDevice or EnterpriseClient is the same.

storageObject = directlyConnectedDevice.createStorageObject("mediaSnapshot.jpg", "image/jpg")
storageObject.setInputPath("../images/mediaSnapshot.jpg")
virtualDevice.set("snapshot", storageObject)

A StorageObject may also be set on an Alert field, or as an Action parameter, provided the type in the device model is URI.

Downloading content

In the virtualization API, the client is notified through an onChange event, onAlert event, or a call callback for an action. The value in the event is a StorageObject. To download the content, the output path is set on the StorageObject, and the content is synchronized by calling the StorageObject sync() API.

This example shows the typical use case from an onChange event. The code for an onAlert or for an action callback is much the same.

class OnChangeCallback(VirtualDevice.ChangeCallback):
    def onChange(self, event):
        namedValue = event.getNamedValue()
        storageObject = namedValue.getValue()
        # only download if image is less than 4M
        if storageObject.getLength() < 4 * 1024L * 1024L:
            try:
                storageObject.setOutputPath("../downloads/"+storageObject.getName())
                storageObject.sync()
            except Exception as e:
                print("cannot create file for download")

virtualDevice.setOnChange("snapshot", OnChangeCallback())

Checking synchronization status

A StorageObject is a reference to some content in the Storage Cloud. The content can be in sync with the storage cloud, not in sync with the storage cloud, or in process of being sync’d with the storage cloud. The synchronization happens on a separate thread, but can be monitored by setting a SyncCallback with setOnSync.

For the upload case, set the SyncCallback on the storage object before setting the virtual device attribute. For the download case, set the SyncCallback on the storage object from within the onChange callback.

class SyncCallback(StorageObject.SyncCallback):
    def onSync(self, event):
        storageObject = event.getStorageObject()
        if storageObject.getSyncStatus() == StorageObject.SyncStatus.IN_SYNC:
             # image was uploaded and can be deleted
        elif storageObject.getSyncStatus() == StorageObject.SyncStatus.SYNC_FAILED:
             # image was not uploaded, take action!

License

iotcs is made available under the MIT License. For more details, see LICENSE