Digital Adapter

The las component that we have to implement to complete our first simple Digital Twin definition through the WLDT library is a Digital Adapter in charge of:

  • Receiving event from the DT’s Core related to the variation of properties, events, available actions and relationships
  • Expose received information to the external world according to its implementation and the supported protocol
  • Handle incoming digital action and forward them to the Core in order to be validated and processed by the Shadowing Function

The basic library class that we are going to extend is called DigitalAdapter and creating a new class named DemoDigitalAdapter. The DigitalTwinAdapter class can take as Generic Type the type of Configuration used to configure its behaviours. In this simplified example we are defining a DigitalAdapter without any Configuration.

A Digital Adapter has direct access to the current DT’s State through callbacks or directly in a synchronous way using the internal variable called: digitalTwinState. Through it is possibile to navigate all the fields currently composing the state of our Digital Twin.

The Digital Adapter class has e long list of callback and notification method to allow the adapter to be updated about all the variation and changes on the twin. Available callbacks can be summarized as follows:

  • Digital Adapter Start/Stop:
    • onAdapterStart(): Feedback when the Digital Adapter correctly starts
    • onAdapterStop(): Feedback when the Digital Adapter has been stopped
  • Digital Twin Life Cycle Notifications:
    • onDigitalTwinCreate(): The DT has been created
    • onDigitalTwinStart(): The DT started
    • onDigitalTwinSync(IDigitalTwinState digitalTwinState): The DT is Synchronized with its physical counterpart. The current DigitalTwinState is passed as parameter to allow the Digital Adapter to know the current state and consequently implement its behaviour
    • onDigitalTwinUnSync(IDigitalTwinState digitalTwinState): The DT is not synchronized anymore with its physical counterpart. The last current DigitalTwinState is passed as parameter to allow the Digital Adapter to know the last state and consequently implement its behaviour
    • onDigitalTwinStop(): The DT is stopped
    • onDigitalTwinDestroy(): The DT has been destroyed and the application stopped

The Digital Adapter DT State variations and DT events are received by the Adapter from the DT core belongs to the following categories:

  • Digital Twin State Update through the method onStateUpdate(...) providing information about the new state of the Digital Twin, the previous state, and a list of changes that occurred between these two states. In the previous version each variation of a property, relationships, actions or events were notified. In the new version only a committed DT’State variation is notified to listeners.
  • Event Notifications through the method onEventNotificationReceived(...) whenever there is a notification about an event related to the Digital Twin’s state coming from the physical world, generated by the twin and processed by the Shadowing Function. For example in the DT State we can have the declaration of the over-heating-alert structured and received in the DT State while the effective occurrence of the event and the associated notification is notified through this dedicated callback

The onStateUpdate method is an abstract method that must be implemented by any class extending the DigitalAdapter class. This method is called whenever there is an update to the Digital Twin’s state. It provides information about the new state of the Digital Twin, the previous state, and a list of changes that occurred between these two states.

The explanation of the parameters is the following:

  1. newDigitalTwinState: This parameter represents the updated state of the Digital Twin. It is an instance of the DigitalTwinState class, which encapsulates the current state information.
  2. previousDigitalTwinState: This parameter represents the state of the Digital Twin before the update. It is also an instance of the DigitalTwinState class.
  3. digitalTwinStateChangeList: This parameter is an ArrayList containing DigitalTwinStateChange objects. Each DigitalTwinStateChange object encapsulates information about a specific change that occurred between the previous and new states. It includes details such as the property or aspect of the state that changed, the previous value, and the new value.

Another core method where a Digital Adapter receive the description of the DT’State is onDigitalTwinSync(IDigitalTwinState digitalTwinState). The Adapter using the parameter digitalTwinState can analyze available properties, actions, events and relationships and decide how to implement its internal behaviour with the methods presented in ShadowingFunction. The DT State is automatically monitored by each Digital Adapter while for the Events potentially generated by the DT can be observed by each adapter using:

  • observeAllDigitalTwinEventsNotifications: Enable the observation of available Digital Twin State Events Notifications.
  • unObserveAllDigitalTwinEventsNotifications: Cancel the observation of Digital Twin State Events Notifications
  • observeDigitalTwinEventsNotifications: Enable the observation of the notification associated to a specific list of Digital Twin State events. With respect to event a notification contains the new associated value
  • unObserveDigitalTwinEventsNotifications: Cancel the observation of a target list of properties
  • observeDigitalTwinEventNotification: Enable the observation of the notification associated to a single Digital Twin State event. With respect to event a notification contains the new associated value
  • unObserveDigitalTwinEventNotification: Cancel the observation of a single target event

The resulting code will be the following after adding the required methods (still empty) and the basic constructor with the id String parameter is the following:

import it.wldt.adapter.digital.DigitalAdapter;
import it.wldt.core.state.*;

public class DemoDigitalAdapter extends DigitalAdapter<Void> {

    public DemoDigitalAdapter(String id) {
        super(id);
    }

    /**
     * Callback to notify the adapter on its correct startup
     */
    @Override
    public void onAdapterStart() {}

    /**
     * Callback to notify the adapter that has been stopped
     */
    @Override
    public void onAdapterStop() {}

    /**
     * DT Life Cycle notification that the DT is correctly on Sync
     * @param digitalTwinState
     */
    @Override
    public void onDigitalTwinSync(DigitalTwinState digitalTwinState) {}

    /**
     * DT Life Cycle notification that the DT is currently Not Sync
     * @param digitalTwinState
     */
    @Override
    public void onDigitalTwinUnSync(DigitalTwinState digitalTwinState) {}

