DT Relationships
The same management that we have illustrated for Properties, Events and Action can be applied also to Digital Twin Relationships. Relationships represent the links that exist between the modeled physical assets and other physical entity of the organizations through links to their corresponding Digital Twins. Like properties, relationships can be observed, dynamically created, and change over time, but unlike properties, they are not properly part of the PA’s state but of its operational context (e.g., a DT of a robot within a production line).
It is necessary to distinguish between two concepts: i) Relationship
; and ii) Relationship Instance
.
The first one models the relationship from a semantic point of view,
defining its name
and target type
.
The second one represents an instantiation of the concept in reality.
For example, in the context of a Smart Home,
the Home Digital Twin (DT) will define a Relationship called has_room
which has possible targets represented by DTs that represent different rooms of the house.
The actual link between the Home DT and the Bedroom DT
will be modeled by a specific Relationship Instance of the has_room
relationship.
Within the state of the DT, it is necessary to
differentiate between the concept of a relationship and that of an instance of a relationship.
In the first case, we refer to a semantic concept where each relationship,
through its name
and the semantic type
of its target,
determines the different type of link that the DT can establish.
On the other hand, an instanc
of a relationship represents the concrete
link present between the DT that establishes it and the target DT.
For instance, in the case of a Smart Home,
the Bedroom DT may have two relationships in its model: one named is_room_of
and another called has_device
.
An instance of the first type of relationship could, for example,
have the Home DT as its target, while the has_device
relationship could have
multiple instances, one for each device present in the room.
An example of a possible instance is one targeting the Air Conditioner DT.
From an implementation perspective, in the Physical Adapter and in particular where we handle the definition of the PAD we can also
specify the existing relationships. In our case, since the Relationship is useful also to define its future instance we
keep a reference of the relationship as in internal variable called insideInRelationship
.
Then we can update the code as follows:
private PhysicalAssetRelationship<String> insideInRelationship = null;
@Override
public void onIncomingPhysicalAction(PhysicalAssetActionWldtEvent<?> physicalAssetActionWldtEvent) {
try{
[...]
//Create Test Relationship to describe that the Physical Device is inside a building
this.insideInRelationship=new PhysicalAssetRelationship<>("insideId");
pad.getRelationships().add(insideInRelationship);
[...]
} catch (Exception e){
e.printStackTrace();
}
}
Of course always in the Physical Adapter we need to publish an effective instance of the definite Relationship. To do that, we have defined a dedicated method that we can call inside the adapter to notify the DT’s Core and in particular the Shadowing Function on the presence of a new Relationship.
The following method can be added for example at the beginning of the Device Emulation:
private void publishPhysicalRelationshipInstance() {
try{
String relationshipTarget = "building-hq";
Map<String, Object> relationshipMetadata = new HashMap<>();
relationshipMetadata.put("floor", "f0");
relationshipMetadata.put("room", "r0");
PhysicalAssetRelationshipInstance<String> relInstance = this.insideInRelationship.createRelationshipInstance(relationshipTarget, relationshipMetadata);
PhysicalAssetRelationshipInstanceCreatedWldtEvent<String> relInstanceEvent = new PhysicalAssetRelationshipInstanceCreatedWldtEvent<>(relInstance);
publishPhysicalAssetRelationshipCreatedWldtEvent(relInstanceEvent);
}catch (Exception e){
e.printStackTrace();
}
}
On the other hand, as already done for all the other Properties, Actions and Events we have to handle them on the
Shadowing Function and in particular updating the onDigitalTwinBound(...)
method managing Relationship declaration.
Also for the Relationships there is the method denoted as observePhysicalAssetRelationship(relationship)
to observe the variation
of the target entity.
@Override
protected void onDigitalTwinBound(Map<String, PhysicalAssetDescription> adaptersPhysicalAssetDescriptionMap) {
try{
//Iterate over all the received PAD from connected Physical Adapters
adaptersPhysicalAssetDescriptionMap.values().forEach(pad -> {
[...]
//Iterate over Physical Relationships
pad.getRelationships().forEach(relationship -> {
try{
if(relationship != null && relationship.getName().equals(GlobalKeywords.INSIDE_IN_RELATIONSHIP)){
DigitalTwinStateRelationship<String> insideInDtStateRelationship = new DigitalTwinStateRelationship<>(relationship.getName(), relationship.getName());
this.digitalTwinState.createRelationship(insideInDtStateRelationship);
observePhysicalAssetRelationship(relationship);
}
}catch (Exception e){
e.printStackTrace();
}
});
});
[...]
}catch (Exception e){
e.printStackTrace();
}
}
When an Instance for a target observed Relationship has been notified by the Physical Adapter, we will receive a call back on the
Shadowing Function method called: onPhysicalAssetRelationshipEstablished(PhysicalAssetRelationshipInstanceCreatedWldtEvent<?> physicalAssetRelationshipInstanceCreatedWldtEvent)
.
The object PhysicalAssetRelationshipInstanceCreatedWldtEvent
describes the events and contains an object PhysicalAssetRelationshipInstance
with all the information about the new Relationship Instance.
The Shadowing Function analyzes the instance and create the corresponding Digital Relationship instance on the DT’State
through the class DigitalTwinStateRelationshipInstance
and the method this.digitalTwinState.addRelationshipInstance(relName, instance);
.
The resulting implemented method is the following:
//// Physical Relationships Notification Callbacks ////
@Override
protected void onPhysicalAssetRelationshipEstablished(PhysicalAssetRelationshipInstanceCreatedWldtEvent<?> physicalAssetRelationshipInstanceCreatedWldtEvent) {
try{
if(physicalAssetRelationshipInstanceCreatedWldtEvent != null
&& physicalAssetRelationshipInstanceCreatedWldtEvent.getBody() != null){
PhysicalAssetRelationshipInstance<?> paRelInstance = physicalAssetRelationshipInstanceCreatedWldtEvent.getBody();
if(paRelInstance.getTargetId() instanceof String){
String relName = paRelInstance.getRelationship().getName();
String relKey = paRelInstance.getKey();
String relTargetId = (String)paRelInstance.getTargetId();
DigitalTwinStateRelationshipInstance<String> instance = new DigitalTwinStateRelationshipInstance<String>(relName, relTargetId, relKey);
//Update Digital Twin State
//NEW from 0.3.0 -> Start State Transaction
this.digitalTwinStateManager.startStateTransaction();
this.digitalTwinStateManager.addRelationshipInstance(instance);
//NEW from 0.3.0 -> Commit State Transaction
this.digitalTwinStateManager.commitStateTransaction();
}
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
protected void onPhysicalAssetRelationshipDeleted(PhysicalAssetRelationshipInstanceDeletedWldtEvent<?> physicalAssetRelationshipInstanceDeletedWldtEvent) {
}
At the end the new DT’s Relationships and the associated instances can be managed
on a Digital Adapter using the onDigitalTwinSync(IDigitalTwinState currentDigitalTwinState)
method and
the following DT state callback method: onStateUpdate()
.
For example a simple implementation logging on the console can be:
@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
[...]
// Specific log example for Relationships Instance Variation
if(resourceType.equals(DigitalTwinStateChange.ResourceType.RELATIONSHIP_INSTANCE))
System.out.println("New Relationship Instance operation:" + operation + " Resource:" + resource);
}
} else {
// No state changes
System.out.println("No state changes detected.");
}
}