mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 02:02:51 +00:00
[remote_base] Add a data option and checksum handling to the Drayton protocol (#13060)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
@@ -1501,22 +1501,34 @@ async def raw_action(var, config, args):
|
||||
) = declare_protocol("Drayton")
|
||||
DRAYTON_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFF)),
|
||||
cv.Required(CONF_CHANNEL): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)),
|
||||
cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)),
|
||||
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_DATA): cv.hex_int_range(min=0, max=0xF),
|
||||
cv.Optional(CONF_CHANNEL): cv.hex_int_range(min=0, max=0x1F),
|
||||
cv.Optional(CONF_COMMAND): cv.hex_int_range(min=0, max=0x7F),
|
||||
}
|
||||
)
|
||||
# The legacy command is the 4 data bits followed by this many checksum bits
|
||||
DRAYTON_NBITS_CHECKSUM_IN_COMMAND = 3
|
||||
# Either `data`, or the legacy `channel` + `command` pair (raw packet bits)
|
||||
_validate_drayton_form = cv.All(
|
||||
cv.has_none_or_all_keys(CONF_CHANNEL, CONF_COMMAND),
|
||||
cv.has_exactly_one_key(CONF_DATA, CONF_COMMAND),
|
||||
)
|
||||
|
||||
|
||||
@register_binary_sensor("drayton", DraytonBinarySensor, DRAYTON_SCHEMA)
|
||||
@register_binary_sensor(
|
||||
"drayton", DraytonBinarySensor, cv.All(DRAYTON_SCHEMA, _validate_drayton_form)
|
||||
)
|
||||
def drayton_binary_sensor(var, config):
|
||||
# Matching compares address and data only
|
||||
if (command := config.get(CONF_COMMAND)) is not None:
|
||||
data = command >> DRAYTON_NBITS_CHECKSUM_IN_COMMAND
|
||||
else:
|
||||
data = config[CONF_DATA]
|
||||
cg.add(
|
||||
var.set_data(
|
||||
cg.StructInitializer(
|
||||
DraytonData,
|
||||
("address", config[CONF_ADDRESS]),
|
||||
("channel", config[CONF_CHANNEL]),
|
||||
("command", config[CONF_COMMAND]),
|
||||
DraytonData, ("address", config[CONF_ADDRESS]), ("data", data)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1537,8 +1549,10 @@ register_protocol_action(
|
||||
"Drayton",
|
||||
DRAYTON_SCHEMA,
|
||||
(CONF_ADDRESS, "address", cg.uint16),
|
||||
(CONF_DATA, "data", cg.uint8),
|
||||
(CONF_CHANNEL, "channel", cg.uint8),
|
||||
(CONF_COMMAND, "command", cg.uint8),
|
||||
validate=_validate_drayton_form,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -12,13 +12,12 @@ static constexpr uint8_t CARRIER_KHZ = 2;
|
||||
static constexpr uint8_t NBITS_PREAMBLE = 12;
|
||||
static constexpr uint8_t NBITS_SYNC = 4;
|
||||
static constexpr uint8_t NBITS_ADDRESS = 16;
|
||||
static constexpr uint8_t NBITS_CHANNEL = 5;
|
||||
static constexpr uint8_t NBITS_DATA = 4;
|
||||
static constexpr uint8_t NBITS_CHECKSUM = 8;
|
||||
static constexpr uint8_t NBITS_COMMAND = 7;
|
||||
static constexpr uint8_t NDATABITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND;
|
||||
static constexpr uint8_t MIN_RX_SRC = (NDATABITS + NBITS_SYNC / 2);
|
||||
|
||||
static constexpr uint8_t CMD_ON = 0x41;
|
||||
static constexpr uint8_t CMD_OFF = 0x02;
|
||||
static constexpr uint8_t NBITS_CHANNEL = 5;
|
||||
static constexpr uint8_t NBITS_PKT = NBITS_ADDRESS + NBITS_DATA + NBITS_CHECKSUM;
|
||||
static constexpr uint8_t MIN_RX_SRC = (NBITS_PKT + NBITS_SYNC / 2);
|
||||
|
||||
/*
|
||||
Drayton Protocol
|
||||
@@ -31,59 +30,72 @@ 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 packet is Manchester coded, 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:
|
||||
Sample 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)
|
||||
28 bits received 01100001100000001000001 10010 (bin) or 6180832 (hex)
|
||||
|
||||
Boiler Off Command as received:
|
||||
Sample 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)
|
||||
28 bits 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.
|
||||
The 28 bits consist of 20 bits of payload and 8 bits of checksum.
|
||||
|
||||
0;17;Drayton;ID=c300;SWITCH=12;CMD=ON;
|
||||
20;18;Drayton;ID=c300;SWITCH=12;CMD=OFF;
|
||||
In order of transmission the payload consists of the following fields:
|
||||
|
||||
--------------------
|
||||
Bits Description
|
||||
12 ID of the thermostat. A Digistat receiver will only respond to a packet if its ID
|
||||
matches the ID that the receiver has previously learnt.
|
||||
4 Pairing field, normally 0000. The first packets sent by the thermostat after its
|
||||
batteries are inserted have this field set to 0001, which lets a Digistat receiver
|
||||
in pairing mode learn the ID of the thermostat.
|
||||
1 Thermostat on (1) or off (0).
|
||||
1 Always 0.
|
||||
1 Low battery (1) or battery OK (0).
|
||||
1 Always 0.
|
||||
|
||||
Spliting my received data into three parts of 16, 7 and 5 bits gives address,
|
||||
channel and Command values of:
|
||||
The YAML/lambda 'address' is the first 16 bits as sent (the 12 bit ID followed by the
|
||||
pairing field, so the pairing flag is the address LSB), and 'data' is the next 4 bits
|
||||
(0x8 = on, 0x2 = low battery).
|
||||
|
||||
On 6180832 0110000110000000 1000001 10010
|
||||
address: '0x6180' channel: '0x12' command: '0x41'
|
||||
Checksum: see calc_cs(); the +1 when the sum is above 0x7F is empirical. The captured
|
||||
packets above give 0x32 (On) and 0x52 (Off). Packets that fail it are logged at verbose level.
|
||||
|
||||
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
|
||||
Legacy split (from RFLink): address (16), command (7: data + top 3 checksum bits), channel (5: low checksum bits).
|
||||
When channel and command are set they are sent as is, with no checksum calculated.
|
||||
|
||||
*/
|
||||
|
||||
static uint8_t calc_cs(uint32_t out_data) {
|
||||
uint16_t cs = 0;
|
||||
uint32_t rev = reverse_bits(out_data);
|
||||
|
||||
for (uint8_t i = 4; i <= 20; i += 4) {
|
||||
cs <<= 1;
|
||||
cs += (rev >> i) & 0x0F;
|
||||
}
|
||||
|
||||
if (cs > 0x7F) {
|
||||
cs += 1;
|
||||
}
|
||||
// Sent LSB first
|
||||
return reverse_bits(uint8_t(cs << 1));
|
||||
}
|
||||
|
||||
void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) {
|
||||
uint16_t khz = CARRIER_KHZ;
|
||||
dst->set_carrier_frequency(khz * 1000);
|
||||
@@ -108,17 +120,21 @@ void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) {
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Send Drayton: address=%04x channel=%03x cmd=%02x", data.address, data.channel, data.command);
|
||||
if (data.channel || data.command) {
|
||||
out_data = data.address;
|
||||
out_data <<= NBITS_COMMAND;
|
||||
out_data |= data.command;
|
||||
out_data <<= NBITS_CHANNEL;
|
||||
out_data |= data.channel;
|
||||
} else {
|
||||
out_data = data.address << (NBITS_DATA + NBITS_CHECKSUM);
|
||||
out_data |= (data.data & ((1u << NBITS_DATA) - 1)) << NBITS_CHECKSUM; // a lambda may exceed 4 bits
|
||||
out_data |= calc_cs(out_data);
|
||||
}
|
||||
ESP_LOGD(TAG, "addr=%04x data=%x channel=%02x command=%02x", data.address, data.data, data.channel, data.command);
|
||||
ESP_LOGV(TAG, "out_data %07" PRIx32, out_data);
|
||||
|
||||
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 %08" PRIx32, out_data);
|
||||
|
||||
for (uint32_t mask = 1UL << (NDATABITS - 1); mask != 0; mask >>= 1) {
|
||||
for (uint32_t mask = 1UL << (NBITS_PKT - 1); mask != 0; mask >>= 1) {
|
||||
if (out_data & mask) {
|
||||
dst->mark(BIT_TIME_US);
|
||||
dst->space(BIT_TIME_US);
|
||||
@@ -130,21 +146,9 @@ void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) {
|
||||
}
|
||||
|
||||
optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||
DraytonData out{
|
||||
.address = 0,
|
||||
.channel = 0,
|
||||
.command = 0,
|
||||
};
|
||||
DraytonData out{};
|
||||
|
||||
while (src.size() - src.get_index() >= MIN_RX_SRC) {
|
||||
ESP_LOGVV(TAG,
|
||||
"Decode Drayton: %" PRId32 ", %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
|
||||
" %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
|
||||
" %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 "",
|
||||
src.size() - src.get_index(), 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);
|
||||
@@ -152,12 +156,12 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||
|
||||
// Look for sync pulse, after. If sucessful index points to space of sync symbol
|
||||
while (src.size() - src.get_index() >= MIN_RX_SRC) {
|
||||
ESP_LOGVV(TAG, "Decode Drayton: sync search %" PRIu32 ", %" PRId32 " %" PRId32, src.size() - src.get_index(),
|
||||
src.peek(), src.peek(1));
|
||||
ESP_LOGVV(TAG, "sync search %" PRIu32 ", %" PRId32 " %" PRId32, src.size() - src.get_index(), src.peek(),
|
||||
src.peek(1));
|
||||
if (src.peek_mark(2 * BIT_TIME_US) &&
|
||||
(src.peek_space(2 * BIT_TIME_US, 1) || src.peek_space(3 * BIT_TIME_US, 1))) {
|
||||
src.advance(1);
|
||||
ESP_LOGVV(TAG, "Decode Drayton: Found SYNC, - %" PRIu32, src.get_index());
|
||||
ESP_LOGVV(TAG, "Found SYNC %" PRIu32, src.get_index());
|
||||
break;
|
||||
} else {
|
||||
src.advance(2);
|
||||
@@ -165,8 +169,8 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||
}
|
||||
|
||||
// No point continuing if not enough samples remaining to complete a packet
|
||||
if (src.size() - src.get_index() < NDATABITS) {
|
||||
ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %" PRIu32, src.get_index());
|
||||
if (src.size() - src.get_index() < NBITS_PKT) {
|
||||
ESP_LOGV(TAG, "Fail1 %" PRIu32, src.get_index());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -174,17 +178,14 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||
// Extract first bit
|
||||
// Checks next bit to leave index pointing correctly
|
||||
uint32_t out_data = 0;
|
||||
uint8_t bit = NDATABITS - 1;
|
||||
ESP_LOGVV(TAG, "Decode Drayton: first bit %" PRId32 " %" PRId32 ", %" PRId32, src.peek(0), src.peek(1),
|
||||
src.peek(2));
|
||||
uint8_t bit = NBITS_PKT - 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 2, - %" PRId32 " %" PRId32 " %" PRId32, src.peek(-1), src.peek(0),
|
||||
src.peek(1));
|
||||
ESP_LOGVV(TAG, "Fail2 %" PRId32 " %" PRId32 " %" PRId32, src.peek(-1), src.peek(0), src.peek(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -192,7 +193,7 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||
// 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 %08" PRIx32, bit, out_data);
|
||||
ESP_LOGVV(TAG, "Data %2d %08" PRIx32, 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;
|
||||
@@ -205,8 +206,7 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||
}
|
||||
|
||||
if (bit > 0) {
|
||||
ESP_LOGVV(TAG, "Decode Drayton: Fail 3, %" PRId32 " %" PRId32 " %" PRId32, src.peek(-1), src.peek(0),
|
||||
src.peek(1));
|
||||
ESP_LOGVV(TAG, "Fail3 %" PRId32 " %" PRId32 " %" PRId32, src.peek(-1), src.peek(0), src.peek(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -218,21 +218,28 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08" PRIx32, bit, out_data);
|
||||
uint8_t cs = out_data & 0xFF;
|
||||
uint8_t expected_cs = calc_cs(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);
|
||||
if (cs != expected_cs) {
|
||||
ESP_LOGV(TAG, "Dropped %07" PRIx32 ": checksum 0x%02x, expected 0x%02x", out_data, cs, expected_cs);
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Data %07" PRIx32, out_data);
|
||||
|
||||
out.data = (out_data >> NBITS_CHECKSUM) & ((1u << NBITS_DATA) - 1);
|
||||
out.address = out_data >> (NBITS_CHECKSUM + NBITS_DATA);
|
||||
out.channel = out_data & ((1u << NBITS_CHANNEL) - 1);
|
||||
out.command = (out_data >> NBITS_CHANNEL) & ((1u << NBITS_COMMAND) - 1);
|
||||
|
||||
return out;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void DraytonProtocol::dump(const DraytonData &data) {
|
||||
ESP_LOGI(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address,
|
||||
((data.address << 1) & 0xffff), data.channel, data.command);
|
||||
ESP_LOGI(TAG, "Received Drayton: address=0x%04X, data=0x%X", data.address, data.data);
|
||||
}
|
||||
|
||||
} // namespace esphome::remote_base
|
||||
|
||||
@@ -11,10 +11,10 @@ struct DraytonData {
|
||||
uint16_t address;
|
||||
uint8_t channel;
|
||||
uint8_t command;
|
||||
uint8_t data; // last, so positional initializers written for the legacy layout keep their meaning
|
||||
|
||||
bool operator==(const DraytonData &rhs) const {
|
||||
return address == rhs.address && channel == rhs.channel && command == rhs.command;
|
||||
}
|
||||
// channel/command: legacy split of the same bits, filled on decode, ignored by operator==
|
||||
bool operator==(const DraytonData &rhs) const { return address == rhs.address && data == rhs.data; }
|
||||
};
|
||||
|
||||
class DraytonProtocol : public RemoteProtocol<DraytonData> {
|
||||
|
||||
@@ -17,6 +17,7 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
"nec",
|
||||
"magiquest",
|
||||
"rc5x",
|
||||
"drayton",
|
||||
"panasonic",
|
||||
):
|
||||
request_protocol(protocol)
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
#include "esphome/components/remote_base/drayton_protocol.h"
|
||||
|
||||
namespace esphome::remote_base::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int32_t BIT_US = 500;
|
||||
constexpr size_t PACKET_BITS = 28;
|
||||
|
||||
// Captured packets from the header comment of drayton_protocol.cpp
|
||||
constexpr uint32_t ON_PACKET = 0x6180832;
|
||||
constexpr uint32_t OFF_PACKET = 0x6180052;
|
||||
const char *const ON_CAPTURE = "101010101010110001101001010101101001010101010101100101010101101001011001";
|
||||
const char *const OFF_CAPTURE = "101010101010110001101001010101101001010101010101010101010110011001011001";
|
||||
|
||||
DraytonData with_data(uint16_t address, uint8_t data) {
|
||||
DraytonData out{};
|
||||
out.address = address;
|
||||
out.data = data;
|
||||
return out;
|
||||
}
|
||||
|
||||
DraytonData with_legacy(uint16_t address, uint8_t channel, uint8_t command) {
|
||||
DraytonData out{};
|
||||
out.address = address;
|
||||
out.channel = channel;
|
||||
out.command = command;
|
||||
return out;
|
||||
}
|
||||
|
||||
RawTimings encode(const DraytonData &data) {
|
||||
RemoteTransmitData tx;
|
||||
DraytonProtocol().encode(&tx, data);
|
||||
return tx.get_data();
|
||||
}
|
||||
|
||||
// The packet bits sent after the preamble and sync, one Manchester pair per bit ('10' = 1)
|
||||
uint32_t sent_packet(const RawTimings &timings) {
|
||||
uint32_t word = 0;
|
||||
for (size_t i = timings.size() - 2 * PACKET_BITS; i < timings.size(); i += 2) {
|
||||
word = (word << 1) | (timings[i] > 0 ? 1 : 0);
|
||||
}
|
||||
return word;
|
||||
}
|
||||
|
||||
// A receiver reports runs of the same level as one item.
|
||||
RawTimings as_received(const RawTimings &timings) {
|
||||
RawTimings merged;
|
||||
for (int32_t item : timings) {
|
||||
if (!merged.empty() && (merged.back() < 0) == (item < 0)) {
|
||||
merged.back() += item;
|
||||
} else {
|
||||
merged.push_back(item);
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
RawTimings from_capture(const std::string &bits) {
|
||||
RawTimings timings;
|
||||
for (char bit : bits) {
|
||||
timings.push_back(bit == '1' ? BIT_US : -BIT_US);
|
||||
}
|
||||
return as_received(timings);
|
||||
}
|
||||
|
||||
optional<DraytonData> decode(const RawTimings &timings) {
|
||||
return DraytonProtocol().decode(RemoteReceiveData(timings, 25, TOLERANCE_MODE_PERCENTAGE));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(DraytonProtocolTest, EncodeAppendsChecksum) {
|
||||
EXPECT_EQ(sent_packet(encode(with_data(0x6180, 0x8))), ON_PACKET);
|
||||
EXPECT_EQ(sent_packet(encode(with_data(0x6180, 0x0))), OFF_PACKET);
|
||||
// A lambda value above 4 bits is masked instead of spilling into the address
|
||||
EXPECT_EQ(sent_packet(encode(with_data(0x6180, 0x18))), ON_PACKET);
|
||||
}
|
||||
|
||||
TEST(DraytonProtocolTest, LegacyEncodeSendsChannelAndCommandAsIs) {
|
||||
EXPECT_EQ(sent_packet(encode(with_legacy(0x6180, 0x12, 0x41))), ON_PACKET);
|
||||
EXPECT_EQ(sent_packet(encode(with_legacy(0x1234, 0x01, 0x05))), 0x12340A1u);
|
||||
}
|
||||
|
||||
TEST(DraytonProtocolTest, DecodesCapturedPackets) {
|
||||
auto on = decode(from_capture(ON_CAPTURE));
|
||||
ASSERT_TRUE(on.has_value());
|
||||
if (!on.has_value()) {
|
||||
return;
|
||||
}
|
||||
EXPECT_EQ(on->address, 0x6180);
|
||||
EXPECT_EQ(on->data, 0x8);
|
||||
EXPECT_EQ(on->channel, 0x12);
|
||||
EXPECT_EQ(on->command, 0x41);
|
||||
|
||||
auto off = decode(from_capture(OFF_CAPTURE));
|
||||
ASSERT_TRUE(off.has_value());
|
||||
if (!off.has_value()) {
|
||||
return;
|
||||
}
|
||||
EXPECT_EQ(off->address, 0x6180);
|
||||
EXPECT_EQ(off->data, 0x0);
|
||||
EXPECT_EQ(off->channel, 0x12);
|
||||
EXPECT_EQ(off->command, 0x02);
|
||||
}
|
||||
|
||||
TEST(DraytonProtocolTest, RoundTrips) {
|
||||
for (uint16_t address : {0x0000, 0x6180, 0x1231, 0xFFFF}) {
|
||||
for (uint8_t data : {0x0, 0x2, 0x8, 0xA, 0xF}) {
|
||||
auto decoded = decode(as_received(encode(with_data(address, data))));
|
||||
ASSERT_TRUE(decoded.has_value()) << std::hex << address << " " << int(data);
|
||||
if (!decoded.has_value()) {
|
||||
return;
|
||||
}
|
||||
EXPECT_EQ(decoded->address, address);
|
||||
EXPECT_EQ(decoded->data, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DraytonProtocolTest, RejectsBadChecksum) {
|
||||
// On packet with the low checksum bits changed from 0x12 to 0x13
|
||||
EXPECT_FALSE(decode(as_received(encode(with_legacy(0x6180, 0x13, 0x41)))).has_value());
|
||||
// On packet with the data bits of the Off packet
|
||||
EXPECT_FALSE(decode(as_received(encode(with_legacy(0x6180, 0x12, 0x01)))).has_value());
|
||||
}
|
||||
|
||||
TEST(DraytonProtocolTest, BinarySensorFormsMatchDecodedPacket) {
|
||||
auto on = decode(from_capture(ON_CAPTURE));
|
||||
ASSERT_TRUE(on.has_value());
|
||||
if (!on.has_value()) {
|
||||
return;
|
||||
}
|
||||
// The decoded channel and command are ignored
|
||||
EXPECT_TRUE(*on == with_data(0x6180, 0x8));
|
||||
EXPECT_FALSE(*on == with_data(0x6181, 0x8));
|
||||
}
|
||||
|
||||
} // namespace esphome::remote_base::testing
|
||||
@@ -51,8 +51,8 @@ on_dooya:
|
||||
on_drayton:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "on_drayton: %u %u %u"
|
||||
args: ["x.address", "x.channel", "x.command"]
|
||||
format: "on_drayton: %u %u %u %u"
|
||||
args: ["x.address", "x.channel", "x.command", "x.data"]
|
||||
on_dyson:
|
||||
then:
|
||||
- logger.log:
|
||||
|
||||
@@ -21,6 +21,17 @@ binary_sensor:
|
||||
address: 0x00
|
||||
command: 0x0D
|
||||
extension: 0x03
|
||||
- platform: remote_receiver
|
||||
name: Drayton Thermostat On
|
||||
drayton:
|
||||
address: 0x6180
|
||||
data: 0x8
|
||||
- platform: remote_receiver
|
||||
name: Drayton Thermostat On Legacy
|
||||
drayton:
|
||||
address: 0x6180
|
||||
channel: 0x12
|
||||
command: 0x41
|
||||
- platform: remote_receiver
|
||||
name: Panasonic 56 bit Remote Input
|
||||
panasonic:
|
||||
|
||||
@@ -27,6 +27,13 @@ button:
|
||||
on_press:
|
||||
remote_transmitter.transmit_brennenstuhl:
|
||||
code: 0xBD2E2C
|
||||
- platform: template
|
||||
name: Drayton Thermostat On
|
||||
id: drayton_thermostat_on
|
||||
on_press:
|
||||
remote_transmitter.transmit_drayton:
|
||||
address: 0x1230
|
||||
data: 0x8
|
||||
- platform: template
|
||||
name: Dyson fan up
|
||||
id: dyson_fan_up
|
||||
|
||||
Reference in New Issue
Block a user