Sensor Things in Coaty Applications

Table of Contents

Sensor Things Types

Sensor Things objects are inspired by the OGC SensorThings API. The Coaty implementation, however, heavily simplifies the entity schema that is specified in the OGC specification:

   ________________________________                      _____________________________________
  |            Sensor              |                    |              Observation            |
  |--------------------------------| 1 +parentObjectId  |-------------------------------------|
  | coreType: CoatyObject          |<-------------------| coreType: CoatyObject               |
  | objectType: OBJECT_TYPE_SENSOR |                    | objectType: OBJECT_TYPE_OBSERVATION |
  |________________________________|                    |_____________________________________|
                            |                                    |
                            | 1 +parentObjectId                  | 0..1 +featureOfInterestId
  0..1 +parentObjectId      |                                    |
   +-------------+          |               +--------------------+
   |             |          |               |                    |
   |     ________|__________v___________    |    ________________v____________________________
   +--> |            Thing              |   |   |              FeatureOfInterest               |
        |-------------------------------|   |   |---------------------------------------------|
        | coreType: CoatyObject         |   |   | coreType: CoatyObject                       |
        | objectType: OBJECT_TYPE_THING |   |   | objectType: OBJECT_TYPE_FEATURE_OF_INTEREST |
        |_______________________________|   |   |_____________________________________________|
              |                             |
              | 0..1 +locationId            |
              |                             |
         _____v___________________          |
        |        Location         |<--------+
        |------------------------ |
        | coreType: Location      |
        |_________________________|

All the displayed objects in the diagram are Coaty objects. Except Location, all other objects are application objects and therefore defined by their unique objectType. Like with all Coaty objects, sensor things objects can be advertised, discovered, queried, and updated.

Thing

According to the OCG, a Thing is:

The OGC SensorThings API follows the ITU-T definition, i.e., with regard to the Internet of Things, a thing is an object of the physical world (physical things) or the information world (virtual things) that is capable of being identified and integrated into communication networks [ITU-T Y.2060].

Link to official Thing specification

For instance, a RapberryPI can be a Thing.

A Thing defines the following properties:

{
    name: string,
    objectType: SensorThingsTypes.OBJECT_TYPE_THING,
    coreType: "CoatyObject",
    objectId: Uuid,
    description: string,
    properties?: { [key: string]: any; },
    locationId?: Uuid
}

The name property is a label for the Thing.

The description property is a short description of the corresponding Thing.

The properties attribute is a Javascript object, supposed to contain properties as key-value pairs. For instance, {'model': '410F', 'power': '7W'} is a valid properties object.

The locationId property is optional and can reference a Coaty Location object that locates the Thing.

Note: Unlike the OGC standard, the Coaty Thing does not have historical locations. In case of a location change, you can generate snapshots and track the changes by listening to them. You can use a specific tag for these snapshots to query them easily.

Location

Coaty reuses the core type Location for SensorThings instead of creating a new type. OGC defines location as:

The Location locates the Thing or the Things it associated with. A Thing’s Location is defined as the last known location of the Thing.

A Thing’s Location may be identical to the Thing’s Observations’ FeatureOfInterest.

Link to official Location specification

Sensor

A Coaty sensor is an instrument that observes and measures a single property and provides these observations of the same type. As a consequence, it is the merged form of OGC’s Sensor, Datastream and ObservedProperty.

OGC defines these three terms as the following:

A Datastream groups a collection of Observations measuring the same ObservedProperty and produced by the same Sensor.

Link to official Datastream specification

A Sensor is an instrument that observes a property or phenomenon with the goal of producing an estimate of the value of the property.

Link to official Sensor specification

An ObservedProperty specifies the phenomenon of an Observation.

Link to official Observed Property specification

As a result of the merge of these three terms, Coaty allows a single Sensor to only provide one type of data (of one single observed property) and one Sensor to be used by a single Thing. This is done in the sake of simplification of the whole system.

A Sensor defines the following properties:

{
    name: string,
    objectType: SensorThingsTypes.OBJECT_TYPE_SENSOR,
    coreType: "CoatyObject",
    objectId: Uuid,
    parentObjectId: Uuid,
    description: string,
    unitOfMeasurement: UnitOfMeasurement,
    observationType: ObservationType,
    observedArea?: Polygon,
    phenomenonTime?: TimeInterval,
    resultTime?: TimeInterval,
    observedProperty: ObservedProperty,
    encodingType: SensorEncodingType,
    metadata: any
}

The unitOfMeasurement property is the unit of the datastream, matching UCUM convention. See details here,

