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