ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
dnc.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 dnc.h
6 * @brief CNC RS-232 DNC (Distributed Numerical Control) drip-feed codec (PROTOCORE_ENABLE_DNC).
7 *
8 * DNC is the classic way a program is streamed to a machine-tool controller: a G-code
9 * program (RS-274 / ISO 6983) is punched as blocks (one line each), framed with a
10 * `%` rewind-stop at the start and end, and drip-fed over an RS-232 link with XON/XOFF
11 * software flow control so the sender pauses when the controller's small input buffer
12 * fills. This codec is the transport-agnostic framing + tape-code layer only - the same
13 * bytes ride RS-232, a raw TCP socket, or a WebSocket; the app owns the wire.
14 *
15 * Two tape codes are supported (the historical split every DNC package still carries):
16 * - **ISO** (::DNC_CODE_ISO): ISO 7-bit / ASCII, End-of-Block = LF, program marker = `%`.
17 * Optional even parity in bit 7 (the ISO tape convention, RS-358).
18 * - **EIA** (::DNC_CODE_EIA): the EIA RS-244 punched-tape code - a distinct, odd-parity
19 * 8-track encoding (parity in channel 5). End-of-Block = 0x80 (channel 8, used only for
20 * EOB), the rewind-stop is EIA End-of-Record 0x0B (the `%` equivalent), and there are no
21 * lowercase letters. The full character table is odd-parity-verified.
22 *
23 * Three pieces, all pure and zero-heap:
24 * 1. character translation (::protocore_dnc_iso_to_eia / ::protocore_dnc_eia_to_iso) + ISO even-parity helper;
25 * 2. XON/XOFF flow state (::DncFlow) the send pump consults before each write;
26 * 3. a streaming block encoder (::protocore_dnc_encode_block + the `%`/leader framing) and a
27 * byte-at-a-time block decoder (::DncDecoder) that reassembles wire bytes back into
28 * ASCII G-code lines and reports the `%` program start/end.
29 *
30 * @author Douglas Quigg (dstroy0)
31 * @date 2026
32 */
33
34#ifndef PROTOCORE_DNC_H
35#define PROTOCORE_DNC_H
36
37#include "protocore_config.h" // the entry point: protocore_types.h for the widths
38
39#if PROTOCORE_ENABLE_DNC
40
42
43/** @brief Which punched-tape character code the wire stream uses. */
44typedef enum PROTO_ENUM_PACKED
45{
46 DNC_CODE_ISO = 0, ///< ISO 7-bit / ASCII (RS-358), EOB = LF, marker = '%'.
47 DNC_CODE_EIA = 1, ///< EIA RS-244 odd-parity tape code, EOB = 0x80, marker = EOR 0x0B.
48} DncCode;
49
50/** @brief Software flow-control bytes (both tape codes; these are the raw ASCII controls). */
51typedef enum PROTO_ENUM_PACKED
52{
53 DNC_XON = 0x11, ///< DC1 - resume sending.
54 DNC_XOFF = 0x13, ///< DC3 - pause sending.
55} DncFlowByte;
56
57/** @brief EIA RS-244 special codes (not general text characters). */
58typedef enum PROTO_ENUM_PACKED
59{
60 DNC_EIA_EOB = 0x80, ///< EIA End-of-Block (channel 8 only; the ISO LF equivalent).
61 DNC_EIA_EOR = 0x0B, ///< EIA End-of-Record (the ISO '%' rewind-stop equivalent).
62 DNC_EIA_DEL = 0x7F, ///< EIA Delete / rubout (leader/trailer runout; skipped on read).
63} DncEiaCode;
64
65/**
66 * @brief Translate one ISO/ASCII character to its EIA RS-244 byte.
67 *
68 * Covers the NC character set: digits, uppercase A-Z, space, `. - + / %` and Tab, plus the
69 * `%` rewind-stop (mapped to EIA End-of-Record 0x0B). Every returned byte carries EIA odd
70 * parity (channel 5).
71 *
72 * @return the EIA byte, or 0xFF if @p c has no EIA representation (fail-closed).
73 */
74uint8_t protocore_dnc_iso_to_eia(char c);
75
76/**
77 * @brief Translate one EIA RS-244 byte back to its ISO/ASCII character.
78 *
79 * The inverse of ::protocore_dnc_iso_to_eia. EIA End-of-Record (0x0B) maps back to '%'.
80 *
81 * @return the ASCII character, or 0 if @p b is not a known EIA code (e.g. blank/runout).
82 */
83char protocore_dnc_eia_to_iso(uint8_t b);
84
85/**
86 * @brief Set even parity in bit 7 of a 7-bit ASCII value (the ISO tape convention).
87 * @param ascii7 a value in 0x00-0x7F (bit 7 is ignored on input).
88 * @return @p ascii7 with bit 7 set so the byte has an even number of 1 bits.
89 */
90uint8_t protocore_dnc_iso_add_parity(uint8_t ascii7);
91
92/** @brief XON/XOFF software flow-control state for the send side. */
93typedef struct
94{
95 proto_bool paused; ///< true after XOFF (DC3), cleared by XON (DC1).
96} DncFlow;
97
98/** @brief Reset flow state to "clear to send". */
99void protocore_dnc_flow_init(DncFlow *f);
100
101/**
102 * @brief Feed one received byte to the flow-control state machine.
103 * @return true if @p rx was a flow-control byte (XON/XOFF) and was consumed; false otherwise
104 * (the byte is ordinary inbound data the caller still owns).
105 */
106proto_bool protocore_dnc_flow_feed(DncFlow *f, uint8_t rx);
107
108/** @brief Whether the send pump may transmit (i.e. not paused by an XOFF). */
109static inline proto_bool protocore_dnc_flow_can_send(const DncFlow *f)
110{
111 return !f->paused;
112}
113
114/** @brief Encoder configuration - the tape code and its framing options. */
115typedef struct
116{
117 DncCode code; ///< ISO or EIA.
118 proto_bool even_parity; ///< ISO only: emit even parity in bit 7 (ignored for EIA, which is always odd).
119 proto_bool crlf; ///< ISO only: emit CR before the LF End-of-Block (some controllers want CR LF).
120 uint16_t leader_len; ///< leader/trailer runout length in bytes (::protocore_dnc_encode_leader / _trailer).
121} DncCfg;
122
123/**
124 * @brief Frame one G-code source line as a block (its characters + an End-of-Block).
125 *
126 * The source is plain ASCII with no terminator. Each character is translated to the
127 * configured tape code (ISO passes 7-bit through, adding even parity if requested; EIA maps
128 * via ::protocore_dnc_iso_to_eia), then the End-of-Block is appended (ISO: optional CR then LF; EIA: 0x80).
129 *
130 * @return bytes written to @p out, or 0 on overflow or a character with no EIA
131 * representation (fail-closed - nothing partial is emitted as a complete block).
132 */
133size_t protocore_dnc_encode_block(const DncCfg *cfg, const char *line, size_t line_len, uint8_t *out, size_t out_cap);
134
135/**
136 * @brief Emit the `%` program-start (or -end) marker followed by an End-of-Block.
137 *
138 * ISO writes '%' (with parity if configured); EIA writes End-of-Record (0x0B). Both then
139 * write the End-of-Block. Start and end are byte-identical; call it at both ends of the program.
140 *
141 * @return bytes written, or 0 on overflow.
142 */
143size_t protocore_dnc_encode_marker(const DncCfg *cfg, uint8_t *out, size_t out_cap);
144
145/**
146 * @brief Emit @ref DncCfg::leader_len runout bytes (NUL - skipped by the reader until `%`).
147 * @return bytes written (== leader_len), or 0 if @p out_cap is too small.
148 */
149size_t protocore_dnc_encode_leader(const DncCfg *cfg, uint8_t *out, size_t out_cap);
150
151/** @brief What ::protocore_dnc_decode_feed produced for the byte just fed. */
152typedef enum PROTO_ENUM_PACKED
153{
154 DNC_EV_NONE = 0, ///< byte absorbed (mid-block, runout, or flow/ignored); nothing to report.
155 DNC_EV_LINE, ///< a complete non-empty block is ready in DncDecoder::line (NUL-terminated).
156 DNC_EV_PROG_START, ///< the first `%` / EOR was seen (program start).
157 DNC_EV_PROG_END, ///< a later `%` / EOR was seen (program end).
158 DNC_EV_OVERFLOW, ///< the current block exceeded PROTOCORE_DNC_LINE_MAX; it was dropped.
159} DncEvent;
160
161/** @brief Streaming block reassembler: wire bytes in, ASCII G-code lines out. */
162typedef struct
163{
164 DncCode code; ///< the tape code being decoded.
165 char line[PROTOCORE_DNC_LINE_MAX + 1]; ///< the current block, NUL-terminated when DNC_EV_LINE fires.
166 uint16_t len; ///< bytes accumulated in @ref line so far (the line length on DNC_EV_LINE).
167 proto_bool overflow; ///< the current block overran; drop until the next End-of-Block.
168 proto_bool in_program; ///< a program-start `%` has been seen (so the next `%` is the end).
169 proto_bool line_ready; ///< internal: the previous feed delivered a line; reset on the next feed.
170} DncDecoder;
171
172/** @brief Reset a decoder for a given tape code. */
173void protocore_dnc_decode_init(DncDecoder *d, DncCode code);
174
175/**
176 * @brief Feed one wire byte to the block reassembler.
177 *
178 * Strips parity (ISO) / translates (EIA), skips runout (NUL / DEL / CR), accumulates a
179 * block until its End-of-Block, and reports the `%` program markers. XON/XOFF are not filtered
180 * here - flow control rides the reverse channel (see ::protocore_dnc_flow_feed); in the forward program
181 * stream 0x13 is the EIA data character '3', not DC3. On ::DNC_EV_LINE the
182 * completed line is in @ref DncDecoder::line (NUL-terminated) and @ref DncDecoder::len is its
183 * length; both are reset on the next call.
184 *
185 * @return the event for this byte (see ::DncEvent).
186 */
187DncEvent protocore_dnc_decode_feed(DncDecoder *d, uint8_t wire);
188
190
191#endif // PROTOCORE_ENABLE_DNC
192
193#endif // PROTOCORE_DNC_H
#define PROTOCORE_DNC_LINE_MAX
Largest G-code block (one line) the DNC decoder reassembles (PROTOCORE_ENABLE_DNC).
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#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