The observationType property is a reference to one Observation Type as defined in http://www.opengis.net/def/observationType/OGC-OM/2.0/. Expected values are described in Observation Type paragraph.

The observedArea property is a GeoJSON Polygon describing the spatial bounding of all FeaturesOfInterest that belong to the Observations associated with this Datastream. For instance this is a valid GeoJson object:

{
    "type": "Polygon",
    "coordinates": [
        [
            [100,0],
            [101,0],
            [101,1],
            [100,1],
            [100,0]
        ]
    ]
}

The phenomenonTime property is the temporal interval of the phenomenon times of all observations belonging to this Sensor. TimeInterval interface is defined in @coaty/core.

The resultTime property is the temporal interval of the result times of all observations belonging to this Sensor. TimeInterval interface is defined in @coaty/core.

The parentObjectId property is the objectId of the Thing associated to this Sensor.

The observedProperty property is the property that is observed at all observations. The Observations of different Sensors may observe the same observed property. See Observed Property paragraph for more details.

The encodingType property defines the encoding type of metadata property. Allowed types are described in Encoding Type paragraph.

The metadata property can store a detailed description of the sensor or the system.

Observation

According to OGC:

An Observation is the act of measuring or otherwise determining the value of a property [OGC 10-004r3 and ISO 19156:2011]

Link to official Observation specification

An Observation defines the following properties:

{
    name: string,
    objectType: SensorThingsTypes.OBJECT_TYPE_OBSERVATION,
    coreType: "CoatyObject",
    objectId: Uuid,
    parentObjectId: Uuid,
    phenomenonTime: number,
    result: any,
    resultTime: number,
    resultQuality?: string[],
    validTime?: TimeInterval,
    parameters?: { [key: string]: any; },
    featureOfInterestId?: Uuid
}

The parentObjectId property is the objectId of the Sensor which emitted this Observation.

The phenomenonTime property is the time instant or period when the Observation happens.

The result property is the estimated value of an ObservedProperty from the Observation. It depends on the observationType defined in the associated Sensor.

The resultTime property is the time instant when the Observation result was generated.

The resultQuality property describes the quality of the result.

Note : resultQuality is supposed to be of type DQ_Element. However DQ_Element is a class composed of many subclasses, thus it would be a large piece of code for a minor feature. As long as even the OGC test suite considered it as a string, so do we.

The validTime property is an optional property which describes the time period during which the result may be used. It uses the time interval interface defined in @coaty/core.

The parameters property is an optional Javascript object, containing the environmental conditions during measurement as key-value pairs. For instance, {'battery': '0.55', 'uptime': '86400'} is a valid parameters object.

The featureOfInterestId property is an optional reference to the observed feature of interest. This can both be a FeatureOfInterest object or a Location.

Feature of Interest

According to OGC:

An Observation results in a value being assigned to a phenomenon. The phenomenon is a property of a feature, the latter being the FeatureOfInterest of the Observation [OGC and ISO 19156:2011]. In the context of the Internet of Things, many Observations’ FeatureOfInterest can be the Location of the Thing. For example, the FeatureOfInterest of a wifi-connect thermostat can be the Location of the thermostat (i.e., the living room where the thermostat is located in). In the case of remote sensing, the FeatureOfInterest can be the geographical area or volume that is being sensed.

Link to the official Feature of Interest specification

A FeatureOfInterest defines the following properties:

{
    name: string,
    objectType: SensorThingsTypes.OBJECT_TYPE_FEATURE_OF_INTEREST,
    coreType: "CoatyObject",
    description: string,
    encodingType: EncodingType,
    metadata: any
}

The description property is a description about the FeatureOfInterest.

The encodingType property defines the data type of the feature property.

The metadata property is a detailed description of the feature.

Sensor Things Additional Objects and Types

Here is a list of objects and types used in SensorThings API that are not Coaty objects.

Unit of Measurement

A unit of measurement is a Json object containing three key-value pairs:

Values of these properties SHOULD follow the Unified Code for Unit of Measure (UCUM).

{
  name: "Celsius",
  symbol: "degC",
  definition: "http://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html#DegreeCelsius"
}

http://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html

A unit of measurement itself cannot be undefined, however, it’s fields can be for instance when there is an observation of type truth.

Observed Property

According to OGC:

An ObservedProperty specifies the phenomenon of an Observation.

An observed property is a simple interface and defines the following properties:

{
    name: string,
    description: string,
    definition: string
}

For instance the temperature is a valid observed property:

{
    name: "DewPoint Temperature",
    description: "The dewpoint temperature is the temperature to which the air must be cooled, at constant pressure, for dew to form.",
    definition: "http://dbpedia.org/page/Dew_point",
}

