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 DTevents
: List of Events that can be generated by the DTrelationships
: 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 KeycontainsProperty(String propertyKey)
: Checks if a target Property Key is already available in the current Digital Twin’s StategetPropertyList()
: Loads the list of available Properties (described by the class DigitalTwinStateProperty) available on the Digital Twin’s StatecreateProperty(DigitalTwinStateProperty<?> dtStateProperty)
: Allows the creation of a new Property on the Digital Twin’s State through the class DigitalTwinStatePropertyreadProperty(String propertyKey)
: Retrieves if present the target DigitalTwinStateProperty by KeyupdateProperty(DigitalTwinStateProperty<?> dtStateProperty)
: Updates the target property using the DigitalTwinStateProperty and the associated Property Key fielddeleteProperty(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 registeredgetAction(String actionKey)
: Loads the target DigitalTwinStateAction by keygetActionList()
: Gets the list of available Actions registered on the Digital Twin’s StateenableAction(DigitalTwinStateAction digitalTwinStateAction)
: Enables and registers the target Action described through an instance of the DigitalTwinStateAction classupdateAction(DigitalTwinStateAction digitalTwinStateAction)
: Update the already registered target Action described through an instance of the DigitalTwinStateAction classdisableAction(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 registeredgetEvent(String eventKey)
: Return the description of a registered Digital Twin State Event according to its KeygetEventList()
: Return the list of existing and registered Digital Twin State EventsregisterEvent(DigitalTwinStateEvent digitalTwinStateEvent)
: Register a new Digital Twin State EventupdateRegisteredEvent(DigitalTwinStateEvent digitalTwinStateEvent)
: Update the registration and signature of an existing Digital Twin State EventunRegisterEvent(String eventKey)
: Un-register a Digital Twin State EventnotifyDigitalTwinStateEvent(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 StatecreateRelationship(DigitalTwinStateRelationship<?> relationship)
: Creates a new Relationships (described by the class DigitalTwinStateRelationship) in the Digital Twin’s StateaddRelationshipInstance(String name, DigitalTwinStateRelationshipInstance<?> instance)
: Adds a new Relationship instance described through the class DigitalTwinStateRelationshipInstance and identified through its namegetRelationshipList()
: Loads the list of existing relationships on the Digital Twin’s State through a list of DigitalTwinStateRelationshipgetRelationship(String name)
: Gets a target Relationship identified through its name and described through the class DigitalTwinStateRelationshipdeleteRelationship(String name)
: Deletes a target Relationship identified through its namedeleteRelationshipInstance(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 calledPhysicalAssetPropertyWldtEvent<?> 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 typeonPhysicalAssetEventNotification(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 classDigitalActionWldtEvent<?> 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.