ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
lsv2.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 lsv2.h
6 * @brief Heidenhain LSV/2 telegram codec (PC_ENABLE_LSV2) - a zero-heap codec for the LSV/2 protocol
7 * Heidenhain TNC controls (iTNC 530, TNC 320/620/640, ...) speak for DNC and data access over a
8 * serial link or, as implemented here, LSV/2-over-TCP (default port 19000). A CNC-native
9 * southbound source for the common European control, alongside `services/focas` (Fanuc) and
10 * `services/haas_mdc` (Haas).
11 *
12 * Wire framing (byte-exact, both directions): a telegram is a 4-byte big-endian payload-length prefix,
13 * then a 4-character ASCII command / response mnemonic, then `payload-length` payload bytes. The length
14 * counts ONLY the payload - the mnemonic is not included - so a telegram with no payload is exactly 8
15 * bytes on the wire (e.g. a bare `T_OK` acknowledgement is `00 00 00 00 'T' '_' 'O' 'K'`). The command
16 * mnemonics group into login / logout (`A_LG` / `A_LO`), file system (`C_*` write, `R_*` read), and
17 * run / status inspection (`R_RI` / `R_ST` / `R_VR`); the control answers with a response mnemonic
18 * (`T_OK` done, `T_FD` file-data finished, `S_*` a data reply, or `T_ER` / `T_BD` a two-byte
19 * error-class + error-code).
20 *
21 * This codec owns the framing and the request builders: @ref pc_lsv2_build frames an arbitrary
22 * mnemonic + payload, @ref pc_lsv2_parse slices one complete telegram off a byte stream (reporting how
23 * many bytes it consumed so a caller can re-frame the rest), and the typed helpers build the common
24 * requests (@ref pc_lsv2_build_login, @ref pc_lsv2_build_filename, @ref pc_lsv2_build_run_info) and
25 * read the response (@ref pc_lsv2_is_ok, @ref pc_lsv2_error). The TCP / serial link to the control is
26 * the application's.
27 *
28 * Reference: the LSV/2 telegram framing + the command / response mnemonic set, cross-checked
29 * byte-for-byte against the `pyLSV2` reference implementation (drunsinn/pyLSV2, the widely-used open
30 * Python LSV/2 client) - the `!L` big-endian length prefix, the 4-character mnemonic, and the `!H`
31 * big-endian run-info selector.
32 *
33 * @author Douglas Quigg (dstroy0)
34 * @date 2026
35 */
36
37#ifndef PROTOCORE_LSV2_H
38#define PROTOCORE_LSV2_H
39
40#include "protocore_config.h"
41
42#if PC_ENABLE_LSV2
43
44#include <stddef.h>
45#include <stdint.h>
46
47/** @brief Default LSV/2-over-TCP port a Heidenhain control listens on. */
48#define PC_LSV2_TCP_PORT 19000
49/** @brief Every LSV/2 command / response mnemonic is exactly four ASCII characters. */
50#define PC_LSV2_MNEMONIC_LEN 4
51/** @brief Fixed telegram header: a 4-byte length prefix + the 4-byte mnemonic (payload follows). */
52#define PC_LSV2_HEADER_LEN 8
53
54// ── command mnemonics (CP -> control) ──────────────────────────────────────────────────────────
55#define PC_LSV2_CMD_LOGIN "A_LG" ///< gain access to a privilege group (payload: login[+password])
56#define PC_LSV2_CMD_LOGOUT "A_LO" ///< drop a privilege group (payload: optional login)
57#define PC_LSV2_CMD_FILE_LOAD "R_FL" ///< read a file from the control (payload: filename)
58#define PC_LSV2_CMD_FILE_SEND "C_FL" ///< send a file to the control (payload: filename)
59#define PC_LSV2_CMD_FILE_DELETE "C_FD" ///< delete a file (payload: filename)
60#define PC_LSV2_CMD_FILE_INFO "R_FI" ///< read info about a file (payload: filename)
61#define PC_LSV2_CMD_DIR_CHANGE "C_DC" ///< change the working directory (payload: path)
62#define PC_LSV2_CMD_DIR_MAKE "C_DM" ///< create a directory (payload: path)
63#define PC_LSV2_CMD_DIR_DELETE "C_DD" ///< delete a directory (payload: path)
64#define PC_LSV2_CMD_DIR_READ "R_DR" ///< read directory contents
65#define PC_LSV2_CMD_DIR_INFO "R_DI" ///< read info about the selected directory
66#define PC_LSV2_CMD_RUN_INFO "R_RI" ///< read run info (payload: 2-byte big-endian selector)
67#define PC_LSV2_CMD_STATUS "R_ST" ///< request remote status
68#define PC_LSV2_CMD_VERSION "R_VR" ///< read control version / identity info
69#define PC_LSV2_CMD_PARAM "R_PR" ///< read a parameter from the control
70
71// ── response mnemonics (control -> CP) ─────────────────────────────────────────────────────────
72#define PC_LSV2_RSP_OK "T_OK" ///< last transaction completed
73#define PC_LSV2_RSP_ERROR "T_ER" ///< transaction error (payload: error-class, error-code)
74#define PC_LSV2_RSP_FIN "T_FD" ///< all file data sent, transfer finished
75#define PC_LSV2_RSP_XFER_ERR "T_BD" ///< file-transfer error (payload: error-class, error-code)
76#define PC_LSV2_RSP_LONG "M_CC" ///< a long-running operation completed
77#define PC_LSV2_RSP_RUN_INFO "S_RI" ///< run-info data reply
78#define PC_LSV2_RSP_STATUS "S_ST" ///< status data reply
79#define PC_LSV2_RSP_VERSION "S_VR" ///< version data reply
80#define PC_LSV2_RSP_FILE "S_FL" ///< file-content data reply
81#define PC_LSV2_RSP_DIR "S_DR" ///< directory-content data reply
82
83// ── login privilege groups (payload for A_LG) ──────────────────────────────────────────────────
84#define PC_LSV2_LOGIN_INSPECT "INSPECT" ///< read machine state / parameters
85#define PC_LSV2_LOGIN_FILE "FILE" ///< file-system access
86#define PC_LSV2_LOGIN_DNC "DNC" ///< DNC functions
87#define PC_LSV2_LOGIN_MONITOR "MONITOR" ///< screen / keypad monitoring
88#define PC_LSV2_LOGIN_DIAG "DIAGNOSTICS" ///< diagnostics
89#define PC_LSV2_LOGIN_PLCDEBUG "PLCDEBUG" ///< PLC debug
90
91/** @brief Run-info selectors for @ref pc_lsv2_build_run_info (the 2-byte `R_RI` argument). */
92enum Lsv2RunInfo : uint16_t // NOSONAR(cpp:S3642): unscoped so a selector converts to the 2-byte R_RI
93 // argument without a cast; no other enum in this header shares its value space
94{
95 LSV2_RI_EXEC_STATE = 23, ///< program execution state (running / stopped / ...)
96 LSV2_RI_SELECTED_PGM = 24, ///< currently selected program
97 LSV2_RI_OVERRIDE = 25, ///< feed / rapid / spindle override values
98 LSV2_RI_PGM_STATE = 26, ///< program status
99};
100
101/** @brief One parsed telegram: the 4-char mnemonic (NOT null-terminated) plus the payload slice, which
102 * points INTO the caller's buffer (zero-copy; the buffer must outlive the struct). */
103struct Lsv2Telegram
104{
105 char mnemonic[PC_LSV2_MNEMONIC_LEN];
106 const uint8_t *payload;
107 size_t payload_len;
108};
109
110/**
111 * @brief Frame a telegram: a 4-byte big-endian @p payload_len, the 4-character @p mnemonic, then the
112 * payload. @p mnemonic must point at (at least) four characters; exactly four are copied.
113 * @return total bytes written (`PC_LSV2_HEADER_LEN + payload_len`), or 0 on overflow / bad input.
114 */
115size_t pc_lsv2_build(uint8_t *buf, size_t cap, const char *mnemonic, const uint8_t *payload, size_t payload_len);
116
117/**
118 * @brief Slice one complete telegram off @p buf: read the big-endian length prefix, and if the whole
119 * telegram (header + payload) is present, fill @p out and report the byte count via @p consumed
120 * (so the caller can advance past it and re-parse the remainder).
121 * @return true if a complete telegram was found; false if fewer than `8 + payload_len` bytes are
122 * available yet (the caller should accumulate more).
123 */
124bool pc_lsv2_parse(const uint8_t *buf, size_t len, Lsv2Telegram *out, size_t *consumed);
125
126/**
127 * @brief True if the parsed telegram's mnemonic equals the four characters at @p mnemonic4.
128 */
129bool pc_lsv2_is(const Lsv2Telegram *t, const char *mnemonic4);
130
131/**
132 * @brief Build a login telegram (`A_LG`): payload is the NUL-terminated privilege group (one of the
133 * `PC_LSV2_LOGIN_*` strings), optionally followed by a NUL-terminated @p password.
134 * @param password pass nullptr for a group that needs no password.
135 * @return total bytes written, or 0 on overflow / bad input.
136 */
137size_t pc_lsv2_build_login(uint8_t *buf, size_t cap, const char *login, const char *password);
138
139/**
140 * @brief Build a logout telegram (`A_LO`): payload is the NUL-terminated privilege group to drop, or
141 * empty when @p login is nullptr (log out of everything).
142 * @return total bytes written, or 0 on overflow / bad input.
143 */
144size_t pc_lsv2_build_logout(uint8_t *buf, size_t cap, const char *login);
145
146/**
147 * @brief Build a filename-argument command: the 4-char @p mnemonic (`R_FL` / `C_FL` / `C_FD` / `C_DC` /
148 * `C_DM` / `C_DD` / `R_FI`) followed by the NUL-terminated @p filename as the payload.
149 * @return total bytes written, or 0 on overflow / bad input.
150 */
151size_t pc_lsv2_build_filename(uint8_t *buf, size_t cap, const char *mnemonic, const char *filename);
152
153/**
154 * @brief Build a run-info request (`R_RI`) with @p info_code (an @ref Lsv2RunInfo selector) as a 2-byte
155 * big-endian payload.
156 * @return total bytes written (always `PC_LSV2_HEADER_LEN + 2` on success), or 0 on overflow.
157 */
158size_t pc_lsv2_build_run_info(uint8_t *buf, size_t cap, uint16_t info_code);
159
160/**
161 * @brief True if the response is `T_OK` (transaction completed).
162 */
163bool pc_lsv2_is_ok(const Lsv2Telegram *t);
164
165/**
166 * @brief True if the response is an error mnemonic (`T_ER` transaction error or `T_BD` transfer error).
167 */
168bool pc_lsv2_is_error(const Lsv2Telegram *t);
169
170/**
171 * @brief Decode an error response's two-byte payload into @p err_class and @p err_code.
172 * @return true on an error mnemonic (`T_ER` / `T_BD`) carrying exactly two payload bytes.
173 */
174bool pc_lsv2_error(const Lsv2Telegram *t, uint8_t *err_class, uint8_t *err_code);
175
176#endif // PC_ENABLE_LSV2
177
178#endif // PROTOCORE_LSV2_H
User-facing configuration for ProtoCore.