The definition property expects a reference to an ontology, e.g. as defined here.

Encoding Type

Encoding Type is used by encodingType properties to describe the encoding type of another property. Technically, one can have any kind of encoding types as it also takes generic strings. However, there are some certain predefined encoding types.

Sensors use SensorEncodingType, which have some custom values defined in SensorEncodingTypes object:

The general EncodingType value is derived from SensorEncodingType and is used with FeatureOfInterests. As the feature of interest can also be the sensor itself, it can use the SensorEncodingTypes. In addition, EncodingTypes object also has the following type:

Observation Type

Observation Type is used by Sensor.observationType to describe the type which is used by the service to encode observations.

Expected values come from OGC OM specification. Refer to their respective documentation for more information.

ObservationTypes Content of result
CATEGORY URI
COUNT integer
MEASUREMENT double
ANY any JSON data
TRUTH boolean

Controllers

SensorThings provide some controllers with some convenience methods for use. These controllers are split in two groups: source and observer controllers. The source controllers are designed to be used by sensor data producers to provide the SensorThings objects whereas the observer controllers should be used by sensor data consumers.

Sensor Source Controller

SensorSourceController controls Sensors that are registered with it and allows them to publish observations in real-time by either channeling or advertising them. The controller reads the value of the Sensor by its hardware interface defined as a SensorIo. SensorIo is the hardware-level communication interface that provides an async read and write method to get and set Sensor values. SensorThings already defines four SensorIo classes:

Note that the Aio, InputGpio, and OutputGpio classes require the mraa npm library to be installed on the board the sensor data is processed. To use any of these classes in your application (see below), import the definitions as follows:

import { Aio, InputGpio, OutputGpio } from "@coaty/core/sensor-things/io";

Users can also easily define their own SensorIo classes:

export class CPULoadSensorIo extends SensorIo {
    private _prescaler: number;

    constructor(parameters?: any) {
        this._prescaler = (parameters && parameters.prescaler) || 1;
    }

    read(callback: (value: any) => void) {
        callback(readCPU() * this._prescaler);
    }

    write(value: any) {
        throw new Error("Cannot write on CPU sensor.");
    }
}

A Sensor then can be registered to the SensorSourceController with the method registerSensor(sensor: Sensor, io: SensorIo) at runtime. You can also define Sensors at configuration in the sensors option of SensorSourceController. This option should contain an array of SensorDefinition. A SensorDefinition contains the Coaty sensor, the type of SensorIo used and optional parameters. For instance, we can add a sensor using the above defined CPUSensorIo as such:

// In controller options:
{
    SensorSourceController: {
        sensors: [{
            sensor: {
                name: "CPU Load Sensor",
                objectType: SensorThingsTypes.OBJECT_TYPE_SENSOR,
                coreType: "CoatyObject",
                objectId: "83dfc46a-0709-4f70-9ea5-beebf8fa89af",
                unitOfMeasurement: {
                    name: "Percentage",
                    symbol: "%",
                    definition: "http://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html#Percent"
                },
                observationType: ObservationTypes.MEASUREMENT,
                parentObjectId: "4c480c29-f65f-496f-8005-03e7503eec2b",
                observedProperty: {
                    name: "CPU Load",
                    description: "CPU Load",
                    definition: "http://wikipedia.org/page/CPULoad"
                },
                description: "The CPU load of the computer",
                encodingType: SensorEncodingTypes.UNDEFINED,
                metadata: {}
            },
            io: CPULoadSensorIo,
            parameters: {
                prescaler: 1.5
            }
        }]
    }
}

When a Sensor is registered, SensorSourceController contains the Sensors in its memory in SensorContainers, which contain both the Coaty Sensor object and the SensorIo used with it. You can access a container with getSensorContainer(sensorId: Uuid) method. You can also find all containers with registeredSensorContainers(). You can find a sensor satisfying a predicate by findSensor().

Once a Sensor is registered, it is advertised (unless skipSensorAdvertise is set in controller options). The controller also starts to listen for Discover and Query events for this Sensor (unless ignoreSensorDiscoverEvents or ignoreSensorQueryEvents options are set in controller options). You can always unregister a Sensor with unregisterSensor(sensorId: Uuid) method. This will send a Deadvertise event unless skipSensorDeadvertise option is set under controller options. When a Sensor is unregistered, it is no longer resolved or retrieved.

Once a sensor is registered, you can choose to automatically sample sensor data from its SensorIo and publish observations.

