CoAP Digital Adapter

The CoapDigitalAdapter acts as a bridge between a Digital Twin and CoAP-based applications, allowing developers to easily expose and interact with Digital Twin data and functionalities over CoAP.

Key features:

  • Configuration builder: The Adapter configuration is managed via the builder provided by the CoapDigitalAdapterConfiguration.build() method, which enables users to effortlessly configure the CoAP server parameters and each Adapter’s feature.
  • Resource discovery: The server exposes resource discovery on the default /.well-known/core resource, making it possible for applications to implement automations based on the discovered resources list.
  • Default resources tree: The adapter is set to make the server expose a default resources tree, so that the resources can be queried even without discovering resources beforehand.
  • Observability: Each resource exposing a dynamic value supports CoAP observability, making it possible for applications to stay updated with the latest DT State updates.
  • Storage & Query: Starting from version 0.1.1 the CoAP Digital Adapter is able to retrieve Storage Statistics and run queries on the target DT.

WLDT-Core Version Compatibility

coap-physical-adapterwldt-core 0.2.1wldt-core 0.3.0wldt-core 0.4.0
0.1.1

Installation

To use the adapter in your Java project, you can include it as a dependency using Maven or Gradle.

Maven

<dependency>
    <groupId>io.github.wldt</groupId>
    <artifactId>coap-digital-adapter</artifactId>
    <version>0.1.1</version>
</dependency>

Gradle

implementation("io.github.wldt:coap-digital-adapter:0.1.1")

Class Structure & Functionalities

CoapDigitalAdapter

The CoapDigitalAdapter class is the core component responsible for handling CoAP requests and interacting with the underlying Digital Twin.

Key-Functionalities:

  • CoAP endpoints: Provides a set of CoAP endpoints to interact with the Digital Twin
  • State updates: Automatically reflects changes in the Digital Twin state to the CoAP endpoints, ensuring real-time information.
  • Event handling: Listens for Digital Twin events and provides events notifications to CoAP clients.

Here is a basic example illustrating how to use the CoAP Digital Adapter:

Getting started

Create the Digital Twin:

DigitalTwin digitalTwin = new DigitalTwin("my-digital-twin", new DefaultShadowingFunction());

Build the configuration:

CoapDigitalAdapterConfiguration config = CoapDigitalAdapterConfiguration.builder(5683)
  // Configure the adapter
  [...]
  .build();

Instantiate a CoAP Digital Adapter:

CoapDigitalAdapter coapDigitalAdapter = new CoapDigitalAdapter(config);

// Add a Physical Adapter to the DT
[...]

Add the CoAP Digital Adapter to the Digital Twin:

digitalTwin.addDigitalAdapter(coapDigitalAdapter);

Start the Digital Twin Engine:

DigitalTwinEngine digitalTwinEngine = new DigitalTwinEngine();
digitalTwinEngine.addDigitalTwin(digitalTwin);
digitalTwinEngine.startAll();

Note: to make the Adapter able to retrieve data regarding the Digital Twin instance it’s necessary to specifically add the Digital Twin instance in the configuration:

CoapDigitalAdapterConfiguration config = CoapDigitalAdapterConfiguration.builder()
  .setDigitalTwinInstance(digitalTwin)
  // Configure the adapter
  [...]
  .build();

CoapDigitalAdapterConfiguration

The CoapDigitalAdapterConfiguration class is a crucial part of the CoAP Digital Adapter, allowing developers to configure the behaviour of the Adapter to meet their needs.

An instance of this class is required by the CoapDigitalAdapter constructor, and can be generated by building it via the CoapDigitalAdapterConfigurationBuilder class.

CoapDigitalAdapterConfigurationBuilder

The CoapDigitalAdapterConfigurationBuilder class makes it possible to create a CoapDigitalAdapterConfiguration instance, by calling the apposite build() method at the end of the configuration process.

The builder can be retrieved by calling the builder() static method contained in CoapDigitalAdapterConfiguration.

A builder can also be retrieved from a YAML file, by calling the fromYaml(File yamlConfig) static method contained in CoapDigitalAdapterConfiguration.

Following there is an overview of each method which can be called on the builder to create the configuration instance. Each of these methods is optional, but makes it possible to use different capabilities of the Adapter or to customize their behaviour based on the specific needs:

Digital Twin instance

  • setDigitalTwinInstance(DigitalTwin digitalTwin): Sets the Digital Twin instance for which the CoAP server will be configured. This instance will be used to expose a /instance CoAP endpoint that provides information about the Digital Twin.

CoRE

  • setCoreRT(CoreRT coreRT): Sets the CoRE resource types which will be assigned to each resource based on its scope. If no resource types are set, the default resource types will be used.

  • setCoreIF(CoreIF coreIF): Sets the CoRE interfaces which will be assigned to each resource based on its scope. If no interfaces are set, the default interfaces will be used.

  • setCoreCT(CoreCT coreCT): Sets the CoRE content types which will be assigned to each resource based on its scope. If no content types are set, the default content types will be used.

