mirror of
https://github.com/esphome/esphome.git
synced 2025-10-30 06:33:51 +00:00
New Midea IR component, improvements and fixes (#2847)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
@@ -234,6 +234,45 @@ async def build_dumpers(config):
|
||||
return dumpers
|
||||
|
||||
|
||||
# Coolix
|
||||
(
|
||||
CoolixData,
|
||||
CoolixBinarySensor,
|
||||
CoolixTrigger,
|
||||
CoolixAction,
|
||||
CoolixDumper,
|
||||
) = declare_protocol("Coolix")
|
||||
COOLIX_SCHEMA = cv.Schema({cv.Required(CONF_DATA): cv.hex_uint32_t})
|
||||
|
||||
|
||||
@register_binary_sensor("coolix", CoolixBinarySensor, COOLIX_SCHEMA)
|
||||
def coolix_binary_sensor(var, config):
|
||||
cg.add(
|
||||
var.set_data(
|
||||
cg.StructInitializer(
|
||||
CoolixData,
|
||||
("data", config[CONF_DATA]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register_trigger("coolix", CoolixTrigger, CoolixData)
|
||||
def coolix_trigger(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_dumper("coolix", CoolixDumper)
|
||||
def coolix_dumper(var, config):
|
||||
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
|
||||
DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol(
|
||||
"Dish"
|
||||
|
||||
84
esphome/components/remote_base/coolix_protocol.cpp
Normal file
84
esphome/components/remote_base/coolix_protocol.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#include "coolix_protocol.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
static const char *const TAG = "remote.coolix";
|
||||
|
||||
static const int32_t TICK_US = 560;
|
||||
static const int32_t HEADER_MARK_US = 8 * TICK_US;
|
||||
static const int32_t HEADER_SPACE_US = 8 * TICK_US;
|
||||
static const int32_t BIT_MARK_US = 1 * TICK_US;
|
||||
static const int32_t BIT_ONE_SPACE_US = 3 * TICK_US;
|
||||
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_SPACE_US = 10 * TICK_US;
|
||||
|
||||
static void encode_data(RemoteTransmitData *dst, const CoolixData &src) {
|
||||
// Break data into bytes, starting at the Most Significant
|
||||
// Byte. Each byte then being sent normal, then followed inverted.
|
||||
for (unsigned shift = 16;; shift -= 8) {
|
||||
// Grab a bytes worth of data.
|
||||
const uint8_t byte = src >> shift;
|
||||
// Normal
|
||||
for (uint8_t mask = 1 << 7; mask; mask >>= 1)
|
||||
dst->item(BIT_MARK_US, (byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
|
||||
// Inverted
|
||||
for (uint8_t mask = 1 << 7; mask; mask >>= 1)
|
||||
dst->item(BIT_MARK_US, (byte & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US);
|
||||
// Data end
|
||||
if (shift == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CoolixProtocol::encode(RemoteTransmitData *dst, const CoolixData &data) {
|
||||
dst->set_carrier_frequency(38000);
|
||||
dst->reserve(2 + 2 * 48 + 2 + 2 + 2 * 48 + 1);
|
||||
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
|
||||
encode_data(dst, data);
|
||||
dst->item(FOOTER_MARK_US, FOOTER_SPACE_US);
|
||||
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
|
||||
encode_data(dst, data);
|
||||
dst->mark(FOOTER_MARK_US);
|
||||
}
|
||||
|
||||
static bool decode_data(RemoteReceiveData &src, CoolixData &dst) {
|
||||
uint32_t data = 0;
|
||||
for (unsigned n = 3;; data <<= 8) {
|
||||
// Read byte
|
||||
for (uint32_t mask = 1 << 7; mask; mask >>= 1) {
|
||||
if (!src.expect_mark(BIT_MARK_US))
|
||||
return false;
|
||||
if (src.expect_space(BIT_ONE_SPACE_US))
|
||||
data |= mask;
|
||||
else if (!src.expect_space(BIT_ZERO_SPACE_US))
|
||||
return false;
|
||||
}
|
||||
// Check for inverse byte
|
||||
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))
|
||||
return false;
|
||||
}
|
||||
// Checking the end of reading
|
||||
if (--n == 0) {
|
||||
dst = data;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
optional<CoolixData> CoolixProtocol::decode(RemoteReceiveData data) {
|
||||
CoolixData first, second;
|
||||
if (data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && decode_data(data, first) &&
|
||||
data.expect_item(FOOTER_MARK_US, FOOTER_SPACE_US) && data.expect_item(HEADER_MARK_US, HEADER_SPACE_US) &&
|
||||
decode_data(data, second) && data.expect_mark(FOOTER_MARK_US) && first == second)
|
||||
return first;
|
||||
return {};
|
||||
}
|
||||
|
||||
void CoolixProtocol::dump(const CoolixData &data) { ESP_LOGD(TAG, "Received Coolix: 0x%06X", data); }
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
||||
30
esphome/components/remote_base/coolix_protocol.h
Normal file
30
esphome/components/remote_base/coolix_protocol.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "remote_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
using CoolixData = uint32_t;
|
||||
|
||||
class CoolixProtocol : public RemoteProtocol<CoolixData> {
|
||||
public:
|
||||
void encode(RemoteTransmitData *dst, const CoolixData &data) override;
|
||||
optional<CoolixData> decode(RemoteReceiveData data) override;
|
||||
void dump(const CoolixData &data) override;
|
||||
};
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(Coolix)
|
||||
|
||||
template<typename... Ts> class CoolixAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
TEMPLATABLE_VALUE(CoolixData, data)
|
||||
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||
CoolixData data = this->data_.value(x...);
|
||||
CoolixProtocol().encode(dst, data);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
||||
@@ -6,89 +6,63 @@ namespace remote_base {
|
||||
|
||||
static const char *const TAG = "remote.midea";
|
||||
|
||||
static const int32_t TICK_US = 560;
|
||||
static const int32_t HEADER_MARK_US = 8 * TICK_US;
|
||||
static const int32_t HEADER_SPACE_US = 8 * TICK_US;
|
||||
static const int32_t BIT_MARK_US = 1 * TICK_US;
|
||||
static const int32_t BIT_ONE_SPACE_US = 3 * TICK_US;
|
||||
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_SPACE_US = 10 * TICK_US;
|
||||
|
||||
uint8_t MideaData::calc_cs_() const {
|
||||
uint8_t cs = 0;
|
||||
for (const uint8_t *it = this->data(); it != this->data() + OFFSET_CS; ++it)
|
||||
cs -= reverse_bits(*it);
|
||||
for (uint8_t idx = 0; idx < OFFSET_CS; idx++)
|
||||
cs -= reverse_bits(this->data_[idx]);
|
||||
return reverse_bits(cs);
|
||||
}
|
||||
|
||||
bool MideaData::check_compliment(const MideaData &rhs) const {
|
||||
const uint8_t *it0 = rhs.data();
|
||||
for (const uint8_t *it1 = this->data(); it1 != this->data() + this->size(); ++it0, ++it1) {
|
||||
if (*it0 != ~(*it1))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
bool MideaData::is_compliment(const MideaData &rhs) const {
|
||||
return std::equal(this->data_.begin(), this->data_.end(), rhs.data_.begin(),
|
||||
[](const uint8_t &a, const uint8_t &b) { return a + b == 255; });
|
||||
}
|
||||
|
||||
void MideaProtocol::data(RemoteTransmitData *dst, const MideaData &src, bool compliment) {
|
||||
for (const uint8_t *it = src.data(); it != src.data() + src.size(); ++it) {
|
||||
const uint8_t data = compliment ? ~(*it) : *it;
|
||||
for (uint8_t mask = 128; mask; mask >>= 1) {
|
||||
if (data & mask)
|
||||
one(dst);
|
||||
else
|
||||
zero(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MideaProtocol::encode(RemoteTransmitData *dst, const MideaData &data) {
|
||||
void MideaProtocol::encode(RemoteTransmitData *dst, const MideaData &src) {
|
||||
dst->set_carrier_frequency(38000);
|
||||
dst->reserve(2 + 48 * 2 + 2 + 2 + 48 * 2 + 2);
|
||||
MideaProtocol::header(dst);
|
||||
MideaProtocol::data(dst, data);
|
||||
MideaProtocol::footer(dst);
|
||||
MideaProtocol::header(dst);
|
||||
MideaProtocol::data(dst, data, true);
|
||||
MideaProtocol::footer(dst);
|
||||
dst->reserve(2 + 48 * 2 + 2 + 2 + 48 * 2 + 1);
|
||||
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
|
||||
for (unsigned idx = 0; idx < 6; idx++)
|
||||
for (uint8_t mask = 1 << 7; mask; mask >>= 1)
|
||||
dst->item(BIT_MARK_US, (src[idx] & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
|
||||
dst->item(FOOTER_MARK_US, FOOTER_SPACE_US);
|
||||
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
|
||||
for (unsigned idx = 0; idx < 6; idx++)
|
||||
for (uint8_t mask = 1 << 7; mask; mask >>= 1)
|
||||
dst->item(BIT_MARK_US, (src[idx] & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US);
|
||||
dst->mark(FOOTER_MARK_US);
|
||||
}
|
||||
|
||||
bool MideaProtocol::expect_one(RemoteReceiveData &src) {
|
||||
if (!src.peek_item(BIT_HIGH_US, BIT_ONE_LOW_US))
|
||||
return false;
|
||||
src.advance(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MideaProtocol::expect_zero(RemoteReceiveData &src) {
|
||||
if (!src.peek_item(BIT_HIGH_US, BIT_ZERO_LOW_US))
|
||||
return false;
|
||||
src.advance(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MideaProtocol::expect_header(RemoteReceiveData &src) {
|
||||
if (!src.peek_item(HEADER_HIGH_US, HEADER_LOW_US))
|
||||
return false;
|
||||
src.advance(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MideaProtocol::expect_footer(RemoteReceiveData &src) {
|
||||
if (!src.peek_item(BIT_HIGH_US, MIN_GAP_US))
|
||||
return false;
|
||||
src.advance(2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MideaProtocol::expect_data(RemoteReceiveData &src, MideaData &out) {
|
||||
for (uint8_t *dst = out.data(); dst != out.data() + out.size(); ++dst) {
|
||||
for (uint8_t mask = 128; mask; mask >>= 1) {
|
||||
if (MideaProtocol::expect_one(src))
|
||||
*dst |= mask;
|
||||
else if (!MideaProtocol::expect_zero(src))
|
||||
static bool decode_data(RemoteReceiveData &src, MideaData &dst) {
|
||||
for (unsigned idx = 0; idx < 6; idx++) {
|
||||
uint8_t data = 0;
|
||||
for (uint8_t mask = 1 << 7; mask; mask >>= 1) {
|
||||
if (!src.expect_mark(BIT_MARK_US))
|
||||
return false;
|
||||
if (src.expect_space(BIT_ONE_SPACE_US))
|
||||
data |= mask;
|
||||
else if (!src.expect_space(BIT_ZERO_SPACE_US))
|
||||
return false;
|
||||
}
|
||||
dst[idx] = data;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
optional<MideaData> MideaProtocol::decode(RemoteReceiveData src) {
|
||||
MideaData out, inv;
|
||||
if (MideaProtocol::expect_header(src) && MideaProtocol::expect_data(src, out) && MideaProtocol::expect_footer(src) &&
|
||||
out.is_valid() && MideaProtocol::expect_data(src, inv) && out.check_compliment(inv))
|
||||
if (src.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && decode_data(src, out) && out.is_valid() &&
|
||||
src.expect_item(FOOTER_MARK_US, FOOTER_SPACE_US) && src.expect_item(HEADER_MARK_US, HEADER_SPACE_US) &&
|
||||
decode_data(src, inv) && src.expect_mark(FOOTER_MARK_US) && out.is_compliment(inv))
|
||||
return out;
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "remote_base.h"
|
||||
@@ -9,70 +10,61 @@ namespace remote_base {
|
||||
|
||||
class MideaData {
|
||||
public:
|
||||
// Make zero-filled
|
||||
MideaData() { memset(this->data_, 0, sizeof(this->data_)); }
|
||||
// Make default
|
||||
MideaData() {}
|
||||
// Make from initializer_list
|
||||
MideaData(std::initializer_list<uint8_t> data) { std::copy(data.begin(), data.end(), this->data()); }
|
||||
MideaData(std::initializer_list<uint8_t> data) {
|
||||
std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin());
|
||||
}
|
||||
// Make from vector
|
||||
MideaData(const std::vector<uint8_t> &data) {
|
||||
memcpy(this->data_, data.data(), std::min<size_t>(data.size(), sizeof(this->data_)));
|
||||
std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin());
|
||||
}
|
||||
// Default copy constructor
|
||||
MideaData(const MideaData &) = default;
|
||||
|
||||
uint8_t *data() { return this->data_; }
|
||||
const uint8_t *data() const { return this->data_; }
|
||||
uint8_t size() const { return sizeof(this->data_); }
|
||||
uint8_t *data() { return this->data_.data(); }
|
||||
const uint8_t *data() const { return this->data_.data(); }
|
||||
uint8_t size() const { return this->data_.size(); }
|
||||
bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); }
|
||||
void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); }
|
||||
bool check_compliment(const MideaData &rhs) const;
|
||||
std::string to_string() const { return format_hex_pretty(this->data_, sizeof(this->data_)); }
|
||||
bool is_compliment(const MideaData &rhs) const;
|
||||
std::string to_string() const { return format_hex_pretty(this->data_.data(), this->data_.size()); }
|
||||
// compare only 40-bits
|
||||
bool operator==(const MideaData &rhs) const { return !memcmp(this->data_, rhs.data_, OFFSET_CS); }
|
||||
bool operator==(const MideaData &rhs) const {
|
||||
return std::equal(this->data_.begin(), this->data_.begin() + OFFSET_CS, rhs.data_.begin());
|
||||
}
|
||||
enum MideaDataType : uint8_t {
|
||||
MIDEA_TYPE_COMMAND = 0xA1,
|
||||
MIDEA_TYPE_CONTROL = 0xA1,
|
||||
MIDEA_TYPE_SPECIAL = 0xA2,
|
||||
MIDEA_TYPE_FOLLOW_ME = 0xA4,
|
||||
};
|
||||
MideaDataType type() const { return static_cast<MideaDataType>(this->data_[0]); }
|
||||
template<typename T> T to() const { return T(*this); }
|
||||
uint8_t &operator[](size_t idx) { return this->data_[idx]; }
|
||||
const uint8_t &operator[](size_t idx) const { return this->data_[idx]; }
|
||||
|
||||
protected:
|
||||
void set_value_(uint8_t offset, uint8_t val_mask, uint8_t shift, uint8_t val) {
|
||||
data_[offset] &= ~(val_mask << shift);
|
||||
data_[offset] |= (val << shift);
|
||||
uint8_t get_value_(uint8_t idx, uint8_t mask = 255, uint8_t shift = 0) const {
|
||||
return (this->data_[idx] >> shift) & mask;
|
||||
}
|
||||
void set_value_(uint8_t idx, uint8_t value, uint8_t mask = 255, uint8_t shift = 0) {
|
||||
this->data_[idx] &= ~(mask << shift);
|
||||
this->data_[idx] |= (value << shift);
|
||||
}
|
||||
void set_mask_(uint8_t idx, bool state, uint8_t mask = 255) { this->set_value_(idx, state ? mask : 0, mask); }
|
||||
static const uint8_t OFFSET_CS = 5;
|
||||
// 48-bits data
|
||||
uint8_t data_[6];
|
||||
std::array<uint8_t, 6> data_;
|
||||
// Calculate checksum
|
||||
uint8_t calc_cs_() const;
|
||||
};
|
||||
|
||||
class MideaProtocol : public RemoteProtocol<MideaData> {
|
||||
public:
|
||||
void encode(RemoteTransmitData *dst, const MideaData &data) override;
|
||||
void encode(RemoteTransmitData *dst, const MideaData &src) override;
|
||||
optional<MideaData> decode(RemoteReceiveData src) override;
|
||||
void dump(const MideaData &data) override;
|
||||
|
||||
protected:
|
||||
static const int32_t TICK_US = 560;
|
||||
static const int32_t HEADER_HIGH_US = 8 * TICK_US;
|
||||
static const int32_t HEADER_LOW_US = 8 * TICK_US;
|
||||
static const int32_t BIT_HIGH_US = 1 * TICK_US;
|
||||
static const int32_t BIT_ONE_LOW_US = 3 * TICK_US;
|
||||
static const int32_t BIT_ZERO_LOW_US = 1 * TICK_US;
|
||||
static const int32_t MIN_GAP_US = 10 * TICK_US;
|
||||
static void one(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); }
|
||||
static void zero(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); }
|
||||
static void header(RemoteTransmitData *dst) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); }
|
||||
static void footer(RemoteTransmitData *dst) { dst->item(BIT_HIGH_US, MIN_GAP_US); }
|
||||
static void data(RemoteTransmitData *dst, const MideaData &src, bool compliment = false);
|
||||
static bool expect_one(RemoteReceiveData &src);
|
||||
static bool expect_zero(RemoteReceiveData &src);
|
||||
static bool expect_header(RemoteReceiveData &src);
|
||||
static bool expect_footer(RemoteReceiveData &src);
|
||||
static bool expect_data(RemoteReceiveData &src, MideaData &out);
|
||||
};
|
||||
|
||||
class MideaBinarySensor : public RemoteReceiverBinarySensorBase {
|
||||
|
||||
Reference in New Issue
Block a user