ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
directnet.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 directnet.h
6 * @brief AutomationDirect / Koyo DirectNET serial frame codec (PROTOCORE_ENABLE_DIRECTNET).
7 *
8 * DirectNET is the AutomationDirect (Koyo) DirectLOGIC-PLC master-slave serial protocol for reading and
9 * writing V-memory. A transaction is a control-char-delimited frame with an LRC checksum. This builds
10 * the two framed messages the master sends:
11 *
12 * - **Header/enquiry**: `SOH [slave-hex][type][addr-hex 4][blocks-hex 2] ETB [LRC]` - the request that
13 * announces a read/write of N data blocks at a V-memory address.
14 * - **Data frame**: `STX [data...] ETX [LRC]` - the payload block.
15 *
16 * The LRC is the longitudinal XOR of the framed bytes (between the start control char and the LRC,
17 * inclusive of the terminating ETB/ETX). This provides the framing + LRC + the ASCII-hex field helpers;
18 * the UART transport + the ACK/NAK handshake sequencing are the device step. Pure, zero heap,
19 * host-testable.
20 */
21
22#ifndef PROTOCORE_DIRECTNET_H
23#define PROTOCORE_DIRECTNET_H
24
25#include "protocore_config.h" // the entry point: protocore_types.h for the widths
26
27#if PROTOCORE_ENABLE_DIRECTNET
28
30
31// This module holds nothing between calls, so it carves no borrow and states none. An entry
32// takes one all the same, and never reads it, so every namespace in the tree is invoked the
33// same way.
34
35/** @brief DirectNET control bytes: wire values compared/emitted, so integer constants in a struct. */
36#define DNET_ENQ 0x05
37#define DNET_ACK 0x06
38#define DNET_NAK 0x15
39#define DNET_SOH 0x01
40#define DNET_STX 0x02
41#define DNET_ETX 0x03
42#define DNET_ETB 0x17
43#define DNET_EOT 0x04
44#define DNET_READ 0x30 ///< request type: read ('0').
45#define DNET_WRITE 0x38 ///< request type: write ('8').
46
47/** @brief What lrc takes: bytes, len. */
48typedef struct
49{
50 const uint8_t *bytes;
51 size_t len;
52} DirectnetLrcArgs;
53
54/** @brief What header takes: slave, type, address, blocks, out, cap. */
55typedef struct
56{
57 uint8_t slave; ///< station number 0..99 (emitted as two ASCII-hex digits)
58 uint8_t type; ///< DNET_READ or DNET_WRITE
59 uint16_t address; ///< V-memory octal address, emitted as 4 ASCII-hex digits
60 uint8_t blocks; ///< number of data blocks, emitted as 2 ASCII-hex digits
61 uint8_t *out;
62 size_t cap;
63} DirectnetHeaderArgs;
64
65/** @brief What data takes: data, data_len, out, cap. */
66typedef struct
67{
68 const uint8_t *data;
69 size_t data_len;
70 uint8_t *out;
71 size_t cap;
72} DirectnetDataArgs;
73
74/** @brief What data_parse takes: frame, len, data, data_len. */
75typedef struct
76{
77 const uint8_t *frame;
78 size_t len;
79 const uint8_t **data;
80 size_t *data_len;
81} DirectnetDataParseArgs;
82
83/**
84 * @brief AutomationDirect / Koyo DirectNET serial frame codec (PROTOCORE_ENABLE_DIRECTNET).
85 *
86 * A caller sets the members a call takes, invokes it through ::Directnet with the bytes it runs
87 * out of, and reads the outcome off the same handle.
88 *
89 * Directnet.lrc_args.bytes = ...;
90 * Directnet.lrc_args.len = ...;
91 * Directnet.lrc(work);
92 * // Directnet.value is what the call reports
93 *
94 * @var DirectnetNs::lrc_args what lrc takes: bytes, len
95 * @var DirectnetNs::header_args what header takes: slave, type, address, blocks, out, cap
96 * @var DirectnetNs::data_args what data takes: data, data_len, out, cap
97 * @var DirectnetNs::data_parse_args what data_parse takes: frame, len, data, data_len
98 * @var DirectnetNs::ok true if it is well-formed and the LRC matches; sets data / data_len ...
99 * @var DirectnetNs::value the value a call reports
100 * @var DirectnetNs::n the frame length, or 0 on overflow. The LRC covers slave..ETB
101 * @var DirectnetNs::lrc longitudinal XOR checksum (the DirectNET LRC) over len bytes
102 * @var DirectnetNs::header build a DirectNET header frame: SOH + ...
103 * @var DirectnetNs::data build a DirectNET data frame: STX + data + ETX + LRC. The LRC ...
104 * @var DirectnetNs::data_parse validate a DirectNET data frame (STX..ETX + LRC) and expose its ...
105 *
106 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
107 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
108 * a caller drives every namespace the same way.
109 */
110typedef struct
111{
112 DirectnetLrcArgs lrc_args;
113 DirectnetHeaderArgs header_args;
114 DirectnetDataArgs data_args;
115 DirectnetDataParseArgs data_parse_args;
116 proto_bool ok;
117 uint8_t value;
118 size_t n;
119} DirectnetVars;
120
121/** @brief The operands and the outcome. */
122extern DirectnetVars DirectnetV;
123
124/** @brief The entries. */
125typedef struct
126{
127 void (*const lrc)(uint8_t *work);
128 void (*const header)(uint8_t *work);
129 void (*const data)(uint8_t *work);
130 void (*const data_parse)(uint8_t *work);
131} DirectnetNs;
132
133// What the table binds, defined once in the .c and taking one parameter each: everything
134// else an entry needs is an operand in DirectnetV or a region of the borrow at a fixed offset.
135void protocore_directnet_lrc(uint8_t *work);
136void protocore_directnet_header(uint8_t *work);
137void protocore_directnet_data(uint8_t *work);
138void protocore_directnet_data_parse(uint8_t *work);
139
140// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
141// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
142// `Directnet.lrc(work)` resolves to a named function and becomes a DIRECT call. An extern table
143// leaves the call indirect and the symbol live at every level, -O2 -flto included.
144static const DirectnetNs Directnet __attribute__((unused)) = {
145 .lrc = protocore_directnet_lrc,
146 .header = protocore_directnet_header,
147 .data = protocore_directnet_data,
148 .data_parse = protocore_directnet_data_parse,
149};
150
152
153#endif // PROTOCORE_ENABLE_DIRECTNET
154
155#endif // PROTOCORE_DIRECTNET_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