ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sb_modbus.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/**
5 * @file sb_modbus.h
6 * @brief Modbus-master southbound driver adapter (PROTOCORE_ENABLE_SOUTHBOUND && PROTOCORE_ENABLE_MODBUS_MASTER).
7 *
8 * Binds the transport-agnostic Modbus TCP master codec (services/fieldbus/modbus/modbus_master) into the
9 * southbound driver framework (services/southbound), so an app addresses a Modbus slave the same way
10 * as any other field device: register the driver, then read/write *points* (register addresses) by name
11 * through the one facade. A point id is a register address; the block (matrix) path is the atomic
12 * multi-register transfer a single Modbus request satisfies (read up to 125, write up to 123 registers).
13 *
14 * A holding-register driver (FC 0x03) is read/write: read / read_block use FC 0x03/0x04, write /
15 * write_block use Write Single (FC 0x06) / Write Multiple (FC 0x10). An input-register driver (FC 0x04)
16 * is read-only - a Modbus input register cannot be written - so its write / write_block stay unbound
17 * (the framework reports SB_ERR_UNSUPPORTED).
18 *
19 * The app owns the transport: it supplies a @ref protocore_sb_modbus_txn seam that sends a request ADU and
20 * receives the reply (over protocore_client for Modbus TCP, or a serial gateway). Pure otherwise - no heap,
21 * no sockets, host-testable with a mock transaction routed straight into the slave codec.
22 *
23 * A caller sets the members a call takes, invokes it through ::SbModbus, and reads the outcome off the
24 * same handle. The module exports one symbol, @ref SbModbus; everything in sb_modbus.c has internal
25 * linkage.
26 *
27 * @author Douglas Quigg (dstroy0)
28 * @date 2026
29 */
30
31#ifndef PROTOCORE_SB_MODBUS_H
32#define PROTOCORE_SB_MODBUS_H
33
34#include "protocore_config.h"
35
36#if PROTOCORE_ENABLE_SOUTHBOUND && PROTOCORE_ENABLE_MODBUS_MASTER
37
38#include "services/fieldbus/modbus/modbus/modbus.h" // ModbusFunction, MODBUS_ADU_MAX
39#include "services/southbound/southbound/southbound.h" // SouthboundDriver, Southbound
40
42
43/**
44 * @brief Request/response transport seam.
45 *
46 * Send the @p req_len request ADU, receive the reply into @p resp (capacity @p resp_cap).
47 * @return the reply length in bytes (> 0), or a negative transport error - the negative is propagated
48 * through the SouthboundDriver read call unchanged, so the app can tell a transport failure
49 * from a Modbus-level one (see PROTOCORE_SB_MODBUS_EXCEPTION).
50 */
51typedef int (*protocore_sb_modbus_txn)(void *io, const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_cap);
52
53/** @brief A Modbus-level exception reply (not a transport error); the raw code is in ctx->last_exception. */
54#define PROTOCORE_SB_MODBUS_EXCEPTION (-100)
55
56/**
57 * @brief One Modbus-master southbound driver instance (borrowed by the registry for its lifetime).
58 *
59 * Fill it through ::SbModbus @c init, then build a SouthboundDriver over it through ::SbModbus @c driver.
60 */
61typedef struct
62{
63 protocore_sb_modbus_txn txn; ///< app transport seam (send request, receive reply).
64 void *io; ///< opaque transport context passed to @ref txn.
65 ModbusFunction fc; ///< MODBUS_FC_READ_HOLDING_REGS (0x03) or MODBUS_FC_READ_INPUT_REGS (0x04).
66 uint8_t unit; ///< Modbus unit / slave id.
67 uint16_t txid; ///< rolling transaction id, incremented per request.
68 uint8_t last_exception; ///< raw Modbus exception code from the last read (0 = none).
69} protocore_sb_modbus_ctx;
70
71/**
72 * @brief The Modbus-master adapter: fill a driver instance, then build a SouthboundDriver over it.
73 *
74 * No storage member: the instance every call acts on is the caller's, named by @c ctx.
75 *
76 * @var SbModbusNs::ctx the driver instance an init fills and a driver call binds
77 * @var SbModbusNs::txn the transport seam an init takes; a null one is rejected
78 * @var SbModbusNs::io the opaque context handed to @c txn on each request; may be null
79 * @var SbModbusNs::fc MODBUS_FC_READ_HOLDING_REGS or MODBUS_FC_READ_INPUT_REGS
80 * @var SbModbusNs::unit the Modbus unit / slave id an init takes
81 * @var SbModbusNs::drv_out the driver vtable a driver call fills (borrowed by the registry)
82 * @var SbModbusNs::name the unique registry name a driver call binds (borrowed)
83 * @var SbModbusNs::i32 SB_OK, or SB_ERR_ARG on a null / out-of-range argument
84 * @var SbModbusNs::init fill @c ctx from @c txn, @c io, @c fc and @c unit
85 * @var SbModbusNs::driver fill @c drv_out with a SouthboundDriver bound to @c ctx
86 */
87typedef struct
88{
89 protocore_sb_modbus_ctx *ctx; ///< the driver instance a call acts on
90 protocore_sb_modbus_txn txn; ///< the transport seam an init takes
91 void *io; ///< the opaque context handed to @c txn
92 ModbusFunction fc; ///< the read function code an init takes
93 uint8_t unit; ///< the Modbus unit / slave id an init takes
94 SouthboundDriver *drv_out; ///< the driver vtable a driver call fills
95 const char *name; ///< the registry name a driver call binds
96 int32_t i32;
97} SbModbusVars;
98
99/** @brief The operands and the outcome. */
100extern SbModbusVars SbModbusV;
101
102/** @brief The entries. */
103typedef struct
104{
105 void (*const init)(uint8_t *work);
106 void (*const driver)(uint8_t *work);
107} SbModbusNs;
108
109// What the table binds, defined once in the .c and taking one parameter each: everything
110// else an entry needs is an operand in SbModbusV or a region of the borrow at a fixed offset.
111void protocore_sb_modbus_init(uint8_t *work);
112void protocore_sb_modbus_driver(uint8_t *work);
113
114// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
115// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
116// `SbModbus.init(work)` resolves to a named function and becomes a DIRECT call. An extern table
117// leaves the call indirect and the symbol live at every level, -O2 -flto included.
118static const SbModbusNs SbModbus __attribute__((unused)) = {
119 .init = protocore_sb_modbus_init,
120 .driver = protocore_sb_modbus_driver,
121};
122
123#endif // PROTOCORE_ENABLE_SOUTHBOUND && PROTOCORE_ENABLE_MODBUS_MASTER
124
126
127#endif // PROTOCORE_SB_MODBUS_H
Zero-heap Modbus TCP slave/server (Modbus Application Protocol v1.1b3).
Southbound protocol-driver framework (PROTOCORE_ENABLE_SOUTHBOUND).
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
#define PROTOCORE_END_DECLS
Definition types.h:97