Physical Adapter
The developer can use an existing Physical Adapter or create a new one to handle the communication with a specific physical twin. In this documentation we focus on the creation of a new Physical Adapter in order to explain library core functionalities. However, existing Physical Adapters can be found on the official repository and linked in the core documentation and webpage (WLDT-GitHub).
In general WLDT Physical Adapter extends the class PhysicalAdapter
and it is responsible to talk with the physical world and handling the following main tasks:
- Generate a PAD describing the properties, events, actions and relationships available on the physical twin using the class
PhysicalAssetDescription
- Generate Physical Event using the class
PhysicalAssetEventWldtEvent
associated to the variation of any aspect of the physical state (properties, events, and relationships) - Handle action request coming from the Digital World through the DT Shadowing Function by implementing the method
onIncomingPhysicalAction
and processing events modeled through the classPhysicalAssetActionWldtEvent
Create a new class called DemoPhysicalAdapter
extending the library class PhysicalAdapter
and implement the following methods:
onAdapterStart
: A callback method used to notify when the adapter has been effectively started withing the DT’s life cycleonAdapterStop
: A call method invoked when the adapter has been stopped and will be dismissed by the coreonIncomingPhysicalAction
: The callback method called when a newPhysicalAssetActionWldtEvent
is sent by the Shadowing Function upon the receiving of a valid Digital Action through a Digital Adapter
Then you have to create a constructor for your Physical Adapter with a single String parameter representing the id of the adapter. This id will be used internally by the library to handle and coordinate multiple adapters, adapts logs and execute functions upon the arrival of a new event. The resulting empty class will the following:
public class DemoPhysicalAdapter extends PhysicalAdapter {
public DemoPhysicalAdapter(String id) {
super(id);
}
@Override
public void onIncomingPhysicalAction(PhysicalAssetActionWldtEvent<?> physicalAssetActionWldtEvent) {
}
@Override
public void onAdapterStart() {
}
@Override
public void onAdapterStop() {
}
}
In our test Physical Adapter example we are going to emulate the communication with an Internet of Things device with the following sensing and actuation characteristics:
- A Temperature Sensor generating data about new measurements
- The possibility to generate OVER-HEATING events
- An action to set the target desired temperature value
The first step will be to generate and publish the PhysicalAssetDescription
(PAD) to describe the capabilities and the characteristics of our object allowing
the Shadowing Function to decide how to digitalize its physical counterpart.
Of course in our case the PAD is generated manually but according to the nature of the
connected physical twin it can be automatically generated starting from a discovery or a configuration passed to the adapter.
The generation of the PAD for each active Physical Adapter is the fundamental DT process to handle the binding procedure and to allow the Shadowing Function and consequently the core of the twin to be aware of what is available in the physical world and consequently decide what to observe and digitalize.
In order to publish the PAD we can update the onAdapterStart method with the following lines of code:
private final static String TEMPERATURE_PROPERTY_KEY = "temperature-property-key";
private final static String OVERHEATING_EVENT_KEY = "overheating-event-key";
private final static String SET_TEMPERATURE_ACTION_KEY = "set-temperatura-action-key";
@Override
public void onAdapterStart() {
try {
//Create an empty PAD
PhysicalAssetDescription pad = new PhysicalAssetDescription();
//Add a new Property associated to the target PAD with a key and a default value
PhysicalAssetProperty<Double> temperatureProperty = new PhysicalAssetProperty<Double>(TEMPERATURE_PROPERTY_KEY, 0.0);
pad.getProperties().add(temperatureProperty);
//Add the declaration of a new type of generated event associated to a event key
// and the content type of the generated payload
PhysicalAssetEvent overheatingEvent = new PhysicalAssetEvent(OVERHEATING_EVENT_KEY, "text/plain");
pad.getEvents().add(overheatingEvent);
//Declare the availability of a target action characterized by a Key, an action type
// and the expected content type and the request body
PhysicalAssetAction setTemperatureAction = new PhysicalAssetAction(SET_TEMPERATURE_ACTION_KEY, "temperature.actuation", "text/plain");
pad.getActions().add(setTemperatureAction);
//Notify the new PAD to the DT's Shadowing Function
this.notifyPhysicalAdapterBound(pad);
//TODO add here the Device Emulation method
} catch (PhysicalAdapterException | EventBusException e) {
e.printStackTrace();
}
}
Now we need a simple code to emulate the generation of new temperature measurements and over-heating events. In a real Physical Adapter implementation we have to implement the real communication with the physical twin in order to read its state variation over time according to the supported protocols. In our simplified Physical Adapter we can the following function:
private final static int MESSAGE_UPDATE_TIME = 1000;
private final static int MESSAGE_UPDATE_NUMBER = 10;
private final static double TEMPERATURE_MIN_VALUE = 20;
private final static double TEMPERATURE_MAX_VALUE = 30;
private Runnable deviceEmulation(){
return () -> {
try {
//Sleep 5 seconds to emulate device startup
Thread.sleep(5000);
//Create a new random object to emulate temperature variations
Random r = new Random();
//Publish an initial Event for a normal condition
publishPhysicalAssetEventWldtEvent(new PhysicalAssetEventWldtEvent<>(OVERHEATING_EVENT_KEY, "normal"));
//Emulate the generation on 'n' temperature measurements
for(int i = 0; i < MESSAGE_UPDATE_NUMBER; i++){
//Sleep to emulate sensor measurement
Thread.sleep(MESSAGE_UPDATE_TIME);
//Update the
double randomTemperature = TEMPERATURE_MIN_VALUE + (TEMPERATURE_MAX_VALUE - TEMPERATURE_MIN_VALUE) * r.nextDouble();
//Create a new event to notify the variation of a Physical Property
PhysicalAssetPropertyWldtEvent<Double> newPhysicalPropertyEvent = new PhysicalAssetPropertyWldtEvent<>(TEMPERATURE_PROPERTY_KEY, randomTemperature);
//Publish the WLDTEvent associated to the Physical Property Variation
publishPhysicalAssetPropertyWldtEvent(newPhysicalPropertyEvent);
}
//Publish a demo Physical Event associated to a 'critical' overheating condition
publishPhysicalAssetEventWldtEvent(new PhysicalAssetEventWldtEvent<>(OVERHEATING_EVENT_KEY, "critical"));
} catch (EventBusException | InterruptedException e) {
e.printStackTrace();
}
};
}
Now we have to call the deviceEmulationFunction()
inside the onAdapterStart()
triggering its execution and emulating the physical counterpart of our DT.
To do that add the following line at the end of the onAdapterStart()
method after the this.notifyPhysicalAdapterBound(pad);
.
The last step will be to handle an incoming action trying to set a new temperature on the device by implementing the method onIncomingPhysicalAction()
.
This method will receive a PhysicalAssetActionWldtEvent<?> physicalAssetActionWldtEvent
associated to the action request generated by the shadowing function.
Since a Physical Adapter can handle multiple action we have to check both action-key
and body
type in order to properly process the action (in our case just logging the request).
The new update method will result like this:
@Override
public void onIncomingPhysicalAction(PhysicalAssetActionWldtEvent<?> physicalAssetActionWldtEvent) {
try{
if(physicalAssetActionWldtEvent != null
&& physicalAssetActionWldtEvent.getActionKey().equals(SET_TEMPERATURE_ACTION_KEY)
&& physicalAssetActionWldtEvent.getBody() instanceof String) {
System.out.println("Received Action Request: " + physicalAssetActionWldtEvent.getActionKey()
+ " with Body: " + physicalAssetActionWldtEvent.getBody());
}
else
System.err.println("Wrong Action Received !");
}catch (Exception e){
e.printStackTrace();
}
}
The overall class will result as following:
import it.wldt.adapter.physical.*;
import it.wldt.adapter.physical.event.PhysicalAssetActionWldtEvent;
import it.wldt.adapter.physical.event.PhysicalAssetEventWldtEvent;
import it.wldt.adapter.physical.event.PhysicalAssetPropertyWldtEvent;
import it.wldt.exception.EventBusException;
import it.wldt.exception.PhysicalAdapterException;
import java.util.Random;
public class DemoPhysicalAdapter extends PhysicalAdapter {
private final static String TEMPERATURE_PROPERTY_KEY = "temperature-property-key";
private final static String OVERHEATING_EVENT_KEY = "overheating-event-key";
private final static String SET_TEMPERATURE_ACTION_KEY = "set-temperature-action-key";
private final static int MESSAGE_UPDATE_TIME = 1000;
private final static int MESSAGE_UPDATE_NUMBER = 10;
private final static double TEMPERATURE_MIN_VALUE = 20;
private final static double TEMPERATURE_MAX_VALUE = 30;
public DemoPhysicalAdapter(String id) {
super(id);
}
@Override
public void onIncomingPhysicalAction(PhysicalAssetActionWldtEvent<?> physicalAssetActionWldtEvent) {
try{
if(physicalAssetActionWldtEvent != null
&& physicalAssetActionWldtEvent.getActionKey().equals(SET_TEMPERATURE_ACTION_KEY)
&& physicalAssetActionWldtEvent.getBody() instanceof Double) {
System.out.println("Received Action Request: " + physicalAssetActionWldtEvent.getActionKey()
+ " with Body: " + physicalAssetActionWldtEvent.getBody());
}
else
System.err.println("Wrong Action Received !");
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void onAdapterStart() {
try {
//Create an empty PAD
PhysicalAssetDescription pad = new PhysicalAssetDescription();
//Add a new Property associated to the target PAD with a key and a default value
PhysicalAssetProperty<Double> temperatureProperty = new PhysicalAssetProperty<Double>(GlobalKeywords.TEMPERATURE_PROPERTY_KEY, 0.0);
pad.getProperties().add(temperatureProperty);
//Add the declaration of a new type of generated event associated to a event key
// and the content type of the generated payload
PhysicalAssetEvent overheatingEvent = new PhysicalAssetEvent(GlobalKeywords.OVERHEATING_EVENT_KEY, "text/plain");
pad.getEvents().add(overheatingEvent);
//Declare the availability of a target action characterized by a Key, an action type
// and the expected content type and the request body
PhysicalAssetAction setTemperatureAction = new PhysicalAssetAction(GlobalKeywords.SET_TEMPERATURE_ACTION_KEY, "temperature.actuation", "text/plain");
pad.getActions().add(setTemperatureAction);
//Notify the new PAD to the DT's Shadowing Function
this.notifyPhysicalAdapterBound(pad);
//Start Device Emulation
new Thread(deviceEmulation()).start();
} catch (PhysicalAdapterException | EventBusException e) {
e.printStackTrace();
}
}
@Override
public void onAdapterStop() {
}
private Runnable deviceEmulation(){
return () -> {
try {
//Sleep 5 seconds to emulate device startup
Thread.sleep(5000);
//Create a new random object to emulate temperature variations
Random r = new Random();
//Publish an initial Event for a normal condition
publishPhysicalAssetEventWldtEvent(new PhysicalAssetEventWldtEvent<>(GlobalKeywords.OVERHEATING_EVENT_KEY, "normal"));
//Emulate the generation on 'n' temperature measurements
for(int i = 0; i < GlobalKeywords.MESSAGE_UPDATE_NUMBER; i++){
//Sleep to emulate sensor measurement
Thread.sleep(GlobalKeywords.MESSAGE_UPDATE_TIME);
//Update the
double randomTemperature = GlobalKeywords.TEMPERATURE_MIN_VALUE + (GlobalKeywords.TEMPERATURE_MAX_VALUE - GlobalKeywords.TEMPERATURE_MIN_VALUE) * r.nextDouble();
//Create a new event to notify the variation of a Physical Property
PhysicalAssetPropertyWldtEvent<Double> newPhysicalPropertyEvent = new PhysicalAssetPropertyWldtEvent<>(GlobalKeywords.TEMPERATURE_PROPERTY_KEY, randomTemperature);
//Publish the WLDTEvent associated to the Physical Property Variation
publishPhysicalAssetPropertyWldtEvent(newPhysicalPropertyEvent);
}
//Publish a demo Physical Event associated to a 'critical' overheating condition
publishPhysicalAssetEventWldtEvent(new PhysicalAssetEventWldtEvent<>(GlobalKeywords.OVERHEATING_EVENT_KEY, "critical"));
} catch (EventBusException | InterruptedException e) {
e.printStackTrace();
}
};
}
}
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.