ESPHome  2024.4.1
drayton_protocol.cpp
Go to the documentation of this file.
1 #include "drayton_protocol.h"
2 #include "esphome/core/log.h"
3 
4 namespace esphome {
5 namespace remote_base {
6 
7 static const char *const TAG = "remote.drayton";
8 
9 static const uint32_t BIT_TIME_US = 500;
10 static const uint8_t CARRIER_KHZ = 2;
11 static const uint8_t NBITS_PREAMBLE = 12;
12 static const uint8_t NBITS_SYNC = 4;
13 static const uint8_t NBITS_ADDRESS = 16;
14 static const uint8_t NBITS_CHANNEL = 5;
15 static const uint8_t NBITS_COMMAND = 7;
16 static const uint8_t NDATABITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND;
17 static const uint8_t MIN_RX_SRC = (NDATABITS + NBITS_SYNC / 2);
18 
19 static const uint8_t CMD_ON = 0x41;
20 static const uint8_t CMD_OFF = 0x02;
21 
22 /*
23 Drayton Protocol
24 Using an oscilloscope to capture the data transmitted by the Digistat two
25 distinct packets for 'On' and 'Off' are transmitted. Each transmitted bit
26 has a period of 500us, a bit rate of 2000 baud.
27 
28 Each packet consists of an initial 1010 pattern to set up the receiver bias.
29 The number of these bits seen at the receiver varies depending on the state
30 of the bias when the packet transmission starts. The receiver algoritmn takes
31 account of this.
32 
33 The packet appears to be Manchester encoded, with a '10' tranmitted pair
34 representing a '1' bit and a '01' pair representing a '0' bit. Each packet is
35 begun with a '1100' syncronisation symbol which breaks this rule. Following
36 the sync are 28 '01' or '10' pairs.
37 
38 --------------------
39 
40 Boiler On Command as received:
41 101010101010110001101001010101101001010101010101100101010101101001011001
42 ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-1-0-0-0-0-0-1-1-0-0-1-0
43 
44 (Where pppp represents the preamble bits and SSSS represents the sync symbol)
45 
46 28 bits of data received 01100001100000001000001 10010 (bin) or 6180832 (hex)
47 
48 Boiler Off Command as received:
49 101010101010110001101001010101101001010101010101010101010110011001011001
50 ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-0-0-0-0-0-1-0-1-0-0-1-0
51 
52 28 bits of data received 0110000110000000000001010010 (bin) or 6180052 (hex)
53 
54 --------------------
55 
56 I have used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) to
57 capture and retransmit the Digistat packets. RFLink splits each packet into an
58 ID, SWITCH, and CMD field.
59 
60 0;17;Drayton;ID=c300;SWITCH=12;CMD=ON;
61 20;18;Drayton;ID=c300;SWITCH=12;CMD=OFF;
62 
63 --------------------
64 
65 Spliting my received data into three parts of 16, 7 and 5 bits gives address,
66 channel and Command values of:
67 
68 On 6180832 0110000110000000 1000001 10010
69 address: '0x6180' channel: '0x12' command: '0x41'
70 
71 Off 6180052 0110000110000000 0000010 10010
72 address: '0x6180' channel: '0x12' command: '0x02'
73 
74 These values are slightly different to those used by RFLink (the RFLink
75 ID/Adress value is rotated/manipulated), and I don't know who's interpretation
76 is correct. A larger data sample would help (I have only found five different
77 packet captures online) or definitive information from Drayton.
78 
79 Splitting each packet in this way works well for me with esphome. Any
80 corrections or additional data samples would be gratefully received.
81 
82 marshn
83 
84 */
85 
87  uint16_t khz = CARRIER_KHZ;
88  dst->set_carrier_frequency(khz * 1000);
89 
90  // Preamble = 101010101010
91  uint32_t out_data = 0x0AAA;
92  for (uint32_t mask = 1UL << (NBITS_PREAMBLE - 1); mask != 0; mask >>= 1) {
93  if (out_data & mask) {
94  dst->mark(BIT_TIME_US);
95  } else {
96  dst->space(BIT_TIME_US);
97  }
98  }
99 
100  // Sync = 1100
101  out_data = 0x000C;
102  for (uint32_t mask = 1UL << (NBITS_SYNC - 1); mask != 0; mask >>= 1) {
103  if (out_data & mask) {
104  dst->mark(BIT_TIME_US);
105  } else {
106  dst->space(BIT_TIME_US);
107  }
108  }
109 
110  ESP_LOGD(TAG, "Send Drayton: address=%04x channel=%03x cmd=%02x", data.address, data.channel, data.command);
111 
112  out_data = data.address;
113  out_data <<= NBITS_COMMAND;
114  out_data |= data.command;
115  out_data <<= NBITS_CHANNEL;
116  out_data |= data.channel;
117 
118  ESP_LOGV(TAG, "Send Drayton: out_data %08" PRIx32, out_data);
119 
120  for (uint32_t mask = 1UL << (NDATABITS - 1); mask != 0; mask >>= 1) {
121  if (out_data & mask) {
122  dst->mark(BIT_TIME_US);
123  dst->space(BIT_TIME_US);
124  } else {
125  dst->space(BIT_TIME_US);
126  dst->mark(BIT_TIME_US);
127  }
128  }
129 }
130 
132  DraytonData out{
133  .address = 0,
134  .channel = 0,
135  .command = 0,
136  };
137 
138  while (src.size() - src.get_index() >= MIN_RX_SRC) {
139  ESP_LOGVV(TAG,
140  "Decode Drayton: %" PRId32 ", %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
141  " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
142  " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 "",
143  src.size() - src.get_index(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4),
144  src.peek(5), src.peek(6), src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12),
145  src.peek(13), src.peek(14), src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
146 
147  // If first preamble item is a space, skip it
148  if (src.peek_space_at_least(1)) {
149  src.advance(1);
150  }
151 
152  // Look for sync pulse, after. If sucessful index points to space of sync symbol
153  while (src.size() - src.get_index() >= MIN_RX_SRC) {
154  ESP_LOGVV(TAG, "Decode Drayton: sync search %d, %" PRId32 " %" PRId32, src.size() - src.get_index(), src.peek(),
155  src.peek(1));
156  if (src.peek_mark(2 * BIT_TIME_US) &&
157  (src.peek_space(2 * BIT_TIME_US, 1) || src.peek_space(3 * BIT_TIME_US, 1))) {
158  src.advance(1);
159  ESP_LOGVV(TAG, "Decode Drayton: Found SYNC, - %d", src.get_index());
160  break;
161  } else {
162  src.advance(2);
163  }
164  }
165 
166  // No point continuing if not enough samples remaining to complete a packet
167  if (src.size() - src.get_index() < NDATABITS) {
168  ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %" PRIu32, src.get_index());
169  break;
170  }
171 
172  // Read data. Index points to space of sync symbol
173  // Extract first bit
174  // Checks next bit to leave index pointing correctly
175  uint32_t out_data = 0;
176  uint8_t bit = NDATABITS - 1;
177  ESP_LOGVV(TAG, "Decode Drayton: first bit %d %" PRId32 ", %" PRId32, src.peek(0), src.peek(1), src.peek(2));
178  if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
179  out_data |= 0 << bit;
180  } else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) &&
181  (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
182  out_data |= 1 << bit;
183  } else {
184  ESP_LOGV(TAG, "Decode Drayton: Fail 2, - %d %d %d", src.peek(-1), src.peek(0), src.peek(1));
185  continue;
186  }
187 
188  // Before/after each bit is read the index points to the transition at the start of the bit period or,
189  // if there is no transition at the start of the bit period, then the transition in the middle of
190  // the previous bit period.
191  while (--bit >= 1) {
192  ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08" PRIx32, bit, out_data);
193  if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) &&
194  (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
195  out_data |= 0 << bit;
196  } else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) &&
197  (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
198  out_data |= 1 << bit;
199  } else {
200  break;
201  }
202  }
203 
204  if (bit > 0) {
205  ESP_LOGVV(TAG, "Decode Drayton: Fail 3, %d %" PRId32 " %" PRId32, src.peek(-1), src.peek(0), src.peek(1));
206  continue;
207  }
208 
209  if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) {
210  out_data |= 0;
211  } else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) {
212  out_data |= 1;
213  } else {
214  continue;
215  }
216 
217  ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
218 
219  out.channel = (uint8_t) (out_data & 0x1F);
220  out_data >>= NBITS_CHANNEL;
221  out.command = (uint8_t) (out_data & 0x7F);
222  out_data >>= NBITS_COMMAND;
223  out.address = (uint16_t) (out_data & 0xFFFF);
224 
225  return out;
226  }
227  return {};
228 }
230  ESP_LOGI(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address,
231  ((data.address << 1) & 0xffff), data.channel, data.command);
232 }
233 
234 } // namespace remote_base
235 } // namespace esphome
void set_carrier_frequency(uint32_t carrier_frequency)
Definition: remote_base.h:29
bool peek_space(uint32_t length, uint32_t offset=0) const
Definition: remote_base.cpp:43
void encode(RemoteTransmitData *dst, const DraytonData &data) override
bool peek_mark(uint32_t length, uint32_t offset=0) const
Definition: remote_base.cpp:34
int32_t peek(uint32_t offset=0) const
Definition: remote_base.h:53
optional< DraytonData > decode(RemoteReceiveData src) override
void dump(const DraytonData &data) 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 advance(uint32_t amount=1)
Definition: remote_base.h:65
bool peek_space_at_least(uint32_t length, uint32_t offset=0) const
Definition: remote_base.cpp:52