ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
modbus_master.cpp
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 pc_modbus_master.cpp
6 * @brief Modbus TCP master codec - build read requests, parse responses (pure).
7 */
8
10
11#if PC_ENABLE_MODBUS_MASTER
12
13size_t pc_modbus_build_read(uint8_t fc, uint16_t txid, uint8_t unit, uint16_t start, uint16_t count, uint8_t *out,
14 size_t cap)
15{
16 if (!out || cap < 12)
17 {
18 return 0;
19 }
20 if (fc != 0x03 && fc != 0x04) // read holding / input registers only
21 {
22 return 0;
23 }
24 if (count < 1 || count > 125)
25 {
26 return 0;
27 }
28
29 // MBAP header
30 out[0] = (uint8_t)(txid >> 8);
31 out[1] = (uint8_t)(txid & 0xFF);
32 out[2] = 0; // protocol id (Modbus) = 0
33 out[3] = 0;
34 out[4] = 0; // length = unit(1) + PDU(5) = 6
35 out[5] = 6;
36 out[6] = unit;
37 // PDU
38 out[7] = fc;
39 out[8] = (uint8_t)(start >> 8);
40 out[9] = (uint8_t)(start & 0xFF);
41 out[10] = (uint8_t)(count >> 8);
42 out[11] = (uint8_t)(count & 0xFF);
43 return 12;
44}
45
46int pc_modbus_parse_response(const uint8_t *adu, size_t len, uint16_t *regs_out, size_t max_regs,
47 uint8_t *exception_out)
48{
49 if (exception_out)
50 {
51 *exception_out = 0;
52 }
53 if (!adu || len < 9) // MBAP(7) + FC(1) + at least one more byte
54 {
55 return -1;
56 }
57 if (adu[2] != 0 || adu[3] != 0) // protocol id must be 0
58 {
59 return -1;
60 }
61
62 uint8_t fc = adu[7];
63 if (fc & 0x80) // exception response: FC | 0x80, then the exception code
64 {
65 if (exception_out)
66 {
67 *exception_out = adu[8];
68 }
69 return 0;
70 }
71 if (fc != 0x03 && fc != 0x04 && fc != 0x17) // read holding / input / read-write-multiple all reply the same
72 {
73 return -1;
74 }
75
76 uint8_t byte_count = adu[8];
77 if ((byte_count & 1) || len < (size_t)(9 + byte_count)) // must be even and present
78 {
79 return -1;
80 }
81
82 int nregs = byte_count / 2;
83 int copied = 0;
84 for (int i = 0; i < nregs && (size_t)i < max_regs; i++)
85 {
86 if (regs_out)
87 {
88 regs_out[i] = (uint16_t)((adu[9 + i * 2] << 8) | adu[9 + i * 2 + 1]);
89 }
90 copied++;
91 }
92 return copied;
93}
94
95size_t pc_modbus_build_read_bits(uint8_t fc, uint16_t txid, uint8_t unit, uint16_t start, uint16_t count, uint8_t *out,
96 size_t cap)
97{
98 if (!out || cap < 12)
99 {
100 return 0;
101 }
102 if (fc != 0x01 && fc != 0x02) // read coils / discrete inputs only
103 {
104 return 0;
105 }
106 if (count < 1 || count > 2000) // FC 0x01/0x02 cap
107 {
108 return 0;
109 }
110
111 // The read PDU is identical for bits and registers: fc | start(2) | count(2).
112 out[0] = (uint8_t)(txid >> 8);
113 out[1] = (uint8_t)(txid & 0xFF);
114 out[2] = 0; // protocol id (Modbus) = 0
115 out[3] = 0;
116 out[4] = 0; // length = unit(1) + PDU(5) = 6
117 out[5] = 6;
118 out[6] = unit;
119 out[7] = fc;
120 out[8] = (uint8_t)(start >> 8);
121 out[9] = (uint8_t)(start & 0xFF);
122 out[10] = (uint8_t)(count >> 8);
123 out[11] = (uint8_t)(count & 0xFF);
124 return 12;
125}
126
127int pc_modbus_parse_read_bits_response(const uint8_t *adu, size_t len, uint16_t count, uint8_t *bits_out,
128 size_t max_bits, uint8_t *exception_out)
129{
130 if (exception_out)
131 {
132 *exception_out = 0;
133 }
134 if (!adu || len < 9) // MBAP(7) + FC(1) + byte count(1)
135 {
136 return -1;
137 }
138 if (adu[2] != 0 || adu[3] != 0) // protocol id must be 0
139 {
140 return -1;
141 }
142
143 uint8_t fc = adu[7];
144 if (fc & 0x80) // exception response: FC | 0x80, then the exception code
145 {
146 if (exception_out)
147 {
148 *exception_out = adu[8];
149 }
150 return 0;
151 }
152 if (fc != 0x01 && fc != 0x02)
153 {
154 return -1;
155 }
156 if (count < 1 || count > 2000)
157 {
158 return -1;
159 }
160
161 uint8_t byte_count = adu[8];
162 uint16_t expect_bytes = (uint16_t)((count + 7) / 8);
163 if (byte_count != expect_bytes || len < (size_t)(9 + byte_count)) // must match the request and be present
164 {
165 return -1;
166 }
167
168 int copied = 0;
169 for (uint16_t i = 0; i < count && (size_t)i < max_bits; i++)
170 {
171 uint8_t byte = adu[9 + (i / 8)];
172 if (bits_out)
173 {
174 bits_out[i] = (uint8_t)((byte >> (i % 8)) & 1); // LSB-first packing (Modbus)
175 }
176 copied++;
177 }
178 return copied;
179}
180
181size_t pc_modbus_build_write_single_coil(uint16_t txid, uint8_t unit, uint16_t addr, bool on, uint8_t *out, size_t cap)
182{
183 if (!out || cap < 12)
184 {
185 return 0;
186 }
187
188 out[0] = (uint8_t)(txid >> 8);
189 out[1] = (uint8_t)(txid & 0xFF);
190 out[2] = 0;
191 out[3] = 0;
192 out[4] = 0; // length = unit(1) + PDU(5) = 6
193 out[5] = 6;
194 out[6] = unit;
195 // PDU (FC 0x05: address + value, where 0xFF00 = on and 0x0000 = off)
196 out[7] = 0x05;
197 out[8] = (uint8_t)(addr >> 8);
198 out[9] = (uint8_t)(addr & 0xFF);
199 out[10] = on ? 0xFF : 0x00;
200 out[11] = 0x00;
201 return 12;
202}
203
204size_t pc_modbus_build_write_multiple_coils(uint16_t txid, uint8_t unit, uint16_t start, const uint8_t *bits,
205 uint16_t count, uint8_t *out, size_t cap)
206{
207 if (!out || !bits)
208 {
209 return 0;
210 }
211 if (count < 1 || count > 1968) // FC 0x0F cap (0x07B0)
212 {
213 return 0;
214 }
215
216 uint8_t byte_count = (uint8_t)((count + 7) / 8);
217 size_t pdu_len = 6u + (size_t)byte_count; // fc(1) + start(2) + count(2) + byte_count(1) + data
218 size_t total = 7u + pdu_len; // MBAP(7) + PDU
219 if (cap < total)
220 {
221 return 0;
222 }
223
224 uint16_t mbap_len = (uint16_t)(1u + pdu_len); // unit + PDU
225 out[0] = (uint8_t)(txid >> 8);
226 out[1] = (uint8_t)(txid & 0xFF);
227 out[2] = 0;
228 out[3] = 0;
229 out[4] = (uint8_t)(mbap_len >> 8);
230 out[5] = (uint8_t)(mbap_len & 0xFF);
231 out[6] = unit;
232 // PDU (FC 0x0F: start + count + byte count + packed bits)
233 out[7] = 0x0F;
234 out[8] = (uint8_t)(start >> 8);
235 out[9] = (uint8_t)(start & 0xFF);
236 out[10] = (uint8_t)(count >> 8);
237 out[11] = (uint8_t)(count & 0xFF);
238 out[12] = byte_count;
239 for (uint16_t i = 0; i < byte_count; i++)
240 {
241 out[13 + i] = 0;
242 }
243 for (uint16_t i = 0; i < count; i++)
244 {
245 if (bits[i])
246 {
247 out[13 + (i / 8)] |= (uint8_t)(1u << (i % 8)); // LSB-first packing (Modbus)
248 }
249 }
250 return total;
251}
252
253size_t pc_modbus_build_write_single(uint16_t txid, uint8_t unit, uint16_t addr, uint16_t value, uint8_t *out,
254 size_t cap)
255{
256 if (!out || cap < 12)
257 {
258 return 0;
259 }
260
261 // MBAP header
262 out[0] = (uint8_t)(txid >> 8);
263 out[1] = (uint8_t)(txid & 0xFF);
264 out[2] = 0; // protocol id (Modbus) = 0
265 out[3] = 0;
266 out[4] = 0; // length = unit(1) + PDU(5) = 6
267 out[5] = 6;
268 out[6] = unit;
269 // PDU (FC 0x06: address + value)
270 out[7] = 0x06;
271 out[8] = (uint8_t)(addr >> 8);
272 out[9] = (uint8_t)(addr & 0xFF);
273 out[10] = (uint8_t)(value >> 8);
274 out[11] = (uint8_t)(value & 0xFF);
275 return 12;
276}
277
278size_t pc_modbus_build_write_multiple(uint16_t txid, uint8_t unit, uint16_t start, const uint16_t *values,
279 uint16_t count, uint8_t *out, size_t cap)
280{
281 if (!out || !values)
282 {
283 return 0;
284 }
285 if (count < 1 || count > 123) // FC 0x10 caps at 123 registers (PDU fits 253 bytes)
286 {
287 return 0;
288 }
289
290 uint8_t byte_count = (uint8_t)(count * 2);
291 size_t pdu_len = 6u + (size_t)byte_count; // fc(1) + start(2) + count(2) + byte_count(1) + data
292 size_t total = 7u + pdu_len; // MBAP(7) + PDU
293 if (cap < total)
294 {
295 return 0;
296 }
297
298 // MBAP header
299 uint16_t mbap_len = (uint16_t)(1u + pdu_len); // unit + PDU
300 out[0] = (uint8_t)(txid >> 8);
301 out[1] = (uint8_t)(txid & 0xFF);
302 out[2] = 0;
303 out[3] = 0;
304 out[4] = (uint8_t)(mbap_len >> 8);
305 out[5] = (uint8_t)(mbap_len & 0xFF);
306 out[6] = unit;
307 // PDU (FC 0x10: start + count + byte count + data)
308 out[7] = 0x10;
309 out[8] = (uint8_t)(start >> 8);
310 out[9] = (uint8_t)(start & 0xFF);
311 out[10] = (uint8_t)(count >> 8);
312 out[11] = (uint8_t)(count & 0xFF);
313 out[12] = byte_count;
314 for (uint16_t i = 0; i < count; i++)
315 {
316 out[13 + i * 2] = (uint8_t)(values[i] >> 8);
317 out[13 + i * 2 + 1] = (uint8_t)(values[i] & 0xFF);
318 }
319 return total;
320}
321
322int pc_modbus_parse_write_response(const uint8_t *adu, size_t len, uint16_t *addr_out, uint8_t *exception_out)
323{
324 if (exception_out)
325 {
326 *exception_out = 0;
327 }
328 if (addr_out)
329 {
330 *addr_out = 0;
331 }
332 if (!adu || len < 9) // MBAP(7) + FC(1) + at least one more byte
333 {
334 return -1;
335 }
336 if (adu[2] != 0 || adu[3] != 0) // protocol id must be 0
337 {
338 return -1;
339 }
340
341 uint8_t fc = adu[7];
342 if (fc & 0x80) // exception response: FC | 0x80, then the exception code
343 {
344 if (exception_out)
345 {
346 *exception_out = adu[8];
347 }
348 return 0;
349 }
350 if (fc != 0x05 && fc != 0x06 && fc != 0x0F && fc != 0x10)
351 {
352 return -1;
353 }
354 if (len < 12) // every reply is MBAP(7) + FC(1) + addr(2) + value-or-count(2) = 12 bytes
355 {
356 return -1;
357 }
358
359 if (addr_out)
360 {
361 *addr_out = (uint16_t)((adu[8] << 8) | adu[9]);
362 }
363 uint16_t tail = (uint16_t)((adu[10] << 8) | adu[11]); // value (0x05/0x06) or quantity (0x0F/0x10)
364 bool single = (fc == 0x05 || fc == 0x06); // single-write echoes a value; multi echoes a count
365 return single ? 1 : (int)tail;
366}
367
368size_t pc_modbus_build_mask_write(uint16_t txid, uint8_t unit, uint16_t addr, uint16_t and_mask, uint16_t or_mask,
369 uint8_t *out, size_t cap)
370{
371 if (!out || cap < 14) // MBAP(7) + FC(1) + addr(2) + And_Mask(2) + Or_Mask(2)
372 {
373 return 0;
374 }
375 // MBAP header: length = unit(1) + PDU(7) = 8.
376 out[0] = (uint8_t)(txid >> 8);
377 out[1] = (uint8_t)(txid & 0xFF);
378 out[2] = 0;
379 out[3] = 0;
380 out[4] = 0;
381 out[5] = 8;
382 out[6] = unit;
383 // PDU (FC 0x16: address + And_Mask + Or_Mask)
384 out[7] = 0x16;
385 out[8] = (uint8_t)(addr >> 8);
386 out[9] = (uint8_t)(addr & 0xFF);
387 out[10] = (uint8_t)(and_mask >> 8);
388 out[11] = (uint8_t)(and_mask & 0xFF);
389 out[12] = (uint8_t)(or_mask >> 8);
390 out[13] = (uint8_t)(or_mask & 0xFF);
391 return 14;
392}
393
394size_t pc_modbus_build_read_write_multiple(uint16_t txid, uint8_t unit, uint16_t read_start, uint16_t read_count,
395 uint16_t write_start, const uint16_t *values, uint16_t write_count,
396 uint8_t *out, size_t cap)
397{
398 if (!out || !values)
399 {
400 return 0;
401 }
402 if (read_count < 1 || read_count > 125 || write_count < 1 || write_count > 121)
403 {
404 return 0;
405 }
406
407 uint8_t byte_count = (uint8_t)(write_count * 2);
408 size_t pdu_len = 10u + (size_t)byte_count; // fc + rStart(2) + rQty(2) + wStart(2) + wQty(2) + wBC(1) + data
409 size_t total = 7u + pdu_len; // MBAP(7) + PDU
410 if (cap < total)
411 {
412 return 0;
413 }
414
415 uint16_t mbap_len = (uint16_t)(1u + pdu_len); // unit + PDU
416 out[0] = (uint8_t)(txid >> 8);
417 out[1] = (uint8_t)(txid & 0xFF);
418 out[2] = 0;
419 out[3] = 0;
420 out[4] = (uint8_t)(mbap_len >> 8);
421 out[5] = (uint8_t)(mbap_len & 0xFF);
422 out[6] = unit;
423 // PDU (FC 0x17: read span, write span, then the write data)
424 out[7] = 0x17;
425 out[8] = (uint8_t)(read_start >> 8);
426 out[9] = (uint8_t)(read_start & 0xFF);
427 out[10] = (uint8_t)(read_count >> 8);
428 out[11] = (uint8_t)(read_count & 0xFF);
429 out[12] = (uint8_t)(write_start >> 8);
430 out[13] = (uint8_t)(write_start & 0xFF);
431 out[14] = (uint8_t)(write_count >> 8);
432 out[15] = (uint8_t)(write_count & 0xFF);
433 out[16] = byte_count;
434 for (uint16_t i = 0; i < write_count; i++)
435 {
436 out[17 + i * 2] = (uint8_t)(values[i] >> 8);
437 out[17 + i * 2 + 1] = (uint8_t)(values[i] & 0xFF);
438 }
439 return total;
440}
441
442int pc_modbus_parse_mask_write_response(const uint8_t *adu, size_t len, uint16_t *addr_out, uint16_t *and_out,
443 uint16_t *or_out, uint8_t *exception_out)
444{
445 if (exception_out)
446 {
447 *exception_out = 0;
448 }
449 if (!adu || len < 9)
450 {
451 return -1;
452 }
453 if (adu[2] != 0 || adu[3] != 0) // protocol id must be 0
454 {
455 return -1;
456 }
457 uint8_t fc = adu[7];
458 if (fc & 0x80)
459 {
460 if (exception_out)
461 {
462 *exception_out = adu[8];
463 }
464 return 0;
465 }
466 if (fc != 0x16 || len < 14) // MBAP(7) + FC(1) + addr(2) + And_Mask(2) + Or_Mask(2)
467 {
468 return -1;
469 }
470 if (addr_out)
471 {
472 *addr_out = (uint16_t)((adu[8] << 8) | adu[9]);
473 }
474 if (and_out)
475 {
476 *and_out = (uint16_t)((adu[10] << 8) | adu[11]);
477 }
478 if (or_out)
479 {
480 *or_out = (uint16_t)((adu[12] << 8) | adu[13]);
481 }
482 return 1;
483}
484
485#endif // PC_ENABLE_MODBUS_MASTER