DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
sdi12.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 sdi12.cpp
6 * @brief SDI-12 command / response codec (pure, host-tested).
7 */
8
10
11#if DETWS_ENABLE_SDI12
12
14#include <string.h>
15
16size_t sdi12_build(char *buf, size_t cap, char addr, const char *body)
17{
18 if (!buf || !body)
19 return 0;
20 size_t blen = strnlen(body, cap);
21 size_t n = 1 + blen + 1; // addr + body + '!'
22 if (cap < n + 1) // + room for the NUL terminator
23 return 0;
24 buf[0] = addr;
25 memcpy(buf + 1, body, blen);
26 buf[1 + blen] = '!';
27 buf[n] = '\0';
28 return n;
29}
30
31size_t sdi12_build_ack(char *buf, size_t cap, char addr)
32{
33 return sdi12_build(buf, cap, addr, "");
34}
35
36size_t sdi12_build_identify(char *buf, size_t cap, char addr)
37{
38 return sdi12_build(buf, cap, addr, "I");
39}
40
41size_t sdi12_build_measure(char *buf, size_t cap, char addr, bool with_crc)
42{
43 return sdi12_build(buf, cap, addr, with_crc ? "MC" : "M");
44}
45
46size_t sdi12_build_concurrent(char *buf, size_t cap, char addr, bool with_crc)
47{
48 return sdi12_build(buf, cap, addr, with_crc ? "CC" : "C");
49}
50
51size_t sdi12_build_data(char *buf, size_t cap, char addr, uint8_t d_index)
52{
53 if (d_index > 9)
54 return 0;
55 char body[3] = {'D', (char)('0' + d_index), '\0'};
56 return sdi12_build(buf, cap, addr, body);
57}
58
59size_t sdi12_build_change_address(char *buf, size_t cap, char addr, char new_addr)
60{
61 char body[3] = {'A', new_addr, '\0'};
62 return sdi12_build(buf, cap, addr, body);
63}
64
65size_t sdi12_build_query_address(char *buf, size_t cap)
66{
67 return sdi12_build(buf, cap, '?', "");
68}
69
70bool sdi12_parse_measure(const char *resp, size_t len, char *addr, uint16_t *ready_sec, uint8_t *num_values)
71{
72 if (!resp || len < 5) // a<ttt><n> is at least 5 octets
73 return false;
74 for (int i = 1; i <= 3; i++)
75 if (!det_np_digit(resp[i]))
76 return false;
77 if (!det_np_digit(resp[4]))
78 return false;
79 if (addr)
80 *addr = resp[0];
81 if (ready_sec)
82 *ready_sec = (uint16_t)((resp[1] - '0') * 100 + (resp[2] - '0') * 10 + (resp[3] - '0'));
83 // The value count is the remaining digits (1 digit for aM!, 2 for aC!).
84 uint16_t count = 0;
85 for (size_t i = 4; i < len && det_np_digit(resp[i]); i++)
86 count = (uint16_t)(count * 10 + (resp[i] - '0'));
87 if (num_values)
88 *num_values = (uint8_t)count;
89 return true;
90}
91
92bool sdi12_parse_values(const char *resp, size_t len, float *out, size_t max, size_t *n)
93{
94 if (!resp || !out || !n)
95 return false;
96 size_t cnt = 0;
97 size_t i = 1; // skip the leading address
98 while (i < len && cnt < max)
99 {
100 char c = resp[i];
101 if (c == '\r' || c == '\n')
102 break;
103 if (c == '+' || c == '-')
104 {
105 const char *start = resp + i;
106 const char *end = start;
107 // det_strtof handles a leading '-'; for '+' parse the magnitude after the sign.
108 float v = (c == '+') ? det_strtof(start + 1, &end) : det_strtof(start, &end);
109 if (end == start || (c == '+' && end == start + 1)) // no digits consumed
110 {
111 i++;
112 continue;
113 }
114 out[cnt++] = v;
115 i = (size_t)(end - resp);
116 }
117 else
118 {
119 i++; // CRC octets / separators are skipped (they never begin with +/-)
120 }
121 }
122 *n = cnt;
123 return true;
124}
125
126uint16_t sdi12_crc16(const uint8_t *data, size_t len)
127{
128 uint16_t crc = 0;
129 for (size_t i = 0; i < len; i++)
130 {
131 crc ^= data[i];
132 for (int b = 0; b < 8; b++)
133 crc = (crc & 1u) ? (uint16_t)((crc >> 1) ^ SDI12_CRC_POLY) : (uint16_t)(crc >> 1);
134 }
135 return crc;
136}
137
138void sdi12_crc_encode(uint16_t crc, char out[SDI12_CRC_CHARS])
139{
140 out[0] = (char)(0x40u | (crc >> 12)); // top bits
141 out[1] = (char)(0x40u | ((crc >> 6) & 0x3Fu));
142 out[2] = (char)(0x40u | (crc & 0x3Fu));
143}
144
145bool sdi12_check_crc(const char *resp, size_t len)
146{
147 if (!resp)
148 return false;
149 // Trim a trailing <CR><LF> if present.
150 while (len > 0 && (resp[len - 1] == '\n' || resp[len - 1] == '\r'))
151 len--;
152 if (len < SDI12_CRC_CHARS + 1) // need at least 1 data octet + the 3 CRC octets
153 return false;
154 size_t data_len = len - SDI12_CRC_CHARS;
155 char enc[SDI12_CRC_CHARS];
156 sdi12_crc_encode(sdi12_crc16((const uint8_t *)resp, data_len), enc);
157 return memcmp(enc, resp + data_len, SDI12_CRC_CHARS) == 0;
158}
159
160#endif // DETWS_ENABLE_SDI12
Tiny no-stdlib base-10 number parsers (strtol/strtoul/strtof replacements).
float det_strtof(const char *s, const char **end)
Parse a float (integer[.frac][e[+/-]exp]); sets end (or to s if none).
Definition numparse.h:124
bool det_np_digit(char c)
Definition numparse.h:26
SDI-12 sensor-bus command / response codec (DETWS_ENABLE_SDI12).