Skip to content

CogniPilot/synapse_fbs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

synapse_fbs

FlatBuffers schemas and generated language bindings for Synapse.

This repository is the schema source of truth for Synapse messages. It keeps the checked-in source small and uses CI to generate the language bindings and release artifacts from the pinned toolchain in tools.lock.

Published Synapse FlatBuffers documentation: https://cognipilot.github.io/synapse_fbs/

Design decisions and the phased plan are recorded in ROADMAP.md; the analysis behind them is in REVIEW.md.

Motivation

Synapse messages are designed for vehicles that exchange state, sensor, and control data in real time across three transport regimes:

  • On chip: message passing between processors over shared memory (Zephyr RTOS or similar).
  • Off chip: IPC between an onboard computer and embedded flight control.
  • Over the air: long-distance ground-control links where latency, range, and reliability matter.

One semantic message set serves all three. Every runtime payload is a fixed-layout FlatBuffers struct, so the same bytes work as a shared-memory ABI, a Zenoh value, a radio frame payload, and a log record with zero re-serialization. Scaled integer fields preserve required precision without wasting bytes: 0.1–1 mm-class position precision is the practical ceiling, so fields are sized to that and no further, and nothing is packed below byte alignment. Little-endian byte order is a protocol requirement.

The schemas also need to be easy to consume outside embedded firmware. The published npm, Python, Rust, C, and C++ artifacts keep browser tools, cloud services, developer scripts, and vehicle software on the same schema source.

Conventions

Frame conventions are layered:

  • Raw sensor topics carry the sensor's native conventions, documented per field. For example GnssFix course over ground and receiver yaw are clockwise from true north, exactly as receivers report them, so logs stay faithful to the hardware.
  • Estimate and command topics follow ROS REP-0103: ENU for local/world vectors (x east, y north, z up), FLU for body vectors (x forward, y left, z up), SI units, angles zero-east positive counter-clockwise. The estimator converts once; consumers never mix conventions within a layer.
  • Operator displays format however pilots expect (compass headings); display formatting is never a wire concern.

Quaternions are Hamilton convention, component order w x y z, and rotate body-frame FLU vectors into the world ENU frame. The quaternion is the only attitude representation on the wire; Euler angles are derived by consumers.

Field validity uses one schema-defined flags bitmask per message — no sentinel values. Core semantics (GNSS fix type, text severity, command result codes, battery charge states, validity bits, command type masks) are FlatBuffers enums in the schema; only genuinely vehicle-specific taxonomies (flight modes, vehicle types) remain producer-defined.

Zenoh

Synapse is designed to work naturally with Zenoh.

Keys. Canonical key expressions are:

synapse/v1/topic/<topic_name>[/<instance>]   # pub/sub topics
synapse/v1/cmd/<command_name>                # queryable commands/transfers
synapse/v1/meta/...                          # reserved: schema metadata
synapse/v1/live/...                          # reserved: liveliness tokens

The v1 segment is the schema-major compatibility signal — bare-struct consumers have no other version indicator, and future breaking revisions run side by side as v2. Multi-instance sensor topics (inertial_sample, gnss_fix, power_status) append an instance segment so subscribers can select one sensor without decoding payloads.

Multi-vehicle deployments prepend a namespace from configuration (never hardcoded in firmware):

cub1/synapse/v1/topic/vehicle_health
cub2/synapse/v1/topic/inertial_sample/0

A ground station subscribes to */synapse/v1/topic/vehicle_health for every vehicle at one namespace level, or **/... for arbitrary nesting, and learns which vehicle a sample came from by the key it arrived on — the namespace replaces per-message system identifiers. The catalog helpers in every language parse namespaced keys back into namespace, topic, and instance.

Encoding. The canonical value for a fixed-layout topic is the bare payload struct bytes; the key identifies the type and the catalog records the exact byte size. Variable-size topics (TextStatus, mocap) and generic bridges use the thin FlatBuffers root tables; the catalog encoding field says which applies per topic.

