Telemetry¶
The telemetry library is the downlink path for in-flight data. It is a
small dispatcher: every backend that registers a
telemetry_backend at link time receives each outgoing
message. Backends own their own framing, transport, worker threads, and
any backend-specific rate limiting.
Today only the HC-12 433 MHz UART-RF bridge backend ships in-tree, but the API is transport-agnostic: a LoRaWAN, CAN-tunnel, or any other backend can be added without touching the dispatcher or callers.
Architecture¶
The dispatcher is synchronous and runs in the caller’s thread: it walks
the iterable section and invokes each backend’s send_sm_update hook
inline. Backends that need to do real work (UART writes, radio TX) must
offload to their own worker thread and return immediately.
The dispatcher itself never blocks.
Backends¶
HC-12 (
CONFIG_AURORA_TELEMETRY_HC12): transparent UART ↔ 433 MHz RF bridge. Selects its UART via the chosenauxspace,telemetry-uartnode. The module itself is provisioned out of band on the bench (channel, air baud, TX power); firmware only opens the UART. See HC-12 wire frame and HC-12 threading and rate limiting.
Adding a new backend¶
A backend is two things: a vtable filled with the operations it
supports, and a single TELEMETRY_BACKEND_DEFINE invocation to
register it at link time.
#include <aurora/lib/telemetry.h>
static int my_init(void) { /* ... */ return 0; }
static int my_send_sm_update(enum sm_state state, enum sm_type type,
const struct sm_inputs *inputs)
{
/* Frame and enqueue. Must not block. Return:
* 0 on accept,
* -EAGAIN if throttled,
* -ENOMEM if your TX queue is full,
* -ENODEV if the transport is not ready.
*/
return 0;
}
static const struct telemetry_backend_api my_api = {
.init = my_init,
.send_sm_update = my_send_sm_update,
};
TELEMETRY_BACKEND_DEFINE(my_backend, &my_api);
Add a CONFIG_AURORA_TELEMETRY_<BACKEND> symbol under
lib/telemetry/Kconfig and a conditional
zephyr_library_sources(...) block in lib/telemetry/CMakeLists.txt
to compile it in. No changes to the dispatcher or to callers
(telemetry_send_sm_update) are needed.
HC-12 wire frame¶
The HC-12 backend frames each state-machine update as a small self-describing packet so the ground station can resync after RF corruption. All multi-byte fields are little-endian.
Offset |
Size |
Field |
|---|---|---|
0 |
1 |
magic0 = |
1 |
1 |
magic1 = |
2 |
1 |
type (see below) |
3 |
1 |
len (payload bytes that follow, excluding CRC) |
4 |
len |
payload |
4 + len |
2 |
CRC-16/CCITT (init |
Packet types:
Value |
Name |
Payload |
|---|---|---|
|
|
State-machine snapshot (see below) |
SM_UPDATE payload (36 bytes):
Offset |
Size |
Type |
Field |
|---|---|---|---|
0 |
4 |
|
|
4 |
1 |
|
|
5 |
1 |
|
|
6 |
1 |
|
|
7 |
1 |
|
reserved (zero) |
8 |
4 |
|
|
12 |
4 |
|
|
16 |
4 |
|
|
20 |
4 |
|
|
24 |
12 |
|
|
At 10 Hz the link runs at roughly 420 B/s, about 44 % of a 9600-baud HC-12 air link, leaving headroom for re-tries and other packet types.
HC-12 threading and rate limiting¶
The HC-12 backend hands every outgoing frame to a dedicated worker
thread via a bounded FIFO message queue. The producer
(telemetry_send_sm_update(), called from the state-machine task)
never blocks:
Frames are framed and CRC’d inline on the caller’s stack, then posted with
K_NO_WAIT. A full queue returns-ENOMEMand drops the frame rather than stalling the SM thread.The worker drains the queue and writes bytes with
uart_poll_out. Keeping it on a low-priority thread (default priority 10) ensures telemetry can never preempt flight-critical threads (sensors and the state machine run at priority 5–6).Optional rate limiting drops frames before they touch the queue or the UART. Useful when the SM tick rate is higher than the air link can carry comfortably (the default 0 disables it).
Tunables (under AURORA_TELEMETRY_HC12):
Kconfig |
Default |
Purpose |
|---|---|---|
|
16 |
Maximum queued frames before overflow drops. |
|
0 |
Minimum spacing between accepted SM updates (ms). 0 = unlimited. |
|
1024 |
Worker thread stack size (bytes). |
|
10 |
Worker thread priority. Keep numerically above flight threads (priority 5) so telemetry never preempts them. |
Device-tree¶
The HC-12 backend is bound to a devicetree node with the compatible
auxspaceev,hc12 (see dts/bindings/hc12/auxspaceev,hc12.yaml).
The node carries a phandle to the host UART and, optionally, the
SET line:
Minimal node. Transparent downlink only, no runtime AT support:
/ {
hc12: hc12 {
compatible = "auxspaceev,hc12";
uart = <&uart1>;
status = "okay";
};
};
Add set-gpios to also enable runtime provisioning through the
shell (see Provisioning shell):
hc12: hc12 {
compatible = "auxspaceev,hc12";
uart = <&uart1>;
set-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;
status = "okay";
};
Boards that do not mount an HC-12 leave the node disabled (or omit it
entirely); the AURORA_TELEMETRY_HC12 symbol is then unselectable
(DT_HAS_AUXSPACEEV_HC12_ENABLED resolves to n) and the backend
is compiled out. The default sensor_board_v2 DTS ships the node
status = "disabled" so each application opts in explicitly via an
overlay.
Provisioning¶
The HC-12 stores channel, air baud, TX power and FU mode in its own
non-volatile flash. Settings persist across power cycles, so the
firmware never sends AT commands at boot: it only configures SET
(when wired) to transparent mode and opens the UART. Provision the
module once, when something needs to change.
The HC-12 enters AT-command mode while its SET pin is pulled low.
In AT mode the module always speaks 9600 baud regardless of the
transparent-mode air baud. There are two ways to provision it:
On the bench with a USB-serial adapter: works on any board, no firmware support required, no risk of stalling the avionics bus.
From the firmware shell: works on boards whose HC-12 has its
SETline wired to a GPIO and that build withCONFIG_AURORA_TELEMETRY_HC12_SHELL=y.
Bench provisioning¶
Pull SET low, connect a USB-serial adapter at 9600 baud, and send:
Command |
Effect |
|---|---|
|
Sanity check; module replies |
|
Set air baud (must match host |
|
Set channel ( |
|
Maximum TX power (~20 dBm). |
|
Mode 3 (good range/throughput compromise; default). |
|
Read back the current settings. |
|
Restore factory defaults. |
Release SET to exit AT mode. The ground-side HC-12 must use the
same channel, baud, and FU mode.
Provisioning shell¶
Enable CONFIG_AURORA_TELEMETRY_HC12_SHELL=y (which selects
CONFIG_AURORA_TELEMETRY_HC12_AT=y) and the telemetry hc12
command tree appears on the Zephyr shell:
Command |
Effect |
|---|---|
|
Issue |
|
|
|
|
|
|
|
|
|
|
|
Raw escape hatch, e.g. |
The shell handler:
Refuses every command unless the state machine is in IDLE. Reprovisioning while armed would silence the downlink mid-flight and stall the UART worker for ~200 ms; that is never the right thing to do in any other state.
Holds a mutex on the HC-12 UART for the duration of each AT exchange. The TX worker thread blocks on the same mutex per outgoing frame, so transparent-mode bytes can never collide with an AT command. Frames generated while AT is in progress queue up normally and drain when the lock is released.
Restores the host UART baud on every exit path (including errors), so a failed
AT+Bcannot leave the host out of sync with the radio.
If the set-gpios property is absent from the HC-12 node, every
command returns -ENOTSUP and prints a hint pointing at the bench
flow.
Usage from the application¶
main.c calls telemetry_init() once at boot, then
telemetry_send_sm_update() once per state-machine tick:
#if defined(CONFIG_AURORA_TELEMETRY)
(void)telemetry_init();
#endif
/* ... inside the state-machine loop, after sm_update(): */
#if defined(CONFIG_AURORA_TELEMETRY)
struct sm_inputs sm_in;
sm_get_inputs(&sm_in);
(void)telemetry_send_sm_update(state, &sm_in);
#endif
The return value of telemetry_send_sm_update() is the first
non-zero error any backend returned; remaining backends are still
called. Callers in the hot path typically ignore it.
API Reference¶
-
int telemetry_init(void)¶
Initialise all registered telemetry backends.
- Return values:
0 – on success, or the first non-zero return from a backend.
-
int telemetry_send_sm_update(enum sm_state state, enum sm_type type, const struct sm_inputs *inputs)¶
Fan out a state-machine update to all registered backends.
Safe to call from any thread context. Never blocks — backends must enforce that themselves (typically by dropping on overflow).
- Parameters:
state – Current flight state.
type – Active state machine implementation ID (see sm_get_type). Forwarded so the receiver can decode
statewithout prior agreement.inputs – Current SM inputs snapshot (see sm_get_inputs).
- Return values:
0 – if every backend accepted the message.
<0 – the first error returned by any backend (others still tried).
-
TELEMETRY_BACKEND_DEFINE(_name, _api)¶
Register a telemetry backend (link-time).
- Parameters:
_name – Unique C identifier for this backend.
_api – Pointer to a telemetry_backend_api vtable.
-
struct telemetry_backend_api¶
- #include <telemetry.h>
Backend operations vtable.
All members are optional; NULL slots are skipped by the dispatcher.
-
struct telemetry_backend¶
- #include <telemetry.h>
Backend descriptor. Collected at link time.