Alarmanlage mit AZ-Touch MOD, ESP8266 und ESP-NOW - AZ-Delivery

Alarm with AZ-Touch MOD, ESP8266, and ESP-NOW

In this project, we will develop an alarm that detects when a door is opened and a person or animal is in a room. An AZ-Touch kit with a 2.8-inch TFT touchscreen, a linear magnetic Hall sensor, a passive motion sensor (commonly referred to as a PIR sensor), and three ESP8266 microcontrollers will be used for the implementation. The ESP-NOW communication protocol is used for communication between the microcontrollers, which avoids wiring between the modules.

The ESP-NOW protocol is very versatile. It can be used to communicate multiple transmitter modules with a single receiver module, a transmitter module with multiple receiver modules, and a transmitter with a receiver, both unidirectionally and bidirectionally. It is also possible to query whether the transmitted information has reached the destination. Let's start.

Hardware and materials needed

Required software, libraries, and sketches

Schematic

If we analyze the component diagram, we can see that we have two sets of NodeMCU Lolin ESP8266 microcontrollers and sensors as transmitters and will use the D1 Mini NodeMCU ESP8266 microcontroller along with the AZ-Touch MOD as receivers. The power supply for the transmitter modules is provided by the micro USB port.

Program flow and sketches

The two ESP82266 of sensors send the information with the data of the number assigned to the sensor and the state (HIGH or LOW) of the digital pin. The linear magnetic Hall sensor is installed on the top frame of a door and a small magnet is placed on the door as close as possible to the sensor. The sensor will be at a HIGH level when the door is closed because the magnet is close to the sensor. This is the normal situation and the message "OK" will appear on the TFT screen next to the name of the sensor. When the door is opened, the small magnet is removed from the sensor and the sensor goes to "LOW level". Then the message on the TFT screen changes to "Alarm" and a warning line appears with the message "Door Alarm Detected". The presence sensor (PIR) is placed in the upper corner of any room. It is at a LOW level as long as it does not detect anything, with the message "OK" appearing. When it detects a presence, it will go to HIGH level and the message "Alarm" will appear on the TFT screen. Just like the other sensor, we are informed about "PIR Alarm Detected". In both cases, when we change to "Alarm", the buzzer will sound until we press "Delete Message" on the screen. In the AZ Touch kit, the D1 Mini NodeMCU microcontroller is installed to receive the sensor identification data and the status of the digital pins. It also controls the display of the messages on the TFT screen and the sound of the buzzer.

To enable communication between the ESP8266, we need the MAC address of the microcontroller we will use as the receiver. To get this address, we connect the microcontroller to the computer, open the Arduino IDE, select the appropriate board and port, open the Serial Monitor, and run the sketch get_mac_address.ino to get the MAC address of the microcontroller. The MAC address of the microcontroller will appear in the Serial Monitor. Save this information as you will need it later.

Hint: to be able to program the ESP8266, you have to install the ESP8266 Arduino Core via the board manager. To do this, you must first add the appropriate board manager URL in the preferences:

https://arduino.esp8266.com/stable/package_esp8266com_index.json

/*-- Sketch to Obtain MAC address ESP module --*/  

#ifdef ESP32
#include <WiFi.h>
  
#else
#include <ESP8266WiFi.h>
#endif

void setup(){
      Serial.begin(115200);
      Serial.println();
      Serial.print("ESP Board MAC Address: ");
      Serial.println(WiFi.macAddress());
}
 
void loop(){

}

With this sketch, you can display the MAC address of ESP8266 and ESP32 microcontrollers. You have to select the appropriate board.

Hint: For the ESP32, which is not used in this project, you also need the matching board manager URL.

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

This alarm project consists of three sketches:

The names are chosen so that they can be easily assigned. The two sensor sketches are practically identical. Only the ID number of the sensor needs to be changed and a line with a delay of the presence sensor needs to be added to ensure optimal operation. Let's describe the sketch of the motion sensor (the two sketches of the transmitters are very similar, so only one of them will be described in more detail). and of the AZ Touch module.

Motion sensor pir_sensor_module_2.ino

