ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sb_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 sb_modbus.h
6 * @brief Modbus-master southbound driver adapter (PC_ENABLE_SOUTHBOUND && PC_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/net/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::SB_ERR_UNSUPPORTED).
18 *
19 * The app owns the transport: it supplies a @ref pc_sb_modbus_txn seam that sends a request ADU and
20 * receives the reply (over pc_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 * @author Douglas Quigg (dstroy0)
24 * @date 2026
25 */
26
27#ifndef PROTOCORE_SB_MODBUS_H
28#define PROTOCORE_SB_MODBUS_H
29
30#include "protocore_config.h"
31#include "services/fieldbus/modbus/modbus.h" // ModbusFunction, MODBUS_ADU_MAX
32#include "services/net/southbound/southbound.h" // SouthboundDriver, Sb
33#include <stddef.h>
34#include <stdint.h>
35
36#if PC_ENABLE_SOUTHBOUND && PC_ENABLE_MODBUS_MASTER
37
38/**
39 * @brief Request/response transport seam.
40 *
41 * Send the @p req_len request ADU, receive the reply into @p resp (capacity @p resp_cap).
42 * @return the reply length in bytes (> 0), or a negative transport error - the negative is propagated
43 * through the SouthboundDriver read call unchanged, so the app can tell a transport failure
44 * from a Modbus-level one (see PC_SB_MODBUS_EXCEPTION).
45 */
46typedef int (*pc_sb_modbus_txn)(void *io, const uint8_t *req, size_t req_len, uint8_t *resp, size_t resp_cap);
47
48/** @brief A Modbus-level exception reply (not a transport error); the raw code is in ctx->last_exception. */
49#define PC_SB_MODBUS_EXCEPTION (-100)
50
51/**
52 * @brief One Modbus-master southbound driver instance (borrowed by the registry for its lifetime).
53 *
54 * Fill it with pc_sb_modbus_init(), then build a SouthboundDriver over it with pc_sb_modbus_driver().
55 */
56struct pc_sb_modbus_ctx
57{
58 pc_sb_modbus_txn txn; ///< app transport seam (send request, receive reply).
59 void *io; ///< opaque transport context passed to @ref txn.
60 ModbusFunction fc; ///< MODBUS_FC_READ_HOLDING_REGS (0x03) or MODBUS_FC_READ_INPUT_REGS (0x04).
61 uint8_t unit; ///< Modbus unit / slave id.
62 uint16_t txid; ///< rolling transaction id, incremented per request.
63 uint8_t last_exception; ///< raw Modbus exception code from the last read (0 = none).
64};
65
66/**
67 * @brief Initialize a driver context.
68 * @param ctx the instance to fill.
69 * @param txn the transport seam (must be non-null).
70 * @param io opaque context passed to @p txn on each request (may be null).
71 * @param fc ModbusFunction::MODBUS_FC_READ_HOLDING_REGS or ::MODBUS_FC_READ_INPUT_REGS.
72 * @param unit Modbus unit / slave id.
73 * @return Sb::SB_OK, or Sb::SB_ERR_ARG on a null ctx/txn or an fc that is not a read function code.
74 */
75int pc_sb_modbus_init(pc_sb_modbus_ctx *ctx, pc_sb_modbus_txn txn, void *io, ModbusFunction fc, uint8_t unit);
76
77/**
78 * @brief Fill @p drv_out with a SouthboundDriver bound to @p ctx.
79 *
80 * A holding-register context binds read + read_block + write + write_block; an input-register context
81 * binds read + read_block only (input registers are read-only).
82 * @param drv_out the driver vtable to fill (borrowed by the registry; must outlive it, as must @p ctx).
83 * @param name the driver's unique registry name (borrowed).
84 * @param ctx an initialized context (see pc_sb_modbus_init).
85 * @return Sb::SB_OK, or Sb::SB_ERR_ARG on a null / uninitialized argument.
86 */
87int pc_sb_modbus_driver(SouthboundDriver *drv_out, const char *name, pc_sb_modbus_ctx *ctx);
88
89#endif // PC_ENABLE_SOUTHBOUND && PC_ENABLE_MODBUS_MASTER
90#endif // PROTOCORE_SB_MODBUS_H
Zero-heap Modbus TCP slave/server (Modbus Application Protocol v1.1b3).
User-facing configuration for ProtoCore.
Southbound protocol-driver framework (PC_ENABLE_SOUTHBOUND).