Skip to content

Unified Device Configuration

Günter Obiltschnig edited this page Aug 16, 2020 · 7 revisions

The new macchina.io device tree subsystem will also provide a unified way of configuring devices. The current implementation of devices is such that devices are implemented in bundles. When a device bundle is started, the BundleActivator reads the device configuration for the bundle's devices from the global configuration and attempts to create the respective device. If creation of a device fails (e.g., because the device is not connected), the device won't be available until the macchina.io application, or at least the specific bundle, is restarted.

This article proposes an alternative device configuration mechanism that allows for more dynamic, and unified configurability of devices. Ideally, a whole device tree shall be described by a single configuration source.

The configuration source can be a JSON document or a SQLite database.

Device Nodes

Both the device tree and individual device properties are defined via a tree structure consisting of device nodes.

Example: the following device structure:

/
  Battery/
    Voltage
    Temperature
    ChargeLevel

would be represented by the following JSON configuration, assuming that all sensor devices are Modbus-RTU based.

{
  "rootDevice":
  {
    "device": "io.macchina.composite",
    "fragments": 
    [
      {
        "device": "io.macchina.composite",
        "name": "Battery",
        "fragments":
        [
          {
            "device": "io.macchina.modbus.sensor",
            "name": "Temperature",
            "configuration":
            {
              "master": "io.macchina.modbus.rtu.bus1",
              "sampleInterval": 5000,
              "slaveId": 1,
              "properties":
              {
                "value":
                {
                  "registerAddress": "0x19",
                  "type": "float32",
                  "physicalQuantity": "temperature",
                  "physicalUnit": "Cel"
                }
              }
            }
          },
          {
            "device": "io.macchina.modbus.sensor",
            "name": "Voltage",
            "configuration":
            {
              "master": "io.macchina.modbus.rtu.bus1",
              "sampleInterval": 1000,
              "slaveId": 2,
              "properties":
              {
                "value":
                {
                  "registerAddress": "0x2300",
                  "type": "ScaledInt16",
                  "slope": 0.01,
                  "intercept": 0.0,
                  "physicalQuantity": "voltage",
                  "physicalUnit": "V"
                }
              }
            }
          },
          {
            "device": "io.macchina.modbus.sensor",
            "name": "ChargeLevel",
            "configuration":
            {
              "master": "io.macchina.modbus.rtu.bus1",
              "sampleInterval": 10000,
              "slaveId": 2,
              "properties":
              {
                "value":
                {
                  "registerAddress": "0x2400",
                  "type": "UInt16"
                }
              }
            }
          }
        ]
      }
    ]
  }
}

Creating the Device Tree

The device tree is created with the help of factory services. For every device type, a factory service that can create and configure instances of that specific type must be registered with the service registry.

Device Factory Interface

The interface of the DeviceFactory service looks as follows:

class DeviceFactoryService: public Poco::OSP::Service
{
public:
    virtual Poco::OSP::ServiceRef::Ptr createDevice(const std::string& name, Poco::Util::AbstractConfiguration::Ptr pDeviceConfig) = 0;
        /// Creates and registers a device service based on the given configuration.
        ///
        /// The given device configuration is the device-specific `configuration` subtree of the entire device tree
        /// configuration tree. The contents of the device configuration are specific to each device class.
};

For each device type that supports the device tree, an instance of a subclass of the DeviceFactoryService class must be registered with the service registry. The service name should be equal to the device type identifier, e.g. "io.macchina.modbus.sensor" for generic Modbus sensor devices.

When creating the device tree, for each device node in the device tree configuration, the corresponding factory service is looked up in the service registry, using the value of the "device" configuration property. The createDevice() method is then called, and passed the device name (value of the "name" property) and configuration (value of the "configuration" property).

The factory is responsible for setting up the device service object, assigning a unique service name, and registering the device with the service registry. The resulting Poco::OSP::ServiceRef object is then returned. The properties of the returned ServiceRef will be amended by the device tree, setting the io.macchina.composite property to the parent composite device's service name.