ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
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 * - pc_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 * - pc_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 * pc_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 pc_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 PROTOCORE_MODBUS_H
33#define PROTOCORE_MODBUS_H
34
35#include "protocore_config.h"
36#include <stddef.h>
37#include <stdint.h>
38
39#if PC_NEED_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 MODBUS_FC_MASK_WRITE_REG = 0x16,
53 MODBUS_FC_READ_WRITE_MULTIPLE_REGS = 0x17,
54};
55
56/** @brief Modbus exception codes (Modbus Application Protocol §7). */
57enum class ModbusException : uint8_t
58{
59 MODBUS_EX_ILLEGAL_FUNCTION = 0x01,
60 MODBUS_EX_ILLEGAL_DATA_ADDRESS = 0x02,
61 MODBUS_EX_ILLEGAL_DATA_VALUE = 0x03,
62 MODBUS_EX_SERVER_FAILURE = 0x04,
63};
64
65/** @brief Largest Modbus TCP ADU (7-byte MBAP + 253-byte PDU). */
66#define MODBUS_ADU_MAX 260
67
68/**
69 * @brief Notified after a client write is applied to the data model.
70 *
71 * @param fc the write function code (5, 6, 0x0F, or 0x10).
72 * @param start first coil/register address written.
73 * @param count number of coils/registers written.
74 */
75typedef void (*ModbusWriteCb)(uint8_t fc, uint16_t start, uint16_t count);
76
77// ---------------------------------------------------------------------------
78// Data model
79// ---------------------------------------------------------------------------
80
81/** @brief Zero the entire data model and clear the write callback. */
82void pc_modbus_server_init();
83
84/** @brief Register a callback invoked after each client write (nullable). */
85void pc_modbus_on_write(ModbusWriteCb cb);
86
87bool pc_modbus_get_coil(uint16_t addr); ///< Read a coil (false if out of range).
88void pc_modbus_set_coil(uint16_t addr, bool on); ///< Set a coil (no-op if out of range).
89bool pc_modbus_get_discrete_input(uint16_t addr); ///< Read a discrete input.
90void pc_modbus_set_discrete_input(uint16_t addr, bool on); ///< Set a discrete input (application side).
91uint16_t pc_modbus_get_holding_reg(uint16_t addr); ///< Read a holding register (0 if out of range).
92void pc_modbus_set_holding_reg(uint16_t addr, uint16_t value); ///< Set a holding register.
93uint16_t pc_modbus_get_input_reg(uint16_t addr); ///< Read an input register.
94void pc_modbus_set_input_reg(uint16_t addr, uint16_t value); ///< Set an input register (application side).
95
96// ---------------------------------------------------------------------------
97// Core processing (host-testable; no sockets, no heap)
98// ---------------------------------------------------------------------------
99
100/**
101 * @brief Process one Modbus TCP ADU and build the response ADU.
102 *
103 * Parses the MBAP header (Transaction/Protocol Id, Length, Unit Id), dispatches
104 * the PDU against the data model, and writes the response ADU (echoing the
105 * Transaction/Unit Id). A non-zero Protocol Id, a truncated/oversized frame, or a
106 * length mismatch yields 0 (send nothing). An unsupported function or an invalid
107 * address/value yields a Modbus exception response.
108 *
109 * @return number of response bytes written, or 0 to send nothing.
110 */
111size_t pc_modbus_process_adu(const uint8_t *req, size_t req_len, uint8_t *resp, size_t pc_resp_cap);
112
113#if PC_ENABLE_MODBUS_RTU
114/**
115 * @brief Process one complete Modbus RTU ADU (`[addr][PDU][CRC16]`) for slave
116 * @p my_addr against the data model, writing the RTU response ADU.
117 *
118 * Validates the trailing CRC16-Modbus (drops the frame silently on mismatch) and
119 * the unit address (frames for another slave are dropped); a broadcast
120 * (address 0) is executed but draws no reply. Pure / host-tested; feed it a
121 * complete frame from a UART/RS-485 driver (delimited by the 3.5-char idle gap).
122 *
123 * @return number of RTU response bytes written, or 0 to send nothing.
124 */
125size_t pc_modbus_rtu_process_adu(const uint8_t *req, size_t req_len, uint8_t *resp, size_t pc_resp_cap,
126 uint8_t my_addr);
127#endif
128
129// ---------------------------------------------------------------------------
130// TCP transport (ConnProto::PROTO_MODBUS data handler; ESP32-only)
131// ---------------------------------------------------------------------------
132
133/** @brief Frame and process received Modbus ADUs for the connection on @p slot. */
134void pc_modbus_rx(uint8_t slot);
135
136/** @brief The Modbus ProtoHandler (accessor; nullptr on host builds; installed by the builtins list). */
137struct ProtoHandler;
138const struct ProtoHandler *pc_modbus_proto_handler(void);
139
140#endif // PC_NEED_MODBUS
141
142#endif // PROTOCORE_MODBUS_H
User-facing configuration for ProtoCore.
Per-protocol connection event/poll callbacks (Layer 5 dispatch vtable).