Request handlers

If for any reason there’s the necessity to change the behaviour of the responses, each request handler can be modified to better suit the use cases via the following methods:

  • setGetRequestHandlers(GetRequestHandlers getRequestHandlers): Sets the handlers for GET requests. These handlers will be invoked when the CoAP server receives GET requests on the resources. If no handlers are set, the default handlers will be used.
  • setPostRequestHandlers(PostRequestHandlers postRequestHandlers): Sets the handlers for POST requests. These handlers will be invoked when the CoAP server receives POST requests on the resources. If no handlers are set, the default handlers will be used.
  • setPutRequestHandlers(PutRequestHandlers putRequestHandlers): Sets the handlers for PUT requests. These handlers will be invoked when the CoAP server receives PUT requests on the resources. If no handlers are set, the default handlers will be used.

Filters

Filters allow to make so that only a specified set of Digital Twin state events are accessible from the client. If no filter is added, the Adapter will automatically create a CoAP endpoint for each state event. To add a filter for a specified state event the following methods can be used:

  • addPropertyFilter(String propertyKey): Adds a property to the filter.
  • addEventFilter(String eventKey): Adds an event to the filter.
  • addActionFilter(String actionKey): Adds an action to the filter.
  • addRelationshipFilter(String relationshipName): Adds a relationship to the filter.

Note: For each method to add a filter exists a method with the same name which, instead of a String, accepts as a parameter a Collection<String> containing all the state events of that type to add to the filter.

CoAP Server Endpoints

The Adapter’s CoAP server exposes a set of resources which allow the users to interact with the DT instance, retrieve its state, read properties, actions, events and relationships description, and trigger actions.

The available endpoints, each with its associated methods and default behaviour, are the following:

  • /instance
    • GET: Retrieves information about the Digital Twin instance.
  • /state
    • GET: Retrieves the current state of the Digital Twin.
  • /state/previous
    • GET: Retrieves the previous state of the Digital Twin.
  • /state/changes
    • GET: Retrieves the list of state changes in the Digital Twin.
  • /state/properties
    • GET: Retrieves the list of properties in the Digital Twin state.
  • /state/properties/{propertyKey}
    • GET: Retrieves the specified property in the Digital Twin state.
  • /state/properties/{propertyKey}/value
    • GET: Retrieves the value of a property in the Digital Twin state.
  • /state/events
    • GET: Retrieves the list of events in the Digital Twin state.
  • /state/events/notifications
    • GET: Retrieves the list of event notifications received by the Digital Twin.
  • /state/events/{eventKey}
    • GET: Retrieves the specified event in the Digital Twin state.
  • /state/actions
    • GET: Retrieves the list of actions in the Digital Twin state.
  • /state/actions/{actionKey}
    • GET: Retrieves a specific action in the Digital Twin state.
    • POST: Triggers the specified action in the Digital Twin state. By default it does not expect a payload.
    • PUT: Triggers the specified action in the Digital Twin state. By default it does expect a payload.
  • /state/relationships
    • GET: Retrieves the list of relationships in the Digital Twin state.
  • /state/relationships/{relationshipName}
    • GET: Retrieves the specified relationship in the Digital Twin state.
  • /state/relationships/{relationshipName}/instances
    • GET: Retrieves the instances of the specified relationship in the Digital Twin state.
  • /storage
    • GET: Retrieves Storage Statistics from the target Digital Twin.
    • PUT: Allows the execution of a query, where the query structure is specified through a JSON Message in the request body. For additional information about the Query System see Query System Page.

Note: Replace {propertyKey}, {eventKey}, {actionKey}, and {relationshipName} with the actual values you want to retrieve or trigger. Make sure to use the appropriate CoAP method (GET, POST, PUT) and include any required parameters or payload.

YAML Configuration

As specified in the CoapDigitalAdapterConfigurationBuilder section, it is possible to create a configuration from a YAML file.

The YAML configuration file should follow the structure of the CoapDigitalAdapterConfigurationData class, which is the class the file is deserialized into.

Following is an overview of the YAML configuration file structure:

port: <port_number>
propertyFilter:
  - "<property_key_1>"
  - "<property_key_2>"
actionFilter:
  - "<action_key_1>"
  - "<action_key_2>"
eventFilter:
  - "<event_key_1>"
  - "<event_key_2>"
relationshipFilter:
  - "<relationship_name_1>"
  - "<relationship_name_2>"
coreRT:
  <resource_scope_1>: "<core_rt_value_1>"
  <resource_scope_2>: "<core_rt_value_2>"