Commands are queryables, not topics. A GCS issues get("cub1/synapse/v1/cmd/vehicle_command", payload) and receives CommandResultData replies (streaming InProgress until terminal). The transport provides correlation, timeout, and retry, so command messages carry no confirmation counters. Mission and parameter transfer use the same pattern with the request/reply tables in fbs/transfer.fbs. Streaming setpoints (AttitudeCommand, RateCommand, LocalPositionCommand) remain pub/sub topics.

Topic Catalog

The generated catalog is the source of truth for bridge and routing metadata: TopicId, canonical key, root table, fixed-layout payload type and byte size, scope (vehicle topics never leave the vehicle network; any topics may be bridged subject to rate policy), encoding, multi_instance, and the command key space. It ships as topics.json plus language helpers.

JavaScript:

import { keyForTopic, topicById, parseKey } from '@cognipilot/synapse-fbs';

const key = keyForTopic('VehicleHealth');
const parsed = parseKey('cub1/synapse/v1/topic/inertial_sample/0');
// parsed.namespace === 'cub1', parsed.topic.name === 'InertialSample',
// parsed.instance === 0

Python:

from synapse import topic_catalog

key = topic_catalog.key_for_topic("VehicleHealth")
parsed = topic_catalog.parse_key("cub1/synapse/v1/topic/inertial_sample/0")

Rust:

let key = synapse_fbs::topic_catalog::key_for_topic("VehicleHealth");
let parsed = synapse_fbs::topic_catalog::parse_key("cub1/synapse/v1/topic/inertial_sample/0");

C and C++ archives include topics.json and include/synapse/topic_catalog.h:

#include <synapse/topic_catalog.h>

size_t namespace_len;
int32_t instance;
const synapse_topic_info_t *topic =
    synapse_topic_parse_key("cub1/synapse/v1/topic/gnss_fix", &namespace_len, &instance);

Serial Links

Constrained raw byte-stream links should frame bare payload structs directly:

[sync][len:u16][topic_id:u16][seq:u8][flags:u8][bare payload struct][crc16]

roughly 8 bytes of overhead per message. Two framing rules replace what the Zenoh transport otherwise provides:

  • Retransmissions reuse the original seq so receivers deduplicate retried frames (important for non-idempotent commands when a reply frame is lost).
  • Command/transfer request-reply: frames with the request or reply flags bit set carry a synapse.cmd.CmdId value in the topic_id field instead of a TopicId, with seq correlating a reply to its request. The payload is the same request/reply message the Zenoh queryable would carry, so mission and parameter transfer work identically over serial.

Link-specific delimiting, integrity, authentication, or encryption belong to the framing layer, never inside topic payloads. The FlatBuffers Frame envelope in fbs/transport.fbs (file_identifier "SYFR") remains for generic bridges and consumers that need a self-contained FlatBuffers container.

Telemetry Aggregate

fbs/telemetry.fbs defines GcsStatus, a 40-byte display-oriented status aggregate (position, yaw, speeds, battery, mode, link, fix) for LoRa or satellite-class links at 0.2–1 Hz. It is never used for control. On SiK-class radios (~57.6 kbps) the normal topic set fits without it: a typical downlink (attitude and global position at 4 Hz, health, power, GNSS, and navigation at 1 Hz) is under 5 kbps in bare structs.

Logging

