Skip to main content

High Level Overview

1) Purpose

The packet dispatcher is used to decode protobuf frames.

Application code usually wants:

  • strongly typed decoded payloads
  • one handler per packet type
  • decoupling between input reception and packet processing

This module solves that by:

  1. receiving a raw protobuf receive_frame
  2. decoding it into PBEnvelope
  3. determining which_payload
  4. finding the corresponding handler
  5. copying the decoded payload into that handler’s (freeRTOS) queue
  6. letting a dedicated task call callback for this handler

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 transport-level bytes and application-level packet handler.

In practical terms, it is a decode-and-dispatch layer between an input source that receives raw bytes and a set of application handlers that want already-decoded payloads

The implementation has some assumptions and hazards that absolutely need to be understood before you start messing with its internal structure.



2) High-level design

The design has three major parts:

  • Global handler registry
  • One queue & task per packet type
  • Shared decode step

a. Global handler registry

The global handler registry contains an array of packet handler tasks (type: packet_handler_config_t) . This is given by the caller at initialization time. One packet handler task contains information on the handler for a specific type of packet, see packet_handler_config_t. This array is stored globally and used by dispatch logic for packet type lookup.

What we call a packet is a raw protobuf.
What we call a handler is a callback function for a specific protobuf/packet.

b. One queue & task per packet type

Each handler configuration creates 1 FreeRTOS queue and 1 FreeRTOS task. The dispatcher enqueues decoded payloads into the correct queue. The corresponding task blocks that queue and calls the handler callback.

The task takes the 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.

Task lifecycle

Each handler task is intended to live forever.

Its lifecycle is:

  1. created by PacketHandlerStart() (part of the initialization process (see PacketDispatcherInit())
  2. validate config
  3. allocate local packet buffer
  4. block forever on queue receive
  5. process packets as they arrive

It terminates only if:

  • config is invalid
  • queue is null
  • heap allocation for packet buffer fails

In those cases it deletes itself.

At the moment, there is no restart or supervision mechanism in this module.

c. Shared decode step

Incoming frames are decoded into a global static PBEnvelope object:

static PBEnvelope DecodingEnvelopeCurrent;

The dispatcher then copies DecodingEnvelopeCurrent.payload into a handler queue.

This detail matters a lot for concurrency and payload sizing.



3) External dependencies

This is not a standalone module. It sits in the middle of RTOS tasking, protobuf decoding, and transport reception.

This module depends on:


specifically used pieces

FreeRTOS

  • xQueueCreateStatic
  • xQueueReceive
  • xQueueSend
  • xTaskCreate
  • vTaskDelete

nanopb / protobuf decoding

  • pb_istream_from_buffer
  • pb_decode

PBEnvelope generated protobuf definitions

  • PBEnvelope_fields
  • PBEnvelope_size

Logging library


Result Library


stm/ethernet_udp.h

  • receive_frame