Shadowing Function

After the definition of the Physical Adapter it is time to start implementing the core of our DT through the definition of its shadowing function in charge of:

  • Handle received PAD from Physical Adapters in order to device which properties, events, relationships or actions available on connected physical twins should be mapped and managed into the DT State
  • Manage incoming notifications/callbacks associated to the variation of physical properties (e.g, temperature variation) or the generation of physical event (e.g., overheating)
  • Process action requests from the digital world that should be validated and forward to the correct Physical Adapter in order to trigger the associated actions on the physical world

The Shadowing Function has the responsibility to build and maintain the updated state of the Digital Twin The internal variable of any WLDT Shadowing Function (available through the base class ShadowingFunction) used to do that is DigitalTwinStateManager accessible through the variable: this.digitalTwinStateManager

When the Shadowing Function has to compute the new DT State it can now work with the following method to handle DT State Transition:

  • Start the DT State Transaction: startStateTransaction()
  • DT State variation methods such as:
    • createProperty()
    • updateProperty()
    • updatePropertyValue()
    • deleteProperty()
    • enableAction()
    • updateAction()
    • disableAction()
    • registerEvent()
    • updateRegisteredEvent()
    • unRegisterEvent()
    • createRelationship()
    • addRelationshipInstance()
    • deleteRelationship()
    • deleteRelationshipInstance()
  • At the end the transaction can be committed using the method: commitStateTransaction()

To access the current DT State the Shadowing Function implementation can use the method this.digitalTwinStateManager.getDigitalTwinState() The information available on the DT State are:

  • properties: List of Properties with their values (if available)
  • actions: List of Actions that can be called on the DT
  • events: List of Events that can be generated by the DT
  • relationships: List of Relationships and their instances (if available)
  • evaluationInstant: The timestamp representing the evaluation instant of the DT state

Available main methods on that class instance are:

  • Properties:
    • getProperty(String propertyKey): Retrieves if present the target DigitalTwinStateProperty by Key
    • containsProperty(String propertyKey): Checks if a target Property Key is already available in the current Digital Twin’s State
    • getPropertyList(): Loads the list of available Properties (described by the class DigitalTwinStateProperty) available on the Digital Twin’s State
    • createProperty(DigitalTwinStateProperty<?> dtStateProperty): Allows the creation of a new Property on the Digital Twin’s State through the class DigitalTwinStateProperty
    • readProperty(String propertyKey): Retrieves if present the target DigitalTwinStateProperty by Key
    • updateProperty(DigitalTwinStateProperty<?> dtStateProperty): Updates the target property using the DigitalTwinStateProperty and the associated Property Key field
    • deleteProperty(String propertyKey): Deletes the target property identified by the specified key
  • Actions:
    • containsAction(String actionKey): Checks if a Digital Twin State Action with the specified key is correctly registered
    • getAction(String actionKey): Loads the target DigitalTwinStateAction by key
    • getActionList(): Gets the list of available Actions registered on the Digital Twin’s State
    • enableAction(DigitalTwinStateAction digitalTwinStateAction): Enables and registers the target Action described through an instance of the DigitalTwinStateAction class
    • updateAction(DigitalTwinStateAction digitalTwinStateAction): Update the already registered target Action described through an instance of the DigitalTwinStateAction class
    • disableAction(String actionKey): Disables and unregisters the target Action described through an instance of the DigitalTwinStateAction class
  • Events:
    • containsEvent(String eventKey): Check if a Digital Twin State Event with the specified key is correctly registered
    • getEvent(String eventKey): Return the description of a registered Digital Twin State Event according to its Key
    • getEventList(): Return the list of existing and registered Digital Twin State Events
    • registerEvent(DigitalTwinStateEvent digitalTwinStateEvent): Register a new Digital Twin State Event
    • updateRegisteredEvent(DigitalTwinStateEvent digitalTwinStateEvent): Update the registration and signature of an existing Digital Twin State Event
    • unRegisterEvent(String eventKey): Un-register a Digital Twin State Event
    • notifyDigitalTwinStateEvent(DigitalTwinStateEventNotification<?> digitalTwinStateEventNotification): Method to notify the occurrence of the target Digital Twin State Event
  • Relationships:
    • containsRelationship(String relationshipName): Checks if a Relationship Name is already available in the current Digital Twin’s State
    • createRelationship(DigitalTwinStateRelationship<?> relationship): Creates a new Relationships (described by the class DigitalTwinStateRelationship) in the Digital Twin’s State
    • addRelationshipInstance(String name, DigitalTwinStateRelationshipInstance<?> instance): Adds a new Relationship instance described through the class DigitalTwinStateRelationshipInstance and identified through its name
    • getRelationshipList(): Loads the list of existing relationships on the Digital Twin’s State through a list of DigitalTwinStateRelationship
    • getRelationship(String name): Gets a target Relationship identified through its name and described through the class DigitalTwinStateRelationship
    • deleteRelationship(String name): Deletes a target Relationship identified through its name
    • deleteRelationshipInstance(String relationshipName, String instanceKey): Deletes the target Relationship Instance using relationship name and instance Key

