|
ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
|
Every optional feature is a compile-time flag (default off unless noted); enable it in platformio.ini build_flags or in protocore_config.h. The feature table in the README links to each entry here, and the description shows on hover. Core HTTP/1.1 parsing, routing, the middleware pipeline, JSON, templating, and chunked responses are always on.
PC_ENABLE_ACCEPT_THROTTLE
Opt-in global accept-rate throttle (connection-flood defense). Default off (zero cost / no behavior change). When set to 1 the accept callback rejects new connections once more than PC_ACCEPT_THROTTLE_MAX have been accepted within a PC_ACCEPT_THROTTLE_WINDOW_MS fixed window (global across all listeners, two static counters - no per-IP table). This bounds connection churn (e.g. reconnect brute-force) on top of the bounded connection pool and the per-connection auth limits. mitigate finer-grained / per-IP attacks at the network layer.
PC_ENABLE_AD9238
SPI configuration-port codec for the AD9238 - and the shared Analog Devices high-speed-ADC SPI register map it belongs to. Default off. The AD9238 (12-bit, 20/40/65 MSPS dual ADC) exposes two interfaces that must not be confused: the sample data path is a parallel CMOS/LVDS bus running far beyond what an MCU can bit-bang, so it is out of scope here (see reverse_engineering/ for the FPGA/CPLD-buffered burst-drain architecture), and the low-speed 3-wire SPI configuration port (SCLK / SDIO / CSB, MSB first) that controls power-down, output data format, output test patterns, and offset trim. services/peripherals/ad9238 is that control-port codec: pc_ad9238_build_* frame the 16-bit instruction word (R/W + 2-bit byte-count + 13-bit address) and the shadow-register "device update" transfer that this whole ADI high-speed-ADC generation shares, per the AD9238 Rev. D datasheet SPI Register Map, with fail-closed input validation (null buffer, undersized cap, zero/oversize byte count, out-of-range address). Pure codec (builds/parses byte sequences); the SPI clocking and the per-register bit-field meanings are the application's / the datasheet's - the same contract as services/instrumentation/scpi and services/gpib. Host-tested (native_ad9238). See src/services/peripherals/ad9238/ad9238.h.
PC_ENABLE_MDNS_ADAPTIVE
Opt-in adaptive mDNS beacon scheduling. Pure scheduling decisions on top of the shipped mDNS service: pc_mdns_beacon_adapt backs the announce interval off toward a ceiling under RF contention and recovers it when the air is quiet, pc_mdns_refresh_interval gives the TTL/2 continuous-refresher cadence, pc_mdns_beacon_due says when an announce is due, and pc_mdns_beacon_presleep_due says whether to announce before a sleep window that would otherwise let the record lapse. Wrap-safe time math, no heap/stdlib. The device binding (pc_mdns_adaptive_begin/_tick/_end, needs PC_ENABLE_MDNS + PC_ENABLE_PROMISC) ties the three shipped pieces together: promiscuous capture pinned to the station's own channel supplies a live frame count, pc_mdns_contention_sample turns that running total into a per-window contention value (host-tested incl. counter- and clock-wrap and uint16 saturation), the beacon scheduler turns that into an interval, and the announce is a TXT re-apply on the running responder - which re-announces on every PCB with no goodbye and no re-probe, a refresh not an evict. The contention signal is a radio-layer callback, not a second socket on UDP 5353: a 5353 co-bind turns the ESP-IDF responder announce-only (it browses but stops resolving), whereas promiscuous capture never touches the responder's sockets. The backoff ceiling is capped at ~7/8 of the TTL regardless of the requested max_interval_ms, because announcing slower than the TTL would let the record lapse - the adaptive range is fundamentally [TTL/2, ~TTL), so a longer TTL buys more room. Host-tested (native_mdns_adaptive, 14 cases). HW-verified on an ESP32-S3 against avahi: the record kept resolving (A + SRV + TXT) while capture ran - the exact case the 5353 approach broke - real ambient frames drove the interval off its base and it capped at the safe ceiling below the TTL, and the refresher re-announced on the adaptive schedule. It owns promiscuous mode, so it cannot run alongside the wifi_sniffer live channel-hop binding. Example MdnsAdaptive. Default off.
PC_ENABLE_ADS
Beckhoff ADS / AMS protocol codec (TwinCAT PC-based control over TCP 48898). Default off. services/fieldbus/ads builds + parses the AMS/TCP + AMS-header frames the TwinCAT router speaks (little-endian throughout, target-before-source AMSNetId+port addressing, a command id + state flags + cbData + invoke id): pc_ads_build_* emit complete request frames for ReadDeviceInfo, Read, Write, ReadWrite, ReadState, WriteControl, and Add/DeleteDeviceNotification, and pc_ads_parse_* decode the matching responses - including the DeviceNotification stamp/sample stream (pc_ads_parse_notification walks every sample via a callback). ReadWrite drives symbol-by-name access (write the name to index group 0xF003 to get a handle, then read/write the value through 0xF005). AMS header field order + command ids + state-flag bits verified against the Beckhoff InfoSys AMS/ADS specification; payload layouts cross-checked with Beckhoff's own open-source ADS library, pyads, and Apache PLC4X. Pure codec, host-tested (native_ads); the caller owns the TCP socket and the AMS route registration on the target router. The most widely used PC-based-control protocol and a machine-tool data source via TwinCAT NC/CNC. See src/services/fieldbus/ads/ads.h.
PC_ENABLE_ADS1115
TI ADS1115 4-channel 16-bit ADC with programmable gain (I2C). Default off. services/peripherals/ads1115 builds the 16-bit config-register word for a single-shot single-ended reading (pc_ads1115_config_single: the OS start bit, the channel multiplexer, the programmable gain, single-shot mode, the data rate, and the disabled comparator - so ch0, ±4.096 V, 128 SPS is 0xC383; out-of-range fields fall back sanely) and converts the signed 16-bit sample to microvolts for the selected gain's full-scale range (pc_ads1115_raw_to_uv: 125 µV/count at ±4.096 V). The config encoder + conversion are pure and host-tested (native_ads1115); only the config write / conversion read touches I2C. A cheap solder-and-test breakout for measuring batteries, potentiometers, and analog sensors with far more resolution than the ESP32 ADC. Example Ads1115 reads a potentiometer. See src/services/peripherals/ads1115/ads1115.h.
PC_ENABLE_AMQP
AMQP 0-9-1 frame codec - the RabbitMQ wire protocol. Default off. services/iot/amqp lets a device be an AMQP client over the outbound client transport: pc_amqp_protocol_header writes the "AMQP" 0 0 9 1 preamble, pc_amqp_build_frame / pc_amqp_parse_frame build and validate a frame (type + channel + 4-octet size + payload + the mandatory 0xCE frame-end), pc_amqp_build_method / pc_amqp_parse_method handle a METHOD frame's class-id / method-id / arguments, pc_amqp_build_content_header frames the content HEADER frame that follows a Basic.Publish (class-id + weight + the 8-octet body size + property flags + an optional property list) so a device can publish a message body, and pc_amqp_build_heartbeat emits a keep-alive. Pure and host-tested; the method-argument field encoding and the connection state are the application's. See src/services/iot/amqp/amqp.h.
PC_ENABLE_ATC
Opt-in ATC (Advanced Traffic Controller) field-I/O interop snapshot. When set, services/machine_tool/atc exposes this device's field-I/O (a fixed table of named input/output points it already gathers via the NTCIP / NEMA-TS2 / gpio services) to an ATC Linux engine over the existing HTTP surface: pc_atc_snapshot_json serializes the FIO map as JSON, and pc_atc_set_output drives an output point from an ATC command. Pure interop codec (ATC is a platform spec, not a wire protocol). Default off.
PC_ENABLE_AUDIT_LOG
Tamper-evident audit log. Default off. services/security/audit_log keeps an append-only, hash-chained security log: each record carries SHA-256(prev_hash || fields), so altering, deleting, or reordering any retained record breaks the chain (pc_audit_verify() detects it). Storage is a fixed RAM ring of PC_AUDIT_LOG_ENTRIES records (no heap); when it wraps, a moving anchor keeps the retained window verifiable. Install a sink (pc_audit_set_sink) to forward every record at creation time to a durable / remote store - SD-card file, syslog or HTTP log service, serial console - preserving the same chain off-device. Pure and host-tested.
PC_ENABLE_AUTH
HTTP Basic Authentication per-route.
PC_ENABLE_AUTH_LOCKOUT
Opt-in per-IP brute-force lockout for HTTP auth (requires AUTH). Default off (zero cost / no behavior change). When set, the auth gate counts consecutive failed authentications per source IPv4 in a fixed BSS table; after PC_AUTH_LOCKOUT_THRESHOLD failures the address is locked out for PC_AUTH_LOCKOUT_BASE_MS, doubling on each further failure up to PC_AUTH_LOCKOUT_MAX_MS. A locked address gets 429 (Retry-After) with no credential check; a successful auth clears it. Bounded memory (no heap); the table evicts idle, then least-recently-used, addresses when full.
PC_ENABLE_BACNET
BACnet/IP BVLC + NPDU codec - the ASHRAE 135 building-automation network framing over UDP (47808). Default off. services/fieldbus/bacnet provides pc_bvlc_build / pc_bvlc_parse for the BACnet/IP virtual-link envelope (type 0x81, function such as Original-Unicast-NPDU 0x0A / Original-Broadcast-NPDU 0x0B, 2-octet length) and pc_npdu_build / pc_npdu_parse for the network layer (version 0x01 + the NPCI control octet + optional DNET/DLEN/DADR destination addressing + hop count), slicing out the APDU. pc_apdu_parse then decodes the APDU header into a BacnetApdu - the PDU type (confirmed / unconfirmed request, simple / complex ACK), the segmentation flags, the invoke id, and the service choice, with the service parameters sliced out (segmented requests skip the sequence-number + window octets) - so an app dispatches on the BACnet service. For the request side, pc_apdu_build_who_is frames a Who-Is unconfirmed-request APDU (service choice 8) - either the 2-octet unbounded form that every device answers, or, with a device-instance search range, the low / high limits appended as minimal-length context-tagged unsigned ints (tags 0 and 1) - so a device discovers others on the internetwork, and pc_apdu_build_i_am frames the matching I-Am answer (service choice 0) carrying the device object identifier, the max APDU length accepted, the segmentation-supported enumeration, and the vendor id as application-tagged values. pc_apdu_build_read_property frames a ReadProperty confirmed-request APDU (service choice 12) - the workhorse a client sends to read one property of one object: the confirmed-request header (the invoke id and the max-segments / max-APDU-length octet), then the object identifier as context tag 0 and the property identifier as context tag 1, with named object-type (BACNET_OBJ_*) and property-identifier (BACNET_PROP_*) constants - so a master reads, say, an analog input's present-value without hand-packing the tags. Layout verified against ASHRAE 135 Annex J / Clause 6 / Clause 15 / Clause 20; pure and host-tested. The remaining service parameters (a ReadProperty-ACK's returned value, and the other confirmed services, encoded as ASN.1 tagged data) layer on top. See src/services/fieldbus/bacnet/bacnet.h.
PC_ENABLE_BLE_GATT
Opt-in Bluetooth ATT protocol codec + GATT characteristic bridge. The wire protocol under GATT for bridging the on-chip BLE radio to the web: services/radio/ble_gatt builds and parses the common ATT PDUs (read / write / notify / error, Bluetooth Core Vol 3 Part F) and serializes a GATT characteristic table as JSON for the web stack (att_read_req / att_write_req / att_notify / att_error_rsp / att_parse / pc_gatt_char_json). The BLE stack owns the radio; this owns the ATT bytes + the northbound JSON. Pure, no heap/stdlib. Default off.
PC_ENABLE_BUS_CAPTURE
Wired field-bus listen-only capture - the wired counterpart to Wi-Fi promiscuous capture. Default off. bus_capture_begin(tx, rx, bitrate, sink) installs the ESP32 CAN (TWAI) controller in listen-only mode - it receives and decodes every frame on the bus but never ACKs or transmits, so it stays invisible to the other nodes - and hands each decoded CanFrame to a sink; bus_capture_poll() drains the RX queue from loop(). Wire the sink into the forwarding plane (PC_ENABLE_FORWARD) to bridge captured CAN frames to another interface - e.g. stream them to a wired collector over Ethernet. can_to_socketcan() (pure) formats a frame as a 16-byte Linux SocketCAN frame (big-endian can_id with the EFF / RTR flags), which with the libpcap DLT_CAN_SOCKETCAN link type is a capture Wireshark reads. The SocketCAN framing is pure and host-tested (native_bus_capture); the TWAI bring-up is ESP32-only and needs a CAN transceiver. Example CanCapture.
PC_ENABLE_C37118
IEEE C37.118.2 synchrophasor frame codec. Default off. services/energy/c37118 is a zero-heap builder + CRC-validating parser for the PMU / PDC wide-area measurement wire protocol: pc_c37118_build_frame emits a SYNC FRAMESIZE IDCODE SOC FRACSEC DATA CHK frame (CHK = CRC-CCITT) for any payload, pc_c37118_build_command handles the fixed Command frame, and pc_c37118_parse_frame validates the CRC and reports the frame type / id / timestamp / payload slice (with pc_c37118_parse_command). pc_c37118_decode_stat decodes the 16-bit STAT word that opens every data frame (IEEE C37.118.2-2011 Table 6) into a C37118Stat - the data-valid and PMU-error flags, the UTC sync / data-sorting / trigger / config-change / data-modified bits, the PMU time quality, the unlocked-time code, and the trigger reason (with named constants) - so a consumer knows whether a measurement is trustworthy and in sync before using it. CRC verified against the canonical CRC-CCITT-FALSE check value; pure and host-tested. The fixed phasor configuration / data model layered on the framing is a future addition. See src/services/energy/c37118/c37118.h.
PC_ENABLE_CANOPEN
CANopen (CiA 301) message codec. Default off. services/fieldbus/canopen is a zero-heap builder + parser for the CANopen messaging set over classic CAN frames (shared_primitives/can.h): NMT node control, SYNC, TIME, the heartbeat / boot-up (NMT error control), EMCY, PDO process data, and expedited SDO read / write / abort. The 11-bit COB-ID is a 4-bit function code plus a 7-bit node id, so pc_canopen_build_* compute it and pc_canopen_parse classifies a received frame back to its function + node; pc_canopen_parse_sdo_response decodes a server's expedited upload value, download acknowledge, or abort code. pc_canopen_build_time / pc_canopen_parse_time carry the CiA 301 §7.2.6 TIME message as a CanopenTime (the TIME_OF_DAY structure: 28-bit milliseconds after midnight plus days since the CANopen epoch of January 1, 1984), masking off the reserved top 4 bits of the millisecond field on both build and parse. Segmented SDO (CiA 301 §7.2.4.3) handles objects larger than the 4-octet expedited limit: pc_canopen_build_sdo_download_init names the object + total size, pc_canopen_build_sdo_download_segment / pc_canopen_build_sdo_upload_segment_req carry / request the 7-octet segments with the alternating toggle bit and last-segment flag, pc_canopen_parse_sdo_segment decodes a segment either direction, and CanopenSdoReasm accumulates an upload's segments into a caller buffer (checking the toggle sequence and buffer bound). The object dictionary itself is the application's; block SDO is not covered. Identifier allocation verified against CiA 301, and the segmented transfer round-trip host-tested; pure. Drive it from the ESP32 TWAI peripheral or an MCP2515 over SPI to bridge a CANopen field bus onto Wi-Fi. See src/services/fieldbus/canopen/canopen.h.
PC_ENABLE_CBOR
Zero-heap CBOR (RFC 8949) encoder for compact binary payloads. Default off. When set, network_drivers/presentation/codec/cbor/cbor.h provides a writer that serializes ints, strings, byte strings, arrays, maps, booleans, null, and float32 into a caller-provided buffer - a compact binary alternative to the JSON writer for telemetry. Pure, no heap, host-tested against the RFC 8949 vectors.
PC_ENABLE_CCLINK
Opt-in CC-Link (CLPA) cyclic fieldbus frame codec. When set, services/fieldbus/cclink builds/validates the CC-Link cyclic frame ([station][command][RX/RY bit data][RWr/RWw word data][sum checksum]) a Mitsubishi CC-Link master exchanges with remote stations over RS-485, plus bit/word process-image accessors. Pure codec (the RS-485 timing + CC-Link IE Field PHY are hardware-gated). Default off.
PC_ENABLE_CC1101
Opt-in CC1101 sub-GHz radio driver. A gateway radio plugin for the TI CC1101 300-928 MHz transceiver over SPI: services/radio/cc1101 drives the chip's SPI header protocol (config registers, command strobes, status registers, TX/RX FIFO) - reset + apply a SmartRF register table + set channel + verify VERSION (pc_cc1101_init), send a variable-length packet (pc_cc1101_send), poll TX-done, enter RX, and read a packet with appended RSSI/LQI (pc_cc1101_recv), plus the RSSI-to-dBm decode. The huge modem config is a caller-supplied register table. Host-tested against a mock; the RF link needs the module. Default off.
Streaming / chunked responses of unbounded length in constant memory via send_chunked(). Always on.
PC_ENABLE_CIA402
CiA 402 / IEC 61800-7-201 drive + motion profile over CANopen. Default off (requires CANOPEN). services/fieldbus/cia402 is the standardized servo / stepper drive profile: pc_cia402_state decodes the power state machine from the Statusword (the CiA 402 mask/value table - Not ready to switch on / Switch on disabled / Ready to switch on / Switched on / Operation enabled / Quick stop active / Fault reaction active / Fault), pc_cia402_controlword and pc_cia402_enable_sequence produce the Controlword commands (Shutdown 0x06, Switch on 0x07, Enable operation 0x0F, Quick stop 0x02, Fault reset 0x80) that walk an axis to Operation Enabled, and the pc_cia402_sdo_set_* / pc_cia402_pack_command / pc_cia402_unpack_status helpers write the Controlword, Modes of Operation (PP / PV / PT / HM / IP / CSP / CSV / CST), and target/actual position-velocity-torque objects (0x6040 / 0x6041 / 0x6060 / 0x607A / 0x60FF / 0x6071 / 0x6064 / ...) through the shipped CANopen SDO / PDO codec. State masks + command values + object indices verified against IEC 61800-7-201 and multiple drive vendors' tables; pure and host-tested (native_cia402). Turns the ESP32 CAN stack (TWAI or MCP2515) into a motion master; close the loop with a services/system/control PID. See src/services/fieldbus/cia402/cia402.h.
PC_ENABLE_CIP
CIP (Common Industrial Protocol) message codec - the message that rides inside an EtherNet/IP Unconnected Data item (PC_ENABLE_ENIP). Default off. services/fieldbus/cip builds the request - pc_cip_build_epath encodes a class/instance/attribute EPATH from logical segments (0x20 | type | format, 8- or 16-bit ids), pc_cip_build_get_attr_single / pc_cip_build_set_attr_single (the read and the write, service 0x0E / 0x10, the write appending the attribute value octets), pc_cip_build_get_attr_all (Get_Attributes_All, service 0x01, a class/instance request with no attribute segment that reads every attribute of an object at once - e.g. the whole Identity object) / pc_cip_build_request prepend the service code + path size - and pc_cip_parse_response splits a reply into service / general status / additional status / data. Service codes and the segment encoding are verified against the Wireshark CIP dissector. Pure and host-tested; wrap the request with pc_eip_build_send_rr_data for a working CIP read / write path. See src/services/fieldbus/cip/cip.h.
PC_ENABLE_CLOUDEVENTS
CloudEvents v1.0 (CNCF) event envelope. Default off. services/iot/cloudevents makes a device's events interoperable with serverless / event-mesh consumers: pc_cloudevents_build_json() emits a structured application/cloudevents+json envelope (the required id / source / type plus optional subject / datacontenttype / data) over the JSON writer, and pc_cloudevents_from_headers() reads an inbound binary-mode event's ce-* headers. Pure and host-tested. See src/services/cloudevents.h.
PC_ENABLE_COAP
CoAP server (RFC 7252) over UDP/5683. A zero-heap Constrained Application Protocol endpoint: a fixed resource table dispatched against the request's Uri-Path, with a pure host-testable message codec (parse/build) and an ESP32 UDP binding via the transport-layer UDP service. Message de-duplication (RFC 7252 sec 4.5) is built in: a retransmitted Confirmable request is re-answered from a small cache keyed on the full (source endpoint, Message-ID) - never a hash, so two peers cannot collide - without re-running its handler, so a client's CON retransmission cannot execute a non-idempotent request (POST/PUT/DELETE) twice (PC_COAP_DEDUP_ENTRIES, default 4, freshness PC_COAP_DEDUP_LIFETIME_MS = EXCHANGE_LIFETIME; set entries 0 to compile it out). Separate (deferred) responses and CON retransmission are non-goals by design: the server answers in-line (a request is served before its handler returns) and sends notifications Non-confirmable, so it never emits a Confirmable message to retransmit. Default off; the codec is otherwise unit-tested standalone (env:native_coap).
PC_ENABLE_COAP_BLOCK
CoAP block-wise transfer - RFC 7959 (requires COAP). Default off. When set, the server understands the Block2 (descriptive, responses) and Block1 (control, request uploads) options: - Block2: a representation larger than one block, or any GET that carries a Block2 option, is served one block at a time. A constrained client requests a small block size (SZX) and pages through with ascending block numbers; the server re-renders the (idempotent) resource and slices out the asked-for block, setting the More bit until the last. - Block1: a POST/PUT payload larger than one block is reassembled into a single BSS buffer. Each non-final block is acknowledged 2.31 Continue; the final block dispatches the handler with the whole reassembled payload. One block-wise transfer is reassembled at a time (deterministic, single buffer); an out-of-order or oversized block yields 4.08 / 4.13. Size1/Size2 options and the /.well-known/core listing are out of scope.
PC_ENABLE_COAP_OBSERVE
CoAP resource observation - RFC 7641 (requires COAP). Default off. When set, a client GET with the Observe option registers as an observer of a resource; the application calls pc_coap_notify(path) to push the resource's current representation to every observer (a CoAP notification from the server port with an increasing Observe sequence). Observers are dropped on a deregister GET, a client RST, or send failure.
PC_ENABLE_CONFIG_IO
Opt-in schema-driven config export / restore. Default off. Requires CONFIG_STORE. The app declares a fixed schema (key + type); services/storage/config_io serializes the current values to a portable key=value text blob (backup / migrate) and parses one back into the store (restore / bulk template). Schema-driven rather than enumerating NVS, so it stays deterministic and zero-heap; the serialize / parse is host-tested.
PC_ENABLE_CONFIG_STORE
Typed NVS configuration store (WiFi creds, IP config,... as blobs). When set, src/services/storage/config_store/config_store.h provides a typed key/value API (string / u32 / blob) that routes core settings into the ESP32's native NVS partition (via Preferences) instead of a JSON file on the filesystem - which survives FS corruption and is the corruption-resistant home for credentials. On host builds it is backed by a fixed in-memory table so the typed contract is unit-testable. Default off.
PC_ENABLE_CONTROL
Closed-loop control law: a zero-heap, FPU-accelerated PID controller. Default off. services/system/control provides a single-precision-float PID (pid_init / pid_update) with the corrections that matter on real hardware - derivative-on-measurement (a setpoint step produces no derivative kick) with an optional single-pole low-pass, output clamping, anti-windup by conditional integration (the integrator freezes while saturated instead of winding up) plus a hard integral clamp, and a feed-forward term - and inline control-law primitives (control_clamp / control_deadband / control_slew / control_lpf). The maths is single-precision end to end so it runs on the ESP32 / ESP32-S3 FPU (madd.s fused multiply-add, never the soft-float double path), and pid_update is defined inline so the whole law folds into the caller. The derivative's /dt is the one divide - ~58 of the ~166 CCOUNT cycles per pid_update on the ESP32-S3, because the LX7 FPU has no divide instruction (a/b becomes a __divsf3 call) - so for a fixed-rate loop pid_set_rate(p, dt) caches 1/dt and pid_update_fixed() runs the hot path with no divide at all (97.4 cyc, ~41% faster; a compile-time-constant dt folds the divide away too). pid_update_n runs a batch of axes off one control tick. Pure maths, host-tested (native_control); see docs/FEATURE_PERFORMANCE.md for the bare-op and PID cycle counts. Pair it with a plant it can command (a services/fieldbus/cia402 drive, a dshot ESC, a heater PWM) and tune the gains offline by replaying the device's run log through tools/pid_tune.py. See src/services/system/control/control.h.
Cross-origin resource sharing with automatic preflight handling. Always on.
PC_ENABLE_COTP
TPKT (RFC 1006) + COTP (ISO 8073 / X.224 class 0) frame codec - the "ISO transport on TCP" foundation under S7comm and IEC 61850 MMS. Default off. services/fieldbus/cotp provides pc_tpkt_build / pc_tpkt_parse for the 4-octet TPKT envelope (version 3 + 16-bit length), pc_cotp_build_dt for a Data TPDU (LI 0xF0 EOT|NR + user data), pc_cotp_build_cr for a Connection Request (destination/source refs, class 0, the TPDU-size parameter, plus caller TSAP parameters), pc_cotp_build_cc for the server-side Connection Confirm that answers a CR (echoing the peer's source reference as the destination reference), and pc_cotp_parse which reports the TPDU type and the DT user data or the CR/CC refs - so the device can accept an incoming COTP connection, not only initiate one. Layout verified against RFC 1006 / ISO 8073; pure and host-tested. See src/services/fieldbus/cotp/cotp.h.
PC_ENABLE_CSRF
Opt-in CSRF protection for state-changing HTTP requests. Default off (zero cost / no behavior change). When set, every POST / PUT / PATCH / DELETE must carry a valid X-CSRF-Token header (a stateless, HMAC-signed token); requests without one get 403 Forbidden. GET / HEAD / OPTIONS are exempt (they are not state-changing). Clients fetch a token from the built-in GET /csrf endpoint, which also sets it as the csrf cookie. No server-side session storage - the token self-validates against an HMAC secret seeded from the hardware RNG at begin(); it is independent of AUTH.
PC_ENABLE_DASHBOARD
Real-time SVG dashboard (DASHBOARD; requires SSE). Default off. Serves a self-contained, hand-rolled SVG dashboard page whose widgets are declared in a fixed compile-time pc_widget table (zero-heap, deterministic). The page fetches the widget layout as JSON and subscribes to an SSE stream of live values; pc_dashboard_set() + pc_dashboard_publish() push the current readings. The widget-table -> JSON serializers are host-testable; WebSocket controls are a follow-up.
PC_ENABLE_DBM
Opt-in log-structured hash key-value store on the write-ahead log (services/dbm; requires WAL). Default off. A Bitcask-style store: each put/delete appends one WAL record (so every write is one of the WAL's fast sequential appends, never a slow durable random write), and an in-RAM open-addressed hash index - a fixed BSS array of PC_DBM_SLOTS slots, no heap - maps each live key to where its value sits in the log. get re-reads the value straight from the log; open rebuilds the index by scanning the WAL and replaying puts and deletes in order, so the live key set is exactly what survived the last mount. Keys are bounded by PC_DBM_KEY_MAX, values by PC_DBM_VAL_MAX; writes are batched and made durable by a checkpoint (pc_dbm_sync). Pure and host-tested over a RAM-backed device (overwrite, tombstone resurrection, persistence across a remount with and without checkpoint, collisions, index-full fail-closed, bounds, max-value round-trip).
PC_ENABLE_DDS
Opt-in DDS / RTPS wire-protocol codec. When set, services/iot/dds provides the RTPS (DDSI-RTPS) message + submessage framing: the 20-octet header (magic / version / vendor / guidPrefix) and the typed submessages (INFO_TS, DATA, HEARTBEAT, ACKNACK, ...) with the endianness flag, built by pc_rtps_header / _submessage and walked by pc_rtps_parse. Pure framing (CDR payloads + SPDP/SEDP discovery layer on top). Default off.
PC_ENABLE_DEVICE_ID
Stable device UUID derived from the chip MAC (RFC 4122 v5). When set, src/services/system/device_id/device_id.h derives a deterministic v5 UUID from a MAC (via the library's SHA-1) - a storage-free, stable identity for mDNS hostnames, MQTT client IDs, etc. The MAC->UUID core is host-testable; pc_device_uuid() reads the ESP32 factory MAC. Default off.
PC_ENABLE_DEVICENET
DeviceNet link-adaptation codec. Default off. services/fieldbus/devicenet is the CAN-specific layer of "CIP over CAN": pc_devicenet_encode_id / pc_devicenet_decode_id pack and unpack the 11-bit DeviceNet identifier as a Message Group (1..4) + Message ID + MAC ID (the four identifier ranges 0x000-0x3FF / 0x400-0x5FF / 0x600-0x7BF / 0x7C0-0x7EF), pc_devicenet_msg_header / pc_devicenet_frag_octet build the explicit-message header and fragmentation octets, pc_devicenet_build_explicit emits a single-frame explicit message, pc_devicenet_build_fragment emits one fragment of a longer message (the sender complement: header + fragmentation octet + up to 6 data octets, split as first / middle / last), and pc_devicenet_frag_feed reassembles a fragmented body (first / middle / last, modulo-64 count) up to PC_DEVICENET_MSG_MAX octets. The CIP application layer (services / EPATH / data) is the same one EtherNet/IP uses, so build the message body with the existing cip codec (PC_ENABLE_CIP). Identifier allocation + fragmentation verified against the ODVA DeviceNet spec; pure and host-tested. Drive it from the ESP32 TWAI peripheral or an MCP2515 over SPI to bridge a DeviceNet segment onto Wi-Fi. See src/services/fieldbus/devicenet/devicenet.h.
PC_ENABLE_DF1
Allen-Bradley DF1 full-duplex frame codec. Default off. services/fieldbus/df1 is a zero-heap framing + DLE byte-stuffing + BCC/CRC codec for the Rockwell serial PLC data-link layer (pub. 1770-6.5.16): pc_df1_build_frame wraps application data in DLE STX ... DLE ETX (a data byte equal to DLE/0x10 is doubled) with either a BCC (the 2's complement of the modulo-256 data sum) or a CRC-16/ARC (poly 0x8005, init 0, over the data plus the ETX, sent low byte first), and pc_df1_parse_frame validates the check and un-stuffs the data. Vectors verified against the manual (BCC 0x20->0xE0, CRC "123456789"->0xBB3D). Pure and host-tested; the PCCC application header lives inside the data. See src/services/fieldbus/df1/df1.h.
PC_ENABLE_DIAG
Expose a diagnostic JSON endpoint via server.diag(). Disabled by default - enabling it exposes compile-time configuration (buffer sizes, feature flags) which could aid an attacker. Only enable in development or behind an authenticated route. When enabled, PC_DIAG_JSON is a compile-time string constant you can serve from any route handler:
PC_ENABLE_DIFFSERV
DiffServ QoS marking (RFC 2474): stamp the 6-bit DSCP into the DS field (the high 6 bits of the IPv4 TOS / IPv6 Traffic-Class byte) of outbound traffic so a QoS-aware network - and the Wi-Fi driver's 802.11e WMM access-category mapping - can prioritize safety / real-time packets over best-effort. Default off (zero cost: the marking code and the DSCP state compile out). network_drivers/transport/diffserv.h gives three levels of control, coarse to fine: pc_set_default_dscp() marks every outbound TCP connection (accepted + client); pc_listen_set_dscp(port, dscp) overrides that for one listener's connections; and pc_conn_set_dscp(slot, dscp) re-tags one live connection with any DSCP - real per-flow QoS, or arbitrarily tagging traffic for network testing. pc_udp_set_dscp() marks outbound UDP datagrams. Convenience code points are defined (PC_DSCP_EF 46 expedited forwarding, PC_DSCP_CS6 48 network control, PC_DSCP_AF41/AF31); any 0-63 value is accepted (0 = best-effort). The DSCP is written to the pcb's TOS field on tcpip_thread as the connection is accepted / connected (or per outbound UDP datagram), so nothing is added to the send hot path; marking applies from the connection's first data segment (the SYN-ACK stays best-effort - it is emitted by lwIP before any app callback, and ESP32 lwIP does not inherit the listen-pcb TOS; HW-tested by stamping the listen pcb and confirming the SYN-ACK is still tos 0x0). Host-tested (native_diffserv) and HW-verified on an ESP32-P4, read straight off the wire with tcpdump: the server default marked the :80 HTTP response Expedited-Forwarding (tos 0xb8); a per-listener override on :8080 marked its responses AF41 (tos 0x88); a per-connection re-tag marked its flow Class-Selector 6 (tos 0xc0); and outbound UDP datagrams were sent marked CS6 (the same pcb->tos path as the verified TCP marking). Example DiffServ. See src/network_drivers/transport/diffserv.h.
PC_ENABLE_DIRECTNET
Opt-in AutomationDirect / Koyo DirectNET serial frame codec. When set, services/fieldbus/directnet builds/validates the DirectNET master-slave serial frames - the header (SOH + slave/type/address/blocks ASCII-hex + ETB + LRC) and the data frame (STX + data + ETX + LRC) - for V-memory read/write on an AutomationDirect DirectLOGIC PLC. Pure codec (the UART transport + ACK/NAK handshake are the device step). Default off.
PC_ENABLE_DMA
Opt-in DMA ingest / egress path (v5 milestone). Default off. services/system/dma moves peripheral bytes (UART / I2C / SPI) between the wire and a static buffer while the CPU is free, then a DMA-complete event carries the result up - the high-throughput field-bus ingest path (RS-485 UART, CAN over SPI, IO-Link). RX is double-buffered (ping-pong): the completed buffer is handed to a callback while the engine fills the other; the canonical callback posts the bytes into the preempting work queue so a high-priority task processes them off the interrupt (see Preempting Work Queue). Zero heap (static PC_DMA_CHANNELS channels x PC_DMA_BUF_SIZE buffers), fail-closed (one TX in flight per channel, feeds past the staging capacity are rejected). PC_DMA_SIMULATE (default on) routes transfers through an in-memory ingress/egress simulator - feed bytes in, capture bytes out, optional TX->RX loopback - so the whole pipeline runs with no physical loopback wire, on the host bench and on-device; a real silicon driver plugs into the pc_dma_hw_* hooks when the flag is off. Host-tested (services/system/dma) and HW-verified on a DevKitV1: 2.2M+ frames ingested with zero integrity errors while an HTTP server was stress-loaded on the same core, no heap growth. See src/services/system/dma/dma.h.
PC_ENABLE_DMX
DMX512 + RDM (ANSI E1.20) lighting codec. Default off. services/peripherals/dmx covers stage / architectural lighting over RS-485: pc_dmx_build and pc_dmx_get_channel assemble and read the positional DMX512 slot packet (a start code, 0x00 for dimmer data, followed by up to 512 channel slots - no checksum or in-frame addressing), and the RDM (Remote Device Management) functions build / parse the addressed management packet that shares the wire: pc_rdm_build / pc_rdm_parse carry 48-bit source / destination UIDs (pc_rdm_uid), a transaction number, a command class (GET / SET / DISCOVERY) + parameter id, optional parameter data, and the 16-bit additive checksum (pc_rdm_checksum), with named command-class / response-type / PID constants. pc_rdm_decode_disc_response decodes the DISC_UNIQUE_BRANCH discovery reply - which is not a normal RDM packet but a 0xFE preamble + 0xAA separator followed by the 6 UID octets and the checksum each sent as two copies OR'd with 0xAA / 0x55 - recovering each octet as the AND of its two copies and verifying the checksum, so the controller learns a responder's UID during device discovery; pc_rdm_build_disc_response frames that same reply from a responder's UID (the encoding complement), so a device on the bus answers a discovery branch. pc_rdm_build_device_info / pc_rdm_parse_device_info pack and decode the DEVICE_INFO (PID 0x0060) parameter block - the 19-octet big-endian descriptor every RDM responder must answer (RDM protocol version, device model, product category, software version, DMX footprint / personality / start address, and sub-device and sensor counts) - so a controller identifies and patches a device without hand-packing the E1.20 Table A-15 fields. RDM packet layout + checksum + discovery encoding + the DEVICE_INFO field order verified against ANSI E1.20 (cross-checked with the Wireshark RDM dissector); pure and host-tested. Drive a MAX485-class transceiver on a UART (250 kbit/s, 8N2; the break is the application's) and bridge a lighting rig onto Wi-Fi. See src/services/peripherals/dmx/dmx.h.
PC_ENABLE_DNC
CNC RS-232 DNC (Distributed Numerical Control) drip-feed codec. Default off. services/machine_tool/dnc is the transport-agnostic framing + tape-code layer that streams a G-code program (RS-274 / ISO 6983) to a machine-tool controller over RS-232 or a socket. It carries the two historical tape codes: ISO 7-bit / ASCII (RS-358; End-of-Block = LF, program marker = %, optional even parity in bit 7) and the EIA RS-244 punched-tape code (a distinct odd-parity 8-track encoding with parity in channel 5, End-of-Block = 0x80, the rewind-stop mapped to EIA End-of-Record 0x0B, uppercase-only). pc_dnc_iso_to_eia / pc_dnc_eia_to_iso translate a character in either direction (fail-closed on a non-representable character); pc_dnc_encode_block frames one G-code line (characters + End-of-Block), pc_dnc_encode_marker writes the % program start/end, and pc_dnc_encode_leader the NUL runout. DncDecoder is a byte-at-a-time reassembler that strips parity / translates, skips runout, reports the % program markers, and delivers each block as an ASCII line (fail-closed, dropping an over-long block whole rather than truncating). DncFlow tracks XON/XOFF (DC1/DC3) software flow control on the reverse channel so the send pump pauses when the controller's buffer fills - kept separate from the forward stream, since the EIA data byte for 3 is 0x13 (DC3). The EIA table is validated by an odd-parity + exact-inverse host guardrail, and full encode -> decode program round-trips pass for both codes. On top of the codec, **dnc_stream** (src/services/machine_tool/dnc/dnc_stream) is the drip-feed engine: it streams a whole program - leader, % start marker, one block per source line, % end marker, trailer - over a send/recv seam, pausing on a reverse-channel XOFF and waiting for XON before the next write. Because it is transport-agnostic (the seam is two function pointers), the same engine drip-feeds over a raw TCP socket ("Ethernet DNC") or a UART; it is host-tested end to end with a scripted mock controller that decodes the streamed program back to lines and exercises the XOFF/XON pause-resume path (native_dnc, 22 cases). Pure and host-tested; you own the UART / socket. See src/services/machine_tool/dnc/dnc.h and dnc_stream.h.
PC_ENABLE_DNP3
DNP3 (IEEE 1815) data-link frame codec. Default off. services/energy/dnp3 is a zero-heap builder + CRC-validating parser for the SCADA / utility outstation data-link layer: pc_dnp3_build_frame emits the 0x0564 LEN CTRL DEST SRC CRC header block plus the CRC'd 16-octet user-data blocks, and pc_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. Addresses are little-endian. On top of the link layer, the transport function (IEEE 1815 §8.2) reassembles application fragments: pc_dnp3_build_transport_segment prepends the 1-octet transport header (FIR / FIN flags + a 6-bit sequence) to up to 249 application octets, and Dnp3TransportRx + pc_dnp3_transport_feed accumulate a fragment across segments, starting on a FIR, checking the sequence increments, finishing on a FIN, and rejecting an out-of-sequence segment or a buffer overflow. On top of that, the application layer (IEEE 1815 §4.2.2) frames the reassembled fragment's header: pc_dnp3_app_control composes the Application Control octet (FIR / FIN / CON / UNS + a 4-bit sequence), pc_dnp3_build_app_request emits an Application Control + function code + object octets, pc_dnp3_build_app_response inserts the two little-endian Internal Indication (IIN) octets a response carries, and pc_dnp3_parse_app_header decodes a fragment header into a Dnp3AppHeader (the control flags + sequence, the function code, and - for a RESPONSE / UNSOLICITED_RESPONSE - the 16-bit IIN, with a pointer to the object octets that follow), with named constants for the function codes and every IIN bit. pc_dnp3_parse_object_header then decodes an object header (IEEE 1815 §4.3) into a Dnp3ObjectHeader - the object group + variation, the qualifier (its index-size / prefix code and range specifier code split out), and the range: the start / stop indexes of a start-stop range (1/2/4-octet forms) or the object count of a count range (1/2/4-octet), the all-objects no-range form, with a pointer to the object data that follows - so a consumer knows which points a request or response references. For the request side, pc_dnp3_build_object_header_range frames a start-stop object header, automatically picking the smallest of the 1/2/4-octet range forms that holds the stop index, and pc_dnp3_build_object_header_all frames the all-objects (qualifier 0x06) header - so a master builds, say, a Class-0 poll or a contiguous point-range read without hand-assembling the qualifier. pc_dnp3_build_crob encodes a Control Relay Output Block (group 12 variation 1) - the 11-octet control object a SELECT / OPERATE carries to command a binary output: the control code (operation type - pulse/latch on/off - the clear bit, and the trip/close code), the count, the on / off time in milliseconds (little-endian), and the status octet - so a master builds a relay control without hand-packing the control-code bits. pc_dnp3_build_aob32 and pc_dnp3_build_aob_float encode the analog counterpart, an Analog Output Block (group 41 variation 1 for a signed 32-bit value, variation 3 for a 32-bit float) - the setpoint object a SELECT / OPERATE carries to command an analog output: the value (little-endian) followed by a status octet - so a master commands an analog point (a valve position, a control loop setpoint) without hand-packing the object. Pure codec, host-tested. See src/services/energy/dnp3/dnp3.h.
PC_ENABLE_DNS_RESOLVER
Opt-in DNS resolver with answer verification. Default off. services/net/dns_resolver resolves a hostname to an IPv4 address (lwIP dns_gethostbyname, marshalled to tcpip_thread like the http_client) and can reject suspicious answers - 0.0.0.0, broadcast, loopback, multicast - which are spoofing / DNS-rebinding indicators for a remote host. The address classifier / verifier is pure and host-tested; the resolve is ESP32-only (blocking, so call it off the request hot path).
PC_ENABLE_DNS_SERVER
Authoritative DNS server on UDP/53. Default off. services/net/dns_server answers A/IN queries from a small fixed table of name -> IPv4 records you register with pc_dns_server_add(), so devices on an offline / air-gapped LAN can use names (printer.lan) instead of raw IPs - a companion to the NTP server for self-hosted infrastructure. It parses the question, looks the name up case-insensitively, and on a hit appends one answer record using DNS name compression (a 2-byte pointer back to the question); an unknown name returns NXDOMAIN and non-A queries return no answer. The response builder (pc_dns_server_build_response) is pure and host-tested against the wire format; the binding is the transport UDP service. Zero heap. This is a general resolver, distinct from the provisioning captive-portal DNS (which points every name at the softAP) - do not enable both. Example DnsServer. See src/services/net/dns_server/dns_server.h.
PC_ENABLE_DOCSTORE
Local JSON document store on the write-ahead log (services/docstore; requires DBM + WAL). Default off. A small NoSQL document store: JSON documents addressed by an id, kept durably on the WAL. It is a thin layer over dbm (the id is the key, the JSON body is the value), so it inherits dbm's zero-heap index, WAL persistence, and crash recovery; what it adds is the document capability - top-level field queries (pc_docstore_find_str / _find_int / _find_bool scan the live documents and match those whose JSON field equals a value, like a small find({field: value})) using the zero-heap JSON reader. put/get/delete forward to dbm; writes are batched and made durable by a checkpoint. Ids are bounded by PC_DBM_KEY_MAX, bodies by PC_DBM_VAL_MAX. Pure and host-tested over a RAM-backed device (put/get/delete, string/int/bool field finds, persistence and query across a remount, and early-stop). See src/services/storage/docstore/docstore.h.
PC_ENABLE_DSHOT
Opt-in DShot ESC throttle protocol codec. When set, services/peripherals/dshot provides pc_dshot_encode() / _decode(): the 16-bit DShot frame (11-bit throttle/command + telemetry bit + 4-bit CRC), the bidirectional/extended inverted-CRC variant, and the per-rate bit timing for an RMT driver. Pure codec (the app clocks it out via RMT). Default off.
PC_ENABLE_DTLS
DTLS 1.3 (RFC 9147) datagram security for UDP transports - the secure datagram counterpart to the TLS 1.3 that already backs HTTP/3, aimed at CoAP-over-DTLS and constrained-device telemetry. Default off. This flag currently gates the DTLS 1.3 record layer (network_drivers/presentation/security/dtls/pc_dtls_record): the DTLSPlaintext classic header (initial flight + alerts) and the DTLSCiphertext unified header with per-record AEAD (AEAD_AES_128_GCM), the RFC 9147 §4.2.3 sequence-number encryption (AES-ECB mask over the ciphertext sample) and §4.2.2 reconstruction, the TLS 1.3 AEAD nonce, and the §4.5.1 anti-replay sliding window. Record and key derivation are reused from the hand-rolled TLS 1.3 stack (pc_quic_hkdf HKDF-Expand-Label, pc_quic_aead AES-128-GCM), so enabling this also compiles those primitives (otherwise gated behind HTTP/3). The record layer is byte-exact against an independent HKDF + AES-128-GCM + AES-ECB reconstruction (host-tested, native_dtls). This flag also gates the handshake framing and reliability layer (network_drivers/presentation/security/dtls/pc_dtls_handshake, RFC 9147 §5 + §7): the 12-byte DTLS handshake header, overlap-tolerant message reassembly (§5.4), the ACK message (content type 26, §7), and the stateless HelloRetryRequest cookie (§5.1) that binds the client address into an HMAC-SHA256 for return-routability; its cookie wire format is byte-exact against an independent Python-stdlib HMAC-SHA256 (host-tested, native_dtls_hs). It also gates the server handshake state machine (network_drivers/presentation/security/dtls/pc_dtls_conn, RFC 9147 §5-6): the one-round-trip full handshake (TLS_AES_128_GCM_SHA256 / X25519 / Ed25519, no PSK / 0-RTT / client auth), transport-neutral like pc_coap_server_process - ClientHello in, then ServerHello (epoch 0) and the encrypted EncryptedExtensions / Certificate / CertificateVerify / Finished flight (epoch 2), the client Finished verified, and epoch 0→2→3 key transitions. It reuses the TLS 1.3 messages + key schedule (pc_tls13_msg, pc_tls13_kdf, guards lifted to HTTP/3 or DTLS). Proven end-to-end (native_dtls_conn) and, most importantly, against a real reference implementation: the wolfSSL DTLS 1.3 client completes a full handshake and an application-data round trip with the server (test/servers/pc_dtls_wolfssl), both offering X25519 directly and through a HelloRetryRequest group renegotiation. DTLS 1.3 derives every secret and record key with the RFC 9147 §5.9 dtls13 HKDF-Expand-Label prefix (not TLS 1.3's tls13), modelled as a Tls13Kdf variant bound once into the key schedule. When a client offers X25519 in supported_groups but sends no X25519 key_share, the server renegotiates the group with a HelloRetryRequest carrying an address-bound, stateless cookie (§5.1) and restarts the transcript with the §4.4.1 message_hash before spending any asymmetric crypto. Lost flights are recovered by a caller-driven retransmission timer (§5.8): pc_dtls_conn_timeout_ms / pc_dtls_conn_on_timeout re-send the whole flight with fresh record sequence numbers on an exponential backoff (capped, with a retransmit ceiling that abandons a dead handshake), inbound ACKs cancel it, and a retransmitted client Finished (whose ACK was lost) is re-acknowledged. The CoAP-over-DTLS front-end (services/iot/coap, when PC_ENABLE_COAP is also set) secures CoAP over UDP end to end: the bridge (coaps/pc_coaps_process) drives the DtlsConn handshake and then unwraps each epoch-3 application record, answers it with pc_coap_server_process(), and re-wraps the response (proven host-side, native_coaps, replay-drop included); and coaps_server binds UDP 5684 (coaps://) to a per-peer DtlsConn pool - routing each datagram to its connection by peer address, driving the handshakes and the retransmission timer from a single pc_coaps_server_poll(), and reaping idle connections (host-tested native_coaps_server: peer routing, poll-driven retransmission, idle reap; example CoapSecure). Pure, zero-heap, not the mbedTLS TCP-TLS engine. See src/network_drivers/presentation/security/dtls/pc_dtls_conn.h.
PC_ENABLE_EDGE_CACHE
Opt-in CDN edge-cache tier (requires HTTP Cache). Default off. services/web/edge_cache is the caching reverse-proxy edge that services/web/httpcache is the origin-side groundwork for: the device sits in front of a remote upstream origin, fetches a response once, and serves subsequent hits from a bounded local store - honoring Cache-Control / Expires / ETag / Last-Modified, revalidating stale entries with conditional requests (If-None-Match / If-Modified-Since -> 304), and serving Range / 206 Partial Content straight from the cache with the existing constant-memory send-pump. A two-tier store: bounded RAM (L1, hot, LRU + TTL) plus an optional dbm/WAL-backed SD tier (L2, persistent across reboot, when DBM is enabled). A miss or a stale-entry revalidation fetches the origin asynchronously - the client request is suspended and resumed from the server poll loop, so the worker never stalls - and every failure path (miss, full cache, origin down, oversize) fails open to the origin. The deterministic cache key is method + host + path (+ optional query), with a SHA-256 digest for the L2 key and Vary handled as a secondary key; explicit purge (single + prefix/wildcard) and stats round it out. Registered as a middleware via pc_edge_cache_enable(server) + pc_edge_cache_map(prefix, origin); zero heap, all buffers fixed. The freshness/validator/key/store logic is pure and host-tested (native_edge_cache); the async origin fetch + serve are HW-verified against a real origin; the L2 SD tier's entry serialization, spill/promote, reboot survival, and purge are host-tested over a RAM WalDev (native_edge_cache_sd) via pc_edge_cache_bind_sd. When PC_ENABLE_RANGE is set, a cached object also answers a single-range Range request with 206 Partial Content + Content-Range (or 416) and advertises Accept-Ranges: bytes, streaming just the window through the same shared parser (server/http_range) the file server uses.
PC_ENABLE_EDGE_ORIGIN_TLS
Opt-in TLS upstream origins for the edge cache (requires Edge Cache + TLS). Default off. When set, a mapped https:// origin is fetched over the shared client-TLS session (pc_tls_csess, the same one MQTTS / wss use) layered over pc_client via mbedTLS BIO callbacks, selected per route by a scheme flag on the route map; without it an https:// origin is rejected. The handshake blocks briefly in the transport's open (like pc_client_open's connect and the MQTT/WS clients), and a pc_tls_client_session_active() guard fails open rather than tearing down a live shared session, so one TLS origin fetch runs at a time. Verification is off by default (encrypt-only, no authentication); pc_edge_cache_set_origin_ca(pem, len) enables full chain + hostname verification and pc_edge_cache_set_origin_pin(sha256) pins the cert (both write the shared client-TLS trust store, so they also apply to MQTTS / wss / the HTTP client). HW-verified on an ESP32-S3 against a real https origin: encrypt-only fetch byte-exact, a correct CA verifies and a wrong CA is rejected (fail-open), Range/206 works over the TLS-fetched body. Note: the TLS engine adds a ~48 KB arena (an S3 / PSRAM board is recommended), and on mbedtls v2 (espressif32 default) an IP-address origin needs a CN-matching cert (IP-address SANs are not matched; DNS origins work normally). Follow-ups: an async handshake so a cold https MISS never blocks the worker, and a multi-session pool for more than one concurrent TLS fetch.
PC_ENABLE_EDGE_MESH
Opt-in mesh (sibling-cache) distribution for the edge cache (requires Edge Cache). Default off. Lets a fleet of edge nodes share one warm cache: on a full local miss a node queries its configured sibling peers (over a plaintext ConnProto::PROTO_MESH TCP link) before hitting the origin, and pulls a fresh copy from whichever peer has it - so the origin is fetched once per fleet, not once per node. Pull (read-through) only: no push, no invalidation protocol, no cross-node consistency window - a stale sibling copy self-expires by its own TTL and the requester re-checks freshness on arrival. The transfer carries the object plus its freshness/age (RFC 9111 §4.2.3 age propagation, reusing the same edge_current_age() math as the single-node serve), so a sibling-fresh object serves for its remaining lifetime with zero origin contact. The wire frame reuses the shared edge_sd entry serializer plus a small timing trailer; the requester ships a bounded snapshot of its request headers so the peer re-runs the exact edge_store_find Vary matcher (a header past PC_MESH_HDRS_MAX degrades to a safe miss, never wrong content). A serving node (pc_edge_cache_mesh_serve, after server.listen(port, ConnProto::PROTO_MESH)) answers only from its LOCAL cache - one hop, never re-querying its own origin or peers, so the fleet cannot loop. The peer query runs as a pre-origin phase of the same async fetch slot (reusing that slot's origin buffer, so no extra per-slot memory) and is pumped from the server poll loop, so it never stalls the worker; a peer MISS or an exhausted peer list transitions the slot to the ordinary origin fetch. Peers are a static list (pc_edge_cache_add_peer(host, port), up to PC_MESH_MAX_PEERS, tried in series, first hit wins). The wire codec + peer-query state machine are pure and host-tested (native_edge_mesh: frame round-trips, age propagation, HIT/MISS/timeout/close), the two-node integration is HW-verified on ESP32-S3 rigs (example 80). Follow-ups: a TLS sibling link, UDP-broadcast peer auto-discovery, and push replication with invalidation.
PC_ENABLE_ENOCEAN
EnOcean ESP3 serial codec (v5 gateway plugin). Default off. services/radio/enocean is the UART telegram codec for EnOcean Serial Protocol 3, the framing every USB / serial EnOcean gateway module (TCM 310 / USB 300) speaks for energy-harvesting 868 MHz switches and sensors. A telegram is 0x55 | data-len(2) | opt-len(1) | packet-type(1) | CRC8H | data | opt | CRC8D. pc_esp3_parse frames one telegram out of a byte stream (returning the length consumed, 0 for "need more", or -1 to drop a byte and resynchronize), verifying both CRC-8s (polynomial 0x07); pc_esp3_build assembles one; pc_esp3_crc8 is the shared checksum. On top of the serial framing, pc_erp1_parse decodes the ERP1 radio telegram carried in a RADIO_ERP1 packet's data field into a pc_erp1 - the RORG (telegram type, named PC_ERP_RORG_RPS / _1BS / _4BS / _VLD etc.), the RORG-specific payload slice, the 4-octet big-endian sender id, and the status octet - so an app dispatches on the switch / sensor that sent it. pc_erp1_build assembles the same telegram (the inverse of the parse: RORG + payload + the 4-octet sender id + status), giving the data field for a RADIO_ERP1 packet, so a device can also transmit a switch / actuator command, not only receive. This is the radio half of an EnOcean-to-web bridge: pull the sender id + payload out of a RADIO_ERP1 telegram and bridge it northbound with pc_gateway_uplink(). Pure - you feed it the UART bytes, the module does the RF - and fully host-tested (CRC-8 known-answer values incl. the 0xF4 check, a build/parse round trip, malformed framing, and resynchronization). Example EnOceanGateway reads a real module over UART. See src/services/radio/enocean/enocean.h.
PC_ENABLE_ESPNOW
ESP-NOW peer messaging. Default off. services/radio/espnow wraps ESP-NOW connectionless peer-to-peer radio messaging in a 3-byte typed envelope (magic + type + length) so a receiver can demux by message type and reject a truncated frame, plus a bounded peer registry (PC_ESPNOW_MAX_PEERS, no heap). The envelope codec + registry are pure and host-tested; the radio path (begin / add_peer / send / broadcast over esp_now, decoded frames to a callback) is ESP32-only and can bridge to WebSocket/SSE. No stdlib.
PC_ENABLE_ETAG
Conditional GET via ETag for served files. When set, serve_file()/serve_static() emit a strong ETag (derived from the file size + last-modified time) and answer a matching If-None-Match with 304 Not Modified, saving bandwidth on repeat fetches of static assets.
PC_ENABLE_ETHERNET
Wired Ethernet bring-up. Default off (the ETH library is not linked). When set, the physical layer gains init_eth_physical() / eth_ready() alongside init_wifi_physical() - a thin wrapper over the Arduino ETH library for an RMII PHY (LAN8720 / TLK110 / RTL8201 / DP83848), so the server runs over a wired uplink (PoE, panel-mount, RF-noisy sites) instead of or alongside Wi-Fi. The PHY pins / address / type / clock come from the standard ETH_PHY_* build flags for your board. Nothing else changes: the egress reporting already classifies a wired default route as PC_IFACE_ETH (pc_net_egress, host-tested classifier), so per-route STA/AP/ETH interface filters and every protocol work over the link the moment it has an IP; Wi-Fi and Ethernet can run dual-homed with the stack picking the default route. ESP32-only bring-up (the classifier is host-tested; the PHY needs the hardware). Example Ethernet. See src/network_drivers/physical/physical.h.
PC_ENABLE_ENIP
EtherNet/IP encapsulation codec - the ODVA EtherNet/IP transport (TCP/UDP 44818) that carries CIP. Default off. services/fieldbus/enip provides pc_eip_build / pc_eip_parse for the 24-octet encapsulation header (little-endian command / length / session handle / status / sender context / options), pc_eip_build_register_session to open a session and pc_eip_build_unregister_session to close it (the session handle in the header, no command data), pc_eip_build_send_rr_data / pc_eip_parse_send_rr_data to wrap + unwrap a CIP message as an unconnected message via the Common Packet Format (a Null Address item plus an Unconnected Data item), and pc_eip_build_list_identity / pc_eip_parse_list_identity for device discovery (the header-only ListIdentity request an originator broadcasts, and a decoder for the List Identity response item that lifts out the device's vendor id / device type / product code / revision / status / serial number / product name / state - the 16-octet CIP socket address, whose fields are network-order unlike the little-endian encapsulation, is skipped rather than reinterpreted). Commands and CPF item types verified against the Wireshark ENIP dissector; pure and host-tested. The CIP object model inside the Unconnected Data item layers on top. See src/services/fieldbus/enip/enip.h.
PC_ENABLE_EXC_DECODER
Opt-in ESP32 panic / exception decoder for a live diagnostics panel, plus core-dump recovery. Default off. services/system/exc_decoder parses a captured Guru Meditation panic dump (the cause, the register PC + EXCVADDR, and the backtrace PC:SP frames) into a structured ExcInfo and serializes it as JSON for a "/exception" panel; the browser or a build server resolves the PCs to file:line against the firmware ELF (addr2line lives off-device). That decoder is pure, no heap/stdlib. Core-dump partition recovery closes the gap that a panic reboots the device and takes the console with it: ESP-IDF also writes a core dump to flash, which survives, so the next boot recovers it - pc_exc_coredump_present reports a stored image (checksum-verified) with its size/address, pc_exc_coredump_summary fills the same ExcInfo so a crash recovered from flash renders through the same panel as a live capture, pc_exc_coredump_save(fs, path) streams the image out of flash to SD/LittleFS in PC_EXC_COREDUMP_CHUNK pieces (no heap, any dump size), and pc_exc_coredump_erase clears the partition once the copy is safe. Offload is transport-agnostic: pc_exc_coredump_read(offset, buf, len) is the seam every consumer pulls through (a range past the image end is refused rather than returning trailing flash), so the same image goes to a filesystem, up an FTP data connection (PC_ENABLE_FTP_SESSION), or into an HTTP body without this owner knowing about any of them - which matters because a dump that only ever reaches the device's own SD card is still lost with the device. The summary is architecture-honest: Xtensa's windowed ABI yields a real on-device backtrace, while RISC-V (C3/C6/H2/P4) carries only a stack dump - so there frame_count is 0 and the trap cause/value are reported rather than inventing frames, which is exactly why the raw offload matters on those parts. The saved file is the raw IDF flash image (24-byte header, then an ET_CORE ELF at offset 24, then a checksum), consumed as esp-coredump ... -t raw. HW-verified on an ESP32-P4 saving to SD: a deliberate null dereference was recovered after reboot as {"cause":"pc_worker","pc":"0x4000004e","excvaddr":"0x00000000","backtrace":[]} - the fault address exactly the null pointer - a 12100-byte image was written to the card, and a following clean boot returned {}, proving the erase. Also HW-verified on an ESP32-S3 (the Xtensa arm, and the FTP transport): the recovered summary carried a real 10-frame backtrace whose PCs addr2line resolves to the true crash path (crash_handler -> dispatch_matched_route -> match_and_execute -> http_poll_slot), the 21540-byte image was streamed off the device to a live FTP server, and Espressif's own esp-coredump info_corefile -t raw then parsed that uploaded file and reported exccause 0x1d (StoreProhibitedCause), excvaddr 0x0, pc <crash_handler(uint8_t, HttpReq*)+33> and crashed task ‘'pc_worker’` - independently confirming both the image format and the task name this decoder reports. Example CoreDump. See src/services/system/exc_decoder/exc_decoder.h.
PC_ENABLE_FAILSAFE
Opt-in software watchdog: deadlock detection + fail-safe safe-state. When set, services/system/failsafe provides a fixed registry of "lifelines" (a task / worker / control loop that must check in within its deadline). pc_failsafe_check() detects one that stopped feeding (a hang / deadlock) and fires a breach callback once per episode so the app can enter a known-safe state. App-defined and per-lifeline, on top of the hardware task watchdog. Pure core, zero heap. Default off.
PC_ENABLE_FOCAS
FANUC FOCAS Ethernet protocol codec (FANUC CNC data over TCP 8193). Default off. services/machine_tool/focas builds + parses the FOCAS wire frames a FANUC control speaks (big-endian throughout, a 10-octet magic/version/type/length envelope + payload): pc_focas_build_open/_close drive the session handshake (FRAME_DST open, empty close), pc_focas_build_request emits the generic command frame (a 6-octet function selector + five signed 32-bit arguments + optional trailing data), and typed wrappers cover SysInfo, alarm status, CNC parameters, macro variables, position/axis data, and the actual feedrate / spindle speed. pc_focas_parse_frame validates the envelope, pc_focas_parse_response decodes the echoed selector + FOCAS return code + data, pc_focas_parse_sysinfo decodes ODBSYS, pc_focas_parse_alarm reads the alarm bitmask, and pc_focas_decode8 / pc_focas_value_f decode the FANUC 8-octet data / base^exp numeric encoding used by positions, feeds, and macros. Frame layout, selector encoding, the SysInfo/alarm response layouts, and the value encoding were reverse-engineered by and cross-checked against diohpix/pyfanuc (the FOCAS wire protocol is not officially published; the proprietary fwlib32 library is not required or used). Pure codec, host-tested (native_focas); the caller owns the TCP socket and drives the open -> command -> close sequence. FANUC is the most widely deployed CNC control, so this is a direct machine-tool data source. See src/services/machine_tool/focas/focas.h.
PC_ENABLE_FANUC_J519
FANUC Stream Motion (option J519) UDP codec - the robot counterpart to FOCAS, which speaks to FANUC CNCs. Default off. Stream Motion is the real-time external motion interface on R-30iB / R-30iA robot controllers: an external controller streams joint (or Cartesian) setpoints over UDP 60015 at the controller's interpolation rate (typically 125 / 250 Hz) and the robot answers every command with its measured state, so a device can close a motion loop around a FANUC arm or simply log it at rate. services/machine_tool/fanuc_j519 is a zero-heap, symmetric codec for that wire protocol. Framing: every packet opens with an 8-octet header (a u32 packet type + a u32 version) and - unlike FOCAS, which is big-endian throughout - every multi-octet field is little-endian, with all real-valued fields IEEE-754 binary32. PC -> robot: pc_j519_build_start / _build_stop emit the 8-octet stream delimiters, pc_j519_build_motion emits the 64-octet Motion Command (sequence number, last-data flag, the read-/write-I/O selectors with their port class, index, mask and value, the data style, and the 9 setpoints), and pc_j519_build_request emits the 16-octet Request for a motion-limit table. robot -> PC: pc_j519_parse_status decodes the 132-octet Robot Status (echoed sequence number, the READY / command-received / SYSRDY / in-motion status bits, the read-back I/O value, the controller timestamp, and three 9-axis binary32 blocks - Cartesian pose, joint pose, and per-axis motor current), and pc_j519_parse_ack decodes the 184-octet Ack (axis number, threshold type, maximum Cartesian speed, and the two 20-entry threshold tables at no load and at maximum load). The mirrored half (pc_j519_build_status / _build_ack, pc_j519_parse_motion / _parse_request) is present too, so the device can stand in as a robot simulator for bench work and every build -> parse round trip is exact. A note on the type word: it is reused across directions - type 0 is Start from the PC but Robot Status from the robot, and type 3 is Request from the PC but Ack from the robot - so direction is encoded in the function name rather than a runtime flag, and each parser accepts only its packet's exact length (8 vs 132, 16 vs 184), which is what actually separates the two. Field offsets, packet sizes, type codes, the I/O-type and threshold-type enumerations, and the status bit assignments were taken from the public Wireshark dissector fanuc-stream-motion/packet-fanuc-stream-motion-j519 - the same class of public reference the FOCAS codec was cross-checked against; no FANUC source, header, or option documentation is used or required. Pure codec, host-tested (native_fanuc_j519, 13 cases - every field offset byte-checked, symmetric round trips, and the length/type guards); the caller owns the UDP socket and the real-time cadence, and HW verification against a real controller is still outstanding. See src/services/machine_tool/fanuc_j519/fanuc_j519.h.
PC_ENABLE_FDC2214
Opt-in FDC2114/2214 capacitance-to-digital field sensor. A field-perturbation sensing peripheral: services/peripherals/fdc2214 decodes the FDC2x14's 28-bit conversion result (a capacitance shift moves the LC-tank frequency, giving contactless proximity / liquid-level / material sensing) - pc_fdc2214_data combines the register pair, pc_fdc2214_error pulls the flags, pc_fdc2214_sensor_freq_hz scales to frequency, and pc_fdc2214_build_config emits a single-channel bring-up; the ESP32 binding replays it and reads the channel over I2C. Pure codec host-tested. Default off.
PC_ENABLE_FILE_SERVING
Static file serving via Arduino FS (LittleFS, SPIFFS, SD).
PC_ENABLE_FINS
Omron FINS frame codec. Default off. services/fieldbus/fins is a zero-heap command/response builder + parser for the Factory Interface Network Service (FINS/UDP): pc_fins_build_command, pc_fins_build_memory_area_read, and pc_fins_build_memory_area_write emit the 10-octet routing header (ICF/RSV/GCT, destination + source net/node/unit, SID) plus the MRC/SRC command code and parameters (the write command appends its data words after the same area / address / count parameters as the read), pc_fins_build_run / pc_fins_build_stop frame the RUN (0401, into MONITOR or RUN mode) and STOP (0402, into PROGRAM mode) operating-mode commands so a device can start / stop the PLC, and pc_fins_parse_command / pc_fins_parse_response read them back (the response MRES/SRES end code included). Talks to an Omron PLC over the shipped UDP transport (pc_udp_sendto). Header layout verified against the FINS spec; pure and host-tested. See src/services/fieldbus/fins/fins.h.
PC_ENABLE_FLOW_EXPORT
Flow-record export codec. Default off. services/net/flow_export is a zero-heap exporter-side codec for on-device flow accounting in three formats: NetFlow v5 (the fixed 24-octet header + 48-octet record, via flow_v5_write_header / flow_v5_write_record), NetFlow v9 (RFC 3954), and IPFIX (RFC 7011). The v9 / IPFIX side is a small cursor (FlowWriter): begin the message (flow_ipfix_begin / flow_v9_begin), emit a Template (flow_export_template), open a matching Data Set and append records (flow_export_data_begin / flow_export_data_record / flow_export_data_end), then flow_export_finish() patches the IPFIX message length or the NetFlow 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 and host-tested. The flow cache (5-tuple + counters) and the UDP send (pc_udp_sendto) are the application's. See src/services/net/flow_export/flow_export.h.
PC_ENABLE_FORWARDED_TRUST
Trusted-reverse-proxy resolution of the forwarded client address (requires AUTH_LOCKOUT). Default off. A Forwarded (RFC 7239) / X-Forwarded-For header is client-spoofable, so behind a reverse proxy the per-IP auth lockout keys on the original client address the proxy reports ONLY when the request's real TCP peer matches a trusted-upstream CIDR you register with pc_forwarded_trust_add_cidr("10.0.0.0/8"); a direct or untrusted peer's header is ignored, and any malformed / obfuscated / unspecified token falls back to the real TCP peer. So one abusive client behind a proxy cannot lock out every client sharing the proxy's address, and no client can spoof its way out of, or another address into, a lockout. The accept-time throttle and the IP allowlist deliberately stay on the real TCP source. The trusted-CIDR table is a fixed BSS array of PC_TRUSTED_PROXY_MAX entries (no heap); the resolver is pure and host-tested (native_forwarded_trust) and HW-verified on an ESP32-S3 (a forwarded client is locked out independently of the TCP source and of other forwarded clients). See src/services/security/forwarded_trust/forwarded_trust.h.
PC_ENABLE_FTP
FTP client wire codec (RFC 959 + RFC 2428). Default off. services/file_transfer/ftp is the pure protocol layer of an FTP client so a device can push/pull files - e.g. drip a .nc program to a CNC controller's FTP program store (Fanuc / Haas / Mazak / Heidenhain all expose one), fetch a config, or archive a log. pc_ftp_build_command frames any control-channel command (VERB or VERB arg + CRLF - USER / PASS / TYPE / CWD / RETR / STOR / LIST / SIZE / DELE / PASV / EPSV / QUIT / ...); pc_ftp_build_port and pc_ftp_build_eprt build the active-mode data-address commands (RFC 2428 EPRT for IPv4 / IPv6). pc_ftp_parse_reply detects a complete reply at the head of the control buffer - single line NNN<SP>text or a multiline NNN-... block ended by the same code followed by a space - and reports the 3-digit code + the bytes consumed (so pipelined replies advance cleanly); pc_ftp_parse_pasv decodes the 227 ...(h1,h2,h3,h4,p1,p2) passive address and pc_ftp_parse_epsv the 229 ...(|||port|) extended-passive port. Reply / PASV / EPSV formats verified against authentic strings captured from a live FTP server; pure and host-tested (the control + data sockets are the application's). See src/services/file_transfer/ftp/ftp.h.
PC_ENABLE_FTP_SESSION (requires PC_ENABLE_FTP)
Opt-in FTP client session driver: the two sockets the codec deliberately does not own. Default off, and gated separately from the codec because it pulls in the outbound TCP client and the DNS resolver, which a build that only wanted the pure protocol layer should not pay for. pc_ftp_store(target, remote_path, total, src, ctx) drives a real control connection through the RFC 959 sequence - greeting, USER/PASS (331 or a straight 230), TYPE I so a binary payload is never CRLF-mangled, a passive data port (EPSV first per RFC 2428 because it carries only a port and so survives NAT, falling back to PASV), STOR, then QUIT - and streams the payload across the data connection it opened. The payload is pulled, not pushed: the caller supplies a pc_ftp_source that fills a chunk at a given offset, so the bytes can come from a file, a log, or the core-dump partition (pc_exc_coredump_read) without this owner knowing about any of them, and nothing ever has to fit in RAM. Success means the server's 226 transfer-complete was seen - a socket that merely accepted bytes is not reported as a stored file - and the data connection is closed before that reply is read, since for STOR the close is end-of-file. Needs PC_CLIENT_CONNS >= 2 (control + data), which a compile-time check enforces. Blocking and synchronous, bounded by PC_FTP_TIMEOUT_MS per step: an offload runs at boot or from a maintenance route, not the request hot path. HW-verified on an ESP32-S3 against a live pyftpdlib server, uploading a recovered 21540-byte core dump: 220 / USER 331 / PASS 230 / TYPE 200 / EPSV 229 / STOR 150 / 226 / QUIT, server-side completed=1 bytes=21540, and the uploaded file then parsed by Espressif's own esp-coredump (see Exception Decoder). Example CoreDump. See src/services/file_transfer/ftp/ftp_session.h.
PC_ENABLE_GOOSE
Opt-in IEC 61850 GOOSE publisher + subscriber codec. When set, services/energy/goose builds the BER-encoded IECGoosePdu (gocbRef / timeAllowedToLive / datSet / goID / t / stNum / sqNum / simulation / confRev / ndsCom / numDatSetEntries / allData) and wraps it in the 8-octet GOOSE header + Ethernet frame (ethertype 0x88B8) for the fast raw-L2 substation-event publish. On the receive side, pc_goose_parse_frame subscribes: it validates the ethertype, walks the BER definite-length TLVs of the IECGoosePdu, and lifts every field into a pc_goose_rx (the numeric stNum / sqNum / timeAllowedToLive / confRev / numDatSetEntries, the booleans, the borrowed gocbRef / datSet / goID strings, the 8-octet UtcTime, and the allData slice), skipping unknown/future tags - so a device reacts to a protection trip, not just publishes one. Pure codec (allData is a caller-encoded BER blob; the raw-L2 transmit / receive is the device step). Default off.
PC_ENABLE_GPIB
GPIB-over-LAN (Prologix-style) controller command codec. Default off. services/instrumentation/gpib is a zero-heap codec for the Prologix-compatible ++ command set that drives a bench of legacy IEEE-488 (GPIB) instruments through a Prologix GPIB-Ethernet / GPIB-USB adapter (raw socket on TCP 1234) - the bridge into pre-LAN test gear that will never speak SCPI-over-TCP directly. It builds the control commands (pc_gpib_command for the generic form, plus typed pc_gpib_addr / pc_gpib_read / pc_gpib_spoll / pc_gpib_eos helpers - the ++eos mapping 0=CR+LF / 1=CR / 2=LF / 3=None), builds an escaped data line (pc_gpib_build_data - a leading ESC before each CR / LF / ESC / + byte in the payload, then an unescaped newline, verified byte-exact against the manual's binary example), classifies a line as command-vs-data (pc_gpib_is_command), and parses the responses (pc_gpib_parse_decimal for the serial-poll status byte / SRQ / address, pc_gpib_parse_addr, pc_gpib_parse_version). Command set + escaping + port verified against the Prologix GPIB-Ethernet / GPIB-USB controller manuals (cross-checked with AR488). Carries SCPI (PC_ENABLE_SCPI) to the instrument as data. Pure codec, host-tested; the socket / serial link to the adapter is the application's. See src/services/instrumentation/gpib/gpib.h.
PC_ENABLE_GPIO_MAP
Opt-in browser GPIO pin-mapper / diagnostics endpoint. Default off. When set, services/system/gpio_map serves a compile-time table of GPIO pins (number, label, direction, live level) as JSON for a browser diag panel, and accepts a control POST (pin, level) to drive an output. The live read / write uses the Arduino digital API on ESP32; the JSON serializer and the control parser are pure and host-testable.
PC_ENABLE_GRAPHQL
GraphQL query subset. Default off. services/iot/graphql parses a GraphQL query into a fixed AST node pool (no heap) and emits a {"data":{...}} response shaped exactly by the requested selection. Schema-free: a field with a sub-selection is an object (the engine recurses), a leaf field calls your single resolver, and arguments collected along the path are handed to it. Supports nested selections, field arguments, and the anonymous / query forms; mutations, subscriptions, fragments, and variables are out of scope. Pure and host-tested; bounds are compile-time (PC_GQL_* in protocore_config.h). Serve it from a POST /graphql route.
PC_ENABLE_GRPC_WEB
gRPC-Web message framing. Default off. services/iot/grpcweb is a zero-heap length-prefixed frame builder + parser for gRPC-Web, the HTTP/1.1-reachable subset of gRPC (gRPC proper needs HTTP/2): pc_grpcweb_frame_message wraps a Protobuf message in the 5-octet [flags][length BE32] prefix, pc_grpcweb_frame_trailer emits the 0x80 trailers frame (grpc-status / grpc-message), and pc_grpcweb_parse reads one frame back (with pc_grpcweb_trailer_status and pc_grpcweb_trailer_message pulling the status code and the human-readable error text out of a trailers frame, so a client sees why a call failed). Wraps the Protobuf codec (PC_ENABLE_PROTOBUF) and rides the shipped HTTP/1.1 server/client. Pure and host-tested. See src/services/iot/grpcweb/grpcweb.h.
PC_ENABLE_GUARDRAILS
Opt-in runtime heap/stack guardrails. Default off. When set, services/security/guardrails samples free heap, the heap low-water mark, the largest free block (fragmentation), and the calling task's remaining stack, and fires a callback when any crosses its threshold - a proactive fail-safe hook beyond the passive numbers in /metrics. The threshold evaluator and the JSON serializer are pure and host-tested; the sample reads esp_* / the FreeRTOS stack high-water on ESP32.
PC_ENABLE_HAAS_MDC
Haas Machine Data Collection (MDC) Q-command codec. Default off. services/machine_tool/haas_mdc is a zero-heap codec for the documented Haas Automation MDC protocol - the ?Q query set a Haas CNC mill / lathe control answers over RS-232 (7-E-1, XON/XOFF) or a raw TCP socket (Setting 143, default port 5051) - so a device becomes a fixed-BSS CNC data collector that fans machine status into HTTP / MQTT (alongside the shipped Fanuc FOCAS, MELSEC, S7comm and DNC machine-tool codecs). As the collector it builds the numbered queries (pc_haas_mdc_build_q for ?Q100 serial / ?Q101 software version / ?Q102 model / ?Q104 mode / ?Q300 power-on time / ?Q500 program+status+parts / ..., taking a HaasQ value or a raw command number) and the macro/system-variable read (pc_haas_mdc_build_var -> ?Q600 <var>); then it parses the reply (pc_haas_mdc_parse locates the CSV payload framed between STX (0x02) and ETB (0x17) - scanning, never by offset - and splits it into trimmed fields), with typed decoders on top: pc_haas_mdc_value (the value of a simple LABEL, value reply such as Q100/Q104), pc_haas_mdc_parse_status (the two-branch Q500 - PROGRAM, Oxxxxx, <status>, PARTS, n when idle/running vs STATUS, BUSY mid-operation), pc_haas_mdc_parse_macro (MACRO, <var>, <value>), and pc_haas_mdc_is_error (the UNKNOWN reply to an unsupported / lowercase command). It also de-multiplexes the unprompted DPRNT(...) lines a running G-code program pushes on the same link (pc_haas_mdc_dprnt_line - raw CRLF text with no STX/ETB, optionally bracketed by DC2 (POPEN) / DC4 (PCLOS)). Request framing, the STX/ETB response wrapper, and the Q100/Q104/Q500/Q600 field layouts are verified against the Haas MDC service manual (Setting 143) + the Q-command table and cross-checked byte-for-byte against a production Haas serial adapter. Pure codec, host-tested (native_haas_mdc, 10 cases); the serial / TCP link to the control is the application's. See src/services/machine_tool/haas_mdc/haas_mdc.h.
PC_ENABLE_HAPPY_EYEBALLS
Opt-in dual-stack Happy Eyeballs destination selection. The client-side IPv6/IPv4 fallback decision on top of the shipped pc_ip: pc_he_pref scores a destination (RFC 6724 scope + family), pc_he_order sorts a candidate list and interleaves the address families (RFC 8305) so successive connection attempts alternate v6/v4, and pc_he_attempt_due gates the next attempt by the Connection Attempt Delay. Fast IPv6 when it works, quick fallback to IPv4 when it does not. Needs PC_ENABLE_IPV6 to matter. No heap/stdlib. Default off.
PC_ENABLE_HW_HEALTH
Opt-in hardware-health diagnostics. Four pure decision cores fed with samples the app reads from the hardware: a power-rail voltage-drop logger (pc_hwhealth_rail_sample tracks worst droop + sag/brownout counts), a SPI-bus CRC audit with hysteretic clock backoff (pc_hwhealth_spi_result halves/doubles the clock on fail/ok streaks), a GPIO short-circuit test (pc_hwhealth_gpio_short: driven vs readback), and a capacitor-leakage diag (pc_hwhealth_cap_leak: measured vs expected RC decay). No heap/stdlib. Default off.
PC_ENABLE_HOTSWAP
Opt-in safeties for removable storage that can vanish mid-write. Default off. An SD card is a connector, and the failure when it leaves is a quiet one: the driver still reports a mounted volume, every write fails into nothing, and code that does not check the return carries on believing it has storage. services/storage/hotswap runs one state machine per volume - ABSENT (nothing mounted, no filesystem call is safe) / READY / FAULTED (was mounted, I/O is failing, unmounted and awaiting a remount probe). pc_hotswap_ready() is the fail-closed gate callers check before any filesystem call, and pc_hotswap_io(ok) reports every outcome; PC_HOTSWAP_FAIL_THRESHOLD (default 3) consecutive failures declare the medium gone and unmount it immediately so nothing can write through a stale handle, while a probe every PC_HOTSWAP_PROBE_MS remounts a card that comes back. The threshold is deliberate: a single failed write is not proof of removal (a transient bus error, a full volume) and tearing down a working mount over one error would be its own bug, so any success resets the run and intermittent noise never accumulates into a false removal. A volume starts ABSENT rather than READY (assuming storage exists because the code compiled is how silent data loss happens at boot), and present-but-unmountable stays ABSENT because a card that will not mount is not storage. The fail counter saturates rather than wrapping, and probe pacing is wrap-safe across a millis() rollover. Core is pure and takes an explicit now, so the whole machine is host-tested with a synthetic clock (native_hotswap, 20 cases); mounting is three app callbacks (mount / unmount / optional card-detect), since how a volume mounts is the application's business. HW-verified on an ESP32-P4 with a real SD card: three good writes left it ready/faults:0, an unmount underneath the app made the next three writes really fail and the third faulted it ({"storage":"faulted","mounts":1,"faults":1}), the following write was refused with storage not ready rather than attempted, and the probe then remounted it by itself ({"storage":"ready","mounts":2,"faults":1}). Serialized as {"storage":..,"mounts":N,"faults":N} for a /health panel. Example HotSwapStorage. See src/services/storage/hotswap/hotswap.h.
PC_ENABLE_HART
Opt-in HART / HART-IP process-instrument protocol codec. When set, services/fieldbus/hart provides the HART command-frame codec (build/parse with the longitudinal XOR checksum, short + long addressing) and the 8-octet HART-IP message header (both directions: pc_hartip_build_header frames a request/response/publish, and pc_hartip_parse_header decodes a received message - version / type / id / status / sequence / byte-count - and exposes its payload slice, e.g. the token-passing PDU, rejecting a truncated message whose declared length exceeds the buffer), so a device speaks HART over UDP/TCP 5094 (front-end-free) or, with a HART FSK modem, over the 4-20 mA loop. Pure, host-tested. Default off.
PC_ENABLE_HISLIP
HiSLIP (High-Speed LAN Instrument Protocol, IVI-6.1) message codec. Default off. services/instrumentation/hislip is a zero-heap codec for the IVI Foundation's modern LXI instrument transport on TCP port 4880 - the successor to VXI-11 that carries SCPI (PC_ENABLE_SCPI) at higher throughput over two TCP channels (a synchronous SCPI command/response stream and an asynchronous out-of-band control channel), correlated by a 16-bit SessionID. pc_hislip_build_header / pc_hislip_parse_header frame the fixed 16-byte header ("HS" prologue + message type + control code + 32-bit MessageParameter + 64-bit PayloadLength, all big-endian); pc_hislip_build_initialize / _initialize_response / _async_initialize / _async_initialize_response (+ the matching parsers) drive the two-channel handshake (the MessageParameter carries (protocol version << 16) | vendor id, then the negotiated version + SessionID); and pc_hislip_build_data frames a Data / DataEND message carrying a SCPI payload keyed by a MessageID (pc_hislip_next_message_id implements the initial-0xFFFFFF00, increment-by-2 rule). The full HislipMsg message-type enum (0-38, including the HiSLIP 2.0 TLS / SASL additions), the header byte layout, and the handshake vectors are verified against IVI-6.1 (cross-checked with the Wireshark dissector, MSL-equipment, and PyHiSLIP). Pure codec, host-tested; the two TCP connections are the application's. See src/services/instrumentation/hislip/hislip.h.
PC_ENABLE_HMMD
Waveshare HMMD 24 GHz mmWave human micro-motion radar (UART). Default off. The HMMD (Waveshare's FMCW micro-motion module, built on the S3KM1110 / SXKMxxx0 radar SoC) is a close relative of the LD2410 and shares its framing exactly - a report frame F4 F3 F2 F1 | len(2) | ... | F8 F7 F6 F5 and a command frame FD FC FB FA | len(2) | word(2) | [value] | 04 03 02 01, with every multi-octet field little-endian. Where the LD2410 splits moving vs stationary energy across 9 range gates, the HMMD reports a single detection flag, one target distance, and the per-gate energy of 16 gates: it is a micro-motion detector, so a still person breathing is the case it exists to catch. A report payload is a fixed 35 octets (detect 1 + distance 2 + 16 gates x 2), making a whole frame 45 - which agrees exactly with the reference library's own kMaxFrameLength, a useful cross-check on the layout. Unlike the LD2410 there is no intra-payload head/tail marker or check byte, so the header, the footer, and the length agreeing with the buffer are the whole of the validation. pc_hmmd_parse_report decodes one frame and HmmdStream reassembles frames byte-by-byte from the UART with resync on dropped bytes or noise (fixed buffer, no heap), mirroring Ld2410Stream including the widen-before-compare guard that stops an absurd length field from wrapping the bounds check. The command encoders cover open / close command mode (words 0x00FF with value 0x0001, and 0x00FE) plus the firmware-version (0x0000), serial-number (0x0011), parameter-config (0x0008) and register (0x0002) reads, over a public pc_hmmd_cmd_build since the module's register and parameter maps are larger than the handful worth naming; the register selector payload is passed through verbatim rather than modelled, because the reference does not specify that map and inventing one would be a guess. pc_hmmd_parse_ack decodes replies, handing back the payload whole rather than splitting off a status word - the reference only establishes that the ACK echoes the request's command octet, which is exactly what pc_hmmd_ack_matches tests. The module's bare GPIO OUT pin carries no protocol at all; feed it to PresenceCore from RCWL-0516 for the same debounced, hold-extended presence, an application-level wiring choice so this service takes no dependency on that one. Framing, payload layout, command words and the open/close encoding come from the public 2Grey/s3km1110 reference library; no vendor SDK is used or required. Pure codec, host-tested (native_hmmd, 11 cases); only the UART read/write touches hardware, and it has not been verified against a physical module. See src/services/peripherals/hmmd/hmmd.h.
PC_ENABLE_HOSTLINK
Omron Host Link (C-mode) frame codec. Default off. services/fieldbus/hostlink is a zero-heap ASCII command/response codec for Omron's serial host-link protocol (the RS-232/485 sibling of FINS): pc_hostlink_build emits @UU + 2-char header code + text + FCS + *CR, and pc_hostlink_parse FCS-validates and splits a frame (pc_hostlink_end_code reads a response's end code). For the most common operations, pc_hostlink_build_read frames an RD (DM-area read) command from a beginning word address + word count (formatting the two 4-digit decimal fields), and pc_hostlink_read_word extracts a word value (4 hex digits after the end code) from the RD response - so an app reads a PLC register without hand-formatting the ASCII; pc_hostlink_build_write frames the complementary WR (DM-area write) command (a 4-digit beginning word address followed by one 4-hex-char value per word), and its response is just an end code (read via pc_hostlink_end_code). The FCS is the 8-bit XOR from @ through the text, rendered as two hex digits (verified against the @00RD00000010 -> 57 example). Pure and host-tested; the UART transport is the application's. See src/services/fieldbus/hostlink/hostlink.h.
PC_ENABLE_HTTP_CACHE
HTTP Cache-Control directive helpers (RFC 9111 + RFC 8246 + RFC 5861). Default off. services/web/httpcache is the origin-side of edge caching so app routes emit correct, edge-cacheable responses (the groundwork for the CDN roadmap; the caching tier itself is a separate piece). cache_control_build serializes a pc_cache_control struct into the canonical directive string (hand it to set_cache_control()), with first-class presets for the common cases - cache_immutable_asset (public, max-age=..., immutable), cache_shared (distinct browser vs shared-cache TTL via s-maxage), cache_revalidatable (stale-while-revalidate), and cache_no_store. cache_control_parse is a tolerant reader (case-insensitive names, bare or quoted delta-seconds, unknown directives ignored) covering the response directives plus no-cache / must-revalidate / proxy-revalidate / must-understand / the extensions and the request directives (only-if-cached / max-stale / min-fresh). cache_freshness_lifetime implements the RFC 9111 sec 4.2.1 first-match precedence (s-maxage for a shared cache, then max-age, then Expires minus Date). Directive set + freshness precedence verified against RFC 9111; pure and host-tested with a build->parse round-trip. See src/services/web/httpcache/httpcache.h.
PC_ENABLE_HTTP_CLIENT
Outbound HTTP(S) client (raw lwIP, optional client-side mbedTLS). Default off. When set, src/services/net/http_client/http_client.h can issue a blocking GET/POST to a remote server: it resolves the host (DNS), opens a raw lwIP TCP connection (https:// goes through client-side mbedTLS over the same static arena as the server TLS), sends the request, and returns the status + body in caller buffers. For webhooks, telemetry push, REST calls from the device. The request builder + response parser are host-testable; the transport is ESP32-only.
PC_ENABLE_HTTP_CLIENT_TLS
HTTPS client support inside the HTTP client (needs TLS).
PC_ENABLE_HTTP_DELIVERY
Opt-in HTTP delivery optimizations that make a slow origin acceptable to a browser. Default off. Stale-while-revalidate (RFC 5861): pc_delivery_swr decides FRESH / serve-stale-and-revalidate / EXPIRED from an age against max-age + stale-while-revalidate, and pc_delivery_cache_control builds the matching header - wired into serving by PC::set_cache_control_swr(max_age_s, swr_s), so every serve_file / serve_static response carries public, max-age=N, stale-while-revalidate=M and the header can never drift from the decision. Service worker: pc_delivery_sw_manifest emits the versioned {"version":..,"precache":[..]} document, and pc_delivery_serve_sw(srv, paths, n, version) registers /sw.js (a flash-resident worker shipped through the web-asset pipeline) plus /precache.json; the worker precaches the shell and then serves it stale-while-revalidate client-side, naming its cache after the version so a bump invalidates the old shell exactly once, and the manifest route answers 500 rather than serving truncated JSON if it would not fit PC_DELIVERY_MANIFEST_BUF (PC_DELIVERY_PRECACHE_MAX paths). Byte ranges are NOT in this service - server/http_range.h (http_parse_byte_range, PC_ENABLE_RANGE) is the single owner of the RFC 7233 range math and is already wired into static file serving and the edge cache, emitting Accept-Ranges, the 206 Content-Range, and a 416 bytes */size; a duplicate parser here was removed rather than given a second call site. Pure cores host-tested (native_http_delivery) and HW-verified on an ESP32-P4 serving from SD: bytes=10-19 / bytes=-5 / bytes=995- each returned byte-exact 206 payloads with the right Content-Range, an out-of-range request returned 416 bytes */1000, and served files carried the SWR header. Example HttpDelivery. See src/services/file_transfer/http_delivery/http_delivery.h.
RFC 7230 request parser - validates method, path, header names and values byte-by-byte before storing anything. Always on.
PC_ENABLE_HTTP2
HTTP/2 (RFC 9113) over the version-agnostic request/response core. Default off. Negotiated by TLS ALPN ("h2", falling back to "http/1.1"); an h2 connection speaks the binary framing + HPACK header compression on top of the same routes and handlers as HTTP/1.1 (the response serializer branches on the connection's protocol). The three layers are pure and host-tested against the RFCs: HPACK (RFC 7541 - static + dynamic table, prefix-integer / string / canonical-Huffman coding; native_hpack vs the Appendix C vectors), the frame layer (RFC 9113 sec 4/6 - the 9-byte header + SETTINGS / WINDOW_UPDATE / RST_STREAM / GOAWAY / PING / HEADERS / DATA; native_h2frame), and the connection/stream engine (preface, SETTINGS exchange, per-stream HEADERS->request + DATA/flow-control, PING/RST/GOAWAY, reassembly; native_h2conn drives a full request/response cycle). A connection's engine is ~28 KB, so it does not fit internal DRAM alongside TLS: HTTP/2 requires PSRAM (set PC_H2_POOL_IN_PSRAM=1 on an S3 / P4 / WROVER; a compile-time guard enforces it). See src/network_drivers/presentation/http/http2/.
PC_ENABLE_HTTP3
HTTP/3 (RFC 9114) over QUIC (RFC 9000) - implemented, host-tested end-to-end. Default off. HTTP/3 runs over QUIC (a reliable transport over UDP) with QPACK (RFC 9204) header compression and its own binary framing. The full stack is in place and exercised by a host end-to-end test: a QUIC client completes the TLS 1.3-in-QUIC handshake, sends a QPACK-encoded HTTP/3 GET, and verifies the 1-RTT response - covering the QUIC variable-length integer (RFC 9000 sec 16), packet protection + framing, the TLS 1.3 handshake, the transport connection engine, and the HTTP/3 + QPACK codecs. On-device (ESP32) HW verification and an example are still pending. Like HTTP/2 this is a PSRAM-class feature.
PC_ENABLE_ICCP
Opt-in ICCP / TASE.2 (IEC 60870-6) inter-control-center telemetry codec. When set, services/energy/iccp builds the TASE.2 Data_Value BER structures - StateQ (a discrete state + quality) and RealQ (a scaled real + quality), each with an optional timestamp - the indication points a control center transfers as MMS Reads (on the shipped services/energy/mms + services/fieldbus/cotp). Pure BER codec. Default off.
PC_ENABLE_IEC60870
IEC 60870-5-101 / -104 telecontrol (SCADA) codec. Default off. services/energy/iec60870 covers the utility-SCADA protocol in both transports: the -104 APCI over TCP (pc_iec104_build_i / _s / _u and pc_iec104_parse for the I / S / U formats - numbered information transfer with 15-bit send/receive sequence numbers, the supervisory acknowledge, and the STARTDT / STOPDT / TESTFR unnumbered commands), the shared ASDU header (pc_iec_asdu_build_header / pc_iec_asdu_parse_header - type id, variable structure qualifier, cause of transmission, common address) with the 3-octet Information Object Address (pc_iec_put_ioa / pc_iec_get_ioa), and the -101 FT1.2 serial link frames (pc_iec101_build_fixed / _variable and pc_iec101_parse, 8-bit sum checksum). Named type-id and cause-of-transmission constants are provided, plus typed information objects for the common type ids: pc_iec_io_build_sp / _parse_sp (single-point M_SP_NA_1 - IOA + SPI + quality), pc_iec_io_build_float / _parse_float (short-float measured value M_ME_NC_1 - IOA + IEEE-754 float + QDS), pc_iec_io_build_scaled / _parse_scaled (scaled measured value M_ME_NB_1 - IOA + a signed 16-bit value + QDS, the integer-range companion to the short float), pc_iec_io_build_normalized / _parse_normalized (normalized measured value M_ME_NA_1 - IOA + a signed 16-bit NVA + QDS, exposed as the fraction NVA/32768 in [-1, +1), the fixed-point companion completing the measured-value trio), pc_iec_io_build_counter / _parse_counter (integrated totals M_IT_NA_1 - IOA + a Binary Counter Reading: a signed 32-bit counter value followed by a sequence-notation octet carrying the 5-bit sequence number and the carry / counter-adjusted / invalid flags, the metering companion for energy and pulse totals), pc_iec_io_build_sc / _parse_sc (single command C_SC_NA_1 - IOA + SCS + select/execute), pc_iec_io_build_dp / _parse_dp (double-point information M_DP_NA_1 - IOA + the 2-bit DPI value + quality), and pc_iec_io_build_dc / _parse_dc (double command C_DC_NA_1 - IOA + the 2-bit DCS state + the 5-bit qualifier + select/execute); the remaining per-type elements are the application's. Frame + APCI + ASDU layout verified against IEC 60870-5-101/-104, and the information objects round-trip host-tested; pure. Run -104 over the shipped TCP stack or -101 over a UART / RS-485 transceiver to bridge an RTU or outstation onto Wi-Fi. See src/services/energy/iec60870/iec60870.h.
PC_ENABLE_IKEV2
IKEv2 (RFC 7296) message + payload codec. Default off. services/security/ikev2 is a zero-heap builder / parser for the Internet Key Exchange v2 wire format that negotiates IPsec security associations over UDP 500 / 4500 (NAT-T) - it is tier 1 (the pure framing) of an IKEv2 / IPsec stack, the standards-track "secure machine bridge over untrusted networks" southbound: the Diffie-Hellman math, the SKEYSEED / SK_* key derivation, the SK AEAD, and the IKE_SA_INIT -> IKE_AUTH state machine are later tiers that reuse the crypto the library already ships. It builds / parses the 28-octet IKE header (pc_ike_hdr_build / _parse - initiator / responder SPIs, next payload, version, exchange type, flags, message id, length, plus pc_ike_set_length to backfill the whole-message length) and walks the generic payload chain (pc_ike_payload_iter_init / pc_ike_payload_next - forward-linked by each payload's next-payload field, with pc_ike_payload_build for a raw payload). Typed builders + parsers cover the IKE_SA_INIT / IKE_AUTH payload set: SA (pc_ike_sa_build writes one proposal with its transforms - ENCR / PRF / INTEG / D-H / ESN - encoding the key-length attribute, and pc_ike_sa_first_proposal + pc_ike_transform_iter_init / pc_ike_transform_next decode the proposal -> transform tree back, reading the key-length attribute), KE (pc_ike_ke_build / _parse - D-H group + key data), Nonce (Ni / Nr), IDi / IDr (pc_ike_id_build / _parse), CERT / CERTREQ (pc_ike_cert_build), AUTH (pc_ike_auth_build / _parse - PSK / RSA / digital-signature methods), N notify (pc_ike_notify_build / _parse - protocol + optional SPI + 16-bit type + data), D delete (pc_ike_delete_build / _parse), TSi / TSr traffic selectors (pc_ike_ts_build / pc_ike_ts_count / pc_ike_ts_get - IPv4 / IPv6 address ranges + port ranges), and the SK encrypted-payload envelope (pc_ike_sk_build / _parse frames IV + ciphertext + ICV so a later tier plugs the AEAD in). Every parser bounds-checks and fails closed on a malformed length. The header, payload, and SA / proposal / transform layouts (including the key-length transform attribute) are verified against RFC 7296 + the IANA registry and cross-checked byte-for-byte against scapy's IKEv2 codec. Pure codec, host-tested (native_ikev2, 16 cases); the UDP transport, the crypto, and the SA state machine are the application's / later tiers. The protocol constants are scoped (enum class) and the types flow through the API - IkePayloadType, IkeExchange, IkeProtocol, IkeIdType, IkeAuthMethod, IkeTransformType, IkeTsType appear on the struct fields and builder / parser signatures, with a cast only where an octet is written to or read from the wire. That is not cosmetic: RFC 7296 numbers the exchange and payload registries into the same range, so IKE_SA_INIT (34) collides with IKE_PL_KE (34), IKE_AUTH (35) with IKE_PL_IDI (35), and so on for every exchange value. Passing the wrong one used to compile silently and emit a structurally valid message a peer would parse and misinterpret; it is now a compile error. See src/services/security/ikev2/ikev2.h.
PC_ENABLE_INA219
TI INA219 high-side current / power monitor (I2C). Default off. services/peripherals/ina219 decodes the bus-voltage register (pc_ina219_bus_mv: value in bits [15:3], LSB 4 mV) and the shunt-voltage register (pc_ina219_shunt_uv: signed, LSB 10 µV), computes the calibration register from the current LSB and shunt resistance (pc_ina219_calibration: 40960000 / (current_lsb_ua * shunt_mohm), so 100 µA + 0.1 Ω -> 4096), and scales the raw current / power registers to microamps / microwatts (pc_ina219_current_ua, pc_ina219_power_uw; the power LSB is 20x the current LSB). All the decode / calibration / scaling math is pure and host-tested (native_ina219); only the register read/write touches I2C. A cheap solder-and-test breakout for measuring how much current and power a circuit draws. Example Ina219 is a live power meter. See src/services/peripherals/ina219/ina219.h.
PC_ENABLE_INTERBUS
Opt-in INTERBUS summation-frame fieldbus codec. When set, services/fieldbus/interbus assembles/disassembles the INTERBUS summation frame (loopback word + per-device 16-bit process-image slices + CRC-16/CCITT FCS) of the Phoenix Contact ring fieldbus, where every device is a shift-register slice of one circulating frame. Pure codec (the physical ring clocking is hardware-gated). Default off.
PC_ENABLE_IFACE_BRIDGE
Opt-in user-defined address:port -> hardware-bus bridge, a configurable "device server". The app registers rules mapping a listen x.x.x.x:nnnn (TCP/UDP) to a UART, an SPI chip-select, or an I2C address (pc_iface_bridge_map(ip, port, proto, target)), so a network client talking to that port is transparently bridged to the bus: raw bidirectional stream passthrough for UART (a ser2net-style serial device server), or framed write-then-read transactions (uint16 write_len || uint16 read_len || write_bytes, big-endian) for the master-initiated SPI / I2C buses, with the bus address / chip-select / clock / mode taken from the rule's target. The fixed-capacity rule table (keyed by port+proto, carrying the full pc_ip bind address, never a flattened one) and the transaction frame codec are a pure, zero-heap, host-tested core (services/net/iface_bridge); the bus I/O (Serial / SPI / Wire) and the PROTO_BRIDGE connection handler are the ESP32 step (iface_bridge_hw), wired up in two calls: server.listen(port, ConnProto::PROTO_BRIDGE) then pc_iface_bridge_publish(listener_id, port, proto, target). UART stream mode is a ser2net-style raw pipe pumped by the server poll loop; SPI/I2C transaction mode peeks a whole frame out of the connection ring, clocks it against the bus (I2C uses a repeated-start read), and returns the read bytes. Default off. See src/services/net/iface_bridge/iface_bridge_hw.h and examples/L7-Application/InterfaceBridge.
PC_ENABLE_FORWARD
Opt-in forwarding plane (v5 milestone). Default off. services/net/forward turns the device into a bridge / router: register interfaces (Wi-Fi STA / AP, Ethernet, a peripheral bus, a radio), each with an egress send callback, then add per-pair rules (pc_forward_add_rule(src, dst, ALLOW/DENY, rate_cap)). A frame arriving on one interface (pc_forward_ingress(), the canonical wiring being a DMA-complete event posted onto the internal FORWARD lane of the preempting queue) is forwarded to every allowed destination by calling that destination's send callback, so the device bridges / routes between its interfaces instead of only terminating traffic. Default-deny (a pair forwards only with an ALLOW rule and no DENY; a DENY always wins), never reflects a frame to its source, and fail-closed (an exceeded rate cap or a send callback returning false drops and is counted via pc_forward_get_stats(), never blocks). Multi-destination fan-out (several ALLOW rules for one source) gives hub behavior; a single ALLOW gives point-to-point routing. An optional ingress ACL filters frames by content before any forwarding rule runs: ordered entries (pc_forward_acl_add()) match on the source interface (or PC_FWD_IF_ANY) and a byte pattern under a mask at an offset, first-match-wins, with a configurable default action (pc_forward_acl_set_default()) - permit-by-default for a denylist, or deny-by-default for an allowlist. Policy routing (route-by-tag) adds path selection on top: pc_forward_route_add() matches a frame by the same byte-pattern primitive (so it keys on any field at a known offset - EtherType, IP protocol, a port, an address prefix) and binds the match to a single egress interface, taking precedence over the src->dst fan-out (first matching route wins), so tagged traffic leaves a chosen NIC / radio; the same rate-cap / never-reflect / fail-closed guarantees apply, and policy_routed is counted. An optional inspection hook (build-time PC_FWD_INSPECT, off by default for cost + privacy; runtime pc_forward_set_inspector()) runs a flexible app callback on every ingress frame after the ACL and before routing - to parse / observe / meter and optionally drop it (counted as inspect_dropped) - complementing the fast fixed-offset ACL. Static tables (zero heap): PC_FWD_MAX_IFACES interfaces, PC_FWD_MAX_RULES rules, PC_FWD_MAX_ACL ACL entries, PC_FWD_MAX_ROUTES policy routes. Host-tested (services/net/forward) + HW-verified on a DevKitV1: 800k+ frames ingested over DMA, ACL-filtered, and forwarded through the plane with exact accounting (forwarded + acl_denied == frames_in), zero loss, and zero integrity errors while an HTTP server was stress-loaded on the same core. This is the generic data path the post-v5 wireless gateway bridges sit on. See src/services/net/forward/forward.h.
PC_ENABLE_IOLINK
IO-Link (SDCI, IEC 61131-9) data-link message codec. Default off. services/fieldbus/iolink implements the point-to-point smart-sensor link's data-link message layer: pc_iol_mc (with decoders) builds the M-sequence Control octet (read/write, communication channel, address), pc_iol_ckt builds a master message's checksum / M-sequence-type octet, pc_iol_cks builds a device reply's checksum / status octet (Event + PD-valid flags), and pc_iol_checksum6 / pc_iol_finalize / pc_iol_verify implement the SDCI message checksum straight from the IO-Link Interface and System Specification v1.1.4 Annex A.1.6 (a 0x52 seed XORed octet by octet with the check octet's checksum bits zeroed, then the 8-to-6-bit compression of equation A.1). The checksum is verified against a vector hand-derived from the spec formula. Lay the per-type M-sequence and ISDU octets out per your device profile, then finalize / verify with this codec. Pure and host-tested. The wire is a UART through an IO-Link transceiver (MAX14819 / L6360 class); bridge sensor data onto Wi-Fi. See src/services/fieldbus/iolink/iolink.h.
PC_ENABLE_IP_ALLOWLIST
Opt-in source-IP allowlist (accept-time firewall, keyed by source IPv4). Default off (zero cost / no behavior change). When set, the accept callback drops any connection whose source address does not match a configured CIDR rule (see listener_ip_allow_add()). An empty allowlist allows everything, so enabling the feature before adding rules never locks the device out. Rules live in a fixed BSS table of PC_IP_ALLOWLIST_SLOTS entries (no heap). This is a coarse first-line filter - a spoofed source address can still pass it - so combine it with the accept throttles and network-layer filtering.
PC_ENABLE_IPV6
Dual-stack IPv6. Default off. The TCP and UDP listeners already bind IPADDR_TYPE_ANY, so the server answers over IPv6 the moment the interface has a v6 address; PC_ENABLE_IPV6 turns IPv6 on for the Wi-Fi netif (init_ipv6_physical() -> SLAAC: a fe80:: link-local address plus a global one if a router advertises a prefix), and net_global_ipv6() reads the acquired global address from lwIP. The pc_ip address core (network_drivers/network/ip.h) is one family-tagged type for both v4 and v6, with RFC 4291 text parsing (:: zero-compression, embedded-v4 ::ffff:a.b.c.d), RFC 5952 canonical formatting, and scope classification (loopback / link-local / private-ULA / multicast / global). The address core is pure and host-tested (native_ip); the netif bring-up is ESP32-only. Example IPv6. Requires an lwIP built with LWIP_IPV6=1 (the stock Arduino-ESP32 core ships it). HW-verified (2026-07-19) on an ESP32-S3: SLAAC formed a link-local + ULA + a router-advertised global address, and the dual-stack :80 listener answered real HTTP GETs over IPv6 from an on-link host.
PC_ENABLE_J1939
SAE J1939 codec. Default off. services/fieldbus/j1939 is a zero-heap codec for the heavy-duty-vehicle / agriculture / marine / genset CAN higher-layer protocol over 29-bit extended frames (shared_primitives/can.h): pc_j1939_encode_id / pc_j1939_decode_id pack and unpack the priority / PGN / source / destination identifier (both the PDU1 peer-to-peer and PDU2 broadcast forms), pc_j1939_build_message emits single frames, pc_j1939_build_request and pc_j1939_build_address_claim (with pc_j1939_build_name for the 64-bit NAME) handle the Request PGN and Address Claimed messages, and the Transport Protocol (BAM announce + TP.DT data packets) reassembles multi-packet messages up to PC_J1939_TP_MAX octets via pc_j1939_tp_feed. Typed decoders lift the two canonical engine PGNs into engineering units (SAE J1939-71): pc_j1939_decode_eec1 (EEC1 / PGN 61444 - engine speed at 0.125 rpm/bit and the driver's-demand / actual percent torque at the -125 offset) pc_j1939_decode_et1 (ET1 / PGN 65262 - coolant, fuel, and oil temperature), pc_j1939_decode_lfe (LFE / PGN 65266 - engine fuel rate at 0.05 L/h/bit, instantaneous and average fuel economy at 1/512 km/L, and throttle position), pc_j1939_decode_amb (AMB / PGN 65269 - barometric pressure at 0.5 kPa/bit and the cab-interior / ambient-air / air-inlet / road-surface temperatures), pc_j1939_decode_ic1 (IC1 / PGN 65270 - the particulate-trap-inlet / boost / air-inlet / air-filter / coolant-filter pressures, the intake-manifold temperature, and the exhaust gas temperature at 0.03125 degC/bit), pc_j1939_decode_vd (VD / PGN 65248 - the trip and total vehicle distance at 0.125 km/bit, held as double so a full 32-bit odometer keeps its precision), and pc_j1939_decode_ccvs (CCVS / PGN 65265 - the wheel-based vehicle speed at 1/256 km/h per bit plus the 2-bit cruise-control-active state; only the two signals with cross-source-verified bit positions are decoded, the many discrete switches in this PGN left to the caller), each applying the SPN scale + offset and clearing a validity flag for a raw in the not-available range. pc_j1939_decode_dm1 decodes DM1 (PGN 65226 - Active Diagnostic Trouble Codes, J1939-73): the four lamp statuses (malfunction / red-stop / amber-warning / protect) and the list of active DTCs (each a suspect parameter number + failure mode identifier + occurrence count, unpacked by SPN conversion method 4), taking the raw body so it works on a single frame or a Transport-Protocol-reassembled buffer and skipping the all-zero "no active fault" placeholder. Identifier layout, NAME bit fields, and the TP control bytes verified against SAE J1939-21 / -81, and the EEC1/ET1 decoders against worked examples (1500 rpm, 90 degC coolant); pure and host-tested. Drive it from the ESP32 TWAI peripheral or an MCP2515 over SPI to bridge a J1939 bus onto Wi-Fi. See src/services/fieldbus/j1939/j1939.h.
PC_ENABLE_J2735
Opt-in SAE J2735 V2X codec. When set, services/transportation/j2735 provides the ASN.1 UPER (Unaligned Packed Encoding Rules) bit-level primitive codec (constrained INTEGER / BOOLEAN / bit fields) and, on top of it, the J2735 BSMcore safety-message block (msgCnt / id / secMark / lat / long / elev / speed / heading) encode + decode, for connected- vehicle messaging. Pure codec (the DSRC / C-V2X radio is an external module). Default off.
Zero-heap JSON writer/reader (json.h) for request bodies and responses. Always on.
PC_ENABLE_JWT
JWT bearer-token authentication (HS256). Default off. When set, src/services/security/jwt/jwt.h verifies Authorization: Bearer <jwt> tokens signed with HMAC-SHA-256 (reusing the SSH crypto layer) and can read integer claims (e.g. exp) so a handler/middleware can gate routes on a stateless token. Signature verification is constant-time.
PC_ENABLE_KEEPALIVE
HTTP/1.1 persistent connections (keep-alive). Default on: a cleanly-parsed request is answered with Connection: keep-alive and the slot is recycled for the next request on the same socket: HTTP/1.1 keeps the connection open unless the client sends Connection: close; HTTP/1.0 closes unless the client sends Connection: keep-alive. Set PC_ENABLE_KEEPALIVE=0 for the legacy behavior (every response carries Connection: close and the connection is closed after one request). The default connection pool is MAX_CONNS=8 to give a persistent-connection workload headroom above its peak concurrency. Error responses (400/413/414 and any non-PARSE_COMPLETE path) always close, since the next request boundary is unknown. Idle keep-alive connections are still reclaimed by the existing conn_timeout sweep, and each connection serves at most PC_KEEPALIVE_MAX_REQUESTS requests before a deliberate close.
PC_ENABLE_LD2410
HLK-LD2410 24 GHz mmWave presence / motion radar (UART). Default off. services/peripherals/ld2410 syncs to the module's framed serial output (256000 baud: header F4 F3 F2 F1, little-endian length, payload, footer F8 F7 F6 F5) and decodes the target report - presence state (none / moving / stationary / both), the moving and stationary target distance (cm) and energy (0-100), the overall detection distance, and, in engineering mode, the per-gate energy of all nine range gates - plus encodes the config commands (enter / exit config, enable / disable engineering, restart). Unlike a PIR sensor it detects a perfectly still person (micro-motion / breathing), in the dark, through thin walls. The Ld2410Stream byte-by-byte reassembler is fixed-buffer, no-heap, and resyncs cleanly on dropped bytes or noise; the frame decoder + reassembler + command encoders are host-tested (native_ld2410), and only the UART read/write touches hardware. Command replies decode through pc_ld2410_parse_ack (the ACK echoes the request word with 0x0100 set, then a 2-octet status), with pc_ld2410_ack_ok / pc_ld2410_ack_mac on top. The HLK-LD2410B is supported by the same driver: it is HiLink's BLE-equipped build of the same radar speaking this identical FD FC FB FA protocol, so the report path is unchanged, and the B-only config commands are encoded over the wired UART - Bluetooth on/off (pc_ld2410_cmd_bluetooth, word 0x00A4), MAC query (pc_ld2410_cmd_get_mac, 0x00A5), and set Bluetooth password (pc_ld2410_cmd_set_bt_password, 0x00A9, 6 octets, factory default the ASCII "HiLink"). "Obtain Bluetooth access" (0x00A8) is deliberately not implemented - the protocol document states it answers only over Bluetooth, not the serial port, and the BLE control channel is out of scope for this wired driver. The B-only frames are pinned byte-for-byte to the worked examples in LD2410B Serial communication protocol V1.07; they have not been verified against physical B hardware. Example Ld2410 lights the onboard LED on presence. See src/services/peripherals/ld2410/ld2410.h.
PC_ENABLE_RCWL0516
RCWL-0516 microwave Doppler presence sensor, and the shared one-GPIO presence facade. Default off. The RCWL-0516 (RCWL-9196 controller + MMBR941M RF amp, ~3.18 GHz Doppler) has no data protocol at all - a single 3.3 V OUT pin that latches HIGH on a moving reflector and returns LOW once its own retrigger window expires - so the entire problem is timing, not bytes, and a bare digitalRead() gets it wrong in two ways this fixes. Chatter: the pin is comparator-driven, so around the detection threshold it flickers and one person walking past becomes a burst of presence events; a level must therefore hold for PC_RCWL0516_DEBOUNCE_MS (default 50) before it is believed. Gaps: the module drops OUT between retriggers, so a present-but-briefly-still person reads as absent for a moment; presence is therefore held for PC_RCWL0516_HOLD_MS (default 2000, matching the module's ~2 s retrigger window) past the last believed-HIGH sample, turning a stream of retriggers into one continuous occupied span instead of a flapping boolean. pc_presence_core_update feeds one sample and returns the resulting presence, pc_presence_core_get reads it without sampling, and pc_presence_take_event consumes a changed-flag exactly once per transition so a caller publishes one event per edge rather than re-publishing a level every poll. The core is level-driven rather than edge-driven, so a missed poll delays a transition instead of losing it, and it starts absent - presence is only reported once actually observed. Pure and taking an explicit now like Hot-Swap Storage, so the whole machine is host-tested against a synthetic clock with no GPIO and no real time (native_rcwl0516, 10 cases incl. chatter rejection, retrigger-gap bridging, and a millis() rollover); every elapsed-time test is an unsigned difference, which is what makes it wrap-safe. PresenceCore is deliberately sensor-agnostic - the RCWL-0516 is just its first user via pc_rcwl0516_core_init, and an HMMD OUT pin, a PIR, or an HB100 reuse the same core by supplying their own two constants. Not HW-verified against a physical module. See src/services/peripherals/rcwl0516/rcwl0516.h.
PC_ENABLE_SEN0192
DFRobot SEN0192 10.525 GHz microwave Doppler motion sensor (single digital OUT line). Default off. Unlike a PIR it senses movement through thin non-metal enclosures and is unaffected by ambient light or temperature; unlike the LD2410 it carries no protocol - just one digital line - so services/peripherals/sen0192 tracks that line as a debounced presence signal: presence asserts on an active sample and is held for PC_SEN0192_HOLD_MS after the last active sample (so brief gaps between Doppler returns don't make presence flap), clears after it, and counts clear-to-present edges. The presence state machine (Sen0192Motion) is pure - it takes a sampled level and a timestamp, needing no clock or GPIO - and host-tested (native_sen0192); the ESP32 binding reads PC_SEN0192_PIN each poll via pc_millis() and only that read touches hardware. The OUT pin, hold window, and polarity are ServerConfig knobs (PC_SEN0192_PIN / PC_SEN0192_HOLD_MS / PC_SEN0192_ACTIVE_HIGH). Example Sen0192 lights the onboard LED on motion. See src/services/peripherals/sen0192/sen0192.h.
PC_ENABLE_LDC1614
Opt-in LDC1614 inductance-to-digital field sensor. A field-perturbation sensing peripheral: services/peripherals/ldc1614 decodes the LDC1614's 28-bit conversion result (a nearby conductor changes the coil inductance via eddy currents, giving contactless metal proximity / displacement / EM-field sensing) - pc_ldc1614_data combines the register pair, pc_ldc1614_error pulls the flags, pc_ldc1614_sensor_freq_hz scales to frequency, and pc_ldc1614_build_config emits a single-channel bring-up; the ESP32 binding replays it and reads the channel over I2C. Pure codec host-tested. Default off.
PC_ENABLE_LINK_MANAGER
Opt-in multi-interface egress selection / failover policy. The policy that drives which interface carries traffic once a device has more than one (a wired Ethernet PHY alongside WiFi STA / softAP): services/system/link_manager keeps a small table of interfaces (kind + priority + up/down) and deterministically selects the best link that is up, escalating to a higher-priority interface when it comes up and failing over when it drops, reporting only real transitions so the app reconfigures the netif once. The PHY bring-up (esp_eth) stays the app's. No heap/stdlib. Default off.
PC_ENABLE_LOGBUF
Opt-in fixed-RAM rotating log buffer with severity traps. Default off. When set, services/system/logbuf keeps the last PC_LOG_LINES log lines in a fixed ring (oldest pruned on overflow - no heap, bounded), dumps them oldest-first for a /logs endpoint, and fires a trap callback when a line is logged at/above a severity threshold (forward criticals as an SNMP trap / webhook). The ring + trap logic is pure and host-tested.
PC_LOG_LEVEL
Abstract logging whose disabled levels cost nothing at all. Instrumentation is only worth leaving in the source permanently if a build that does not want it pays nothing for it - not a branch, not a call, and not a format string sitting in flash, where a runtime if (level >= threshold) still links every message. So the filter is the preprocessor: PC_LOGD / PC_LOGI / PC_LOGW / PC_LOGE below PC_LOG_LEVEL expand to a form that names its arguments only inside sizeof(...), an unevaluated context, which emits no code and no string literal yet still runs the compiler's printf format checking over them and marks the arguments used (so a variable read only by a log does not warn). Enable the level and the same line starts logging, with no source change. Defaults to PC_LOG_LEVEL_NONE, so it is opt-in per build (-DPC_LOG_LEVEL=PC_LOG_LEVEL_WARN); at NONE the implementation compiles to nothing at all, defining no symbols and no BSS. An emitted line is formatted once into a PC_LOG_LINE_LEN stack buffer and handed to services/system/logbuf's ring (when PC_ENABLE_LOGBUF is on) and to a sink registered with pc_log_set_sink() (Serial, syslog, a websocket console). Measured, not asserted: a translation unit with four discarded log statements compiles byte-identical to the same function with none (7 bytes .text, 87 total in both), a bad format at a discarded level is still diagnosed by -Wformat, and a discarded call never evaluates its arguments. Host-tested (native_log); dogfooded by services/ftp_session, whose PC_LOGD trace is what localized a real transport-gating bug on hardware. See src/shared_primitives/log.h.
PC_ENABLE_LONWORKS
Opt-in LonWorks / LON-IP (ISO/IEC 14908) network-variable codec. When set, services/fieldbus/lonworks builds/parses the LonTalk network-variable PDU ([msg-code][14-bit selector][value]) that a building-automation device exchanges - over LON/IP (14908-4) UDP, so no Neuron chip is needed - plus the common SNVT scalar encodings (SNVT_temp, SNVT_switch). Pure codec (the UDP transport is the shipped UDP layer). Default off.
PC_ENABLE_LORA
Opt-in LoRa radio codec + driver (v5 gateway plugin). Default off. services/radio/lora is the southbound-radio half of a LoRa-to-web bridge (see Radio Gateway), in two layers. Codec: pc_lora_frame_parse / pc_lora_frame_build handle the RadioHead-compatible 4-byte header (to / from / id / flags) that virtually every hobby / sensor LoRa deployment lays over the header-less LoRa PHY. Driver: the Semtech SX127x (SX1276-79 / RFM95-96) register protocol - pc_lora_init (verifies the chip id, switches to LoRa mode, programs the carrier frequency, spreading factor / bandwidth / coding rate, sync word, and PA power), pc_lora_send / pc_lora_tx_done, pc_lora_set_rx, and pc_lora_recv (reads the FIFO + packet RSSI on RxDone, drops on a CRC error) - all over a caller-supplied register-access bus (two callbacks that read/write a chip register), so the SPI + chip-select wiring is the integration's and the register sequence is portable. Bridge received frames northbound with pc_gateway_uplink(). The codec and the full register protocol are host-tested against a mock SX127x (a register file + a FIFO with the chip's auto-incrementing address pointer); the RF link itself needs the module. Example LoRaGateway drives a real RFM95 over SPI. See src/services/radio/lora/lora.h.
PC_ENABLE_LSV2
Heidenhain LSV/2 telegram codec. Default off. services/machine_tool/lsv2 is a zero-heap codec for the LSV/2 protocol Heidenhain TNC controls (iTNC 530, TNC 320 / 620 / 640, ...) speak for DNC and data access over a serial link or, as implemented here, LSV/2-over-TCP (default port 19000) - so a device becomes a fixed-BSS collector for the common European CNC control, alongside the shipped Fanuc FOCAS, Haas MDC, MELSEC, S7comm and DNC machine-tool codecs. Framing (byte-exact, both directions): a telegram is a 4-byte big-endian payload-length prefix, a 4-character ASCII command / response mnemonic, then the payload - the length counts the payload only (the mnemonic is not included), so a telegram with no payload is exactly 8 bytes on the wire (a bare T_OK acknowledgement is ‘00 00 00 00 'T’ '_' 'O' 'K').pc_lsv2_buildframes an arbitrary mnemonic + payload andpc_lsv2_parseslices one complete telegram off a byte stream (reporting the consumed byte count so a caller can re-frame the rest). **Typed builders** cover the common requests:pc_lsv2_build_login/pc_lsv2_build_logout(theA_LG/A_LOprivilege-group access with theINSPECT/FILE/DNC/MONITOR/DIAGNOSTICS/PLCDEBUGgroups + optional password),pc_lsv2_build_filename(the null-terminated-filename file commands -R_FLload,C_FLsend,C_FDdelete,C_DCchange dir,C_DM/C_DDmake / delete dir), andpc_lsv2_build_run_info(R_RIwith a 2-byte big-endian run-info selector: execution state / selected program / override / program state). **Response readers** decode the reply mnemonic (pc_lsv2_is_okforT_OK,pc_lsv2_is_errorfor theT_ER/T_BDerrors,pc_lsv2_errorfor their two-byte error-class + error-code, andpc_lsv2_isfor theS_*data replies). The telegram framing, mnemonic set, and the login / filename / run-info payload layouts are cross-checked byte-for-byte against the pyLSV2 reference (drunsinn/pyLSV2). Pure codec, host-tested (native_lsv2`, 12 cases); the serial / TCP link to the control is the application's. See src/services/machine_tool/lsv2/lsv2.h.
PC_ENABLE_LWM2M
OMA LwM2M TLV codec. Default off. services/iot/lwm2m is a zero-heap writer + cursor reader for the LwM2M application/vnd.oma.lwm2m+tlv resource encoding carried over the shipped CoAP service for device management: pc_lwm2m_tlv_write (with typed pc_lwm2m_tlv_write_int shortest-form / pc_lwm2m_tlv_write_bool / pc_lwm2m_tlv_write_string / pc_lwm2m_tlv_write_float helpers), pc_lwm2m_tlv_read, and pc_lwm2m_tlv_value_int. Handles 8-/16-bit identifiers, inline / 8- / 16- / 24-bit lengths, and the Object-Instance / Resource / Multiple-Resource / Resource-Instance kinds; type-byte layout verified against the LwM2M spec. Pure and host-tested. The registration interface and the standard object model layer on top. See src/services/iot/lwm2m/pc_lwm2m_tlv.h.
PC_ENABLE_MBUS
Wired M-Bus (Meter-Bus, EN 13757) frame codec. Default off. services/fieldbus/mbus is a zero-heap builder + parser for the M-Bus link-layer frames used by utility meters (water / gas / heat / electricity): pc_mbus_build_ack (the single-character 0xE5), pc_mbus_build_short (10 C A CS 16), and pc_mbus_build_long (68 L L 68 C A CI ... CS 16, with convenience pc_mbus_build_snd_nke / pc_mbus_build_req_ud2 / pc_mbus_build_req_ud1, the last requesting class-1 alarm data where REQ_UD2 requests routine class-2 data), plus pc_mbus_parse which validates the start / stop octets, the doubled length, and the 8-bit sum checksum. pc_mbus_parse_var_header decodes the 12-octet fixed header that opens a variable-data response (CI 0x72) into an MbusVarHeader - the meter's identification serial (from the 4-octet BCD), the 3-letter manufacturer code (unpacked from the 2-octet FLAG value), the version, the medium / device type (named MBUS_MEDIUM_* codes), and the access number / status / signature - so an app identifies the meter before reading its records. pc_mbus_record_next walks the EN 13757-3 variable-data records that follow, skipping the DIFE / VIFE extension chains and decoding the data length from the DIF coding (incl. the LVAR variable form). Each record's value then decodes into a usable number and unit: pc_mbus_record_value_int reads the integer (little-endian, sign-extended) and BCD (with the 0xF negative-sign nibble) codings into an int64, pc_mbus_record_value_real reads the REAL32 coding into a float, and pc_mbus_vif_decode maps a VIF octet to its physical unit (Wh / J / m³ / kg / W / m³·h⁻¹ / °C / K / bar) and the base-10 exponent to apply, so an app gets, say, 12345 × 10⁻³ m³. Frame formats + checksum verified against EN 13757-2, and the value / VIF decoding against worked examples; pure and host-tested. Talk to the powered two-wire bus over a UART through an M-Bus level converter (a TSS721-based master) and bridge meter readings onto Wi-Fi. See src/services/fieldbus/mbus/mbus.h.
PC_ENABLE_MDNS
mDNS / DNS-SD advertisement (name.local + _http._tcp) via ESPmDNS.
PC_ENABLE_MELSEC
Mitsubishi MELSEC MC protocol (binary 3E frame) codec. Default off. services/fieldbus/melsec builds + parses the QnA-compatible binary 3E frames for MELSEC PLCs over TCP/UDP: pc_melsec_build_read emits a batch-read (word units) request (little-endian fields, request subheader 0x5000, command 0x0401, a device code - D 0xA8 / M 0x90 / X / Y / R / ... - plus a 24-bit head device number and a point count), pc_melsec_build_write emits the symmetric batch-write (command 0x1401) with the same device / head / point parameters followed by the write data words (two little-endian octets per point, the request-length field extended to cover them), and pc_melsec_parse_response validates the 0xD000 response and reports the end code (0x0000 = success) and the read data. Frame layout + device codes verified against a third-party MC implementation; pure and host-tested. Completes the major-vendor PLC read set alongside FINS / Host Link (Omron), DF1 (Allen-Bradley), and S7comm (Siemens). See src/services/fieldbus/melsec/melsec.h.
PC_ENABLE_MSGPACK
Zero-heap MessagePack encoder and decoder for compact binary payloads. Default off. When set, network_drivers/presentation/codec/msgpack/msgpack.h provides a writer that serializes ints, strings, byte strings, arrays, maps, booleans, nil, and float32 into a caller-provided buffer, plus a cursor decoder (pc_msgpack_peek / pc_msgpack_read_*, no-copy strings) over a caller buffer - the MessagePack-format sibling of the CBOR / JSON readers and writers. Pure, no heap, host-tested against the spec encodings and round-trip.
PC_ENABLE_METRICS
Prometheus /metrics endpoint (text exposition format 0.0.4). Default off (requires STATS for the underlying counters). When set, PC::metrics() emits the runtime stats as Prometheus metrics (pc_uptime_seconds, pc_http_requests_total, pc_http_responses_total{class=...}, pc_active_connections, pc_free_heap_bytes,...) so a Prometheus server can scrape the device.
Composable use() pipeline with a fixed-window rate limiter. Always on.
PC_ENABLE_MMS
Opt-in IEC 61850 MMS PDU codec. When set, services/energy/mms builds/parses the MMS (ISO 9506) confirmed-request/response Read PDUs (BER-encoded, the ACSI client/server core of IEC 61850) - pc_mms_read_request builds a Read of a named Data Object, pc_mms_read_response the data reply. Carried over ISO-on-TCP (TPKT + COTP via the shipped services/fieldbus/cotp) on port 102. Pure BER codec. Default off.
PC_ENABLE_MODBUS
Modbus TCP slave/server (Modbus Application Protocol v1.1b3) on TCP/502. Default off. When set, listen(502, PROTO_MODBUS) serves a fixed data model (coils, discrete inputs, holding + input registers, all in BSS) over Modbus TCP: Read/Write Coils (FC 1/5/15), Read Discrete Inputs (FC 2), Read/Write Holding Registers (FC 3/6/16), and Read Input Registers (FC 4). The codec (MBAP framing + PDU dispatch) is pure and host-tested; the TCP transport is ESP32-only. The application reads/writes the model with the accessor functions and is notified of client writes via pc_modbus_on_write(). Modbus has no authentication or encryption - run it only on a trusted control network.
PC_ENABLE_MODBUS_MASTER
Opt-in Modbus master codec + register scanner. Default off. services/fieldbus/modbus/pc_modbus_master builds Modbus TCP read-request ADUs and parses the responses (register values or exception), so an app can poll / auto-discover a slave's registers. Pure and host-tested as a full round-trip against the slave codec (pc_modbus_process_adu); the actual send is the app's TCP.
PC_ENABLE_MBPLUS
Opt-in Modbus Plus HDLC token-bus frame codec. When set, services/fieldbus/mbplus builds/validates the Modbus Plus HDLC frame (7E addr ctrl payload CRC-16/X-25 7E) that Schneider's token-passing peer bus exchanges, plus the token-rotation helper (next station in the logical ring). Reuses the shipped Modbus PDU model for the data. Pure codec (the 1 Mbit/s bus is hardware-gated). Default off.
PC_ENABLE_MODBUS_RTU
Modbus RTU framing (serial / RS-485). Default off; implies MODBUS. Adds the RTU ADU codec pc_modbus_rtu_process_adu() - a [slave addr][PDU][CRC16] frame (CRC16-Modbus, little-endian) wrapped around the existing host-tested PDU dispatch: a CRC mismatch or a non-matching unit address is dropped silently (no reply, per the spec), and a broadcast (address 0) is processed with no reply. Pure and host-tested; feed it from a UART/RS-485 driver (the serial transport, framed by the 3.5-character inter-frame idle, is the application's). See src/services/fieldbus/modbus/modbus.h.
PC_ENABLE_MPR121
NXP MPR121 12-channel capacitive-touch controller (I2C). Default off. services/peripherals/mpr121 decodes the touch-status word (pc_mpr121_touched masks the 12 electrode bits out of the 16-bit status, which also carries the proximity electrode at bit 12 and the over-current flag at bit 15) and the chip's 10-bit filtered / baseline per-electrode data (pc_mpr121_word10), and builds the whole register bring-up as (register, value) byte pairs (pc_mpr121_build_init: soft reset, the NXP AN3944 rising/falling/touched filter defaults, per-electrode touch/release thresholds, CONFIG1/2, and the electrode-configuration register that starts it running with baseline tracking). The decode + init-sequence builder are pure and host-tested (native_mpr121); only the register read/write touches I2C. A cheap solder-and-test breakout for touch buttons / sliders. Example Mpr121 prints which pad you touch. See src/services/peripherals/mpr121/mpr121.h.
PC_ENABLE_MQTT
MQTT 3.1.1 publish/subscribe client (raw lwIP, optional MQTTS over TLS). Default off. When set, src/services/iot/mqtt/mqtt.h provides a persistent outbound client: connect to a broker, PUBLISH (QoS 0/1/2) and SUBSCRIBE to topics, receive incoming messages via a callback, with keep-alive pings - the dominant IoT messaging pattern, for telemetry push and remote command. The packet codec is host-testable; the transport (DNS + raw lwIP TCP, MQTTS via client-side mbedTLS) is ESP32-only. Full QoS 0/1/2 (outbound DUP retransmit, inbound QoS-2 de-duplication by packet id) and Last-Will are supported.
PC_ENABLE_MQTT_SN
MQTT-SN v1.2 wire codec. Default off. services/iot/mqtt/mqtt_sn is a zero-heap codec for MQTT for Sensor Networks - the UDP / non-TCP MQTT variant for 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, plus pc_mqttsn_parse_header() (the 1- and 3-octet Length forms, big-endian fields) and typed parsers for CONNACK / REGACK / PUBACK / SUBACK / PUBLISH / REGISTER, with a pc_mqttsn_make_flags() helper (DUP / QoS / retain / will / clean / TopicIdType). Wire bytes verified against the spec and the Eclipse Paho reference; pure and host-tested. The datagram send (pc_udp_sendto), topic-ID registry, and sleep / retransmit state are the application's. See src/services/iot/mqtt/mqtt_sn.h.
PC_ENABLE_MQTT_TLS
MQTTS: run the MQTT client over client-side TLS (needs TLS).
PC_ENABLE_MTCONNECT
Opt-in MTConnect agent response codec. When set, services/machine_tool/mtconnect builds the MTConnectStreams (current/sample) and MTConnectError XML response documents (ANSI/MTC1.4) into a caller buffer - header with instanceId + nextSequence, then per-DataItem Samples/Events/Condition observations - so the web server is an MTConnect agent over the existing HTTP stack. Pure text framing (values XML-escaped). Default off.
PC_ENABLE_MTLS
Mutual TLS - require and verify a client certificate (mTLS). Default off. When set (requires TLS), the server can be given a trust-anchor CA via PC::tls_require_client_cert(): the TLS handshake then demands a client certificate chaining to that CA (MBEDTLS_SSL_VERIFY_REQUIRED) and aborts the connection if the client presents none or an untrusted one. The verified peer's subject DN is available to handlers via PC::tls_client_subject(). Strong transport-level client authentication with no passwords.
PC_ENABLE_MULTIPART
multipart/form-data body parser.
PC_ENABLE_NATS
NATS client protocol codec - the text-based NATS pub/sub messaging protocol. Default off. services/iot/nats lets a device be a NATS client over the outbound client transport: builders for CONNECT, PUB (with optional reply-to), HPUB (a NATS 2.2+ publish carrying a header block), SUB (with optional queue group), UNSUB, PING, and PONG, plus pc_nats_parse which decodes an inbound MSG / HMSG / INFO / PING / PONG / +OK / -ERR (a MSG yields subject / sid / reply-to / payload; an HMSG additionally exposes its header block via the two length fields). Line-oriented (CRLF, space-delimited); only PUB / HPUB / MSG / HMSG carry a payload. Pure and host-tested; the connection and subscription state are the application's. See src/services/iot/nats/nats.h.
PC_ENABLE_NEMA_TS2
Opt-in NEMA TS 2 traffic-cabinet SDLC frame codec. When set, services/transportation/nema_ts2 builds/validates the TS 2 SDLC bus frames ([address][control][frame-type] [data][CRC-16/X-25]) that link a traffic-signal controller to the MMU, BIUs, and detector racks. Pure codec (the synchronous serial PHY + BIU timing are hardware-gated). Default off.
PC_ENABLE_NETADAPT
Opt-in network adaptation decisions. When set, services/net/netadapt provides two pure decisions: pc_netadapt_window() sizes the TCP receive window from the free heap (bigger when RAM is plentiful, shrinking when tight), and pc_netadapt_dhcp_fallback() decides when to give up on DHCP and use a static IP. The app applies the results (lwIP window / netif config). Default off.
PC_ENABLE_NMEA0183
NMEA 0183 sentence codec. Default off. services/timing_position/nmea0183 is a zero-heap codec for the marine / GPS ASCII protocol (sentences like $GPGGA,123519,4807.038,N,...*47): pc_nmea0183_build emits a sentence (adding the $, the XOR checksum, and CR/LF), pc_nmea0183_checksum computes the XOR check, pc_nmea0183_parse validates the *HH checksum and splits the comma-separated fields (deriving the talker id + sentence type from the address field), and pc_nmea0183_field_float / pc_nmea0183_field_int decode field values (the field substrings are delimited so pc_strtof / pc_strtol stop cleanly). Typed decoders for the two common GPS sentences lift a parsed sentence into a position struct: pc_nmea0183_parse_gga (fix data - time, ddmm.mmmm latitude/longitude converted to signed decimal degrees, fix quality, satellite count, HDOP, MSL altitude) and pc_nmea0183_parse_rmc (recommended minimum - the A/V validity, time + date, position, speed over ground in knots, and course). pc_nmea0183_parse_gsv decodes the variable-length GSV (satellites in view): the sentence count / index / total-in-view header and up to four per-satellite records (PRN, elevation, azimuth, and SNR with a valid flag that a blank field - a satellite in view but not tracked - clears). pc_nmea0183_parse_zda decodes ZDA (time + date): the UTC time, the calendar day / month and - unlike RMC - the full four-digit year, plus the optional local-zone hours / minutes offset (which reads back 0 when the receiver leaves it blank), so ZDA is the sentence to read for wall-clock time sync. pc_nmea0183_parse_vtg decodes VTG (track made good + ground speed): the course over ground in degrees true and magnetic, the speed over ground in both knots and km/h, and the optional NMEA 2.3+ mode indicator (which reads back \0 on an older receiver) - the sentence to read for heading and speed. pc_nmea0183_parse_gsa decodes GSA (DOP + active satellites): the manual/automatic selection mode, the 2D/3D fix type, the list of satellite PRNs actually used in the solution (blank slots skipped), and all three dilution-of-precision values (PDOP / HDOP / VDOP) - the fix-geometry companion to GSV's satellites-in-view. pc_nmea0183_parse_mwv decodes MWV (wind speed + angle): the wind angle, the relative/true reference, the wind speed with its units (km/h / m/s / knots), and the A/V status - the standard wind-instrument sentence, the ASCII companion to the NMEA 2000 wind PGN. pc_nmea0183_parse_dpt decodes DPT (depth of water): the depth relative to the transducer, the transducer offset (positive to the waterline, negative to the keel, so the app can report depth below surface or below keel), and the optional maximum-range-scale field - the ASCII companion to the NMEA 2000 water-depth PGN. pc_nmea0183_parse_hdg decodes HDG (heading + deviation + variation): the magnetic sensor heading plus the deviation and variation (each folded to a signed value, East positive / West negative, so true heading = heading + deviation + variation) - the compass sentence, distinct from VTG's GPS course over ground. pc_nmea0183_parse_gll decodes GLL (geographic position - latitude / longitude): the position converted to signed decimal degrees, the UTC time, the A/V validity, and the optional NMEA 2.3+ FAA mode indicator (which reads back \0 on an older receiver) - the minimal position report, where GGA adds fix quality and RMC adds velocity. pc_nmea0183_parse_vhw decodes VHW (water speed + heading): the vessel's heading in degrees true and magnetic and its speed through the water in both knots and km/h - the log / paddlewheel sentence, distinct from VTG's GPS speed over ground (the difference between the two is the current / leeway). pc_nmea0183_parse_vlw decodes VLW (distance traveled through the water): the total cumulative and the trip (since-reset) water distance in nautical miles - the marine odometer, the through-water distance companion to VHW's speed. Sentence framing + checksum verified against the NMEA 0183 standard (the canonical GGA example checks to 0x47), and the GGA/RMC decoders against the textbook 48.1173 N / 11.5167 E example; pure and host-tested. GPS / marine receivers are cheap UART breakouts, so this is a plain HardwareSerial link; bridge position / wind / depth data onto Wi-Fi. See src/services/timing_position/nmea0183/nmea0183.h.
PC_ENABLE_NMEA2000
NMEA 2000 codec. Default off; implies J1939 (NMEA 2000 is J1939 at the transport layer). services/timing_position/nmea2000 is a zero-heap codec for the marine instrumentation network over CAN: it reuses the J1939 29-bit identifier codec and adds the NMEA-specific Fast Packet transport. pc_n2k_fastpacket_num_frames sizes a transfer, pc_n2k_fastpacket_build_frame emits frame N of a 9..223-octet message (a control octet of sequence counter + frame counter, the first frame carrying the total length and 6 data octets, continuations carrying 7), and pc_n2k_fastpacket_feed reassembles a sequence (matching source / PGN / sequence counter, rejecting out-of-order frames and ignoring interleaved sequences); pc_n2k_build_single wraps a single-frame message. Typed decoders lift common single-frame PGNs into engineering units: pc_n2k_decode_position_rapid (PGN 129025 - latitude / longitude at 1e-7 deg/bit into decimal degrees), pc_n2k_decode_cog_sog_rapid (PGN 129026 - course over ground at 0.0001 rad, speed over ground at 0.01 m/s, and the true/magnetic course reference), pc_n2k_decode_engine_rapid (PGN 127488 - engine instance, engine speed at 0.25 rpm/bit, boost pressure at 100 Pa/bit, and the signed tilt/trim percent), pc_n2k_decode_engine_dynamic (PGN 127489, the Fast Packet engine-monitoring record decoded from the reassembled body - oil pressure / temperature, coolant temperature / pressure, alternator voltage, fuel rate, total engine hours, fuel pressure, the two discrete-status bitfields, and the signed engine load / torque percent), pc_n2k_decode_wind_data (PGN 130306 - wind speed at 0.01 m/s, angle at 0.0001 rad, and the reference), pc_n2k_decode_water_depth (PGN 128267 - depth below the transducer at 0.01 m and the transducer offset at 0.001 m), pc_n2k_decode_vessel_heading (PGN 127250 - heading / deviation / variation at 0.0001 rad and the true/magnetic reference), pc_n2k_decode_rudder (PGN 127245 - the rudder instance, the direction order, and the commanded angle + actual position at 0.0001 rad/bit), pc_n2k_decode_attitude (PGN 127257 - the vessel's yaw / pitch / roll, each signed at 0.0001 rad/bit), pc_n2k_decode_speed (PGN 128259 - the through-water and over-ground speed at 0.01 m/s and the water-speed sensor type), pc_n2k_decode_temperature (PGN 130312 - instance, source, and the actual / set temperatures, carried in Kelvin at 0.01 K/bit and exposed in Celsius), pc_n2k_decode_battery_status (PGN 127508 - a battery bank's instance, voltage at 0.01 V/bit, the signed current at 0.1 A/bit, and the temperature carried in Kelvin and exposed in Celsius), pc_n2k_decode_fluid_level (PGN 127505 - a tank's instance + fluid type packed in one octet, the fill level as a percentage at 0.004 %/bit, and the total capacity in litres at 0.1 L/bit), and pc_n2k_decode_actual_pressure (PGN 130314 - a measured pressure's instance, its source - atmospheric / water / oil / fuel / ... - and the value in pascals at 0.1 Pa/bit, signed), each clearing a validity flag for a not-available raw (0x7FFFFFFF for the signed position and pressure, 0x7F for the signed tilt/trim, all-ones for a course / speed / engine / wind / depth / heading field). Fast Packet framing verified against the NMEA 2000 / J1939 layout, and the PGN decoders against worked examples; pure and host-tested. Drive it from the ESP32 TWAI peripheral or an MCP2515 over SPI to bridge an NMEA 2000 backbone (GPS, wind, depth, engine PGNs) onto Wi-Fi. See src/services/timing_position/nmea2000/nmea2000.h.
PC_ENABLE_NRF24
Opt-in nRF24L01+ radio driver (v5 gateway plugin). Default off. services/radio/nrf24 is a driver for the Nordic nRF24L01+ 2.4 GHz module - cheap point-to-multipoint sensor links bridged to the web stack. Unlike the SX127x (plain register read/write), the nRF24 speaks an SPI command protocol (each transaction is a command byte + data with the STATUS register shifted out first) and needs a separate CE pin, so the driver runs over an nrf_bus that carries a full-duplex SPI transfer plus a CE-set callback - the only board-specific code. pc_nrf24_init verifies the chip via a register read-back and programs the channel, data rate, power, 5-byte address, and static payload width; pc_nrf24_send (zero-padded to the width) / pc_nrf24_tx_done, pc_nrf24_set_rx, and pc_nrf24_recv (reports the receiving pipe). The chip's hardware pipe addressing means a received frame's source is the pipe number (no in-payload codec); bridge it northbound with pc_gateway_uplink(port, pipe, ...). The command protocol is host-tested against a mock chip (register file + payload buffers + STATUS write-1-to-clear); the RF link itself needs the module. Example Nrf24Gateway drives a real module over SPI. See src/services/radio/nrf24/nrf24.h.
PC_ENABLE_NTCIP
Opt-in NTCIP transportation-device object identifiers. When set, services/transportation/ntcip provides the NTCIP (National Transportation Communications for ITS Protocol) object OID definitions for the common device classes - NTCIP 1202 (actuated signal controller: phases, timing, live states) and 1203 (dynamic message sign) - plus an OID builder, so an app exposes them via the shipped SNMP agent (services/net/snmp). Pure OID data. Default off.
PC_ENABLE_NTP
SNTP wall-clock time sync via the ESP-IDF SNTP client.
PC_ENABLE_NTP_SERVER
NTP/SNTP time server (RFC 5905 / RFC 4330 server mode) on UDP/123. Default off. services/timing_position/ntp_server turns the device into a local time source: it answers client NTP requests from its own clock, so an offline or air-gapped LAN can keep its devices in sync without reaching the public pool. The 48-octet response builder (pc_ntp_server_build_response) is pure - it echoes the request's protocol version, copies the client's transmit timestamp into the origin field (so the client can compute round-trip delay), and stamps reference/receive/transmit times - and is host-tested against the wire format. pc_ntp_server_begin(stratum, refid) binds the port via the transport UDP service and drives it from pc_time_now() (seconds) plus a pc_millis()-derived sub-second fraction; while the device has no time it stays silent rather than serve a wrong clock. Pair it with a GPS receiver (parsed via the NMEA 0183 codec into a stratum-1 time source) and an upstream-NTP fallback for a self-hosted, offline-capable time server. Example NtpServer. See src/services/timing_position/ntp_server/ntp_server.h.
PC_ENABLE_NTRIP_CASTER
Opt-in GNSS RTK base station + NTRIP caster (services/timing_position/gnss). Default off (implies PC_ENABLE_NMEA0183). Turns the device into a differential-GNSS correction source: it surveys in a fixed antenna position and serves RTCM 3.x corrections to rovers over the network (the NTRIP protocol), so a rover applies them for RTK / DGPS accuracy instead of the ~2.5 m of a bare receiver. Three pure, zero-heap, host-tested cores plus a ConnProto listener: (1) the RTCM3 codec (services/timing_position/gnss/rtcm3) - the transport frame (0xD3 preamble, 6 reserved + 10-bit length, payload, 24-bit CRC-24Q), MSB-first bit I/O, and the Stationary Antenna Reference Point messages 1005 (no height) / 1006 (with antenna height) that advertise the base's surveyed ECEF position (38-bit signed coordinates at 0.0001 m resolution), verified byte-for-byte against pyrtcm; (2) the survey-in core (services/timing_position/gnss/pc_gnss_survey) - the exact WGS84 geodetic<->ECEF transform (matched against pyproj), a shifted-origin position averager with a 3-D accuracy estimate and a min-observations / accuracy-limit convergence gate, and a GGA-fix fold (ellipsoidal height = MSL + geoid separation); (3) the NTRIP caster protocol (services/timing_position/gnss/pc_ntrip_caster) - rover request parsing (mountpoint, NTRIP 1.0 / 2.0 version, optional HTTP Basic auth), the stream-accept / error / 401 responses, and the RTCM source table (STR records + ENDSOURCETABLE). The ConnProto::PROTO_NTRIP_CASTER listener (services/timing_position/gnss/pc_ntrip_caster_listener) answers rovers and fans RTCM corrections out to every subscriber, published like the relay: server.listen(2101, ConnProto::PROTO_NTRIP_CASTER) then pc_ntrip_caster_add_mount() / pc_ntrip_caster_broadcast(). Example NtripCaster runs a base (survey-in + caster) and a rover (NTRIP client that CRC-validates and decodes the 1005) on two boards. Generating RTCM3 observation messages (the MSM sets 1074/1077/1084/... that let a rover fix carrier-phase ambiguities for centimeter RTK) requires a receiver that outputs raw measurements (u-blox RXM-RAWX: F9P / M8T class); a raw-less module (NEO-6/7, GT-U7) can still survey in and serve the reference point + sourcetable. See src/services/timing_position/gnss/pc_ntrip_caster_listener.h.
PC_ENABLE_NTS
Opt-in Network Time Security (NTS, RFC 8915) wire codec. When set, services/timing_position/nts provides the NTS-KE record codec (build/parse the TLV records - next protocol, AEAD, cookies, server/port) and the NTS NTP extension-field framing (Unique Identifier, Cookie, Authenticator). Pure framing (the AES-SIV-CMAC-256 AEAD + TLS-exporter key derivation are the crypto integration on top). Default off.
PC_ENABLE_OAUTH2
OAuth2 token-endpoint client. Default off. services/security/oauth2 obtains tokens - the counterpart to the OIDC ID-token verifier. It builds the percent-encoded form body for the authorization_code and refresh_token grants (RFC 6749), supporting a confidential client (client_secret) or a public client with PKCE (code_verifier, RFC 7636), and parses the JSON token response (reusing the zero-heap JSON reader). The build + parse core is pure and host-tested; the POST to the token endpoint uses the HTTP(S) client (needs HTTP_CLIENT). No heap, no stdlib.
PC_ENABLE_OBSERVABILITY
Transport-layer observability: connection-event hook + counters. Default off (zero cost when unset - the notify points compile to nothing). When set, the transport (L4) fires an application callback on every connection state transition - pc_conn_on_event(slot, old_state, new_state, reason) - and maintains lock-free counters (accepts, closes by reason, idle timeouts, RX backpressure events, dropped deferred events, and a live CONN_CLOSING gauge) readable via pc_conn_counters_get(). The only state-transition trace the L4/L5 core exposes; pair it with STATS for request-level metrics.
PC_ENABLE_OCIT
Opt-in OCIT-Outstations message codec. When set, services/transportation/ocit builds/parses the OCIT (DE/AT/CH road-traffic-control) object messages ([msg-type][object-type][instance][data-type][value]) between central traffic computers and field controllers / detectors, with typed values (bool / byte / u16 / u32 / octets). Pure codec (the OCIT transport is the shipped transport). Default off.
PC_ENABLE_OIDC
OpenID Connect ID-token verification, RS256. Default off. services/security/oidc verifies an OIDC ID token (JWT) as a relying party: requires alg RS256, selects the issuer key by kid from a JWKS, verifies the RSASSA-PKCS1-v1.5 SHA-256 signature (real RSA modexp via ssh_rsa, mbedTLS- accelerated on ESP32), and checks iss / aud / exp / nbf, extracting sub / email. Pure and host-tested; the caller fetches + caches the JWKS over HTTPS (off the request hot path) and passes the JSON in. Builds on the SSH RSA primitive, not the HS256 JWT module (services/security/jwt), so the two are independent.
PC_ENABLE_OPCUA
OPC UA Binary server. Default off. services/fieldbus/opcua provides an OPC UA (IEC 62541) Binary server: the little-endian built-in-type codec (incl. NodeId / ExtensionObject / DateTime / Variant / DataValue / ReferenceDescription), UA-TCP (UACP) message framing, the Hello/Acknowledge handshake (with a transport-level ERR message - pc_opcua_build_error - the server can return before closing when a Hello is malformed or a buffer size is unacceptable, carrying an OPC UA StatusCode + reason string), the SecureChannel (OpenSecureChannel, SecurityPolicy None), the Session (CreateSession + ActivateSession), GetEndpoints, the Read, Write and Browse services (registered resolvers map a NodeId to a value / accept a written value / list child references), plus CloseSession + CloseSecureChannel and a ServiceFault for unsupported services, served on TCP via PROTO_OPCUA (listen(4840, PROTO_OPCUA)). The MSG framing is spec-faithful (incl. SecureChannelId), so standard clients interoperate (verified with python asyncua: connect + browse + read + write/read-back). All pure and host-tested. No heap, no stdlib.
PC_ENABLE_OPCUA_CLIENT
OPC UA Binary client. Default off (requires OPC-UA, shares the codec). services/fieldbus/opcua_client builds the client-side requests (Hello, OpenSecureChannel, CreateSession, ActivateSession, Read, Browse, CloseSession, CloseSecureChannel) and parses the server responses, reusing the opcua.h codec. Transport-agnostic - the app supplies the outbound socket (e.g. an Arduino WiFiClient). No heap, no stdlib.
PC_ENABLE_UMATI
umati / OPC UA for Machine Tools information model (OPC 40501-1). Default off (requires OPC-UA). services/machine_tool/umati exposes a fixed MachineTool node hierarchy - Identification, Monitoring (MachineTool / Channel / Spindle / Axis_X..Z), Production, and Notification - through the OPC UA Browse + Read resolvers, served out of a caller-owned UmatiMachineTool struct you refresh each loop. Faithful BrowseNames per OPC 40501-1 (namespace http://opcfoundation.org/UA/MachineTool/); a read-only monitoring model any umati / OPC UA client browses and reads by BrowseName. No heap, no stdlib.
PC_ENABLE_ROBOTICS
OPC UA for Robotics information model (OPC 40010-1). Default off (requires OPC-UA). services/machine_tool/robotics exposes a fixed MotionDeviceSystem node hierarchy - MotionDevices (MotionDevice / ParameterSet / parametric Axes), Controllers (Controller / Software), and SafetyStates (SafetyState / ParameterSet) - through the OPC UA Browse + Read resolvers, served out of a caller-owned RoboticsMotionDeviceSystem struct you refresh each loop. The axis count is parametric (PC_ROBOTICS_AXES, default 6): each bound axis becomes an Axis_k object with ActualPosition / ActualSpeed / ActualAcceleration / MotionProfile. Faithful BrowseNames per OPC 40010-1 (namespace http://opcfoundation.org/UA/Robotics/); a read-only monitoring model any OPC UA client browses and reads by BrowseName. The twin of umati (machine tools). No heap, no stdlib.
PC_ENABLE_EUROMAP77
EUROMAP 77 / OPC 40077 IMM_MES_Interface information model (OPC UA for injection moulding machines <-> MES, enums from EUROMAP 83 / OPC 40083). Default off (requires OPC-UA). services/machine_tool/euromap77 exposes a fixed IMM_MES_Interface node hierarchy - MachineInformation, MachineStatus, and Jobs (ActiveJob + ActiveJobValues with the UInt64 production counters JobCycleCounter / *PartsCounter / MachineCycleCounter) - through the OPC UA Browse + Read resolvers, served out of a caller-owned EmImm struct you refresh each loop. Faithful BrowseNames per the EUROMAP 77 NodeSet (namespace http://www.euromap.org/euromap77/); the counters are 64-bit (adds Int64/UInt64 to the OPC UA Variant codec). A read-only monitoring model any OPC UA / MES client browses and reads by BrowseName. The plastics-industry twin of umati / robotics. No heap, no stdlib.
PC_ENABLE_OPENADR
Opt-in OpenADR 3.0 (Automated Demand Response) JSON codec. When set, services/energy/openadr builds the OpenADR 3.0 event (a demand-response signal: programID + eventName + interval payload points) and report (a VEN reading back to the VTN) JSON objects into a caller buffer, over the existing HTTP client/server + OAuth2. Pure JSON framing. Default off.
PC_ENABLE_OTA
Authenticated OTA firmware update (streaming POST to the ESP32 Update API).
PC_ENABLE_OTA_ROLLBACK
Opt-in OTA rollback protection / soft-brick safeguard. Default off. After an OTA update the new image boots in PENDING_VERIFY; this service confirms it (esp_ota_mark_app_valid) once a self-test passes, or rolls back to the previous image if the self-test fails or the confirm window elapses without success - so a bad update self-heals instead of soft-bricking. The decision logic is pure and host-tested; the commit / rollback use esp_ota_ops. Requires the bootloader's app-rollback support (CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE).
PC_ENABLE_PACKML
PackML / OMAC packaging-machine state model. Default off. services/machine_tool/packml is a zero-heap implementation of the ISA-TR88.00.02 (ISBN 978-1-64331-224-8, "Machine and Unit States: An Implementation Example of ISA-88") PackML state model that packaging and process machines expose so a line controller - usually over OPC UA - can command and observe them uniformly. Two layers. The pure state engine: pc_packml_command(state, cmd) applies a control command (Reset / Start / Stop / Hold / Unhold / Suspend / Unsuspend / Abort / Clear) and returns the resulting state across the 17-state model (the wait states Stopped / Idle / Execute / Held / Suspended / Complete / Aborted plus the transient "acting" states Starting / Resetting / Holding / Unholding / Suspending / Unsuspending / Completing / Stopping / Aborting / Clearing), with Stop and Abort as the near-universal transitions to the shutdown and fault branches; pc_packml_state_complete(state) auto-advances an acting state to its target when its action finishes (Starting -> Execute, Aborting -> Aborted, ...); pc_packml_execute_complete(state) ends a production run (Execute -> Completing); and pc_packml_command_valid / pc_packml_is_acting / pc_packml_state_name / pc_packml_command_name classify and label. The PackMlState enum's underlying value is the ISA-TR88.00.02 StateCurrent wire number (Stopped=2, Execute=6, Aborted=9, Complete=17, ...) so a PackTags Status.StateCurrent needs no translation. The owned service (pc_packml_svc_*) layers the PackTags on top: it holds the current state + unit mode (Producing / Maintenance / Manual), advances on command / state-complete, tracks the ProdProcessedCount / ProdDefectiveCount production counters (only while executing), enforces the unit-mode-change rule (allowed only in a stable non-producing state), reflects the commanded MachSpeed as MachSpeedActual while in Execute, and reports the StateCurrentTime / AccTimeSinceReset timers off the pluggable clock. Pure and host-tested (native_packml, 16 cases covering the full transition graph, command validity, counters, mode rules, speed, and timers) and HW-verified on an ESP32-P4 - the whole state graph, the fault branch (Abort -> Aborted -> Clear -> Stopped), the production counters, and illegal-command rejection (HTTP 409, state unchanged) driven live over the example's HTTP routes; the OPC UA / tag transport is the application's. See src/services/machine_tool/packml/packml.h.
PC_ENABLE_PARTITION_MONITOR
Opt-in flash partition-map monitor endpoint. Default off. When set, services/storage/partition_monitor reports the device's flash partition table (label, kind, type / subtype, offset, size, and which app slot is running) as JSON, for diagnostics and OTA dashboards. The partition walk uses esp_partition / esp_ota_ops; the JSON serializer and the kind classifier are pure and host-testable.
PC_ENABLE_PCA9685
NXP PCA9685 16-channel 12-bit PWM / servo driver (I2C). Default off. services/peripherals/pca9685 turns the ESP32's two I2C wires into sixteen hardware PWM outputs. pc_pca9685_prescale computes the PRESCALE register for a PWM frequency from the 25 MHz oscillator (round(25e6 / (4096*freq)) - 1, clamped 3..255; 50 Hz -> 121); pc_pca9685_channel_reg gives a channel's register base (0x06 + 4*channel); pc_pca9685_us_to_count converts a servo pulse width (microseconds) to a 12-bit OFF count at the configured frequency; and pc_pca9685_set_pwm_bytes packs the 5-byte channel write (12-bit ON/OFF little-endian, preserving the full-on/off flag). The prescale / count math + the register encoder are pure and host-tested (native_pca9685); only the register writes touch I2C. A cheap solder-and-test breakout for up to 16 servos or LEDs. Example Pca9685 sweeps a servo. See src/services/peripherals/pca9685/pca9685.h.
PC_ENABLE_PER_IP_THROTTLE
Opt-in per-IP accept-rate throttle (connection-flood defense, keyed by source IPv4). Default off (zero cost / no behavior change). Complements the global accept throttle: the accept callback rejects a new connection once one source IPv4 address has opened more than PC_PER_IP_THROTTLE_MAX connections within a PC_PER_IP_THROTTLE_WINDOW_MS fixed window. A fixed BSS table of PC_PER_IP_THROTTLE_SLOTS buckets tracks the most-recently-seen source addresses; when a new address arrives and the table is full, an expired or least-recently-started bucket is reused, so memory stays bounded (no heap). This bounds reconnect/brute-force churn from a single host (the gap left by the global throttle, which cannot tell one noisy client from many). It is best-effort: an attacker spreading across many source addresses can still churn the bounded connection pool, so combine it with the global throttle and network-layer filtering.
PC_ENABLE_PN532
PN532 NFC frame codec (v5 gateway plugin). Default off. services/peripherals/pn532 is the command-frame protocol of the NXP PN532 - the ubiquitous NFC / RFID reader on I2C / SPI / HSU breakouts - so a tag read/write becomes an HTTP / MQTT event. The host and the chip exchange normal information frames 00 00 FF | LEN | LCS | TFI | PData | DCS | 00, where TFI is 0xD4 (host) / 0xD5 (chip), LCS is the length checksum and DCS the data checksum, plus a 6-byte ACK frame. pc_pn532_build_frame assembles a command (the per-command PData - GetFirmwareVersion, InListPassiveTarget, InDataExchange - is the application's), pc_pn532_parse_frame frames + verifies a response (returning the length consumed, need-more, or a resync signal), and pc_pn532_is_ack / pc_pn532_build_ack handle the ACK. Pure - you carry the bytes over your I2C / SPI / UART - and host-tested against the documented GetFirmwareVersion command / response frames and their LCS / DCS. Example NfcGateway reads a real PN532 over I2C and bridges each tag UID northbound. See src/services/peripherals/pn532/pn532.h.
PC_ENABLE_POWERLINK
Opt-in Ethernet POWERLINK (EPSG) basic frame codec. When set, services/fieldbus/powerlink builds/parses the EPL basic frames ([messageType][dest][source][payload]) of the isochronous managed-node cycle - SoC (start of cycle), PReq (poll request), PRes (poll response with process data), SoA (start of async, pc_epl_soa), and ASnd (asynchronous send, pc_epl_asnd, unicast or broadcast) - over raw L2 (ethertype 0x88AB, on the shipped services/fieldbus/rawl2). Pure codec (the raw-L2 transmit + isochronous timing are the device step). Default off.
PC_ENABLE_PQC_KEX
Post-quantum / traditional hybrid key exchange: ML-KEM-768 (FIPS 203) combined with X25519. Closes the harvest-now-decrypt-later gap - OpenSSH 9.9+ and current browsers now DEFAULT to a hybrid group, so without this the device negotiates DOWN to classical X25519. When set (and SSH is on) the server advertises mlkem768x25519-sha256 (draft-ietf-sshm-mlkem-hybrid-kex) first in its SSH KEX list and, on selection, ML-KEM-Encaps to the client's key + X25519, combining K = SHA256(K_PQ || K_CL) per the RFC 9370 concatenation combiner. As a server the device is the KEM responder (Encaps); as a reverse-SSH CLIENT (PC_ENABLE_SSH_CLIENT) it is the KEM initiator, so KeyGen and Decaps also ship - Decaps carries the full constant-time Fujisaki-Okamoto transform (re-encrypt m' under the embedded ek, select the real key vs the implicit-reject key J(z || ct) under a constant-time ciphertext compare, FIPS 203 sec 6.3). The ML-KEM core (network_drivers/presentation/pqc) is a software NTT over q=3329 with Montgomery reduction plus a Keccak/SHA-3/SHAKE sponge (FIPS 202); zero heap, peak ~7 KB (Encaps) to ~9 KB (Decaps) of worker stack (raise PC_WORKER_TASK_STACK to >= PC_WORKER_STACK_PQC_MIN = 16384). KeyGen, Encaps and Decaps are all byte-exact against the FIPS 203 reference (kyber-py): keygen (d,z)->(ek,dk), encaps (ek,m)->(ct,ss), decaps (dk,ct)->ss, the initiator/responder round-trip, and the FO implicit-reject. Wired into both transports from the one core: the SSH key exchange (PC_ENABLE_SSH, mlkem768x25519-sha256, K = SHA256(K_PQ || K_CL), K as an RFC 4251 string) and the HTTP/3 QUIC TLS 1.3 handshake (PC_ENABLE_HTTP3, the X25519MLKEM768 group, IANA 0x11ec, ML-KEM-first client/server shares and a 64-byte ML-KEM || X25519 secret into the key schedule). A PQC-capable peer (OpenSSH 9.9+, current browsers) negotiates the hybrid; others fall back to classical X25519. Default off.
PC_ENABLE_SSH_SNTRUP761
A second SSH PQ/T hybrid alongside ML-KEM: sntrup761x25519-sha512@openssh.com - Streamlined NTRU Prime sntrup761 (a lattice KEM with a conservative security margin, OpenSSH's long-standing default) crossed with X25519, SHA-512 exchange hash. Defaults to PC_ENABLE_PQC_KEX (on wherever the hybrid is enabled), so a PQC-capable peer is offered both mlkem768x25519-sha256 and sntrup761x25519-sha512@openssh.com (ML-KEM first, matching OpenSSH's order). As a server the device is the KEM responder (Encaps); as a reverse-SSH CLIENT (PC_ENABLE_SSH_CLIENT) it is the initiator, so KeyGen and Decaps also ship - Decaps carries the full constant-time Fujisaki-Okamoto transform (re-encrypt under the embedded public key, select the real key vs the implicit-reject key rho under a constant-time ciphertext compare). The combined secret is K = SHA512(K_PQ || K_CL), a 64-byte RFC 4251 string, and (per the method's -sha512 suffix, RFC 4253 sec 8) the exchange hash and the RFC 4253 sec 7.2 key derivation run over SHA-512 - the library's exchange-hash/KDF seam dispatches SHA-256 or SHA-512 by the negotiated method, so the classical and ML-KEM methods are unchanged. The sntrup761 core (network_drivers/presentation/pqc/sntrup761) is a zero-heap software KEM over Z_q[x]/(x^761-x-1) (q=4591, w=286), width-aware constant-time (Encode/Decode recursive base conversion, R3_recip/Rq_recip3 GCD inversions for KeyGen); byte-exact both directions against the public-domain OpenSSH/Bernstein reference. It is heavier than ML-KEM on the worker stack - the server Encaps peaks ~22 KB, the client KeyGen+Decaps ~32 KB (the FO re-encrypt holds the biggest working set) - so PC_WORKER_TASK_STACK must be >= PC_WORKER_STACK_SNTRUP_MIN (32768 server / 40960 reverse-SSH client); a footprint-bound PQC build (e.g. a classic ESP32 that only wants ML-KEM) can set this to 0 to drop sntrup761 and keep the lighter 16384 floor. Requires PC_ENABLE_PQC_KEX. Default tracks the hybrid. HW-verified on an ESP32-P4 (wired Ethernet) against OpenSSH 10: the server negotiates sntrup761x25519-sha512@openssh.com, signs the SHA-512 exchange hash with its ssh-ed25519 host key, reaches SSH2_MSG_NEWKEYS, authenticates, and echoes channel bytes back byte-exact - the whole handshake completing on the 32 KB worker stack with no overflow. The reverse-SSH client direction (KeyGen+Decaps as the initiator) is verified by construction (the KEM is byte-exact both directions vs the OpenSSH reference and it reuses the same HW-verified SHA-512 exchange-hash/KDF seam); its on-relay tunnel interop is a follow-up.
PC_ENABLE_POWER_MGMT
Opt-in SoC power governor. Default off. services/system/radio_power owns the radio and services/system/sleep_sched decides how long to sleep; neither owns the SoC, which is where the rest of the power budget goes. pc_power_plan() decides the CPU clock every tick from three inputs, with a deliberate precedence - brownout beats thermal beats load, because a board that cannot hold its supply must not be clocked up merely because it is busy, and neither must a hot one. Scaling: an idle server drops to PC_POWER_MHZ_MIN instead of spinning a 240 MHz core to poll a quiet socket. Thermal throttle: a hot die clocks down, and the restore threshold is lower than the throttle threshold - the caller feeds the previous tick's throttled flag back in, which is what supplies the hysteresis. Brownout recovery: after an ESP_RST_BROWNOUT reset (read once and latched at boot) the part holds the floor clock for PC_POWER_RECOVER_MS rather than slamming back into the load that collapsed the rail and boot-looping. Gating: pc_power_gate_bt() disables and releases the Bluetooth controller's power domain on a build that never uses BT, which draws current whether or not anything is connected. A part with no usable internal sensor (classic ESP32) reports INT16_MIN, treated as "no reading" rather than ice-cold, so it neither throttles nor silently releases one. Pure decision core taking every input explicitly, host-tested (native_power_mgmt, 19 cases incl. a 20-tick feedback loop asserting exactly one throttle transition). HW-verified on an ESP32-S3 with its real die sensor: the Bluetooth domain was released, an idle server clocked itself 240 -> 80 MHz, GET /busy took it to 240 MHz with the die rising 36 -> 38 C and back, and with the thresholds moved to a reachable band the throttle engaged at exactly temp_hot_c and released at exactly temp_cool_c. That run also produced a tuning rule now documented at the flag: the hysteresis band must be wider than the temperature swing the clock change itself causes (measured ~2 C for 240 -> 80 MHz on an S3 within one 500 ms tick), or the throttle's own effect carries the die back across the release threshold and it self-sustains regardless of how correct the comparison is - which is why the default band is 10 C. Example PowerGovernor. See src/services/system/power_mgmt/power_mgmt.h.
PC_ENABLE_PREEMPT_QUEUE
Opt-in real-time ingest primitive (v5 milestone). Default off. Fixed-capacity queues, each feeding one dedicated core-pinned task: a producer posts a fixed-size item from a task (back or urgent-front, each with a wait timeout) or from an ISR (interrupt-safe via xQueueSendFromISR + portYIELD_FROM_ISR), and the scheduler preempts straight to the processing task so the work runs immediately instead of on the next tick - the clean ISR-to-"process now" hand-off for DMA-complete / GPIO / bus events. There are named lanes: one USER lane exposed to the app (the no-arg pc_pq_* API drives it) plus internal DMA / FORWARD / DEVICE lanes for the library's own real-time work. The internal lanes run above the user lane (base PC_PQ_INTERNAL_PRIORITY, DMA highest) and below the lwIP tcpip / WiFi tasks, so internal ingest always preempts user work without starving networking; pc_pq_lane_priority() reports the ordering. Zero heap (static FreeRTOS queue storage per lane, compile-time PC_PQ_DEPTH / PC_PQ_ITEM_SIZE / PC_PQ_STACK; a lane's task stack is created only when it starts, so unused lanes cost only their queue storage), fail-closed on a full queue, no hot-path locks so latency stays bounded. pc_pq_high_water_lane() reports the peak depth for sizing. HW-verified on a DevKitV1 (~12 us ISR-to-handler latency; the DMA and USER lanes ran continuously with zero errors under an HTTP flood); host-tested via the per-lane ring core (services/system/preempt_queue). See src/services/system/preempt_queue/preempt_queue.h.
PC_ENABLE_PROFIBUS
Opt-in PROFIBUS-DP FDL telegram codec. When set, services/fieldbus/profibus builds/validates the PROFIBUS-DP FDL telegrams - SD1 (no-data: SD1 DA SA FC FCS ED), SD2 (variable-data: SD2 LE LEr SD2 DA SA FC data FCS ED, arithmetic-sum FCS), and SD3 (fixed 8-octet data: SD3 DA SA FC data[8] FCS ED) - a Siemens DP master exchanges with slaves over RS-485 (the DP-V0 cyclic I/O exchange). Pure codec (the RS-485 timing + DP state machine are the device step). Default off.
PC_ENABLE_PROFINET
Opt-in PROFINET DCP (Discovery and Configuration Protocol) frame codec. When set, services/fieldbus/profinet builds/parses the DCP frames (10-octet header + option/suboption blocks) PROFINET uses to discover and name IO-Devices over raw L2 (ethertype 0x8892) - Identify request/ response and Set (assign NameOfStation / IP). Pure codec (the raw-L2 transmit via services/fieldbus/rawl2 + esp_eth is the device step). Default off.
PC_ENABLE_PROTOBUF
Protocol Buffers wire codec. Default off. services/iot/protobuf is a zero-heap streaming Protobuf encoder + cursor reader over caller buffers (the same shape as the CBOR / MessagePack codecs): a writer for varint / ZigZag / fixed32 / fixed64 / length-delimited fields (pc_pb_uint64, pc_pb_sint64, pc_pb_fixed32, pc_pb_fixed64, pc_pb_float, pc_pb_double, pc_pb_bytes, pc_pb_string; embedded messages are built into a sub-buffer and added with pc_pb_bytes), and a reader (pc_pb_read_field) that decodes one field at the buffer head and reports bytes consumed, with ZigZag / float / double value decoders. Host-tested against the spec vectors. This is the standalone Protobuf deliverable; gRPC (framed Protobuf over HTTP/2) is gated on the HTTP/2 roadmap item. See src/services/iot/protobuf/protobuf.h.
PC_ENABLE_PROVISIONING
First-boot WiFi provisioning: softAP + captive-portal credentials form.
PC_ENABLE_PROXY_PROTOCOL
HAProxy PROXY protocol codec - recover the real client IPv4 when the server sits behind a load balancer / reverse proxy that prepends a PROXY header. Default off. services/net/proxy_protocol provides proxy_parse (detects + decodes a v1 text PROXY TCP4 ...\r\n header or a v2 binary header - 12-octet signature + ver_cmd / family / address block - and reports the bytes to skip before the real stream) plus proxy_v1_build / proxy_v2_build for a TCP/IPv4 header. Handles the library's IPv4 family; IPv6 / UNIX / LOCAL headers parse to their length but yield no addresses. Format per the HAProxy PROXY protocol spec; pure and host-tested. See src/services/net/proxy_protocol/proxy_protocol.h.
PC_ENABLE_PSRAM_POOL
Opt-in buffer placement policy (DRAM vs PSRAM) + SPI DMA ping-pong manager. Pure buffer-management decisions for a PSRAM-equipped ESP32: pc_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 pc_pingpong_* keeps the classic SPI DMA double-buffer bookkeeping (CPU fills one buffer while DMA drains the other; swap flips their roles). The actual heap_caps_calloc is the app's. No heap/stdlib. Default off.
PC_ENABLE_PTP
PTP / IEEE 1588-2008 (PTPv2) message codec + ordinary-clock slave math. Default off. The Precision Time Protocol disciplines LAN clocks to sub-microsecond accuracy by exchanging timestamped messages; services/timing_position/ptp is a zero-heap codec for the PTPv2 wire format: pc_ptp_build_header / pc_ptp_parse_header frame the 34-octet common header (all multi-octet fields big-endian), pc_ptp_ts_write / pc_ptp_ts_read handle the 10-octet timestamp (48-bit seconds + 32-bit nanoseconds), and pc_ptp_build_sync / _delay_req / _follow_up / _delay_resp plus pc_ptp_parse_timestamp_msg / _parse_delay_resp / _parse_announce build and decode the five messages a two-step ordinary clock needs (each build stamps the type-specific messageType / control / messageLength for you). pc_ptp_compute derives the slave's offsetFromMaster and meanPathDelay from the four transfer timestamps t1..t4 (offset = ((t2-t1) - (t4-t3)) / 2, delay = ((t2-t1) + (t4-t3)) / 2). The P2P peer-delay mechanism (IEEE 1588-2008 §11.4) is also supported: pc_ptp_build_pdelay_req / _pdelay_resp / _pdelay_resp_follow_up and their parsers build and decode the Pdelay_Req / Pdelay_Resp / Pdelay_Resp_Follow_Up messages, and pc_ptp_compute_link_delay computes the per-link meanLinkDelay (((t4-t1) - (t3-t2)) / 2) that a transparent/boundary clock measures to each neighbor independently of the master offset. Header field order, timestamp layout, and message bodies verified against IEEE 1588-2008 clause 13 and interop-checked against Wireshark's PTP dissector (every built message - Announce/Sync/Follow_Up/Delay_Req/Delay_Resp - decodes with the correct type, length, clock identity, and grandmaster-quality fields); pure and host-tested (native_ptp), and the slave is HW-verified against linuxptp ptp4l over a LAN (an ESP32-S3 on Wi-Fi ran the full Announce/Sync/Follow_Up/Delay_Req/Delay_Resp exchange with a real grandmaster, decoding its Announce and computing offset + path delay each cycle). Supports both roles: an ordinary-clock slave and a grandmaster (pc_ptp_build_announce advertises clockClass / timeSource so the device can hand out GPS/RTC-sourced time). The UDP transport (event port 319, general port 320, multicast 224.0.1.129) and the local timestamping are the application's. Example Ptp is an ordinary-clock slave (PTP_MASTER 1 makes it a grandmaster). See src/services/timing_position/ptp/ptp.h.
PC_ENABLE_GATEWAY
Opt-in radio / wireless gateway bridge (v5 milestone). Default off. services/net/gateway is the generic southbound-to-northbound bridge that ties the hardware-ingest pipeline to the web stack: a southbound radio (LoRa / nRF24 / CC1101 / Zigbee / Z-Wave / ... reached over SPI / I2C / UART) is a port. When it receives a frame - the data-ready ISR reads it over DMA (services/system/dma), posts it onto the FORWARD lane (services/system/preempt_queue), and a per-radio codec extracts the source node address + payload - you call pc_gateway_uplink(); the gateway envelopes it (source address, port, RSSI, sequence) and publishes it northbound through the uplink callback, which you wire to MQTT / HTTP / WebSocket / UDP. A command runs the other way via pc_gateway_downlink() to the port's transmit callback (the radio's SPI / UART write). pc_gateway_topic() formats a routing key <prefix>/<port>/<addr>; a per-port uplink rate cap and fail-closed drops (no sink / unknown port / exceeded cap / refused, all counted via pc_gateway_get_stats()) keep it bounded. The radio transmit and the northbound publish are callbacks (the seam a real radio driver / protocol binding plugs into), so the bridge is host- and device-testable with no radio. Zero-heap static tables (PC_GW_MAX_PORTS). Host-tested (services/net/gateway) + HW-verified on a DevKitV1: 690k+ radio frames bridged northbound over DMA + the FORWARD lane with exact accounting (up_in == published) and zero payload-integrity errors while an HTTP server was stress-loaded on the same core. This is the framework the per-radio gateways (LoRa, Zigbee, ...) on the roadmap plug into. See src/services/net/gateway/gateway.h.
PC_ENABLE_RADIO_POWER
Opt-in radio power controls. Default off. services/system/radio_power applies the WiFi modem-sleep mode and an optional max-TX-power cap in one call (esp_wifi_set_ps / esp_wifi_set_max_tx_power) - trade throughput/latency for lower average power on a battery device. The mode names are host-tested; the apply is ESP32-only.
PC_ENABLE_RADIO_SNIFF
Opt-in receive-only radio channel sniffer to pcap. Feeds frames pulled off the air by the RF gateway drivers (CC1101 / LoRa / 802.15.4) in receive-only mode into the capture pipeline: services/radio/radio_sniff wraps each 802.15.4 MAC frame in the Wireshark TAP pseudo-header (carrying per-frame RSSI + channel) and a pcap record so the forwarded stream is a valid .pcap. pc_radiosniff_global writes the DLT-TAP global header and pc_radiosniff_tap_record writes one record. Pure framing (no heap/stdlib); the radio drivers own the receive. Default off.
PC_ENABLE_RANGE
HTTP Range requests / 206 Partial Content for served files. Default off. When set (requires FILE_SERVING), serve_file() / serve_static() honor a single-range Range: bytes=... request header: they answer 206 Partial Content with a Content-Range header and stream only the requested bytes (seeking the file to the start offset), advertise Accept-Ranges: bytes on full responses, and answer an unsatisfiable range with 416 Range Not Satisfiable. This enables resumable downloads and media seeking. Multi-range (multipart/byteranges) requests are not supported - the server falls back to a full 200 response, which is RFC 7233 §3.1 compliant.
PC_ENABLE_RAWL2
Opt-in raw Layer-2 Ethernet frame codec. When set, services/fieldbus/rawl2 builds/parses Ethernet II + 802.1Q VLAN frames (no FCS - the MAC appends it; pc_eth_fcs is provided for the cases that need it), so the app can inject/receive arbitrary L2 frames via esp_eth_transmit / esp_wifi_80211_tx - the basis for the raw-L2 industrial protocols (PROFINET DCP, GOOSE, POWERLINK). Pure codec, host-tested. Default off.
PC_ENABLE_REDIS
Redis RESP2/RESP3 wire codec. Default off. services/iot/redis_resp lets a device drive a Redis server over the shipped outbound client transport: pc_resp_encode_command builds a command (an array of bulk strings, binary-safe via explicit arg lengths) and pc_resp_parse is a streaming cursor reply decoder covering RESP2 (simple / error / integer / bulk / array / nil) plus the RESP3 additions (null / boolean / double / big number / bulk error / verbatim / map / set / push); aggregates report a child count and the caller walks each child, so nested replies decode with no heap. Pure and host-tested against Redis spec vectors and verified live against a real redis-server (SET/GET, arrays, and a RESP3 map from HELLO 3); the connection is the application's. See src/services/redis_resp.h.
PC_ENABLE_RELAY
TCP relay / DNAT port forwarding. Default off. services/net/relay publishes an internal host:port through the server: an inbound (accepted) connection is relayed to an origin (an outbound pc_client connection to the internal service), moving bytes in both directions so the device fronts a service that lives behind it. pc_relay_step is a pure, non-blocking byte pump over two send/recv seams: the app calls it each poll tick until it returns PC_RELAY_DONE, then closes both sockets. It handles backpressure (a send seam that accepts a partial write carries the rest in a per-direction buffer and retries before reading more) and independent half-close (each direction finishes when its source hits EOF and drains; the opposite peer's optional shutdown seam is then called once to propagate the FIN, and the relay is done only when both directions have finished). Zero heap - each active relay owns two PC_RELAY_BUF buffers. Transports that report a close out of band (an on_close event rather than a short read - e.g. the server's pc_conn) call pc_relay_note_eof to finish a direction cleanly. Host-tested with two mock sockets covering a bidirectional transfer, the backpressure carry, half-close with shutdown propagation, a large multi-step byte-exact transfer, a seam error, and the out-of-band EOF path (native_relay, 6 cases). The server-side listener (src/services/net/relay/relay_listener) wires it in: after server.listen(port, PROTO_RELAY) you call pc_relay_publish(listener_id, origin_host, origin_port), and a PROTO_RELAY connection handler dials the origin through pc_client on each inbound accept, pumps pc_relay_step from the server's poll loop, and tears both sockets down on close - so the app publishes an internal service in two calls (a fixed static bind/bridge table, zero heap; opt-in twice - compiled out by default and inert until you publish). Verified on ESP32 via the PortForward example build. See src/services/net/relay/relay.h and relay_listener.h.
Exact, wildcard (/*), :param path parameters, bounded allocation-free regex routes, and per-interface STA/softAP route filters. Always on.
PC_ENABLE_RTC
I2C real-time-clock driver (DS1307 / DS3231). Default off. services/peripherals/rtc reads and sets a battery-backed RTC over I2C (Wire, address 0x68), so the device knows the correct wall-clock time the instant it boots - offline, across power loss - with no network. It is the ideal middle of a time-source chain (GPS -> RTC -> upstream NTP), feeding pc_time_now() and the NTP server; pc_rtc_time_source() plugs straight into pc_time_source_add(). The BCD <-> Unix-epoch conversion of the seven time registers (12/24-hour, leap years, the clock-halt / century bit masks, range validation) is pure and host-tested across the chip's 2000-2099 range (that round-trip test caught a real 32-bit overflow past 2038); only the register read/write touches I2C. Zero heap. Example Rtc. See src/services/peripherals/rtc/rtc.h.
PC_ENABLE_SAFETY_SCL
IEC 61784-3 black-channel Safety Communication Layer primitives. Default off. The functional-safety profiles - PROFIsafe (IEC 61784-3-3), CIP Safety (-3-2), FSoE (-3-12), IO-Link Safety - all work the same way: they treat the underlying fieldbus as an untrusted "black channel" and layer their own end-to-end checks on top, so the transport may corrupt, lose, duplicate, delay or reorder frames without defeating the safety function. Three mechanisms do that work and are common to every profile: a CRC signature over the safety payload, a monitoring / consecutive counter, and a receive watchdog. services/machine_tool/safety_scl lands the counter state machine and the watchdog, plus the fail-safe state machine that combines them with the caller's signature verdict, so each profile's codec composes these instead of reimplementing them. pc_scl_on_frame accepts a frame only when the signature is good and the counter is exactly the expected next value - which is what makes one comparison sufficient for four distinct failure modes, since a lost or inserted frame runs the counter ahead, a duplicate repeats it, and a reordered pair sends it backwards. pc_scl_poll runs the receive watchdog every cycle including cycles with no frame, because a silent channel is itself a fault rather than a stale-but-happy reading; it does not arm until the first frame has been accepted, since a connection that has never received anything is starting up rather than timing out. Fail-safe latches: any failure puts the connection in FAILSAFE and it stays there until an explicit pc_scl_reset - a safety layer that silently reheals would let an intermittent fault present as a working link, which is precisely what a SIL rating exists to exclude, so even a subsequent perfectly good frame must not revive it. The first fault is preserved rather than overwritten by later ones, and the accepted/rejected tallies survive a reset so a flapping link stays visible. pc_scl_next_counter is exposed for the sender so both ends wrap a narrow consecutive number identically - the commonest way a hand-rolled counter desynchronizes after its first wrap. This module deliberately does not compute the CRC: every profile defines its own polynomial, width, seed and the order payload / counter / connection identifier are fed in, those constants live in paid standards, and a guessed CRC in a safety layer would look authoritative while silently failing to detect the very corruption it exists to catch - so the caller computes its profile's CRC and passes the verdict in as signature_ok, and this module owns the consequence, which genuinely is profile-independent. Pure and taking an explicit now like Hot-Swap Storage, so the whole machine is host-tested against a synthetic clock (native_safety_scl, 16 cases incl. all four black-channel failure modes, a millis() rollover, and the no-silent-reheal latch); every elapsed-time test is an unsigned difference, which is what makes the watchdog wrap-safe. No heap, no stdlib. See src/services/machine_tool/safety_scl/safety_scl.h.
PC_ENABLE_S7COMM
Siemens S7comm PDU codec. Default off. services/fieldbus/s7comm builds + parses the S7-300/400 communication PDUs carried inside a COTP Data TPDU (PC_ENABLE_COTP) over ISO-on-TCP (port 102): pc_s7_build_setup (Setup Communication), pc_s7_build_read_request (Read Var with S7-ANY items over the DB / I / Q / M areas, encoding the byte address as a 24-bit bit-address), pc_s7_build_write_request (Write Var - the same S7-ANY item specs plus a data section carrying, per item, a return code + data transport size + the value bytes, applying the same length-in-bits rule and even-item padding as the response reader), pc_s7_parse_header, and pc_s7_read_next_item which walks the response data items honoring the length-in-bits transport sizes (BIT/BYTE/INT) and the even-item padding. All constants (protocol id 0x32, ROSCTR, function/area/transport codes) are verified against the Wireshark S7comm dissector. Pure and host-tested; wrap the PDU with pc_cotp_build_dt + pc_tpkt_build. See src/services/fieldbus/s7comm/s7comm.h.
PC_ENABLE_SCPI
SCPI / IEEE 488.2 instrument-control codec. Default off. services/instrumentation/scpi is a zero-heap codec for the text command language nearly every modern bench instrument speaks (DMMs, oscilloscopes, power supplies, function/arbitrary generators, SMUs, spectrum/network analyzers, electronic loads) over a raw TCP socket on port 5025 (also USBTMC / VXI-11 / HiSLIP / serial). The codec is symmetric: as a controller it builds command lines with pc_scpi_build (a :-hierarchy header + comma-separated params + newline) and pc_scpi_common (the 13 mandatory IEEE 488.2 commands *IDN? / *RST / *CLS / *ESR? / *STB? / ...), then parses replies - pc_scpi_parse_number (NR1/NR2/NR3, hand-rolled, no stdlib), _parse_bool (1/0/ON/OFF), _parse_string (quote-stripping with doubled-quote collapse), and _parse_block (the definite #<n><len><data> and indefinite #0<data> arbitrary block used for waveform captures); as an instrument it carries the IEEE 488.2 status model - ScpiStatus (the Status Byte, Standard Event Status Register + its enable mask, the Service Request Enable mask, and the FIFO error/event queue) with pc_scpi_push_error (latching the CME/EXE/DDE/QYE/... ESR class bit from the error-number range and replacing the tail with -350 "Queue overflow" when full), pc_scpi_pop_error (the SYSTem:ERRor? action, 0,"No error" when empty), pc_scpi_stb (EAV/ESB/MSS computation), and pc_scpi_cls (*CLS) - plus pc_scpi_match, a SCPI short/long-form + numeric-suffix header matcher for dispatching an incoming command against a pattern like "SYSTem:ERRor?". Register bits, error-number classes, and the exact standard error strings are verified against the SCPI-1999 standard and IEEE 488.2-1992. Pure codec, host-tested; the TCP/USB/serial transport is the application's. See src/services/instrumentation/scpi/scpi.h.
PC_ENABLE_SDI12
SDI-12 sensor-bus codec. Default off. services/peripherals/sdi12 is a zero-heap command / response codec for the 1200-baud single-wire ASCII bus used by environmental / agricultural sensors (soil moisture, water level, weather). pc_sdi12_build and the pc_sdi12_build_measure / _measure_additional / _concurrent / _concurrent_additional / _continuous / _verify / _data / _identify / _ack / _change_address / _query_address helpers emit the standard <addr><command>! requests (_measure_additional / _concurrent_additional frame the aM<n>! / aC<n>! secondary measurement sets (1..9) a multi-parameter sensor exposes beyond the primary aM! / aC!, _continuous frames the aR<n>! / aRC<n>! continuous-measurement command whose sensor returns values with no service-request delay, and _verify the aV! start-verification command); pc_sdi12_parse_measure reads the atttn measurement response (seconds-until-ready + value count, both the 1-digit aM! and 2-digit aC! forms); pc_sdi12_parse_values splits a data response into floats; pc_sdi12_parse_identify decodes the aI! identify response into an Sdi12Identity (the fixed-width SDI-12 version, vendor, model, and sensor-version fields), so a bus scan reports what each sensor is; and pc_sdi12_crc16 / pc_sdi12_crc_encode / pc_sdi12_check_crc implement the SDI-12 CRC (poly 0xA001, encoded as 3 printable octets) for the CRC-protected aMC! / aCC! variants. Command set + CRC verified against the SDI-12 specification; pure and host-tested. Drive the single 1200-baud line over a UART and bridge sensor readings onto Wi-Fi. See src/services/peripherals/sdi12/sdi12.h.
PC_ENABLE_SENML
SenML (RFC 8428) measurement-pack builder. Default off; implies CBOR. services/iot/senml is a zero-heap SenML-JSON + SenML-CBOR encoder over the shipped JSON / CBOR writers: the caller fills a SenmlRecord array (optional base name / base time, name, unit, one value - number / string / boolean, optional time) and pc_senml_json_build / pc_senml_cbor_build emit the whole pack. SenML-JSON uses the text labels (bn/n/u/v/vs/vb/t); SenML-CBOR uses the integer labels (n=0, u=1, v=2, ..., bn=-2, bt=-3). Numbers that are integral are emitted as integers so timestamps keep full precision. On the consuming side, pc_senml_resolve (RFC 8428 §4.6) folds the base name and base time across a pack into SenmlResolved records - each with the full concatenated name and the absolute (base + record) time, the base fields carried forward until a later record overrides them - so a receiver gets standalone measurements. The standard measurement format for CoAP / LwM2M / HTTP telemetry; verified against the RFC example, and the resolver host-tested against a base-name/base-time pack. See src/services/iot/senml/senml.h.
PC_ENABLE_SEP2
Opt-in IEEE 2030.5 (Smart Energy Profile 2.0) resource codec. When set, services/energy/sep2 builds the core 2030.5 XML resource documents (DeviceCapability, EndDevice, DERControl) in the urn:ieee:std:2030.5:ns namespace, so the web server is a 2030.5 smart-grid server/client over the existing HTTP stack (DER dispatch / curtailment). Pure text framing. Default off.
PC_ENABLE_SERCOS
Opt-in SERCOS III motion-bus telegram codec. When set, services/fieldbus/sercos builds/parses the SERCOS III MDT/AT telegrams (type + phase + cycle + cyclic device data) the real-time drive/motion bus exchanges over raw L2 (ethertype 0x88CD, on the shipped services/fieldbus/rawl2), plus the IDN (IDentification Number) encode/decode for drive-parameter addressing. Pure codec (the isochronous timing + ring topology are hardware-gated). Default off.
PC_ENABLE_SHT3X
Sensirion SHT3x (SHT30/31/35) temperature / humidity sensor (I2C). Default off. services/peripherals/sht3x issues the single-shot measurement command (0x2400), then verifies the CRC-8 on each returned 16-bit word before trusting it (pc_sht3x_crc8: polynomial 0x31, init 0xFF - the Sensirion datasheet check value 0xBEEF -> 0x92 is a unit test) and converts the raw ticks with the linear map (T = -45 + 175*raw/65535, RH = 100*raw/65535) into signed integer milli-units (milli-degrees C, milli-percent RH), so there is no heap and no float printf. pc_sht3x_parse rejects the whole six-byte reading on any CRC mismatch. The CRC + conversion + parse are pure and host-tested (native_sht3x); only the command write / data read touches I2C. A cheap solder-and-test breakout (GY-SHT31) for environmental telemetry. Example Sht3x prints temperature and humidity. See src/services/peripherals/sht3x/sht3x.h.
PC_ENABLE_SIGFOX
Sigfox modem AT-command codec (v5 gateway plugin). Default off. services/radio/sigfox is the tiny-uplink half of a Sigfox-to-web bridge: a Wisol (SFM10R) / Murata Sigfox modem is driven by AT commands over UART for ultra-low-power telemetry over the Sigfox 0G network (a message is capped at 12 bytes and ~140/day, so uplinks are rare and small). pc_sigfox_build_uplink formats an AT$SF=<hex> command (uppercase hex of the payload) and pc_sigfox_parse_response classifies the modem's reply as OK, ERROR, or still pending. Uplink-only (the common Sigfox use - a device sends readings up, it is not addressed downlink). Pure text codec - you carry the bytes over your UART - and host-tested (the AT$SF encoding, its 12-byte / output-buffer bounds, and the response classification). Example SigfoxUplink sends a reading from a real modem. See src/services/radio/sigfox/sigfox.h.
PC_ENABLE_SIMATIC
Siemens SIMATIC serial point-to-point codec (3964R + RK512). Default off. services/fieldbus/simatic is a zero-heap implementation of the pre-Ethernet Siemens point-to-point link the S5/S7 PtP CP modules (CP 341 / CP 441 / CP 524 / CP 525) speak - so a device becomes a fixed-BSS collector/agent for legacy Siemens controls, alongside the shipped Fanuc FOCAS, Haas MDC, MELSEC, S7comm and DNC machine-tool codecs. Two layers. 3964R (the byte-oriented, half-duplex link protocol): pc_3964r_build_block frames a block as the DLE-doubled payload + DLE ETX + (on the "R" variant) the BCC - the longitudinal XOR (even parity, not DF1's 2's-complement sum) over the stuffed data and the DLE ETX, so a doubled DLE pair cancels (0x10 ^ 0x10 = 0); pc_3964r_parse_block un-stuffs and validates, fail-closed on bad framing / a lone DLE / a missing terminator / a BCC mismatch / overflow. On top, an owned link state machine (Simatic3964Ctx, no file-scope mutable) drives the interactive handshake: the sender emits STX and waits for the receiver's connect DLE within QVZ, sends the block, waits for the end DLE/NAK, and repeats the block up to 6× then the connection up to 6× on NAK/timeout; the receiver replies DLE to a partner STX, collects the block enforcing the ZVZ inter-character timeout, and answers DLE (ok) or NAK (bad); on a simultaneous STX collision the low-priority station yields to receive (a per-context priority bit). Timers run off the pluggable pc_millis() (injectable for host tests); no delay(). RK512 (the computer-link telegrams carried as the 3964R block payload): pc_rk512_build_send / pc_rk512_build_fetch build the fixed header (command + coordination byte + area code + DB number + big-endian start address + word count) plus the big-endian data words for SEND, and pc_rk512_parse_header / pc_rk512_parse_reaction decode a request header and a reaction telegram (status word 0 = ok, plus any FETCH-returned words). The 3964R control-char handshake, QVZ/ZVZ timeouts, priority arbitration and the XOR-BCC follow the Siemens "3964(R) transmission protocol" manual; the RK512 header field order + big-endian words follow the "RK 512 computer link" manual (the exact area/command byte values are pinned per CP-module doc). The two layers are cleanly separated (RK512 never touches DLE/STX; 3964R never interprets the telegram). Pure codec + one owned link context, host-tested (native_simatic, 14 cases across framing, the link state machine, and RK512) and cross-checked byte-for-byte against an independent python 3964R+RK512 reference implementation (test/servers/peers/simatic_peer.py); the RS-232 / RS-485 UART is the application's. MPI (token-bus network layer) and the generic PtP IF are a separate, larger follow-on. See src/services/fieldbus/simatic/simatic.h.
PC_ENABLE_SLEEP_SCHED
Opt-in dynamic sleep-cycle scheduler. When set, services/system/sleep_sched provides pc_sleep_next(): from the time since the last activity it returns how long a low-power device should sleep (0 = stay awake), ramping the window from a floor up to a ceiling the longer the idle streak runs. Pure decision core (the app applies the window via light / modem / deep sleep). Complements services/radio_power. Default off.
PC_ENABLE_SMB
SMB2 client wire codec (MS-SMB2). Default off. services/file_transfer/smb is the pure protocol layer of an SMB2 client so a device can read/write files on a Windows share - e.g. a CNC controller's .nc program store (Fanuc / Haas / Mazak / Heidenhain expose one). Increment 1 (no crypto): pc_smb2_transport_frame / pc_smb2_transport_len handle the Direct-TCP transport header (0x00 + a 24-bit big-endian length on port 445); pc_smb2_build_header / pc_smb2_parse_header build and validate the 64-byte little-endian SMB2 sync header (ProtocolId FE 53 4D 42 + StructureSize 64, exposing the command / status / MessageId / TreeId / SessionId); pc_smb2_build_negotiate offers the dialect list (SMB 2.0.2 / 2.1 / 3.0 / 3.0.2) with the client GUID; and pc_smb2_parse_negotiate_response extracts the chosen dialect, server GUID, max transact/read/write sizes, and the SPNEGO/NTLM security token (bounds-checked against the message). All fields little-endian; you own the TCP socket. Field layout verified against MS-SMB2 §2.2.1.2 / §2.2.3 / §2.2.4. Increment 2 adds the NTLM digests (src/crypto/md): MD4 (RFC 1320 - the NT hash primitive, MD4(UTF-16LE(password))), MD5 (RFC 1321), and HMAC-MD5 (RFC 2104 - the NTLMv2 MAC), streaming + zero-heap, KAT-verified against the RFC test vectors and the well-known NT hash of "password". (MD4/MD5 are cryptographically broken and are included only because NTLM requires them on the wire.) Increment 3 is the NTLMv2 response (src/services/file_transfer/smb/ntlm, MS-NLMP §3.3.2): pc_ntlm_nt_hash (MD4(UTF-16LE(password))), pc_ntlm_ntowfv2 (HMAC-MD5(NThash, UTF-16LE(Uppercase(user)+domain))), and pc_ntlm_v2_response, which builds temp (version + timestamp + client challenge + the server's target-info AV_PAIRs) and computes the NTProofStr / NtChallengeResponse / SessionBaseKey from the server challenge - verified byte-for-byte against the MS-NLMP §4.2 worked example. Increment 4 is the NTLMSSP message codec (src/services/file_transfer/smb/ntlmssp, MS-NLMP §2.2.1): pc_ntlmssp_build_negotiate (type 1), pc_ntlmssp_parse_challenge (type 2 - extracts the flags, the 8-byte server challenge, and the target-info AV_PAIRs, bounds-checked), and pc_ntlmssp_build_authenticate (type 3 - lays out the LM/NT responses + domain/user/workstation in the little-endian Len/MaxLen/Offset payload fields); an end-to-end test parses a CHALLENGE, computes the NTLMv2 response, and confirms the AUTHENTICATE carries it. Increment 5 is the SPNEGO GSS-API wrapping (src/services/file_transfer/smb/spnego, RFC 4178): a zero-heap definite-length DER codec that wraps the NTLMSSP NEGOTIATE token in a GSS-API InitialContextToken ([APPLICATION 0] + the SPNEGO OID 1.3.6.1.5.5.2 + a NegTokenInit advertising the NTLM mech OID 1.3.6.1.4.1.311.2.2.10), extracts the CHALLENGE from the server's NegTokenResp (skipping negState / supportedMech), and wraps the AUTHENTICATE in a reply NegTokenResp - the tokens SMB2 SESSION_SETUP carries. Verified byte-exact against a hand-computed vector, round-trip, and independently via openssl asn1parse. Increment 6 is the SMB2 SESSION_SETUP framing (src/services/file_transfer/smb/smb2, MS-SMB2 §2.2.5/§2.2.6): pc_smb2_build_session_setup builds the request (SecurityMode + PreviousSessionId + the SPNEGO security buffer at offset 88, echoing the server SessionId on the second round) and pc_smb2_parse_session_setup_response parses the response (StructureSize 9, SessionFlags, and the server's security buffer, bounds-checked) - the caller reads the SessionId and the STATUS_MORE_PROCESSING_REQUIRED / SUCCESS status from the header. An end-to-end test routes a full auth round through the framing (wrap an NTLMSSP NEGOTIATE in SPNEGO, frame it, then unwind a server response back through framing -> SPNEGO -> NTLMSSP and recover the server challenge intact). Increment 7 adds the file commands (src/services/file_transfer/smb/smb2, MS-SMB2 §2.2.9-§2.2.16): pc_smb2_build_tree_connect / pc_smb2_parse_tree_connect_response connect to a \\server\share UNC path (the TreeId comes back in the response header, the ShareType tells disk vs pipe vs print); pc_smb2_build_create / pc_smb2_parse_create_response open or create a file with a DesiredAccess / ShareAccess / CreateDisposition / CreateOptions set, returning the 16-byte FileId handle and the EndofFile size; and pc_smb2_build_close / pc_smb2_parse_close_response release the handle. Increment 8 completes the client with READ / WRITE (src/services/file_transfer/smb/smb2, MS-SMB2 §2.2.19-§2.2.22): pc_smb2_build_read / pc_smb2_parse_read_response read a length of bytes at a file offset (the response data is returned bounds-checked as a pointer into the message), and pc_smb2_build_write / pc_smb2_parse_write_response write a data buffer at an offset (the response reports the byte count). All little-endian, field layout verified against the MS-SMB2 request/response structures. Pure and host-tested (native_smb, 36 cases across the codec). This is the full read/write-a-file-on-a-share codec: NEGOTIATE -> SESSION_SETUP (NTLMv2 over SPNEGO) -> TREE_CONNECT -> CREATE -> READ / WRITE -> CLOSE. On top of the codecs, **smb_client** (src/services/file_transfer/smb/smb_client) is the dialogue engine that runs the exchange: smb_open drives NEGOTIATE, the two-round NTLMv2 SESSION_SETUP (building the NTLMSSP tokens, wrapping them in SPNEGO, echoing the server SessionId, extracting the server's MsvAvTimestamp for the NTLMv2 response), TREE_CONNECT to \\server\share, and CREATE - returning an SmbHandle (session / tree / file id + file size); smb_close releases it. smb_read and smb_write then move the file bytes on that handle, looping the READ / WRITE commands in PC_SMB_BUF-sized chunks (read stops at a short read / STATUS_END_OF_FILE; write grows the cached file size), giving a device a small POSIX-like open/read/write/close surface. It works against a send/recv seam (Direct-TCP framing handled internally), so the whole exchange - handshake and file transfer - is host-tested end to end with a scripted mock SMB2 server (native_smb, 46 cases total, including a byte-exact write-then-read round trip) and rides pc_client on the device. Increment 9 adds SMB 2.x message signing: the shared pc_sha256 / pc_hmac_sha256 (FIPS 180-4 + RFC 2104, KAT-verified against the FIPS and RFC 4231 vectors) from src/crypto, and pc_smb2_sign / pc_smb2_verify (MS-SMB2 §3.1.4.1/§3.1.5.1) set the SMB2_FLAGS_SIGNED flag, HMAC-SHA256 the message with its 16-byte Signature field zeroed, and write / constant-time-verify the MAC's first 16 octets - verified against a reference signature computed with Python's hmac/hashlib plus tamper / wrong-key / short-message rejection. Increment 10 wires the signer into **smb_client**: smb_open reads the server's NEGOTIATE SecurityMode and, when signing is required, derives the SMB 2.x session key (the NTLMv2 SessionBaseKey), signs the round-2 SESSION_SETUP, and marks the SmbHandle signing-active (unless the session is GUEST/NULL, MS-SMB2 §3.2.5.3.1); every later request (TREE_CONNECT / CREATE / READ / WRITE / CLOSE) is then HMAC-SHA256 signed and every response verified, failing closed on a missing or wrong signature - host-tested end to end with the mock server re-deriving the same key as a reference signing peer (a full signed read/write/close round trip, a tampered-response rejection, and an unsigned-when-not-required regression). Increment 11 adds the SMB 3.1.1 negotiate-context codec (src/services/file_transfer/smb/smb2, MS-SMB2 §2.2.3 / §2.2.3.1 / §2.2.4): pc_smb2_build_negotiate_311 offers the dialect list through 0x0311 with the mandatory PREAUTH_INTEGRITY_CAPABILITIES context (SHA-512 + a client salt) and a SIGNING_CAPABILITIES context advertising HMAC-SHA256, each 8-byte aligned; pc_smb2_parse_negotiate_contexts walks the response NegotiateContextList (located by NegotiateContextOffset / NegotiateContextCount) and extracts the negotiated preauth hash + salt, the signing algorithm, and the cipher, with every context header and its data bounds-checked (test_smb2). Increment 12 adds the SP800-108 counter-mode KDF (src/crypto/kdf, pc_kdf_ctr_hmac_sha256): HMAC-SHA256 as the PRF with a 32-bit counter before the fixed input (NIST SP800-108 §5.1), the primitive SMB 3.x uses to derive its signing and encryption keys (MS-SMB2 §3.1.4.2). The caller passes the assembled Label || 0x00 || Context || [L] fixed input, so the primitive stays independent of the per-key label choices; it is verified against the NIST CAVP KBKDF (KDFCTR) vectors, including a two-block case that exercises the counter loop and truncation. Increment 13 adds SHA-512 (src/crypto/sha512, FIPS 180-4): streaming pc_sha512_init / _update / _final plus a one-shot pc_sha512, the hash the SMB 3.1.1 preauth-integrity chain runs over the handshake messages, KAT-verified against the FIPS 180-4 vectors (including the 112-byte two-block example) with a streaming-equals-one-shot check. These primitives are assembled into the full 3.1.1 flow (the preauth-integrity hash chain over the concatenated NEGOTIATE / SESSION_SETUP messages + the per-key label/context assembly feeding the KDF + AES-CMAC signing, plus the NTLMSSP MIC), HW-verified against a real Samba. Increment 14 adds SMB 3.x transport encryption (MS-SMB2 §2.2.41 / §3.1.4.3-4): all four SMB 3.1.1 ciphers - AES-128-GCM / AES-256-GCM (src/crypto/aes128gcm, src/crypto/aesgcm) and AES-128-CCM / AES-256-CCM (the new src/crypto/aesccm, NIST SP800-38C) - wired into the TRANSFORM_HEADER codec (pc_smb2_encrypt / pc_smb2_decrypt dispatch on the negotiated cipher, selecting the 16/32-byte key and 12/11-byte GCM/CCM nonce), the §2.2.3.1.2 ENCRYPTION_CAPABILITIES negotiate (offer all four, and advertise SMB2_GLOBAL_CAP_ENCRYPTION so a required-encryption server negotiates a cipher), and the SP800-108 cipher-key derivation (SMBC2SCipherKey / SMBS2CCipherKey, [L]=128 or 256). smb_client derives the keys at SESSION_SETUP and encrypts every request / decrypts every response from TREE_CONNECT on, driven by a monotonic per-session nonce (never reused, so no replay); a client-forced mode (SmbConfig.encrypt, like smbclient -e) reaches a share whose server requires encryption. The CCM / 256-GCM primitives are KAT-verified against pyca/cryptography, and the whole path is HW-verified against a real Samba smb encrypt = required share with every cipher reading byte-exact. See src/services/file_transfer/smb/smb_client.h.
PC_ENABLE_SMTP_TLS
Secure SMTP: run the mail client over client-side TLS (needs TLS). Covers both SmtpSecurity::SMTP_TLS (implicit, port 465) and SmtpSecurity::SMTP_STARTTLS (the RFC 3207 in-band upgrade on the submission port, 587). Separate from PC_ENABLE_SMTP because the plain codec needs neither the TLS stack nor the client-session singleton, and it is what pulls SMTP into the PC_ENABLE_CLIENT_TLS derivation - without that every pc_tls_client_session_* call resolves to a stub that always fails. See src/services/net/smtp/smtp.h.
PC_ENABLE_SMTP
Outbound SMTP client (RFC 5321) for device email alerts. Default off. services/net/smtp runs a blocking one-shot send over the shared outbound client transport (pc_client): read the greeting, EHLO, optional AUTH LOGIN (username/password base64-encoded), then MAIL FROM / RCPT TO / DATA the message and QUIT. The body is dot-stuffed (RFC 5321 sec 4.5.2) and CRLF-normalized so it can never end early, and the whole exchange is zero-heap (fixed PC_SMTP_* buffers). SmtpConfig.security selects how the connection is secured - a SmtpSecurity enum (PLAIN / TLS / STARTTLS) rather than a bool, so "implicit and explicit at once" is unrepresentable. Implicit TLS (SMTPS, port 465) encrypts from the first byte; STARTTLS (RFC 3207, the submission port 587) issues STARTTLS after the first EHLO, upgrades the transport in place, and reissues EHLO because RFC 3207 sec 4.2 requires discarding capabilities learned in the clear. STARTTLS fails closed: if it is configured and the server does not advertise it, the send aborts with SMTP_ERR_NO_STARTTLS before AUTH, so an attacker stripping the capability line cannot downgrade the exchange into sending credentials in plaintext - and the capability is matched as a whole keyword, so "STARTTLSX" does not count. The upgrade is invisible to the dialogue engine: the transport owns one send/recv pair that flips to TLS underneath it, so no path can keep writing plaintext after the handshake. Both need PC_ENABLE_SMTP_TLS (which is what pulls in the client-TLS session). The dialogue engine (smtp_run) is written against a send/recv seam, so the full protocol - reply codes, AUTH, dot-stuffing, the terminating dot - is host-tested with a scripted mock server (env:native_smtp). SMS fallback needs no extra code (an email-to-SMS carrier gateway address). HW-verified on an ESP32-S3 against a live aiosmtpd submission server: the device connected in the clear to :587, saw STARTTLS advertised, upgraded, reissued EHLO encrypted, and the server logged DELIVERED from=esp32@... bytes=183 tls=True with the message body intact. Getting there fixed three latent bugs in a TLS path that had never been compiled - a missing <mbedtls/ssl.h> include, SMTP being absent from the PC_ENABLE_CLIENT_TLS derivation (so the session API resolved to stubs), and BIO callbacks dereferencing a ctx the TLS layer never passes them. Example SmtpAlert ships a from-scratch beginner walkthrough (including standing up a Postfix server). See src/services/net/smtp/smtp.h.
PC_ENABLE_SNMP
SNMP agent (v1/v2c, + v3 USM when SNMP_V3) over lwIP UDP. Zero-heap ASN.1 BER codec + a fixed MIB table on UDP/161. Default off. The BER codec itself is gated by this flag and is otherwise unit-tested standalone (env:native_snmp).
PC_ENABLE_SNMP_TRAP
Outbound SNMP notifications - traps and informs (requires SNMP). Default off. When set, src/services/net/snmp/pc_snmp_notify.h sends SNMPv2c (and, with SNMP_V3, SNMPv3 USM) Trap / InformRequest PDUs to a manager over UDP - so the agent can push alerts instead of only answering polls. Reuses the BER codec and the transport-layer UDP service; the PDU builder is host-testable.
PC_ENABLE_SNMP_V3
Add SNMPv3 USM (auth via HMAC-SHA, privacy via AES-128-CFB). Default off.
PC_ENABLE_SNP
Opt-in GE Fanuc SNP (Series Ninety Protocol) serial frame codec. When set, services/fieldbus/snp builds/validates the SNP master-slave serial frame ([control][length][data] [arithmetic-sum BCC]) for reading/writing registers on a GE Fanuc Series 90 (90-30/90-70) PLC over RS-485. Pure codec (the UART transport + SNP-X session are the device step). Default off.
PC_ENABLE_SOCKPOOL
Opt-in dynamic socket recycling: an LRU connection-slot pool. The transport-pool half of the adaptive-networking work: services/net/sockpool keeps a fixed table of connection slots and, when saturated, recycles the least-recently-used slot for a new peer (pc_sockpool_acquire returns the evicted id so the transport closes it), plus touch / release / find. The app owns the real sockets; this owns which slot a connection lives in and which to reclaim under pressure. No heap/stdlib. Default off.
PC_ENABLE_SOUTHBOUND
Opt-in southbound protocol-driver framework. The uniform seam every field-device driver plugs into so the app polls/drives any southbound device (a Modbus slave, a BACnet controller, a raw sensor over SPI/I2C/UART) through one facade: register a SouthboundDriver (a read/write/read_block/write_block vtable + its transport ctx), then address points by driver name via pc_southbound_read / _write / _read_block / _write_block. The block calls are the atomic multi-point (register-matrix) path. Bounded registry (PC_SOUTHBOUND_MAX_DRIVERS, default 8), no heap; Modbus master is the one such driver today. Default off.
PC_ENABLE_SPA_ROUTER
Opt-in single-page-app micro-routing decision. When set, services/web/spa_router provides pc_spa_route(): given a request path it returns whether to serve a real asset file, serve the SPA shell (index.html) for a client-side route, or pass through to the app's handlers under an API prefix - so a single-page UI's client routing works. Pure decision core (the caller wires the result into serve_static / the router). Fallback HMI: on a machine-control device the SPA is a convenience, not the contract, so pc_spa_route_ex() takes a pc_spa_ctx (shell available? client will script? device degraded?) and resolves a client route to PC_SPA_SERVE_FALLBACK - a plain server-rendered control page needing no JavaScript and no asset files - whenever the SPA cannot serve it. Two rules are what make that worth having: the API prefix keeps passing through in fallback mode (the page own controls post to those endpoints, so cutting them off would leave an operator unable to actuate anything), and asset requests are never rewritten (handing back HTML where the browser asked for CSS). Conditional UI streaming: that page is a fragment table, each entry with a predicate, emitted by pc_ui_stream_next() into a buffer of any size - it resumes mid-fragment, so a page far larger than the buffer streams out in pieces, and predicates run as the stream reaches each fragment rather than all up front, so a long render reflects the state that holds when it gets there. Host-tested (native_spa_router, 16 cases including byte-identical output across chunk sizes 1..40). HW-verified on an ESP32-S3: healthy routes served the shell; ?nojs=1, a missing shell asset, and a degraded device each served the fallback; and actuating through the still-live API while in fallback mode flipped two predicates in opposite directions on the next render (ALARM ACTIVE appeared, Status: normal disappeared) - only observable because the API kept working when the SPA did not. Example SpaFallback. Default off.
PC_ENABLE_SPARKPLUG
Sparkplug B payload + topic codec - the Eclipse Sparkplug B industrial-IoT format over MQTT. Default off; implies PROTOBUF (the payload is a Protobuf message). services/iot/sparkplug builds the topic (pc_spb_build_topic -> spBv1.0/group/type/node[/device]) and serializes the payload over the protobuf codec: pc_spb_build_metric emits one Tahu Metric (name / alias / timestamp / datatype + a value - int / long / float / double / boolean / string), and pc_spb_build_payload wraps the timestamp + metrics + seq. On the subscriber side, pc_spb_parse_payload decodes a received payload's timestamp + sequence number, pc_spb_payload_next_metric iterates its metric sub-messages, and pc_spb_parse_metric decodes each into an SpbMetricDecoded (name, alias, timestamp, datatype, and the value oneof) - so a device consumes NDATA / DDATA from a broker, not only publishes. Field numbers and datatype codes are verified against the Eclipse Tahu sparkplug_b.proto, and a build -> decode round-trip is host-tested. Pure; publish / subscribe the payload with the MQTT client. See src/services/iot/sparkplug/sparkplug.h.
PC_ENABLE_SQLITE
Opt-in SQLite3 on-disk file-format reader (services/storage/sqlite). Default off. This is file-format access, not the SQLite library: the documented SQLite3 database file structure parsed by hand - the 100-byte database header, the b-tree page header, the record varint, and record serial types - so a device can read a SQLite file (from pc_wal_fs / fs::FS) without the SQLite amalgamation, which needs a heap and stdio and does not fit the no-stdlib zero-heap model. It reads rows: cell pointers, the leaf-table cell (rowid + payload + overflow detection), a record cursor that yields each column's serial type and value bytes (with int/float decoders), and a multi-page table cursor that walks an interior b-tree over a table's rootpage in rowid order using a bounded descent stack and two page buffers (it pulls pages through a reader callback, so it works over a RAM image, pc_wal_fs, or fs::FS). Read-first (a bounded writer and overflow-page chains are later steps); pure (you hand it page bytes, it does no I/O) and host-tested against real databases produced by the sqlite3 CLI - reading the actual pc_sqlite_schema row column-by-column and scanning a 40-row, two-level b-tree - plus varint and serial-type spec vectors.
PC_ENABLE_SSE
Server-Sent Events push support.
PC_ENABLE_SSH
SSH server support (RFC 4253/4252/4254). Channels are multiplexed per connection (PC_SSH_MAX_CHANNELS, default 1), each routed by its recipient id with its own flow-control window. Beyond session channels, direct-tcpip (the ssh -L local-forward request) is parsed and routed through a normalized forwarding seam: the codec extracts the target host:port and routes channel data but does no TCP I/O itself. With PC_SSH_PORT_FORWARD set, the ssh_forward owner plugs into that seam and does the I/O - it opens the outbound TCP through the client transport (pc_client) and bridges bytes both ways, with an optional target policy callback, a per-poll target-to-client pump bounded by the channel window, and EOF/CLOSE propagation. Forwarding is opt-in twice over (compiled out by default, and inert until the app calls pc_ssh_forward_begin()) because any authenticated client could otherwise reach arbitrary hosts; with it off every direct-tcpip open is refused (no open relay).
PC_ENABLE_SSH_KEYBOARD_INTERACTIVE
Keyboard-interactive user authentication (RFC 4256) alongside password/publickey. When set (and SSH is on) the server advertises keyboard-interactive in the USERAUTH_FAILURE method list; on selection it sends one SSH_MSG_USERAUTH_INFO_REQUEST with a single non-echoed Password: prompt and verifies the client's SSH_MSG_USERAUTH_INFO_RESPONSE through the same pc_ssh_auth_set_password_cb callback - so it is the challenge-response face of password auth (the common OpenSSH -o PreferredAuthentications=keyboard-interactive / PAM-password case), not a second credential store. Per-slot exchange state lives in the auth owner: an INFO_RESPONSE with no armed exchange (or a replayed one) is refused, and the response is wiped from the stack after every attempt. Because it is password-backed it requires PC_SSH_ALLOW_PASSWORD. Default off. HW-verified on an ESP32-P4 vs OpenSSH 10: authenticated via keyboard-interactive with a byte-exact channel echo.
PC_ENABLE_SSH_ZLIB
SSH zlib@openssh.com / zlib compression (RFC 4253 sec 6.2), both directions. When set (and SSH is on) the server advertises zlib@openssh.com (delayed, OpenSSH's default) and zlib for the client-to-server and server-to-client lists alike, negotiating each independently. Server->client compresses every outbound payload through a single context-takeover DEFLATE stream (network_drivers/presentation/ssh/transport/ssh_zlib) - a persistent sliding window carried across packets, sync-flushed per packet, wrapped as a zlib stream - which a standard inflate() (OpenSSH) decodes. Client->server decompresses every inbound payload through the matching resumable inflate (network_drivers/presentation/ssh/transport/ssh_inflate): OpenSSH compresses its outbound with Z_PARTIAL_FLUSH, whose DEFLATE blocks end mid-byte and spill into the next packet, so the decoder carries the bit position + a 32 KB context-takeover window across packets and commits only whole blocks (it retains the un-decoded flush-block tail and re-decodes it next packet, so no mid-block Huffman/symbol state persists). zlib@openssh.com starts each direction after SSH_MSG_USERAUTH_SUCCESS; plain zlib at NEWKEYS. Both directions are one owner (network_drivers/presentation/ssh/transport/ssh_comp) the packet layer asks per packet - compress on send, decompress on receive - and inbound decompression is capped at the uncompressed payload limit so a decompression bomb fails closed. PSRAM-class (~80 KB/connection incl. the 32 KB inflate window): on ESP32 set PC_SSH_ZLIB_IN_PSRAM=1 on a PSRAM board or PC_SSH_ZLIB_ACK_DRAM=1 to accept the internal-DRAM cost. Default off. The s2c stream is HW-verified against OpenSSH 10.3 (ssh -o Compression=yes); the c2s inflate is verified byte-exact against golden vectors produced by real zlib (windowBits 15, Z_PARTIAL_FLUSH per packet; tools/gen_ssh_inflate_vectors.py), with HW interop against OpenSSH pending.
PC_ENABLE_SSH_SFTP
SFTP v3 server subsystem over SSH (draft-ietf-secsh-filexfer-02; requires SSH + File Serving). Default off. A client's subsystem "sftp" request opens an SFTP session and the device serves files from an fs::FS mount (SD / LittleFS / SPIFFS) bound via pc_ssh_sftp_begin(fs, root) - the standards-track southbound path for securely dropping files (e.g. NC / G-code programs) onto the device over the one authenticated SSH port. The channel layer recognizes the subsystem request, tags the channel, and routes its bytes to the binding; the binding accumulates SSH_FXP_* request packets from the channel byte stream and executes each against the filesystem, reusing the same file operations WebDAV and the static file server use: OPEN/CLOSE, READ (a short DATA the client re-requests), WRITE (streamed straight to the file as fragments arrive, so a transfer is never buffered whole), OPENDIR/READDIR (with ls -l-style longnames), STAT/LSTAT/FSTAT, MKDIR/RMDIR/REMOVE/RENAME, REALPATH, and SETSTAT (accepted, best-effort). Handles are a fixed BSS table (PC_SFTP_MAX_HANDLES), 4-byte indices; every path is checked for .. traversal via the shared server/fs_path.h guard before any filesystem call; zero heap beyond the fs layer's file handles. The wire codec (services/file_transfer/sftp) is pure and host-tested (native_ssh_sftp: frame round-trips, ATTRS + age of every field, length-framing, longname); the fs binding is HW-verified against the OpenSSH sftp client on an ESP32-S3 with an SD card (mkdir / put / ls / get / rename / rm, a 60 KB file byte-exact). Note: a READ returns at most one SSH packet of data (PC_SFTP_MAX_READ, kept within SSH_PKT_BUF_SIZE); raise both for throughput. Follow-ups: the SCP SOURCE (download) direction and a graceful SSH_MSG_DISCONNECT on teardown.
PC_ENABLE_SSH_SCP
SCP server over SSH - the legacy rcp protocol via exec "scp -t <path>" (requires SSH + File Serving). Default off. scp localfile admin@device:/path drops a file onto the fs::FS mount bound by pc_ssh_scp_begin(fs, root): the SINK direction (client -> device). The binding drives the rcp handshake over the exec channel - send a ready ack, read the C<mode> <size> <name> control line, ack it, stream the data straight to the file, read the trailing end-of-record byte, final ack - reusing the SFTP fs seam + the .. traversal guard; error/ack bytes ride the main channel (no CHANNEL_EXTENDED_DATA). One file per transfer (no -r). The rcp codec (services/file_transfer/scp) is pure and host-tested (native_scp: command + control-line parse/build, malformed rejection); the binding is HW-verified against the OpenSSH scp client on an ESP32-S3 with an SD card (a 60 KB upload byte-exact). Follow-ups: the SOURCE direction (scp device:/path localfile, device -> client - use sftp get meanwhile), recursive directory transfers, and the graceful teardown noted under SSH SFTP.
PC_ENABLE_SSH_CLIENT
Outbound SSH client + reverse tunnel - the mirror of the SSH server: the device dials OUT to a relay with a public address, authenticates, and requests a remote forward (tcpip-forward, RFC 4254 sec 7.1, the ssh -R seam), so a connection to the relay's forwarded port is tunnelled back to a local service on the device. This is how a device behind NAT / a firewall stays reachable over one authenticated SSH port (e.g. serve its web UI to the world via a cloud relay). Default off. The client negotiates the FULL modern suite and interoperates with any current SSH server (RFC 4253 sec 7.1 order): KEX mlkem768x25519-sha256 (PQ/T hybrid, when PC_ENABLE_PQC_KEX) and sntrup761x25519-sha512@openssh.com (PQ/T hybrid, when PC_ENABLE_SSH_SNTRUP761; as the KEM initiator it runs sntrup761 KeyGen+Decaps and derives over SHA-512), curve25519-sha256, ecdh-sha2-nistp256, diffie-hellman-group14-sha256; host keys ssh-ed25519 / ecdsa-sha2-nistp256 / rsa-sha2-512 / rsa-sha2-256, each verified against a caller-supplied pin (the SHA-256 of the relay's host-key blob - type-agnostic, no trust-on-first-use); ciphers chacha20-poly1305@openssh.com / aes256-gcm@openssh.com / aes256-ctr (+ hmac-sha2-256/512, ETM and E&M); the device authenticates with its own ssh-ed25519 key (publickey). Reuses the server's transport crypto through a role-aware packet layer (an is_client flag flips the send/receive key direction; the RFC 4253 sec 7.2 KDF letters are role-independent) - this file is the client state machine only, not a second crypto stack. Forwarded connections are bridged through a channel POOL (PC_SSH_CLIENT_MAX_CHANNELS, per-variant default) so concurrent / rapid requests each get a channel; each opens one local bridge connection, so the outbound pool auto-provisions PC_CLIENT_CONNS >= 1 + PC_SSH_CLIENT_MAX_CHANNELS. The tunnel runs in the caller's task and owns a dedicated scratch arena (PC_SCRATCH_SLOTS = workers + 1); drive pc_ssh_tunnel_begin()/poll()/end() from one task with enough stack for the negotiated KEX (curve25519/ed25519 ~10.5 KB; the ML-KEM hybrid ~16 KB; the sntrup761 hybrid ~32 KB - a dedicated 40 KB task with sntrup761 enabled, else 20 KB, not loop()'s 8 KB). HW-verified against OpenSSH 10.0 on an ESP32-S3: curve25519 KEX + ed25519 pin/auth + tcpip-forward + the forwarded-tcpip bridge to the device's :80, concurrent and rapid-sequential requests returning the device's response byte-for-byte.
PC_ENABLE_STATS
Runtime stats endpoint (uptime, request/error counts, pool usage, heap).
PC_ENABLE_STATSD
StatsD metrics client - push counters/gauges/timings/sets to a StatsD collector. Default off. services/iot/statsd emits metrics in the StatsD wire format (name:value|type, e.g. api.hits:1|c) over UDP via pc_udp_sendto to any StatsD-speaking backend (Graphite/StatsD, Telegraf, Datadog, InfluxDB), with counters (c), gauges (g, absolute or a signed +/- delta), timings (ms), and sets (s), plus an optional sample rate (|@0.1) and DogStatsD tags (|#env:prod). This is the push counterpart to the pull-based Prometheus /metrics endpoint - useful behind NAT/firewalls where nothing can reach in to scrape. The value/rate are rendered without printf float/64-bit formatting; the line builder (pc_statsd_format) is pure and host-tested, and the emit helpers are host-tested through the transport UDP capture seam. Zero heap. Example StatsdMetrics. See src/services/iot/statsd/statsd.h.
PC_ENABLE_STOMP
STOMP 1.2 frame codec. Default off. services/iot/stomp is a zero-heap codec for the Simple/Streaming Text Oriented Messaging Protocol: pc_stomp_build_frame() writes a frame (command + escaped key:value headers + blank line + NUL-terminated body) and pc_stomp_parse_frame() is a non-mutating cursor reporting the command, header key/value slices, and body (honoring content-length, tolerating \r\n line endings, and skipping broker heart-beats), with pc_stomp_header() lookup and pc_stomp_unescape() for the header escapes (\r \n \c \\). Drives CONNECT / SEND / SUBSCRIBE / MESSAGE / ACK against a broker (ActiveMQ / RabbitMQ / Artemis) over the shipped outbound client transport, or STOMP-over-WebSocket via the WS client. Pure and host-tested; the connection and subscription state are the application's. See src/services/stomp.h.
PC_ENABLE_STREAM_BODY
Opt-in streaming delivery of large request bodies. Default off. Normally a request body is buffered whole and handed to the route once complete; with this flag the HTTP/1.1 parser instead drives per-chunk stream hooks (begin / data / abort) as body bytes arrive, so an arbitrarily large upload - a firmware image, a WebDAV PUT, a large multipart part - is written straight to its destination (flash, a file) as it streams, without ever holding the whole body in RAM. This preserves the zero-heap, bounded-memory contract for uploads that exceed any fixed buffer: memory use is one RX_BUF_SIZE window regardless of body size. The canonical consumer is a WebDAV PUT under a DAV mount (dav_stream_put_begin / dav_stream_put_data / abort trampolines wire the parser hook to the file), and the OTA update path uses the same seam. Because the body is never fully buffered, it raises the minimum RX_BUF_SIZE (see src/board_drivers/board_profiles/derived_sizing.h). The parser streaming hook is host-tested (native_ota); the FS-backed WebDAV PUT path is hardware-verified. See src/network_drivers/presentation/http/http_parser/http_parser.cpp.
PC_ENABLE_SUNSPEC
SunSpec Modbus device-information-model codec. Default off. services/energy/sunspec is a zero-heap codec for the SunSpec Alliance register maps layered on the holding-register model: a model-chain walker (pc_sunspec_check_marker / pc_sunspec_begin / pc_sunspec_next_model - verify the SunS 0x53756E53 marker, then iterate each model's id / length / body to the 0xFFFF end model) plus typed point readers (pc_sunspec_u16, pc_sunspec_i16, pc_sunspec_u32, pc_sunspec_i32, pc_sunspec_string), and a map writer (pc_sunspec_write_marker, pc_sunspec_write_model_header, the point writers, pc_sunspec_write_end_model) for a device exposing its own map. Makes a solar inverter / meter / battery interoperable. Marker and header format verified against the SunSpec spec; pure and host-tested. Pairs with the Modbus service. See src/services/energy/sunspec/sunspec.h.
PC_ENABLE_SYSLOG
Syslog client (RFC 5424 over UDP). Default off. When set, the device can ship log lines to a remote syslog server (e.g. rsyslog / journald / a SIEM) as RFC 5424 UDP datagrams via the transport-layer UDP service - a zero-heap structured-logging sink for fleets of constrained devices. See src/services/net/syslog/syslog.h.
PC_ENABLE_TELEMETRY
Telemetry math helpers (moving-window stats, rate-of-change, totalizer). Default off. When set, src/services/iot/telemetry/telemetry.h provides zero-heap pure-computation helpers over caller-supplied storage: a moving-window stats accumulator (mean / variance / stddev / min / max), a derivative / rate-of- change tracker, and a trapezoidal run-time totalizer. No ESP32 dependency, so the whole cluster is host-testable; it feeds dashboards, alert triggers, and odometer-style counters.
PC_ENABLE_TELNET
Telnet server support (RFC 854 / IAC option negotiation).
{{var}} response templating via send_template(). Always on.
PC_ENABLE_THEMES
Embed the theme stylesheet library as runtime-selectable blobs (default off). Off by default: build-time theme injection (<!--#theme NAME-->) costs nothing extra, but embedding the whole library for runtime switching links every theme's CSS into flash (~1 KB each). When set, application/binary_asset_blobs.{h,cpp} exposes pc_theme_css(name) + the registry PC_THEME_BLOBS, so a route (e.g. /themes/<name>.css) or a picker can switch themes live. Regenerate with web_assets/wizard/gen_theme_blobs.py after adding a theme.
PC_ENABLE_THREAD
Thread spinel / HDLC-lite framing codec (v5 gateway plugin). Default off. services/radio/thread is the HDLC-lite data-link layer that carries spinel frames to an OpenThread radio co-processor (an nRF52840 / EFR32 RCP) over UART, so an 802.15.4 / Thread mesh is bridged to IP and the web (the basis of a Thread / Matter border router). HDLC-lite appends an FCS, byte-stuffs the reserved bytes (Flag 0x7E, Escape 0x7D, XON, XOFF), and terminates with the Flag. The FCS is the HDLC frame check sequence - CRC-16/X-25 (poly 0x1021 reflected, init 0xFFFF, reflected in/out, final XOR 0xFFFF), transmitted low byte first, distinct from Zigbee's ASH CRC. pc_spinel_frame_encode wraps a payload; pc_spinel_frame_decode finds the flag, removes the stuffing, and verifies the FCS (returning the bytes consumed, need-more, or a resync signal); pc_spinel_fcs is the shared checksum. On top of the framing there is a spinel command layer: pc_spinel_pack_uint / pc_spinel_unpack_uint handle spinel's packed-unsigned-integer encoding (7 bits/byte, little-endian, continuation bit), and pc_spinel_command_build / pc_spinel_command_parse assemble and split a property command (header | CMD | PROP | value) - so you can issue a PROP_VALUE_GET/SET or read a PROP_VALUE_IS update, not just move opaque frames. On top of that there is a property registry + value semantics: SpinelProp / SpinelStatus name the property ids and status codes a gateway acts on, pc_spinel_prop_lookup / pc_spinel_prop_name / pc_spinel_status_name resolve an id to its name and primary datatype, and a typed cursor (SpinelReader / SpinelWriter with an accessor per spinel datatype - get/put for bool, uint8/int8, uint16/int16, uint32/int32, packed-uint, EUI64, IPv6, UTF8 string, raw data, and length-prefixed data) decodes or builds a property value field by field, with out-of-bounds reads latched into an error you check once. So NCP_VERSION decodes to its version string, PROTOCOL_VERSION to its two packed ints, HWADDR / MAC_15_4_LADDR to an EUI64, MAC_15_4_PANID to a uint16, and LAST_STATUS to a named status. Pure - you carry the bytes over your UART - and host-tested against the CRC-16/X-25 catalog check value (0x906E), the byte-stuffing round trip, the packed-int values (127 -> 7F, 128 -> 80 01, 1337 -> B9 0A), the full command -> frame -> decode -> parse stack, and the typed value round trips + registry lookups. Example ThreadGateway decodes named properties and bridges a real RCP. Verified two ways: on real hardware against an ESP32-C6 running ESP-IDF ot_rcp (a native 802.15.4 radio), where the codec drove a spinel RESET and decoded 8/8 properties off the wire - NCP_VERSION (openthread-esp32/...; esp32c6; ...), PROTOCOL_VERSION 4.3, INTERFACE_TYPE 3, CAPS (7 entries incl. a multi-byte packed 513), the factory HWADDR EUI64, PHY_CHAN 11, PANID, MAC_15_4_LADDR; and against the OpenThread reference RCP (OpenThread's own ot-rcp over a pty) for protocol conformance against the reference implementation. See src/services/radio/thread/thread.h.
PC_ENABLE_TIME_SOURCE
Multi-source time fallback (NTP / RTC / GPS /... by priority). When set, src/services/timing_position/time_source/time_source.h provides a small registry of user-defined time sources, each a callback returning Unix epoch seconds (0 when that source has no valid time). pc_time_now() queries them in priority order (lowest value first) and returns the first valid result, so the device falls back automatically when its preferred clock is unavailable. Pure and zero-heap (a fixed source table); host-testable. Default off.
PC_ENABLE_TLS
TLS (HTTPS/WSS) via mbedTLS with a static memory pool (ESP32-only). When set, the server can accept TLS connections using mbedTLS configured with MBEDTLS_MEMORY_BUFFER_ALLOC_C over a fixed BSS arena (PC_TLS_ARENA_SIZE) - no system heap, so the determinism guarantee is preserved. The TLS engine is compiled only on Arduino/ESP32 (mbedTLS is not part of the native build). Default off.
PC_ENABLE_TLS_POLICY
Opt-in TLS version negotiation + pinned cipher-suite policy. A policy layer on top of the mbedTLS-backed transport TLS (which already runs the 1.2 / 1.3 record + handshake): services/security/tls_policy pins the version to an audited [min,max] and makes the negotiated version observable (pc_tls_negotiate_version / pc_tls_version_name), and pins the cipher suites to an audited allowlist selected by server preference (pc_tls_select_cipher), with an AEAD-only classifier (pc_tls_is_aead) for a hardened profile. Pure, host-tested; the app feeds the results to the mbedTLS config. Default off.
PC_ENABLE_TLS_RPK
Raw Public Keys (RFC 7250) for the hand-rolled TLS 1.3 stack (requires DTLS or HTTP/3). Default off. A cert-less TLS credential: when a client offers the server_certificate_type extension (IANA 20) with RawPublicKey(2), the server answers with that certificate type in EncryptedExtensions and sends a Certificate message whose entry is a bare DER SubjectPublicKeyInfo (the 44-byte Ed25519 SPKI, RFC 8410) instead of an X.509 chain. The same Ed25519 key still signs CertificateVerify, so there is no security downgrade - only a smaller handshake with no certificate to parse, the natural fit for provisioned, key-pinned ESP32 fleets and the RFC 7252 §9 CoAP-over-DTLS RawPublicKey profile. Purely additive and server-side: a client that does not offer the extension still receives the X.509 certificate, and the handshake never requests a client certificate. Wired into the DTLS 1.3 handshake (pc_dtls_conn); the shared TLS 1.3 codec (pc_tls13_msg) also carries it for a future HTTP/3 use. The SPKI encoder, the RawPublicKey Certificate, the server_certificate_type EncryptedExtensions, and the ClientHello parse are pure and host-tested (native_dtls_tls13), and an end-to-end DTLS 1.3 handshake completes with the server presenting the raw key and the client verifying CertificateVerify against it (native_dtls_conn). Verified against two independent reference implementations: the wolfSSL DTLS 1.3 client in RawPublicKey-only mode (--rpk, so it rejects an X.509 answer) and the CycloneSSL DTLS 1.3 client (whose RFC 7250 verify callback fires only on a real RawPublicKey) each complete a full handshake and an application-data round trip with the server (test/servers/pc_dtls_wolfssl, test/servers/cyclone_dtls) - proof the presented SubjectPublicKeyInfo is wire-conformant, not self-consistent. Pure, zero-heap. See src/network_drivers/presentation/http/http3/pc_tls13_msg.h.
PC_ENABLE_TLS_RESUMPTION
TLS session resumption via RFC 5077 session tickets (requires TLS). Default off. When set, the TLS 1.2 server issues encrypted session tickets and accepts them on reconnect, so a returning client completes an abbreviated handshake (no certificate or full key exchange) - much faster and far less CPU than the ~RSA/ECDHE full handshake. Resumption is stateless: the session state lives in the client's ticket, sealed with a server-held key, so there is no growing per-session cache (the determinism / zero-heap-growth guarantee holds; only a small fixed ticket key and a little arena headroom are added). The ticket key rotates automatically on the PC_TLS_TICKET_LIFETIME_S schedule. Needs the mbedTLS build to provide MBEDTLS_SSL_TICKET_C (stock arduino-esp32 does).
PC_ENABLE_TOTP
Opt-in TOTP two-factor auth (RFC 6238). Default off. services/security/totp computes and verifies time-based one-time passwords (HMAC-SHA1 over the existing SHA-1, Google Authenticator compatible) and decodes base32 shared secrets, for a second factor on top of password / JWT auth. Pure and host-tested against the RFC 6238 vectors; the verifier checks a +/- step window for clock skew.
PC_ENABLE_TRACE_CAPTURE
Pre/post-trigger sample-window assembler - the high-rate acquisition primitive that sits downstream of services/system/dma on a sampling front end. Default off. pc_tc_feed() is called with every batch of samples as they arrive (most naturally from a DMA-complete handler) into a continuously running pre-trigger ring that always holds the most recent pretrigger_samples. When pc_tc_trigger() fires - a GPIO ISR, a software threshold detector, or an external front-end's own trigger line - the ring's current contents become the pre-trigger half of the emitted window and subsequent feeds fill the post-trigger half, so the window straddles the trigger instant exactly like a benchtop oscilloscope's pretrigger/posttrigger split, even though the trigger is only detected after the pre-trigger samples already went by. One capture in flight at a time, fail-closed: a trigger while a window is still filling is rejected and counted (triggers_dropped), never queued or stomped - the same determinism contract as services/system/dma's one-TX-in-flight rule. Static storage, no heap. The natural source is an external ADC front-end (an AD9226/AD9238 draining over SPI or UART DMA) or a scope digitizer. Host-tested (native_trace_capture). See src/services/system/trace_capture/trace_capture.h.
PC_ENABLE_UBX
u-blox UBX binary GNSS protocol codec - the binary companion to NMEA 0183 that u-blox receivers speak on the same UART. Default off. services/timing_position/ubx is a zero-heap codec for the UBX frame B5 62 <class> <id> <len-LE> <payload> <CK_A> <CK_B>: pc_ubx_build emits a frame (adding the sync chars, the little-endian length, and the 8-bit Fletcher checksum), pc_ubx_build_poll emits the zero-length poll request that asks a receiver to send a message, pc_ubx_parse validates one frame (sync chars, declared length against the buffer, and checksum) and exposes its class/id/payload, pc_ubx_ack decodes UBX-ACK-ACK / -NAK, and pc_ubx_u16 / pc_ubx_u32 / pc_ubx_i32 read little-endian payload fields. pc_ubx_parse_nav_pvt decodes the all-in-one UBX-NAV-PVT navigation solution (the message most applications read) into a pc_ubx_nav_pvt - GPS/UTC time with validity + accuracy, fixType + gnssFixOK + satellite count, lat/lon (1e-7 deg) and ellipsoid/MSL height with horizontal/vertical accuracy, NED + ground velocity with heading, and pDOP - verified field-by-field against a u-blox-8 reference frame. pc_ubx_parse_nav_sat + pc_ubx_nav_sat_get decode the variable-length UBX-NAV-SAT (per-satellite signal + usage): the header gives the satellite count, then each block yields the GNSS/SV id, carrier-to-noise (dB-Hz), elevation/azimuth, pseudorange residual, and the flags (signal-quality indicator + the used-in-solution bit), with the declared length checked against the block count. pc_ubx_parse_nav_timeutc decodes UBX-NAV-TIMEUTC (the receiver's validated UTC time solution) into a pc_ubx_nav_time_utc - iTOW, the estimated time accuracy (ns) and the sub-second nano correction, the broken-down UTC year/month/day/hour/minute/second, and the validity byte (validTOW / validWKN / validUTC), where the validUTC bit means the leap-second count is resolved so the wall-clock fields are trustworthy - the message a PTP grandmaster or an SNTP source reads to discipline its clock. To configure the receiver, pc_ubx_build_cfg_msg emits a CFG-MSG that sets how often a given message is output (rate 0 disables it, N sends every Nth solution) and pc_ubx_build_cfg_rate a CFG-RATE that sets the measurement period + navigation rate (e.g. 200 ms / 5 Hz) and the time reference - so an app turns on NAV-PVT / NAV-SAT and dials the update rate, then decodes what it asked for. Because a u-blox module multiplexes UBX with ASCII NMEA on one link, pc_ubx_stream_feed is a byte-at-a-time demultiplexer: it pulls complete, checksum-valid UBX frames out of the stream and hands every non-UBX byte back to the caller for an NMEA line assembler (so one UART carries both, with an over-long-frame guard bounded by PC_UBX_MAX_PAYLOAD). Frame framing + Fletcher checksum verified against the published u-blox poll frames (UBX-MON-VER B5 62 0A 04 00 00 0E 34, UBX-CFG-PRT ... 06 18); pure and host-tested (native_ubx). GNSS receivers are cheap UART breakouts, so this is a plain HardwareSerial link; send config/poll as UBX, read fixes as UBX or NMEA. Pairs with services/nmea0183. Example UbloxGnss. See src/services/timing_position/ubx/ubx.h.
PC_ENABLE_UDP_TELEMETRY
Opt-in fire-and-forget UDP telemetry cast. Default off. When set, services/iot/udp_telemetry casts metric lines (InfluxDB line protocol: measurement field=val,field2=val2) to a configured collector over UDP via pc_udp_sendto - zero-heap, fire-and-forget (no ACK, no retry), ideal for shipping device metrics to Telegraf/InfluxDB/a log sink. The line builder is pure and host-tested; only the send touches the network.
PC_ENABLE_UPLOAD
Streaming file upload: POST a body straight to a file on the filesystem. Default off. When set, src/services/upload_service.h registers a POST route that streams the request body directly into an Arduino FS file (LittleFS / SPIFFS / SD) - the upload never has to fit in RAM. Reuses the same parser streaming-body hook as OTA. Enabling upload (or OTA / WebDAV) raises RX_BUF_SIZE to the streaming floor of 8192 automatically (in board_drivers/board_profiles/derived_sizing.h, regardless of a board profile's smaller pin), comfortably above a full TCP receive window - the transport refuses-and-redelivers a segment that will not fit the receive ring (lossless backpressure), so a ring below one window would otherwise stall. Override RX_BUF_SIZE higher for very large windows; the 1024 base suits ordinary requests, not uploads.
PC_ENABLE_UTMC
Opt-in UTMC (Urban Traffic Management and Control) common-database codec. When set, services/transportation/utmc builds/parses the UTMC common-database HTTP+XML messages - a UTMCRequest for an object id and a UTMCResponse carrying the object value + a data-quality flag + a timestamp - the UK modular framework for sharing traffic data across municipal systems, over the existing HTTP server. Pure text framing. Default off.
PC_ENABLE_VFS
Unified virtual filesystem wrapper. Default off. services/storage/vfs exposes one small file API (open/read/write/close, exists/size/remove/rename, whole-file helpers) over a pluggable backend, so a feature can target storage without knowing the medium. A built-in zero-heap RAM backend (fixed BSS pool - deterministic, host-identical) ships for scratch / tests; an Arduino-FS backend (ESP32) wraps a real fs::FS (LittleFS / SD / SPIFFS) for persistence. Mount one at startup; the API fails closed otherwise. Pool dimensions are tunable in protocore_config.h (PC_VFS_RAM_FILES, _RAM_FILE_SIZE, _MAX_OPEN, _NAME_MAX).
PC_ENABLE_VL53L0X
Opt-in VL53L0X optical time-of-flight ranging sensor. A field-perturbation sensing peripheral for contactless distance / gesture: services/peripherals/vl53l0x decodes the ST VL53L0X ranging registers - pc_vl53l0x_range_mm combines the range byte pair, pc_vl53l0x_data_ready decodes the interrupt-status byte, and pc_vl53l0x_range_valid checks the device range-status field; the ESP32 binding verifies the model id, starts continuous ranging, and reads the distance over I2C. Default-settings ranging (ST's tuning blob is not applied). Pure codec host-tested. Default off.
PC_ENABLE_WAMP
WAMP messaging codec. Default off. services/iot/wamp is 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 / UNREGISTER / YIELD / GOODBYE (JSON arrays emitted via the shared JsonWriter - Options/Details default to {}, Arguments / ArgumentsKw are passed as JSON literals), plus a nesting-aware positional parser (pc_wamp_get_type, pc_wamp_get_uint, pc_wamp_get_uri, pc_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 and host-tested. It rides the shipped WebSocket layer; the session / subscription / registration tables are the application's. See src/services/iot/wamp/wamp.h.
PC_ENABLE_WAVE
Opt-in IEEE 1609 WAVE (WSMP + 1609.2 envelope) codec. When set, services/transportation/wave builds/parses the IEEE 1609 vehicular-radio framing that carries J2735: the 1609.3 WSMP header (version + P-encoded PSID + length + payload) and the 1609.2 secured-message envelope header (version + content type). Pairs with services/j2735. Pure codec (the DSRC / C-V2X radio is an external module). Default off.
PC_ENABLE_WEARLEVEL
Opt-in flash wear-leveling slot selector. When set, services/storage/wearlevel provides pc_wearlevel_pick(): given per-slot write counts it returns the least-worn slot to write next, so repeated flash/NVS writes spread evenly and the region ages together instead of burning out one block. Pure core (the app owns the slots + persisted counts). Default off.
PC_ENABLE_WEB_TERMINAL
Browser "web serial" terminal over WebSocket (src/services/web/web_terminal). Serves a self-contained terminal page and a WebSocket endpoint: device output is broadcast to all connected browsers, browser input is delivered to a command callback. Requires WEBSOCKET. Default off.
PC_ENABLE_WEBDAV
WebDAV server (RFC 4918, class 1 + class 2 locking) over the file system. Default off. When set (requires FILE_SERVING), dav() mounts an FS subtree that answers the WebDAV methods - OPTIONS, PROPFIND (Depth 0/1), PROPPATCH, GET, HEAD, PUT, DELETE, MKCOL, COPY, MOVE, and LOCK/UNLOCK - so a client (rclone, cadaver, curl, or a mounted network drive) can browse and edit files. PROPFIND returns a 207 Multi-Status document built into a fixed buffer (PC_WEBDAV_BUF_SIZE); a Depth-1 listing is capped at PC_WEBDAV_MAX_ENTRIES children. PROPPATCH returns a 207 with each requested property refused 403 Forbidden (read-only live properties, no dead-property store) so Explorer/Finder - which PROPPATCH a timestamp right after a PUT - do not error on a 405. PUT streams the request body straight to the file (via the shared streaming-body sink), so an upload is not bounded by BODY_BUF_SIZE. Locks are enforced by a small fixed lock table (PC_DAV_LOCK_MAX): LOCK stores an exclusive or shared token (Depth 0 or infinity), a write (PUT/DELETE/MKCOL/COPY/MOVE) to a locked resource without the matching token in its If header is refused 423 Locked, and UNLOCK requires the token. Each lock carries a Second-3600 timeout and is swept once it expires; a LOCK presenting the token in its If header refreshes it. See docs/SECURITY.md before exposing it.
PC_ENABLE_VXI11
VXI-11 (TCP/IP Instrument Protocol) codec over ONC RPC / XDR. Default off. services/instrumentation/vxi11 is a zero-heap codec for the legacy LXI instrument transport that predates HiSLIP (PC_ENABLE_HISLIP) - VXI-11 rides on ONC RPC (Sun RPC, RFC 5531) with XDR (RFC 4506) over TCP, carrying SCPI (PC_ENABLE_SCPI). It provides the reusable ONC-RPC framing - pc_rpc_record_mark / pc_rpc_parse_record_mark (the TCP record-marking header) and pc_rpc_parse_reply (the accepted-reply header with AUTH_NONE) over big-endian, 4-byte-aligned, length-prefixed XDR (no sub-word types on the wire) - plus the DEVICE_CORE procedures: pc_vxi11_build_create_link / _device_write / _device_read / _device_readstb / _device_clear (empty the instrument's I/O buffers) / _device_trigger (the protocol-level *TRG) / _destroy_link (program 0x0607AF v1) with their response parsers, and the portmapper pc_vxi11_build_getport call that maps the program to its dynamic TCP port (the instrument channel is not on a fixed port). Device_Flags (waitlock / end / termchrset), the read reason bits (REQCNT / CHR / END), and the Device_ErrorCode set are covered; pc_vxi11_error_str names an error. The XDR struct layouts, procedure numbers, and RPC headers are verified against the VXI-11 spec + RFC 5531 / 4506 / 1833 (cross-checked with python-vxi11 and the Wireshark dissector), with a byte-exact create_link vector. Pure codec, host-tested; the TCP connection is the application's. See src/services/instrumentation/vxi11/vxi11.h.
PC_ENABLE_WEBHOOK
Opt-in outbound webhooks / IFTTT. Default off. Requires HTTP_CLIENT. services/net/webhook builds an IFTTT Maker URL and a value1/value2/value3 JSON payload (pure, host-tested) and fires them - or any JSON to any URL - via the outbound http_client (POST). Use it to push an event from the device to IFTTT, a Slack/Discord hook, or your own API.
PC_ENABLE_WEBSOCKET
WebSocket support (RFC 6455 framing + SHA-1/base64 handshake).
PC_ENABLE_PROMISC
Passive 802.11 promiscuous (monitor) capture. Default off. pc_promisc_begin(channel, sink) puts the radio in promiscuous mode (esp_wifi_set_promiscuous) and delivers every frame - with RSSI and channel - to a sink; capture is strictly passive (no injection). Wire the sink into the forwarding plane (PC_ENABLE_FORWARD) to bridge captured Wi-Fi frames to another interface - e.g. stream them to a wired collector over Ethernet ("capture on Wi-Fi, forward to Ethernet"). Ships a pure 802.11 MAC-header parser (wifi_frame_parse: type/subtype, the to/from-DS src/dst/bssid layout, QoS, WDS 4-address, sequence number) and libpcap framing (DLT_IEEE802_11) so a forwarded frame is a valid PCAP a wired Wireshark / tcpdump reads. The parser and PCAP framing are pure and host-tested (native_promisc); the radio bring-up is ESP32-only. Example WifiCapture.
PC_ENABLE_ROAMING
Wi-Fi roaming decision layer. Default off. services/system/roaming is the pure, stateless policy that decides whether and where to roam to a better access point, fusing three inputs into one decision: the current link's RSSI, a candidate neighbor list (from an 802.11k neighbor report or a scan), and an optional 802.11v BSS-Transition-Management hint from the network. pc_roam_decide returns a roam/stay decision with a target BSSID + channel and a reason, in priority order: a disassociation-imminent BTM forces a roam (to the network's preferred candidate if listed, else the strongest); a non-imminent BTM steering hint is honoured only when its preferred AP is not weaker than the current link; otherwise, when the current RSSI is at/below the caller's threshold and the strongest candidate beats it by at least the hysteresis margin, it roams to that candidate (the current AP is never chosen). This is the fusion layer above the simpler two-AP RSSI-hysteresis helper (pc_wifi_should_roam) in the sniffer: it adds the 802.11v steering path, the candidate-list selection, and the reason codes. pc_roam_parse_neighbor_report decodes the 802.11k Neighbor Report elements (id 52) an AP returns into that candidate list - each element's BSSID and operating channel, with the RSSI left unknown for the caller to fill after measuring - so a real neighbor report feeds straight into pc_roam_decide. pc_roam_parse_btm_request decodes an 802.11v BSS Transition Management Request action frame into the BTM hint: the Request Mode's disassociation-imminent flag and, when a preferred candidate list is present, the top-preference target's BSSID (found past the optional BSS Termination Duration and Session Information URL fields). Together the two parsers turn real 802.11k/v frames into the decision layer's inputs. Pure and host-tested (native_roaming); the scan / neighbor-report request and the 802.11r fast transition that executes the decision live in the Wi-Fi supplicant. See src/services/system/roaming/roaming.h.
PC_ENABLE_WIFI_SNIFFER
Opt-in 802.11 sniffer / traffic analyzer with channel-agility roaming. Default off. pc_wifi_parse decodes an 802.11 MAC header (frame-control type/subtype + flags and the addresses whose roles depend on ToDS/FromDS), pc_wifi_stats_* tallies frames by type for a traffic panel, and pc_wifi_should_roam decides when a candidate AP is enough stronger (RSSI hysteresis) to justify roaming. On top of that, pc_wifi_scan_* is the channel-hop dwell schedule (sweep a channel range, wrap-safe against a pc_millis() rollover, counting sweeps) and pc_wifi_survey_* is the per-channel RSSI survey - frames heard, strongest RSSI, and the BSSID that sent it, per channel - with pc_wifi_survey_best returning the strongest other channel, which is precisely the roam candidate pc_wifi_should_roam judges. With PC_ENABLE_PROMISC also set, the live binding (pc_wifi_sniffer_begin / _tick / _end + _stats / _survey / _scan) drives the promiscuous-capture owner (services/promisc) rather than installing a second radio callback: its sink decodes, tallies, and surveys each captured frame, and _tick() hops channels on the dwell. The stats/survey are deliberately lock-free (whole-word counters, updated in the radio callback) so capture is never stalled by a reader; a report is a live snapshot, not an instantaneous cut. Schedule, survey, decode, and decision are pure and host-tested (native_wifi_sniffer); only the radio binding is ESP32-only. HW-verified on an ESP32-S3 against live 2.4 GHz traffic: 490 frames decoded across 10 channel sweeps, nine channels surveyed with real APs, and the roam decision correctly picked the -31 dBm channel over the -66 dBm one it was sitting on. Example WifiSniffer.
PC_ENABLE_WISUN
Opt-in Wi-SUN FAN border-router connector. Wi-SUN FAN is an IPv6/UDP/CoAP mesh terminated by a border router, so the connector rides the existing IP stack rather than driving a radio: services/radio/wisun keeps a table of FAN nodes (their pc_ip addresses + join state) behind the border router and builds the CoAP client requests to their resources (pc_wisun_build_coap frames an RFC 7252 header + Uri-Path options + payload; the CoAP service ships only a server). pc_wisun_nodes_json exposes the mesh to the web. The app sends the built PDU over pc_udp; the chosen devboard only sets which border router you point at. Pure, no heap/stdlib. Default off.
PC_ENABLE_WAL
Opt-in write-ahead store for atomic buffer-to-flash storage, the substrate for on-device data stores (dbm / sqlite / nosql). services/storage/wal frames each record with a CRC-32 (pc_wal_record_encode), and pc_wal_replay walks a journal image on mount and stops at the first record with a bad magic, a failed CRC, or a truncated tail - the torn write a power loss leaves - so a crash costs at most the last un-checkpointed record. On top of that codec, pc_wal_store adds a mountable durable log over a block-device seam (WalDev, three function pointers - so it binds to any fs::FS: SD card or LittleFS): records append sequentially and are committed in bulk by pc_wal_store_checkpoint, which flips an A/B superblock (the single durable pointer move); mount picks the newest valid superblock and replays the tail past it, so records appended after the last checkpoint are still recovered and a torn superblock falls back to the other copy. Built to the SD-over-SPI envelope measured in docs/FEATURE_PERFORMANCE.md (append sequentially in ~32 KiB pages, checkpoint every ~128-256 KiB; never scatter small durable writes). Pure and host-tested (CRC-32 check vector, torn/truncated-tail recovery, checkpoint durability, superblock fallback), and pc_wal_fs.h binds the seam to a real fs::FS file (preallocated, random-access over File::seek + File::flush). The whole path is hardware-verified on an SD card over SPI: checkpoint recovery, torn-tail drop, byte-level payload persistence, and survival across a chip reset all pass. Zero heap. Default off.
PC_ENABLE_WS_CLIENT
Outbound WebSocket client (RFC 6455 over raw lwIP, optional wss:// TLS). Default off. When set, src/services/net/ws_client/ws_client.h connects to a remote WebSocket endpoint (ws://, or wss:// over client-side mbedTLS), performs the RFC 6455 client handshake (Sec-WebSocket-Key/Accept), and sends masked text / binary frames + receives server frames via a callback - for streaming to cloud dashboards or bidirectional control. The frame/handshake codec is host-testable.
PC_ENABLE_WS_CLIENT_TLS
wss://: run the WebSocket client over client-side TLS (needs TLS).
PC_ENABLE_WS_DEFLATE
WebSocket permessage-deflate (RFC 7692) - bidirectional compression. When set (and WEBSOCKET is on), the server negotiates the permessage-deflate extension and both decompresses inbound compressed (RSV1) messages via a bounded INFLATE (network_drivers/presentation/inflate._) and compresses outbound data frames via a bounded DEFLATE (network_drivers/presentation/deflate._); both borrow their table scratch from the shared per-dispatch arena. The extension is negotiated with {client,server}_no_context_takeover so every message (de)compresses independently - no window is carried between messages. An outbound frame that would not shrink is sent uncompressed (the per-message RSV1 flag permits this). Default off.
PC_ENABLE_XMPP
Opt-in XMPP (RFC 6120) stanza codec. When set, services/iot/xmpp builds correctly XML-escaped <stream:stream> / <message> / <presence> / <iq> stanzas into a caller buffer and reads the stanza element name + an attribute value out of a received stanza, so a device is an IoT XMPP client. Pure text framing (TLS/SASL ride the client TLS path; the IoT XEPs layer inside <iq>). Default off.
PC_ENABLE_ZWAVE
Z-Wave Serial API frame codec (v5 gateway plugin). Default off. services/radio/zwave is the host-side Serial API of a Silicon Labs 500 / 700-series Z-Wave controller over UART, so a Z-Wave mesh is bridged to the web. Data frames are SOF (0x01) | LEN | Type | Command | Data | Checksum, where LEN counts Type..Checksum, Type is 0x00 (REQ) / 0x01 (RES), and the checksum is 0xFF XOR-folded over LEN..last-data; single-byte ACK (0x06) / NAK (0x15) / CAN (0x18) frames flow-control them. pc_zwave_build_frame assembles a function command (GetVersion, SendData, AddNodeToNetwork, ...), pc_zwave_parse_frame frames + verifies a response (length / need-more / resync), and pc_zwave_is_ack / _nak / _can / pc_zwave_build_ack handle the control bytes; the per-command payload is the application's. Pull the source node id + payload out of an ApplicationCommandHandler report and bridge it northbound with pc_gateway_uplink(). Pure - you carry the bytes over your UART - and host-tested against the documented GetVersion frame (01 03 00 15 E9) and its XOR checksum. Example ZWaveGateway bridges a real controller. See src/services/radio/zwave/zwave.h.
PC_ENABLE_ZIGBEE
Zigbee EZSP / ASH framing codec (v5 gateway plugin). Default off. services/radio/zigbee is the ASH (Asynchronous Serial Host, UG101) data-link layer that carries EZSP frames to a Silicon Labs EmberZNet network co-processor over UART, so a Zigbee network is bridged to the web. Each ASH frame is [control | payload | CRC-16/CCITT], byte-stuffed so the reserved control bytes (Flag 0x7E, Escape 0x7D, XON/XOFF, Substitute, Cancel) never appear in the body, and terminated by the Flag byte. pc_ash_frame_encode wraps a control byte + payload into a stuffed, CRC'd frame; pc_ash_frame_decode finds the flag, removes the stuffing, and verifies the CRC (returning the bytes consumed, need-more, or a resync signal); pc_ash_crc16 is the shared checksum. Bridge the EZSP payload of an incoming-message DATA frame northbound with pc_gateway_uplink(); interpreting the EZSP command itself is the application's. Pure - you carry the bytes over your UART - and host-tested against the documented ASH RST frame (C0 38 BC 7E), the CRC-16 (0x38BC), and the byte-stuffing round trip. Example ZigbeeGateway bridges a real NCP. See src/services/radio/zigbee/zigbee.h.