# Embedded Infastructure

# Getting Started

How to setup and get started working on the embedded code

# STM32CubeMX

***This page:** *the short, concrete workflow for using STM32CubeMX to configure an STM32 project and generate init code without accidentally nuking your work.**

<p class="callout info">**Download:**<span style="white-space: pre-wrap;"> </span>[https://www.st.com/en/development-tools/stm32cubemx.html](https://www.st.com/en/development-tools/stm32cubemx.html)</p>

## 1) What is it

> **STM32CubeMX** is a graphical tool that simplifies the configuration of STM32 products, and generates the corresponding initialization code through a guided step-by-step process.  
> <span style="white-space: pre-wrap;">&gt; </span>[st.com](https://www.st.com/en/development-tools/stm32cubemx.html)

In the embedded subteam, we use STM32 Nucleos to make our robot come to life. We use CubeMX to enable these boards to do what we want by **setting the pins**<span style="white-space: pre-wrap;"> on the physical board and </span>**generating code**<span style="white-space: pre-wrap;"> that we can use to drive those pins. </span>

---

## 2) Starting a (new) project

<span style="white-space: pre-wrap;">Once you have successfully installed CubeMX, you can either create or open a project. You will most likely be working with already existing CubeMX projects. </span>**You can open any project by finding the `<strong class="editor-theme-bold editor-theme-code">.ioc</strong>` file.**<span style="white-space: pre-wrap;"> This is the configuration file for any CubeMX project.</span>

However, there are some **important settings**<span style="white-space: pre-wrap;"> that any project needs.</span>

---

### a. New project

<p class="callout danger"><span style="white-space: pre-wrap;">When creating a NEW project make sure you use the </span>**board selector and NOT the MCU selector**<span style="white-space: pre-wrap;"> to start your project (given the fact that you will be working with a board). If you don't do this, it will cause problems down the line.</span></p>

### b. Project Settings

##### **Project Manager &gt; Project**

<span style="white-space: pre-wrap;">Once you have your project open, navigate to the project manager. </span>

- <span style="white-space: pre-wrap;">It is important that your </span>**project name is "firmware"**, since this is the name the folder is supposed to have in the embedded structure. (This is platformio configuration related.)
- **Do not generate main()**. You should only generate a main function to check what is in there and use it as an example, but when you want to build, you can not have a main function in your auto-generated code. It will conflict with your own main function.
- <span style="white-space: pre-wrap;">Set the toolchain to </span>**Makefile**. You should not use another toolchain, because the post code generation script uses information from the Makefile!

[![afbeelding.png](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/scaled-1680-/G5rafbeelding.png)](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/G5rafbeelding.png)

##### **Project Manager &gt; Code Generator**

- <span style="white-space: pre-wrap;">Click the box to generate </span>**separate files per peripheral**<span style="white-space: pre-wrap;">. Otherwise you will encounter errors surrounding missing libraries when building. </span>
- <span style="white-space: pre-wrap;">Set the after code generation script. It can be found in </span>`<span class="editor-theme-code">scripts/post_code_generation.bash</span>`<span style="white-space: pre-wrap;">. More information in </span>[Post-Generation Scripts](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/post-generation-scripts "https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/post-generation-scripts").

<p class="callout info">**NOTE for Windows users:**<span style="white-space: pre-wrap;"> the post generation script will NOT automatically be ran for you. Instead, you will have to run the script by hand in the </span>**git bash**<span style="white-space: pre-wrap;"> terminal.</span></p>

[![afbeelding.png](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/scaled-1680-/WV6afbeelding.png)](https://bookstack.roboteamtwente.nl/uploads/images/gallery/2026-04/WV6afbeelding.png)

---

## 3) Typical Workflow

### a. Configure pins &amp; peripherals

- <span style="white-space: pre-wrap;">In </span>**Pinout &amp; Configuration**: enable the peripherals you need (UART/SPI/I2C/CAN/Timers/ADC/etc.).
- Assign pins and resolve conflicts (CubeMX will warn you).
- Configure DMA + NVIC if needed (especially for high-rate IO or RTOS systems).

### b. Set up the clocks

- <span style="white-space: pre-wrap;">Go to </span>**Clock Configuration**<span style="white-space: pre-wrap;"> and set your clock source (HSI/HSE) and PLL to the target system frequency.</span>
- Verify peripheral clocks (UART baud rates and timer frequencies depend on this).
- If USB is used, make sure USB clock requirements are satisfied (CubeMX will usually flag invalid setups).

### c. Code generation

**Do not write custom code in CubeMX-generated files.**

<span style="white-space: pre-wrap;">CubeMX will overwrite generated files during regeneration. Any custom code placed there </span>**will be lost**, even if it appears to work temporarily.

**Rule:**

- <span style="white-space: pre-wrap;">Generated code is </span>**read-only**.
- <span style="white-space: pre-wrap;">Your code lives </span>**outside**<span style="white-space: pre-wrap;"> of it.</span>

**What to do instead:**

- Put all application logic in your own source files (`<span class="editor-theme-code">src/</span>`, modules, drivers, etc.).
- Only use generated code as initialization and hardware configuration.
- <span style="white-space: pre-wrap;">Call your own code from the appropriate entry points (e.g. after init in </span>`<span class="editor-theme-code">main()</span>`).

**Bottom line:**  
If your code depends on surviving a “Generate Code” click, it’s in the wrong place.

### d. Generate code, then build &amp; verify

- <span style="white-space: pre-wrap;">Open </span>**Project Manager**<span style="white-space: pre-wrap;"> and confirm the project type/toolchain and output path are correct.</span>
- <span style="white-space: pre-wrap;">Click </span>**Generate Code**.
- <span style="white-space: pre-wrap;">Immediately review changes (e.g. </span>`<span class="editor-theme-code">git diff</span>`). If CubeMX changed a lot more than expected, stop and investigate before committing.
- Build the firmware and run a basic smoke test (UART prints, LED blink, peripheral init success, etc.)

# Git Submodules

## On this page

- [1. Why submodules?](#why)
- [2. Key concepts](#terms)
- [3. Cloning a repo with submodules](#clone)
- [4. Adding ERC-Protobufs as a submodule](#add)
- [5. Updating ERC-Protobufs (pinning a new commit)](#update)
- [6. Branches, detached HEAD, and what “pinned” means](#branches)
- [7. Common mistakes](#mistakes)
- [8. Command cheat sheet](#cheatsheet)

## 1) Why submodules?

<span style="white-space: pre-wrap;">A </span>[git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules)<span style="white-space: pre-wrap;"> lets one repository “mount” another repository at a specific commit. That sounds fancy, but it’s really just Git saying: “this folder is a separate repo, and we are pinning it to a specific SHA because reproducibility is not optional.”</span>

We use this for shared code/assets that:

- should be versioned and reviewed independently,
- <span style="white-space: pre-wrap;">must be </span>**pinned**<span style="white-space: pre-wrap;"> (reproducible builds, fewer “works on my machine” sightings),</span>
- is shared across multiple firmware/software repositories.

**RoboTeam example:**<span style="white-space: pre-wrap;"> </span>**ERC-Protobufs**<span style="white-space: pre-wrap;"> can be shared between embedded firmware, tooling, and PC-side code. Pinning ensures everyone generates/uses the same message definitions — which is what prevents “my robot speaks protobuf dialect #3” incidents.</span>

## 2) Key concepts

<table id="bkmrk-termwhat-it-meanswhy"><colgroup><col style="width: 192px;"></col><col></col><col></col></colgroup><tbody><tr><th>Term

</th><th>What it means

</th><th>Why care about it?

</th></tr><tr><td>`<span class="editor-theme-code">.gitmodules</span>`

</td><td>A file in the parent repo that stores submodule name/path/URL.

</td><td>This is what gets committed so everyone else can actually fetch the submodule without guessing.

</td></tr><tr><td>“Pinned commit”

</td><td>The parent repo records a specific commit SHA for the submodule.

</td><td>Builds are reproducible; updating is an explicit change (and therefore reviewable).

</td></tr><tr><td>Detached HEAD

</td><td>By default, a submodule checks out the exact pinned commit, not a branch.

</td><td>Normal. It looks scary the first time, but it just means “you’re on a commit, not a branch.”

</td></tr><tr><td>`<span class="editor-theme-code">git submodule update</span>`

</td><td>Checks out the submodule commit referenced by the parent repo.

</td><td>Use after switching branches or pulling changes, because submodules do not magically follow along.

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

## 3) Cloning a repo with submodules

<span style="white-space: pre-wrap;">If a repository already uses </span>[ERC-Protobufs](https://github.com/RoboTeamTwente/ERC-Protobufs/tree/aa3ea9bfd9cbe811f719f328bf4927445820d1f1)<span style="white-space: pre-wrap;"> as a submodule, you must fetch it after cloning. Otherwise Git will politely give you an empty folder and let you discover the problem at build time.</span>

### Recommended (one command)

```
git clone --recurse-submodules <PARENT_REPO_URL>
```

### If you already cloned (two commands)

```
git submodule init
git submodule update
```

**Tip:**<span style="white-space: pre-wrap;"> Add </span>`<span class="editor-theme-code">--recursive</span>`<span style="white-space: pre-wrap;"> if the submodule itself contains submodules:</span>

```
git submodule update --init --recursive
```

**Symptom you forgot submodules:**<span style="white-space: pre-wrap;"> build errors like “file not found”, missing generated headers, missing </span>`<span class="editor-theme-code">.proto</span>`<span style="white-space: pre-wrap;"> files, or empty directories where ERC-Protobufs should be.</span>

## 4) Adding ERC-Protobufs as a submodule

<span style="white-space: pre-wrap;">Use this when a parent repository needs to include </span>[ERC-Protobufs](https://github.com/RoboTeamTwente/ERC-Protobufs/tree/aa3ea9bfd9cbe811f719f328bf4927445820d1f1)<span style="white-space: pre-wrap;"> for builds/code generation. This is a dependency decision, not a casual Friday activity.</span>

### Step-by-step

1. <span style="white-space: pre-wrap;">Choose where it should live in your repo, for example: </span>`<span class="editor-theme-code">third_party/ERC-Protobufs</span>`<span style="white-space: pre-wrap;"> (or </span>`<span class="editor-theme-code">libs/ERC-Protobufs</span>`).
2. Add the submodule:```
    git submodule add <ERC_PROTOBUFS_REPO_URL> third_party/ERC-Protobufs
    ```
3. Commit the changes:```
    git add .gitmodules third_party/ERC-Protobufs
    git commit -m "Add ERC-Protobufs as a submodule"
    ```

**What gets committed?**<span style="white-space: pre-wrap;"> (a.k.a. “what did I just do to the repo”) </span>

- `<span class="editor-theme-code">.gitmodules</span>`<span style="white-space: pre-wrap;"> file (submodule metadata)</span>
- <span style="white-space: pre-wrap;">a “gitlink” entry at </span>`<span class="editor-theme-code">third_party/ERC-Protobufs</span>`<span style="white-space: pre-wrap;"> that pins a specific commit SHA</span>

The submodule’s full contents are not copied into the parent repo history. You’re committing a pointer, not a copy.

**Protocol for RoboTeam:**<span style="white-space: pre-wrap;"> confirm with your lead whether the submodule path is standardized across repositories (helps tooling and scripts, and prevents everyone inventing </span>`<span class="editor-theme-code">thirdparty/</span>`<span style="white-space: pre-wrap;"> in six different spellings).</span>

## 5) Updating ERC-Protobufs (pinning a new commit)

Updating a submodule means: “the parent repo now points to a newer commit of ERC-Protobufs”. This should be done intentionally and reviewed because it can change message definitions and compatibility. Treat it like an API bump, not like updating a meme folder.

### Update flow (safe + explicit)

1. Enter the submodule directory:```
    cd third_party/ERC-Protobufs
    ```
2. Fetch latest commits:```
    git fetch --all --tags
    ```
3. Check out the desired commit (or a tag):```
    git checkout <commit-sha-or-tag>
    ```
4. Go back to the parent repo and commit the updated pin:```
    cd ../..
    git status
    git add third_party/ERC-Protobufs
    git commit -m "Bump ERC-Protobufs submodule to <sha-or-tag>"
    ```

**Optional (if you want the newest remote-tracking commit):**<span style="white-space: pre-wrap;"> inside the parent repo:</span>

```
git submodule update --remote --merge
```

This requires the submodule to have a branch configured; it is less explicit, so use with care (automation is great right up until it updates something you didn’t mean to update).

**Do not “fix” submodule issues by deleting the folder.**<span style="white-space: pre-wrap;"> That often creates messy diffs and makes Git sad. Use actual submodule commands instead.</span>

## 6) Branches, detached HEAD and what “pinned” means

<span style="white-space: pre-wrap;">When you run </span>`<span class="editor-theme-code">git submodule update</span>`<span style="white-space: pre-wrap;">, Git checks out the exact commit recorded by the parent repo. This usually results in a </span>**detached HEAD**<span style="white-space: pre-wrap;"> state inside the submodule.</span>

**This is normal.**<span style="white-space: pre-wrap;"> A consumer repo typically should not make local changes inside the submodule. If you need to change ERC-Protobufs itself, do that in the ERC-Protobufs repository and then bump the pin in the consumer repo. Submodules are for consuming, not freestyle surgery.</span>

### How to tell what commit you are pinned to

```
# From the parent repo root:
git submodule status
```

### How to see what changed after a submodule bump

```
# From the parent repo root:
git diff --submodule
```

## 7) Common mistakes

### “Directory is empty / looks uninitialized”

```
git submodule update --init --recursive
```

### “Submodule shows changes but I didn’t touch it”

Often caused by being on the wrong commit, or having local edits in the submodule. Either way, Git is not gaslighting you — something really is different.

```
cd third_party/ERC-Protobufs
git status
git reset --hard
git clean -fd
cd ../..
git submodule update --init --recursive
```

**Warning:**<span style="white-space: pre-wrap;"> </span>`<span class="editor-theme-code">git reset --hard</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">git clean -fd</span>`<span style="white-space: pre-wrap;"> will delete local submodule changes. Only do this if you are sure you don’t need them (i.e., you didn’t secretly do work inside the submodule and forget).</span>

### “I switched branches and submodules are wrong”

```
git submodule update --init --recursive
```

### “I updated the submodule but forgot to commit in the parent repo”

After updating inside the submodule, you must commit the new pin from the parent repo. Otherwise you updated your local checkout and told nobody, which is the Git equivalent of whispering into the void.

```
git add third_party/ERC-Protobufs
git commit -m "Bump ERC-Protobufs submodule"
```

## 8) Command cheat sheet

### Clone with submodules

```
git clone --recurse-submodules <repo-url>
```

### Initialize/update after cloning

```
git submodule update --init --recursive
```

### Show pinned commits

```
git submodule status
```

### Add ERC-Protobufs

```
git submodule add <erc-protobufs-url> third_party/ERC-Protobufs
git commit -m "Add ERC-Protobufs submodule"
```

### Bump ERC-Protobufs to a specific commit/tag

```
cd third_party/ERC-Protobufs
git fetch --all --tags
git checkout <sha-or-tag>
cd ../..
git add third_party/ERC-Protobufs
git commit -m "Bump ERC-Protobufs submodule to <sha-or-tag>"
```

# Gaslight your boss :D

<span style="white-space: pre-wrap;">(If you need help with abusive bosses reach out </span>[@mybrosky\_nam](https://creations.mtdv.me/Rick-ZarolERC-Embedded-Simplified-Official), I couldnt do anything about it but I'll try to help you so you don't suffer as well ☮️)

# Project Structure

How the code is structured and organized

# Layout

## Code Structure

### Architecture (Summary)

<span style="white-space: pre-wrap;">Each board’s </span>`<span class="editor-theme-code">main.c</span>`<span style="white-space: pre-wrap;"> acts strictly as an orchestrator. It initializes the runtime, creates tasks, and delegates all functional behavior to component modules.</span>

### Core Design Contract

<span style="white-space: pre-wrap;">The repository enforces a strict separation between </span>**entrypoints**<span style="white-space: pre-wrap;"> and </span>**components**:

- `<span class="editor-theme-code">src/<board>/main.c</span>`<span style="white-space: pre-wrap;"> defines the process entrypoint for each board target.</span>
- `<span class="editor-theme-code">components/common/*</span>`<span style="white-space: pre-wrap;"> contains reusable logic shared across multiple boards.</span>
- `<span class="editor-theme-code">components/<board>/*</span>`<span style="white-space: pre-wrap;"> contains board-specific functionality.</span>
- `<span class="editor-theme-code">components/<board>/firmware/*</span>`<span style="white-space: pre-wrap;"> contains generated or vendor-provided firmware and MCU integration code.</span>

### Primary Rule

> `<span class="editor-theme-code">main.c</span>`<span style="white-space: pre-wrap;"> must not contain domain logic. It is responsible only for system wiring and task startup.</span>
> 
> \- Albert Einstein

## Repository Layout

```
erc/
├─ src/
│  ├─ arm_board/main.c
│  ├─ driving_board/main.c
│  ├─ sensor_board/main.c
│  ├─ network_board/main.c
│  └─ debugging_board/main.c
│
├─ components/
│  ├─ common/              # Shared modules across boards
│  ├─ arm_board/           # Arm board-specific modules
│  ├─ driving_board/       # Driving board-specific modules
│  ├─ sensor_board/        # Sensor board-specific modules
│  ├─ network_board/       # Network board-specific modules
│  └─ debugging_board/     # Debugging board-specific modules
│
├─ test/
│  ├─ common/
│  ├─ arm_board/
│  ├─ driving_board/
│  ├─ sensor_board/
│  └─ debugging_board/
│
├─ scripts/                # Utility scripts (codegen, post-processing)
└─ platformio.ini          # Build environments and board filters
```

## Entrypoint Responsibilities (`<span class="editor-theme-code">src/<board>/main.c</span>`)

<span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">main.c</span>`<span style="white-space: pre-wrap;"> file is intentionally minimal and should perform only the following:</span>

1. Execute mandatory low-level initialization  
    **(HAL, clock, cache, MPU, RTOS initialization as required)**
2. Initialize infrastructure dependencies  
    **(GPIO, UART, timers, networking wrappers, etc.)**
3. Create one or more RTOS tasks
4. Start the scheduler/kernel
5. Delegate all functional behavior to components

### <span style="white-space: pre-wrap;">What Must NOT Be Implemented in </span>`<span class="editor-theme-code">main.c</span>`

<span style="white-space: pre-wrap;">The following must never reside in </span>`<span class="editor-theme-code">main.c</span>`:

- Sensor processing algorithms
- Business or control logic
- Packet parsing or dispatch policy
- Device-specific runtime behavior beyond initialization
- Long-running loops implementing application logic

If logic grows beyond simple initialization or task creation, it must be moved into a component module and invoked from a task.

## Component Responsibilities (`<span class="editor-theme-code">components/*</span>`)

All functional behavior belongs in components. Tasks must delegate to components rather than implementing logic inline.

### Examples

- <span style="white-space: pre-wrap;">Sensor-related behavior → </span>`<span class="editor-theme-code">components/sensor_board/*</span>`  
    **(e.g., GPS, IMU, pH sensors, acquisition pipelines)**
- <span style="white-space: pre-wrap;">Driving logic → </span>`<span class="editor-theme-code">components/driving_board/*</span>`  
    **(e.g., motor control, calculations, protocol parsing, Simulink integration)**
- <span style="white-space: pre-wrap;">Debugging UI and diagnostics → </span>`<span class="editor-theme-code">components/debugging_board/*</span>`
- <span style="white-space: pre-wrap;">Shared infrastructure → </span>`<span class="editor-theme-code">components/common/*</span>`  
    **(e.g., result handling, logging, queues, dispatch systems)**

## Execution Model

The execution flow for each board follows a consistent structure:

```
main.c
  → platform/runtime initialization
  → create RTOS task(s)
  → each task calls component APIs
  → component modules execute all functional logic
```

This ensures that:

- `<span class="editor-theme-code">main.c</span>`<span style="white-space: pre-wrap;"> remains stable and minimal</span>
- behavior is modular and testable
- functionality is reusable across boards

# Post-Generation Scripts

### Introduction

<span style="white-space: pre-wrap;">We use one post code generation script. We do this because we do not want to write code inside the auto-generated code, and this helps with that. If you are on Linux or (possibly, untested but likely) mac, you can refer to the script in the cubeMX software by going into Project Manager -&gt; Code Generator -&gt; User Actions -&gt; After Code Generation. If you are not on a UNIX based system you have to run it every time after generating code manually from the folder where the file is located. The file is located under scripts and is called post\_code\_generation.bash. For mac, the script is called post\_code\_generator\_mac.bash, because there are some small formatting changes, more explained in Mac Changes. </span>

### Functions

The script has 4 different functions

#### Renaming main files

It starts by renaming all main.h and main.c files that are in components. It also saved all boards that have main.c files, because those are the newly generated boards. This way you don't do certain actions on the files twice, if you would run the script again.

```bash
while IFS= read -r FILE; do
  # Extract the basename (filename without path)
  base="$(basename "$FILE")"

  if [[ "$base" == "main.c" ]]; then
    
    subdir="${FILE#"$BASE"/}" # Path from the board dir
    main_dir="${subdir%%/*}" # The board dir
    GENERATED_BOARDS+=("$main_dir") # Gets all boards that are generated again, and thus have a main
    
    dir=$(dirname "$FILE") # Get directory of the file
    mv "$FILE" "$dir/cubemx_main.c"
    echo "Renamed $FILE to $dir/cubemx_main.c"
  fi

  if [[ "$base" == "main.h" ]]; then
    dir=$(dirname "$FILE") # Get directory of the file
    mv "$FILE" "$dir/cubemx_main.h"
    echo "Renamed $FILE to $dir/cubemx_main.h"
  fi
done < <(find "$BASE" -type f)
```

#### Adding firmware definitions

Certain constants might have to be set in the main.h files from cubemx. This happens most likely because of the order in which the files are build, but I am not totally sure. However, if you do need to have some constants set in the main.h file, you can at them to any file in the folder called "firmware\_definitions" in the common folder of components. This second code block adds it to the .h file.

```bash
find "$BASE" -type d -name firmware_definitions | while read -r FW_DIR; do
    BOARD_DIR_PATH="$(dirname "$FW_DIR")"
    BOARD_DIR="${BOARD_DIR_PATH#"$BASE"/}"
    if printf '%s\n' "$COMMON_COMPONENT" "${GENERATED_BOARDS[@]}" | grep -Fx "$BOARD_DIR" > /dev/null; then
      BOARD_DIR_PATHS=("$BOARD_DIR_PATH")
    
      if [[ "$BOARD_DIR" == "$COMMON_COMPONENT" ]]; then  
        BOARD_DIR_PATHS=(${GENERATED_BOARDS[@]/#/"$BASE"/})
      fi  
      for BOARD_DIR_PATH in "${BOARD_DIR_PATHS[@]}"; do
      
        CUBEMX_FILE="$BOARD_DIR_PATH/firmware/Core/Inc/cubemx_main.h"

        # Skip if cubemx file does not exist
        [[ -f "$CUBEMX_FILE" ]] || continue

        echo "Appending firmware_definitions to: $CUBEMX_FILE"

        TMP_FILE="$(mktemp)"

        head -n -1 "$CUBEMX_FILE" >> "$TMP_FILE"
    
        echo -e "\n/* ---- START firmware_definitions ---- */\n" >> "$TMP_FILE"
        find "$FW_DIR" -type f -exec cat {} \; >> "$TMP_FILE"
        echo -e "\n/* ---- END firmware_definitions ---- */\n" >> "$TMP_FILE"

        tail -n -1 "$CUBEMX_FILE" >> "$TMP_FILE"



        # 3) Replace original file
        mv "$TMP_FILE" "$CUBEMX_FILE"
      done 
    fi
done
```

#### Adding static wrappers

Some functions generated by cubemx you do need, but they are static so you cannot use them outside of the main file. To still be able to do that, the script adds wrappers for those files.

```bash
find "$BASE" -type f -name "cubemx_main.c" | while read -r FILE; do
  subdir="${FILE#"$BASE"/}" # Path from the board dir
  BOARD_DIR="${subdir%%/*}" # The board dir
  if printf '%s\n' "${GENERATED_BOARDS[@]}" | grep -Fx "$BOARD_DIR" > /dev/null; then
    TMP_FILE="$(mktemp)"
    echo "READING $FILE"
    while read -r line; do
        echo "$line" >> "$TMP_FILE"
        if [[ "$line" =~ ^[[:space:]]*static[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*\([^\)]*\)\;[[:space:]]*$ ]]; then        # Remove 'static' and trailing ';'
          echo "Static function found: $line"
          proto=$(echo "$line" | sed -E 's/^[[:space:]]*static[[:space:]]+//; s/;[[:space:]]*$//')

          # Extract function name
          name=$(echo "$proto" | sed -E 's/.*[[:space:]]+([a-zA-Z_][a-zA-Z0-9_]*)\(.*/\1/')

          # Extract return type
          ret=$(echo "$proto" | sed -E "s/[[:space:]]+$name\(.*//")

          # Extract argument list
          args=$(echo "$proto" | sed -E "s/.*$name\((.*)\)/\1/")

          # Build argument names (remove types)
          call_args=$(echo "$args" | sed -E 's/[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]+//g')
          if [[ "$call_args" == "void" ]]; then 
            call_args=""
          fi

          echo "$ret ${name}_wrapper($args) {" >> "$TMP_FILE"
          if [[ "$ret" == "void" ]]; then
              echo "    $name($call_args);" >> "$TMP_FILE"
          else
              echo "    return $name($call_args);" >> "$TMP_FILE"
          fi
          echo "}" >> "$TMP_FILE"
          echo >> "$TMP_FILE"
          echo "added wrapper for static function ${name} in ${FILE}"
        fi
    done < "$FILE"
    mv "$TMP_FILE" "$FILE"
  fi
done
```

#### Changing the includes

Because of the name change from main.c/h, to cubemx\_main.c/h, the includes are now wrong. This last code block changes all the includes to the right name.

```bash
while IFS= read -r FILE; do
  subdir="${FILE#"$BASE"/}" # Path from the board dir
  BOARD_DIR="${subdir%%/*}" # The board dir
  if printf '%s\n' "${GENERATED_BOARDS[@]}" | grep -Fx "$BOARD_DIR" > /dev/null; then
    sed -i 's/#include "main.h"/#include "cubemx_main.h"/g' "$FILE"
    echo "Updated include in $FILE"
  fi
done < <(grep -rl '#include "main.h"' ../components/)
```

### MAC Changes

<span style="white-space: pre-wrap;">1) Added </span><span style="color: rgb(106, 153, 85);">\#! /usr/bin/env bash</span><span style="white-space: pre-wrap;"> in the first line</span>

<span style="white-space: pre-wrap;">2) changed </span><span style="color: rgb(212, 212, 212); white-space: pre-wrap;">sed </span><span style="color: rgb(206, 145, 120);">-i</span><span style="color: rgb(212, 212, 212); white-space: pre-wrap;"> </span><span style="color: rgb(206, 145, 120);">'s/#include "main.h"/#include "cubemx\_main.h"/g'</span><span style="color: rgb(212, 212, 212); white-space: pre-wrap;"> </span><span style="color: rgb(206, 145, 120);">"$FILE"</span><span style="white-space: pre-wrap;"> </span>

<span style="white-space: pre-wrap;">to </span><span style="color: rgb(212, 212, 212); white-space: pre-wrap;">sed </span><span style="color: rgb(206, 145, 120);">-i</span><span style="color: rgb(212, 212, 212); white-space: pre-wrap;"> </span><span style="color: rgb(206, 145, 120);">''</span><span style="color: rgb(212, 212, 212); white-space: pre-wrap;"> </span><span style="color: rgb(206, 145, 120);">'s/#include "main.h"/#include "cubemx\_main.h"/g'</span><span style="color: rgb(212, 212, 212); white-space: pre-wrap;"> </span><span style="color: rgb(206, 145, 120); white-space: pre-wrap;">"$FILE" </span><span style="color: rgb(0, 0, 0);">because mac uses a different version of sed.</span>

# Simple PIOC

### Introduction

This Python script processes a custom PlatformIO configuration file (`<span class="editor-theme-code">platformio.pioc</span>`<span style="white-space: pre-wrap;">) and generates a standard </span>`<span class="editor-theme-code">platformio.ini</span>`<span style="white-space: pre-wrap;"> file.</span>

It extends PlatformIO’s configuration capabilities by:

- <span style="white-space: pre-wrap;">Supporting </span>**dynamic include paths**<span style="white-space: pre-wrap;"> using glob patterns</span>
- <span style="white-space: pre-wrap;">Extracting </span>**C preprocessor defines**<span style="white-space: pre-wrap;"> from board-specific Makefiles</span>
- <span style="white-space: pre-wrap;">Expanding </span>**custom syntax**<span style="white-space: pre-wrap;"> into valid </span>`<span class="editor-theme-code">build_flags</span>`
- <span style="white-space: pre-wrap;">Resolving </span>**absolute paths**.

### Key Features

#### Custom build\_flags Processing

Supports two types of entries:

- **+&lt;pattern&gt;**: Include directories
- **-&lt;pattern&gt;**: Exclude directories
- Other entries are treated as standard compiler flags

#### Include Path Resolution

<span style="white-space: pre-wrap;">Glob patterns are expanded into directory paths using recursive search. </span>

#### Board-specific C Defines

Defines are extracted from:

```
components/<board>/firmware/Makefile
```

<span style="white-space: pre-wrap;">The script looks for a </span>`<span class="editor-theme-code">C_DEFS</span>`<span style="white-space: pre-wrap;"> section and includes all compiler defines.</span>

<span style="white-space: pre-wrap;">This is done, because cubeMX generates important definitions in the auto-generated makefile. These are not used if we don't copy them to the .ini file. </span>

#### Environment Detection

```
[env:my_board]
```

This determines which board folder is used.

#### Get Absolute Path

<span style="white-space: pre-wrap;">For some functions, like nanopb, you might need the absolute path. There is no way to get in the default platformio.ini file, so that would mean that you would have to hard code it. We do not want that, so we have a placeholder for an absolute path. </span>

The placeholder:

```
${{project_absolute_path}}$
```

<span style="white-space: pre-wrap;">is replaced with the absolute path of the project. </span>

## Workflow

1. <span style="white-space: pre-wrap;">Read </span>`<span class="editor-theme-code">platformio.pioc</span>`
2. Detect environment
3. <span style="white-space: pre-wrap;">Parse </span>`<span class="editor-theme-code">build_flags</span>`
4. Resolve glob patterns
5. Extract C defines
6. <span style="white-space: pre-wrap;">Write </span>`<span class="editor-theme-code">platformio.ini</span>`
7. Replace placeholders

## Example Input

```
[env:my_board]
build_flags =
    +<lib/**>
    -<lib/exclude/**>
    -DDEBUG
```

## Example Output

```
[env:my_board]
build_flags =
    -I lib/module1
    -I lib/module2
    -DDEBUG
    -DDEFINE_FROM_MAKEFILE
```

# .pioc file

## Introduction

<span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">platformio.pioc</span>`<span style="white-space: pre-wrap;"> file is the central configurationhere file used to define build environments, dependencies, compiler flags, and project structure. It uses a format with sections and key-value pairs. It is similar to platformio.ini, which is actually used by platformio, but has some changes to use it easily with our project. For more information, read </span>[Simple PIOC](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/simple-pioc "https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/simple-pioc").

## File Structure

<span style="white-space: pre-wrap;">The configuration is divided into sections such as </span>`<span class="editor-theme-code">[platformio]</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">[env]</span>`<span style="white-space: pre-wrap;">, and environment-specific sections like </span>`<span class="editor-theme-code">[env:network_board]</span>`.

## Core Sections

### \[platformio\]

Defines global project settings.

- `<span class="editor-theme-code">default_envs</span>`: Default environment(s) to build.
- `<span class="editor-theme-code">src_dir</span>`: Source directory.
- `<span class="editor-theme-code">lib_dir</span>`: Library directory.

### \[extra\]

Custom user-defined variables for reuse.

### \[env\]

Base configuration shared across all environments.

## Environment Sections

<span style="white-space: pre-wrap;">Each </span>`<span class="editor-theme-code">[env:<name>]</span>`<span style="white-space: pre-wrap;"> defines a specific build target. These inherit from </span>`<span class="editor-theme-code">[env]</span>`.

## Custom Enhancements

### 1. Glob Patterns in build\_flags

<span style="white-space: pre-wrap;">Unlike standard PlatformIO, this configuration allows glob-style include/exclude patterns directly in </span>`<span class="editor-theme-code">build_flags</span>`<span style="white-space: pre-wrap;"> using </span>`<span class="editor-theme-code">+<...></span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">-<...></span>`<span style="white-space: pre-wrap;"> syntax.</span>

```
build_flags=
    +<components/network_board/**>
    -<components/network_board/firmware/Drivers/**>
```

This enables fine-grained control over which directories are included in compilation.

### 2. Absolute Path Variable

<span style="white-space: pre-wrap;">The variable </span>`<span class="editor-theme-code">${{project_absolute_path}}$</span>`<span style="white-space: pre-wrap;"> expands to the absolute path of the project root.</span>

```
custom_nanopb_project_dir = ${{project_absolute_path}}$/ERC-Protobufs
```

## Source Filtering

`<span class="editor-theme-code">build_src_filter</span>`<span style="white-space: pre-wrap;"> defines which source files are compiled. It supports inclusion (+) and exclusion (-) rules.</span>

```
+<src/${this.__env__}/**/*.c>
-<components/${this.__env__}/firmware/Drivers/*>
```

## Variable Substitution

- `<span class="editor-theme-code">${extra.common_lib_deps}</span>`: Reference shared variables.
- `<span class="editor-theme-code">${this.__env__}</span>`: Current environment name.

## Library Dependencies

External libraries can be defined using Git URLs or registry references.

```
lib_deps = https://github.com/nanopb/nanopb.git#commit
```

## Compiler and Linker Flags

Standard compiler flags are also supported alongside glob patterns.

```
-mthumb
-mfpu=fpv4-sp-d16
-D CONFIG_LOG_LEVEL=LOG_INFO
```

## Example Configuration

```
[env:network_board]
board = nucleo_h753zi
build_flags=
    +<components/network_board/**>
    -<components/network_board/firmware/Drivers/**>
    -mthumb
    -D CONFIG_LOG_LEVEL=LOG_INFO
```

## Compilation

<span style="white-space: pre-wrap;">To use the file, you have to convert it to a </span>`<span class="editor-theme-code">platformio.ini</span>`<span style="white-space: pre-wrap;"> file. You can do that by running </span>

```
python3 simple_pioc.py
```

## Extra information

<span style="white-space: pre-wrap;">If more information is needed, look at the documentation specifically for platformio.ini files. You can find it </span>[here](https://docs.platformio.org/en/latest/projectconf/index.html).

# Packet Dispatcher

# High Level Overview

## **This Page**

1. [Purpose](#bkmrk-1%29-purpose "1) Purpose")
2. [High-level design](#bkmrk-2%29-high-level-design "2) High-level design")
3. [External dependencies](#bkmrk-3%29-external-dependen "3) External dependencies")

---

## **Purpose**

<span style="white-space: pre-wrap;">The packet dispatcher is used to decode protobuf frames. </span>

Application code usually wants:

- **strongly typed**<span style="white-space: pre-wrap;"> decoded payloads</span>
- **one handler**<span style="white-space: pre-wrap;"> per packet type</span>
- **decoupling** between input reception and packet processing

This module solves that by:

1. **receiving** <span style="white-space: pre-wrap;">a raw protobuf </span>`<span class="editor-theme-code">receive_frame</span>`
2. **decoding** <span style="white-space: pre-wrap;">it into </span>`<span class="editor-theme-code">PBEnvelope</span>`
3. **determining** `<span class="editor-theme-code">which_payload</span>`
4. **finding** <span style="white-space: pre-wrap;">the corresponding handler </span>
5. **copying** <span style="white-space: pre-wrap;">the decoded payload </span>**into** <span style="white-space: pre-wrap;">that handler’s (freeRTOS) </span>**queue**
6. <span style="white-space: pre-wrap;">letting a dedicated task </span>**call callback** for this handler

<span style="white-space: pre-wrap;">In short, each packet type gets its own handler callback, queue and task. That makes the system modular and easy to extend, at least conceptually. So the module acts as a bridge between </span>**transport-level bytes** <span style="white-space: pre-wrap;">and </span>**application-level packet handler.**

<p class="callout success"><span style="white-space: pre-wrap;">In practical terms, it is a </span>**decode-and-dispatch layer**<span style="white-space: pre-wrap;"> between an </span>**input source**<span style="white-space: pre-wrap;"> that receives raw bytes and </span>**a set of application handlers**<span style="white-space: pre-wrap;"> that want already-decoded payloads</span></p>

<p class="callout warning"><span style="white-space: pre-wrap;">The implementation has </span>**some assumptions and hazards**<span style="white-space: pre-wrap;"> that absolutely need to be understood before you start messing with its internal structure.</span></p>

---

## **High-level design**

The design has three major parts:

- Global handler registry  
    <span style="white-space: pre-wrap;">The global handler registry contains an </span>**array** <span style="white-space: pre-wrap;">of packet handler tasks (see </span>[packet\_handler\_config\_t](https://bookstack.roboteamtwente.nl/link/229#bkmrk-b.-packet_handler_co)<span style="white-space: pre-wrap;"> ). A packet handler task configures (amongst other things) the callback function for a certain type of packet.</span>  
    <span style="white-space: pre-wrap;">The array of configs is given by the caller at initialization time. This array is stored globally and </span>**used by dispatch logic for packet type lookup**.

<p class="callout info"><span style="white-space: pre-wrap;">What we call a </span>**packet** is a raw protobuf.  
<span style="white-space: pre-wrap;">What we call a </span>**handler** is a (configuration of a) callback function for a specific protobuf/packet.</p>

- One queue &amp; task per packet type  
    <span style="white-space: pre-wrap;">The dispatcher takes each handler configuration and creates </span>**1 FreeRTOS queue** and **1 FreeRTOS task**<span style="white-space: pre-wrap;">. When receiving messages, the dispatcher enqueues decoded payloads into the corresponding queue. </span>**The corresponding task blocks that queue and calls the handler callback** (which saved in the registry).

<p class="callout success"><span style="white-space: pre-wrap;">The task takes the </span>**correspoding** payload out of the queue and calls the specified handler/callback function. By corresponding we mean that each type of packet has their own queue.</p>

- Shared decode step  
    <span style="white-space: pre-wrap;">Incoming frames are decoded into a global static </span>`<span class="editor-theme-code">PBEnvelope</span>`<span style="white-space: pre-wrap;"> object: </span>`<span class="editor-theme-code">static PBEnvelope DecodingEnvelopeCurrent;</span>`.  
    <span style="white-space: pre-wrap;">The dispatcher then copies </span>`<span class="editor-theme-code">DecodingEnvelopeCurrent.payload</span>`<span style="white-space: pre-wrap;"> into a handler queue. </span>**This detail matters a lot for concurrency and payload sizing.**

#### NOTE on handler task lifecycles

<p class="callout danger">Each handler task is intended to live forever.</p>

<span style="white-space: pre-wrap;">A task is responsible for passing a specific packet type from the corresponding queue to the correct callback. As stated above, a handler task </span>**gets created by the dispatcher** <span style="white-space: pre-wrap;">according to the configuration (see </span>[packet\_handler\_config\_t](https://bookstack.roboteamtwente.nl/link/229#bkmrk-b.-packet_handler_co)<span style="white-space: pre-wrap;"> ) done by the caller when initializing the dispatcher.</span>

##### Lifecycle

1. **Created by** `<span class="editor-theme-code">PacketHandlerStart()</span>`<span style="white-space: pre-wrap;"> </span>  
    <span style="white-space: pre-wrap;">As part of </span>[PacketDispatcherInit()](#bkmrk-c.-packetdispatcheri "c. PacketDispatcherInit()").
2. **Validate configuration**  
    <span style="white-space: pre-wrap;">Task\_name, handler and queue need to be present for it to work. These params are set in </span>[packet\_handler\_config\_t](https://bookstack.roboteamtwente.nl/link/229#bkmrk-b.-packet_handler_co)<span style="white-space: pre-wrap;">. If you use the </span>[macros](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/helper-macros-for-handler-config "Helper Macros for Static Handler Config"), this should be fine.
3. **Allocate local packet buffer**
4. **Block forever on queue receive**  
    So, when we receive a packet in the corresponding queue, we wait for it to be handled.
5. **Process packets as they arrive**  
    The processing is done by the callback specified in the handler.

##### Terminates only if...

- <span style="white-space: pre-wrap;">config is </span>**invalid**
- <span style="white-space: pre-wrap;">queue is </span>**null**
- <span style="white-space: pre-wrap;">heap </span>**allocation fails** <span style="white-space: pre-wrap;">for packet buffer </span>

In those cases it deletes itself.

<p class="callout danger">At the moment, there is no restart or supervision mechanism in this module!</p>

---

## **External dependencies**

<p class="callout warning">This is not a standalone module. It sits in the middle of RTOS tasking, protobuf decoding, and transport reception.</p>

This module depends on:

<table id="bkmrk-specifically-used-pi"><colgroup><col style="width: 303px;"></col><col style="width: 279px;"></col></colgroup><tbody><tr><td></td><td>**specifically used pieces**

</td></tr><tr><td>FreeRTOS

</td><td>- `<span class="editor-theme-code">xQueueCreateStatic</span>`
- `<span class="editor-theme-code">xQueueReceive</span>`
- `<span class="editor-theme-code">xQueueSend</span>`
- `<span class="editor-theme-code">xTaskCreate</span>`
- `<span class="editor-theme-code">vTaskDelete</span>`

</td></tr><tr><td>nanopb / protobuf decoding

</td><td>- `<span class="editor-theme-code">pb_istream_from_buffer</span>`
- `<span class="editor-theme-code">pb_decode</span>`

</td></tr><tr><td>`<span class="editor-theme-code">PBEnvelope</span>`<span style="white-space: pre-wrap;"> generated protobuf definitions</span>

</td><td>- `<span class="editor-theme-code">PBEnvelope_fields</span>`
- `<span class="editor-theme-code">PBEnvelope_size</span>`

</td></tr><tr><td>[Logging library](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/logging "Logging")

</td><td></td></tr><tr><td>[Result Library](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/result-library "Result Library")

</td><td></td></tr><tr><td>`<span class="editor-theme-code">stm/ethernet_udp.h</span>`

</td><td>- `<span class="editor-theme-code">receive_frame</span>`

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

# Functions of the Packet Dispatcher

## **Public API**

<span style="white-space: pre-wrap;">The following functions are available for the boards to use </span>**outside of**<span style="white-space: pre-wrap;"> the library.</span>

<span style="white-space: pre-wrap;">The public API consists of: </span>`<span class="editor-theme-code">packet_handler_t</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">packet_handler_config_t</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">PacketDispatcherInit(...)</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">DispatchPacket()</span>`  
<span style="white-space: pre-wrap;">There are also stack depth macros: </span>`<span class="editor-theme-code">PACKET_HANDLER_TASK_STACK_DEPTH_DEFAULT</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">PACKET_DISPATCHER_TASK_STACK_DEPTH</span>`

### 1) Stack depth macros

<p class="callout warning">**NOTE:** `<span class="editor-theme-code">PACKET_DISPATCHER_TASK_STACK_DEPTH</span>`<span style="white-space: pre-wrap;"> is currently defined but not actually used in the provided implementation!</span></p>

```none
#define PACKET_HANDLER_TASK_STACK_DEPTH_DEFAULT ((configSTACK_DEPTH_TYPE)512U)
#define PACKET_DISPATCHER_TASK_STACK_DEPTH ((configSTACK_DEPTH_TYPE)1024U)
```

---

### 2) packet\_handler\_t (callback)

```c
typedef result_t (*packet_handler_t)(void* buffer);
```

<span style="white-space: pre-wrap;">This type represents the </span>**callback function** <span style="white-space: pre-wrap;">invoked by a </span>**handler task**<span style="white-space: pre-wrap;"> when a packet of its type is received.</span>

#### Parameters

- `<span class="editor-theme-code">buffer</span>`

<span style="white-space: pre-wrap;">Pointer to the </span>**decoded packet payload** <span style="white-space: pre-wrap;">copied from the queue. The actual type of </span>`<span class="editor-theme-code">buffer</span>`<span style="white-space: pre-wrap;"> depends on the registered </span>`<span class="editor-theme-code">packet_type</span>`<span style="white-space: pre-wrap;"> in the config for the handler (see </span>[packet\_handler\_config\_t](#bkmrk-b.-packet_handler_co "b. packet_handler_config_t (struct)")).

<span style="white-space: pre-wrap;">For example, if a handler is registered for one specific protobuf payload type, the handler should cast </span>`<span class="editor-theme-code">buffer</span>`<span style="white-space: pre-wrap;"> to the corresponding generated struct type.</span>

<details id="bkmrk-examplestatic-result"><summary>Example</summary>

```c
static result_t Callback_ArmBoardControlSignals(void *buffer) {
    ArmBoardControlSignals* pckt = (ArmBoardControlSignals *)buffer;
    }
```

</details>##### Note on buffer typecasting

<p class="callout warning"><span style="white-space: pre-wrap;">The callback receives only a raw </span>`<span class="editor-theme-code">void *</span>`<span style="white-space: pre-wrap;">. That means type safety is </span>**entirely dependent on correct configuration!**</p>

- `<span class="editor-theme-code">packet_type</span>`<span style="white-space: pre-wrap;"> must match the actual protobuf payload member</span>
- `<span class="editor-theme-code">item_size</span>`<span style="white-space: pre-wrap;"> must match the size of that decoded payload type</span>
- <span style="white-space: pre-wrap;">handler must cast </span>`<span class="editor-theme-code">buffer</span>`<span style="white-space: pre-wrap;"> to the correct struct type</span>

If any of those mismatch, the code may compile while quietly doing something stupid (and it will be your fault :D).

#### Return value

<span style="white-space: pre-wrap;">Returns </span>`<span class="editor-theme-code">result_t</span>`<span style="white-space: pre-wrap;">. The handler task logs a warning if the return value is not </span>`<span class="editor-theme-code">RESULT_OK</span>`.

---

### 3) packet\_handler\_config\_t (struct)

<p class="callout info">**NOTE:**<span style="white-space: pre-wrap;"> there exist macros to make the configuration easier! </span>**See:**<span style="white-space: pre-wrap;"> </span>[Helper Macros for Static Handler Config](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/helper-macros-for-handler-config "Helper Macros for Static Handler Config")</p>

```c
typedef struct {
    packet_handler_t handler;
    const char* task_name;
    pb_size_t packet_type;

    UBaseType_t task_priority;
    configSTACK_DEPTH_TYPE task_stack_depth;

    size_t item_size;
    UBaseType_t queue_length;

    uint8_t* queue_buffer;
    StaticQueue_t queue_struct;
    QueueHandle_t queue;
} packet_handler_config_t;
```

#### Purpose

<span style="white-space: pre-wrap;">Describes one packet type and the task/queue resources needed to process it. </span>**Each entry in the handler config array** <span style="white-space: pre-wrap;">(passed to </span>[PacketDispatcherInit(...)](#bkmrk-c.-packetdispatcheri "c. PacketDispatcherInit(...)")) **corresponds to one routed packet type!**

#### Fields

- `<span class="editor-theme-code">handler</span>`  
    <span style="white-space: pre-wrap;">Callback invoked when a packet of this type is received. Must not be </span>`<span class="editor-theme-code">NULL</span>`.
- `<span class="editor-theme-code">task_name</span>`  
    <span style="white-space: pre-wrap;">Name used when creating the FreeRTOS task. Must not be </span>`<span class="editor-theme-code">NULL</span>`.
- `<span class="editor-theme-code">packet_type</span>`  
    <span style="white-space: pre-wrap;">The protobuf discriminator value to match against </span>`<span class="editor-theme-code">DecodingEnvelopeCurrent.which_payload</span>`, which is the routing key.
- `<span class="editor-theme-code">task_priority</span>`  
    Priority of the FreeRTOS handler task. If set to zero, that is still a valid FreeRTOS priority value. There is no separate “unset” semantic here.
- `<span class="editor-theme-code">task_stack_depth</span>`  
    Stack depth for the handler task.  
    <span style="white-space: pre-wrap;">If </span>`<span class="editor-theme-code"><= 0</span>`<span style="white-space: pre-wrap;">, the implementation replaces it with: </span>`<span class="editor-theme-code">PACKET_HANDLER_TASK_STACK_DEPTH_DEFAULT</span>`<span style="white-space: pre-wrap;">. Since this type is typically unsigned, the </span>`<span class="editor-theme-code"><= 0</span>`<span style="white-space: pre-wrap;"> check effectively means “zero” in practice.</span>
- `<span class="editor-theme-code">item_size</span>`  
    <span style="white-space: pre-wrap;">Size of </span>**one** <span style="white-space: pre-wrap;">queued item. </span>

<p class="callout warning">This must match the size of the decoded payload type copied into the queue.</p>

- `<span class="editor-theme-code">queue_length</span>`  
    Number of items the queue can hold.
- `<span class="editor-theme-code">queue_buffer</span>`  
    Backing storage for static queue data.  
    <span style="white-space: pre-wrap;">Must be large enough for </span>`<span class="editor-theme-code">queue_length * item_size</span>`
- `<span class="editor-theme-code">queue_struct</span>`  
    <span style="white-space: pre-wrap;">Static queue control structure used internally by </span>`<span class="editor-theme-code">xQueueCreateStatic()</span>`. Caller provides storage but should not manually initialize runtime content.
- `<span class="editor-theme-code">queue</span>`  
    Queue handle written internally during initialization.

<p class="callout danger">Caller should not pre-fill it!</p>

---

### 4) PacketDispatcherInit(...)

```c
result_t PacketDispatcherInit(packet_handler_config_t* handlers,
                              size_t handler_count);
```

**Initializes the dispatcher**<span style="white-space: pre-wrap;"> by...</span>

- storing the handler registry
- <span style="white-space: pre-wrap;">creating </span>**one queue**<span style="white-space: pre-wrap;"> and </span>**one task**<span style="white-space: pre-wrap;"> per handler entry</span>

#### Parameters

- `<span class="editor-theme-code">handlers</span>`  
    <span style="white-space: pre-wrap;">Pointer to an array of handler configurations (see above: </span>[packet\_handler\_config\_t](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/packet-dispatcher#bkmrk-b.-packet_handler_co "b. (struct) packet_handler_config_t")). The implementation stores a global pointer to it and passes individual entries to tasks.
- `<span class="editor-theme-code">handler_count</span>`  
    Number of entries in the array.

<p class="callout danger">**IMPORTANT:**<span style="white-space: pre-wrap;"> The </span>`<span class="editor-theme-code">handlers</span>`<span style="white-space: pre-wrap;"> array </span>**must remain valid for the full lifetime**<span style="white-space: pre-wrap;"> of the system. Do </span>**NOT** allocate this array on a temporary stack frame unless you are into being abused by segfaults :)</p>

---

### 5) DispatchPacket(...)

```c
void DispatchPacket(receive_frame* incoming_packet);
```

**Decodes**<span style="white-space: pre-wrap;"> one incoming raw frame and </span>**routes** its decoded payload to the appropriate handler queue.

##### Internal functioning

1. validates basic frame properties
2. creates a nanopb input stream from the raw bytes
3. <span style="white-space: pre-wrap;">decodes into the global static </span>`<span class="editor-theme-code">DecodingEnvelopeCurrent</span>`
4. scans the registered handler list
5. <span style="white-space: pre-wrap;">finds the first handler whose </span>`<span class="editor-theme-code">packet_type</span>`<span style="white-space: pre-wrap;"> matches </span>`<span class="editor-theme-code">which_payload</span>`
6. <span style="white-space: pre-wrap;">sends </span>`<span class="editor-theme-code">DecodingEnvelopeCurrent.payload</span>`<span style="white-space: pre-wrap;"> to that handler’s queue</span>
7. returns

If no matching handler is found, it logs a warning. If decode fails, it logs an error.

<p class="callout danger">**NOTE:**<span style="white-space: pre-wrap;"> This function returns </span>`<span class="editor-theme-code">void</span>`, so dispatch failure is **only observable through logs**.</p>

#### Parameters

- `<span class="editor-theme-code">incoming_packet</span>`  
    <span style="white-space: pre-wrap;">Pointer to a transport frame containing </span>`<span class="editor-theme-code">payload</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">len</span>`<span style="white-space: pre-wrap;"> of the incoming packet.</span>

---

## **Internal (private) task model**

### `<span class="editor-theme-code">PacketHandlerTask()</span>`

<p class="callout info"><span style="white-space: pre-wrap;">Also see </span>[note on handler task lifecycles](https://bookstack.roboteamtwente.nl/link/228#bkmrk-note-on-handler-task)<span style="white-space: pre-wrap;"> !</span></p>

Each handler config gets its own task (and corresponding queue, remember ladies?) running this loop:

1. validate config and resources
2. <span style="white-space: pre-wrap;">allocate one packet buffer using </span>`<span class="editor-theme-code">malloc(conf->item_size)</span>`
3. <span style="white-space: pre-wrap;">block forever on </span>`<span class="editor-theme-code">xQueueReceive()</span>`
4. when a packet arrives:
    - <span style="white-space: pre-wrap;">call </span>`<span class="editor-theme-code">conf->handler(packet_buffer)</span>`
    - log if handler returns error

#### Purpose of per-task buffer

<span style="white-space: pre-wrap;">The queue copies incoming items into the task’s local </span>`<span class="editor-theme-code">packet_buffer</span>`<span style="white-space: pre-wrap;">. That means the handler callback receives a stable task-local buffer for the duration of the callback. The callback does </span>**not**<span style="white-space: pre-wrap;"> receive a pointer directly into the global decode object.</span>

<p class="callout warning"><span style="white-space: pre-wrap;">The task allocates its buffer dynamically with </span>`<span class="editor-theme-code">malloc()</span>`<span style="white-space: pre-wrap;"> once at startup and never frees it, because the task is intended to live forever.</span></p>

---

## **Macros**

<p class="callout info"><span style="white-space: pre-wrap;">There exist macros to make the configuration of a handler easier! See: </span>[Helper Macros for Static Handler Config](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/helper-macros-for-handler-config "Helper Macros for Static Handler Config").</p>

##   

# Helper Macros for Handler Config

## **Purpose**

<span style="white-space: pre-wrap;">To reduce repetitive boilerplate when defining packet handlers, the module also provides a set of helper macros in </span>`<span class="editor-theme-code">packet_dispatcher_macros.h</span>`.

These macros generate:

- a statically allocated queue buffer
- <span style="white-space: pre-wrap;">a fully initialized </span>`<span class="editor-theme-code">packet_handler_config_t</span>`

<p class="callout info"><span style="white-space: pre-wrap;">They are especially useful because they automatically derive the correct queue item size from the selected </span>`<span class="editor-theme-code">PBEnvelope</span>`<span style="white-space: pre-wrap;"> payload member, which helps avoid one of the easiest mistakes in this module: mismatching </span>`<span class="editor-theme-code">item_size</span>`<span style="white-space: pre-wrap;"> with the actual decoded protobuf payload type.</span></p>

### Why these macros are useful

Without these macros, every handler config has to manually specify:

- queue storage buffer
- queue length
- item size
- task name
- default priority
- default stack depth
- queue initialization fields

That is tedious and error-prone.

#### <span style="white-space: pre-wrap;">I) They derive </span>`<span class="editor-theme-code">item_size</span>`<span style="white-space: pre-wrap;"> automatically</span>

<span style="white-space: pre-wrap;">Each macro uses: </span>`<span class="editor-theme-code">sizeof(((PBEnvelope*)0)->payload.payload_member)</span>`<span style="white-space: pre-wrap;"> to compute the exact size of the selected envelope payload member at compile time. This removes the need to manually write </span>`<span class="editor-theme-code">.item_size = sizeof(MyPayloadType)</span>`<span style="white-space: pre-wrap;"> and reduces the chance of queue item size mismatches.</span>

#### II) They allocate queue storage automatically

Each macro also declares:

```c
static uint8_t name##_queue_buffer[...];
```

with the correct total size based on:

- payload member size
- selected queue length

So the queue backing storage is generated alongside the config object.

### Important consequence of these macros

<p class="callout info"><span style="white-space: pre-wrap;">These macros define </span>**static objects**.</p>

That means each use creates:

- a static queue buffer
- <span style="white-space: pre-wrap;">a static </span>`<span class="editor-theme-code">packet_handler_config_t</span>`

<p class="callout info">This is generally what you want for a dispatcher configuration that should live for the full lifetime of the system.</p>

It also means:

- they should normally be used at file scope
- <span style="white-space: pre-wrap;">using the same </span>`<span class="editor-theme-code">name</span>`<span style="white-space: pre-wrap;"> twice in one translation unit will cause symbol redefinition</span>
- they are not runtime factory macros, they are compile-time object definition helpers

---

## **Shared Functionality**

<span style="white-space: pre-wrap;">For </span>**all** of these macros, the generated config uses:

```c
#define PACKET_HANDLER_CONFIG_STATIC(name, packet_tag, payload_member_size, handler_fn)

.handler = (handler_fn)
.task_name = #name
.packet_type = (packet_tag)
.item_size = payload_member_size
.queue_buffer = name##_queue_buffer
.queue_struct = {0}
.queue = NULL
```

This is helpful for two reasons:

- `<span class="editor-theme-code">task_name</span>`<span style="white-space: pre-wrap;"> is automatic</span>  
    The task name becomes **the same as the symbol (handler config itself) name**, which keeps config definitions compact and readable.
- Queue internals are initialized consistently  
    <span style="white-space: pre-wrap;">The queue control structure is zero-initialized, and the runtime queue handle starts as </span>`<span class="editor-theme-code">NULL</span>`, matching the expectations of the dispatcher startup code.

### <span style="white-space: pre-wrap;">IMPORTANT NOTE on </span>`<span class="editor-theme-code">payload_member</span>`

<p class="callout info"><span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">payload_member</span>`<span style="white-space: pre-wrap;"> argument is not the packet type name. It is the </span>**member name inside** `<span class="editor-theme-code">PBEnvelope.payload</span>`**!**</p>

<span style="white-space: pre-wrap;">This matters because the macros compute size using direct member access syntax: </span>`<span class="editor-theme-code">sizeof( ((PBEnvelope*)0) -> payload.payload_member)</span>`<span style="white-space: pre-wrap;">. </span>**So, if the wrong member name is used, compilation will fail, which is actually helpful for once.**

**The member names are defined in** `<span class="editor-theme-code">envelope.pb.h</span>`<span style="white-space: pre-wrap;"> </span>**.**  
<span style="white-space: pre-wrap;">For example, currently </span>`<span class="editor-theme-code">envelope.pb.h</span>`<span style="white-space: pre-wrap;"> contains the following:</span>

```c
typedef struct _PBEnvelope {
    pb_size_t which_payload;
  
    union _PBEnvelope_payload {
            /* Sensorboard messages */
            SensorBoardPHInfo ph_info;
            
            /* Armboard messages */
            ArmBoardControlSignals arm_ctrl;
            ArmBoardDiagnostics arm_diag;
    
            //etc etc...
    }
}
```

<p class="callout success"><span style="white-space: pre-wrap;">So, the macro must be called with the member name </span>**matching the rest of the config**<span style="white-space: pre-wrap;">, such as </span>`<span class="editor-theme-code">ph_info</span>`<span style="white-space: pre-wrap;"> or </span>`<span class="editor-theme-code">arm_ctrl</span>`<span style="white-space: pre-wrap;"> and </span>**NOT** the protobuf struct type name!</p>

---

## **Available macros**

### 1) Default configuration macros

The header defines these default values:

```none
#define PACKET_HANDLER_DEFAULT_PRIORITY (tskIDLE_PRIORITY + 2U)
#define PACKET_HANDLER_DEFAULT_QUEUE_LENGTH (5U)
#define PACKET_HANDLER_DEFAULT_STACK_DEPTH (0U)
```

- `<span class="editor-theme-code">PACKET_HANDLER_DEFAULT_PRIORITY</span>`  
    Default FreeRTOS task priority assigned to handler tasks created with the simpler macros.
- `<span class="editor-theme-code">PACKET_HANDLER_DEFAULT_QUEUE_LENGTH</span>`  
    Default number of queued packets per handler.
- `<span class="editor-theme-code">PACKET_HANDLER_DEFAULT_STACK_DEPTH</span>`  
    Default stack depth field stored in the config.  
    <span style="white-space: pre-wrap;">A value of </span>`<span class="editor-theme-code">0U</span>`<span style="white-space: pre-wrap;"> is intentional here. In the dispatcher implementation, a task stack depth of zero is treated as “use the dispatcher default,” which becomes: </span>`<span class="editor-theme-code">PACKET_HANDLER_TASK_STACK_DEPTH_DEFAULT</span>`<span style="white-space: pre-wrap;">. So this macro does </span>**not**<span style="white-space: pre-wrap;"> mean “zero stack.” It means “defer to the runtime default chosen by the dispatcher.”</span>

---

### <span style="white-space: pre-wrap;">2) Basic config: </span>`<span class="editor-theme-code">PACKET_HANDLER_CONFIG_STATIC</span>`

```
#define PACKET_HANDLER_CONFIG_STATIC(name, packet_tag, payload_member_size, handler_fn)
```

This is the simplest form. Creates a handler config using:

- **default** priority
- **default** queue length
- **default** stack depth behaviour

##### Parameters

- `<span class="editor-theme-code">name</span>`  
    User defined name, go crazy.
- `<span class="editor-theme-code">packet_tag</span>`  
    <span style="white-space: pre-wrap;">This is the Nanopb generated tag for the packet type. They follow the pattern </span>`<span class="editor-theme-code">PBEnvelope_[</span>`*`<em class="editor-theme-code editor-theme-italic">payload_member</em>`*`<span class="editor-theme-code">]_tag</span>`. So for example: PBEnvelope\_arm\_ctrl\_tag
- `<span class="editor-theme-code">payload_member</span>`  
    <span style="white-space: pre-wrap;">See </span>[important note on payload\_member](#header-3a64 "IMPORTANT NOTE on payload_member")<span style="white-space: pre-wrap;">. </span>**Needs to match the packet\_tag and the buffer type the callback is specified for!**
- `<span class="editor-theme-code">handler_fn</span>`  
    <span style="white-space: pre-wrap;">Callback function. Type signature </span>[packet\_handler\_t](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/functions-of-the-packet-dispatcher#bkmrk-a.-packet_handler_t- "Functions of the packet dispatcher").

#####   


##### Example

```c
/* Config for: ArmBoardMovementFeedback */

//Define the callback function with the specified signature
static result_t Callback_ArmBoardMovementFeedback(void *buffer) {
  if (buffer == NULL) {
    return RESULT_ERR_INVALID_ARG;
  }

  //Retreive the packet
  ArmBoardMovementFeedback* pckt = (ArmBoardMovementFeedback *)buffer;
  //Get all fields
  pckt->arm_error; 

  /*
  Go wild...
  */
  return RESULT_OK;
}

PACKET_HANDLER_CONFIG_STATIC(
  Handler_ArmBoardMovementFeedback,   // NOTE: This name is USER DEFINED, let your imagination run
  PBEnvelope_arm_feedback_tag,        //  Make sure these...
  arm_feedback,                       //                   ... MATCH!
  Callback_ArmBoardMovementFeedback); // Callback as above
```

---

### <span style="white-space: pre-wrap;">3) Full config: </span>`<span class="editor-theme-code">PACKET_HANDLER_CONFIG_STATIC_EX</span>`

```none
#define PACKET_HANDLER_CONFIG_STATIC_EX(name, packet_tag, payload_member, handler_fn, 
                                        priority_, stack_depth_, queue_length_)
```

Full explicit version. Lets you set:

- name, packet\_tag, payload\_member, handler\_fn as above
- **custom** priority
- **custom** stack depth
- **custom** queue length

##### Best used when

- the handler needs a non-default stack size
- you want fully explicit resource configuration

##### Example

```c
PACKET_HANDLER_CONFIG_STATIC_EX(vision_handler_cfg,
                                PBEnvelope_detected_object_tag,
                                detected_object,
                                handle_detected_object,
                                tskIDLE_PRIORITY + 3U,
                                768U,
                                16U);
```

---

###   


<details id="bkmrk-these-r-not-in-the-c"><summary>these r not in the code lol</summary>

begin here

### <span style="white-space: pre-wrap;">II) </span>`<span class="editor-theme-code">PACKET_HANDLER_CONFIG_STATIC_QUEUE</span>`

```c
#define PACKET_HANDLER_CONFIG_STATIC_QUEUE(name, packet_tag, payload_member_size, handler_fn, queue_length_)
```

Same as the basic macro, but lets you override queue length.

##### Best used when

- handler needs a longer or shorter queue
- default priority is still fine

##### Example

```c
PACKET_HANDLER_CONFIG_STATIC_QUEUE(sensor_handler_cfg,
                                   PBEnvelope_sensor_diag_tag,
                                   sensor_diag,
                                   handle_sensor_diag,
                                   12);
```

---

### <span style="white-space: pre-wrap;">III) </span>`<span class="editor-theme-code">PACKET_HANDLER_CONFIG_STATIC_PRIO</span>`

```c
#define PACKET_HANDLER_CONFIG_STATIC_PRIO(name, packet_tag, payload_member, handler_fn, priority_)
```

Same as the basic macro, but lets you override task priority.

##### Best used when

- one handler must run at a different RTOS priority
- default queue length is still fine

##### Example

```c
PACKET_HANDLER_CONFIG_STATIC_PRIO(emergency_handler_cfg,
                                  PBEnvelope_arm_obstructions_tag,
                                  arm_obstructions,
                                  handle_arm_obstructions,
                                  tskIDLE_PRIORITY + 4U);
```

---

### <span style="white-space: pre-wrap;">IV) </span>`<span class="editor-theme-code">PACKET_HANDLER_CONFIG_STATIC_PRIO_QUEUE</span>`

```c
#define PACKET_HANDLER_CONFIG_STATIC_PRIO_QUEUE(    name, packet_tag, payload_member, handler_fn, queue_length_, priority_)
```

Lets you override both:

- queue length
- task priority

##### Best used when

- a handler has non-default scheduling needs
- and also non-default backlog requirements

##### Example

```c
PACKET_HANDLER_CONFIG_STATIC_PRIO_QUEUE(nav_handler_cfg,
                                        PBEnvelope_ph_info_tag,
                                        ph_info,
                                        handle_ph_info,
                                        10,
                                        tskIDLE_PRIORITY + 3U);
```

---

end here

</details>

# Recommended Usage Pattern

<p class="callout info"><span style="white-space: pre-wrap;">More information on the mentioned steps can be found in </span>[Functions of the Packet Dispatcher](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/functions-of-the-packet-dispatcher "Functions of the Packet Dispatcher")</p>

---

## **Typical Usage Model**

### Intended setup

1. <span style="white-space: pre-wrap;">Define one </span>[handler function](https://bookstack.roboteamtwente.nl/link/229#bkmrk-2%29-packet_handler_t-)<span style="white-space: pre-wrap;"> per packet type</span>
2. <span style="white-space: pre-wrap;">define one </span>[packet\_handler\_config\_t](https://bookstack.roboteamtwente.nl/link/229#bkmrk-2%29-packet_handler_co)<span style="white-space: pre-wrap;"> entry per packet type (using the </span>[macros](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/helper-macros-for-handler-config "Helper Macros for Handler Config"))
3. provide queue storage buffers  
    <span style="white-space: pre-wrap;">(When using the </span>[macros](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/helper-macros-for-handler-config), you do not need to do this manually)
4. <span style="white-space: pre-wrap;">call </span>[PacketDispatcherInit(...)](https://bookstack.roboteamtwente.nl/link/229#bkmrk-4%29-packetdispatcheri)
5. <span style="white-space: pre-wrap;">whenever a frame arrives, call </span>[DispatchPacket()](https://bookstack.roboteamtwente.nl/link/229#bkmrk-5%29-dispatchpacket%28..)

### Flow after setup

1. Ethernet/UDP receives raw frame
2. <span style="white-space: pre-wrap;">networking code builds </span>`<span class="editor-theme-code">receive_frame</span>`
3. `<span class="editor-theme-code">DispatchPacket()</span>`<span style="white-space: pre-wrap;"> decodes it</span>
4. payload type is matched
5. decoded payload is copied into target queue
6. matching handler task wakes
7. the callback processes typed payload

---

## **IMPORTANT configuration rules**

<p class="callout danger">This module is heavily configuration-driven. Several things must match exactly.</p>

#### <span style="white-space: pre-wrap;">I. </span>`<span class="editor-theme-code">packet_type</span>`<span style="white-space: pre-wrap;"> must match the protobuf discriminator</span>

<span style="white-space: pre-wrap;">Each handler’s </span>`<span class="editor-theme-code">packet_type</span>`<span style="white-space: pre-wrap;"> must be the exact value used by </span>`<span class="editor-theme-code">PBEnvelope.which_payload</span>`. If this is wrong, packets will never reach that handler.

#### <span style="white-space: pre-wrap;">II. </span>`<span class="editor-theme-code">item_size</span>`<span style="white-space: pre-wrap;"> must match the decoded payload type</span>

<span style="white-space: pre-wrap;">The queue copies bytes from </span>`<span class="editor-theme-code">&DecodingEnvelopeCurrent.payload</span>`<span style="white-space: pre-wrap;"> into a queue item of size </span>`<span class="editor-theme-code">item_size</span>`.

<span style="white-space: pre-wrap;">If </span>`<span class="editor-theme-code">item_size</span>`<span style="white-space: pre-wrap;"> is:</span>

- too small -&gt; payload will be truncated
- too large -&gt; copied data may include unrelated union bytes or layout assumptions
- wrong type entirely -&gt; handler sees garbage with confidence

#### <span style="white-space: pre-wrap;">III. </span>`<span class="editor-theme-code">queue_buffer</span>`<span style="white-space: pre-wrap;"> must be sized correctly</span>

<span style="white-space: pre-wrap;">The backing storage must be at least: </span>`<span class="editor-theme-code">queue_length * item_size</span>`<span style="white-space: pre-wrap;">. </span>**If not, queue creation or runtime behavior is invalid.**

#### <span style="white-space: pre-wrap;">IV. Handler must cast </span>`<span class="editor-theme-code">void *</span>`<span style="white-space: pre-wrap;"> correctly</span>

The callback receives a raw buffer pointer. It must cast to the correct generated protobuf type.

#### V. Handlers array must be an array of structs

<span style="white-space: pre-wrap;">The current </span>`<span class="editor-theme-code">PacketDispatcherInit()</span>`<span style="white-space: pre-wrap;"> API expects:</span>

```c
packet_handler_config_t* handlers
```

meaning a contiguous array of structs, not an array of pointers.

So with the current implementation, the final array should actually be:

```c
static packet_handler_config_t* handlers[] = {
    drive_handler_cfg,
    sensor_diag_handler_cfg,
};
```

NOT an array of pointers.

---

## **Examples**

### 1) Using macros

```c
//Imports
#include "packet_dispatcher.h"
#include "packet_dispatcher_macros.h"

/*Define handler callbacks*/
//Callback for protobuf of type ArmBoardMovementFeedback
static result_t Callback_ArmBoardMovementFeedback(void *buffer) {  
  if (buffer == NULL) {
        return RESULT_ERR_INVALID_ARG;
    }
  
  ArmBoardMovementFeedback* pckt = (ArmBoardMovementFeedback *)buffer; //Retreive the packet
  pckt->arm_error; //Get fields of protobuf
  //Do something...

  return RESULT_OK;
}

//Config using most basic macro
PACKET_HANDLER_CONFIG_STATIC(Handler_ArmBoardMovementFeedback, PBEnvelope_arm_feedback_tag, arm_feedback, Callback_ArmBoardMovementFeedback); 

//Callback for protobuf of type ArmBoardControlSignals
static result_t Callback_ArmBoardControlSignals(void *buffer) {
  if (buffer == NULL) {
        return RESULT_ERR_INVALID_ARG;
    }
  
    ArmBoardControlSignals* pckt = (ArmBoardControlSignals *)buffer;
    pckt->control_base; //Get fields of protobuf
    pckt->control_gripper_pitch; 
    //... etc etc
    //Do something...
  
    return RESULT_OK;
}

//Config using most basic macro
PACKET_HANDLER_CONFIG_STATIC(Handler_ArmBoardControlSignals, PBEnvelope_arm_ctrl_tag, arm_ctrl, Callback_ArmBoardControlSignals);

//Add configs to the list of configs
static packet_handler_config_t* handlers[] = {Handler_ArmBoardMovementFeedback, Handler_ArmBoardControlSignals};

//HERE WE PUT ETH_init(...) and the creation of queues from the networking board
//See respective documentation

PacketDispatcherInit(handlers, 2);
ETH_udp_init(2, queues, DispatchPacket); //Passing DispatchPacket to ETH_udp_init makes sure it gets called upon receiving msgs

//Once again, after this we can use networking and do ETH_add_arp(...) and ETH_udp_send(...)
```

---

### 2) Manual configuration

```c
//Imports
#include "packet_dispatcher.h"

static result_t handle_drive_cmd(void* buffer) {
    PBDriveCommand* msg = (PBDriveCommand*)buffer;
    return drive_process(msg);
}

static result_t handle_arm_cmd(void* buffer) {
    PBArmCommand* msg = (PBArmCommand*)buffer;
    return arm_process(msg);
}

static uint8_t drive_queue_storage[8 * sizeof(PBDriveCommand)];
static uint8_t arm_queue_storage[4 * sizeof(PBArmCommand)];

static packet_handler_config_t handlers[] = {
    {
        .handler = handle_drive_cmd,
        .task_name = "drive_pkt",
        .packet_type = PBEnvelope_drive_cmd_tag,
        .task_priority = 3,
        .task_stack_depth = 512,
        .item_size = sizeof(PBDriveCommand),
        .queue_length = 8,
        .queue_buffer = drive_queue_storage,
    },
    {
        .handler = handle_arm_cmd,
        .task_name = "arm_pkt",
        .packet_type = PBEnvelope_arm_cmd_tag,
        .task_priority = 3,
        .task_stack_depth = 512,
        .item_size = sizeof(PBArmCommand),
        .queue_length = 4,
        .queue_buffer = arm_queue_storage,
    },
};
```

Then during startup:

```c
result_t res = PacketDispatcherInit(handlers, ARRAY_LEN(handlers));
```

And during frame reception:

```c
DispatchPacket(&rx_frame);
```

####

# Embedded Common Libraries

All Common Libraries used by all Microcontrollers in the Rover

# Overview

This page gives a high-level overview of the shared libraries described so far, what each one is for, how they fit together, and how they are meant to be used in the codebase.

<p class="callout warning"><span style="white-space: pre-wrap;">The goal is not to replace the detailed documentation for each module. </span></p>

These libraries are all small, but they are not random utilities. Together they form a set of shared infrastructure for building embedded application code that is:

- more consistent
- easier to reason about
- easier to extend
- less likely to devolve into every subsystem inventing its own incompatible habits

Which, naturally, is what happens the moment shared infrastructure is missing.

# Design philosophy of the shared library layer

Before going into the individual libraries, it helps to understand the common design pattern behind them.

<p class="callout info"><span style="white-space: pre-wrap;">These libraries are not trying to be a giant framework. They are trying to provide </span>**targeted, reusable building blocks**<span style="white-space: pre-wrap;"> for recurring embedded problems:</span></p>

- reporting success and failure consistently
- logging runtime behavior in a uniform way
- storing shared variable-sized state in a controlled memory region
- scheduling work by discrete priority
- decoding and dispatching incoming protocol messages to the correct subsystem

The philosophy behind them is mostly this:

### Centralize recurring patterns

If every module invents its own result codes, logging style, queueing scheme, and packet dispatching logic, the system becomes harder to maintain very quickly.

These libraries centralize those patterns so the rest of the application can focus on subsystem logic instead of re-solving the same infrastructure problems over and over.

### Keep APIs small and practical

The libraries generally expose narrow APIs with very specific purposes.

### Prefer explicit ownership and caller-provided resources

Several of these modules rely on the caller to provide memory, configuration, or queue storage.

<p class="callout info">That is not accidental. It keeps ownership visible and lets the application control where resources live.</p>

### Separate policy from mechanism where useful

A few libraries expose a generic interface while allowing board-specific or implementation-specific backends.

Examples:

- the logging API is conceptually transport-agnostic even though the current implementation uses UART
- the packet dispatcher API is conceptually about routing decoded packets even though the current implementation uses FreeRTOS queues and tasks
- the KV pool lets callers decide where metadata and storage memory live

### Be honest about constraints

These are embedded libraries and we are not that good at coding.

A lot of their usefulness depends on respecting their assumptions:

- some are single-consumer by design
- some are not ISR-safe
- some assume static lifetime of configuration objects
- some have concurrency limitations that matter a lot

<p class="callout success">That is why documentation matters here. These modules are only “simple” if you already know their rules.</p>

# How the libraries fit together

At a system level, the libraries can be thought of as falling into a few categories.

### Core utility infrastructure

These are foundational and broadly reusable:

- `<span class="editor-theme-code">result</span>`
- `<span class="editor-theme-code">logging</span>`

<p class="callout info">They define how modules report status and how the system reports runtime information.</p>

### Storage and local scheduling infrastructure

These are reusable building blocks for internal system behavior:

- `<span class="editor-theme-code">bucketed_pqueue</span>`
- `<span class="editor-theme-code">kv_pool</span>`

They solve internal resource management and work scheduling problems.

### Communication and protocol infrastructure

These are more application-flow oriented:

- packet dispatcher / decoding task
- packet dispatcher macros

<p class="callout info">They take incoming protocol messages and move them to the right processing logic.</p>

A good mental model is:

- `<span class="editor-theme-code">result</span>`<span style="white-space: pre-wrap;"> defines how functions communicate success and failure</span>
- `<span class="editor-theme-code">logging</span>`<span style="white-space: pre-wrap;"> defines how the system communicates information outward</span>
- `<span class="editor-theme-code">bucketed_pqueue</span>`<span style="white-space: pre-wrap;"> defines how work can be prioritized internally</span>
- `<span class="editor-theme-code">kv_pool</span>`<span style="white-space: pre-wrap;"> defines how variable-sized keyed data can be stored safely in managed memory</span>
- the dispatcher layer defines how external messages enter the application and get routed to the correct handlers

# Result Library

## Purpose

<span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">result</span>`<span style="white-space: pre-wrap;"> module defines a </span>**shared result code system**<span style="white-space: pre-wrap;"> for the codebase.</span>

Its job is to give functions a consistent way to report success and failure without inventing random local conventions like:

- `<span class="editor-theme-code">0</span>`<span style="white-space: pre-wrap;"> means success here</span>
- `<span class="editor-theme-code">1</span>`<span style="white-space: pre-wrap;"> means success there</span>
- negative values somewhere else
- <span style="white-space: pre-wrap;">and one cursed module returning </span>`<span class="editor-theme-code">true</span>`<span style="white-space: pre-wrap;"> for failure because someone was feeling creative</span>

<span style="white-space: pre-wrap;">Instead, functions return a </span>`<span class="editor-theme-code">result_t</span>`, which makes error handling:

- consistent
- readable
- easier to propagate upward
- easier to log
- easier to document

This module also provides:

- string conversion helpers for result codes
- helper macros for early-return error propagation
- optional logging-aware propagation macros when the logging module is included

## What files belong to this module

This module consists of:

- `<span class="editor-theme-code">result.h</span>`
- `<span class="editor-theme-code">result.c</span>`

### `<span class="editor-theme-code">result.h</span>`

Defines:

- <span style="white-space: pre-wrap;">the </span>`<span class="editor-theme-code">result_t</span>`<span style="white-space: pre-wrap;"> enum</span>
- the public string conversion functions
- the helper macros:
    - `<span class="editor-theme-code">TRY</span>`
    - `<span class="editor-theme-code">TRY_CLEAN</span>`
    - `<span class="editor-theme-code">TRY_LOG</span>`
    - `<span class="editor-theme-code">TRY_LOG_CLEAN</span>`

### `<span class="editor-theme-code">result.c</span>`

Implements:

- `<span class="editor-theme-code">result_to_short_str()</span>`
- `<span class="editor-theme-code">result_to_desc_str()</span>`

## What problem this solves

In an embedded system, functions fail for many reasons:

- invalid arguments
- timeouts
- communication issues
- bad packet formats
- state machine misuse
- lack of memory
- busy resources
- and the usual parade of avoidable pain

<p class="callout info">Without a shared result type, every module ends up inventing its own error style. This library creates one common language for reporting outcomes across modules.</p>

That gives the codebase several benefits:

- function contracts are clearer
- errors can be passed up the call chain without translation
- logs can use the same human-readable error text
- helper macros reduce repetitive boilerplate

## Core design

The design is intentionally simple:

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> means success</span>
- <span style="white-space: pre-wrap;">every other </span>`<span class="editor-theme-code">result_t</span>`<span style="white-space: pre-wrap;"> value represents a failure or exceptional condition</span>
- functions return a single enum value
- callers decide whether to:
    - handle the error locally
    - return it upward
    - clean up before returning
    - log it before returning

<span style="white-space: pre-wrap;">This makes </span>`<span class="editor-theme-code">result_t</span>`<span style="white-space: pre-wrap;"> a lightweight, shared error protocol.</span>

## Public API overview

The public API consists of:

- `<span class="editor-theme-code">result_t</span>`
- `<span class="editor-theme-code">result_to_short_str()</span>`
- `<span class="editor-theme-code">result_to_desc_str()</span>`
- `<span class="editor-theme-code">TRY(expr)</span>`
- `<span class="editor-theme-code">TRY_CLEAN(expr)</span>`
- `<span class="editor-theme-code">TRY_LOG(expr)</span>`
- `<span class="editor-theme-code">TRY_LOG_CLEAN(expr)</span>`

## String conversion functions

The module provides two functions for converting result codes into human-readable text.

These are useful for:

- logs
- diagnostics
- debug output
- CLI or terminal status messages
- test failure reporting

### `<span class="editor-theme-code">result_to_short_str()</span>`

```c
const char *result_to_short_str(result_t code);
```

#### Purpose

Returns a short label for a result code.

#### Examples

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> -&gt; </span>`<span class="editor-theme-code">"OK"</span>`
- `<span class="editor-theme-code">RESULT_ERR_TIMEOUT</span>`<span style="white-space: pre-wrap;"> -&gt; </span>`<span class="editor-theme-code">"Timeout"</span>`
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> -&gt; </span>`<span class="editor-theme-code">"Invalid Argument"</span>`

#### Default behavior

If the result code is unknown or unsupported, it returns:

```
"Unknown Error"
```

#### Intended use

This is best for compact output such as:

```
ERROR: Timeout
ERROR: Invalid Packet
ERROR: Buffer too small
```

---

### `<span class="editor-theme-code">result_to_desc_str()</span>`

```c
const char *result_to_desc_str(result_t code);
```

#### Purpose

Returns a longer descriptive explanation of a result code.

#### Examples

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> -&gt; </span>`<span class="editor-theme-code">"The operation completed successfully."</span>`
- `<span class="editor-theme-code">RESULT_ERR_TIMEOUT</span>`<span style="white-space: pre-wrap;"> -&gt; </span>`<span class="editor-theme-code">"An operation failed to complete within the allotted time."</span>`
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> -&gt; </span>`<span class="editor-theme-code">"A provided argument is null, out of range, or otherwise invalid."</span>`

#### Default behavior

If the code is unknown or unsupported, it returns:

```
"An unknown error code was encountered."
```

#### Intended use

This is useful when more context is needed, especially in logs:

```
Invalid Argument: A provided argument is null, out of range, or otherwise invalid.
```

---

## Important implementation detail: mapping must stay synchronized

<span style="white-space: pre-wrap;">The enum in </span>`<span class="editor-theme-code">result.h</span>`<span style="white-space: pre-wrap;"> and the switch statements in </span>`<span class="editor-theme-code">result.c</span>`<span style="white-space: pre-wrap;"> must stay synchronized.</span>

<p class="callout warning"><span style="white-space: pre-wrap;">At the moment, they are </span>**not fully synchronized**.</p>

### Why this matters

This causes:

- misleading logs
- incomplete diagnostics
- confusion for anyone trying to use those result codes

### Maintenance rule

<span style="white-space: pre-wrap;">Whenever a new </span>`<span class="editor-theme-code">result_t</span>`<span style="white-space: pre-wrap;"> value is added, both conversion functions must be updated in the same change.</span>

<p class="callout danger">**This should be treated as mandatory.**</p>

## Error propagation macros

<span style="white-space: pre-wrap;">The module provides a set of helper macros that reduce repetitive boilerplate when working with </span>`<span class="editor-theme-code">result_t</span>`.

These macros assume the common pattern:

- <span style="white-space: pre-wrap;">call a function returning </span>`<span class="editor-theme-code">result_t</span>`
- if it failed, stop current flow
- either return immediately or jump to cleanup

---

### `<span class="editor-theme-code">TRY(expr)</span>`

```c
#define TRY(expr) \
  do { \
    result_t _try_status = (expr); \
    if (_try_status != RESULT_OK) { \
      return _try_status; \
    } \
  } while (0)
```

#### Purpose

<span style="white-space: pre-wrap;">Evaluates an expression returning </span>`<span class="editor-theme-code">result_t</span>`.

<span style="white-space: pre-wrap;">If the result is not </span>`<span class="editor-theme-code">RESULT_OK</span>`, the current function immediately returns that result.

#### Example

```c
result_t motor_start(void) {
  TRY(motor_check_ready());
  TRY(motor_enable_power());
  TRY(motor_configure_pwm());

  return RESULT_OK;
}
```

### Expanded behavior

This behaves roughly like:

```c
result_t status = motor_check_ready();
if (status != RESULT_OK) {
  return status;
}
```

for each call.

### When to use it

<span style="white-space: pre-wrap;">Use </span>`<span class="editor-theme-code">TRY()</span>`<span style="white-space: pre-wrap;"> when:</span>

- <span style="white-space: pre-wrap;">the current function also returns </span>`<span class="editor-theme-code">result_t</span>`
- no local cleanup is needed before returning
- you want simple upward propagation

### `<span class="editor-theme-code">TRY_CLEAN(expr)</span>`

```c
#define TRY_CLEAN(expr) \
  do { \
    result_t _try_status = (expr); \
    if (_try_status != RESULT_OK) { \
      goto cleanup; \
    } \
  } while (0)
```

#### Purpose

<span style="white-space: pre-wrap;">Evaluates an expression returning </span>`<span class="editor-theme-code">result_t</span>`.

<span style="white-space: pre-wrap;">If the result is not </span>`<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;">, execution jumps to a </span>`<span class="editor-theme-code">cleanup:</span>`<span style="white-space: pre-wrap;"> label.</span>

#### Example

```c
result_t process_frame(void) {
  result_t status = RESULT_OK;
  void *buffer = NULL;

  buffer = malloc(128);
  if (buffer == NULL) {
    return RESULT_ERR_NO_MEM;
  }

  TRY_CLEAN(step_one());
  TRY_CLEAN(step_two());
  TRY_CLEAN(step_three());

  return RESULT_OK;

cleanup:
  free(buffer);
  return RESULT_ERR;
}
```

### `<span class="editor-theme-code">TRY_LOG(expr)</span>`

<span style="white-space: pre-wrap;">When </span>`<span class="editor-theme-code">LOGGING_H</span>`<span style="white-space: pre-wrap;"> is defined, this macro becomes:</span>

```c
#define TRY_LOG(expr) \
  do { \
    result_t _try_status = (expr); \
    if (_try_status != RESULT_OK) { \
      LOGE(TAG, "%s: %s", result_to_short_str(_try_status), \
           result_to_desc_str(_try_status)); \
      return _try_status; \
    } \
  } while (0)
```

#### Purpose

<span style="white-space: pre-wrap;">Like </span>`<span class="editor-theme-code">TRY()</span>`, but also emits a log message before returning.

#### Required assumption

<span style="white-space: pre-wrap;">The surrounding scope must define </span>`<span class="editor-theme-code">TAG</span>`, because the macro calls:

```c
LOGE(TAG, ...)
```

<span style="white-space: pre-wrap;">If </span>`<span class="editor-theme-code">TAG</span>`<span style="white-space: pre-wrap;"> is not defined, compilation will fail.</span>

#### Example

```c
#define TAG "NET"

result_t net_start(void) {
  TRY_LOG(net_hw_init());
  TRY_LOG(net_link_up());

  return RESULT_OK;
}
```

<span style="white-space: pre-wrap;">If </span>`<span class="editor-theme-code">net_link_up()</span>`<span style="white-space: pre-wrap;"> returns </span>`<span class="editor-theme-code">RESULT_ERR_TIMEOUT</span>`, the log might look like:

```
[ERROR] NET: Timeout: An operation failed to complete within the allotted time.
```

and then the function returns that result.

### `<span class="editor-theme-code">TRY_LOG_CLEAN(expr)</span>`

<span style="white-space: pre-wrap;">When </span>`<span class="editor-theme-code">LOGGING_H</span>`<span style="white-space: pre-wrap;"> is defined, this macro becomes:</span>

```c
#define TRY_LOG_CLEAN(expr) \
  do { \
    result_t _try_status = (expr); \
    if (_try_status != RESULT_OK) { \
      LOGE(TAG, "%s: %s", result_to_short_str(_try_status), \
           result_to_desc_str(_try_status)); \
      goto cleanup; \
    } \
  } while (0)
```

#### Purpose

<span style="white-space: pre-wrap;">Like </span>`<span class="editor-theme-code">TRY_CLEAN()</span>`<span style="white-space: pre-wrap;">, but logs before jumping to </span>`<span class="editor-theme-code">cleanup</span>`.

## Behavior when logging is not available

<span style="white-space: pre-wrap;">The logging-aware macros depend on whether </span>`<span class="editor-theme-code">LOGGING_H</span>`<span style="white-space: pre-wrap;"> is defined.</span>

<span style="white-space: pre-wrap;">This means they behave differently depending on whether the logging header has been included before </span>`<span class="editor-theme-code">result.h</span>`.

That is an important design detail.

### <span style="white-space: pre-wrap;">When </span>`<span class="editor-theme-code">LOGGING_H</span>`<span style="white-space: pre-wrap;"> is defined</span>

<span style="white-space: pre-wrap;">If the logging header has already been included, </span>`<span class="editor-theme-code">TRY_LOG</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">TRY_LOG_CLEAN</span>`<span style="white-space: pre-wrap;"> perform logging through </span>`<span class="editor-theme-code">LOGE</span>`.

<span style="white-space: pre-wrap;">This couples the macros to the logging module without hard-including it from </span>`<span class="editor-theme-code">result.h</span>`.

<span style="white-space: pre-wrap;">That keeps </span>`<span class="editor-theme-code">result.h</span>`<span style="white-space: pre-wrap;"> lightweight, but also makes behavior depend on include order.</span>

### <span style="white-space: pre-wrap;">When </span>`<span class="editor-theme-code">LOGGING_H</span>`<span style="white-space: pre-wrap;"> is not defined</span>

The code falls back to compiler-specific warning behavior.

#### GCC / Clang

<span style="white-space: pre-wrap;">The macros emit a compile-time warning via </span>`<span class="editor-theme-code">_Pragma(...)</span>`<span style="white-space: pre-wrap;"> and then degrade to:</span>

- `<span class="editor-theme-code">TRY(expr)</span>`
- `<span class="editor-theme-code">TRY_CLEAN(expr)</span>`

#### MSVC

They emit a compiler message and also degrade to the non-logging versions.

#### Other compilers

<span style="white-space: pre-wrap;">A general </span>`<span class="editor-theme-code">#warning</span>`<span style="white-space: pre-wrap;"> is emitted and the macros degrade to the non-logging versions.</span>

#### Practical meaning

If logging is not available, the macros still work for flow control. They just do not log.

## Recommended usage guidelines

### Prefer specific result codes

<span style="white-space: pre-wrap;">Use the most precise </span>`<span class="editor-theme-code">result_t</span>`<span style="white-space: pre-wrap;"> value that matches the failure.</span>

Prefer:

- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`
- `<span class="editor-theme-code">RESULT_ERR_TIMEOUT</span>`
- `<span class="editor-theme-code">RESULT_ERR_NOT_INITIALIZED</span>`

over generic:

- `<span class="editor-theme-code">RESULT_FAIL</span>`

when possible.

### <span style="white-space: pre-wrap;">Keep </span>`<span class="editor-theme-code">RESULT_FAIL</span>`<span style="white-space: pre-wrap;"> as fallback only</span>

`<span class="editor-theme-code">RESULT_FAIL</span>`<span style="white-space: pre-wrap;"> should mean:</span>

> “something failed, but no existing specific code fits cleanly.”

It should not become the default.

### <span style="white-space: pre-wrap;">Use </span>`<span class="editor-theme-code">TRY()</span>`<span style="white-space: pre-wrap;"> only in functions returning </span>`<span class="editor-theme-code">result_t</span>`

<span style="white-space: pre-wrap;">Otherwise the generated </span>`<span class="editor-theme-code">return _try_status;</span>`<span style="white-space: pre-wrap;"> is wrong.</span>

### <span style="white-space: pre-wrap;">Use </span>`<span class="editor-theme-code">TRY_CLEAN()</span>`<span style="white-space: pre-wrap;"> only when a cleanup label exists</span>

And only when you understand whether the error code is preserved.

### <span style="white-space: pre-wrap;">Define </span>`<span class="editor-theme-code">TAG</span>`<span style="white-space: pre-wrap;"> before using logging-aware macros</span>

<span style="white-space: pre-wrap;">Without it, </span>`<span class="editor-theme-code">TRY_LOG</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">TRY_LOG_CLEAN</span>`<span style="white-space: pre-wrap;"> are not valid.</span>

### Keep conversion functions updated

Whenever a new enum value is added, update:

- `<span class="editor-theme-code">result_to_short_str()</span>`
- `<span class="editor-theme-code">result_to_desc_str()</span>`

in the same commit.

This should be treated as mandatory maintenance.

## Suggested mental model

Think of this module as:

> “The project-wide language for function outcomes.”

It is not just a list of enum values.

<p class="callout info">It defines how modules communicate success and failure to each other, and the helper macros define the common patterns for passing those outcomes upward through the call stack.</p>

That makes it foundational infrastructure, even if the code itself is small and visually innocent.

# Logging

## Purpose

The logging library provides a simple UART-based logging interface for embedded firmware.

Its main job is to let the application print formatted log messages such as:

```
[INFO] MOTOR: Initialization complete
[WARNING] SENSOR: Value out of range: 8123
[ERROR] CAN: Failed to transmit frame
```

The module is intentionally small:

- <span style="white-space: pre-wrap;">it supports </span>**three log levels**
- it formats messages in a consistent style
- it sends all output over a single UART
- it provides convenience macros for compile-time log filtering

<p class="callout warning"><span style="white-space: pre-wrap;">This is a </span>**printf-style logging system**, not a structured logger, ring buffer, or asynchronous trace system.</p>

## What problem this solves

Without this module, code would need to:

- manually format strings
- manually select a UART
- manually prepend log level tags
- duplicate formatting logic across the project

This library centralizes that behavior so all logs:

- use the same format
- use the same transport
- can be filtered by log level at compile time
- remain easy to call from application code

## Output format

<span style="white-space: pre-wrap;">Every log produced by </span>`<span class="editor-theme-code">LOG()</span>`<span style="white-space: pre-wrap;"> is formatted as:</span>

```
[LEVEL] TAG: message\r\n
```

### Example

```c
LOG(LOG_INFO, "IMU", "Sensor ready");
```

produces:

```
[INFO] IMU: Sensor ready\r\n
```

Another example:

```c
LOG(LOG_ERROR, "ETH", "TX failed with code %d", err);
```

produces something like:

```
[ERROR] ETH: TX failed with code -3\r\n
```

### Components

A log line contains:

- <span style="white-space: pre-wrap;">opening bracket </span>`<span class="editor-theme-code">[</span>`
- <span style="white-space: pre-wrap;">level string such as </span>`<span class="editor-theme-code">INFO</span>`
- <span style="white-space: pre-wrap;">closing bracket and space </span>`<span class="editor-theme-code">]</span>`
- tag string
- <span style="white-space: pre-wrap;">separator </span>`<span class="editor-theme-code">:</span>`
- user message string
- <span style="white-space: pre-wrap;">CRLF line ending </span>`<span class="editor-theme-code">\r\n</span>`

The line ending is Windows/terminal friendly and also common for UART console output.

## Public API overview

The public interface consists of:

- `<span class="editor-theme-code">LogLevel</span>`
- `<span class="editor-theme-code">log_level_to_string()</span>`
- `<span class="editor-theme-code">LOG_init()</span>`
- `<span class="editor-theme-code">LOG()</span>`
- convenience macros:
    - `<span class="editor-theme-code">LOGE</span>`
    - `<span class="editor-theme-code">LOGW</span>`
    - `<span class="editor-theme-code">LOGI</span>`

---

## Log levels

### Enum definition

```c
typedef enum {
  LOG_INFO,
  LOG_WARNING,
  LOG_ERROR,
  _LOG_LAST_LEVEL_DONT_EDIT
} LogLevel;
```

### Meaning

The library supports three levels:

- `<span class="editor-theme-code">LOG_INFO</span>`
- `<span class="editor-theme-code">LOG_WARNING</span>`
- `<span class="editor-theme-code">LOG_ERROR</span>`

These are stored as enum values starting at 0.

The final enum value:

```
_LOG_LAST_LEVEL_DONT_EDIT
```

is not an actual log level. It is a sentinel used to:

- count how many log levels exist
- validate the string table size

### Why that sentinel exists

The library keeps a parallel array of strings:

```c
static const char *LOG_LEVEL_STRINGS[] = {
    "INFO",
    "WARNING",
    "ERROR",
};
```

The enum and string table must stay aligned.

<span style="white-space: pre-wrap;">The sentinel lets the code verify that automatically with </span>`<span class="editor-theme-code">static_assert</span>`.

### `<span class="editor-theme-code">log_level_to_string()</span>`

```c
static inline const char *log_level_to_string(LogLevel logLevel);
```

#### Purpose

<span style="white-space: pre-wrap;">Converts a </span>`<span class="editor-theme-code">LogLevel</span>`<span style="white-space: pre-wrap;"> enum into its corresponding string.</span>

#### Valid conversions

- `<span class="editor-theme-code">LOG_INFO</span>`<span style="white-space: pre-wrap;"> -&gt; </span>`<span class="editor-theme-code">"INFO"</span>`
- `<span class="editor-theme-code">LOG_WARNING</span>`<span style="white-space: pre-wrap;"> -&gt; </span>`<span class="editor-theme-code">"WARNING"</span>`
- `<span class="editor-theme-code">LOG_ERROR</span>`<span style="white-space: pre-wrap;"> -&gt; </span>`<span class="editor-theme-code">"ERROR"</span>`

If the value is outside the valid range, it returns:

```
"NoLevel"
```

#### Notes

<span style="white-space: pre-wrap;">This function is declared </span>`<span class="editor-theme-code">static inline</span>`<span style="white-space: pre-wrap;"> in the header, so each translation unit including the header gets its own inline copy.</span>

It also contains a compile-time check:

```c
static_assert((sizeof(LOG_LEVEL_STRINGS) / sizeof(LOG_LEVEL_STRINGS[0])) ==
                    _LOG_LAST_LEVEL_DONT_EDIT,
                "Mismatch in number of log level strings!");
```

This prevents someone from adding or removing enum levels without updating the string table.

That is one of the few parts of this module behaving like it has trust issues, which is correct.

## Backend independence of the API

<p class="callout info"><span style="white-space: pre-wrap;">Although the current implementation sends logs over </span>**UART**<span style="white-space: pre-wrap;">, the </span>**public API itself is not inherently UART-specific**.</p>

From the perspective of code using the logger, the interface is simply:

- <span style="white-space: pre-wrap;">initialize the logging system with </span>`<span class="editor-theme-code">LOG_init(...)</span>`
- <span style="white-space: pre-wrap;">emit logs with </span>`<span class="editor-theme-code">LOG(...)</span>`
- <span style="white-space: pre-wrap;">or use the convenience macros </span>`<span class="editor-theme-code">LOGI</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">LOGW</span>`<span style="white-space: pre-wrap;">, and </span>`<span class="editor-theme-code">LOGE</span>`

Nothing in normal application code needs to know how the log is actually transported.

### What this means in practice

The current board-specific implementation uses:

- <span style="white-space: pre-wrap;">a UART handle passed into </span>`<span class="editor-theme-code">LOG_init()</span>`
- `<span class="editor-theme-code">HAL_UART_Transmit()</span>`<span style="white-space: pre-wrap;"> for output</span>
- `<span class="editor-theme-code">_write()</span>`<span style="white-space: pre-wrap;"> retargeting for stdout</span>

But that is only one possible backend.

A different board or firmware target could keep the same header/API and provide a different implementation, for example:

- USB CDC logging
- SWO / ITM logging
- RTT logging
- CAN or Ethernet debug output
- buffered logging to memory
- semihosting during development

### Why this matters

<p class="callout success"><span style="white-space: pre-wrap;">This separation means the API should be understood as a </span>**logical logging interface**, not as a UART contract.</p>

In this codebase, each board can provide its own implementation behind the same header, as long as it preserves the expected external behavior of the API.

That makes the module portable across boards without forcing higher-level application code to care about the physical logging transport, which is one of the few times abstraction is actually doing something useful instead of just breeding paperwork.

### Maintenance guidance

If a future board needs a different logging transport, the preferred approach is:

- <span style="white-space: pre-wrap;">keep </span>`<span class="editor-theme-code">logging.h</span>`<span style="white-space: pre-wrap;"> stable if possible</span>
- replace or adapt the implementation file for that board
- preserve the meaning of:
    - `<span class="editor-theme-code">LOG_init()</span>`
    - `<span class="editor-theme-code">LOG()</span>`
    - `<span class="editor-theme-code">LOGI/LOGW/LOGE</span>`

This allows application code to remain unchanged while the backend changes per target.

## Initialization

### `<span class="editor-theme-code">LOG_init()</span>`

```c
void LOG_init(void *arg);
```

### Purpose

Initializes the logging system by providing the UART handle that will be used for all later output.

### Expected argument

`<span class="editor-theme-code">arg</span>`<span style="white-space: pre-wrap;"> must point to a valid </span>`<span class="editor-theme-code">UART_HandleTypeDef</span>`.

### In practice:

```c
LOG_init(&huart2);
```

or whichever UART handle should be used for logging.

### What it does internally

`<span class="editor-theme-code">LOG_init() </span>`performs these steps:

1. <span style="white-space: pre-wrap;">Casts </span>`<span class="editor-theme-code">args</span>`<span style="white-space: pre-wrap;"> to </span>`<span class="editor-theme-code">UART_HandleTypeDef</span>`
2. copies the pointed-to UART handle into a private static variable
3. <span style="white-space: pre-wrap;">sets an internal </span>`<span class="editor-theme-code">initialized</span>`<span style="white-space: pre-wrap;"> flag</span>
4. <span style="white-space: pre-wrap;">writes a boot banner directly using </span>`<span class="editor-theme-code">_write()</span>`
5. emits an info log saying logging was initialized

## Important requirements

`<span class="editor-theme-code">LOG_init()</span>`<span style="white-space: pre-wrap;"> must be called before any normal logging is expected to work.</span>

<span style="white-space: pre-wrap;">If </span>`<span class="editor-theme-code">LOG()</span>`<span style="white-space: pre-wrap;"> is called before initialization, it silently returns without output.</span>

## Main logging function

### `<span class="editor-theme-code">LOG()</span>`

```c
void LOG(LogLevel level, const char *TAG, const char *log_message, ...);
```

### Purpose

Formats and transmits a log line over UART.

### Parameters

#### `<span class="editor-theme-code">level</span>`

The severity level of the message.

Expected values:

- `<span class="editor-theme-code">LOG_INFO</span>`
- `<span class="editor-theme-code">LOG_WARNING</span>`
- `<span class="editor-theme-code">LOG_ERROR</span>`

If the value is invalid, the implementation falls back to:

```c
"UNKNOWN"
```

for formatting.

#### `<span class="editor-theme-code">TAG</span>`

A short text label identifying the source of the log.

Typical examples:

- `<span class="editor-theme-code">"IMU"</span>`
- `<span class="editor-theme-code">"CAN"</span>`
- `<span class="editor-theme-code">"DISPATCHER"</span>`
- `<span class="editor-theme-code">"ETH"</span>`

This appears in the formatted output after the log level.

#### `<span class="editor-theme-code">log_message</span>`

A printf-style format string.

Examples:

- `<span class="editor-theme-code">"Init done"</span>`
- `<span class="editor-theme-code">"Received packet %u"</span>`
- `<span class="editor-theme-code">"Voltage too high: %d mV"</span>`

Optional variadic arguments used by the format string.

### Example usage

```
LOG(LOG_INFO, "MOTOR", "Started with speed %u", speed);
LOG(LOG_WARNING, "TEMP", "High temperature: %d", temp);
LOG(LOG_ERROR, "FLASH", "Write failed");
```

### Behavior when not initialized

If logging has not been initialized yet, the function returns immediately:

```c
if (initialized == 0) {
    return;
}
```

No output is produced.

This is deliberate.

## Internal formatting process

The implementation builds the final message in two phases.

### Phase 1: Build a full format string

It first constructs a format string like:

```
[INFO] MOTOR: Started with speed %u\r\n
```

<span style="white-space: pre-wrap;">This is stored in dynamically allocated memory called </span>`<span class="editor-theme-code">format_message</span>`.

### Phase 2: Format variadic arguments into final output

<span style="white-space: pre-wrap;">It then uses </span>`<span class="editor-theme-code">vsnprintf()</span>`<span style="white-space: pre-wrap;"> twice:</span>

1. once to calculate the final required length
2. once to write the fully formatted message into another dynamically allocated buffer

That final message is transmitted using:

```c
HAL_UART_Transmit(&huart_handler, (uint8_t *)total_message, total_len, HAL_MAX_DELAY);
```

After transmission, both heap allocations are freed.

## <span style="white-space: pre-wrap;">Retargeted </span>`<span class="editor-theme-code">_write()</span>`

```c
int _write(int file, char *ptr, int len);
```

### Purpose

This function retargets standard output to the configured UART.

### Behavior

<span style="white-space: pre-wrap;">If the file descriptor is </span>`<span class="editor-theme-code">1</span>`:

```c
if (file == 1)
```

<span style="white-space: pre-wrap;">the function transmits the provided buffer over UART using </span>`<span class="editor-theme-code">HAL_UART_Transmit()</span>`.

<span style="white-space: pre-wrap;">It then returns </span>`<span class="editor-theme-code">len</span>`.

### Why this exists

<span style="white-space: pre-wrap;">On many embedded toolchains, overriding </span>`<span class="editor-theme-code">_write()</span>`<span style="white-space: pre-wrap;"> allows C library output functions such as </span>`<span class="editor-theme-code">printf()</span>`<span style="white-space: pre-wrap;"> to write to UART.</span>

<p class="callout warning">That means this module is not only a custom logging module. It also partially redirects stdout.</p>

### Important note

<span style="white-space: pre-wrap;">This implementation only handles file descriptor </span>`<span class="editor-theme-code">1</span>`, which is typically stdout.

It does not distinguish stderr or other descriptors.

### <span style="white-space: pre-wrap;">Interaction with </span>`<span class="editor-theme-code">LOG()</span>`

`<span class="editor-theme-code">LOG()</span>`<span style="white-space: pre-wrap;"> does not actually use </span>`<span class="editor-theme-code">printf()</span>`<span style="white-space: pre-wrap;"> or </span>`<span class="editor-theme-code">_write()</span>`<span style="white-space: pre-wrap;"> for its main output path. It calls </span>`<span class="editor-theme-code">HAL_UART_Transmit()</span>`<span style="white-space: pre-wrap;"> directly after formatting its message.</span>

`<span class="editor-theme-code">LOG_init()</span>`<span style="white-space: pre-wrap;"> does use </span>`<span class="editor-theme-code">_write()</span>`<span style="white-space: pre-wrap;"> once to print the boot banner.</span>

<span style="white-space: pre-wrap;">So </span>`<span class="editor-theme-code">_write()</span>`<span style="white-space: pre-wrap;"> exists mainly for stdout retargeting and the boot line, not as the core mechanism used by </span>`<span class="editor-theme-code">LOG()</span>`<span style="white-space: pre-wrap;"> itself.</span>

## Convenience macros

The header defines these macros:

- `<span class="editor-theme-code">LOGE</span>`
- `<span class="editor-theme-code">LOGW</span>`
- `<span class="editor-theme-code">LOGI</span>`

<span style="white-space: pre-wrap;">These call </span>`<span class="editor-theme-code">LOG()</span>`<span style="white-space: pre-wrap;"> with a fixed level, but only if that level is enabled by </span>`<span class="editor-theme-code">CONFIG_LOG_LEVEL</span>`.

## Default log level configuration

<span style="white-space: pre-wrap;">If </span>`<span class="editor-theme-code">CONFIG_LOG_LEVEL</span>`<span style="white-space: pre-wrap;"> is not defined by the build system, the header sets:</span>

```c
#define CONFIG_LOG_LEVEL LOG_INFO
```

This means all log levels are enabled by default.

### Macro behavior

#### `<span class="editor-theme-code">LOGE</span>`

```c
#define LOGE(TAG, format, ...) LOG(LOG_ERROR, TAG, format, ##__VA_ARGS__)
```

Enabled when:

```c
(CONFIG_LOG_LEVEL <= LOG_ERROR)
```

<span style="white-space: pre-wrap;">Because </span>`<span class="editor-theme-code">LOG_ERROR</span>`<span style="white-space: pre-wrap;"> is the highest enum value in this setup, this macro is enabled for all current supported configurations.</span>

#### `<span class="editor-theme-code">LOGW</span>`

```c
#define LOGW(TAG, format, ...) LOG(LOG_WARNING, TAG, format, ##__VA_ARGS__)
```

Enabled when:

```
(CONFIG_LOG_LEVEL <= LOG_WARNING)
```

#### `<span class="editor-theme-code">LOGI</span>`

```c
#define LOGI(TAG, format, ...) LOG(LOG_INFO, TAG, format, ##__VA_ARGS__)
```

Enabled when:

```
(CONFIG_LOG_LEVEL <= LOG_INFO)
```

---

## Filtering semantics

Since the enum values are ordered:

- `<span class="editor-theme-code">LOG_INFO = 0</span>`
- `<span class="editor-theme-code">LOG_WARNING = 1</span>`
- `<span class="editor-theme-code">LOG_ERROR = 2</span>`

<span style="white-space: pre-wrap;">a lower configured value means </span>**more logs enabled**.

### Examples

#### `<span class="editor-theme-code">CONFIG_LOG_LEVEL = LOG_INFO</span>`

Enabled:

- info
- warning
- error

#### `<span class="editor-theme-code">CONFIG_LOG_LEVEL = LOG_WARNING</span>`

Enabled:

- warning
- error

Disabled:

- info

#### `<span class="editor-theme-code">CONFIG_LOG_LEVEL = LOG_ERROR</span>`

Enabled:

- error only

Disabled:

- warning
- info

### Disabled macro behavior

When disabled, the macro expands to:

```
(void)0
```

So the call is compiled out.

This is compile-time filtering, not runtime filtering.

That matters because disabled log calls impose essentially no runtime cost.

## Typical usage pattern

### Initialization

At system startup, once the UART peripheral is ready:

```c
LOG_init(&huart2);
```

This should happen before any code that expects logging output.

---

### Logging from application code

Use one of the convenience macros in normal code:

```c
LOGI("NET", "Ethernet initialized");
LOGW("ADC", "Reading outside expected range: %u", sample);
LOGE("FLASH", "Erase failed at sector %u", sector);
```

This is the intended public usage style.

<span style="white-space: pre-wrap;">Using </span>`<span class="editor-theme-code">LOG()</span>`<span style="white-space: pre-wrap;"> directly is also valid when needed.</span>

### Tag conventions

The module does not enforce tag format, so the team should adopt a convention.

A good pattern is to use short subsystem names, such as:

- `<span class="editor-theme-code">"ETH"</span>`
- `<span class="editor-theme-code">"CAN"</span>`
- `<span class="editor-theme-code">"SCHED"</span>`
- `<span class="editor-theme-code">"MOTOR"</span>`
- `<span class="editor-theme-code">"UI"</span>`
- `<span class="editor-theme-code">"SENSOR"</span>`

Keep tags short enough for readable UART logs.

Since this logger is plain text over UART, bloated tags just make the output harder to scan.

# Priority Queue

## Summary

`<span class="editor-theme-code">bucketed_pqueue</span>`<span style="white-space: pre-wrap;"> is a simple, efficient strict-priority queue for FreeRTOS systems where:</span>

- items fall into a small fixed set of priority levels
- producers may be tasks or ISRs
- a single consumer drains work in priority order

Its design is intentionally lightweight:

- one FreeRTOS queue per priority
- one bitmap to track which priorities are active
- optional task notification for efficient wakeup

<p class="callout success">Used correctly, it is a clean fit for embedded event dispatch and deferred work handling.</p>

<p class="callout warning">Used incorrectly, mainly with multiple consumers or inconsistent bucket item types, it becomes a fine little trap with excellent timing and poor manners.</p>

## Purpose

<p class="callout info"><span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">bucketed_pqueue</span>`<span style="white-space: pre-wrap;"> module implements a </span>**strict-priority queue**<span style="white-space: pre-wrap;"> on top of standard FreeRTOS queues.</span></p>

<span style="white-space: pre-wrap;">Instead of storing all items in one queue, it uses </span>**multiple FIFO queues**<span style="white-space: pre-wrap;">, called </span>**buckets**, where each bucket represents one priority level.

- **Bucket 0**<span style="white-space: pre-wrap;"> = lowest priority</span>
- **Bucket `<strong class="editor-theme-bold editor-theme-code">num_buckets - 1</strong>`**<span style="white-space: pre-wrap;"> = highest priority</span>

<span style="white-space: pre-wrap;">When consuming items, the module always returns an item from the </span>**highest non-empty priority bucket**.

<span style="white-space: pre-wrap;">Within the same priority bucket, ordering remains </span>**FIFO**, because each bucket is an ordinary FreeRTOS queue.

This gives the system:

- **strict priority across buckets**
- **FIFO ordering within each bucket**
- <span style="white-space: pre-wrap;">support for </span>**task producers**
- <span style="white-space: pre-wrap;">support for </span>**ISR producers**
- a lightweight way to wake a consumer task when new work arrives

## When to use this module

Use this module when:

- <span style="white-space: pre-wrap;">work items have a </span>**small fixed number of priority levels**
- **higher-priority items must always be processed first**
- you want to keep FreeRTOS queue semantics
- <span style="white-space: pre-wrap;">producers may run in both </span>**task**<span style="white-space: pre-wrap;"> and </span>**interrupt**<span style="white-space: pre-wrap;"> context</span>
- <span style="white-space: pre-wrap;">only </span>**one consumer**<span style="white-space: pre-wrap;"> is responsible for draining the queue</span>

Typical use cases:

- event dispatching where alarms/errors must preempt normal work
- deferred interrupt processing with different urgency levels
- message handling where command classes have discrete priority bands

## <span style="white-space: pre-wrap;">When to </span>**not** use this module

<p class="callout warning"><span style="white-space: pre-wrap;">This module is </span>**not**<span style="white-space: pre-wrap;"> a general-purpose concurrent priority queue.</span></p>

<span style="white-space: pre-wrap;">Do </span>**not**<span style="white-space: pre-wrap;"> use it if:</span>

- <span style="white-space: pre-wrap;">you need </span>**multiple consumer tasks**<span style="white-space: pre-wrap;"> calling </span>`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> or </span>`<span class="editor-theme-code">peek()</span>`<span style="white-space: pre-wrap;"> concurrently</span>
- <span style="white-space: pre-wrap;">you need </span>**arbitrary numeric priorities**<span style="white-space: pre-wrap;"> rather than a small fixed bucket range</span>
- <span style="white-space: pre-wrap;">you need </span>**blocking pop**<span style="white-space: pre-wrap;"> behavior built into the module</span>
- you need stable ordering across different priorities based on timestamp or insertion order

<span style="white-space: pre-wrap;">The implementation is designed around </span>**multiple producers, single consumer**.

## High-Level Design

The queue is made from:

- an array of FreeRTOS queue handles
- a count of how many buckets exist
- a 32-bit bitmap tracking which buckets are currently believed to be non-empty
- an optional task handle to notify when something is pushed

### Core idea

Each priority level has its own FreeRTOS queue.

The module maintains a bitmap:

- <span style="white-space: pre-wrap;">bit </span>`<span class="editor-theme-code">n</span>`<span style="white-space: pre-wrap;"> set = bucket </span>`<span class="editor-theme-code">n</span>`<span style="white-space: pre-wrap;"> is believed to contain at least one item</span>
- <span style="white-space: pre-wrap;">bit </span>`<span class="editor-theme-code">n</span>`<span style="white-space: pre-wrap;"> clear = bucket </span>`<span class="editor-theme-code">n</span>`<span style="white-space: pre-wrap;"> is believed to be empty</span>

This bitmap lets the consumer avoid blindly probing every queue all the time.

### Example

If there are 4 buckets:

- bucket 0 = low
- bucket 1 = medium
- bucket 2 = high
- bucket 3 = critical

And the bitmap is:

```c
non_empty_mask = 0b1010
```

then bucket 1 and bucket 3 are non-empty.

<span style="white-space: pre-wrap;">A call to </span>`<span class="editor-theme-code">bucketed_pqueue_pop()</span>`<span style="white-space: pre-wrap;"> will check from highest to lowest, so it will try:</span>

1. bucket 3
2. bucket 2
3. bucket 1
4. bucket 0

and return the first available item it finds.

## Data Structure

```c
typedef struct {  
  QueueHandle_t *buckets;  
  uint8_t num_buckets;  
  uint32_t non_empty_mask;  
  TaskHandle_t notifier;
} bucketed_pqueue_t;
```

### Fields

#### `<span class="editor-theme-code">buckets</span>`

<span style="white-space: pre-wrap;">Pointer to an array of </span>`<span class="editor-theme-code">QueueHandle_t</span>`.

Each entry is a FreeRTOS queue representing one priority bucket.

<p class="callout info"><span style="white-space: pre-wrap;">This array is </span>**not owned**<span style="white-space: pre-wrap;"> by the module. The caller must create the queues and ensure the array remains valid for the entire lifetime of the priority queue.</span></p>

#### `<span class="editor-theme-code">num_buckets</span>`

<span style="white-space: pre-wrap;">Number of buckets in the </span>`<span class="editor-theme-code">buckets</span>`<span style="white-space: pre-wrap;"> array.</span>

<span style="white-space: pre-wrap;">Valid range: </span>**1 to 32**.

<span style="white-space: pre-wrap;">The upper limit exists because </span>`<span class="editor-theme-code">non_empty_mask</span>`<span style="white-space: pre-wrap;"> is a 32-bit bitmap.</span>

#### `<span class="editor-theme-code">non_empty_mask</span>`

Bitmap used as a fast summary of which buckets contain items.

- <span style="white-space: pre-wrap;">bit </span>`<span class="editor-theme-code">0</span>`<span style="white-space: pre-wrap;"> corresponds to bucket </span>`<span class="editor-theme-code">0</span>`
- <span style="white-space: pre-wrap;">bit </span>`<span class="editor-theme-code">1</span>`<span style="white-space: pre-wrap;"> corresponds to bucket </span>`<span class="editor-theme-code">1</span>`
- etc.

This bitmap is updated on push, and cleared when the consumer determines a bucket is empty.

#### `<span class="editor-theme-code">notifier</span>`

Optional task to notify when an item is successfully pushed.

<span style="white-space: pre-wrap;">If not </span>`<span class="editor-theme-code">NULL</span>`, a push sets the notification bit:

```c
1UL << prio
```

in the target task’s notification value.

This is useful when the consumer task waits on task notifications instead of polling.

## Important behavioral guarantees

This module provides the following behavior:

### Strict priority

Higher-priority buckets are always preferred over lower-priority buckets.

<span style="white-space: pre-wrap;">If bucket 3 and bucket 1 both contain items, </span>`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> will always return from bucket 3 first.</span>

### FIFO within one priority

Because each bucket is a FreeRTOS queue, items in the same bucket are processed in insertion order.

### Non-blocking pop/peek

`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">peek()</span>`<span style="white-space: pre-wrap;"> do not wait. If no item is available, they return </span>`<span class="editor-theme-code">RESULT_ERR_NOT_FOUND</span>`.

### Multi-context producers

There are separate APIs for:

- <span style="white-space: pre-wrap;">task context: </span>`<span class="editor-theme-code">bucketed_pqueue_push()</span>`
- <span style="white-space: pre-wrap;">ISR context: </span>`<span class="editor-theme-code">bucketed_pqueue_push_from_isr()</span>`

### Single-consumer design

<span style="white-space: pre-wrap;">The implementation assumes only one consumer performs </span>`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">peek()</span>`.

That is not just a suggestion. It is a design constraint.

## Required setup

<span style="white-space: pre-wrap;">This module does </span>**not**<span style="white-space: pre-wrap;"> create FreeRTOS queues itself.</span>

The caller must:

1. create one FreeRTOS queue for each priority level
2. store those queue handles in an array
3. <span style="white-space: pre-wrap;">initialize a </span>`<span class="editor-theme-code">bucketed_pqueue_t</span>`<span style="white-space: pre-wrap;"> using that array</span>

### Example setup

```c
#define NUM_BUCKETS 4

static QueueHandle_t bucket_handles[NUM_BUCKETS];

bucketed_pqueue_t pq;

void app_init(void) {
  bucket_handles[0] = xQueueCreate(8, sizeof(my_msg_t));
  bucket_handles[1] = xQueueCreate(8, sizeof(my_msg_t));
  bucket_handles[2] = xQueueCreate(8, sizeof(my_msg_t));
  bucket_handles[3] = xQueueCreate(8, sizeof(my_msg_t));

  bucketed_pqueue_init(&pq, bucket_handles, NUM_BUCKETS, consumer_task_handle);
}
```

### Strong recommendation

<p class="callout info"><span style="white-space: pre-wrap;">All buckets should use the </span>**same item type or at least size**.</p>

<span style="white-space: pre-wrap;">Technically the module does not enforce that. Practically, mixing queue item sizes makes the API awkward and error-prone, because </span>`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">peek()</span>`<span style="white-space: pre-wrap;"> write into a single </span>`<span class="editor-theme-code">out</span>`<span style="white-space: pre-wrap;"> buffer and the caller has no type information at that point.</span>

## Usage model

### Producer side

A producer decides the priority and pushes into the matching bucket.

Example:

```c
my_msg_t msg = { ... };
bucketed_pqueue_push(&pq, PRIORITY_HIGH, &msg, pdMS_TO_TICKS(10));
```

From an ISR:

```c
BaseType_t higher_woken = pdFALSE;
my_msg_t msg = { ... };

bucketed_pqueue_push_from_isr(&pq, PRIORITY_HIGH, &msg, &higher_woken);
portYIELD_FROM_ISR(higher_woken);
```

### Consumer side

The consumer repeatedly pops the highest-priority item available.

Example:

```c
my_msg_t msg;

while (bucketed_pqueue_pop(&pq, &msg) == RESULT_OK) {
  process_msg(&msg);
}
```

<span style="white-space: pre-wrap;">Because </span>`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> is non-blocking, a typical design is:</span>

1. consumer task blocks on a task notification
2. <span style="white-space: pre-wrap;">on wakeup, it calls </span>`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> in a loop until </span>`<span class="editor-theme-code">RESULT_ERR_NOT_FOUND</span>`

Example pattern:

```c
for (;;) {
  uint32_t notified_bits = 0;
  xTaskNotifyWait(0, UINT32_MAX, &notified_bits, portMAX_DELAY);

  my_msg_t msg;
  while (bucketed_pqueue_pop(&pq, &msg) == RESULT_OK) {
    process_msg(&msg);
  }
}
```

<span style="white-space: pre-wrap;">This pattern works well with the module’s </span>`<span class="editor-theme-code">notifier</span>`<span style="white-space: pre-wrap;"> mechanism.</span>

## Notification behavior

<span style="white-space: pre-wrap;">If </span>`<span class="editor-theme-code">notifier</span>`<span style="white-space: pre-wrap;"> is provided during initialization, every successful push sends a task notification with:</span>

```c
1UL << prio
```

<span style="white-space: pre-wrap;">using </span>`<span class="editor-theme-code">eSetBits</span>`.

This means:

- <span style="white-space: pre-wrap;">pushes do </span>**not overwrite**<span style="white-space: pre-wrap;"> previous notification bits</span>
- multiple buckets can be represented in the task notification value
- repeated pushes to the same priority keep the same bit set

### What the notification means

The notification indicates that at least one push occurred into that bucket.

<span style="white-space: pre-wrap;">It does </span>**not**<span style="white-space: pre-wrap;"> guarantee:</span>

- how many items are in that bucket
- that the bucket is still non-empty by the time the task wakes
- that the bitmap and queues are perfectly synchronized at all times

It is a wakeup hint, not a count.

<span style="white-space: pre-wrap;">That is fine. The consumer should drain the queue with repeated </span>`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> calls rather than assuming one notification equals one item.</span>

## Concurrency model and assumptions

This section matters more than the function list.

### Supported access pattern

#### Supported

- multiple producer tasks
- ISR producers
- <span style="white-space: pre-wrap;">one consumer task calling </span>`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> and/or </span>`<span class="editor-theme-code">peek()</span>`

#### Not supported

- <span style="white-space: pre-wrap;">multiple concurrent consumers calling </span>`<span class="editor-theme-code">pop()</span>`
- one task peeking while another task pops in a way that assumes strong synchronization guarantees
- external code modifying the underlying bucket queues directly behind this module’s back

<p class="callout danger"><span style="white-space: pre-wrap;">The module uses a bitmap plus queue operations, but it does </span>**not**<span style="white-space: pre-wrap;"> implement a full multi-consumer synchronization scheme around dequeue behavior.</span></p>

### Why single-consumer matters

The bitmap is read, scanned, and repaired in steps.

That is acceptable with one consumer, because any race is limited to producer updates and queue state changes, and the consumer can repair stale bits safely.

With multiple consumers, two tasks could:

- observe the same bitmap snapshot
- both decide the same bucket has data
- one drains the queue
- the other sees an empty queue and clears the bit

That alone is survivable, but once multiple consumers are simultaneously probing and repairing, reasoning about ordering and fairness gets messy fast. This module avoids that entire circus by assuming a single consumer.

### Critical sections

The bitmap is protected with FreeRTOS critical sections:

- `<span class="editor-theme-code">taskENTER_CRITICAL()</span>`<span style="white-space: pre-wrap;"> / </span>`<span class="editor-theme-code">taskEXIT_CRITICAL()</span>`
- `<span class="editor-theme-code">taskENTER_CRITICAL_FROM_ISR()</span>`<span style="white-space: pre-wrap;"> / </span>`<span class="editor-theme-code">taskEXIT_CRITICAL_FROM_ISR()</span>`

<span style="white-space: pre-wrap;">These critical sections protect </span>**bitmap access**, not the whole queue operation sequence.

That means queue operations and bitmap updates are not one indivisible transaction.

<span style="white-space: pre-wrap;">This is intentional and mostly fine for the intended model, but maintainers should understand that the bitmap is a </span>**best-effort summary**, not a perfect mirror of queue state.

## Known implementation characteristics

### Priority scan is linear in number of buckets

`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">peek()</span>`<span style="white-space: pre-wrap;"> scan from highest to lowest priority:</span>

```c
for (int prio = num_buckets - 1; prio >= 0; prio--)
```

<span style="white-space: pre-wrap;">So the dequeue cost is </span>`<span class="editor-theme-code">O(num_buckets)</span>`<span style="white-space: pre-wrap;"> in the worst case.</span>

<span style="white-space: pre-wrap;">Since </span>`<span class="editor-theme-code">num_buckets <= 32</span>`, this is usually acceptable in embedded systems.

<p class="callout warning">If someone later decides to turn this into 128 priorities with a 32-bit bitmap, you might want to consider a better scanning system for all buckets.</p>

That said, the loop is incredibly efficient doing only a bit-wise check to know if the bucket is populated or not.

### Bitmap may be temporarily stale

The module can have these transient states:

- queue contains data but bitmap not yet updated
- bitmap says non-empty but queue is empty

<span style="white-space: pre-wrap;">The code handles the second case explicitly by clearing stale bits when </span>`<span class="editor-theme-code">xQueueReceive()</span>`<span style="white-space: pre-wrap;"> or </span>`<span class="editor-theme-code">xQueuePeek()</span>`<span style="white-space: pre-wrap;"> fails.</span>

The first case is shorter-lived and happens between a successful queue send and the bitmap update, or if initialization starts with pre-filled queues.

This is why the bitmap should be viewed as a hint structure.

### No ownership of queue storage

<p class="callout info">The module does not allocate or destroy the bucket queues.</p>

It only stores the queue handles provided by the caller.

The caller is responsible for:

- creating queues before initialization
- keeping them alive while the priority queue is in use
- ensuring item types and queue lengths are appropriate

### No reset/deinit API

There is no deinitialization function.

If needed, the caller must manage queue lifecycle itself.

If a reset feature is ever added, it must consider:

- draining or recreating each bucket
- <span style="white-space: pre-wrap;">resetting </span>`<span class="editor-theme-code">non_empty_mask</span>`
- possible interaction with producers still running

## Practical example

### Scenario

A system has three priorities:

- `<span class="editor-theme-code">0</span>`: telemetry
- `<span class="editor-theme-code">1</span>`: commands
- `<span class="editor-theme-code">2</span>`: emergency actions

All use the same message type:

```c
typedef struct {
  uint8_t type;
  uint32_t value;
} app_msg_t;
```

### Setup

```c
#define APP_NUM_PRIORITIES 3

static QueueHandle_t app_buckets[APP_NUM_PRIORITIES];
static bucketed_pqueue_t app_pq;

void app_queue_init(TaskHandle_t consumer_task) {
  app_buckets[0] = xQueueCreate(16, sizeof(app_msg_t));
  app_buckets[1] = xQueueCreate(16, sizeof(app_msg_t));
  app_buckets[2] = xQueueCreate(8, sizeof(app_msg_t));

  bucketed_pqueue_init(&app_pq, app_buckets, APP_NUM_PRIORITIES, consumer_task);
}
```

### Producer task

```c
void send_command(uint32_t cmd) {
  app_msg_t msg = {
    .type = 1,
    .value = cmd,
  };

  (void)bucketed_pqueue_push(&app_pq, 1, &msg, 0);
}
```

### ISR producer

```c
void emergency_isr(void) {
  BaseType_t higher_woken = pdFALSE;

  app_msg_t msg = {
    .type = 2,
    .value = 0xDEADU,
  };

  (void)bucketed_pqueue_push_from_isr(&app_pq, 2, &msg, &higher_woken);
  portYIELD_FROM_ISR(higher_woken);
}
```

### Consumer task

```c
void consumer_task(void *arg) {
  (void)arg;

  for (;;) {
    uint32_t notify_bits;
    xTaskNotifyWait(0, UINT32_MAX, &notify_bits, portMAX_DELAY);

    app_msg_t msg;
    while (bucketed_pqueue_pop(&app_pq, &msg) == RESULT_OK) {
      handle_message(&msg);
    }
  }
}
```

### [Result](https://bookstack.roboteamtwente.nl/books/embedded-infastructure/page/result-library "Result Library")

If telemetry, commands, and emergency messages all arrive, the consumer will process:

1. emergency
2. commands
3. telemetry

Within each class, messages remain FIFO.

## Error handling

<span style="white-space: pre-wrap;">The module uses </span>`<span class="editor-theme-code">result_t</span>`, which is defined elsewhere.

Based on the implementation, these results are used:

### `<span class="editor-theme-code">RESULT_OK</span>`

Operation succeeded.

### `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`

Returned when the caller passes invalid arguments, such as null pointers or out-of-range priority.

### `<span class="editor-theme-code">RESULT_ERR_OVERFLOW</span>`

Returned by push functions when the selected bucket queue cannot accept the item.

This usually means the bucket queue is full, or the send timed out.

### `<span class="editor-theme-code">RESULT_ERR_NOT_FOUND</span>`

<span style="white-space: pre-wrap;">Returned by </span>`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> or </span>`<span class="editor-theme-code">peek()</span>`<span style="white-space: pre-wrap;"> when no item is available.</span>

This is not necessarily an error in the usual sense. It is the normal result for an empty priority queue.

## Maintenance notes

### If you change the number of buckets beyond 32

You must also change:

- the bitmap type
- all bit shift logic
- input validation
- <span style="white-space: pre-wrap;">notification assumptions based on </span>`<span class="editor-theme-code">1UL << prio</span>`

<span style="white-space: pre-wrap;">Right now, 32 buckets is a </span>**hard architectural limit**.

### If you add blocking pop behavior

Be careful.

A naive implementation that blocks on each bucket in order would break strict priority or become awkward and expensive.

A better approach is usually to keep the existing design:

- producers notify a task
- consumer waits on notification
- <span style="white-space: pre-wrap;">consumer drains with non-blocking </span>`<span class="editor-theme-code">pop()</span>`

If you still add a blocking API, document its wakeup semantics very clearly.

### If you want multiple consumers

This requires redesign.

You would need to revisit:

- bitmap synchronization
- dequeue race handling
- <span style="white-space: pre-wrap;">semantics of </span>`<span class="editor-theme-code">peek()</span>`
- fairness between consumers
- whether the notifier model still makes sense

Do not label it “thread-safe” just because critical sections exist.

---

### If different buckets need different item types

The current API is not a good fit for that.

`<span class="editor-theme-code">pop()</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">peek()</span>`<span style="white-space: pre-wrap;"> return into one generic </span>`<span class="editor-theme-code">out</span>`<span style="white-space: pre-wrap;"> buffer with no explicit type metadata.</span>

If you need heterogeneous payloads, safer patterns are:

- use a tagged union message type
- store pointers to separately managed objects
- wrap payloads in a common envelope struct

### If queues are pre-filled before init

<span style="white-space: pre-wrap;">The bitmap starts at zero during </span>`<span class="editor-theme-code">bucketed_pqueue_init()</span>`.

So pre-filled queues will not be visible until something later sets the relevant bits, or until code is changed to rebuild the bitmap.

<span style="white-space: pre-wrap;">If supporting pre-filled queues matters, one possible improvement is for </span>`<span class="editor-theme-code">init()</span>`<span style="white-space: pre-wrap;"> to inspect each bucket using </span>`<span class="editor-theme-code">uxQueueMessagesWaiting()</span>`<span style="white-space: pre-wrap;"> and initialize </span>`<span class="editor-theme-code">non_empty_mask</span>`<span style="white-space: pre-wrap;"> accordingly.</span>

<span style="white-space: pre-wrap;">That behavior does </span>**not**<span style="white-space: pre-wrap;"> exist today.</span>

# Key-Value Pool

## Purpose

<span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">kv_pool</span>`<span style="white-space: pre-wrap;"> module implements a </span>**fixed-key key-value store backed by a custom memory pool**.

It is designed for systems where:

- keys are known as integer indices in a fixed range
- values are variable-sized blobs of bytes
- dynamic allocation from the general heap is undesirable or unavailable
- memory must come from a caller-provided region
- multiple threads may access the store concurrently

The module combines two things:

- <span style="white-space: pre-wrap;">a </span>**lookup table**<span style="white-space: pre-wrap;"> that maps keys to stored values</span>
- <span style="white-space: pre-wrap;">an internal </span>**heap allocator**<span style="white-space: pre-wrap;"> that manages the memory used by those values</span>

The result is a storage system where each key corresponds to one slot, and each valid slot points to a block allocated from the pool’s private heap.

<p class="callout info"><span style="white-space: pre-wrap;">This is not a dictionary in the desktop-software sense. Keys are not hashed, compared, or discovered dynamically. A key is just an </span>**index into a preallocated slot table**.</p>

## What problem this solves

This module exists to store variable-sized values in a memory-constrained system without relying on the standard heap.

It solves these problems:

- fixed set of logical keys, but variable-sized data per key
- need to provide memory externally
- need to safely read and update values across threads
- need to reclaim storage when a key is removed
- need predictable ownership of all stored data

<p class="callout info"><span style="white-space: pre-wrap;">Instead of calling </span>`<span class="editor-theme-code">malloc()</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">free()</span>`<span style="white-space: pre-wrap;"> from the general runtime allocator, the module manages a private heap inside caller-provided memory.</span></p>

That gives the application explicit control over:

- where memory lives
- how large the pool is
- how many keys exist
- whether metadata and data live together or in different memory regions

## High-Level Design

The module consists of two main parts.

### Lookup table

<span style="white-space: pre-wrap;">Each key corresponds to one </span>`<span class="editor-theme-code">kv_slot</span>`.

A slot contains:

- a per-slot lock
- a pointer to the stored data block
- the size of that block
- a validity flag

If a slot is valid, the key currently has stored data.

If a slot is invalid, the key is empty.

### Internal heap

Actual data bytes are stored inside a custom heap managed by the module.

This heap:

- lives in caller-provided memory
- uses a free-list allocator
- allocates variable-sized blocks
- coalesces adjacent free blocks when freeing

This means the slot table stores metadata only. The actual value bytes are stored elsewhere in the pool heap.

## Memory model

The pool manages two logical memory regions:

- **lookup table memory**
- **data heap memory**

These can be provided in two ways:

### Contiguous mode

<span style="white-space: pre-wrap;">One big memory block is given to </span>`<span class="editor-theme-code">kv_pool_init()</span>`. The module splits it into:

1. lookup table
2. data heap

### Fragmented mode

<span style="white-space: pre-wrap;">Separate memory regions are given to </span>`<span class="editor-theme-code">kv_pool_init_fragmented()</span>`. This allows the lookup table and data heap to live in different memory banks.

That can be useful when, for example:

- metadata should live in fast SRAM
- bulk value storage should live in larger but slower RAM

## Public API overview

The public API consists of:

- data structures:
    - `<span class="editor-theme-code">kv_slot</span>`
    - `<span class="editor-theme-code">kv_header</span>`
    - `<span class="editor-theme-code">kv_pool</span>`
- macros:
    - `<span class="editor-theme-code">KV_ALIGNMENT</span>`
    - `<span class="editor-theme-code">ALIGN(x)</span>`
    - `<span class="editor-theme-code">MINIMUM_BLOCK_SIZE</span>`
    - `<span class="editor-theme-code">LOOKUP_TABLE_SIZE(x)</span>`
- initialization:
    - `<span class="editor-theme-code">kv_pool_init_fragmented()</span>`
    - `<span class="editor-theme-code">kv_pool_init()</span>`
- key operations:
    - `<span class="editor-theme-code">kv_pool_get()</span>`
    - `<span class="editor-theme-code">kv_pool_write()</span>`
    - `<span class="editor-theme-code">kv_pool_insert()</span>`
    - `<span class="editor-theme-code">kv_pool_remove()</span>`
    - `<span class="editor-theme-code">kv_pool_is_index_valid()</span>`
- allocator functions:
    - `<span class="editor-theme-code">kv_pool_allocate()</span>`
    - `<span class="editor-theme-code">kv_pool_free()</span>`

The last two are currently exposed in the header, although they behave more like internal allocator primitives than ordinary user-facing API.

## Data structures

### `<span class="editor-theme-code">kv_slot</span>`

```c
typedef struct {
  atomic_flag slot_lock;
  void *data_ptr;
  size_t data_size;
  bool is_valid;
} kv_slot;
```

#### Purpose

Represents the metadata for one key.

##### Fields

`<span class="editor-theme-code">slot_lock</span>`

A spinlock protecting this slot’s metadata and associated data access.

Used to synchronize operations on a single key.

`<span class="editor-theme-code">data_ptr</span>`

Pointer to the allocated data block in the internal heap.

The caller must not free or reallocate this pointer directly.

`<span class="editor-theme-code">data_size</span>`

Size in bytes of the stored value.

`<span class="editor-theme-code">is_valid</span>`

Whether this slot currently contains valid data.

If false, the key is considered empty.

#### Important note

<p class="callout info">The key itself is not stored in the slot. The key is simply the slot’s index in the lookup table.</p>

### `<span class="editor-theme-code">kv_header</span>`

```c
typedef struct kv_header {
  size_t size;
  union {
    struct kv_header *next_free;
    char data[1];
  } as;
} kv_header;
```

#### Purpose

Header for blocks in the internal heap.

#### Role

<span style="white-space: pre-wrap;">When a block is free, </span>`<span class="editor-theme-code">as.next_free</span>`<span style="white-space: pre-wrap;"> links it into the free list.</span>

<span style="white-space: pre-wrap;">When a block is allocated, </span>`<span class="editor-theme-code">as.data</span>`<span style="white-space: pre-wrap;"> is the start of the user-visible payload.</span>

#### <span style="white-space: pre-wrap;">Meaning of </span>`<span class="editor-theme-code">size</span>`

`<span class="editor-theme-code">size</span>`<span style="white-space: pre-wrap;"> is the total size of the block, including the header and payload region.</span>

This is important for:

- pointer arithmetic
- block splitting
- coalescing adjacent free blocks

---

### `<span class="editor-theme-code">kv_pool</span>`

```c
typedef struct {
  void *pool_start;
  size_t pool_size;

  atomic_flag heap_lock;
  void (*delay)(void);
  kv_header *free_list_head;

  size_t max_keys;
  kv_slot *lookup_table;
} kv_pool;
```

#### Purpose

Represents the entire key-value pool.

#### Fields

`<span class="editor-theme-code">pool_start</span>`

Start address of the data heap region.

`<span class="editor-theme-code">pool_size</span>`

Size of the data heap region in bytes.

`<span class="editor-theme-code">heap_lock</span>`

Spinlock protecting heap allocator operations.

`<span class="editor-theme-code">delay</span>`

Callback invoked while waiting for a lock.

This is used during busy-waiting to avoid a pure tight spin.

`<span class="editor-theme-code">free_list_head</span>`

Head of the free-list allocator.

`<span class="editor-theme-code">max_keys</span>`

Maximum number of keys supported by this pool.

Valid keys are:

```c
0 <= key < max_keys
```

`<span class="editor-theme-code">lookup_table</span>`

Pointer to the slot array.

## Alignment and size macros

### `<span class="editor-theme-code">KV_ALIGNMENT</span>`

```c
#define KV_ALIGNMENT 16
```

All allocations are aligned to this boundary.

This affects:

- payload placement
- allocator block sizes
- pointer arithmetic

If this value changes, allocator behavior changes with it.

---

### `<span class="editor-theme-code">ALIGN(x)</span>`

```c
#define ALIGN(x) (((x) + (KV_ALIGNMENT - 1)) & ~(KV_ALIGNMENT - 1))
```

<span style="white-space: pre-wrap;">Rounds a size up to the next </span>`<span class="editor-theme-code">KV_ALIGNMENT</span>`<span style="white-space: pre-wrap;"> boundary.</span>

Used by the allocator.

---

### `<span class="editor-theme-code">MINIMUM_BLOCK_SIZE</span>`

```c
#define MINIMUM_BLOCK_SIZE sizeof(kv_header) + 1
```

Minimum block size allowed in the heap.

Used to decide whether a free block can be split.

The allocator will not create a leftover free fragment smaller than this.

---

### `<span class="editor-theme-code">LOOKUP_TABLE_SIZE(x)</span>`

```c
#define LOOKUP_TABLE_SIZE(x) (sizeof(kv_slot) * (x))
```

<span style="white-space: pre-wrap;">Returns the number of bytes required for a lookup table supporting </span>`<span class="editor-theme-code">x</span>`<span style="white-space: pre-wrap;"> keys.</span>

Used during initialization and memory size validation.

## Initialization APIs

### `<span class="editor-theme-code">kv_pool_init_fragmented()</span>`

```c
result_t kv_pool_init_fragmented(void *lookup_table,
                                 size_t lookup_table_size,
                                 size_t max_keys,
                                 void *pool_data,
                                 size_t pool_size,
                                 kv_pool *pool,
                                 void (*delay)(void));
```

#### Purpose

Initializes a pool from two separate memory regions:

- one for slot metadata
- one for heap storage

#### Parameters

`<span class="editor-theme-code">lookup_table</span>`

<span style="white-space: pre-wrap;">Memory for the </span>`<span class="editor-theme-code">kv_slot</span>`<span style="white-space: pre-wrap;"> array.</span>

`<span class="editor-theme-code">lookup_table_size</span>`

Size of the lookup table memory region in bytes.

`<span class="editor-theme-code">max_keys</span>`

Maximum number of keys.

`<span class="editor-theme-code">pool_data</span>`

Memory for the heap region.

`<span class="editor-theme-code">pool_size</span>`

Size of the heap region in bytes.

`<span class="editor-theme-code">pool</span>`

Output pool structure to initialize.

`<span class="editor-theme-code">delay</span>`

Function called while waiting for spinlocks.

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if required pointers are null or </span>`<span class="editor-theme-code">max_keys == 0</span>`
- `<span class="editor-theme-code">RESULT_ERR_BUFFER_TOO_SMALL</span>`<span style="white-space: pre-wrap;"> if:</span>
    - heap is too small
    - lookup table region is too small

#### What it does

1. validates inputs
2. <span style="white-space: pre-wrap;">clears both memory regions with </span>`<span class="editor-theme-code">memset</span>`
3. sets up the lookup table
4. sets up the free-list heap as one large free block
5. clears locks
6. marks all slots invalid

#### Important lifetime rule

<p class="callout warning">The memory regions provided to this function must remain valid for the entire lifetime of the pool.</p>

The module does not copy them.

---

### `<span class="editor-theme-code">kv_pool_init()</span>`

```c
result_t kv_pool_init(void *data,
                      size_t data_size,
                      size_t max_keys,
                      kv_pool *pool,
                      void (*delay)(void));
```

#### Purpose

Initializes a pool from one contiguous memory block.

#### Parameters

`<span class="editor-theme-code">data</span>`

Start of the full memory region.

`<span class="editor-theme-code">data_size</span>`

Total size of the region.

`<span class="editor-theme-code">max_keys</span>`

Number of keys.

`<span class="editor-theme-code">pool</span>`

Output pool structure.

`<span class="editor-theme-code">delay</span>`

Lock wait callback.

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_BUFFER_TOO_SMALL</span>`<span style="white-space: pre-wrap;"> if total memory is insufficient</span>
- <span style="white-space: pre-wrap;">any error returned by </span>`<span class="editor-theme-code">kv_pool_init_fragmented()</span>`

#### What it does

It computes:

- <span style="white-space: pre-wrap;">lookup table size = </span>`<span class="editor-theme-code">LOOKUP_TABLE_SIZE(max_keys)</span>`
- <span style="white-space: pre-wrap;">heap start = </span>`<span class="editor-theme-code">data + LOOKUP_TABLE_SIZE(max_keys)</span>`
- heap size = remaining bytes

<span style="white-space: pre-wrap;">Then it delegates to </span>`<span class="editor-theme-code">kv_pool_init_fragmented()</span>`.

#### When to use it

Use this function when you want simple setup from one static buffer.

<span style="white-space: pre-wrap;">Use </span>`<span class="editor-theme-code">kv_pool_init_fragmented()</span>`<span style="white-space: pre-wrap;"> when memory placement matters.</span>

## Key operations

### `<span class="editor-theme-code">kv_pool_get()</span>`

```c
result_t kv_pool_get(kv_pool *pool, int key, void *buffer, size_t *buffer_size);
```

#### Purpose

Copies the value for a key into a caller-provided buffer.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">key</span>`

Key index to read.

`<span class="editor-theme-code">buffer</span>`

Destination buffer for copied data.

`<span class="editor-theme-code">buffer_size</span>`

Input/output parameter.

- <span style="white-space: pre-wrap;">on input: capacity of </span>`<span class="editor-theme-code">buffer</span>`
- on output:
    - actual copied size on success
    - required size if buffer is too small

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if </span>`<span class="editor-theme-code">pool == NULL</span>`<span style="white-space: pre-wrap;"> or </span>`<span class="editor-theme-code">buffer_size == NULL</span>`
- `<span class="editor-theme-code">RESULT_ERR_NOT_FOUND</span>`<span style="white-space: pre-wrap;"> if key is out of range or not valid</span>
- `<span class="editor-theme-code">RESULT_ERR_BUFFER_TOO_SMALL</span>`<span style="white-space: pre-wrap;"> if destination buffer is too small</span>

#### Behavior

The function:

1. validates inputs
2. checks key bounds
3. locks the slot
4. verifies the slot is valid
5. checks whether the provided buffer is large enough
6. <span style="white-space: pre-wrap;">copies the stored data with </span>`<span class="editor-theme-code">memcpy</span>`
7. unlocks the slot

#### Important note

<span style="white-space: pre-wrap;">The function does </span>**not**<span style="white-space: pre-wrap;"> require </span>`<span class="editor-theme-code">buffer</span>`<span style="white-space: pre-wrap;"> to be non-null when the buffer is too small path is taken first, but in practice a null </span>`<span class="editor-theme-code">buffer</span>`<span style="white-space: pre-wrap;"> with a large enough </span>`<span class="editor-theme-code">buffer_size</span>`<span style="white-space: pre-wrap;"> would lead to invalid </span>`<span class="editor-theme-code">memcpy</span>`. So callers should always provide a valid buffer unless they intentionally use this as a size query pattern and know what they are doing.

<p class="callout warning"><span style="white-space: pre-wrap;">The implementation does not explicitly validate </span>`<span class="editor-theme-code">buffer != NULL</span>`. Why? No clue.</p>

### `<span class="editor-theme-code">kv_pool_write()</span>`

```c
result_t kv_pool_write(kv_pool *pool, int key, void *buffer, size_t buffer_size);
```

#### Purpose

Overwrites the existing value for a valid key without reallocating memory.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">key</span>`

Key index to overwrite.

`<span class="editor-theme-code">buffer</span>`

Source data to copy from.

`<span class="editor-theme-code">buffer_size</span>`

Size of new data.

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if </span>`<span class="editor-theme-code">pool == NULL</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">buffer == NULL</span>`, or size mismatches existing allocation
- `<span class="editor-theme-code">RESULT_ERR_NOT_FOUND</span>`<span style="white-space: pre-wrap;"> if key is invalid or not currently in use</span>

#### Behavior

The function:

1. validates arguments
2. checks that the key refers to a valid existing slot
3. locks the slot
4. <span style="white-space: pre-wrap;">checks that </span>`<span class="editor-theme-code">buffer_size</span>`<span style="white-space: pre-wrap;"> exactly matches the stored size</span>
5. copies new bytes over existing allocation
6. unlocks the slot

#### Important constraint

<p class="callout info"><span style="white-space: pre-wrap;">This function does </span>**not**<span style="white-space: pre-wrap;"> resize.</span></p>

The size must exactly match the existing allocation.

If the caller wants to store a different-sized value, they must:

- remove the key
- insert again with the new size

or implement a resize API in the future.

### `<span class="editor-theme-code">kv_pool_insert()</span>`

```c
result_t kv_pool_insert(kv_pool *pool, int key, void *data, size_t data_size);
```

#### Purpose

Allocates heap space and stores new data for a key.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">key</span>`

Key index to populate.

`<span class="editor-theme-code">data</span>`

Source bytes to copy into pool storage.

`<span class="editor-theme-code">data_size</span>`

Size of the data to store.

#### Returns

Actual implementation returns:

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if:</span>
    - `<span class="editor-theme-code">pool == NULL</span>`
    - `<span class="editor-theme-code">data == NULL</span>`
    - key is out of range
- `<span class="editor-theme-code">RESULT_ERR_NO_MEM</span>`<span style="white-space: pre-wrap;"> if allocation fails</span>

#### Inserting on an already allocated slot

<p class="callout info"><span style="white-space: pre-wrap;">If </span>`<span class="editor-theme-code">kv_pool_insert()</span>`<span style="white-space: pre-wrap;"> is called on a slot that is already valid, the old allocation is orphaned and leaked from the pool heap.</span></p>

So the safe usage rule today is:

- <span style="white-space: pre-wrap;">only call </span>`<span class="editor-theme-code">kv_pool_insert()</span>`<span style="white-space: pre-wrap;"> on an empty key</span>
- remove existing keys first before reinserting

The implementation does not enforce that, but callers must.

#### Internal behavior

The function:

1. validates inputs
2. locks the slot
3. marks the slot invalid and clears metadata
4. allocates a heap block
5. copies data into the new block
6. marks the slot valid
7. unlocks and returns

---

### `<span class="editor-theme-code">kv_pool_remove()</span>`

```c
result_t kv_pool_remove(kv_pool *pool, int key);
```

#### Purpose

Removes a key and frees its allocated data block.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">key</span>`

Key index to remove.

#### Returns

Actual implementation returns:

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if </span>`<span class="editor-theme-code">pool == NULL</span>`
- `<span class="editor-theme-code">RESULT_ERR_NOT_FOUND</span>`<span style="white-space: pre-wrap;"> if key is out of bounds or not valid</span>

#### Behavior

The function:

1. validates the pool pointer
2. <span style="white-space: pre-wrap;">verifies the key is valid with </span>`<span class="editor-theme-code">kv_pool_is_index_valid()</span>`
3. <span style="white-space: pre-wrap;">calls </span>`<span class="editor-theme-code">kv_pool_free()</span>`<span style="white-space: pre-wrap;"> on the stored pointer</span>

### `<span class="editor-theme-code">kv_pool_is_index_valid()</span>`

```c
result_t kv_pool_is_index_valid(kv_pool *pool, int key);
```

#### Purpose

Checks whether a key currently contains valid data.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">key</span>`

Key index to check.

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> if key is in range and valid</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if pool is null or key is out of range</span>
- `<span class="editor-theme-code">RESULT_ERR_NOT_FOUND</span>`<span style="white-space: pre-wrap;"> if slot is currently invalid</span>

#### Behavior

The function:

1. validates arguments
2. locks the slot
3. <span style="white-space: pre-wrap;">checks </span>`<span class="editor-theme-code">is_valid</span>`
4. unlocks and returns a snapshot result

#### Important concurrency note

<p class="callout warning">This is only a snapshot.</p>

<p class="callout warning">A slot that is valid at the time of the check may become invalid immediately afterward.</p>

So callers must not do:

1. `<span class="editor-theme-code">kv_pool_is_index_valid()</span>`
2. assume later access is now guaranteed safe forever

They must still handle failure from the actual operation.

## Allocator APIs

These are declared in the header and can be called externally, but they behave like internal heap primitives.

Using them directly requires understanding the allocator and slot ownership rules.

### `<span class="editor-theme-code">kv_pool_allocate()</span>`

```c
result_t kv_pool_allocate(kv_pool *pool, size_t size, void **out_ptr);
```

#### Purpose

Allocates a block from the pool heap.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">size</span>`

Payload size requested.

`<span class="editor-theme-code">out_ptr</span>`

Output pointer for the allocated payload address.

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> for null pointers or zero size</span>
- `<span class="editor-theme-code">RESULT_ERR_NO_MEM</span>`<span style="white-space: pre-wrap;"> if no free block can satisfy the request</span>

#### Allocation strategy

<span style="white-space: pre-wrap;">The allocator uses </span>**first fit**<span style="white-space: pre-wrap;"> on the free list.</span>

It scans from the head until it finds the first block large enough.

#### Block sizing

The allocator computes:

- header offset to payload
- total required size including header
- alignment rounding
- minimum block size enforcement

#### Splitting

If the selected block is larger than needed and the remainder is big enough, it splits the block and leaves the remainder on the free list.

Otherwise it consumes the whole block.

### `<span class="editor-theme-code">kv_pool_free()</span>`

```c
result_t kv_pool_free(kv_pool *pool, void *ptr);
```

#### Purpose

Returns a previously allocated block to the pool heap.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">ptr</span>`

<span style="white-space: pre-wrap;">Pointer previously returned by </span>`<span class="editor-theme-code">kv_pool_allocate()</span>`<span style="white-space: pre-wrap;"> or stored in a slot.</span>

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if arguments are null</span>

#### Behavior

The function:

1. derives the block header from the payload pointer
2. inserts the block back into the sorted free list
3. coalesces with adjacent free neighbors when possible
4. <span style="white-space: pre-wrap;">scans the slot table for a matching </span>`<span class="editor-theme-code">data_ptr</span>`
5. if found, clears that slot’s metadata

#### Important consequence

`<span class="editor-theme-code">kv_pool_free()</span>`<span style="white-space: pre-wrap;"> does not merely free heap memory. It also tries to invalidate any slot pointing at that memory.</span>

That means allocator state and key-value metadata are coupled.

### Safe usage implication

<span style="white-space: pre-wrap;">External callers should not casually use </span>`<span class="editor-theme-code">kv_pool_allocate()</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">kv_pool_free()</span>`<span style="white-space: pre-wrap;"> unless they understand this coupling.</span>

If you free a pointer that a slot still references, the function will clear that slot.

<p class="callout info">If you allocate memory manually and never attach it to a slot, the allocator still works, but you are now using the pool partly as a raw allocator and partly as a key-value store, which increases maintenance complexity.</p>

## <span style="white-space: pre-wrap;">Role of </span>`<span class="editor-theme-code">delay()</span>`

<p class="callout info">The caller supplies the delay function during pool initialization.</p>

This allows board or environment-specific waiting behavior, such as:

- yielding
- sleeping
- short pause loop
- RTOS task delay
- platform-specific backoff

The pool does not define how delay behaves. That is the caller’s responsibility.

## Example usage

### Contiguous initialization

```c
static uint8_t kv_memory[2048];
static kv_pool pool;

static void pool_delay(void) {
  /* platform-specific wait/yield */
}

result_t app_kv_init(void) {
  return kv_pool_init(kv_memory, sizeof(kv_memory), 16, &pool, pool_delay);
}
```

This creates a pool with:

- 16 keys
- <span style="white-space: pre-wrap;">lookup table at the start of </span>`<span class="editor-theme-code">kv_memory</span>`
- heap in the remaining bytes

## Insert value

```c
uint32_t value = 1234;
result_t res = kv_pool_insert(&pool, 3, &value, sizeof(value));
```

<span style="white-space: pre-wrap;">This stores 4 bytes at key </span>`<span class="editor-theme-code">3</span>`.

---

## Read value

```c
uint32_t value = 0;
size_t size = sizeof(value);

result_t res = kv_pool_get(&pool, 3, &value, &size);
```

On success:

- `<span class="editor-theme-code">res == RESULT_OK</span>`
- `<span class="editor-theme-code">value</span>`<span style="white-space: pre-wrap;"> contains the stored bytes</span>
- `<span class="editor-theme-code">size == sizeof(uint32_t)</span>`

### Handle too-small buffer

```c
uint8_t small_buf[4];
size_t size = sizeof(small_buf);

result_t res = kv_pool_get(&pool, key, small_buf, &size);
if (res == RESULT_ERR_BUFFER_TOO_SMALL) {
  /* size now contains required size */
}
```

This is the intended size negotiation pattern.

### Overwrite same-sized value

```c
uint32_t new_value = 5678;
result_t res = kv_pool_write(&pool, 3, &new_value, sizeof(new_value));
```

<span style="white-space: pre-wrap;">This succeeds only if key </span>`<span class="editor-theme-code">3</span>`<span style="white-space: pre-wrap;"> already exists and has exactly 4 bytes allocated.</span>

### Remove key

```c
result_t res = kv_pool_remove(&pool, 3);
```

This frees the associated heap block and invalidates the slot.

## Backend and platform independence

<p class="callout info">The API is largely independent of where memory comes from and how waiting is implemented.</p>

The caller provides:

- raw memory regions
- a delay function
- <span style="white-space: pre-wrap;">the </span>`<span class="editor-theme-code">kv_pool</span>`<span style="white-space: pre-wrap;"> object itself</span>

That means different boards or environments can use the same API with different backing strategies, for example:

- contiguous static RAM region on one board
- split metadata/data regions across different RAM banks on another
- RTOS yield in the delay callback on one target
- busy-wait pause or test hook in host-side simulation

<p class="callout info"><span style="white-space: pre-wrap;">The implementation is therefore </span>**memory-placement agnostic**<span style="white-space: pre-wrap;"> and </span>**wait-strategy agnostic**, even though the current source file provides one specific allocator and lock implementation.</p>

This is useful for portability, provided each target respects the same concurrency and memory lifetime contract.

## Recommended usage rules for current code

Given the implementation as it exists today, these rules are the safest:

1. Initialize once before concurrent use.
2. Provide memory that remains valid for the full pool lifetime.
3. <span style="white-space: pre-wrap;">Use </span>`<span class="editor-theme-code">insert()</span>`<span style="white-space: pre-wrap;"> only on empty keys.</span>
4. <span style="white-space: pre-wrap;">Use </span>`<span class="editor-theme-code">write()</span>`<span style="white-space: pre-wrap;"> only when new data size exactly matches old size.</span>
5. <span style="white-space: pre-wrap;">Do not rely on </span>`<span class="editor-theme-code">is_index_valid()</span>`<span style="white-space: pre-wrap;"> as a guarantee for later access.</span>
6. Treat direct allocator calls as advanced/internal use.
7. Do not assume allocator concurrency is fully correct (there is definitely a bug or two in there).
8. <span style="white-space: pre-wrap;">Always pass a valid destination buffer to </span>`<span class="editor-theme-code">get()</span>`<span style="white-space: pre-wrap;"> when copying data.</span>

# Unit Testing

## Purpose

<span style="white-space:pre-wrap;">Unit tests in this repository are designed to validate </span>**component behavior**, not entrypoint wiring.

- <span style="white-space:pre-wrap;">Tests target logic in </span>`<span class="editor-theme-code">components/common/*</span>`<span style="white-space:pre-wrap;"> and </span>`<span class="editor-theme-code">components/<board>/*</span>`.
- `<span class="editor-theme-code">src/<board>/main.c</span>`<span style="white-space:pre-wrap;"> remains focused on initialization and task orchestration.</span>
- <span style="white-space:pre-wrap;">Test ownership mirrors component ownership: each component should have corresponding tests under </span>`<span class="editor-theme-code">test/<owner>/</span>`.

## Test stack

The project uses PlatformIO’s unit testing framework with Unity.

- Each test binary follows the Unity lifecycle:
    - `<span class="editor-theme-code">UNITY_BEGIN()</span>`
    - `<span class="editor-theme-code">RUN_TEST(...)</span>`
    - `<span class="editor-theme-code">UNITY_END()</span>`
- Global Unity output configuration is provided via:
    - `<span class="editor-theme-code">test/unity_config.h</span>`
    - `<span class="editor-theme-code">test/unity_config.c</span>`
- The current output implementation initializes UART and writes test output character-by-character over a COM interface.

---

## Test layout

Tests are organized by ownership and module:

```
test/
├─ common/
│  ├─ test_bucketed_pqueue/
│  ├─ test_kv_pool/
├─ sensor_board/
│  ├─ test_gps_sensor/
│  ├─ test_imu_sensor/
│  ├─ test_ph_sensor/
│  ├─ test_sensor_basics/
├─ driving_board/
│  ├─ test_calculator/
│  ├─ test_motor/
├─ debugging_board/
│  ├─ test_input_handler/
├─ unity_config.c
└─ unity_config.h
```

Naming conventions:

- <span style="white-space:pre-wrap;">Directory: </span>`<span class="editor-theme-code">test/<owner>/test_<module>/</span>`
- <span style="white-space:pre-wrap;">File: </span>`<span class="editor-theme-code">test_<behavior>.c</span>`<span style="white-space:pre-wrap;"> or </span>`<span class="editor-theme-code">test_<module>.c</span>`
- <span style="white-space:pre-wrap;">Function: </span>`<span class="editor-theme-code">test_<expected_behavior>()</span>`

## Running tests

Run all tests for a specific environment:

```
pio test -e sensor_board
```

Run all tests across all environments:

```
pio test
```

Run a specific test directory:

```
pio test -e sensor_board -f test_imu_sensor
```

## Environment test selection (`<span class="editor-theme-code">platformio.ini</span>`)

<span style="white-space:pre-wrap;">Test execution is controlled per environment using the </span>`<span class="editor-theme-code">test_filter</span>`<span style="white-space:pre-wrap;"> setting.</span>

Current configuration:

- `<span class="editor-theme-code">env:sensor_board</span>`<span style="white-space:pre-wrap;"> → </span>`<span class="editor-theme-code">test_filter = sensor_board/*</span>`
- `<span class="editor-theme-code">env:driving_board</span>`<span style="white-space:pre-wrap;"> → </span>`<span class="editor-theme-code">test_filter = driving_board/*</span>`
- `<span class="editor-theme-code">env:debugging_board</span>`<span style="white-space:pre-wrap;"> → </span>`<span class="editor-theme-code">test_filter = common/*</span>`

<span style="white-space:pre-wrap;">When adding new tests, ensure the corresponding environment includes the test path in its </span>`<span class="editor-theme-code">test_filter</span>`. Otherwise, the tests will not be executed.

## Writing a new unit test

### 1) Place it by ownership

Tests must follow the same ownership structure as the components.

Example:

- <span style="white-space:pre-wrap;">Component: </span>`<span class="editor-theme-code">components/common/kv_pool</span>`
- <span style="white-space:pre-wrap;">Test location: </span>`<span class="editor-theme-code">test/common/test_kv_pool/</span>`

### 2) Use Unity structure

```c
#include "unity.h"

void setUp(void) {}
void tearDown(void) {}

void test_example_behavior(void) {
    TEST_ASSERT_TRUE(1);
}

int main(void) {
    UNITY_BEGIN();
    RUN_TEST(test_example_behavior);
    return UNITY_END();
}
```

---

### 3) Assert behavior, not implementation

- Use public APIs instead of accessing internal state directly.
- Cover both success and failure cases.
- Use precise assertions:
    - `<span class="editor-theme-code">TEST_ASSERT_EQUAL</span>`
    - `<span class="editor-theme-code">TEST_ASSERT_FLOAT_WITHIN</span>`
    - etc.

### 4) Keep tests deterministic

- Avoid reliance on shared or previous test state.
- <span style="white-space:pre-wrap;">Reset all required state in </span>`<span class="editor-theme-code">setUp</span>`.
- Use time-based operations only when necessary, and keep them bounded.

## What to test

- Public functions in component modules
- Input validation and error handling
- Boundary conditions and invalid arguments
- State transitions and invariants

## What not to test as unit tests

The following are outside the scope of unit testing:

- <span style="white-space:pre-wrap;">Full board startup flows in </span>`<span class="editor-theme-code">main.c</span>`
- End-to-end hardware integration
- Multi-component system orchestration

These belong to integration or system-level testing.

## Troubleshooting

- <span style="white-space:pre-wrap;">No tests executed: verify </span>`<span class="editor-theme-code">test_filter</span>`<span style="white-space:pre-wrap;"> for the selected environment (</span>`<span class="editor-theme-code">-e</span>`)
- Test not detected: ensure correct directory naming (`<span class="editor-theme-code">test_<module></span>`<span style="white-space:pre-wrap;">) under </span>`<span class="editor-theme-code">test/</span>`
- <span style="white-space:pre-wrap;">No Unity output: check UART/COM configuration in </span>`<span class="editor-theme-code">test/unity_config.c</span>`<span style="white-space:pre-wrap;"> and board connection settings</span>