Coolix IR protocol improvements (#5105)

* coolix protocol

* tests

* 24-bit range

* some DRY in coolix

* added short condition

* one more change

* final prettify

* v2023.8
This commit is contained in:
Sergey Dudanov 2023-07-23 00:15:37 +04:00 committed by GitHub
parent 80154b280e
commit 827b2def1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 45 deletions

View file

@ -114,7 +114,7 @@ bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteRecei
if (!decoded.has_value()) if (!decoded.has_value())
return false; return false;
// Decoded remote state y 3 bytes long code. // Decoded remote state y 3 bytes long code.
uint32_t remote_state = *decoded; uint32_t remote_state = (*decoded).second;
ESP_LOGV(TAG, "Decoded 0x%06X", remote_state); ESP_LOGV(TAG, "Decoded 0x%06X", remote_state);
if ((remote_state & 0xFF0000) != 0xB20000) if ((remote_state & 0xFF0000) != 0xB20000)
return false; return false;

View file

@ -17,6 +17,7 @@ from esphome.const import (
CONF_PROTOCOL, CONF_PROTOCOL,
CONF_GROUP, CONF_GROUP,
CONF_DEVICE, CONF_DEVICE,
CONF_SECOND,
CONF_STATE, CONF_STATE,
CONF_CHANNEL, CONF_CHANNEL,
CONF_FAMILY, CONF_FAMILY,
@ -39,6 +40,7 @@ AUTO_LOAD = ["binary_sensor"]
CONF_RECEIVER_ID = "receiver_id" CONF_RECEIVER_ID = "receiver_id"
CONF_TRANSMITTER_ID = "transmitter_id" CONF_TRANSMITTER_ID = "transmitter_id"
CONF_FIRST = "first"
ns = remote_base_ns = cg.esphome_ns.namespace("remote_base") ns = remote_base_ns = cg.esphome_ns.namespace("remote_base")
RemoteProtocol = ns.class_("RemoteProtocol") RemoteProtocol = ns.class_("RemoteProtocol")
@ -349,19 +351,48 @@ async def canalsatld_action(var, config, args):
CoolixAction, CoolixAction,
CoolixDumper, CoolixDumper,
) = declare_protocol("Coolix") ) = declare_protocol("Coolix")
COOLIX_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t})
@register_binary_sensor("coolix", CoolixBinarySensor, COOLIX_SCHEMA) COOLIX_BASE_SCHEMA = cv.Schema(
{
cv.Required(CONF_FIRST): cv.hex_int_range(0, 16777215),
cv.Optional(CONF_SECOND, default=0): cv.hex_int_range(0, 16777215),
cv.Optional(CONF_DATA): cv.invalid(
"'data' option has been removed in ESPHome 2023.8. "
"Use the 'first' and 'second' options instead."
),
}
)
COOLIX_SENSOR_SCHEMA = cv.Any(cv.hex_int_range(0, 16777215), COOLIX_BASE_SCHEMA)
@register_binary_sensor("coolix", CoolixBinarySensor, COOLIX_SENSOR_SCHEMA)
def coolix_binary_sensor(var, config): def coolix_binary_sensor(var, config):
if isinstance(config, dict):
cg.add( cg.add(
var.set_data( var.set_data(
cg.StructInitializer( cg.StructInitializer(
CoolixData, CoolixData,
("data", config[CONF_DATA]), ("first", config[CONF_FIRST]),
("second", config[CONF_SECOND]),
) )
) )
) )
else:
cg.add(
var.set_data(
cg.StructInitializer(CoolixData, ("first", 0), ("second", config))
)
)
@register_action("coolix", CoolixAction, COOLIX_BASE_SCHEMA)
async def coolix_action(var, config, args):
template_ = await cg.templatable(config[CONF_FIRST], args, cg.uint32)
cg.add(var.set_first(template_))
template_ = await cg.templatable(config[CONF_SECOND], args, cg.uint32)
cg.add(var.set_second(template_))
@register_trigger("coolix", CoolixTrigger, CoolixData) @register_trigger("coolix", CoolixTrigger, CoolixData)
@ -374,12 +405,6 @@ def coolix_dumper(var, config):
pass pass
@register_action("coolix", CoolixAction, COOLIX_SCHEMA)
async def coolix_action(var, config, args):
template_ = await cg.templatable(config[CONF_DATA], args, cg.uint32)
cg.add(var.set_data(template_))
# Dish # Dish
DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol( DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol(
"Dish" "Dish"

View file

@ -15,11 +15,21 @@ static const int32_t BIT_ZERO_SPACE_US = 1 * TICK_US;
static const int32_t FOOTER_MARK_US = 1 * TICK_US; static const int32_t FOOTER_MARK_US = 1 * TICK_US;
static const int32_t FOOTER_SPACE_US = 10 * TICK_US; static const int32_t FOOTER_SPACE_US = 10 * TICK_US;
static void encode_data(RemoteTransmitData *dst, const CoolixData &src) { bool CoolixData::operator==(const CoolixData &other) const {
if (this->first == 0)
return this->second == other.first || this->second == other.second;
if (other.first == 0)
return other.second == this->first || other.second == this->second;
return this->first == other.first && this->second == other.second;
}
static void encode_frame(RemoteTransmitData *dst, const uint32_t &src) {
// Append header
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
// Break data into bytes, starting at the Most Significant // Break data into bytes, starting at the Most Significant
// Byte. Each byte then being sent normal, then followed inverted. // Byte. Each byte then being sent normal, then followed inverted.
for (unsigned shift = 16;; shift -= 8) { for (unsigned shift = 16;; shift -= 8) {
// Grab a bytes worth of data. // Grab a bytes worth of data
const uint8_t byte = src >> shift; const uint8_t byte = src >> shift;
// Normal // Normal
for (uint8_t mask = 1 << 7; mask; mask >>= 1) for (uint8_t mask = 1 << 7; mask; mask >>= 1)
@ -27,27 +37,33 @@ static void encode_data(RemoteTransmitData *dst, const CoolixData &src) {
// Inverted // Inverted
for (uint8_t mask = 1 << 7; mask; mask >>= 1) for (uint8_t mask = 1 << 7; mask; mask >>= 1)
dst->item(BIT_MARK_US, (byte & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US); dst->item(BIT_MARK_US, (byte & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US);
// Data end // End of frame
if (shift == 0) if (shift == 0) {
// Append footer
dst->mark(FOOTER_MARK_US);
break; break;
} }
} }
}
void CoolixProtocol::encode(RemoteTransmitData *dst, const CoolixData &data) { void CoolixProtocol::encode(RemoteTransmitData *dst, const CoolixData &data) {
dst->set_carrier_frequency(38000); dst->set_carrier_frequency(38000);
dst->reserve(2 + 2 * 48 + 2 + 2 + 2 * 48 + 1); dst->reserve(100 + 100 * data.has_second());
dst->item(HEADER_MARK_US, HEADER_SPACE_US); encode_frame(dst, data.first);
encode_data(dst, data); if (data.has_second()) {
dst->item(FOOTER_MARK_US, FOOTER_SPACE_US); dst->space(FOOTER_SPACE_US);
dst->item(HEADER_MARK_US, HEADER_SPACE_US); encode_frame(dst, data.second);
encode_data(dst, data); }
dst->mark(FOOTER_MARK_US);
} }
static bool decode_data(RemoteReceiveData &src, CoolixData &dst) { static bool decode_frame(RemoteReceiveData &src, uint32_t &dst) {
// Checking for header
if (!src.expect_item(HEADER_MARK_US, HEADER_SPACE_US))
return false;
// Reading data
uint32_t data = 0; uint32_t data = 0;
for (unsigned n = 3;; data <<= 8) { for (unsigned n = 3;; data <<= 8) {
// Read byte // Reading byte
for (uint32_t mask = 1 << 7; mask; mask >>= 1) { for (uint32_t mask = 1 << 7; mask; mask >>= 1) {
if (!src.expect_mark(BIT_MARK_US)) if (!src.expect_mark(BIT_MARK_US))
return false; return false;
@ -57,13 +73,16 @@ static bool decode_data(RemoteReceiveData &src, CoolixData &dst) {
return false; return false;
} }
} }
// Check for inverse byte // Checking for inverted byte
for (uint32_t mask = 1 << 7; mask; mask >>= 1) { for (uint32_t mask = 1 << 7; mask; mask >>= 1) {
if (!src.expect_item(BIT_MARK_US, (data & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US)) if (!src.expect_item(BIT_MARK_US, (data & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US))
return false; return false;
} }
// Checking the end of reading // End of frame
if (--n == 0) { if (--n == 0) {
// Checking for footer
if (!src.expect_mark(FOOTER_MARK_US))
return false;
dst = data; dst = data;
return true; return true;
} }
@ -71,15 +90,24 @@ static bool decode_data(RemoteReceiveData &src, CoolixData &dst) {
} }
optional<CoolixData> CoolixProtocol::decode(RemoteReceiveData data) { optional<CoolixData> CoolixProtocol::decode(RemoteReceiveData data) {
CoolixData first, second; CoolixData result;
if (data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && decode_data(data, first) && const auto size = data.size();
data.expect_item(FOOTER_MARK_US, FOOTER_SPACE_US) && data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && if ((size != 200 && size != 100) || !decode_frame(data, result.first))
decode_data(data, second) && data.expect_mark(FOOTER_MARK_US) && first == second)
return first;
return {}; return {};
if (size == 100 || !data.expect_space(FOOTER_SPACE_US) || !decode_frame(data, result.second))
result.second = 0;
return result;
} }
void CoolixProtocol::dump(const CoolixData &data) { ESP_LOGD(TAG, "Received Coolix: 0x%06X", data); } void CoolixProtocol::dump(const CoolixData &data) {
if (data.is_strict()) {
ESP_LOGD(TAG, "Received Coolix: 0x%06X", data.first);
} else if (data.has_second()) {
ESP_LOGD(TAG, "Received unstrict Coolix: [0x%06X, 0x%06X]", data.first, data.second);
} else {
ESP_LOGD(TAG, "Received unstrict Coolix: [0x%06X]", data.first);
}
}
} // namespace remote_base } // namespace remote_base
} // namespace esphome } // namespace esphome

View file

@ -7,7 +7,16 @@
namespace esphome { namespace esphome {
namespace remote_base { namespace remote_base {
using CoolixData = uint32_t; struct CoolixData {
CoolixData() {}
CoolixData(uint32_t a) : first(a), second(a) {}
CoolixData(uint32_t a, uint32_t b) : first(a), second(b) {}
bool operator==(const CoolixData &other) const;
bool is_strict() const { return this->first == this->second; }
bool has_second() const { return this->second != 0; }
uint32_t first;
uint32_t second;
};
class CoolixProtocol : public RemoteProtocol<CoolixData> { class CoolixProtocol : public RemoteProtocol<CoolixData> {
public: public:
@ -19,10 +28,10 @@ class CoolixProtocol : public RemoteProtocol<CoolixData> {
DECLARE_REMOTE_PROTOCOL(Coolix) DECLARE_REMOTE_PROTOCOL(Coolix)
template<typename... Ts> class CoolixAction : public RemoteTransmitterActionBase<Ts...> { template<typename... Ts> class CoolixAction : public RemoteTransmitterActionBase<Ts...> {
TEMPLATABLE_VALUE(CoolixData, data) TEMPLATABLE_VALUE(uint32_t, first)
TEMPLATABLE_VALUE(uint32_t, second)
void encode(RemoteTransmitData *dst, Ts... x) override { void encode(RemoteTransmitData *dst, Ts... x) override {
CoolixData data = this->data_.value(x...); CoolixProtocol().encode(dst, {this->first_.value(x...), this->second_.value(x...)});
CoolixProtocol().encode(dst, data);
} }
}; };

View file

@ -1594,6 +1594,18 @@ binary_sensor:
-2267, -2267,
1709, 1709,
] ]
- platform: remote_receiver
name: Coolix Test 1
coolix: 0xB21F98
- platform: remote_receiver
name: Coolix Test 2
coolix:
first: 0xB2E003
- platform: remote_receiver
name: Coolix Test 3
coolix:
first: 0xB2E003
second: 0xB21F98
- platform: as3935 - platform: as3935
name: Storm Alert name: Storm Alert
- platform: analog_threshold - platform: analog_threshold
@ -2265,7 +2277,15 @@ switch:
- platform: template - platform: template
name: MIDEA_RAW name: MIDEA_RAW
turn_on_action: turn_on_action:
remote_transmitter.transmit_midea: - remote_transmitter.transmit_coolix:
first: 0xB21F98
- remote_transmitter.transmit_coolix:
first: 0xB21F98
second: 0xB21F98
- remote_transmitter.transmit_coolix:
first: !lambda "return 0xB21F98;"
second: !lambda "return 0xB21F98;"
- remote_transmitter.transmit_midea:
code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF]
- platform: gpio - platform: gpio
name: "MCP23S08 Pin #0" name: "MCP23S08 Pin #0"
@ -2846,6 +2866,9 @@ tm1651:
remote_receiver: remote_receiver:
pin: GPIO32 pin: GPIO32
dump: all dump: all
on_coolix:
then:
delay: !lambda "return x.first + x.second;"
status_led: status_led:
pin: GPIO2 pin: GPIO2