ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
lonworks.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 lonworks.h
6 * @brief LonWorks / LON-IP (ISO/IEC 14908) network-variable codec (PROTOCORE_ENABLE_LONWORKS).
7 *
8 * LonWorks is the ISO/IEC 14908 building-automation network. Devices exchange **network variables**
9 * (SNVTs - Standard Network Variable Types) as LonTalk application PDUs. LON/IP (14908-4) tunnels those
10 * PDUs over UDP so a device speaks LON without a Neuron chip. This codec builds/parses the LonTalk
11 * application-layer message a network-variable update carries:
12 *
13 * [1 d sel13..8 : 1][sel7..0 : 1][value...]
14 *
15 * where bit 7 marks the APDU a network variable message, `d` is the direction (1 outgoing, 0 incoming),
16 * and the remaining six bits join the second octet to form the 14-bit selector that addresses the bound
17 * network variable. The value is the SNVT-encoded data. It also provides the two most-common
18 * SNVT scalar encodings from the LONMARK SNVT master list: **SNVT_temp** (index 39, tenths of a degree
19 * Celsius above -274) and **SNVT_switch** (index 95, a level 0..100 % in 0.5 % steps + a state), so an
20 * app reads/writes those without a full SNVT table. Pure, zero heap, no stdlib, host-testable; the
21 * LON/IP UDP transport is the shipped UDP layer.
22 */
23
24#ifndef PROTOCORE_LONWORKS_H
25#define PROTOCORE_LONWORKS_H
26
27#include "protocore_config.h" // the entry point: protocore_types.h for the widths
28
29#if PROTOCORE_ENABLE_LONWORKS
30
32
33// This module holds nothing between calls, so it carves no borrow and states none. An entry
34// takes one all the same, and never reads it, so every namespace in the tree is invoked the
35// same way.
36
37// LonTalk NV header fields: wire values, so integer constants in a struct.
38#define LON_NV_HDR_LEN 2 ///< the NV APDU header is two octets.
39#define LON_NV_TYPE_MASK 0xC0 ///< octet 0 bits 7..6: the message bit and the direction bit.
40#define LON_NV_SEL_HI_MASK 0x3F ///< octet 0 bits 5..0: selector bits 13..8.
41#define LON_MSG_NV_UPDATE 0x80 ///< message bit set, direction incoming: `1 0 000000`.
42#define LON_MSG_NV_POLL 0x81 ///< message bit set, direction incoming, selector bit 0 set.
43#define LON_NV_SELECTOR_MAX 0x3FFF ///< the NV selector is 14 bits.
44
45/** @brief A parsed LonTalk NV PDU (value points into the input). */
46typedef struct
47{
48 uint8_t msg_code;
49 uint16_t selector;
50 const uint8_t *value;
51 size_t value_len;
52} LonNv;
53
54/** @brief What build_nv takes: msg_code, selector, value, value_len, ... */
55typedef struct
56{
57 uint8_t msg_code; ///< supplies bits 7..6; its low bits are the caller's and are dropped
58 uint16_t selector; ///< the 14-bit NV selector (0..0x3FFF)
59 const uint8_t *value; ///< the SNVT-encoded value (may be null if value_len == 0)
60 size_t value_len; ///< value length
61 uint8_t *out;
62 size_t cap;
63} LonworksBuildNvArgs;
64
65/** @brief What parse_nv takes: pdu, len, out. */
66typedef struct
67{
68 const uint8_t *pdu;
69 size_t len;
70 LonNv *out;
71} LonworksParseNvArgs;
72
73/** @brief What snvt_temp_encode takes: celsius, out. */
74typedef struct
75{
76 double celsius;
77 uint8_t *out; ///< 2 bytes.
78} LonworksSnvtTempEncodeArgs;
79
80/** @brief What snvt_temp_decode takes: in. */
81typedef struct
82{
83 const uint8_t *in; ///< 2 bytes.
84} LonworksSnvtTempDecodeArgs;
85
86/** @brief What snvt_switch_encode takes: percent, state, out. */
87typedef struct
88{
89 double percent;
90 uint8_t state;
91 uint8_t *out; ///< 2 bytes.
92} LonworksSnvtSwitchEncodeArgs;
93
94/** @brief What snvt_switch_decode takes: in, percent, state. */
95typedef struct
96{
97 const uint8_t *in; ///< 2 bytes.
98 double *percent;
99 uint8_t *state;
100} LonworksSnvtSwitchDecodeArgs;
101
102/**
103 * @brief LonWorks / LON-IP (ISO/IEC 14908) network-variable codec (PROTOCORE_ENABLE_LONWORKS).
104 *
105 * A caller sets the members a call takes, invokes it through ::Lonworks with the bytes it runs
106 * out of, and reads the outcome off the same handle.
107 *
108 * Lonworks.build_nv_args.msg_code = ...;
109 * Lonworks.build_nv_args.selector = ...;
110 * Lonworks.build_nv_args.value = ...;
111 * Lonworks.build_nv_args.value_len = ...;
112 * Lonworks.build_nv_args.out = ...;
113 * Lonworks.build_nv_args.cap = ...;
114 * Lonworks.build_nv(work);
115 * // Lonworks.n is what the call reports
116 *
117 * @var LonworksNs::build_nv_args what build_nv takes: msg_code, selector, value, value_len,
118 * @var LonworksNs::parse_nv_args what parse_nv takes: pdu, len, out
119 * @var LonworksNs::snvt_temp_encode_args what snvt_temp_encode takes: celsius, out
120 * @var LonworksNs::snvt_temp_decode_args what snvt_temp_decode takes: in
121 * @var LonworksNs::snvt_switch_encode_args what snvt_switch_encode takes: percent, state, out
122 * @var LonworksNs::snvt_switch_decode_args what snvt_switch_decode takes: in, percent, state
123 * @var LonworksNs::ok a call's true/false outcome
124 * @var LonworksNs::n the PDU length (2 + value_len), or 0 on overflow / bad args
125 * @var LonworksNs::value the value a call reports
126 * @var LonworksNs::build_nv build a LonTalk NV application PDU: ...
127 * @var LonworksNs::parse_nv parse a LonTalk NV PDU. true if len >= 3
128 * @var LonworksNs::snvt_temp_encode encode degrees C as the 2-byte big-endian SNVT_temp raw: (celsius * ...
129 * @var LonworksNs::snvt_temp_decode decode a SNVT_temp 2-byte value to degrees C: (raw - 2740) / 10
130 * @var LonworksNs::snvt_switch_encode encode a SNVT_switch (value 0..100 % in 0.5 % steps, state 0 OFF / ...
131 * @var LonworksNs::snvt_switch_decode decode a SNVT_switch 2-byte value (percent out via percent, state ...
132 *
133 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
134 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
135 * a caller drives every namespace the same way.
136 */
137typedef struct
138{
139 LonworksBuildNvArgs build_nv_args;
140 LonworksParseNvArgs parse_nv_args;
141 LonworksSnvtTempEncodeArgs snvt_temp_encode_args;
142 LonworksSnvtTempDecodeArgs snvt_temp_decode_args;
143 LonworksSnvtSwitchEncodeArgs snvt_switch_encode_args;
144 LonworksSnvtSwitchDecodeArgs snvt_switch_decode_args;
145 proto_bool ok;
146 size_t n;
147 double value;
148} LonworksVars;
149
150/** @brief The operands and the outcome. */
151extern LonworksVars LonworksV;
152
153/** @brief The entries. */
154typedef struct
155{
156 void (*const build_nv)(uint8_t *work);
157 void (*const parse_nv)(uint8_t *work);
158 void (*const snvt_temp_encode)(uint8_t *work);
159 void (*const snvt_temp_decode)(uint8_t *work);
160 void (*const snvt_switch_encode)(uint8_t *work);
161 void (*const snvt_switch_decode)(uint8_t *work);
162} LonworksNs;
163
164// What the table binds, defined once in the .c and taking one parameter each: everything
165// else an entry needs is an operand in LonworksV or a region of the borrow at a fixed offset.
166void protocore_lonworks_build_nv(uint8_t *work);
167void protocore_lonworks_parse_nv(uint8_t *work);
168void protocore_lonworks_snvt_temp_encode(uint8_t *work);
169void protocore_lonworks_snvt_temp_decode(uint8_t *work);
170void protocore_lonworks_snvt_switch_encode(uint8_t *work);
171void protocore_lonworks_snvt_switch_decode(uint8_t *work);
172
173// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
174// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
175// `Lonworks.build_nv(work)` resolves to a named function and becomes a DIRECT call. An extern table
176// leaves the call indirect and the symbol live at every level, -O2 -flto included.
177static const LonworksNs Lonworks __attribute__((unused)) = {
178 .build_nv = protocore_lonworks_build_nv,
179 .parse_nv = protocore_lonworks_parse_nv,
180 .snvt_temp_encode = protocore_lonworks_snvt_temp_encode,
181 .snvt_temp_decode = protocore_lonworks_snvt_temp_decode,
182 .snvt_switch_encode = protocore_lonworks_snvt_switch_encode,
183 .snvt_switch_decode = protocore_lonworks_snvt_switch_decode,
184};
185
187
188#endif // PROTOCORE_ENABLE_LONWORKS
189
190#endif // PROTOCORE_LONWORKS_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