ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
enocean.h
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.h
6 * @brief EnOcean ESP3 serial codec (PC_ENABLE_ENOCEAN) - energy-harvesting 868 MHz.
7 *
8 * A UART telegram codec for EnOcean Serial Protocol 3 (ESP3), the framing every USB /
9 * serial EnOcean gateway (TCM 310 / USB 300) speaks. A telegram is:
10 *
11 * 0x55 | data-len (2, big-endian) | opt-len (1) | packet-type (1) | CRC8H
12 * | data[data-len] | opt[opt-len] | CRC8D
13 *
14 * where CRC8H protects the 4 header bytes and CRC8D protects the data + optional data (both
15 * CRC-8, polynomial 0x07, init 0). pc_esp3_parse() frames one telegram out of a byte stream,
16 * resynchronizing on a bad sync / CRC, and pc_esp3_build() assembles one. This is the radio-
17 * plugin codec for the gateway: an inbound RADIO_ERP1 telegram carries a sender id (its
18 * source address) and payload; bridge it northbound with pc_gateway_uplink(). Pure - you feed
19 * it the UART bytes - so it is fully host-testable. See example EnOceanGateway.
20 *
21 * @author Douglas Quigg (dstroy0)
22 * @date 2026
23 */
24
25#ifndef PROTOCORE_ENOCEAN_H
26#define PROTOCORE_ENOCEAN_H
27
28#include "protocore_config.h"
29
30#if PC_ENABLE_ENOCEAN
31
32#include <stddef.h>
33#include <stdint.h>
34
35/** @brief ESP3 sync byte that starts every telegram. */
36#define ESP3_SYNC 0x55
37
38/** @brief ESP3 packet types (the common ones). */
39enum class pc_esp3_type : uint8_t
40{
41 ESP3_RADIO_ERP1 = 0x01,
42 ESP3_RESPONSE = 0x02,
43 ESP3_RADIO_SUB_TEL = 0x03,
44 ESP3_EVENT = 0x04,
45 ESP3_COMMON_COMMAND = 0x05,
46 ESP3_SMART_ACK = 0x06,
47 ESP3_REMOTE_MAN = 0x07,
48 ESP3_RADIO_ERP2 = 0x0A,
49};
50
51/** @brief A parsed ESP3 telegram (pointers alias the caller's buffer). */
52struct pc_esp3_packet
53{
54 const uint8_t *data; ///< data field
55 const uint8_t *opt; ///< optional-data field
56 uint16_t data_len; ///< data length
57 uint8_t opt_len; ///< optional-data length
58 pc_esp3_type type; ///< packet type (pc_esp3_type)
59};
60
61/** @brief CRC-8 used by ESP3 (polynomial 0x07, MSB-first, init 0x00). */
62uint8_t pc_esp3_crc8(const uint8_t *buf, uint16_t len);
63
64/**
65 * @brief Frame one ESP3 telegram from the front of @p raw.
66 * @return the telegram length consumed (> 0, fields in @p out) if a valid telegram is at
67 * @p raw[0]; 0 if more bytes are needed to complete it; -1 if @p raw[0] is not a
68 * valid telegram start (wrong sync, header CRC, data CRC, or an over-length data
69 * field) - the caller should drop one byte and retry to resynchronize.
70 */
71int pc_esp3_parse(const uint8_t *raw, uint16_t len, pc_esp3_packet *out);
72
73/**
74 * @brief Assemble an ESP3 telegram into @p out.
75 * @return the total telegram length, or 0 if it would not fit @p cap or @p data_len exceeds
76 * PC_ENOCEAN_MAX_DATA.
77 */
78uint16_t pc_esp3_build(pc_esp3_type type, const uint8_t *data, uint16_t data_len, const uint8_t *opt, uint8_t opt_len,
79 uint8_t *out, uint16_t cap);
80
81// --- ERP1 radio telegram (the data field of a RADIO_ERP1 ESP3 packet) ---
82//
83// An ERP1 telegram is: a RORG (telegram-type) octet, a RORG-specific payload, the 4-octet sender id
84// (big-endian), and a status octet. The payload length is fixed per RORG (RPS / 1BS carry 1 octet, 4BS
85// carries 4) or variable (VLD); it is always (data length - 6), the 6 being RORG + sender id + status.
86
87// Common RORG (telegram-type) codes.
88#define PC_ERP_RORG_RPS 0xF6 ///< Repeated Switch communication (rocker switches): 1 payload octet
89#define PC_ERP_RORG_1BS 0xD5 ///< 1-byte communication (contacts): 1 payload octet
90#define PC_ERP_RORG_4BS 0xA5 ///< 4-byte communication (sensors): 4 payload octets
91#define PC_ERP_RORG_VLD 0xD2 ///< Variable-Length Data
92#define PC_ERP_RORG_MSC 0xD1 ///< Manufacturer-Specific Communication
93#define PC_ERP_RORG_ADT 0xA6 ///< Addressing Destination Telegram
94#define PC_ERP_RORG_UTE 0xD4 ///< Universal Teach-in
95
96/** @brief A decoded ERP1 radio telegram (the payload aliases the caller's buffer). */
97struct pc_erp1
98{
99 uint8_t rorg; ///< telegram type (PC_ERP_RORG_*)
100 const uint8_t *payload; ///< RORG-specific data, or nullptr if none
101 uint8_t payload_len; ///< payload octets (data length - 6)
102 uint32_t sender_id; ///< 4-octet sender id (big-endian)
103 uint8_t status; ///< status octet (repeater count + telegram-type bits)
104};
105
106/**
107 * @brief Decode an ERP1 radio telegram: RORG + payload + 4-octet sender id + status octet.
108 * @return true iff @p len is at least 6 octets (RORG + sender id + status); false otherwise.
109 */
110bool pc_erp1_parse(const uint8_t *data, uint16_t len, pc_erp1 *out);
111
112/**
113 * @brief Assemble an ERP1 radio telegram (the inverse of pc_erp1_parse): @p rorg + @p payload + the 4-octet
114 * @p sender_id (big-endian) + @p status. The result is the data field of a RADIO_ERP1 ESP3 packet -
115 * wrap it with pc_esp3_build to transmit, so a device can send a switch / actuator command.
116 * @return the telegram length (1 + @p payload_len + 5), or 0 on a null buffer, a null payload with a nonzero
117 * length, or an overflow.
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#endif // PC_ENABLE_ENOCEAN
123
124#endif // PROTOCORE_ENOCEAN_H
User-facing configuration for ProtoCore.