Since we will use the ESP8266 microcontroller and ESP-NOW communication protocol, we need to include the appropriate libraries to implement its methods and instructions.

#include <ESP8266WiFi.h
#include <espnow.h>

After the libraries we have to define the GPIO pin of the ESP8266 to which we will connect the digital pin of the sensor. The connection pin to the ESP8266 is D7, which according to the datasheet corresponds to GPIO 13. In the next line, we need to specify the MAC address of the module to which we will send the data. In this project, this MAC address is that of the receiver microcontroller D1 Mini NodeMCU ESP8266 of the AZ-Touch. In the third line, we need to configure the number that will identify the sensor. For the PIR sensor it is emiter_board_id 2while for the linear magnetic Hall sensor it is emitter_board_id 1 is selected.

int pir_sensor = 13;
uint8_t receiver_board_MAC_address[] = {0xA4, 0xCF, 0x12, 0xF4, 0xCE, 0x12};
#define emitter_board_id 2

Next, we need to configure the structure that contains the data we will send. It gets the name sensor_state. It will then define the variables that we will use for the project. ID will be the sensor number (number 2 for the PIR sensor and number 1 for the magnetic hall sensor linear) and the variable value that contains the state value of the digital output of the sensor.

typedef struct sensor_state {
    int id;
    int value;
} sensor_state;

Once we have defined the structure, we need to implement an object of this structure, it gets the name state.

sensor_state state

Two variables are created for the interval of the sensor check. These variables contain the time of the last performed check. lastTime and timerDelay are included. In this case, the sensor check repeats every 1000 ms, i.e. at an interval of one second.

unsigned long lastTime = 0;
unsigned long timerDelay = 1000;

One of the features of the ESP-NOW protocol is that we can get information about the status of the sent data. This requires a "callback" (callback) with the OnDataSent-method, whose arguments are *mac_addrwhich is the MAC address of the receiving microcontroller, and sendStatus, which is the feedback for sending. A 0 means that the sending of the data was successful. A 1 means the sending of the data failed. The status information about sending data is displayed in the Serial Monitor.

void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
        Serial.print("\r\nLast Packet Send Status: ");
        if (sendStatus == 0){
          Serial.println("Delivery success");
        } else{
              Serial.println("Delivery fail");
        }
}

We implement the method setup()to configure the initial state of the microcontroller when it is powered or reset. First, we initialize the Serial Monitor.

Serial.begin(115200);

The communication between the modules will be done via Wi-Fi. But we will not use the wireless network of the home router, so we have to connect the ESP8266 with WiFi.disconnect() from the local network and configure it as an independent WiFi station with WiFi.mode(WIFI_STA).

WiFi.mode(WIFI_STA);
WiFi.disconnect();

Then it is checked whether the ESP8266 module was initialized correctly. If not, this is displayed in the Serial Monitor.

if (esp_now_init() != 0) {
  Serial.println("Error initializing ESP-NOW");
  return;
}

After the ESP8266 has been initialized, the module will be initialized with the statement esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER) to configure it as a transmitter. In the next line, we create the register esp_now_register_send_cbto execute the callback. Then we have to create a communication channel with the receiver by adding the receiving ESP8266 to the ESP-NOW communication table using the instruction esp_now_add_peer(receiver_board_MAC_address, ESP_NOW_ROLE_SLAVE, 1, NULL, 0) add. The arguments are the MAC address of the receiver module, the role of the receiver as "SLAVE" and the communication channel. Generally from 1 to 13, in this case, we choose channel 1. The last two arguments would be the communication key and the length. Since the access key is not configured here, it is set to NULL so its length is 0 (the last argument).

esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
esp_now_register_send_cb(OnDataSent);
esp_now_add_peer(receiver_board_MAC_address, ESP_NOW_ROLE_SLAVE, 2, NULL, 0);

The following line is only included in the motion sensor sketch because the sensor should be stabilized by applying a voltage to enable reliable detection. A waiting time of one minute is set.

delay (60*1000);