The basic library class that we are going to extend is called ShadowingFunction and creating a new class named DemoShadowingFunction the resulting code is the same after implementing required methods the basic constructor with the id String parameter.

import it.wldt.adapter.digital.event.DigitalActionWldtEvent;
import it.wldt.adapter.physical.PhysicalAssetDescription;
import it.wldt.adapter.physical.event.PhysicalAssetEventWldtEvent;
import it.wldt.adapter.physical.event.PhysicalAssetPropertyWldtEvent;
import it.wldt.adapter.physical.event.PhysicalAssetRelationshipInstanceCreatedWldtEvent;
import it.wldt.adapter.physical.event.PhysicalAssetRelationshipInstanceDeletedWldtEvent;
import it.wldt.core.model.ShadowingModelFunction;
import java.util.Map;

public class DemoShadowingFunction extends ShadowingModelFunction {

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

    //// Shadowing Function Management Callbacks ////

    @Override
    protected void onCreate() {

    }

    @Override
    protected void onStart() {

    }

    @Override
    protected void onStop() {

    }

    //// Bound LifeCycle State Management Callbacks ////

    @Override
    protected void onDigitalTwinBound(Map<String, PhysicalAssetDescription> adaptersPhysicalAssetDescriptionMap) {

    }

    @Override
    protected void onDigitalTwinUnBound(Map<String, PhysicalAssetDescription> map, String s) {

    }

    @Override
    protected void onPhysicalAdapterBidingUpdate(String s, PhysicalAssetDescription physicalAssetDescription) {

    }

    //// Physical Property Variation Callback ////

    @Override
    protected void onPhysicalAssetPropertyVariation(PhysicalAssetPropertyWldtEvent<?> physicalAssetPropertyWldtEvent) {

    }

    //// Physical Event Notification Callback ////

    @Override
    protected void onPhysicalAssetEventNotification(PhysicalAssetEventWldtEvent<?> physicalAssetEventWldtEvent) {

    }

    //// Physical Relationships Notification Callbacks ////

    @Override
    protected void onPhysicalAssetRelationshipEstablished(PhysicalAssetRelationshipInstanceCreatedWldtEvent<?> physicalAssetRelationshipInstanceCreatedWldtEvent) {

    }

    @Override
    protected void onPhysicalAssetRelationshipDeleted(PhysicalAssetRelationshipInstanceDeletedWldtEvent<?> physicalAssetRelationshipInstanceDeletedWldtEvent) {

    }

    //// Digital Action Received Callbacks ////

    @Override
    protected void onDigitalActionEvent(DigitalActionWldtEvent<?> digitalActionWldtEvent) {

    }
}

The methods onCreate(), onStart() and onStop() are used to receive callbacks from the DT’s core when the Shadowing Function has been effectively created within the twin, is started or stopped according to the evolution of its life cycle. In our initial implementation we are not implementing any of them but they can be useful to trigger specific behaviours according to the different phases.

The first method that we have to implement in order to analyze received PAD and build the Digital Twin State in terms of properties, events, relationships and available actions is the onDigitalTwinBound(Map<String, PhysicalAssetDescription> map) method. In our initial implementation we just pass through all the received characteristics recevied from each connected Physical Adapter mapping every physical entity into the DT’s state without any change or adaptation (Of course complex behaviour can be implemented to customized the digitalization process).

Through the following method we implement the following behaviour:

  • Analyze each received PAD from each connected and active Physical Adapter (in our case we will have just 1 Physical Adapter)
  • Iterate over all the received Properties for each PAD and create the same Property on the Digital Twin State
  • Start observing target Physical Properties in order to receive notification callback about physical variation through the method observePhysicalAssetProperty(property);
  • Analyze received PAD’s Events declaration and recreates them also on the DT’s State
  • Start observing target Physical Event in order to receive notification callback about physical event generation through the method observePhysicalAssetEvent(event);
  • Check available Physical Action and enable them on the DT’s State. Enabled Digital Action are automatically observed by the Shadowing Function in order to receive action requests from active Digital Adapters

The possibility to manually observe Physical Properties and Event has been introduced to allow the Shadowing Function to decide what to do according to the nature of the property or of the target event. For example in some cases with static properties it will not be necessary to observe any variation, and it will be enough to read the initial value to build the digital replica of that specific property.

