ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
keeloq_protocol.cpp
Go to the documentation of this file.
1#include "keeloq_protocol.h"
2#include "esphome/core/log.h"
3
4#include <cinttypes>
5
6namespace esphome::remote_base {
7
8ESPHOME_LOG_TAG(TAG, "remote.keeloq");
9
10static constexpr uint32_t BIT_TIME_US = 380;
11static constexpr uint8_t NBITS_PREAMBLE = 12;
12static constexpr uint8_t NBITS_REPEAT = 1;
13static constexpr uint8_t NBITS_VLOW = 1;
14static constexpr uint8_t NBITS_SERIAL = 28;
15static constexpr uint8_t NBITS_BUTTONS = 4;
16static constexpr uint8_t NBITS_DISC = 12;
17static constexpr uint8_t NBITS_SYNC_CNT = 16;
18
19static constexpr uint8_t NBITS_FIXED_DATA = NBITS_REPEAT + NBITS_VLOW + NBITS_BUTTONS + NBITS_SERIAL;
20static constexpr uint8_t NBITS_ENCRYPTED_DATA = NBITS_BUTTONS + NBITS_DISC + NBITS_SYNC_CNT;
21static constexpr uint8_t NBITS_DATA = NBITS_FIXED_DATA + NBITS_ENCRYPTED_DATA;
22static constexpr uint8_t NBITS_SUFFIX_MAX = 16;
23// A space this long is not part of the PWM: data spaces are at most 2 bit times, the guard is 39.
24static constexpr uint32_t GAP_TIME_US = 4 * BIT_TIME_US;
25
26/*
27KeeLoq Protocol
28
29Coded using information from datasheet for Microchip HCS301 KeeLow Code Hopping Encoder
30
31Encoder - Hopping code is not generated. Send the encrypted field from the caller.
32Optional suffix bits may follow the 66-bit HCS301 word (same PWM coding).
33
34Decoder - Hopping code is ignored and not checked when received. Serial number of
35transmitter and button command is decoded. Extra bits after the word are returned
36as suffix / suffix_bits when present.
37
38*/
39
41 uint32_t out_data = 0x0;
42
43 ESP_LOGD(TAG, "Send Keeloq: address=%07" PRIx32 " command=%03x encrypted=%08" PRIx32 " suffix_bits=%u", data.address,
44 data.command, data.encrypted, data.suffix_bits);
45 ESP_LOGV(TAG, "Send Keeloq: data bits (%d + %d)", NBITS_ENCRYPTED_DATA, NBITS_FIXED_DATA);
46
47 // Preamble = '01' x 12
48 for (uint8_t cnt = NBITS_PREAMBLE; cnt; cnt--) {
49 dst->space(BIT_TIME_US);
50 dst->mark(BIT_TIME_US);
51 }
52
53 // Header = 10 bit space
54 dst->space(10 * BIT_TIME_US);
55
56 // Encrypted field
57 out_data = data.encrypted;
58
59 ESP_LOGV(TAG, "Send Keeloq: Encrypted data %04" PRIx32, out_data);
60
61 for (uint32_t mask = 1, cnt = 0; cnt < NBITS_ENCRYPTED_DATA; cnt++, mask <<= 1) {
62 if (out_data & mask) {
63 dst->mark(1 * BIT_TIME_US);
64 dst->space(2 * BIT_TIME_US);
65 } else {
66 dst->mark(2 * BIT_TIME_US);
67 dst->space(1 * BIT_TIME_US);
68 }
69 }
70
71 // first 32 bits of fixed portion
72 out_data = (data.command & 0x0f);
73 out_data <<= NBITS_SERIAL;
74 out_data |= data.address;
75 ESP_LOGV(TAG, "Send Keeloq: Fixed data %04" PRIx32, out_data);
76
77 for (uint32_t mask = 1, cnt = 0; cnt < (NBITS_FIXED_DATA - 2); cnt++, mask <<= 1) {
78 if (out_data & mask) {
79 dst->mark(1 * BIT_TIME_US);
80 dst->space(2 * BIT_TIME_US);
81 } else {
82 dst->mark(2 * BIT_TIME_US);
83 dst->space(1 * BIT_TIME_US);
84 }
85 }
86
87 // low battery flag
88 if (data.vlow) {
89 dst->mark(1 * BIT_TIME_US);
90 dst->space(2 * BIT_TIME_US);
91 } else {
92 dst->mark(2 * BIT_TIME_US);
93 dst->space(1 * BIT_TIME_US);
94 }
95
96 // repeat flag
97 if (data.repeat) {
98 dst->mark(1 * BIT_TIME_US);
99 dst->space(2 * BIT_TIME_US);
100 } else {
101 dst->mark(2 * BIT_TIME_US);
102 dst->space(1 * BIT_TIME_US);
103 }
104
105 const uint8_t extra = data.suffix_bits > NBITS_SUFFIX_MAX ? NBITS_SUFFIX_MAX : data.suffix_bits;
106 for (uint8_t i = 0; i < extra; i++) {
107 if (data.suffix & (1 << i)) {
108 dst->mark(1 * BIT_TIME_US);
109 dst->space(2 * BIT_TIME_US);
110 } else {
111 dst->mark(2 * BIT_TIME_US);
112 dst->space(1 * BIT_TIME_US);
113 }
114 }
115
116 // Guard time at end of packet
117 dst->space(39 * BIT_TIME_US);
118}
119
122 .encrypted = 0,
123 .address = 0,
124 .command = 0,
125 .repeat = false,
126 .vlow = false,
127 .suffix = 0,
128 .suffix_bits = 0,
129 };
130
131 if (src.size() < (NBITS_PREAMBLE + NBITS_DATA) * 2) {
132 return {};
133 }
134
135 ESP_LOGVV(TAG,
136 "%2" PRId32 ": %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
137 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
138 " %" PRId32 " %" PRId32 " %" PRId32,
139 src.size(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6),
140 src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
141 src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
142
143 // Check preamble bits
144 int8_t bit = NBITS_PREAMBLE - 1;
145 while (--bit >= 0) {
146 if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(BIT_TIME_US)) {
147 ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %" PRId32, bit + 1, src.peek());
148 return {};
149 }
150 }
151 if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(10 * BIT_TIME_US)) {
152 ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %" PRId32, bit + 1, src.peek());
153 return {};
154 }
155
156 // Read encrypted bits
157 uint32_t out_data = 0;
158 for (bit = 0; bit < NBITS_ENCRYPTED_DATA; bit++) {
159 if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) {
160 out_data |= 0 << bit;
161 } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
162 out_data |= 1 << bit;
163 } else {
164 ESP_LOGV(TAG, "Decode KeeLoq: Fail 2, %" PRIu32 " %" PRId32, src.get_index(), src.peek());
165 return {};
166 }
167 }
168 ESP_LOGVV(TAG, "Decode KeeLoq: Data, %d %08" PRIx32, bit, out_data);
169 out.encrypted = out_data;
170
171 // Read Serial Number and Button Status
172 out_data = 0;
173 for (bit = 0; bit < NBITS_SERIAL + NBITS_BUTTONS; bit++) {
174 if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) {
175 out_data |= 0 << bit;
176 } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
177 out_data |= 1 << bit;
178 } else {
179 ESP_LOGV(TAG, "Decode KeeLoq: Fail 3, %" PRIu32 " %" PRId32, src.get_index(), src.peek());
180 return {};
181 }
182 }
183 ESP_LOGVV(TAG, "Decode KeeLoq: Data, %2d %08" PRIx32, bit, out_data);
184 out.command = (out_data >> 28) & 0xf;
185 out.address = out_data & 0xfffffff;
186
187 // Read Vlow bit
188 if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) {
189 out.vlow = false;
190 } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
191 out.vlow = true;
192 } else {
193 ESP_LOGV(TAG, "Decode KeeLoq: Fail 4, %" PRId32, src.peek());
194 return {};
195 }
196
197 // Read Repeat bit
198 if (src.expect_mark(2 * BIT_TIME_US) && src.peek_space_at_least(BIT_TIME_US)) {
199 out.repeat = false;
200 } else if (src.expect_mark(BIT_TIME_US) && src.peek_space_at_least(2 * BIT_TIME_US)) {
201 out.repeat = true;
202 } else {
203 ESP_LOGV(TAG, "Decode KeeLoq: Fail 5, %" PRId32, src.peek());
204 return {};
205 }
206
207 if (out.repeat) {
208 src.expect_space(2 * BIT_TIME_US);
209 } else {
210 src.expect_space(BIT_TIME_US);
211 }
212
213 while (out.suffix_bits < NBITS_SUFFIX_MAX) {
214 bool one = false;
215 if (src.expect_mark(2 * BIT_TIME_US)) {
216 one = false;
217 } else if (src.expect_mark(BIT_TIME_US)) {
218 one = true;
219 } else {
220 break;
221 }
222 // The last bit's space is merged into the guard gap, or the capture ends at idle.
223 if (!src.expect_space(one ? 2 * BIT_TIME_US : BIT_TIME_US) && src.is_valid() &&
224 !src.peek_space_at_least(GAP_TIME_US)) {
225 break;
226 }
227 if (one) {
228 out.suffix |= 1 << out.suffix_bits;
229 }
230 out.suffix_bits++;
231 }
232 // The suffix ends at a gap or at the end of the capture. PWM that goes on is not a suffix.
233 if (src.is_valid() && !src.peek_space_at_least(GAP_TIME_US)) {
234 out.suffix = 0;
235 out.suffix_bits = 0;
236 }
237
238 return out;
239}
240
242 if (data.suffix_bits == 6) {
243 // Jarolift TDEF frame: show the group as transmit_keeloq takes it.
244 ESP_LOGD(TAG, "Received Keeloq: address=0x%08" PRIx32 ", command=0x%02x, encrypted=0x%08" PRIx32 ", group=0x%02x",
245 data.address, data.command, data.encrypted, data.group());
246 return;
247 }
248 ESP_LOGD(TAG, "Received Keeloq: address=0x%08" PRIx32 ", command=0x%02x, encrypted=0x%08" PRIx32 ", suffix=0x%04x/%u",
249 data.address, data.command, data.encrypted, data.suffix, data.suffix_bits);
250}
251
252} // namespace esphome::remote_base
void encode(RemoteTransmitData *dst, const KeeloqData &data)
optional< KeeloqData > decode(RemoteReceiveData src)
void dump(const KeeloqData &data)
std::vector< uint8_t > & out
Definition gif.cpp:49
ESPHOME_LOG_TAG(TAG, "remote.abbwelcome")
const void * src
Definition hal.h:68
static void uint32_t