DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
hostlink.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 hostlink.cpp
6 * @brief Omron Host Link (C-mode) frame builder + parser (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_HOSTLINK
12
13#include <string.h>
14
15uint8_t hostlink_fcs(const char *data, size_t len)
16{
17 uint8_t f = 0;
18 for (size_t i = 0; i < len; i++)
19 f ^= (uint8_t)data[i];
20 return f;
21}
22
23static char hex_digit(uint8_t v)
24{
25 return (char)(v < 10 ? '0' + v : 'A' + (v - 10));
26}
27
28// Parse one hex digit (0-9 A-F a-f) into 0..15, or -1 if invalid.
29static int hex_val(char c)
30{
31 if (c >= '0' && c <= '9')
32 return c - '0';
33 if (c >= 'A' && c <= 'F')
34 return c - 'A' + 10;
35 if (c >= 'a' && c <= 'f')
36 return c - 'a' + 10;
37 return -1;
38}
39
40size_t hostlink_build(char *buf, size_t cap, uint8_t node, const char *header_code, const char *text, size_t text_len)
41{
42 if (!buf || !header_code || node > 99 || (text_len && !text))
43 return 0;
44 if (header_code[0] == '\0' || header_code[1] == '\0') // need exactly 2 header characters
45 return 0;
46 size_t total = 1 + 2 + 2 + text_len + 2 + 2; // @ + UU + XX + text + FCS + *CR
47 if (total >= cap) // need room for the frame + a NUL terminator
48 return 0;
49
50 size_t p = 0;
51 buf[p++] = '@';
52 buf[p++] = (char)('0' + node / 10);
53 buf[p++] = (char)('0' + node % 10);
54 buf[p++] = header_code[0];
55 buf[p++] = header_code[1];
56 if (text_len)
57 {
58 memcpy(buf + p, text, text_len);
59 p += text_len;
60 }
61 uint8_t f = hostlink_fcs(buf, p); // XOR over '@' .. end of text
62 buf[p++] = hex_digit((uint8_t)(f >> 4));
63 buf[p++] = hex_digit((uint8_t)(f & 0x0F));
64 buf[p++] = '*';
65 buf[p++] = '\r';
66 buf[p] = '\0'; // NUL-terminate so callers may treat the ASCII frame as a string (matches sdi12_build)
67 return p;
68}
69
70bool hostlink_parse(const char *buf, size_t len, HostlinkFrame *out)
71{
72 // minimum: @ UU XX FF * CR = 1 + 2 + 2 + 2 + 1 + 1 = 9
73 if (!buf || !out || len < 9)
74 return false;
75 if (buf[0] != '@' || buf[len - 1] != '\r' || buf[len - 2] != '*')
76 return false;
77 if (buf[1] < '0' || buf[1] > '9' || buf[2] < '0' || buf[2] > '9')
78 return false;
79
80 size_t fcs_pos = len - 4; // the two FCS chars precede '*' CR
81 int hi = hex_val(buf[fcs_pos]);
82 int lo = hex_val(buf[fcs_pos + 1]);
83 if (hi < 0 || lo < 0)
84 return false;
85 uint8_t got = (uint8_t)((hi << 4) | lo);
86 if (hostlink_fcs(buf, fcs_pos) != got) // XOR over '@' .. last text char
87 return false;
88
89 out->node = (uint8_t)((buf[1] - '0') * 10 + (buf[2] - '0'));
90 out->header_code[0] = buf[3];
91 out->header_code[1] = buf[4];
92 out->header_code[2] = '\0';
93 out->text = buf + 5;
94 out->text_len = fcs_pos - 5;
95 return true;
96}
97
98bool hostlink_end_code(const HostlinkFrame *f, uint8_t *code)
99{
100 if (!f || f->text_len < 2)
101 return false;
102 int hi = hex_val(f->text[0]);
103 int lo = hex_val(f->text[1]);
104 if (hi < 0 || lo < 0)
105 return false;
106 if (code)
107 *code = (uint8_t)((hi << 4) | lo);
108 return true;
109}
110
111#endif // DETWS_ENABLE_HOSTLINK