- iDigi Dia Home
- User
  - Getting Started
  - Dia Concepts
  - Devices
  - Loggers
  - Presentations
  - Services
  - Tracing
- Developer
  - Overview
  - Drivers
  - Loggers
  - Presentations
  - Services
  - Integrating Application
  - Tracing Manager
  - Debugging
  - Specific Considerations
  - API Reference

- Email
- Phone
- Contact us:

### Navigation

- index
- next |
- previous |
- iDigi Dia |
- Developer Documentation »

# Device Drivers

## I. Introduction to Dia Device Drivers

A device driver in the iDigi Dia is Python code which abstracts a real physical device (such as a thermometer or a light sensor) or some control logic (such as a virtual thermostat) into a set of channel properties. Channel properties are exported to the channel database in order to be consumed by other system services such as other device drivers, the logging sub-system, or various presentation instances. Channels are named in the channel database as “driver_instance.channel_name”.

To work from an example, a GPS device driver in the Dia may interface to a real, physical GPS device by opening a serial port. The device driver’s primary responsibility would be to read GPS NMEA strings from the serial port, parse them into Dia Sample objects, and place these samples into appropriate channel properties. Applicable channel properties for this device might be named “latitude”, “longitude”, “num_satellites”, etc. and accessible from the channel database using an instance of this driver “gps0” as “gps0.latitude”, “gps0.longitude”, “gps0.num_satellites”, etc. A device driver may also be a purely virtual device which imports channels from other devices (perhaps via a configurable setting) and makes decisions on that data. An example of a virtual device is a “geofencing” driver.

Geofencing defines a region on a map in which an asset must stay within. In its simplest form, a geofence has two states: inside the region or outside the region. A geofencing virtual device driver could be defined to accept settings to define a geofencing region as a list of coordinates and a GPS source device’s channels. The geofencing driver would then produce an Boolean output property which would indicate if the location being reported by the GPS channels are inside or outside the geofence. As this driver is only operating on a stream of samples from another device driver, it is considered a “virtual” device driver. Such virtual device drivers often house portions of control logic for a complex application.

All device drivers in the Dia must meet the following requirements:

> - They must inherit from `DeviceBase`.

> - They must have zero or more settings.

> - They must have zero or more device properties to create zero or more channels in the Channel Database.

> - They must implement the following `DeviceBase` interface methods:

>   > 1. apply_settings(), called when new settings are applied.
>   > 2. start(), called when the device driver is started.
>   > 3. stop(), called when the device driver is stopped .

We will touch upon two device driver architectures in this document: a regular device driver inheriting from `DeviceBase` as well as a device driver using the Dia XBee device driver architecture.

## II. Standard Device Drivers

### Where are the Existing Device Drivers Located?

Existing device drivers in the Dia are stored in the “src/devices” directory. This directory contains a few sub-directories. Some of these sub-directories (such as “gps”) contain files for one specific driver. Other directories serve other, more general organizational purposes:

> - The “vendor” directory contains a place for vendors to create their own library of Dia device drivers.
> - The “xbee” directory contains the XBee device driver stack and Digi-specific device drivers. This will be covered in detail (see section III XBee Device Drivers).

### The Template Device Driver Illustrated

The template device driver is located “src/drivers/template_device.py”. The template device driver serves as an example and a starting place for those who wish to learn how to implement their own device drivers in the iDigi Dia.

The template device is a virtual device driver. It does not connect to any hardware peripherals. The driver is comprised of the following two features:

> - A counter which updates at a configurable rate.
> - A pair of two settable channel proprieties which when set are added together and output to a third, gettable channel property.

Below a brief description of the template driver is the imports section The imports section includes other Python modules into the template driver’s scope in order to include modular pieces of functionality. The bare minimum set of includes for a device driver are the following:

    from devices.device_base import DeviceBase
    from settings.settings_base import SettingsBase, Setting
    from channels.channel_source_device_property import *

