Configurable Adapters

The WLDT library provides a native method to define Configurable Physical ad Digital Adapters specifying a custom configuration class passed as parameter in the constructor.

Starting with the Physical Adapter created in the previous example DemoPhysicalAdapter instead of extending the base class PhysicalAdapter we can extend now ConfigurablePhysicalAdapter<C> where C is the name of the that we would like to use as configuration.

In our example we can create a simple configuration class called DemoPhysicalAdapterConfiguration where we move the constant variable used to implement the behaviour of our demo physical adapter. The resulting class will be the following:

public class DemoPhysicalAdapterConfiguration {

    private int messageUpdateTime = GlobalKeywords.MESSAGE_UPDATE_TIME;

    private int messageUpdateNumber = GlobalKeywords.MESSAGE_UPDATE_NUMBER;

    private double temperatureMinValue = GlobalKeywords.TEMPERATURE_MIN_VALUE;

    private double temperatureMaxValue = GlobalKeywords.TEMPERATURE_MAX_VALUE;

    public DemoPhysicalAdapterConfiguration() {
    }

    public DemoPhysicalAdapterConfiguration(int messageUpdateTime, int messageUpdateNumber, double temperatureMinValue, double temperatureMaxValue) {
        this.messageUpdateTime = messageUpdateTime;
        this.messageUpdateNumber = messageUpdateNumber;
        this.temperatureMinValue = temperatureMinValue;
        this.temperatureMaxValue = temperatureMaxValue;
    }

    public int getMessageUpdateTime() {
        return messageUpdateTime;
    }

    public void setMessageUpdateTime(int messageUpdateTime) {
        this.messageUpdateTime = messageUpdateTime;
    }

    public int getMessageUpdateNumber() {
        return messageUpdateNumber;
    }

    public void setMessageUpdateNumber(int messageUpdateNumber) {
        this.messageUpdateNumber = messageUpdateNumber;
    }

    public double getTemperatureMinValue() {
        return temperatureMinValue;
    }

    public void setTemperatureMinValue(double temperatureMinValue) {
        this.temperatureMinValue = temperatureMinValue;
    }

    public double getTemperatureMaxValue() {
        return temperatureMaxValue;
    }

    public void setTemperatureMaxValue(double temperatureMaxValue) {
        this.temperatureMaxValue = temperatureMaxValue;
    }
}

Now we can create or update our Physical Adapter extending ConfigurablePhysicalAdapter<DemoPhysicalAdapterConfiguration> as illustrated in the following snippet:

public class DemoPhysicalAdapter extends ConfigurablePhysicalAdapter<DemoPhysicalAdapterConfiguration> { 
  [...]
}

Extending this class also the constructor should be updated getting as a parameter the expected configuration instance. Our constructor will be the following:

public DemoConfPhysicalAdapter(String id, DemoPhysicalAdapterConfiguration configuration) {
    super(id, configuration);
}

After that change since we removed and moved the used constant values into the new Configuration class we have also to update the deviceEmulation() method having access to the configuration through the method getConfiguration() or this.getConfiguration() directly on the adapter.

private Runnable deviceEmulation(){
    return () -> {
        try {


            System.out.println("[DemoPhysicalAdapter] -> Sleeping before Starting Physical Device Emulation ...");

            //Sleep 5 seconds to emulate device startup
            Thread.sleep(10000);

            System.out.println("[DemoPhysicalAdapter] -> Starting Physical Device Emulation ...");

            //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 < getConfiguration().getMessageUpdateNumber(); i++){

                //Sleep to emulate sensor measurement
                Thread.sleep(getConfiguration().getMessageUpdateTime());

                //Update the
                double randomTemperature = getConfiguration().getTemperatureMinValue() + (getConfiguration().getTemperatureMaxValue() - getConfiguration().getTemperatureMinValue()) * 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();
        }
    };
}

A similar approach can be adopted also for the Digital Adapter with the small difference that the base class DigitalAdapter already allow the possibility to specify a configuration. For this reason in the previous example we extended DigitalAdapter<Void> avoiding to specifying a configuration.

In this updated version we can create a new DemoDigitalAdapterConfiguration class containing the parameter association to the emulation of the action and then update our adapter to support the new configuration. Our new configuration class will be:

public class DemoDigitalAdapterConfiguration {

  private int sleepTimeMs = GlobalKeywords.ACTION_SLEEP_TIME_MS;

  private int emulatedActionCount = GlobalKeywords.EMULATED_ACTION_COUNT;

  private double temperatureMinValue = GlobalKeywords.TEMPERATURE_MIN_VALUE;

  private double temperatureMaxValue = GlobalKeywords.TEMPERATURE_MAX_VALUE;

  public DemoDigitalAdapterConfiguration() {
  }

  public DemoDigitalAdapterConfiguration(int sleepTimeMs, int emulatedActionCount, double temperatureMinValue, double temperatureMaxValue) {
    this.sleepTimeMs = sleepTimeMs;
    this.emulatedActionCount = emulatedActionCount;
    this.temperatureMinValue = temperatureMinValue;
    this.temperatureMaxValue = temperatureMaxValue;
  }

  public int getSleepTimeMs() {
    return sleepTimeMs;
  }

  public void setSleepTimeMs(int sleepTimeMs) {
    this.sleepTimeMs = sleepTimeMs;
  }

  public int getEmulatedActionCount() {
    return emulatedActionCount;
  }

  public void setEmulatedActionCount(int emulatedActionCount) {
    this.emulatedActionCount = emulatedActionCount;
  }

  public double getTemperatureMinValue() {
    return temperatureMinValue;
  }

  public void setTemperatureMinValue(double temperatureMinValue) {
    this.temperatureMinValue = temperatureMinValue;
  }

  public double getTemperatureMaxValue() {
    return temperatureMaxValue;
  }

  public void setTemperatureMaxValue(double temperatureMaxValue) {
    this.temperatureMaxValue = temperatureMaxValue;
  }
}

After that we can update the declaration of our Digital Adapter and modify its constructor to accept the configuration. The resulting class will be:

public class DemoDigitalAdapter extends DigitalAdapter<DemoDigitalAdapterConfiguration> { 
  
  public DemoDigitalAdapter(String id, DemoDigitalAdapterConfiguration configuration) {
    super(id, configuration);
  }
  
  [...]
}

Of course the possibility to have this configuration will allow us to improve the emulateIncomingDigitalAction method in the following way having access to the configuration through the method getConfiguration() or this.getConfiguration() directly on the adapter:

private Runnable emulateIncomingDigitalAction(){
    return () -> {
        try {

            System.out.println("[DemoDigitalAdapter] -> Sleeping before Emulating Incoming Digital Action ...");
            Thread.sleep(5000);
            Random random = new Random();

            //Emulate the generation on 'n' temperature measurements
            for(int i = 0; i < getConfiguration().getEmulatedActionCount(); i++){

                //Sleep to emulate sensor measurement
                Thread.sleep(getConfiguration().getSleepTimeMs());

                double randomTemperature = getConfiguration().getTemperatureMinValue() + (getConfiguration().getTemperatureMaxValue() - getConfiguration().getTemperatureMinValue()) * random.nextDouble();
                publishDigitalActionWldtEvent("set-temperature-action-key", randomTemperature);

            }

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

When we have updated both adapters making them configurable we can update our main function in the process that we have previouly device using the updated adapters and passing the configurations:

public class DemoDigitalTwin {

    public static void main(String[] args)  {
        try{

            WldtEngine digitalTwinEngine = new WldtEngine(new DemoShadowingFunction("test-shadowing-function"), "test-digital-twin");

            //Default Physical and Digital Adapter
            //digitalTwinEngine.addPhysicalAdapter(new DemoPhysicalAdapter("test-physical-adapter"));
            //digitalTwinEngine.addDigitalAdapter(new DemoDigitalAdapter("test-digital-adapter"));

            //Physical and Digital Adapters with Configuration
            digitalTwinEngine.addPhysicalAdapter(new DemoConfPhysicalAdapter("test-physical-adapter", new DemoPhysicalAdapterConfiguration()));
            digitalTwinEngine.addDigitalAdapter(new DemoConfDigitalAdapter("test-digital-adapter", new DemoDigitalAdapterConfiguration()));

            digitalTwinEngine.startLifeCycle();

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