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()