|
|
Here, I'll walk you through the source code of the [Demo Controller](TODO), running on an ESP32 microcontroller.
|
|
|
|
|
|
The discussed file can be found [here](TODO)
|
|
|
|
|
|
```
|
|
|
#define ANIMATION1_SW 27
|
|
|
#define ANIMATION2_SW 26
|
|
|
#define ANIMATOR_STOP_SW 25
|
|
|
|
|
|
#define RED_LIGHT_SW 33
|
|
|
The discussed file can be found [here](https://gitlab.hrz.tu-chemnitz.de/vws-demo/vws-spielwiese/-/blob/b9c09b1570bc62ac529487caf35e79647a67c752/esp/DemoController/DemoController.ino)
|
|
|
|
|
|
```
|
|
|
#define ANIMATION1_SW 14
|
|
|
bool set_animation_1 = false;
|
|
|
void IRAM_ATTR animation_1_inthandler() { set_animation_1 = true; }
|
|
|
#define ANIMATION2_SW 13
|
|
|
bool set_animation_2 = false;
|
|
|
void IRAM_ATTR animation_2_inthandler() { set_animation_2 = true; }
|
|
|
#define ANIMATOR_STOP_SW 27
|
|
|
bool set_animation_off = false;
|
|
|
void IRAM_ATTR animation_off_inthandler() { set_animation_off = true; }
|
|
|
|
|
|
#define RED_LIGHT_SW 25
|
|
|
bool set_lights_red = false;
|
|
|
void IRAM_ATTR set_red_inthandler() { set_lights_red = true; }
|
|
|
#define GREEN_LIGHT_SW 32
|
|
|
#define ALL_LIGHTS_SW 35
|
|
|
#define BLINK_SW 34
|
|
|
#define CONTROLLER_STOP_SW 22
|
|
|
bool set_lights_green = false;
|
|
|
void IRAM_ATTR set_green_inthandler() { set_lights_green = true; }
|
|
|
#define ALL_LIGHTS_SW 33
|
|
|
bool set_all_lights = false;
|
|
|
void IRAM_ATTR set_all_inthandler() { set_all_lights = true; }
|
|
|
#define BLINK_SW 26
|
|
|
bool set_blink = false;
|
|
|
void IRAM_ATTR set_blink_inthandler() { set_blink = true; }
|
|
|
#define CONTROLLER_STOP_SW 35
|
|
|
bool stop_controller = false;
|
|
|
void IRAM_ATTR stop_controller_inthandler() { stop_controller = true; }
|
|
|
|
|
|
#define UPDATE_SW 13
|
|
|
#define UPDATE_SW 34
|
|
|
bool update_lights = false;
|
|
|
void IRAM_ATTR update_lights_inthandler() { update_lights = true; }
|
|
|
|
|
|
#define ANIMATOR_RED 5
|
|
|
#define ANIMATOR_GREEN 18
|
|
|
#define CONTROLLER_RED 19
|
|
|
#define CONTROLLER_GREEN 21
|
|
|
#define ANIMATOR_RED 23
|
|
|
#define ANIMATOR_GREEN 22
|
|
|
#define CONTROLLER_RED 16
|
|
|
#define CONTROLLER_GREEN 4
|
|
|
|
|
|
#define SERVER_RED 15
|
|
|
#define SERVER_GREEN 2
|
|
|
#define REGISTRY_RED 4
|
|
|
#define REGISTRY_GREEN 23
|
|
|
```
|
|
|
These Definitions link the GPIO ports of the ESP32 to readable names, according to their purpose. See [wiring up the demo](setup/Wiring-of-the-demo) for more information.
|
|
|
#define SERVER_RED 21
|
|
|
#define SERVER_GREEN 19
|
|
|
#define REGISTRY_RED 18
|
|
|
#define REGISTRY_GREEN 17
|
|
|
|
|
|
```
|
|
|
#define RED_LED 4
|
|
|
#define GREEN_LED 2
|
|
|
```
|
|
|
Here, the GPIO pins used for the red and green LEDs attached to the ESP32 Dev Board are defined. If you were to diverge from the [default wiring of the demo](setup/Wiring-of-the-demo), you'll need to adjust the GPIO numbers here.
|
|
|
These Definitions link the GPIO ports of the ESP32 to readable names, according to their purpose.
|
|
|
See [wiring up the demo](setup/Wiring-of-the-demo) for more information.
|
|
|
|
|
|
Also for every action button a boolean is defined and an interrupt handler that sets it to `true`.
|
|
|
The interrupt handler is triggered automatically whenever a button is pressed.
|
|
|
|
|
|
```
|
|
|
#include <ESPmDNS.h>
|
... | ... | @@ -55,9 +72,13 @@ While on the Raspberry Pis the underlying network stack takes care of this, on t |
|
|
|
|
|
```c++
|
|
|
IPAddress AASServerIP = IPAddress(0, 0, 0, 0);
|
|
|
IPAddress RegistryIP = IPAddress(0, 0, 0, 0);
|
|
|
#define AASServerPort 4001
|
|
|
IPAddress RegistryIP = AASServerIP;
|
|
|
#define RegistryPort 4000
|
|
|
```
|
|
|
In these `IPAddress`es, the resolved ip addresses will be stored in.
|
|
|
As currently the registry and the server are on the same host they have the same IP.
|
|
|
The ports for both of the modules are defined here, too.
|
|
|
|
|
|
```c++
|
|
|
HTTPClient http;
|
... | ... | @@ -88,10 +109,15 @@ When the resolution succeeded, we print it and conclude the method. |
|
|
void setup()
|
|
|
{
|
|
|
pinMode(ANIMATION1_SW, INPUT);
|
|
|
attachInterrupt(ANIMATION1_SW, animation_1_inthandler, RISING);
|
|
|
pinMode(ANIMATION2_SW, INPUT);
|
|
|
...
|
|
|
attachInterrupt(ANIMATION2_SW, animation_2_inthandler, RISING);
|
|
|
// ...
|
|
|
```
|
|
|
The `setup()` function is called once on startup of the ESP32. Here, all one-time setup happens, including the initialization of the GPIO ports assigned names above. Switches are `INPUT`s and LEDs are `OUTPUT`s.
|
|
|
The `setup()` function is called once on startup of the ESP32.
|
|
|
Here, all one-time setup happens, including the initialization of the GPIO ports assigned names above.
|
|
|
Switches are `INPUT`s and LEDs are `OUTPUT`s.
|
|
|
The interrupt handlers get registered here too and listen too a rising edge (in voltage).
|
|
|
|
|
|
```c++
|
|
|
Serial.begin(115200);
|
... | ... | @@ -119,7 +145,7 @@ Initializes the mDNS resolution for the ESP32 |
|
|
|
|
|
```c++
|
|
|
resolve_mdns("aas-server", &AASServerIP);
|
|
|
resolve_mdns("registry", &RegistryIP);
|
|
|
RegistryIP = AASServerIP;
|
|
|
```
|
|
|
Lastly in the setup, we'll resolve the addresses for the first time.
|
|
|
|
... | ... | @@ -135,18 +161,19 @@ This method in particular returns `true`, if the call to the AAS Servers root en |
|
|
|
|
|
```c++
|
|
|
bool ret;
|
|
|
String request = "http://" + AASServerIP.toString() + ":4000/aasServer/shells/";
|
|
|
```
|
|
|
To do this, we need to define the Endpoint we want to query. We do so after declaring our return variable and using the resolved IP of the AAS Server.
|
|
|
The unresolved endpoint is `http://aas-server.local:4000/aasServer/shells/`
|
|
|
|
|
|
```c++
|
|
|
http.begin(request);
|
|
|
http.setReuse(false);
|
|
|
http.begin(AASServerIP.toString(), AASServerPort, "/aasServer/shells/");
|
|
|
ret = (http.GET() == 200);
|
|
|
http.end();
|
|
|
```
|
|
|
After having defined our request endpoint, we initialize the connection using `http.begin`. We then send a `GET` request to the specified endpoint and store whether or not the request was successful in our return variable. `200` being the [HTTP Status Code] indicating success.
|
|
|
`http.end()` closes the connection again.
|
|
|
To do this, we need an endpoint we want to query.
|
|
|
We do so after declaring our return variable and set the reuse of the http connection to false.
|
|
|
Latter is because of [some bug](https://github.com/esp8266/Arduino/issues/8331) that happened often.
|
|
|
We use the resolved IP and the port for the request.
|
|
|
The unresolved endpoint is `http://main.local:4001/aasServer/shells/`
|
|
|
|
|
|
After that we check if our connection succeded (HTTP status code 200).
|
|
|
`http.end()` closes the connection.
|
|
|
|
|
|
```c++
|
|
|
//Remember: LEDs are active LOW
|
... | ... | @@ -170,7 +197,7 @@ Green is lit, when the AAS Server is up, red, when the request failed. |
|
|
bool isRegistryUp()
|
|
|
{
|
|
|
```
|
|
|
The same is done for the registry and it's status LEDs. This time the request goes to `http://registry.local:4000/registry/api/v1/registry`
|
|
|
The same is done for the registry and it's status LEDs. This time the request goes to `http://main.local:4000/registry/api/v1/registry`
|
|
|
|
|
|
```c++
|
|
|
bool isLightControllerActive()
|
... | ... | @@ -181,12 +208,11 @@ Testing wether or not the Light controller is active (ie. actively controlling t |
|
|
```c++
|
|
|
bool ret;
|
|
|
//Endpoint for the 'isActive' Property of the main Submodel of the LightController
|
|
|
String request = "http://" + AASServerIP.toString() + ":4000/aasServer/shells/urn:de.olipar.basyx:LightControllerAAS/aas/submodels/lightControllerSM/submodel/submodelElements/isActive/value";
|
|
|
http.begin(request);
|
|
|
http.begin(AASServerIP.toString(), AASServerPort, "/aasServer/shells/urn:de.olipar.basyx:LightControllerAAS/aas/submodels/lightControllerSM/submodel/submodelElements/isActive/value");
|
|
|
ret = (http.GET() == 200);
|
|
|
```
|
|
|
The beginning should look familiar by now, this time the endpoint of our request is
|
|
|
`http://aas-server.local:4000/aasServer/shells/urn:de.olipar.basyx:LightControllerAAS/aas/submodels/lightControllerSM/submodel/submodelElements/isActive/value`
|
|
|
`http://main.local:4001/aasServer/shells/urn:de.olipar.basyx:LightControllerAAS/aas/submodels/lightControllerSM/submodel/submodelElements/isActive/value`
|
|
|
It pays to play around with the endpoints in a browser a bit. If you use Firefox, the json will be nicely formatted out of the box and you'll soon find your way round them. If you get stuck, there's always the [official API specification](https://app.swaggerhub.com/apis/BaSyx/basyx_submodel_http_rest_api/v1) available.
|
|
|
Let's walk through this URL backwards:
|
|
|
We want to get the `value` of the Property `isActive`, which is an element of the sumbodel `lightControllerSM` of the AAS, which is known as `urn:de.olipar.basyx:LightControllerAAS`.
|
... | ... | @@ -217,13 +243,12 @@ We then close the connection, set the LEDs and return what we've learned. |
|
|
bool isLightAnimatorActive()
|
|
|
{
|
|
|
```
|
|
|
The same is done for the Animator and its lights. The endpoint here is `http://aas-server.local:4000/aasServer/shells/urn:de.olipar.basyx:LightAnimatorAAS/aas/submodels/lightAnimatorSM/submodel/submodelElements/isActive/value`
|
|
|
The same is done for the Animator and its lights. The endpoint here is `http://main.local:4001/aasServer/shells/urn:de.olipar.basyx:LightAnimatorAAS/aas/submodels/lightAnimatorSM/submodel/submodelElements/isActive/value`
|
|
|
|
|
|
```c++
|
|
|
void animation1(){
|
|
|
//Endpoint for the operation 'animation1' of the main Submodel of the LightAnimator
|
|
|
String request = "http://" + AASServerIP.toString() + ":4000/aasServer/shells/urn:de.olipar.basyx:LightAnimatorAAS/aas/submodels/lightAnimatorSM/submodel/submodelElements/animation1/invoke";
|
|
|
http.begin(request);
|
|
|
http.begin(AASServerIP.toString(), AASServerPort, "/aasServer/shells/urn:de.olipar.basyx:LightAnimatorAAS/aas/submodels/lightAnimatorSM/submodel/submodelElements/animation1/invoke");
|
|
|
http.POST("");
|
|
|
http.end();
|
|
|
}
|
... | ... | @@ -240,8 +265,8 @@ After `setup()`, this function is called endlessly. |
|
|
|
|
|
```c++
|
|
|
if (millis() - last_mDNS_refresh > 60000) {
|
|
|
resolve_mdns("aas-server", &AASServerIP);
|
|
|
resolve_mdns("registry", &RegistryIP);
|
|
|
resolve_mdns("main", &AASServerIP);
|
|
|
RegistryIP = AASServerIP;
|
|
|
|
|
|
last_mDNS_refresh=millis();
|
|
|
}
|
... | ... | @@ -262,13 +287,15 @@ isLightAnimatorActive(); |
|
|
Then, we update the LEDs indicating, whether Animator and/or Controller are currently Active (ie. Controlling lights)
|
|
|
|
|
|
```c++
|
|
|
if(digitalRead(ANIMATION1_SW)){
|
|
|
if(set_animation_1){
|
|
|
set_animation_1 = false;
|
|
|
Serial.println("invoke animation1");
|
|
|
animation1();
|
|
|
animation1();
|
|
|
} else if
|
|
|
```
|
|
|
Then follows a long `if` - `else if `-segment, checking whether a button is currently being pressed and invoking the corresponding action.
|
|
|
`else if` is used to only act on one button being pressed, regardless of how many are being held down.
|
|
|
Then follows a long `if` - `else if `-segment, checking whether a button was pressed and invoking the corresponding action.
|
|
|
As we set up the interrupt handler we know when a boolean is `true` it is because the button was pressed in while the last loop was running.
|
|
|
`else if` is used because because multiple buttons could have been pressed in the last loop.
|
|
|
|
|
|
```c++
|
|
|
delay(100);
|
... | ... | |