Synapse logs use MCAP as the container: schema records carry the generated .bfbs reflection schemas (flatbuffer schema encoding), channel topics are the canonical Zenoh keys, and messages are the table-wrapped topic payloads so Foxglove, PlotJuggler, and the mcap CLI decode them directly. Flight controllers stream index-less MCAP and files are recovered/reindexed post-flight. Release archives include bfbs/*.bfbs and bfbs.sha256 manifests for exactly this use.

ROS And FlatROS

ROS messages are local integration types, not the Synapse wire format. The common ROS message definitions are dynamically sized (string frame ids, float64[36] covariances), which makes them bulky on constrained links and ineligible for zero-copy loans, while CDR buffers cannot be overlaid as native structs. Synapse remains the compact fixed-layout protocol for vehicles, shared memory, Zenoh, logs, and serial frames.

ROS 2 integration happens at the edge through bridge nodes that translate selected Synapse topics into ROS concepts for visualization, autonomy stacks, simulation, and operator workflows. The planned flatros2 path generates a synapse_msgs package of fixed-size ROS mirrors from these schemas, uses rclcpp::TypeAdapter so nodes work on the generated structs directly, and drives a data-driven bridge from topics.json plus the .bfbs reflection schemas. ROS 2's Zenoh RMW derives its keys from topic and type names, so Synapse keys and the ROS graph share one router without collisions.

Schema Design Priorities

Fixed memory layout is the default for protocol payloads. Runtime telemetry, state, command, and control samples use FlatBuffers struct definitions so adapters share predictable native layouts and avoid allocation where the target language allows it. The fixed struct payload is the shared ABI for chip-to-chip communication and the wire encoding for Zenoh and radio links; serialized FlatBuffers tables remain available for transports and consumers that need root objects.

Use FlatBuffers table, string, or vector fields only when the data is naturally variable-size, optional, or needs FlatBuffers root/union behavior: thin root wrappers around fixed structs, transport envelopes, text status, cached definition records (mocap), and request/reply transfer messages.

Schema validation is enforced by xtask: every entity and field must be documented, quantitative fields must carry a recognized unit suffix, TopicId must be contiguous and mirror the SynapseMessage union, and payload struct sizes are computed and checked on every build.

Contents

  • fbs/types.fbs: shared math structs (synapse.types), core protocol enums, and topic identifiers.
  • fbs/sensors.fbs: GNSS, inertial, air data, and power telemetry (raw layer).
  • fbs/state.fbs: vehicle health, estimates, mission progress, and navigation status (estimate layer).
  • fbs/control.fbs: manual input, setpoints, commands, actuators, and loop metrics.
  • fbs/telemetry.fbs: compact ground-control status aggregate.
  • fbs/transport.fbs: optional multiplexed frame and message union.
  • fbs/transfer.fbs: mission and parameter queryable request/reply messages.
  • fbs/{mocap,optical_flow,sil}.fbs: focused support schemas.
  • fbs/all.fbs: aggregate include used by package generation.
  • topics.json / topic catalog helpers: topic IDs, canonical keys, payload sizes, scopes, encodings, and command metadata in release artifacts.
  • bfbs/*.bfbs: generated FlatBuffers reflection schemas included in C/C++ release archives and the npm package.
  • rust/, python/, js/, c/, cpp/: package skeletons for the published artifacts.
  • xtask/: reproducible local and CI build driver.
  • tools.lock: pinned package, generator, and runtime versions.

Generated Rust, Python, and JavaScript package trees are intentionally not committed. The xtask build stages package skeletons under target/xtask/packages/, renders .jinja templates, and generates bindings from fbs/all.fbs before building release packages.

Version Pins

Generation is version-locked from tools.lock. CI builds a vendored flatc from flatbuffers-build = "=0.2.4+flatc-25.12.19" and verifies that the compiler reports flatc version 25.12.19. The Rust package depends on flatbuffers = "=25.12.19" and the Python package depends on flatbuffers==25.12.19 so generated code and runtimes stay in lockstep. CI also builds pinned FlatCC, uses pinned mdbook for schema documentation, and publishes generated C and C++ archives for downstream CMake consumers. Release tags must match PACKAGE_VERSION in tools.lock; the build fails otherwise.

Rust

Add the published crate to Cargo.toml:

synapse_fbs = "0.2.0"

After a local xtask build, use the staged crate directly:

synapse_fbs = { path = "../synapse_fbs/target/xtask/packages/rust" }

Python

Install the published package:

pip install synapse-fbs

After a local xtask build, install the staged wheel:

pip install target/xtask/packages/python/dist/*.whl

JavaScript / TypeScript

Install the published npm package:

npm install @cognipilot/synapse-fbs

Unlike the Rust and Python packages, the npm package ships schema assets (fbs/*.fbs plus generated bfbs/*.bfbs reflection schemas) rather than generated bindings, and has no flatbuffers runtime dependency. The npm flatbuffers release cadence does not track the pinned flatc version, so JS consumers generate their own bindings from the shipped schemas or decode via the reflection schemas. After a local xtask build, the staged package lives under target/xtask/packages/js.

C and C++ Archives

Release CI publishes generated C and C++ archives for downstream CMake consumers. Firmware projects should fetch the release archive directly where they need it instead of vendoring generated files:

include(FetchContent)

FetchContent_Declare(
  synapse_fbs
  URL https://github.com/CogniPilot/synapse_fbs/releases/download/v0.2.0/synapse_fbs-c.tar.gz
  URL_HASH SHA256=<release sha256>
  DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(synapse_fbs)

target_link_libraries(app PRIVATE synapse_fbs::c)

The C archive also carries zephyr/module.yml, so west manifest projects can add it as a Zephyr module. Link synapse_fbs::flatcc_runtime only when using generated builders, verifiers, or JSON helpers — reader accessors are header-only. The C++ archive provides the analogous synapse_fbs::cpp interface target.

Local Build

Fast schema validation (parse, doc-comment enforcement, unit-suffix lint, TopicId/union consistency, payload sizes, catalog helper smoke tests):

cargo run --locked --manifest-path xtask/Cargo.toml -- check

Run the same full task that CI runs:

cargo run --locked --manifest-path xtask/Cargo.toml -- ci

The ci task builds pinned flatc and FlatCC, stages Rust/Python/JavaScript packages under target/xtask/packages/, creates the C/C++ tarballs under target/xtask/artifacts/, includes pinned bfbs/*.bfbs reflection schemas and bfbs.sha256 manifests in those archives, and smoke-tests the C archive through CMake FetchContent.

Generate the static schema documentation locally:

cargo install mdbook --version "$(awk -F= '/^MDBOOK_VERSION=/{print $2}' tools.lock)" --locked
cargo run --locked --manifest-path xtask/Cargo.toml -- docs --version 0.2.0 --out-dir target/xtask/docs

The docs are generated from fbs/*.fbs into an mdBook site with sidebar navigation, search, selectable themes, and version selection. The generated site copies the source schemas alongside the HTML and infers unit/scale notes from field suffixes such as _enu_, _flu_, _deg_e7, _mm, _cm_s, _da, _cv, _cdeg, _dpermille, and _milli.

Releases

CI generates bindings and builds all packages on pull requests and branch pushes.

Pushing a tag like v0.2.0 (which must match PACKAGE_VERSION in tools.lock) publishes:

  • staged target/xtask/packages/rust/ to crates.io using CARGO_REGISTRY_TOKEN
  • staged target/xtask/packages/python/dist/ to PyPI using trusted publishing (the synapse-fbs PyPI project must have this repository's release.yml workflow registered as a trusted publisher before tagging)
  • staged target/xtask/packages/js/ to npm using NPM_TOKEN with provenance
  • GitHub Release assets:
    • Python wheel and sdist
    • Rust .crate source package
    • C++ generated header tarball with matching FlatBuffers C++ runtime headers plus bfbs/*.bfbs reflection schemas
    • C generated header tarball with matching FlatCC headers, runtime sources, zephyr/module.yml, and bfbs/*.bfbs reflection schemas

The generated C archive is intentionally generic. Downstream firmware projects that need it should fetch the release tarball directly from their own CMake using a versioned URL and URL_HASH SHA256=....

Schema Docs

The docs workflow publishes versioned schema documentation to the gh-pages branch. Pushes to main update /main/; release tags like v0.2.0 update /0.2.0/. The root docs URL redirects to /main/, and the mdBook version selector links to published release docs: https://cognipilot.github.io/synapse_fbs/.

About

Synapse Flatbuffer Messages

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors