java.lang.Object
com.scaleoutsoftware.digitaltwin.development.Workbench
All Implemented Interfaces:
AutoCloseable

public class Workbench extends Object implements AutoCloseable
The Workbench is used to represent an environment where developers can test real-time and simulated digital twins.

Quick start:

Build a real-time digital twin model for testing real-time message processing with messages generated by a simulated digital twin.

The real-time model will represent a car and the simulated digital twin will represent a pump increasing the real-time car's tire pressure. The real-time car will process messages from the simulated pump and send information back to the simulated pump when the tire is full.

The quickstart will demonstrate the following:

  • Define a real-time car digital twin
  • Define a tire pressure change message
  • Define a real-time car message processor
  • Define a simulated pump digital twin
  • Define a simulated pump message processor
  • Define a simulated pump simulation processor

Defining the real-time car model:

Create a class that extends the DigitalTwinBase class and add an integer property for tire pressure. When constructed by the Workbench this will be our real-time car instance.

     public class RealTimeCar extends DigitalTwinBase {
         private int _tirePressure;
         public RealTimeCar() { _tirePressure=0; }
         public RealTimeCar(int startingTirePressure) {
             _tirePressure = startingTirePressure;
         }

         public void incrementTirePressure(int increment) {
             _tirePressure += increment;
         }

         public int getTirePressure() {
             return _tirePressure;
         }
     }
 

Defining the tire pressure change message:

Implement a message to send from the simulated pump, to the real-time model. The message will tell the real-time car to increase the tire pressure by some value.

     public class TirePressureMessage {
         final int _pressureChange;
         public TirePressureMessage(int pressureChange) {
             _pressureChange = pressureChange;
         }

         public int getPressureChange() {
             return _pressureChange;
         }
     }
 

Defining the real-time car message processor:

Create the real-time car MessageProcessor. The message processor will apply the tire pressure change from the tire pressure message to the real-time car digital twin instance. When the tire is full, the message processor will send a message back to the simulated pump.

     public class RealTimeCarMessageProcessor extends MessageProcessor<RealTimeCar, TirePressureMessage> implements Serializable {
         final int TIRE_PRESSURE_FULL = 100;
         public ProcessingResult processMessages(ProcessingContext processingContext, RealTimeCar car, Iterable<TirePressureMessage> messages) throws Exception {
             // apply the updates from the messages
             for(TirePressureMessage message : messages) {
                 car.incrementTirePressure(message.getPressureChange());
             }
             if(car.getTirePressure() > TIRE_PRESSURE_FULL) {
                 processingContext.sendToDataSource(new TirePressureMessage(car.getTirePressure()));
             }
             return ProcessingResult.UpdateDigitalTwin;
         }
     }
 

Defining the simulated pump model:

Create a class that extends the DigitalTwinBase class and add a double property for tire pressure change. When constructed by the Workbench this will be our simulated pump instance.

     public class SimulatedPump extends DigitalTwinBase {
     private double _tirePressureChange;
     private boolean _tirePressureReached = false;
     public SimulatedPump() {}
     public SimulatedPump(double pressureChange) {
         _tirePressureChange = pressureChange;
     }

     public double getTirePressureChange() {
         return _tirePressureChange;
     }

     public void setTirePressureReached() {
         _tirePressureReached = true;
     }

     public boolean isTireFull() {
         return _tirePressureReached;
     }
 }
 

Defining the simulated pump message processor:

The simulated pump should stop when the simulated pump message processor receives a message. The simulated pump message processor will update the state of the simulated pump indicating that the tire is full.

     public class PumpMessageProcessor extends MessageProcessor<SimulatedPump, TirePressureMessage> implements Serializable {
         public ProcessingResult processMessages(ProcessingContext processingContext, SimulatedPump pump, Iterable<TirePressureMessage> messages) throws Exception {
             // apply the updates from the messages
             pump.setTirePressureReached();
             return ProcessingResult.UpdateDigitalTwin;
         }
     }
 

Defining the pump simulation processor:

Define the simulated pump SimulationProcessor. This piece of code will be called at each simulation interval so long as the simulation has instances to run. This pump simulation processor will send a message to the real-time car with a tire pressure change derived from the state of the simulated pump. While the simulated pump has not been told to stop, it will continue sending tire pressure changes to the real-time car.

     public class PumpSimulationProcessor extends SimulationProcessor<SimulatedPump> implements Serializable {
         public ProcessingResult processModel(ProcessingContext processingContext, SimulatedPump simPump, Date date) {
             SimulationController controller = processingContext.getSimulationController();
             if(simPump.isTireFull()) {
                 controller.deleteThisInstance();
             } else {
                 int change = (int) (100 * simPump.getTirePressureChange());
                 controller.emitTelemetry("RealTimeCar", new TirePressureMessage(change));
             }
             return ProcessingResult.UpdateDigitalTwin;
         }
     }
 

