-
Notifications
You must be signed in to change notification settings - Fork 1
Usage C Cpp
The C API is exposed via C-Headers/sedsprintf.h (source) and a static library built by Cargo.
# Example: building for an embedded target
set(SEDSPRINTF_RS_TARGET "thumbv7em-none-eabihf" CACHE STRING "" FORCE)
set(SEDSPRINTF_EMBEDDED_BUILD ON CACHE BOOL "" FORCE)
# Optional: force the Rust crate into the release profile even when the parent
# CMake build is Debug.
# set(SEDSPRINTF_RS_FORCE_RELEASE ON CACHE BOOL "" FORCE)
# set the sender name
set(SEDSPRINTF_RS_DEVICE_IDENTIFIER "FC26_MAIN" CACHE STRING "" FORCE)
# optional compile-time env overrides
set(SEDSPRINTF_RS_MAX_STACK_PAYLOAD "256" CACHE STRING "" FORCE)
set(SEDSPRINTF_RS_MAX_QUEUE_BUDGET "65536" CACHE STRING "" FORCE)
set(SEDSPRINTF_RS_MAX_RECENT_RX_IDS "256" CACHE STRING "" FORCE)
add_subdirectory(${CMAKE_SOURCE_DIR}/sedsprintf_rs sedsprintf_rs_build)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE sedsprintf_rs::sedsprintf_rs)Important CMake variables:
-
SEDSPRINTF_EMBEDDED_BUILD(ON/OFF) -
SEDSPRINTF_RS_FORCE_RELEASE(ON/OFF) -
SEDSPRINTF_RS_TARGET(Rust target triple) SEDSPRINTF_RS_DEVICE_IDENTIFIERSEDSPRINTF_RS_MAX_STACK_PAYLOADSEDSPRINTF_RS_MAX_QUEUE_BUDGETSEDSPRINTF_RS_MAX_RECENT_RX_IDS-
SEDSPRINTF_RS_ENV_<KEY>for any config env var
SEDSPRINTF_RS_FORCE_RELEASE is useful when your top-level CMake build remains Debug but you
still want sedsprintf_rs built with Cargo's release profile. If it is left OFF, the wrapper
follows the parent CMake configuration for single-config generators.
If you want to call Cargo directly:
DEVICE_IDENTIFIER=FC26_MAIN cargo build --release
The static library will be under target/release/ (or under target/<triple>/release for embedded targets).
#include "sedsprintf.h"
static uint64_t now_ms(void *user) { (void)user; return 0; }
static SedsResult tx_send(const uint8_t *bytes, size_t len, void *user)
{
(void)bytes; (void)len; (void)user;
return SEDS_OK;
}
static SedsResult on_packet(const SedsPacketView *pkt, void *user)
{
(void)user;
char buf[seds_pkt_to_string_len(pkt)];
seds_pkt_to_string(pkt, buf, sizeof(buf));
return SEDS_OK;
}
int main(void)
{
const SedsLocalEndpointDesc locals[] = {
{ .endpoint = SEDS_EP_SD_CARD, .packet_handler = on_packet, .user = NULL },
};
SedsRouter *r = seds_router_new(
Seds_RM_Relay,
NULL,
NULL,
locals,
sizeof(locals) / sizeof(locals[0])
);
seds_router_add_side_serialized(r, "TX", 2, tx_send, NULL, true);
float data[3] = {1.0f, 2.0f, 3.0f};
seds_router_log(r, SEDS_DT_GPS_DATA, data, sizeof(data));
seds_router_process_all_queues(r);
seds_router_free(r);
return 0;
}On std builds, passing NULL for now_ms_cb makes the router use its own internal monotonic
clock. On no_std builds, provide a monotonic clock callback.
The first seds_router_new(...) mode argument is retained for ABI compatibility with older
headers. Current routers use runtime side route controls instead of sink/relay construction modes,
so local-only behavior is achieved by creating no sides or disabling the relevant routes.
Reserved internal endpoints:
- Do not register
SEDS_EP_DISCOVERYinSedsLocalEndpointDesc. - Do not register
SEDS_EP_TIME_SYNCinSedsLocalEndpointDescwhentimesyncis enabled. - Those endpoints are reserved for the router's built-in discovery and time-sync control traffic,
and
seds_router_new(...)rejects them.
See c-example-code/ (source) for a more complete example. Time sync is demonstrated in c-example-code/src/timesync_example.c (source). See Time-Sync for the time sync packet flow and roles.
Side-level reliability in the C API is controlled by the reliable_enabled argument passed to:
seds_router_add_side_serializedseds_router_add_side_packetseds_relay_add_side_serializedseds_relay_add_side_packet
That flag is per hop, not global. It controls what the router/relay does on the connection between itself and that specific side callback.
What it means in practice:
- reliable schema types only use the router/relay's hop-level reliable layer on sides where
reliable_enabled == true - on serialized sides, that hop-level layer adds sequence numbers, ACKs, packet requests, and retransmits
- on sides where
reliable_enabled == false, the router/relay sends the application packet once without that hop-level reliable wrapper - packet-view side callbacks do not preserve the serialized hop-level wrapper, so the most complete router/relay-managed reliable behavior is on serialized sides
For routers, this side setting is separate from router-wide and end-to-end reliability:
-
RouterConfig::with_reliable_enabled(false)on the Rust side disables the router-managed hop-level reliable layer entirely - otherwise, the source router can still track reliable packets end-to-end across the network even if one particular egress side does not use hop-level reliable framing
For ordered reliable links, packets that arrive after a missing sequence are buffered and partial-ACKed. Partial ACKs suppress timeout retransmit for packets already received, while explicit packet requests can still replay them. Once the missing sequence arrives, the buffered packets are dispatched immediately in order.
Router and relay queue-backed state shares the compile-time MAX_QUEUE_BUDGET dynamically:
RX work, TX work, recent packet IDs, reliable buffers/replay state, and discovery topology all draw
from it. Recent packet ID caches preallocate their final storage and reserve that byte cost
immediately. Discovery topology eviction emits a warning in std builds.
With timesync enabled, the router owns an internal network clock and handles TIME_SYNC
packets internally. Use seds_router_get_network_time_ms / seds_router_get_network_time to
read the current synthesized network time. Source/master nodes can seed that clock directly with
the seds_router_set_local_network_* functions for date-only, time-only, millisecond, or
nanosecond precision inputs.
SEDS_EP_TIME_SYNC remains reserved for that internal machinery and must not be registered as a
local endpoint handler.
For normal application loops, call seds_router_periodic(...) to run time sync, discovery, and
queue draining together. If you need to skip time sync for a cycle while keeping the feature
enabled, call seds_router_periodic_no_timesync(...) instead.
seds_router_poll_timesync(...) remains available as a lower-level non-blocking hook when you
want to manage maintenance phases manually.
With discovery enabled, seds_router_poll_discovery(...) remains available as a lower-level
hook to queue due discovery advertisements, and seds_router_announce_discovery(...) still forces
an immediate announce. Relays now expose seds_relay_periodic(...) for the normal main-loop path,
alongside the lower-level seds_relay_poll_discovery(...) and seds_relay_announce_discovery(...)
functions.
Topology export is also available in the C ABI:
-
seds_router_export_topology_len(...)/seds_router_export_topology(...) -
seds_relay_export_topology_len(...)/seds_relay_export_topology(...)
These return a JSON snapshot. The top-level routers array contains each discovered router, the
endpoints/time-sync source IDs it owns, and its connections. Per-side route entries also include
their upstream announcer detail.
Common calls:
-
seds_router_log/seds_router_log_ts: log typed payloads. -
seds_router_transmit_serialized_message: send raw bytes. -
seds_router_receive_serialized: receive bytes immediately. -
seds_router_rx_serialized_packet_to_queue: enqueue for later processing. -
seds_router_process_all_queues: process queued RX/TX.
Immediate vs queued variants:
-
receive*/transmit*act immediately in the current call -
*_to_queue*only enqueue work for a later queue drain -
*_from_side*variants tag the traffic with an explicit ingress side id - non-
from_sidevariants treat the traffic as locally-originated
Main-loop guidance:
-
seds_router_periodic(...)is the normal router loop entry point because it polls time sync, polls discovery, and drains queues -
seds_router_periodic_no_timesync(...)does the same but skips time sync for that iteration -
seds_relay_periodic(...)is the normal relay loop entry point -
seds_router_process_*andseds_relay_process_*are lower-level phase helpers when you need manual control
As of v3.0.0, most applications should call the plain receive APIs above. Side IDs are tracked internally by the router. If you need to explicitly override ingress (custom relay or bridge), use the side-aware variants:
seds_router_receive_serialized_from_sideseds_router_receive_from_sideseds_router_rx_serialized_packet_to_queue_from_sideseds_router_rx_packet_to_queue_from_side
Runtime side policy and routing controls are also available:
seds_router_remove_sideseds_router_set_side_ingress_enabledseds_router_set_side_egress_enabledseds_router_set_routeseds_router_clear_routeseds_router_set_typed_routeseds_router_clear_typed_routeseds_router_set_source_route_modeseds_router_set_route_weightseds_router_set_route_priorityseds_relay_remove_sideseds_relay_set_side_ingress_enabledseds_relay_set_side_egress_enabledseds_relay_set_routeseds_relay_clear_routeseds_relay_set_typed_routeseds_relay_clear_typed_routeseds_relay_set_source_route_modeseds_relay_set_route_weightseds_relay_set_route_priority
Pass -1 as the source side to seds_router_set_route / seds_router_clear_route when you want
to control locally-originated router TX rather than traffic received from a specific side. The
same -1 convention also applies to seds_router_set_typed_route /
seds_router_clear_typed_route. The relay route APIs use the same -1 convention for
locally-originated discovery TX.
Typed route rules act as per-DataType allowlists for a given source side. If any typed rules
exist for (src_side, ty), only the enabled destination sides for that type remain eligible.
That allows dedicated command, abort, or other special-purpose links while keeping ordinary
traffic on the default routing policy.
SedsRouteSelectionMode controls multi-path behavior:
-
Seds_RSM_Fanout: send to all eligible paths. -
Seds_RSM_Weighted: send one packet on one eligible path using weighted round-robin. -
Seds_RSM_Failover: send only on the lowest-priority eligible path.
The routing parameters mean:
-
src_side_id: the ingress side that traffic arrived from; pass-1for locally-originated router/relay traffic -
dst_side_id: the candidate egress side being allowed, blocked, weighted, or prioritized -
ty: theDataTypeaffected by a typed-route override -
enabled: whether that route is allowed -
weight: relative share used bySeds_RSM_Weighted -
priority: lower values win inSeds_RSM_Failover
Payloads are little-endian. The schema defines element type and count. For dynamic payloads, sizes must be a multiple of element width.
Strings must be valid UTF-8. For static strings, the payload is padded or truncated to STATIC_STRING_LENGTH.
Bare-metal builds must provide:
void *telemetryMalloc(size_t)void telemetryFree(void *)void telemetry_lock(void)void telemetry_unlock(void)void seds_error_msg(const char *, size_t)void telemetry_panic_hook(const char *, size_t)
A simple stub is shown in README.md and can be adapted for your platform.
The router uses internal locking, so the C API is safe to call from multiple threads if your platform supports it. In bare-metal contexts, you may still want to serialize access around interrupts.
Do not call router/logging APIs from ISR context on RTOS targets, because platform lock hooks may block.