diff --git a/components/daikin_s21/climate/__init__.py b/components/daikin_s21/climate/__init__.py index 9a096f3..95e288c 100644 --- a/components/daikin_s21/climate/__init__.py +++ b/components/daikin_s21/climate/__init__.py @@ -15,6 +15,7 @@ CONF_ROOM_TEMPERATURE_SENSOR = "room_temperature_sensor" CONF_SETPOINT_INTERVAL = "setpoint_interval" +CONF_HAS_PRESETS = "has_presets" DaikinS21Climate = daikin_s21_ns.class_( "DaikinS21Climate", climate.Climate, cg.PollingComponent, DaikinS21Client @@ -30,6 +31,7 @@ cv.Optional( CONF_SETPOINT_INTERVAL, default="300s" ): cv.positive_time_period_seconds, + cv.Optional(CONF_HAS_PRESETS, default=True): cv.boolean, } ) .extend(cv.polling_component_schema("5s")) @@ -44,6 +46,8 @@ async def to_code(config): await climate.register_climate(var, config) s21_var = await cg.get_variable(config[CONF_S21_ID]) cg.add(var.set_s21(s21_var)) + if CONF_HAS_PRESETS in config: + cg.add(var.set_has_presets(config[CONF_HAS_PRESETS])) if CONF_ROOM_TEMPERATURE_SENSOR in config: sens = await cg.get_variable(config[CONF_ROOM_TEMPERATURE_SENSOR]) cg.add(var.set_room_sensor(sens)) diff --git a/components/daikin_s21/climate/daikin_s21_climate.cpp b/components/daikin_s21/climate/daikin_s21_climate.cpp index cc3cc30..7a55b8c 100644 --- a/components/daikin_s21/climate/daikin_s21_climate.cpp +++ b/components/daikin_s21/climate/daikin_s21_climate.cpp @@ -63,6 +63,15 @@ climate::ClimateTraits DaikinS21Climate::traits() { climate::CLIMATE_SWING_HORIZONTAL, }); + if(this->has_presets) + { + traits.set_supported_presets({ + climate::CLIMATE_PRESET_NONE, + climate::CLIMATE_PRESET_BOOST, + climate::CLIMATE_PRESET_ECO, + }); + } + return traits; } @@ -139,6 +148,8 @@ void DaikinS21Climate::save_setpoint(float value) { case DaikinClimateMode::Heat: this->save_setpoint(value, this->heat_setpoint_pref); break; + default: + break; } } } @@ -148,7 +159,10 @@ optional DaikinS21Climate::load_setpoint(ESPPreferenceObject &pref) { if (!pref.load(&stored_val)) { return {}; } - return static_cast(stored_val) / 10.0; + float result = static_cast(stored_val) / 10.0; + if(result<0) + result = 0; + return result; } optional DaikinS21Climate::load_setpoint(DaikinClimateMode mode) { @@ -163,6 +177,8 @@ optional DaikinS21Climate::load_setpoint(DaikinClimateMode mode) { case DaikinClimateMode::Heat: loaded = this->load_setpoint(this->heat_setpoint_pref); break; + default: + break; } return loaded; } @@ -283,6 +299,26 @@ climate::ClimateAction DaikinS21Climate::d2e_climate_action() { } } +climate::ClimatePreset DaikinS21Climate::d2e_preset_mode(bool powerful, bool econo) +{ + if(powerful) + return climate::CLIMATE_PRESET_BOOST; + if(econo) + return climate::CLIMATE_PRESET_ECO; + return climate::CLIMATE_PRESET_NONE; +} + +bool DaikinS21Climate::e2d_powerful(climate::ClimatePreset mode) +{ + return mode==climate::CLIMATE_PRESET_BOOST; +} + +bool DaikinS21Climate::e2d_econo(climate::ClimatePreset mode) +{ + return mode==climate::CLIMATE_PRESET_ECO; +} + + climate::ClimateSwingMode DaikinS21Climate::d2e_swing_mode(bool swing_v, bool swing_h) { if (swing_v && swing_h) @@ -323,6 +359,7 @@ void DaikinS21Climate::update() { this->set_custom_fan_mode_(this->d2e_fan_mode(this->s21->get_fan_mode())); this->swing_mode = this->d2e_swing_mode(this->s21->get_swing_v(), this->s21->get_swing_h()); + this->preset = this->d2e_preset_mode(this->s21->get_powerful(),this->s21->get_econo()); this->current_temperature = this->get_effective_current_temperature(); if (this->should_check_setpoint(this->mode)) { @@ -400,6 +437,12 @@ void DaikinS21Climate::control(const climate::ClimateCall &call) { this->e2d_swing_h(swing_mode)); } + if (call.get_preset().has_value()) { + climate::ClimatePreset preset = call.get_preset().value(); + this->s21->set_powerful_settings(this->e2d_powerful(preset)); + this->s21->set_econo_settings(this->e2d_econo(preset)); + } + this->update(); } @@ -407,7 +450,7 @@ void DaikinS21Climate::set_s21_climate() { this->expected_s21_setpoint = this->calc_s21_setpoint(this->target_temperature); ESP_LOGI(TAG, "Controlling S21 climate:"); - ESP_LOGI(TAG, " Mode: %s", climate::climate_mode_to_string(this->mode)); + ESP_LOGI(TAG, " Mode: %s", LOG_STR_ARG(climate::climate_mode_to_string(this->mode))); ESP_LOGI(TAG, " Setpoint: %.1f (s21: %.1f)", this->target_temperature, this->expected_s21_setpoint); ESP_LOGI(TAG, " Fan: %s", this->custom_fan_mode.value().c_str()); diff --git a/components/daikin_s21/climate/daikin_s21_climate.h b/components/daikin_s21/climate/daikin_s21_climate.h index 837ed02..89b61cf 100644 --- a/components/daikin_s21/climate/daikin_s21_climate.h +++ b/components/daikin_s21/climate/daikin_s21_climate.h @@ -36,6 +36,10 @@ class DaikinS21Climate : public climate::Climate, void set_setpoint_interval(uint16_t seconds) { this->setpoint_interval = seconds; }; + void set_has_presets(bool value) { + this->has_presets = value; + this->s21->set_has_presets(value); + }; float get_s21_setpoint() { return this->s21->get_setpoint(); } float get_room_temp_offset(); @@ -48,6 +52,9 @@ class DaikinS21Climate : public climate::Climate, climate::ClimateSwingMode d2e_swing_mode(bool swing_v, bool swing_h); bool e2d_swing_v(climate::ClimateSwingMode mode); bool e2d_swing_h(climate::ClimateSwingMode mode); + climate::ClimatePreset d2e_preset_mode(bool powerful, bool econo); + bool e2d_powerful(climate::ClimatePreset mode); + bool e2d_econo(climate::ClimatePreset mode); protected: sensor::Sensor *room_sensor_{nullptr}; @@ -55,6 +62,7 @@ class DaikinS21Climate : public climate::Climate, uint8_t skip_setpoint_checks = 0; uint16_t setpoint_interval = 0; uint32_t last_setpoint_check = 0; + bool has_presets = true; ESPPreferenceObject auto_setpoint_pref; diff --git a/components/daikin_s21/s21.cpp b/components/daikin_s21/s21.cpp index 45d6e56..556035a 100644 --- a/components/daikin_s21/s21.cpp +++ b/components/daikin_s21/s21.cpp @@ -191,6 +191,21 @@ std::string str_repr(std::vector &bytes) { return str_repr(&bytes[0], bytes.size()); } +bool DaikinS21::wait_byte_available(uint32_t timeout) +{ + uint32_t start = millis(); + bool reading = false; + while (true) { + if (millis() - start > timeout) { + ESP_LOGW(TAG, "Timeout waiting for byte"); + return false; + } + if(this->rx_uart->available()) + return true; + yield(); + } +} + bool DaikinS21::read_frame(std::vector &payload) { uint8_t byte; std::vector bytes; @@ -252,6 +267,7 @@ bool DaikinS21::s21_query(std::vector code) { } this->write_frame(code); + this->wait_byte_available(S21_RESPONSE_TIMEOUT); uint8_t byte; if (!this->rx_uart->read_byte(&byte)) { ESP_LOGW(TAG, "Timeout waiting for %s response", c.c_str()); @@ -307,6 +323,12 @@ bool DaikinS21::parse_response(std::vector rcode, this->swing_v = payload[0] & 1; this->swing_h = payload[0] & 2; return true; + case '6': // F6 -> G6 - "powerful" mode + this->powerful = (payload[0] == '2') ? 1 : 0; + return true; + case '7': // F7 - G7 - "eco" mode + this->econo = (payload[1] == '2') ? 1 : 0; + return true; } break; case 'S': // R -> S @@ -354,7 +376,11 @@ bool DaikinS21::run_queries(std::vector queries) { } void DaikinS21::update() { - std::vector queries = {"F1", "F5", "RH", "RI", "Ra", "RL", "Rd"}; + std::vector queries; + if(has_presets) + queries = {"F1", "F5", "F6", "F7", "RH", "RI", "Ra", "RL", "Rd"}; + else + queries = {"F1", "F5", "RH", "RI", "Ra", "RL", "Rd"}; if (this->run_queries(queries) && !this->ready) { ESP_LOGI(TAG, "Daikin S21 Ready"); this->ready = true; @@ -433,6 +459,30 @@ void DaikinS21::set_swing_settings(bool swing_v, bool swing_h) { } } +void DaikinS21::set_powerful_settings(bool value) +{ + std::vector cmd = { + (uint8_t) ('0' + (value ? 2 : 0)), '0', '0', '0'}; + ESP_LOGD(TAG, "Sending swing CMD (D6): %s", str_repr(cmd).c_str()); + if (!this->send_cmd({'D', '6'}, cmd)) { + ESP_LOGW(TAG, "Failed powerful CMD"); + } else { + this->update(); + } +} + +void DaikinS21::set_econo_settings(bool value) +{ + std::vector cmd = { + '0', (uint8_t) ('0' + (value ? 2 : 0)), '0', '0'}; + ESP_LOGD(TAG, "Sending swing CMD (D7): %s", str_repr(cmd).c_str()); + if (!this->send_cmd({'D', '7'}, cmd)) { + ESP_LOGW(TAG, "Failed econo CMD"); + } else { + this->update(); + } +} + bool DaikinS21::send_cmd(std::vector code, std::vector payload) { std::vector frame; @@ -446,6 +496,7 @@ bool DaikinS21::send_cmd(std::vector code, } this->write_frame(frame); + this->wait_byte_available(S21_RESPONSE_TIMEOUT); if (!this->rx_uart->read_byte(&byte)) { ESP_LOGW(TAG, "Timeout waiting for ACK to %s", str_repr(frame).c_str()); return false; diff --git a/components/daikin_s21/s21.h b/components/daikin_s21/s21.h index a725622..5e900b9 100644 --- a/components/daikin_s21/s21.h +++ b/components/daikin_s21/s21.h @@ -7,8 +7,8 @@ namespace esphome { namespace daikin_s21 { enum class DaikinClimateMode : uint8_t { - Disabled = '0', - Auto = '1', + Disabled = '1', + Auto = '0', Dry = '2', Cool = '3', Heat = '4', @@ -54,6 +54,13 @@ class DaikinS21 : public PollingComponent { bool is_idle() { return this->idle; } bool get_swing_h() { return this->swing_h; } bool get_swing_v() { return this->swing_v; } + bool get_powerful() { return this->powerful; } + bool get_econo() { return this->econo; } + void set_powerful_settings(bool value); + void set_econo_settings(bool value); + void set_has_presets(bool value) { + this->has_presets = value; + }; protected: bool read_frame(std::vector &payload); @@ -63,6 +70,7 @@ class DaikinS21 : public PollingComponent { bool run_queries(std::vector queries); void dump_state(); void check_uart_settings(); + bool wait_byte_available(uint32_t timeout); uart::UARTComponent *tx_uart{nullptr}; uart::UARTComponent *rx_uart{nullptr}; @@ -75,11 +83,14 @@ class DaikinS21 : public PollingComponent { int16_t setpoint = 23; bool swing_v = false; bool swing_h = false; + bool powerful = false; + bool econo = false; int16_t temp_inside = 0; int16_t temp_outside = 0; int16_t temp_coil = 0; uint16_t fan_rpm = 0; bool idle = true; + bool has_presets = true; }; class DaikinS21Client {