ESPHome  2024.6.1
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 
6 namespace esphome {
7 namespace remote_base {
8 
9 static const char *const TAG = "remote.keeloq";
10 
11 static const uint32_t BIT_TIME_US = 380;
12 static const uint8_t NBITS_PREAMBLE = 12;
13 static const uint8_t NBITS_REPEAT = 1;
14 static const uint8_t NBITS_VLOW = 1;
15 static const uint8_t NBITS_SERIAL = 28;
16 static const uint8_t NBITS_BUTTONS = 4;
17 static const uint8_t NBITS_DISC = 12;
18 static const uint8_t NBITS_SYNC_CNT = 16;
19 
20 static const uint8_t NBITS_FIXED_DATA = NBITS_REPEAT + NBITS_VLOW + NBITS_BUTTONS + NBITS_SERIAL;
21 static const uint8_t NBITS_ENCRYPTED_DATA = NBITS_BUTTONS + NBITS_DISC + NBITS_SYNC_CNT;
22 static const uint8_t NBITS_DATA = NBITS_FIXED_DATA + NBITS_ENCRYPTED_DATA;
23 
24 /*
25 KeeLoq Protocol
26 
27 Coded using information from datasheet for Microchip HCS301 KeeLow Code Hopping Encoder
28 
29 Encoder - Hopping code is generated at random.
30 
31 Decoder - Hopping code is ignored and not checked when received. Serial number of
32 transmitter and nutton command is decoded.
33 
34 */
35 
37  uint32_t out_data = 0x0;
38 
39  ESP_LOGD(TAG, "Send Keeloq: address=%07" PRIx32 " command=%03x encrypted=%08" PRIx32, data.address, data.command,
40  data.encrypted);
41  ESP_LOGV(TAG, "Send Keeloq: data bits (%d + %d)", NBITS_ENCRYPTED_DATA, NBITS_FIXED_DATA);
42 
43  // Preamble = '01' x 12
44  for (uint8_t cnt = NBITS_PREAMBLE; cnt; cnt--) {
45  dst->space(BIT_TIME_US);
46  dst->mark(BIT_TIME_US);
47  }
48 
49  // Header = 10 bit space
50  dst->space(10 * BIT_TIME_US);
51 
52  // Encrypted field
53  out_data = data.encrypted;
54 
55  ESP_LOGV(TAG, "Send Keeloq: Encrypted data %04" PRIx32, out_data);
56 
57  for (uint32_t mask = 1, cnt = 0; cnt < NBITS_ENCRYPTED_DATA; cnt++, mask <<= 1) {
58  if (out_data & mask) {
59  dst->mark(1 * BIT_TIME_US);
60  dst->space(2 * BIT_TIME_US);
61  } else {
62  dst->mark(2 * BIT_TIME_US);
63  dst->space(1 * BIT_TIME_US);
64  }
65  }
66 
67  // first 32 bits of fixed portion
68  out_data = (data.command & 0x0f);
69  out_data <<= NBITS_SERIAL;
70  out_data |= data.address;
71  ESP_LOGV(TAG, "Send Keeloq: Fixed data %04" PRIx32, out_data);
72 
73  for (uint32_t mask = 1, cnt = 0; cnt < (NBITS_FIXED_DATA - 2); cnt++, mask <<= 1) {
74  if (out_data & mask) {
75  dst->mark(1 * BIT_TIME_US);
76  dst->space(2 * BIT_TIME_US);
77  } else {
78  dst->mark(2 * BIT_TIME_US);
79  dst->space(1 * BIT_TIME_US);
80  }
81  }
82 
83  // low battery flag
84  if (data.vlow) {
85  dst->mark(1 * BIT_TIME_US);
86  dst->space(2 * BIT_TIME_US);
87  } else {
88  dst->mark(2 * BIT_TIME_US);
89  dst->space(1 * BIT_TIME_US);
90  }
91 
92  // repeat flag - always sent as a '1'
93  dst->mark(1 * BIT_TIME_US);
94  dst->space(2 * BIT_TIME_US);
95 
96  // Guard time at end of packet
97  dst->space(39 * BIT_TIME_US);
98 }
99 
101  KeeloqData out{
102  .encrypted = 0,
103  .address = 0,
104  .command = 0,
105  .repeat = false,
106  .vlow = false,
107 
108  };
109 
110  if (src.size() != (NBITS_PREAMBLE + NBITS_DATA) * 2) {
111  return {};
112  }
113 
114  ESP_LOGVV(TAG,
115  "%2" PRId32 ": %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
116  " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
117  " %" PRId32 " %" PRId32 " %" PRId32,
118  src.size(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6),
119  src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
120  src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
121 
122  // Check preamble bits
123  int8_t bit = NBITS_PREAMBLE - 1;
124  while (--bit >= 0) {
125  if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(BIT_TIME_US)) {
126  ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %" PRId32, bit + 1, src.peek());
127  return {};
128  }
129  }
130  if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(10 * BIT_TIME_US)) {
131  ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %" PRId32, bit + 1, src.peek());
132  return {};
133  }
134 
135  // Read encrypted bits
136  uint32_t out_data = 0;
137  for (bit = 0; bit < NBITS_ENCRYPTED_DATA; bit++) {
138  if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) {
139  out_data |= 0 << bit;
140  } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
141  out_data |= 1 << bit;
142  } else {
143  ESP_LOGV(TAG, "Decode KeeLoq: Fail 2, %" PRIu32 " %" PRId32, src.get_index(), src.peek());
144  return {};
145  }
146  }
147  ESP_LOGVV(TAG, "Decode KeeLoq: Data, %d %08" PRIx32, bit, out_data);
148  out.encrypted = out_data;
149 
150  // Read Serial Number and Button Status
151  out_data = 0;
152  for (bit = 0; bit < NBITS_SERIAL + NBITS_BUTTONS; bit++) {
153  if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) {
154  out_data |= 0 << bit;
155  } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
156  out_data |= 1 << bit;
157  } else {
158  ESP_LOGV(TAG, "Decode KeeLoq: Fail 3, %" PRIu32 " %" PRId32, src.get_index(), src.peek());
159  return {};
160  }
161  }
162  ESP_LOGVV(TAG, "Decode KeeLoq: Data, %2d %08" PRIx32, bit, out_data);
163  out.command = (out_data >> 28) & 0xf;
164  out.address = out_data & 0xfffffff;
165 
166  // Read Vlow bit
167  if (src.expect_mark(2 * BIT_TIME_US) && src.expect_space(BIT_TIME_US)) {
168  out.vlow = false;
169  } else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
170  out.vlow = true;
171  } else {
172  ESP_LOGV(TAG, "Decode KeeLoq: Fail 4, %" PRId32, src.peek());
173  return {};
174  }
175 
176  // Read Repeat bit
177  if (src.expect_mark(2 * BIT_TIME_US) && src.peek_space_at_least(BIT_TIME_US)) {
178  out.repeat = false;
179  } else if (src.expect_mark(BIT_TIME_US) && src.peek_space_at_least(2 * BIT_TIME_US)) {
180  out.repeat = true;
181  } else {
182  ESP_LOGV(TAG, "Decode KeeLoq: Fail 5, %" PRId32, src.peek());
183  return {};
184  }
185 
186  return out;
187 }
188 
189 void KeeloqProtocol::dump(const KeeloqData &data) {
190  ESP_LOGD(TAG, "Received Keeloq: address=0x%08" PRIx32 ", command=0x%02x", data.address, data.command);
191 }
192 
193 } // namespace remote_base
194 } // namespace esphome
int32_t peek(uint32_t offset=0) const
Definition: remote_base.h:58
void dump(const KeeloqData &data) override
optional< KeeloqData > decode(RemoteReceiveData src) override
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
void encode(RemoteTransmitData *dst, const KeeloqData &data) override
bool peek_space_at_least(uint32_t length, uint32_t offset=0) const
Definition: remote_base.cpp:52