mirror of
https://github.com/esphome/esphome.git
synced 2024-12-01 13:13:54 +01:00
RF Codec for Drayton Digistat heating controller (#4494)
This commit is contained in:
parent
c13e20643b
commit
379b1d84dd
@ -791,6 +791,57 @@ async def raw_action(var, config, args):
|
|||||||
cg.add(var.set_carrier_frequency(templ))
|
cg.add(var.set_carrier_frequency(templ))
|
||||||
|
|
||||||
|
|
||||||
|
# Drayton
|
||||||
|
(
|
||||||
|
DraytonData,
|
||||||
|
DraytonBinarySensor,
|
||||||
|
DraytonTrigger,
|
||||||
|
DraytonAction,
|
||||||
|
DraytonDumper,
|
||||||
|
) = 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)),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_binary_sensor("drayton", DraytonBinarySensor, DRAYTON_SCHEMA)
|
||||||
|
def drayton_binary_sensor(var, config):
|
||||||
|
cg.add(
|
||||||
|
var.set_data(
|
||||||
|
cg.StructInitializer(
|
||||||
|
DraytonData,
|
||||||
|
("address", config[CONF_ADDRESS]),
|
||||||
|
("channel", config[CONF_CHANNEL]),
|
||||||
|
("command", config[CONF_COMMAND]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_trigger("drayton", DraytonTrigger, DraytonData)
|
||||||
|
def drayton_trigger(var, config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@register_dumper("drayton", DraytonDumper)
|
||||||
|
def drayton_dumper(var, config):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@register_action("drayton", DraytonAction, DRAYTON_SCHEMA)
|
||||||
|
async def drayton_action(var, config, args):
|
||||||
|
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16)
|
||||||
|
cg.add(var.set_address(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
|
||||||
|
cg.add(var.set_channel(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
|
||||||
|
cg.add(var.set_command(template_))
|
||||||
|
|
||||||
|
|
||||||
# RC5
|
# RC5
|
||||||
RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol("RC5")
|
RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol("RC5")
|
||||||
RC5_SCHEMA = cv.Schema(
|
RC5_SCHEMA = cv.Schema(
|
||||||
|
213
esphome/components/remote_base/drayton_protocol.cpp
Normal file
213
esphome/components/remote_base/drayton_protocol.cpp
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
#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
|
44
esphome/components/remote_base/drayton_protocol.h
Normal file
44
esphome/components/remote_base/drayton_protocol.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "remote_base.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace remote_base {
|
||||||
|
|
||||||
|
struct DraytonData {
|
||||||
|
uint16_t address;
|
||||||
|
uint8_t channel;
|
||||||
|
uint8_t command;
|
||||||
|
|
||||||
|
bool operator==(const DraytonData &rhs) const {
|
||||||
|
return address == rhs.address && channel == rhs.channel && command == rhs.command;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class DraytonProtocol : public RemoteProtocol<DraytonData> {
|
||||||
|
public:
|
||||||
|
void encode(RemoteTransmitData *dst, const DraytonData &data) override;
|
||||||
|
optional<DraytonData> decode(RemoteReceiveData src) override;
|
||||||
|
void dump(const DraytonData &data) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_REMOTE_PROTOCOL(Drayton)
|
||||||
|
|
||||||
|
template<typename... Ts> class DraytonAction : public RemoteTransmitterActionBase<Ts...> {
|
||||||
|
public:
|
||||||
|
TEMPLATABLE_VALUE(uint16_t, address)
|
||||||
|
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||||
|
TEMPLATABLE_VALUE(uint8_t, command)
|
||||||
|
|
||||||
|
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||||
|
DraytonData data{};
|
||||||
|
data.address = this->address_.value(x...);
|
||||||
|
data.channel = this->channel_.value(x...);
|
||||||
|
data.command = this->command_.value(x...);
|
||||||
|
DraytonProtocol().encode(dst, data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace remote_base
|
||||||
|
} // namespace esphome
|
Loading…
Reference in New Issue
Block a user