-
Notifications
You must be signed in to change notification settings - Fork 1
Usage Python
Python bindings are built with pyo3 and maturin. The module name is sedsnet.
Recommended in this repo:
./build.py pythonDirect maturin is also supported:
maturin developimport sedsnet as seds
def tx(bytes_buf):
pass
def on_packet(pkt):
print(pkt)
schema = b"""
{
"endpoints": [
{"rust": "SdCard", "name": "SD_CARD", "description": "Local storage endpoint"},
{"rust": "Radio", "name": "RADIO", "description": "External radio link"}
],
"types": [
{
"rust": "GpsData",
"name": "GPS_DATA",
"description": "Three f32 GPS values",
"priority": 80,
"class": "Data",
"element": {"kind": "Static", "data_type": "Float32", "count": 3},
"endpoints": ["Radio", "SdCard"]
}
]
}
"""
seds.register_schema_json_bytes(schema)
sd_card = seds.endpoint_info_by_name("SD_CARD")["id"]
gps_data = seds.data_type_info_by_name("GPS_DATA")["id"]
router = seds.Router(
handlers=[(sd_card, on_packet, None)],
)
router.add_side_packed("RADIO", tx, reliable_enabled=True)
router.log_f32(gps_data, [1.0, 2.0, 3.0])
router.process_all_queues()If you need a custom monotonic source for tests or simulation, pass now_ms=....
Prebuilt wheels are not locked to their packaged defaults. Configure the active node at runtime:
import sedsnet as seds
seds.set_runtime_device_identifier("GROUND_STATION")
seds.set_runtime_tuning_config(
payload_compress_threshold=24,
static_string_length=512,
static_hex_length=512,
string_precision=6,
max_handler_retries=4,
reliable_retransmit_ms=300,
reliable_max_retries=10,
reliable_max_pending=96,
reliable_max_return_routes=96,
reliable_max_end_to_end_pending=96,
reliable_max_end_to_end_ack_cache=256,
)
router = seds.Router(
hostname="FC26_MAIN",
address_mode=2, # 0=dynamic, 1=requested, 2=static
requested_address=0x10203040,
timesync_enabled=True,
timesync_role=2, # 0=consumer, 1=source, 2=auto
max_queue_budget=65536,
max_recent_rx_ids=256,
)
router.configure_timesync(enabled=True, role=1, priority=10)
router.configure_address(address_mode=1, requested_address=0x10203041)runtime_tuning_config() returns the active process-wide values. Router/relay memory limits are per
instance, while identity/address and time-sync settings are per router except for the process-wide
default device identifier used by packet helper constructors.
Python exposes the same runtime registry as Rust and C:
-
register_endpoint(...)andregister_data_type(...)add explicit entries -
register_schema_json_file(...)andregister_schema_json_bytes(...)seed entries from JSON -
endpoint_info_by_name(...)anddata_type_info_by_name(...)return IDs and metadata -
remove_endpoint_by_name(...)andremove_data_type_by_name(...)remove user entries
The DataType and DataEndpoint enums only contain built-in control IDs. Application schema IDs
should be looked up by string name after registration or JSON seeding.
Network variables cache the latest value packet for a data type. User code uses a setter and getter; the getter returns the cached value and internally requests a refresh when the value has never been seen or is stale. No separate endpoint registration is needed for the network-variable machinery. Caches are tiered: any router that has enabled or seen the variable can answer the refresh from its local cache, so reconnecting boards can resync from a nearby node instead of always reaching the original producer/master.
import sedsnet as seds
RADIO = 101
FLIGHT_STATE = 3100
seds.register_endpoint(RADIO, "RADIO")
seds.register_data_type(
FLIGHT_STATE,
"FLIGHT_STATE",
True,
1,
0, # UInt8
0, # Data
[RADIO],
priority=90,
e2e_encryption=2, # RequireOn
)
router = seds.Router(e2e_mode=1, e2e_key_id=7) # RequiredOnly
router.enable_network_variable(FLIGHT_STATE, True, True)
router.on_network_variable_update(FLIGHT_STATE, on_flight_state_update)
router.set_network_variable(packet)
cached = router.get_network_variable(FLIGHT_STATE, 1000)
router.process_all_queues()If the router lacks read or write permission, getters/setters raise the normal SEDSnet Python
exception for PermissionDenied. Peers answer denied refreshes with a telemetry error packet. The
update callback runs only for inbound updates and refresh replies that change the local cache; local
setters/seeds update the cache without firing that callback.
E2E policy values are 0=PreferOff, 1=PreferOn, and 2=RequireOn. Router E2E modes are
0=Disabled, 1=RequiredOnly, 2=Preferred, and 3=ForceAll. The constructor default
e2e_mode=255 means "build default": Preferred for normal Python builds because cryptography
is part of the python feature. Custom extensions built without cryptography default to Disabled
and reject RequireOn traffic instead of silently downgrading it.
Key exchange is board/application owned. Run your quantum-resistant asynchronous exchange when
discovery learns a peer, derive symmetric traffic keys, and have the cryptography provider select those keys by
e2e_key_id. For three boards advertising the same endpoint, use an endpoint/group traffic key so
all intended boards can open the same message; authenticated payloads reject header or ciphertext
changes before handlers see data.
There is no Python RouterMode anymore.
-
Routernow uses the same rule-driven forwarding model as the Rust API - routers and relays both default to a full forwarding mesh across eligible sides
- runtime route rules are how you restrict forwarding
Useful controls:
set_side_ingress_enabled(...)set_side_egress_enabled(...)set_route(...)clear_route(...)set_typed_route(...)clear_typed_route(...)set_source_route_mode(...)set_route_weight(...)set_route_priority(...)
Use None for src_side_id when controlling locally-originated traffic.
With discovery enabled:
- routers and relays learn endpoint reachability per side
- discovery also propagates a transitive router graph, not just flattened endpoint sets
- normal traffic defaults to adaptive discovered-path load balancing
- reliable traffic still fans out across all known discovered candidates
export_topology() is available on both Router and Relay.
- it returns a Python
dict - the top-level
routerskey lists each discovered router, the endpoint names/source IDs it owns, and its connected router sender IDs - the top-level
linkskey lists deduplicated board-to-board graph edges as{source, target} - graph-facing endpoint fields such as
reachable_endpointsandadvertised_endpointscontain schema-advertised names; companion fields such asreachable_endpoint_idsandadvertised_endpoint_idspreserve the numeric IDs - SEDSnet-owned control endpoints (
SEDSNET_TIME_SYNC,SEDSNET_DISCOVERY,SEDSNET_ERROR) are not included in user endpoint reachability fields - each route entry also includes
announcersso you can see which upstream router advertised each piece of topology
Router.client_stats("BOARD_ID") and Relay.client_stats("BOARD_ID") return a dictionary for one
discovered client or None if that sender is unknown. The dictionary includes connected state,
side IDs/names, last-seen/age timing, named reachable endpoints, reachable time-sync sources, and
packet/byte counters aggregated from the side(s) currently reaching that client.
Call announce_leave() before a planned shutdown or disconnect so peers receive
SEDSNET_DISCOVERY_LEAVE and remove that sender from topology immediately instead of waiting for
the discovery TTL.
Reliable delivery is enabled on a per-side basis with reliable_enabled=True for packed
sides. Packets already in flight also carry a compact internal wire contract so topology or
runtime-schema changes do not redirect them to the wrong holder or make them undecodable mid-flight.
Applications do not construct that contract directly; routers and relays manage it internally.
As of 3.11.0, reliable delivery is end-to-end verified:
- the source router tracks reliable packets it originated
- each discovered destination holder emits an end-to-end acknowledgement after local delivery
- routers and relays route that acknowledgement back only along the learned return path
- unrelated sides do not receive those end-to-end acknowledgements
- the source keeps retransmitting only toward holders that are still missing an acknowledgement
- if a discovered holder ages out of topology, the source removes it from the pending holder set
- newer reliable packets on the same side still do not block while those end-to-end ACKs are pending
For ordered reliable links, later packets that arrive after a missing sequence are buffered and partial-ACKed. Partial ACKs suppress timeout retransmit for packets already received, but explicit packet requests can still replay them. The buffered packets are dispatched as soon as the missing sequence arrives.
Useful maintenance calls:
process_rx_queue()process_tx_queue()process_all_queues()periodic(timeout_ms)-
periodic_no_timesync(timeout_ms)when time sync is enabled but should be skipped for one loop
Router and relay queue-backed state shares one dynamic budget. Prebuilt Python wheels use the packaged defaults unless you pass constructor overrides:
router = sedsnet.Router(max_queue_budget=262144, max_recent_rx_ids=512)
relay = sedsnet.Relay(max_queue_budget=131072, starting_queue_size=4096)RX work, TX work, recent packet IDs, reliable buffers/replay state, discovery topology, and runtime
schema registry memory all count against the active budget. Recent packet ID caches preallocate
their final storage and reserve that byte cost immediately. Discovery topology eviction emits a
warning in std builds.
Use export_memory_layout_json() on a router or relay to profile queue pressure. The JSON includes
shared allocated/used bytes plus per-area queue, reliable-buffer, schema, discovery, and
network-variable-cache breakdowns.
Use add_side_packed_profile(...) to select a compact side-wire profile from Python:
router.add_side_packed_profile(
"RADIO",
tx,
reliable_enabled=True,
profile="ipv4_like",
max_frame_bytes=0,
max_side_transport_templates=64,
)Profiles are canonical, template, ipv6_like, and ipv4_like. A
compact_header_target_bytes value of 0 uses the IPv6-like 40-byte or IPv4-like 20-byte default
target for the selected profile. The ipv4_like profile also omits unchanged compact timestamps.
The same method is available on Relay. Per-data-type timestamp omission policy is currently a
Rust-side option; Python callers use profile-wide timestamp omission through ipv4_like.
Routers expose discovery-backed service ports for byte protocols that should run over SEDSnet instead of IP:
router.bind_p2p_port(80, lambda meta, payload: handle_http(payload))
client.send_p2p_to_hostname("http-service", 80, 49152, b"GET / HTTP/1.1\r\n\r\n")
client.send_p2p_to_address(0x10203040, 80, 49152, b"GET / HTTP/1.1\r\n\r\n")router.current_address returns the current compact address, and
router.resolve_hostname("name") returns discovered address metadata when known.
For byte protocols that need a session lifecycle, bind a stream port and open a stream:
router.bind_p2p_stream_port(8080, lambda meta, payload: handle_stream(meta, payload))
stream = client.open_p2p_stream_to_hostname("http-service", 8080, 49152)
client.send_p2p_stream(stream, b"GET /stream HTTP/1.1\r\n\r\n")
client.close_p2p_stream(stream)The stream callback receives a metadata dictionary with kind, stream_id, peer_stream_id,
sequence, peer_hostname, peer_address, local_port, and peer_port.
When built with timesync, Router keeps an internal network clock and handles SEDSNET_TIME_SYNC
traffic internally.
Construct Router(..., timesync_enabled=False) if the extension was built with timesync but you
do not want time sync for a particular instance. SEDSNET_TIME_SYNC, SEDSNET_DISCOVERY, and
SEDSNET_ERROR remain reserved internal endpoints; do not register handlers for them or try to emit
those packets manually.
See Time-Sync for protocol details.