Using the workbench:

The real-time and simulation models are complete. The workbench can now load up the models and then run a simulation. When beginning testing, the "step loop" is used to track the state of the twins and the simulation. Instantiate the workbench and add the models.

     Workbench workbench = new Workbench();
     workbench.addRealTimeModel("RealTimeCar", new RealTimeCarMessageProcessor(), RealTimeCar.class, TirePressureMessage.class);
     workbench.addSimulationModel("SimPump", new SimulatedPumpMessageProcessor(), new PumpSimulationProcessor(), SimulationPump.class, TirePressureMessage.class);
 

The workbench is loaded up with the models. Add a single simulated pump instance. Note that no real-time car digital twin is created and added to the workbench. The first message from the simulated pump digital twin will cause the workbench to create a new real-time instance.

     workbench.addInstance("SimPump", "23", new SimulationPump(0.29d));
 

Initialize the simulation and then step through the simulation intervals. Start the simulation now and end the simulation in 60 seconds.

     SimulationStep step = workbench.initializeSimulation(System.currentTimeMillis(), System.currentTimeMillis()+60000, 1000);
 

At each interval, view the state of the real-time car to ensure the tire pressure is changing as expected.

     while(step.getStatus() == SimulationStatus.Running) {
         step = workbench.step();
         HashMap<String, DigitalTwinBase> realTimeCars = workbench.getInstances("RealTimeCar");
         RealTimeCar rtCar = (RealTimeCar) realTimeCars.get("23");
         System.out.println("rtCar: " + rtCar.getTirePressure());
     }
 

Summary:

The simulated pump at each simulation step emits telemetry to the real-time car digital twin. With each tire pressure change message, the real-time car twin accrues state about the pressure of the tire. When the real-time car twins tire is full, it sends a message back to the simulated pump. When the simulated pump receives a message from the real-time car, it updates some internal state indicating to stop pumping. During the next simulation interval, the simulated pump deletes itself to stop pumping. This completes the simulation as there are no more remaining simulated pumps.

  • Constructor Details

    • Workbench

      public Workbench()
      Instantiate the workbench.
  • Method Details

    • addRealTimeModel

      public <T extends DigitalTwinBase, V> void addRealTimeModel(String modelName, MessageProcessor<T,V> digitalTwinMessageProcessor, Class<T> dtType, Class<V> messageClass) throws WorkbenchException
      Adds a real-time digital twin model to the workbench.
      Type Parameters:
      T - the type of the digital twin.
      V - the type of the message.
      Parameters:
      modelName - the name of the model.
      digitalTwinMessageProcessor - the model's MessageProcessor implementation. Must be marked as Serializable.
      dtType - the model's DigitalTwinBase implementation.
      messageClass - the model's message type.
      Throws:
      WorkbenchException - if any of the parameters are null or the model does not pass validation (the message processor must be serializable, and the digital twin implementation must have a parameterless constructor).
    • addSimulationModel

      public <T extends DigitalTwinBase, V> void addSimulationModel(String modelName, MessageProcessor<T,V> digitalTwinMessageProcessor, SimulationProcessor<T> simulationProcessor, Class<T> dtType, Class<V> messageClass) throws WorkbenchException
      Adds a simulation digital twin model to the workbench.
      Type Parameters:
      T - the type of the digital twin.
      V - the type of the message.
      Parameters:
      modelName - the name of the model.
      digitalTwinMessageProcessor - the model's MessageProcessor implementation. Must be marked as Serializable.
      simulationProcessor - the model's SimulationProcessor implementation. Must be marked as Serializable.
      dtType - the model's DigitalTwinBase implementation.
      messageClass - the model's message type.
      Throws:
      WorkbenchException - if any of the parameters are null or the model does not pass validation (the message processor must be serializable, and the digital twin implementation must have a parameterless constructor).
    • addInstance

      public void addInstance(String modelName, String id, DigitalTwinBase instance) throws WorkbenchException
      Adds a digital twin instance to the workbench. Instances cannot be added to the workbench after runSimulation(long, long, double, long) or initializeSimulation(long, long, long) has been called.
      Parameters:
      modelName - the instances model.
      id - the instance identifier.
      instance - the real-time or simulation instance.
      Throws:
      WorkbenchException - If the model does not exist or if a simulation is already running.
    • addAlertProvider

      public void addAlertProvider(String modelName, AlertProviderConfiguration configuration) throws WorkbenchException
      Adds an alert provider configuration to the specified model on this workbench. Alert provider configurations cannot be added to the workbench after runSimulation(long, long, double, long) or initializeSimulation(long, long, long) has been called.
      Parameters:
      modelName - the instances model.
      configuration - the alert provider configuration.
      Throws:
      WorkbenchException - If the model does not exist or if a simulation is already running.
    • runSimulation

      public SimulationStep runSimulation(long startTime, long endTime, double speedup, long interval) throws WorkbenchException
      Runs a simulation from the given startTime until the given endTime OR there is no more work to do. A simulation has reached the end time when the time to run the next interval is greater than the end time or there are no more simulated twins to run.
      Parameters:
      startTime - the start time of the simulation.
      endTime - the end time of the simulation.
      speedup - the speedup of the interval (in real-time).
      interval - the interval between simulation steps.
      Returns:
      a SimulationStep that details the final runtime and the SimulationStatus.
      Throws:
      WorkbenchException - if an exception is thrown by the simulated model or real-time model.
    • initializeSimulation

      public SimulationStep initializeSimulation(long startTime, long endTime, long interval)
      Initializes the simulation so that each interval can be run separately by calling the step() function.
      Parameters:
      startTime - the start time of the simulation.
      endTime - the end time of the simulation.
      interval - the interval between simulation steps.
      Returns:
      a SimulationStep that details the startTime and the SimulationStatus -- which will always be SimulationStatus.Running.
    • step

      public SimulationStep step() throws WorkbenchException
      Run the next simulation interval.
      Returns:
      a SimulationStep that shows the time interval that was run and the corresponding SimulationStatus
      Throws:
      WorkbenchException - if an exception is thrown by the simulated model or real-time model.
    • getTime

      public Date getTime() throws WorkbenchException
      Retrieves the current time interval of the simulation.
      Returns:
      a Date representation for the current interval time for the simulation.
      Throws:
      WorkbenchException - if the simulation is not started or initialized.
    • peek

      public Date peek() throws WorkbenchException
      Retrieves the next interval time of the simulation.
      Returns:
      a Date representation for the next interval time for the simulation.
      Throws:
      WorkbenchException - if the simulation is not started or initialized.
    • getInstances

      public HashMap<String,DigitalTwinBase> getInstances(String modelName) throws WorkbenchException
      Retrieves DigitalTwin instances for a given model.
      Parameters:
      modelName - the digital twin model name
      Returns:
      the instances associated with the parameter model
      Throws:
      WorkbenchException - if an exception occurs while retrieving digital twin instances for the parameter modelName
    • getLoggedMessages

      public List<LogMessage> getLoggedMessages(String model, long timestamp)
      Retrieves messages logged by digital twin instances for a specified mdoel. If the provided timestamp is 0, all messages will be returned. Timestamps greater than 0 will return a sublist of logged messages where the first message in the returned list will be greater than the provided timestamp. If no messages exist after the timestamp, the returned list will be empty.
      Parameters:
      model - the model name for the logged messages.
      timestamp - the timestamp used to filter the retrieved list.
      Returns:
      the list of messages defined by the timestamp
    • getAlertMessages

      public List<AlertMessage> getAlertMessages(String model, String alertProvider) throws WorkbenchException
      Retrieves alert messages from digital twin instances.
      Parameters:
      model - the model to retrieve alert messages from.
      alertProvider - the alert provider that generated the alerts.
      Returns:
      the list of alert messages generated by digital twin instances.
      Throws:
      WorkbenchException - if an exception occurs while retrieving logged messages.
    • generateModelSchema

      public String generateModelSchema(String modelName) throws WorkbenchException
      Generates a ModelSchema for the defined model
      Parameters:
      modelName - the digital twin model's name to generate a schema.
      Returns:
      a JSON string of the model's schema
      Throws:
      WorkbenchException - if an exception occurs while generating a model schema.
    • generateModelSchema

      public String generateModelSchema(String modelName, String outputDirectory) throws WorkbenchException
      Generates a ModelSchema for the parameter modelName and writes the schema to a file on the file system. If the parameter outputDirectory is null the file will be written to the working directory of the JVM.
      Parameters:
      modelName - the name of the digital twin model
      outputDirectory - the directory to write the file to, or null to write the file to the current working directory.
      Returns:
      the full file path of the model.json schema file
      Throws:
      WorkbenchException - if an exception occurs while generating a model schema.
    • send

      public SendingResult send(String modelName, String id, List<Object> messages) throws WorkbenchException
      Send a list of messages to a real-time or simulation model.
      Parameters:
      modelName - The model name.
      id - the instance id.
      messages - the messages to send.
      Returns:
      SendingResult.Handled unless an exception is thrown.
      Throws:
      WorkbenchException - if model name, id, or messages are null. Also thrown if the model's MessageProcessor.processMessages(ProcessingContext, DigitalTwinBase, Iterable) throws an exception.
    • close

      public void close() throws Exception
      Specified by:
      close in interface AutoCloseable
      Throws:
      Exception