Fejemis Bridge ROS2: Difference between revisions

From Rsewiki
(Created page with "Back to Fejemis ROS2 Software == Fejemis Bridge — Design, flow and file map == === Purpose === `fejemis_bridge` translates the Teensy serial protocol into typed ROS2 messages and back. This page explains the runtime flow, the core files to edit, and the small number of extension points developers need. === Overview === The bridge runs as a ROS2 node that creates two serial sources (`front` and `drive`). Each source registers lightweight "proxies" that convert be...")
 
No edit summary
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
Back to [[Fejemis ROS2 Software]]
Back to [[Fejemis ROS2 Software]]


== Fejemis Bridge — Design, flow and file map ==
== Fejemis Bridge ==


=== Purpose ===
=== Overview ===
`fejemis_bridge` translates the Teensy serial protocol into typed ROS2 messages and back. This page explains the runtime flow, the core files to edit, and the small number of extension points developers need.
This document explains how the <code>fejemis_bridge</code> package is organized and how its pieces interact. It focuses on the runtime flow, the core files you need to know, and the responsibilities of each component.
 
=== Core idea ===
The bridge translates a text-based serial protocol used by the Teensy controllers into typed ROS2 messages and back. It is implemented as a ROS2 node that instantiates two serial "sources" (front and drive). Each source registers a set of "proxies" that convert between device text prefixes and ROS message types. The low-level serial framing, CRC, confirmation and retry logic are provided by the <code>raubase</code> serial framework.
 
