ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
directnet.cpp
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 directnet.cpp
6 * @brief AutomationDirect / Koyo DirectNET serial frame codec (see directnet.h).
7 */
8
10
11#if PC_ENABLE_DIRECTNET
12
13#include <string.h>
14
15namespace
16{
17char hex_digit(uint8_t nibble)
18{
19 nibble &= 0x0F;
20 return (char)(nibble < 10 ? ('0' + nibble) : ('A' + nibble - 10));
21}
22// Write @p value as @p digits ASCII-hex chars (big-endian) at p.
23void put_hex(uint8_t *p, uint32_t value, int digits)
24{
25 for (int i = 0; i < digits; i++)
26 {
27 p[i] = (uint8_t)hex_digit((uint8_t)(value >> (4 * (digits - 1 - i))));
28 }
29}
30} // namespace
31
32uint8_t pc_dnet_lrc(const uint8_t *bytes, size_t len)
33{
34 uint8_t lrc = 0;
35 for (size_t i = 0; i < len; i++)
36 {
37 lrc ^= bytes[i];
38 }
39 return lrc;
40}
41
42size_t pc_dnet_header(uint8_t slave, uint8_t type, uint16_t address, uint8_t blocks, uint8_t *out, size_t cap)
43{
44 // SOH + slave(2) + type(1) + addr(4) + blocks(2) + ETB + LRC = 11 bytes.
45 const size_t n = 1 + 2 + 1 + 4 + 2 + 1 + 1;
46 if (!out || cap < n)
47 {
48 return 0;
49 }
50 size_t i = 0;
51 out[i++] = DnetByte::DNET_SOH;
52 put_hex(out + i, slave, 2);
53 i += 2;
54 out[i++] = type;
55 put_hex(out + i, address, 4);
56 i += 4;
57 put_hex(out + i, blocks, 2);
58 i += 2;
59 out[i++] = DnetByte::DNET_ETB;
60 // LRC over the framed body (slave..ETB), i.e. everything after SOH up to and including ETB.
61 out[i] = pc_dnet_lrc(out + 1, i - 1);
62 i++;
63 return i;
64}
65
66size_t pc_dnet_data(const uint8_t *data, size_t data_len, uint8_t *out, size_t cap)
67{
68 if (!out || (data_len && !data))
69 {
70 return 0;
71 }
72 size_t n = 1 + data_len + 1 + 1; // STX + data + ETX + LRC
73 if (n > cap)
74 {
75 return 0;
76 }
77 size_t i = 0;
78 out[i++] = DnetByte::DNET_STX;
79 if (data_len)
80 {
81 memcpy(out + i, data, data_len);
82 i += data_len;
83 }
84 out[i++] = DnetByte::DNET_ETX;
85 // LRC over data..ETX (everything after STX up to and including ETX).
86 out[i] = pc_dnet_lrc(out + 1, i - 1);
87 i++;
88 return i;
89}
90
91bool pc_dnet_data_parse(const uint8_t *frame, size_t len, const uint8_t **data, size_t *data_len)
92{
93 if (!frame || len < 3) // STX + ETX + LRC minimum
94 {
95 return false;
96 }
97 if (frame[0] != DnetByte::DNET_STX)
98 {
99 return false;
100 }
101 // The byte before the LRC must be ETX.
102 size_t etx_idx = len - 2;
103 if (frame[etx_idx] != DnetByte::DNET_ETX)
104 {
105 return false;
106 }
107 if (pc_dnet_lrc(frame + 1, len - 2) != frame[len - 1]) // over data..ETX
108 {
109 return false;
110 }
111 if (data)
112 {
113 *data = (etx_idx > 1) ? (frame + 1) : nullptr;
114 }
115 if (data_len)
116 {
117 *data_len = etx_idx - 1;
118 }
119 return true;
120}
121
122#endif // PC_ENABLE_DIRECTNET
AutomationDirect / Koyo DirectNET serial frame codec (PC_ENABLE_DIRECTNET).