mirror of
https://github.com/esphome/esphome.git
synced 2024-11-14 11:08:10 +01: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:
parent
b406c6403c
commit
9a70bfa471
19 changed files with 664 additions and 230 deletions
|
@ -102,6 +102,7 @@ esphome/components/mcp9808/* @k7hpn
|
||||||
esphome/components/md5/* @esphome/core
|
esphome/components/md5/* @esphome/core
|
||||||
esphome/components/mdns/* @esphome/core
|
esphome/components/mdns/* @esphome/core
|
||||||
esphome/components/midea/* @dudanov
|
esphome/components/midea/* @dudanov
|
||||||
|
esphome/components/midea_ir/* @dudanov
|
||||||
esphome/components/mitsubishi/* @RubyBailey
|
esphome/components/mitsubishi/* @RubyBailey
|
||||||
esphome/components/modbus_controller/* @martgras
|
esphome/components/modbus_controller/* @martgras
|
||||||
esphome/components/modbus_controller/binary_sensor/* @martgras
|
esphome/components/modbus_controller/binary_sensor/* @martgras
|
||||||
|
|
|
@ -10,21 +10,22 @@ climate::ClimateTraits ClimateIR::traits() {
|
||||||
auto traits = climate::ClimateTraits();
|
auto traits = climate::ClimateTraits();
|
||||||
traits.set_supports_current_temperature(this->sensor_ != nullptr);
|
traits.set_supports_current_temperature(this->sensor_ != nullptr);
|
||||||
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
|
traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL});
|
||||||
if (supports_cool_)
|
if (this->supports_cool_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||||
if (supports_heat_)
|
if (this->supports_heat_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||||
if (supports_dry_)
|
if (this->supports_dry_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||||
if (supports_fan_only_)
|
if (this->supports_fan_only_)
|
||||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||||
|
|
||||||
traits.set_supports_two_point_target_temperature(false);
|
traits.set_supports_two_point_target_temperature(false);
|
||||||
traits.set_visual_min_temperature(this->minimum_temperature_);
|
traits.set_visual_min_temperature(this->minimum_temperature_);
|
||||||
traits.set_visual_max_temperature(this->maximum_temperature_);
|
traits.set_visual_max_temperature(this->maximum_temperature_);
|
||||||
traits.set_visual_temperature_step(this->temperature_step_);
|
traits.set_visual_temperature_step(this->temperature_step_);
|
||||||
traits.set_supported_fan_modes(fan_modes_);
|
traits.set_supported_fan_modes(this->fan_modes_);
|
||||||
traits.set_supported_swing_modes(swing_modes_);
|
traits.set_supported_swing_modes(this->swing_modes_);
|
||||||
|
traits.set_supported_presets(this->presets_);
|
||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +51,7 @@ void ClimateIR::setup() {
|
||||||
roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_));
|
roundf(clamp(this->current_temperature, this->minimum_temperature_, this->maximum_temperature_));
|
||||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||||
|
this->preset = climate::CLIMATE_PRESET_NONE;
|
||||||
}
|
}
|
||||||
// Never send nan to HA
|
// Never send nan to HA
|
||||||
if (std::isnan(this->target_temperature))
|
if (std::isnan(this->target_temperature))
|
||||||
|
@ -65,6 +67,8 @@ void ClimateIR::control(const climate::ClimateCall &call) {
|
||||||
this->fan_mode = *call.get_fan_mode();
|
this->fan_mode = *call.get_fan_mode();
|
||||||
if (call.get_swing_mode().has_value())
|
if (call.get_swing_mode().has_value())
|
||||||
this->swing_mode = *call.get_swing_mode();
|
this->swing_mode = *call.get_swing_mode();
|
||||||
|
if (call.get_preset().has_value())
|
||||||
|
this->preset = *call.get_preset();
|
||||||
this->transmit_state();
|
this->transmit_state();
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
|
||||||
public:
|
public:
|
||||||
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
|
ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f,
|
||||||
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
|
bool supports_dry = false, bool supports_fan_only = false, std::set<climate::ClimateFanMode> fan_modes = {},
|
||||||
std::set<climate::ClimateSwingMode> swing_modes = {}) {
|
std::set<climate::ClimateSwingMode> swing_modes = {}, std::set<climate::ClimatePreset> presets = {}) {
|
||||||
this->minimum_temperature_ = minimum_temperature;
|
this->minimum_temperature_ = minimum_temperature;
|
||||||
this->maximum_temperature_ = maximum_temperature;
|
this->maximum_temperature_ = maximum_temperature;
|
||||||
this->temperature_step_ = temperature_step;
|
this->temperature_step_ = temperature_step;
|
||||||
|
@ -30,6 +30,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
|
||||||
this->supports_fan_only_ = supports_fan_only;
|
this->supports_fan_only_ = supports_fan_only;
|
||||||
this->fan_modes_ = std::move(fan_modes);
|
this->fan_modes_ = std::move(fan_modes);
|
||||||
this->swing_modes_ = std::move(swing_modes);
|
this->swing_modes_ = std::move(swing_modes);
|
||||||
|
this->presets_ = std::move(presets);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
|
@ -61,6 +62,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base:
|
||||||
bool supports_fan_only_{false};
|
bool supports_fan_only_{false};
|
||||||
std::set<climate::ClimateFanMode> fan_modes_ = {};
|
std::set<climate::ClimateFanMode> fan_modes_ = {};
|
||||||
std::set<climate::ClimateSwingMode> swing_modes_ = {};
|
std::set<climate::ClimateSwingMode> swing_modes_ = {};
|
||||||
|
std::set<climate::ClimatePreset> presets_ = {};
|
||||||
|
|
||||||
remote_transmitter::RemoteTransmitterComponent *transmitter_;
|
remote_transmitter::RemoteTransmitterComponent *transmitter_;
|
||||||
sensor::Sensor *sensor_{nullptr};
|
sensor::Sensor *sensor_{nullptr};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "coolix.h"
|
#include "coolix.h"
|
||||||
|
#include "esphome/components/remote_base/coolix_protocol.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -6,29 +7,29 @@ namespace coolix {
|
||||||
|
|
||||||
static const char *const TAG = "coolix.climate";
|
static const char *const TAG = "coolix.climate";
|
||||||
|
|
||||||
const uint32_t COOLIX_OFF = 0xB27BE0;
|
static const uint32_t COOLIX_OFF = 0xB27BE0;
|
||||||
const uint32_t COOLIX_SWING = 0xB26BE0;
|
static const uint32_t COOLIX_SWING = 0xB26BE0;
|
||||||
const uint32_t COOLIX_LED = 0xB5F5A5;
|
static const uint32_t COOLIX_LED = 0xB5F5A5;
|
||||||
const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6;
|
static const uint32_t COOLIX_SILENCE_FP = 0xB5F5B6;
|
||||||
|
|
||||||
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
|
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
|
||||||
const uint8_t COOLIX_COOL = 0b0000;
|
static const uint8_t COOLIX_COOL = 0b0000;
|
||||||
const uint8_t COOLIX_DRY_FAN = 0b0100;
|
static const uint8_t COOLIX_DRY_FAN = 0b0100;
|
||||||
const uint8_t COOLIX_AUTO = 0b1000;
|
static const uint8_t COOLIX_AUTO = 0b1000;
|
||||||
const uint8_t COOLIX_HEAT = 0b1100;
|
static const uint8_t COOLIX_HEAT = 0b1100;
|
||||||
const uint32_t COOLIX_MODE_MASK = 0b1100;
|
static const uint32_t COOLIX_MODE_MASK = 0b1100;
|
||||||
const uint32_t COOLIX_FAN_MASK = 0xF000;
|
static const uint32_t COOLIX_FAN_MASK = 0xF000;
|
||||||
const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000;
|
static const uint32_t COOLIX_FAN_MODE_AUTO_DRY = 0x1000;
|
||||||
const uint32_t COOLIX_FAN_AUTO = 0xB000;
|
static const uint32_t COOLIX_FAN_AUTO = 0xB000;
|
||||||
const uint32_t COOLIX_FAN_MIN = 0x9000;
|
static const uint32_t COOLIX_FAN_MIN = 0x9000;
|
||||||
const uint32_t COOLIX_FAN_MED = 0x5000;
|
static const uint32_t COOLIX_FAN_MED = 0x5000;
|
||||||
const uint32_t COOLIX_FAN_MAX = 0x3000;
|
static const uint32_t COOLIX_FAN_MAX = 0x3000;
|
||||||
|
|
||||||
// Temperature
|
// Temperature
|
||||||
const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
|
static const uint8_t COOLIX_TEMP_RANGE = COOLIX_TEMP_MAX - COOLIX_TEMP_MIN + 1;
|
||||||
const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode.
|
static const uint8_t COOLIX_FAN_TEMP_CODE = 0b11100000; // Part of Fan Mode.
|
||||||
const uint32_t COOLIX_TEMP_MASK = 0b11110000;
|
static const uint32_t COOLIX_TEMP_MASK = 0b11110000;
|
||||||
const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
|
static const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
|
||||||
0b00000000, // 17C
|
0b00000000, // 17C
|
||||||
0b00010000, // 18c
|
0b00010000, // 18c
|
||||||
0b00110000, // 19C
|
0b00110000, // 19C
|
||||||
|
@ -45,17 +46,6 @@ const uint8_t COOLIX_TEMP_MAP[COOLIX_TEMP_RANGE] = {
|
||||||
0b10110000 // 30C
|
0b10110000 // 30C
|
||||||
};
|
};
|
||||||
|
|
||||||
// Constants
|
|
||||||
static const uint32_t BIT_MARK_US = 660;
|
|
||||||
static const uint32_t HEADER_MARK_US = 560 * 8;
|
|
||||||
static const uint32_t HEADER_SPACE_US = 560 * 8;
|
|
||||||
static const uint32_t BIT_ONE_SPACE_US = 1500;
|
|
||||||
static const uint32_t BIT_ZERO_SPACE_US = 450;
|
|
||||||
static const uint32_t FOOTER_MARK_US = BIT_MARK_US;
|
|
||||||
static const uint32_t FOOTER_SPACE_US = HEADER_SPACE_US;
|
|
||||||
|
|
||||||
const uint16_t COOLIX_BITS = 24;
|
|
||||||
|
|
||||||
void CoolixClimate::transmit_state() {
|
void CoolixClimate::transmit_state() {
|
||||||
uint32_t remote_state = 0xB20F00;
|
uint32_t remote_state = 0xB20F00;
|
||||||
|
|
||||||
|
@ -111,119 +101,60 @@ void CoolixClimate::transmit_state() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ESP_LOGV(TAG, "Sending coolix code: 0x%02X", remote_state);
|
ESP_LOGV(TAG, "Sending coolix code: 0x%06X", remote_state);
|
||||||
|
|
||||||
auto transmit = this->transmitter_->transmit();
|
auto transmit = this->transmitter_->transmit();
|
||||||
auto data = transmit.get_data();
|
auto data = transmit.get_data();
|
||||||
|
remote_base::CoolixProtocol().encode(data, remote_state);
|
||||||
data->set_carrier_frequency(38000);
|
|
||||||
uint16_t repeat = 1;
|
|
||||||
for (uint16_t r = 0; r <= repeat; r++) {
|
|
||||||
// Header
|
|
||||||
data->mark(HEADER_MARK_US);
|
|
||||||
data->space(HEADER_SPACE_US);
|
|
||||||
// Data
|
|
||||||
// Break data into bytes, starting at the Most Significant
|
|
||||||
// Byte. Each byte then being sent normal, then followed inverted.
|
|
||||||
for (uint16_t i = 8; i <= COOLIX_BITS; i += 8) {
|
|
||||||
// Grab a bytes worth of data.
|
|
||||||
uint8_t byte = (remote_state >> (COOLIX_BITS - i)) & 0xFF;
|
|
||||||
// Normal
|
|
||||||
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
|
||||||
data->mark(BIT_MARK_US);
|
|
||||||
data->space((byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
|
|
||||||
}
|
|
||||||
// Inverted
|
|
||||||
for (uint64_t mask = 1ULL << 7; mask; mask >>= 1) {
|
|
||||||
data->mark(BIT_MARK_US);
|
|
||||||
data->space(!(byte & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Footer
|
|
||||||
data->mark(BIT_MARK_US);
|
|
||||||
data->space(FOOTER_SPACE_US); // Pause before repeating
|
|
||||||
}
|
|
||||||
|
|
||||||
transmit.perform();
|
transmit.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) {
|
bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data) {
|
||||||
|
auto decoded = remote_base::CoolixProtocol().decode(data);
|
||||||
|
if (!decoded.has_value())
|
||||||
|
return false;
|
||||||
// Decoded remote state y 3 bytes long code.
|
// Decoded remote state y 3 bytes long code.
|
||||||
uint32_t remote_state = 0;
|
uint32_t remote_state = *decoded;
|
||||||
// The protocol sends the data twice, read here
|
ESP_LOGV(TAG, "Decoded 0x%06X", remote_state);
|
||||||
uint32_t loop_read;
|
if ((remote_state & 0xFF0000) != 0xB20000)
|
||||||
for (uint16_t loop = 1; loop <= 2; loop++) {
|
|
||||||
if (!data.expect_item(HEADER_MARK_US, HEADER_SPACE_US))
|
|
||||||
return false;
|
|
||||||
loop_read = 0;
|
|
||||||
for (uint8_t a_byte = 0; a_byte < 3; a_byte++) {
|
|
||||||
uint8_t byte = 0;
|
|
||||||
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
|
|
||||||
if (data.expect_item(BIT_MARK_US, BIT_ONE_SPACE_US))
|
|
||||||
byte |= 1 << a_bit;
|
|
||||||
else if (!data.expect_item(BIT_MARK_US, BIT_ZERO_SPACE_US))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Need to see this segment inverted
|
|
||||||
for (int8_t a_bit = 7; a_bit >= 0; a_bit--) {
|
|
||||||
bool bit = byte & (1 << a_bit);
|
|
||||||
if (!data.expect_item(BIT_MARK_US, bit ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Receiving MSB first: reorder bytes
|
|
||||||
loop_read |= byte << ((2 - a_byte) * 8);
|
|
||||||
}
|
|
||||||
// Footer Mark
|
|
||||||
if (!data.expect_mark(BIT_MARK_US))
|
|
||||||
return false;
|
|
||||||
if (loop == 1) {
|
|
||||||
// Back up state on first loop
|
|
||||||
remote_state = loop_read;
|
|
||||||
if (!data.expect_space(FOOTER_SPACE_US))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Decoded 0x%02X", remote_state);
|
|
||||||
if (remote_state != loop_read || (remote_state & 0xFF0000) != 0xB20000)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (remote_state == COOLIX_OFF) {
|
if (remote_state == COOLIX_OFF) {
|
||||||
this->mode = climate::CLIMATE_MODE_OFF;
|
parent->mode = climate::CLIMATE_MODE_OFF;
|
||||||
} else if (remote_state == COOLIX_SWING) {
|
} else if (remote_state == COOLIX_SWING) {
|
||||||
this->swing_mode =
|
parent->swing_mode =
|
||||||
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
parent->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
||||||
} else {
|
} else {
|
||||||
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
|
if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT)
|
||||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
parent->mode = climate::CLIMATE_MODE_HEAT;
|
||||||
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
|
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO)
|
||||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
parent->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||||
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
|
else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) {
|
||||||
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
|
if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY)
|
||||||
this->mode = climate::CLIMATE_MODE_DRY;
|
parent->mode = climate::CLIMATE_MODE_DRY;
|
||||||
else
|
else
|
||||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
parent->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||||
} else
|
} else
|
||||||
this->mode = climate::CLIMATE_MODE_COOL;
|
parent->mode = climate::CLIMATE_MODE_COOL;
|
||||||
|
|
||||||
// Fan Speed
|
// Fan Speed
|
||||||
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL ||
|
if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL ||
|
||||||
this->mode == climate::CLIMATE_MODE_DRY)
|
parent->mode == climate::CLIMATE_MODE_DRY)
|
||||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
parent->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||||
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)
|
else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN)
|
||||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
parent->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||||
else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED)
|
else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED)
|
||||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
parent->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||||
else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX)
|
else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX)
|
||||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
parent->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||||
|
|
||||||
// Temperature
|
// Temperature
|
||||||
uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK;
|
uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK;
|
||||||
for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++)
|
for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++)
|
||||||
if (COOLIX_TEMP_MAP[i] == temperature_code)
|
if (COOLIX_TEMP_MAP[i] == temperature_code)
|
||||||
this->target_temperature = i + COOLIX_TEMP_MIN;
|
parent->target_temperature = i + COOLIX_TEMP_MIN;
|
||||||
}
|
}
|
||||||
this->publish_state();
|
parent->publish_state();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,15 @@ class CoolixClimate : public climate_ir::ClimateIR {
|
||||||
climate_ir::ClimateIR::control(call);
|
climate_ir::ClimateIR::control(call);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This static method can be used in other climate components that accept the Coolix protocol. See midea_ir for
|
||||||
|
/// example.
|
||||||
|
static bool on_coolix(climate::Climate *parent, remote_base::RemoteReceiveData data);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Transmit via IR the state of this climate controller.
|
/// Transmit via IR the state of this climate controller.
|
||||||
void transmit_state() override;
|
void transmit_state() override;
|
||||||
/// Handle received IR Buffer
|
/// Handle received IR Buffer
|
||||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
bool on_receive(remote_base::RemoteReceiveData data) override { return CoolixClimate::on_coolix(this, data); }
|
||||||
|
|
||||||
bool send_swing_cmd_{false};
|
bool send_swing_cmd_{false};
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,12 +23,12 @@ class IrFollowMeData : public IrData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TEMPERATURE */
|
/* TEMPERATURE */
|
||||||
uint8_t temp() const { return this->data_[4] - 1; }
|
uint8_t temp() const { return this->get_value_(4) - 1; }
|
||||||
void set_temp(uint8_t val) { this->data_[4] = std::min(MAX_TEMP, val) + 1; }
|
void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); }
|
||||||
|
|
||||||
/* BEEPER */
|
/* BEEPER */
|
||||||
bool beeper() const { return this->data_[3] & 128; }
|
bool beeper() const { return this->get_value_(3, 128); }
|
||||||
void set_beeper(bool val) { this->set_value_(3, 1, 7, val); }
|
void set_beeper(bool val) { this->set_mask_(3, val, 128); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static const uint8_t MAX_TEMP = 37;
|
static const uint8_t MAX_TEMP = 37;
|
||||||
|
|
0
esphome/components/midea_ir/__init__.py
Normal file
0
esphome/components/midea_ir/__init__.py
Normal file
25
esphome/components/midea_ir/climate.py
Normal file
25
esphome/components/midea_ir/climate.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import climate_ir
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
AUTO_LOAD = ["climate_ir", "coolix"]
|
||||||
|
CODEOWNERS = ["@dudanov"]
|
||||||
|
|
||||||
|
midea_ir_ns = cg.esphome_ns.namespace("midea_ir")
|
||||||
|
MideaIR = midea_ir_ns.class_("MideaIR", climate_ir.ClimateIR)
|
||||||
|
|
||||||
|
CONF_USE_FAHRENHEIT = "use_fahrenheit"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(MideaIR),
|
||||||
|
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await climate_ir.register_climate_ir(var, config)
|
||||||
|
cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT]))
|
92
esphome/components/midea_ir/midea_data.h
Normal file
92
esphome/components/midea_ir/midea_data.h
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/remote_base/midea_protocol.h"
|
||||||
|
#include "esphome/components/climate/climate_mode.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace midea_ir {
|
||||||
|
|
||||||
|
using climate::ClimateMode;
|
||||||
|
using climate::ClimateFanMode;
|
||||||
|
using remote_base::MideaData;
|
||||||
|
|
||||||
|
class ControlData : public MideaData {
|
||||||
|
public:
|
||||||
|
// Default constructor (power: ON, mode: AUTO, fan: AUTO, temp: 25C)
|
||||||
|
ControlData() : MideaData({MIDEA_TYPE_CONTROL, 0x82, 0x48, 0xFF, 0xFF}) {}
|
||||||
|
// Copy from Base
|
||||||
|
ControlData(const MideaData &data) : MideaData(data) {}
|
||||||
|
|
||||||
|
void set_temp(float temp);
|
||||||
|
float get_temp() const;
|
||||||
|
|
||||||
|
void set_mode(ClimateMode mode);
|
||||||
|
ClimateMode get_mode() const;
|
||||||
|
|
||||||
|
void set_fan_mode(ClimateFanMode mode);
|
||||||
|
ClimateFanMode get_fan_mode() const;
|
||||||
|
|
||||||
|
void set_sleep_preset(bool value) { this->set_mask_(1, value, 64); }
|
||||||
|
bool get_sleep_preset() const { return this->get_value_(1, 64); }
|
||||||
|
|
||||||
|
void set_fahrenheit(bool value) { this->set_mask_(2, value, 32); }
|
||||||
|
bool get_fahrenheit() const { return this->get_value_(2, 32); }
|
||||||
|
|
||||||
|
void fix();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
enum Mode : uint8_t {
|
||||||
|
MODE_COOL,
|
||||||
|
MODE_DRY,
|
||||||
|
MODE_AUTO,
|
||||||
|
MODE_HEAT,
|
||||||
|
MODE_FAN_ONLY,
|
||||||
|
};
|
||||||
|
enum FanMode : uint8_t {
|
||||||
|
FAN_AUTO,
|
||||||
|
FAN_LOW,
|
||||||
|
FAN_MEDIUM,
|
||||||
|
FAN_HIGH,
|
||||||
|
};
|
||||||
|
void set_fan_mode_(FanMode mode) { this->set_value_(1, mode, 3, 3); }
|
||||||
|
FanMode get_fan_mode_() const { return static_cast<FanMode>(this->get_value_(1, 3, 3)); }
|
||||||
|
void set_mode_(Mode mode) { this->set_value_(1, mode, 7); }
|
||||||
|
Mode get_mode_() const { return static_cast<Mode>(this->get_value_(1, 7)); }
|
||||||
|
void set_power_(bool value) { this->set_mask_(1, value, 128); }
|
||||||
|
bool get_power_() const { return this->get_value_(1, 128); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class FollowMeData : public MideaData {
|
||||||
|
public:
|
||||||
|
// Default constructor (temp: 30C, beeper: off)
|
||||||
|
FollowMeData() : MideaData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {}
|
||||||
|
// Copy from Base
|
||||||
|
FollowMeData(const MideaData &data) : MideaData(data) {}
|
||||||
|
// Direct from temperature and beeper values
|
||||||
|
FollowMeData(uint8_t temp, bool beeper = false) : FollowMeData() {
|
||||||
|
this->set_temp(temp);
|
||||||
|
this->set_beeper(beeper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TEMPERATURE */
|
||||||
|
uint8_t temp() const { return this->get_value_(4) - 1; }
|
||||||
|
void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); }
|
||||||
|
|
||||||
|
/* BEEPER */
|
||||||
|
bool beeper() const { return this->get_value_(3, 128); }
|
||||||
|
void set_beeper(bool value) { this->set_mask_(3, value, 128); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static const uint8_t MAX_TEMP = 37;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SpecialData : public MideaData {
|
||||||
|
public:
|
||||||
|
SpecialData(uint8_t code) : MideaData({MIDEA_TYPE_SPECIAL, code, 0xFF, 0xFF, 0xFF}) {}
|
||||||
|
static const uint8_t VSWING_STEP = 1;
|
||||||
|
static const uint8_t VSWING_TOGGLE = 2;
|
||||||
|
static const uint8_t TURBO_TOGGLE = 9;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace midea_ir
|
||||||
|
} // namespace esphome
|
201
esphome/components/midea_ir/midea_ir.cpp
Normal file
201
esphome/components/midea_ir/midea_ir.cpp
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
#include "midea_ir.h"
|
||||||
|
#include "midea_data.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/components/coolix/coolix.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace midea_ir {
|
||||||
|
|
||||||
|
static const char *const TAG = "midea_ir.climate";
|
||||||
|
|
||||||
|
void ControlData::set_temp(float temp) {
|
||||||
|
uint8_t min;
|
||||||
|
if (this->get_fahrenheit()) {
|
||||||
|
min = MIDEA_TEMPF_MIN;
|
||||||
|
temp = esphome::clamp<float>(celsius_to_fahrenheit(temp), MIDEA_TEMPF_MIN, MIDEA_TEMPF_MAX);
|
||||||
|
} else {
|
||||||
|
min = MIDEA_TEMPC_MIN;
|
||||||
|
temp = esphome::clamp<float>(temp, MIDEA_TEMPC_MIN, MIDEA_TEMPC_MAX);
|
||||||
|
}
|
||||||
|
this->set_value_(2, lroundf(temp) - min, 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
float ControlData::get_temp() const {
|
||||||
|
const uint8_t temp = this->get_value_(2, 31);
|
||||||
|
if (this->get_fahrenheit())
|
||||||
|
return fahrenheit_to_celsius(static_cast<float>(temp + MIDEA_TEMPF_MIN));
|
||||||
|
return static_cast<float>(temp + MIDEA_TEMPC_MIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlData::fix() {
|
||||||
|
// In FAN_AUTO, modes COOL, HEAT and FAN_ONLY bit #5 in byte #1 must be set
|
||||||
|
const uint8_t value = this->get_value_(1, 31);
|
||||||
|
if (value == 0 || value == 3 || value == 4)
|
||||||
|
this->set_mask_(1, true, 32);
|
||||||
|
// In FAN_ONLY mode we need to set all temperature bits
|
||||||
|
if (this->get_mode_() == MODE_FAN_ONLY)
|
||||||
|
this->set_mask_(2, true, 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlData::set_mode(ClimateMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case ClimateMode::CLIMATE_MODE_OFF:
|
||||||
|
this->set_power_(false);
|
||||||
|
return;
|
||||||
|
case ClimateMode::CLIMATE_MODE_COOL:
|
||||||
|
this->set_mode_(MODE_COOL);
|
||||||
|
break;
|
||||||
|
case ClimateMode::CLIMATE_MODE_DRY:
|
||||||
|
this->set_mode_(MODE_DRY);
|
||||||
|
break;
|
||||||
|
case ClimateMode::CLIMATE_MODE_FAN_ONLY:
|
||||||
|
this->set_mode_(MODE_FAN_ONLY);
|
||||||
|
break;
|
||||||
|
case ClimateMode::CLIMATE_MODE_HEAT:
|
||||||
|
this->set_mode_(MODE_HEAT);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this->set_mode_(MODE_AUTO);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this->set_power_(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClimateMode ControlData::get_mode() const {
|
||||||
|
if (!this->get_power_())
|
||||||
|
return ClimateMode::CLIMATE_MODE_OFF;
|
||||||
|
switch (this->get_mode_()) {
|
||||||
|
case MODE_COOL:
|
||||||
|
return ClimateMode::CLIMATE_MODE_COOL;
|
||||||
|
case MODE_DRY:
|
||||||
|
return ClimateMode::CLIMATE_MODE_DRY;
|
||||||
|
case MODE_FAN_ONLY:
|
||||||
|
return ClimateMode::CLIMATE_MODE_FAN_ONLY;
|
||||||
|
case MODE_HEAT:
|
||||||
|
return ClimateMode::CLIMATE_MODE_HEAT;
|
||||||
|
default:
|
||||||
|
return ClimateMode::CLIMATE_MODE_HEAT_COOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlData::set_fan_mode(ClimateFanMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case ClimateFanMode::CLIMATE_FAN_LOW:
|
||||||
|
this->set_fan_mode_(FAN_LOW);
|
||||||
|
break;
|
||||||
|
case ClimateFanMode::CLIMATE_FAN_MEDIUM:
|
||||||
|
this->set_fan_mode_(FAN_MEDIUM);
|
||||||
|
break;
|
||||||
|
case ClimateFanMode::CLIMATE_FAN_HIGH:
|
||||||
|
this->set_fan_mode_(FAN_HIGH);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this->set_fan_mode_(FAN_AUTO);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClimateFanMode ControlData::get_fan_mode() const {
|
||||||
|
switch (this->get_fan_mode_()) {
|
||||||
|
case FAN_LOW:
|
||||||
|
return ClimateFanMode::CLIMATE_FAN_LOW;
|
||||||
|
case FAN_MEDIUM:
|
||||||
|
return ClimateFanMode::CLIMATE_FAN_MEDIUM;
|
||||||
|
case FAN_HIGH:
|
||||||
|
return ClimateFanMode::CLIMATE_FAN_HIGH;
|
||||||
|
default:
|
||||||
|
return ClimateFanMode::CLIMATE_FAN_AUTO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MideaIR::control(const climate::ClimateCall &call) {
|
||||||
|
// swing and preset resets after unit powered off
|
||||||
|
if (call.get_mode() == climate::CLIMATE_MODE_OFF) {
|
||||||
|
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||||
|
this->preset = climate::CLIMATE_PRESET_NONE;
|
||||||
|
} else if (call.get_swing_mode().has_value() && ((*call.get_swing_mode() == climate::CLIMATE_SWING_OFF &&
|
||||||
|
this->swing_mode == climate::CLIMATE_SWING_VERTICAL) ||
|
||||||
|
(*call.get_swing_mode() == climate::CLIMATE_SWING_VERTICAL &&
|
||||||
|
this->swing_mode == climate::CLIMATE_SWING_OFF))) {
|
||||||
|
this->swing_ = true;
|
||||||
|
} else if (call.get_preset().has_value() &&
|
||||||
|
((*call.get_preset() == climate::CLIMATE_PRESET_NONE && this->preset == climate::CLIMATE_PRESET_BOOST) ||
|
||||||
|
(*call.get_preset() == climate::CLIMATE_PRESET_BOOST && this->preset == climate::CLIMATE_PRESET_NONE))) {
|
||||||
|
this->boost_ = true;
|
||||||
|
}
|
||||||
|
climate_ir::ClimateIR::control(call);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MideaIR::transmit_(MideaData &data) {
|
||||||
|
data.finalize();
|
||||||
|
auto transmit = this->transmitter_->transmit();
|
||||||
|
remote_base::MideaProtocol().encode(transmit.get_data(), data);
|
||||||
|
transmit.perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MideaIR::transmit_state() {
|
||||||
|
if (this->swing_) {
|
||||||
|
SpecialData data(SpecialData::VSWING_TOGGLE);
|
||||||
|
this->transmit_(data);
|
||||||
|
this->swing_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->boost_) {
|
||||||
|
SpecialData data(SpecialData::TURBO_TOGGLE);
|
||||||
|
this->transmit_(data);
|
||||||
|
this->boost_ = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ControlData data;
|
||||||
|
data.set_fahrenheit(this->fahrenheit_);
|
||||||
|
data.set_temp(this->target_temperature);
|
||||||
|
data.set_mode(this->mode);
|
||||||
|
data.set_fan_mode(this->fan_mode.value_or(ClimateFanMode::CLIMATE_FAN_AUTO));
|
||||||
|
data.set_sleep_preset(this->preset == climate::CLIMATE_PRESET_SLEEP);
|
||||||
|
data.fix();
|
||||||
|
this->transmit_(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MideaIR::on_receive(remote_base::RemoteReceiveData data) {
|
||||||
|
auto midea = remote_base::MideaProtocol().decode(data);
|
||||||
|
if (midea.has_value())
|
||||||
|
return this->on_midea_(*midea);
|
||||||
|
return coolix::CoolixClimate::on_coolix(this, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MideaIR::on_midea_(const MideaData &data) {
|
||||||
|
ESP_LOGV(TAG, "Decoded Midea IR data: %s", data.to_string().c_str());
|
||||||
|
if (data.type() == MideaData::MIDEA_TYPE_CONTROL) {
|
||||||
|
const ControlData status = data;
|
||||||
|
if (status.get_mode() != climate::CLIMATE_MODE_FAN_ONLY)
|
||||||
|
this->target_temperature = status.get_temp();
|
||||||
|
this->mode = status.get_mode();
|
||||||
|
this->fan_mode = status.get_fan_mode();
|
||||||
|
if (status.get_sleep_preset())
|
||||||
|
this->preset = climate::CLIMATE_PRESET_SLEEP;
|
||||||
|
else if (this->preset == climate::CLIMATE_PRESET_SLEEP)
|
||||||
|
this->preset = climate::CLIMATE_PRESET_NONE;
|
||||||
|
this->publish_state();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (data.type() == MideaData::MIDEA_TYPE_SPECIAL) {
|
||||||
|
switch (data[1]) {
|
||||||
|
case SpecialData::VSWING_TOGGLE:
|
||||||
|
this->swing_mode = this->swing_mode == climate::CLIMATE_SWING_VERTICAL ? climate::CLIMATE_SWING_OFF
|
||||||
|
: climate::CLIMATE_SWING_VERTICAL;
|
||||||
|
break;
|
||||||
|
case SpecialData::TURBO_TOGGLE:
|
||||||
|
this->preset = this->preset == climate::CLIMATE_PRESET_BOOST ? climate::CLIMATE_PRESET_NONE
|
||||||
|
: climate::CLIMATE_PRESET_BOOST;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this->publish_state();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace midea_ir
|
||||||
|
} // namespace esphome
|
47
esphome/components/midea_ir/midea_ir.h
Normal file
47
esphome/components/midea_ir/midea_ir.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/climate_ir/climate_ir.h"
|
||||||
|
#include "midea_data.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace midea_ir {
|
||||||
|
|
||||||
|
// Temperature
|
||||||
|
const uint8_t MIDEA_TEMPC_MIN = 17; // Celsius
|
||||||
|
const uint8_t MIDEA_TEMPC_MAX = 30; // Celsius
|
||||||
|
const uint8_t MIDEA_TEMPF_MIN = 62; // Fahrenheit
|
||||||
|
const uint8_t MIDEA_TEMPF_MAX = 86; // Fahrenheit
|
||||||
|
|
||||||
|
class MideaIR : public climate_ir::ClimateIR {
|
||||||
|
public:
|
||||||
|
MideaIR()
|
||||||
|
: climate_ir::ClimateIR(
|
||||||
|
MIDEA_TEMPC_MIN, MIDEA_TEMPC_MAX, 1.0f, true, true,
|
||||||
|
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
|
||||||
|
climate::CLIMATE_FAN_HIGH},
|
||||||
|
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL},
|
||||||
|
{climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_SLEEP, climate::CLIMATE_PRESET_BOOST}) {}
|
||||||
|
|
||||||
|
/// Override control to change settings of the climate device.
|
||||||
|
void control(const climate::ClimateCall &call) override;
|
||||||
|
|
||||||
|
/// Set use of Fahrenheit units
|
||||||
|
void set_fahrenheit(bool value) {
|
||||||
|
this->fahrenheit_ = value;
|
||||||
|
this->temperature_step_ = value ? 0.5f : 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Transmit via IR the state of this climate controller.
|
||||||
|
void transmit_state() override;
|
||||||
|
void transmit_(MideaData &data);
|
||||||
|
/// Handle received IR Buffer
|
||||||
|
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||||
|
bool on_midea_(const MideaData &data);
|
||||||
|
bool fahrenheit_{false};
|
||||||
|
bool swing_{false};
|
||||||
|
bool boost_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace midea_ir
|
||||||
|
} // namespace esphome
|
|
@ -234,6 +234,45 @@ async def build_dumpers(config):
|
||||||
return dumpers
|
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
|
# Dish
|
||||||
DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol(
|
DishData, DishBinarySensor, DishTrigger, DishAction, DishDumper = declare_protocol(
|
||||||
"Dish"
|
"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 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 MideaData::calc_cs_() const {
|
||||||
uint8_t cs = 0;
|
uint8_t cs = 0;
|
||||||
for (const uint8_t *it = this->data(); it != this->data() + OFFSET_CS; ++it)
|
for (uint8_t idx = 0; idx < OFFSET_CS; idx++)
|
||||||
cs -= reverse_bits(*it);
|
cs -= reverse_bits(this->data_[idx]);
|
||||||
return reverse_bits(cs);
|
return reverse_bits(cs);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MideaData::check_compliment(const MideaData &rhs) const {
|
bool MideaData::is_compliment(const MideaData &rhs) const {
|
||||||
const uint8_t *it0 = rhs.data();
|
return std::equal(this->data_.begin(), this->data_.end(), rhs.data_.begin(),
|
||||||
for (const uint8_t *it1 = this->data(); it1 != this->data() + this->size(); ++it0, ++it1) {
|
[](const uint8_t &a, const uint8_t &b) { return a + b == 255; });
|
||||||
if (*it0 != ~(*it1))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MideaProtocol::data(RemoteTransmitData *dst, const MideaData &src, bool compliment) {
|
void MideaProtocol::encode(RemoteTransmitData *dst, const MideaData &src) {
|
||||||
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) {
|
|
||||||
dst->set_carrier_frequency(38000);
|
dst->set_carrier_frequency(38000);
|
||||||
dst->reserve(2 + 48 * 2 + 2 + 2 + 48 * 2 + 2);
|
dst->reserve(2 + 48 * 2 + 2 + 2 + 48 * 2 + 1);
|
||||||
MideaProtocol::header(dst);
|
dst->item(HEADER_MARK_US, HEADER_SPACE_US);
|
||||||
MideaProtocol::data(dst, data);
|
for (unsigned idx = 0; idx < 6; idx++)
|
||||||
MideaProtocol::footer(dst);
|
for (uint8_t mask = 1 << 7; mask; mask >>= 1)
|
||||||
MideaProtocol::header(dst);
|
dst->item(BIT_MARK_US, (src[idx] & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US);
|
||||||
MideaProtocol::data(dst, data, true);
|
dst->item(FOOTER_MARK_US, FOOTER_SPACE_US);
|
||||||
MideaProtocol::footer(dst);
|
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) {
|
static bool decode_data(RemoteReceiveData &src, MideaData &dst) {
|
||||||
if (!src.peek_item(BIT_HIGH_US, BIT_ONE_LOW_US))
|
for (unsigned idx = 0; idx < 6; idx++) {
|
||||||
return false;
|
uint8_t data = 0;
|
||||||
src.advance(2);
|
for (uint8_t mask = 1 << 7; mask; mask >>= 1) {
|
||||||
return true;
|
if (!src.expect_mark(BIT_MARK_US))
|
||||||
}
|
return false;
|
||||||
|
if (src.expect_space(BIT_ONE_SPACE_US))
|
||||||
bool MideaProtocol::expect_zero(RemoteReceiveData &src) {
|
data |= mask;
|
||||||
if (!src.peek_item(BIT_HIGH_US, BIT_ZERO_LOW_US))
|
else if (!src.expect_space(BIT_ZERO_SPACE_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))
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
dst[idx] = data;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<MideaData> MideaProtocol::decode(RemoteReceiveData src) {
|
optional<MideaData> MideaProtocol::decode(RemoteReceiveData src) {
|
||||||
MideaData out, inv;
|
MideaData out, inv;
|
||||||
if (MideaProtocol::expect_header(src) && MideaProtocol::expect_data(src, out) && MideaProtocol::expect_footer(src) &&
|
if (src.expect_item(HEADER_MARK_US, HEADER_SPACE_US) && decode_data(src, out) && out.is_valid() &&
|
||||||
out.is_valid() && MideaProtocol::expect_data(src, inv) && out.check_compliment(inv))
|
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 out;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "remote_base.h"
|
#include "remote_base.h"
|
||||||
|
@ -9,70 +10,61 @@ namespace remote_base {
|
||||||
|
|
||||||
class MideaData {
|
class MideaData {
|
||||||
public:
|
public:
|
||||||
// Make zero-filled
|
// Make default
|
||||||
MideaData() { memset(this->data_, 0, sizeof(this->data_)); }
|
MideaData() {}
|
||||||
// Make from initializer_list
|
// 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
|
// Make from vector
|
||||||
MideaData(const std::vector<uint8_t> &data) {
|
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
|
// Default copy constructor
|
||||||
MideaData(const MideaData &) = default;
|
MideaData(const MideaData &) = default;
|
||||||
|
|
||||||
uint8_t *data() { return this->data_; }
|
uint8_t *data() { return this->data_.data(); }
|
||||||
const uint8_t *data() const { return this->data_; }
|
const uint8_t *data() const { return this->data_.data(); }
|
||||||
uint8_t size() const { return sizeof(this->data_); }
|
uint8_t size() const { return this->data_.size(); }
|
||||||
bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); }
|
bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); }
|
||||||
void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); }
|
void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); }
|
||||||
bool check_compliment(const MideaData &rhs) const;
|
bool is_compliment(const MideaData &rhs) const;
|
||||||
std::string to_string() const { return format_hex_pretty(this->data_, sizeof(this->data_)); }
|
std::string to_string() const { return format_hex_pretty(this->data_.data(), this->data_.size()); }
|
||||||
// compare only 40-bits
|
// 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 {
|
enum MideaDataType : uint8_t {
|
||||||
MIDEA_TYPE_COMMAND = 0xA1,
|
MIDEA_TYPE_CONTROL = 0xA1,
|
||||||
MIDEA_TYPE_SPECIAL = 0xA2,
|
MIDEA_TYPE_SPECIAL = 0xA2,
|
||||||
MIDEA_TYPE_FOLLOW_ME = 0xA4,
|
MIDEA_TYPE_FOLLOW_ME = 0xA4,
|
||||||
};
|
};
|
||||||
MideaDataType type() const { return static_cast<MideaDataType>(this->data_[0]); }
|
MideaDataType type() const { return static_cast<MideaDataType>(this->data_[0]); }
|
||||||
template<typename T> T to() const { return T(*this); }
|
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:
|
protected:
|
||||||
void set_value_(uint8_t offset, uint8_t val_mask, uint8_t shift, uint8_t val) {
|
uint8_t get_value_(uint8_t idx, uint8_t mask = 255, uint8_t shift = 0) const {
|
||||||
data_[offset] &= ~(val_mask << shift);
|
return (this->data_[idx] >> shift) & mask;
|
||||||
data_[offset] |= (val << shift);
|
|
||||||
}
|
}
|
||||||
|
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;
|
static const uint8_t OFFSET_CS = 5;
|
||||||
// 48-bits data
|
// 48-bits data
|
||||||
uint8_t data_[6];
|
std::array<uint8_t, 6> data_;
|
||||||
// Calculate checksum
|
// Calculate checksum
|
||||||
uint8_t calc_cs_() const;
|
uint8_t calc_cs_() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MideaProtocol : public RemoteProtocol<MideaData> {
|
class MideaProtocol : public RemoteProtocol<MideaData> {
|
||||||
public:
|
public:
|
||||||
void encode(RemoteTransmitData *dst, const MideaData &data) override;
|
void encode(RemoteTransmitData *dst, const MideaData &src) override;
|
||||||
optional<MideaData> decode(RemoteReceiveData src) override;
|
optional<MideaData> decode(RemoteReceiveData src) override;
|
||||||
void dump(const MideaData &data) 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 {
|
class MideaBinarySensor : public RemoteReceiverBinarySensorBase {
|
||||||
|
|
|
@ -378,7 +378,7 @@ std::string format_hex(const uint8_t *data, size_t length) {
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
std::string format_hex(std::vector<uint8_t> data) { return format_hex(data.data(), data.size()); }
|
std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
|
||||||
|
|
||||||
static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; }
|
static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; }
|
||||||
std::string format_hex_pretty(const uint8_t *data, size_t length) {
|
std::string format_hex_pretty(const uint8_t *data, size_t length) {
|
||||||
|
@ -396,6 +396,6 @@ std::string format_hex_pretty(const uint8_t *data, size_t length) {
|
||||||
return ret + " (" + to_string(length) + ")";
|
return ret + " (" + to_string(length) + ")";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
std::string format_hex_pretty(std::vector<uint8_t> data) { return format_hex_pretty(data.data(), data.size()); }
|
std::string format_hex_pretty(const std::vector<uint8_t> &data) { return format_hex_pretty(data.data(), data.size()); }
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -110,6 +110,11 @@ void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation,
|
||||||
/// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1)
|
/// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1)
|
||||||
void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue);
|
void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue);
|
||||||
|
|
||||||
|
/// Convert degrees Celsius to degrees Fahrenheit.
|
||||||
|
static inline float celsius_to_fahrenheit(float value) { return value * 1.8f + 32.0f; }
|
||||||
|
/// Convert degrees Fahrenheit to degrees Celsius.
|
||||||
|
static inline float fahrenheit_to_celsius(float value) { return (value - 32.0f) / 1.8f; }
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* An interrupt helper class.
|
* An interrupt helper class.
|
||||||
*
|
*
|
||||||
|
@ -491,7 +496,7 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional<
|
||||||
/// Format the byte array \p data of length \p len in lowercased hex.
|
/// Format the byte array \p data of length \p len in lowercased hex.
|
||||||
std::string format_hex(const uint8_t *data, size_t length);
|
std::string format_hex(const uint8_t *data, size_t length);
|
||||||
/// Format the vector \p data in lowercased hex.
|
/// Format the vector \p data in lowercased hex.
|
||||||
std::string format_hex(std::vector<uint8_t> data);
|
std::string format_hex(const std::vector<uint8_t> &data);
|
||||||
/// Format an unsigned integer in lowercased hex, starting with the most significant byte.
|
/// Format an unsigned integer in lowercased hex, starting with the most significant byte.
|
||||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex(T val) {
|
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex(T val) {
|
||||||
val = convert_big_endian(val);
|
val = convert_big_endian(val);
|
||||||
|
@ -501,7 +506,7 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::stri
|
||||||
/// Format the byte array \p data of length \p len in pretty-printed, human-readable hex.
|
/// Format the byte array \p data of length \p len in pretty-printed, human-readable hex.
|
||||||
std::string format_hex_pretty(const uint8_t *data, size_t length);
|
std::string format_hex_pretty(const uint8_t *data, size_t length);
|
||||||
/// Format the vector \p data in pretty-printed, human-readable hex.
|
/// Format the vector \p data in pretty-printed, human-readable hex.
|
||||||
std::string format_hex_pretty(std::vector<uint8_t> data);
|
std::string format_hex_pretty(const std::vector<uint8_t> &data);
|
||||||
/// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte.
|
/// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte.
|
||||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex_pretty(T val) {
|
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex_pretty(T val) {
|
||||||
val = convert_big_endian(val);
|
val = convert_big_endian(val);
|
||||||
|
|
|
@ -1718,6 +1718,9 @@ climate:
|
||||||
name: HeatpumpIR Climate
|
name: HeatpumpIR Climate
|
||||||
min_temperature: 18
|
min_temperature: 18
|
||||||
max_temperature: 30
|
max_temperature: 30
|
||||||
|
- platform: midea_ir
|
||||||
|
name: Midea IR
|
||||||
|
use_fahrenheit: true
|
||||||
- platform: midea
|
- platform: midea
|
||||||
on_state:
|
on_state:
|
||||||
logger.log: "State changed!"
|
logger.log: "State changed!"
|
||||||
|
|
Loading…
Reference in a new issue