DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
modbus.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file modbus.h
6 * @brief Zero-heap Modbus TCP slave/server (Modbus Application Protocol v1.1b3).
7 *
8 * Split like the CoAP/SNMP services into a pure, host-testable core and an
9 * ESP32-only TCP transport:
10 *
11 * - modbus_process_adu() takes a complete Modbus TCP ADU (MBAP header + PDU) and
12 * produces the response ADU in a caller buffer - no sockets, no heap. It is
13 * unit-tested on the host (env:native_modbus).
14 * - modbus_rx() is the ConnProto::PROTO_MODBUS data handler dispatched by the session layer;
15 * it frames ADUs out of the rx ring and feeds them through
16 * modbus_process_adu(). The slave keeps no per-connection state (a partial
17 * frame waits in the rx ring), so no accept/close hooks are needed. Open the
18 * port with listen(502, ConnProto::PROTO_MODBUS).
19 *
20 * The data model is four fixed BSS tables (coils, discrete inputs, holding
21 * registers, input registers). The application reads and writes them with the
22 * accessors below; a write arriving from a client also fires modbus_on_write().
23 *
24 * Supported function codes: 0x01 Read Coils, 0x02 Read Discrete Inputs,
25 * 0x03 Read Holding Registers, 0x04 Read Input Registers, 0x05 Write Single Coil,
26 * 0x06 Write Single Register, 0x0F Write Multiple Coils, 0x10 Write Multiple
27 * Registers. Any other function code returns exception 0x01 (Illegal Function).
28 *
29 * Modbus has no authentication or encryption - run it only on a trusted network.
30 */
31
32#ifndef DETERMINISTICESPASYNCWEBSERVER_MODBUS_H
33#define DETERMINISTICESPASYNCWEBSERVER_MODBUS_H
34
35#include "ServerConfig.h"
36#include <stddef.h>
37#include <stdint.h>
38
39#if DETWS_ENABLE_MODBUS
40
41/** @brief Modbus function codes (Modbus Application Protocol §6). */
42enum class ModbusFunction : uint8_t
43{
44 MODBUS_FC_READ_COILS = 0x01,
45 MODBUS_FC_READ_DISCRETE_INPUTS = 0x02,
46 MODBUS_FC_READ_HOLDING_REGS = 0x03,
47 MODBUS_FC_READ_INPUT_REGS = 0x04,
48 MODBUS_FC_WRITE_SINGLE_COIL = 0x05,
49 MODBUS_FC_WRITE_SINGLE_REG = 0x06,
50 MODBUS_FC_WRITE_MULTIPLE_COILS = 0x0F,
51 MODBUS_FC_WRITE_MULTIPLE_REGS = 0x10,
52};
53
54/** @brief Modbus exception codes (Modbus Application Protocol §7). */
55enum class ModbusException : uint8_t
56{
57 MODBUS_EX_ILLEGAL_FUNCTION = 0x01,
58 MODBUS_EX_ILLEGAL_DATA_ADDRESS = 0x02,
59 MODBUS_EX_ILLEGAL_DATA_VALUE = 0x03,
60 MODBUS_EX_SERVER_FAILURE = 0x04,
61};
62
63/** @brief Largest Modbus TCP ADU (7-byte MBAP + 253-byte PDU). */
64#define MODBUS_ADU_MAX 260
65
66/**
67 * @brief Notified after a client write is applied to the data model.
68 *
69 * @param fc the write function code (5, 6, 0x0F, or 0x10).
70 * @param start first coil/register address written.
71 * @param count number of coils/registers written.
72 */
73typedef void (*ModbusWriteCb)(uint8_t fc, uint16_t start, uint16_t count);
74
75// ---------------------------------------------------------------------------
76// Data model
77// ---------------------------------------------------------------------------
78
79/** @brief Zero the entire data model and clear the write callback. */
80void modbus_server_init();
81
82/** @brief Register a callback invoked after each client write (nullable). */
83void modbus_on_write(ModbusWriteCb cb);
84
85bool modbus_get_coil(uint16_t addr); ///< Read a coil (false if out of range).
86void modbus_set_coil(uint16_t addr, bool on); ///< Set a coil (no-op if out of range).
87bool modbus_get_discrete_input(uint16_t addr); ///< Read a discrete input.
88void modbus_set_discrete_input(uint16_t addr, bool on); ///< Set a discrete input (application side).
89uint16_t modbus_get_holding_reg(uint16_t addr); ///< Read a holding register (0 if out of range).
90void modbus_set_holding_reg(uint16_t addr, uint16_t value); ///< Set a holding register.
91uint16_t modbus_get_input_reg(uint16_t addr); ///< Read an input register.
92void modbus_set_input_reg(uint16_t addr, uint16_t value); ///< Set an input register (application side).
93
94// ---------------------------------------------------------------------------
95// Core processing (host-testable; no sockets, no heap)
96// ---------------------------------------------------------------------------
97
98/**
99 * @brief Process one Modbus TCP ADU and build the response ADU.
100 *
101 * Parses the MBAP header (Transaction/Protocol Id, Length, Unit Id), dispatches
102 * the PDU against the data model, and writes the response ADU (echoing the
103 * Transaction/Unit Id). A non-zero Protocol Id, a truncated/oversized frame, or a
104 * length mismatch yields 0 (send nothing). An unsupported function or an invalid
105 * address/value yields a Modbus exception response.
106 *
107 * @return number of response bytes written, or 0 to send nothing.
108 */
109size_t modbus_process_adu(const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_cap);
110
111#if DETWS_ENABLE_MODBUS_RTU
112/**
113 * @brief Process one complete Modbus RTU ADU (`[addr][PDU][CRC16]`) for slave
114 * @p my_addr against the data model, writing the RTU response ADU.
115 *
116 * Validates the trailing CRC16-Modbus (drops the frame silently on mismatch) and
117 * the unit address (frames for another slave are dropped); a broadcast
118 * (address 0) is executed but draws no reply. Pure / host-tested; feed it a
119 * complete frame from a UART/RS-485 driver (delimited by the 3.5-char idle gap).
120 *
121 * @return number of RTU response bytes written, or 0 to send nothing.
122 */
123size_t modbus_rtu_process_adu(const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_cap, uint8_t my_addr);
124#endif
125
126// ---------------------------------------------------------------------------
127// TCP transport (ConnProto::PROTO_MODBUS data handler; ESP32-only)
128// ---------------------------------------------------------------------------
129
130/** @brief Frame and process received Modbus ADUs for the connection on @p slot. */
131void modbus_rx(uint8_t slot);
132
133/** @brief The Modbus ProtoHandler (accessor; nullptr on host builds; installed by the builtins list). */
134struct ProtoHandler;
135const struct ProtoHandler *modbus_proto_handler(void);
136
137#endif // DETWS_ENABLE_MODBUS
138
139#endif // DETERMINISTICESPASYNCWEBSERVER_MODBUS_H
User-facing configuration for DeterministicESPAsyncWebServer.
Per-protocol connection event/poll callbacks (Layer 5 dispatch vtable).