ESPHome 2025.5.0
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 {
7namespace remote_base {
8
9static const char *const TAG = "remote.drayton";
10
11static const uint32_t BIT_TIME_US = 500;
12static const uint8_t CARRIER_KHZ = 2;
13static const uint8_t NBITS_PREAMBLE = 12;
14static const uint8_t NBITS_SYNC = 4;
15static const uint8_t NBITS_ADDRESS = 16;
16static const uint8_t NBITS_CHANNEL = 5;
17static const uint8_t NBITS_COMMAND = 7;
18static const uint8_t NDATABITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND;
19static const uint8_t MIN_RX_SRC = (NDATABITS + NBITS_SYNC / 2);
20
21static const uint8_t CMD_ON = 0x41;
22static const uint8_t CMD_OFF = 0x02;
23
24/*
25Drayton Protocol
26Using an oscilloscope to capture the data transmitted by the Digistat two
27distinct packets for 'On' and 'Off' are transmitted. Each transmitted bit
28has a period of 500us, a bit rate of 2000 baud.
29
30Each packet consists of an initial 1010 pattern to set up the receiver bias.
31The number of these bits seen at the receiver varies depending on the state
32of the bias when the packet transmission starts. The receiver algoritmn takes
33account of this.
34
35The packet appears to be Manchester encoded, with a '10' tranmitted pair
36representing a '1' bit and a '01' pair representing a '0' bit. Each packet is
37begun with a '1100' syncronisation symbol which breaks this rule. Following
38the sync are 28 '01' or '10' pairs.
39
40--------------------
41
42Boiler On Command as received:
43101010101010110001101001010101101001010101010101100101010101101001011001
44ppppppppppppSSSS-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
4828 bits of data received 01100001100000001000001 10010 (bin) or 6180832 (hex)
49
50Boiler Off Command as received:
51101010101010110001101001010101101001010101010101010101010110011001011001
52ppppppppppppSSSS-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
5428 bits of data received 0110000110000000000001010010 (bin) or 6180052 (hex)
55
56--------------------
57
58I have used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) to
59capture and retransmit the Digistat packets. RFLink splits each packet into an
60ID, SWITCH, and CMD field.
61
620;17;Drayton;ID=c300;SWITCH=12;CMD=ON;
6320;18;Drayton;ID=c300;SWITCH=12;CMD=OFF;
64
65--------------------
66
67Spliting my received data into three parts of 16, 7 and 5 bits gives address,
68channel and Command values of:
69
70On 6180832 0110000110000000 1000001 10010
71address: '0x6180' channel: '0x12' command: '0x41'
72
73Off 6180052 0110000110000000 0000010 10010
74address: '0x6180' channel: '0x12' command: '0x02'
75
76These values are slightly different to those used by RFLink (the RFLink
77ID/Adress value is rotated/manipulated), and I don't know who's interpretation
78is correct. A larger data sample would help (I have only found five different
79packet captures online) or definitive information from Drayton.
80
81Splitting each packet in this way works well for me with esphome. Any
82corrections or additional data samples would be gratefully received.
83
84marshn
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
optional< DraytonData > decode(RemoteReceiveData src) override
void encode(RemoteTransmitData *dst, const DraytonData &data) override
void dump(const DraytonData &data) override
bool peek_space(uint32_t length, uint32_t offset=0) const
int32_t peek(uint32_t offset=0) const
Definition remote_base.h:58
bool peek_space_at_least(uint32_t length, uint32_t offset=0) const
bool peek_mark(uint32_t length, uint32_t offset=0) const
void set_carrier_frequency(uint32_t carrier_frequency)
Definition remote_base.h:34
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7