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