Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support powerful and econo modes #9

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions components/daikin_s21/climate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defaulting to True here is backwards-incompatible and will break existing configs for some units. For instance, my units do not support the F6/F7 queries (even though they do support powerful mode). As a result, building my existing configs with this change causes the unit to report incorrect data to Home Assistant, but setting this to false fixes the issue.

An alternative would be some sort of graceful degradation in the event presets do not work, though that would require some reworking of how the module elects which queries to use. This would be a worthwhile/valuable enhancement in general, as I suspect there are going to be more cases where different queries are required, but I don't expect that in this PR.

}
)
.extend(cv.polling_component_schema("5s"))
Expand All @@ -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))
Expand Down
42 changes: 41 additions & 1 deletion components/daikin_s21/climate/daikin_s21_climate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -139,6 +148,8 @@ void DaikinS21Climate::save_setpoint(float value) {
case DaikinClimateMode::Heat:
this->save_setpoint(value, this->heat_setpoint_pref);
break;
default:
break;
}
}
}
Expand All @@ -163,6 +174,8 @@ optional<float> DaikinS21Climate::load_setpoint(DaikinClimateMode mode) {
case DaikinClimateMode::Heat:
loaded = this->load_setpoint(this->heat_setpoint_pref);
break;
default:
break;
}
return loaded;
}
Expand Down Expand Up @@ -283,6 +296,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)
Expand Down Expand Up @@ -323,6 +356,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)) {
Expand Down Expand Up @@ -400,14 +434,20 @@ 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();
}

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());
Expand Down
8 changes: 8 additions & 0 deletions components/daikin_s21/climate/daikin_s21_climate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -48,13 +52,17 @@ 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};
float expected_s21_setpoint;
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;
Expand Down
53 changes: 52 additions & 1 deletion components/daikin_s21/s21.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,21 @@ std::string str_repr(std::vector<uint8_t> &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<uint8_t> &payload) {
uint8_t byte;
std::vector<uint8_t> bytes;
Expand Down Expand Up @@ -252,6 +267,7 @@ bool DaikinS21::s21_query(std::vector<uint8_t> 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());
Expand Down Expand Up @@ -307,6 +323,12 @@ bool DaikinS21::parse_response(std::vector<uint8_t> 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
Expand Down Expand Up @@ -354,7 +376,11 @@ bool DaikinS21::run_queries(std::vector<std::string> queries) {
}

void DaikinS21::update() {
std::vector<std::string> queries = {"F1", "F5", "RH", "RI", "Ra", "RL", "Rd"};
std::vector<std::string> 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;
Expand Down Expand Up @@ -433,6 +459,30 @@ void DaikinS21::set_swing_settings(bool swing_v, bool swing_h) {
}
}

void DaikinS21::set_powerful_settings(bool value)
{
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> code,
std::vector<uint8_t> payload) {
std::vector<uint8_t> frame;
Expand All @@ -446,6 +496,7 @@ bool DaikinS21::send_cmd(std::vector<uint8_t> 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;
Expand Down
11 changes: 11 additions & 0 deletions components/daikin_s21/s21.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t> &payload);
Expand All @@ -63,6 +70,7 @@ class DaikinS21 : public PollingComponent {
bool run_queries(std::vector<std::string> 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};
Expand All @@ -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 {
Expand Down