# Internet of Things

# PlatformIO Guide

### About

PlatformIO is a versatile, open-source ecosystem designed for embedded development, providing a unified platform for building, managing, and debugging firmware across a wide range of microcontroller architectures. Integrated with popular IDEs like VSCode and Atom, it offers powerful tools for code editing, dependency management, and cross-compilation. PlatformIO simplifies the development process with its rich library ecosystem, support for multiple frameworks, and seamless integration with version control systems, making it an invaluable tool for both hobbyists and professionals in the embedded systems community.

### Install PlatformIO

#### 1\. Install VScode

If you haven't already, install VSCode on your machine. [Download Here](https://code.visualstudio.com/download)

#### 2\. Install PlatformIO Extension

After you've finished installing and setting up **VSCode**, open the **Extensions** menu, search for **PlatformIO**, and then install it.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-hkx0svho.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725091869668.png)

  

### Creating A New Project

#### 1\. Open the Projects & Configurations Tab

Open the **PlatformIO** menu. Then, within the **Quick Access** tab, select the **Projects & Configuration**s tab.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-sgmjnwgh.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725091985813.png)

#### 2\. Click Create New Project and Follow the Project Wizard

Click the **Create New Project** button, then fill out the required fields for the project.

First, set the name of the project.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-ownwcqeb.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725092300014.png)

Second, choose the board you want to use. In this example it will be **DOIT ESP32 DEVKIT V1**

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-q80utss8.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725092411197.png)

Third, choose which framework you want to develop in. In this example, it will be the default choice, which is the **Arduino** framework.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-sboyileh.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725092499290.png)

_**(Optional)**_ Uncheck the **Use default location** checkbox and choose the project location.

Navigate through your file system and choose a folder.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-tqum4omr.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725092670242.png)

Lastly, click **Finish**

#### 3\. Open the Created Project

The project should be opened automatically, but if not, open it in the **Projects & Configuration**s tab, similar to step 1.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-yygi1fya.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725108822028.png)

Once the project is opened. Navigate to src/main.cpp file via the **Explorer** tab.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-xodceu0i.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725108936163.png)

And done. This main.cpp file will be where you'll be coding your project, similar to a .ino file in the Arduino IDE.

### Choosing Ports

By default, PlatformIO automatically chooses the correct port to upload your code to your board. Most of the time, it works. But if it doesn't work or maybe you have 2 or more boards connected, here's how you can choose the port of your active board.

#### 1\. Click the Port Button

Click the **Set upload/monitor/test port** button at the bottom.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-6x0moc26.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725109838911.png)

#### 2\. Select an Active Port

Choose the port connected to your machine. Its most likely described as either **CP210x** or **CH340**.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-xsacjvxl.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725109939037.png)

### Choosing a Project Environtment

If you have more that 1 project, you need to choose a default project so that PlatformIO knows which project are you currently working on.

#### 1\. Choose the Project Button

Click the **Switch PlatformIO Project Environtment** button at the bottom.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-sp6mxawd.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725110279158.png)

#### 2\. Choose a Project

Choose the project you want to work on. Utilize the search feature to ease your searching.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-hgrxl1tz.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725110351600.png)

### Uploading Code

Make sure that your port and project environtment is configured properly. Follow the steps above if you haven't done this already.

#### 1\. Click the Upload Button

Click the Upload Button

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-gpbvmamy.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725110582312.png)

#### 2\. Hold the Boot Button on ESP32 if using an ESP32

Wait for your code to be compiled first. Then, when the **connecting........** appears, hold the boot button on the ESP32 until the uploading process is complete.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-pzeez4dc.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725110750391.png)

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-jizo6ewm.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725110797679.png)

### Serial Monitor

#### 1\. Configure the Serial Baud Rate

Navigate to the **platformio.ini** file within your project. Then, add the line

    monitor_speed = [Your baud rate]
    

Example :

    monitor_speed = 115200
    

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-y6h4pyii.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725111379396.png)

#### 2\. Click the Serial Monitor Button

Wait for the project to update, then click the **Serial Monitor** button.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-oxkazf0m.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725111700684.png)

### Installing Libraries

You can install any library from PlatformIO.

#### 1\. Open the Libraries Page

Click the PlatformIO button, then open the **Libraries** Page

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-mqq5sbxu.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725111895158.png)

#### 2\. Search for a Library

Search for the Library you need

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-g5jmfvsu.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725112099312.png)

#### 3\. Add to Project

Click the **Add to Project** button. From this page, you can also view code examples, link to github repo, etc.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-grv7bytz.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725112162085.png)

#### 4\. Choose Project

Choose the project you want to add the library. Then, click **Add**

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-spm7nfms.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725112259311.png)

#### 5\. Check the .ini File

Check if the lib\_deps line is added. Save the file (Ctrl + S) if your file doesn't autosave.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-recnw8ep.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725112343105.png)

#### 6\. Press the Build Button

Build the project so you can have code auto-completion.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-co4xadf1.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725112591439.png)

#### 7\. Include the Project

Include the header file and you code auto-completion should work. Then, you can freely use the libary.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-7ryd7llr.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725112632568.png)

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-08/embedded-image-jlml2rpz.png)](https://learn.digilabdte.com/uploads/images/gallery/2024-08/image-1725112956005.png)

# Module 1 - Introduction to SMP with RTOS

# 1.1 Learning Objectives

After completing this module, students are expected to be able to:
- Understand the difference between a General-Purpose Operating System (GPOS) and a Real-Time Operating System (RTOS)
- Understand the differences and benefits of Multi-Threading on a Microcontroller
- Understand the specifications of the ESP-32 as a microcontroller for IoT purposes

##### What Will We Learn?

In previous courses, you may already be familiar with Operating Systems (OS) and the use of Microcontrollers for Cyber-Physical Systems.

In those two cases, you used Linux, a General-Purpose Operating System (GPOS), and programmed using Arduino with a bare-metal architecture (running directly on the hardware without an OS).

In this IoT practicum, we will introduce the Real-Time Operating System (RTOS), a crucial architecture in embedded systems and IoT applications.

# 1.2 Introduction to RTOS

##### GPOS
The types of OS we often use (Windows, Linux, Mac, Android, iOS) can be classified as GPOS, which, as the name suggests, are designed for general purposes and typically utilize a GUI or CLI as the human interaction interface.

GPOS systems are designed to run multiple processes simultaneously, generally supported by multitasking and multi-threading, allowing the user to run several tasks at once. In general, timing deadlines for tasks in a GPOS are not crucial, and delays in tasks can be tolerated as long as they are not noticeable to the user[[1](https://www.digikey.com/en/maker/projects/what-is-a-realtime-operating-system-rtos/28d8087f53844decafa5000d89608016)]. For example, when a user opens a PDF document while listening to music on Spotify, both applications can run concurrently. If the system experiences a slight delay, such as the PDF page rendering taking longer or the audio buffering for a moment, this is still tolerable and does not significantly disrupt the user experience.

GPOS is non-deterministic, meaning the OS does not guarantee that a task will be fully completed within its allocated time (non-strict deadlines) [[2](https://www.intervalzero.com/understanding-hard-real-time-determinism)]. This is not an issue for everyday GPOS applications that do not require strict timing certainty.

##### RTOS
Unlike GPOS, an RTOS plays a critical role in certain real-world applications. Imagine you are designing a car's airbag system, where the system must process sensor data with extreme speed and accuracy during a collision, then immediately deploy the airbag within microseconds. Even a slight delay could be fatal, thus requiring an operating system capable of guaranteeing real-time responses and consistently meeting strict deadlines.

Generally, an RTOS is designed to run on a microcontroller that does not have a user interface (GUI / CLI). The main advantage of an RTOS is its deterministic scheduling method, meaning the start time of a task can be known before it begins [[1](https://www.digikey.com/en/maker/projects/what-is-a-realtime-operating-system-rtos/28d8087f53844decafa5000d89608016)]. This ensures the timeliness of task execution, allowing the system to respond to events consistently, which is often crucial in IoT or embedded systems applications.

# 1.3 Microcontroller Architecture

Besides the differences in the type of OS used, there are also differences in the microcontrollers used. In this IoT lab, the ESP-32 microcontroller is used, which differs from the Arduino Uno used in the Embedded Systems lab. Look at the table below for a comparison between the two microcontrollers. [[3](https://www.instructables.com/Introduction-to-ESP32/)]

![What-Are-the-Advantages-of-EPP32-Over-Arduino-UNO](https://hackmd.io/_uploads/BkDDbOtYel.jpg)

In addition to the increase in RAM and wireless communication modules, a key difference between the ESP32 and the Arduino UNO is the number of cores.

This means the ESP32 can achieve true parallelism in executing its tasks.

##### Why is this important?
First, let's look at the program structure used on the Arduino Uno, which lacks parallelism and runs on bare metal, often using a "Superloop" architecture. In this architecture, a single setup process is performed to initialize components before entering a loop that executes all tasks. During the loop, interrupts can be processed based on external events. For many use cases, this architectural structure is sufficient to complete the required tasks.

![a5ac711c-328b-48f6-9d8c-f207e3abb184](https://hackmd.io/_uploads/B1GjghKYge.jpg)

Tasks in this architecture are executed sequentially. Consequently, if there are many tasks to run, there is a possibility of missing deadlines.

For example, if you create a device to read sensor data (like temperature or heart rate) and simultaneously upload it in real-time to a server via a WiFi connection, the superloop architecture on an Arduino Uno would struggle. This is because the process of communicating with the server (e.g., an HTTP request) takes a relatively long time and will block the main loop. As a result, sensor readings could be delayed or even missed entirely. This means that if sensor data needs to be read with precision (e.g., every few microseconds or milliseconds), this architecture cannot guarantee that timing.

##### The Solution?
The ESP-32 can leverage parallelism to complete these tasks so they run concurrently.

GPOS systems are designed to run multiple processes at once, commonly supported by multi-tasking and multi-threading, so the user can execute several tasks simultaneously. In general, timing deadlines for tasks on a GPOS are not crucial, and delays can be tolerated as long as they are not visible to the user.

In this context, the RTOS acts as the operating system that manages resource allocation and scheduling for each task. [[1](https://www.digikey.com/en/maker/projects/what-is-a-realtime-operating-system-rtos/28d8087f53844decafa5000d89608016)].
![16adc64e-5857-4505-b27e-e66b375037a1](https://hackmd.io/_uploads/rJ8XE2ttgl.jpg)

Based on the image above, an RTOS can divide program execution into several tasks. For instance, Task 1 is responsible for reading data from a sensor, Task 2 is responsible for uploading data to a server, while Task 3 can be used for a periodic process like logging.

Each task has its own priority, and through the RTOS API, we can configure it to run on a specific thread or schedule it as needed. In this way, the RTOS ensures that each task can be executed concurrently and on schedule, without interfering with each other.

This does not mean the number of tasks in an RTOS is limited to the two cores of the ESP-32. Rather, the RTOS can manage priorities and perform event scheduling and time-slicing to ensure the timeliness and deadlines of each task are met according to its priority.

###### Takeaway

Although an RTOS offers many advantages, it doesn't mean the bare-metal (super loop) architecture is always unsuitable. On 8-bit microcontrollers like the Arduino UNO (ATmega328p), the bare-metal approach is actually more efficient because the overhead of an RTOS scheduler is too large for the available resources. With bare metal, simple applications like reading a sensor, turning on an LED, or serial communication can run more lightly without additional overhead. Consequently, microcontrollers that utilize an RTOS tend to have higher minimum specifications.

![cbf78b6c-3d84-4222-a636-2b619714ef66](https://hackmd.io/_uploads/HyqiFhFtge.jpg)

When moving to more powerful microcontrollers like the ESP32, the use of an RTOS becomes increasingly relevant. Besides having dual-core capabilities, the ESP32 is designed for IoT applications, thus requiring multitasking capabilities so that the WiFi and Bluetooth stacks can run concurrently with the user's application, while also ensuring the deadlines of its operations are met.

# 1.4 FreeRTOS

So what is FreeRTOS? [[4](https://www.freertos.org/Documentation/01-FreeRTOS-quick-start/01-Beginners-guide/01-RTOS-fundamentals?utm_source=chatgpt.com)]

FreeRTOS is one of the most widely used RTOS implementations in the world of embedded systems and IoT. As its name implies, FreeRTOS is open-source and free to use.

Based on the previous explanation of RTOS, FreeRTOS acts as a lightweight OS that runs on a microcontroller (like the ESP32) to perform task scheduling, memory management, and inter-task communication. FreeRTOS provides an API that allows developers to:
- Create and manage multiple tasks that can run concurrently.
- Set priority scheduling so that important tasks (e.g., critical sensor readings) are always processed faster than low-priority tasks (e.g., logging).
- Use time-slicing and event-driven scheduling so that tasks outnumbering the available cores can still run according to their schedule.

# 1.5 Additional References

- [What Is a Real-Time Operating System (RTOS)? – DigiKey Maker.io](https://www.digikey.com/en/maker/projects/what-is-a-realtime-operating-system-rtos/28d8087f53844decafa5000d89608016)  
- [Real-Time Operating System (RTOS): Components, Types, Examples – Guru99](https://www.guru99.com/real-time-operating-system.html)  
- [RTOS Fundamentals – FreeRTOS Official Documentation](https://www.freertos.org/Documentation/01-FreeRTOS-quick-start/01-Beginners-guide/01-RTOS-fundamentals)

# Module 2 - Task Management

# Learning Objectives

After completing this module, students are expected to be able to:

- Understand the basic concepts of task scheduling in RTOS.
- Understand the types of task scheduling algorithms.
- Understand and apply FreeRTOS APIs to create and manage tasks on the ESP-32.

# The Importance of Task Scheduling in IoT

One of the most important aspects in an RTOS is task scheduling since it defines the sequence in which many operations run on the CPU at the appropriate instant. Each process or task in the context of real-time applications has a particular function, such reading sensor data, handling images, or sending messages to other devices.   
   
Scheduling takes great significance in applications using the **Internet of Things** (IoT) as many devices connect with one other and engage their surroundings using sensors and actuators. Usually having strict timing demands, IoT applications need dependability and rather quick reactions. An IoT greenhouse system, for instance, has to continuously monitor temperature and moisture in real time while also controlling ventilation or irrigation on time to keep ideal conditions.   
   
Task scheduling in an RTOS is geared toward two primary objectives:   
 1. **Feasibility**: Every job needs to be finished by its deadline; nothing is left out.   
 2. **Optimality**: Optimize characteristics including CPU use, power consumption, and reaction time such that the system performs effectively.   
   
Attaining both objectives is difficult since there are usually several duties with varying priorities, execution periods, due dates, and dependencies. In addition, task scheduling helps considerably with:

- Making sure projects fulfill Quality of Service (QoS) specifications including low latency, great throughput, reliability, and energy efficiency.
- Improving the use of CPU, memory, bandwidth, and power among other resources.
- Managing the workload on multicore systems.
- Adjusting to shifting environmental circumstances including fluctuating workloads, network conditions, et cetera.

# Categories of Task Scheduling Algorithms

Several task-scheduling techniques available in a Real-Time Operating System (RTOS) have both benefits and drawbacks depending on the the needs of the system and the nature of the tasks under execution. Among the most often used algorithms are:

#### 1. Run for Completion (RTC)

The simplest approach is the Run to Completion algorithm. Every job runs till finished before moving onto the next one. Once all activities are finished, the sequence repeats itself from the start.

<table border="1" id="bkmrk-advantages-simple-an" style="border-collapse: collapse; width: 100%; height: 59.5938px;"><colgroup><col style="width: 50%;"></col><col style="width: 50%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Advantages</td><td style="height: 29.7969px;">Simple and quick to put into practice.

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Disadvantages</td><td style="height: 29.7969px;">Other tasks influence the completion time of a job, therefore reducing the determinacy of the system.

</td></tr></tbody></table>

#### 2. Round Robin (RR)

Round Robin is similar to RTC, but a task does not have to complete all its work before releasing the CPU. When it is scheduled again, the task resumes from where it left off.

<table border="1" id="bkmrk-advantages-provides-" style="border-collapse: collapse; width: 100%; height: 99.836px;"><colgroup><col style="width: 50%;"></col><col style="width: 50%;"></col></colgroup><tbody><tr style="height: 70.0391px;"><td style="height: 70.0391px;">Advantages</td><td style="height: 70.0391px;">- Provides a fairer time allocation to each task.
- More adaptable than RTC.

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Disadvantages</td><td style="height: 29.7969px;">Still relies on how each job behaves and is unable to stop one from taking over the processor for too long.

</td></tr></tbody></table>

#### 3. Time Slice (TS)

A pre-emptive scheduling algorithm in which execution time is broken into minute pieces known as time slices or ticks (e.g., 1 ms). The scheduler picks one task from the whole task list to run every time an interrupt happens.

<table border="1" id="bkmrk-advantages-stops-sta" style="border-collapse: collapse; width: 100%; height: 59.5938px;"><colgroup><col style="width: 50%;"></col><col style="width: 50%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Advantages</td><td style="height: 29.7969px;">Stops starvation (a job kept too long).

</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">Disadvantages</td><td style="height: 29.7969px;">Can lead to repeated context switching, hence raising system overhead.

</td></tr></tbody></table>

#### 4. Fixed Priority (FP)  


Fixed Priority assigns a static priority based on urgency to every job. The scheduler always chooses the task with the highest priority to run first.

If:

- Many activities share the same priority; they are done in round robin.
- While another task is running, a higher-priority task emerges and instantly interrupts the current operation.

<table border="1" id="bkmrk-advantages-uncomplic" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 50%;"></col><col style="width: 50%;"></col></colgroup><tbody><tr><td>Advantages</td><td>Uncomplicated and efficient; regularly used in real-time applications.

</td></tr><tr><td>Disadvantages</td><td>Less flexible in response to workload or shifting system conditions.

</td></tr></tbody></table>

#### 5. Earliest Deadline First (EDF)

  
Dynamic priority according to the deadline of each assignment is provided by the Earliest Deadline First method. The one with the shortest deadline is always done first.

- Two jobs with the same deadline are done in round robin.

Theoretically, EDF is ideal since it can plan any possible collection of jobs.

<table border="1" id="bkmrk-advantages-offers-be" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 50%;"></col><col style="width: 50%;"></col></colgroup><tbody><tr><td>Advantages</td><td>Offers best performance in deadline compliance.

</td></tr><tr><td>Disadvantages</td><td>Practically verifying execution can be somewhat challenging and difficult.

</td></tr></tbody></table>

# Introduction to FreeRTOS

Particularly for IoT uses, FreeRTOS is a well-known open-source RTOS kernel found in embedded systems.

FreeRTOS has three basic ideas guiding its design:

- **Simplicity**: Simple to grasp and put to use.
- **Portability**: It can operate on several different processor systems.
- **Flexibility**: Lets you change things depending on what you need.

Many architectures are supported by FreeRTOS, including ARM, AVR, PIC, MSP430, and ESP32. It also works with a variety of platforms, like Arduino, Raspberry Pi, and even AWS IoT.

Important aspects and services provided by FreeRTOS consist of:

#### 1. Task

FreeRTOS lets you build and manage several tasks that may run simultaneously across several processor cores or on a single core.   
 Every task includes:

- Priority will decide the sequence of execution.
- Manage memory usage by stack size.
- Optional name meant to help with task management and bug fixes.

API allow users to create, remove, suspend, resume, postpone, or synchronize tasks.

#### 2. Queue

For synchronization and inter-task communication, FreeRTOS offers queues.

- A data structure storing a set of objects is called a queue.
- Through queues, tasks can send and retrieve data.
- Queue can also be used to enforce access-control by means of semaphores and mutexes.

#### 3. Timer

FreeRTOS has software timers that allow:

- Execution of a callback function periodically or one-shot.
- Timer objects with properties such as period, expiry time, and optional name.
- Timers can be created, deleted, started, stopped, and reset using the available APIs.

#### 4. Event Groups

Event groups are used to signal between tasks or between tasks and interrupts. Characteristics include:

- Based on bit flags that can be set or cleared individually or together.
- Tasks can wait for one or more specific bits in an event group to be set before proceeding, using the provided APIs.

#### 5. Notification

FreeRTOS provides task notifications for lightweight and fast communication between tasks or between tasks and interrupts. Key points:

- Notifications are 32-bit values sent to a task using APIs.
- They might serve as basic data values, mutexes, event flags, or even a semaphore.

# Practical Sections

#### Setting Up FreeRTOS on ESP-32

Two cores in ESP-32 let this low-power microcontroller operate:

- <span style="background-color: rgb(236, 240, 241);">CPU0</span>: Handles BLE, Bluetooth, and Wi-Fi wireless protocols.
- <span style="background-color: rgb(236, 240, 241);">CPU1</span>: Executes code for user apps.

Installing and configuring the ESP-32 Arduino Core:   
1\. Obtain the most recent Arduino IDE and install it.   
2\. Launch Arduino IDE then go to File / Preferences. Enter in the field **Additional Boards Manager URLs**:

<span style="background-color: rgb(236, 240, 241);">[https://dl.espressif.com/dl/package\_esp32\_index.json](https://dl.espressif.com/dl/package_esp32_index.json)</span>

3\. Go to Tools / Board / Boards Manager, look for **esp32**, and install the most recent release from Espressif Systems.   
4\. Go to Tools / Board / ESP32 Arduino and pick the right board (like **ESP32 Dev Module** or **ESP32 Wrover Module**).

#### All about FreeRTOS APIs

##### 1. xTaskCreate()

- <span style="background-color: rgb(255, 255, 255);">Purpose: Creates a new task and dynamically allocates the required memory. Returns a handle to the created task, or NULL if creation fails. Syntax:</span>

```c
BaseType_t xTaskCreate(
    TaskFunction_t pvTaskCode,
    const char * const pcName,
    const uint32_t usStackDepth,
    void * const pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t * const pvCreatedTask
);
```

**Parameters**

1. **pvTaskCode**: Pointer to the function that implements the task. The function must have a prototype of void vTaskCode(void \* pvParameters).
2. **pcName**: Descriptive name of the task (helps with debugging).
3. **usStackDepth**: Stack size in words (not bytes) for the task.
4. **pvParameters**: Pointer to the arguments passed to the task function.
5. **uxPriority**: Priority of the task execution (higher number = higher priority).
6. **pvCreatedTask**: Pointer to the variable that will receive the created task handle.

**Return Value**

1. **pdPASS**: The task was successfully created and added to the ready list.
2. **errCOULD\_NOT\_ALLOCATE\_REQUIRED\_MEMORY**: The task could not be created because there is not enough heap memory available.

---

##### 2. xTaskCreatePinnedToCore()

- <span style="background-color: rgb(255, 255, 255);">Purpose: Similar to xTaskCreate(), except that you can specify the core number on which the task will run. This is useful for performance reasons. Here is the syntax of the function:</span>

```c
BaseType_t xTaskCreatePinnedToCore(
TaskFunction_t pvTaskCode,
const char * const pcName,
const uint32_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pvCreatedTask,
const BaseType_t xCoreID
);
```

**Parameters**

Same as xTaskCreate(), except for:

1. **xCoreID**: The core number on which the task should run. Can be 0 or 1 for a dual-core target ESP, or any other valid number of cores for a multi-core target ESP, i.e. 2 cores, 3 cores, etc.

**Return Value**

Same as xTaskCreate().

---

##### 3. vTaskDelete()

- <span style="background-color: rgb(255, 255, 255);">Purpose: Delete a task and free the memory allocated by it; delete other tasks. The syntax of this function is as follows:</span>

```c
void vTaskDelete(TaskHandle_t xTask);
```

**Parameters**

1. **xTask**: The handler of the task to be deleted. Passing NULL will delete the calling task.

**Return Value**

None.

---

##### 4. vTaskDelay()

- <span style="background-color: rgb(255, 255, 255);">Purpose: Causes the calling task to block for the specified number of ticks (ms). The syntax of this function is as follows:</span>

```c
void vTaskDelay(const TickType_t xTicksToDelay);
```

**Parameters**

1. **xTicksToDelay**: The number of ticks to delay. One tick = the unit of time specified by the configTICK\_RATE\_HZ configuration constant in FreeRTOSConfig.h.

**Return Value**

None.

---

##### 5. vTaskDelayUntil()

- <span style="background-color: rgb(255, 255, 255);">Purpose: This function blocks the calling task for a specified period of time, relative to the time the function was last called. In other words, it can be used when you want a task to run with a fixed frequency. The syntax of this function is as follows:</span>

```c
void vTaskDelayUntil(TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement);
```

**Parameters**

1. **pxPreviousWakeTime**: Pointer to a TickType\_t variable that stores the time when the task was last unblocked. This variable must be initialized with the current time before the first call to the vTaskDelayUntil() function. The function will update the variable with the current time after each call.
2. **xTimeIncrement**: The time period between executions (cycle time) in ticks. The task will be unblocked at times (pxPreviousWakeTime + xTimeIncrement), (pxPreviousWakeTime + xTimeIncrement2), and so on.

**Return Value**

None.

---

##### 6. vTaskSuspend()

- <span style="background-color: rgb(255, 255, 255);">Purpose: This function temporarily suspends a task, preventing it from being scheduled until it is reactivated by another task. The syntax of this function is as follows:</span>

```c
void vTaskSuspend(TaskHandle_t xTaskToSuspend);
```

**Parameters**

1. **xTaskToSuspend**: The handle of the task to be suspended. Passing a NULL value will suspend the calling task.

**Return Value**

None.

---

##### 7. vTaskResume()

- Purpose: This function reactivates a task that has been paused by vTaskSuspend(). The syntax of this function is as follows:

```c
void vTaskResume(TaskHandle_t xTaskToResume);
```

**Parameters**

1. **xTaskToResume**: The handle of the task to be reactivated.

**Return Value**

None.

---

##### 8. vTaskPrioritySet()

- Purpose: Change the priority of a task. The syntax of this function is as follows:

```c
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority);
```

**Parameters**

1. **xTask**: The handle of the task whose priority will be changed. Passing a NULL value will change the priority of the calling task.
2. **uxNewPriority**: The new priority for the task.

**Return Value**

None.

---

##### 9. uxTaskPriorityGet()

- Purpose: Returns the priority of a task. The syntax of this function is as follows:

```c
UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask);
```

**Parameters**

1. **xTask**: The handle of the task whose priority is to be obtained. Passing a NULL value will return the priority of the calling task.

**Return Value**

- The priority of the task.

---

##### 10. eTaskGetState()

- Purpose: Returns the status of a task. The syntax of this function is as follows:

```c
eTaskState eTaskGetState(TaskHandle_t xTask);
```

**Parameters**

1. **xTask**: The handle of the task whose status is to be obtained.

**Return Value**

1. **xTask**: The handle of the task whose status is to be returned.

---

The following are the possible task states:

<table border="1" id="bkmrk-status-description-e" style="border-collapse: collapse; width: 100%; height: 208.578px;"><colgroup><col style="width: 50%;"></col><col style="width: 50%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="align-center" style="height: 29.7969px;">Status</td><td class="align-center" style="height: 29.7969px;">Description</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">**eRunning**</td><td style="height: 29.7969px;">The task is running.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">**eReady**</td><td style="height: 29.7969px;">The task is ready to run.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">**eBlocked**</td><td style="height: 29.7969px;">The task is blocked, waiting for an event.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">**eSuspended**</td><td style="height: 29.7969px;">The task is temporarily suspended.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">**eDeleted**</td><td style="height: 29.7969px;">The task has been deleted.</td></tr><tr style="height: 29.7969px;"><td style="height: 29.7969px;">**eInvalid**</td><td style="height: 29.7969px;">The task marker is invalid.</td></tr></tbody></table>

# Additional References

- \[1\]“Introduction to RTOS Part 3 - Task Scheduling | Digi-Key Electronics,” www.youtube.com. [https://www.youtube.com/watch?v=95yUbClyf3E](https://www.youtube.com/watch?v=95yUbClyf3E).
- \[2\]“Introduction to RTOS - Solution to Part 3 (Task Scheduling),” DigiKey, 2021. [https://www.digikey.com/en/maker/projects/introduction-to-rtos-solution-to-part-3-task-scheduling/8fbb9e0b0eed4279a2dd698f02ce125f.](https://www.digikey.com/en/maker/projects/introduction-to-rtos-solution-to-part-3-task-scheduling/8fbb9e0b0eed4279a2dd698f02ce125f)

# Module 3 - Memory Management & Queue

By WN

# Tujuan Pembelajaran

Setelah menyelesaikan modul ini, praktikan diharapkan mampu:
- Memahami dan dapat mendemonstrasikan jenis-jenis alokasi memory yang terjadi pada suatu sistem.
- Memahami kasus dan cara manajemen heap pada RTOS.
- Memahami definisi data structure Queue dan kepentingannya dalam aplikasi IOT dan Sistem Embedded Multithreaded.

# Why Memory Management?

Manajemen memori merupakan hal yang sangat penting dalam aplikasi IoT dan Sistem Embedded. Bayangkan bila sistem menggunakan tipe data yang memerlukan ukuran data yang lebih dari yang dialokasikan, fungsi yang dipanggil pada task tidak diterminasi dengan baik sehingga mengakibatkan stack overflow, atau memory yang dialokasikan pada heap tidak di free sehingga terjadi memory leak. 

Hal-hal tersebut Masalah-masalah tersebut dapat berujung pada kegagalan sistem, crash, atau error yang memengaruhi keseluruhan performa perangkat. Oleh karena itu, pemahaman dan penerapan manajemen memori yang baik sangat penting untuk menjaga sistem tetap stabil, efisien, dan andal.

Beberapa masalah seperti memory leak pada dasarnya dapat diatasi dengan teknologi garbage collector seperti pada bahasa pemrograman modern. Namun, ini jarang digunakan pada sistem real-time karena dua alasan utama: keterbatasan resource yaitu RAM dan Processor yang terbatas, serta strict deadline yang harus dipenuhi oleh task.

# Tipe Memory Allocation

Dalam sebuah program, baik secara umum maupun pada Sistem Embedded, terdapat beberapa jenis alokasi memory yang dapat dilakukan, diantaranya sebagai berikut: [[1](https://www.digikey.com/en/maker/projects/introduction-to-rtos-solution-to-part-4-memory-management/6d4dfcaa1ff84f57a2098da8e6401d9c)]

![60c3b8b7-f4af-4a07-b139-0acae5a846fb](https://hackmd.io/_uploads/HyngRlziel.jpg)

### Static Variable
Static memory digunakan untuk menyimpan variabel global maupun variabel yang dideklarasikan sebagai static di dalam kode. Berbeda dengan variabel lokal biasa, variabel static tidak hilang setelah fungsi selesai dijalankan, melainkan tetap ada (persist) sepanjang program berjalan. Nilainya akan diingat pada pemanggilan fungsi berikutnya.

Variable-Variable seperti global counter, variable pin / port, merupakan contoh-contoh static variable pada sistem Embedded.

Dalam praktikum ini, karena pemrograman menggunakan bahasa C++, setiap variabel bersifat type-based, artinya ukuran memori sudah ditentukan berdasarkan tipe data yang digunakan. Oleh karena itu, pada static variable, pemilihan tipe dan ukuran variabel harus tepat agar penggunaan memori lebih efisien dan sesuai kebutuhan. Oleh sebab itu, tidak jarang ditemukan fixed-width integer seperti int8_t, int16_t, uint16_t, uint32_t dan sebagainya.

Informasi mengenai fixed width integer dapat dipelajari secara lebih pada [link berikut](https://en.cppreference.com/w/cpp/header/cstdint.html).

### Stack

Stack digunakan ketika terjadi alokasi otomatis oleh variabel lokal. Memori stack diatur dengan prinsip Last-In-First-Out (LIFO), di mana variabel dari suatu fungsi akan didorong (push) ke stack saat fungsi dipanggil. Setelah fungsi selesai dan kembali, variabel-variabel tersebut akan dikeluarkan (pop) dari stack, sehingga fungsi dapat melanjutkan eksekusi seperti sebelumnya.

Pada FreeRTOS, ketika sebuah task dibuat menggunakan xTaskCreate(), sistem operasi akan mengalokasikan sebagian memori heap untuk task tersebut. Alokasi ini terdiri dari dua bagian:

* Task Control Block (TCB)
Berisi informasi penting tentang task, seperti prioritas, state, dan pointer ke local stack.
* Local Stack Task
Digunakan khusus untuk menyimpan variabel lokal dan data saat fungsi dalam task tersebut dipanggil, mirip seperti stack global pada program utama tetapi terbatas hanya untuk task itu.

Setiap variabel lokal yang dibuat dalam sebuah fungsi task akan ditempatkan di local stack milik task tersebut. Oleh karena itu, sangat penting untuk memperkirakan kebutuhan stack sebelum membuat task, lalu menentukan ukurannya pada parameter stack size di xTaskCreate(). Jika ukuran stack terlalu kecil, task bisa mengalami stack overflow yang menyebabkan error atau crash pada sistem.

### Heap

Heap adalah area memori yang digunakan untuk alokasi dinamis. Tidak seperti alokasi stack yang dilakukan secara otomatis, heap harus dialokasikan secara eksplisit oleh programmer. Pada bahasa pemrograman C dan C++, proses alokasi memory pada heap dilakukan melalui fungsi berikut.

* malloc() → untuk mengalokasikan memori
* free() → untuk melepaskan kembali memori yang sudah tidak dipakai

Proses ini disebut dynamic allocation. Jika memori heap tidak dibebaskan setelah selesai digunakan, maka akan terjadi memory leak, yang bisa menyebabkan sistem kehabisan memori, crash, atau bahkan korupsi data pada area memori lain.


Dalam FreeRTOS, terdapat potensi dimana dua buah task berbeda mencoba mengakses lokasi memory yang sama, sehingga fungsi malloc() dan free() tidak thread-safe dan tidak aman digunakan antar task. Karena itu, FreeRTOS menyediakan fungsi khusus:

* pvPortMalloc() → untuk mengalokasikan memori dari heap global FreeRTOS
* vPortFree() → untuk mengembalikan memori yang sudah tidak digunakan

Dengan cara ini, alokasi heap lebih aman di lingkungan multitasking, karena RTOS mengatur sinkronisasi dan pengelolaan memori agar tidak saling bertabrakan antar task.

### Hubungan Stack dan Heap

Pada kebanyakan sistem, stack dan heap tumbuh saling mendekati dalam ruang memori yang sama. Jika penggunaan keduanya tidak dikontrol, keduanya bisa bertabrakan (stack-heap collision), yang menyebabkan data saling menimpa dan mengakibatkan error serius.

![b9d52446-ebca-4ae5-9e8d-9a4a035d1e4d](https://hackmd.io/_uploads/BkuL--zoel.jpg)

## Demonstrasi Memory Allocation
Berikut merupakan contoh task yang dapat menyebabkan memory leak akibat kurangnya memory deallocation menggunakan vPortFree. Jika dijalankan, sewaktu-waktu jumlah memory yang ada akan habis sehingga sistem akan berhenti membaca sensor.

```cpp
#include <Arduino.h>
#include <FreeRTOS.h>

void SensorTask(void *pvParameters) {
    while (1) {
        // Alokasikan buffer untuk data sensor
        int *sensorData = (int *) pvPortMalloc(50 * sizeof(int)); // buffer 50 data
        if (sensorData == NULL) {
            Serial.println("Error - Heap exhausted.");
        }

        // Pengambilan data sensor
        for (int i = 0; i < 50; i++) {
            sensorData[i] = analogRead(A0);
        }

        // Cetak sebagian hasil
        Serial.print("Sensor[0] = ");
        Serial.print(sensorData[0]);
        Serial.print(", Sensor[49] = ");
        Serial.println(sensorData[49]);

        // Tidak ada vPortFree(sensorData); 


        vTaskDelay(1000 / portTICK_PERIOD_MS); 
    }
}

void setup() {
    Serial.begin(115200);
    delay(1000);

    xTaskCreate(
        SensorTask,      
        "SensorTask",    
        2048,           
        NULL,            
        1,               
        NULL             
    );
}

void loop() {
    
}

```

# Heap Configuration FreeRTOS

FreeRTOS menyediakan beberapa skema pengelolaan heap (memori dinamis), yang berbeda dari segi kompleksitas, fitur, dan trade-off-nya [[2]](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/09-Memory-management/01-Memory-management). 

 Saat FreeRTOS membutuhkan memori dinamis (misalnya saat membuat task, queue, atau objek kernel lainnya), ia menggunakan fungsi pvPortMalloc() dan vPortFree() bukan malloc()/free() standar. 

Ada lima implementasi contoh heap yang dibawa oleh FreeRTOS yang memiliki karakteristiknya masing-masing:

1. heap_1: sederhana, memori hanya dialokasikan tapi tidak bisa dibebaskan (tidak ada vPortFree()). Cocok jika objek hanya dibuat sekali dan tidak dihapus. 
2. heap_2: memungkinkan free, tetapi tidak menggabungkan blok-blok memori bebas yang berdekatan (tidak ada coalescence). 
3. heap_3: membungkus (wraps) malloc()/free() standar sehingga bisa memakai alokasi memori dari C library, dengan tambahan pengendalian (misalnya keamanan antar thread) oleh FreeRTOS. 
4. heap_4: menggunakan algoritma first-fit, serta mendukung coalescing (menggabungkan blok bebas yang bersebelahan), yang mengurangi fragmentasi memori. Cocok jika sistem sering melakukan alokasi dan pembebasan memori dengan ukuran yang berbeda-beda. 
5. heap_5: mirip dengan heap_4, tetapi memiliki kemampuan untuk menggunakan beberapa wilayah memori yang tidak harus kontinu (tidak berurutan) sebagai satu heap kumulatif. 

Proses perubahan heap management biasanya dilakukan jika melakukan build FreeRTOS secara mandiri atau pada ESP-IDF dan diluar cakupan modul ini.

# Queue

Data structure queue mungkin sudah familiar setelah digunakan pada praktikum pemrograman sebelum sebelumnya. Data structure ini bersifat FIFO dimana data yang masuk pertama kedalam queue akan menjadi data yang pertama keluar dari queue. [[3]](https://www.digikey.com/en/maker/projects/introduction-to-rtos-solution-to-part-5-freertos-queue-example/72d2b361f7b94e0691d947c7c29a03c9)

Dalam konteks IoT dan FreeRTOS, queue digunakan sebagai medium untuk mengirimkan data antar task atau antar perangkat dan server.

### Mengapa queue diperlukan?
Queue diperlukan karena pada sistem multitasking, beberapa task bisa saja mencoba mengakses atau menulis ke memori yang sama secara bersamaan. 

Hal ini dapat kita contohkan pada skenario tersebut. Pada skenario ini kita memiliki satu buah global variable (seperti counter) yang akan diakses ataupun diubah oleh task. Task A akan mengubah nilai global variable ini (seperti mengincrement global counter) setelah menjalankan task yang dimiliki. Task C juga akan melakukan hal yang sama setelah menyelesaikan tasknya. Task B akan membaca nilai dari global variable ini pada proses tasknya dan melakukan printing pada serial monitor. Dikarenakan seluruh task berjalan secara bersamaan / paralel, akan terdapat kemungkinan dimana hasil write pada global variable oleh task A akan di overwrite oleh task B.
![image](https://hackmd.io/_uploads/S1raOWGilg.png)

Dengan kata lain, jika pengaksesan dan transfer data pada sistem multithreaded / parallel ini dibiarkan tanpa pengaturan, maka akan terjadi memory overwriting atau race condition, di mana data yang sudah ditulis oleh satu task bisa tertimpa oleh task lain sebelum sempat diproses. 

Dengan adanya queue, setiap data yang dikirim akan disimpan secara terpisah dalam antrian, sehingga proses tulis dan baca berlangsung secara atomic (tidak bisa diinterupsi oleh task lain di tengah jalan). Selain itu, sifat FIFO dari queue menjamin bahwa urutan data tetap terjaga, sehingga task penerima dapat memproses data sesuai dengan urutan kedatangannya. 

![88d32074-22e7-4fc3-943f-9e59b06dede9](https://hackmd.io/_uploads/SynyqWzsll.jpg)

Dalam konteks IoT, queue juga berperan sebagai buffer, misalnya ketika perangkat ingin mengirimkan data ke server tetapi koneksi sedang terputus. Data dapat terlebih dahulu disimpan di dalam queue untuk kemudian dikirimkan kembali saat koneksi sudah tersedia, sehingga tidak ada data yang hilang.

## Menggunakan Queue pada FreeRTOS

Pada FreeRTOS, disediakan API khusus untuk membuat, menulis, dan membaca queue.
#### Membuat Queue
Queue dibuat dengan fungsi xQueueCreate(), yang membutuhkan dua parameter utama:
* uxQueueLength → jumlah maksimum item yang dapat disimpan dalam queue.
* uxItemSize → ukuran (dalam byte) tiap item yang akan disimpan.

Contoh: membuat queue yang dapat menyimpan 10 integer (int):

```cpp
QueueHandle_t xQueue;
xQueue = xQueueCreate(10, sizeof(int));
if (xQueue == NULL) {
    Serial.println("Error: Queue creation failed.");
}
```
Dapat dilihat bahwa queue memiliki ukuran tetap, dalam hal ini adalah 10 x ukuran byte dari integer.

**if (xQueue == NULL)** dapat ditambahkan untuk memastikan queue berhasil dibuat, dan mencegah kegagalan pembuatan queue karena keterbatasan memori.

#### Mengirim Data ke Queue

Task atau ISR (Interrupt Service Routine) dapat mengirim data ke queue menggunakan:
xQueueSend() → untuk mengirim dari task biasa.
xQueueSendFromISR() → untuk mengirim dari ISR.
Contoh: mengirim data sensor ke queue dari sebuah task:

```cpp
int sensorValue = analogRead(A0);
xQueueSend(xQueue, &sensorValue, portMAX_DELAY);

```
### Membaca Data dari Queue

Task penerima membaca data dari queue menggunakan xQueueReceive(). Data akan dihapus dari queue setelah berhasil dibaca.

```cpp
int receivedValue;
if (xQueueReceive(xQueue, &receivedValue, portMAX_DELAY) == pdPASS) {
    Serial.print("Received: ");
    Serial.println(receivedValue);
}

```
### Parameter-Parameter pada Queue

- Parameter portMAX_DELAY memiliki fungsi berikut:
    * Pada xQueueSend(): task akan menunggu jika queue sedang penuh, sampai ada space kosong untuk memasukkan data.
    * Pada xQueueReceive(): task akan menunggu jika queue kosong, sampai ada data baru masuk.\
- Blocking vs Non-Blocking pada Queue
    - Blocking :Task akan masuk mode blocking ketika kita memberikan timeout > 0 atau portMAX_DELAY pada fungsi queue (xQueueSend() atau xQueueReceive()). Pada saat ini, task akan menunggu hingga block berakhir (dalam hal ini, space tersedia pada queue)
    - Non-Blocking: Task akan masuk mode non-blocking ketika kita memberikan timeout = 0, dimana fungsi queue akan langsung kembali meskipun queue penuh atau kosong. Dengan kata lain, task harus tetap berjalan terus-menerus tanpa tertahan oleh queue.
- Return Value
    - pdTRUE / pdFALSE → digunakan pada fungsi seperti xQueueSend() dan xQueueReceive(), menunjukkan apakah operasi berhasil (pdTRUE) atau gagal (pdFALSE).
    - pdPASS / pdFAIL → digunakan pada operasi yang lebih kompleks atau alokasi memory, menunjukkan keberhasilan (pdPASS) atau kegagalan (pdFAIL).

    - pdTRUE/pdFALSE biasanya dipakai pada fungsi queue (xQueueSend(), xQueueReceive()) untuk menunjukkan status keberhasilan operasi. Sedangkan pdPASS/pdFAIL dipakai pada operasi FreeRTOS yang lebih umum (misal pembuatan queue, semaphore, atau alokasi heap)

**Informasi lebih lanjut mengenai parameter-parameter pada API queue dapat dibaca pada [dokumentasi FreeRTOS](https://www.freertos.org/media/2018/FreeRTOS_Reference_Manual_V10.0.0.pdf)** 
    
### Contoh Mengirimkan dan Menerima Data Serial Via Queue
```cpp
#include <Arduino.h>
#include <FreeRTOS.h>

// Handle untuk Queue
QueueHandle_t xQueue;

// Ukuran buffer pesan
#define MSG_MAX_LEN 50

// Task untuk membaca input serial sampai newline
void SerialReadTask(void *pvParameters) {
  while (1) {
    if (Serial.available() > 0) {
      // Baca string sampai newline
      String input = Serial.readStringUntil('\n');

      // Pastikan tidak melebihi buffer
      if (input.length() > 0 && input.length() < MSG_MAX_LEN) {
        char buffer[MSG_MAX_LEN];
        input.toCharArray(buffer, MSG_MAX_LEN);

        // Kirim ke queue
        if (xQueueSend(xQueue, buffer, portMAX_DELAY) == pdPASS) {
          Serial.println("[Sent to Queue]");
        }
      }
    }
    vTaskDelay(10 / portTICK_PERIOD_MS);
  }
}

// Task untuk membaca dari queue dan menampilkan pesan
void SerialPrintTask(void *pvParameters) {
  char received[MSG_MAX_LEN];

  while (1) {
    if (xQueueReceive(xQueue, &received, portMAX_DELAY) == pdPASS) {
      Serial.print("Message received: ");
      Serial.println(received);
    }
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  // Buat queue dengan kapasitas 5 pesan
  xQueue = xQueueCreate(5, MSG_MAX_LEN);

  if (xQueue != NULL) {
    xTaskCreate(SerialReadTask, "SerialRead", 4096, NULL, 1, NULL);
    xTaskCreate(SerialPrintTask, "SerialPrint", 4096, NULL, 1, NULL);
  } else {
    Serial.println("Error: Queue creation failed!");
  }
}

void loop() {
}

```

# Mengirimkan Struct dengan Queue

Umumnya data tidak dikirimkan secara langsung pada queue, namun terlebih dahulu di masukkan kedalam sebuah struct. Ini berguna ketika data yang dikirimkan berbentuk objek yang memiliki beberapa atribut, contohnya ketika mengirimkan data suhu, kelembapan, beserta waktu saat ini dalam satu buah struct. 

### Pass Struct by Value
Jika struct dikirimkan by value ke dalam queue, maka FreeRTOS akan menyalin seluruh isi struct ke dalam buffer queue. Cara ini sederhana dan aman, tetapi untuk struct berukuran besar, proses copy bisa memperlambat sistem dan memperboros RAM untuk melakukan proses duplikasi. 

```cpp
//Send
Data_t data;
data.tick = xTaskGetTickCount();
snprintf(data.msg, sizeof(data.msg), "Hello Value");
xQueueSend(queue, &data, portMAX_DELAY);

// Receive
Data_t received;
xQueueReceive(queue, &received, portMAX_DELAY);
Serial.println(received.msg);

```

### Pass Struct By Reference
Dalam hal ini, pointer memiliki peran penting, dimana dengan pass by reference, kita hanya mengirimkan alamat memori dari struct tersebut ke dalam queue, bukan seluruh isi datanya sehingga data tidak perlu disalin ulang ke dalam buffer queue, sehingga lebih efisien dalam penggunaan memori.

```cpp
// Send
Data_t *data = (Data_t *) pvPortMalloc(sizeof(Data_t));
data->tick = xTaskGetTickCount();
snprintf(data->msg, sizeof(data->msg), "Hello Pointer");
xQueueSend(queue, &data, portMAX_DELAY);

// Receive
Data_t *received;
xQueueReceive(queue, &received, portMAX_DELAY);
Serial.println(received->msg);
vPortFree(received); // Don't Forget
```
Pass by reference lebih direkomendasikan karena efisiensi dan performanya, namun dalam proses melakukan pass by reference penggunaan pointer perlu diteliti sehingga pengiriman value / memory tidak salah dan memory yang telah dialokasi di free.
### Contoh Penggunaan
Berikut merupakan contoh aplikasi Struct dan Queue untuk mengirimkan tick dan pesan dari serial monitor dari suatu task, untuk ditampilkan pada task lainnya dengan format tertentu.
```cpp
/* In this FreeRTOS example, we use xTaskCreatePinnedToCore() 
   to show queue communication between tasks. 
   We'll send both a serial message and a tick count via a struct pointer. */

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "Arduino.h"

// Handle untuk Queue
QueueHandle_t xQueue;

// Ukuran buffer pesan
#define MSG_MAX_LEN 50

// Struct untuk data pesan
struct Message {
  TickType_t ticks;
  char text[MSG_MAX_LEN];
};

// Task untuk membaca input serial sampai newline
void SerialReadTask(void *pvParameters) {
  while (1) {
    if (Serial.available() > 0) {
      // Baca string sampai newline
      String input = Serial.readStringUntil('\n');

      if (input.length() > 0 && input.length() < MSG_MAX_LEN) {
        // Alokasikan memori untuk struct
        Message *msg = (Message *)pvPortMalloc(sizeof(Message));
        if (msg != NULL) {
          msg->ticks = xTaskGetTickCount();
          input.toCharArray(msg->text, MSG_MAX_LEN);

          // Kirim pointer ke queue
          if (xQueueSend(xQueue, &msg, portMAX_DELAY) == pdPASS) {
            Serial.println("[Sent to Queue]");
          } else {
            // Jika gagal, bebaskan memori
            vPortFree(msg);
          }
        }
      }
    }
    vTaskDelay(10 / portTICK_PERIOD_MS);
  }
}

// Task untuk membaca dari queue dan menampilkan pesan
void SerialPrintTask(void *pvParameters) {
  Message *received;

  while (1) {
    if (xQueueReceive(xQueue, &received, portMAX_DELAY) == pdPASS) {
      Serial.print("Message received after ");
      Serial.print((unsigned long)received->ticks);
      Serial.print(" ticks: \"");
      Serial.print(received->text);
      Serial.println("\"");

      // Bebaskan memori setelah dipakai
      vPortFree(received);
    }
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  // Buat queue untuk menampung 5 pointer ke Message
  xQueue = xQueueCreate(5, sizeof(Message *));
  if (xQueue != NULL) {
    xTaskCreatePinnedToCore(SerialReadTask, "SerialRead", 4096, NULL, 1, NULL, 0);
    xTaskCreatePinnedToCore(SerialPrintTask, "SerialPrint", 4096, NULL, 1, NULL, 1);
  } else {
    Serial.println("Error: Queue creation failed!");
  }
}

void loop() {
}

```

# Referensi Lebih Lanjut

* “The FreeRTOSTM Reference Manual.” Available: https://www.freertos.org/media/2018/FreeRTOS_Reference_Manual_V10.0.0.pdf
* “FreeRTOS Memory Management,” Digikey.com, 2021. https://www.digikey.com/en/maker/projects/introduction-to-rtos-solution-to-part-4-memory-management/6d4dfcaa1ff84f57a2098da8e6401d9c
* “FreeRTOS heap memory management - FreeRTOSTM,” Freertos.org, 2024. https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/09-Memory-management/01-Memory-management
* ShawnHymel, “Introduction to RTOS - Solution to Part 5 (FreeRTOS Queue Example),” DigiKey, Feb. 08, 2021. https://www.digikey.com/en/maker/projects/introduction-to-rtos-solution-to-part-5-freertos-queue-example/72d2b361f7b94e0691d947c7c29a03c9
* “C++ Pointers to Structure (With Examples),” Programiz.com, 2025. https://www.programiz.com/cpp-programming/structure-pointer

# Module 4 - Deadlock & Synchronization

# 4.1 Learning Objectives

After completing this module, students are expected to be able to:
- Understand the importance of synchronization in multi-tasking systems and the risks of race conditions.
- Recognize and implement basic synchronization mechanisms: Mutexes and Semaphores in FreeRTOS.
- Differentiate the appropriate use cases for Mutexes versus Semaphores.
- Identify and understand common problems in concurrent systems: Deadlock, Starvation, and Priority Inversion.

# 4.2 Introduction: The Problem of Shared Resource Access

In multi-tasking systems, multiple tasks often need to access the same resource simultaneously, such as a global variable, a sensor interface, or a data structure. If this access is not managed properly, it can lead to data corruption, inconsistencies, or unexpected system behavior.

- **Race Condition**
  
   A race condition is a situation where the outcome of a process depends on the unpredictable sequence of execution of concurrent tasks. Imagine two tasks: one is responsible for incrementing a counter variable, and the other for decrementing it. If both tasks read the counter's value, modify it, and write it back at nearly the same time, one of the updates could be lost, resulting in an incorrect final value.
  <br><br>
    To prevent race conditions, we need a mechanism that ensures only one task can access the shared resource at a time. This mechanism is known as Mutual Exclusion, or Mutex for short.

# 4.3 Synchronization Mechanisms in FreeRTOS

FreeRTOS provides several synchronization primitives, the most common of which are Mutexes and Semaphores. Both are built upon a basic data structure called a queue.

1. **Mutex (Mutual Exclusion)**

    A mutex can be thought of as a "key" to a resource. A task that wants to access the resource must first "take" the key. As long as that task holds the key, any other task that also needs the resource must wait. Once finished, the first task must "release" the key so another task can use it.
<br><br>
   *Note: In FreeRTOS, the task that takes a mutex must be the same task that releases it.*
<br><br>
2. **Semaphore**

   A semaphore is a more general synchronization mechanism than a mutex. It acts like a counter that controls access to a number of resources.
  <br><br>
   Types of Semaphores:
    - Binary Semaphore : Has a maximum count of 1 (it can be either 0 or 1). It is often used for signaling between tasks (event synchronization) rather than for pure mutual exclusion, as it lacks a priority inheritance mechanism.
    - Counting Semaphore : Has a count value greater than 1. It is very useful for managing access to a pool of identical resources, such as connections to a server, memory buffers, or slots in a pool.

# 4.4 Common Problems in Synchronization

1. **Deadlock**

   A deadlock is a situation where two or more tasks are blocked forever, each waiting for a resource that is held by another task in the cycle.
<br><br>
   Example Deadlock Scenario:

    Task A successfully locks Mutex 1. Task B successfully locks Mutex 2. Task A now tries to lock Mutex 2, but it must wait because Mutex 2 is held by Task B. Task B now tries to lock Mutex 1, but it must wait because Mutex 1 is held by Task A. Both tasks are now waiting for each other indefinitely, and neither can proceed.
<br><br>

    The Four Conditions for Deadlock **(Coffman's Conditions)**:

    1. **Mutual Exclusion**: At least one resource must be non-sharable (can only be used by one task at a time).
    2.  **Hold and Wait**: A task holds at least one resource while waiting for another resource held by a different task.
    3.  **No Preemption**: A resource cannot be forcibly taken from the task holding it; it can only be released voluntarily.
    4.  **Circular Wait**: A circular chain of two or more tasks exists, where each task is waiting for a resource held by the next task in the chain.
<br><br>

3. **Priority Inversion**

   Priority inversion is a scenario where a high-priority task is forced to wait for a much lower-priority task. This happens when the low-priority task is holding a lock (e.g., a mutex) that the high-priority task needs. The problem worsens if a medium-priority task starts running, as it will preempt the low-priority task, preventing it from releasing the lock. As a result, the high-priority task never gets a chance to run.
<br><br>

4. **Starvation**

   Starvation is a condition where a task is perpetually denied access to the resources it needs to complete its execution. This often happens to low-priority tasks that are constantly being preempted by higher-priority tasks, preventing them from ever getting their slice of CPU time.

# 4.5 Prevention and Handling Strategies

1. **Overcoming Deadlock**

   Since detecting and recovering from a deadlock in an embedded system is very difficult, the best approach is prevention. This is done by breaking one of the four Coffman conditions.

   - **Break Circular Wait**: Enforce a strict lock ordering for all resources. If all tasks are required to lock Mutex 1 before Mutex 2, the deadlock scenario described above could never happen. This is the most common and effective deadlock prevention strategy
   - **Break Hold and Wait**: Request all required resources at once.
   - **Detection and Recovery**: Mechanisms like a wait-for graph can be used to detect cycles. If a deadlock is detected, the system can recover by aborting one of the tasks (termination) or forcibly taking a resource (preemption). However, this is rarely implemented on microcontrollers.
<br><br>

2. **Overcoming Priority Inversion and Starvation**

   - **Priority Inheritance**: This is the solution to priority inversion. If a high-priority task is blocked waiting for a mutex held by a low-priority task, the low-priority task will temporarily "inherit" the priority of the high-priority task. This allows the low-priority task to run quickly, finish its work, and release the mutex as soon as possible.

     *Note: Mutexes in FreeRTOS already implement this mechanism automatically.*
   - **Priority Ceiling**: Each resource is assigned a priority ceiling, which is the highest priority level of any task that can access it. When a task locks the resource, its priority is immediately raised to that ceiling level until it releases it.
   - **Aging**: To prevent starvation, the priority of a task that has been waiting for a long time can be gradually increased. This way, a low-priority task will eventually have a high enough priority to be executed.
   - **Fair Scheduling**: Using a scheduling algorithm (like round-robin for tasks of the same priority) to ensure all tasks get a fair share of CPU time.

# Module 5 - Software Timer

# 5.1 Introduction to Real-Time Multitasking

### <span class="ng-star-inserted">What is an RTOS? Tasks and Scheduling</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">Real-Time Operating System (RTOS)</span>**<span class="ng-star-inserted"> is a specialized operating system designed for embedded systems that must process data and events within a strict, predictable timeframe. Unlike a desktop OS (like Windows or macOS) which prioritizes throughput and fairness, an RTOS prioritizes </span>**<span class="ng-star-inserted">determinism, </span>**<span class="ng-star-inserted">the ability to guarantee that a task will be completed within a specific deadline.</span>

<span class="ng-star-inserted">The fundamental unit of execution in an RTOS is a </span>**<span class="ng-star-inserted">Task</span>**<span class="ng-star-inserted">. You can think of a task as an independent function that runs in its own context, with its own stack and priority. For example, in a smart device, you might have one task for managing the Wi-Fi connection, another for reading sensor data, and a third for updating a display.</span>

<span class="ng-star-inserted">The core component of the RTOS is the </span>**<span class="ng-star-inserted">Scheduler</span>**<span class="ng-star-inserted">. Its job is to manage which task gets to use the CPU at any given moment. By rapidly switching between tasks (a process called a "context switch"), the scheduler creates the illusion that all tasks are running simultaneously, a concept known as </span>**<span class="ng-star-inserted">multitasking</span>**<span class="ng-star-inserted">. In a priority-based scheduler like the one in FreeRTOS, the scheduler will always ensure that if multiple tasks are ready to run, the one with the highest priority is the one that executes.</span>

### <span class="ng-star-inserted">The Problem with </span>`<span class="inline-code ng-star-inserted">delay()</span>`<span class="ng-star-inserted">: Blocking vs. Non-Blocking Operations</span>

<span class="ng-star-inserted">In simple microcontroller programming (like a basic Arduino sketch), it is common to use a </span>`<span class="inline-code ng-star-inserted">delay()</span>`<span class="ng-star-inserted"> function to control timing. For example, to blink an LED every second, you might write:</span>

```c++
void loop() {
  digitalWrite(LED_PIN, HIGH);
  delay(1000); // Wait for 1 second
  digitalWrite(LED_PIN, LOW);
  delay(1000); // Wait for 1 second
}
```

<span class="ng-star-inserted">This works, but it is extremely inefficient. During the </span>`<span class="inline-code ng-star-inserted">delay(1000)</span>`<span class="ng-star-inserted"> call, the CPU is completely occupied doing nothing, it is stuck in a busy-wait loop, unable to perform any other work. This is known as a </span>**<span class="ng-star-inserted">blocking</span>**<span class="ng-star-inserted"> operation. A blocking function halts the execution of its thread or task until a specific event occurs (in this case, the passage of time).</span>

<span class="ng-star-inserted">In a multitasking RTOS environment, blocking is the enemy of responsiveness. If one task calls </span>`<span class="inline-code ng-star-inserted">delay()</span>`<span class="ng-star-inserted">, it effectively puts the entire system on hold (unless a higher-priority task preempts it), preventing other, potentially critical, tasks from running. The goal in an RTOS is to use </span>**<span class="ng-star-inserted">non-blocking</span>**<span class="ng-star-inserted"> operations. A task should perform its work and, if it needs to wait, it should inform the scheduler so that a lower-priority task can use the CPU in the meantime.</span>

### <span class="ng-star-inserted">The Need for Asynchronous Events</span>

<span class="ng-star-inserted">The solution to the blocking problem is to design systems around </span>**<span class="ng-star-inserted">asynchronous events</span>**<span class="ng-star-inserted">. An asynchronous event is one that occurs independently of the main program flow, allowing the system to react to it without having to constantly wait and check for it.</span>

<span class="ng-star-inserted">An RTOS provides two primary mechanisms for handling asynchronous events:</span>

1. **<span class="ng-star-inserted">Hardware Interrupts:</span>**<span class="ng-star-inserted"> These are signals sent directly from hardware peripherals to the CPU, demanding immediate attention. When an interrupt occurs, the CPU immediately pauses whatever it is doing, executes a special function called an </span>**<span class="ng-star-inserted">Interrupt Service Routine (ISR)</span>**<span class="ng-star-inserted">, and then resumes its previous work. This is ideal for high-priority, time-critical events triggered by the outside world, such as a button being pressed, data arriving on a communication bus, or a hardware timer reaching zero.</span>
2. **<span class="ng-star-inserted">Software Timers:</span>**<span class="ng-star-inserted"> These are timers managed by the RTOS itself. You can ask the RTOS to execute a specific function (a "callback") at some point in the future, either once or repeatedly. This allows you to schedule application-level events without blocking. Instead of calling </span><span class="inline-code ng-star-inserted">delay()</span><span class="ng-star-inserted">, a task can start a software timer and then continue with other work or yield control of the CPU to other tasks. The RTOS will ensure the callback function is executed at the correct time.</span>

<span class="ng-star-inserted">By using timers and interrupts, you can build complex, responsive applications where tasks spend almost no time waiting and are instead driven by the occurrence of events.</span>

# 5.2 An Overview of Asynchronous Tools in FreeRTOS

### <span class="ng-star-inserted">Software Timers: For Application-Scheduled Events</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">FreeRTOS Software Timer</span>**<span class="ng-star-inserted"> is a tool used to schedule the execution of a function at a future time. It's like setting an alarm clock within your software. When the timer expires, the RTOS automatically calls a predefined function, known as a </span>**<span class="ng-star-inserted">callback function</span>**<span class="ng-star-inserted">.</span>

**<span class="ng-star-inserted">Key Characteristics:</span>**

- **<span class="ng-star-inserted">Managed by the RTOS:</span>**<span class="ng-star-inserted"> Software timers are managed by a dedicated RTOS task (the "timer daemon"). This means they do not consume CPU time while they are waiting to expire.</span>
- **<span class="ng-star-inserted">Tied to the System Tick:</span>**<span class="ng-star-inserted"> The resolution of a software timer is determined by the FreeRTOS system tick rate (</span>`<span class="inline-code ng-star-inserted">configTICK_RATE_HZ</span>`<span class="ng-star-inserted">). You cannot schedule a timer for a period shorter than one tick.</span>
- **<span class="ng-star-inserted">Use Case:</span>**<span class="ng-star-inserted"> Ideal for repetitive, low-priority, or application-level timing. For example, you might use a software timer to:</span>
    
    
    - <span class="ng-star-inserted">Read a temperature sensor every five seconds.</span>
    - <span class="ng-star-inserted">Update a clock display once per minute.</span>
    - <span class="ng-star-inserted">Turn off an LED 500ms after it was turned on.</span>

**<span class="ng-star-inserted">Types of Software Timers:</span>**

1. **<span class="ng-star-inserted">One-Shot Timer:</span>**<span class="ng-star-inserted"> Executes its callback function only once after it is started.</span>
2. **<span class="ng-star-inserted">Auto-Reload Timer:</span>**<span class="ng-star-inserted"> Executes its callback function repeatedly at a fixed interval until it is explicitly stopped.</span>

### <span class="ng-star-inserted">Hardware Interrupts: For Hardware-Triggered Events</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">Hardware Interrupt</span>**<span class="ng-star-inserted"> is a mechanism for a hardware peripheral to signal the CPU that it needs immediate attention. Unlike a software timer, which is scheduled by your application, an interrupt is triggered by an external, physical event.</span>

**<span class="ng-star-inserted">Key Characteristics:</span>**

- **<span class="ng-star-inserted">High Priority:</span>**<span class="ng-star-inserted"> An interrupt will immediately preempt the currently running code, regardless of the task's priority. The CPU will save its current state and jump to execute the ISR.</span>
- **<span class="ng-star-inserted">Hardware-Driven:</span>**<span class="ng-star-inserted"> They are generated by peripherals like GPIO pins (e.g., a button press), hardware timers (for precise timing), or communication interfaces like UART/SPI (e.g., data has arrived).</span>
- **<span class="ng-star-inserted">Use Case:</span>**<span class="ng-star-inserted"> Essential for time-critical operations and reacting to external events with minimal latency. For example, you would use a hardware interrupt to:</span>
    
    
    - <span class="ng-star-inserted">Count pulses from a motor encoder to measure its speed.</span>
    - <span class="ng-star-inserted">Immediately stop a machine when a safety limit switch is triggered.</span>
    - <span class="ng-star-inserted">Capture incoming data from a high-speed sensor before it is overwritten.</span>

<span class="ng-star-inserted">In summary, the choice between them is driven by the source of the event:</span>

- <span class="ng-star-inserted">Use a </span>**<span class="ng-star-inserted">Software Timer</span>**<span class="ng-star-inserted"> when the event is driven by the logic of your application ("</span><span class="ng-star-inserted">I need to do X in 500 milliseconds</span><span class="ng-star-inserted">").</span>
- <span class="ng-star-inserted">Use a </span>**<span class="ng-star-inserted">Hardware Interrupt</span>**<span class="ng-star-inserted"> when the event is driven by an external hardware signal that requires an immediate response ("</span><span class="ng-star-inserted">The hardware needs attention NOW</span><span class="ng-star-inserted">").</span>

# 5.3 Deep Dive: FreeRTOS Software Timers

### <span class="ng-star-inserted">Creating, Starting, and Stopping Timers</span>

<span class="ng-star-inserted">Interacting with FreeRTOS software timers is done through a standard set of API functions. The core steps are to create a timer, start it, and, if needed, stop, reset, or delete it.</span>

#### <span class="ng-star-inserted">Creating a Timer</span>

<span class="ng-star-inserted">A software timer is created using the </span>`<span class="inline-code ng-star-inserted">xTimerCreate()</span>`<span class="ng-star-inserted"> function. This function does not start the timer; it only allocates the necessary resources and returns a handle that you will use to reference the timer in other API calls.</span>

<span class="ng-star-inserted">The function signature is:</span>

```c
TimerHandle_t xTimerCreate( const char * const pcTimerName,
                            const TickType_t xTimerPeriodInTicks,
                            const UBaseType_t uxAutoReload,
                            void * const pvTimerID,
                            TimerCallbackFunction_t pxCallbackFunction );
```

**<span class="ng-star-inserted">Parameters:</span>**

1. `<span class="inline-code ng-star-inserted">pcTimerName</span>`<span class="ng-star-inserted">: A descriptive name for the timer, used mainly for debugging.</span>
2. `<span class="inline-code ng-star-inserted">xTimerPeriodInTicks</span>`<span class="ng-star-inserted">: The timer's period in system ticks. You can use the </span>`<span class="inline-code ng-star-inserted">pdMS_TO_TICKS()</span>`<span class="ng-star-inserted"> macro to easily convert milliseconds to ticks.</span>
3. `<span class="inline-code ng-star-inserted">uxAutoReload</span>`<span class="ng-star-inserted">: Set to </span>`<span class="inline-code ng-star-inserted">pdTRUE</span>`<span class="ng-star-inserted"> for an auto-reload timer or </span>`<span class="inline-code ng-star-inserted">pdFALSE</span>`<span class="ng-star-inserted"> for a one-shot timer.</span>
4. `<span class="inline-code ng-star-inserted">pvTimerID</span>`<span class="ng-star-inserted">: A unique identifier for the timer. This is an application-defined value that can be used within the callback function to determine which timer has expired.</span>
5. `<span class="inline-code ng-star-inserted">pxCallbackFunction</span>`<span class="ng-star-inserted">: A pointer to the function that will be executed when the timer expires.</span>

<span class="ng-star-inserted">It is crucial to </span>**<span class="ng-star-inserted">always check the return value</span>**<span class="ng-star-inserted"> of </span>`<span class="inline-code ng-star-inserted">xTimerCreate()</span>`<span class="ng-star-inserted">. If it returns </span>`<span class="inline-code ng-star-inserted">NULL</span>`<span class="ng-star-inserted">, the timer could not be created, most likely due to insufficient FreeRTOS heap memory.</span>

#### <span class="ng-star-inserted">Controlling a Timer</span>

<span class="ng-star-inserted">Once you have a valid timer handle, you can control it with the following functions:</span>

- **<span class="ng-star-inserted">To start or restart a timer:</span>**  
    `<span class="inline-code ng-star-inserted">xTimerStart(TimerHandle_t xTimer, TickType_t xBlockTime)</span>`  
    <span class="ng-star-inserted">This places the timer into the active state. If the timer was already running, it will be reset to its initial period.</span>
- **<span class="ng-star-inserted">To stop a timer:</span>**  
    `<span class="inline-code ng-star-inserted">xTimerStop(TimerHandle_t xTimer, TickType_t xBlockTime)</span>`  
    <span class="ng-star-inserted">This stops the timer from running.</span>
- **<span class="ng-star-inserted">To reset a timer:</span>**  
    `<span class="inline-code ng-star-inserted">xTimerReset(TimerHandle_t xTimer, TickType_t xBlockTime)</span>`  
    <span class="ng-star-inserted">This is equivalent to calling </span><span class="inline-code ng-star-inserted">xTimerStart()</span><span class="ng-star-inserted"> on a running timer. It resets the timer's period back to its starting value.</span>
- **<span class="ng-star-inserted">To delete a timer:</span>**  
    `<span class="inline-code ng-star-inserted">xTimerDelete(TimerHandle_t xTimer, TickType_t xBlockTime)</span>`  
    <span class="ng-star-inserted">This frees the memory allocated when the timer was created. Once deleted, the handle is no longer valid.</span>

<span class="ng-star-inserted">The </span><span class="inline-code ng-star-inserted">xBlockTime</span><span class="ng-star-inserted"> parameter in these functions specifies how long the calling task should wait if the command cannot be sent to the timer daemon task immediately (because its command queue is full). Using </span>`<span class="inline-code ng-star-inserted">portMAX_DELAY</span>`<span class="ng-star-inserted"> will cause the task to wait indefinitely, which is a safe option in most cases.</span>

### <span class="ng-star-inserted">3.2 One-Shot vs. Auto-Reload Timers</span>

<span class="ng-star-inserted">FreeRTOS offers two types of software timers, defined at creation time by the </span><span class="inline-code ng-star-inserted">uxAutoReload</span><span class="ng-star-inserted"> parameter.</span>

#### <span class="ng-star-inserted">One-Shot Timer (</span><span class="inline-code ng-star-inserted">`uxAutoReload` = `pdFALSE`</span><span class="ng-star-inserted">)</span>

<span class="ng-star-inserted">A one-shot timer will execute its callback function </span>**<span class="ng-star-inserted">only once</span>**<span class="ng-star-inserted"> after its period expires. It is useful for performing a single, delayed action.</span>

- **<span class="ng-star-inserted">Example Use Case:</span>**<span class="ng-star-inserted"> You want to turn off a motor 10 seconds after it has been started.</span>

**<span class="ng-star-inserted">Example Creation:</span>**

```c
TimerHandle_t xOneShotTimer;

void vOneShotCallback(TimerHandle_t xTimer); // Forward declaration

void setup() {
  xOneShotTimer = xTimerCreate(
      "OneShot",                // Timer name
      pdMS_TO_TICKS(2000),      // 2000ms period
      pdFALSE,                  // Set as a one-shot timer
      (void *) 0,               // Timer ID = 0
      vOneShotCallback          // Callback function
  );

  if (xOneShotTimer != NULL) {
    xTimerStart(xOneShotTimer, 0);
  }
}
```

#### <span class="ng-star-inserted">Auto-Reload Timer (</span><span class="inline-code ng-star-inserted">`uxAutoReload `= `pdTRUE`</span><span class="ng-star-inserted">)</span>

<span class="ng-star-inserted">An auto-reload timer will execute its callback function </span>**<span class="ng-star-inserted">repeatedly</span>**<span class="ng-star-inserted"> at a fixed interval. After the callback is executed, the timer automatically resets and starts counting down again.</span>

- **<span class="ng-star-inserted">Example Use Case:</span>**<span class="ng-star-inserted"> You need to read a sensor and print its value every 1000 milliseconds.</span>

**<span class="ng-star-inserted">Example Creation:</span>**

```c
TimerHandle_t xAutoReloadTimer;

void vAutoReloadCallback(TimerHandle_t xTimer); // Forward declaration

void setup() {
  xAutoReloadTimer = xTimerCreate(
      "AutoReload",             // Timer name
      pdMS_TO_TICKS(1000),      // 1000ms period
      pdTRUE,                   // Set as an auto-reload timer
      (void *) 1,               // Timer ID = 1
      vAutoReloadCallback       // Callback function
  );

  if (xAutoReloadTimer != NULL) {
    xTimerStart(xAutoReloadTimer, 0);
  }
}
```

### <span class="ng-star-inserted">3.3 Writing Effective Timer Callback Functions</span>

<span class="ng-star-inserted">The callback function is the heart of the software timer. It's the code that runs when the timer expires. To ensure system stability, it must be written carefully.</span>

<span class="ng-star-inserted">The function must have the following signature:</span>

```c
void YourCallbackName(TimerHandle_t xTimer);
```

<span class="ng-star-inserted">The single parameter, </span>`<span class="inline-code ng-star-inserted">xTimer</span>`<span class="ng-star-inserted">, is the handle of the timer that just expired. This is very useful when a single callback function is used for multiple timers. You can retrieve the Timer ID you assigned during creation to identify which timer it was.</span>

**<span class="ng-star-inserted">Example Callback Implementation:</span>**

```c
void vTimerCallback(TimerHandle_t xTimer) {
  // Get the ID of the timer that expired
  uint32_t ulTimerID = (uint32_t) pvTimerGetTimerID(xTimer);

  // Check which timer it was and perform an action
  if (ulTimerID == 0) {
    // This was the one-shot timer
    Serial.println("One-shot timer expired.");
  } else if (ulTimerID == 1) {
    // This was the auto-reload timer
    Serial.println("Auto-reload timer expired.");
  }
}
```

#### <span class="ng-star-inserted">Rules for Writing Callback Functions</span>

<span class="ng-star-inserted">Timer callbacks execute in the context of the FreeRTOS timer daemon task, not an ISR. However, they share similar restrictions because multiple callbacks may need to execute in sequence.</span>

1. **<span class="ng-star-inserted">Keep them short and fast.</span>**<span class="ng-star-inserted"> A long-running callback will delay the execution of other pending timer callbacks.</span>
2. **<span class="ng-star-inserted">Never block.</span>**<span class="ng-star-inserted"> Do not call any function that could block, such as </span>`<span class="inline-code ng-star-inserted">vTaskDelay()</span>`<span class="ng-star-inserted"> or waiting on a semaphore or queue with a long timeout. Doing so will halt the timer daemon task, preventing any other software timers in the system from running.</span>

# 5.4 Deep Dive: ESP32 Hardware Interrupts

### <span class="ng-star-inserted">Configuring Hardware Timers on the ESP32</span>

<span class="ng-star-inserted">The ESP32 microcontroller comes with four general-purpose 64-bit hardware timers. These timers are highly precise and can be used to generate interrupts at specific intervals, independent of the RTOS scheduler.</span>

<span class="ng-star-inserted">The configuration involves four main steps:</span>

1. **<span class="ng-star-inserted">Initialize the Timer:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">timerBegin(uint8_t num, uint16_t prescaler, bool countUp)</span>`
    
    
    - `<span class="inline-code ng-star-inserted">num</span>`<span class="ng-star-inserted">: The timer you want to use (0 to 3).</span>
    - `<span class="inline-code ng-star-inserted">prescaler</span>`<span class="ng-star-inserted">: A value used to divide the base clock (usually 80 MHz). A prescaler of 80 will make the timer count up every 1 microsecond (80,000,000 Hz / 80 = 1,000,000 Hz).</span>
    - `<span class="inline-code ng-star-inserted">countUp</span>`<span class="ng-star-inserted">: </span><span class="inline-code ng-star-inserted">true</span><span class="ng-star-inserted"> for counting up, </span><span class="inline-code ng-star-inserted">false</span><span class="ng-star-inserted"> for counting down.</span>
2. **<span class="ng-star-inserted">Attach the ISR:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">timerAttachInterrupt(hw_timer_t *timer, void (*fn)(void), bool edge)</span>`
    
    
    - <span class="ng-star-inserted">This function links your ISR function to the hardware timer.</span>
3. **<span class="ng-star-inserted">Set the Alarm Value:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">timerAlarmWrite(hw_timer_t *timer, uint64_t alarm_value, bool autoreload)</span>`
    
    
    - <span class="ng-star-inserted">This sets the counter value at which the interrupt will be generated. For a 1 MHz timer clock, an </span><span class="inline-code ng-star-inserted">alarm\_value</span><span class="ng-star-inserted"> of 1,000,000 will trigger an interrupt every second.</span>
    - <span class="ng-star-inserted">If </span><span class="inline-code ng-star-inserted">autoreload</span><span class="ng-star-inserted"> is </span><span class="inline-code ng-star-inserted">true</span><span class="ng-star-inserted">, the timer will automatically restart after the interrupt, making it periodic.</span>
4. **<span class="ng-star-inserted">Enable the Alarm:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">timerAlarmEnable(hw_timer_t *timer)</span>`
    
    
    - <span class="ng-star-inserted">This starts the timer and enables the interrupt generation.</span>

### <span class="ng-star-inserted">Writing an Interrupt Service Routine (ISR)</span>

<span class="ng-star-inserted">An ISR is a special function that the CPU executes in response to a hardware interrupt.</span>

<span class="ng-star-inserted">On the ESP32, it is critical to use the </span>`<span class="inline-code ng-star-inserted">IRAM_ATTR</span>` <span class="ng-star-inserted">attribute in the function definition:</span>

```c
void IRAM_ATTR onTimer() {
  // Your interrupt code here...
}
```

`<strong class="ng-star-inserted"><span class="inline-code ng-star-inserted">IRAM_ATTR</span></strong>`<span class="ng-star-inserted"> tells the compiler to place the ISR code into the ESP32's Internal RAM (IRAM). This is essential for performance and reliability. If an ISR is in flash memory, the CPU may have to wait for the flash to be read, which can introduce unacceptable latency and jitter into the interrupt response time.</span>

**<span class="ng-star-inserted">Example of a complete hardware timer setup:</span>**

```c
// Timer handle
hw_timer_t *timer = NULL;

// The ISR function to be called
void IRAM_ATTR onTimer() {
  // Toggle an LED or perform a quick action
  digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}

void setup() {
  pinMode(LED_PIN, OUTPUT);

  // 1. Initialize timer 0 with a prescaler of 80
  timer = timerBegin(0, 80, true);

  // 2. Attach the ISR to our timer
  timerAttachInterrupt(timer, &onTimer, true);

  // 3. Set the alarm to trigger every 1,000,000 counts (1 second)
  timerAlarmWrite(timer, 1000000, true);

  // 4. Enable the alarm
  timerAlarmEnable(timer);
}
```

### <span class="ng-star-inserted">The Golden Rules of ISRs</span>

<span class="ng-star-inserted">Interrupt Service Routines are powerful but dangerous if used incorrectly. Because they can interrupt any part of your code at any time, they must follow strict rules to avoid crashing the system.</span>

1. **<span class="ng-star-inserted">Keep it Fast:</span>**<span class="ng-star-inserted"> An ISR must execute as quickly as possible. The longer an ISR runs, the more it delays the main program and other interrupts. The best ISRs do the absolute minimum work required, such as setting a flag or sending data to a queue, and then exit. Defer all complex data processing to a regular FreeRTOS task.</span>
2. **<span class="ng-star-inserted">Never Block:</span>**<span class="ng-star-inserted"> An ISR </span>**<span class="ng-star-inserted">must never, ever block</span>**<span class="ng-star-inserted">. This means you cannot call functions like </span>`<span class="inline-code ng-star-inserted">vTaskDelay()</span>`<span class="ng-star-inserted">, </span>`<span class="inline-code ng-star-inserted">delay()</span>`<span class="ng-star-inserted">, or wait for a semaphore, mutex, or queue. The system is in a special interrupt context, and attempting to block will lead to a system crash.</span>
3. **<span class="ng-star-inserted">Use ISR-Safe API Functions:</span>**<span class="ng-star-inserted"> When you need to interact with FreeRTOS from an ISR (for example, to signal a task), you </span>**<span class="ng-star-inserted">must</span>**<span class="ng-star-inserted"> use the special ISR-safe version of the API function. These functions are specially designed to be non-blocking and safe to call from an interrupt context. They are easily recognizable as they all end with the suffix </span>`<strong class="ng-star-inserted"><span class="inline-code ng-star-inserted">...FromISR()</span></strong>`<span class="ng-star-inserted">.</span>
    
    
    - **<span class="ng-star-inserted">Correct:</span>** `<span class="inline-code ng-star-inserted">xSemaphoreGiveFromISR()</span>`
    - **<span class="ng-star-inserted">Incorrect:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">xSemaphoreGive()</span>`
    - **<span class="ng-star-inserted">Correct:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">xQueueSendFromISR()</span>`
    - **<span class="ng-star-inserted">Incorrect:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">xQueueSend()</span>`
4. **<span class="ng-star-inserted">Be Careful with Global Variables:</span>**<span class="ng-star-inserted"> If an ISR modifies a global variable that is also accessed by a task, you must protect that variable to prevent data corruption (a "race condition"). The primary method for this is using a </span>**<span class="ng-star-inserted">critical section</span>**<span class="ng-star-inserted">, which will be discussed in the next part.</span>

# 5.5 The Core Challenge: ISRs and Tasks Synchronization

### <span class="ng-star-inserted">Understanding the Problem: Shared Data and Race Conditions</span>

<span class="ng-star-inserted">When a hardware interrupt occurs, the CPU immediately stops executing the current task and jumps to the ISR. This can happen at any time, even in the middle of a single line of C code that takes multiple machine instructions to execute. If the ISR and the task both access the same global variable, the system is vulnerable to a </span>**<span class="ng-star-inserted">race condition</span>**<span class="ng-star-inserted">.</span>

<span class="ng-star-inserted">A race condition is an undesirable situation that occurs when the outcome of a process depends on the uncontrollable sequence of events. In our case, it's a bug that occurs when the timing of the interrupt corrupts shared data.</span>

**<span class="ng-star-inserted">A Classic Example of a Race Condition:</span>**  
<span class="ng-star-inserted">Imagine a global variable </span>`<span class="inline-code ng-star-inserted">volatile int counter = 0;</span>`<span class="ng-star-inserted">.</span>

- <span class="ng-star-inserted">An ISR, triggered by a timer, is programmed to increment the counter: </span><span class="inline-code ng-star-inserted">`counter++`;</span><span class="ng-star-inserted">.</span>
- <span class="ng-star-inserted">A task in the main application is programmed to decrement it: </span><span class="inline-code ng-star-inserted">`counter--`;</span><span class="ng-star-inserted">.</span>

<span class="ng-star-inserted">Let's trace a potential failure scenario. Assume </span>`<span class="inline-code ng-star-inserted">counter</span>`<span class="ng-star-inserted"> is currently </span>`<span class="inline-code ng-star-inserted">10</span>`<span class="ng-star-inserted">.</span>

1. **<span class="ng-star-inserted">Task Executes:</span>**<span class="ng-star-inserted"> The task reads the value of </span>`<span class="inline-code ng-star-inserted">counter</span>`<span class="ng-star-inserted"> (10) into a CPU register.</span>
2. **<span class="ng-star-inserted">Task Calculates:</span>**<span class="ng-star-inserted"> The CPU calculates the new value, </span>`<span class="inline-code ng-star-inserted">10 - 1 = 9</span>`<span class="ng-star-inserted">.</span>
3. **<span class="ng-star-inserted">CONTEXT SWITCH (INTERRUPT):</span>**<span class="ng-star-inserted"> Before the task can write the value </span>`<span class="inline-code ng-star-inserted">9</span>`<span class="ng-star-inserted"> back to the </span>`<span class="inline-code ng-star-inserted">counter</span>`<span class="ng-star-inserted"> variable in memory, a hardware interrupt occurs!</span>
4. **<span class="ng-star-inserted">ISR Executes:</span>**<span class="ng-star-inserted"> The ISR runs. It reads the value of </span>`<span class="inline-code ng-star-inserted">counter</span>`<span class="ng-star-inserted"> from memory (which is still </span><span class="inline-code ng-star-inserted">10</span><span class="ng-star-inserted">).</span>
5. **<span class="ng-star-inserted">ISR Calculates:</span>**<span class="ng-star-inserted"> The ISR calculates </span>`<span class="inline-code ng-star-inserted">10 + 1 = 11</span>`<span class="ng-star-inserted">.</span>
6. **<span class="ng-star-inserted">ISR Writes:</span>**<span class="ng-star-inserted"> The ISR writes the value </span>`<span class="inline-code ng-star-inserted">11</span>`<span class="ng-star-inserted"> back to the </span><span class="inline-code ng-star-inserted">counter</span><span class="ng-star-inserted"> variable in memory.</span>
7. **<span class="ng-star-inserted">ISR Finishes:</span>**<span class="ng-star-inserted"> The interrupt is complete, and the CPU returns control to the task, restoring its state exactly where it left off.</span>
8. **<span class="ng-star-inserted">Task Resumes:</span>**<span class="ng-star-inserted"> The task is completely unaware that it was interrupted. Its next step is to write its calculated value (</span>`<span class="inline-code ng-star-inserted">9</span>`<span class="ng-star-inserted">) back to the </span>`<span class="inline-code ng-star-inserted">counter</span>`<span class="ng-star-inserted"> variable.</span>
9. **<span class="ng-star-inserted">Corruption:</span>**<span class="ng-star-inserted"> The value </span>`<span class="inline-code ng-star-inserted">11</span>`<span class="ng-star-inserted"> that the ISR correctly calculated is now overwritten with </span>`<span class="inline-code ng-star-inserted">9</span>`<span class="ng-star-inserted">. The increment operation has been completely lost.</span>

<span class="ng-star-inserted">This is the fundamental problem of concurrency: protecting shared resources from uncontrolled, simultaneous access.</span>

### <span class="ng-star-inserted">The ISR Context and </span>`<span class="inline-code ng-star-inserted">...FromISR()</span>`<span class="ng-star-inserted"> Functions</span>

<span class="ng-star-inserted">To solve the synchronization problem, we need tools to manage access to shared data. However, as we learned in Part 2, ISRs operate in a special </span>**<span class="ng-star-inserted">interrupt context</span>**<span class="ng-star-inserted">. They are not tasks and are not managed by the RTOS scheduler. This leads to a critical rule: </span>**<span class="ng-star-inserted">an ISR can never block</span>**<span class="ng-star-inserted">.</span>

<span class="ng-star-inserted">If an ISR tried to wait for a resource (like calling </span>`<span class="inline-code ng-star-inserted">xQueueSend()</span>`<span class="ng-star-inserted"> and the queue was full), it would effectively block. But since the ISR is not a task, the scheduler has no other context to switch to. The entire system would freeze, leading to a crash.</span>

<span class="ng-star-inserted">To solve this, FreeRTOS provides a special set of ISR-safe functions that are non-blocking. You can recognize them by their` `</span>`<strong class="ng-star-inserted"><span class="inline-code ng-star-inserted">...FromISR()</span></strong>`<span class="ng-star-inserted"> suffix.</span>

- `<span class="inline-code ng-star-inserted">xQueueSend()</span>`<span class="ng-star-inserted"> -&gt; </span>`<span class="inline-code ng-star-inserted">xQueueSendFromISR()</span>`
- `<span class="inline-code ng-star-inserted">xSemaphoreGive()</span>`<span class="ng-star-inserted"> -&gt; </span>`<span class="inline-code ng-star-inserted">xSemaphoreGiveFromISR()</span>`

<span class="ng-star-inserted">These functions include an optional parameter, </span>`<span class="inline-code ng-star-inserted">pxHigherPriorityTaskWoken</span>`<span class="ng-star-inserted">. An ISR uses this parameter to inform the RTOS if its action (e.g., giving a semaphore) has unblocked a task that has a higher priority than the task that was originally interrupted. If so, the RTOS can perform an immediate context switch to the higher-priority task as soon as the ISR finishes, ensuring the system remains as responsive as possible.</span>

# 5.6 Synchronization Mechanisms: A Comparative Guide

<span class="ng-star-inserted">FreeRTOS provides three primary mechanisms for safely managing shared resources between tasks and ISRs.</span>

### <span class="ng-star-inserted">Critical Sections: The "Big Hammer" for Protection</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">critical section</span>**<span class="ng-star-inserted"> is a section of code that is guaranteed to run to completion without being preempted by an interrupt or another task. It is the most direct and forceful way to prevent a race condition.</span>

**<span class="ng-star-inserted">How it Works:</span>**<span class="ng-star-inserted"> It works by temporarily disabling all interrupts system-wide.</span>

- <span class="ng-star-inserted">In a task, you wrap the critical code with </span>`<span class="inline-code ng-star-inserted">taskENTER_CRITICAL()</span>`<span class="ng-star-inserted"> and </span>`<span class="inline-code ng-star-inserted">taskEXIT_CRITICAL()</span>`<span class="ng-star-inserted">.</span>
- <span class="ng-star-inserted">In an ISR, you use </span>`<span class="inline-code ng-star-inserted">portENTER_CRITICAL_ISR()</span>`<span class="ng-star-inserted"> and </span>`<span class="inline-code ng-star-inserted">portEXIT_CRITICAL_ISR()</span>`<span class="ng-star-inserted">.</span>

**<span class="ng-star-inserted">Example:</span>**

```c
volatile int counter = 0;

void IRAM_ATTR onTimer() {
    portENTER_CRITICAL_ISR(&timerMux);
    counter++; // This is now safe
    portEXIT_CRITICAL_ISR(&timerMux);
}

void printValues(void * parameter) {
    while (true) {
        taskENTER_CRITICAL();
        counter--; // This is now safe
        Serial.println(counter);
        taskEXIT_CRITICAL();
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}
```

- **<span class="ng-star-inserted">When to Use:</span>**<span class="ng-star-inserted"> Only for protecting very short, fast operations on shared variables where other mechanisms are too slow or complex.</span>
- **<span class="ng-star-inserted">Risk:</span>**<span class="ng-star-inserted"> While a critical section is active, all interrupts are disabled. This can severely impact the real-time responsiveness of the system. </span>**<span class="ng-star-inserted">Keep critical sections as short as humanly possible.</span>**

### <span class="ng-star-inserted">Semaphores: The Best Tool for Pure Signaling</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">semaphore</span>**<span class="ng-star-inserted"> is a signaling mechanism. It does not transfer data. It is used to signal that an event has occurred or to control access to a resource. For ISR-to-task communication, a </span>**<span class="ng-star-inserted">binary semaphore</span>**<span class="ng-star-inserted"> is typically used.</span>

**<span class="ng-star-inserted">How it Works:</span>**<span class="ng-star-inserted"> Think of it as a flag.</span>

1. <span class="ng-star-inserted">A task tries to "take" the semaphore using </span>`xSemaphoreTake()`<span class="ng-star-inserted">. If the semaphore is not available, the task enters the Blocked state, consuming no CPU time.</span>
2. <span class="ng-star-inserted">An ISR, responding to a hardware event, "gives" the semaphore using </span>`<span class="inline-code ng-star-inserted">xSemaphoreGiveFromISR()</span>`<span class="ng-star-inserted">.</span>
3. <span class="ng-star-inserted">Giving the semaphore unblocks the waiting task, moving it to the Ready state. The scheduler will then run the task when it is its turn.</span>

**<span class="ng-star-inserted">Example:</span>**

```c
SemaphoreHandle_t binSemaphore = NULL;

void IRAM_ATTR onTimer() {
    xSemaphoreGiveFromISR(binSemaphore, NULL);
}

void processValues(void * parameter) {
    while (true) {
        // Wait here until the ISR gives the semaphore
        if (xSemaphoreTake(binSemaphore, portMAX_DELAY) == pdTRUE) {
            // The event occurred. Process the data.
            Serial.println("Processing data now...");
        }
    }
}
```

- **<span class="ng-star-inserted">When to Use:</span>**<span class="ng-star-inserted"> When an ISR needs to notify a task that an event has happened (e.g., "ADC conversion is complete, the data is ready to be read"). It's a pure synchronization primitive.</span>

### <span class="ng-star-inserted">Queues: The Best Tool for Transferring Data</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">queue</span>**<span class="ng-star-inserted"> is the most powerful and often the best mechanism for ISR-to-task communication. It provides a thread-safe First-In, First-Out (FIFO) buffer to not only signal an event but to also safely transfer data from the ISR to the task.</span>

**<span class="ng-star-inserted">How it Works:</span>**

1. <span class="ng-star-inserted">A task waits to "receive" from a queue using </span>`<span class="inline-code ng-star-inserted">xQueueReceive()</span>`<span class="ng-star-inserted">. If the queue is empty, the task enters the Blocked state.</span>
2. <span class="ng-star-inserted">An ISR generates some data (e.g., reads a sensor). It then "sends" this data to the queue using </span>`<span class="inline-code ng-star-inserted">xQueueSendFromISR()</span>`<span class="ng-star-inserted">. This action copies the data into the queue's buffer.</span>
3. <span class="ng-star-inserted">The act of sending data to the queue unblocks the waiting task. The task then receives the copy of the data from the queue for safe processing.</span>

<span class="ng-star-inserted">This approach is superior because the ISR and the task never access the same variable directly. They only interact via the RTOS-managed queue, which eliminates race conditions by design.</span>

**<span class="ng-star-inserted">Example:</span>**

```c
QueueHandle_t sensorQueue = NULL;

void IRAM_ATTR onTimer() {
    // Read sensor and package data into a struct
    sensorData_t data;
    data.adcValue1 = analogRead(34);
    
    // Send a COPY of the data to the queue
    xQueueSendFromISR(sensorQueue, &data, NULL);
}

void processValues(void * parameter) {
    sensorData_t receivedData;
    while (true) {
        // Wait here until data arrives in the queue
        if (xQueueReceive(sensorQueue, &receivedData, portMAX_DELAY) == pdTRUE) {
            // Safely process the received data
            Serial.println(receivedData.adcValue1);
        }
    }
}
```

- **<span class="ng-star-inserted">When to Use:</span>**<span class="ng-star-inserted"> Whenever an ISR needs to pass data to a task for processing. This is the preferred method in almost all data-generating ISR scenarios.</span>

# 5.7 Choosing the Right Tool: A Practical Comparison

<span class="ng-star-inserted">Deciding which synchronization mechanism to use is a key skill in embedded programming. Use the following table and questions as a guide.</span>

<table border="1" id="bkmrk-mechanism-purpose-tr" style="border-collapse: collapse; width: 100%; height: 303.953px;"><colgroup><col style="width: 20.0238%;"></col><col style="width: 20.0238%;"></col><col style="width: 20.0238%;"></col><col style="width: 20.0238%;"></col><col style="width: 20.0238%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="ng-star-inserted" style="height: 29.7969px;">**<span class="ng-star-inserted">Mechanism</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">Purpose</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">Transfers Data?</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">When to Use</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">Primary Risk</span>**</td></tr><tr style="height: 96.9844px;"><td class="ng-star-inserted" style="height: 96.9844px;">**<span class="ng-star-inserted">Critical Section</span>**</td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Mutual Exclusion</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">No</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Protecting a few lines of code that modify a shared variable. Must be extremely fast.</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Halts system responsiveness by disabling all interrupts. Can easily break real-time deadlines.</span></td></tr><tr style="height: 80.1875px;"><td class="ng-star-inserted" style="height: 80.1875px;">**<span class="ng-star-inserted">Semaphore</span>**</td><td class="ng-star-inserted align-center" style="height: 80.1875px;"><span class="ng-star-inserted">Signaling</span></td><td class="ng-star-inserted align-center" style="height: 80.1875px;"><span class="ng-star-inserted">No</span></td><td class="ng-star-inserted align-center" style="height: 80.1875px;"><span class="ng-star-inserted">Notifying a task that a specific event has occurred. Deferring ISR work to a task.</span></td><td class="ng-star-inserted align-center" style="height: 80.1875px;"><span class="ng-star-inserted">Does not help with transferring the actual data associated with the event.</span></td></tr><tr style="height: 96.9844px;"><td class="ng-star-inserted" style="height: 96.9844px;">**<span class="ng-star-inserted">Queue</span>**</td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Data Transfer</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Yes</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Sending data of any type from an ISR to a task for processing.</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Minor overhead for copying data into the queue. May not be suitable for very large data structures.</span></td></tr></tbody></table>

<div _ngcontent-ng-c4139270029="" class="table-container ng-star-inserted" id="bkmrk-"></div>### <span class="ng-star-inserted">Decision-Making Guide</span>

<span class="ng-star-inserted">When designing an interaction between an ISR and a task, ask yourself these questions:</span>

1. **<span class="ng-star-inserted">Do I need to pass data from the ISR to the task?</span>**
    
    
    - **<span class="ng-star-inserted">Yes:</span>**<span class="ng-star-inserted"> Use a </span>**<span class="ng-star-inserted">Queue</span>**<span class="ng-star-inserted">. This is the safest and most robust solution for transferring data.</span>
    - **<span class="ng-star-inserted">No:</span>**<span class="ng-star-inserted"> Go to question 2.</span>
2. **<span class="ng-star-inserted">Is my goal simply to wake up a task to do some work when an interrupt occurs?</span>**
    
    
    - **<span class="ng-star-inserted">Yes:</span>**<span class="ng-star-inserted"> Use a </span>**<span class="ng-star-inserted">Semaphore</span>**<span class="ng-star-inserted">. It is a lightweight and highly efficient signaling mechanism.</span>
    - **<span class="ng-star-inserted">No:</span>**<span class="ng-star-inserted"> Go to question 3.</span>
3. **<span class="ng-star-inserted">Do I only need to protect a single, simple variable (like a counter or flag) during a very quick read-modify-write operation?</span>**
    
    
    - **<span class="ng-star-inserted">Yes:</span>**<span class="ng-star-inserted"> A </span>**<span class="ng-star-inserted">Critical Section</span>**<span class="ng-star-inserted"> is an option, but only if the operation is genuinely just a few lines of code. Be aware of the impact on system latency.</span>
    - **<span class="ng-star-inserted">No:</span>**<span class="ng-star-inserted"> Re-evaluate your design. You likely need a semaphore or a queue.</span>

<span class="ng-star-inserted">In modern RTOS development, </span>**<span class="ng-star-inserted">Queues and Semaphores are almost always preferred over Critical Sections</span>**<span class="ng-star-inserted"> for managing ISR-task interactions. They provide cleaner, safer, and more scalable solutions that have less impact on the overall real-time performance of your system.</span>

# 5.8 Advanced Project: A Multi-Sensor Data Logger

<span class="ng-star-inserted">In this chapter, we will build a complete data logging application that utilizes all the core concepts we have learned: hardware interrupts for precise data acquisition, queues for safe data transfer, multiple tasks with different priorities for processing and logging, and a software timer for periodic status checks.</span>

### <span class="ng-star-inserted">Project Goal</span>

<span class="ng-star-inserted">We will create a system that performs the following actions:</span>

1. <span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">hardware timer</span>**<span class="ng-star-inserted"> will generate an interrupt every 200 milliseconds.</span>
2. <span class="ng-star-inserted">The </span>**<span class="ng-star-inserted">Interrupt Service Routine (ISR)</span>**<span class="ng-star-inserted"> will simulate reading data from two sensors (e.g., temperature and humidity) and will send this data package to a queue.</span>
3. <span class="ng-star-inserted">A high-priority </span>**<span class="ng-star-inserted">"Processing Task"</span>**<span class="ng-star-inserted"> will wait for data to arrive in the queue. When it does, it will perform a simple calculation (e.g., calculate an average) and place the result into a second queue.</span>
4. <span class="ng-star-inserted">A low-priority </span>**<span class="ng-star-inserted">"Logging Task"</span>**<span class="ng-star-inserted"> will wait for results to arrive in the second queue and print them to the Serial Monitor.</span>
5. <span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">software timer</span>**<span class="ng-star-inserted"> will fire every 5 seconds to print a "System OK" status message, demonstrating a non-critical, periodic background action.</span>

<span class="ng-star-inserted">This architecture is a common and robust pattern in embedded systems. It decouples the time-critical data acquisition (in the ISR) from the less critical data processing and logging (in the tasks), ensuring the system remains responsive.</span>

### <span class="ng-star-inserted">You Will Need</span>

- <span class="ng-star-inserted">An ESP32 development board.</span>
- <span class="ng-star-inserted">The Arduino IDE with the ESP32 board package installed.</span>

```c++
#include <Arduino.h>

// Define task priorities
#define PROCESSING_TASK_PRIORITY 2
#define LOGGING_TASK_PRIORITY    1

// Define handles for RTOS objects
QueueHandle_t sensorDataQueue;
QueueHandle_t resultQueue;
TimerHandle_t systemHealthTimer;
hw_timer_t *hardwareTimer = NULL;

// Data structure to hold raw sensor readings
typedef struct {
  int temperature;
  int humidity;
} SensorData;

// Data structure for the processed result
typedef struct {
  float averageValue;
} ProcessedResult;

// --- Interrupt Service Routine ---
// This function runs every time the hardware timer fires.
// It must be fast and non-blocking.
void IRAM_ATTR onTimer() {
  // Simulate reading sensor data
  SensorData data;
  data.temperature = random(20, 30); // Simulate temp between 20-29°C
  data.humidity = random(40, 60);    // Simulate humidity between 40-59%

  // Send a COPY of the data to the queue.
  // Use the ISR-safe version of the function.
  xQueueSendFromISR(sensorDataQueue, &data, NULL);
}

// --- Software Timer Callback ---
// This function runs every time the software timer expires.
void systemHealthCallback(TimerHandle_t xTimer) {
  Serial.println("[HEALTH] System OK");
}

// --- High-Priority Task: Processing ---
void processingTask(void *parameter) {
  SensorData receivedData;
  ProcessedResult result;

  while (true) {
    // Wait indefinitely until an item arrives in the sensorDataQueue
    if (xQueueReceive(sensorDataQueue, &receivedData, portMAX_DELAY) == pdPASS) {
      // We have data, now process it.
      Serial.println("[PROCESS] Data received. Calculating average...");
      
      result.averageValue = (receivedData.temperature + receivedData.humidity) / 2.0;

      // Send the result to the logging task via the resultQueue
      xQueueSend(resultQueue, &result, portMAX_DELAY);
    }
  }
}

// --- Low-Priority Task: Logging ---
void loggingTask(void *parameter) {
  ProcessedResult receivedResult;

  while (true) {
    // Wait indefinitely until a result arrives in the resultQueue
    if (xQueueReceive(resultQueue, &receivedResult, portMAX_DELAY) == pdPASS) {
      // We have a result, now log it to the console.
      Serial.print("[LOG] Processed Average: ");
      Serial.println(receivedResult.averageValue);
      Serial.println("--------------------");
    }
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("--- Multi-Sensor Data Logger ---");

  // 1. Create the queues
  // Queue to hold 10 SensorData structs
  sensorDataQueue = xQueueCreate(10, sizeof(SensorData));
  // Queue to hold 5 ProcessedResult structs
  resultQueue = xQueueCreate(5, sizeof(ProcessedResult));

  // 2. Create the software timer for system health checks
  systemHealthTimer = xTimerCreate(
      "HealthTimer",          // Name
      pdMS_TO_TICKS(5000),    // 5000ms period
      pdTRUE,                 // Auto-reload
      (void *) 0,             // Timer ID
      systemHealthCallback    // Callback function
  );

  // 3. Create the tasks
  xTaskCreate(
      processingTask,         // Function to implement the task
      "Processing Task",      // Name of the task
      2048,                   // Stack size in words
      NULL,                   // Task input parameter
      PROCESSING_TASK_PRIORITY, // Priority of the task
      NULL                    // Task handle
  );

  xTaskCreate(
      loggingTask,
      "Logging Task",
      2048,
      NULL,
      LOGGING_TASK_PRIORITY,
      NULL
  );

  // 4. Configure the hardware timer
  // Use a prescaler of 80 to get a 1MHz clock (80MHz / 80)
  hardwareTimer = timerBegin(0, 80, true);
  timerAttachInterrupt(hardwareTimer, &onTimer, true);
  // Set alarm for 200,000 counts (200ms at 1MHz)
  timerAlarmWrite(hardwareTimer, 200000, true); 
  timerAlarmEnable(hardwareTimer);

  // 5. Start the software timer
  if (systemHealthTimer != NULL) {
    xTimerStart(systemHealthTimer, 0);
  }
  
  Serial.println("System initialized. Starting data acquisition...");
}

void loop() {
  // The main loop is empty. All work is done by RTOS tasks and timers.
  vTaskDelete(NULL); // Delete the loop task to save resources
}
```

### <span class="ng-star-inserted">Code Walkthrough</span>

1. **<span class="ng-star-inserted">Data Structures:</span>**<span class="ng-star-inserted"> We define two </span>`<span class="inline-code ng-star-inserted">structs</span>`<span class="ng-star-inserted">, </span>`<span class="inline-code ng-star-inserted">SensorData</span>`<span class="ng-star-inserted"> and </span>`<span class="inline-code ng-star-inserted">ProcessedResult</span>`<span class="ng-star-inserted">, to create organized data packages that can be sent to queues. This is much cleaner than passing raw variables.</span>
2. **<span class="ng-star-inserted">Hardware Interrupt (</span>`<span class="inline-code ng-star-inserted">onTimer</span>`<span class="ng-star-inserted">)</span>**<span class="ng-star-inserted">: This ISR is the "producer." It runs at a precise interval, generates data, and immediately sends it to the </span>`<span class="inline-code ng-star-inserted">sensorDataQueue</span>`<span class="ng-star-inserted">. It does no processing and exits quickly, as a good ISR should.</span>
3. **<span class="ng-star-inserted">Processing Task (</span>`<span class="inline-code ng-star-inserted">processingTask</span>`<span class="ng-star-inserted">)</span>**<span class="ng-star-inserted">: This task is a "consumer-producer." It has a higher priority because we want to process data as soon as it's available. It waits on </span>`<span class="inline-code ng-star-inserted">sensorDataQueue</span>`<span class="ng-star-inserted">. When data arrives, it performs a quick calculation and sends the result to </span>`<span class="inline-code ng-star-inserted">resultQueue</span>`<span class="ng-star-inserted">.</span>
4. **<span class="ng-star-inserted">Logging Task (</span>`<span class="inline-code ng-star-inserted">loggingTask</span>`<span class="ng-star-inserted">)</span>**<span class="ng-star-inserted">: This is the final "consumer." It has a lower priority because logging is generally not a time-critical operation. It waits for fully processed data on </span>`<span class="inline-code ng-star-inserted">resultQueue</span>`<span class="ng-star-inserted"> and prints it. Because of its lower priority, it will only run when the </span>`<span class="inline-code ng-star-inserted">processingTask</span>`<span class="ng-star-inserted"> is blocked (waiting for data).</span>
5. **<span class="ng-star-inserted">Software Timer (</span>`<span class="inline-code ng-star-inserted">systemHealthCallback</span>`<span class="ng-star-inserted">)</span>**<span class="ng-star-inserted">: This runs completely independently of the main data flow. Every 5 seconds, it prints a status message, demonstrating how you can easily add periodic, non-critical background functions to your application without interfering with the main logic.</span>
6. **`<span class="inline-code ng-star-inserted">setup()</span>`<span class="ng-star-inserted"> Function</span>**<span class="ng-star-inserted">: This is where we initialize all our RTOS objects. We create the queues, create the tasks, configure the hardware timer, and start the software timer.</span>
7. **`<span class="inline-code ng-star-inserted">loop()</span>`<span class="ng-star-inserted"> Function</span>**<span class="ng-star-inserted">: In a complex RTOS application, the main </span>`<span class="inline-code ng-star-inserted">loop()</span>`<span class="ng-star-inserted"> function is often no longer needed. All continuous work is handled by tasks. We can delete the loop task with </span>`<span class="inline-code ng-star-inserted">vTaskDelete(NULL)</span>`<span class="ng-star-inserted"> to free up its stack memory.</span>

### <span class="ng-star-inserted">How to Test It</span>

1. <span class="ng-star-inserted">Upload the code to your ESP32.</span>
2. <span class="ng-star-inserted">Open the Arduino Serial Monitor at 115200 baud.</span>
3. <span class="ng-star-inserted">You should see the following pattern:</span>
    
    
    - <span class="ng-star-inserted">Every 200 milliseconds, the "\[PROCESS\]" message will appear, indicating the high-priority task has received data from the ISR.</span>
    - <span class="ng-star-inserted">Immediately after, the "\[LOG\]" message will print the calculated average, followed by a separator.</span>
    - <span class="ng-star-inserted">Every 5 seconds, a "\[HEALTH\] System OK" message will appear, independently of the other messages.</span>

# Module 6 - Bluetooth & BLE

# 6.1 Introduction to Bluetooth Technology

### What is Bluetooth?
Bluetooth is a global wireless technology standard for exchanging data over short distances. Its primary purpose is to replace the cables connecting electronic devices, allowing for communication in a clean, efficient manner. It operates in the unlicensed Industrial, Scientific, and Medical (ISM) radio frequency band, specifically from 2.402 GHz to 2.480 GHz.

At its core, Bluetooth facilitates the creation of Wireless Personal Area Networks (WPANs). This means it connects devices that are in close proximity to a user, such as a smartphone, wireless headphones, a smartwatch, a keyboard, and a laptop, allowing them to work together seamlessly.

All Bluetooth devices are certified and managed by the Bluetooth Special Interest Group (SIG), a non-profit organization that oversees the development of the standards, manages the licensing of the technology, and ensures that devices from different manufacturers can interoperate correctly.

### The Origin of the Name and Technology
The name "Bluetooth" is an homage to a 10th-century Viking king, Harald "Bluetooth" Gormsson. King Harald was famous for uniting the disparate tribes of Denmark and Norway into a single kingdom. Similarly, the creators of the technology saw it as a way to unite different communication protocols from various devices into one universal standard.

The iconic Bluetooth logo is a combination of two ancient Norse runes, which are the initials of Harald Bluetooth:

- ᚼ (Hagall): The rune for the letter 'H'.
- ᛒ (Bjarkan): The rune for the letter 'B'.

The technology itself was initiated in 1989 at Ericsson Mobile in Sweden. The goal was to develop a low-power, low-cost radio interface for wireless headsets. In 1998, Ericsson, along with Intel, Nokia, and Toshiba, formed the Bluetooth Special Interest Group (SIG) to establish a single, open standard, which has since grown to include tens of thousands of member companies.

# 6.2  Core Specifications and Evolution

Bluetooth technology is not static; it has evolved through numerous versions, each adding new capabilities, increasing speed, and reducing power consumption.

### Bluetooth 1.0 (1999):

- The initial release. It laid the groundwork but had significant issues with interoperability between devices from different manufacturers.
- Data Rate: ~1 Mbps.

### Bluetooth 1.2 (2003):

- **Key Feature**: **Adaptive Frequency Hopping (AFH)**. This was a major step in improving reliability. AFH allows a Bluetooth device to detect which frequencies in the 2.4 GHz band are noisy (e.g., from Wi-Fi or microwave ovens) and avoid them, reducing interference.

### Bluetooth 2.0 + EDR (2004):

- **Key Feature**: **Enhanced Data Rate (EDR)**. This introduced a new modulation scheme that tripled the theoretical data rate to 3 Mbps (with a realistic throughput of about 2.1 Mbps).

### Bluetooth 2.1 + EDR (2007):

- **Key Feature**: **Secure Simple Pairing (SSP)**. This dramatically improved the user experience of connecting devices. It introduced methods like Numeric Comparison, removing the need for users to enter a "0000" or "1234" PIN for most use cases, while also strengthening security against eavesdropping.

### Bluetooth 3.0 + HS (2009):

- **Key Feature**: **High Speed (HS)**. This version introduced a method to transfer large files by using a co-located 802.11 (Wi-Fi) radio for the actual data transfer, while Bluetooth was used for negotiation. It offered theoretical speeds of up to 24 Mbps but saw limited adoption due to its power requirements.

### Bluetooth 4.0 (2010): The Birth of BLE

- **Key Feature**: **Bluetooth Low Energy (BLE)**. This was a revolutionary update. BLE is a completely different protocol stack designed from the ground up for ultra-low-power applications. It allows devices like sensors and wearables to run for months or even years on a small coin-cell battery. Devices with both protocols are called "Dual-Mode."

### Bluetooth 4.1 (2013):

- Focused on the Internet of Things (IoT). It allowed devices to act as both a central and a peripheral simultaneously and improved coexistence with 4G/LTE signals.

### Bluetooth 4.2 (2014):

- Introduced key IoT features, including support for IPv6 (allowing devices to connect directly to the internet) and significant privacy and security upgrades.

### Bluetooth 5.0 (2016): A Major Leap for BLE

- **2x Speed**: Increased the BLE data rate from 1 Mbps to 2 Mbps, enabling faster firmware updates and data transfers.
- **4x Range**: Introduced new physical layer (PHY) options to quadruple the range of BLE connections, enabling whole-home or building-wide coverage.
- **8x Advertising Data**: Increased the size of advertising packets, allowing for richer beacon applications and connectionless data transfer.

### Bluetooth 5.1 (2019):

- **Key Feature**: **Direction Finding**. Introduced **Angle of Arrival (AoA)** and **Angle of Departure (AoD)** methods, enabling high-accuracy, real-time location systems (RTLS) with sub-meter precision.

### Bluetooth 5.2 (2020):

- **Key Feature**: **LE Audio**. The next generation of wireless audio. It introduced the highly efficient LC3 Codec and Isochronous Channels, which are the foundation for new capabilities like Multi-Stream Audio and Auracast™ broadcast audio.

### Bluetooth 5.3 (2021):

- Focused on efficiency and reliability with features like **Connection Subrating** for improved responsiveness at low power, and **Channel Classification Enhancement** to avoid noisy channels.

### Bluetooth 5.4 (2023):

- **Key Feature**: **Periodic Advertising with Responses (PAwR)**. Enables secure, large-scale, bidirectional communication for thousands of low-power IoT devices, such as Electronic **Shelf Labels (ESL)**. Also introduced **Encrypted Advertising Data** for secure broadcasts.

# 6.3 Core Technology Architectures

<span class="ng-star-inserted">Modern Bluetooth is not a single technology but a combination of three distinct architectures designed for different use cases. A device can implement one or more of these.</span>

### <span class="ng-star-inserted">Bluetooth Classic (BR/EDR)</span>

<span class="ng-star-inserted">This is the original Bluetooth protocol, designed for continuous, point-to-point data streaming.</span>

- **<span class="ng-star-inserted">Primary Use Case:</span>**<span class="ng-star-inserted"> Audio streaming and data transfer where throughput is more important than power consumption.</span>
- **<span class="ng-star-inserted">Topology:</span>**<span class="ng-star-inserted"> It forms a </span>**<span class="ng-star-inserted">piconet</span>**<span class="ng-star-inserted">, where a single </span>**<span class="ng-star-inserted">master</span>**<span class="ng-star-inserted"> device can connect to up to seven active </span>**<span class="ng-star-inserted">slave</span>**<span class="ng-star-inserted"> devices. The communication is connection-oriented.</span>
- **<span class="ng-star-inserted">Strengths:</span>**<span class="ng-star-inserted"> High data throughput (up to 3 Mbps) ideal for high-quality audio or file transfers.</span>
- **<span class="ng-star-inserted">Weaknesses:</span>**<span class="ng-star-inserted"> Higher power consumption, making it unsuitable for battery-powered IoT devices.</span>
- **<span class="ng-star-inserted">Example Applications:</span>**<span class="ng-star-inserted"> Wireless headphones, speakers, in-car audio systems, legacy file transfers.</span>

### <span class="ng-star-inserted">Bluetooth Low Energy (BLE)</span>

<span class="ng-star-inserted">BLE was introduced in Bluetooth 4.0 and is the dominant technology for the Internet of Things.</span>

- **<span class="ng-star-inserted">Primary Use Case:</span>**<span class="ng-star-inserted"> Short bursts of data from low-power, battery-operated devices.</span>
- **<span class="ng-star-inserted">Topology:</span>**<span class="ng-star-inserted"> A </span>**<span class="ng-star-inserted">central</span>**<span class="ng-star-inserted"> device (like a smartphone) can connect to many </span>**<span class="ng-star-inserted">peripheral</span>**<span class="ng-star-inserted"> devices (like sensors). It operates by </span>**<span class="ng-star-inserted">advertising</span>**<span class="ng-star-inserted"> its presence and can form fast, temporary connections to transfer data.</span>
- **<span class="ng-star-inserted">Strengths:</span>**<span class="ng-star-inserted"> Extremely low power consumption, allowing for multi-year battery life. Very fast connection setup time.</span>
- **<span class="ng-star-inserted">Weaknesses:</span>**<span class="ng-star-inserted"> Lower data throughput than Classic, not designed for continuous streaming.</span>
- **<span class="ng-star-inserted">Example Applications:</span>**<span class="ng-star-inserted"> Fitness trackers, smartwatches, environmental sensors, proximity beacons, smart home devices.</span>

### <span class="ng-star-inserted">Bluetooth Mesh</span>

<span class="ng-star-inserted">Bluetooth Mesh is not a separate radio technology; it's a networking protocol that operates </span><span class="ng-star-inserted">on top of</span><span class="ng-star-inserted"> the BLE radio.</span>

- **<span class="ng-star-inserted">Primary Use Case:</span>**<span class="ng-star-inserted"> Large-scale device networks requiring reliable, building-wide coverage.</span>
- **<span class="ng-star-inserted">Topology:</span>**<span class="ng-star-inserted"> A true </span>**<span class="ng-star-inserted">mesh network</span>**<span class="ng-star-inserted">. Devices (or </span>**<span class="ng-star-inserted">nodes</span>**<span class="ng-star-inserted">) can relay messages for other nodes, extending the range of the network far beyond the reach of a single device. This creates a many-to-many communication system.</span>
- **<span class="ng-star-inserted">Strengths:</span>**<span class="ng-star-inserted"> Enormous scalability (up to 32,000 nodes), high reliability (no single point of failure), and extended range.</span>
- **<span class="ng-star-inserted">Weaknesses:</span>**<span class="ng-star-inserted"> Higher latency than a direct BLE connection and is not suitable for high-throughput or streaming applications.</span>
- **<span class="ng-star-inserted">Example Applications:</span>**<span class="ng-star-inserted"> Smart lighting systems in commercial buildings, industrial sensor networks for predictive maintenance, asset tracking across a large facility.</span>

### <span class="ng-star-inserted">Key Differences: A Summary</span>

<table border="1" id="bkmrk-feature-bluetooth-cl" style="border-collapse: collapse; width: 100%; height: 271.969px;"><colgroup><col style="width: 25.0298%;"></col><col style="width: 25.0298%;"></col><col style="width: 25.0298%;"></col><col style="width: 25.0298%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">Feature</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">Bluetooth Classic (BR/EDR)</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">Bluetooth Low Energy (BLE)</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">Bluetooth Mesh</span>**</td></tr><tr style="height: 46.5938px;"><td class="ng-star-inserted align-left" style="height: 46.5938px;">**<span class="ng-star-inserted">Primary Use Case</span>**</td><td class="ng-star-inserted align-center" style="height: 46.5938px;"><span class="ng-star-inserted">Audio Streaming, File Transfer</span></td><td class="ng-star-inserted align-center" style="height: 46.5938px;"><span class="ng-star-inserted">IoT Sensors, Wearables, Beacons</span></td><td class="ng-star-inserted align-center" style="height: 46.5938px;"><span class="ng-star-inserted">Large-Scale Control Networks</span></td></tr><tr style="height: 29.7969px;"><td class="ng-star-inserted align-left" style="height: 29.7969px;">**<span class="ng-star-inserted">Throughput</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Medium-High (~2.1 Mbps)</span></td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Low-Medium (~1-2 Mbps)</span></td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Low</span></td></tr><tr style="height: 29.7969px;"><td class="ng-star-inserted align-left" style="height: 29.7969px;">**<span class="ng-star-inserted">Power Consumption</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Medium</span></td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Very Low</span></td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Low (node-dependent)</span></td></tr><tr style="height: 29.7969px;"><td class="ng-star-inserted align-left" style="height: 29.7969px;">**<span class="ng-star-inserted">Topology</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Piconet (Master-Slave)</span></td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Star (Central-Peripheral)</span></td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Mesh (Node-to-Node)</span></td></tr><tr style="height: 29.7969px;"><td class="ng-star-inserted align-left" style="height: 29.7969px;">**<span class="ng-star-inserted">Connection Time</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Slower (~100ms)</span></td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Very Fast (&lt;3ms)</span></td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">N/A (Always on or advertising)</span></td></tr><tr style="height: 46.5938px;"><td class="ng-star-inserted align-left" style="height: 46.5938px;">**<span class="ng-star-inserted">Number of Devices</span>**</td><td class="ng-star-inserted align-center" style="height: 46.5938px;"><span class="ng-star-inserted">1 Master to 7 Slaves</span></td><td class="ng-star-inserted align-center" style="height: 46.5938px;"><span class="ng-star-inserted">1 Central to Many Peripherals</span></td><td class="ng-star-inserted align-center" style="height: 46.5938px;"><span class="ng-star-inserted">Thousands of Nodes in a Network</span></td></tr><tr style="height: 29.7969px;"><td class="ng-star-inserted align-left" style="height: 29.7969px;">**<span class="ng-star-inserted">Example</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Wireless Headphones</span></td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Heart Rate Monitor</span></td><td class="ng-star-inserted align-center" style="height: 29.7969px;"><span class="ng-star-inserted">Smart Building Lighting</span></td></tr></tbody></table>

# 6.4 Bluetooth Audio: From Classic to Auracast™ (Optional)

### <span class="ng-star-inserted">Legacy Audio (Classic Profiles)</span>

<span class="ng-star-inserted">For over two decades, Bluetooth audio has been powered by profiles running on the Bluetooth Classic radio. These profiles are the foundation of the wireless audio market.</span>

- **<span class="ng-star-inserted">A2DP (Advanced Audio Distribution Profile):</span>**<span class="ng-star-inserted"> This is the profile used for high-quality, one-way audio streaming, primarily for music. It defines how stereo audio can be compressed and transmitted from a source (like a smartphone) to a sink (like wireless headphones or speakers). A2DP relies on a mandatory codec called </span>**<span class="ng-star-inserted">SBC (Low Complexity Subband Codec)</span>**<span class="ng-star-inserted">, which provides decent quality but is less efficient than modern alternatives.</span>
- **<span class="ng-star-inserted">HFP (Hands-Free Profile) &amp; HSP (Headset Profile):</span>**<span class="ng-star-inserted"> These profiles are designed for two-way voice communication, such as phone calls. They enable features like answering calls, redialing, and volume control. To support simultaneous input and output, the audio quality is mono and highly compressed, making it unsuitable for music.</span>

<span class="ng-star-inserted">While functional, this legacy audio architecture has limitations: it is relatively power-hungry, the SBC codec is inefficient, and it cannot natively support use cases like True Wireless Stereo earbuds without vendor-specific workarounds.</span>

### <span class="ng-star-inserted">Introduction to LE Audio</span>

<span class="ng-star-inserted">Introduced in the Bluetooth 5.2 specification, </span>**<span class="ng-star-inserted">LE Audio</span>**<span class="ng-star-inserted"> is the next generation of wireless sound, designed to address the limitations of Classic Audio. It is a completely new architecture that operates exclusively on the power-efficient Bluetooth Low Energy (BLE) radio.</span>

<span class="ng-star-inserted">LE Audio brings significant benefits:</span>

- **<span class="ng-star-inserted">Lower Power Consumption:</span>**<span class="ng-star-inserted"> Extends the battery life of audio devices.</span>
- **<span class="ng-star-inserted">Higher Audio Quality &amp; Efficiency:</span>**<span class="ng-star-inserted"> Achieved through a new, mandatory codec.</span>
- **<span class="ng-star-inserted">Multi-Stream Audio:</span>**<span class="ng-star-inserted"> Natively supports transmitting multiple, independent, synchronized audio streams to one or more devices. This is the standardized solution for True Wireless Stereo earbuds, improving performance and reliability.</span>
- **<span class="ng-star-inserted">Broadcast Audio Capabilities:</span>**<span class="ng-star-inserted"> Enables entirely new audio sharing use cases.</span>

### <span class="ng-star-inserted">The LC3 Codec (Low Complexity Communications Codec)</span>

<span class="ng-star-inserted">The cornerstone of LE Audio is the </span>**<span class="ng-star-inserted">LC3 codec</span>**<span class="ng-star-inserted">. It is the new mandatory codec for all LE Audio devices, representing a massive leap in efficiency and flexibility over the classic SBC codec.</span>

<span class="ng-star-inserted">The primary advantage of LC3 is its ability to provide high-quality audio at much lower data rates. This gives developers a choice:</span>

1. **<span class="ng-star-inserted">Deliver Higher Quality:</span>**<span class="ng-star-inserted"> At the same data rate as SBC, LC3 provides a significant and noticeable improvement in audio fidelity.</span>
2. **<span class="ng-star-inserted">Extend Battery Life:</span>**<span class="ng-star-inserted"> LC3 can provide the </span><span class="ng-star-inserted">same</span><span class="ng-star-inserted"> or slightly better audio quality as SBC but at roughly half the data rate. A lower data rate means the radio is active for less time, drastically reducing power consumption.</span>

<span class="ng-star-inserted">This efficiency makes LC3 a superior technology for all wireless audio applications, from high-fidelity headphones to power-constrained hearing aids.</span>

### <span class="ng-star-inserted">Auracast™ Broadcast Audio</span>

**<span class="ng-star-inserted">Auracast™</span>**<span class="ng-star-inserted"> is a revolutionary new capability built on LE Audio that enables a single source device to broadcast audio to an unlimited number of nearby receivers. Think of it as public Wi-Fi, but for audio.</span>

**<span class="ng-star-inserted">How It Works:</span>**

1. <span class="ng-star-inserted">An </span>**<span class="ng-star-inserted">Auracast™ transmitter</span>**<span class="ng-star-inserted"> (e.g., a TV in an airport, a laptop in a lecture hall) broadcasts its audio stream.</span>
2. <span class="ng-star-inserted">Listeners with </span>**<span class="ng-star-inserted">Auracast™ assistants</span>**<span class="ng-star-inserted"> (e.g., smartphones or smartwatches) can scan for these broadcasts in the area.</span>
3. <span class="ng-star-inserted">The assistant presents a list of available Auracast™ streams to the user, who can then select one to join.</span>
4. <span class="ng-star-inserted">The audio is then routed to the user's </span>**<span class="ng-star-inserted">Auracast™ receiver</span>**<span class="ng-star-inserted"> (e.g., earbuds, headphones, or hearing aids).</span>

**<span class="ng-star-inserted">Key Use Cases:</span>**

- **<span class="ng-star-inserted">Share Your Audio:</span>**<span class="ng-star-inserted"> A user can share music from their phone with multiple friends, allowing them to listen to the same playlist on their own headphones.</span>
- **<span class="ng-star-inserted">Listen in Public Spaces:</span>**<span class="ng-star-inserted"> Unmute the silent TVs in public venues like gyms, airport gates, or sports bars by streaming the audio directly to your personal earbuds.</span>
- **<span class="ng-star-inserted">Assistive Listening:</span>**<span class="ng-star-inserted"> In public spaces like theaters, conference centers, or places of worship, Auracast™ can be used to broadcast a high-quality audio feed directly to visitors with compatible hearing aids or headphones.</span>

# 6.5 High-Accuracy Location Services (Optional)

### <span class="ng-star-inserted">Proximity Solutions (Beacons &amp; RSSI)</span>

<span class="ng-star-inserted">The simplest form of Bluetooth location services is based on proximity. This is typically implemented using </span>**<span class="ng-star-inserted">beacons</span>**<span class="ng-star-inserted">, which are small BLE devices that continuously broadcast advertising packets.</span>

<span class="ng-star-inserted">A receiver, such as a smartphone, can listen for these packets and measure the </span>**<span class="ng-star-inserted">Received Signal Strength Indicator (RSSI)</span>**<span class="ng-star-inserted">. RSSI provides a rough estimate of the distance between the receiver and the beacon—a stronger signal generally means a closer device.</span>

<span class="ng-star-inserted">This method is useful for applications like:</span>

- <span class="ng-star-inserted">Triggering a notification when a shopper enters a specific department in a store.</span>
- <span class="ng-star-inserted">Marking attendance when a student enters a classroom.</span>
- <span class="ng-star-inserted">Simple "find my item" trackers.</span>

<span class="ng-star-inserted">However, RSSI is not very accurate. The signal strength can be affected by obstacles (walls, people), device orientation, and environmental interference, making it unsuitable for applications that require precise location data.</span>

### <span class="ng-star-inserted">Direction Finding (AoA &amp; AoD)</span>

<span class="ng-star-inserted">Introduced in Bluetooth 5.1, </span>**<span class="ng-star-inserted">Direction Finding</span>**<span class="ng-star-inserted"> provides a way to determine the precise direction of a Bluetooth signal, enabling Real-Time Location Systems (RTLS) with sub-meter accuracy. It uses two distinct methods:</span>

- **<span class="ng-star-inserted">Angle of Arrival (AoA):</span>**
    
    
    - **<span class="ng-star-inserted">Concept:</span>**<span class="ng-star-inserted"> A mobile device (e.g., a tag on an asset) with a single antenna transmits a special direction-finding signal. A fixed receiver (e.g., a locator mounted on the ceiling) with an array of multiple antennas receives the signal.</span>
    - **<span class="ng-star-inserted">Mechanism:</span>**<span class="ng-star-inserted"> As the radio wave crosses the antenna array, the receiver measures the tiny phase difference of the signal at each individual antenna. This phase difference data is used to calculate the angle from which the signal arrived. By using multiple locators, you can triangulate the exact position of the tag.</span>
- **<span class="ng-star-inserted">Angle of Departure (AoD):</span>**
    
    
    - **<span class="ng-star-inserted">Concept:</span>**<span class="ng-star-inserted"> This method reverses the roles. A fixed transmitter (e.g., a locator) with an antenna array sends signals, and a mobile device (e.g., a smartphone) with a single antenna receives them.</span>
    - **<span class="ng-star-inserted">Mechanism:</span>**<span class="ng-star-inserted"> The transmitter sends the signal sequentially from each antenna in its array. The receiver knows the layout of the transmitter's array and measures the phase difference of the signals as they arrive. This allows it to calculate its own position relative to the transmitter.</span>

<span class="ng-star-inserted">This technology is the foundation for a new class of high-precision services, including indoor navigation, industrial asset tracking, and secure digital key access.</span>

# 6.6 Bluetooth and the Internet of Things (IoT)

### <span class="ng-star-inserted">Bluetooth Mesh Networking in Detail</span>

<span class="ng-star-inserted">Bluetooth Mesh is a software-based networking solution that runs on top of the BLE physical radio. It is designed to support large-scale, many-to-many device communication, making it ideal for smart buildings and industrial IoT.</span>

<span class="ng-star-inserted">Key concepts of a Mesh network include:</span>

- **<span class="ng-star-inserted">Nodes:</span>**<span class="ng-star-inserted"> Any device on the mesh network is a node. Nodes can transmit, receive, and relay messages. This relaying capability (called </span>**<span class="ng-star-inserted">managed flooding</span>**<span class="ng-star-inserted">) is what allows the network to cover a very large area.</span>
- **<span class="ng-star-inserted">Provisioning:</span>**<span class="ng-star-inserted"> The process of securely adding a new device to the mesh network.</span>
- **<span class="ng-star-inserted">Models:</span>**<span class="ng-star-inserted"> Models define the fundamental behaviors of a node. For example, a light bulb might implement a "Generic OnOff Server" model, while a wall switch might implement a "Generic OnOff Client" model.</span>
- **<span class="ng-star-inserted">Publish-Subscribe (Pub/Sub):</span>**<span class="ng-star-inserted"> Mesh uses a pub/sub messaging system. Instead of sending a message to a specific device address, a node </span><span class="ng-star-inserted">publishes</span><span class="ng-star-inserted"> a message to a group address (e.g., "First Floor Lights"). All nodes that have </span><span class="ng-star-inserted">subscribed</span><span class="ng-star-inserted"> to that address will receive and process the message. This is highly efficient for controlling large groups of devices simultaneously.</span>

<span class="ng-star-inserted">The architecture is highly reliable because there is no single point of failure; if one node goes down, messages can automatically find an alternative path through other nodes.</span>

### <span class="ng-star-inserted">Periodic Advertising with Responses (PAwR)</span>

<span class="ng-star-inserted">Introduced in Bluetooth 5.4, </span>**<span class="ng-star-inserted">Periodic Advertising with Responses (PAwR)</span>**<span class="ng-star-inserted"> is a new communication mode designed for large-scale, one-to-many IoT applications that require low-power, bidirectional communication </span><span class="ng-star-inserted">without</span><span class="ng-star-inserted"> forming a connection.</span>

**<span class="ng-star-inserted">How It Works:</span>**  
<span class="ng-star-inserted">A central device (a </span>**<span class="ng-star-inserted">broadcaster</span>**<span class="ng-star-inserted">) sends out small, time-synchronized advertising packets on a predictable schedule. The thousands of listening devices (</span>**<span class="ng-star-inserted">observers</span>**<span class="ng-star-inserted">) are synchronized to this schedule and only wake up for a brief moment to listen for a relevant packet. This saves an enormous amount of power.</span>

<span class="ng-star-inserted">Crucially, after each broadcast event, there are dedicated time slots where the observers can send back a small response. This enables bidirectional communication for acknowledgements, sensor data, or status updates.</span>

### <span class="ng-star-inserted">Use Case: Electronic Shelf Labels (ESL)</span>

<span class="ng-star-inserted">The primary and first officially adopted profile for PAwR is </span>**<span class="ng-star-inserted">Electronic Shelf Labels (ESL)</span>**<span class="ng-star-inserted">. In a retail environment, a single gateway can control and update prices on tens of thousands of e-paper labels throughout the store.</span>

- **<span class="ng-star-inserted">Price Update:</span>**<span class="ng-star-inserted"> The gateway broadcasts price update information. Only the specific ESLs targeted in the broadcast will wake up, receive the new price, and update their display.</span>
- **<span class="ng-star-inserted">Acknowledgement:</span>**<span class="ng-star-inserted"> The ESL can then send a small response back to the gateway in its designated response slot, confirming that the price was successfully updated.</span>
- **<span class="ng-star-inserted">Battery Life:</span>**<span class="ng-star-inserted"> Because the labels are not maintaining a constant connection and only wake for milliseconds at a time, they can run for 5-10 years on a single coin-cell battery.</span>

# 6.7 Bluetooth Security

### <span class="ng-star-inserted">Legacy Pairing vs. LE Secure Connections</span>

<span class="ng-star-inserted">Pairing is the process of creating a trusted relationship between two devices by generating and storing shared secret keys.</span>

- **<span class="ng-star-inserted">Legacy Pairing:</span>**<span class="ng-star-inserted"> Used in Bluetooth versions prior to 4.2. While it provided security, certain association models (like "Just Works") were vulnerable to passive eavesdropping and Man-in-the-Middle (MITM) attacks because they did not authenticate the user or device.</span>
- **<span class="ng-star-inserted">LE Secure Connections:</span>**<span class="ng-star-inserted"> The modern security standard for BLE. It is a significantly more robust pairing method that uses a government-grade cryptographic algorithm called </span>**<span class="ng-star-inserted">Elliptic Curve Diffie-Hellman (ECDH)</span>**<span class="ng-star-inserted"> for key exchange. This algorithm provides a very high level of protection against passive eavesdropping, even if an attacker manages to capture all the pairing packets. LE Secure Connections is the mandatory security foundation for modern BLE devices.</span>

### <span class="ng-star-inserted">Encryption, Privacy, and MITM Protection</span>

<span class="ng-star-inserted">Modern Bluetooth security is built on three core pillars:</span>

1. **<span class="ng-star-inserted">Encryption (Confidentiality):</span>**<span class="ng-star-inserted"> Once devices are paired, the connection can be encrypted. Bluetooth uses the </span>**<span class="ng-star-inserted">AES-CCM</span>**<span class="ng-star-inserted"> algorithm to encrypt all data sent over the link. This ensures that if an attacker were to listen to the radio traffic, they would only see unintelligible encrypted data, not the actual information.</span>
2. **<span class="ng-star-inserted">Privacy (Anti-Tracking):</span>**<span class="ng-star-inserted"> To prevent malicious actors from tracking a user by listening for their device's Bluetooth address, BLE uses </span>**<span class="ng-star-inserted">Resolvable Private Addresses (RPAs)</span>**<span class="ng-star-inserted">. A device with this feature enabled will periodically change its public Bluetooth address to a new, randomized one. Only devices that have previously paired with it possess the key (the IRK - Identity Resolving Key) needed to resolve this random address and identify the device.</span>
3. **<span class="ng-star-inserted">Authentication and MITM Protection:</span>**<span class="ng-star-inserted"> A Man-in-the-Middle (MITM) attack occurs when an attacker secretly sits between two devices and relays their communication, potentially altering it. LE Secure Connections protects against this by authenticating the connection during pairing. This is done using one of several association models:</span>
    
    
    - **<span class="ng-star-inserted">Passkey Entry:</span>**<span class="ng-star-inserted"> The user enters a 6-digit number on both devices.</span>
    - **<span class="ng-star-inserted">Numeric Comparison:</span>**<span class="ng-star-inserted"> A 6-digit number is displayed on both devices, and the user confirms they are the same. This is the most common method for devices with displays.</span>
    - <span class="ng-star-inserted">If a connection is authenticated, the devices have proven they are communicating directly with each other and not an imposter.</span>

### <span class="ng-star-inserted">Security Best Practices for Developers</span>

<span class="ng-star-inserted">For students building Bluetooth applications, security should be a primary concern.</span>

- **<span class="ng-star-inserted">Use LE Secure Connections:</span>**<span class="ng-star-inserted"> Always use the highest security mode available on your platform. Avoid legacy pairing if possible.</span>
- **<span class="ng-star-inserted">Authenticate When Possible:</span>**<span class="ng-star-inserted"> For devices with a display or keyboard, use Numeric Comparison or Passkey Entry to protect against MITM attacks. For devices without a user interface (like a sensor), you must be aware that the connection is unauthenticated.</span>
- **<span class="ng-star-inserted">Enable Privacy:</span>**<span class="ng-star-inserted"> Use Resolvable Private Addresses to prevent your device from being tracked over time.</span>
- **<span class="ng-star-inserted">Validate Data:</span>**<span class="ng-star-inserted"> Do not blindly trust the data received over a BLE link. Always validate it at the application layer to ensure it is in the expected format and range.</span>
- **<span class="ng-star-inserted">Use the Correct Security Level for Characteristics:</span>**<span class="ng-star-inserted"> Define the minimum security level (encryption, authentication) required to read or write specific GATT characteristics. Don't expose sensitive data on an open, unencrypted connection.</span>

# 6.8 The Bluetooth Protocol Stack

<span class="ng-star-inserted">The Bluetooth protocol stack is a software framework that defines how Bluetooth devices communicate. It is structured in layers, where each layer provides services to the layer above it and uses services from the layer below it. The stack is divided into two main components: the </span>**<span class="ng-star-inserted">Controller</span>**<span class="ng-star-inserted"> and the </span>**<span class="ng-star-inserted">Host</span>**<span class="ng-star-inserted">.</span>

### <span class="ng-star-inserted">The Controller</span>

<span class="ng-star-inserted">The Controller is responsible for the low-level radio operations. It handles the transmission and reception of radio signals and manages the physical connection between devices. It is often implemented as a dedicated chip (a "System-on-a-Chip" or SoC) that includes the radio hardware.</span>

- **<span class="ng-star-inserted">Physical Layer (PHY):</span>**<span class="ng-star-inserted"> This is the actual radio hardware that transmits and receives signals in the 2.4 GHz band. Bluetooth 5 introduced multiple PHY options for BLE:</span>
    
    
    - **<span class="ng-star-inserted">LE 1M PHY:</span>**<span class="ng-star-inserted"> The original 1 Mbps PHY, providing a balance of range and speed.</span>
    - **<span class="ng-star-inserted">LE 2M PHY:</span>**<span class="ng-star-inserted"> A 2 Mbps PHY that doubles the speed at the cost of slightly reduced range.</span>
    - **<span class="ng-star-inserted">LE Coded PHY:</span>**<span class="ng-star-inserted"> A long-range PHY that uses error correction to significantly increase range (up to 4x), but with lower data rates.</span>
- **<span class="ng-star-inserted">Link Layer (LL):</span>**<span class="ng-star-inserted"> This is the core of the Controller. It manages the state of the radio (advertising, scanning, initiating, connected) and defines the fundamental device roles in BLE:</span>
    
    
    - **<span class="ng-star-inserted">Advertiser/Broadcaster:</span>**<span class="ng-star-inserted"> A device sending out advertising packets.</span>
    - **<span class="ng-star-inserted">Scanner/Observer:</span>**<span class="ng-star-inserted"> A device listening for advertising packets.</span>
    - **<span class="ng-star-inserted">Master/Central:</span>**<span class="ng-star-inserted"> A device that initiates and manages a connection.</span>
    - **<span class="ng-star-inserted">Slave/Peripheral:</span>**<span class="ng-star-inserted"> A device that accepts a connection request.</span>

### <span class="ng-star-inserted">The Host</span>

<span class="ng-star-inserted">The Host is responsible for the high-level logic, data organization, and application functionality. It typically runs on the main processor of a device (e.g., in your ESP32 code).</span>

- **<span class="ng-star-inserted">Host-Controller Interface (HCI):</span>**<span class="ng-star-inserted"> A standardized protocol that allows the Host and Controller to communicate. This standard interface means a Host from one manufacturer can work with a Controller from another.</span>
- **<span class="ng-star-inserted">Logical Link Control and Adaptation Protocol (L2CAP):</span>**<span class="ng-star-inserted"> This layer acts as a multiplexer. It takes data from the upper layers and prepares it for transmission by the Link Layer.</span>
- **<span class="ng-star-inserted">Security Manager (SM):</span>**<span class="ng-star-inserted"> Manages the entire security process, including pairing, key distribution, and encryption.</span>
- **<span class="ng-star-inserted">Attribute Protocol (ATT):</span>**<span class="ng-star-inserted"> Defines a simple client-server protocol for data exchange. The server holds a set of data called "attributes," and the client can read or write these attributes.</span>
- **<span class="ng-star-inserted">Generic Attribute Profile (GATT):</span>**<span class="ng-star-inserted"> This is the most critical layer for application developers. GATT provides a structured way to organize and exchange data based on the ATT protocol. It defines the hierarchy of data:</span>
    
    
    - **<span class="ng-star-inserted">Profile:</span>**<span class="ng-star-inserted"> A collection of services for a specific use case (e.g., a "Heart Rate Profile").</span>
    - **<span class="ng-star-inserted">Service:</span>**<span class="ng-star-inserted"> A collection of related data points, identified by a unique number called a </span>**<span class="ng-star-inserted">UUID</span>**<span class="ng-star-inserted">. A service can be official (e.g., "Heart Rate Service") or custom.</span>
    - **<span class="ng-star-inserted">Characteristic:</span>**<span class="ng-star-inserted"> A single data point or value, also identified by a UUID (e.g., "Heart Rate Measurement"). This is what your application will read from or write to.</span>
    - **<span class="ng-star-inserted">Descriptor:</span>**<span class="ng-star-inserted"> Provides additional information about a characteristic.</span>
- **<span class="ng-star-inserted">Generic Access Profile (GAP):</span>**<span class="ng-star-inserted"> This profile defines how devices interact with the outside world. GAP is responsible for:</span>
    
    
    - **<span class="ng-star-inserted">Device Discovery:</span>**<span class="ng-star-inserted"> How a device makes itself known (advertising) and finds other devices (scanning).</span>
    - **<span class="ng-star-inserted">Connection Management:</span>**<span class="ng-star-inserted"> How connections are established and terminated.</span>
    - **<span class="ng-star-inserted">Security:</span>**<span class="ng-star-inserted"> Defining the security model for a device.</span>

# 6.9 Practical Implementation with ESP32

<span class="ng-star-inserted">This chapter provides a hands-on project to demonstrate the core concepts of a BLE peripheral device using an ESP32. We will move beyond a simple serial example and create a simulated </span>**<span class="ng-star-inserted">BLE Heart Rate Sensor</span>**<span class="ng-star-inserted">. This is a standard profile that teaches the essential concepts of services, characteristics, and notifications.</span>

### <span class="ng-star-inserted">Project: Create a BLE Heart Rate Sensor</span>

**<span class="ng-star-inserted">Goal:</span>**<span class="ng-star-inserted"> Configure the ESP32 to act as a BLE peripheral that advertises the standard Heart Rate service. When a central device (like a smartphone) connects and enables notifications, the ESP32 will periodically send a simulated heart rate measurement.</span>

**<span class="ng-star-inserted">You Will Need:</span>**

- <span class="ng-star-inserted">An ESP32 development board.</span>
- <span class="ng-star-inserted">The Arduino IDE with the ESP32 board package installed.</span>
- <span class="ng-star-inserted">A smartphone with a BLE scanner app (e.g., "nRF Connect for Mobile" or "LightBlue").</span>

```c
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

// Standard Bluetooth Service and Characteristic UUIDs for Heart Rate
#define SERVICE_UUID        "0000180d-0000-1000-8000-00805f9b34fb" // Heart Rate Service
#define CHARACTERISTIC_UUID "00002a37-0000-1000-8000-00805f9b34fb" // Heart Rate Measurement

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;

// This class handles server events like client connect/disconnect
class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
      Serial.println("Client Connected");
    }

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
      Serial.println("Client Disconnected");
    }
};

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE Heart Rate Sensor...");

  // 1. Initialize the BLE device and set its name
  BLEDevice::init("ESP32 Heart Rate Sensor");

  // 2. Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks()); // Set the event handler

  // 3. Create the BLE Service using the standard Heart Rate UUID
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // 4. Create a BLE Characteristic for the Heart Rate Measurement
  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ |
                      BLECharacteristic::PROPERTY_NOTIFY
                    );

  // 5. Add a 2902 descriptor to the characteristic. This is ESSENTIAL
  // for the client to be able to enable notifications.
  pCharacteristic->addDescriptor(new BLE2902());

  // 6. Start the service
  pService->start();

  // 7. Start advertising, so other BLE devices can find this one
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID); // Advertise our service
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
  
  Serial.println("Characteristic defined! Now you can scan for 'ESP32 Heart Rate Sensor' on your phone.");
}

void loop() {
  // Check if a client is connected
  if (deviceConnected) {
    // Generate a simulated heart rate value
    // The first byte is a flag (0), the second is the 8-bit heart rate value
    static uint8_t heartRate = 60;
    heartRate++;
    if (heartRate > 100) {
      heartRate = 60; // Reset after 100
    }
    
    uint8_t heartRateData[2] = {0, heartRate};
    
    // Set the characteristic's new value
    pCharacteristic->setValue(heartRateData, 2);
    
    // Send a notification to the connected client
    pCharacteristic->notify();
    
    Serial.print("Heart Rate Notification Sent: ");
    Serial.println(heartRate);
  }
  delay(1000);
}
```

### <span class="ng-star-inserted">Code Walkthrough</span>

1. **<span class="ng-star-inserted">Initialization:</span>**<span class="ng-star-inserted"> We initialize the BLE stack using </span>`<span class="inline-code ng-star-inserted">BLEDevice::init()</span>`<span class="ng-star-inserted"> and give our device a public name.</span>
2. **<span class="ng-star-inserted">Server and Service:</span>**<span class="ng-star-inserted"> We create a </span>`<span class="inline-code ng-star-inserted">BLEServer</span>`<span class="ng-star-inserted"> to manage connections and a </span>`<span class="inline-code ng-star-inserted">BLEService</span>`<span class="ng-star-inserted"> to hold our data. We use the official UUID for the "Heart Rate Service."</span>
3. **<span class="ng-star-inserted">Characteristic:</span>**<span class="ng-star-inserted"> Inside the service, we create a </span>`<span class="inline-code ng-star-inserted">BLECharacteristic</span>`<span class="ng-star-inserted"> for the "Heart Rate Measurement." We set its properties to allow a client to both </span><span class="inline-code ng-star-inserted">READ</span><span class="ng-star-inserted"> the value and subscribe to </span><span class="inline-code ng-star-inserted">NOTIFY</span><span class="ng-star-inserted"> (notifications).</span>
4. **<span class="ng-star-inserted">Descriptor (BLE2902):</span>**<span class="ng-star-inserted"> This is a critical step. The </span><span class="inline-code ng-star-inserted">BLE2902</span><span class="ng-star-inserted"> descriptor is the Client Characteristic Configuration Descriptor (CCCD). A client (your phone) writes to this descriptor to tell the server (the ESP32) that it wants to receive notifications. Without this, notifications will not work.</span>
5. **<span class="ng-star-inserted">Advertising:</span>**<span class="ng-star-inserted"> We start advertising and include the Service UUID. This tells scanning devices what services we offer before they even connect.</span>
6. **<span class="ng-star-inserted">The Loop:</span>**<span class="ng-star-inserted"> In the main loop, we check if a client is connected. If so, we generate a new simulated heart rate value, update the characteristic with </span>`<span class="inline-code ng-star-inserted">setValue()</span>`<span class="ng-star-inserted">, and then send it to the client using </span>`<span class="inline-code ng-star-inserted">notify()</span>`<span class="ng-star-inserted">.</span>

### <span class="ng-star-inserted">How to Test It</span>

1. <span class="ng-star-inserted">Upload the code to your ESP32.</span>
2. <span class="ng-star-inserted">Open the Arduino Serial Monitor to see the status messages.</span>
3. <span class="ng-star-inserted">On your smartphone, open a BLE scanner app (like nRF Connect for Mobile).</span>
4. **<span class="ng-star-inserted">Scan</span>**<span class="ng-star-inserted"> for devices. You should see "ESP32 Heart Rate Sensor" in the list.</span>
5. **<span class="ng-star-inserted">Connect</span>**<span class="ng-star-inserted"> to the device. In the Serial Monitor, you should see "Client Connected."</span>
6. <span class="ng-star-inserted">Find the </span>**<span class="ng-star-inserted">Heart Rate Service</span>**<span class="ng-star-inserted"> and expand it to see the </span>**<span class="ng-star-inserted">Heart Rate Measurement</span>**<span class="ng-star-inserted"> characteristic.</span>
7. <span class="ng-star-inserted">Tap the "subscribe" or "enable notifications" icon (often a single or triple downward arrow).</span>
8. <span class="ng-star-inserted">You should now see the value updating in your app every second, and the Serial Monitor will show the "Notification Sent" logs.</span>

# 6.10 Real-World Applications and The Future

### <span class="ng-star-inserted">Modern Case Studies</span>

<span class="ng-star-inserted">Bluetooth is now a foundational technology in nearly every major tech domain:</span>

- **<span class="ng-star-inserted">Wearables and Personal Health:</span>**<span class="ng-star-inserted"> This is a classic BLE use case. Devices like fitness trackers, smartwatches, and Continuous Glucose Monitors (CGMs) rely on BLE's ultra-low power consumption to run for days or weeks while constantly connected to a smartphone.</span>
- **<span class="ng-star-inserted">Automotive:</span>**<span class="ng-star-inserted"> Modern cars use Bluetooth for more than just hands-free calls. </span>**<span class="ng-star-inserted">Digital Key</span>**<span class="ng-star-inserted"> solutions use BLE Direction Finding to allow a car to be unlocked and started securely with a smartphone, with the precision to know if the phone is inside or outside the vehicle.</span>
- **<span class="ng-star-inserted">Smart Home:</span>**<span class="ng-star-inserted"> Bluetooth is used in two ways in the smart home. BLE is used for direct device control (e.g., configuring a smart light bulb from your phone). </span>**<span class="ng-star-inserted">Bluetooth Mesh</span>**<span class="ng-star-inserted"> is used for whole-home automation, allowing a single command from a light switch or sensor to reliably control lights and devices across the entire house.</span>
- **<span class="ng-star-inserted">Industrial and Commercial:</span>**<span class="ng-star-inserted"> </span>**<span class="ng-star-inserted">Real-Time Location Systems (RTLS)</span>**<span class="ng-star-inserted"> use Bluetooth Direction Finding to track thousands of assets and personnel in warehouses, factories, and hospitals with sub-meter accuracy. </span>**<span class="ng-star-inserted">Electronic Shelf Labels (ESL)</span>**<span class="ng-star-inserted"> in retail stores use the new PAwR feature to update prices and receive acknowledgements from thousands of battery-powered labels.</span>

### <span class="ng-star-inserted">The Future of Bluetooth</span>

<span class="ng-star-inserted">The evolution of Bluetooth is ongoing, driven by the needs of emerging markets. Key areas of future development include:</span>

- **<span class="ng-star-inserted">Higher Data Throughput:</span>**<span class="ng-star-inserted"> The Bluetooth SIG is actively working on future specifications to increase the raw data rates of the BLE radio. This could enable new use cases like high-fidelity wireless audio over LE Audio and faster, large-scale firmware updates for IoT fleets.</span>
- **<span class="ng-star-inserted">Enhanced Location Services:</span>**<span class="ng-star-inserted"> The accuracy and capabilities of Direction Finding will continue to improve, likely adding features for height/elevation detection and becoming even more power-efficient and secure, further solidifying Bluetooth's role in the RTLS market.</span>
- **<span class="ng-star-inserted">AI and Machine Learning at the Edge:</span>**<span class="ng-star-inserted"> As low-power microcontrollers become more powerful, Bluetooth will be the key communication link for edge devices that gather sensor data (e.g., motion, vibration, audio) and run local machine learning models for tasks like predictive maintenance or keyword detection, only sending important results to the cloud.</span>
- **<span class="ng-star-inserted">Continued Expansion in IoT:</span>**<span class="ng-star-inserted"> Bluetooth will continue to push into new IoT verticals, with standardized models and profiles being developed for an even wider range of applications, ensuring interoperability and accelerating market growth.</span>

# Module 7 - MQTT, HTTP, WIFI

# 7.1 Introduction: The IoT Communication Stack

For an IoT device to be useful, it needs to communicate. This communication happens in layers, much like a conversation. You need to have connectivity, then you need a common language to request things (web communication), and sometimes a specialized shorthand (messaging).
- **Connectivity (Wi-Fi)**: Wi-Fi allows your ESP32 to connect to a local network, giving it a path to the internet or other local devices. This module will cover how to configure the ESP32 as both a client that connects to a router and as an access point that creates its own network.
- **Web Communication (HTTP/HTTPS)**: HTTP allows your device to request data from and send data to web servers and APIs (Application Programming Interfaces). This is essential for tasks like fetching weather data, logging information to a cloud database, or being controlled by a web dashboard. HTTPS is the secure version, encrypting the conversation to protect your data.
- **Messaging (MQTT/MQTTS)**: While HTTP is powerful, it can be inefficient for the constant, small data packets typical of IoT sensors. MQTT is a lightweight, efficient protocol designed specifically for this purpose. It uses a publish/subscribe model, which is perfect for real-time telemetry and control. MQTTS is its secure counterpart.

# 7.2 Local Network Connectivity with Wi-Fi

Wi-Fi is a technology based on the **IEEE 802.11 standards** that enables wireless data exchange. The ESP32 supports common standards like 802.11b, 802.11g, and 802.11n, operating in the 2.4 GHz frequency band.

##### ESP32 Wi-Fi Modes
1. **Station Mode (STA)**: The ESP32 acts as a client, connecting to an existing access point (AP) like your home or university router. This is the most common mode for devices that need internet access.
2. **Access Point Mode (AP)**: The ESP32 creates its own Wi-Fi network, allowing other devices (like your phone or laptop) to connect directly to it. This is useful for initial device configuration or creating isolated local networks.
3. **STA + AP Mode**: The ESP32 can do both simultaneously, connecting to one network while also providing its own. This allows it to act as a range extender or a bridge between networks.

##### Example
```cpp
#include <WiFi.h>

// --- Replace with your network credentials ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// -----------------------------------------

void setup() {
  Serial.begin(115200); // Allow serial to initialize

  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  // Start the Wi-Fi connection
  WiFi.begin(ssid, password);

  // Wait for the connection to complete
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected successfully!");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  // The main work is done in setup for this example.
  // In a real application, you would do your networking tasks here.
  delay(10000);
}
```

# 7.3 Web Communication with HTTP/HTTPS

##### HTTP
HTTP (Hypertext Transfer Protocol) is the foundation of data communication on the World Wide Web. It operates on a request-response model.
- Client (ESP32): The client make a request to the server for a resource, like a webpage or data.
- Server (A Web Server): The server takes your request, process it, and send the processed request back to you as a response.

##### HTTPS
HTTPS is simply HTTP Secure. It adds a layer of SSL/TLS encryption to the conversation. This prevents anyone from eavesdropping on the data exchanged between the client and server, which is crucial for protecting sensitive information like passwords or personal data.

##### Example
```cpp
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// --- Replace with your network credentials ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// -----------------------------------------

// The URL of the API we want to request data from
const char* api_url = "http://jsonplaceholder.typicode.com/posts/1";

void setup() {
  Serial.begin(115200);
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected!");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  // Check if we are connected to WiFi before proceeding
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;

    Serial.println("Making HTTP GET request...");
    http.begin(api_url); // Initialize the HTTP request
    
    int httpCode = http.GET(); // Send the GET request

    if (httpCode > 0) { // Check if the request was successful
      Serial.printf("HTTP Response code: %d\n", httpCode);

      if (httpCode == HTTP_CODE_OK) {
        String payload = http.getString(); // Get the response payload as a string
        
        // --- Parse the JSON response ---
        JsonDocument doc;
        DeserializationError error = deserializeJson(doc, payload);

        if (error) {
          Serial.print("deserializeJson() failed: ");
          Serial.println(error.c_str());
        }
        else {
          // Extract the value associated with the "id" and "title" key
        
          int postId = doc["id"];
          const char* title = doc["title"];
  
          Serial.printf("Post ID: %d\n", postId);
          Serial.printf("Title: %s\n", title);
        }
      }
    } else {
      Serial.printf("HTTP GET request failed, error: %s\n", http.errorToString(httpCode).c_str());
    }

    http.end(); // Free resources
  } else {
    Serial.println("WiFi not connected.");
  }

  // Wait 30 seconds before the next request
  delay(30000); 
}
```

# 7.4 Efficient IoT Messaging with MQTT

MQTT (Message Queuing Telemetry Transport) is a lightweight messaging protocol designed for constrained devices and unreliable networks, making it perfect for IoT. Instead of the request-response model, MQTT uses a publish/subscribe (pub/sub) model.

##### MQTT Components
- **Broker**: A central server that acts as a intermediary, receiving messages from publishers and distributing them to interested subscribers
- **Client (Publisher)**: A device (like an ESP32 with a sensor) that publishes messages (e.g., temperature readings) to a specific "topic" on the broker. It doesn't know or care who reads the message.
- **Client (Subscriber)**: Another device or application (like a mobile app or a server) that subscribes to that same topic. The broker automatically forwards any message published to that topic to all subscribers.

##### MQTT Concepts
- **Topic**: A hierarchical string that acts as a channel for messages (e.g., home/livingroom/lamp).
- **Quality of Service (QoS)**:
  - **QoS 0 (At most once)**: The message is sent once ("fire and forget"). It's fast but offers no delivery confirmation.
  - **QoS 1 (At least once)**: The message is guaranteed to be delivered, but it might arrive more than once.
  - **QoS 2 (Exactly once)**: The most reliable level, guaranteeing the message is delivered exactly one time. It is the slowest due to a more complex handshake.

##### MQTTS
Just like HTTPS, MQTTS is MQTT secured with TLS encryption to protect data confidentiality and integrity.

##### Example
```cpp
#include <WiFi.h>
#include <PubSubClient.h>

// --- Replace with your network credentials ---
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// -----------------------------------------

// --- MQTT Broker Configuration ---
const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;

// --- Topics ---
// Topic to publish messages to
const char* publish_topic = "Digilab/Modul7/status"; 
// Topic to subscribe to for incoming messages
const char* subscribe_topic = "Digilab/Modul7/command"; 

WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);

// This function is called whenever a message arrives on a subscribed topic
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  String message;
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  Serial.println(message);

  // Example: Turn on a built-in LED if the message is "ON"
  if (message == "ON") {
    Serial.println("Turning on lamp");
    digitalWrite(LED_BUILTIN, HIGH);
  } else if (message == "OFF") {
    Serial.println("Turning off lamp");
    digitalWrite(LED_BUILTIN, LOW);
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!mqttClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP32Client-";
    clientId += String(random(0xffff), HEX);
    
    // Attempt to connect
    if (mqttClient.connect(clientId.c_str())) {
      Serial.println("connected");
      // Subscribe to the command topic upon connection
      mqttClient.subscribe(subscribe_topic);
    } else {
      Serial.print("failed, rc=");
      Serial.print(mqttClient.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}


void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected!");
  
  // Configure MQTT client
  mqttClient.setServer(mqtt_server, mqtt_port); 
  mqttClient.setCallback(callback);
}

void loop() {
  // Ensure the MQTT client is connected
  if (!mqttClient.connected()) {
    reconnect();
  }
  mqttClient.loop(); // This allows the client to process incoming messages

  // --- Publish a message every 10 seconds ---
  static unsigned long lastMsg = 0;
  unsigned long now = millis();
  if (now - lastMsg > 10000) {
    lastMsg = now;
    
    char msg[50];
    snprintf(msg, 50, "Uptime: %lu seconds", millis() / 1000);
    
    Serial.printf("Publishing message to %s: %s\n", publish_topic, msg);
    mqttClient.publish(publish_topic, msg);
  }
}
```

# Module 8 - Power Management

# Tujuan Pembelajaran

Setelah menyelesaikan modul ini, praktikan diharapkan mampu:
- Memahami tingkat konsumsi daya pada ESP32 berdasarkan proses yang dijalankan di dalamnya.
- Mengetahui metode-metode yang ada untuk mengurangi jumlah konsumsi daya ESP32.
- Mengetahui berbagai konfigurasi ESP32 berdasarkan tingkat konsumsi dayanya serta menentukan mode penggunaan yang paling cocok dengan kebutuhan aplikasi.

# Konsumsi Daya pada ESP-32

Sebagai mikrokontroler, ESP-32 memerlukan pasokan daya yang stabil agar dapat beroperasi dengan optimal. Daya tersebut digunakan oleh core prosesor untuk menjalankan berbagai, serta oleh komponen pendukung seperti I²C, Wi-Fi, dan Bluetooth untuk melakukan komunikasi.

Apabila ESP32 terhubung secara langsung dan terus-menerus ke sumber daya seperti USB power, kebutuhan daya ini umumnya tidak menjadi masalah. Namun, ketika ESP32 digunakan dengan sumber daya terbatas seperti baterai ataupun power bank dan diharapkan dapat beroperasi secara remote dalam jangka waktu lama, maka efisiensi konsumsi daya menjadi faktor penting agar perangkat dapat berfungsi selama mungkin.

## Besaran Konsumsi Daya ESP-32
Konsumsi daya ESP-32 berkisar dari rentang ratusan mA hingga satuan μA. Besaran ini bergantung pada jenis mode kerja yang digunakan oleh ESP-32. 

Detail dari jenis mode kerja beserta besaran konsumsi daya ESP-32 dapat dilihat pada tabel [berikut](https://documentation.espressif.com/esp32_datasheet_en.pdf):
![image](https://hackmd.io/_uploads/HyY6kpnAex.png)
Pembahasan mendetail mengenai setiap jenis power mode akan dilakukan pada subbab selanjutnya.

## Pengukuran Daya ESP-32
Pembacaan konsumsi daya pada ESP32 ditunjukkan melalui besaran arus yang diterima yang memiliki skala kecil, sehingga diperlukan ampermeter dengan tingkat akurasi tinggi. Hal ini dikarenakan ESP32 memiliki rentang arus yang sangat dinamis pada mode aktif, arus dapat mencapai ratusan miliampere, sedangkan pada mode deep sleep, arus dapat turun hingga beberapa mikroampere saja. 

Ampermeter umum seperti yang digunakan pada multimeter tidak mampu merespons perubahan arus yang terjadi dengan sangat cepat ini. Alat tersebut hanya akan menampilkan nilai rata-rata, bukan fluktuasi aktual yang mencerminkan perbedaan konsumsi daya antar mode. Selain itu, multimeter konvensional memiliki resistansi internal yang relatif tinggi, yang dapat menyebabkan penurunan tegangan yang menyebabkan ketidakstabilan ESP-32.

Perlu diketahui bahwa jika pengukuran daya dilakukan pada development board seperti ESP32 Doit Devkit, pembacaan tidak akan merepresentasikan mikrokontroler dikarenakan development board sudah dilengkapi komponen-komponen lainnya seperti voltage regulator dan LED yang tetap akan mengonsumsi daya meskipun ESP-32 dikonfigurasikan dalam mode deep sleep. Informasi lebih lanjut mengenai pengukuran arus dapat ditemukan pada [dokumentasi berikut](https://https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/current-consumption-measurement-modules) dan [demonstrasi dalam video berikut](https://https://www.youtube.com/watch?v=JFDiqPHw3Vc).

# Metode Mengurangi Konsumsi Daya ESP-32 : Mengurangi Clock Speed CPU

Terdapat beberapa faktor yang mempengaruhi tingkat konsumsi daya ESP-32, diantaranya sebagai berikut:

## Mengurangi Clock Speed CPU 

Menurunkan clockspeed (kecepatan clock) pada ESP32 dapat secara langsung menurunkan konsumsi daya karena frekuensi clock berpengaruh terhadap jumlah siklus kerja prosesor per detik. Semakin rendah clockspeed, semakin sedikit jumlah operasi yang dilakukan dalam satu waktu, sehingga arus yang digunakan juga berkurang.

Penggantian frequensi dari CPU hanya dapat dilakukan ke beberapa fixed value, diantaranya 

```
240, 160, 80    <<< For all XTAL types
40, 20, 10      <<< For 40MHz XTAL
26, 13          <<< For 26MHz XTAL
24, 12          <<< For 24MHz XTAL
```
XTAL dalam sini merupakan crystal oscillator yang digunakan oleh mikrokontroler. Untuk ESP32, umumnya XTAL 40Mhz digunakan sehingga ESP32 juga mendukung frekuensi 40, 20, dan 10.

Terdapat dua cara penggantian frekuensi CPU, yaitu sebagai berikut:

1.Mengganti langsung melalui Arduino IDE

Pada bagian tools > CPU Frequency > dengan tampilan berikut 

![image](https://hackmd.io/_uploads/B1-qsrNyWg.png)

Dari menu ini, dapat dilakukan pemilihan nilai frekuensi yang diinginkan.

2. Menggunakan Function setCpuFrequencyMhz()

Nilai-nilai yang serupa dapat dipilih dan diganti menggunakan function berikut
```cpp
bool setCpuFrequencyMhz(uint32_t cpu_freq_mhz);

//Contoh Penggunaan:

setCpuFrequencyMhz(80);
//Kode diatas akan mengubah clockspeed menjadi 80MHz
```

*Berdasarkan gambar setting pada Arduino IDE, dapat dilihat bahwa (WiFi/BT) hanya terdapat pada rentang frekuensi yang lebih besar atau sama dengan 80MHz, ini artinya modul WiFi atau bluetooth dari ESP32 tidak dapat digunakan pada rentang frekuensi lebih kecil dari 80MHz*

Cara ESP32 membentuk frekuensi 80, 160, dan 240 MHz adalah dengan memanfaatkan PLL (Phased Locked Loop) yang cara kerja nya dapat dipelajari melalui [video ini](https://www.youtube.com/watch?v=Ab0sLGI5UTw).

# Metode Mengurangi Konsumsi Daya ESP-32 : Mengganti Operating Mode

Seperti pada tabel sebelumnya, terdapat bebeerapa operating mode yang didukung oleh ESP-32, diantaranya sebagai berikut:
## Deep Sleep
Mode Deep Sleep merupakan mode daya sangat rendah di mana hampir seluruh sistem pada ESP32 dimatikan, sehingga menghasilkan konsumsi energi yang sangat kecil.

Dalam mode ini, ESP32 hanya mengonsumsi arus sebesar beberapa mikroampere, menjadikannya sangat ideal untuk aplikasi yang menggunakan daya baterai atau sistem dengan pasokan energi terbatas.

Saat berada dalam Deep Sleep, ESP32 berhenti mengeksekusi program dan masuk ke kondisi suspended. Sebagian besar sirkuit internal mikrokontroler akan dimatikan, kecuali beberapa komponen penting seperti Real-Time Clock (RTC) yang tetap aktif untuk menjaga waktu dan mendeteksi *wakeup source*.

*Bila wakeup source juga dimatikan sehingga ESP hanya bekerja berdasarkan RTC, maka ESP32 memasuki mode Hibernation yang lebih hemat daya namun terbatas, silahkan pelajari perbedaannya pada [link berikut](https://mischianti.org/esp32-practical-power-saving-deep-sleep-and-hibernation-3/)*

Ketika ESP32 keluar dari mode Deep Sleep, program akan dijalankan kembali dari awal seperti saat perangkat baru dinyalakan. Semua variabel yang disimpan di memori biasa akan hilang, karena daya pada bagian tersebut telah dimatikan. Dengan kata lain, proses ini mirip dengan melakukan reboot pada ESP32.

Penggunaan deep sleep hanya dapat dilakukan bila dilakukan import header berikut
```#include <esp_sleep.h>```
Sedangkan syntax penggunaan deep sleep adalah sebagai berikut
```cpp
esp_sleep_enable_timer_wakeup(5 * 1000000);
esp_deep_sleep_start();
```

### Menggunakan RTC Memory untuk Menyimpan State antar Sleep
Karena RTC Memory tetap dijalankan dalam kondisi sleep, ia dapat digunakan untuk menyimpan state state sederhana yang tetap bertahan antar state boot, seperti penggunaan counter untuk menghitung berapa kali kita memasuki kondisi deep sleep dibawah ini.
```cpp
RTC_DATA_ATTR int bootCount = 0;

void setup()
{
  Serial.begin(115200);
  Serial.println("Starting...");
   
  bootCount++;
  Serial.println(bootCount);
  esp_deep_sleep(2 * 1000000);  // enter deep sleep for 10 seconds
  
  // This function will never execute due to Deep Sleep mode
}

void loop()
{
  // This function will never execute due to Deep Sleep mode
}
```

## Light Sleep
Mode Light Sleep merupakan salah satu mode konsumsi daya rendah yang tersedia pada ESP32, dan bekerja seperti mode “suspend” pada komputer.Dalam mode ini, ESP32 mengonsumsi daya kurang dari 1 mA yaitu sekitar 800 µA pada ESP32.

Pada kondisi ini, CPU, RAM, dan periferal digital akan diputus dari sumber clock dan tegangan kerjanya diturunkan. Ketika terputus dari clock, komponen-komponen tersebut berhenti berfungsi sementara, namun tetap mendapatkan daya dan mempertahankan statusnya sehingga dapat kembali aktif dengan cepat saat dibangunkan.

Karena mempertahankan seluruh nilai variable dan posisi program counter, light sleep dapat berperan sebagai delay, dengan syarat penggunaan ESP32 tidak memanfaatkan WiFi atau Bluetooth.

Penggunaan light sleep dapat dilakukan dengan syntax berikut.

```cpp
esp_sleep_enable_timer_wakeup(2 * 1000000); //light sleep for 2 seconds
esp_light_sleep_start();
```

## Wakeup Source
Parameter yang dipassing kedalam function esp_sleep_enable_timer_wakeup() adalah *Wake Up Source* berupa waktu dalam hal ini 2 × 10^6 µs mikroseconds atau 2 second.

Terdapat beberapa jenis wake up source yang dapat digunakan pada kondisi sleep di ESP32, diantaranya:

### Yang didukung di Deep Sleep dan Light Sleep
- Timer
- Touchpad (TouchPin)
- ULP Coprocessor Wakeup
- External Wakeup (Ext0 and Ext1)

### Yang hanya didukung oleh Light Sleep
- GPIO Wakeup
- UART Wakeup
- WIFI Wakeup

Syntax penggunaan tiap mode wakeup adalah sebagai berikut:
```cpp
// Light Sleep and Deep Sleep
esp_err_t esp_sleep_enable_timer_wakeup(uint64_t time_in_us);
esp_err_t esp_sleep_enable_touchpad_wakeup(void);
esp_err_t esp_sleep_enable_ulp_wakeup(void);
esp_err_t esp_sleep_enable_ext0_wakeup(gpio_num_t gpio_num, int level);
esp_err_t esp_sleep_enable_ext1_wakeup(uint64_t mask, esp_sleep_ext1_wakeup_mode_t mode);

// Only in Light Sleep
esp_err_t esp_sleep_enable_gpio_wakeup(void);
esp_err_t esp_sleep_enable_uart_wakeup(int uart_num);
esp_err_t esp_sleep_enable_wifi_wakeup(void);
void esp_sleep_enable_gpio_switch(bool enable);
```
Informasi lebih lanjut mengenai wakeup modes dapat dibaca melalui [link berikut](https://www.luisllamas.es/en/esp32-sleep-modes/).

## Modem Sleep
Mode Modem Sleep pada ESP32 sering kali menimbulkan kebingungan karena penjelasannya berbeda-beda di berbagai sumber.

Beberapa blog dan pengguna komunitas menyebutkan bahwa Modem Sleep adalah mode di mana WiFi berada dalam keadaan tidur (sleep mode), sementara Bluetooth tetap aktif atau tidak digunakan sama sekali. Namun, menurut dokumentasi resmi dari Espressif, Modem Sleep memang secara khusus mengacu pada WiFi Sleep Mode, yaitu kondisi ketika radio WiFi dimatikan sementara CPU masih tetap berjalan untuk menjalankan tugas lain.

Sedangkan terdapat dua definisi untuk modem sleep. Kita akan membahas implementasi kedua definisi ini.

### Definisi 1 
```cpp
#include <WiFi.h>
#include <BluetoothSerial.h>
#include "driver/adc.h"
#include <esp_bt.h>
 
#define STA_SSID "<YOUR-SSID>"
#define STA_PASS "<YOUR-PASSWD>"
 
BluetoothSerial SerialBT;
 
void setModemSleep();
void wakeModemSleep();
 
void setup() {
    Serial2.begin(115200);
 
    while(!Serial2){delay(500);}
 
    SerialBT.begin("ESP32test"); //Bluetooth device name
    SerialBT.println("START BT");
 
    Serial2.println("START WIFI");
    WiFi.begin(STA_SSID, STA_PASS);
 
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial2.print(".");
    }
 
    Serial2.println("");
    Serial2.println("WiFi connected");
    Serial2.println("IP address: ");
    Serial2.println(WiFi.localIP());
 
    setModemSleep();
    Serial2.println("MODEM SLEEP ENABLED FOR 5secs");
}
 
//void loop() {}
unsigned long startLoop = millis();
bool started = false;
void loop() {
    if (!started && startLoop+5000<millis()){
        // Not use delay It has the own policy
        wakeModemSleep();
        Serial2.println("MODEM SLEEP DISABLED");
        started = true;
    }
}
 
 
void setModemSleep() {
    WiFi.setSleep(true);
    setCpuFrequencyMhz(80);
}
 
void wakeModemSleep() {
    setCpuFrequencyMhz(240);
}
```
Kode ini memperlihatkan bagaimana ESP32 masuk ke mode Modem Sleep setelah berhasil terkoneksi ke jaringan WiFi, dengan cara mengurangi frekuensi clock CPU dan memasukkan modul WiFi ke mode tidur (sleep) selama 5 detik.

Penurunan frekuensi clock dilakukan menggunakan fungsi setCpuFrequencyMhz(80), yang mengubah kecepatan kerja prosesor dari frekuensi normalnya (misalnya 240 MHz) menjadi 80 MHz. Dengan frekuensi yang lebih rendah, kecepatan pemrosesan memang berkurang, tetapi konsumsi daya juga menurun karena arus dan tegangan yang digunakan CPU menjadi lebih kecil.

Selama periode ini, modul WiFi berada dalam keadaan tidur, sehingga tidak memancarkan atau menerima sinyal. Setelah 5 detik berlalu, sistem “dibangunkan” kembali melalui fungsi setCpuFrequencyMhz(240), yang mengembalikan clock CPU ke frekuensi normal (240 MHz) agar performa penuh dapat digunakan kembali untuk menjalankan tugas-tugas utama ESP32.

### Definisi 2

Berdasarkan definisi ke 2 dari modem sleep, dapat dijabarkan sebagai proses untuk mengnonaktifkan Wi-Fi, Bluetooth, dan mengurangi frekuensi CPU seperti pada cuplikan kode berikut.
```cpp
#include <WiFi.h>
#include <BluetoothSerial.h>
#include "driver/adc.h"
#include <esp_bt.h>
 
#define STA_SSID "<YOUR-SSID>"
#define STA_PASS "<YOUR-PASSWD>"
 
BluetoothSerial SerialBT;
 
void setModemSleep();
void wakeModemSleep();
 
void setup() {
    Serial2.begin(115200);
 
    while(!Serial2){delay(500);}
 
    SerialBT.begin("ESP32test"); //Bluetooth device name
    SerialBT.println("START BT");
 
    Serial2.println("START WIFI");
    WiFi.begin(STA_SSID, STA_PASS);
 
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial2.print(".");
    }
 
    Serial2.println("");
    Serial2.println("WiFi connected");
    Serial2.println("IP address: ");
    Serial2.println(WiFi.localIP());
 
    setModemSleep();
    Serial2.println("MODEM SLEEP ENABLED FOR 5secs");
}
 
//void loop() {}
unsigned long startLoop = millis();
bool started = false;
void loop() {
    if (!started && startLoop+5000<millis()){
        // Not use delay It has the own policy
        wakeModemSleep();
        Serial2.println("MODEM SLEEP DISABLED");
        started = true;
    }
}
 
void disableWiFi(){
    adc_power_off();
    WiFi.disconnect(true);  // Disconnect from the network
    WiFi.mode(WIFI_OFF);    // Switch WiFi off
    Serial2.println("");
    Serial2.println("WiFi disconnected!");
}
void disableBluetooth(){
    // Quite unusefully, no relevable power consumption
    btStop();
    Serial2.println("");
    Serial2.println("Bluetooth stop!");
}
 
void setModemSleep() {
    disableWiFi();
    disableBluetooth();
    setCpuFrequencyMhz(80);
}
 
void enableWiFi(){
    adc_power_on();
    delay(200);
 
    WiFi.disconnect(false);  // Reconnect the network
    WiFi.mode(WIFI_STA);    // Switch WiFi off
 
    delay(200);
 
    Serial2.println("START WIFI");
    WiFi.begin(STA_SSID, STA_PASS);
 
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial2.print(".");
    }
 
    Serial2.println("");
    Serial2.println("WiFi connected");
    Serial2.println("IP address: ");
    Serial2.println(WiFi.localIP());
}
 
void wakeModemSleep() {
    setCpuFrequencyMhz(240);
    enableWiFi();
}
```

# Referensi Lebih Lanjut

Penggunaan mode-mode sleep dalam kode belum dibahas secara terlalu detail dalam modul ini, silahkan refer ke [Light Sleep](https://https://randomnerdtutorials.com/esp32-light-sleep-arduino/) untuk mempelajari penggunaan light sleep dan [Deep Sleep](https://randomnerdtutorials.com/esp32-deep-sleep-arduino-ide-wake-up-sources/) untuk mempelajari penggunaan Deep Sleep pada kode.

Referensi Lainnya:
- “Power Management - ESP32 - — ESP-IDF Programming Guide v5.5.1 documentation,” Espressif.com, 2016. https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/power_management
- L. Llamas, “Energy consumption in ESP32,” Luis Llamas, Sep. 22, 2023. https://www.luisllamas.es/en/esp32-power-consumption/ (accessed Nov. 02, 2025).
- Renzo Mischianti, “ESP32 practical power saving: manage WiFi and CPU - 1,” Renzo Mischianti, Mar. 06, 2021. https://mischianti.org/esp32-practical-power-saving-manage-wifi-and-cpu-1/ (accessed Nov. 02, 2025).

# Module 9 - IoT Platforms, Blynk, and Red Node

# 9.1 Learning Objectives

- Understand Blynk as a cloud-dependent IoT platform for mobile control and monitoring

- Implement basic control using Blynk's virtual pin system for LED switching

- Understand Node-RED as a flow-based visual programming tool for IoT integration

- Create basic data flows using Node-RED's visual editor to connect devices and services

# 9.2 Blynk

### Introduction to Blynk
Blynk is an IoT platform designed to facilitate remote monitoring and control of microcontroller-based projects through mobile applications. The platform operates on a client-server architecture where hardware devices communicate with a cloud server, which then relays information to and from mobile applications. This setup eliminates the need for direct device-to-app communication, simplifying the development process for IoT applications.

The platform consists of three main components:

1. Blynk Cloud server that handles message routing
2. Mobile applications for creating control interfaces
3. client libraries that run on embedded devices.

Devices connect to the Blynk server using various communication protocols including Wi-Fi, Ethernet, or cellular networks, while users interact with their devices through customizable dashboards on their smartphones.

### Technical Architecture
Blynk employs a token-based authentication system where each hardware device requires a unique authentication token to establish a connection with the Blynk cloud. This token links the physical device to a specific project created within the Blynk mobile application. The communication between devices and the cloud server occurs over standard protocols like TCP, with optional SSL encryption for secure data transmission.

Data exchange in Blynk follows a virtual pin model, where hardware devices map their sensors and actuators to virtual pin numbers. The mobile application widgets interact with these virtual pins, creating an abstraction layer that separates the hardware implementation from the user interface. This virtual pin system allows for flexible project design, as hardware changes don't necessarily require modifications to the mobile interface.

### Key Concepts and Limitations
Blynk operates primarily through widget-based controls that send and receive data via virtual pins. Buttons can send digital or analog values to devices, while display widgets can show sensor readings and other device states. The platform supports real-time data visualization, push notifications for alert conditions, and basic data logging capabilities.

However, Blynk has several technical limitations. The platform relies heavily on its cloud infrastructure, meaning all device communication must pass through Blynk's servers, creating potential points of failure and privacy concerns. There are also restrictions on the rate of data transmission and the number of widgets available in the free tier. For applications requiring local network communication or complex data processing, Blynk may not provide sufficient flexibility without paid upgrades or workarounds.

# 9.3 Blynk Tutorial

### Setup Blynk

1. Go to [Blynk Official Website](https://www.blynk.io/)

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762679205212.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762679205212.png)

2. Sign Up for a new account and login

3. Once you get redirected to Blynk Console, go to Developer Zone > My Templates and click the "New Template Button"

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762679588897.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762679588897.png)

4. Give the project name and description (optional)

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762679882935.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762679882935.png)

5. After creating the project, navigate to "Datastreams" and click the "edit" button

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762680061410.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762680061410.png)

6. Click on "New Datastream" button

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762680118329.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762680118329.png)

7. Click on "Virtual Pin"

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762680186143.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762680186143.png)

8. You can leave everything as it is for now and click "Create".

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762680296149.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762680296149.png)

Note: There are different data types available (Integer, Double, and String). You may need to change this depending on your needs, but for now we are going to use integer.

9. Now go to the Web Dashboard section and drag the "Switch" to the dashboard

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762680648578.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762680648578.png)

Note: you can adjust the layout of the "Switch" by dragging the small arrow in the bottom right corner

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762680862478.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762680862478.png)

10. Hover your cursor on the "Switch" and click the setting icon

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762680930507.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762680930507.png)

11. Change the Title to "LED" (Optional) and select Integer V0 (V0) for the Datastream, then click the "Save" button

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762681075898.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762681075898.png)

Note: Usually after selecting the datastream, additional options will show. For this tutorial I've set the On/Off label for demonstration purpose, however keep it mind that this is **optional**.

12. Click the "Save and Apply" Button

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762681917419.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762681917419.png)

12. Now select "Devices" in the Blynk Console side bar and click the "New Device" button

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762681356604.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762681356604.png)

13. Click "From Template"

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762681404919.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762681404919.png)

14. Choose the template that we've created previously and click the "Create" button

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762681465273.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762681465273.png)

15. Copy all the information in the notification

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762681952834.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762681952834.png)

16. This will be your main web dashboard. If you want to edit it, you can refer back to step 9

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762682081761.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762682081761.png)

### Setup Wokwi

1. Go to [Wokwi Official Website](https://wokwi.com/) and create a new ESP32 project (you should've known how by now)

2. Copy this code and paste it into the wokwi project that you have created

```cpp
#define BLYNK_TEMPLATE_ID ""
#define BLYNK_TEMPLATE_NAME ""
#define BLYNK_AUTH_TOKEN ""

#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>

// Your WiFi credentials
char ssid[] = "Wokwi-GUEST";
char pass[] = "";

// LED pin
const int ledPin = 2;

void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);
  
  // Initialize LED pin as output
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);  // Start with LED off
  
  // Connect to Blynk
  Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);
  
  Serial.println("Connecting to Blynk...");
}

// Blynk function that runs when Virtual Pin V0 changes
BLYNK_WRITE(V0) {
  int buttonState = param.asInt(); // Get button state (0 or 1)
  
  if (buttonState == 1) {
    digitalWrite(ledPin, HIGH);  // Turn LED ON
    Serial.println("LED turned ON");
  } else {
    digitalWrite(ledPin, LOW);   // Turn LED OFF
    Serial.println("LED turned OFF");
  }
}

void loop() {
  Blynk.run(); // Keep Blynk connection alive
}
```

Note: This code is used to connect to out Blynk Project that we've created. Notice how there is an led pin with number 2 — we'll try to toggle it using our switch in the Blynk dashboard.

3. Replace this code section with the information that you've just copied in step 15 of the Blynk Setup tutorial

```cpp
#define BLYNK_TEMPLATE_ID ""
#define BLYNK_TEMPLATE_NAME ""
#define BLYNK_AUTH_TOKEN ""
```

4. Create this sketch in your wokwi project

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762682625923.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762682625923.png)

5. Go to the Library Manager section in your wokwi and add "Blynk"

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762682692630.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762682692630.png)

Note: For reference, you can look up to this [Wokwi Project](https://wokwi.com/projects/447133612142700545)

6. Start the Simulation

7. Wait until the Serial Monitor shows "Connecting to Blynk.."

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762683017061.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762683017061.png)

8. Click the LED Switch in the Blynk Dashboard

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762683048819.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762683048819.png)

# 9.4 Node Red

### Introduction to Node-RED
Node-RED is a flow-based programming tool built on Node.js that enables visual development of IoT applications and automation systems. Originally created by IBM for wiring together hardware devices, APIs, and online services, it provides a browser-based editor where developers create applications by connecting nodes together to form processing flows. Unlike Blynk's mobile-focused approach, Node-RED operates as a local server that can be deployed on various devices from single-board computers to cloud instances.

The platform uses a modular architecture where each node represents a specific function, such as reading sensors, processing data, or triggering actions. These nodes are connected together in sequences called flows, which define how data moves through the system and gets transformed along the way. This visual programming approach makes complex IoT logic more accessible to developers who may not have extensive coding experience.

### Architecture and Deployment
Node-RED runs as a Node.js application, typically operating as a local web server on a designated port (default 1880). The runtime engine executes the flows, while the built-in web server provides both the visual editor and HTTP endpoints for external access. This architecture allows Node-RED to function as both a development environment and a runtime engine simultaneously.

A key advantage of Node-RED is its flexible deployment options. It can run directly on edge devices like Raspberry Pi for local processing, on servers for centralized control, or in cloud environments. The platform includes a built-in flow repository for saving and sharing configurations, and projects can be easily backed up through simple JSON file exports. This flexibility makes it suitable for everything from simple home automation to complex industrial IoT systems.

### Core Concepts and Data Handling
The fundamental building blocks in Node-RED are nodes, which are categorized into three types: input nodes that inject data or receive external messages, processing nodes that transform or route data, and output nodes that send data to external systems or devices. Nodes are connected by wires that carry message objects containing payload data and optional properties.

Node-RED handles data as JSON message objects that flow between connected nodes. Each message contains a payload property that holds the primary data, along with optional topic and metadata properties. The platform includes numerous built-in nodes for common IoT protocols including MQTT, HTTP, WebSockets, and serial communication. Additional functionality can be added through the extensive package manager, which provides thousands of community-contributed nodes for specialized hardware and services.

### Integration Capabilities and Use Cases
Node-RED excels at integrating diverse systems and protocols within a single visual environment. It can connect to databases, web services, messaging systems, and hardware devices simultaneously, making it effective for building complex IoT gateways. The platform includes built-in support for function nodes where developers can write custom JavaScript code for specialized processing logic that isn't available in pre-built nodes.

Common IoT applications for Node-RED include data aggregation from multiple sources, protocol translation between different IoT devices, automation rule execution, and data visualization through dashboard nodes. The platform's ability to handle both real-time processing and scheduled tasks makes it suitable for monitoring systems, smart home automation, industrial control systems, and data processing pipelines. Unlike cloud-dependent platforms, Node-RED can operate completely locally, providing greater control over data privacy and system reliability.

# 9.5 Node Red Tutorial

### Node Red Setup

1. In this tutorial, I am using a Linux terminal in Windows (WSL) for simplicity, if you're using other environment, that is fine too but some steps might be a little bit different, so be ready to adapt.

2. Install Node and NPM

```shell
sudo apt update
sudo apt install nodejs npm
```

3. Install Node Red

```shell
sudo npm install -g --unsafe-perm node-red
```

4. Run Node Red

```shell
node-red
```

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762684419132.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762684419132.png)

5. Access the Node Red server [http://127.0.0.1:1880/](http://127.0.0.1:1880/) in the web browser

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762688581191.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762688581191.png)

### Node Red Debug

1. Drag Inject Node in to the editor

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762688652702.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762688652702.png)

2. Double click the node and set the payload type to String

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762688699606.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762688699606.png)

3. Set the Payload and Topic to whatever you want, then click the "Done" button

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762688777355.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762688777355.png)

4. Drag the Debug Node in to the editor

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762688823185.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762688823185.png)

5. Connect both nodes

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762688863642.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762688863642.png)

6. Click the "Deploy" button in the top right corner

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762688911321.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762688911321.png)

7. Click the button in the inject node

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762688997300.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762688997300.png)

Node, ehem I mean Note: you can see the debug messages in the right section.

### Node Red Dashboard

1. Click the 3 stripes button in the top-right corner, then click "Manage Palette"

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762691282573.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762691282573.png)

2. Go to the "Install" section, search for `dashboard` in the search bar, then install the dashboard palette from `@flowfuse`. Click the "Done" button once it is finished.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762691472306.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762691472306.png)

3. You should now see the "Dashboard 2" node section if you scroll down enough. If you don't, then try to refresh the web or try to restart Node Red. Once you found it, drag the "button" node to the editor

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762691758315.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762691758315.png)

4. Double click the button node, then set the name to "LED", group to default (look at the screenshot below for reference), Label to "Toggle LED" and click the "Done" button once it is all finished

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762691801156.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762691801156.png)

5. Now scroll above in the node section till you found the `Function` node section. Drag the "function" node to the editor and connect it with the "button" node that you've setup previously.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762691905967.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762691905967.png)

6. Double click the "function" node and insert this code below, then click the "Done" button.

```python
// Store LED state
context.ledState = context.ledState || false;

// Toggle LED state when button pressed
context.ledState = !context.ledState;

// Create message
msg.payload = {
    led_state: context.ledState,
    timestamp: new Date().toISOString(),
    message: context.ledState ? "LED ON" : "LED OFF"
};

return msg;
```
[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762691986709.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762691986709.png)

Note: The code here is to simulate an LED, since we haven't integrate to Wokwi yet in this Tutorial.

7. Go back to the `Dashboard 2` section and drag the "Text Output" node in to the editor and connect it with the "function" node

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762692202990.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762692202990.png)

8. Double click the "Text Output" node then apply these configurations. Set name to "LED Status", set the group to default (see the screenshot below for reference), set the label to "LED Status:", make sure the value type is `msg` and its value is "payload.message", and set the Layout the same as seen in the screenshot below. Click the "Done" button once eveything is finished.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762692278980.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762692278980.png)

9, Drag the "debug" node to the editor and connect it to the "function" node

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762692457531.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762692457531.png)

10. Click the dropdown icon in the top-right corner, then click on "Dashboard 2.0"

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762692501213.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762692501213.png)

11. Click on the setting of your default group

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762692562936.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762692562936.png)

12. Set the size to `3 x 1`. You can change the value later on to experiment on the dashboard layout, but for now we keep it on `3 x 1` for simplicity. Click the "Update" button, once it is finished.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762692629381.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762692629381.png)

13. Click the "Deploy" button so that the changes are reflected

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762692766146.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762692766146.png)

14. To check on the dashboard, click the "open dashboard" button

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762692811449.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762692811449.png)

15. You should be directed to a page similar to this. Try clicking on the "Toggle LED" button and see what happens.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762692863136.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762692863136.png)

16. You can also see the log history in the debug section since you use the "debug" node

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762692911406.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762692911406.png)

### Node Red x Wokwi

1. Drag the "Button Group" Node to the editor

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762696840921.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762696840921.png)

2. Double click the "Button Group" Node and apply this configuration

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762697327183.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762697327183.png)

3. Drag the "MQTT Out" Node to the editor

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762697367733.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762697367733.png)

4. Double click the "MQTT Out" node and add a MQTT server

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762697414244.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762697414244.png)

5. Apply this configuration and save the server

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762697453418.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762697453418.png)

6. Copy the remaining configuration for the "MQTT Out" node

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762697742198.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762697742198.png)

Note: After this I added a "Debug" node that connects with the "Button Group" Node, but this is fully optional for you

7. Drag the "MQTT In" node to the editor

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762697851109.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762697851109.png)

8. Double click the "MQTT IN" node and apply this configuration

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762697894757.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762697894757.png)

9. Drag the "Text Output" Node from the "Dashboard 2" Section and apply this configuration

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762697968748.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762697968748.png)

10. Create a new WOKWI ESP32 project and copy paste this code

```cpp
#include <WiFi.h>
#include <PubSubClient.h>

// Wokwi WiFi - works in simulation
const char* ssid = "Wokwi-GUEST";
const char* password = "";

const char* mqtt_server = "test.mosquitto.org";

WiFiClient espClient;
PubSubClient client(espClient);

const int ledPin = 2;
bool ledState = false;

void setup_wifi() {
  delay(10);
  Serial.println("Connecting to WiFi...");
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* message, unsigned int length) {
  Serial.print("Message arrived on topic: ");
  Serial.print(topic);
  Serial.print(". Message: ");
  
  String messageTemp;
  for (int i = 0; i < length; i++) {
    messageTemp += (char)message[i];
  }
  Serial.println(messageTemp);

  // Check if message is for LED control
  if (String(topic) == "wokwi/led/control") {
    Serial.print("Changing LED to ");
    if(messageTemp == "ON"){
      digitalWrite(ledPin, HIGH);
      ledState = true;
      Serial.println("ON");
      client.publish("wokwi/led/status", "ON");
    }
    else if(messageTemp == "OFF"){
      digitalWrite(ledPin, LOW);
      ledState = false;
      Serial.println("OFF");
      client.publish("wokwi/led/status", "OFF");
    }
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "WokwiClient-";
    clientId += String(random(0xffff), HEX);
    
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      // Subscribe to control topic
      client.subscribe("wokwi/led/control");
      // Publish connected message
      client.publish("wokwi/led/status", "connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void setup() {
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  
  Serial.begin(115200);
  setup_wifi();
  
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  // Add a small delay
  delay(100);
}
```

11. Create the sketch where an LED is connected to PIN. The result should be similar to the following screenshot

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762698153192.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762698153192.png)

12. Add the `PubSubClient` library in the library manager

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762698188090.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762698188090.png)

13. Run the Wokwi simulation

Note: if you're having difficulties, you can follow [this](https://wokwi.com/projects/447150309147845633) as a reference

14. Deploy the Node Red project and go to the dashboard. You should be able to control the LED using the button there.

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762698319776.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762698319776.png)

[![](https://learn.digilabdte.com/uploads/images/gallery/2025-11/scaled-1680-/image-1762698344916.png)](https://learn.digilabdte.com/uploads/images/gallery/2025-11/image-1762698344916.png)

# Module 10 - Mesh

# 10.1 Introduction

# **Module 10: Mesh**

Author: YP

### **Learning Objectives**

After completing this module, students are expected to be able to:
* **Explain** the concept, advantages, and basic architecture of IoT mesh networks.
* **Implement** a simple mesh network using ESP32 with the `painlessMesh` library.
* **Build** a *gateway* (*Root Node*) to forward data from the mesh network to a server.

# 10.2 What is Mesh?

#### **10.2.1 What is a Mesh Network?**

![image](https://hackmd.io/_uploads/rkqRhdlGyg.png)

A **mesh network** is a topology where each device (*node*) is interconnected, creating multiple paths for data. Unlike traditional networks that rely on a single central point (such as a router), a mesh network is **decentralized**.

**How It Works**

In a mesh network, each ESP32 node acts as both a sender and receiver, functioning as a repeater. This differs from a star network where communication only goes through one central hub. A mesh network is more decentralized, thus improving coverage and network reliability.

In this setup, one ESP32 acts as the root node, which connects the mesh network to an external network, while other nodes can serve as intermediate parents that forward data or as leaf nodes that only send and receive their own data. Data in the network is automatically routed through available nodes until it reaches the final destination, either another node within the mesh or an external network via the root node. This creates an efficient, flexible, and fault-tolerant network.

Key characteristics include:
* **Self-Healing**: If one *node* fails, data is automatically rerouted through another available path.
* **Wide Coverage**: Each *node* functions as a *repeater*, significantly extending signal range.

---

#### **10.2.2 Topology Comparison: Mesh vs. Star**
To understand the advantages, let’s compare it with the commonly used Star topology in home Wi-Fi.

- Mesh

  ![Mesh Topology](https://hackmd.io/_uploads/SyQt3OgMJe.png)

  Connects devices directly, creating multiple paths for data. A mesh network is more flexible than a star network and requires fewer gateways to communicate with the same number of devices. However, it is more complex and costly compared to a star network. Mesh networks also lack universal standards, which may cause compatibility issues between devices from different vendors.

- Star

  ![Star Topology](https://hackmd.io/_uploads/Hkki2Oxz1g.png)

  Connects devices through a central hub. A star network is simpler and cheaper than a mesh network but depends heavily on the central hub. Device failures in a star network do not affect the rest of the system, but if the hub fails, the entire network goes down. Star networks are best suited for devices that need to communicate directly with a central node, such as office equipment, security cameras, and medical devices.

| Component         | **Mesh Topology**                                     | **Star Topology**                                         |
| :---------------- | :--------------------------------------------------- | :------------------------------------------------------- |
| **Resilience**    | **Very High**. Supports self-healing.                 | **Low**. If the hub/router fails, the entire network fails. |
| **Coverage**      | **Wide and flexible**. Easy to expand.                | **Limited** by the hub/router’s range.                    |
| **Complexity**    | More complex to configure.                            | Simple and easy to set up.                                |

---

#### **10.2.3 Types of Nodes in a Mesh Network**
In this lab, we will learn about 4 types of nodes:

- **Root Node**  
  The root node is the highest-level node in a Wi-Fi mesh network and acts as the only link between the mesh network and the external IP network. It connects directly to a conventional Wi-Fi router and forwards data packets between the external network and the mesh nodes. There must only be one root node in a mesh network, and it can only have one upstream connection—to the router. The root node is crucial in ensuring that all mesh network data can be accessed by external devices.

- **Leaf Nodes**  
  Leaf nodes are nodes that have no child nodes (no downstream connections). They can only send or receive their own data packets and do not forward data from other nodes. Typically, leaf nodes are located at the farthest edge of the mesh where no new downstream connections are possible. If a node only has a station interface (station-only node) and lacks a softAP interface, it is designated as a leaf node since downstream connections require a softAP.

- **Intermediate Parent Nodes**  
  Intermediate parent nodes are neither root nor leaf nodes. They have one upstream connection (to a parent node) and may have multiple downstream connections (to child nodes). These nodes can send, receive, and forward data from both upstream and downstream connections. Unlike leaf nodes, they can still form downstream connections in the future. They serve as bridges in the mesh network, enabling data to flow between different layers.

- **Idle Nodes**  
  Idle nodes are nodes that have not yet joined the mesh network. They attempt to establish an upstream connection with an existing intermediate parent node or try to become the root node if certain conditions are met (for example, when no root node exists in the network). Idle nodes remain passive until they successfully join or are integrated into the mesh network.

# 10.3 Example Code

#### **10.3.1 Root Node**

```cpp=
#include <Arduino.h>
#include <painlessMesh.h>
#include <WiFi.h>

// --- Konfigurasi Jaringan ---
#define MESH_PREFIX     "jaringan_mesh_saya" // HARUS SAMA dengan semua node
#define MESH_PASSWORD   "password_mesh"      // HARUS SAMA dengan semua node
#define MESH_PORT       5555

painlessMesh mesh;

// Callback ketika Root menerima pesan dari Leaf
void receivedCallback(uint32_t from, String &msg) {
  Serial.printf("Pesan diterima dari node %u: %s\n", from, msg.c_str());
}

void setup() {
  Serial.begin(115200);

  // Inisialisasi Mesh
  mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
  mesh.init(MESH_PREFIX, MESH_PASSWORD, MESH_PORT);

  // Set sebagai ROOT
  mesh.setRoot(true);
  mesh.setContainsRoot(true);

  // Pasang callback untuk menerima pesan
  mesh.onReceive(&receivedCallback);

  Serial.println("Mesh dimulai sebagai ROOT");
  Serial.print("Root ESP32 SoftAP IP: ");
  Serial.println(WiFi.softAPIP());
}

void loop() {
  mesh.update();
}

```

#### **10.3.2 Leaf Node**

```cpp=
#include <Arduino.h>
#include <painlessMesh.h>

// --- Konfigurasi Jaringan ---
#define MESH_PREFIX     "jaringan_mesh_saya" // HARUS SAMA dengan semua node
#define MESH_PASSWORD   "password_mesh"      // HARUS SAMA dengan semua node
#define MESH_PORT       5555

painlessMesh mesh;

// Ganti sesuai nomor leaf
String leafName = "Leaf1";

void sendMessage() {
  String msg = "Hello guys im " + leafName;
  mesh.sendBroadcast(msg);
  Serial.println("Sent: " + msg);
}

void setup() {
  Serial.begin(115200);
  mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
  mesh.init(MESH_PREFIX, MESH_PASSWORD, MESH_PORT);
}

void loop() {
  mesh.update();
  static unsigned long lastSend = 0;
  if (millis() - lastSend > 5000) {  // kirim tiap 5 detik
    lastSend = millis();
    sendMessage();
  }
}

```

#### **10.3.3 Intermediate Parent Node**

```cpp=
#include <Arduino.h>
#include <painlessMesh.h>

// --- Konfigurasi Jaringan ---
#define MESH_PREFIX     "jaringan_mesh_saya" // HARUS SAMA dengan semua node
#define MESH_PASSWORD   "password_mesh"      // HARUS SAMA dengan semua node
#define MESH_PORT       5555

painlessMesh mesh;

// Callback ketika node menerima pesan
void receivedCallback(uint32_t from, String &msg) {
  Serial.printf("Pesan diterima dari %u: %s\n", from, msg.c_str());
  // Intermediate node tidak perlu kirim ke server, cukup teruskan pesan
  mesh.sendBroadcast(msg);
}

// Callback saat node terhubung
void newConnectionCallback(uint32_t nodeId) {
  Serial.printf("Node baru terhubung: %u\n", nodeId);
}

// Callback saat node terputus
void changedConnectionCallback() {
  Serial.println("Perubahan koneksi terjadi!");
}

// Callback saat node time sync
void nodeTimeAdjustedCallback(int32_t offset) {
  Serial.printf("Waktu sinkronisasi, offset = %d\n", offset);
}

void setup() {
  Serial.begin(115200);

  // Inisialisasi mesh
  mesh.setDebugMsgTypes(ERROR | STARTUP);  
  mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
  
  // Callback
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
}

void loop() {
  mesh.update();
}

```

#### **10.3.4 Idle Node**

There is no code because this is just a status if the node is not yet connected to the mesh.