|
DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
|
Forward-looking feature ideas for DeterministicESPAsyncWebServer, organized by theme and sized S / M / L. This is a backlog of possibilities, not a commitment or a schedule.
(shipped) - already exists in the library today.* - explicitly requested but not yet built.Bugfixes, maintenance, known limitations, and the record of shipped work live in TODO.md; released changes are in CHANGELOG.md. Every item here must keep the library's core guarantees: no heap after begin(), fixed-size buffers, a host-testable core where possible, and a DETWS_ENABLE_* flag (default off) so it costs nothing when unused.
server.diag()), MAC-derived UUID, raw-UDP telemetry cast.DetAtomic (acquire/release), so the single-producer/single-consumer ring is race-free by construction; proven under ThreadSanitizer in CI (env native_tsan).begin() runs the pipeline in its own pinned FreeRTOS task (Core 1 by default) instead of the user's loop(), which is freed; this also removes the first-connection-after-idle stall that came from loop()-cadence coupling.DETWS_WORKER_COUNT (default 1) workers each own a disjoint set of connection slots (round-robin at accept) with their own event queue + scratch arena: shared-nothing, no hot-path locks, so both cores process disjoint connections in parallel with bounded latency (determinism preserved). N=1 is byte-for-byte the original single pipeline.DetWebServer::defer(slot, fn, arg) runs a callback in the slot's owning worker, so application code on loop() or another task can push (SSE/WS sends) without racing the worker.services/clock.h: all library timing flows through detws_millis() (a single 1000 Hz source). Feed your own clock with detws_set_clock(fn, ticks_per_second) and it is divided down to the internal 1000 Hz, so timeouts/polling keep the tested 1 ms granularity whatever your clock's rate; default is the platform millis() (host-tested).vTaskDelay poll; producers (listener_enqueue, detws_defer) nudge it the moment work is queued, so events are serviced immediately. This decouples event latency from the idle-sweep cadence: DETWS_WORKER_POLL_TICKS is now purely the idle interval (default 1, unchanged), so raising it cuts idle wakeups (CPU/power) at no latency cost (HW: GET latency identical at POLL_TICKS 1 vs 100). Zero heap (notifications, not a QueueSet).The next big concurrency step (the v5 milestone): turn the worker model into a real-time, hardware-ingest pipeline where data lands in a queue and the scheduler preempts immediately to process it, with the priorities exposed to the user.
DETWS_ENABLE_PREEMPT_QUEUE: services/preempt_queue, static (zero-heap) queues feeding dedicated core-pinned tasks. From a task, detws_pq_post() / detws_pq_post_urgent() (xQueueSendToBack / xQueueSendToFront) with a wait timeout; the scheduler preempts the lower-priority producer the instant the item is queued. From an ISR, detws_pq_post_from_isr() (xQueueSendFromISR + portYIELD_FROM_ISR) switches right after posting, not on the next tick. Fail-closed on a full queue. Named lanes: one USER lane exposed to the app plus internal DMA / FORWARD / DEVICE lanes that run above it (DMA highest, below tcpip / WiFi), so internal ingest preempts user work without starving networking. HW-verified (~12 us ISR-to-handler latency; DMA + USER lanes ran continuously with zero errors under an HTTP flood); host-tested via the per-lane ring core (examples Foundation/06.PreemptQueue + 08.PreemptLanes).detws_pq_start[_lane]() and the queue depth is compile-time (DETWS_PQ_DEPTH); the no-arg API drives the USER lane so users own their task priorities, while internal lanes default above the user lane.DETWS_ENABLE_DMA: services/dma, channels moving peripheral bytes to a static ping-pong (RX) / staging (TX) buffer while the CPU is free; a DMA-complete event carries the bytes to a callback that posts them into the preempting queue. The ingest path for the cheap-breakout field-bus codecs (CAN over SPI, RS-485 UART, IO-Link). Zero-heap, fail-closed. The ingress/egress simulator (DETWS_DMA_SIMULATE, default on) runs the whole pipeline with no physical loopback - host bench and on-device; a real silicon driver plugs into det_dma_hw_*. Host-tested (native_dma) + HW-verified (2.2M+ frames, zero integrity errors, under HTTP stress). Remaining: the UHCI-UART / spi_master-DMA silicon backend.services/det_clock gains a microsecond time base beside the 1000 Hz detws_millis(): detws_micros() (pluggable like the ms clock, ISR-safe, for hardware-event timestamps) plus a DetwsLatencyStat budget helper (detws_lat_begin / _end / _avg_us: count, min/max/mean, over-budget count). Header-only, zero heap. HW-verified measuring the preempting queue's ISR-to-handler latency against a budget (avg 12 us, 0 over a 50 us budget). Host-tested in native_clock.DETWS_ENABLE_FORWARD: services/forward, a forwarding plane over the ingest pipeline. Register interfaces (Wi-Fi STA / AP, Ethernet, a peripheral bus / radio), each with an egress send callback, then add per-pair rules (src -> dst, allow / deny, rate cap). A frame arriving on one interface (det_forward_ingress(), wired from a DMA-complete event on the FORWARD lane) is forwarded to every allowed destination, so the device bridges / routes instead of only terminating traffic. Default-deny, never reflects to the source, fail-closed (exceeded cap / refused send drops and is counted, never blocks); multi-destination fan-out for hub behavior. Zero-heap static tables. Host-tested (native_forward) + HW-verified (600k+ frames ingested over DMA and forwarded with zero loss / zero integrity errors under an HTTP flood; example Foundation/09.InterfaceForward). This is the generic data path the wireless gateway bridges below sit on top of.Once the hardware-ingest pipeline lands (the preempting queue + DMA peripheral transfers above), the same codec-to-northbound pattern that bridges the wired field buses (CAN over SPI, RS-485 / IO-Link over UART) extends to wireless radios: each radio is a southbound peripheral reached over SPI / I2C / UART, and a gateway function bridges its frames northbound to the existing WiFi / MQTT / HTTP / WebSocket stack. Deterministic, zero-heap, fixed buffers; the DMA-complete / data-ready ISR posts into the preempting queue, so wireless ingest rides the same real-time path as the wired codecs. (ESP32 Wi-Fi / classic + BLE are already on-chip; everything below is an external module we wire to a bus.)
The generic gateway framework is shipped (DETWS_ENABLE_GATEWAY, services/gateway): ports, address-aware northbound enveloping + topic (det_gw_uplink / det_gw_topic), bidirectional up/down-link, a per-port rate cap, and stats - all fail-closed and zero-heap, HW-verified end to end over DMA + the FORWARD lane. Each radio below is now a per-module codec + driver (frame parse, SPI/UART register access) plugged into that framework's callbacks; the items stay open until verified against the real hardware.
SPI radios:
DETWS_ENABLE_LORA, services/lora): the RadioHead 4-byte header codec + the SX1276 register protocol (init / send / recv / RSSI) over a portable register-access bus, host-tested against a mock chip; example 11.LoRaGateway drives a real RFM95 and bridges frames northbound via the gateway. Remaining: verify the RF link on the module, an SX126x variant, and a bounded LoRaWAN Class A uplink/downlink codec.DETWS_ENABLE_NRF24, services/nrf24): the SPI command protocol (init / send / recv, pipe-addressed, static payload) over an SPI + CE bus, host-tested against a mock chip; example 12.Nrf24Gateway bridges frames northbound via the gateway. Remaining: verify the RF link on the module (and optional dynamic-payload / auto-ack).DETWS_ENABLE_CC1101, services/cc1101): the CC1101 SPI header protocol (config registers, the 13 command strobes, status registers, TX/RX FIFO) - reset + apply a caller-supplied SmartRF register table + set channel + verify VERSION (cc1101_init), variable-length cc1101_send, TX-done poll, cc1101_set_rx, and cc1101_recv with the appended RSSI/LQI status + cc1101_rssi_dbm decode, over a portable SPI bus; host-tested against a mock chip (native_cc1101). Bridges northbound via the gateway framework. Remaining: verify the RF link on the module (and an OOK/ASK preset variant).DETWS_ENABLE_THREAD, services/thread): the byte-stuffed framing + CRC-16/X-25 FCS (encode/decode) that carries spinel, plus the spinel command layer - the packed-uint encoding and spinel_command_build / _parse for a header | CMD | PROP | value property command - host-tested against the X-25 check valueUART radios:
DETWS_ENABLE_ZIGBEE, services/zigbee): the byte-stuffed, CRC-16/CCITT ASH data-link (encode/decode) that carries EZSP, host-tested against the documented RST frame; example 17.ZigbeeGateway bridges a real NCP. Remaining: the EZSP command layer (version negotiation, incomingMessageHandler) + verify against an NCP.DETWS_ENABLE_ZWAVE, services/zwave): SOF/LEN/Type/Cmd/Data frames + the XOR checksum + ACK/NAK/CAN, host-tested against the documented GetVersion frame; example 16.ZWaveGateway bridges a real controller. Remaining: verify against a controller + inclusion / SendData sequences.DETWS_ENABLE_ENOCEAN, services/enocean): telegram parse/build + the CRC-8 (poly 0x07), host-tested with known-answer CRCs and malformed/resync framing; example 13.EnOceanGateway bridges a real TCM 310 over UART. Remaining: verify against a module + EEP profile decoding.DETWS_ENABLE_SIGFOX, services/sigfox): the AT$SF=<hex> uplink formatter + OK/ERROR/pending response classifier, host-tested; example 15.SigfoxUplink sends a reading from a real modem. Remaining: verify against a module + subscription.DETWS_ENABLE_WISUN, services/wisun): since the CoAP service ships only a server, the connector adds the CoAP client request builder (wisun_build_coap: RFC 7252 header + delta-encoded Uri-Path options with extended-length + payload) plus the FAN node registry (register / find / joined-count) keyed on DetIp, and wisun_nodes_json for the web. The app sends the built PDU to a node's IPv6 address over det_udp; pure + host-tested (native_wisun). Remaining: point it at a chosen border-router devboard and verify the mesh end to end (the devboard choice is the only external dependency; the connector code is board-agnostic).I2C / SPI / UART:
DETWS_ENABLE_PN532, services/pn532): normal-information-frame build/parse + ACK with the LCS / DCS checksums, host-tested against the documented GetFirmwareVersion frames; example 14.NfcGateway reads a real PN532 over I2C. Remaining: verify against a reader + the MFRC522 variant.Built-in radio:
DETWS_ENABLE_BLE_GATT, services/ble_gatt): the Attribute Protocol PDUs under GATT (Bluetooth Core Vol 3 Part F) - build/parse read / write / notify / error with little-endian handles - plus a GATT characteristic-table JSON serializer for the web, pure + host-tested (native_ble_gatt). Remaining: wire it to the NimBLE / Bluedroid radio (scan + a GATT server) and an HCI-UART transport variant.
A read-only capture mode across the same interfaces: instead of joining a network and terminating traffic, listen to every frame on a channel and surface it northbound (live over WebSocket, or batched to a PCAP / log) for diagnostics, an on-device IDS, and field debugging. Capture is strictly passive (no injection on the capture path) and fail-closed: a full capture queue drops frames rather than stalling the live data path.
DETWS_ENABLE_PROMISC, services/promisc): promisc_begin(channel, sink) over esp_wifi_set_promiscuous, a pure wifi_frame_parse() 802.11 header decoder (to/from-DS src/dst/bssid, QoS, WDS), and libpcap DLT_IEEE802_11 framing. The sink feeds the forwarding plane, so captured frames bridge to another interface - capture on Wi-Fi, forward to Ethernet to a wired PCAP collector (example 21.WifiCapture). Host-tested (native_promisc); both cores compiled. Northbound-over-WebSocket is a follow-on wiring.DETWS_ENABLE_BUS_CAPTURE (services/bus_capture): the TWAI controller in listen-only mode decodes every CAN frame without ACKing and feeds the forwarding plane, so captured frames bridge to another interface. can_to_socketcan() + libpcap DLT_CAN_SOCKETCAN make the stream Wireshark-readable (example 22.CanCapture, host-tested native_bus_capture; the SocketCAN framing shares shared_primitives/pcap.h with the Wi-Fi capture). Remaining: RS-485 receive-only decode into the same sink.DETWS_ENABLE_RADIO_SNIFF, services/radio_sniff): wraps each received 802.15.4 MAC frame in the Wireshark IEEE 802.15.4 TAP pseudo-header (per-frame RSSI + channel TLVs, an exact int->float32 RSSI encode) and a pcap record (new DET_DLT_IEEE802_15_4_TAP / _NOFCS link types in shared_primitives/pcap.h), so a sniffed channel opens directly in Wireshark; pure + host-tested (native_radio_sniff). Remaining: put the CC1101 / LoRa / Thread drivers into receive-only and route their frames through this into the existing forwarding sink on hardware.
Sensors that read the environment by measuring a perturbation in an emitted or ambient field, the same southbound-peripheral pattern pointed at sensing instead of comms: the device reads the sensor over SPI / I2C / UART and bridges the readings northbound to the dashboard, telemetry math, and MQTT / WebSocket. The data-ready ISR posts into the preempting queue, so sensing shares the real-time ingest path.
DETWS_ENABLE_LD2410, services/ld2410): the framed 256000-baud report codec (ld2410_parse_report basic + engineering mode, a byte-by-byte Ld2410Stream reassembler that resyncs on noise, presence / distance helpers) and the FD FC FB FA config-command encoders, pure + host-tested (native_ld2410), with an ESP32 UART binding (example wired). Remaining: an MR60BHA 60 GHz vital-sign variant, the Infineon BGT60 SPI radar, and the analog Doppler (HB100 / RCWL-0516) presence path.DETWS_ENABLE_MPR121, services/mpr121) and the FDC2114/2214 capacitance-to-digital field sensor (DETWS_ENABLE_FDC2214, services/fdc2214): the 28-bit data combine + error flags + frequency scale + single-channel config-sequence builder, pure codec host-tested (native_fdc2214), with an ESP32 I2C binding that verifies the device id, applies the config, and reads the channel. Remaining: verify capacitance shift on a real electrode / bench.DETWS_ENABLE_LDC1614, services/ldc1614): the LDC1614 28-bit data combine + error flags + frequency scale (data/2^28 * fref) + single-channel config-sequence builder, pure codec host-tested (native_ldc1614), with an ESP32 I2C binding that verifies the device id, applies the config, and reads the channel. Remaining: verify eddy-current shift on a real coil / bench, and the magnetometer EM path.DETWS_ENABLE_VL53L0X, services/vl53l0x): the documented ranging registers - vl53l0x_range_mm combines the range byte pair, vl53l0x_data_ready decodes the interrupt-status byte, vl53l0x_range_valid checks the device range-status field - pure codec host-tested (native_vl53l0x), with an ESP32 I2C binding that verifies the model id, starts continuous ranging, and reads the distance. Remaining: apply ST's tuning blob for best accuracy, a VL53L1X variant, and a bench range check.
DETWS_ENABLE_DASHBOARD: a compile-time DetwsWidget table served as hand-rolled SVG (gauge / value / bar / sparkline, no external JS) via the web_assets pipeline, live over SSE; detws_dashboard_set / _publish (example 62.Dashboard). Flagship.detws_dashboard_on_control callback) and a Canvas chart widget for dense series.services/telemetry: moving-window stats (mean / variance / stddev / min / max), a rate-of-change tracker, and a trapezoidal run-time totalizer (example 61.Telemetry).Cache-Control beside ETag _(shipped)_ - set_cache_control() injects it into serve_file / serve_static responses.DETWS_ENABLE_HTTP_DELIVERY (services/http_delivery): RFC 5861 detws_delivery_swr freshness decision + detws_delivery_cache_control header (stale-while-revalidate), RFC 7233 detws_delivery_range byte-range parse (X-Y / X- / -N, clamped, 416/multi-range rejected) plus detws_delivery_content_range for a 206 (delta/offset log fetch), and detws_delivery_sw_manifest emitting the versioned {"version":..,"precache":[..]} a service worker consumes (SW cache injection). Pure, host-tested (native_http_delivery). Remaining: wiring the cores into the served endpoints + shipping the static service-worker asset (M).DETWS_ENABLE_CBOR: a zero-heap RFC 8949 writer plus a cursor decoder (cbor_peek / cbor_read_*, no-copy strings) over caller buffers - ints / strings / bytes / arrays / maps / bool / null / float; host-tested against the spec vectors + round-trip (example 65.Cbor).DETWS_ENABLE_MSGPACK: a zero-heap streaming writer over a caller buffer - shortest-form ints (fixint / 8 / 16 / 32 / 64) / str / bin / arrays / maps / bool / nil / float32; overflow tracked, fails closed - plus a cursor decoder (msgpack_peek / msgpack_read_*, no-copy strings, fail-closed on malformed/truncated input, ext reported as INVALID) host-tested against spec vectors + round-trip and fuzzed in the pentest harness (example 66.MsgPack, both directions). Remaining (M-L): Protobuf / FlatBuffers zero-copy.DETWS_ENABLE_GRAPHQL: services/graphql parses a query into a fixed AST pool (no heap) and emits {"data":{...}} shaped by the selection; schema-free (sub-selection = object, leaf = one resolver call, args collected along the path), with nesting + arguments (example 81.GraphQL). Feature-dependent schema generation remains open (M).DETWS_ENABLE_GPIO_MAP: a compile-time pin table (number / label / direction / live level) served at GET /gpio as JSON, with a POST control (pin, level) that drives a mapped output; the serializer + control parser are host-tested, and the example ships a zero-dependency browser panel (example 67.GpioMap). Remaining (M): ping / tracert panel, web logic analyzer.DETWS_ENABLE_SPA_ROUTER: services/spa_router detws_spa_route() decides, from a request path, whether to serve a real asset file, serve the SPA shell (index.html) for an extensionless client-side route, or pass through to the app under an API prefix - so a single-page UI's client routing works over static file serving. Pure decision core, host-tested (native_spa_router). Remaining: conditional UI streaming + a local SCADA/HMI fallback (M).DETWS_WS_FRAG_SIZE (0 = off, default) / the runtime ws_set_frag_size(): an outbound data message longer than the set payload size is split into that-sized WebSocket frames (RFC 6455 sec 5.4: opcode+RSV1 on the first, CONTINUATION on the rest, FIN on the last), so each frame maps to whole TCP segments (MTU-aligned) and a peer with a bounded per-frame reassembly buffer can receive an arbitrarily long message. Compression (RFC 7692) applies to the whole message first, then the compressed bytes are split. Host-tested (test_ws_outbound_fragmentation); default 0 keeps the single-frame behavior unchanged.
DETWS_ENABLE_OPCUA: the OPC UA Binary built-in-type codec, UA-TCP (UACP) framing, Hello/Acknowledge handshake, OpenSecureChannel, CreateSession/ActivateSession, GetEndpoints, Read/Write (Variant/DataValue), and Browse, served on PROTO_OPCUA (listen(4840, PROTO_OPCUA); example 55.OpcUa), plus an OPC UA client (example 56.OpcUaClient). Host-tested (native_opcua, native_opcua_client) and HW-verified end-to-end against the asyncua reference stack via the interop harness (opcua-client 3/3: connect+session, browse Objects, read node). Remaining (L): a secure SecurityPolicy (Basic256Sha256 encryption/signing), subscriptions / monitored items, and Node-RED integration (M).DETWS_ENABLE_MODBUS_MASTER: services/modbus/modbus_master builds read-request ADUs and parses responses (register values or exception) so an app can poll / auto-discover a slave's registers; pure, host-tested as a full round-trip against the slave codec, HW-verified via self-scan (example 72.ModbusScan).DETWS_ENABLE_SOUTHBOUND (services/southbound): the uniform seam every field-device driver plugs into so the app polls/drives any southbound device (Modbus slave, BACnet controller, raw SPI/I2C/UART sensor) through one facade - a bounded driver registry (detws_southbound_register / _find / _count / _clear) plus name-dispatched detws_southbound_read / _write / _read_block / _write_block, where the block calls are the atomic multi-point (register-matrix) path. Each driver owns its transport (a read/write vtable + ctx); Modbus master is the one such driver today. Pure registry + dispatch, host-tested (native_southbound). Remaining: a Modbus adapter binding modbus_master into a SouthboundDriver + the concrete atomic register matrix (M), both transport-gated.DETWS_ENABLE_WEBHOOK: services/webhook builds an IFTTT Maker URL + value1/2/3 JSON payload (pure, host-tested with JSON escaping) and fires it - or any JSON to any URL (Slack/Discord/your API) - via the outbound http_client; HW-verified by a self-loopback POST (example 75.Webhook).DETWS_ENABLE_SMTP): services/smtp runs a blocking RFC 5321 send over the shared det_client transport (EHLO, optional AUTH LOGIN, MAIL/RCPT/DATA with dot-stuffing, QUIT), with implicit TLS (SMTPS, port 465) when the message sets tls and DETWS_ENABLE_TLS is on; zero heap, fixed buffers. The dialogue engine (smtp_run) is split behind a send/recv seam and host-tested against a scripted mock server (native_smtp, 11 cases: happy path, AUTH, dot-stuffing, multi-line replies, every error). SMS needs no new code (email-to-SMS carrier gateway address). Example 57.SmtpAlert ships a full beginner walkthrough that stands up a Postfix server. Remaining: STARTTLS on the submission port (587) upgrade, and a live over-the-wire HW send (blocked in the current lab by AP client isolation + ISP port-25 filtering, same as the concurrent-TLS soak - the code compiles + boots + connects on the modern core).DETWS_ENABLE_ESPNOW: services/espnow adds a typed 3-byte envelope (demux by type + length check) over connectionless ESP-NOW, a bounded peer registry, and the esp_now radio binding (begin / add_peer / send / broadcast, decoded frames to a callback for bridging to WS/SSE) (example 82.EspNow). Codec + registry host-tested; ESP-NOW<->MQTT auto-bridge remains open (M).
det_net_egress() / det_net_egress_ip() read the live lwIP default route so the app always knows which interface (WiFi STA / softAP / Ethernet) its traffic is leaving on; the stack owns the actual failover, so no manager or polling tick is added (example 63.NetEgress).DETWS_ENABLE_LINK_MANAGER (services/link_manager): a table of interfaces (kind + priority + up/down) with deterministic best-link-up selection, graceful escalation to a higher-priority interface when it comes up, failover to the next best when it drops, and change detection so the app reconfigures the netif only on a real transition. Pure, host-tested (native_link_manager). Remaining: the esp_eth PHY init on hardware + the multi-interface bridge / packet forwarding (the v5 interface-forwarding milestone).DetIp (RFC 4291 parse / RFC 5952 format / scope classify / CIDR) and the netif bring-up is DETWS_ENABLE_IPV6 (physical layer; listeners bind IPADDR_TYPE_ANY). The client-side fallback is DETWS_ENABLE_HAPPY_EYEBALLS (services/happy_eyeballs): detws_he_pref scores a destination (RFC 6724 scope + family), detws_he_order sorts a candidate list and interleaves the address families (RFC 8305) so successive attempts alternate v6/v4, and detws_he_attempt_due gates the next attempt by the Connection Attempt Delay - fast IPv6, quick IPv4 fallback. Pure, host-tested (native_happy_eyeballs). Remaining: VPN tunneling + the reverse-SSH tunnel to a relay (L; the ssh -R tcpip-forward seam already landed).ssh_curve25519 (D-H group 31 = X25519, and MODP groups via ssh_bignum), ssh_chachapoly (ChaCha20-Poly1305 per RFC 7634), ssh_sha256/512 (PRF/INTEG HMAC), AES-GCM (the quic_aead core) - so the primitive surface is largely done; this is mostly framing + the key-derivation (SKEYSEED / the SK_* chain).DETWS_ENABLE_WIFI_SNIFFER (services/wifi_sniffer): detws_wifi_parse decodes an 802.11 MAC header (frame-control type/subtype + flags and the addresses whose roles depend on the ToDS/FromDS bits), detws_wifi_stats_* tallies frames by type for a traffic panel, and detws_wifi_should_roam is the RSSI-hysteresis channel-agility roaming decision. Pure, host-tested (native_wifi_sniffer). Remaining: the promiscuous-mode radio callback + a live channel-hop scan loop on hardware (M).DETWS_ENABLE_DNS_RESOLVER: services/dns_resolver resolves a hostname to IPv4 (lwIP dns_gethostbyname marshalled to tcpip_thread, dotted-quad fast path) and verifies the answer - rejecting 0.0.0.0 / broadcast / loopback / multicast as spoof / DNS-rebinding indicators; classifier + verifier host-tested, HW-verified against live DNS (example 77.DnsResolver). Remaining (M): captive-portal DNS-spoof mitigation, captive-portal auto-teardown timer._https._tcp / extra services _(shipped)_ - detws_mdns_txt / detws_mdns_add_service.DETWS_ENABLE_MDNS_ADAPTIVE (services/mdns_adaptive): detws_mdns_beacon_adapt backs the announce interval off toward a ceiling under RF contention and recovers it toward the nominal cadence when the air is quiet, detws_mdns_refresh_interval gives the TTL/2 continuous-refresher cadence, detws_mdns_beacon_due says when an announce is due, and detws_mdns_beacon_presleep_due is the auto-sleep beacon (announce before a sleep window that would let the record lapse). Wrap-safe time math, pure, host-tested (native_mdns_adaptive). Remaining: wiring the schedule to the mDNS transmit + a live contention counter from the WiFi driver (M).DETWS_ENABLE_UDP_TELEMETRY: services/udp_telemetry builds InfluxDB line-protocol records (measurement field=val,..., host-tested) and casts them to a collector over UDP via det_udp_sendto, zero-heap fire-and-forget (example 68.UdpTelemetry).DETWS_ENABLE_NETADAPT: services/netadapt detws_netadapt_window() sizes the TCP receive window from the free heap (a reserve left untouched, then a quarter of the spare, clamped to [min, max]) so a RAM-rich device gets throughput and a tight one stays alive; detws_netadapt_dhcp_fallback() decides when to give up on DHCP and use a static IP (elapsed timeout or retry budget). Pure cores, host-tested (native_netadapt); the app applies the results (lwIP window / netif config). Dynamic socket recycling (the transport-pool concern) shipped separately as DETWS_ENABLE_SOCKPOOL (services/sockpool): a fixed LRU connection-slot pool that, when saturated, recycles the least-recently-used slot for a new peer and reports the evicted id so the transport closes it (acquire / touch / release / find), pure + host-tested (native_sockpool). Remaining: none (cores complete).
DETWS_ENABLE_RADIO_POWER: services/radio_power applies a WiFi modem-sleep mode (DETWS_RADIO_WIFI_PS none/min/max) + an optional max-TX cap (DETWS_RADIO_MAX_TX_DBM) in one call (esp_wifi_set_ps / set_max_tx_power), trading throughput for lower average power; mode names host-tested, apply/readback HW-verified (example 76.RadioPower). Remaining: BT-coexistence preference (only relevant on a BT-enabled build).DETWS_ENABLE_SLEEP_SCHED: services/sleep_sched detws_sleep_next() decides, from the idle time, how long a low-power device should sleep (0 = awake), ramping the window from a floor up to a ceiling (doubling every ramp_ms) the longer the idle streak runs. Pure wrap-safe decision core, fully host-tested (native_sleep_sched); the app applies the window via light / modem / deep sleep. Complements services/radio_power. Remaining (need real hardware to verify): dynamic power scaling, thermal throttling, brownout recovery, peripheral power gating (M).
listener_ip_allow_add_cidr("192.168.1.0/24") / listener_ip_allowed (IPv4 + IPv6 CIDR rules matched on the full address, DETWS_ENABLE_IP_ALLOWLIST; example 58.IpAllowlist).DETWS_ENABLE_AUTH_LOCKOUT; auth_lockout_* table (keyed on the full IPv4/IPv6 address) issues 429 + Retry-After on the HTTP auth gate (example 59.AuthLockout).DETWS_ENABLE_CSRF; global enforcement on POST/PUT/PATCH/DELETE via a stateless HMAC-signed X-CSRF-Token (built-in GET /csrf issues it; example 60.Csrf).jwt_claim_str() reads string claims (sub / role / scope) and jwt_scope_allows() matches a space-separated OAuth2 scope claim, so a handler can authorize per role/scope on the verified JWT (example 21.JWTAuth).DETWS_ENABLE_TOTP: services/totp computes / verifies RFC 6238 time-based one-time passwords (HMAC-SHA1 on the software SHA-1, Authenticator-compatible) and decodes base32 secrets, for a second factor on top of password / JWT; host-tested against the RFC vectors, HW-verified (example 74.Totp). An external-API verifier can also be called from a handler via the http_client.services/oidc: JWKS key selection by kid, real RSA-2048 signature check, iss/aud/exp/nbf, claim extraction) plus the OAuth2 token-endpoint client (services/oauth2: authorization_code + refresh_token grants, confidential-client or PKCE, JSON token parsing; example 83.OAuth2). SAML remains open (heavy XML/canonicalization - poor fit) (L).detws_uuid_from_mac / detws_device_uuid (RFC 4122 v5; example 57.DeviceUuid).curve25519-sha256 + ssh-ed25519 host key + ed25519 client auth, alongside the diffie-hellman-group14-sha256 + rsa-sha2-256 set, with the preference runtime-selectable (ssh_kex_set_prefer_rsa, default RSA). The curve backend is a bespoke constant-time X25519 / Ed25519 that fits the zero-heap / no-stdlib idiom (no micro-ecc / donna / wolfSSL dependency), with the ESP32 MPI accelerator driving field inversion on-device and a software fallback retained. RSA stays as a fallback; ed25519 host-key provisioning sits alongside the RSA DER path; crypto KATs cover both suites and the OpenSSH interop test passes with zero forced algorithms. HW-verified on an ESP32-S3 (both suites; worker stack raised to 12288 when SSH is enabled, curve peaks ~10.5 KB).
Comparison against Oryx Embedded's CycloneSSL (TLS/DTLS) and CycloneSSH - the closest portable, zero-external-dependency C stacks to ours. Only gaps that matter on an ESP32 are tracked (their Camellia / SEED / ARIA / Serpent / Twofish / RC4 / Blowfish / DSS ciphers are deliberately skipped). Benchmark caveat: Oryx publishes their numbers at -O3, so any apples-to-apples throughput / handshake comparison must build our crypto at -O3 too (our default examples build -Os); measure the same core on the same S3 at -O3 before claiming a delta. Our current surface: TLS = mbedTLS 1.2/1.3 (ECDHE curve-pref, AES-GCM, ChaCha20-Poly1305, tickets/resumption); SSH = curve25519-sha256 + dh-group14, ssh-ed25519 + ecdsa-sha2-nistp256 + rsa-sha2-512/256 host keys, chach.nosp@m.a20-.nosp@m.poly1.nosp@m.305@.nosp@m.opens.nosp@m.sh.c.nosp@m.om + aes256-gcm + aes256-ctr, hmac-sha2-256/512 (+ETM), zlib@.nosp@m.open.nosp@m.ssh.c.nosp@m.om s2c, password + publickey auth.
DETWS_ENABLE_PQC_KEX) - the headline gap is closed for SSH. Shipped a constant-time, zero-heap ML-KEM-768 core (FIPS 203, the novel primitive: software NTT over q=3329 + a Keccak/SHA-3/SHAKE sponge) in network_drivers/presentation/pqc, Encaps-only (the device is always the responder). The SSH transport advertises mlkem768x25519-sha256 (draft-ietf-sshm-mlkem-hybrid-kex) first, runs the existing X25519 alongside ML-KEM-Encaps, and combines K = SHA256(K_PQ || K_CL) (RFC 9370). Byte-exact vs the FIPS 203 reference (kyber-py) + a decisive end-to-end host test (independent ML-KEM Decaps client, string-K exchange hash / KDF, ed25519 signature). Peak ~7 KB worker stack (DETWS_WORKER_STACK_PQC_MIN). Pending: interop vs real OpenSSH 9.9+ on the S3 rig.tls13_msg supported_groups + key_share (client share = ek(1184) || X25519(32); server share = ciphertext(1088) || X25519(32) - ML-KEM first per draft-ietf-tls-ecdhe-mlkem), concatenated the two secrets (ML-KEM || X25519, 64 B) into the tls13_kdf handshake secret, and enlarged the QUIC ServerHello flight buffer for the ~1.1 KB key_share. A PQC-capable browser negotiates it; others fall back to X25519. Verified by a full hybrid handshake host test (independent ML-KEM Decaps client + both Finished MACs). The classical HTTPS path stays on mbedTLS (whose hybrid support tracks its version). Pending: curl/browser HW interop.aes256-gcm@openssh.com** _(shipped)_ - AES-256-GCM AEAD (RFC 5647): length-in-clear as AAD, per-packet invocation counter, no separate MAC. Self-contained ssh/crypto/ssh_aesgcm (AES block HW via mbedTLS on ESP32, GHASH in software; AES-256, so not the AES-128 http3/quic_aead core). Seal/open KAT vs the NIST/McGrew Test Case 16 vector + a packet-layer round-trip; negotiated second after chacha.rsa-sha2-512** host-key signatures _(shipped)_ - RFC 8332 SHA-512 PKCS#1 v1.5 over the exchange hash, hash-parameterized ssh_rsa_sign/_verify (SHA-512 DigestInfo). The one ssh-rsa key now advertises both rsa-sha2-512 (preferred) and rsa-sha2-256 in server_host_key_algorithms and server-sig-algs; client-auth picks the verify hash from the signature name. SHA-512 KAT byte-exact vs openssl + hash-binding, e2e KEXDH reply, and a genuine client-auth signature all tested.ecdsa-sha2-nistp256** host key + client auth _(shipped)_ - ECDSA over NIST P-256 (RFC 5656, SHA-256). New ssh/crypto/ssh_ecdsa: ESP32 mbedTLS (HW, side-channel-hardened); native is a self-contained software P-256 (field/Jacobian point math) with RFC 6979 deterministic signing, so the sign path is byte-exact against the RFC 6979 A.2.5 vectors. Installed via ssh_hostkey_ecdsa_set(); negotiated in server_host_key_algorithms + server-sig-algs; the KEXDH reply carries the P-256 blob and an mpint(r)||mpint(s) signature; client-auth verifies. KAT + e2e KEXDH + genuine client-auth all tested. _(aes128-gcm skipped: 256 is the ESP32-relevant one.)_ecdh-sha2-nistp256** key exchange _(shipped)_ - completes RFC 5656 (its §4 ECDH alongside the §3 ECDSA host key). The shared secret is the X coordinate of d_S * Q_C, hashed as an mpint with Q_C/Q_S as 65-byte point strings; the peer point is on-curve checked. Reuses the one P-256 curve in ssh/crypto/ssh_ecdsa (ESP32 mbedtls_ecdh_compute_shared, HW; native software P-256), added ssh_ecdsa_p256_ecdh(). Negotiated in kex_algorithms. Byte-exact vs the RFC 5903 §8.1 shared-secret KAT + a negotiation test + an e2e KEXDH that verifies the host signature over the reconstructed hash.services/dnc) over the one authenticated SSH port. Fixed-BSS handle table, streamed reads/writes, no heap.DETWS_ENABLE_DTLS, network_drivers/presentation/dtls):** the RFC 9147 §4 record layer (dtls_record) - DTLSPlaintext + DTLSCiphertext (unified header, AEAD_AES_128_GCM, §4.2.3 sequence-number encryption via an AES-ECB mask + §4.2.2 reconstruction, the TLS 1.3 nonce, §4.5.1 anti-replay window); the §5 + §7 handshake framing / reliability (dtls_handshake) - the 12-byte handshake header, overlap-tolerant reassembly, the ACK message, and the stateless HelloRetryRequest cookie; and the §5-6 server handshake state machine (dtls_conn) - the one-round-trip full handshake (TLS_AES_128_GCM_SHA256 / X25519 / Ed25519), epoch 0→2→3 transitions, reusing the TLS 1.3 messages + key schedule. Byte-exact host KATs (native_dtls, native_dtls_hs, native_dtls_tls13) plus an end-to-end handshake proof (native_dtls_conn: a from-scratch peer completes it, both sides install matching app keys) and wolfSSL DTLS 1.3 interop (direct + HelloRetryRequest group renegotiation). HelloRetryRequest group renegotiation (§5.1, address-bound cookie) and ACK/timeout retransmission (§5.8, fresh-seq flight re-send on exponential backoff) are shipped, and the CoAP-over-DTLS front-end is complete - the coaps_process bridge plus coaps_server, a per-peer DtlsConn pool binding UDP 5684 (coaps://), driven by one coaps_server_poll() (native_coaps / native_coaps_server; example 78.CoapSecure). Connection IDs (§9 / RFC 9146) are shipped too - the connection_id extension is negotiated in the handshake, every DTLSCiphertext carries the peer's CID, and coaps_server routes by CID so a session survives a client address change / NAT rebinding (native_dtls* + wolfSSL --cid interop). Remaining: full HW-rig interop.
DETWS_ENABLE_PARTITION_MONITOR: detws_partition_monitor_begin() serves the flash partition table (label, kind, type/subtype, offset, size, running app slot) as JSON via esp_partition / esp_ota_ops; kind classifier + serializer host-tested (example 64.PartitionMonitor).DETWS_ENABLE_CONFIG_IO: services/config_io serializes a declared schema of fields to a portable key=value text blob and parses one back into the NVS config store - backup / migrate / bulk-provision, deterministic and zero-heap (host-tested round-trip; example 71.ConfigExport). Remaining (M): full enumeration-based export (needs NVS key iteration), ZTP multi-stage provisioning.DETWS_ENABLE_VFS: services/vfs gives one file API (open/read/write/close, exists/size/remove/rename, whole-file helpers) over a pluggable backend - a zero-heap RAM pool (deterministic, host-tested) or a real fs::FS (LittleFS / SD / SPIFFS) on ESP32 - so features target storage without knowing the medium (example 80.Vfs).DETWS_ENABLE_WEARLEVEL: services/wearlevel detws_wearlevel_pick() returns the least-worn slot (from per-slot write counts the app persists) so repeated flash/NVS writes spread evenly and the region ages together instead of burning out one block, plus a detws_wearlevel_spread() imbalance metric. Pure core, host-tested (native_wearlevel; a 4000-write pick+mark loop levels every slot exactly). Log offload rides the existing services/logbuf pluggable sink (SD / syslog / HTTP). Remaining: hot-swap storage safeties (M).DETWS_ENABLE_OTA_ROLLBACK: services/ota_rollback commits a freshly-updated image once a self-test passes, or rolls back to the previous image if the self-test fails or the confirm window elapses, so a bad update self-heals; decision logic pure + host-tested, commit/rollback via esp_ota_ops, HW-verified (example 73.OtaRollback). Remaining: modular partition swapping (M).heap_caps_calloc(MALLOC_CAP_SPIRAM) at begin) + asset offloading + COMPONENT_EMBED_TXTFILES (M); SPI DMA ping-pong buffers (M) _(placement policy shipped)_ - DETWS_ENABLE_PSRAM_POOL (services/psram_pool): detws_psram_place picks DRAM vs PSRAM for a buffer by size, DMA requirement, and free-heap headroom (large/cold to PSRAM, small/hot + DMA to DRAM, always leaving an internal-DRAM reserve), and detws_pingpong_* keeps the SPI DMA double-buffer bookkeeping (CPU fills one buffer while DMA drains the other; swap flips their roles). Pure, host-tested (native_psram_pool). Remaining: the heap_caps_calloc allocation glue at begin + the COMPONENT_EMBED_TXTFILES asset-offload build wiring (M).
DETWS_ENABLE_LOGBUF: services/logbuf keeps the last DETWS_LOG_LINES lines in a fixed RAM ring (oldest pruned, no heap), dumps them oldest-first for a /logs endpoint, and fires a trap callback on lines at/above a severity threshold (forward critical lines as an SNMP trap / webhook); pure + host-tested (example 70.LogBuffer).DETWS_ENABLE_EXC_DECODER (services/exc_decoder): detws_exc_parse extracts a captured Guru Meditation panic dump (cause, register PC + EXCVADDR, and the Backtrace: pc:sp ... frames, tolerating missing fields + a trailing |<-CORRUPTED) into a structured ExcInfo, and detws_exc_json serializes it for a live /exception panel; the browser / build server resolves the PCs to file:line against the firmware ELF (addr2line lives off-device). Pure, host-tested (native_exc_decoder). Remaining: the core-dump-partition read + SD/FTP offload transport, and zero-overhead abstract logging (M).DETWS_ENABLE_GUARDRAILS: services/guardrails samples free heap, the heap low-water mark, the largest free block (fragmentation), and a task's remaining stack, and fires a breach callback when any crosses its DETWS_GUARDRAIL_* floor - a proactive fail-safe hook on top of the passive /metrics numbers; evaluator + JSON host-tested, served at /health (example 69.Guardrails).DETWS_ENABLE_FAILSAFE: services/failsafe, a software watchdog. Register a "lifeline" (a task / worker / control loop) that must check in (detws_failsafe_feed) within its deadline; detws_failsafe_check() detects one that wedged (hang / deadlock) via a wrap-safe time delta and fires a breach callback once per stuck episode so the app drives a known-safe state. App-defined and per-lifeline, on top of the hardware task WDT; the pure core takes an explicit now so it is fully host-tested (native_failsafe), plus a /health-style JSON serializer. Zero heap, no stdlib.DETWS_ENABLE_HW_HEALTH (services/hw_health): detws_hwhealth_rail_sample tracks a rail's worst droop + sag/brownout counts (with a /health JSON serializer), detws_hwhealth_spi_result is a hysteretic clock-backoff state machine (halve on a CRC-fail streak, step up on an ok streak, clamped to a floor/ceiling), detws_hwhealth_gpio_short classifies a driven-vs-readback mismatch as a short to ground / Vcc, and detws_hwhealth_cap_leak compares a measured RC decay to expected (too fast = leak, too slow = high ESR). Pure, host-tested (native_hw_health). Remaining: the ADC / SPI / GPIO sampling glue that feeds them on real hardware (M).
server.diag() / DETWS_ENABLE_DIAG serves a build-info JSON (example 42.Diagnostics); the feature enumeration could be extended.docs/utilities/gen_configurator.py generator parses src/ServerConfig.h and builds the flags as a hierarchy grouped under the file's section titles, with the hard dependency edges (#if child && !parent guards) encoded - the build-flag tree, kept from drifting by the check CI gate. Remaining: the virtual protocol-mocking toggles (swap a real driver for a mock transport at build time).python test/servers/interop.py <protocol>, drives the device against the real reference implementation, not just our own round-trip. Peers so far: HTTP (stdlib client), WebSocket (websockets), SNMP (net-snmp, pysnmp fallback), Modbus client + server (pymodbus), CoAP (aiocoap), MQTT broker (mosquitto + paho), OPC UA client + server (asyncua). Each peer says whether the device is the server (the harness probes it, --host ...) or the client (the harness serves a reference peer the device connects to), reports uniform PASS/FAIL, and exits 0/1/2. HW-verified against the board on real third-party stacks: HTTP 4/4, WebSocket 3/3, CoAP 2/2, Modbus 6/6, SNMP 3/3, MQTT (device client -> real mosquitto), and OPC UA (asyncua) 3/3 - all seven protocol families. Adding a protocol is one module in peers/ (documented in its README). Remaining: wiring it into CI containers, and a peer per new protocol as it lands.native_pentest + a nightly Pentest CI job, not part of the per-commit unit-test run) that fuzzes the untrusted-input parsers (HTTP request line/headers/body, Modbus ADU, base32) with malformed, oversized, partial slowloris-style, binary/protocol-confusion, and deterministically-random input, asserting the device's safety invariants: fixed footprint (no buffer index past its bound), fail-closed (defined error states only), and liveness (no hang/over-read). Plus a documented on-device stress playbook (slowloris / floods / brute-force vs the throttle / lockout / allowlist defenses). Full guide: PENTEST.md. Extend it to the remaining codecs (CBOR / SNMP / CoAP / WS / multipart) as you go.configurator/) (L) _(GUI + source-of-truth shipped)_ - a guided front end for the ~200 DETWS_ENABLE_* and sizing flags so a user assembles a firmware build without hand-editing build_flags. Shipped as docs/utilities/gen_configurator.py -> docs/configurator.html: it parses ServerConfig.h (the single source of truth, so it never drifts - a check CI gate fails on staleness) for every feature flag + tuning knob + section group + the hard #if child && !parent dependencies, and emits one self-contained page that ticks features, tunes knobs, resolves dependencies (mutual-exclusion), and copies out a platformio.ini build_flags block or a #define set (only the values that differ from the defaults). Beginner-friendly, ships to Pages. Remaining: a live per-option build-footprint estimate (flash + RAM from the FEATURES tables), advisory "this is unwise, but here you go" guardrails, and a standalone CLI backend (the emission is client-side today).
The big-ticket transport/protocol upgrades, sequenced last. Each is large (L) and gated on the internal-piping straightening (a clean transport read/write API) so a new framing/record layer slots in behind one owner instead of threading through every layer. The current HTTP/1.1 core already tracks the modern HTTP specs (RFC 9110 semantics, RFC 9112 messaging - which obsolete RFC 7230/7231).
network_drivers/tls (mbedTLS, floored at TLS 1.2). The version + cipher policy shipped as DETWS_ENABLE_TLS_POLICY (services/tls_policy): detws_tls_negotiate_version picks the version server-style, detws_tls_version_name makes it observable, detws_tls_select_cipher pins the suite allowlist by server preference, and detws_tls_is_aead classifies suites - pure + host-tested (native_tls_policy). Remaining: wire the policy into the mbedTLS conf (cert/cipher list) + session-resumption tickets.services/tls_policy (detws_tls_is_aead + the 1.3 suites 0x1301-0x1303 in the allowlist). Remaining: optional 0-RTT early data with replay safeguards, and exposing a 1.3-only profile toggle.h2 ALPN over the TLS layer above; streams mapped onto the deterministic per-connection model without per-stream heap.quic_server pool (DCID routing, ingest ring); and the DetWebServer bridge that serves HTTP/3 through the same routes as HTTP/1.1 and HTTP/2 (server.h3_cert(...) + begin()). curl 8.14.1 (OpenSSL 3.5.6 QUIC + nghttp3) completes the handshake and serves GET / POST at ~14 ms/request (see tools/interop/); the ESP32-S3 mbedTLS hardware-crypto path is byte-identical to the software path. Remaining as v5.x hardening (not correctness blockers): PTO loss recovery, CONNECTION_CLOSE on idle / error, and the PSRAM build guard for a device deployment.
set_cookie() (name/value plus a freeform attributes string for Secure / HttpOnly / SameSite / Max-Age / Expires / Path / Domain), and inbound reading via http_get_cookie(req, name, out, cap) - parses the request Cookie header (case-sensitive names, DQUOTE-stripped values, = preserved in values), mirroring http_get_header(). Host-tested (test_http_parser, 4 cookie cases); pairs with the session / CSRF / auth features.DETWS_ENABLE_ETAG: a strong ETag + Last-Modified, and 304 Not Modified for a matching If-None-Match or (when no If-None-Match) an If-Modified-Since not older than the file; plus Cache-Control via set_cache_control(). Remaining (optional): response-side freshness heuristics / Age, request Cache-Control.http_forwarded_client() recovers the original client address + scheme from Forwarded (RFC 7239) or the de-facto X-Forwarded-For / X-Forwarded-Proto (leftmost = the client). Both IPv4 (optional :port) and IPv6 (bracketed for="[2001:db8::1]:port" or a bare X-Forwarded-For literal) are recovered and canonicalized (RFC 5952); the candidate is validated with det_ip_parse, so unknown / obfuscated _id tokens are rejected. Host-tested (test_http_parser, 5 cases). It is a helper (like http_get_cookie) the app reads in a handler; auto-wiring the recovered IP into the per-IP auth lockout / audit, gated on a configured trusted upstream (the header is client-spoofable), is the optional remaining step. The IP allowlist stays accept-time (the proxy's real TCP source).
det_conn_send / det_conn_sndbuf / det_conn_flush, plus the private resp_end) no longer threads a tcp_pcb *; it resolves the slot's pcb internally, exactly as the RX read path and the client surface already do, so a caller can no longer pass a pcb that disagrees with the slot. The remaining LOW findings (CBOR text vs MessagePack str; builder-returns-length vs parser-returns-bool) are spec-faithful, consistent house style - left as is..github/workflows/esp32-build.yml) rebuilds every example on each push to main, merges the totals into docs/footprints.json, regenerates docs/FOOTPRINTS.md and the per-feature ranges (tools/ci/example_footprints.py + feature_budget.py), and commits them (docs: update ESP32 build footprints [skip ci]). So the documented numbers stay current after a batch of changes with no manual step - the opt-in codec services added since track through their flags, and the auto-commit lands right after each release bump.🔷 entries are documented scope boundaries tracked here, not violations. The audit is re-run per subsystem change - the verdict + evidence columns are kept current.
The ESP32 does expose raw Layer-2 access on both interfaces, and the hard-real-time cyclic timing is reachable too - so the earlier "PHY can't do raw L2 / can't hit the timing" caveats are corrected here. Timing model: build the device as a single feature + the base web server, and run the protocol's cyclic loop as a hardware-timer / RMT ISR feeding a high-priority preempting task (a top-priority, core-pinned FreeRTOS task that preempts the web server) off a small interrupt queue. The web server runs underneath at lower priority; the cyclic deadline owns the CPU when it fires. With the whole device budgeted to one cyclic loop, the sub-millisecond isochronous deadlines are achievable - the determinism guarantees (fixed buffers, no heap, bounded work) are exactly what makes a hard real-time loop tractable here.
Front-end assumption (applies to every "hardware-gated" item below): the user can attach an external adapter on SPI / UART / I2C to supply any missing PHY, radio, or line transceiver - so the deliverable is always the protocol + a small adapter driver, never "impossible." Canonical examples: an EtherCAT Slave Controller (e.g. LAN9252) over SPI, a HART FSK modem over UART, a DSRC / C-V2X radio module over SPI/UART, an IO-Link master IC over SPI, RS-485 / CAN / SDLC / MBP / LON transceivers, etc. Analog I/O is native: a 4-20 mA current loop (or 0-10 V) is read straight off the ESP32 ADC over a known-range sense resistor (scale = known full-scale range), and driven out via the DAC / PWM - so analog instrument variables (incl. HART's 4-20 mA primary value) need no special front end.
DETWS_ENABLE_RAWL2 (services/rawl2): the host-testable core - build/parse Ethernet II + 802.1Q VLAN frames and the 802.3 FCS (CRC-32, check value 0xCBF43926); host-tested (native_rawl2). The raw-frame API the raw-L2 protocols build on. Remaining (device transport):esp_wifi_80211_tx() injects arbitrary management / data frames (custom beacons, proprietary MAC headers) - bypasses the Wi-Fi state machine. Also the basis for the pentest/observability angle (beacon/mgmt crafting).esp_eth_transmit() + esp_eth_update_input_path() detach the Ethernet MAC from lwIP so the app reads/writes raw Ethernet frames at L2 directly - this is what makes PROFINET-RT / EtherCAT / GOOSE / POWERLINK / SERCOS framing reachable - the cyclic deadline is met by the high-priority preempting-ISR/task model above (single-feature build), not blocked by the platform.
DETWS_ENABLE_UMATI (services/umati, requires DETWS_ENABLE_OPCUA): the umati companion-spec MachineTool information model - Identification, Monitoring (MachineTool / Channel / Spindle / Axis_X..Z), Production, and Notification - served through the OPC UA Browse + Read resolvers out of a caller-owned UmatiMachineTool struct, with faithful BrowseNames per OPC 40501-1 (namespace http://opcfoundation.org/UA/MachineTool/). Host-tested (native_umati, 11/11) and ESP32-compiled (example 72.Umati); a read-only monitoring model any umati / OPC UA client browses and reads by BrowseName. Remaining: companion-spec TypeDefinitions + the MachineTool namespace in the server NamespaceArray (needs array-Variant support), multiple channels/spindles, and Equipment (tool magazine).DETWS_ENABLE_MTCONNECT (services/mtconnect): the MTConnect agent response documents over the existing HTTP stack - an incremental MTConnectStreams builder (the current / sample response: header with instanceId + nextSequence, then per-DataItem <Samples> / <Events> / <Condition> observations) and the MTConnectError document, XML-escaped into a caller buffer; host-tested (native_mtconnect). Remaining: the probe MTConnectDevices doc, MTConnectAssets, the fixed-capacity Devices->Components->DataItems model + the circular sample buffer keyed by instanceId + sequence (the from/count/interval long-poll semantics). Pairs with gpio_map / dashboard as DataItems.DETWS_ENABLE_ENIP (services/enip): the encapsulation layer (TCP/UDP 44818) - eip_build / eip_parse (the 24-octet header), eip_build_register_session, and eip_build_send_rr_data / eip_parse_send_rr_data (wrap/unwrap a CIP message via the Common Packet Format). DETWS_ENABLE_CIP (services/cip): the CIP message inside it - cip_build_epath (class/instance/attribute logical segments), cip_build_get_attr_single / cip_build_request, and cip_parse_response. Together they form a working CIP read path (wrap the CIP request with eip_build_send_rr_data); both verified against the Wireshark ENIP/CIP dissectors, host-tested. Remaining: the broader CIP object dictionary + implicit/IO messaging. PROFINET (RT, raw L2 frames) and EtherCAT rely on raw L2, which the ESP32 does provide (see Low-level networking above - esp_eth_transmit / esp_eth_update_input_path), and the IRT/isochronous cyclic deadline is met by the high-priority preempting-ISR/task model in a single-feature build (this protocol + base web server only). So the full cyclic RT/IRT stack is on the table, not just a discovery subset; PROFINET DCP is the easy first milestone, EtherCAT/IRT the hard one. Each: fixed BSS object/process-image model, no heap.DETWS_ENABLE_CANOPEN (services/canopen); PROFIBUS-DP is DETWS_ENABLE_PROFIBUS (services/profibus); DeviceNet (CIP-over-CAN) is DETWS_ENABLE_DEVICENET; Modbus TCP + RTU ship as modbus (DETWS_ENABLE_MODBUS / _MODBUS_RTU). All host-tested behind their own build flags. Remaining: the per-bus serial/CAN PHY drivers + HW-over-RS-485/CAN verify (tracked per protocol above).DETWS_ENABLE_MODBUS_RTU adds modbus_rtu_process_adu(): the [unit-id][PDU][CRC-16/Modbus] serial frame around the existing data model + function-code dispatch - a CRC mismatch or a non-matching unit address is dropped silently, a broadcast (address 0) executes with no reply. Host-tested (test_modbus, 5 RTU cases incl. the 0xCDC5 CRC vector + read round-trip + bad-CRC/wrong-addr/broadcast). The pure codec is fed a complete frame; a UART/RS-485 driver (3.5-char inter-frame idle) + HW-over-RS-485 verify are the remaining transport step. (Modbus ASCII is a lower-priority follow-on.)DETWS_ENABLE_PROFINET (services/profinet) - the DCP frame codec (10-octet header + option/suboption blocks with even-padding, built by detws_pn_dcp_header / _block, walked by detws_pn_dcp_walk) for Identify + Set (assign NameOfStation / IP) over raw L2 (ethertype 0x8892); host-tested (native_profinet). PROFIBUS-DP: DETWS_ENABLE_PROFIBUS (services/profibus) - the FDL telegram codec - SD1 (no-data) and SD2 (variable-data: SD2 LE LEr SD2 DA SA FC data FCS ED, arithmetic-sum FCS) that a DP master exchanges with slaves over RS-485; host-tested (native_profibus). Remaining: the PROFINET IO cyclic data exchange + GSDML, and the DP-V0 master state machine + GSD slave model. Fixed BSS process image, no heap.services/s7comm, S7-300/400) already shipped, over the same COTP/TPKT transport (services/cotp): the S7CommPlus PDU framing + the integrity/session handshake (protocol versioning + the anti-replay integrity id) and read/write of tag values by symbolic access. Harder than classic S7comm (session keying); zero-heap codec first, then the session state machine.services/focas, DETWS_ENABLE_FOCAS). The FANUC Open CNC API's Ethernet wire protocol (TCP 8193) as a zero-heap client codec - no fwlib32 (the licensed FANUC binary, part # A02B-0207-K732/K737, which cannot run on an ESP32) is used or required. focas_build_* emit the on-wire frames (a 10-octet big-endian magic/version/type/length envelope + payload) for the open/close handshake and the documented read functions (SysInfo, alarm status, CNC parameters, macro variables, position/axis data, actual feed / spindle), and focas_parse_* decode the responses (echoed selector + FOCAS return code + data), including ODBSYS and the FANUC 8-octet data / base^exp value encoding. Frame layout, selector set, and value decoding reverse-engineered vs diohpix/pyfanuc; host-tested (native_focas, 11/11) and byte-cross-checked against an independent Python reference (test/servers/peers/focas_peer.py, which also serves a mock FANUC CNC for on-device interop). HW verification against a real FANUC control pending a rig. Fixed BSS, no heap. Refs: pyfanuc, node-red-contrib-fanuc-focas.services/ads, DETWS_ENABLE_ADS). The Automation Device Specification over TCP 48898: the AMS/TCP + AMS header (target-before-source AMSNetId + port, command id, state flags, cbData, invoke id) plus the ADS commands (ReadDeviceInfo, Read, Write, ReadWrite, ReadState, WriteControl, Add/Delete/DeviceNotification) that read + write PLC symbols by index group/offset or by handle (name -> handle via 0xF003, value via 0xF005). Header layout + command ids verified against the Beckhoff InfoSys spec; payloads cross-checked with Beckhoff's ADS library, pyads, and Apache PLC4X. Host-tested (native_ads), example 73.AdsClient. Fixed BSS, no heap.services/umati (the machine/mould/job model through the Browse + Read resolvers out of a caller struct) - reuses the umati pattern, low risk, high value for moulding shops.services/canopen) / EtherCAT-CoE: the state machine (Controlword / Statusword), Modes of Operation (Profile Position / Velocity / Torque + Cyclic Sync), and the target/actual position-velocity-torque objects. Turns the CAN stack into a motion master; pairs with the dshot / ESC actuator set.Q command set: Q100 machine serial, Q500 program+status, Q600 macro-variable read) that Haas CNC controls answer over RS-232 / the network port. A small, fully-documented CNC read source.services/enip + the PROFINET roadmap item - map the robot's assembly objects), the FANUC Robot Ethernet / Stream Motion UDP position stream, and the KAREL / R-30iB web + socket messaging (SNPX / the RPC the robot's PC interface uses). Start with a zero-heap codec byte-checked against a public reference, then HW-verify against a real controller. Note: "Run MyRobot" (above) is the Siemens SINUMERIK route to running a robot from a CNC - a distinct path we also want. (Robot cell interop is currently unimplemented - this is the tracked entry point.)services/upload_service) and the existing DNC drip-feed (services/dnc) for controllers with no network stack. The unifying goal behind the machine-tool protocol set.DETWS_ENABLE_DNP3 (services\dnp3): a zero-heap builder + CRC-validating parser for the data-link layer - dnp3_build_frame emits the 0x0564 LEN CTRL DEST SRC CRC header block plus the CRC'd 16-octet user-data blocks, and dnp3_parse_frame validates the header and every block CRC (CRC-16/DNP, verified against the canonical 0xEA82 check value) and de-blocks the user data. The remaining layers (transport-function segmentation, the application objects with groups/variations, Class 0/1/2/3 polling, unsolicited responses, time-sync) layer on the de-blocked user data; fixed BSS point database, no heap. Optional Secure Authentication (IEEE 1815 SAv5) later.DETWS_ENABLE_HART (services/hart): the HART command-frame codec - detws_hart_build / _parse with the longitudinal XOR checksum and short (polling) + long (unique-ID) addressing - and the 8-octet HART-IP message header (version / type / id / status / seq / length) for the front-end-free UDP/TCP 5094 path; host-tested against hand-verified vectors (native_hart, command-0 checksum 0x82). Remaining: the command set (universal + common-practice) + device-variable/status model + burst mode, and the FSK modem (over UART) / 4-20 mA-off-the-ADC transports. Fixed BSS, no heap.DETWS_ENABLE_CCLINK (services/cclink): the CC-Link cyclic frame - detws_cclink_build / _parse for [station][command][RX/RY bit data][RWr/RWw word data][sum checksum] that a Mitsubishi CC-Link master exchanges with remote stations over RS-485, plus the bit/word process-image accessors (get_bit / set_bit / get_word); host-tested (native_cclink). Remaining: the master poll/refresh state machine + the RS-485 timing, and CC-Link IE Field (Gbit PHY-gated). Fixed BSS station model, no heap.services/profibus FDL telegrams) over the MBP (Manchester Bus Powered, IEC 61158-2) physical layer for hazardous areas. The FDL/DP telegram layer is done; remaining: the PA device profiles (transmitters/valves) + the MBP physical layer (hardware-gated - couples to a DP segment via a segment coupler in practice).DETWS_ENABLE_CANOPEN (services/canopen): a zero-heap CiA 301 message codec over the shared CAN frame (shared_primitives/can.h) - NMT, SYNC, heartbeat, EMCY, PDO, and expedited SDO read/write/abort plus the COB-ID classifier; host-tested (native_canopen, 17 cases). Remaining: a first-class object dictionary + node-guarding, the DS401 generic-I/O device profile, and the ESP32 TWAI/CAN transport binding. Fixed BSS, no heap.DETWS_ENABLE_IOLINK (services/iolink): the SDCI data-link message codec - the MC / CKT / CKS control octets and the SDCI checksum (seed 0x52 + the 8->6 compression of IO-Link spec A.1.6), against a hand-computed known-answer vector; host-tested (native_iolink). Remaining: the on-request ISDU parameter service, the IODD-described device model + state machine, and the 3-wire physical layer (hardware-gated). Fixed BSS, no heap.DETWS_ENABLE_POWERLINK (services/powerlink): the EPL basic frames - detws_epl_build / _parse (and the SoC / PReq / PRes convenience builders) for [messageType][dest][source][payload], the four cyclic message types (SoC start-of-cycle, PReq poll-request, PRes poll-response with process data, SoA start-of-async) that make the isochronous managed-node cycle, over raw L2 (ethertype 0x88AB, on the shipped services/rawl2); host-tested (native_powerlink). Remaining: the MN slot-schedule state machine + async SDO + object dictionary, and the isochronous timing (the preempting-task model). Fixed BSS, no heap.DETWS_ENABLE_SERCOS (services/sercos): the cyclic MDT/AT telegram codec - detws_sercos_build / _parse for [type][phase][cycle][cyclic data] (the master's setpoint telegram + the drive's actual-value telegram) over raw L2 (ethertype 0x88CD, on the shipped services/rawl2) - and the 16-bit IDN encode/decode (S/P bit + parameter-set + data-block, the "S-0-0100" drive-parameter addressing); host-tested (native_sercos). Remaining: the hard-real-time slot schedule + the IDN service-channel transfer state machine, and the isochronous timing (preempting-task model). Fixed BSS, no heap.DETWS_ENABLE_DEVICENET (services/devicenet): the CAN link adaptation for CIP-over-CAN - the 4-group 11-bit CAN id, the explicit-message header octet, single-frame explicit messages, and the fragmentation reassembler (the CIP body itself is built with the shared cip codec); host-tested (native_devicenet). Remaining: the predefined master/slave connection set, I/O (implicit) messaging, and the ESP32 TWAI/CAN transport. Fixed BSS, no heap; pairs with the EtherNet/IP CIP work.DETWS_ENABLE_LONWORKS (services/lonworks): the LonTalk network-variable PDU - detws_lon_build_nv / _parse_nv for [msg-code][14-bit selector][value] (an NV update / poll), over LON/IP (IEC 14908-4, over UDP, so no Neuron/transceiver is needed - the host-reachable path) - plus the common SNVT scalar encodings (SNVT_temp 0.01 K fixed-point, SNVT_switch level + state); host-tested (native_lonworks). Remaining: the fuller SNVT type table + the NV binding/address table. Fixed BSS NV table, no heap.DETWS_ENABLE_MBPLUS (services/mbplus): the Modbus Plus HDLC frame codec - detws_mbplus_build / _parse for [7E][addr][ctrl][payload][CRC-16/X-25][7E] and detws_mbplus_next_token (the next station in the logical ring) - reusing the shipped Modbus PDU model for the payload; host-tested (native_mbplus, CRC check value 0x906E). Remaining: the full path/routing model + the token-rotation timing, and the custom 1 Mbit/s bus (hardware-gated).DETWS_ENABLE_INTERBUS (services/interbus): the summation frame - detws_interbus_build / _parse assemble the single rotating frame (loopback word + each device's 16-bit process-image slice + CRC-16/CCITT FCS) from a list of per-device word slices and disassemble a received frame back into them; host-tested (native_interbus, FCS check value 0x29B1). Remaining: the PCP parameter channel + the physical ring shift-register clocking (hardware-gated). Fixed BSS, no heap.DETWS_ENABLE_AMQP (services\amqp): amqp_protocol_header (the "AMQP" 0 0 9 1 preamble), amqp_build_frame / amqp_parse_frame (type + channel + size + payload + the 0xCE frame-end), amqp_build_method / amqp_parse_method (a METHOD frame's class-id / method-id / arguments), and amqp_build_heartbeat; host-tested. Remaining: the 0-9-1 method-argument field encoding (the connection/channel/exchange/queue/basic classes) for RabbitMQ interop, and the AMQP 1.0 type system + framing (open / begin / attach / transfer / disposition) for broker links. Zero-heap: fixed link/session state, the payload streamed through the client transport, one build flag. Pairs with the MQTT / webhook outbound integrations.DETWS_ENABLE_DF1 (services\df1): a zero-heap framing + DLE byte-stuffing + BCC/CRC codec for the serial link layer (pub. 1770-6.5.16) - df1_build_frame wraps application data in DLE STX ... DLE ETX (doubled-DLE escape) with a BCC (2's complement of the data sum) or CRC-16/ARC (over the data + ETX, low byte first), and df1_parse_frame validates and un-stuffs; vectors verified against the manual (BCC 0x20->0xE0, CRC 0xBB3D). Remaining: the PCCC command set (PLC-5 / SLC-500 data-table read/write) inside the application data, the half-duplex master/slave framing, and DH+ (Data Highway Plus token LAN, physical-layer-gated). Fixed BSS, no heap.DETWS_ENABLE_COTP (services\cotp): tpkt_build / tpkt_parse, cotp_build_dt, cotp_build_cr, cotp_parse. S7comm itself is shipped - DETWS_ENABLE_S7COMM (services\s7comm): s7_build_setup (Setup Communication), s7_build_read_request (Read Var, S7-ANY items over DB/I/Q/M with the byte-to-bit address encoding), s7_parse_header, and s7_read_next_item (the response data items, honoring the length-in-bits transport sizes and the even-item padding); constants verified against the Wireshark dissector, host-tested. Remaining: S7comm write (function 0x05) + the userdata services, and S7comm-Plus (the S7-1200/1500 successor with its session/integrity wrapping). Fixed BSS data model, no heap.DETWS_ENABLE_MELSEC (services\melsec): melsec_build_read emits the binary 3E batch-read (word) frame (little-endian fields, subheader 0x5000, command 0x0401, the device code + 24-bit head device + point count) and melsec_parse_response validates the 0xD000 response and reports the end code + data; layout + device codes verified against a third-party MC impl, host-tested. Completes the major-vendor PLC read set (Omron FINS / Host Link, AB DF1, Siemens S7comm). Remaining: MC batch write + the 1E/4E + ASCII frame variants, and MELSECNET/H / **/10** (the cyclic control network, PHY/timing-gated). Fixed BSS device model, no heap.DETWS_ENABLE_FINS (services\fins): a zero-heap command/response builder + parser - fins_build_command / fins_build_memory_area_read emit the 10-octet routing header + MRC/SRC command code + parameters, and fins_parse_command / fins_parse_response read them back (the response MRES/SRES end code included), over the shipped UDP transport. Header layout verified against the FINS spec; pure, host-tested. Remaining: the full command set (memory-area write / run-stop / clock) and the FINS/TCP framing + the FINS/Hostlink-gateway addressing model. Fixed BSS device model, no heap.DETWS_ENABLE_HOSTLINK (services\hostlink): a zero-heap ASCII frame builder + FCS-validating parser for Omron's serial C-mode protocol - hostlink_build emits @UU + header code + text + FCS + *CR (FCS = the 8-bit XOR from @ through the text, verified against the @00RD00000010 -> 57 vector), and hostlink_parse / hostlink_end_code validate and split a frame. Pairs with the FINS work (Host Link is the serial sibling); pure, host-tested. Remaining: the per-command text encoders (RD/WD/... of the DM/CIO areas) and the UART transport. Fixed BSS, no heap.DETWS_ENABLE_SNP (services/snp): the SNP master-slave serial frame - detws_snp_build / _parse for [control][length][data][arithmetic-sum BCC] with the control-byte constants, for register read/write on a GE Fanuc Series 90 (90-30/90-70) PLC over RS-485; host-tested (native_snp). Remaining: the SNP-X session setup + the per-command register-access encoders, and the UART transport. Fixed BSS, no heap.DETWS_ENABLE_DIRECTNET (services/directnet): the DirectNET master-slave serial frames - detws_dnet_header (SOH + ASCII-hex slave/type/address/blocks + ETB + LRC) and detws_dnet_data / _data_parse (STX + data + ETX + LRC) - for V-memory read/write on an AutomationDirect DirectLOGIC PLC, with the longitudinal-XOR LRC and control-byte constants; host-tested (native_directnet). Remaining: the ACK/NAK handshake sequencing + the V-memory address map, and the UART transport. Fixed BSS, no heap.DETWS_ENABLE_IEC60870 (services/iec60870): the tele-control codec - the -104 APCI (I/S/U frames), the ASDU header + 3-octet IOA, and the -101 FT1.2 fixed/variable link frames (sum checksum); host-tested (native_iec60870). Remaining: the fuller ASDU information-object type set (double points, measured values, commands) + the k/w sequence flow-control state machine over the shipped TCP transport. Fixed BSS point database, no heap.DETWS_ENABLE_GOOSE (services/goose) - the raw-L2 multicast event publish for fast trips (the BER-encoded IECGoosePdu wrapped in the 8-octet GOOSE header + Ethernet frame, ethertype 0x88B8, on the shipped raw-L2 codec; host-tested native_goose). MMS: DETWS_ENABLE_MMS (services/mms) - the ACSI client/server core, the BER confirmed-request/response Read PDUs (invokeID + read service + a named Data-Object ObjectName) build + parse, over ISO-on-TCP (TPKT + COTP via the shipped services/cotp) on port 102; host-tested (native_mms). Remaining: GOOSE subscribe/decode + the fast-retransmit timer, the fuller MMS service set (Write, GetNameList, reports) + the SCL-driven object model. Fixed BSS, no heap.DETWS_ENABLE_C37118 (services\c37118): a zero-heap builder + CRC-validating parser for the PMU synchrophasor wire frame (SYNC FRAMESIZE IDCODE SOC FRACSEC DATA CHK, CHK = CRC-CCITT) - c37118_build_frame frames any payload, c37118_build_command handles the fixed Command frame, and c37118_parse_frame validates the CRC and reports the frame type / id / timestamp / payload, with c37118_parse_command. CRC verified against the canonical CRC-CCITT-FALSE check value; pure, host-tested. The fixed phasor configuration / data model (encoding the CFG-2 channel layout and the matching data frame, over TCP/UDP 4712/4713, streamed via the chunked / UDP cast path) remains to be layered on top. Pairs with the telemetry-math service for on-device PMU analytics.DETWS_ENABLE_SEP2 (services/sep2): the core 2030.5 XML resource documents in the urn:ieee:std:2030.5:ns namespace - DeviceCapability (the root function-set links), EndDevice (sFDI/lFDI registration), and DERControl (a DER dispatch/curtailment event: interval + opModFixedW setpoint) - built into a caller buffer over the existing HTTP + TLS stack; host-tested (native_sep2). Remaining: the fuller function-set resource schema (metering, pricing, demand response) + the resource discovery walk + optional EXI, on a fixed BSS model.DETWS_ENABLE_OPENADR (services/openadr): the OpenADR 3.0 REST/JSON objects - detws_openadr_event (a demand-response signal: programID + eventName + interval payload points with start / duration / type / value) and detws_openadr_report (a VEN reading back to the VTN) - built into a caller buffer over the existing HTTP client/server + OAuth2, with JSON escaping and a no-stdlib 3-decimal value formatter; host-tested (native_openadr). Remaining: the full VEN/VTN role state machine + subscriptions, and the OpenADR 2.0 XML/EXI profile. Fixed BSS, no heap.DETWS_ENABLE_SUNSPEC (services\sunspec): a zero-heap codec for the SunSpec Alliance register maps layered on the holding-register model. A model-chain walker (sunspec_check_marker / sunspec_begin / sunspec_next_model - verify the SunS 0x53756E53 marker, then iterate each model's id / length / body to the 0xFFFF end model) + typed point readers (sunspec_u16 / _i16 / _u32 / _i32 / _string) and a map writer (sunspec_write_marker / _model_header / point writers / _write_end_model), so a solar inverter / meter / battery is interoperable. Marker + header format verified against the SunSpec spec; pure, host-tested. Pairs with the shipped Modbus (modbus).DETWS_ENABLE_ICCP (services/iccp): the TASE.2 indication-point Data_Value BER structures - detws_iccp_state_q (StateQ: a discrete state + quality flags) and detws_iccp_real_q (RealQ: a scaled INTEGER value + quality), each with an optional 4-octet TimeStamp - the telemetry a control center transfers as MMS Reads (on the shipped services/mms + services/cotp, ISO-on-TCP 102); host-tested (native_iccp). Remaining: the data-set / transfer-set / bilateral-table object model on top of the MMS ACSI core. Fixed BSS object model, no heap.
Casting the interop net over bench and rack test equipment: a wireless ESP32 becomes a lab-instrument bridge/controller. SCPI is near-universal across modern instruments and is the highest-value first step; the LXI transports (VXI-11 / HiSLIP) and a GPIB gateway extend reach to older and higher-throughput gear.
: hierarchy, parameter formatting), the IEEE 488.2 common commands (*IDN? / *RST / *OPC? / *CLS / *ESR? / *STB?), and response parsers (numeric, boolean, string, arbitrary block #<n><len><data> for waveform captures) + the status byte / event-status register / error queue model. Makes the device a bench-instrument controller or a wireless bridge that fans instrument telemetry into HTTP/MQTT. Trivially host-verifiable against a Python pyvisa / raw-socket reference and any SCPI simulator.create_link / device_write / device_read / device_readstb / destroy_link calls, plus the LXI mDNS/VXI-11 discovery beacon. Still the fallback transport for a large installed base of LAN instruments that predate HiSLIP; also introduces a reusable ONC RPC codec.++-command GPIB-Ethernet gateway control set (++addr, ++read, ++eoi, ++mode), so the device drives a bench of legacy IEEE-488 (GPIB) instruments through a common Prologix-compatible adapter - the bridge into pre-LAN test gear that will never speak SCPI-over-TCP directly.
DETWS_ENABLE_NTCIP (services/ntcip): the NTCIP object definitions on top of the shipped SNMP agent (snmp) - the 1202 (Actuated Signal Controller: maxPhases, phaseMinimumGreen, live phaseStatusGroupGreens) and 1203 (Dynamic Message Sign: dmsMaxMultiStringLength, dmsMessageMultiString) object roots under 1.3.6.1.4.1.1206.4.2, plus detws_ntcip_oid() to build a full object OID (root + instance index) to register with snmp_agent_add_*; host-tested (native_ntcip). Remaining: the fuller 1202/1203 object set + 1211 (Signal Control and Prioritization). No heap.DETWS_ENABLE_UTMC (services/utmc): the UTMC common-database HTTP+XML message set - detws_utmc_request (a UTMCRequest for an object id), detws_utmc_response (a UTMCResponse carrying the object value + a data-quality flag + timestamp), and detws_utmc_parse_request (extract the requested id) - over the existing HTTP server, XML-escaped; host-tested (native_utmc). Remaining: the fuller object model + the DATEX-II profile. Fixed BSS, no heap.DETWS_ENABLE_OCIT (services/ocit): the OCIT-Outstations object message - detws_ocit_build / _parse for [msg-type][object-type][instance][data-type][value] (get / set / report of a field object) with the typed values (bool / byte / u16 / u32 / octets) and typed-value accessors - between central traffic computers and field controllers / detectors; host-tested (native_ocit). Remaining: the fuller object dictionary + the OCIT-O BTPPL transport profile. Fixed BSS device/detector model, no heap.DETWS_ENABLE_J2735 (services/j2735): the ASN.1 UPER (Unaligned Packed Encoding Rules) bit-level primitive codec - constrained INTEGER (offset in ceil(log2(range)) bits), BOOLEAN, raw bit fields, a bit writer/reader - and the three core V2X messages encode + decode on top: BSMcore (the safety kernel: msgCnt / id / secMark / lat / long / elev / speed / heading), SPaT (the MovementState list: per-signal-group phase + min/max end-time countdown), and MAP (the intersection geometry: id + reference point + per-lane id / ingress flag / node XY offsets). Host-tested against hand-computed bit patterns (native_j2735; the 162-bit BSMcore packs to 21 octets). Remaining: the full BSM part II optionals + the 1609.2 security envelope. The DSRC / C-V2X radio is an external module; the message layer is the deliverable.DETWS_ENABLE_WAVE (services/wave): the 1609.3 WSMP (WAVE Short Message Protocol) header - detws_wsmp_build / _parse (version + a P-encoded PSID + length + payload) with the PSID variable-length p-encoding (detws_wave_encode_psid / _decode_psid) - and the 1609.2 secured-message envelope header (detws_wave_1609dot2_wrap: protocolVersion + contentType); these carry the shipped J2735 messages. Host-tested (native_wave). Remaining: the full 1609.2 signature / certificate machinery (the crypto layer) + the WSA service advertisement. The DSRC / C-V2X radio is an external module; no heap.DETWS_ENABLE_NEMA_TS2 (services/nema_ts2): the traffic-cabinet SDLC bus frame - detws_nema_ts2_build / _parse for [address][control][frame-type][data][CRC-16/X-25] linking the controller to the MMU / BIUs / detector racks, with the common frame-type constants; host-tested (native_nema_ts2, CRC check value 0x906E). Remaining: the per-frame-type cabinet object model + the synchronous serial PHY / BIU timing (hardware-gated).DETWS_ENABLE_ATC (services/atc): the field-I/O map serialized as {"inputs":[...],"outputs":[...]} JSON for an ATC engine over HTTP, plus the output setter/getter. Remaining: the Linux ATC engine + video analytics stack (out of scope).
DETWS_ENABLE_LWM2M (services\lwm2m): a zero-heap writer + cursor reader for the OMA TLV (application/vnd.oma.lwm2m+tlv) resource encoding - lwm2m_tlv_write (+ typed _write_int shortest-form / _write_bool / _write_string / _write_float helpers), lwm2m_tlv_read, and lwm2m_tlv_value_int, handling 8-/16-bit ids and inline / 8- / 16- / 24-bit lengths and the Object-Instance / Resource / Multiple-Resource / Resource-Instance kinds; type-byte layout verified against the spec, host-tested. Built on the shipped CoAP service (coap). Remaining: the client interfaces (Bootstrap, Registration, Device Management, Information Reporting / Observe) and the standard object model (Security/0, Server/1, Device/3, Firmware Update/5, ...) on a fixed BSS model. The SenML-CBOR / SenML-JSON content formats are now shipped separately - DETWS_ENABLE_SENML (services\senml): senml_json_build / senml_cbor_build emit a SenML (RFC 8428) pack (base name/time, name, unit, one value, time per record) over the JSON / CBOR writers, integral numbers kept as integers; verified against the RFC example, host-tested. DTLS is gated on the TLS work; scope the NoSec + registration/observe core first. No heap, one flag.
DETWS_ENABLE_STOMP (services\stomp): a zero-heap STOMP 1.2 frame codec - stomp_build_frame() writes a frame (command + escaped key:value headers + blank line + NUL-terminated body) and stomp_parse_frame() is a non-mutating cursor that reports the command, header key/value slices, and body (honoring content-length, tolerating \r\n endings, skipping broker heart-beats), with stomp_header() lookup and stomp_unescape() for header escapes (\r \n \c \\). Drives CONNECT / SEND / SUBSCRIBE / MESSAGE / ACK ... against ActiveMQ / RabbitMQ / Artemis over the shipped outbound client transport (or STOMP-over-WebSocket via the WS client); pure, host-tested. The connection / subscription state is the application's.DETWS_ENABLE_NATS (services\nats): a zero-heap codec for the text-based NATS pub/sub protocol - builders for CONNECT / PUB (with optional reply-to) / SUB (with optional queue group) / UNSUB / PING / PONG, and nats_parse() which decodes an inbound MSG / INFO / PING / PONG / +OK / -ERR (MSG yields subject / sid / reply-to / payload). Line-oriented (CRLF), space-delimited, payload only on PUB/MSG; pure, host-tested. Rides the outbound client transport; the connection / subscription state is the application's.DETWS_ENABLE_SPARKPLUG (services\sparkplug, implies Protobuf): a zero-heap builder for the Eclipse Sparkplug B MQTT payload + topic - spb_build_topic() (spBv1.0/group/type/node[/device]), spb_build_metric() (a Tahu Metric: name / alias / timestamp / datatype + an int / long / float / double / boolean / string value), and spb_build_payload() (timestamp + metrics + seq) over the Protobuf codec. Field numbers + datatype codes verified against the Eclipse Tahu sparkplug_b.proto; pure, host-tested. Publish it with the MQTT client.DETWS_ENABLE_PROTOBUF (services\protobuf): a zero-heap streaming writer (pb_uint64 / pb_sint64 / pb_fixed32 / pb_fixed64 / pb_float / pb_double / pb_bytes / pb_string, embedded messages via a sub-buffer + pb_bytes) and a cursor reader (pb_read_field over varint / ZigZag / I32 / I64 / length-delimited), host-tested against the spec vectors (08 96 01, the "testing" string, ZigZag mapping). gRPC-Web framing also shipped - DETWS_ENABLE_GRPC_WEB (services\grpcweb): the 5-octet [flags][len BE32] message frame (grpcweb_frame_message), the 0x80 trailers frame (grpcweb_frame_trailer, grpc-status / grpc-message), and grpcweb_parse, wrapping the Protobuf codec over the shipped HTTP/1.1 server/client (host-tested). Full gRPC (the same framing but over HTTP/2 with application/grpc) remains gated on the HTTP/2 roadmap item above. Fixed BSS, no heap.DETWS_ENABLE_DDS (services/dds): the RTPS (DDSI-RTPS) message + submessage framing codec - the 20-octet header (magic / version / vendor / guidPrefix) and the typed submessages (INFO_TS, DATA, HEARTBEAT, ACKNACK, ...) with the endianness flag, built by detws_rtps_header / _submessage and walked by detws_rtps_parse; host-tested (native_dds). Remaining: the CDR serialized-payload encoding, SPDP/SEDP discovery, and the reliability/heartbeat reader-writer protocol + QoS subset (all zero-heap BSS); DDS-XRCE (the resource-constrained agent/client profile) is the more MCU-appropriate full target.DETWS_ENABLE_WAMP (services\wamp): a zero-heap codec for the Web Application Messaging Protocol (unified RPC + PubSub over WebSocket, subprotocol wamp.2.json). Builders for HELLO / SUBSCRIBE / UNSUBSCRIBE / PUBLISH / CALL / REGISTER / YIELD / GOODBYE (JSON arrays emitted via the shared JsonWriter; Options/Details default to {}, Arguments / ArgumentsKw passed as JSON literals) and a nesting-aware positional parser (wamp_get_type / wamp_get_uint / wamp_get_uri / wamp_element) that pulls the message type, ids, and URIs out of an inbound WELCOME / SUBSCRIBED / EVENT / RESULT / INVOCATION / ERROR. Message codes verified against the WAMP spec; pure, host-tested. It rides the shipped WebSocket layer; the session / subscription / registration tables are the application's. The caller can still serialize payloads with the MessagePack / CBOR codecs.DETWS_ENABLE_CLOUDEVENTS, services/cloudevents: cloudevents_build_json() emits a structured application/cloudevents+json envelope (required id / source / type + specversion 1.0, optional subject / datacontenttype / data - data either a verbatim JSON value or an escaped string) over the existing JSON writer, and cloudevents_from_headers() reads an inbound binary-mode event's ce-* headers. Host-tested (test_cloudevents, 7 cases). The HTTP body / ce-* header bindings are emitted with the normal send / add_response_header paths; an app reuses the same envelope over MQTT / WebHook. Fixed BSS, no heap, one build flag.DETWS_ENABLE_MQTT_SN (services\mqtt\mqtt_sn): a zero-heap MQTT-SN v1.2 wire codec for the UDP / non-TCP MQTT variant on constrained, lossy links (numeric topic IDs instead of strings, gateway discovery, sleeping-client keep-alive). Builders for CONNECT / REGISTER / PUBLISH / SUBSCRIBE (by name or pre-defined id) / PINGREQ / DISCONNECT / SEARCHGW and a mqttsn_parse_header() (both the 1- and 3-octet Length forms, big-endian fields) + typed parsers for CONNACK / REGACK / PUBACK / SUBACK / PUBLISH / REGISTER, with a mqttsn_make_flags() helper (DUP / QoS / retain / will / clean / TopicIdType). Wire bytes verified against the spec + the Eclipse Paho reference; pure, host-tested. The datagram send (det_udp_sendto), topic-ID registry, and sleep/retransmit state are the application's. Pairs with the existing MQTT client.
DETWS_ENABLE_FLOW_EXPORT (services\flow_export): a zero-heap exporter-side codec for on-device flow accounting. NetFlow v5 is fixed (flow_v5_write_header + flow_v5_write_record, 24- and 48-octet layouts); NetFlow v9 (RFC 3954) and IPFIX (RFC 7011) share a small cursor (FlowWriter) - flow_ipfix_begin / flow_v9_begin, then flow_export_template() (a Template Set/FlowSet), flow_export_data_begin/record/end (matching Data Set), and flow_export_finish() which patches the IPFIX message length or the v9 record count (and pads each v9 FlowSet to a 4-octet boundary). Field offsets verified against RFC 7011 / RFC 3954 / the published v5 layout; pure, host-tested. The flow cache (5-tuple + counters) and the UDP send (det_udp_sendto) are the app's.
DETWS_ENABLE_BACNET (services\bacnet): bvlc_build / bvlc_parse (the BACnet/IP virtual-link envelope - type 0x81, function, length) and npdu_build / npdu_parse (the network layer - version + NPCI control + optional DNET/DADR destination addressing + hop count, slicing the APDU); layout per ASHRAE 135 Annex J / Clause 6, host-tested. Remaining: the APDU application layer (the object model - Device / Analog-Input / Binary-Output / ... objects, properties, ReadProperty / WriteProperty / COV) and the BBMD foreign-device registration; then BACnet/SC (Secure Connect) reuses the shipped WebSocket + static-pool TLS for its BVLC-SC framing + the same APDU/object model. Fixed BSS object database, no heap.DETWS_ENABLE_XMPP (services/xmpp): the RFC 6120 stanza codec - correctly XML-escaped <stream:stream> / <message> / <presence> / <iq> builders and the stanza-name + attribute readers; host-tested against exact-output vectors (native_xmpp). Remaining: the streaming XML parser + the SASL/TLS handshake (TLS reuses the shipped client TLS) and the IoT XEPs (0030 disco, 0060 pub/sub, 0323 sensor data, 0325 control) on a fixed BSS roster/node model.
DetwsLine builder (services/udp_telemetry, DETWS_ENABLE_UDP_TELEMETRY) emits the full measurement,tag=v field=v timestamp form: detws_line_add_tag() (escaped tag set, must precede fields) + detws_line_set_timestamp() on top of the existing int/uint/float fields. Host-tested (test_udp_telemetry: tags+timestamp ordering, special-char escaping, tag-after-field fail-closed). Cast over UDP (detws_udp_telemetry_cast) or POST the same line to InfluxDB /write with the shipped HTTP client. Pairs with the telemetry-math service. One build flag, no heap.DETWS_ENABLE_REDIS (services/redis_resp): a zero-heap resp_encode_command() (array of bulk strings, binary-safe via explicit arg lengths - drives any command incl. SET/GET/HSET/XADD) + a cursor resp_parse() reply decoder (simple / error / integer / bulk / array / nil; incomplete + malformed fail closed). Host-tested (test_redis_resp, 8 cases). Drive it over the shipped outbound client transport. Heavier candidates (MongoDB wire protocol, Postgres frontend/backend protocol) are larger and lower-priority. Fixed BSS, no heap, one flag per backend.
For drone / robotics use - drive ESCs and ingest their telemetry straight from the web server, using the ESP32's hardware timing peripherals (RMT / MCPWM) so the microsecond-precise pulse trains run without locking the CPU. Each behind a build flag; fixed BSS, no heap.
DETWS_ENABLE_DSHOT (services/dshot): the DShot frame codec - detws_dshot_encode / _decode build + validate the 16-bit packet (11-bit throttle / command + telemetry-request + 4-bit nibble-xor CRC), with the special-command constants and the per-rate (150/300/600/1200) bit high-times for an RMT driver. Host-tested against hand-computed vectors (native_dshot). Remaining: the RMT pulse-train transport (the HW timing backend) and the HTTP/WS/dashboard throttle+arming control surface.detws_dshot_encode(..., bidirectional=true) (and validated by _decode). Remaining: decoding the ESC's GCR-encoded return frame (eRPM / temperature / voltage / current) off the same wire, so the server can stream live ESC telemetry (pairs with the telemetry-math + dashboard services).detws_dshot_encode, shipped) as four pulse-position symbols. Remaining: the nibble->pulse-position encoding + the RMT emission (the packet + CRC are done).detws_esc_pwm_ns() (services/dshot) maps a 0..1000 throttle to the pulse high-time for standard PWM (1-2 ms), OneShot125 (125-250 us), OneShot42 (42-84 us), and Multishot (5-25 us); host-tested (native_dshot). Remaining: driving it from the MCPWM peripheral synced to the control loop.
Authenticated time - the secure successor to plain NTP (the current DETWS_ENABLE_NTP client trusts whatever the network hands it, so a MITM can shift the device's clock and break TLS validity windows, JWT/OIDC exp, TOTP, and log timestamps). NTS adds cryptographic authentication on top of NTPv4 with no shared secret. Builds on the existing NTP client + the TLS stack + AEAD primitives; fixed BSS, no heap, behind a build flag.
The plain (unauthenticated) NTP server already ships (
DETWS_ENABLE_NTP_SERVER, RFC 5905 server mode on UDP/123 - see the feature reference; example 58.NtpServer pairs it with a GPS stratum-1 source + upstream-NTP fallback). NTS below hardens the time the device consumes; a future NTS server mode could extend the server the same way.
DETWS_ENABLE_NTS (services/nts): the NTS-KE TLV record codec - detws_nts_ke_request builds the standard client request (Next Protocol NTPv4 + AEAD AES-SIV-CMAC-256 + End-of-Message, all critical) and detws_nts_ke_parse walks a response, surfacing each record (cookies, negotiated AEAD, server, port, error) via a callback; host-tested (native_nts). Remaining: running it over the static-pool mbedTLS client (ALPN ntske/1) + the RFC 5705 exporter key derivation (the label is exposed).services/nts also builds the NTS NTP extension fields (Unique Identifier, NTS Cookie, and the RFC 7822 4-byte-padded framing); host-tested. Remaining: the AEAD_AES_SIV_CMAC_256 (RFC 5297) authenticator protect/verify + the anti-replay unique-id echo + cookie rotation, then feed the validated offset to det_clock.
The final roadmap item - tackle only after everything else on the TODO and the roadmap above is checked off.
The docs today are Doxygen + a custom CSS theme. The goal is to add Sphinx on top and then apply **"squirty"** styling over it for a polished, modern docs site.
docs/sphinx: docs/Doxyfile now emits XML (GENERATE_XML=YES -> docs/sphinx/xml), Breathe renders it into the API reference (the DetWebServer public surface), and MyST pulls the hand-written Markdown guides (ARCHITECTURE.md, the docs/learn primers) into the same site via include stubs (one source of truth). Built + verified on the RPi (doxygen + a ~/.sphinxdocs venv from docs/sphinx/requirements.txt, then sphinx-build), and kept buildable by the Docs (Sphinx) CI workflow (.github/workflows/docs-sphinx.yml, build + artifact). The full per-symbol doxygenindex is not inlined (the Sphinx C++ domain does not parse every macro-heavy declaration); that complete reference stays in the Doxygen HTML.docs/squirty.svg) plus the Retro TTY Green Screen palette (phosphor #2bb35a on a near-black #080c08 CRT, from docs/custom.css). Layered over Furo via docs/sphinx/conf.py (light/dark brand css-variables) + docs/sphinx/_static/squirty.css (the mascot logo, phosphor glow, a faint scanline wash, monospace headings, terminal-styled Breathe signatures), light/dark aware.
Today a fresh build enables far more than a minimal HTTP server needs (
src/ServerConfig.hdefaultsDETWS_ENABLE_WEBSOCKET,DETWS_ENABLE_SSE,DETWS_ENABLE_FILE_SERVING,DETWS_ENABLE_AUTH, ... to1). The default should be a single-purpose HTTP server and everything else strictly opt-in, so the baseline footprint is as small as possible and the cost of each feature is explicit.
src/ServerConfig.h DETWS_ENABLE_* defaults so only the core request/response path is on out of the box; every other service (WebSocket, SSE, auth, file serving, WebDAV, the L6/L7 protocols, the industrial/IoT buses, crypto, storage, ...) defaults off and is turned on per build via its own flag. Bare-bones by default shrinks the baseline flash/RAM and makes each feature a conscious opt-in. Update the examples that rely on a default-on feature to set its flag explicitly, and re-check the configurator + FEATURES grid.tools/ci/example_footprints.py) to emit a "cost of enabling service X alone" delta against the minimal build and land it as a table in docs/FOOTPRINTS.md.
src/dwserver.cppis a ~4,000-line monolith holding the whole DetWebServer implementation (listen/begin lifecycle, routing + dispatch, request handling, the response builders, chunked streaming, static-file + WebDAV serving, auth, and the HTTP/2 + HTTP/3 seams). One giant TU is harder to navigate, debug, and extend than a set of focused units.
src/server/ of single-purpose files (L) - carve the monolith into cohesive translation units (e.g. server/lifecycle for listen/begin/restart, server/routing for route matching + dispatch, server/response for the send/send_template/send_chunked builders, server/file_serving, server/webdav, server/auth, and the server/http2 / server/http3 seams), each with a clear internal header. Pure move-and-split - no behavior change - so the native + dual-core builds and the full test suite stay green throughout; keep the owned-context / no-stdlib / single-owner rules. Makes the core easier to debug, extend, and maintain.
Convention: a service that has both a
.hand a.cpplives in its ownsrc/services/<name>/directory; only header-only services (a.hwith no.cpp) are allowed to sit at thesrc/services/root. Today nine root-level services break this.
.h+.cpp service at the src/services/ root into its own folder (M) _(shipped)_ - cloudevents, mdns_service, ntp_service, ota_service, provisioning_service, redis_resp, stomp, upload_service, and web_terminal currently live as loose src/services/<name>.{h,cpp} pairs. Move each into src/services/<name>/<name>.{h,cpp}, update the #include paths, the build_src_filter globs in platformio.ini, the compile-DB / dep-graph tooling, and any docs references. Header-only services (e.g. clock.h, i2c.h) stay at the root. Pure relocation - all builds + tests stay green.
Hot-path validity/membership checks that loop over a small fixed pool (conn_pool, ws_pool, sse_pool, the SSH per-conn tables) should be O(1) bitflag+mask ops or fully-unrolled branchless compares, not
forscans. The stale-pcb guard indet_tcp_do(pcb_still_bound) is the motivating case: SEND/OUTPUT already reduce to the O(1)k->pcb == conn_pool[k->slot].pcbslot compare; only RAWSEND (TLS BIO, slot unknown) still scans.
uint32_t active-slot mask (bit i = slot i has a live pcb/conn), updated at the single set/clear-pcb sites, so membership and "any active?" become one & (1u << slot) / != 0 test. For pointer->slot lookups that cannot carry the slot (the RAWSEND / TLS BIO path), unroll the fixed CONN_POOL_SLOTS scan into a branchless OR of compares (the compiler already does this at -O2; make it explicit + measured). Benchmark before/after via the JTAG CCOUNT harness on the S3 rig so the win is real, not assumed.uint32 (8 into a uint64) and apply the range comparisons to all lanes in parallel with word-wide branchless masks, running the classification once per word instead of once per char (~4-8x fewer ops), still data-independent. Decode is once-per-request (not a hot byte loop) so the win is modest - do it only with (a) RFC 4648 test vectors both directions, and (b) a timing-invariance check on the S3 rig (CCOUNT must not vary with the input bytes), measured through /bench.sse_format() builds each event:/id:/data: record with three snprintf("%s") calls; the CCOUNT bench measured ~3393 cyc / 14 us for a fully-addressed record on the S3, dominated by the Xtensa vsnprintf path (~7x the mime_type lookup). A branchless memcpy-based framer (fixed field prefixes + strlen/memcpy of each value + the terminators) would cut it by roughly an order of magnitude. Not worth it at SSE's per-event cadence, but a real win for a high-rate broadcast fan-out (many subscribers, high push rate). Gate on the existing test_sse_format byte-exact tests + the /bench figure.
Two more presence radars for the EM / radar presence + motion section above (LD2410 is the shipped reference codec): a framed-UART mmWave module and a bare-GPIO Doppler module - opposite ends of the presence-sensing cost/capability range. Both bridge northbound to the web stack like the other sensor drivers; a device reads them over UART / GPIO and publishes presence.
DETWS_ENABLE_HMMD / services/hmmd: a pure, host-tested little-endian frame codec - the default ON/OFF + distance-gate report parse and a byte-by-byte resyncing stream reassembler (mirror the Ld2410Stream reassembler), plus the command encoders (read firmware version 0x0000, serial number 0x0011, register 0x0002, parameter config 0x0008). Bridge presence / distance northbound exactly like LD2410. Test = captured-frame vectors both directions (native_hmmd); the sensor's GPIO OUT pin shares the debounced presence bridge below.DETWS_ENABLE_RCWL0516 / services/rcwl0516: a tiny debounced edge-detect + hold-time state machine over one GPIO (pure, host-testable by injecting pin levels against a fake clock via detws_millis), surfaced to the web stack as a boolean presence event - the same one-GPIO presence facade the HMMD OUT pin and other bare-output detectors (PIR, HB100) can reuse.FD FC FB FA framed UART protocol the shipped services/ld2410 codec already builds and parses (datasheet). So this is a verification + thin extension of the existing driver, not a new codec: confirm the LD2410B report / ACK frames decode byte-for-byte with ld2410_parse_report + the Ld2410Stream reassembler (add captured-frame vectors to native_ld2410), document it as a supported part, and add the few LD2410B-only config commands (Bluetooth enable / permission, MAC query) to the FD FC FB FA command encoders. The BLE control channel itself is out of scope for the wired driver - the UART report / config path is the shared surface.
The "black channel" functional-safety communication profiles (IEC 61784-3) layer a SIL2/SIL3-capable safety protocol - its own CRC signature, a monitoring / consecutive counter (detects lost, repeated, inserted, or reordered frames), a watchdog timeout, and a per-connection authentication - on top of a standard, non-safe fieldbus, treating the transport as an untrusted pipe (so the underlying network needs no safety certification). The base fieldbuses these ride on are already shipped here as pure codecs (
services/profinet,profibus,enip,cip,iolink), so each safety profile is a black-channel Safety-PDU codec on top and stays a pure, host-testable codec - the device carries and validates the safety frames; the safety PLC / logic solver owns the SIL claim. All follow [[implement-protocols-faithfully]] against the published specs + a real reference peer.
services/safety_scl): a CRC-signature helper, a monitoring-counter state machine, and a watchdog/timeout guard, then have each profile's codec compose them. Pure + host-tested against per-profile vectors.DETWS_ENABLE_PROFISAFE / services/profisafe: the Safety-PDU (F-I/O data + status/control byte + consecutive number + CRC2 signature), the F-Parameters (F_Dest_Add / F_WD_Time ...) with their CRC1, and the host/device watchdog timing. Pure codec; host-tested and interop-checked against a PROFIsafe reference (e.g. a Siemens F-host / codesys-safety).DETWS_ENABLE_CIP_SAFETY / services/cip_safety: the safety I/O message (Base + Extended format: Mode Byte, Actual + Complemented data, Time Stamp, CRC-S1/S3/S5), the Time Coordination exchange, and the safety connection (SafetyOpen / SafetyClose) producer/consumer time expectation. Pure codec; interop vs an ODVA CIP Safety originator.DETWS_ENABLE_FSOE / services/fsoe: the FSoE frame (Command + SafeData + a CRC per data slot + Connection-ID / sequence), the FSoE connection state machine (Reset -> Session -> Connection -> Parameter -> Data), and the watchdog. Gated on an EtherCAT (mailbox) base transport first - EtherCAT is noted but not yet shipped (see the field-device roadmap above); FSoE itself is black-channel so it host-tests standalone against ETG.5100 vectors.DETWS_ENABLE_IOLINK_SAFETY / services/iolink_safety: the OSSDe signal handling plus the safety-PDU over IO-Link process data / ISDU (CRC + counter + watchdog, FS-Master / FS-Device roles). Pure codec; host-tested against the IO-Link Safety test spec vectors.services/enip. DETWS_ENABLE_CIP_SECURITY / services/cip_security; interop vs an ODVA CIP Security stack.DETWS_ENABLE_PROFINET_SECURITY / extend services/profinet: the SecurityControl + integrity-MAC frame wrapping and the certificate-based identity, reusing the library's crypto (HMAC / AES-GCM, X.509). Interop vs a PROFINET Security controller.
Wireless industrial traffic - especially the functional-safety frames above - needs prioritized, coexistence-managed delivery. These items give the device the transport-side QoS and the wireless profiles a plant floor expects.
services/hart command codec (FieldComm Group). Scope the realistic slice: the WirelessHART command set (network-manager / device commands) + the network management data (superframe / link / route reports) as a pure codec over HART; the full TSMP-style TDMA MAC + channel-hopping schedule is radio-firmware territory (like the other 802.15.4 radios) and is called out as HW-gated. DETWS_ENABLE_WIRELESSHART / services/wirelesshart.det_udp / det_conn to lwIP's ip_tos / the IP_TOS socket option, with a policy that tags the functional-safety and CIP / PROFIsafe flows EF by default. Host-testable by asserting the TOS byte on the emitted datagram; DETWS_ENABLE_QOS_DSCP.DETWS_ENABLE_COEXISTENCE / services/coexistence: the reporting data model + the channel-plan policy engine (pure, host-tested); the actual RF scan feeds it from the radio driver.
Real-time and functional-safety traffic cannot tolerate a multi-second reassociation when the device roams between plant-floor access points. The ESP32 Wi-Fi firmware / supplicant already provides the 802.11r/k/v primitives; the library's job is to enable + configure them and add the roaming decision layer that uses them, holding the DSCP-EF safety flows (above) alive across a handover.
DETWS_ENABLE_FAST_ROAM; PSK or WPA2/3-Enterprise FT), expose the handover timing, and keep the safety flows pinned across the transition. Measure the handover on the S3 rig against the 50 ms budget.DETWS_ENABLE_RRM.DETWS_ENABLE_BTM.services/roaming.