Skip to content

Events between nodes

Pods exchange messages across src/ipc/channel.cpp over ESP-NOW and UDP port 7400. Each frame uses the transport that discovered the peer. The simulator uses UDP on port 7400 plus the node ID.

Host API

Call Effect Requirement
k32_event_emit(topic, payload) Publishes to local subscribers and known peers hardware.kube32.io/ipc
kube32_channel_open(name, flags) Opens a receive queue for name hardware.kube32.io/ipc
kube32_channel_recv(handle, buf, cap, timeout_ms) Reads one frame from the queue Open handle
kube32_channel_send(handle, data, len) Delivers to the local queue only Open handle
k32_event(topic_ptr, topic_len, ptr, len) Receives topics listed in kube32.io/event-topics k32_event and k32_alloc exports

Set the pod annotation kube32.io/event-topics: a,b to deliver channel topics from any node to the pod's k32_event export. Messages queue up to 16 entries and run on the k32_events thread, which uses an 8 KiB PSRAM stack, or 4 KiB of internal RAM on boards without PSRAM. Its calls into pods run on the shared interpreter stack. In the tessera app, tessera-hud publishes keystrokes with k32_event_emit("tessera.input", ...) and reads tessera.events with kube32_channel_open and kube32_channel_recv. tessera-api subscribes to tessera.input via kube32.io/event-topics and publishes updates on tessera.events, which the Ingress streams as Server-Sent Events via kube32.io/event-stream.

Limits

Item Limit
k32_event_emit payload 200 bytes; topic 1 to 64 bytes
k32_events queue 16 messages; stack 8 KiB PSRAM or 4 KiB internal RAM, 256 KiB on the simulator
Published message 1024 bytes, split into 224-byte payload frames
Reassembly 32 fragments, 500 ms timeout, 8 messages in flight
Receive queue per channel 8 slots of 512 bytes on hardware; 16 slots of 1024 bytes on the simulator
Maximum frame size 250 bytes
Delivery Best effort, no acknowledgement or retransmission

Peer discovery

Nodes send a HELLO frame carrying the node ID and name on topic k32.hello every 3 s on hardware, or every 2 s on the simulator, to:

  • The ESP-NOW broadcast address on hardware.
  • Every known peer.
  • UDP seed addresses on port 7400.

Workers seed the control plane address at boot. Every 10 s, the control plane reads node objects and seeds the InternalIP of each registered node on UDP port 7400. UDP seeding routes traffic when boards sit on different access points or Wi-Fi channels and cannot receive ESP-NOW broadcast frames. On the simulator, pass --peer name:id:port to add static UDP peers. Peers never expire. ESP-NOW runs unencrypted at 24 Mbps.

Frame format

Defined as a packed little-endian struct in include/ipc/channel.hpp:

Offset Size Field
0 4 Magic K32C (0x4332334B as little-endian u32)
4 1 Version 1
5 1 Flags: 0x01 fragment, 0x02 first, 0x04 last, 0x08 ack unused, 0x10 HELLO
6 2 Sequence number
8 4 FNV-1a hash of channel or topic name
12 4 FNV-1a hash of sending pod
16 4 FNV-1a hash of target pod, ignored on receive
20 1 Fragment index
21 1 Fragment count
22 2 Payload length, 0 to 224
24 n Payload
24 + n 2 CRC-16/CCITT-FALSE, polynomial 0x1021, initial 0xFFFF, over header and payload

Frames with an invalid magic, version, length, or CRC are dropped.

Limitations

kube32_channel_send delivers only to the local node; use k32_event_emit to reach other boards.