|
|
# Display
|
|
|
|
|
|
A display and a script to show the data of the different sensors were already added as part of the first follow up. You can find the documentation here. Displaying the values was realized with a python script and a system service and no Basyx implementation existed for the LCD. Therefore, it was not possible to show a custom text. The display now is fully integrated into the Basyx environment and has its own AAS with invokable operations to do so.
|
|
|
|
|
|
## Implementation Basyx
|
|
|
|
|
|
The concept of the implementation is compareable to the one for the light or sensor implementation. An interface ```IDisplay``` as well as the class ```Display``` implementig it are used. The `LcdDisplayController` creates the displays and uses the `DisplaySubmodelProvider` to create, register and upload the AAS as well as host and register the submodules.
|
|
|
|
|
|
### General
|
|
|
|
|
|
While the Basyx part is written in Java, python scripts are used to access the LCD. The LCD is directly connected to the Raspberry Pi running the Basyx implementation. But it is possible to use another device to access the display and run the python scripts. In this case one Rasperry Pi could control multiple displays instead of one. The communication between the controller and the displays is realized with MQTT.
|
|
|
|
|
|
### IDisplay
|
|
|
|
|
|
The file can be found here. The interface includes these methods:
|
|
|
|
|
|
| Method | Purpose |
|
|
|
|-----|-----------|
|
|
|
| `displayText(String text)` | Display a custom text passed as argument. |
|
|
|
| `cycleSensorValues()` | Cycle through data of the sensors. |
|
|
|
|
|
|
### LcdDisplay
|
|
|
|
|
|
The file can be found here. The class implements the interface `IDisplay`.
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
public boolean displayText(String text) {
|
|
|
try{
|
|
|
publishMqtt("display/Lcd_0/text", text);
|
|
|
return true;
|
|
|
}
|
|
|
catch (Exception e){
|
|
|
logger.error("Displaying text failed: ", e);
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
public boolean cycleSensorValues() {
|
|
|
try{
|
|
|
publishMqtt("display/Lcd_0/cycle", " ");
|
|
|
return true;
|
|
|
}
|
|
|
catch (Exception e){
|
|
|
logger.error("Start of cycling sensor values failed: ", e);
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
In both methods a MQTT message is published using the corresponding topic. The argument of `displayText()` is passed on as payload of the message. The `mqtt_handler` listens for these messages and starts the respective python script.
|
|
|
|
|
|
```java
|
|
|
private void publishMqtt(String topic, String msg){
|
|
|
try{
|
|
|
MqttClient mqttClient = new MqttClient(Common.getMQTTBrokerHost(), "DisplayController");
|
|
|
MqttConnectOptions connOpts = new MqttConnectOptions();
|
|
|
connOpts.setCleanSession(true);
|
|
|
connOpts.setAutomaticReconnect(true);
|
|
|
mqttClient.connect(connOpts);
|
|
|
MqttMessage message = new MqttMessage(msg.getBytes());
|
|
|
message.setQos(0);
|
|
|
mqttClient.publish(topic, message);
|
|
|
}
|
|
|
catch(Exception e){
|
|
|
logger.error("MQTT connection failed", e);
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
This method enables to publish MQTT messages. A MQTT client is initialized and the message constructed. QoS level 0 is used.
|
|
|
|
|
|
### LcdDisplayController
|
|
|
|
|
|
The file can be found here.
|
|
|
|
|
|
```java
|
|
|
public static void main(String[] args) {
|
|
|
final int HOSTPORT = Common.getHostPort();
|
|
|
displayAmount = getAmount();
|
|
|
boolean inDocker = Common.inDocker();
|
|
|
|
|
|
for (int i = 0; i < displayAmount; i++) {
|
|
|
LcdDisplay display = new LcdDisplay(Integer.toString(i), inDocker);
|
|
|
DisplaySubmodelProvider dsp = new DisplaySubmodelProvider("Display:Lcd_" + display.getID());
|
|
|
dsp.hostUploadAndRegister(display, "Lcd_"+display.getID(), "Lcd_"+display.getID(), HOSTPORT+i);
|
|
|
display.cycleSensorValues();
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
The amount of displays which need to be created is retrieved from the environment variable.
|
|
|
|
|
|
### DisplaySubmodelProvider
|
|
|
|
|
|
Like in the other submodel provider in the project a method for creating a submodel is defined and the operations are added.
|
|
|
|
|
|
```java
|
|
|
Function<Object[], Object> cycleFunction = (args) -> display.cycleSensorValues();
|
|
|
Operation cycleOperation = new Operation(cycleFunction);
|
|
|
cycleOperation.setIdShort("cycleSensorValues");
|
|
|
displaySubmodel.addSubmodelElement(cycleOperation);
|
|
|
```
|
|
|
|
|
|
The function simply calls the `cycleSensorValues()` method of the LCD.
|
|
|
|
|
|
```java
|
|
|
Property input = new Property("textToDisplay", "defaultInput");
|
|
|
input.setModelingKind(ModelingKind.TEMPLATE);
|
|
|
input.setValueType(ValueType.String);
|
|
|
|
|
|
Function<Object[], Object> displayTextFunction = (args) -> display.displayText(args[0].toString());
|
|
|
Operation displayTextOperation = new Operation(displayTextFunction);
|
|
|
displayTextOperation.setIdShort("displayText");
|
|
|
OperationVariable var = new OperationVariable(input);
|
|
|
displayTextOperation.setInputVariables(Arrays.asList(var));
|
|
|
displaySubmodel.addSubmodelElement(displayTextOperation);
|
|
|
```
|
|
|
|
|
|
Because an input variable is necessary to pass the text which will be displayed, a propterty is initialized and its modeling kind and value type are set. The defined function passes the first argument to `displayText()`. Using the function and the property the second operation is added to the submodel.
|
|
|
|
|
|
The created submodel is returned and used to host, upload and register it in the following step.
|
|
|
|
|
|
```java
|
|
|
public void hostUploadAndRegister(IDisplay display, String AASID, String AASShortID, int port){
|
|
|
Submodel displayModel = createDisplayModel(display);
|
|
|
super.hostUploadAndRegister(displayModel, AASID, AASShortID, port);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Because the `hostUploadAndRegister()` method is almost the same for all submodelProviders in this project, I moved it into a seperate class called `GeneralSubmodelProvider`. The `DisplaySubmodelProvider` extends this class and passes the created submodel for the LCD to the function of its super class.
|
|
|
|
|
|
## Implementation Python
|
|
|
|
|
|
### mqtt_handler.py
|
|
|
|
|
|
In this script, a paho MQTT client is implemented. Depending on the received message a corresponding python script for displaying something is started.
|
|
|
|
|
|
```python
|
|
|
brokerAddress = environ.get('MQTTBROKERHOST')
|
|
|
proc = None
|
|
|
```
|
|
|
|
|
|
The broker address is retrieved from the environment variable and an empty variable for the currently running process is defined.
|
|
|
|
|
|
```python
|
|
|
def on_connect(client, userdata, flags, rc):
|
|
|
client.subscribe("display/Lcd_0/cycle")
|
|
|
client.subscribe("display/Lcd_0/text")
|
|
|
```
|
|
|
|
|
|
The function is used to subscribe to the necessary topics.
|
|
|
|
|
|
```python
|
|
|
def on_message(client, userdata, message):
|
|
|
payload = None
|
|
|
try:
|
|
|
payload = str(message.payload.decode("utf-8"))
|
|
|
print("message received:", payload)
|
|
|
print("message topic:", message.topic)
|
|
|
except Exception as e:
|
|
|
print("Error. Could not decode payload: ")
|
|
|
print(e)
|
|
|
|
|
|
global proc
|
|
|
|
|
|
try:
|
|
|
proc.kill()
|
|
|
except Exception as e:
|
|
|
print(e)
|
|
|
|
|
|
if(message.topic == "display/Lcd_0/cycle"):
|
|
|
try:
|
|
|
proc = subprocess.Popen([sys.executable, 'cycle_lcd.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
except Exception as e:
|
|
|
print("Error. Could not start cycle: ")
|
|
|
print(e)
|
|
|
|
|
|
if(message.topic == "display/Lcd_0/text"):
|
|
|
try:
|
|
|
text = payload
|
|
|
proc = subprocess.Popen([sys.executable, 'display_text_lcd.py', text], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
except Exception as e:
|
|
|
print("Error. Could not display text: ")
|
|
|
print(e)
|
|
|
```
|
|
|
|
|
|
The payload of the given message is decoded. Depending on the topic different processes are started. If a message with the topic `display/Lcd_0/cycle` is received, `cycle_lcd.py` will be run. If the topic is `display/Lcd_0/text`, `display_text_lcd.py` is started with the payload of the message as argument.
|
|
|
|
|
|
```python
|
|
|
client = mqtt.Client("display")
|
|
|
client.on_connect = on_connect
|
|
|
client.on_message = on_message
|
|
|
client.connect(brokerAddress)
|
|
|
client.loop_forever()
|
|
|
```
|
|
|
|
|
|
A MQTT client is initialized and the previous functions are assigned as on_connect and on_message. `on_connect` is executed when the client connects to the broker and `on_message` every time a message is received. The client connects to the broker and `client.loop_forever()` enables to run the script indefinitly. Furthermore, the function handles automatic reconnects.
|
|
|
|
|
|
### display_text_lcd.py
|
|
|
|
|
|
After setting all the pins and the settings for the LCD, text an be displayed.
|
|
|
|
|
|
```python
|
|
|
lines = sys.argv[1].split(',')
|
|
|
|
|
|
for idx, line in enumerate(lines):
|
|
|
display.text(f"{line}", 0, (idx)*8, 1)
|
|
|
|
|
|
display.show()
|
|
|
```
|
|
|
|
|
|
The text to display is the second entry of the sys argv list. The input should be comma-separated. Each entry will be displayed in a new line. `display.show()` needs to be called. Otherwise nothing will be displayed.
|
|
|
|
|
|
### cycle_lcd.py
|
|
|
|
|
|
This script is the script `display.pi`. Nothing was changed.
|
|
|
|
|
|
|
|
|
## HTTP/REST
|
|
|
|
|
|
To for example invoke the cycle on a LCD called `Lcd_0` the following URL can be used for a POST request:
|
|
|
```http://main.local:4001/aasServer/shells/urn:de.olipar.basyx:lcd-monitor.local:Lcd_0/aas/submodels/display/submodel/submodelElements/cycleSensorValues/invoke```
|
|
|
|
|
|
For displaying custom text use e.g.: ```http://main.local:4001/aasServer/shells/urn:de.olipar.basyx:lcd-monitor.local:Lcd_0/aas/submodels/display/submodel/submodelElements/displayText/invoke```
|
|
|
|
|
|
The json body should look like this:
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"requestId" : "1",
|
|
|
"inputArguments": [
|
|
|
{
|
|
|
"value": {
|
|
|
"idShort": "textToDisplay",
|
|
|
"valueType": "string",
|
|
|
"value": "theTextYouWantToDisplay",
|
|
|
"modelType": {
|
|
|
"name": "Property"
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
],
|
|
|
"timeout" : 5000
|
|
|
}
|
|
|
```
|
|
|
|
|
|
The second value parameter should contain the text to display. To show multiple lines of text, separate the parts by commas. |