mirror of
https://github.com/esphome/esphome.git
synced 2026-11-15 17:45:57 +00:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55f15f45ed | ||
|
|
0f691dcca3 | ||
|
|
2d7a8ef338 | ||
|
|
db4c9ba24e | ||
|
|
acf4da7bc8 | ||
|
|
b6dbc9f2f0 | ||
|
|
170a3b7374 | ||
|
|
d53db6af87 | ||
|
|
2e5e080df1 | ||
|
|
0221318335 | ||
|
|
f2468415e4 | ||
|
|
b83255590a | ||
|
|
f738157382 | ||
|
|
85e7a22ee8 | ||
|
|
101ee872cb | ||
|
|
3b546cc5fa | ||
|
|
8acd9df0cf | ||
|
|
6f3270f318 | ||
|
|
822c0f6e49 | ||
|
|
fd76602635 | ||
|
|
0eee107373 | ||
|
|
2347e0dcef |
No files matched your search
@@ -9,6 +9,27 @@ ESPHOME_LOG_TAG(TAG, "airton.climate");
|
||||
// Distinct from the climate's own RESTORE_STATE_VERSION, which shares the entity's preference key
|
||||
static constexpr uint32_t RESTORE_SETTINGS_VERSION = 0x2E86A1F3UL;
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
static constexpr uint8_t AIRTON_POWER_ON = 0b1000;
|
||||
// A received code that is not listed is ignored
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_COOL, AIRTON_POWER_ON | AIRTON_MODE_COOL},
|
||||
{climate::CLIMATE_MODE_DRY, AIRTON_POWER_ON | AIRTON_MODE_DRY},
|
||||
{climate::CLIMATE_MODE_HEAT, AIRTON_POWER_ON | AIRTON_MODE_HEAT},
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, AIRTON_POWER_ON | AIRTON_MODE_AUTO},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, AIRTON_POWER_ON | AIRTON_MODE_FAN},
|
||||
};
|
||||
// Fan speeds 2 and 4 are only received
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, AIRTON_FAN_AUTO}, {climate::CLIMATE_FAN_LOW, AIRTON_FAN_1},
|
||||
{climate::CLIMATE_FAN_MEDIUM, AIRTON_FAN_3}, {climate::CLIMATE_FAN_HIGH, AIRTON_FAN_5},
|
||||
{climate::CLIMATE_FAN_LOW, AIRTON_FAN_2}, {climate::CLIMATE_FAN_HIGH, AIRTON_FAN_4},
|
||||
};
|
||||
|
||||
// From IRutils.h of IRremoteESP8266 library
|
||||
static uint8_t checksum(const uint8_t *frame) {
|
||||
return static_cast<uint8_t>(0x7F - sum_bytes(frame, AIRTON_STATE_FRAME_SIZE - 1)) ^ 0x2C;
|
||||
@@ -114,7 +135,9 @@ void AirtonClimate::transmit_state() {
|
||||
uint8_t remote_state[AIRTON_STATE_FRAME_SIZE] = {
|
||||
AIRTON_HEADER_BYTE0,
|
||||
AIRTON_HEADER_BYTE1,
|
||||
static_cast<uint8_t>(this->operation_mode_() | (this->fan_speed_() << 4)),
|
||||
static_cast<uint8_t>(
|
||||
this->operation_mode_() |
|
||||
(to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), AIRTON_FAN_AUTO) << 4)),
|
||||
this->temperature_(),
|
||||
static_cast<uint8_t>(this->settings_.vertical_direction),
|
||||
this->operation_settings_(),
|
||||
@@ -139,48 +162,9 @@ void AirtonClimate::transmit_state() {
|
||||
}
|
||||
|
||||
uint8_t AirtonClimate::operation_mode_() {
|
||||
uint8_t operating_mode = 0b1000; // First bit is for power state
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
operating_mode |= AIRTON_MODE_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
operating_mode |= AIRTON_MODE_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
operating_mode |= AIRTON_MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
operating_mode |= AIRTON_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
operating_mode |= AIRTON_MODE_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
operating_mode = 0b0111 & this->previous_mode_; // Set previous mode with power state bit off
|
||||
}
|
||||
this->previous_mode_ = operating_mode;
|
||||
return operating_mode;
|
||||
}
|
||||
|
||||
uint8_t AirtonClimate::fan_speed_() {
|
||||
uint8_t fan_speed;
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = AIRTON_FAN_1;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
fan_speed = AIRTON_FAN_3;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
fan_speed = AIRTON_FAN_5;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
fan_speed = AIRTON_FAN_AUTO;
|
||||
}
|
||||
return fan_speed;
|
||||
// Off, or a mode not listed, repeats the previous mode with the power bit clear
|
||||
this->previous_mode_ = to_ir_code(MODES, this->mode, this->previous_mode_ & 0b0111);
|
||||
return this->previous_mode_;
|
||||
}
|
||||
|
||||
uint8_t AirtonClimate::temperature_() {
|
||||
@@ -217,47 +201,13 @@ void AirtonClimate::parse_state_frame_(const uint8_t *frame) {
|
||||
uint8_t mode = frame[2];
|
||||
// A later "off" repeats the mode the remote last sent, as the remote itself does
|
||||
this->previous_mode_ = mode & 0b00001111;
|
||||
if (mode & 0b00001000) { // The power bit
|
||||
switch (mode & 0b00000111) {
|
||||
case AIRTON_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case AIRTON_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case AIRTON_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case AIRTON_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case AIRTON_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!(mode & AIRTON_POWER_ON)) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
this->mode = from_ir_code(MODES, mode & 0b00001111, this->mode);
|
||||
}
|
||||
uint8_t fan_mode = (frame[2] & 0b01110000) >> 4;
|
||||
|
||||
switch (fan_mode) {
|
||||
case AIRTON_FAN_1:
|
||||
case AIRTON_FAN_2:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case AIRTON_FAN_3:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case AIRTON_FAN_4:
|
||||
case AIRTON_FAN_5:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case AIRTON_FAN_AUTO:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (const auto entry = find_ir_code(FANS, (frame[2] & 0b01110000) >> 4, true))
|
||||
this->fan_mode = static_cast<climate::ClimateFanMode>(entry->value);
|
||||
|
||||
this->target_temperature = (frame[3] & 0b00001111) + AIRTON_TEMP_MIN;
|
||||
|
||||
|
||||
@@ -117,7 +117,6 @@ class AirtonClimate : public climate_ir::ClimateIR {
|
||||
|
||||
void transmit_state() override;
|
||||
uint8_t operation_mode_();
|
||||
uint8_t fan_speed_();
|
||||
uint8_t temperature_();
|
||||
uint8_t operation_settings_();
|
||||
|
||||
|
||||
@@ -31,6 +31,23 @@ const uint8_t BALLU_SWING_VER = 0x07;
|
||||
const uint8_t BALLU_SWING_HOR = 0xe0;
|
||||
const uint8_t BALLU_POWER = 0x20;
|
||||
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// A fan mode not listed sends no fan bits and a received mode not listed is ignored
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_AUTO, BALLU_AUTO}, {climate::CLIMATE_MODE_HEAT, BALLU_HEAT},
|
||||
{climate::CLIMATE_MODE_COOL, BALLU_COOL}, {climate::CLIMATE_MODE_DRY, BALLU_DRY},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, BALLU_FAN},
|
||||
};
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, BALLU_FAN_AUTO},
|
||||
{climate::CLIMATE_FAN_HIGH, BALLU_FAN_HIGH},
|
||||
{climate::CLIMATE_FAN_MEDIUM, BALLU_FAN_MED},
|
||||
{climate::CLIMATE_FAN_LOW, BALLU_FAN_LOW},
|
||||
};
|
||||
|
||||
void BalluClimate::transmit_state() {
|
||||
uint8_t remote_state[BALLU_STATE_LENGTH] = {0};
|
||||
|
||||
@@ -46,46 +63,8 @@ void BalluClimate::transmit_state() {
|
||||
remote_state[9] = (this->mode == climate::CLIMATE_MODE_OFF) ? 0 : BALLU_POWER;
|
||||
remote_state[11] = 0x1e;
|
||||
|
||||
// Fan speed
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
remote_state[4] |= BALLU_FAN_HIGH;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
remote_state[4] |= BALLU_FAN_MED;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
remote_state[4] |= BALLU_FAN_LOW;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
remote_state[4] |= BALLU_FAN_AUTO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Mode
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
remote_state[6] |= BALLU_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state[6] |= BALLU_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state[6] |= BALLU_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
remote_state[6] |= BALLU_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
remote_state[6] |= BALLU_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
remote_state[6] |= BALLU_AUTO;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
remote_state[4] = to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_ON), 0);
|
||||
remote_state[6] = to_ir_code(MODES, this->mode, BALLU_AUTO);
|
||||
|
||||
// Checksum
|
||||
for (uint8_t i = 0; i < BALLU_STATE_LENGTH - 1; i++)
|
||||
@@ -149,25 +128,7 @@ bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
} else {
|
||||
auto mode = remote_state[6] & 0xe0;
|
||||
ESP_LOGV(TAG, "Mode: %02X", mode);
|
||||
switch (mode) {
|
||||
case BALLU_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case BALLU_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case BALLU_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case BALLU_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
case BALLU_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->mode = from_ir_code(MODES, mode, this->mode);
|
||||
}
|
||||
|
||||
// Set received temp
|
||||
@@ -180,21 +141,7 @@ bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
// Set received fan speed
|
||||
auto fan = remote_state[4] & 0xe0;
|
||||
ESP_LOGVV(TAG, "Fan: %02X", fan);
|
||||
switch (fan) {
|
||||
case BALLU_FAN_HIGH:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case BALLU_FAN_MED:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case BALLU_FAN_LOW:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case BALLU_FAN_AUTO:
|
||||
default:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
this->fan_mode = from_ir_code(FANS, fan, climate::CLIMATE_FAN_AUTO);
|
||||
|
||||
// Set received swing status
|
||||
ESP_LOGVV(TAG, "Swing status: %02X %02X", remote_state[1] & BALLU_SWING_VER, remote_state[2] & BALLU_SWING_HOR);
|
||||
|
||||
@@ -95,4 +95,19 @@ void ClimateIR::dump_config() {
|
||||
YESNO(this->modes_.count(climate::CLIMATE_MODE_HEAT_COOL)));
|
||||
}
|
||||
|
||||
int find_ir_code_index(const IRCode *table, size_t count, uint8_t key, bool match_code) {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (progmem_read_byte(match_code ? &table[i].code : &table[i].value) == key)
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t lookup_ir_code(const IRCode *table, size_t count, uint8_t key, bool match_code, uint8_t fallback) {
|
||||
const int index = find_ir_code_index(table, count, key, match_code);
|
||||
if (index < 0)
|
||||
return fallback;
|
||||
return progmem_read_byte(match_code ? &table[index].value : &table[index].code);
|
||||
}
|
||||
|
||||
} // namespace esphome::climate_ir
|
||||
@@ -6,9 +6,42 @@
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
#include "esphome/components/remote_transmitter/remote_transmitter.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
namespace esphome::climate_ir {
|
||||
|
||||
/// One entry of a PROGMEM table that maps a climate enum value to the unit's code.
|
||||
struct IRCode {
|
||||
uint8_t value; // a climate enum; they are all uint8_t
|
||||
uint8_t code;
|
||||
};
|
||||
|
||||
/// Index of the entry whose value (or, with match_code, code) is key, or -1. Reads the table from PROGMEM.
|
||||
int find_ir_code_index(const IRCode *table, size_t count, uint8_t key, bool match_code);
|
||||
|
||||
/// The entry whose value (or, with match_code, code) is key, read from PROGMEM.
|
||||
template<size_t N>
|
||||
ESPHOME_ALWAYS_INLINE optional<IRCode> find_ir_code(const IRCode (&table)[N], uint8_t key, bool match_code) {
|
||||
const int index = find_ir_code_index(table, N, key, match_code);
|
||||
if (index < 0)
|
||||
return {};
|
||||
return IRCode{progmem_read_byte(&table[index].value), progmem_read_byte(&table[index].code)};
|
||||
}
|
||||
|
||||
/// The code for value (or, with match_code, the value for code), or fallback when the table has no such entry.
|
||||
uint8_t lookup_ir_code(const IRCode *table, size_t count, uint8_t key, bool match_code, uint8_t fallback);
|
||||
|
||||
/// The code for value, or fallback when the table has no such value.
|
||||
template<size_t N> uint8_t to_ir_code(const IRCode (&table)[N], uint8_t value, uint8_t fallback) {
|
||||
return lookup_ir_code(table, N, value, false, fallback);
|
||||
}
|
||||
/// The value for code, or fallback when the table has no such code; pass the current state to keep it.
|
||||
template<typename T, size_t N> T from_ir_code(const IRCode (&table)[N], uint8_t code, T fallback) {
|
||||
static_assert(sizeof(T) == sizeof(uint8_t), "IRCode values are one byte");
|
||||
return static_cast<T>(lookup_ir_code(table, N, code, true, static_cast<uint8_t>(fallback)));
|
||||
}
|
||||
|
||||
/* A base for climate which works by sending (and receiving) IR codes
|
||||
|
||||
To send IR codes implement
|
||||
|
||||
@@ -112,6 +112,27 @@ const uint32_t TEMP_SHIFT = 8;
|
||||
|
||||
const uint16_t BITS = 28;
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// Mode commands sit in bits 12-15; the command that also powers the unit on clears the top one
|
||||
static constexpr uint8_t MODE_SHIFT = 12;
|
||||
static constexpr uint8_t MODE_NOT_POWER_ON = COMMAND_COOL >> MODE_SHIFT;
|
||||
// A mode not listed is sent as off
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_COOL, COMMAND_COOL >> MODE_SHIFT},
|
||||
{climate::CLIMATE_MODE_DRY, COMMAND_DRY >> MODE_SHIFT},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, COMMAND_FAN_ONLY >> MODE_SHIFT},
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, COMMAND_AI >> MODE_SHIFT},
|
||||
{climate::CLIMATE_MODE_HEAT, COMMAND_HEAT >> MODE_SHIFT},
|
||||
};
|
||||
// F2 and F4 are only received
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, FAN_AUTO}, {climate::CLIMATE_FAN_HIGH, FAN_MAX}, {climate::CLIMATE_FAN_MEDIUM, FAN_MED},
|
||||
{climate::CLIMATE_FAN_LOW, FAN_MIN}, {climate::CLIMATE_FAN_LOW, FAN_F2}, {climate::CLIMATE_FAN_MEDIUM, FAN_F4},
|
||||
};
|
||||
|
||||
void LgIrClimate::transmit_state() {
|
||||
uint32_t remote_state = LG_HEADER;
|
||||
|
||||
@@ -144,27 +165,13 @@ void LgIrClimate::transmit_state() {
|
||||
this->transmit_(remote_state);
|
||||
return;
|
||||
} else { // Mode commands
|
||||
const bool climate_is_off = (this->mode_before_ == climate::CLIMATE_MODE_OFF);
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state |= climate_is_off ? COMMAND_ON_COOL : COMMAND_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
remote_state |= climate_is_off ? COMMAND_ON_DRY : COMMAND_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
remote_state |= climate_is_off ? COMMAND_ON_FAN_ONLY : COMMAND_FAN_ONLY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state |= climate_is_off ? COMMAND_ON_AI : COMMAND_AI;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state |= climate_is_off ? COMMAND_ON_HEAT : COMMAND_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
remote_state |= CommandSys::HEADER_SYS;
|
||||
remote_state |= CommandSys::COMMAND_OFF;
|
||||
if (const auto entry = find_ir_code(MODES, this->mode, false)) {
|
||||
const bool climate_is_off = (this->mode_before_ == climate::CLIMATE_MODE_OFF);
|
||||
remote_state |= static_cast<uint32_t>(climate_is_off ? entry->code & ~MODE_NOT_POWER_ON : entry->code)
|
||||
<< MODE_SHIFT;
|
||||
} else {
|
||||
remote_state |= CommandSys::HEADER_SYS;
|
||||
remote_state |= CommandSys::COMMAND_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,21 +182,7 @@ void LgIrClimate::transmit_state() {
|
||||
// Set fan speed
|
||||
if (this->mode !=
|
||||
climate::CLIMATE_MODE_OFF) { // https://github.com/esphome/esphome/pull/10875#issuecomment-5042765948
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
remote_state |= FAN_MAX;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
remote_state |= FAN_MED;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
remote_state |= FAN_MIN;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
remote_state |= FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
remote_state |= to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), FAN_AUTO);
|
||||
}
|
||||
|
||||
uint8_t temp;
|
||||
@@ -294,52 +287,25 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
}
|
||||
this->publish_state();
|
||||
return true;
|
||||
// Following commands also contain fan speed and temperature, so no 'return' in these cases
|
||||
case COMMAND_DRY:
|
||||
case COMMAND_ON_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
default: {
|
||||
// Mode commands also contain fan speed and temperature, so no 'return' here
|
||||
const uint8_t command = (remote_state & COMMAND_HEADER_MASK) >> MODE_SHIFT;
|
||||
const auto mode = find_ir_code(MODES, command | MODE_NOT_POWER_ON, true);
|
||||
if (!mode.has_value()) {
|
||||
ESP_LOGD(TAG, "Got unknown command! Ignoring!");
|
||||
return false;
|
||||
}
|
||||
this->mode = static_cast<climate::ClimateMode>(mode->value);
|
||||
break;
|
||||
case COMMAND_FAN_ONLY:
|
||||
case COMMAND_ON_FAN_ONLY:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
case COMMAND_AI:
|
||||
case COMMAND_ON_AI:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case COMMAND_HEAT:
|
||||
case COMMAND_ON_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case COMMAND_COOL:
|
||||
case COMMAND_ON_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGD(TAG, "Got unknown command! Ignoring!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode fan speed
|
||||
switch (remote_state & FAN_SPEED_MASK) {
|
||||
case FAN_AUTO:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
case FAN_MIN:
|
||||
case FAN_F2:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case FAN_MED:
|
||||
case FAN_F4:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case FAN_MAX:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGD(TAG, "Got unknown fan speed! Ignoring!");
|
||||
return false;
|
||||
const auto fan = find_ir_code(FANS, remote_state & FAN_SPEED_MASK, true);
|
||||
if (!fan.has_value()) {
|
||||
ESP_LOGD(TAG, "Got unknown fan speed! Ignoring!");
|
||||
return false;
|
||||
}
|
||||
this->fan_mode = static_cast<climate::ClimateFanMode>(fan->value);
|
||||
|
||||
// Keep previous behavior
|
||||
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL && !(this->advanced_commands_support_)) {
|
||||
|
||||
@@ -17,6 +17,27 @@ static constexpr uint8_t DAIKIN_FRAME_TYPE_INDEX = 4;
|
||||
static constexpr uint8_t DAIKIN_FRAME_HEADER_SIZE = DAIKIN_FRAME_TYPE_INDEX + 1;
|
||||
static constexpr uint8_t DAIKIN_STATE_FRAME_OFFSET = 2 * DAIKIN_COMFORT_FRAME_SIZE;
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// Codes include the ON bit so the OFF entry never matches a running unit; an unlisted received code is ignored
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_OFF, DAIKIN_MODE_OFF},
|
||||
{climate::CLIMATE_MODE_COOL, DAIKIN_MODE_ON | DAIKIN_MODE_COOL},
|
||||
{climate::CLIMATE_MODE_DRY, DAIKIN_MODE_ON | DAIKIN_MODE_DRY},
|
||||
{climate::CLIMATE_MODE_HEAT, DAIKIN_MODE_ON | DAIKIN_MODE_HEAT},
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, DAIKIN_MODE_ON | DAIKIN_MODE_AUTO},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, DAIKIN_MODE_ON | DAIKIN_MODE_FAN},
|
||||
};
|
||||
// Fan speeds 2 and 4 are only received
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, DAIKIN_FAN_AUTO}, {climate::CLIMATE_FAN_QUIET, DAIKIN_FAN_SILENT},
|
||||
{climate::CLIMATE_FAN_LOW, DAIKIN_FAN_1}, {climate::CLIMATE_FAN_MEDIUM, DAIKIN_FAN_3},
|
||||
{climate::CLIMATE_FAN_HIGH, DAIKIN_FAN_5}, {climate::CLIMATE_FAN_LOW, DAIKIN_FAN_2},
|
||||
{climate::CLIMATE_FAN_HIGH, DAIKIN_FAN_4},
|
||||
};
|
||||
|
||||
void DaikinClimate::setup() {
|
||||
climate_ir::ClimateIR::setup();
|
||||
// A boost preset restored from flash gets its timer too
|
||||
@@ -35,7 +56,7 @@ void DaikinClimate::transmit_state() {
|
||||
0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00};
|
||||
|
||||
this->set_presets_(remote_state);
|
||||
remote_state[21] = this->operation_mode_();
|
||||
remote_state[21] = to_ir_code(MODES, this->mode, DAIKIN_MODE_OFF);
|
||||
remote_state[22] = this->temperature_();
|
||||
put_be16(remote_state + 24, this->fan_speed_());
|
||||
|
||||
@@ -67,52 +88,8 @@ void DaikinClimate::transmit_state() {
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
uint8_t DaikinClimate::operation_mode_() const {
|
||||
uint8_t operating_mode = DAIKIN_MODE_ON;
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
operating_mode |= DAIKIN_MODE_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
operating_mode |= DAIKIN_MODE_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
operating_mode |= DAIKIN_MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
operating_mode |= DAIKIN_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
operating_mode |= DAIKIN_MODE_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
operating_mode = DAIKIN_MODE_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
return operating_mode;
|
||||
}
|
||||
|
||||
uint16_t DaikinClimate::fan_speed_() const {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
fan_speed = DAIKIN_FAN_SILENT << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DAIKIN_FAN_1 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
fan_speed = DAIKIN_FAN_3 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
fan_speed = DAIKIN_FAN_5 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
fan_speed = DAIKIN_FAN_AUTO << 8;
|
||||
}
|
||||
uint16_t fan_speed = to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), DAIKIN_FAN_AUTO) << 8;
|
||||
|
||||
// If swing is enabled switch first 4 bits to 1111
|
||||
switch (this->swing_mode) {
|
||||
@@ -189,29 +166,12 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
// Temperature is given in degrees celsius * 2
|
||||
// only update for states that use the temperature
|
||||
uint8_t temperature = frame[6];
|
||||
if (mode & DAIKIN_MODE_ON) {
|
||||
switch (mode & 0xF0) {
|
||||
case DAIKIN_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
this->target_temperature = static_cast<float>(temperature * 0.5f);
|
||||
break;
|
||||
case DAIKIN_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case DAIKIN_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
this->target_temperature = static_cast<float>(temperature * 0.5f);
|
||||
break;
|
||||
case DAIKIN_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
this->target_temperature = static_cast<float>(temperature * 0.5f);
|
||||
break;
|
||||
case DAIKIN_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!(mode & DAIKIN_MODE_ON)) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else if (const auto entry = find_ir_code(MODES, mode & (0xF0 | DAIKIN_MODE_ON), true)) {
|
||||
this->mode = static_cast<climate::ClimateMode>(entry->value);
|
||||
if (this->mode != climate::CLIMATE_MODE_DRY && this->mode != climate::CLIMATE_MODE_FAN_ONLY)
|
||||
this->target_temperature = static_cast<float>(temperature * 0.5f);
|
||||
}
|
||||
uint8_t fan_mode = frame[8];
|
||||
uint8_t swing_mode = frame[9];
|
||||
@@ -224,25 +184,8 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
} else {
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
}
|
||||
switch (fan_mode & 0xF0) {
|
||||
case DAIKIN_FAN_1:
|
||||
case DAIKIN_FAN_2:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case DAIKIN_FAN_3:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case DAIKIN_FAN_4:
|
||||
case DAIKIN_FAN_5:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case DAIKIN_FAN_AUTO:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
case DAIKIN_FAN_SILENT:
|
||||
this->fan_mode = climate::CLIMATE_FAN_QUIET;
|
||||
break;
|
||||
}
|
||||
if (const auto entry = find_ir_code(FANS, fan_mode & 0xF0, true))
|
||||
this->fan_mode = static_cast<climate::ClimateFanMode>(entry->value);
|
||||
|
||||
// Comfort lives in the first frame, recorded as pending until this frame proves the transmission valid
|
||||
// A comfort frame whose own state frame never came must not attach to a later transmission
|
||||
|
||||
@@ -72,7 +72,6 @@ class DaikinClimate final : public climate_ir::ClimateIR {
|
||||
void control(const climate::ClimateCall &call) override;
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void transmit_state() override;
|
||||
uint8_t operation_mode_() const;
|
||||
uint16_t fan_speed_() const;
|
||||
uint8_t temperature_() const;
|
||||
void set_presets_(uint8_t *remote_state) const;
|
||||
|
||||
@@ -10,6 +10,28 @@ namespace esphome::daikin_arc {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "daikin.climate");
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// Codes include the ON bit so the OFF entry never matches a running unit; an unlisted received code is ignored
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_OFF, DAIKIN_MODE_OFF},
|
||||
{climate::CLIMATE_MODE_COOL, DAIKIN_MODE_ON | DAIKIN_MODE_COOL},
|
||||
{climate::CLIMATE_MODE_DRY, DAIKIN_MODE_ON | DAIKIN_MODE_DRY},
|
||||
{climate::CLIMATE_MODE_HEAT, DAIKIN_MODE_ON | DAIKIN_MODE_HEAT},
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, DAIKIN_MODE_ON | DAIKIN_MODE_AUTO},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, DAIKIN_MODE_ON | DAIKIN_MODE_FAN},
|
||||
};
|
||||
// Fan speeds 2 and 4 and silent are only received
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, DAIKIN_FAN_AUTO}, {climate::CLIMATE_FAN_LOW, DAIKIN_FAN_1},
|
||||
{climate::CLIMATE_FAN_MEDIUM, DAIKIN_FAN_3}, {climate::CLIMATE_FAN_HIGH, DAIKIN_FAN_5},
|
||||
{climate::CLIMATE_FAN_LOW, DAIKIN_FAN_2}, {climate::CLIMATE_FAN_LOW, DAIKIN_FAN_SILENT},
|
||||
{climate::CLIMATE_FAN_HIGH, DAIKIN_FAN_4},
|
||||
};
|
||||
|
||||
void DaikinArcClimate::setup() {
|
||||
climate_ir::ClimateIR::setup();
|
||||
|
||||
@@ -82,7 +104,7 @@ void DaikinArcClimate::transmit_state() {
|
||||
/* OF 0x80 */
|
||||
};
|
||||
|
||||
remote_state[5] = this->operation_mode_() | 0x08;
|
||||
remote_state[5] = to_ir_code(MODES, this->mode, DAIKIN_MODE_OFF) | 0x08;
|
||||
remote_state[6] = this->temperature_();
|
||||
remote_state[7] = this->humidity_();
|
||||
if (remote_state[7] != this->last_humidity_ && this->mode != climate::CLIMATE_MODE_OFF) {
|
||||
@@ -126,49 +148,8 @@ void DaikinArcClimate::transmit_state() {
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
uint8_t DaikinArcClimate::operation_mode_() {
|
||||
uint8_t operating_mode = DAIKIN_MODE_ON;
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
operating_mode |= DAIKIN_MODE_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
operating_mode |= DAIKIN_MODE_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
operating_mode |= DAIKIN_MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
operating_mode |= DAIKIN_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
operating_mode |= DAIKIN_MODE_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
operating_mode = DAIKIN_MODE_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
return operating_mode;
|
||||
}
|
||||
|
||||
uint16_t DaikinArcClimate::fan_speed_() {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DAIKIN_FAN_1 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
fan_speed = DAIKIN_FAN_3 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
fan_speed = DAIKIN_FAN_5 << 8;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
fan_speed = DAIKIN_FAN_AUTO << 8;
|
||||
}
|
||||
uint16_t fan_speed = to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), DAIKIN_FAN_AUTO) << 8;
|
||||
|
||||
// If swing is enabled switch first 4 bits to 1111
|
||||
switch (this->swing_mode) {
|
||||
@@ -241,26 +222,10 @@ bool DaikinArcClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
ESP_LOGD(TAG, "FRAME %s", buf);
|
||||
|
||||
uint8_t mode = frame[5];
|
||||
if (mode & DAIKIN_MODE_ON) {
|
||||
switch (mode & 0xF0) {
|
||||
case DAIKIN_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case DAIKIN_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case DAIKIN_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case DAIKIN_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case DAIKIN_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!(mode & DAIKIN_MODE_ON)) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
this->mode = from_ir_code(MODES, mode & (0xF0 | DAIKIN_MODE_ON), this->mode);
|
||||
}
|
||||
uint8_t temperature = frame[6];
|
||||
if (!(temperature & 0xC0)) {
|
||||
@@ -279,23 +244,8 @@ bool DaikinArcClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
} else {
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
}
|
||||
switch (fan_mode & 0xF0) {
|
||||
case DAIKIN_FAN_1:
|
||||
case DAIKIN_FAN_2:
|
||||
case DAIKIN_FAN_SILENT:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case DAIKIN_FAN_3:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case DAIKIN_FAN_4:
|
||||
case DAIKIN_FAN_5:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case DAIKIN_FAN_AUTO:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
if (const auto entry = find_ir_code(FANS, fan_mode & 0xF0, true))
|
||||
this->fan_mode = static_cast<climate::ClimateFanMode>(entry->value);
|
||||
/*
|
||||
05 0 [1:3]MODE 1 [OFF TMR] [ON TMR] Power
|
||||
06-07 TEMP
|
||||
|
||||
@@ -64,7 +64,6 @@ class DaikinArcClimate final : public climate_ir::ClimateIR {
|
||||
void transmit_query_();
|
||||
void transmit_state() override;
|
||||
climate::ClimateTraits traits() override;
|
||||
uint8_t operation_mode_();
|
||||
uint16_t fan_speed_();
|
||||
uint8_t temperature_();
|
||||
uint8_t humidity_();
|
||||
|
||||
@@ -12,6 +12,26 @@ static constexpr uint8_t DAIKIN_BRC_HEADER_BYTE2 = 0x17;
|
||||
static constexpr uint8_t DAIKIN_BRC_HEADER_BYTE3 = 0x18;
|
||||
static constexpr uint8_t DAIKIN_BRC_FRAME_TYPE_STATE = 0x00;
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// Codes include the ON bit so the OFF entry never matches a running unit; an unlisted received code is ignored
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_OFF, DAIKIN_BRC_MODE_OFF},
|
||||
{climate::CLIMATE_MODE_COOL, DAIKIN_BRC_MODE_ON | DAIKIN_BRC_MODE_COOL},
|
||||
{climate::CLIMATE_MODE_DRY, DAIKIN_BRC_MODE_ON | DAIKIN_BRC_MODE_DRY},
|
||||
{climate::CLIMATE_MODE_HEAT, DAIKIN_BRC_MODE_ON | DAIKIN_BRC_MODE_HEAT},
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, DAIKIN_BRC_MODE_ON | DAIKIN_BRC_MODE_AUTO},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, DAIKIN_BRC_MODE_ON | DAIKIN_BRC_MODE_FAN},
|
||||
};
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_LOW, DAIKIN_BRC_FAN_1},
|
||||
{climate::CLIMATE_FAN_MEDIUM, DAIKIN_BRC_FAN_2},
|
||||
{climate::CLIMATE_FAN_HIGH, DAIKIN_BRC_FAN_3},
|
||||
};
|
||||
|
||||
void DaikinBrcClimate::control(const climate::ClimateCall &call) {
|
||||
this->mode_button_ = 0x00;
|
||||
if (call.get_mode().has_value()) {
|
||||
@@ -33,7 +53,7 @@ void DaikinBrcClimate::transmit_state() {
|
||||
|
||||
remote_state[12] = this->alt_mode_();
|
||||
remote_state[13] = this->mode_button_;
|
||||
remote_state[14] = this->operation_mode_();
|
||||
remote_state[14] = to_ir_code(MODES, this->mode, DAIKIN_BRC_MODE_OFF);
|
||||
remote_state[17] = this->temperature_();
|
||||
remote_state[18] = this->fan_speed_swing_();
|
||||
|
||||
@@ -82,48 +102,8 @@ uint8_t DaikinBrcClimate::alt_mode_() {
|
||||
return alt_mode;
|
||||
}
|
||||
|
||||
uint8_t DaikinBrcClimate::operation_mode_() {
|
||||
uint8_t operating_mode = DAIKIN_BRC_MODE_ON;
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
operating_mode |= DAIKIN_BRC_MODE_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
operating_mode |= DAIKIN_BRC_MODE_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
operating_mode |= DAIKIN_BRC_MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
operating_mode |= DAIKIN_BRC_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
operating_mode |= DAIKIN_BRC_MODE_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
operating_mode = DAIKIN_BRC_MODE_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
return operating_mode;
|
||||
}
|
||||
|
||||
uint8_t DaikinBrcClimate::fan_speed_swing_() {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DAIKIN_BRC_FAN_1;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
fan_speed = DAIKIN_BRC_FAN_2;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
fan_speed = DAIKIN_BRC_FAN_3;
|
||||
break;
|
||||
default:
|
||||
fan_speed = DAIKIN_BRC_FAN_1;
|
||||
}
|
||||
uint16_t fan_speed = to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_LOW), DAIKIN_BRC_FAN_1);
|
||||
|
||||
// If swing is enabled switch first 4 bits to 1111
|
||||
switch (this->swing_mode) {
|
||||
@@ -168,26 +148,10 @@ bool DaikinBrcClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
}
|
||||
|
||||
uint8_t mode = frame[7];
|
||||
if (mode & DAIKIN_BRC_MODE_ON) {
|
||||
switch (mode & 0xF0) {
|
||||
case DAIKIN_BRC_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case DAIKIN_BRC_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case DAIKIN_BRC_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case DAIKIN_BRC_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case DAIKIN_BRC_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!(mode & DAIKIN_BRC_MODE_ON)) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
this->mode = from_ir_code(MODES, mode & (0xF0 | DAIKIN_BRC_MODE_ON), this->mode);
|
||||
}
|
||||
|
||||
uint8_t temperature = frame[10];
|
||||
@@ -211,17 +175,8 @@ bool DaikinBrcClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (fan_mode & 0xF0) {
|
||||
case DAIKIN_BRC_FAN_1:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case DAIKIN_BRC_FAN_2:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case DAIKIN_BRC_FAN_3:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
}
|
||||
if (const auto entry = find_ir_code(FANS, fan_mode & 0xF0, true))
|
||||
this->fan_mode = static_cast<climate::ClimateFanMode>(entry->value);
|
||||
this->publish_state();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ class DaikinBrcClimate final : public climate_ir::ClimateIR {
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void transmit_state() override;
|
||||
uint8_t alt_mode_();
|
||||
uint8_t operation_mode_();
|
||||
uint8_t fan_speed_swing_();
|
||||
uint8_t temperature_();
|
||||
// Handle received IR Buffer
|
||||
|
||||
@@ -6,12 +6,33 @@ namespace esphome::delonghi {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "delonghi.climate");
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// A received code that is not listed is ignored
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_OFF, DELONGHI_MODE_OFF},
|
||||
{climate::CLIMATE_MODE_COOL, DELONGHI_MODE_ON | DELONGHI_MODE_COOL},
|
||||
{climate::CLIMATE_MODE_DRY, DELONGHI_MODE_ON | DELONGHI_MODE_DRY},
|
||||
{climate::CLIMATE_MODE_HEAT, DELONGHI_MODE_ON | DELONGHI_MODE_HEAT},
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, DELONGHI_MODE_ON | DELONGHI_MODE_AUTO},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, DELONGHI_MODE_ON | DELONGHI_MODE_FAN},
|
||||
};
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, DELONGHI_FAN_AUTO},
|
||||
{climate::CLIMATE_FAN_LOW, DELONGHI_FAN_LOW},
|
||||
{climate::CLIMATE_FAN_MEDIUM, DELONGHI_FAN_MEDIUM},
|
||||
{climate::CLIMATE_FAN_HIGH, DELONGHI_FAN_HIGH},
|
||||
};
|
||||
|
||||
void DelonghiClimate::transmit_state() {
|
||||
uint8_t remote_state[DELONGHI_STATE_FRAME_SIZE] = {0};
|
||||
remote_state[0] = DELONGHI_ADDRESS;
|
||||
remote_state[1] = this->temperature_();
|
||||
remote_state[1] |= (this->fan_speed_()) << 5;
|
||||
remote_state[2] = this->operation_mode_();
|
||||
remote_state[1] |= to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), DELONGHI_FAN_AUTO) << 5;
|
||||
remote_state[2] = to_ir_code(MODES, this->mode, DELONGHI_MODE_OFF);
|
||||
// Calculate checksum
|
||||
for (int i = 0; i < DELONGHI_STATE_FRAME_SIZE - 1; i++) {
|
||||
remote_state[DELONGHI_STATE_FRAME_SIZE - 1] += remote_state[i];
|
||||
@@ -30,51 +51,6 @@ void DelonghiClimate::transmit_state() {
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
uint8_t DelonghiClimate::operation_mode_() {
|
||||
uint8_t operating_mode = DELONGHI_MODE_ON;
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
operating_mode |= DELONGHI_MODE_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
operating_mode |= DELONGHI_MODE_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
operating_mode |= DELONGHI_MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
operating_mode |= DELONGHI_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
operating_mode |= DELONGHI_MODE_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
operating_mode = DELONGHI_MODE_OFF;
|
||||
break;
|
||||
}
|
||||
return operating_mode;
|
||||
}
|
||||
|
||||
uint16_t DelonghiClimate::fan_speed_() {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DELONGHI_FAN_LOW;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
fan_speed = DELONGHI_FAN_MEDIUM;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
fan_speed = DELONGHI_FAN_HIGH;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
fan_speed = DELONGHI_FAN_AUTO;
|
||||
}
|
||||
return fan_speed;
|
||||
}
|
||||
|
||||
uint8_t DelonghiClimate::temperature_() {
|
||||
// Force special temperatures depending on the mode
|
||||
uint8_t temperature = 0b0001;
|
||||
@@ -102,26 +78,10 @@ bool DelonghiClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
return false;
|
||||
}
|
||||
uint8_t mode = frame[2] & 0x0F;
|
||||
if (mode & DELONGHI_MODE_ON) {
|
||||
switch (mode & 0x0E) {
|
||||
case DELONGHI_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case DELONGHI_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case DELONGHI_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case DELONGHI_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case DELONGHI_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!(mode & DELONGHI_MODE_ON)) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
this->mode = from_ir_code(MODES, mode, this->mode);
|
||||
}
|
||||
uint8_t temperature = frame[1] & 0x0F;
|
||||
if (this->mode == climate::CLIMATE_MODE_HEAT) {
|
||||
@@ -129,23 +89,8 @@ bool DelonghiClimate::parse_state_frame_(const uint8_t frame[]) {
|
||||
} else {
|
||||
this->target_temperature = temperature + DELONGHI_TEMP_OFFSET_COOL;
|
||||
}
|
||||
uint8_t fan_mode = frame[1] >> 5;
|
||||
switch (fan_mode) {
|
||||
case DELONGHI_FAN_LOW:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case DELONGHI_FAN_MEDIUM:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case DELONGHI_FAN_HIGH:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case DELONGHI_FAN_AUTO:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (const auto entry = find_ir_code(FANS, frame[1] >> 5, true))
|
||||
this->fan_mode = static_cast<climate::ClimateFanMode>(entry->value);
|
||||
this->publish_state();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,8 +51,6 @@ class DelonghiClimate final : public climate_ir::ClimateIR {
|
||||
protected:
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void transmit_state() override;
|
||||
uint8_t operation_mode_();
|
||||
uint16_t fan_speed_();
|
||||
uint8_t temperature_();
|
||||
// Handle received IR Buffer
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
|
||||
@@ -50,34 +50,25 @@ static constexpr uint8_t STATE_TEMPLATE[STATE_MESSAGE_LENGTH] PROGMEM = {
|
||||
static constexpr uint8_t OFF_FRAME[STATE_MESSAGE_LENGTH_UTIL] PROGMEM = {0x00, 0x08, 0x08, BYTE3_POWER_OFF,
|
||||
255 - BYTE3_POWER_OFF};
|
||||
|
||||
struct Code {
|
||||
uint8_t value; // a climate enum, all of which are uint8_t
|
||||
uint8_t code;
|
||||
};
|
||||
static constexpr Code MODES[] PROGMEM = {
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// A received mode that is not listed is ignored
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, BYTE7_MODE_AUTO},
|
||||
{climate::CLIMATE_MODE_COOL, 0x80},
|
||||
{climate::CLIMATE_MODE_DRY, 0x40},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, 0xC0},
|
||||
{climate::CLIMATE_MODE_HEAT, 0x20},
|
||||
};
|
||||
static constexpr Code FANS[] PROGMEM = {
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, BYTE8_FAN_AUTO}, {climate::CLIMATE_FAN_HIGH, 0x80},
|
||||
{climate::CLIMATE_FAN_MEDIUM, 0x40}, {climate::CLIMATE_FAN_LOW, 0xC0},
|
||||
{climate::CLIMATE_FAN_QUIET, 0x20},
|
||||
};
|
||||
|
||||
// The entry whose value (or code) matches key. Entries are copied out of PROGMEM.
|
||||
template<size_t N> static optional<Code> find_entry(const Code (&table)[N], uint8_t key, bool match_code) {
|
||||
for (const Code &slot : table) {
|
||||
Code entry;
|
||||
progmem_memcpy(&entry, &slot, sizeof(entry));
|
||||
if ((match_code ? entry.code : entry.value) == key)
|
||||
return entry;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// The temperature code is the 4-bit reversal of (F - TEMP_MIN) / TEMP_STEP; the mapping is its own inverse.
|
||||
static uint8_t reverse_nibble(uint8_t value) { return reverse_bits(value) >> 4; }
|
||||
|
||||
@@ -151,14 +142,11 @@ void FriedrichClimate::transmit_state() {
|
||||
temperature += temperature % 2;
|
||||
this->target_temperature = temperature;
|
||||
|
||||
const auto mode = find_entry(MODES, this->mode, false);
|
||||
const auto fan = find_entry(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), false);
|
||||
|
||||
uint8_t frame[STATE_MESSAGE_LENGTH];
|
||||
progmem_memcpy(frame, STATE_TEMPLATE, sizeof(frame));
|
||||
frame[6] |= reverse_nibble((temperature - TEMP_MIN) / TEMP_STEP);
|
||||
frame[7] = mode.has_value() ? mode->code : BYTE7_MODE_AUTO;
|
||||
frame[8] = fan.has_value() ? fan->code : BYTE8_FAN_AUTO;
|
||||
frame[7] = to_ir_code(MODES, this->mode, BYTE7_MODE_AUTO);
|
||||
frame[8] = to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), BYTE8_FAN_AUTO);
|
||||
if (this->swing_mode != climate::CLIMATE_SWING_OFF)
|
||||
frame[8] |= BYTE8_FAN_SWING;
|
||||
frame[STATE_MESSAGE_LENGTH - 1] = checksum_state(frame);
|
||||
@@ -209,7 +197,7 @@ bool FriedrichClimate::on_receive(remote_base::RemoteReceiveData src) {
|
||||
return false;
|
||||
|
||||
// Ignore frames with a mode this component does not expose (e.g. MIN_HEAT, COIL_DRY)
|
||||
const auto mode = find_entry(MODES, d[7], true);
|
||||
const auto mode = find_ir_code(MODES, d[7], true);
|
||||
if (!mode.has_value()) {
|
||||
ESP_LOGV(TAG, "Ignoring unknown mode 0x%02X", d[7]);
|
||||
return false;
|
||||
@@ -224,8 +212,7 @@ bool FriedrichClimate::on_receive(remote_base::RemoteReceiveData src) {
|
||||
this->target_temperature = TEMP_MIN + temp_index * TEMP_STEP;
|
||||
|
||||
this->swing_mode = (d[8] & BYTE8_FAN_SWING) ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
||||
const auto fan = find_entry(FANS, d[8] & ~BYTE8_FAN_SWING, true);
|
||||
this->fan_mode = fan.has_value() ? static_cast<climate::ClimateFanMode>(fan->value) : climate::CLIMATE_FAN_AUTO;
|
||||
this->fan_mode = from_ir_code(FANS, d[8] & ~BYTE8_FAN_SWING, climate::CLIMATE_FAN_AUTO);
|
||||
|
||||
this->publish_state();
|
||||
return true;
|
||||
|
||||
@@ -7,6 +7,26 @@ namespace esphome::hitachi_ac344 {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "climate.hitachi_ac344");
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// A received code that is not listed is ignored
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_COOL, HITACHI_AC344_MODE_COOL},
|
||||
{climate::CLIMATE_MODE_DRY, HITACHI_AC344_MODE_DRY},
|
||||
{climate::CLIMATE_MODE_HEAT, HITACHI_AC344_MODE_HEAT},
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, HITACHI_AC344_MODE_AUTO},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, HITACHI_AC344_MODE_FAN},
|
||||
};
|
||||
// The max speed is only received
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, HITACHI_AC344_FAN_AUTO}, {climate::CLIMATE_FAN_QUIET, HITACHI_AC344_FAN_MIN},
|
||||
{climate::CLIMATE_FAN_LOW, HITACHI_AC344_FAN_LOW}, {climate::CLIMATE_FAN_MEDIUM, HITACHI_AC344_FAN_MEDIUM},
|
||||
{climate::CLIMATE_FAN_HIGH, HITACHI_AC344_FAN_HIGH}, {climate::CLIMATE_FAN_HIGH, HITACHI_AC344_FAN_MAX},
|
||||
};
|
||||
|
||||
void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, const uint8_t data) {
|
||||
if (offset >= 8 || !nbits)
|
||||
return; // Short circuit as it won't change.
|
||||
@@ -166,49 +186,17 @@ void HitachiClimate::setup() {
|
||||
void HitachiClimate::transmit_state() { this->transmit_state_(HITACHI_AC344_BUTTON_POWER); }
|
||||
|
||||
void HitachiClimate::transmit_state_(uint8_t button) {
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
set_mode_(HITACHI_AC344_MODE_COOL);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
set_mode_(HITACHI_AC344_MODE_DRY);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
set_mode_(HITACHI_AC344_MODE_HEAT);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
set_mode_(HITACHI_AC344_MODE_AUTO);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
set_mode_(HITACHI_AC344_MODE_FAN);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
set_power_(false);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unsupported mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
||||
if (const auto entry = find_ir_code(MODES, this->mode, false)) {
|
||||
set_mode_(entry->code);
|
||||
} else if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||
set_power_(false);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unsupported mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
||||
}
|
||||
|
||||
set_temp_(static_cast<uint8_t>(this->target_temperature));
|
||||
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
set_fan_(HITACHI_AC344_FAN_MIN);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
set_fan_(HITACHI_AC344_FAN_LOW);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
set_fan_(HITACHI_AC344_FAN_MEDIUM);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
set_fan_(HITACHI_AC344_FAN_HIGH);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_ON:
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
set_fan_(HITACHI_AC344_FAN_AUTO);
|
||||
}
|
||||
set_fan_(to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), HITACHI_AC344_FAN_AUTO));
|
||||
|
||||
const bool swing_v = swing_vertical(this->swing_mode);
|
||||
const bool swing_h =
|
||||
@@ -254,28 +242,10 @@ bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) {
|
||||
ESP_LOGV(TAG, "Power: %02X %02X", remote_state[HITACHI_AC344_POWER_BYTE], power);
|
||||
uint8_t mode = remote_state[HITACHI_AC344_MODE_BYTE] & 0xF;
|
||||
ESP_LOGV(TAG, "Mode: %02X %02X", remote_state[HITACHI_AC344_MODE_BYTE], mode);
|
||||
if (power == HITACHI_AC344_POWER_ON) {
|
||||
switch (mode) {
|
||||
case HITACHI_AC344_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case HITACHI_AC344_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case HITACHI_AC344_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case HITACHI_AC344_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case HITACHI_AC344_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (power != HITACHI_AC344_POWER_ON) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
this->mode = from_ir_code(MODES, mode, this->mode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -292,26 +262,8 @@ bool HitachiClimate::parse_temperature_(const uint8_t remote_state[]) {
|
||||
bool HitachiClimate::parse_fan_(const uint8_t remote_state[]) {
|
||||
uint8_t fan_mode = remote_state[HITACHI_AC344_FAN_BYTE] >> 4 & 0xF;
|
||||
ESP_LOGV(TAG, "Fan: %02X %02X", remote_state[HITACHI_AC344_FAN_BYTE], fan_mode);
|
||||
switch (fan_mode) {
|
||||
case HITACHI_AC344_FAN_MIN:
|
||||
this->fan_mode = climate::CLIMATE_FAN_QUIET;
|
||||
break;
|
||||
case HITACHI_AC344_FAN_LOW:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case HITACHI_AC344_FAN_MEDIUM:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case HITACHI_AC344_FAN_HIGH:
|
||||
case HITACHI_AC344_FAN_MAX:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case HITACHI_AC344_FAN_AUTO:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (const auto entry = find_ir_code(FANS, fan_mode, true))
|
||||
this->fan_mode = static_cast<climate::ClimateFanMode>(entry->value);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,26 @@ namespace esphome::hitachi_ac424 {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "climate.hitachi_ac424");
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// A received code that is not listed is ignored
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_COOL, HITACHI_AC424_MODE_COOL},
|
||||
{climate::CLIMATE_MODE_DRY, HITACHI_AC424_MODE_DRY},
|
||||
{climate::CLIMATE_MODE_HEAT, HITACHI_AC424_MODE_HEAT},
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, HITACHI_AC424_MODE_AUTO},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, HITACHI_AC424_MODE_FAN},
|
||||
};
|
||||
// The min and max speeds are only received
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, HITACHI_AC424_FAN_AUTO}, {climate::CLIMATE_FAN_LOW, HITACHI_AC424_FAN_LOW},
|
||||
{climate::CLIMATE_FAN_MEDIUM, HITACHI_AC424_FAN_MEDIUM}, {climate::CLIMATE_FAN_HIGH, HITACHI_AC424_FAN_HIGH},
|
||||
{climate::CLIMATE_FAN_HIGH, HITACHI_AC424_FAN_MAX}, {climate::CLIMATE_FAN_LOW, HITACHI_AC424_FAN_MIN},
|
||||
};
|
||||
|
||||
void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, const uint8_t data) {
|
||||
if (offset >= 8 || !nbits)
|
||||
return; // Short circuit as it won't change.
|
||||
@@ -142,46 +162,17 @@ uint8_t HitachiClimate::get_button_() { return remote_state_[HITACHI_AC424_BUTTO
|
||||
void HitachiClimate::set_button_(uint8_t button) { remote_state_[HITACHI_AC424_BUTTON_BYTE] = button; }
|
||||
|
||||
void HitachiClimate::transmit_state() {
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
set_mode_(HITACHI_AC424_MODE_COOL);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
set_mode_(HITACHI_AC424_MODE_DRY);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
set_mode_(HITACHI_AC424_MODE_HEAT);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
set_mode_(HITACHI_AC424_MODE_AUTO);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
set_mode_(HITACHI_AC424_MODE_FAN);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
set_power_(false);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unsupported mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
||||
if (const auto entry = find_ir_code(MODES, this->mode, false)) {
|
||||
set_mode_(entry->code);
|
||||
} else if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||
set_power_(false);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unsupported mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
||||
}
|
||||
|
||||
set_temp_(static_cast<uint8_t>(this->target_temperature));
|
||||
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
set_fan_(HITACHI_AC424_FAN_LOW);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
set_fan_(HITACHI_AC424_FAN_MEDIUM);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
set_fan_(HITACHI_AC424_FAN_HIGH);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_ON:
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
set_fan_(HITACHI_AC424_FAN_AUTO);
|
||||
}
|
||||
set_fan_(to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), HITACHI_AC424_FAN_AUTO));
|
||||
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_BOTH:
|
||||
@@ -230,28 +221,10 @@ bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) {
|
||||
ESP_LOGV(TAG, "Power: %02X %02X", remote_state[HITACHI_AC424_POWER_BYTE], power);
|
||||
uint8_t mode = remote_state[HITACHI_AC424_MODE_BYTE] & 0xF;
|
||||
ESP_LOGV(TAG, "Mode: %02X %02X", remote_state[HITACHI_AC424_MODE_BYTE], mode);
|
||||
if (power == HITACHI_AC424_POWER_ON) {
|
||||
switch (mode) {
|
||||
case HITACHI_AC424_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case HITACHI_AC424_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case HITACHI_AC424_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case HITACHI_AC424_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case HITACHI_AC424_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (power != HITACHI_AC424_POWER_ON) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
this->mode = from_ir_code(MODES, mode, this->mode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -268,24 +241,8 @@ bool HitachiClimate::parse_temperature_(const uint8_t remote_state[]) {
|
||||
bool HitachiClimate::parse_fan_(const uint8_t remote_state[]) {
|
||||
uint8_t fan_mode = remote_state[HITACHI_AC424_FAN_BYTE] >> 4 & 0xF;
|
||||
ESP_LOGV(TAG, "Fan: %02X %02X", remote_state[HITACHI_AC424_FAN_BYTE], fan_mode);
|
||||
switch (fan_mode) {
|
||||
case HITACHI_AC424_FAN_MIN:
|
||||
case HITACHI_AC424_FAN_LOW:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case HITACHI_AC424_FAN_MEDIUM:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case HITACHI_AC424_FAN_HIGH:
|
||||
case HITACHI_AC424_FAN_MAX:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case HITACHI_AC424_FAN_AUTO:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (const auto entry = find_ir_code(FANS, fan_mode, true))
|
||||
this->fan_mode = static_cast<climate::ClimateFanMode>(entry->value);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,19 +21,16 @@ constexpr uint8_t MISTRAL_POWER_ON = 0x20;
|
||||
constexpr uint8_t MISTRAL_TEMP_OFFSET = 0x9F;
|
||||
constexpr uint8_t MISTRAL_MODE_AUTO = 0x10;
|
||||
|
||||
// The tables live in flash, so entries are copied out before use
|
||||
struct ModeCode {
|
||||
climate::ClimateMode mode;
|
||||
uint8_t code;
|
||||
};
|
||||
static const ModeCode MODES[] PROGMEM = {
|
||||
// OFF sends the auto mode code and only clears the power byte
|
||||
static constexpr climate_ir::IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, MISTRAL_MODE_AUTO},
|
||||
{climate::CLIMATE_MODE_COOL, 0xC0},
|
||||
{climate::CLIMATE_MODE_DRY, 0x40},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, 0x80},
|
||||
{climate::CLIMATE_MODE_HEAT, 0x60},
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, MISTRAL_MODE_AUTO},
|
||||
};
|
||||
|
||||
// Each fan speed sets two bytes; the table lives in flash, so entries are copied out before use
|
||||
struct FanCode {
|
||||
climate::ClimateFanMode fan;
|
||||
uint8_t byte1;
|
||||
@@ -78,7 +75,6 @@ static bool is_valid_frame(const uint8_t *frame) {
|
||||
}
|
||||
|
||||
void MistralIR::transmit_state() {
|
||||
const auto mode = find_entry(MODES, [this](const ModeCode &m) { return m.mode == this->mode; });
|
||||
const climate::ClimateFanMode fan_mode = this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO);
|
||||
const FanCode fan =
|
||||
find_entry(FANS, [fan_mode](const FanCode &f) { return f.fan == fan_mode; }).value_or(load_entry(FANS[0]));
|
||||
@@ -88,7 +84,7 @@ void MistralIR::transmit_state() {
|
||||
progmem_memcpy(frame, FRAME_TEMPLATE, sizeof(frame));
|
||||
frame[1] = fan.byte1;
|
||||
frame[3] = this->mode == climate::CLIMATE_MODE_OFF ? MISTRAL_POWER_OFF : MISTRAL_POWER_ON;
|
||||
frame[4] = mode.has_value() ? mode->code : MISTRAL_MODE_AUTO;
|
||||
frame[4] = climate_ir::to_ir_code(MODES, this->mode, MISTRAL_MODE_AUTO);
|
||||
frame[5] = reverse_bits(static_cast<uint8_t>(MISTRAL_TEMP_OFFSET - temp));
|
||||
frame[6] = fan.byte6;
|
||||
frame[11] = compute_checksum(frame);
|
||||
@@ -116,12 +112,12 @@ bool MistralIR::on_receive(remote_base::RemoteReceiveData data) {
|
||||
return false;
|
||||
|
||||
// Decode everything before touching the state so an unknown code leaves it untouched
|
||||
const auto mode = find_entry(MODES, [frame](const ModeCode &m) { return m.code == frame[4]; });
|
||||
const auto mode = climate_ir::find_ir_code(MODES, frame[4], true);
|
||||
const auto fan = find_entry(FANS, [frame](const FanCode &f) { return f.byte1 == frame[1] && f.byte6 == frame[6]; });
|
||||
if (!mode.has_value() || !fan.has_value())
|
||||
return false;
|
||||
|
||||
this->mode = mode->mode;
|
||||
this->mode = static_cast<climate::ClimateMode>(mode->value);
|
||||
this->target_temperature =
|
||||
clamp<float>(MISTRAL_TEMP_OFFSET - reverse_bits(frame[5]), MISTRAL_TEMP_MIN, MISTRAL_TEMP_MAX);
|
||||
this->fan_mode = fan->fan;
|
||||
|
||||
@@ -29,6 +29,24 @@ using IRNoblexFan = enum IRNoblexFan {
|
||||
IR_NOBLEX_FAN_HIGH = 0b11,
|
||||
};
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// A mode not listed is sent as power off, and ignored when received
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, IR_NOBLEX_MODE_AUTO}, {climate::CLIMATE_MODE_COOL, IR_NOBLEX_MODE_COOL},
|
||||
{climate::CLIMATE_MODE_DRY, IR_NOBLEX_MODE_DRY}, {climate::CLIMATE_MODE_FAN_ONLY, IR_NOBLEX_MODE_FAN},
|
||||
{climate::CLIMATE_MODE_HEAT, IR_NOBLEX_MODE_HEAT},
|
||||
};
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, IR_NOBLEX_FAN_AUTO},
|
||||
{climate::CLIMATE_FAN_LOW, IR_NOBLEX_FAN_LOW},
|
||||
{climate::CLIMATE_FAN_MEDIUM, IR_NOBLEX_FAN_MEDIUM},
|
||||
{climate::CLIMATE_FAN_HIGH, IR_NOBLEX_FAN_HIGH},
|
||||
};
|
||||
|
||||
// Transmit via IR the state of this climate controller.
|
||||
void NoblexClimate::transmit_state() {
|
||||
uint8_t remote_state[8] = {0x80, 0x10, 0x00, 0x0A, 0x50, 0x00, 0x20, 0x00}; // OFF, COOL, 24C, FAN_AUTO
|
||||
@@ -44,47 +62,18 @@ void NoblexClimate::transmit_state() {
|
||||
auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, NOBLEX_TEMP_MIN, NOBLEX_TEMP_MAX));
|
||||
remote_state[1] = reverse_bits(uint8_t((temp - NOBLEX_TEMP_MIN) & 0x0F));
|
||||
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_AUTO << 5);
|
||||
if (const auto entry = find_ir_code(MODES, this->mode, false)) {
|
||||
remote_state[0] |= entry->code << 5;
|
||||
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL)
|
||||
remote_state[1] = 0x90; // NOBLEX_TEMP_MAP 25C
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_COOL << 5);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_DRY << 5);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_FAN << 5);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state[0] |= (IRNoblexMode::IR_NOBLEX_MODE_HEAT << 5);
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
powered_on = false;
|
||||
this->powered_on_assumed = powered_on;
|
||||
remote_state[0] &= 0xEF;
|
||||
remote_state[2] = 0x00;
|
||||
break;
|
||||
} else {
|
||||
powered_on = false;
|
||||
this->powered_on_assumed = powered_on;
|
||||
remote_state[0] &= 0xEF;
|
||||
remote_state[2] = 0x00;
|
||||
}
|
||||
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_LOW << 2);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_MEDIUM << 2);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_HIGH << 2);
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
remote_state[0] |= (IRNoblexFan::IR_NOBLEX_FAN_AUTO << 2);
|
||||
break;
|
||||
}
|
||||
remote_state[0] |= to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), IR_NOBLEX_FAN_AUTO) << 2;
|
||||
|
||||
switch (this->swing_mode) {
|
||||
case climate::CLIMATE_SWING_VERTICAL:
|
||||
@@ -236,25 +225,7 @@ bool NoblexClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
if (powered_on_assumed) {
|
||||
auto mode = (this->remote_state_[0] & 0xE0) >> 5;
|
||||
ESP_LOGV(TAG, "Mode: %02X", mode);
|
||||
switch (mode) {
|
||||
case IRNoblexMode::IR_NOBLEX_MODE_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case IRNoblexMode::IR_NOBLEX_MODE_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case IRNoblexMode::IR_NOBLEX_MODE_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case IRNoblexMode::IR_NOBLEX_MODE_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
case IRNoblexMode::IR_NOBLEX_MODE_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->mode = from_ir_code(MODES, mode, this->mode);
|
||||
}
|
||||
|
||||
// Set received temp
|
||||
@@ -269,21 +240,7 @@ bool NoblexClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
// Set received fan speed
|
||||
auto fan = (this->remote_state_[0] & 0x0C) >> 2;
|
||||
ESP_LOGV(TAG, "Fan: %02X", fan);
|
||||
switch (fan) {
|
||||
case IRNoblexFan::IR_NOBLEX_FAN_HIGH:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case IRNoblexFan::IR_NOBLEX_FAN_MEDIUM:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case IRNoblexFan::IR_NOBLEX_FAN_LOW:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case IRNoblexFan::IR_NOBLEX_FAN_AUTO:
|
||||
default:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
this->fan_mode = from_ir_code(FANS, fan, climate::CLIMATE_FAN_AUTO);
|
||||
|
||||
// Set received swing status
|
||||
if (this->remote_state_[0] & 0x02) {
|
||||
|
||||
@@ -47,46 +47,28 @@ static bool is_powered_on(const uint8_t *state) {
|
||||
return get_field(state, POWER_1) == POWER_ON && get_field(state, POWER_2) == POWER_ON;
|
||||
}
|
||||
|
||||
struct Code {
|
||||
uint8_t value; // a climate enum, all of which are uint8_t
|
||||
uint8_t code;
|
||||
};
|
||||
// The first entry of each table is the fallback in both directions
|
||||
static constexpr Code MODES[] PROGMEM = {
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// Codes not listed fall back to auto (mode and fan) and to swing off
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, 0}, {climate::CLIMATE_MODE_COOL, 1}, {climate::CLIMATE_MODE_DRY, 2},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, 3}, {climate::CLIMATE_MODE_HEAT, 4},
|
||||
};
|
||||
static constexpr Code FANS[] PROGMEM = {
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, 0},
|
||||
{climate::CLIMATE_FAN_LOW, 2},
|
||||
{climate::CLIMATE_FAN_MEDIUM, 4},
|
||||
{climate::CLIMATE_FAN_HIGH, 5},
|
||||
};
|
||||
static constexpr Code SWINGS[] PROGMEM = {
|
||||
static constexpr IRCode SWINGS[] PROGMEM = {
|
||||
{climate::CLIMATE_SWING_OFF, 0b111},
|
||||
{climate::CLIMATE_SWING_VERTICAL, 0b010},
|
||||
{climate::CLIMATE_SWING_HORIZONTAL, 0b011},
|
||||
{climate::CLIMATE_SWING_BOTH, 0b100},
|
||||
};
|
||||
|
||||
// Returns the entry whose value (or code) matches key, else the first entry. Entries are copied out of PROGMEM.
|
||||
static Code find_entry(const Code *table, size_t count, uint8_t key, bool match_code) {
|
||||
Code entry{};
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
progmem_memcpy(&entry, &table[i], sizeof(entry));
|
||||
if ((match_code ? entry.code : entry.value) == key)
|
||||
return entry;
|
||||
}
|
||||
progmem_memcpy(&entry, table, sizeof(entry));
|
||||
return entry;
|
||||
}
|
||||
template<size_t N> static uint8_t to_code(const Code (&table)[N], uint8_t value) {
|
||||
return find_entry(table, N, value, false).code;
|
||||
}
|
||||
template<typename T, size_t N> static T from_code(const Code (&table)[N], uint8_t code) {
|
||||
return static_cast<T>(find_entry(table, N, code, true).value);
|
||||
}
|
||||
|
||||
// Inverted count of the one bits in a section, skipping the two nibbles that hold the checksum itself.
|
||||
// See https://github.com/crankyoldgit/IRremoteESP8266/issues/1538#issuecomment-894645947
|
||||
static uint8_t section_checksum(const uint8_t *section) {
|
||||
@@ -123,13 +105,13 @@ void SamsungClimate::transmit_state() {
|
||||
|
||||
if (on) {
|
||||
progmem_memcpy(state, RESET_STATE, SAMSUNG_AC_STATE_LENGTH);
|
||||
set_field(state, MODE, to_code(MODES, this->mode));
|
||||
set_field(state, MODE, to_ir_code(MODES, this->mode, 0));
|
||||
} // else keep the last mode so the power off frame still carries a valid state
|
||||
|
||||
const uint8_t temp = clamp<uint8_t>(this->target_temperature, SAMSUNG_AC_TEMP_MIN, SAMSUNG_AC_TEMP_MAX);
|
||||
set_field(state, TEMP, temp - SAMSUNG_AC_TEMP_MIN);
|
||||
set_field(state, SWING, to_code(SWINGS, this->swing_mode));
|
||||
set_field(state, FAN, to_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO)));
|
||||
set_field(state, SWING, to_ir_code(SWINGS, this->swing_mode, 0b111));
|
||||
set_field(state, FAN, to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), 0));
|
||||
set_field(state, POWER_1, on ? POWER_ON : 0);
|
||||
set_field(state, POWER_2, on ? POWER_ON : 0);
|
||||
|
||||
@@ -212,11 +194,11 @@ bool SamsungClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
memcpy(state, frame, SAMSUNG_AC_SECTION_LENGTH);
|
||||
memcpy(state + SAMSUNG_AC_SECTION_LENGTH, frame + length - SAMSUNG_AC_SECTION_LENGTH, SAMSUNG_AC_SECTION_LENGTH);
|
||||
|
||||
this->swing_mode = from_code<climate::ClimateSwingMode>(SWINGS, get_field(state, SWING));
|
||||
this->swing_mode = from_ir_code(SWINGS, get_field(state, SWING), climate::CLIMATE_SWING_OFF);
|
||||
this->target_temperature = get_field(state, TEMP) + SAMSUNG_AC_TEMP_MIN;
|
||||
this->fan_mode = from_code<climate::ClimateFanMode>(FANS, get_field(state, FAN));
|
||||
this->mode =
|
||||
is_powered_on(state) ? from_code<climate::ClimateMode>(MODES, get_field(state, MODE)) : climate::CLIMATE_MODE_OFF;
|
||||
this->fan_mode = from_ir_code(FANS, get_field(state, FAN), climate::CLIMATE_FAN_AUTO);
|
||||
this->mode = is_powered_on(state) ? from_ir_code(MODES, get_field(state, MODE), climate::CLIMATE_MODE_HEAT_COOL)
|
||||
: climate::CLIMATE_MODE_OFF;
|
||||
|
||||
ESP_LOGD(TAG, "Received %u byte frame, power %s", length, ONOFF(is_powered_on(state)));
|
||||
this->publish_state();
|
||||
|
||||
@@ -6,70 +6,32 @@ namespace esphome::siemens_ira211 {
|
||||
|
||||
using namespace remote_base;
|
||||
|
||||
struct PresetMapping {
|
||||
climate::ClimatePreset preset;
|
||||
IRA211Mode mode;
|
||||
// Presets map to IRA211Mode and fan modes to IRA211Fan. Values the other side does not know fall back to comfort
|
||||
// and auto.
|
||||
static constexpr climate_ir::IRCode PRESETS[] PROGMEM = {
|
||||
{climate::CLIMATE_PRESET_COMFORT, static_cast<uint8_t>(IRA211Mode::IRA211_MODE_COMFORT)},
|
||||
{climate::CLIMATE_PRESET_ECO, static_cast<uint8_t>(IRA211Mode::IRA211_MODE_TIMER)},
|
||||
};
|
||||
static constexpr climate_ir::IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, static_cast<uint8_t>(IRA211Fan::IRA211_FAN_AUTO)},
|
||||
{climate::CLIMATE_FAN_LOW, static_cast<uint8_t>(IRA211Fan::IRA211_FAN_LOW)},
|
||||
{climate::CLIMATE_FAN_MEDIUM, static_cast<uint8_t>(IRA211Fan::IRA211_FAN_MEDIUM)},
|
||||
{climate::CLIMATE_FAN_HIGH, static_cast<uint8_t>(IRA211Fan::IRA211_FAN_HIGH)},
|
||||
};
|
||||
|
||||
static const PresetMapping PRESETS[] PROGMEM = {
|
||||
{climate::CLIMATE_PRESET_COMFORT, IRA211Mode::IRA211_MODE_COMFORT},
|
||||
{climate::CLIMATE_PRESET_ECO, IRA211Mode::IRA211_MODE_TIMER},
|
||||
};
|
||||
|
||||
struct FanMapping {
|
||||
climate::ClimateFanMode fan_mode;
|
||||
IRA211Fan fan;
|
||||
};
|
||||
|
||||
static const FanMapping FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, IRA211Fan::IRA211_FAN_AUTO},
|
||||
{climate::CLIMATE_FAN_LOW, IRA211Fan::IRA211_FAN_LOW},
|
||||
{climate::CLIMATE_FAN_MEDIUM, IRA211Fan::IRA211_FAN_MEDIUM},
|
||||
{climate::CLIMATE_FAN_HIGH, IRA211Fan::IRA211_FAN_HIGH},
|
||||
};
|
||||
|
||||
// The tables live in flash, so entries are copied out before use
|
||||
template<typename T> static T load_entry(const T &slot) {
|
||||
T entry;
|
||||
progmem_memcpy(&entry, &slot, sizeof(T));
|
||||
return entry;
|
||||
}
|
||||
|
||||
// The first entry of each table is the fallback for values the other side does not know
|
||||
static IRA211Mode to_ira211_mode(climate::ClimatePreset preset) {
|
||||
for (const auto &slot : PRESETS) {
|
||||
const auto entry = load_entry(slot);
|
||||
if (entry.preset == preset)
|
||||
return entry.mode;
|
||||
}
|
||||
return load_entry(PRESETS[0]).mode;
|
||||
return static_cast<IRA211Mode>(
|
||||
climate_ir::to_ir_code(PRESETS, preset, static_cast<uint8_t>(IRA211Mode::IRA211_MODE_COMFORT)));
|
||||
}
|
||||
|
||||
static climate::ClimatePreset to_climate_preset(IRA211Mode mode) {
|
||||
for (const auto &slot : PRESETS) {
|
||||
const auto entry = load_entry(slot);
|
||||
if (entry.mode == mode)
|
||||
return entry.preset;
|
||||
}
|
||||
return load_entry(PRESETS[0]).preset;
|
||||
return climate_ir::from_ir_code(PRESETS, static_cast<uint8_t>(mode), climate::CLIMATE_PRESET_COMFORT);
|
||||
}
|
||||
|
||||
static IRA211Fan to_ira211_fan(climate::ClimateFanMode fan_mode) {
|
||||
for (const auto &slot : FANS) {
|
||||
const auto entry = load_entry(slot);
|
||||
if (entry.fan_mode == fan_mode)
|
||||
return entry.fan;
|
||||
}
|
||||
return load_entry(FANS[0]).fan;
|
||||
return static_cast<IRA211Fan>(
|
||||
climate_ir::to_ir_code(FANS, fan_mode, static_cast<uint8_t>(IRA211Fan::IRA211_FAN_AUTO)));
|
||||
}
|
||||
|
||||
static climate::ClimateFanMode to_climate_fan_mode(IRA211Fan fan) {
|
||||
for (const auto &slot : FANS) {
|
||||
const auto entry = load_entry(slot);
|
||||
if (entry.fan == fan)
|
||||
return entry.fan_mode;
|
||||
}
|
||||
return load_entry(FANS[0]).fan_mode;
|
||||
return climate_ir::from_ir_code(FANS, static_cast<uint8_t>(fan), climate::CLIMATE_FAN_AUTO);
|
||||
}
|
||||
|
||||
void SiemensIRA211Climate::setup() {
|
||||
|
||||
@@ -32,6 +32,24 @@ static constexpr remote_base::BitTimings TCL112_BIT_TIMINGS{
|
||||
.bit_mark = TCL112_BIT_MARK, .one_space = 1100, .zero_space = 350};
|
||||
const uint32_t TCL112_GAP = TCL112_HEADER_SPACE;
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// A mode not listed is sent as power off, and ignored when received
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, TCL112_AUTO}, {climate::CLIMATE_MODE_COOL, TCL112_COOL},
|
||||
{climate::CLIMATE_MODE_HEAT, TCL112_HEAT}, {climate::CLIMATE_MODE_DRY, TCL112_DRY},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, TCL112_FAN},
|
||||
};
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, TCL112_FAN_AUTO},
|
||||
{climate::CLIMATE_FAN_HIGH, TCL112_FAN_HIGH},
|
||||
{climate::CLIMATE_FAN_MEDIUM, TCL112_FAN_MED},
|
||||
{climate::CLIMATE_FAN_LOW, TCL112_FAN_LOW},
|
||||
};
|
||||
|
||||
void Tcl112Climate::transmit_state() {
|
||||
uint8_t remote_state[TCL112_STATE_LENGTH] = {0};
|
||||
|
||||
@@ -46,31 +64,11 @@ void Tcl112Climate::transmit_state() {
|
||||
remote_state[8] = 0x40;
|
||||
|
||||
// Set mode
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state[6] &= 0xF0;
|
||||
remote_state[6] |= TCL112_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state[6] &= 0xF0;
|
||||
remote_state[6] |= TCL112_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state[6] &= 0xF0;
|
||||
remote_state[6] |= TCL112_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
remote_state[6] &= 0xF0;
|
||||
remote_state[6] |= TCL112_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
remote_state[6] &= 0xF0;
|
||||
remote_state[6] |= TCL112_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
remote_state[5] &= ~TCL112_POWER_MASK;
|
||||
break;
|
||||
if (const auto entry = find_ir_code(MODES, this->mode, false)) {
|
||||
remote_state[6] &= 0xF0;
|
||||
remote_state[6] |= entry->code;
|
||||
} else {
|
||||
remote_state[5] &= ~TCL112_POWER_MASK;
|
||||
}
|
||||
|
||||
// Set temperature
|
||||
@@ -88,22 +86,7 @@ void Tcl112Climate::transmit_state() {
|
||||
remote_state[7] |= ((uint8_t) TCL112_TEMP_MAX - half_degrees / 2);
|
||||
|
||||
// Set fan
|
||||
uint8_t selected_fan;
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
selected_fan = TCL112_FAN_HIGH;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
selected_fan = TCL112_FAN_MED;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
selected_fan = TCL112_FAN_LOW;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
selected_fan = TCL112_FAN_AUTO;
|
||||
}
|
||||
remote_state[8] |= selected_fan;
|
||||
remote_state[8] |= to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), TCL112_FAN_AUTO);
|
||||
|
||||
// Set swing
|
||||
if (this->swing_mode == climate::CLIMATE_SWING_VERTICAL) {
|
||||
@@ -169,47 +152,13 @@ bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
if ((remote_state[5] & TCL112_POWER_MASK) == 0) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
auto mode = remote_state[6] & 0x0F;
|
||||
switch (mode) {
|
||||
case TCL112_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case TCL112_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case TCL112_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case TCL112_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
case TCL112_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->mode = from_ir_code(MODES, remote_state[6] & 0x0F, this->mode);
|
||||
}
|
||||
auto temp = TCL112_TEMP_MAX - remote_state[7];
|
||||
if (remote_state[12] & TCL112_HALF_DEGREE)
|
||||
temp += .5f;
|
||||
this->target_temperature = temp;
|
||||
auto fan = remote_state[8] & 0x7;
|
||||
switch (fan) {
|
||||
case TCL112_FAN_HIGH:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case TCL112_FAN_MED:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case TCL112_FAN_LOW:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case TCL112_FAN_AUTO:
|
||||
default:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
this->fan_mode = from_ir_code(FANS, remote_state[8] & 0x7, climate::CLIMATE_FAN_AUTO);
|
||||
if ((remote_state[8] & TCL112_VSWING_MASK) == TCL112_VSWING_MASK) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
|
||||
} else {
|
||||
|
||||
@@ -57,6 +57,24 @@ static constexpr uint8_t HITACHI_BYTE19 = 0x08; // byte 19
|
||||
// Header, 21 bytes of 8 bits, two dividers and the footer mark
|
||||
static constexpr uint32_t WHIRLPOOL_FRAME_TIMINGS = 2 + WHIRLPOOL_STATE_LENGTH * 16 + 2 * 2 + 1;
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// Off is not listed: it is sent as a power toggle or state
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_HEAT_COOL, WHIRLPOOL_AUTO}, {climate::CLIMATE_MODE_HEAT, WHIRLPOOL_HEAT},
|
||||
{climate::CLIMATE_MODE_COOL, WHIRLPOOL_COOL}, {climate::CLIMATE_MODE_DRY, WHIRLPOOL_DRY},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, WHIRLPOOL_FAN},
|
||||
};
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, WHIRLPOOL_FAN_AUTO},
|
||||
{climate::CLIMATE_FAN_HIGH, WHIRLPOOL_FAN_HIGH},
|
||||
{climate::CLIMATE_FAN_MEDIUM, WHIRLPOOL_FAN_MED},
|
||||
{climate::CLIMATE_FAN_LOW, WHIRLPOOL_FAN_LOW},
|
||||
};
|
||||
|
||||
WhirlpoolClimate::WhirlpoolClimate()
|
||||
: climate_ir::ClimateIR(
|
||||
WHIRLPOOL_DG11J1_3A_TEMP_MIN, WHIRLPOOL_DG11J1_3A_TEMP_MAX, 1.0f, true, true,
|
||||
@@ -81,37 +99,13 @@ void WhirlpoolClimate::transmit_state() {
|
||||
remote_state[15] = 1;
|
||||
}
|
||||
this->powered_on_assumed = powered_on;
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
// set fan auto
|
||||
// set temp auto temp
|
||||
// set sleep false
|
||||
remote_state[3] = WHIRLPOOL_AUTO;
|
||||
remote_state[15] = 0x17;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state[3] = WHIRLPOOL_HEAT;
|
||||
remote_state[15] = 6;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state[3] = WHIRLPOOL_COOL;
|
||||
remote_state[15] = 6;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
remote_state[3] = WHIRLPOOL_DRY;
|
||||
remote_state[15] = 6;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
remote_state[3] = WHIRLPOOL_FAN;
|
||||
remote_state[15] = 6;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
// These remotes keep the cool code in the power-off frame
|
||||
if (this->is_hisense_arg_() || this->is_hitachi_hcra31newh_())
|
||||
remote_state[3] = WHIRLPOOL_COOL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (const auto entry = find_ir_code(MODES, this->mode, false)) {
|
||||
remote_state[3] = entry->code;
|
||||
// Auto also sets fan auto, auto temperature and sleep off
|
||||
remote_state[15] = this->mode == climate::CLIMATE_MODE_HEAT_COOL ? 0x17 : 6;
|
||||
} else if (this->mode == climate::CLIMATE_MODE_OFF && (this->is_hisense_arg_() || this->is_hitachi_hcra31newh_())) {
|
||||
// These remotes keep the cool code in the power-off frame
|
||||
remote_state[3] = WHIRLPOOL_COOL;
|
||||
}
|
||||
|
||||
const bool hisense_arg = this->is_hisense_arg_();
|
||||
@@ -127,19 +121,7 @@ void WhirlpoolClimate::transmit_state() {
|
||||
}
|
||||
|
||||
// Fan speed
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
remote_state[2] |= WHIRLPOOL_FAN_HIGH;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
remote_state[2] |= WHIRLPOOL_FAN_MED;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
remote_state[2] |= WHIRLPOOL_FAN_LOW;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
remote_state[2] |= to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), WHIRLPOOL_FAN_AUTO);
|
||||
|
||||
// Swing
|
||||
ESP_LOGV(TAG, "send swing %s", this->send_swing_cmd_ ? LOG_STR_LITERAL("true") : LOG_STR_LITERAL("false"));
|
||||
@@ -328,25 +310,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
if (powered_on_assumed) {
|
||||
auto mode = remote_state[3] & 0x7;
|
||||
ESP_LOGV(TAG, "Mode: %02X", mode);
|
||||
switch (mode) {
|
||||
case WHIRLPOOL_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case WHIRLPOOL_COOL:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
case WHIRLPOOL_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case WHIRLPOOL_FAN:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
case WHIRLPOOL_AUTO:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->mode = from_ir_code(MODES, mode, this->mode);
|
||||
}
|
||||
|
||||
// Set received temp; Hisense ARG sends fixed values in auto and fan-only
|
||||
@@ -363,21 +327,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
// Set received fan speed
|
||||
auto fan = remote_state[2] & WHIRLPOOL_FAN_MASK;
|
||||
ESP_LOGVV(TAG, "Fan: %02X", fan);
|
||||
switch (fan) {
|
||||
case WHIRLPOOL_FAN_HIGH:
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
case WHIRLPOOL_FAN_MED:
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
break;
|
||||
case WHIRLPOOL_FAN_LOW:
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
break;
|
||||
case WHIRLPOOL_FAN_AUTO:
|
||||
default:
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
this->fan_mode = from_ir_code(FANS, fan, climate::CLIMATE_FAN_AUTO);
|
||||
|
||||
// Set received swing status
|
||||
if (hisense_arg) {
|
||||
|
||||
@@ -39,48 +39,40 @@ const uint32_t UNIT_MASK = 1 << UNIT_SHIFT;
|
||||
const uint32_t TEMP_MASK = 0xFF;
|
||||
const uint32_t TEMP_OFFSET_C = 16;
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
// Codes are stored shifted down to one byte. A mode not listed is sent as power off, and ignored when received.
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, MODE_FAN >> MODE_SHIFT},
|
||||
{climate::CLIMATE_MODE_DRY, MODE_DRY >> MODE_SHIFT},
|
||||
{climate::CLIMATE_MODE_HEAT, MODE_HEAT >> MODE_SHIFT},
|
||||
{climate::CLIMATE_MODE_COOL, MODE_COOL >> MODE_SHIFT},
|
||||
};
|
||||
// A received code that is not listed is ignored
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_HIGH, FAN_HIGH >> FAN_SHIFT},
|
||||
{climate::CLIMATE_FAN_LOW, FAN_LOW >> FAN_SHIFT},
|
||||
{climate::CLIMATE_FAN_MEDIUM, FAN_MED >> FAN_SHIFT},
|
||||
};
|
||||
|
||||
void Whynter::transmit_state() {
|
||||
uint32_t remote_state = COMMAND_CODE;
|
||||
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL)
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
remote_state |= POWER_MASK;
|
||||
remote_state |= MODE_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
remote_state |= POWER_MASK;
|
||||
remote_state |= MODE_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state |= POWER_MASK;
|
||||
remote_state |= MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state |= POWER_MASK;
|
||||
remote_state |= MODE_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
remote_state |= POWER_OFF;
|
||||
break;
|
||||
default:
|
||||
remote_state |= POWER_OFF;
|
||||
if (const auto entry = find_ir_code(MODES, this->mode, false)) {
|
||||
remote_state |= POWER_MASK;
|
||||
remote_state |= static_cast<uint32_t>(entry->code) << MODE_SHIFT;
|
||||
} else {
|
||||
remote_state |= POWER_OFF;
|
||||
}
|
||||
mode_before_ = this->mode;
|
||||
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
remote_state |= FAN_LOW;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
remote_state |= FAN_MED;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
remote_state |= FAN_HIGH;
|
||||
break;
|
||||
default:
|
||||
remote_state |= FAN_HIGH;
|
||||
}
|
||||
remote_state |=
|
||||
static_cast<uint32_t>(to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_HIGH), FAN_HIGH >> FAN_SHIFT))
|
||||
<< FAN_SHIFT;
|
||||
|
||||
if (fahrenheit_) {
|
||||
remote_state |= UNIT_MASK;
|
||||
@@ -123,15 +115,7 @@ bool Whynter::on_receive(remote_base::RemoteReceiveData data) {
|
||||
if ((remote_state & POWER_MASK) != POWER_MASK) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else {
|
||||
if ((remote_state & MODE_MASK) == MODE_FAN) {
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
} else if ((remote_state & MODE_MASK) == MODE_DRY) {
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
} else if ((remote_state & MODE_MASK) == MODE_HEAT) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
} else if ((remote_state & MODE_MASK) == MODE_COOL) {
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
}
|
||||
this->mode = from_ir_code(MODES, (remote_state & MODE_MASK) >> MODE_SHIFT, this->mode);
|
||||
|
||||
// Temperature
|
||||
if ((remote_state & UNIT_MASK) == UNIT_MASK) { // Fahrenheit
|
||||
@@ -141,13 +125,8 @@ bool Whynter::on_receive(remote_base::RemoteReceiveData data) {
|
||||
}
|
||||
|
||||
// Fan Speed
|
||||
if ((remote_state & FAN_MASK) == FAN_LOW) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
} else if ((remote_state & FAN_MASK) == FAN_MED) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
} else if ((remote_state & FAN_MASK) == FAN_HIGH) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
}
|
||||
if (const auto entry = find_ir_code(FANS, (remote_state & FAN_MASK) >> FAN_SHIFT, true))
|
||||
this->fan_mode = static_cast<climate::ClimateFanMode>(entry->value);
|
||||
}
|
||||
this->publish_state();
|
||||
|
||||
|
||||
@@ -7,6 +7,24 @@ ESPHOME_LOG_TAG(TAG, "zhlt01.climate");
|
||||
|
||||
static constexpr uint8_t ZHLT01_MESSAGE_LENGTH = 12;
|
||||
|
||||
using climate_ir::find_ir_code;
|
||||
using climate_ir::from_ir_code;
|
||||
using climate_ir::IRCode;
|
||||
using climate_ir::to_ir_code;
|
||||
|
||||
static constexpr IRCode MODES[] PROGMEM = {
|
||||
{climate::CLIMATE_MODE_AUTO, AC1_MODE_AUTO}, {climate::CLIMATE_MODE_COOL, AC1_MODE_COOL},
|
||||
{climate::CLIMATE_MODE_HEAT, AC1_MODE_HEAT}, {climate::CLIMATE_MODE_DRY, AC1_MODE_DRY},
|
||||
{climate::CLIMATE_MODE_FAN_ONLY, AC1_MODE_FAN},
|
||||
};
|
||||
// A received fan code that is not listed is ignored
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, AC1_FAN_AUTO},
|
||||
{climate::CLIMATE_FAN_LOW, AC1_FAN1},
|
||||
{climate::CLIMATE_FAN_MEDIUM, AC1_FAN2},
|
||||
{climate::CLIMATE_FAN_HIGH, AC1_FAN3},
|
||||
};
|
||||
|
||||
void ZHLT01Climate::transmit_state() {
|
||||
uint8_t ir_message[ZHLT01_MESSAGE_LENGTH] = {0};
|
||||
|
||||
@@ -52,46 +70,12 @@ void ZHLT01Climate::transmit_state() {
|
||||
ir_message[7] |= AC1_FAN_SILENT;
|
||||
break;
|
||||
default:
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
ir_message[7] |= AC1_FAN1;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
ir_message[7] |= AC1_FAN2;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
ir_message[7] |= AC1_FAN3;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
ir_message[7] |= AC1_FAN_AUTO;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ir_message[7] |= to_ir_code(FANS, this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO), AC1_FAN_AUTO);
|
||||
}
|
||||
|
||||
// Byte 9 : AC Mode | Temperature
|
||||
// -- AC Mode
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
ir_message[9] = AC1_MODE_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
ir_message[9] = AC1_MODE_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
ir_message[9] = AC1_MODE_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
ir_message[9] = AC1_MODE_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
ir_message[9] = AC1_MODE_FAN;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ir_message[9] = to_ir_code(MODES, this->mode, AC1_MODE_AUTO);
|
||||
|
||||
// -- Temperature
|
||||
ir_message[9] |= (uint8_t) (this->target_temperature - 16.0f);
|
||||
@@ -185,28 +169,12 @@ bool ZHLT01Climate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
} else if ((ir_message[7] & 0xE1) == AC1_FAN_SILENT) {
|
||||
this->preset = climate::CLIMATE_PRESET_SLEEP;
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
} else if ((ir_message[7] & 0xE1) == AC1_FAN_AUTO) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
} else if ((ir_message[7] & 0xE1) == AC1_FAN1) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_LOW;
|
||||
} else if ((ir_message[7] & 0xE1) == AC1_FAN2) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
|
||||
} else if ((ir_message[7] & 0xE1) == AC1_FAN3) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
} else if (const auto entry = find_ir_code(FANS, ir_message[7] & 0xE1, true)) {
|
||||
this->fan_mode = static_cast<climate::ClimateFanMode>(entry->value);
|
||||
}
|
||||
|
||||
// AC Mode
|
||||
if ((ir_message[9] & 0xE0) == AC1_MODE_COOL) {
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
} else if ((ir_message[9] & 0xE0) == AC1_MODE_HEAT) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
} else if ((ir_message[9] & 0xE0) == AC1_MODE_DRY) {
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
} else if ((ir_message[9] & 0xE0) == AC1_MODE_FAN) {
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
} else {
|
||||
this->mode = climate::CLIMATE_MODE_AUTO;
|
||||
}
|
||||
this->mode = from_ir_code(MODES, ir_message[9] & 0xE0, climate::CLIMATE_MODE_AUTO);
|
||||
|
||||
// Target Temperature
|
||||
this->target_temperature = (ir_message[9] & 0x1F) + 16.0f;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "esphome/components/climate_ir/climate_ir.h"
|
||||
|
||||
namespace esphome::climate_ir::testing {
|
||||
|
||||
static constexpr IRCode FANS[] PROGMEM = {
|
||||
{climate::CLIMATE_FAN_AUTO, 0x10},
|
||||
{climate::CLIMATE_FAN_LOW, 0x20},
|
||||
{climate::CLIMATE_FAN_HIGH, 0x30},
|
||||
};
|
||||
|
||||
TEST(ClimateIRCodeTest, FindByValue) {
|
||||
const auto entry = find_ir_code(FANS, climate::CLIMATE_FAN_LOW, false);
|
||||
ASSERT_TRUE(entry.has_value());
|
||||
EXPECT_EQ(entry->value, climate::CLIMATE_FAN_LOW);
|
||||
EXPECT_EQ(entry->code, 0x20);
|
||||
}
|
||||
|
||||
TEST(ClimateIRCodeTest, FindByCode) {
|
||||
const auto entry = find_ir_code(FANS, 0x30, true);
|
||||
ASSERT_TRUE(entry.has_value());
|
||||
EXPECT_EQ(entry->value, climate::CLIMATE_FAN_HIGH);
|
||||
}
|
||||
|
||||
TEST(ClimateIRCodeTest, FindMissing) {
|
||||
EXPECT_FALSE(find_ir_code(FANS, climate::CLIMATE_FAN_MEDIUM, false).has_value());
|
||||
// A code that is also a listed value must not match when looking up codes
|
||||
EXPECT_FALSE(find_ir_code(FANS, climate::CLIMATE_FAN_LOW, true).has_value());
|
||||
}
|
||||
|
||||
TEST(ClimateIRCodeTest, ToCode) {
|
||||
EXPECT_EQ(to_ir_code(FANS, climate::CLIMATE_FAN_HIGH, 0x00), 0x30);
|
||||
EXPECT_EQ(to_ir_code(FANS, climate::CLIMATE_FAN_MEDIUM, 0x00), 0x00);
|
||||
}
|
||||
|
||||
TEST(ClimateIRCodeTest, FromCode) {
|
||||
EXPECT_EQ(from_ir_code(FANS, 0x20, climate::CLIMATE_FAN_AUTO), climate::CLIMATE_FAN_LOW);
|
||||
EXPECT_EQ(from_ir_code(FANS, 0x99, climate::CLIMATE_FAN_AUTO), climate::CLIMATE_FAN_AUTO);
|
||||
// The current state as the fallback leaves it unchanged on an unknown code
|
||||
EXPECT_EQ(from_ir_code(FANS, 0x99, climate::CLIMATE_FAN_MEDIUM), climate::CLIMATE_FAN_MEDIUM);
|
||||
}
|
||||
|
||||
} // namespace esphome::climate_ir::testing
|
||||
Reference in new issue
Block a user