Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

BaSyx / Introductory Examples / Java / Step 3

Step 3: Creating the oven Submodel

This step will create the oven Submodel and a test application for that Submodel. The AAS and its Submodels can be realized in different ways with Eclipse BaSyx:

  • If the Submodel contains static data only, it can be deserialized from an AASX file, or from a JSON file that defines the Submodel structure and data.
  • If the Submodel provides also dynamic data, but all dynamic data is pushed from outside the Submodel, e.g. from the device, the Submodel can be deserialized from an AASX file, or from a JSON file.
  • If the Submodel also contains active parts, e.g. services or the ability to actively pull data from data sources, e.g. from the device, it can be created using the Eclipse BaSyx SDKs.


Submodels of the same AAS can be created by different means. In this example, we will illustrate the creation of an AAS and of the oven Submodel with the Java SDK. Communication between the oven and the edge device is via an analogues connection that encodes values as current, which must be periodically sampled by the edge device. Commands for controlling the oven are provided via individual digital lines. The edge device therefore needs to periodically sample input values, and set digital output lines to reflect commands to the oven. This device specific interface code is not part of the example. We define a Java interface that enables access to these functions, and that enables us to substitute the real-world edge device with a stub that simulates the device behavior to create a self-contained example.


  1. /**
  2.  * Generic interface for sensors
  3.  */
  4. public interface ISensor {
  5.  
  6. 	/**
  7. 	 * Read sensor value
  8. 	 * 
  9. 	 * @return The last sampled sensor value
  10. 	 */
  11. 	public double readValue();
  12. }


The temperature sensor stub that we are using in this example for testing implements this interface and therefore can be accessed through the readValue() function. The user of this interface therefore does not need to know whether he will be communicating with a real-world asset or with a simulated instance.


Realizing the Submodel

Submodels define all domain and asset specific properties. We will therefore first define a Submodel for the oven. The oven Submodel defines two properties: The ID that identifies this heater, and the temperature that provides access to the current temperature from the temperature sensor. Two operations enable to activate and to deactivate the oven.

The ID of the oven will be a static value. It includes the idShort, a locally unique name, as well as the Identification that has to unique identifier.

  1. Submodel ovenSubmodel = new Submodel("oven", new ModelUrn("heater1"));


The temperature property provides access to the current temperature of the oven. As the oven in our case does provide a very simple interface, the Submodel samples the current temperature value whenever a temperature value is requested. This is realized with a function that is invoked whenever the property value is requested. For this reason, it uses the Oven interface IOven.

  1. // Create a dynamic property that can resolve its value during runtime
  2. // 1. Create a supplier function that can determine the oven temperature using the sensor
  3. Supplier<Object> lambdaReadFunction = () -> oven.getSensor().readValue();
  4.  
  5. // 2. Create a new empty Property
  6. Property dynamicTemperatureProperty = new Property();
  7.  
  8. // 3. Set the id of the new Property
  9. dynamicTemperatureProperty.setIdShort("temperature");
  10.  
  11. // 4. Use the AASLambdaPropertyHelper to add the Getter to the new Property
  12. // NOTE: A setter function is not required (=> null), because a sensor temperature is "read only"
  13. AASLambdaPropertyHelper.setLambdaValue(dynamicTemperatureProperty, lambdaReadFunction, null)
  14.  
  15. // 5. Add that lambda property to the model
  16. ovenSubmodel.addSubmodelElement(dynamicTemperatureProperty);


The temperature is read through a call to the readValue() call of the sensor (obtained via getSensor()). Besides of properties, the Submodel also may export operations.

The Submodel will define two functions: An activate and an deactivate function. Both functions are created as lambda function objects and are placed in the operations map.

  1. // Add a function that activates the oven and implements a functional interface
  2. Function<Object[], Object> activateFunction = (args) -> {
  3. 	oven.getHeater().activate();
  4. 	return null;
  5. };
  6.  
  7. // Encapsulate the function in an operation
  8. Operation activateOperation = new Operation(activateFunction);
  9.  
  10. // Set the id of the operation
  11. activateOperation.setIdShort("activateOven");
  12.  
  13. // Add an operation that activates the oven and implements a functional interface
  14. ovenSubmodel.addSubmodelElement(activateOperation);
  15.  
  16.  
  17. // Add a function that deactivates the oven and implements a functional interface
  18. Function<Object[], Object> deactivateFunction = (args) -> {
  19. 	oven.getHeater().deactivate();
  20. 	return null;
  21. };
  22.  
  23. // Encapsulate the function in an operation
  24. Operation deactivateOperation = new Operation(deactivateFunction);
  25.  
  26. // Set the id of the operation
  27. deactivateOperation.setIdShort("deactivateOven");
  28.  
  29. // Add an operation that deactivates the oven and implements a functional interface
  30. ovenSubmodel.addSubmodelElement(deactivateOperation);


Putting everything together

The complete code is provided below:


  1. 	public static Submodel createMyOvenModel(Oven oven) {
  2. 		// Create an empty Submodel
  3. 		Submodel ovenSubmodel = new Submodel();
  4.  
  5. 		// Set its idShort
  6. 		ovenSubmodel.setIdShort("Oven");
  7.  
  8. 		// Set its unique identification
  9. 		ovenSubmodel.setIdentification(new ModelUrn("heater1"));
  10.  
  11. 		// Now we want to create a dynamic property that can resolve its value during runtime
  12. 		// 1. Create a supplier function that can determine the oven temperature using the sensor
  13. 		Supplier<Object> lambdaReadFunction = () -> oven.getSensor().readValue();
  14. 		// 2. Create a new empty Property
  15. 		Property dynamicTemperatureProperty = new Property();
  16. 		// 3. Set the id of the new Property
  17. 		dynamicTemperatureProperty.setIdShort("temperature");
  18. 		// 4. Use the AASLambdaPropertyHelper to add the Getter to the new Property
  19. 		// NOTE: A setter function is not required (=> null), because a sensor temperature is "read only"
  20. 		AASLambdaPropertyHelper.setLambdaValue(dynamicTemperatureProperty, lambdaReadFunction, null)
  21. 		// 5. Add that lambda property to the model
  22. 		ovenSubmodel.addSubmodelElement(dynamicTemperatureProperty);
  23.  
  24. 		// Add a function that activates the oven and implements a functional interface
  25. 		Function<Object[], Object> activateFunction = (args) -> {
  26. 			oven.getHeater().activate();
  27. 			return null;
  28. 		};
  29. 		// Encapsulate the function in an operation
  30. 		Operation activateOperation = new Operation(activateFunction);
  31. 		// Set the id of the operation
  32. 		activateOperation.setIdShort("activateOven");
  33. 		// Add an operation that activates the oven and implements a functional interface
  34. 		ovenSubmodel.addSubmodelElement(activateOperation);
  35.  
  36.  
  37. 		// Add a function that deactivates the oven and implements a functional interface
  38. 		Function<Object[], Object> deactivateFunction = (args) -> {
  39. 			oven.getHeater().deactivate();
  40. 			return null;
  41. 		};
  42. 		// Encapsulate the function in an operation
  43. 		Operation deactivateOperation = new Operation(deactivateFunction);
  44. 		// Set the id of the operation
  45. 		deactivateOperation.setIdShort("deactivateOven");
  46. 		// Add an operation that deactivates the oven and implements a functional interface
  47. 		ovenSubmodel.addSubmodelElement(deactivateOperation);
  48.  
  49. 		// Return the Submodel
  50. 		return ovenSubmodel;
  51. 	}


Testing the Submodel

It always makes sense to create regression tests that ensure that changes to not have unwanted side effects. Therefore, a Submodel can be tested without an extensive BaSyx deployment. The first step when testing a Submodel is to warp it into a VABLambaProvider that enables access to this Submodel. The code below first creates the Submodel under test and then wraps it into a provider. The model connects to a simulated oven instance using the Oven stub.

  1. // Create a model for an oven device.
  2. Submodel myOvenModel = createMyOvenModel(new Oven());


Now, the model can be used to read/write values, and to execute lambda functions that e.g. preprocess data or access a simulation model during testing.

  1. // Get the Identification of the Submodel
  2. String id = myOvenModel.getIdentification().getId();
  3. System.out.println("Heater id: " + id);
  4.  
  5. // The operations can be invoked via the model provider like this:
  6. IOperation activateOperation = myOvenModel.getOperations().get("activateOven");
  7. activateOperation.invoke();


Conclusion

This second step did illustrate the creation of a Submodel for the oven. This is not yet a complete Asset Administration Shell but a specific domain model that describes the specific properties and services of the oven. The Submodel defines an interface that may be for all ovens that can be activated and deactivated, and that provide the current temperature. More complex systems will define multiple Submodels for their assets, and keep every Submodel focused on a specific aspect. Depending on the type of the asset, many Submodel types are possible, e.g. the nameplate, spare parts and their availability, sensor data, asset health, or offered services.

Back to the top