With this last line, we have the method setup() method. The method loop() has only a simple conditional block that is executed at an interval of one second. To execute the if block, we check if 1 second has passed by taking the stored time of the last data transfer (lastTime) with the current time (millis). If one second has passed, a data packet is sent. It contains the field sensor number and pin status of the sensor, which are stored in the data structure sensor_state data structure. The three arguments of the instruction esp_now_send(0, (uint8_t *) &state, sizeof(state)) are the MAC address of the receiving microcontroller, in this case with 0 we send the data to all addresses in the table (we have only one receiver), the packet with the two data (ID and state of the digital pin) and the size of the data packet to send. With the last line, we store the time of the last data transmission in the variable lastTime.

if ((millis() - lastTime) > timerDelay) {
  state.id = emitter_board_id;
  state.value = digitalRead (pir_sensor); 
  esp_now_send(0, (uint8_t *) &state, sizeof(state));
  lastTime = millis();
}

Receiver AZ-Touch az_touch_receiver_esp8266.ino

This sketch is loaded onto the microcontroller D1 Mini NodeMCU ESP8266, which will be installed in the AZ-Touch and will be the data receiver. The first two libraries that need to be included are the ones for the ESP8266 WiFi connection and the ESP-NOW communication protocol.

#include <ESP8266WiFi.h>
#include <espnow.h>

The other libraries are necessary to use the Arduino functions (Arduino.h), and the SPI communication protocol (SPI.h) to be able to communicate with the TFT screen. Also, the libraries for the use of the said TFT screen (Adafruit_ILI9341.h), the graphical function statements (Adafruit_GFX.h), and the "touch screen" (XPT2046_Touchscreen.h) are needed. The last library (Fonts/FreeSans9pt7B.h) contains the bitmap of the fonts or letters.

#include <Arduino.h>
#include <SPI.h>
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include <XPT2046_Touchscreen.h>
#include <Fonts/FreeSans9pt7b.h>

The next line describes the 2.8-inch screen in the AZ Touch as a constant.

#define AZ_TOUCH_MOD_BIG_TFT

The pins to which the TFT screen is connected are already given by default by the AZ-Touch design. In the declaration, we specify the GPIO pins for it. The first line is an indication that the display has a yellow pin connection. The second statement (#define TFT_CS 5) is the connection to the GPIO pin to enable or disable the TFT display. The third instruction (#define TFT_DC 4) is the connection to the GPIO pin where the data command pin of the TFT is connected. This tells it whether it is receiving data or a command. The last instruction (#define TFT_LED 15) is the connection to the GPIO pin that turns the display backlight on or off.

#define touch_yellow_header
#define TFT_CS 5
#define TFT_DC 4
#define TFT_LED 15

Next, the pins to which the "touch screen" of the TFT display is connected are declared. These pins are also specified by the design of the AZ-Touch. The first line is an indication that the screen has a touch screen. The second instruction (#define TOUCH_CS 0) is the connection to the GPIO pin that enables or disables the touch screen. The third instruction (#define TOUCH_IRQ 4) is the connection to the GPIO pin that signals that the touchscreen has been touched.

#define HAVE_TOUCHPAD
#define TOUCH_CS 0
#define TOUCH_IRQ 2

A buzzer is also used, the pin of which is specified with the following line:

#define BUZZER 16

The other constants are the parameters for using the touch screen. To interact with it, a minimum pressure on the screen is needed. This is configured with #define MINPRESSURE 10. In addition, the limits of the X- and Y-axis are needed. Values converted from analog to digital are used here to specify the minimum and maximum respectively.

#define MINPRESSURE 10
#define TS_MINX 370
#define TS_MINY 470
#define TS_MAXX 3700
#define TS_MAXY 3600

To use the methods and instructions of both the screen and the touchscreen, an object from each library is implemented. The CS pin is specified as an argument, for the screen additionally the DC pin. The functions of the individual pins have already been explained above.

Adafruit_ILI9341 tft_screen = Adafruit_ILI9341(TFT_CS, TFT_DC);
XPT2046_Touchscreen touch_screen(TOUCH_CS);

Now the structure that receives the data sent by the sender modules is configured. This structure must be the same as the one implemented in the two transmitter module sketches. As you will remember, it was sensor_state and contains the variables id (sensor number) and the variable value of the state of the digital pin of each sensor. The state variable of this structure is then created to store the received data in it.

typedef struct sensor_state {
      int id;
      int value;
} sensor_state;

sensor_state state;

Next, we create a variable of the previously specified structure for each transmitter, which will later be assigned the data received from the corresponding transmitter module. We also create an array containing these variables.

sensor_state hall_sensor;
sensor_state pir_sensor;

sensor_state emitter_boards[2] = {hall_sensor, pir_sensor};

As programmed in the transmitter module sketch, we also make a "callback" in the receiver module, which is executed when the receiver module receives data via ESP-NOW. The function void OnDataRecv() gets several arguments. The MAC address of the transmitter module with uint8_t * mac_addr, the status data of the digital pin of the transmitter module with uint8_t *incomingData and the size of the transmitted data with uint8_t len.

In the 18-character char array char macStr[18] the characters received from the MAC address of the modules are stored and processed with the function snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x : %02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]) everything is assembled to a string, and then it is displayed in the Serial Monitor with Serial.println(macStr) to spend.

void OnDataRecv(uint8_t * mac_addr, uint8_t *incomingData, uint8_t len) {     
  char macStr[18];
  Serial.print("Packet received from: ");
  
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);

  Serial.println(macStr);
}

Before we continue with the explanation of the sketch, we need to remember two things:

  • In the respective sketches of the transmitters the linear magnetic Hall sensor (#define emitter_board_id 1) is assigned ID 1 and the proximity sensor is assigned ID 2 (#define emitter_board_id 2) is assigned.
  • In the sketch of the receiver module an array with the names of the sensors is created (sensor_state emitter_boards[2] = {hall_sensor, pir_sensor}) is created. In the array, the first element is assigned ID 0, and the second ID 1.

It is necessary to remember this because [state.id-1] is used to identify the sensor that sent the information. So the ESP8266 of the magnetic Hall sensor sends the information with ID 1. 1 is subtracted from it and the sensor corresponding to this position in the array is checked.

Continue in the sketch. With memcpy(&state, incomingData, sizeof(state)) the data content of the incomingData-variable is written to the state-variable of the structure with the values sent by one of the ESP8266. With emitter_boards[state.id-1] the transmitter that sent the data is identified. This also determines the state of the digital pin of this module with emitter_boards[state.id-1].value. The information of the digital pin state with 0 or 1 and the length of the information, normally 8 bytes, are output in the Serial Monitor.

memcpy(&state, incomingData, sizeof(state));
Serial.printf("Sensor %u: %u bytes\n", state.id, len); 

emitter_boards[state.id-1].value = state.value;
Serial.printf("state: %d \n", emitter_boards[state.id-1].value);
Serial.println();

The information displayed on the TFT screen is divided into static text (the title, the names of the sensors, and the line for clearing the messages) and dynamic text (the status of the sensors and the line with the information of the sensor that detected the intrusion). Since the static text does not change, it is stored in the method setup() method. The dynamic text is programmed in the function OnDataRecv function, which is executed all the time. The following will describe how the dynamic text is displayed.

Since the digital pin status of the sensors can be HIGH or LOW and there are two sensors, a nested condition is programmed. Thus, the value received from the digital pin status is checked first. In each condition, multiple conditions are programmed with a switch instruction to check which of the sensors sent the value.

Then the first condition checked is whether the digital pin state of the received data is HIGH (if(state.value == HIGH)). If this condition is met, the instructions of the if block are executed, if it is LOW, the else block is executed.

if(state.value == HIGH) {                                           // If the value is a 1, this block is executed
  switch (state.id-1) {                                         // Emitter module check
    case 0:                                                   // Hall sensor module,the first position in the array
        Serial.println("Hall sensor normal situation"); 
        ...
    case 1:
        Serial.println("PIR Sensor Intrusion Detected");
        ...
  }
} else {                                                            // The value is not 1, then this other block is executed 
  switch (state.id-1) {                                         // Emitter module check
    case 0:
        Serial.println("Hall Sensor Intrusion Detected ");
        ...
    case 1:
        Serial.println("PIR Sensoe Normal Situation ");
        ...
  }
}

In the switch instruction, the following is printed via (state.id-1) is checked which of the sensors has sent the value. The sensor with position 0 of the matrix is the linear Hall sensor. The "Hall sensor normal situation" message is displayed on the Serial Monitor. The following instructions show the dynamic text on the TFT screen:

  • Set font with setFont(&FreeSans9pt7b)
  • Set text size to 1 with setTextSize(1)
  • Set cursor to coordinates x=180 and y=45 with setCursor(180, 45)
  • Set the text color to black with setTextColor(ILI9341_BLACK)
  • set the text "Alarm" with println("Alarm") to delete it, because it has the same color as the background
  • Set the cursor to the coordinates x=150 and y=45 withsetCursor(150, 45)
  • Set the text color to green with setTextColor(ILI9341_GREEN)
  • set the text "OK" with println("OK") set to green

In contrast, if the ID is 1 when the sensor is checked, it means that the data information has been sent from the motion sensor and the instructions for case 1 are executed. These are:

  • Output "PIR Sensor Intrusion Detected" in the Serial Monitor.
  • Set the font of the text with setFont(&FreeSans9pt7b)
  • Set text size withsetTextSize(1)
  • Cursor with setCursor(150, 70) set to the coordinates x=150 and y=70
  • Set the text color to black with setTextColor(ILI9341_BLACK)
  • Write the text "OK" with println("OK"), to delete the text in these coordinates, because the background color of the screen is black just like the text
  • Cursor with setCursor(180, 70) set to the coordinates x=180 and y=70
  • Set the text color to red with setTextColor(ILI9341_RED)
  • output the text "Alarm" with println("Alarm")
  • Cursor with setCursor(2, 145) set to the coordinates x=2 and y=145
  • Set the text color to black with setTextColor(ILI9341_BLACK)
  • output the text "Door Alarm Detected" with println("Door Alarm Detected")to delete the text at these coordinates, since the background color of the screen is also black.
  • Cursor with setCursor(2, 145) set to the coordinates x=2 and y=145
  • Set the text color to red with setTextColor(ILI9341_RED)
  • output the text PIR "Alarm Detected" withprintln("PIR Alarm Detected")
  • Buzzer sounds with a frequency of 4000 Hz with tone(buzzer, 4000)until the alarm message is cleared later

In case the digital pin of the sensors is LOW, the else block is executed as described above. The commands are very similar to the if block.

Now let's see what needs to be implemented to clear the alarm message from the TFT screen and turn off the sound. For this, a condition is programmed, because to clear the message, an area of the TFT screen must be touched with pressure. It must be larger than the configured minimum. If you click in the area bounded by the coordinates x>4 and y>280 pixels and apply a pressure greater than a value of 10, a black box with a width of 239 and a height of 30 pixels will be drawn on the screen with its upper left corner at the coordinates x=0 and y = 145. So everything inside this box is cleared. Also, the state of the buzzer's GPIO pin is changed to LOW so that it stops beeping.

if (touch_screen.touched()) {
  TS_Point p = touch_screen.getPoint();
  if (p.x > 4 && p.y > 280 && p.z > MINPRESSURE) {
    tft_screen.fillRect (0, 145, 239, 30, ILI9341_BLACK); 
    digitalWrite(buzzer, LOW);        
  }
}

As far as the OnDataRecv()-function. The following is the description of the setup() function. At first the initialization of the Serial Monitor with Serial.begin(115200) is programmed.

As with the ESP8266 of the transmitter modules, the WLAN of the home router is not used. So the ESP8266 is disconnected from the local network with WiFi.disconnect() and is programmed as an independent WiFi station with WiFi.mode(WIFI_STA) configured.

WiFi.mode(WIFI_STA);
WiFi.disconnect();

It is also checked if the ESP8266 module was initialized correctly. If not, it is output in the Serial Monitor.

if (esp_now_init() != 0) {
  Serial.println("Error initializing ESP-NOW");
  return;
}

With esp_now_set_self_role(ESP_NOW_ROLE_SLAVE) we configure the receiver as "SLAVE". In the next line, we create the register esp_now_register_recv_cb(OnDataRecv), to use the function OnDataRecv function when the data is received.

esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
esp_now_register_send_cb(OnDataRecv);

With the function pinMode(TFT_LED, OUTPUT) the pin of the ESP8266 to which the backlight of the TFT screen is connected is configured as output.

In the following lines, the TFT screen is configured with tft_screen.begin() and the touchscreen with touch_screen.begin() initialized. In the Serial Monitor, the information of the width (X-axis) and height (Y-axis) of the screen is output in pixels. With the last line, the whole screen is filled with black.

Serial.println("Init TFT and Touch ...");      
tft_screen.begin();         
touch_screen.begin();         
Serial.print("TFT x = ");         
Serial.print(tft_screen.width());       
Serial.print("TFT y = ");         
Serial.print(tft_screen.height());      
tft_screen.fillScreen(ILI9341_BLACK);

The instruction digitalWrite(TFT_LED, HIGH) sets the GPIO pin of the microcontroller, to which the backlight of the TFT screen is connected, to a HIGH state. This is how it is switched on. The rest of the code in the setup() is the output of the static text on the screen.

In this sketch the loop()-function is empty.

Have fun with this project and we look forward to your comments and suggestions, thank you.



Esp-8266SensorenSmart home

8 commentaires

Lutz

Lutz

Ein Link zu einer Beschreibung ,die das mit der Benutzung der Verschlüsselung zeigt. Man sollte sich aber nicht zuviel davon versprechen, es sind eben nur 128 Bits (16*8). Und die Anzahl der Aktoren wird auf ca. 6 begrenzt
https://www.14core.com/how-to-esp32-encrypted-data-transmission-send-receive-data-with-esp-now-protocol/

Miguel Torres

Miguel Torres

Hi Sven,

We are glad to hear that you liked this project and will be using it.

Thank you very much and best regards,

Miguel Torres Gordo

Miguel Torres

Miguel Torres

Hi Ilija,

Thanks for the comment about MQTT.

Best regards,

Miguel Torres Gordo

Sven Hesse

Sven Hesse

Hallo Miguel,

vielen Dank für dieses Tutorial.
Da bei mir immer einige Teile auf “Halde” liegen – so auch alle benötigten für diese Alarmanlage – habe ich direkt aufgebaut.
Nachdem ich einen, durch zu schnelles Lesen entstandenen, Fehler beseitigt habe, funktioniert alles bestens.
Da der Stromverbrauch recht gering ist, werde ich diese Anlage wohl für meine Gartenlaube verwenden (hier habe ich nur Solarstrom zur Verfügung).

Also – nochmals vielen Dank.
Sven

Ilija Iliev

Ilija Iliev

Hi Freddy,
MQTT need server to re-transmit messages between units. This need additional resources as Raspberry or Esp32 as server and it cannot go to sleep mode, so need much more energy. In addition MQTT can be hacked easy, if you want stable project, then you should activate security on server. Activating security/encryption is nightmare and need cerificates, so not an easy game.
Regards
Ilija

Miguel Torres

Miguel Torres

Hello Freddy Aout,

For powering the ESP8266 external batteries with 5VDC outputs have been used in this project to charge cell phones. The power consumption of the ESP is low, so the battery life should be great and depends on the battery capacity.

From what I have been able to read, the MQTT communication protocol is compatible with ESP8266, but the sketch would have to be adapted to the new communication protocol and tested.

Best regards,
Miguel Torres Gordo

Freddy Aout

Freddy Aout

Qu’utilisez vous comme batterie pour alimenter les ESP8266 ? Combien de temps tiennent elles ?
Une autre question : Pourrait on remplacer EPS-NOW par des messages MQTT ?

What do you use as a battery to power the ESP8266? How long do they last?
Another question: Could EPS-NOW be replaced by MQTT messages?

Joachim

Joachim

Schönes Projekt.
Schade das Sie für eine Alarmanlage keine verschlüsselte Übertragung gewählt oder beschrieben haben.

Laisser un commentaire

Tous les commentaires sont modérés avant d'être publiés

Articles de blog recommandés

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery