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

< BaSyx ‎ | Introductory Examples
Revision as of 08:03, 7 April 2021 by Thomas.kuhn.iese.fraunhofer.de (Talk | contribs) (Step 2: Creating the oven sub model)

Step 2: Creating the oven sub model

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

  • If the sub model contains static data only, it can be deserialized from an AASX file, or from a JSON file that defines the sub model structure and data.
  • If the sub model provides also dynamic data, but all dynamic data is pushed from outside the sub model, e.g. from the device, the sub model can be deserialized from an AASX file, or from a JSON file.
  • If the sub model 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.


Sub models 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 sub model 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.


/**
 * Generic interface for sensors
 */
public interface ISensor {
 
	/**
	 * Read sensor value
	 * 
	 * @return The last sampled sensor value
	 */
	public double readValue();
}


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 sub model

Sub models define all domain and asset specific properties. We will therefore first define a sub model for the oven. The oven sub model 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.

Currently, Eclipse BaSyx handles sub model definitions as hash maps. A map will therefore contain all properties and services of a sub model, which are stored as maps as well. The first map that we create for the oven sub model is therefore a map that contains all sub model properties.

Map<String, Object> properties = new HashMap<>();


The ID of the oven will be a static value. It is stored in the ID property. The path to this property in the sub model is therefore /id:

properties.put("id", "heater01");


The second property temperature provides access to the current temperature of the oven. As the oven in our case does provide a very simple interface, the sub model 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.

// Create a dynamic property that can resolve its value during runtime
// 1. Create a supplier function that can determine the oven temperature using the sensor
 
Supplier<Object> lambdaReadFunction = () -> oven.getSensor().readValue();
 
// 2. Use a VABLambdaProviderHelper in order to create a lambda property out of that supplier
// NOTE: A setter function is not required (=> null), because a sensor temperature is "read only"
 
Map<String, Object> dynamicTemperatureProperty = VABLambdaProviderHelper.createSimple(lambdaReadFunction, null);
 
// 3. Add that lambda property to the model exactly like the static property before
properties.put("temperature", dynamicTemperatureProperty);


The temperature is read through a call to the readValue() call of the sensor (obtained via getSensor()). Besides of properties, the sub model also may export operations. Similar to sub model properties, operations are provided as map as well.

// Create an empty container for custom operations
Map<String, Object> operations = new HashMap<>();


The sub model 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.

// Add a function that activates the oven and implements a functional interface
Function<Object, Object> activateFunction = (args) -> {
	oven.getHeater().activate();
	return null;
};
 
// Add a function that deactivates the oven and implements a functional interface
operations.put("activateOven", activateFunction);
 
 
// Add a function that deactivates the oven and implements a functional interface
Function<Object, Object> deactivateFunction = (args) -> {
	oven.getHeater().deactivate();
	return null;
};
 
// Add a function that deactivates the oven and implements a functional interface
operations.put("deactivateOven", deactivateFunction);


The sub model is now created by combining the properties and operations maps:

Map<String, Object> subModel = new HashMap<>();
subModel.put("operations", operations);
subModel.put("properties", properties);



Putting everything together

The complete code is provided below:


	public static Map<String, Object> createMyOvenModel(Oven oven) {
		// Create an empty container for custom properties
		Map<String, Object> properties = new HashMap<>();
 
		// Add a static element
		properties.put("id", "heater01");
		// Now we want to create a dynamic property that can resolve its value during runtime
		// 1. Create a supplier function that can determine the oven temperature using the +sensor
		Supplier<Object> lambdaFunction = () -> oven.getSensor().readValue();
		// 2. Use a VABLambdaProviderHelper in order to create a lambda property out of that supplier
		// NOTE: A setter function is not required (=> null), because a sensor temperature is "read only"
		Map<String, Object> lambdaProperty = VABLambdaProviderHelper.createSimple(lambdaFunction, null);
		// 3. Add that lambda property to the model exactly like the static property before
		properties.put("temperature", lambdaProperty);
 
		// Create an empty container for custom operations
		Map<String, Object> operations = new HashMap<>();
		// Add a function that activates the oven and implements a functional interface
		Function<Object, Object> activateFunction = (args) -> {
			oven.getHeater().activate();
			return null;
		};
		// Add a function that deactivates the oven and implements a functional interface
		operations.put("activateOven", activateFunction);
 
		// Add a function that deactivates the oven and implements a functional interface
		Function<Object, Object> deactivateFunction = (args) -> {
			oven.getHeater().deactivate();
			return null;
		};
		// Add a function that deactivates the oven and implements a functional interface
		operations.put("deactivateOven", deactivateFunction);
 
		// Create a root map and return a single model with the created operations and properties
		Map<String, Object> subModel = new HashMap<>();
		subModel.put("operations", operations);
		subModel.put("properties", properties);
		return subModel ;
	}
}


Testing the sub model

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

// Create a model for an oven device. Models in the BaSyx Java SDK are based on HashMaps.
Map<String, Object> myOvenModel = createMyOvenModel(new Oven());
 
// Wrap the device model in a VAB model provider. In this case, a VABLambdaProvider is used that enables dynamic
// resolution of model properties.
// There are also other providers like the VABMapProvider and the FileSystemProvider
IModelProvider provider = new VABLambdaProvider(myOvenModel);


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.

// Now you can access the properties of the oven via the five primitives using the model provider.
// CREATE: IModelProvider::createValue
// DELETE: IModelProvider::deleteValue
// RETRIEVE: IModelProvider::getModelPropertyValue
// UPDATE: IModelProvider::setModelPropertyValue
// INVOKE: IModelProvider::invokeOperation
String id = (String) provider.getValue("/properties/id");
System.out.println("Heater id: " + id);
 
// NOTE A: getModelPropertyValue has "Object" as the return type, so you probably will have to cast the result
// to the appropriate type.
// NOTE B: The argument "/properties/id" references the static id property of the model. This path depends
// on the structure of the HashMap => /properties/id assumes that the model structure matches the intended map
// structure that is proposed at the beginning of "helperfunction".
 
// The operations can be invoked via the model provider like this:
provider.invokeOperation("/operations/activateOven");


Conclusion

This second step did illustrate the creation of a sub model 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 sub model 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 sub models for their assets, and keep every sub model focused on a specific aspect. Depending on the type of the asset, many sub model types are possible, e.g. the nameplate, spare parts and their availability, sensor data, asset health, or offered services.

Back to the top