mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 08:28:12 +01:00
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:
parent
80154b280e
commit
827b2def1e
5 changed files with 130 additions and 45 deletions
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue