# 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);
```

####