All device drivers are implemented by defining a class which is derived from `DeviceBase`. A `DeviceBase` class will contain `Setting` objects (accessed via methods defined in `SettingsBase`) and a set of channel properties to be exposed to the `Channel Database`. These channel properties are of the type `ChannelSourceDeviceProperty`.

The template device driver implements a counter which must asynchronously update. This requires threading–a lightweight parallel task–and the ability to delay this task with the standard time module’s sleep() function. These libraries are also imported in the subsequent two lines of the file:

    import threading
    import time

For more information on standard Python library functions, please see:

> http://docs.python.org/library/

Below the imports section of the template driver file our device driver class is created. It derives from both `DeviceBase` and the threading module’s Thread class:

    class TemplateDevice(DeviceBase, threading.Thread):

When an instance of this device driver is created from the `Device Driver Manager`, execution will begin at the __init__() method. In the __init__() method, the following tasks are completed:

> 1. Each device should be assigned a name that is passed into the __init__() method upon instantiation. The first line of code following the __init__() is self.__name = name. The name is a uniquely identifying tag that will allow you to distinguish between devices. It may be assigned automatically in a system where XBee devices are auto-enumerated.
> 2. The template device assigns self.__core to core_services. The `CoreServices` object will allow the driver to access settings, devices, services, and presentations across the system. Consult the documentation for `CoreServices` for more information.
> 3. The settings “count_init” and “update_rate” are created as Setting objects and are added to a list called settings_list.
> 4. The `ChannelSourceDeviceProperty` objects “counter”, “adder_total”, “counter_reset”, “global_reset”, “adder_reg1”, and “adder_reg2” are created and added to a list called property_list.
> 5. The `DeviceBase` constructor is called and it is handed references to the current object (self), the name of the current instance, core services object, as well as the settings_list and property_list.
> 6. The Thread is initialized by calling threading.Thread’s __init__() method. When __init__() returns, the `Device Driver Manager` will attempt to start the driver. Before a driver is started, its settings must be applied. This occurs in the apply_settings() method.

Settings are stored inside of a special registry managed by the `DeviceBase` object (which inherits this functionality from `SettingsBase`). Settings have two states: pending settings which have not been applied yet and running settings.

The implementation of the template device driver’s apply_settings() method performs the bare minimum of operations in order to import new settings:

> 1. A call to `SettingsBase`.merge_settings() is made to merge the new settings from the pending registry and the existing settings from the running registry into the pending registry.
> 2. A call to `SettingsBase`.verify_settings() is made to parse the settings from the pending registry into three lists: a list of accepted settings, a list of rejected settings, and a list of required settings that were not found.
> 3. `SettingsBase`.commit_settings() is made to commit the accepted settings to the running registry.
> 4. The lists of accepted, rejected, and settings which were not found are returned from the apply_settings() method.

After settings are applied, the driver’s start() method will be called. The template driver’s start method is very simple: it calls the threading.Thread module’s start() function which will in turn start our device driver’s thread by starting execution from the driver’s run() method.

When the driver begins its execution loop in the run() method, the first thing it does is execute the driver’s prop_set_global_reset() function. If the implementation of this function is inspected, it can be seen that it resets the properties “counter”, “adder_total”, “adder_reg1”, and “adder_reg2” by using the property_set() method. The function prop_set_global_reset() is also called as a channel callback. This will be explained momentarily.

Continuing on in the execution of run(), a while loop manages the continuous execution of the thread. At the top of the loop, a flag is checked to see if the thread should exit. If the loop continues, the current counter is read from the “counter” channel with the property_get() method. Next, a new sample is constructed containing the counter value plus one and this new sample object is written back to the channel using property_set(). Finally, the time.sleep() method is called for the “update_rate” settings value of seconds before the loop executes again. This is how the driver’s counter channel is updated. As alluded to earlier, there are other places of execution within the driver. There are several callback methods defined: prop_set_counter_reset(), prop_set_global_reset(), and prop_set_adder().

Each of these callback methods are called in response to a set() method being performed on a channel from another device driver or presentation. The callbacks are registered upon a channel during the definition of the channel (in this driver, this is performed in the __init__() method) by giving a set_cb argument to the constructor of a `ChannelSourceDeviceProperty` object. As these callback methods are very similar, let us only inspect one of them: prop_set_adder().

The prop_set_adder() method is used twice as a callback: once for the “adder_reg1” channel and once for the “adder_reg2” channel. The method takes two arguments: the name of the register to be set and a Sample object containing the floating-point value to be stored in the channel. How can the same callback method be used for two separate channels?

If we refer briefly to the channel definition for “adder_reg1” and “adder_reg2” in the __init__() method of this driver, we will see that the set_cb argument given to the `ChannelSourceDeviceProperty` object is not simply a callback function name but a lambda expression. A lambda expression is an expression that returns a new function (see: http://www.python.org/doc/2.5.2/ref/lambdas.html). In this case, a new function is being defined for the “adder_reg1” and “adder_reg2” channels which will call prop_set_adder() with the correct register name argument respectively.

Referring back to the prop_set_adder() method, its operation is simple. First the new floating point Sample object is set to the correct channel. Next the values for “adder_reg1” and “adder_reg2” are retrieved from each channel. Finally the “adder_total” channel is set using a new Sample object constructed by adding both of these channel values together.

The final method we should consider in this driver is the stop() method. The stop() method in this driver operates by setting a flag to terminate the loop in the run() method. The stop method could potentially be called by the driver manager in response to an user request. If the stop() method returns True, the `Device Driver Manager` assumes that the driver has been stopped, its resources have been released and the driver could be restarted again by calling the driver’s start() method.

### A Summary of How Device Drivers Run

> 1. A device driver is loaded and its __init__() method is called.
> 2. Settings are applied by calling the driver’s apply_settings() method.

> > 1. First a call to `SettingsBase`.merge_settings() should be made to merge new settings from the pending registry and the running settings to the pending registry.
> > 2. Second, a call to `SettingsBase`.verify_settings() will return three items: a list of accepted settings, a list of rejected settings, and a list of required settings that were not found.
> > 3. The `SettingsBase`.apply_settings() method will then take the list of accepted settings and apply them to the device driver’s current running state.
> > 4. `SettingsBase`.commit_settings() should be called with a list of the settings that were successfully accepted and applied. This will adjust the device’s running settings registry.
> > 5. Finally, the `SettingsBase`.apply_settings() method should return the list of settings accepted, rejected, and not_found.

> 3. The device driver’s start() method is called. The driver and all of its channels are now available.
> 4. The device driver’s stop() method may be called. The device driver is expected to stop and the method should return True. Once the method has returned the Device Driver Manager may restart the driver by calling the start() method once again.

## III. XBee Device Drivers

### A. Introductory Information on the Dia XBee Device Driver Stack

An event-based set of abstractions have been provided in order to make creating device drivers which are connected via the XBee module built-in to Digi gateways. This collection of events and abstractions are known as the iDigi Dia XBee Device Driver Stack. All XBee Device Drivers must inherit from XBeeBase.

The XBee Device Driver Stack is broken down into the following components:

> - XBee Device Drivers, instances which represent individual XBee sensors, XBee adapters, or other integrated wireless devices connected to a XBee network.
> - The `XBeeDeviceManager`, an instance of which manages the state and communication for all of the XBee devices managed by a gateway.
> - XBee Configuration Blocks, specialized abstractions used by the XBee Device Drivers and given to the `XBeeDeviceManager` in order to define configuration and sleeping parameters for wireless devices.

These components are organized in the Dia file-system in the following way:

> - “src/devices/xbee/xbee_devices” - Individual XBee Device Driver implementations.
> - “src/devices/xbee/xbee_device_manager” - Files relating to the `XBeeDeviceManager`, including XBee event specifications.
> - “src/devices/xbee/xbee_config_blocks” - XBee Configuration Block abstraction implementations.
> - “src/devices/xbee/common” - Contains files related to identifying XBee device type, address, as well as the drivers to run the XBee display.

### B. Theory of Operation

A driver which uses the XBee Driver stack is essentially no different than any other type of device driver in the Dia system. It contains settings and driver channel properties just as any other driver. It differs only in that it makes use of the XBee Device Driver stack.

An XBee Device Driver has three distinct phases of operation:

> 1. The Initialization Phase, wherein:
>    - The XBee Device Driver instance is registered with a `XBeeDeviceManager` instance.
>    - One or many event specification objects are given to the `XBeeDeviceManager`; these objects inform the Manager of what types of events the driver should called back for and in what manner the driver should be called.
>    - Configuration blocks are given to the XBee Device Manager informing the Manager how the Device Manager should configure the XBee: e.g. which DDO parameters should be set and for what hardware, what sleep parameters should be used, etc.
> 2. The Configuration Phase where the `XBeeDeviceManager`‘s xbee_device_configure() method is called from the device driver informing the `XBeeDeviceManager` that it should begin to schedule and attempt applying configuration blocks to the remote device.
> 3. The Running Phase, which begins after all configuration blocks have been successfully marked as complete. From the Running Phase, callbacks are called from the XBee Device Manager to the XBee Device instance based on the Event Specification objects that were registered during the Initialization Phase or by additional callbacks or schedule events registered during the Running Phase.

### C. Application Programming Interface

XBee Device instances may make use of the following Application Programming Interface (API). These methods belong to an instance of a XBeeDeviceManager object. They are broken down by functional group below:

#### XBee Device Driver Registration:

These functions are called when a XBee Device Driver instance wishes to register or unregister itself from the XBee Device Driver Manager instance.

> - xbee_device_register()
> - xbee_device_unregister()

#### XBee Event Callback Specifications:

Event specifications inform the `XBeeDeviceManager` instance for which events a given XBee Device Driver instance needs to be called back.

The Event specifications are defined in “xbee_device_manager/xbee_device_manager_event_specs.py”.

> - xbee_device_event_spec_add()
> - xbee_device_event_spec_remove()

#### XBee Configuration Blocks:

XBee Configuration blocks define which Digi Device Object (DDO) parameters, sleep parameters, or callbacks to a driver need to be made at configuration time. If a device should become unconfigured, or if a device is hard to configure (e.g. it is sleeping most of the time) the system is designed to resume or retry configurations.

All configuration blocks are defined in the “xbee_config_blocks” directory.

> - xbee_device_config_block_add()
> - xbee_device_config_block_remove()
> - xbee_device_configure()

#### XBee DDO Immediates:

Setting DDO parameters on a device temporarily consumes a DDO resource on the XBee in the gateway. By performing DDO requests through these functions, resources requests can be optimized.

> - xbee_device_ddo_get_param()
> - xbee_device_ddo_set_param()

#### Scheduling Events:

Some drivers need gateway initiated polling (such as sending serial data requests periodically to a remote XBee RS-485 adapter). These functions provide primitives for scheduling such requests.

> - xbee_device_schedule_after()
> - xbee_device_schedule_cancel()

#### Scheduling Data Transmission:

Data is received asynchronously via call backs from event specifications. Transmitting data is also performed asynchronously (i.e. the call will not block).

> - xbee_device_xmit()

### D. Event Specification Types:

Event specifications are registered with a `XBeeDeviceManager` instance by first creating an event specification instance and then calling the `XBeeDeviceManager` instance’s xbee_device_event_spec_add() method.

XBee event specification types are given below.

#### XBeeDeviceManagerRxEventSpec:

Registering one or more of these receive event specification objects will cause the `XBeeDeviceManager` to call back a callback function when a frame is received by the XBee radio which matches a given set of addressing criteria. The callback function will be processed if and only if the device driver has entered the Running Phase (i.e. after it has completed configuration).

The callback is set using the XBeeDeviceManagerRxEventSpec instance’s cb_set() method.

The address matching criteria is set by the instance’s match_spec_set() method. match_spec_set() takes two parameters; a XBee address tuple, and a mask. The address tuple is in the following format:

The mask specifies which portion of this address must be matched. It is given as a tuple of four Boolean values. For example, in order to match only the extended address one gives the tuple:

    (True, False, False, False)

In order to match the extended address, endpoint, profile and cluster id one would specify:

    (True, True, True, True)

Note

If the first argument to this tuple is False, it is possible to receive frames destined for another driver in the system.

#### XBeeDeviceManagerRxConfigEventSpec:

This event specification is identical to the above XBeeDeviceManagerRxEventSpec class except that it will process callbacks if and only if the device is in the Configuration Phase. This functionality is expected only to be used in conjunction with a XBeeConfigBlockConfigCb–the XBee Driver Stack XBee Custom Configuration Block (see section E: Configuration Block Types).

#### XBeeDeviceManagerRunningEventSpec:

Registering this XBee event specification will cause the XBee Driver Stack to call the specified callback when the driver transitions from the Configuration Phase to the Running Phase. This event is useful for performing an action once, just after a device has been initialized and configured. An example where this is useful is to query a device that normally only reports by exception in order to generate initial sample values for a device driver’s channels.

### E. Configuration Block Types:

There are a number of configuration block types which may be added to a XBee device driver abstraction. The configuration blocks are kept in the “src/devices/xbee/xbee_config_blocks” directory.

Once a configuration block is created and configured, it may be added to a `XBeeDeviceManager` instance by calling the `XBeeDeviceManager`‘s xbee_device_config_block_add() method. After all the configuration blocks have been added, the driver must call the xbee_device_configure() method in order to transition the device from the initializing phase to the configuration phase so that the `XBeeDeviceManager` may begin to execute the device’s configuration blocks.

A listing of configuration blocks are given below.

#### `XBeeConfigBlockDDO`:

The `XBeeConfigBlockDDO` block which accepts any number of two letter Digi Device Object mnemonic parameters and their corresponding value. Any DDO mnemonic except sleep parameter mnemonics (e.g. “SP”, “SN”, etc.) may be applied using this configuration block type.

Parameters are added to the block by using the add_parameter() function.

#### `XBeeConfigBlockSleep`:

The `XBeeConfigBlockSleep` configuration block provides a useful front-end interface for calculating and applying sleep parameters to remote XBee devices. All XBee sleep modes may be set using this block typing including pin hibernation, cyclic sleep, cyclic sleep with pin wake, and disabling sleep altogether.

To setup a XBee for sleeping only a call to sleep_cycle_set() is needed. The function takes three arguments:time_awake_ms, time_asleep_ms, and the Boolean enable_pin_wake parameter. time_asleep_ms specifies the number of milliseconds the XBee should sleep between wake cycles, time_awake_ms specifies the number of milliseconds the XBee remains awake after a period of inactivity, and enable_pin_wake informs the XBee if it should honor its Sleep_RQ pin to wake or sleep.

The code in the `XBeeConfigBlockSleep` endeavors to set the DDO parameters to correctly match the sleep times requested as closely as possible. In order to do so, it may factor the requested sleep time into the SP and SN parameters as well as set the SO parameter to enable deep sleeping on the XBee module.

#### `XBeeConfigBlockFinalWrite`:

This configuration block is not intended for use by a device driver writer. It is automatically appended to the end of a chain of configuration blocks. It adds a final Digi Device Object parameter write “WR” command to save all DDO parameters to the XBee’s non-volatile storage.

### F. Getting Started Writing Your Own XBee Device Driver

A good place to begin in writing your own XBee based device driver in the Digi Device Integration Application is by looking a device driver for a device you already have. Two such devices may be the XBee XBIB serial or USB development board driver or the XBee Sensor /L/T(/H) driver located here, respectively:

> - src/devices/xbee/xbee_devices/xbee_xbib.py
> - src/devices/xbee/xbee_devices/xbee_sensor.py

Each driver illustrates what is required in order to register with the `XBeeDeviceManager`, create and register configuration blocks, create and register data events, and the methods necessary for processing registered callbacks, creating sample objects, and pushing these samples into channels.

### Navigation

- index
- next |
- previous |
- iDigi Dia |
- Developer Documentation »