|
DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
|
Many of this library's codecs do not talk to a web browser. They talk to real machines: programmable logic controllers (PLCs), solar inverters, power-grid sensors, building controllers, and other ESP32 boards. To use them you have to connect the ESP32 to that machine with the right wires, the right adapter chip, and the right communication settings.
This guide is the wiring-and-settings reference for every codec that needs external hardware. It assumes no prior electronics knowledge: it explains what each connection is, what part you need to buy, how to wire it, and what numbers to type into your sketch.
One rule before anything else. The ESP32's pins run at 3.3 volts and are not 5-volt tolerant. Connecting a 5 V signal, a 12/24 V industrial signal, or an RS-232/RS-485 line directly to a GPIO pin can destroy the chip. Every serial bus below needs an adapter chip (a "transceiver" or "level shifter") between the machine and the ESP32. Never skip it.
Every protocol codec in this library is pure: it turns a message into bytes and bytes back into a message. It does not open the serial port or the network socket for you. Your sketch owns the wire. That keeps the library deterministic and lets the same codec run over a UART, a radio, Wi-Fi, or a host test.
In practice that means three jobs are yours:
HardwareSerial, or this library's UDP/TCP transport where noted).The codec does the hard part in the middle: framing, checksums/CRCs, and the wire format, all verified against the published spec. Each section below links to the matching header in src/services/ and its description in FEATURES.md.
| Family | What carries the bytes | Extra hardware you supply |
|---|---|---|
| Serial field bus | A UART pin pair, through an adapter chip | An RS-485 or RS-232 transceiver, wiring, termination |
| CAN field bus | The ESP32 TWAI controller (or MCP2515 over SPI) | A CAN transceiver (SN65HVD230), wiring, 120 ohm terminators |
| Networked | TCP or UDP over the ESP32's Wi-Fi (or wired PHY) | Usually none beyond Wi-Fi; the device and a shared network |
| Radio (ESP-NOW) | The ESP32's built-in 2.4 GHz radio | None; just a second ESP-class board as the peer |
"Flag" is the DETWS_ENABLE_* macro you set to compile the codec in (all default off). "Talks to" is the machine on the other end.
| Codec | Flag | Transport | External hardware | Typical settings | Talks to |
|---|---|---|---|---|---|
| Modbus RTU | DETWS_ENABLE_MODBUS_RTU | Serial (RS-485/232) | RS-485 transceiver | 19200 8E1, unit 1-247 | Modbus sensors, drives, meters |
| Modbus TCP | DETWS_ENABLE_MODBUS | TCP | Wi-Fi/Ethernet | port 502 | Modbus PLCs and gateways |
| Modbus master | DETWS_ENABLE_MODBUS_MASTER | TCP (client) | Wi-Fi/Ethernet | port 502 | Modbus slave devices |
| SunSpec | DETWS_ENABLE_SUNSPEC | Modbus (RTU or TCP) | per Modbus row | model base reg 40000 | Solar inverters, meters, batteries |
| DF1 | DETWS_ENABLE_DF1 | Serial (RS-232/485) | RS-232 or RS-485 transceiver | 19200, full-duplex | Allen-Bradley (Rockwell) PLCs |
| Host Link | DETWS_ENABLE_HOSTLINK | Serial (RS-232/422/485) | transceiver | 9600 7E2, unit 00-31 | Omron PLCs (C-mode) |
| FINS | DETWS_ENABLE_FINS | UDP | Wi-Fi/Ethernet | UDP port 9600 | Omron PLCs (FINS) |
| MELSEC | DETWS_ENABLE_MELSEC | TCP or UDP | Wi-Fi/Ethernet | port set on the PLC | Mitsubishi PLCs (MC 3E) |
| S7comm | DETWS_ENABLE_S7COMM | TCP (ISO-on-TCP) | Wi-Fi/Ethernet | port 102, rack/slot | Siemens S7-300/400/1200/1500 |
| BACnet/IP | DETWS_ENABLE_BACNET | UDP | Wi-Fi/Ethernet | UDP port 47808 | Building automation controllers |
| EtherNet/IP | DETWS_ENABLE_ENIP | TCP + UDP | Wi-Fi/Ethernet | TCP 44818, UDP 2222 | Allen-Bradley / ODVA devices |
| CIP | DETWS_ENABLE_CIP | over EtherNet/IP | Wi-Fi/Ethernet | (rides ENIP) | CIP objects on ODVA devices |
| DNP3 | DETWS_ENABLE_DNP3 | Serial or TCP | transceiver or Wi-Fi | TCP 20000, 16-bit addresses | SCADA / utility outstations |
| C37.118 | DETWS_ENABLE_C37118 | Serial, TCP or UDP | transceiver or Wi-Fi | no fixed port (often 4712/4713) | Power-grid PMUs / PDCs |
| M-Bus | DETWS_ENABLE_MBUS | Serial (M-Bus bus) | M-Bus level converter (TSS721) | 2400 8E1, primary addr 1-250 | Water / gas / heat / power meters |
| SDI-12 | DETWS_ENABLE_SDI12 | Serial (1-wire SDI-12) | level / direction circuit | 1200 7E1, sensor addr 0-9/A-Z | Soil / water / weather sensors |
| DMX512 / RDM | DETWS_ENABLE_DMX | Serial (RS-485) | RS-485 transceiver (MAX485) | 250k 8N2, up to 512 channels | Stage / architectural lighting |
| NMEA 0183 | DETWS_ENABLE_NMEA0183 | Serial (UART / RS-422) | none (TTL) or RS-422 receiver | 4800 or 9600 8N1 | GPS / marine instruments |
| IO-Link | DETWS_ENABLE_IOLINK | Serial (3-wire SDCI) | IO-Link transceiver (MAX14819) | 4.8 / 38.4 / 230.4 kbit/s | Smart sensors / actuators |
| IEC 60870 | DETWS_ENABLE_IEC60870 | TCP (-104) / serial (-101) | Wi-Fi or RS-232/485 transceiver | TCP 2404; -101 link address | Utility RTUs / SCADA outstations |
| CANopen | DETWS_ENABLE_CANOPEN | CAN (TWAI or SPI) | CAN transceiver | 125k-1M bit/s, node 1-127 | Motion drives, I/O, CANopen nodes |
| J1939 | DETWS_ENABLE_J1939 | CAN (TWAI or SPI) | CAN transceiver | 250k bit/s, 29-bit ids | Trucks, tractors, gensets, marine |
| DeviceNet | DETWS_ENABLE_DEVICENET | CAN (TWAI or SPI) | CAN transceiver (+ 24 V bus) | 125/250/500k, MAC 0-63 | DeviceNet I/O, drives (CIP/CAN) |
| NMEA 2000 | DETWS_ENABLE_NMEA2000 | CAN (TWAI or SPI) | CAN transceiver (+ N2K tap) | 250k bit/s, Fast Packet | Marine GPS / wind / depth / engine |
| OPC UA | DETWS_ENABLE_OPCUA | TCP | Wi-Fi/Ethernet | port 4840, SecurityPolicy None | OPC UA clients / SCADA |
| OPC UA client | DETWS_ENABLE_OPCUA_CLIENT | TCP (client) | Wi-Fi/Ethernet | port 4840 | OPC UA servers |
| SNMP | DETWS_ENABLE_SNMP | UDP | Wi-Fi/Ethernet | UDP 161 (agent), 162 (trap) | Network monitoring systems |
| ESP-NOW | DETWS_ENABLE_ESPNOW | 2.4 GHz radio | none (a peer ESP board) | shared channel, peer MAC | Other ESP32 / ESP8266 boards |
A "serial field bus" sends data one bit at a time down a wire, the way the classic serial port did. Industrial buses use a tougher electrical standard than a plain UART so the signal survives long noisy cable runs. The ESP32 cannot drive those wires directly; a small transceiver chip sits between the ESP32's UART and the bus.
RS-485 is the workhorse of factory wiring. It sends each bit as the difference between two wires (called A/**D-** and B/**D+**), which rejects electrical noise and reaches over a kilometer. Many devices share one pair of wires (a "multidrop" bus), so only one device may transmit at a time.
What you need: an RS-485 transceiver. Prefer a 3.3 V part so its logic pins match the ESP32 directly: MAX3485, SP3485, ADM3485, or THVD1450. The classic MAX485 is a 5 V part; if you use one, add a logic-level shifter on its data pins. Cheap "RS-485 to TTL" breakout modules bundle the chip and the screw terminals.
The connections:
| Transceiver pin | Connect to |
|---|---|
RO (receiver out) | ESP32 UART RX |
DI (driver in) | ESP32 UART TX |
DE + RE (tied together) | One ESP32 GPIO (the direction-control pin) |
A / B | The bus pair, to the same A/B on every device |
VCC / GND | 3.3 V and ground, shared with the ESP32 |
Because two wires carry traffic both directions, you must tell the transceiver when to talk and when to listen. That is the direction-control pin: drive the GPIO high to transmit, low to receive. (Some modules auto-switch and need no GPIO; they are simpler but slightly less reliable at high baud.)
Two more parts that matter on a real bus:
A 2-wire ("half-duplex") bus is by far the most common. A 4-wire ("full-duplex") RS-485 uses two pairs (one to transmit, one to receive) and does not need the direction pin.
RS-232 is the old PC serial port: one wire per direction, swinging between about +/-3 and +/-15 volts, point-to-point (one device per port), short cables. You need a level shifter such as the 3.3 V MAX3232 between the device's RS-232 connector and the ESP32 UART:
| MAX3232 side | Connect to |
|---|---|
TTL T1IN | ESP32 UART TX |
TTL R1OUT | ESP32 UART RX |
RS-232 T1OUT/R1IN | the device's RX/TX |
VCC/GND | 3.3 V and ground |
Cross TX to RX and RX to TX (a "null-modem" crossover) if both ends are devices rather than a PC-and-modem pair.
The ESP32 has three hardware UARTs. UART0 is the USB programming/console port; leave it alone. Use UART1 or UART2 for the field bus. On the classic ESP32, Serial2 defaults to GPIO16 (RX) and GPIO17 (TX), but you can map a UART to almost any free GPIO. Avoid the boot strapping pins (GPIO0, 2, 5, 12, 15) and the flash pins (GPIO6-11), and on ESP32-S3/C3 pick any free GPIO through the pin matrix.
Open the port with the baud rate and framing your device expects, then pump bytes through the codec. A half-duplex RS-485 send looks like this:
Framing (deciding where one message ends and the next begins) is the transport's job. For Modbus RTU that is the 3.5-character idle gap between frames; collect a frame, then hand it to the codec.
DETWS_ENABLE_MODBUS_RTU (turns on DETWS_ENABLE_MODBUS too).modbus_rtu_process_adu() validates the CRC-16 and the unit address and dispatches to the host-tested PDU layer; a bad CRC or a non-matching address is dropped silently, exactly as the spec requires. See src/services/modbus/modbus.h.DETWS_ENABLE_DF1.df1_build_frame / df1_parse_frame handle the DLE STX ... DLE ETX framing and byte-stuffing. See src/services/df1/df1.h.DETWS_ENABLE_HOSTLINK.hostlink_build / hostlink_parse build the @ + unit + code + text + FCS + *CR ASCII frame and validate the XOR checksum (FCS). See src/services/hostlink/hostlink.h.Both can run over serial as well as IP. Wire them like Modbus RTU (RS-485 or RS-232 transceiver) and see their entries under networked codecs for addressing and the codec calls; the framing is transport-independent.
DETWS_ENABLE_MBUS. Wired M-Bus is the European utility-meter bus (water, gas, heat, electricity). Electrically it is not RS-485: it is its own powered two-wire bus where the master sends by voltage modulation and the meter replies by current modulation. So you need an M-Bus master level converter (a TSS721-based module is the cheap option) between an ESP32 UART and the bus. Wire the ESP32 TX/RX to the module's UART side and the two M-Bus terminals to the meters. Typical line settings are 2400 baud, 8E1; meters have a primary address 1-250.
The codec is the framing + record layer:
mbus_build_snd_nke(buf, cap, addr) (link reset), then mbus_build_req_ud2(buf, cap, addr, fcb) to request data (toggle fcb each poll).mbus_parse() validates the frame and gives you C / A / CI + the user data; then walk the values with mbus_record_next() (each record's DIF gives the data type/length, the VIF the unit).A natural wireless meter gateway: poll meters over the M-Bus and publish the readings over MQTT / HTTP. Only the LVAR raw/ASCII variable form is decoded; the BCD-length LVAR variants are not. See src/services/mbus/mbus.h.
DETWS_ENABLE_SDI12. SDI-12 is the 1200-baud single-wire bus for soil, water, and weather sensors. One data line carries both directions (half-duplex, 7E1); the recorder addresses a sensor by one character (0-9, A-Z, a-z). The electrical layer is a 5 V line with a marking/break convention, so on a 3.3 V ESP32 you add a small level + direction circuit (a couple of transistors, or one of the ready-made SDI-12 interface boards) between a UART and the bus.
A measurement is two steps: start it, wait, then fetch:
sdi12_build_measure(buf, cap, addr, false) sends aM!; parse the reply with sdi12_parse_measure() to learn how many seconds to wait and how many values to expect.sdi12_build_data(buf, cap, addr, 0) sends aD0!; split the reply into floats with sdi12_parse_values().aMC! / aCC!), check the reply with sdi12_check_crc().Poll a sensor string and publish the readings over Wi-Fi. See src/services/sdi12/sdi12.h.
DETWS_ENABLE_DMX. DMX512 drives stage and architectural lighting over RS-485 at 250 kbit/s, 8N2 - so wire the same RS-485 transceiver (a MAX485 is the classic cheap part) as in the RS-485 section above. DMX is one-way and positional: each fixture listens on a start address and reads N consecutive channel slots.
dmx_build(buf, cap, DMX_SC_DIMMER,
channels, n); your transport sends a break then the returned bytes.dmx_get_channel() reads a 1-based channel from a received packet.RDM (ANSI E1.20) adds two-way device management on the same pair (the transceiver must be switched to receive for the reply, and RDM needs proper direction timing). Build a request with rdm_build() (set the destination / source rdm_uid(), command class, and PID) and read a reply with rdm_parse(), which checks the RDM checksum. Discover, address, and configure fixtures from a web UI - a tidy wireless lighting controller. See src/services/dmx/dmx.h.
DETWS_ENABLE_NMEA0183. The classic GPS / marine sentence protocol. A hobby GPS breakout outputs 3.3 V TTL serial that wires straight to an ESP32 RX pin (no transceiver) - typically 9600 baud, 8N1 (older units 4800). Full-size marine instruments use RS-422, so for those add an RS-422 receiver. It is mostly one-way (the receiver talks; you listen).
nmea0183_parse(line, len, &m). Check m.type ("GGA", "RMC", "VTG", ...) and pull fields with nmea0183_field_float() / nmea0183_field_int() (field 0 is the address; data fields are 1..n).nmea0183_build() adds the $, checksum, and CR/LF around your comma-separated body.Decode position / speed / heading and republish it over Wi-Fi (a web map, MQTT, or an NMEA-0183-to-NMEA-2000 bridge with the codec next door). See src/services/nmea0183/nmea0183.h.
DETWS_ENABLE_IOLINK. IO-Link (SDCI) is the point-to-point link to modern smart sensors and actuators - a single device per port over a 3-wire cable (L+, L-, and the C/Q data line). The data line is not plain UART levels: it swings to the 24 V supply, so you need a dedicated IO-Link master transceiver (a MAX14819 or L6360-class chip) between an ESP32 UART and the port. The transceiver also generates the wake-up pulse and handles the line driving; the UART runs at one of the three SDCI rates (COM1 4.8 / COM2 38.4 / COM3 230.4 kbit/s).
This codec is the data-link message layer - in particular the SDCI checksum, which is the easy thing to get wrong:
iol_mc() control octet, any on-request / process octets, and an iol_ckt() checksum/type octet), then iol_finalize(msg, len, check_index) fills the checksum.iol_verify(msg, len, check_index) checks the reply's checksum/status octet; read its Event and PD-valid flags.The per-device M-sequence and ISDU layout come from the device's IODD profile. This gets a sensor's data onto Wi-Fi via a single transceiver. See src/services/iolink/iolink.h.
CAN (Controller Area Network) is a two-wire differential bus (CAN_H / CAN_L) used across factory automation and vehicles. It is cheap and robust, and the ESP32 already has a CAN controller built in (called TWAI), so you only add a transceiver chip to drive the differential pair.
You need a CAN transceiver between the ESP32 and the bus. Two cheap options:
TX GPIO -> transceiver D (TXD), transceiver R (RXD) -> ESP32 RX GPIO, plus 3V3 and GND. CAN_H / CAN_L go to the bus.Bus rules that matter: terminate both ends of the bus with a 120 ohm resistor (many breakouts have a jumper for it), keep stubs short, and set every node to the same bit rate (125 kbit/s, 250 k, 500 k, and 1 Mbit/s are common). All nodes on a CAN bus must agree on the bit rate.
DETWS_ENABLE_CANOPEN. CANopen (CiA 301) is the dominant higher-layer protocol for CAN in factory automation: motion drives, I/O blocks, sensors. Each node has an id 1-127. The codec builds and parses the messages; your sketch moves the frames with the TWAI driver (or the MCP2515):
canopen_build_nmt(&frame, CANOPEN_NMT_START, node).canopen_build_sdo_read(&frame, node, index, sub), send it, then canopen_parse_sdo_response() on the reply.canopen_build_sdo_write(...).canopen_parse_heartbeat() on each 0x700+node frame; canopen_parse_emcy() on emergencies.canopen_parse() classifies each frame, and a TPDO's data[] is the raw mapped payload.This is the classic wireless bridge: poll CANopen drives over the wire and publish their state over MQTT / HTTP / a WebSocket. SDO transfers are expedited (<= 4 octets); segmented / block transfer is a future addition. See src/services/canopen/canopen.h.
DETWS_ENABLE_J1939. The CAN higher-layer protocol for heavy-duty vehicles, agriculture, marine, and gensets - same wiring as above (transceiver + terminators), but it uses 29-bit extended ids, almost always at 250 kbit/s. The codec packs and unpacks the id (priority / PGN / source / destination) and handles multi-packet messages:
j1939_decode_id(frame.id, &id) gives you the PGN, source, and destination; the 8 data octets are the parameter group's signals (SPNs), which you scale per the PGN definition.j1939_build_request(&frame, my_addr, dest, pgn).j1939_build_address_claim(&frame, my_addr, j1939_build_name(...)).j1939_tp_feed(&rx, &frame); when it returns J1939_TP_COMPLETE, rx.buf holds the reassembled message for rx.pgn. To send one, j1939_build_bam_cm() then a j1939_build_tp_dt() per 7-octet chunk.A classic wireless gateway: decode engine / transmission / genset PGNs off the bus and publish them over MQTT or a web dashboard. See src/services/j1939/j1939.h.
DETWS_ENABLE_DEVICENET. DeviceNet is CIP over CAN (the same CIP objects as EtherNet/IP, but on a CAN wire). Electrically it is CAN with a twist: a DeviceNet cable carries 24 V power alongside CAN_H / CAN_L, so a real drop also needs the power conductors and the standard 5-pin connector - but the signalling is ordinary CAN, so the same transceiver wiring as above applies (use 125, 250, or 500 kbit/s; each node has a MAC id 0-63).
This module supplies the DeviceNet-specific link layer; you build the CIP message body with the cip_* functions (enable DETWS_ENABLE_CIP):
devicenet_encode_id(&id, DEVICENET_GROUP_2, msg_id, mac) picks the message group + MAC id; devicenet_decode_id() reverses it.cip_*, then devicenet_build_explicit(&frame, group, msg_id, mac, body, len) prepends the message-header octet (fits when the body is <= 7 octets).devicenet_frag_feed(&rx, body, len); on DEVICENET_FRAG_COMPLETE, rx.buf holds the reassembled CIP response to parse with cip_*.Bridge a DeviceNet segment onto Wi-Fi the same way as the other CAN buses. See src/services/devicenet/devicenet.h.
DETWS_ENABLE_NMEA2000. The marine instrumentation backbone (GPS, wind, depth, AIS, engine data) - electrically it is CAN at 250 kbit/s, and protocol-wise it is J1939, so the wiring is the same transceiver setup. A real N2K backbone uses a powered trunk with drop "tees"; tap a drop with your transceiver (it only needs CAN_H / CAN_L / GND - leave the N2K power to the backbone).
NMEA 2000 carries most data either in a single 8-octet frame or via Fast Packet (up to 223 octets). The codec reuses the J1939 id functions and adds Fast Packet:
j1939_decode_id(); for a single-frame PGN read the 8 octets directly, and for a Fast Packet PGN feed each frame to n2k_fastpacket_feed(&rx, &frame) until N2K_FP_COMPLETE, then parse rx.buf per the PGN definition.n2k_build_single() for short PGNs, or loop n2k_fastpacket_build_frame() over n2k_fastpacket_num_frames() for long ones.Bridge the backbone onto Wi-Fi: turn boat data into a web dashboard or MQTT feed. See src/services/nmea2000/nmea2000.h.
These codecs reach a device over a network instead of a dedicated wire. The "external hardware" is usually just the target device plus a shared network; the ESP32's built-in Wi-Fi supplies the link.
send_chunked, breadboard wiring): ~7.2 Mbit/s at the default 20 MHz SPI clock, ~8.2 Mbit/s at 24 MHz, plateauing near the W5500's internal ~8.3 Mbit/s ceiling around 30 MHz. For near-100-Mbit speed use an RMII PHY (LAN8720) instead.DETWS_ETH_W5500_SPI_MHZ sets the SPI clock (default 20; the W5500 allows up to 33.3 MHz). Higher clocks need clean, short wiring: on breadboard jumpers, sustained transfers stay reliable to about 24 MHz, and above ~33 MHz the SPI reads corrupt (a mis-read chip ID or truncated transfers) - keep the clock conservative unless the W5500 is on a short, clean PCB trace.DETWS_ENABLE_MODBUS (the ESP32 is the slave/server, using this library's TCP server with the PROTO_MODBUS listener) or DETWS_ENABLE_MODBUS_MASTER (the ESP32 is the master/client, your sketch owns the TCP connection).modbus_* data model and PDU dispatch as RTU, minus the CRC (TCP already guarantees integrity). See src/services/modbus/modbus.h.DETWS_ENABLE_SUNSPEC.SunS** signature. Read that register block over Modbus, then walk it.sunspec_check_marker / sunspec_begin / sunspec_next_model plus typed point readers. Makes a solar inverter, meter, or battery interoperable. See src/services/sunspec/sunspec.h.DETWS_ENABLE_S7COMM (needs DETWS_ENABLE_COTP).s7_build_setup / s7_build_read_request / s7_parse_header / s7_read_next_item, wrapped with cotp_build_dt + tpkt_build. See src/services/s7comm/s7comm.h.DETWS_ENABLE_MELSEC.melsec_build_read / melsec_parse_response. See src/services/melsec/melsec.h.DETWS_ENABLE_FINS.fins_build_command / fins_build_memory_area_read / fins_parse_response, carried over this library's UDP transport (det_udp_sendto). See src/services/fins/fins.h.DETWS_ENABLE_BACNET.network number. The codec builds the BVLC + NPDU envelope; the APDU object/service layer rides on top.src/services/bacnet/bacnet.h.DETWS_ENABLE_ENIP (the encapsulation) and DETWS_ENABLE_CIP (the object messages, which turn ENIP on automatically).enip_* builds the 24-byte encapsulation header and CPF items; cip_build_epath + cip_build_get_attribute_single build the CIP request. See src/services/enip/enip.h and src/services/cip/cip.h.DETWS_ENABLE_DNP3): the IEEE 1815 SCADA outstation link. Over IP it uses TCP (or UDP) port 20000; data-link source and destination addresses are 16-bit. The codec frames and CRC-checks the 0x0564 data-link layer; the transport-function and application (objects/function codes) layer on top. See src/services/dnp3/dnp3.h.DETWS_ENABLE_C37118): the IEEE C37.118.2 synchrophasor stream from a power-grid PMU/PDC. There is no IANA-assigned port (TCP/UDP 4712 and 4713 are common conventions). Each PMU/PDC is identified by an IDCODE; a Command frame starts/stops the data stream. See src/services/c37118/c37118.h.DETWS_ENABLE_IEC60870. The European utility-SCADA protocol. It has two transports and this codec does both:
STARTDT act (iec104_build_u), then exchange I-format APDUs (iec104_build_i wraps an ASDU; iec104_parse reads one back) and acknowledge with S-format. Build the ASDU with iec_asdu_build_header + iec_put_ioa and the type-specific information elements.iec101_build_fixed / iec101_build_variable and parse with iec101_parse (the ASDU payload is identical to -104's).This makes a clean protocol-converting gateway: terminate -101 from an old RTU on the serial side and re-expose it as -104 over Wi-Fi (or vice-versa), since both share the ASDU layer. See src/services/iec60870/iec60870.h.
DETWS_ENABLE_OPCUA (the ESP32 is the server, using this library's TCP listener) and/or DETWS_ENABLE_OPCUA_CLIENT (the ESP32 connects out to another server; your sketch owns the socket).opc.tcp://<ip>:4840). This library implements SecurityPolicy None (no message encryption), so configure the peer to allow an unencrypted endpoint, or put the link on a trusted/segmented network.src/services/opcua/ and src/services/opcua_client/.DETWS_ENABLE_SNMP (agent), DETWS_ENABLE_SNMP_V3 (USM authentication/privacy), DETWS_ENABLE_SNMP_TRAP (outbound traps/informs).src/services/snmp/.DETWS_ENABLE_ESPNOW.esp_now API. See src/services/espnow/.Not everything the ESP32 talks to is an industrial machine. This library also drives the cheap sensor and actuator breakout boards you solder onto a breadboard: a clock, a presence radar, touch pads, a thermometer, a servo driver, and two kinds of meter. Unlike the field buses above, these run at 3.3 V and wire directly - no transceiver, no level shifter. Most are I2C (two shared wires); the LD2410 and a GPS are plain UART.
Each is a DETWS_ENABLE_* flag (default off) with a pure, host-tested codec and a hand-held beginner example under examples/L7-Application/.
| Breakout | Flag | Bus | Address | Wiring | Measures / does |
|---|---|---|---|---|---|
| DS3231 / DS1307 | DETWS_ENABLE_RTC | I2C | 0x68 | SDA 21, SCL 22 (+ coin cell) | Battery-backed wall-clock time |
| SHT3x (GY-SHT31) | DETWS_ENABLE_SHT3X | I2C | 0x44 | SDA 21, SCL 22 | Temperature + humidity |
| MPR121 | DETWS_ENABLE_MPR121 | I2C | 0x5A | SDA 21, SCL 22, ADDR->GND | 12 capacitive touch buttons |
| ADS1115 | DETWS_ENABLE_ADS1115 | I2C | 0x48 | SDA 21, SCL 22, ADDR->GND | 16-bit analog voltage (4 channels) |
| INA219 | DETWS_ENABLE_INA219 | I2C | 0x40 | SDA 21, SCL 22, Vin+/Vin- in series | Current + power of a load |
| PCA9685 | DETWS_ENABLE_PCA9685 | I2C | 0x40 | SDA 21, SCL 22, V+ = servo supply | 16 PWM / servo outputs |
| LD2410 | DETWS_ENABLE_LD2410 | UART | - | module TX -> GPIO 16, RX -> GPIO 17 | mmWave human presence / motion |
| GT-U7 GPS (NMEA) | DETWS_ENABLE_NMEA0183 | UART | - | module TX -> GPIO 16 (9600 baud) | Position / time (NMEA 0183) |
I2C lets many chips share just two wires: SDA (data) and SCL (clock). On the classic ESP32 these default to GPIO 21 (SDA) and GPIO 22 (SCL). Wire every I2C breakout's SDA to 21 and SCL to 22 (they all share the pair), give each 3V3 and GND, and the ESP32 tells them apart by their distinct address. Most breakouts already include the small pull-up resistors I2C needs. If two boards share an address (the PCA9685 and INA219 both default to 0x40), change one with its address solder pads.
All the I2C drivers bring the bus up through one shared helper (detws_i2c_begin() in services/i2c.h), so there is a single place to move the pins: **DETWS_I2C_SDA_PIN / DETWS_I2C_SCL_PIN** (default -1 = the platform default 21 / 22). Set both to free GPIOs to relocate the whole bus.
Running alongside wired Ethernet. Only the classic ESP32 (WROOM/WROVER) and the ESP32-P4 have the built-in RMII Ethernet MAC that a LAN8720 PHY needs; the ESP32-S3 / C3 have no RMII MAC and do wired Ethernet over SPI with a W5500 instead. On an RMII board the LAN8720 uses GPIO 21 (TX_EN) and GPIO 22 (TXD1) - the very pins I2C defaults to - plus, on boards that route the 50 MHz RMII clock there, GPIO 16/17. So with an RMII PHY enabled you must move the peripheral buses off those pins: set
-DDETWS_I2C_SDA_PIN=32 </blockquote>-DDETWS_I2C_SCL_PIN=33(or any free pair) to relocate the I2C sensors, and passalternate RX/TX to
ld2410_begin(rx, tx)/ your GPSSerial.begin(baud, SERIAL_8N1, rx, tx)(e.g. GPIO 4 / 2). Avoid the other RMII pins (19, 23, 25, 26, 27, 18) and the strapping pins (0, 2, 5, 12, 15). On an S3 with a W5500 there is no RMII clash, but still keep the I2C/UART pins clear of the SPI pins the W5500 uses.
DETWS_ENABLE_RTC - a battery-backed clock chip at I2C 0x68 so the board knows the time the instant it boots, offline. Wire SDA/SCL, fit its coin cell, and register rtc_time_source with the time-source chain; rtc_read_epoch() / rtc_set_epoch() read and set it. Example 61.Rtc.DETWS_ENABLE_NMEA0183 - the GT-U7 is a u-blox-7 GPS that streams NMEA 0183 sentences over a plain 3.3 V UART at 9600 baud (it also has a PPS pin that pulses once a second for precise timing). Wire its TX to an ESP32 RX (its RX is optional - only needed to send it config). Accumulate a line and nmea0183_parse() it, then read the fix with the field helpers - see NMEA 0183 above and example 58.Nmea0183. In a time-source chain it is the best source: GPS -> RTC -> upstream NTP (feed all three to the NTP server to serve time to your whole LAN).DETWS_ENABLE_LD2410 - a 24 GHz presence sensor that sees a still person (breathing), in the dark, through thin walls - over a UART at 256000 baud. Cross the data wires (module TX -> ESP32 RX). ld2410_poll() decodes each frame; ld2410_present() / ld2410_distance_cm() act on it. Example 62.Ld2410.DETWS_ENABLE_MPR121 - turns 12 wires or pads into touch buttons (I2C 0x5A). mpr121_read_touched() returns a 12-bit mask. Example 63.Mpr121.DETWS_ENABLE_SHT3X - a CRC-checked Sensirion sensor (I2C 0x44). sht3x_read() returns temperature and humidity in integer milli-units. Example 64.Sht3x.DETWS_ENABLE_ADS1115 - four precise analog inputs with a programmable gain (I2C 0x48). ads1115_read_uv() gives a channel's voltage in microvolts. Example 66.Ads1115.DETWS_ENABLE_INA219 - sits in series with a load (Vin+ -> shunt -> Vin-) and reports voltage, current, and power (I2C 0x40). ina219_read_current_ua() / ina219_read_power_uw(). Example 67.Ina219.DETWS_ENABLE_PCA9685 - drives up to 16 servos or LEDs from the I2C bus (0x40), with its own precise PWM timer. Power the servos from the board's V+ screw terminal (a separate 5-6 V supply), never the ESP32. pca9685_set_servo_us() positions a servo; pca9685_set_pwm() dims an LED. Example 65.Pca9685.These all follow the same shape as the field-bus codecs: a pure codec you can unit-test on a PC, and a thin binding that does the actual I2C / UART transfer on the ESP32.
These format data but do not add a new physical connection: they ride whatever transport already carries the data (MQTT, CoAP, HTTP), so their hardware needs are simply that transport's.
DETWS_ENABLE_SENML, implies CBOR): a standard JSON/CBOR shape for sensor measurements. The "hardware" is whatever sensor produces the readings; send the encoded payload over MQTT/CoAP/HTTP. See src/services/senml/.DETWS_ENABLE_SPARKPLUG, implies Protobuf): an MQTT payload and topic convention for SCADA. Needs an MQTT broker and a Sparkplug host (see the MQTT client setup); no new wiring. See src/services/sparkplug/.DETWS_ENABLE_LWM2M): the device-management value encoding carried over CoAP (this library's CoAP/UDP service); no new wiring. See src/services/lwm2m/.Field wiring lives in a harsher world than a breadboard. A few habits protect the ESP32 and keep the bus reliable:
For the security posture of each protocol (many industrial protocols have no authentication or encryption and must run on a trusted network), see SECURITY.md. For the full description and spec reference of each codec, see FEATURES.md and STANDARDS.md.