mirror of
https://github.com/esphome/esphome.git
synced 2025-01-06 21:01:43 +01:00
213 lines
7.5 KiB
C++
213 lines
7.5 KiB
C++
#include "drayton_protocol.h"
|
|
#include "esphome/core/log.h"
|
|
|
|
namespace esphome {
|
|
namespace remote_base {
|
|
|
|
static const char *const TAG = "remote.drayton";
|
|
|
|
static const uint32_t BIT_TIME_US = 500;
|
|
static const uint8_t CARRIER_KHZ = 2;
|
|
static const uint8_t NBITS_PREAMBLE = 12;
|
|
static const uint8_t NBITS_SYNC = 4;
|
|
static const uint8_t NBITS_ADDRESS = 16;
|
|
static const uint8_t NBITS_CHANNEL = 5;
|
|
static const uint8_t NBITS_COMMAND = 7;
|
|
static const uint8_t NBITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND;
|
|
|
|
static const uint8_t CMD_ON = 0x41;
|
|
static const uint8_t CMD_OFF = 0x02;
|
|
|
|
/*
|
|
Drayton Protocol
|
|
Using an oscilloscope to capture the data transmitted by the Digistat two
|
|
distinct packets for 'On' and 'Off' are transmitted. Each transmitted bit
|
|
has a period of 500us, a bit rate of 2000 baud.
|
|
|
|
Each packet consists of an initial 1010 pattern to set up the receiver bias.
|
|
The number of these bits seen at the receiver varies depending on the state
|
|
of the bias when the packet transmission starts. The receiver algoritmn takes
|
|
account of this.
|
|
|
|
The packet appears to be Manchester encoded, with a '10' tranmitted pair
|
|
representing a '1' bit and a '01' pair representing a '0' bit. Each packet is
|
|
begun with a '1100' syncronisation symbol which breaks this rule. Following
|
|
the sync are 28 '01' or '10' pairs.
|
|
|
|
--------------------
|
|
|
|
Boiler On Command as received:
|
|
101010101010110001101001010101101001010101010101100101010101101001011001
|
|
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
|
|
|
|
(Where pppp represents the preamble bits and SSSS represents the sync symbol)
|
|
|
|
28 bits of data received 01100001100000001000001 10010 (bin) or 6180832 (hex)
|
|
|
|
Boiler Off Command as received:
|
|
101010101010110001101001010101101001010101010101010101010110011001011001
|
|
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
|
|
|
|
28 bits of data received 0110000110000000000001010010 (bin) or 6180052 (hex)
|
|
|
|
--------------------
|
|
|
|
I have used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) to
|
|
capture and retransmit the Digistat packets. RFLink splits each packet into an
|
|
ID, SWITCH, and CMD field.
|
|
|
|
0;17;Drayton;ID=c300;SWITCH=12;CMD=ON;
|
|
20;18;Drayton;ID=c300;SWITCH=12;CMD=OFF;
|
|
|
|
--------------------
|
|
|
|
Spliting my received data into three parts of 16, 7 and 5 bits gives address,
|
|
channel and Command values of:
|
|
|
|
On 6180832 0110000110000000 1000001 10010
|
|
address: '0x6180' channel: '0x12' command: '0x41'
|
|
|
|
Off 6180052 0110000110000000 0000010 10010
|
|
address: '0x6180' channel: '0x12' command: '0x02'
|
|
|
|
These values are slightly different to those used by RFLink (the RFLink
|
|
ID/Adress value is rotated/manipulated), and I don't know who's interpretation
|
|
is correct. A larger data sample would help (I have only found five different
|
|
packet captures online) or definitive information from Drayton.
|
|
|
|
Splitting each packet in this way works well for me with esphome. Any
|
|
corrections or additional data samples would be gratefully received.
|
|
|
|
marshn
|
|
|
|
*/
|
|
|
|
void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) {
|
|
uint16_t khz = CARRIER_KHZ;
|
|
dst->set_carrier_frequency(khz * 1000);
|
|
|
|
// Preamble = 101010101010
|
|
uint32_t out_data = 0x0AAA;
|
|
for (uint32_t mask = 1UL << (NBITS_PREAMBLE - 1); mask != 0; mask >>= 1) {
|
|
if (out_data & mask) {
|
|
dst->mark(BIT_TIME_US);
|
|
} else {
|
|
dst->space(BIT_TIME_US);
|
|
}
|
|
}
|
|
|
|
// Sync = 1100
|
|
out_data = 0x000C;
|
|
for (uint32_t mask = 1UL << (NBITS_SYNC - 1); mask != 0; mask >>= 1) {
|
|
if (out_data & mask) {
|
|
dst->mark(BIT_TIME_US);
|
|
} else {
|
|
dst->space(BIT_TIME_US);
|
|
}
|
|
}
|
|
|
|
ESP_LOGD(TAG, "Send Drayton: address=%04x channel=%03x cmd=%02x", data.address, data.channel, data.command);
|
|
|
|
out_data = data.address;
|
|
out_data <<= NBITS_COMMAND;
|
|
out_data |= data.command;
|
|
out_data <<= NBITS_CHANNEL;
|
|
out_data |= data.channel;
|
|
|
|
ESP_LOGV(TAG, "Send Drayton: out_data %08x", out_data);
|
|
|
|
for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) {
|
|
if (out_data & mask) {
|
|
dst->mark(BIT_TIME_US);
|
|
dst->space(BIT_TIME_US);
|
|
} else {
|
|
dst->space(BIT_TIME_US);
|
|
dst->mark(BIT_TIME_US);
|
|
}
|
|
}
|
|
}
|
|
|
|
optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
|
DraytonData out{
|
|
.address = 0,
|
|
.channel = 0,
|
|
.command = 0,
|
|
};
|
|
|
|
if (src.size() < 45) {
|
|
return {};
|
|
}
|
|
|
|
ESP_LOGVV(TAG, "Decode Drayton: %d, %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(),
|
|
src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7),
|
|
src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
|
|
src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
|
|
|
|
// If first preamble item is a space, skip it
|
|
if (src.peek_space_at_least(1)) {
|
|
src.advance(1);
|
|
}
|
|
|
|
// Look for sync pulse, after. If sucessful index points to space of sync symbol
|
|
for (uint16_t preamble = 0; preamble <= NBITS_PREAMBLE * 2; preamble += 2) {
|
|
ESP_LOGVV(TAG, "Decode Drayton: preamble %d %d %d", preamble, src.peek(preamble), src.peek(preamble + 1));
|
|
if (src.peek_mark(2 * BIT_TIME_US, preamble) &&
|
|
(src.peek_space(2 * BIT_TIME_US, preamble + 1) || src.peek_space(3 * BIT_TIME_US, preamble + 1))) {
|
|
src.advance(preamble + 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Read data. Index points to space of sync symbol
|
|
// Extract first bit
|
|
// Checks next bit to leave index pointing correctly
|
|
uint32_t out_data = 0;
|
|
uint8_t bit = NBITS_ADDRESS + NBITS_COMMAND + NBITS_CHANNEL - 1;
|
|
if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
|
|
out_data |= 0 << bit;
|
|
} else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) &&
|
|
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
|
|
out_data |= 1 << bit;
|
|
} else {
|
|
ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %d", src.get_index());
|
|
return {};
|
|
}
|
|
|
|
// Before/after each bit is read the index points to the transition at the start of the bit period or,
|
|
// if there is no transition at the start of the bit period, then the transition in the middle of
|
|
// the previous bit period.
|
|
while (--bit >= 1) {
|
|
ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
|
|
if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) &&
|
|
(src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
|
|
out_data |= 0 << bit;
|
|
} else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) &&
|
|
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
|
|
out_data |= 1 << bit;
|
|
} else {
|
|
ESP_LOGVV(TAG, "Decode Drayton: Fail 2, %2d %08x", bit, out_data);
|
|
return {};
|
|
}
|
|
}
|
|
if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) {
|
|
out_data |= 0;
|
|
} else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) {
|
|
out_data |= 1;
|
|
}
|
|
ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
|
|
|
|
out.channel = (uint8_t) (out_data & 0x1F);
|
|
out_data >>= NBITS_CHANNEL;
|
|
out.command = (uint8_t) (out_data & 0x7F);
|
|
out_data >>= NBITS_COMMAND;
|
|
out.address = (uint16_t) (out_data & 0xFFFF);
|
|
|
|
return out;
|
|
}
|
|
void DraytonProtocol::dump(const DraytonData &data) {
|
|
ESP_LOGD(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address,
|
|
((data.address << 1) & 0xffff), data.channel, data.command);
|
|
}
|
|
|
|
} // namespace remote_base
|
|
} // namespace esphome
|