11#if DETWS_ENABLE_MODBUS
28 ModbusWriteCb write_cb =
nullptr;
30static ModbusCtx s_modbus;
32static bool bit_get(
const uint8_t *a, uint16_t i)
34 return (a[i >> 3] >> (i & 7)) & 1u;
36static void bit_set(uint8_t *a, uint16_t i,
bool v)
39 a[i >> 3] |= (uint8_t)(1u << (i & 7));
41 a[i >> 3] &= (uint8_t)~(1u << (i & 7));
44void modbus_server_init()
46 memset(s_modbus.coils, 0,
sizeof(s_modbus.coils));
47 memset(s_modbus.discrete, 0,
sizeof(s_modbus.discrete));
48 memset(s_modbus.holding, 0,
sizeof(s_modbus.holding));
49 memset(s_modbus.input, 0,
sizeof(s_modbus.input));
50 s_modbus.write_cb =
nullptr;
53void modbus_on_write(ModbusWriteCb cb)
55 s_modbus.write_cb = cb;
58bool modbus_get_coil(uint16_t addr)
62void modbus_set_coil(uint16_t addr,
bool on)
65 bit_set(s_modbus.coils, addr, on);
67bool modbus_get_discrete_input(uint16_t addr)
71void modbus_set_discrete_input(uint16_t addr,
bool on)
74 bit_set(s_modbus.discrete, addr, on);
76uint16_t modbus_get_holding_reg(uint16_t addr)
80void modbus_set_holding_reg(uint16_t addr, uint16_t value)
83 s_modbus.holding[addr] = value;
85uint16_t modbus_get_input_reg(uint16_t addr)
89void modbus_set_input_reg(uint16_t addr, uint16_t value)
92 s_modbus.input[addr] = value;
99static uint16_t rd16(
const uint8_t *p)
101 return (uint16_t)((p[0] << 8) | p[1]);
103static void wr16(uint8_t *p, uint16_t v)
105 p[0] = (uint8_t)(v >> 8);
106 p[1] = (uint8_t)(v & 0xFF);
110static size_t pdu_exception(ModbusFunction fc, ModbusException code, uint8_t *out)
112 out[0] = (uint8_t)((uint8_t)fc | 0x80);
113 out[1] = (uint8_t)code;
119static size_t modbus_process_pdu(
const uint8_t *pdu,
size_t pdu_len, uint8_t *out,
size_t out_cap)
123 ModbusFunction fc =
static_cast<ModbusFunction
>(pdu[0]);
130 case ModbusFunction::MODBUS_FC_READ_COILS:
131 case ModbusFunction::MODBUS_FC_READ_DISCRETE_INPUTS: {
133 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
134 uint16_t start = rd16(pdu + 1), qty = rd16(pdu + 3);
137 const uint8_t *src = (fc == ModbusFunction::MODBUS_FC_READ_COILS) ? s_modbus.coils : s_modbus.discrete;
138 if (qty < 1 || qty > 2000)
139 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
140 if ((uint32_t)start + qty > limit)
141 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
142 uint8_t bytes = (uint8_t)((qty + 7) / 8);
143 if ((
size_t)2 + bytes > out_cap)
145 out[0] = (uint8_t)fc;
147 memset(out + 2, 0, bytes);
148 for (uint16_t i = 0; i < qty; i++)
149 if (bit_get(src, (uint16_t)(start + i)))
150 out[2 + (i >> 3)] |= (uint8_t)(1u << (i & 7));
151 return (
size_t)2 + bytes;
155 case ModbusFunction::MODBUS_FC_READ_HOLDING_REGS:
156 case ModbusFunction::MODBUS_FC_READ_INPUT_REGS: {
158 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
159 uint16_t start = rd16(pdu + 1), qty = rd16(pdu + 3);
162 const uint16_t *src = (fc == ModbusFunction::MODBUS_FC_READ_HOLDING_REGS) ? s_modbus.holding : s_modbus.input;
163 if (qty < 1 || qty > 125)
164 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
165 if ((uint32_t)start + qty > limit)
166 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
167 uint8_t bytes = (uint8_t)(qty * 2);
168 if ((
size_t)2 + bytes > out_cap)
170 out[0] = (uint8_t)fc;
172 for (uint16_t i = 0; i < qty; i++)
173 wr16(out + 2 + i * 2, src[start + i]);
174 return (
size_t)2 + bytes;
178 case ModbusFunction::MODBUS_FC_WRITE_SINGLE_COIL: {
180 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
181 uint16_t addr = rd16(pdu + 1), value = rd16(pdu + 3);
182 if (value != 0x0000 && value != 0xFF00)
183 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
185 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
186 bit_set(s_modbus.coils, addr, value == 0xFF00);
187 if (s_modbus.write_cb)
188 s_modbus.write_cb((uint8_t)fc, addr, 1);
196 case ModbusFunction::MODBUS_FC_WRITE_SINGLE_REG: {
198 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
199 uint16_t addr = rd16(pdu + 1), value = rd16(pdu + 3);
201 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
202 s_modbus.holding[addr] = value;
203 if (s_modbus.write_cb)
204 s_modbus.write_cb((uint8_t)fc, addr, 1);
212 case ModbusFunction::MODBUS_FC_WRITE_MULTIPLE_COILS: {
214 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
215 uint16_t start = rd16(pdu + 1), qty = rd16(pdu + 3);
217 if (qty < 1 || qty > 1968 || bc != (uint8_t)((qty + 7) / 8) || pdu_len < (
size_t)6 + bc)
218 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
220 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
221 for (uint16_t i = 0; i < qty; i++)
223 bool v = (pdu[6 + (i >> 3)] >> (i & 7)) & 1u;
224 bit_set(s_modbus.coils, (uint16_t)(start + i), v);
226 if (s_modbus.write_cb)
227 s_modbus.write_cb((uint8_t)fc, start, qty);
230 out[0] = (uint8_t)fc;
231 wr16(out + 1, start);
237 case ModbusFunction::MODBUS_FC_WRITE_MULTIPLE_REGS: {
239 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
240 uint16_t start = rd16(pdu + 1), qty = rd16(pdu + 3);
242 if (qty < 1 || qty > 123 || bc != (uint8_t)(qty * 2) || pdu_len < (
size_t)6 + bc)
243 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_VALUE, out);
245 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_DATA_ADDRESS, out);
246 for (uint16_t i = 0; i < qty; i++)
247 s_modbus.holding[start + i] = rd16(pdu + 6 + i * 2);
248 if (s_modbus.write_cb)
249 s_modbus.write_cb((uint8_t)fc, start, qty);
252 out[0] = (uint8_t)fc;
253 wr16(out + 1, start);
260 return pdu_exception(fc, ModbusException::MODBUS_EX_ILLEGAL_FUNCTION, out);
264size_t modbus_process_adu(
const uint8_t *req,
size_t req_len, uint8_t *resp,
size_t resp_cap)
266 if (req_len < 8 || resp_cap < 8)
269 uint16_t tid = rd16(req);
270 uint16_t pid = rd16(req + 2);
271 uint16_t len = rd16(req + 4);
272 uint8_t uid = req[6];
276 if (len < 2 || (
size_t)6 + len > req_len)
279 const uint8_t *pdu = req + 7;
280 size_t pdu_len = (size_t)len - 1;
282 size_t rlen = modbus_process_pdu(pdu, pdu_len, resp + 7, resp_cap - 7);
288 wr16(resp + 4, (uint16_t)(1 + rlen));
293#if DETWS_ENABLE_MODBUS_RTU
295static uint16_t modbus_crc16(
const uint8_t *data,
size_t len)
297 uint16_t crc = 0xFFFFu;
298 for (
size_t i = 0; i < len; i++)
301 for (
int b = 0; b < 8; b++)
302 crc = (crc & 1u) ? (uint16_t)((crc >> 1) ^ 0xA001u) : (uint16_t)(crc >> 1);
307size_t modbus_rtu_process_adu(
const uint8_t *req,
size_t req_len, uint8_t *resp,
size_t resp_cap, uint8_t my_addr)
309 if (req_len < 4 || resp_cap < 4)
313 uint16_t want = modbus_crc16(req, req_len - 2);
314 uint16_t got = (uint16_t)(req[req_len - 2] | (req[req_len - 1] << 8));
318 uint8_t addr = req[0];
319 bool broadcast = (addr == 0);
320 if (!broadcast && addr != my_addr)
323 const uint8_t *pdu = req + 1;
324 size_t pdu_len = req_len - 3;
326 size_t rlen = modbus_process_pdu(pdu, pdu_len, resp + 1, resp_cap - 3);
333 uint16_t crc = modbus_crc16(resp, 1 + rlen);
334 resp[1 + rlen] = (uint8_t)(crc & 0xFFu);
335 resp[2 + rlen] = (uint8_t)(crc >> 8);
352static size_t ring_avail(
const TcpConn *c)
354 return det_conn_available(c->
id);
356static void ring_peek(
const TcpConn *c,
size_t off, uint8_t *dst,
size_t n)
358 det_conn_peek(c->
id, off, dst, n);
360static void ring_consume(
TcpConn *c,
size_t n)
362 det_conn_consume(c->
id, n);
365static void raw_send(uint8_t slot,
const void *data,
size_t n)
367 if (!det_conn_active(slot) || n == 0)
373static void close_conn(uint8_t slot)
378void modbus_rx(uint8_t slot)
385 size_t avail = ring_avail(conn);
390 ring_peek(conn, 0, hdr, 6);
391 uint16_t pid = (uint16_t)((hdr[2] << 8) | hdr[3]);
392 uint16_t len = (uint16_t)((hdr[4] << 8) | hdr[5]);
393 if (pid != 0 || len < 2 || len > (MODBUS_ADU_MAX - 6))
398 size_t frame_total = (size_t)6 + len;
399 if (avail < frame_total)
402 uint8_t adu[MODBUS_ADU_MAX];
403 ring_peek(conn, 0, adu, frame_total);
404 ring_consume(conn, frame_total);
406 uint8_t resp[MODBUS_ADU_MAX];
407 size_t rl = modbus_process_adu(adu, frame_total, resp,
sizeof(resp));
409 raw_send(slot, resp, rl);
416static const ProtoHandler s_modbus_handler = {
nullptr, modbus_rx,
nullptr,
nullptr};
419 return &s_modbus_handler;
#define DETWS_MODBUS_HOLDING_REGS
Number of Modbus holding registers (FC 3/6/16), 16-bit R/W (BSS).
#define DETWS_MODBUS_INPUT_REGS
Number of Modbus input registers (FC 4), 16-bit read-only (BSS).
#define DETWS_MODBUS_COILS
Number of Modbus coils (FC 1/5/15), single-bit R/W (BSS, bit-packed).
#define DETWS_MODBUS_DISCRETE_INPUTS
Number of Modbus discrete inputs (FC 2), single-bit read-only (BSS, bit-packed).
Zero-heap Modbus TCP slave/server (Modbus Application Protocol v1.1b3).
Layer 5 (Session) - per-protocol connection handler dispatch table.
Per-protocol connection event/poll callbacks (Layer 5 dispatch vtable).
A single TCP connection context.
uint8_t id
Fixed slot index (0 … MAX_CONNS-1).
bool det_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
void det_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
void det_conn_close(uint8_t slot)
Close connection slot gracefully (tcp_close), aborting if the FIN cannot be queued....
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.