coreIF:
  <resource_scope_1>: "<core_if_value_1>"
  <resource_scope_2>: "<core_if_value_2>"
coreCT:
  <resource_scope_1>: "<core_ct_value_1>"
  <resource_scope_2>: "<core_ct_value_2>"

Examples

YAML Configuration Example

To load a YAML configuration file, you can use the fromYaml(File yamlConfig) method of the CoapDigitalAdapterConfiguration class. Below is an example of how to load a YAML configuration file called daconfig.yaml, followed by a configuration example:

[...]

CoapDigitalAdapterConfiguration configuration = CoapDigitalAdapterConfiguration.fromYaml(new File(
        TestMain.class
                .getClassLoader()
                .getResource("daconfig.yaml")
                .toURI()));

[...]
port: 5683
coreRT:
  dtInstance: "digitaltwin.instance"
  state: "digitaltwin.state"
  previousState: "digitaltwin.state.previous"
coreIF:
  dtInstance: "core.p"
  state: "core.p"
propertyFilter:
  - "energy"
eventFilter:
  - "overheating"

Integrated example

This example demonstrates the integration of a CoapDigitalAdapter within a Digital Twin setup, where multiple adapters are added to a Digital Twin, and the overall system is managed by a DigitalTwinEngine.

// Create Digital Twin instance
DigitalTwin dt = new DigitalTwin("coap-digital-twin", new DefaultShadowingFunction());

// Add Physical Adapters
dt.addPhysicalAdapter(new DummyPhysicalAdapter("test-pa"));
dt.addPhysicalAdapter(createPhysicalAdapter("test-pa-2", Arrays.asList("temperature", "volume")));
dt.addPhysicalAdapter(createPhysicalAdapter("test-pa-3", Arrays.asList("intensity", "color")));

// Build CoAP Digital Adapter configuration, setting only the port and the DT instance
CoapDigitalAdapterConfiguration coapDigitalAdapterConfiguration = CoapDigitalAdapterConfiguration.builder(5683)
  .setDigitalTwinInstance(dt)
  .build();

// Instantiate a CoAP Digital Adapter and add it to the Digital Twin
coapDigitalAdapter = new CoapDigitalAdapter("coap-da", coapDigitalAdapterConfiguration);
dt.addDigitalAdapter(coapDigitalAdapter);

// Instantiate WLDT Storage and add it to the DT
wldtStorage = new DefaultWldtStorage("test-storage", true);
dt.getStorageManager().putStorage(wldtStorage);

// Create DT engine
engine = new DigitalTwinEngine();

// Add DT to the engine and start it
engine.addDigitalTwin(dt);
engine.startAll();

Using custom request handlers

The following example demonstrates the creation of a configuration with a custom request handler to manage a POST request different from the default one:

CoapDigitalAdapterConfiguration coapDigitalAdapterConfiguration = CoapDigitalAdapterConfiguration.builder(5683)
  .setDigitalTwinInstance(dt)
  .setPostRequestHandlers(new PostRequestHandlers() {{
    setStateActionHandler((exchange, configuration, optional) -> {
      // Check that the request does not have a payload
      if (exchange.getRequestPayload().length != 0) {
        exchange.respond(CoAP.ResponseCode.BAD_REQUEST, "Payload not allowed");
        return Optional.empty();
      }
      // Check that the action optional is present
      if (!optional.isPresent()) {
        exchange.respond(CoAP.ResponseCode.NOT_FOUND);
        return Optional.empty();
      }

      // Get the action from the optional
      DigitalTwinStateAction action = optional.get();

      try {
        // Create a new action event
        DigitalActionWldtEvent<?> event = new DigitalActionWldtEvent<>(action.getKey());
        event.setType(DummyPhysicalAdapter.TYPE_ACTION_TOGGLE);

        // Respond to the client
        exchange.respond(CoAP.ResponseCode.CHANGED);

        // Return the event to be invoked
        return Optional.of(event);
      } catch (EventBusException e) {
        // If the event creation goes wrong respond with an internal server error code
        exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR);
        return Optional.empty();
      }
    });
  }})
  .build();

Storage query request payloads

Example of Storage Query Requests are the following:

Retrieve the first 4 Digital Twin State Variations

{
  "resourceType": "DIGITAL_TWIN_STATE",
  "queryType": "SAMPLE_RANGE",
  "startIndex": 0,
  "endIndex": 3
}

Retrieve Digital Twin State Variations in a Time Range

{
    "resourceType": "DIGITAL_TWIN_STATE",
    "queryType": "TIME_RANGE",
    "startIndex": 161989898,
    "endIndex": 162989898
}

Retrieve the last Digital Twin State

{
    "resourceType": "DIGITAL_TWIN_STATE",
    "queryType": "LAST_VALUE"
}