mirror of
https://github.com/esphome/esphome.git
synced 2024-12-28 17:37:44 +01:00
[remote_base] WIP: nec protocol fix
This commit is contained in:
parent
387bde665e
commit
795afd69ad
@ -750,7 +750,7 @@ NEC_SCHEMA = cv.Schema(
|
|||||||
{
|
{
|
||||||
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
|
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
|
||||||
cv.Required(CONF_COMMAND): cv.hex_uint16_t,
|
cv.Required(CONF_COMMAND): cv.hex_uint16_t,
|
||||||
cv.Optional(CONF_COMMAND_REPEATS, default=1): cv.uint16_t,
|
cv.Optional(CONF_COMMAND_REPEATS, default=1): cv.uint16_t, # // TODO: command_repeats -> repeats (repeat as repeat code, not whole message frame)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -763,7 +763,7 @@ def nec_binary_sensor(var, config):
|
|||||||
NECData,
|
NECData,
|
||||||
("address", config[CONF_ADDRESS]),
|
("address", config[CONF_ADDRESS]),
|
||||||
("command", config[CONF_COMMAND]),
|
("command", config[CONF_COMMAND]),
|
||||||
("command_repeats", config[CONF_COMMAND_REPEATS]),
|
("command_repeats", config[CONF_COMMAND_REPEATS]), # // TODO: command_repeats -> repeats (repeat as repeat code, not whole message frame)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -785,8 +785,8 @@ async def nec_action(var, config, args):
|
|||||||
cg.add(var.set_address(template_))
|
cg.add(var.set_address(template_))
|
||||||
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint16)
|
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint16)
|
||||||
cg.add(var.set_command(template_))
|
cg.add(var.set_command(template_))
|
||||||
template_ = await cg.templatable(config[CONF_COMMAND_REPEATS], args, cg.uint16)
|
template_ = await cg.templatable(config[CONF_COMMAND_REPEATS], args, cg.uint16) # // TODO: command_repeats -> repeats (repeat as repeat code, not whole message frame)
|
||||||
cg.add(var.set_command_repeats(template_))
|
cg.add(var.set_command_repeats(template_)) # // TODO: command_repeats -> repeats (repeat as repeat code, not whole message frame)
|
||||||
|
|
||||||
|
|
||||||
# Pioneer
|
# Pioneer
|
||||||
|
@ -6,93 +6,138 @@ namespace remote_base {
|
|||||||
|
|
||||||
static const char *const TAG = "remote.nec";
|
static const char *const TAG = "remote.nec";
|
||||||
|
|
||||||
static const uint32_t HEADER_HIGH_US = 9000;
|
// NEC IR Protocol
|
||||||
static const uint32_t HEADER_LOW_US = 4500;
|
// Protocol Reference: https://techdocs.altium.com/display/FPGA/NEC%2bInfrared%2bTransmission%2bProtocol
|
||||||
static const uint32_t BIT_HIGH_US = 560;
|
|
||||||
static const uint32_t BIT_ONE_LOW_US = 1690;
|
|
||||||
static const uint32_t BIT_ZERO_LOW_US = 560;
|
|
||||||
|
|
||||||
|
// Timing constants in microseconds
|
||||||
|
static const uint32_t HEADER_HIGH_US = 9000; // AGC burst: 9ms HIGH
|
||||||
|
static const uint32_t HEADER_LOW_US = 4500; // AGC burst: 4.5ms LOW
|
||||||
|
static const uint32_t BIT_HIGH_US = 562; // Bit HIGH duration: 562.5µs
|
||||||
|
static const uint32_t BIT_ONE_LOW_US = 1687; // Logic '1': 562.5µs HIGH + 1687.5µs LOW
|
||||||
|
static const uint32_t BIT_ZERO_LOW_US = 562; // Logic '0': 562.5µs HIGH + 562.5µs LOW
|
||||||
|
static const uint32_t SPACE_INTER_FRAME_US = 40000; // Inter-frame space: 40ms
|
||||||
|
static const uint32_t SPACE_AGC_REPEAT_US = 96187; // AGC Repeat space: ~96.1875ms
|
||||||
|
|
||||||
|
// TODO: command_repeats -> repeats (repeat as repeat code, not whole message frame)
|
||||||
void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) {
|
void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) {
|
||||||
ESP_LOGD(TAG, "Sending NEC: address=0x%04X, command=0x%04X command_repeats=%d", data.address, data.command,
|
ESP_LOGD(TAG, "Sending NEC: address=0x%04X, command=0x%04X, command_repeats=%d",
|
||||||
data.command_repeats);
|
data.address, data.command, data.command_repeats);
|
||||||
|
|
||||||
dst->reserve(2 + 32 + 32 * data.command_repeats + 2);
|
// Reserve: AGC Header (2) + Address bits (32) + Command bits (32) + Final mark (2) + Repeat codes (4 per repeat)
|
||||||
dst->set_carrier_frequency(38000);
|
dst->reserve(2 + 32 + 32 + 2 + data.command_repeats * 4);
|
||||||
|
dst->set_carrier_frequency(38222);
|
||||||
|
|
||||||
|
// Send the AGC Header (start of frame)
|
||||||
dst->item(HEADER_HIGH_US, HEADER_LOW_US);
|
dst->item(HEADER_HIGH_US, HEADER_LOW_US);
|
||||||
|
|
||||||
|
// Encode 16-bit Address
|
||||||
for (uint16_t mask = 1; mask; mask <<= 1) {
|
for (uint16_t mask = 1; mask; mask <<= 1) {
|
||||||
if (data.address & mask) {
|
if (data.address & mask) {
|
||||||
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
|
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); // Logic '1'
|
||||||
} else {
|
} else {
|
||||||
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
|
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); // Logic '0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint16_t repeats = 0; repeats < data.command_repeats; repeats++) {
|
// Encode 16-bit Command
|
||||||
for (uint16_t mask = 1; mask; mask <<= 1) {
|
for (uint16_t mask = 1; mask; mask <<= 1) {
|
||||||
if (data.command & mask) {
|
if (data.command & mask) {
|
||||||
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US);
|
dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); // Logic '1'
|
||||||
} else {
|
} else {
|
||||||
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US);
|
dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); // Logic '0'
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Final mark to end the message frame
|
||||||
dst->mark(BIT_HIGH_US);
|
dst->mark(BIT_HIGH_US);
|
||||||
|
|
||||||
|
// Space between message frame and first AGC repeat code
|
||||||
|
if (data.command_repeats > 0) {
|
||||||
|
dst->space(SPACE_INTER_FRAME_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send AGC Repeat Codes if requested
|
||||||
|
for (uint16_t repeats = 0; repeats < data.command_repeats; repeats++) {
|
||||||
|
// AGC Repeat header (shorter version of the initial AGC header)
|
||||||
|
dst->item(HEADER_HIGH_US, HEADER_LOW_US / 2); // Shortened AGC header
|
||||||
|
dst->mark(BIT_HIGH_US); // Mark to complete the repeat code
|
||||||
|
|
||||||
|
// Add space after repeat code, except after the final repeat
|
||||||
|
if (repeats < data.command_repeats - 1) {
|
||||||
|
dst->space(SPACE_AGC_REPEAT_US);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: command_repeats -> repeats (repeat as repeat code, not whole message frame)
|
||||||
optional<NECData> NECProtocol::decode(RemoteReceiveData src) {
|
optional<NECData> NECProtocol::decode(RemoteReceiveData src) {
|
||||||
NECData data{
|
NECData data{
|
||||||
.address = 0,
|
.address = 0,
|
||||||
.command = 0,
|
.command = 0,
|
||||||
.command_repeats = 1,
|
.command_repeats = 0, // Start with 0, as the first frame is counted explicitly
|
||||||
};
|
};
|
||||||
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US))
|
|
||||||
|
// Validate the AGC header (start of frame)
|
||||||
|
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US)) {
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode 16-bit Address
|
||||||
for (uint16_t mask = 1; mask; mask <<= 1) {
|
for (uint16_t mask = 1; mask; mask <<= 1) {
|
||||||
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
|
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
|
||||||
data.address |= mask;
|
data.address |= mask; // Logic '1'
|
||||||
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
|
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
|
||||||
data.address &= ~mask;
|
data.address &= ~mask; // Logic '0'
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode 16-bit Command
|
||||||
for (uint16_t mask = 1; mask; mask <<= 1) {
|
for (uint16_t mask = 1; mask; mask <<= 1) {
|
||||||
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
|
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
|
||||||
data.command |= mask;
|
data.command |= mask; // Logic '1'
|
||||||
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
|
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
|
||||||
data.command &= ~mask;
|
data.command &= ~mask; // Logic '0'
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (src.peek_item(BIT_HIGH_US, BIT_ONE_LOW_US) || src.peek_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
|
// Validate the final mark (end of the message frame)
|
||||||
uint16_t command = 0;
|
if (!src.expect_mark(BIT_HIGH_US)) {
|
||||||
for (uint16_t mask = 1; mask; mask <<= 1) {
|
return {};
|
||||||
if (src.expect_item(BIT_HIGH_US, BIT_ONE_LOW_US)) {
|
}
|
||||||
command |= mask;
|
|
||||||
} else if (src.expect_item(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
|
// Check for inter-frame space (only if repeats are expected)
|
||||||
command &= ~mask;
|
if (src.peek_space(SPACE_INTER_FRAME_US)) {
|
||||||
} else {
|
src.expect_space(SPACE_INTER_FRAME_US); // Consume inter-frame space
|
||||||
return {};
|
}
|
||||||
}
|
|
||||||
|
// Decode AGC Repeat Codes
|
||||||
|
while (src.peek_item(HEADER_HIGH_US, HEADER_LOW_US / 2)) {
|
||||||
|
// Validate the repeat header
|
||||||
|
if (!src.expect_item(HEADER_HIGH_US, HEADER_LOW_US / 2)) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the extra/repeated data matches original command
|
// Validate the repeat mark
|
||||||
if (command != data.command) {
|
if (!src.expect_mark(BIT_HIGH_US)) {
|
||||||
return {};
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Increment the repeat count
|
||||||
data.command_repeats += 1;
|
data.command_repeats += 1;
|
||||||
|
|
||||||
|
// Check for space between repeat codes
|
||||||
|
if (src.peek_space(SPACE_AGC_REPEAT_US)) {
|
||||||
|
src.expect_space(SPACE_AGC_REPEAT_US); // Consume space between repeats
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
src.expect_mark(BIT_HIGH_US);
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NECProtocol::dump(const NECData &data) {
|
void NECProtocol::dump(const NECData &data) {
|
||||||
ESP_LOGI(TAG, "Received NEC: address=0x%04X, command=0x%04X command_repeats=%d", data.address, data.command,
|
ESP_LOGI(TAG, "Received NEC: address=0x%04X, command=0x%04X command_repeats=%d", data.address, data.command,
|
||||||
data.command_repeats);
|
data.command_repeats);
|
||||||
|
@ -8,7 +8,7 @@ namespace remote_base {
|
|||||||
struct NECData {
|
struct NECData {
|
||||||
uint16_t address;
|
uint16_t address;
|
||||||
uint16_t command;
|
uint16_t command;
|
||||||
uint16_t command_repeats;
|
uint16_t command_repeats; // TODO: command_repeats -> repeats (repeat as repeat code, not whole message frame)
|
||||||
|
|
||||||
bool operator==(const NECData &rhs) const { return address == rhs.address && command == rhs.command; }
|
bool operator==(const NECData &rhs) const { return address == rhs.address && command == rhs.command; }
|
||||||
};
|
};
|
||||||
@ -26,7 +26,7 @@ template<typename... Ts> class NECAction : public RemoteTransmitterActionBase<Ts
|
|||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(uint16_t, address)
|
TEMPLATABLE_VALUE(uint16_t, address)
|
||||||
TEMPLATABLE_VALUE(uint16_t, command)
|
TEMPLATABLE_VALUE(uint16_t, command)
|
||||||
TEMPLATABLE_VALUE(uint16_t, command_repeats)
|
TEMPLATABLE_VALUE(uint16_t, command_repeats) // TODO: command_repeats -> repeats (repeat as repeat code, not whole message frame)
|
||||||
|
|
||||||
void encode(RemoteTransmitData *dst, Ts... x) override {
|
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||||
NECData data{};
|
NECData data{};
|
||||||
|
@ -153,6 +153,7 @@ CONF_COLOR_TEMPERATURE = "color_temperature"
|
|||||||
CONF_COLORS = "colors"
|
CONF_COLORS = "colors"
|
||||||
CONF_COMMAND = "command"
|
CONF_COMMAND = "command"
|
||||||
CONF_COMMAND_REPEATS = "command_repeats"
|
CONF_COMMAND_REPEATS = "command_repeats"
|
||||||
|
# TODO: CONF_REPEATS = "repeats"
|
||||||
CONF_COMMAND_RETAIN = "command_retain"
|
CONF_COMMAND_RETAIN = "command_retain"
|
||||||
CONF_COMMAND_TOPIC = "command_topic"
|
CONF_COMMAND_TOPIC = "command_topic"
|
||||||
CONF_COMMENT = "comment"
|
CONF_COMMENT = "comment"
|
||||||
|
Loading…
Reference in New Issue
Block a user