=== Primary components and responsibilities ===
* <code>bridge</code> node (wrapper): coordinates sources and the ROS2 side. The node registers sources, runs the executor and exposes high-level services. See <code>src/bridge/main.cpp</code> and <code>include/fejemis_bridge/bridge.hpp</code>.
* <code>SerialSource</code> implementations: the device-specific classes that open the serial port, run the read/write loop, queue outbound messages and dispatch inbound lines to proxies. In this package: <code>FrontTeensy</code> and <code>DriveTeensy</code> (see <code>include/fejemis_bridge/sources/*.hpp</code> and <code>src/bridge/sources/*.cpp</code>).
* <code>Proxies</code>: conversion modules that map device text prefixes to ROS message types and back. Each proxy knows a prefix (e.g. <code>hbt</code>, <code>fwl</code>, <code>manage</code>) and implements <code>from_device(...)</code> and <code>to_device(...)</code> conversions. Proxies are declared in <code>include/fejemis_bridge/proxies.hpp</code> and implemented under <code>src/bridge/proxies/</code>.
* <code>raubase</code> serial layer: provides <code>Message</code> objects, CRC, confirmation handling, thread-safe queues, and the TX/RX loop behavior. Relevant files live in the <code>deps/raubase_core</code> tree (e.g. <code>message.hpp</code>, <code>source_txrx.cpp</code>).


=== Overview ===
=== Key files and what they do ===
The bridge runs as a ROS2 node that creates two serial sources (`front` and `drive`). Each source registers lightweight "proxies" that convert between device text prefixes and ROS messages. Low-level framing, CRC, confirmation and retry are handled by the shared `raubase` serial layer.
* <code>src/bridge/main.cpp</code> — process entry point: constructs the <code>Bridge</code> instance and runs it.
* <code>include/fejemis_bridge/bridge.hpp</code> — wrapper around <code>raubase::SerialBridge</code>, declares parameters and registers the <code>FrontTeensy</code> and <code>DriveTeensy</code> sources.
* <code>include/fejemis_bridge/sources/*.hpp</code> and <code>src/bridge/sources/*.cpp</code> — <code>FrontTeensy</code> and <code>DriveTeensy</code> classes. They register the proxies relevant to each Teensy.
* <code>include/fejemis_bridge/proxies.hpp</code> — proxy declarations and proxy-to-prefix mappings.
* <code>src/bridge/proxies/*.cpp</code> — proxy implementation: parse device strings and publish ROS messages; or receive ROS messages and generate device strings.
* <code>config/bridge.yaml</code> — runtime parameters used by the node (message rates, enabling specific proxies, device defaults).
* <code>launch/bridge.launch.py</code> — launch-time wiring and remappings (used by operators, not needed for design understanding).
* <code>deps/raubase_core/.../message.hpp</code> and <code>.../source_txrx.cpp</code> — serial framing, CRC, confirm/resend and the TX/RX loop. Read these to understand byte-level behavior.


=== Primary components ===
=== Runtime flow (concise) ===
* `bridge` node — wrapper around `raubase::SerialBridge` that registers sources and parameters. (`src/bridge/main.cpp`, `include/fejemis_bridge/bridge.hpp`)
1. Initialization: the <code>Bridge</code> (wrapper) declares parameters and registers both sources. Each source registers its proxies.
* `SerialSource` subclasses — device-specific handlers that run the TRX loop and own proxies (`include/fejemis_bridge/sources/*`, `src/bridge/sources/*`).
2. Each <code>SerialSource</code> opens its serial port and enters a TRX loop that alternates reading bytes and sending queued messages.
* Proxies — single-purpose adapters mapping device prefixes ↔ ROS messages (`include/fejemis_bridge/proxies.hpp`, `src/bridge/proxies/*`).
3. Inbound line handling:
* `raubase` serial core — framing, CRC, confirm/resend, queues (`deps/raubase_core/.../message.hpp`, `.../source_txrx.cpp`).
  * The TRX loop assembles bytes into a line starting with <code>;</code> and ending with <code>\n</code>.
  * CRC is validated (3-char <code>;NN</code> prefix). Confirmation messages are detected separately and handled by queue logic.
  * Valid data lines are stripped of CRC and passed to proxy candidates; the first proxy whose prefix matches the line handles it and typically publishes a ROS message.
4. Outbound handling:
  * A ROS message (or internal code) is converted to device text by a proxy, wrapped in a <code>Message</code> object (CRC added, optional <code>!</code> requesting confirmation), and pushed to a thread-safe TX queue.
  * The TRX loop sends the first message in the queue. If the message requested confirmation, the source waits until a matching confirmation arrives; if none arrives within the timeout, the message is retried (and eventually dropped after a max resend count).


=== Key files (quick map) ===
=== Proxies and topics (how they interact) ===
* `include/fejemis_bridge/bridge.hpp` — registers `FrontTeensy` and `DriveTeensy`.
Proxies are the translators between raw device lines and typed ROS messages. Think of each proxy as a small adapter that knows:
* `src/bridge/sources/*.cpp` — source constructors (where proxies are registered).
* `include/fejemis_bridge/proxies.hpp` and `src/bridge/proxies/*.cpp` — proxy declarations and conversions.
* `config/bridge.yaml` — runtime parameters (proxies, rates, device defaults).


=== Runtime flow (short) ===
* the device text prefix it handles (for example <code>hbt</code>, <code>fwl</code>, <code>manage</code>),
1. Bridge constructs sources and registers proxies.
* how to parse the device payload into a ROS message, and
2. Each source runs a TRX loop: read bytes → assemble framed line → validate CRC → dispatch to proxy; and handle TX queue → send messages → wait for confirm if requested.
* how to format a ROS message back into a device string when sending to the Teensy.
3. Inbound: device line → proxy `from_device()` → publish ROS message.
4. Outbound: ROS message → proxy `to_device()` → enqueue `Message` → TRX loop sends (CRC added).


=== Message framing (exact) ===
How inbound messages are handled (device → ROS):
* Frame start: `;` (CRC prefix starts immediately after).
1. The <code>SerialSource</code> receives a framed line and strips the CRC prefix.
* CRC: 3-char prefix `;NN` (computed by `Message::generateCRC`).
2. It looks up the first proxy whose prefix matches the start of the line.
* Terminator: `\n` (newline).
3. The proxy's <code>from_device(...)</code> parses the remaining payload and publishes a typed ROS message (typically under the <code>fejemis</code> namespace).
* Confirmation: messages may request confirmation (`!` flag); confirmations are matched to queue head and cause the entry to be popped.


=== Proxies and topics (simple) ===
Example: device sends <code>;NNhbt 123 1\n</code> → <code>SerialSource</code> finds the <code>hbt</code> proxy → <code>hbt</code> proxy parses the numbers and publishes a <code>Heartbeat</code> message.
Proxies are tiny adapters. For each proxy you should know three things:
1. Device prefix it handles (e.g. `hbt`, `fwl`, `manage`).
2. Which ROS message type it publishes/subscribes.
3. Which source (front/drive) it is registered with.


Inbound example:
How outbound messages are handled (ROS → device):
* `;NNhbt 123 1\n` → `SerialSource` strips CRC → finds `hbt` proxy → `hbt` proxy parses payload → publishes `Heartbeat` message.
1. A ROS node or bridge logic creates a typed message (or calls a helper API).
2. The relevant proxy's <code>to_device(...)</code> (or equivalent helper) formats the message as a device string.
3. The <code>SerialSource::send()</code> method enqueues that string as a <code>Message</code> (CRC will be added) and the TRX loop transmits it.


Outbound example:
Example: a controller publishes <code>DriveManage</code> with <code>cmd_type = START_MOTORS</code> → Drive proxy converts it to <code>manage start\n</code> → bridge sends it to the drive Teensy.
* Publish `DriveManage` with `cmd_type=START_MOTORS` → Drive proxy formats `manage start` → bridge sends `;NNmanage start\n` to Teensy.


Where to find exact topic names: open the proxy implementation in `src/bridge/proxies/*.cpp` it shows the publisher/subscriber topic strings and message types used.
=== Practical notes ===
* Proxies are registered per source (see <code>FrontTeensy</code> and <code>DriveTeensy</code>), so a prefix is handled only by proxies attached to that source.
* Only the first matching proxy handles an inbound line; ensure prefixes are unique or ordered appropriately.
* Topic names and remapping are controlled by the node's namespace and launch remaps check the proxy implementation to see the exact topic names.


=== Extension (what to edit) ===
=== Where to look / extend ===
To add a new device message:
* Declarations: <code>include/fejemis_bridge/proxies.hpp</code> (which prefix maps to which proxy).
* Add or reuse a ROS `msg` (if needed).
* Implementations: <code>src/bridge/proxies/*.cpp</code> (parsing and publish/format logic).
* Implement conversions in a new proxy (`src/bridge/proxies/YourProxy.cpp`).
* To add a new mapping: create or reuse a <code>msg</code>, implement conversions in a new proxy, and register it in the appropriate source constructor.
* Declare the proxy in `include/fejemis_bridge/proxies.hpp` and register it in the appropriate source constructor (`src/bridge/sources/*`).
* Optionally add parameters to `config/bridge.yaml`.


=== Troubleshooting (short) ===
If you're unsure what topic a proxy publishes, open its <code>.cpp</code> implementation — it shows the exact ROS topic and message type used.
* CRC failures: check RX logs (CRC check messages) — invalid CRC means device or framing differs.
* Missing confirmations: inspect `source_txrx.cpp` logs for timeouts and resend messages; check `timeout_confirm` / `max_resend` in `SourceConfig`.
* Swapped serial ports: verify `front_device` and `drive_device` parameters and use `/dev/serial/by-id/*` symlinks if needed.


=== Quick checklist to include in the wiki ===
=== Extension points (practical recipe) ===
* Parameters & defaults: `front_device`, `drive_device`, `timeout_confirm`, `max_resend` (see `bridge.hpp` and `source` config).
If you need to add support for a new device message or new telemetry:
* Proxy → prefix → topic → msg table (optional, can be generated from `src/bridge/proxies`).
1. Add a ROS <code>msg</code> if a new typed payload is required.
* One inbound and one outbound framed example (in Message framing section).
2. Implement the conversion functions for the proxy (both directions if needed).
3. Declare the new proxy in <code>include/fejemis_bridge/proxies.hpp</code>.
4. Add the proxy to the appropriate source in the source constructor (<code>FrontTeensy</code> or <code>DriveTeensy</code>).
5. Add any runtime parameters to <code>config/bridge.yaml</code> and, if helpful, a launch remap.

Latest revision as of 11:03, 28 May 2026

Back to Fejemis ROS2 Software

Fejemis Bridge

Overview

This document explains how the fejemis_bridge package is organized and how its pieces interact. It focuses on the runtime flow, the core files you need to know, and the responsibilities of each component.

Core idea

The bridge translates a text-based serial protocol used by the Teensy controllers into typed ROS2 messages and back. It is implemented as a ROS2 node that instantiates two serial "sources" (front and drive). Each source registers a set of "proxies" that convert between device text prefixes and ROS message types. The low-level serial framing, CRC, confirmation and retry logic are provided by the raubase serial framework.

Primary components and responsibilities

  • bridge node (wrapper): coordinates sources and the ROS2 side. The node registers sources, runs the executor and exposes high-level services. See src/bridge/main.cpp and include/fejemis_bridge/bridge.hpp.
  • SerialSource implementations: the device-specific classes that open the serial port, run the read/write loop, queue outbound messages and dispatch inbound lines to proxies. In this package: FrontTeensy and DriveTeensy (see include/fejemis_bridge/sources/*.hpp and src/bridge/sources/*.cpp).
  • Proxies: conversion modules that map device text prefixes to ROS message types and back. Each proxy knows a prefix (e.g. hbt, fwl, manage) and implements from_device(...) and to_device(...) conversions. Proxies are declared in include/fejemis_bridge/proxies.hpp and implemented under src/bridge/proxies/.
  • raubase serial layer: provides Message objects, CRC, confirmation handling, thread-safe queues, and the TX/RX loop behavior. Relevant files live in the deps/raubase_core tree (e.g. message.hpp, source_txrx.cpp).

Key files and what they do

  • src/bridge/main.cpp — process entry point: constructs the Bridge instance and runs it.
  • include/fejemis_bridge/bridge.hpp — wrapper around raubase::SerialBridge, declares parameters and registers the FrontTeensy and DriveTeensy sources.
  • include/fejemis_bridge/sources/*.hpp and src/bridge/sources/*.cppFrontTeensy and DriveTeensy classes. They register the proxies relevant to each Teensy.
  • include/fejemis_bridge/proxies.hpp — proxy declarations and proxy-to-prefix mappings.
  • src/bridge/proxies/*.cpp — proxy implementation: parse device strings and publish ROS messages; or receive ROS messages and generate device strings.
  • config/bridge.yaml — runtime parameters used by the node (message rates, enabling specific proxies, device defaults).
  • launch/bridge.launch.py — launch-time wiring and remappings (used by operators, not needed for design understanding).
  • deps/raubase_core/.../message.hpp and .../source_txrx.cpp — serial framing, CRC, confirm/resend and the TX/RX loop. Read these to understand byte-level behavior.

Runtime flow (concise)

1. Initialization: the Bridge (wrapper) declares parameters and registers both sources. Each source registers its proxies. 2. Each SerialSource opens its serial port and enters a TRX loop that alternates reading bytes and sending queued messages. 3. Inbound line handling:

  * The TRX loop assembles bytes into a line starting with ; and ending with \n.
  * CRC is validated (3-char ;NN prefix). Confirmation messages are detected separately and handled by queue logic.
  * Valid data lines are stripped of CRC and passed to proxy candidates; the first proxy whose prefix matches the line handles it and typically publishes a ROS message.

4. Outbound handling:

  * A ROS message (or internal code) is converted to device text by a proxy, wrapped in a Message object (CRC added, optional ! requesting confirmation), and pushed to a thread-safe TX queue.
  * The TRX loop sends the first message in the queue. If the message requested confirmation, the source waits until a matching confirmation arrives; if none arrives within the timeout, the message is retried (and eventually dropped after a max resend count).

Proxies and topics (how they interact)

Proxies are the translators between raw device lines and typed ROS messages. Think of each proxy as a small adapter that knows:

  • the device text prefix it handles (for example hbt, fwl, manage),
  • how to parse the device payload into a ROS message, and
  • how to format a ROS message back into a device string when sending to the Teensy.

How inbound messages are handled (device → ROS): 1. The SerialSource receives a framed line and strips the CRC prefix. 2. It looks up the first proxy whose prefix matches the start of the line. 3. The proxy's from_device(...) parses the remaining payload and publishes a typed ROS message (typically under the fejemis namespace).

Example: device sends ;NNhbt 123 1\nSerialSource finds the hbt proxy → hbt proxy parses the numbers and publishes a Heartbeat message.

How outbound messages are handled (ROS → device): 1. A ROS node or bridge logic creates a typed message (or calls a helper API). 2. The relevant proxy's to_device(...) (or equivalent helper) formats the message as a device string. 3. The SerialSource::send() method enqueues that string as a Message (CRC will be added) and the TRX loop transmits it.

Example: a controller publishes DriveManage with cmd_type = START_MOTORS → Drive proxy converts it to manage start\n → bridge sends it to the drive Teensy.

Practical notes

  • Proxies are registered per source (see FrontTeensy and DriveTeensy), so a prefix is handled only by proxies attached to that source.
  • Only the first matching proxy handles an inbound line; ensure prefixes are unique or ordered appropriately.
  • Topic names and remapping are controlled by the node's namespace and launch remaps — check the proxy implementation to see the exact topic names.

Where to look / extend

  • Declarations: include/fejemis_bridge/proxies.hpp (which prefix maps to which proxy).
  • Implementations: src/bridge/proxies/*.cpp (parsing and publish/format logic).
  • To add a new mapping: create or reuse a msg, implement conversions in a new proxy, and register it in the appropriate source constructor.

If you're unsure what topic a proxy publishes, open its .cpp implementation — it shows the exact ROS topic and message type used.

Extension points (practical recipe)

If you need to add support for a new device message or new telemetry: 1. Add a ROS msg if a new typed payload is required. 2. Implement the conversion functions for the proxy (both directions if needed). 3. Declare the new proxy in include/fejemis_bridge/proxies.hpp. 4. Add the proxy to the appropriate source in the source constructor (FrontTeensy or DriveTeensy). 5. Add any runtime parameters to config/bridge.yaml and, if helpful, a launch remap.