ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
hostlink.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 hostlink.h
6 * @brief Omron Host Link (C-mode) frame codec (PROTOCORE_ENABLE_HOSTLINK) - zero-heap ASCII
7 * command/response framing for the Omron serial host-link protocol, the RS-232/485
8 * sibling of FINS.
9 *
10 * A Host Link frame is ASCII:
11 * @code
12 * @ UU XX <text> FF * CR
13 * @endcode
14 * - `@` start, `UU` the 2-digit unit/node number, `XX` the 2-char header code (e.g. `RD`),
15 * `<text>` the data, `FF` the 2-hex-char FCS, then the `*` + CR (0x0D) terminator.
16 * - FCS = the 8-bit XOR of every character from `@` through the last text character,
17 * rendered as two uppercase hex digits.
18 * - A response's text begins with a 2-char end code (00 = normal).
19 *
20 * This is the frame codec (build + FCS-validated parse); the serial transport is the app's.
21 *
22 * @author Douglas Quigg (dstroy0)
23 * @date 2026
24 */
25
26#ifndef PROTOCORE_HOSTLINK_H
27#define PROTOCORE_HOSTLINK_H
28
29#include "protocore_config.h" // the entry point: protocore_types.h for the widths
30
31#if PROTOCORE_ENABLE_HOSTLINK
32
34
35// This module holds nothing between calls, so it carves no borrow and states none. An entry
36// takes one all the same, and never reads it, so every namespace in the tree is invoked the
37// same way.
38
39/** @brief A parsed frame; @ref text points INTO the source buffer (after the header, before the FCS). */
40typedef struct
41{
42 uint8_t node;
43 char header_code[3]; ///< 2 chars + NUL
44 const char *text;
45 size_t text_len;
46} HostlinkFrame;
47
48/** @brief What fcs takes: data, len. */
49typedef struct
50{
51 const char *data;
52 size_t len;
53} HostlinkFcsArgs;
54
55/** @brief What build takes: buf, cap, node, header_code, text, ... */
56typedef struct
57{
58 char *buf;
59 size_t cap;
60 uint8_t node; ///< unit/node number (0-99, rendered as 2 BCD-style digits)
61 const char *header_code; ///< the 2-character header code (e.g. "RD"); must be 2 chars
62 const char *text;
63 size_t text_len;
64} HostlinkBuildArgs;
65
66/** @brief What parse takes: buf, len, out. */
67typedef struct
68{
69 const char *buf;
70 size_t len;
71 HostlinkFrame *out;
72} HostlinkParseArgs;
73
74/** @brief What end_code takes: f, code. */
75typedef struct
76{
77 const HostlinkFrame *f;
78 uint8_t *code;
79} HostlinkEndCodeArgs;
80
81/** @brief What build_read takes: buf, cap, node, address, count. */
82typedef struct
83{
84 char *buf;
85 size_t cap;
86 uint8_t node;
87 uint16_t address;
88 uint16_t count;
89} HostlinkBuildReadArgs;
90
91/** @brief What read_word takes: f, index, out. */
92typedef struct
93{
94 const HostlinkFrame *f;
95 size_t index;
96 uint16_t *out;
97} HostlinkReadWordArgs;
98
99/** @brief What build_write takes: buf, cap, node, address, words, ... */
100typedef struct
101{
102 char *buf;
103 size_t cap;
104 uint8_t node;
105 uint16_t address;
106 const uint16_t *words;
107 size_t word_count;
108} HostlinkBuildWriteArgs;
109
110/**
111 * @brief Omron Host Link (C-mode) frame codec (PROTOCORE_ENABLE_HOSTLINK) - zero-heap ASCII command/response framing
112 * for the Omron serial host-link protocol, the RS-232/485 sibling of FINS.
113 *
114 * A caller sets the members a call takes, invokes it through ::Hostlink with the bytes it runs
115 * out of, and reads the outcome off the same handle.
116 *
117 * Hostlink.fcs_args.data = ...;
118 * Hostlink.fcs_args.len = ...;
119 * Hostlink.fcs(work);
120 * // Hostlink.value is what the call reports
121 *
122 * @var HostlinkNs::fcs_args what fcs takes: data, len
123 * @var HostlinkNs::build_args what build takes: buf, cap, node, header_code, text,
124 * @var HostlinkNs::parse_args what parse takes: buf, len, out
125 * @var HostlinkNs::end_code_args what end_code takes: f, code
126 * @var HostlinkNs::build_read_args what build_read takes: buf, cap, node, address, count
127 * @var HostlinkNs::read_word_args what read_word takes: f, index, out
128 * @var HostlinkNs::build_write_args what build_write takes: buf, cap, node, address, words,
129 * @var HostlinkNs::ok true on a complete, FCS-valid `@...*CR` frame; false otherwise
130 * @var HostlinkNs::value the value a call reports
131 * @var HostlinkNs::n total characters written (NOT counting the NUL), or 0 on overflow / ...
132 * @var HostlinkNs::fcs FCS: 8-bit XOR of [data, data+len)
133 * @var HostlinkNs::build build a frame: `@UU` + header_code(2) + text + FCS(2 hex) + `*` + CR
134 * @var HostlinkNs::parse parse + FCS-validate a frame (command or response)
135 * @var HostlinkNs::end_code read a response's 2-char end code (the first two text characters) ...
136 * @var HostlinkNs::build_read build an RD (DM-area read) command: `@UU` + `RD` + a 4-digit ...
137 * @var HostlinkNs::read_word extract word index (0-based) from an RD response's text: a ...
138 * @var HostlinkNs::build_write build a WR (DM-area write) command: `@UU` + `WR` + a 4-digit ...
139 *
140 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
141 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
142 * a caller drives every namespace the same way.
143 */
144typedef struct
145{
146 HostlinkFcsArgs fcs_args;
147 HostlinkBuildArgs build_args;
148 HostlinkParseArgs parse_args;
149 HostlinkEndCodeArgs end_code_args;
150 HostlinkBuildReadArgs build_read_args;
151 HostlinkReadWordArgs read_word_args;
152 HostlinkBuildWriteArgs build_write_args;
153 proto_bool ok;
154 uint8_t value;
155 size_t n;
156} HostlinkVars;
157
158/** @brief The operands and the outcome. */
159extern HostlinkVars HostlinkV;
160
161/** @brief The entries. */
162typedef struct
163{
164 void (*const fcs)(uint8_t *work);
165 void (*const build)(uint8_t *work);
166 void (*const parse)(uint8_t *work);
167 void (*const end_code)(uint8_t *work);
168 void (*const build_read)(uint8_t *work);
169 void (*const read_word)(uint8_t *work);
170 void (*const build_write)(uint8_t *work);
171} HostlinkNs;
172
173// What the table binds, defined once in the .c and taking one parameter each: everything
174// else an entry needs is an operand in HostlinkV or a region of the borrow at a fixed offset.
175void protocore_hostlink_fcs(uint8_t *work);
176void protocore_hostlink_build(uint8_t *work);
177void protocore_hostlink_parse(uint8_t *work);
178void protocore_hostlink_end_code(uint8_t *work);
179void protocore_hostlink_build_read(uint8_t *work);
180void protocore_hostlink_read_word(uint8_t *work);
181void protocore_hostlink_build_write(uint8_t *work);
182
183// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
184// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
185// `Hostlink.fcs(work)` resolves to a named function and becomes a DIRECT call. An extern table
186// leaves the call indirect and the symbol live at every level, -O2 -flto included.
187static const HostlinkNs Hostlink __attribute__((unused)) = {
188 .fcs = protocore_hostlink_fcs,
189 .build = protocore_hostlink_build,
190 .parse = protocore_hostlink_parse,
191 .end_code = protocore_hostlink_end_code,
192 .build_read = protocore_hostlink_build_read,
193 .read_word = protocore_hostlink_read_word,
194 .build_write = protocore_hostlink_build_write,
195};
196
198
199#endif // PROTOCORE_ENABLE_HOSTLINK
200
201#endif // PROTOCORE_HOSTLINK_H
#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