|
|
Here, I'll walk you through the java source code, responsible for starting the Light Controller. Additional startup procedures are explained in the documentation for the start_component.sh and the run_demo.sh scripts.
|
|
|
|
|
|
We'll skip the license headers, imports and comments, as they are either pretty much self explanatory or not the product of conscious development effort.
|
|
|
|
|
|
# ILightController
|
|
|
|
|
|
The discussed file can be found [here](https://gitlab.hrz.tu-chemnitz.de/vws-demo/vws-spielwiese/-/blob/6d65d24412bda2e555e70a4f9581eb142096bd49/basyx.lichterkette/src/main/java/de/olipar/basyx/lichterkette/ILightController.java)
|
|
|
|
|
|
This interface defines what methods a light controller has to provide:
|
|
|
| Method | Purpose |
|
|
|
| --------------- | ------------------------------------------------------------------- |
|
|
|
| `redLight()` | Turn on all red lights, turn off all others |
|
|
|
| `greenLight()` | Turn on all green lights, turn off all others |
|
|
|
| `blinkLight()` | Alternate between red and green lights |
|
|
|
| `staticLight()` | Turn on all available lights |
|
|
|
| `updateLamps()` | Refresh the list of known lights to control |
|
|
|
| `deactivate()` | Stop controlling any light until reactivation |
|
|
|
| `isRedOn()` | Return, whether or not this controller has red lights switched on |
|
|
|
| `isGreenOn()` | Return, whether or not this controller has green lights switched on |
|
|
|
| `isActive()` | Return whether this controller is active or not. |
|
|
|
|
|
|
# LightController
|
|
|
|
|
|
The discussed file can be found [here](https://gitlab.hrz.tu-chemnitz.de/vws-demo/vws-spielwiese/-/blob/6d65d24412bda2e555e70a4f9581eb142096bd49/basyx.lichterkette/src/main/java/de/olipar/basyx/lichterkette/LightController.java)
|
|
|
|
|
|
```java
|
|
|
public class LightController implements ILightController {
|
|
|
```
|
|
|
This class is so far the only implementation of a Light controller in this demo. It provides all the methods specified above.
|
|
|
|
|
|
```java
|
|
|
private boolean isActive = false;
|
|
|
private boolean isRedOn = false;
|
|
|
private boolean isGreenOn = false;
|
|
|
private boolean isBlinking = false;
|
|
|
```
|
|
|
We start of by defining some internal states, three of which are exposed through getters defined by the interface.
|
|
|
|
|
|
```java
|
|
|
private final double updatePeriod = 1.0D;
|
|
|
```
|
|
|
This period is the minimum wait between consecutive actions by the controller. It is given in seconds.
|
|
|
|
|
|
```java
|
|
|
private final AASRegistryProxy registry;
|
|
|
private ConnectedAssetAdministrationShellManager manager;
|
|
|
```
|
|
|
We also declare a proxy object for the AAS Registry and an AAS manager instance, which will later be instantiated using the registry proxy. The manager will handle the communication with other Components and the lookup of lights. More on that later.
|
|
|
|
|
|
```java
|
|
|
private List<ISubmodel> redLightSwitches = new LinkedList<ISubmodel>();
|
|
|
private List<ISubmodel> greenLightSwitches = new LinkedList<ISubmodel>();
|
|
|
private boolean isStale = true;
|
|
|
```
|
|
|
These are our internal lists of the light switch submodels, that are created on startup of light hosts. They are filled on the first activation, when a Submodel contained in the lists can't be reached and when `updateLamps` is invoked. To do this, the latter boolean stores whether or not the lists should be refreshed on the next update period.
|
|
|
|
|
|
```java
|
|
|
public LightController(final AASRegistryProxy registry) {
|
|
|
this.registry = registry;
|
|
|
this.manager = new ConnectedAssetAdministrationShellManager(registry);
|
|
|
|
|
|
```
|
|
|
The constructor handles the creation of the manager object and the creation of the main work thread:
|
|
|
|
|
|
```java
|
|
|
new Thread(() -> {
|
|
|
while (true) {
|
|
|
try {
|
|
|
Thread.sleep((long) (updatePeriod * 1000));
|
|
|
} catch (InterruptedException e) {
|
|
|
e.printStackTrace();
|
|
|
}
|
|
|
if (isActive) {
|
|
|
```
|
|
|
The main work thread of the light controller. It runs until the program is stopped and waits for one updatePeriod on every loop and only updates states or lists, if the controller is not deactivated.
|
|
|
|
|
|
```java
|
|
|
if (isStale) {
|
|
|
System.out.println("Light list of this controller is stale. Updating...");
|
|
|
this.updateLampList();
|
|
|
isStale = false;
|
|
|
}
|
|
|
```
|
|
|
If the lists are outdated, the lists are regenerated using the method explained later.
|
|
|
|
|
|
```java
|
|
|
if (isBlinking) {
|
|
|
isRedOn = !isRedOn;
|
|
|
isGreenOn = !isGreenOn;
|
|
|
}
|
|
|
```
|
|
|
if we are currently blinking, ie. alternating between red and green lights, we invert their supposed states every loop.
|
|
|
|
|
|
We then enter a `try`-block
|
|
|
```java
|
|
|
for (ISubmodel light : greenLightSwitches) {
|
|
|
if (isGreenOn) {
|
|
|
((IOperation) light.getSubmodelElement("activateLight")).invoke();
|
|
|
} else {
|
|
|
((IOperation) light.getSubmodelElement("deactivateLight")).invoke();
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
For every Submodel in our list corresponding to green lights, we invoke either the activation or deactivation of said light, depending on the state we saved internally.
|
|
|
|
|
|
The same is done for the red lights, just by operating over the other list of lights.
|
|
|
|
|
|
```java
|
|
|
} catch (ResourceNotFoundException e) {
|
|
|
if (!isStale) {
|
|
|
System.out.println("Invoking a light switch failed. Lights will be updated on next update while active.");
|
|
|
}
|
|
|
isStale = true;
|
|
|
}
|
|
|
```
|
|
|
If we could not successfully invoke an Operation on any saves light, a warning is issued and the lists will be updated on the next loop.
|
|
|
|
|
|
```java
|
|
|
}).start();
|
|
|
```
|
|
|
This concludes the main work thread and starts it.
|
|
|
|
|
|
```java
|
|
|
private void activate() {
|
|
|
if (!isActive) {
|
|
|
System.out.println("Light controller activated");
|
|
|
isActive = true;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
This method is used internally to set the controller active, when either `redLight`, `greenLight`, `staticLight` or `blinkLight` is invoked, in case the controller is inactive.
|
|
|
|
|
|
```java
|
|
|
public void deactivate() {
|
|
|
if (isActive) {
|
|
|
System.out.println("Light controller deactivated");
|
|
|
isActive = false;
|
|
|
isRedOn = false;
|
|
|
isGreenOn = false;
|
|
|
isBlinking = false;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
Upon deactivation, the main loop will not do anything after finishing the current loop until reactivation.
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
public void redLight() {
|
|
|
activate();
|
|
|
isGreenOn = false;
|
|
|
isRedOn = true;
|
|
|
isBlinking = false;
|
|
|
}
|
|
|
```
|
|
|
Following the getter for `isActive`, there are the definitions of the methods to be defined as by the interface implemented. They are either setting internal states like above, which will affect the main work thread, or they are just getters.
|
|
|
We'll skip them.
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
public void updateLamps() {
|
|
|
isStale = true;
|
|
|
}
|
|
|
```
|
|
|
Except this one. Here we don't update the lamps directly, but defer it to the main work thread, as we otherwise risk modification of the lists while iterating over them, which will crash our application.
|
|
|
|
|
|
```java
|
|
|
public void updateLampList() {
|
|
|
```
|
|
|
This method uses the registry proxy and the manager instantiated prior to fill the lists of light switch submodules for the red and green lights present in the BaSyx environment.
|
|
|
|
|
|
```java
|
|
|
redLightSwitches.clear();
|
|
|
greenLightSwitches.clear();
|
|
|
```
|
|
|
We start by emptying both lists.
|
|
|
|
|
|
```java
|
|
|
for (AASDescriptor iter : registry.lookupAll()) {
|
|
|
if (iter.getIdShort().contains("Light")) {
|
|
|
```
|
|
|
Then, we get desctiptors for all registered AAS and look for those, whose short ID contains the substring "Light", as in "greenLight-1", for example.
|
|
|
|
|
|
```java
|
|
|
for (SubmodelDescriptor lightModel : iter.getSubmodelDescriptors()) {
|
|
|
if (lightModel.getIdShort().equals("lightSwitch")) {
|
|
|
```
|
|
|
Then, for each AAS whose shortID contains "Light", we look through the submodels of said AAS for one, whose shortID is "lightSwitch".
|
|
|
|
|
|
```java
|
|
|
ISubmodel submodel = manager.retrieveSubmodel(iter.getIdentifier(), lightModel.getIdentifier());
|
|
|
if (iter.getIdShort().contains("red")) {
|
|
|
redLightSwitches.add(submodel);
|
|
|
} else if (iter.getIdShort().contains("green")) {
|
|
|
greenLightSwitches.add(submodel);
|
|
|
}
|
|
|
```
|
|
|
Then, having found a Submodel with the ShortID "lightSwitch" as part of an AAS, whose name contains "Light", we use the Manager to retrieve said submodel.
|
|
|
|
|
|
We then decide whether we should put the submodel into to the list for green or red lights, depending on whether the short ID of the AAS (in addition to "Light") also contains the substring "red" or "green".
|
|
|
|
|
|
This could probably be done more elegantly by not searching for parts of the short ID but rather implementations of templates of submodels or AAS. This might be an interesting [future project](TODO)
|
|
|
|
|
|
# LightControllerSubmodelProvider
|
|
|
|
|
|
Just like the ordinary lights, The Light controller is itself an Asset, being integrated into the BaSyx environment hosting a submodel exposing it's interface to other members of the system.
|
|
|
|
|
|
The discussed file can be found [here](https://gitlab.hrz.tu-chemnitz.de/vws-demo/vws-spielwiese/-/blob/6d65d24412bda2e555e70a4f9581eb142096bd49/basyx.lichterkette/src/main/java/de/olipar/basyx/lichterkette/LightControllerSubmodelProvider.java)
|
|
|
|
|
|
I advise you to take a look at the file, as it is already heavily commented.
|
|
|
|
|
|
```java
|
|
|
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(LightControllerSubmodelProvider.class);
|
|
|
|
|
|
public static Submodel createMyLightControllerModel(ILightController lightController) {
|
|
|
```
|
|
|
|
|
|
just like with the Lights, we use a logger and outsource the actual creation of the submodel to be hosted to its own method.
|
|
|
|
|
|
```java
|
|
|
Submodel lightControllerSubmodel = new Submodel();
|
|
|
lightControllerSubmodel.setIdShort("lightControllerSM");
|
|
|
lightControllerSubmodel.setIdentification(new ModelUrn("lightControllerSM1"));
|
|
|
```
|
|
|
We start by creating a new empty Submodel and configuring its short and unique ID. Note, that here too from my testing it appears, that this is neither strictly necessary, nor a problem, if it's not globally unique. Since all the IDs here are hard-coded, that means that if you were to start a second instance of a light controller, the latter would override the AAS and registry entries made by the first light controller.
|
|
|
By default, only one light controller is to be running in the demo at any given time.
|
|
|
|
|
|
```java
|
|
|
Supplier<Object> lambdaGreenReadFunction = () -> lightController.isGreenOn();
|
|
|
```
|
|
|
Differing from the lights now, we also have Properties in this submodel, for instance whether or not the green lights are currently activated by the controller.
|
|
|
The value of this is dynamically acquired by BaSyx on every request to the API that exposes this value.
|
|
|
To do this, a Supplier is created, that represents the invokation of `isGreenOn()`.
|
|
|
|
|
|
```java
|
|
|
Property dynamicGreenLightProperty = new Property();
|
|
|
dynamicGreenLightProperty.setIdShort("isGreenOn");
|
|
|
```
|
|
|
We then create a new property with the shortID "isGreenOn", this ID must be unique in the context of this submodule.
|
|
|
|
|
|
```java
|
|
|
AASLambdaPropertyHelper.setLambdaValue(dynamicGreenLightProperty, lambdaGreenReadFunction, null);
|
|
|
```
|
|
|
The `AASLambdaPropertyHelper` provided by BaSyx attaches the previously created Supplier to the Property. the `null` would be filled by a Supplier for the setter, if one would be required.
|
|
|
|
|
|
```java
|
|
|
lightControllerSubmodel.addSubmodelElement(dynamicGreenLightProperty);
|
|
|
```
|
|
|
Lastly, the Property is added to the Submodel we are creating.
|
|
|
|
|
|
This whole process of creating a Supplier object, a Property, binding to the Property and adding to the submodel is repeated for `isRedOn()` and `isActive()`
|
|
|
|
|
|
After establishing all the Properties, functions are declared, bound to instantiated Operations and addes to the submodel, exactly like in the light hosts; one for each method defined in the interface, that aren't already covered by properties.
|
|
|
|
|
|
```java
|
|
|
public static void main(String[] args) {
|
|
|
final String HOSTADDRESS = Common.getHostAddress();
|
|
|
logger.info("Local host address is set to " + HOSTADDRESS);
|
|
|
final int HOSTPORT = Common.getHostPort();
|
|
|
logger.info("Local host port is set to " + HOSTPORT);
|
|
|
final String REGISTRYPATH = Common.getRegistryPath();
|
|
|
logger.info("Registry path set to " + REGISTRYPATH);
|
|
|
final String AASSERVERPATH = Common.getAASServerPath();
|
|
|
logger.info("AAS server path set to " + AASSERVERPATH);
|
|
|
|
|
|
AASRegistryProxy registry = new AASRegistryProxy(REGISTRYPATH);
|
|
|
```
|
|
|
|
|
|
Then, also like in the light host provider, we aquire all relevant information about our environment from the `Common`-class and creating a registry proxy.
|
|
|
|
|
|
```java
|
|
|
Submodel lightControllerModel = createMyLightControllerModel(new LightController(registry));
|
|
|
```
|
|
|
After that, we create a new LightController instance, to which we'll pass our registry proxy, and bind it to a Submodel creates like described above.
|
|
|
|
|
|
```java
|
|
|
IModelProvider modelProvider = new SubmodelProvider(lightControllerModel);
|
|
|
BaSyxContext context = new BaSyxContext("/handson", "", HOSTADDRESS, HOSTPORT);
|
|
|
```
|
|
|
We then create a new Provider instance for the freshly created Submodel and define the hosting context. Note that this is a bit more straight-forward as what happens for the lights, as we're only setting up one single host and don't need to worry about GPIO or any communication outside of BaSyx really.
|
|
|
|
|
|
```java
|
|
|
HttpServlet modelServlet = new VABHTTPInterface<IModelProvider>(modelProvider);
|
|
|
logger.info("Created a servlet for the light controller model");
|
|
|
context.addServletMapping("/lightController/*", modelServlet);
|
|
|
```
|
|
|
Then, we create a Servlet to host our Submodel in and add the servlet to the context.
|
|
|
|
|
|
```java
|
|
|
BaSyxHTTPServer server = new BaSyxHTTPServer(context);
|
|
|
server.start();
|
|
|
logger.info("HTTP server started");
|
|
|
```
|
|
|
After the hosting is configured, the actual hosting is done by a Server provided by BaSyx.
|
|
|
|
|
|
```java
|
|
|
ModelUrn aasURN = new ModelUrn("urn:de.olipar.basyx:LightControllerAAS");
|
|
|
```
|
|
|
Now that our Submodel is created and hosted, we start creating the AAS for the Light Controller. We start by assigning a GUID in form of a URN.
|
|
|
Note here too, that since all the IDs here are hard-coded, if you were to start a second instance of a light controller, the latter would override the AAS and registry entries made by the first light controller.
|
|
|
By default, only one light controller is to be running in the demo at any given time.
|
|
|
|
|
|
```java
|
|
|
Asset asset = new Asset("lightControllerAsset", aasURN, AssetKind.INSTANCE);
|
|
|
AssetAdministrationShell aas = new AssetAdministrationShell("lightController", aasURN, asset);
|
|
|
```
|
|
|
Then, using the URN, an Asset and corresponding AAS is instantiated for the light controller. The shortID of the AAS is set to "lightController". The Light Animator will use this ID to find the controller in the System.
|
|
|
|
|
|
```java
|
|
|
aas.addSubmodel(lightControllerModel);
|
|
|
|
|
|
SubmodelDescriptor lightControllerSMDescriptor = new SubmodelDescriptor(lightControllerModel, "http://" + HOSTADDRESS + ":" + HOSTPORT + "/handson/lightController/submodel");
|
|
|
```
|
|
|
Following that, the Submodel created above is added to the aas and a descriptor created, that links the submodel to the endpoint hosted on the machine.
|
|
|
|
|
|
```java
|
|
|
ConnectedAssetAdministrationShellManager manager = new ConnectedAssetAdministrationShellManager(registry);
|
|
|
|
|
|
manager.createAAS(aas, AASSERVERPATH);
|
|
|
registry.register(aas.getIdentification(), lightControllerSMDescriptor);
|
|
|
```
|
|
|
Concluding that, a manager object is created and uses to push the AAS to the AAS Server and publish it on the registry. We then register the submodel and its endpoint too.
|
|
|
|
|
|
```java
|
|
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
|
|
public void run() {
|
|
|
System.out.println("Shutting down Light Controller...");
|
|
|
manager.deleteSubmodel(aas.getIdentification(), lightControllerSMDescriptor.getIdentifier());
|
|
|
manager.deleteAAS(aas.getIdentification());
|
|
|
}
|
|
|
});
|
|
|
```
|
|
|
Lastly, we're adding a shutdown hook to the main process. This allows us to delete and deregister AAS and Submodel on shutdown, preventing inconsistencies in the overall system. |
|
|
\ No newline at end of file |