You can also publish observations manually for registered Sensors. You can decide to channel the observations with publishChanneledObservation or advertise them with publishAdvertisedObservation. For each invocation, the controller will use the SensorIo interface of the sensor to read its current value and create an Observation object with that.

Thing Observer Controller

ThingObserverController provides three methods to observe Things in a system:

Sensor Observer Controller

SensorObserverController provides methods to observe Sensors in a system and to listen to their observations.

Thing Sensor Observation Observer Controller

ThingSensorObservationObserverController observes things and associated sensor and observation objects, combining the functionality of ThingObserverController and SensorObserverController. This controller is designed to be used by a sensor data consumer that wants to easily handle sensor-related events as well as sensor observations.

This controller provides the following getters to handle incoming events for sensors, and observations:

The following filter predicates can be set to further restrict observed things and sensors:

The following example shows how to use this controller to observe sensors and sensor measurements:

import { Thing, ThingSensorObservationObserverController } from "@coaty/core/sensor-things";
import { Subscription } from "rxjs";

export class SensorDataObserverController extends ThingSensorObservationObserverController {

    private _sensorsSubscription: Subscription;
    private _observationSubscription: Subscription;

    onInit() {
        super.onInit();
    }

    onCommunicationManagerStarting() {
        super.onCommunicationManagerStarting();

        // Observe sensors
        this._sensorsSubscription = this.observeSensors();

        // Observe incoming sensor measurements
        this._observationSubscription = this.observeObservations();
    }

    onCommunicationManagerStopping() {
        super.onCommunicationManagerStopping();

        this._sensorsSubscription?.unsubscribe();
        this._observationSubscription?.unsubscribe();
    }

    observeSensors() {
        // Monitor information about changes in the currently registered sensors.
        return this.registeredSensorsChangeInfo$.subscribe(changeInfo => {
            console.log("New Sensors added", JSON.stringify(changeInfo.added));
            console.log("Sensors removed", JSON.stringify(changeInfo.removed));
            console.log("Sensors changed", JSON.stringify(changeInfo.changed));
            console.log("Sensors total (after changes)", JSON.stringify(changeInfo.total));

            // Access the Thing objects associated with added/removed/changed/total sensors.
            changeInfo.added.forEach(sensor => console.log(sensor["thing"] as Thing));
            changeInfo.removed.forEach(sensor => console.log(sensor["thing"] as Thing));
            changeInfo.changed.forEach(sensor => console.log(sensor["thing"] as Thing));
            changeInfo.total.forEach(sensor => console.log(sensor["thing"] as Thing));
        });
    }

    observeObservations() {
        // Monitor incoming sensor observations.
        return this.sensorObservation$.subscribe(({ obs, sensor }) => {
            console.log(`Incoming observation for sensor ${sensor.name}: result=${obs.result} phenomenonTime=${obs.phenomenonTime}`);
        });
    }

}

Discovering Sensor Things

You can discover the whole SensorThings network with the help of the provided controllers and some addditional functions.

SensorObserverController                       SensorSourceController
        |                                                |
        | 1) querySensorsOfThing(thingId)                |
        |----------------------------------------------->|
        |                               RETRIEVE Sensors |
        |<-----------------------------------------------'
        |                                                |
        |                                                |
        | 2) observeAdvertisedSensors()                  |
        |                                                |<--- registerSensor()
        |                               ADVERTISE Sensor |
        |<-----------------------------------------------|
        |                                                |<--- unregisterSensor()
        |                             DEADVERTISE Sensor |
        |<-----------------------------------------------|
        |                                                |
        |                                                |
        | 3) observeChanneledObservations()              |
        |----------------------------------------------->|
        |                                                |<--- publishChanneledObservation()
        |                            CHANNEL Observation |
        |<-----------------------------------------------|

SensorObserverController and SensorSourceController provide full counter-operative functionality and cover all necessary methods for Sensors. Applications can use ThingObserverController to find the Things in the system, however, they should create their own ThingSourceController to handle the requests from ThingObserverController.

Detecting online and offline state of Sensor Things agents

To detect the online/offline state of Sensor Things objects, the Deadvertise event communication pattern in combination with the last will concept can be used. To ease programming this pattern, Coaty provides a convenience controller class named ObjectLifecycleController. This is described in detail in the Coaty Developer Guide under section ObjectLifecycleController.

Basically, the idea is to set the parent object ID of advertised Thing objects originating from a specific Coaty sensor agent to the agent’s identity ID. In case this agent is disconnected other agents can observe Deadvertise events and check whether one of the deadvertised object IDs correlates with the parent object ID of any Thing objects the agent is managing and invoke specific actions.


Copyright (c) 2018 Siemens AG. This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.