ESP32 with FreeRTOS Queues: Inter-Task Communication (Arduino IDE)

In this guide, you’ll learn how to use FreeRTOS queues for safe and efficient communication between tasks on the ESP32, using the Arduino IDE. Queues allow you to exchange data between tasks in a safe way. We’ll cover the basic principles of how queues work and walk you through three practical examples to show how to pass data between tasks.

ESP32 with FreeRTOS Queues: Inter-Task Communication (Arduino IDE)

New to FreeRTOS? Start with this tutorial: ESP32 with FreeRTOS (Arduino IDE)—Getting Started Guide: Creating Tasks.

Table of Contents

In this tutorial, we’ll cover the following subjects:

FreeRTOS Communication Between Tasks

In FreeRTOS there are different ways for inter-task communication and synchronization of data between tasks, suited for different purposes.

  • Queues: allow tasks to send and receive data, like sensor readings, button states, or other data, as we’ll see in the examples covered in this tutorial. Here we’ll focus on FreeRTOS queues.
  • Semaphores are like signaling tools in FreeRTOS used to coordinate tasks. They are often used to indicate that an event has occurred or a resource is ready. They don’t carry data, just a count (or flag) to trigger actions after something has happened. We’ll cover this in a future tutorial.
  • Mutexes (mutual exclusions) are used to protect shared resources by allowing only one task to access them at a time. This prevents conflicts in concurrent operations that need to access the same data. Unlike queues, mutexes focus on resource access rather than data transfer. We’ll cover this in a future tutorial.

What are FreeRTOS Queues?

FreeRTOS queues are thread-safe data structures that allow tasks to send and receive data for synchronized communication in a multitasking environment.

They ensure that data is passed safely between tasks without conflicts or data loss.

Queues are especially useful when one task needs to send data and another task needs to handle it later, like a sensor task sending readings to a logging task or to be processed to display on a screen. This way, each task can run at its own speed without interfering with the other.

FreeRTOS Queue Basics

Here are some basic concepts about creating and handling queues on your Arduino IDE code for the ESP32:

Creating a Queue

Use the xQueueCreate(size, item_size) to create a task—size corresponds to the number of items that can be on the queue and item_size is the bytes size of heap allocated for each item on the queue.

Sending Data to a Queue

To send data to a queue use the xQueueSend() to add data to the queue. Or use xQueueSendFromISR() if sending the data from an ISR (interrupt service routine).

Receive Data from a Queue

The xQueueReceive() function reads data from the queue, if available.


Example 1: Using Queues to Control LED Brightness

ESP32 with an LED and a Potentiometer

In this example, we’ll create two tasks. One task will send data to a queue. The other task will read data from the queue.

We’ll control the LED brightness using a potentiometer. This is a basic example, you’ve probably seen before, to learn about analog reading and PWM. In this case, we’ll use that basic example as a starting point to explain queues.

We’ll create two tasks with the following names:

  • potTask: reads the value from the potentiometer and sends it to a queue.
  • LEDBrightnessTask: checks if there are values to receive from the queue and adjusts the LED brightness accordingly.

Parts Required

For this project, you need the following parts:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Wiring the Circuit

For this example, wire an LED and a potentiometer to your ESP32 board. We’re connecting the LED to GPIO 2 and the potentiometer to GPIO 15.

ESP32 Connected to an LED and a potentiometer

Code – Control LED Brightness (Queues Basic Example)

The following code creates two tasks. One reads the values from the potentiometer and sends them to a queue. The other task reads the values from the queue and controls the LED brightness accordingly.

You can copy the following code to the Arduino IDE and upload it to your board.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-queues-inter-task-arduino/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define POT_PIN 15 
#define LED_PIN 2   
#define PWM_FREQ 5000
#define PWM_RESOLUTION 12  // 12-bit (0–4095)
#define QUEUE_SIZE 5

// Create an handle for the queue
QueueHandle_t potQueue = NULL;

void potTask(void *parameter) {
  for (;;) {
    uint16_t potValue = analogRead(POT_PIN);  // Read 0–4095
    xQueueSend(potQueue, &potValue, portMAX_DELAY);  // Send to queue
    Serial.printf("potTask: Sent pot value %u\n", potValue);
    vTaskDelay(100 / portTICK_PERIOD_MS);  // 100ms
  }
}

void LEDBrightnessTask(void *parameter) {
  for (;;) {
    uint16_t potValue;
    if (xQueueReceive(potQueue, &potValue, portMAX_DELAY)) {
      uint16_t brightness = potValue;
      ledcWrite(LED_PIN, brightness);
      Serial.printf("LEDBrightnessTask: Set brightness %u\n", brightness);
    }
  }
}

void setup() {
  Serial.begin(115200);

  // Setup PWM for LED
  ledcAttach(LED_PIN, PWM_FREQ, PWM_RESOLUTION);

  // Create queue (5 items, each uint16_t)
  potQueue = xQueueCreate(QUEUE_SIZE, sizeof(uint16_t));
  if (potQueue == NULL) {
    Serial.println("Failed to create queue!");
    while (1);
  }

  // Create tasks
  xTaskCreatePinnedToCore(
    potTask,
    "potTask",
    3000,  // Task stack
    NULL,
    1,
    NULL,
    1  // Core 1
  );

  xTaskCreatePinnedToCore(
    LEDBrightnessTask,
    "LEDBrightnessTask",
    3000,
    NULL,
    1,
    NULL,
    1
  );
}

void loop() {
  // Empty
}

View raw code

How Does the Code Work?

Let’s take a look at the code to see how it works.

Define the LED and Potentiometer GPIOs

First define the GPIO pins for the LED and for the potentiometer.

#define POT_PIN 15
#define LED_PIN 2   

PWM Properties

Then, define the PWM frequency and resolution to adjust the LED brightness. We’re setting 12-bit resolution (the same default resolution for the analog reading).

#define PWM_FREQ 5000
#define PWM_RESOLUTION 12  // 12-bit (0–4095)

If you’re new to PWM with the ESP32, check this tutorial to learn more: ESP32 PWM with Arduino IDE (Analog Output).

FreeRTOS Queue Size

Define the size of the FreeRTOS queue. This is the number of items that can the queue can hold. In this case, we’re setting it to 5.

#define QUEUE_SIZE 5

In our case, this is a good estimate because the items sent to the queue will be almost imediately consumed by the other task.

Queue Handle

Create an handle for the queue. This is how we’ll refer to our queue throughout our code. We’ll refer to the queue as potQueue.

// Create an handle for the queue
QueueHandle_t potQueue = NULL;

potTask Function

Create a function for the pontentiometer task: potTask. This is the task that will read from the pontentiometer.

void potTask(void *parameter) {
  for (;;) {
    uint16_t potValue = analogRead(POT_PIN);  // Read 0–4095
    xQueueSend(potQueue, &potValue, portMAX_DELAY);  // Send to queue
    Serial.printf("potTask: Sent pot value %u\n", potValue);
    vTaskDelay(100 / portTICK_PERIOD_MS);  // 100ms
  }
}

We start by reading the value from the potentiometer and save it in a uint16_t type of variable called potValue.

uint16_t potValue = analogRead(POT_PIN);  // Read 0–4095

To learn more about reading analog values with the ESP32, check out this tutorial: ESP32 ADC – Read Analog Values with Arduino IDE.

Send an Item to the Queue

Then, we’ll use the xQueueSend() function to send an item to the queue. We’re sending the potValue to the potQueue as follows.

Serial.printf("potTask: Sent pot value %u\n", potValue);

The last parameter, portMAX_DELAY means that the task will wait indefinitely until there is space in the queue for this new item. If you don’t want it to wait, and want to return immmediately, pass 0 instead.

vTaskDelay

Then, we wait 100 milliseconds between each reading.

vTaskDelay(100 / portTICK_PERIOD_MS);  // 100ms

In FreeRTOS tasks, we must use the vTaskDelay() function instead of the usual delay() arduino function. It accepts as argument the number of ticks to wait. In case of the ESP32, one tick corresponds to 1ms=portTICK_PERIOD_MS.

LEDBrightnessTask

The following lines create the function for the task that controls the LED brightness. It’s called LEDBrightnessTask.

void LEDBrightnessTask(void *parameter) {
  for (;;) {
    uint16_t potValue;
    if (xQueueReceive(potQueue, &potValue, portMAX_DELAY)) {
      uint16_t brightness = potValue;
      ledcWrite(LED_PIN, brightness);
      Serial.printf("LEDBrightnessTask: Set brightness %u\n", brightness);
    }
  }
}

We create a local variable called potValue that will receive the values in the queue.

uint16_t potValue;
Get Values From the Queue

To get values from the queue we can use the xQueueReceive() function. Pass the queue handle as an argument, a pointer to the variable where the value should be saved and whether to wait for an item to be available (portMAX_DELAY) or to return immediately (pass 0).

if (xQueueReceive(potQueue, &potValue, portMAX_DELAY)) {
Control the LED Brightness

Because we’re controlling PWM with 12-bit resolution and using the default resolution for PWM (12-bit), the brightness will the be the same as the potValue.

uint16_t brightness = potValue;

Finally, we can use the ledcWrite() function to adjust the brightness of the LED.

ledcWrite(LED_PIN, brightness);

setup()

In the setup(), initialize the Serial Monitor at a baud rate of 115200.

Serial.begin(115200);

Set up the PWM properties.

ledcAttach(LED_PIN, PWM_FREQ, PWM_RESOLUTION);
Creating the Queue

Create the actual queue using the xQueueCreate() function. This accepts as argument the queue size and the size of each item in bytes. Since we’re storing uint16_t variables, we’re passing the size of uint16_t, which is 2 bytes (16 bits).

// Create queue (5 items, each uint16_t)
potQueue = xQueueCreate(QUEUE_SIZE, sizeof(uint16_t));
if (potQueue == NULL) {
  Serial.println("Failed to create queue!");
  while (1);
}
Creating the Tasks

Then, create the tasks that handle the potentiometer and control the LED brightness. We’re assigning both tasks to core 1.

// Create tasks
xTaskCreatePinnedToCore(
  potTask,
  "potTask",
  3000,  // Task stack
  NULL,
  1,
  NULL,
  1  // Core 1
);

xTaskCreatePinnedToCore(
  LEDBrightnessTask,
  "LEDBrightnessTask",
  3000,
  NULL,
  1,
  NULL,
  1
);

We covered how to create tasks in this previous tutorial: ESP32 with FreeRTOS (Arduino IDE) – Getting Started: Create Tasks

loop()

The loop() is empty because FreeRTOS handles the tasks.

void loop() {
  // Empty
}

Demonstration

Upload the code to your board. Press the onboard RST button, so that the ESP32 starts running the code.

Then, open the Serial Monitor at a baud rate of 115200. You should get something similar.

ESP32 freertos tasks with queues - demonstration in serial monitor

Rotate the potentiometer and see the LED brightness increase and decrease accordingly.

ESP32 Control LED brightness with pontentiometer

Example 2: FreeRTOS Queues – Sharing Data Between Three Tasks

Data can be shared between multiple FreeRTOS tasks. In the previous example, we passed data from one task to another. But we can pass data to as many tasks as we want.

This example is a modification of the previous project, with the addition of a third task. This third task will be responsible for retrieving the potentiometer values from the queue and displaying the values along with a timestamp in the Serial Monitor. I also removed all Serial prints from the other tasks.

This is a simple example that shows how you can use queues to share data between multiple tasks.

Wiring the Circuit

Keep the same circuit with an LED and a potentiometer from the previous example.

Code: FreeRTOS Queues – Three Tasks

Copy the following code to the Arduino IDE.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-queues-inter-task-arduino/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define POT_PIN 15 
#define LED_PIN 2   
#define PWM_FREQ 5000
#define PWM_RESOLUTION 12  // 12-bit (0–4095)
#define QUEUE_SIZE 5

QueueHandle_t potQueue = NULL;

void SensorTask(void *parameter) {
  for (;;) {
    uint16_t potValue = analogRead(POT_PIN);  // Read 0–4095
    xQueueSend(potQueue, &potValue, portMAX_DELAY);  // Send to queue
    vTaskDelay(300 / portTICK_PERIOD_MS);  // 100ms
  }
}

void LEDBrightnessTask(void *parameter) {
  for (;;) {
    uint16_t potValue;
    if (xQueueReceive(potQueue, &potValue, portMAX_DELAY)) {
      uint16_t brightness = potValue;
      ledcWrite(LED_PIN, brightness);
    }
  }
}

void SerialLoggerTask(void *parameter) {
  for (;;) {
    uint16_t potValue;
    if (xQueueReceive(potQueue, &potValue, portMAX_DELAY)) {
      Serial.printf("SerialLoggerTask: Pot value %u at %lu ms\n", potValue, millis());
    }
  }
}

void setup() {
  Serial.begin(115200);

  // Setup PWM for LED
  ledcAttach(LED_PIN, PWM_FREQ, PWM_RESOLUTION);

  // Create queue (5 items, each uint16_t)
  potQueue = xQueueCreate(QUEUE_SIZE, sizeof(uint16_t));
  if (potQueue == NULL) {
    Serial.println("Failed to create queue!");
    while (1);
  }

  // Create tasks with one parameter per line
  xTaskCreatePinnedToCore(
    SensorTask,             // Task function
    "SensorTask",           // Task name
    3000,                   // Stack size (bytes)
    NULL,                   // Task parameters
    1,                      // Priority
    NULL,                   // Task handle
    1                       // Core ID
  );

  xTaskCreatePinnedToCore(
    LEDBrightnessTask,      // Task function
    "LEDBrightnessTask",    // Task name
    3000,                   // Stack size (bytes)
    NULL,                   // Task parameters
    1,                      // Priority
    NULL,                   // Task handle
    1                       // Core ID
  );

  xTaskCreatePinnedToCore(
    SerialLoggerTask,       // Task function
    "SerialLoggerTask",     // Task name
    3000,                   // Stack size (bytes)
    NULL,                   // Task parameters
    1,                      // Priority
    NULL,                   // Task handle
    1                       // Core ID
  );
}

void loop() {
  // Empty
}

View raw code

How Does the Code Work?

This code is very similar to the previous example, but we add an additional task. The SerialLoggerTask().

void SerialLoggerTask(void *parameter) {
  for (;;) {
    uint16_t potValue;
    if (xQueueReceive(potQueue, &potValue, portMAX_DELAY)) {
      Serial.printf("SerialLoggerTask: Pot value %u at %lu ms\n", potValue, millis());
    }
  }
}

This task gets the potValue from the potQueue.

if (xQueueReceive(potQueue, &potValue, portMAX_DELAY)) {

And then, displays it in the Serial Monitor along a timestamp.

Serial.printf("SerialLoggerTask: Pot value %u at %lu ms\n", potValue, millis());

You also need to create this new task in the setup().

xTaskCreatePinnedToCore(
  SerialLoggerTask,       // Task function
  "SerialLoggerTask",     // Task name
  3000,                   // Stack size (bytes)
  NULL,                   // Task parameters
  1,                      // Priority
  NULL,                   // Task handle
  1                       // Core ID
);

Demonstration

The result will be similar to the previous example, but now you have the SerialLoggerTask displaying the values and the timestamp in the Serial Monitor.

Esp32 with a freertos task that prints data to the Serial Monitor - demonstration

This simple example is to demonstrate that you can pass data to as many tasks as you want with multiple queues.


Example 3: Display Sensor Data on a Screen (Sharing a Data Structure)

ESP32 Display Sensor REadings from a BME280 sensor on an OLED display

In this example, we’ll display sensor readings from a BME280 on an OLED display using FreeRTOS tasks. The process is divided into two main tasks:

SensorTask:

  • Reads temperature, humidity, and pressure from the BME280 sensor
  • Sends the sensor data to a queue

DisplayTask

  • Receives sensor data from the queue
  • Displays the readings on the OLED screen

The aim of this example is to demonstrate how to share a data structure containing multiple variables (temperature, humidity, and pressure) between tasks.

Parts Required

For this example, you need the following parts:

Wiring the Circuit

Wire the BME280 and the OLED display to the ESP32 default I2C pins.

ESP32 circuit with OLED Display and BME280 connected to the ESP32 default I2C pins

You may also like reading: ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals (Arduino IDE).

BME280ESP32
VIN3V3
GNDGND
SCLGPIO 22
SDAGPIO 21
OLED DisplayESP32
VIN3V3
GNDGND
SCLGPIO 22
SDAGPIO 21

Code – Display Sensor Data on a Screen using FreeRTOS Queues

Copy the following code to the Arduino IDE.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-queues-inter-task-arduino/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDRESS 0x3C

#define QUEUE_SIZE 5

Adafruit_BME280 bme;

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

QueueHandle_t sensorQueue = NULL;

typedef struct {
  float temperature;
  float humidity;
  float pressure;
} SensorData;

void SensorTask(void *parameter) {
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
  for (;;) {
    SensorData data;
    data.temperature = bme.readTemperature();  
    data.humidity = bme.readHumidity();        
    data.pressure = bme.readPressure() / 100.0F;  
    xQueueSend(sensorQueue, &data, portMAX_DELAY);
    Serial.print("SensorTask: Sent Temp=");
    Serial.print(data.temperature, 1);
    Serial.print("°C, Hum=");
    Serial.print(data.humidity, 1);
    Serial.print("%, Pres=");
    Serial.print(data.pressure, 1);
    Serial.println("hPa");
    vTaskDelay(2000 / portTICK_PERIOD_MS);  // 2s
  }
}

void DisplayTask(void *parameter) {
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
    Serial.println("OLED init failed!");
    while (1);
  }
  display.clearDisplay();
  display.setTextSize(1); 
  display.setTextColor(SSD1306_WHITE);

  for (;;) {
    SensorData data;
    if (xQueueReceive(sensorQueue, &data, portMAX_DELAY)) {

      // Display on OLED
      display.clearDisplay();
      display.setCursor(0, 0);
      display.print("Temperature: ");
      display.print(data.temperature, 1);
      display.print(" ");
      display.cp437(true);
      display.write(167);
      display.println("C");
      display.setCursor(0, 20);
      display.print("Humidity: ");
      display.print(data.humidity, 1);
      display.println(" %");
      display.setCursor(0, 40);
      display.print("Pressure: ");
      display.print(data.pressure, 1);
      display.println(" hPa");
      display.display();
    }
  }
}

void setup() {
  Serial.begin(115200);
  
  // Starts I2C on the board default's  I2C pins
  Wire.begin();

  // Create queue
  sensorQueue = xQueueCreate(QUEUE_SIZE, sizeof(SensorData));
  if (sensorQueue == NULL) {
    Serial.println("Failed to create queue!");
    while (1);
  }

  // Create tasks
  xTaskCreatePinnedToCore(
    SensorTask,             // Task function
    "SensorTask",           // Task name
    4000,                   // Stack size (bytes)
    NULL,                   // Task parameters
    1,                      // Priority
    NULL,                   // Task handle
    1                       // Core ID
  );

  xTaskCreatePinnedToCore(
    DisplayTask,            // Task function
    "DisplayTask",          // Task name
    4000,                   // Stack size (bytes)
    NULL,                   // Task parameters
    1,                      // Priority
    NULL,                   // Task handle
    1                       // Core ID
  );
}

void loop() {
  // Empty
}

View raw code

How Does the Code Work?

At this point, you should be familiar with most of the code.

To send multiple variables to the queue, we create a structure that will contain temperature, humidity, and pressure. We call that type of structure SensorData.

Alternatively, you can also use JSON objects, but I find structures easier to use for this scenario.

typedef struct {
  float temperature;
  float humidity;
  float pressure;
} SensorData;

The SensorTask function is responsible for retrieving the current sensor readings and sending them to the queue. We create a variable called data, which is a data structure of type SensorData (the one we created previously).

SensorData data;

Then, we add values to the data structure as follows.

data.temperature = bme.readTemperature();  
data.humidity = bme.readHumidity();        
data.pressure = bme.readPressure() / 100.0F;  

Now that we have populated the data structure with the latest sensor readings, we can send it to the queue, just as we did in the previous examples, using the xQueueSend() function.

xQueueSend(sensorQueue, &data, portMAX_DELAY);

In the DisplayTask function, we get the data from the queue and display it on the OLED screen.

void DisplayTask(void *parameter) {
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
    Serial.println("OLED init failed!");
    while (1);
  }
  display.clearDisplay();
  display.setTextSize(1); 
  display.setTextColor(SSD1306_WHITE);

  for (;;) {
    SensorData data;
    if (xQueueReceive(sensorQueue, &data, portMAX_DELAY)) {

      // Display on OLED
      display.clearDisplay();
      display.setCursor(0, 0);
      display.print("Temperature: ");
      display.print(data.temperature, 1);
      display.print(" ");
      display.cp437(true);
      display.write(167);
      display.println("C");
      display.setCursor(0, 20);
      display.print("Humidity: ");
      display.print(data.humidity, 1);
      display.println(" %");
      display.setCursor(0, 40);
      display.print("Pressure: ");
      display.print(data.pressure, 1);
      display.println(" hPa");
      display.display();
    }
  }
}

As in the previous examples, in the setup(), we create the queue and the tasks, and assign them to an ESP32 core.

void setup() {
  Serial.begin(115200);
  
  // Starts I2C on the board default's  I2C pins
  Wire.begin();

  // Create queue
  sensorQueue = xQueueCreate(QUEUE_SIZE, sizeof(SensorData));
  if (sensorQueue == NULL) {
    Serial.println("Failed to create queue!");
    while (1);
  }

  // Create tasks
  xTaskCreatePinnedToCore(
    SensorTask,             // Task function
    "SensorTask",           // Task name
    4000,                   // Stack size (bytes)
    NULL,                   // Task parameters
    1,                      // Priority
    NULL,                   // Task handle
    1                       // Core ID
  );

  xTaskCreatePinnedToCore(
    DisplayTask,            // Task function
    "DisplayTask",          // Task name
    4000,                   // Stack size (bytes)
    NULL,                   // Task parameters
    1,                      // Priority
    NULL,                   // Task handle
    1                       // Core ID
  );
}

The loop() is empty because FreeRTOS handles the tasks.

void loop() {
  // Empty
}

Demonstration

Upload the previous code to your ESP32 board. After uploading, press the onboard RST button so that the board starts running the code.

Open the Serial Monitor at a baud rate of 115200. Every two seconds, it will display updated sensor readings.

ESP32 Sensor Task (FreeRTOS) displayed on the serial monitor every two seconds

The screen is updated at the same rate as new readings are sent to the queue.

ESP32 Display BME280 Sensor Readings on OLED Display using FreeRTOS queues

Wrapping Up

In this tutorial, you learned how to use FreeRTOS queues to pass data from one task to the other in a safe way. We’ve shown you three different examples that demonstrate how easy it is to use queues to communicate between tasks. After understanding those basic examples, the idea is to apply this concept to more complex projects, where accessing variables’ values simultaneously might be critical to the program.

We hope you’ve found this tutorial useful. We’ll be creating more FreeRTOS guides with the ESP32 programmed with Arduino IDE soon. So, stay tuned.

Learn more about the ESP32 with our resources:

Thanks for reading.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.