ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
enocean.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 enocean.cpp
6 * @brief EnOcean ESP3 serial codec - implementation.
7 *
8 * ESP3 telegram: 0x55 | data-len(2) | opt-len(1) | type(1) | CRC8H | data | opt | CRC8D.
9 * CRC-8 is polynomial 0x07, MSB-first, init 0x00 (the ESP3 u8CRC8Table generator).
10 */
11
13
14#if PC_ENABLE_ENOCEAN
15
16#include <string.h>
17
18#include "shared_primitives/crc.h" // PC_CRC8_SMBUS
19
20uint8_t pc_esp3_crc8(const uint8_t *buf, uint16_t len)
21{
22 // The ESP3 CRC-8 (the u8CRC8Table generator) is the cataloge's CRC-8/SMBUS: poly 0x07,
23 // MSB-first, init 0, no final XOR. test_crc diffs the shared engine against the loop that used
24 // to live here over every length 0..64, so this is byte-identical to it.
25 return (uint8_t)pc_crc(&PC_CRC8_SMBUS, buf, len);
26}
27
28int pc_esp3_parse(const uint8_t *raw, uint16_t len, pc_esp3_packet *out)
29{
30 if (!raw || len < 1)
31 {
32 return 0;
33 }
34 if (raw[0] != ESP3_SYNC)
35 {
36 return -1; // not a telegram start
37 }
38 if (len < 6)
39 {
40 return 0; // need sync + 4-byte header + CRC8H
41 }
42 uint16_t data_len = (uint16_t)((raw[1] << 8) | raw[2]);
43 uint8_t opt_len = raw[3];
44 uint8_t type = raw[4];
45 if (data_len > PC_ENOCEAN_MAX_DATA)
46 {
47 return -1; // implausible length -> resynchronize
48 }
49 if (pc_esp3_crc8(&raw[1], 4) != raw[5])
50 {
51 return -1; // header CRC mismatch
52 }
53 uint32_t total = 6u + data_len + opt_len + 1u;
54 if (len < total)
55 {
56 return 0; // wait for the rest of the telegram
57 }
58 if (pc_esp3_crc8(&raw[6], (uint16_t)(data_len + opt_len)) != raw[6 + data_len + opt_len])
59 {
60 return -1; // data CRC mismatch
61 }
62 if (out)
63 {
64 out->type = (pc_esp3_type)type;
65 out->data = &raw[6];
66 out->data_len = data_len;
67 out->opt = &raw[6 + data_len];
68 out->opt_len = opt_len;
69 }
70 return (int)total;
71}
72
73uint16_t pc_esp3_build(pc_esp3_type type, const uint8_t *data, uint16_t data_len, const uint8_t *opt, uint8_t opt_len,
74 uint8_t *out, uint16_t cap)
75{
76 if (!out || data_len > PC_ENOCEAN_MAX_DATA)
77 {
78 return 0;
79 }
80 uint32_t total = 6u + data_len + opt_len + 1u;
81 if (total > cap)
82 {
83 return 0;
84 }
85 out[0] = ESP3_SYNC;
86 out[1] = (uint8_t)(data_len >> 8);
87 out[2] = (uint8_t)(data_len & 0xFF);
88 out[3] = opt_len;
89 out[4] = (uint8_t)type;
90 out[5] = pc_esp3_crc8(&out[1], 4);
91 for (uint16_t i = 0; i < data_len; i++)
92 {
93 out[6 + i] = data[i];
94 }
95 for (uint8_t i = 0; i < opt_len; i++)
96 {
97 out[6 + data_len + i] = opt[i];
98 }
99 out[6 + data_len + opt_len] = pc_esp3_crc8(&out[6], (uint16_t)(data_len + opt_len));
100 return (uint16_t)total;
101}
102
103bool pc_erp1_parse(const uint8_t *data, uint16_t len, pc_erp1 *out)
104{
105 if (!data || !out || len < 6) // RORG(1) + sender id(4) + status(1)
106 {
107 return false;
108 }
109 out->rorg = data[0];
110 out->payload = (len > 6) ? data + 1 : nullptr;
111 out->payload_len = (uint8_t)(len - 6);
112 const uint8_t *id = data + len - 5; // the 4-octet sender id precedes the status octet
113 out->sender_id =
114 ((uint32_t)id[0] << 24) | ((uint32_t)id[1] << 16) | ((uint32_t)id[2] << 8) | (uint32_t)id[3]; // big-endian
115 out->status = data[len - 1];
116 return true;
117}
118
119uint16_t pc_erp1_build(uint8_t *out, uint16_t cap, uint8_t rorg, const uint8_t *payload, uint8_t payload_len,
120 uint32_t sender_id, uint8_t status)
121{
122 if (!out || (payload_len && !payload))
123 {
124 return 0;
125 }
126 uint16_t total = (uint16_t)(1 + payload_len + 4 + 1); // RORG + payload + sender id + status
127 if (total > cap)
128 {
129 return 0;
130 }
131 uint16_t p = 0;
132 out[p++] = rorg;
133 if (payload_len)
134 {
135 memcpy(out + p, payload, payload_len);
136 p = (uint16_t)(p + payload_len);
137 }
138 out[p++] = (uint8_t)(sender_id >> 24); // 4-octet sender id, big-endian
139 out[p++] = (uint8_t)(sender_id >> 16);
140 out[p++] = (uint8_t)(sender_id >> 8);
141 out[p++] = (uint8_t)sender_id;
142 out[p++] = status;
143 return p;
144}
145
146#endif // PC_ENABLE_ENOCEAN
Parameterized CRC engine - one source of truth for every cyclic redundancy check.
constexpr pc_crc_params PC_CRC8_SMBUS
CRC-8/SMBUS (a.k.a. CRC-8). check = 0xF4.
Definition crc.h:173
uint32_t pc_crc(const pc_crc_params *p, const uint8_t *data, size_t len)
One-shot CRC of len octets at data.
Definition crc.h:158
EnOcean ESP3 serial codec (PC_ENABLE_ENOCEAN) - energy-harvesting 868 MHz.
#define PC_ENOCEAN_MAX_DATA
Reject an ESP3 telegram whose declared data length exceeds this (framing sanity).