    /**
     * DT Life Cycle notification that the DT has been created
     */
    @Override
    public void onDigitalTwinCreate() {}

    /**
     * DT Life Cycle Notification that the DT has correctly Started
     */
    @Override
    public void onDigitalTwinStart() {}

    /**
     * DT Life Cycle Notification that the DT has been stopped
     */
    @Override
    public void onDigitalTwinStop() {}

    /**
     * DT Life Cycle Notification that the DT has destroyed
     */
    @Override
    public void onDigitalTwinDestroy() {}

    /**
     * Callback method allowing the Digital Adapter to receive the updated Digital Twin State together with
     * the previous state and the list of applied changes
     *
     * @param newDigitalTwinState The new Digital Twin State computed by the Shadowing Function
     * @param previousDigitalTwinState The previous Digital Twin State
     * @param digitalTwinStateChangeList The list of applied changes to compute the new Digital Twin State
     */
    @Override
    protected void onStateUpdate(DigitalTwinState newDigitalTwinState, DigitalTwinState previousDigitalTwinState, ArrayList<DigitalTwinStateChange> digitalTwinStateChangeList) {}
  
    /**
     * Callback method to receive a new computed Event Notification (associated to event declared in the DT State)
     *
     * @param digitalTwinStateEventNotification The generated Notification associated to a DT Event
     */
    @Override
    protected void onEventNotificationReceived(DigitalTwinStateEventNotification<?> digitalTwinStateEventNotification) {}
}

By default, a Digital Adapter observes all the variation on the DT’s State in terms of Properties, Relationships, Actions and Events. As previously mentioned the observation of DT’s State Properties allows to receive also properties variation on the method since a property is natively composed by its description (e.g., type) and its current value. On the opposite the observation on DT’s State Action, Relationships and Events allow ONLY to receive callbacks when a new entity is added or an update is occurred without receiving updates on values variation.

The only thing that we should add in the onDigitalTwinSync(IDigitalTwinState currentDigitalTwinState) callback is the direct observation for Events. Following this approach we can change our Digital Adapter in the following methods:

In onDigitalTwinSync we observe in this first simple implementation only the incoming values for declared Events in the DT’State. As previously mentioned the observation of any variation of the State structure together with Properties Values are by default observed by any Digital Adapter. In this method we use the internal variable digitalTwinState to access the DT’s state and find available Events declaration that we would like to observe.

public void onDigitalTwinSync(IDigitalTwinState currentDigitalTwinState) {

      try {
          
          //Retrieve the list of available events and observe all variations
          digitalTwinState.getEventList()
                  .map(eventList -> eventList.stream()
                          .map(DigitalTwinStateEvent::getKey)
                          .collect(Collectors.toList()))
                  .ifPresent(eventKeys -> {
                      try {
                          observeDigitalTwinEventsNotifications(eventKeys);
                      } catch (EventBusException e) {
                          e.printStackTrace();
                      }
                  });

      } catch (Exception e) {
          e.printStackTrace();
      }

  }

Developers extending the DigitalAdapter class should implement the onStateUpdate method to define custom logic that needs to be executed whenever the state of the Digital Twin is updated. This could include tasks such as processing state changes, updating internal variables, triggering specific actions, or notifying other components about the state update.

Here’s an example of how the method might be implemented in a concrete subclass of DigitalAdapter:

@Override
protected void onStateUpdate(DigitalTwinState newDigitalTwinState,
                              DigitalTwinState previousDigitalTwinState,
                              ArrayList<DigitalTwinStateChange> digitalTwinStateChangeList) {

	// In newDigitalTwinState we have the new DT State
	System.out.println("New DT State is: " + newDigitalTwinState);

	// The previous DT State is available through the variable previousDigitalTwinState
	System.out.println("Previous DT State is: " + previousDigitalTwinState);

    // We can also check each DT's state change potentially differentiating the behaviour for each change
    if (digitalTwinStateChangeList != null && !digitalTwinStateChangeList.isEmpty()) {
        
        // Iterate through each state change in the list
        for (DigitalTwinStateChange stateChange : digitalTwinStateChangeList) {
            
            // Get information from the state change
            DigitalTwinStateChange.Operation operation = stateChange.getOperation();
            DigitalTwinStateChange.ResourceType resourceType = stateChange.getResourceType();
            DigitalTwinStateResource resource = stateChange.getResource();
            
            // Perform different actions based on the type of operation
            switch (operation) {
                case OPERATION_UPDATE:
                    // Handle an update operation
                    System.out.println("Update operation on " + resourceType + ": " + resource);
                    break;
                case OPERATION_UPDATE_VALUE:
                    // Handle an update value operation
                    System.out.println("Update value operation on " + resourceType + ": " + resource);
                    break;
                case OPERATION_ADD:
                    // Handle an add operation
                    System.out.println("Add operation on " + resourceType + ": " + resource);
                    break;
                case OPERATION_REMOVE:
                    // Handle a remove operation
                    System.out.println("Remove operation on " + resourceType + ": " + resource);
                    break;
                default:
                    // Handle unknown operation (optional)
                    System.out.println("Unknown operation on " + resourceType + ": " + resource);
                    break;
            }
        }
    } else {
        // No state changes
        System.out.println("No state changes detected.");
    }
}

In this example, the method iterates over the list of state changes, extracts information about each change, and performs custom actions based on the changes. Developers can adapt this method to suit the specific requirements of their Digital Twin application.

Both Physical Adapters and Digital Adapters can be defined natively with a custom configuration provided by the developer as illustrated in the dedicated Section: Configurable Physical & Digital Adapters.