Since the DT State is managed through the DigitalTwinStateManager class all the changes and variation should be applied on the DT ShadowingFunction using the previously presented transaction management and the correct call of methods startStateTransaction() and commitStateTransaction().

@Override
protected void onDigitalTwinBound(Map<String, PhysicalAssetDescription> adaptersPhysicalAssetDescriptionMap) {

    try{

      // NEW from 0.3.0 -> Start DT State Change Transaction  
      this.digitalTwinStateManager.startStateTransaction();

      //Iterate over all the received PAD from connected Physical Adapters
        adaptersPhysicalAssetDescriptionMap.values().forEach(pad -> {

            //Iterate over all the received PAD from connected Physical Adapters
            adaptersPhysicalAssetDescriptionMap.values().forEach(pad -> {
                pad.getProperties().forEach(property -> {
                try {
                    
                    //Create and write the property on the DT's State
                    this.digitalTwinState.createProperty(new DigitalTwinStateProperty<>(property.getKey(),(Double) property.getInitialValue()));
            
                    //Start observing the variation of the physical property in order to receive notifications
                    //Without this call the Shadowing Function will not receive any notifications or callback about
                    //incoming physical property of the target type and with the target key
                    this.observePhysicalAssetProperty(property);
        
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });

            //Iterate over available declared Physical Events for the target Physical Adapter's PAD
            pad.getEvents().forEach(event -> {
                try {

                    //Instantiate a new DT State Event with the same key and type
                    DigitalTwinStateEvent dtStateEvent = new DigitalTwinStateEvent(event.getKey(), event.getType());

                    //Create and write the event on the DT's State
                    this.digitalTwinState.registerEvent(dtStateEvent);

                    //Start observing the variation of the physical event in order to receive notifications
                    //Without this call the Shadowing Function will not receive any notifications or callback about
                    //incoming physical events of the target type and with the target key
                    this.observePhysicalAssetEvent(event);

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

            //Iterate over available declared Physical Actions for the target Physical Adapter's PAD
            pad.getActions().forEach(action -> {
                try {

                    //Instantiate a new DT State Action with the same key and type
                    DigitalTwinStateAction dtStateAction = new DigitalTwinStateAction(action.getKey(), action.getType(), action.getContentType());

                    //Enable the action on the DT's State
                    this.digitalTwinState.enableAction(dtStateAction);

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

        });
            
        // NEW from 0.3.0 -> Commit DT State Change Transaction to apply the changes on the DT State and notify about the change
        this.digitalTwinStateManager.commitStateTransaction();

        //Start observation to receive all incoming Digital Action through active Digital Adapter
        //Without this call the Shadowing Function will not receive any notifications or callback about
        //incoming request to execute an exposed DT's Action
        observeDigitalActionEvents();

        //Notify the DT Core that the Bounding phase has been correctly completed and the DT has evaluated its
        //internal status according to what is available and declared through the Physical Adapters
        notifyShadowingSync();

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

In particular the method observeDigitalActionEvents() should be called start the observation of digital actions and to receive all incoming Digital Action through active Digital Adapters. Without this call the Shadowing Function will not receive any notifications or callback about incoming request to execute an exposed DT’s Action. Of course, we have to call this method if we are mapping any digital action in our DT.

Another fundamental method is notifyShadowingSync() used to notify the DT Core that the Bounding phase has been correctly completed and the DT has evaluated its internal status according to what is available and declared through the Physical Adapters.

As mentioned, in the previous example the Shadowing Function does not apply any control or check on the nature of declared physical property. Of course in order to have a more granular control, it will be possible to use property Key or any other field or even the type of the instance through an instanceof check to implement different controls and behaviours.

A variation (only for the property management code) to the previous method can be the following:

//Iterate over available declared Physical Property for the target Physical Adapter's PAD 
pad.getProperties().forEach(property -> {
    try {

        //Check property Key and Instance of to validate that is a Double
        if(property.getKey().equals("temperature-property-key")
                && property.getInitialValue() != null
                &&  property.getInitialValue() instanceof Double) {

            //Instantiate a new DT State Property of the right type, the same key and initial value
            DigitalTwinStateProperty<Double> dtStateProperty = new DigitalTwinStateProperty<Double>(property.getKey(),(Double) property.getInitialValue());

            //Create and write the property on the DT's State
            this.digitalTwinState.createProperty(dtStateProperty);

            //Start observing the variation of the physical property in order to receive notifications
            //Without this call the Shadowing Function will not receive any notifications or callback about
            //incoming physical property of the target type and with the target key
            this.observePhysicalAssetProperty(property);
        }

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

The next method that we have to implement in order to properly define and implement the behaviour of our DT through its ShadowingModelFunction are:

  • onPhysicalAssetPropertyVariation: Method called when a new variation for a specific Physical Property has been detected by the associated Physical Adapter. The method receive as parameter a specific WLDT Event called PhysicalAssetPropertyWldtEvent<?> physicalPropertyEventMessage containing all the information generated by the Physical Adapter upon the variation of the monitored physical counterpart.
  • onPhysicalAssetEventNotification: Callback method used to be notified by a PhysicalAdapter about the generation of a Physical Event. As for the previous method, also this function receive a WLDT Event parameter of type onPhysicalAssetEventNotification(PhysicalAssetEventWldtEvent<?> physicalAssetEventWldtEvent)) containing all the field of the generated physical event.
  • onDigitalActionEvent: On the opposite this method is triggered from one of the active Digital Adapter when an Action request has been received on the Digital Interface. The method receive as parameter an instance of the WLDT Event class DigitalActionWldtEvent<?> digitalActionWldtEvent describing the target digital action request and the associated body.

For the onPhysicalAssetPropertyVariation a simple implementation in charge ONLY of mapping the new Physical Property value into the corresponding DT’State property can be implemented as follows:

The DT State transaction management should be applied in the point of the code where the Shadowing Function receive a variation from the Physical world through a target adapter and the callback method onPhysicalAssetPropertyVariation(...)

@Override
protected void onPhysicalAssetPropertyVariation(PhysicalAssetPropertyWldtEvent<?> physicalPropertyEventMessage) {
    try {

      //Update Digital Twin State  
      //NEW from 0.3.0 -> Start State Transaction
      this.digitalTwinStateManager.startStateTransaction();

      this.digitalTwinState.updateProperty(new DigitalTwinStateProperty<>(physicalPropertyEventMessage.getPhysicalPropertyId(), physicalPropertyEventMessage.getBody()));

      //NEW from 0.3.0 -> Commit State Transaction
      this.digitalTwinStateManager.commitStateTransaction();
      
    } catch (WldtDigitalTwinStatePropertyException | WldtDigitalTwinStatePropertyBadRequestException | WldtDigitalTwinStatePropertyNotFoundException | WldtDigitalTwinStateException e) {
        e.printStackTrace();
    }
}

In this case as reported in the code, we call the method this.digitalTwinState.updateProperty on the Shadowing Function in order to update an existing DT’State property (previously created in the onDigitalTwinBound method). To update the value we directly use the received data on the PhysicalAssetPropertyWldtEvent without any additional check or change that might be instead needed in advanced examples.

Following the same principle, a simplified digital mapping between physical and digital state upon the receving of a physical event variation can be the following:

@Override
protected void onPhysicalAssetEventNotification(PhysicalAssetEventWldtEvent<?> physicalAssetEventWldtEvent) {
    try {
        this.digitalTwinStateManager.notifyDigitalTwinStateEvent(new DigitalTwinStateEventNotification<>(physicalAssetEventWldtEvent.getPhysicalEventKey(), physicalAssetEventWldtEvent.getBody(), physicalAssetEventWldtEvent.getCreationTimestamp()));
    } catch (WldtDigitalTwinStateEventNotificationException | EventBusException e) {
        e.printStackTrace();
    }
}

With respect to events management, we use the Shadowint Function method this.digitalTwinState.notifyDigitalTwinStateEvent to notify the other DT Components (e.g., Digital Adapters) the incoming Physical Event by creating a new instance of a DigitalTwinStateEventNotification class containing all the information associated to the event. Of course, additional controls and checks can be introduced in this method validating and processing the incoming physical message to define complex behaviours.

The last method that we are going to implement is the onDigitalActionEvent one where we have to handle an incoming Digital Action request associated to an Action declared on the DT’s State in the onDigitalTwinBound method. In that case the Digital Action should be forwarded to the Physical Interface in order to be sent to the physical counterpart for the effective execution.

@Override
protected void onDigitalActionEvent(DigitalActionWldtEvent<?> digitalActionWldtEvent) {
    try {
        this.publishPhysicalAssetActionWldtEvent(digitalActionWldtEvent.getActionKey(), digitalActionWldtEvent.getBody());
    } catch (EventBusException e) {
        e.printStackTrace();
    }
}

Also in that case we are forwarding the incoming Digital Action request described through the class DigitalActionWldtEvent to the Physical Adapter with the method of the Shadowing Function denoted as this.publishPhysicalAssetActionWldtEvent and passing directly the action key and the target Body. No additional processing or validation have been introduced here, but they might be required in advanced scenario in order to properly adapt incoming digital action request to what is effectively expected on the physical counterpart.