ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
modbus_master.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4#ifndef PROTOCORE_MODBUS_MASTER_H
5#define PROTOCORE_MODBUS_MASTER_H
6
7#include "protocore_config.h" // the entry point: protocore_types.h for the widths
8
10
11/**
12 * @file modbus_master.h
13 * @brief Modbus TCP master codec + register scanner (PROTOCORE_ENABLE_MODBUS_MASTER).
14 *
15 * The master/client side of Modbus: build a read-request ADU (MBAP header + PDU)
16 * and parse the slave's response into register values, so an application can poll
17 * or auto-discover a slave's registers. Pure - no sockets, no heap - so it is
18 * host-tested as a full round-trip against the slave codec (Modbus.process_adu).
19 * The app supplies the transport (send the ADU, receive the reply).
20 *
21 * Auto-discovery pattern: walk the address space one read at a time; a register
22 * exists where the response parses without a Modbus exception.
23 *
24 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
25 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
26 * a caller drives every namespace the same way.
27 *
28 * @author Douglas Quigg (dstroy0)
29 * @date 2026
30 */
31
32// PROTOCORE_MODBUS_MASTER_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
33// it into its arena. Its size and its offset are each a static_assert, so a feature
34// combination that does not fit fails to compile rather than overrunning at run time.
35
36/** @brief Dispatch table. Addressed by offset, so the layout is asserted below. */
37typedef struct
38{
39 size_t (*build_read)(uint8_t *, uint8_t, uint16_t, uint8_t, uint16_t, uint16_t, uint8_t *, size_t);
40 int (*parse_response)(uint8_t *, const uint8_t *, size_t, uint16_t *, size_t, uint8_t *);
41 size_t (*build_read_bits)(uint8_t *, uint8_t, uint16_t, uint8_t, uint16_t, uint16_t, uint8_t *, size_t);
42 int (*parse_read_bits_response)(uint8_t *, const uint8_t *, size_t, uint16_t, uint8_t *, size_t, uint8_t *);
43 size_t (*build_write_single_coil)(uint8_t *, uint16_t, uint8_t, uint16_t, proto_bool, uint8_t *, size_t);
44 size_t (*build_write_multiple_coils)(uint8_t *, uint16_t, uint8_t, uint16_t, const uint8_t *, uint16_t, uint8_t *,
45 size_t);
46 size_t (*build_write_single)(uint8_t *, uint16_t, uint8_t, uint16_t, uint16_t, uint8_t *, size_t);
47 size_t (*build_write_multiple)(uint8_t *, uint16_t, uint8_t, uint16_t, const uint16_t *, uint16_t, uint8_t *,
48 size_t);
49 int (*parse_write_response)(uint8_t *, const uint8_t *, size_t, uint16_t *, uint8_t *);
50 size_t (*build_mask_write)(uint8_t *, uint16_t, uint8_t, uint16_t, uint16_t, uint16_t, uint8_t *, size_t);
51 size_t (*build_read_write_multiple)(uint8_t *, uint16_t, uint8_t, uint16_t, uint16_t, uint16_t, const uint16_t *,
52 uint16_t, uint8_t *, size_t);
53 int (*parse_mask_write_response)(uint8_t *, const uint8_t *, size_t, uint16_t *, uint16_t *, uint16_t *, uint8_t *);
55PROTOCORE_NS_LAYOUT(ModbusMasterNs, build_read, parse_response, build_read_bits, parse_read_bits_response,
56 build_write_single_coil, build_write_multiple_coils, build_write_single, build_write_multiple,
57 parse_write_response, build_mask_write, build_read_write_multiple, parse_mask_write_response);
58
59/**
60 * @brief Build a read-request ADU (FC 0x03 holding or 0x04 input registers).
61 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
62 * @param fc MODBUS_FC_READ_HOLDING_REGS (0x03) or MODBUS_FC_READ_INPUT_REGS (0x04)
63 * @param txid transaction id echoed by the slave (caller's correlation token)
64 * @param unit unit / slave id
65 * @param start first register address
66 * @param count number of registers (1..125)
67 * @param out destination buffer
68 * @param cap destination capacity (>= 12)
69 * @return The size_t.
70 */
71size_t protocore_modbus_master_build_read(uint8_t *work, uint8_t fc, uint16_t txid, uint8_t unit, uint16_t start,
72 uint16_t count, uint8_t *out, size_t cap);
73/**
74 * @brief Parse a read-response ADU into register values.
75 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
76 * @param adu response bytes (MBAP + PDU)
77 * @param len response length
78 * @param regs_out destination for parsed 16-bit register values
79 * @param max_regs capacity of regs_out
80 * @param exception_out set to the Modbus exception code if the slave returned one (then the function returns 0
81 * @return The int.
82 */
83int protocore_modbus_master_parse_response(uint8_t *work, const uint8_t *adu, size_t len, uint16_t *regs_out,
84 size_t max_regs, uint8_t *exception_out);
85/**
86 * @brief Build a read-bits request ADU (FC 0x01 coils or 0x02 discrete .
87 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
88 * @param fc MODBUS_FC_READ_COILS (0x01) or ::MODBUS_FC_READ_DISCRETE_INPUTS (0x02)
89 * @param txid transaction id echoed by the slave
90 * @param unit unit / slave id
91 * @param start first bit address
92 * @param count number of bits (1..2000)
93 * @param out destination buffer
94 * @param cap destination capacity (>= 12)
95 * @return The size_t.
96 */
97size_t protocore_modbus_master_build_read_bits(uint8_t *work, uint8_t fc, uint16_t txid, uint8_t unit, uint16_t start,
98 uint16_t count, uint8_t *out, size_t cap);
99/**
100 * @brief Parse a read-bits response ADU (FC 0x01 / 0x02) into one byte (0/1).
101 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
102 * @param adu Adu
103 * @param len Len
104 * @param count the number of bits requested (1..2000)
105 * @param bits_out destination for count unpacked bits (nullable to just validate)
106 * @param max_bits capacity of bits_out
107 * @param exception_out set to the Modbus exception code if the slave returned one (then 0 is returned)
108 * @return The int.
109 */
110int protocore_modbus_master_parse_read_bits_response(uint8_t *work, const uint8_t *adu, size_t len, uint16_t count,
111 uint8_t *bits_out, size_t max_bits, uint8_t *exception_out);
112/**
113 * @brief Build a Write Single Coil request ADU (FC 0x05).
114 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
115 * @param txid Txid
116 * @param unit Unit
117 * @param addr Addr
118 * @param on the coil value; encoded on the wire as 0xFF00 (on) or 0x0000 (off) per the Modbus spec
119 * @param out Out
120 * @param cap destination capacity (>= 12)
121 * @return The size_t.
122 */
123size_t protocore_modbus_master_build_write_single_coil(uint8_t *work, uint16_t txid, uint8_t unit, uint16_t addr,
124 proto_bool on, uint8_t *out, size_t cap);
125/**
126 * @brief Build a Write Multiple Coils request ADU (FC 0x0F).
127 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
128 * @param txid Txid
129 * @param unit Unit
130 * @param start Start
131 * @param bits one byte (0/1) per coil to write; packed LSB-first into the wire bytes
132 * @param count number of coils (1..1968)
133 * @param out Out
134 * @param cap destination capacity (>= 14 + ceil(count/8))
135 * @return The size_t.
136 */
137size_t protocore_modbus_master_build_write_multiple_coils(uint8_t *work, uint16_t txid, uint8_t unit, uint16_t start,
138 const uint8_t *bits, uint16_t count, uint8_t *out,
139 size_t cap);
140/**
141 * @brief Build a Write Single Register request ADU (FC 0x06).
142 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
143 * @param txid transaction id echoed by the slave
144 * @param unit unit / slave id
145 * @param addr register address
146 * @param value 16-bit value to write
147 * @param out destination buffer
148 * @param cap destination capacity (>= 12)
149 * @return The size_t.
150 */
151size_t protocore_modbus_master_build_write_single(uint8_t *work, uint16_t txid, uint8_t unit, uint16_t addr,
152 uint16_t value, uint8_t *out, size_t cap);
153/**
154 * @brief Build a Write Multiple Registers request ADU (FC 0x10).
155 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
156 * @param txid transaction id echoed by the slave
157 * @param unit unit / slave id
158 * @param start first register address
159 * @param values the count register values
160 * @param count number of registers (1..123)
161 * @param out destination buffer
162 * @param cap destination capacity (>= 13 + 2*count)
163 * @return The size_t.
164 */
165size_t protocore_modbus_master_build_write_multiple(uint8_t *work, uint16_t txid, uint8_t unit, uint16_t start,
166 const uint16_t *values, uint16_t count, uint8_t *out, size_t cap);
167/**
168 * @brief Parse a write-response ADU (FC 0x05, 0x06, 0x0F, or 0x10). A normal .
169 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
170 * @param adu response bytes (MBAP + PDU)
171 * @param len response length
172 * @param addr_out set to the echoed address / start (nullable)
173 * @param exception_out set to the Modbus exception code if the slave returned one (then 0 is returned)
174 * @return The int.
175 */
176int protocore_modbus_master_parse_write_response(uint8_t *work, const uint8_t *adu, size_t len, uint16_t *addr_out,
177 uint8_t *exception_out);
178/**
179 * @brief Build a Mask Write Register request ADU (FC 0x16). The slave .
180 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
181 * @param txid Txid
182 * @param unit Unit
183 * @param addr Addr
184 * @param and_mask And mask
185 * @param or_mask Or mask
186 * @param out Out
187 * @param cap destination capacity (>= 14)
188 * @return The size_t.
189 */
190size_t protocore_modbus_master_build_mask_write(uint8_t *work, uint16_t txid, uint8_t unit, uint16_t addr,
191 uint16_t and_mask, uint16_t or_mask, uint8_t *out, size_t cap);
192/**
193 * @brief Build a Read/Write Multiple Registers request ADU (FC 0x17): write.
194 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
195 * @param txid Txid
196 * @param unit Unit
197 * @param read_start / read_count the registers to read back (1..125)
198 * @param read_count Read count
199 * @param write_start / write_count the registers to write (1..121); values holds write_count words
200 * @param values Values
201 * @param write_count Write count
202 * @param out Out
203 * @param cap destination capacity (>= 17 + 2*write_count)
204 * @return The size_t.
205 */
206size_t protocore_modbus_master_build_read_write_multiple(uint8_t *work, uint16_t txid, uint8_t unit,
207 uint16_t read_start, uint16_t read_count, uint16_t write_start,
208 const uint16_t *values, uint16_t write_count, uint8_t *out,
209 size_t cap);
210/**
211 * @brief Parse a Mask Write Register response (FC 0x16), which echoes the .
212 * @param work PROTOCORE_MODBUS_MASTER_BORROW bytes the caller took. Not held past the call.
213 * @param adu Adu
214 * @param len Len
215 * @param addr_out / and_out / or_out receive the echoed fields (each nullable)
216 * @param and_out And out
217 * @param or_out Or out
218 * @param exception_out set to the Modbus exception code if the slave returned one
219 * @return The int.
220 */
221int protocore_modbus_master_parse_mask_write_response(uint8_t *work, const uint8_t *adu, size_t len, uint16_t *addr_out,
222 uint16_t *and_out, uint16_t *or_out, uint8_t *exception_out);
223
224/** @brief Module namespace. */
238
240
241#endif // PROTOCORE_MODBUS_MASTER_H
int protocore_modbus_master_parse_mask_write_response(uint8_t *work, const uint8_t *adu, size_t len, uint16_t *addr_out, uint16_t *and_out, uint16_t *or_out, uint8_t *exception_out)
Parse a Mask Write Register response (FC 0x16), which echoes the .
size_t protocore_modbus_master_build_write_single_coil(uint8_t *work, uint16_t txid, uint8_t unit, uint16_t addr, proto_bool on, uint8_t *out, size_t cap)
Build a Write Single Coil request ADU (FC 0x05).
size_t protocore_modbus_master_build_write_multiple(uint8_t *work, uint16_t txid, uint8_t unit, uint16_t start, const uint16_t *values, uint16_t count, uint8_t *out, size_t cap)
Build a Write Multiple Registers request ADU (FC 0x10).
size_t protocore_modbus_master_build_read(uint8_t *work, uint8_t fc, uint16_t txid, uint8_t unit, uint16_t start, uint16_t count, uint8_t *out, size_t cap)
Build a read-request ADU (FC 0x03 holding or 0x04 input registers).
int protocore_modbus_master_parse_write_response(uint8_t *work, const uint8_t *adu, size_t len, uint16_t *addr_out, uint8_t *exception_out)
Parse a write-response ADU (FC 0x05, 0x06, 0x0F, or 0x10). A normal .
size_t protocore_modbus_master_build_mask_write(uint8_t *work, uint16_t txid, uint8_t unit, uint16_t addr, uint16_t and_mask, uint16_t or_mask, uint8_t *out, size_t cap)
Build a Mask Write Register request ADU (FC 0x16). The slave .
size_t protocore_modbus_master_build_read_write_multiple(uint8_t *work, uint16_t txid, uint8_t unit, uint16_t read_start, uint16_t read_count, uint16_t write_start, const uint16_t *values, uint16_t write_count, uint8_t *out, size_t cap)
Build a Read/Write Multiple Registers request ADU (FC 0x17): write.
size_t protocore_modbus_master_build_write_multiple_coils(uint8_t *work, uint16_t txid, uint8_t unit, uint16_t start, const uint8_t *bits, uint16_t count, uint8_t *out, size_t cap)
Build a Write Multiple Coils request ADU (FC 0x0F).
size_t protocore_modbus_master_build_read_bits(uint8_t *work, uint8_t fc, uint16_t txid, uint8_t unit, uint16_t start, uint16_t count, uint8_t *out, size_t cap)
Build a read-bits request ADU (FC 0x01 coils or 0x02 discrete .
int protocore_modbus_master_parse_read_bits_response(uint8_t *work, const uint8_t *adu, size_t len, uint16_t count, uint8_t *bits_out, size_t max_bits, uint8_t *exception_out)
Parse a read-bits response ADU (FC 0x01 / 0x02) into one byte (0/1).
PROTOCORE_NS ModbusMasterNs ModbusMaster PROTOCORE_UNUSED
Module namespace.
int protocore_modbus_master_parse_response(uint8_t *work, const uint8_t *adu, size_t len, uint16_t *regs_out, size_t max_regs, uint8_t *exception_out)
Parse a read-response ADU into register values.
size_t protocore_modbus_master_build_write_single(uint8_t *work, uint16_t txid, uint8_t unit, uint16_t addr, uint16_t value, uint8_t *out, size_t cap)
Build a Write Single Register request ADU (FC 0x06).
#define PROTOCORE_NS_LAYOUT(T,...)
Pin every dispatch slot of a table that is nothing but function pointers.
#define PROTOCORE_NS
Storage for a dispatch table. The const is load bearing.
Dispatch table. Addressed by offset, so the layout is asserted below.
size_t(* build_read)(uint8_t *, uint8_t, uint16_t, uint8_t, uint16_t, uint16_t, uint8_t *, size_t)
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97