diff --git a/esphome/components/airton/airton.cpp b/esphome/components/airton/airton.cpp index 2e13dc3d5c..8b2daee42a 100644 --- a/esphome/components/airton/airton.cpp +++ b/esphome/components/airton/airton.cpp @@ -6,6 +6,45 @@ namespace airton { static const char *const TAG = "airton.climate"; +void AirtonClimate::set_sleep_state(bool state) { + if (state != this->settings_.sleep_state) { + this->settings_.sleep_state = state; +#ifdef USE_SWITCH + this->sleep_switch_->publish_state(state); +#endif + this->airton_rtc_.save(&this->settings_); + } +} + +bool AirtonClimate::get_sleep_state() const { return this->settings_.sleep_state; } + +void AirtonClimate::set_display_state(bool state) { + if (state != this->settings_.display_state) { + this->settings_.display_state = state; +#ifdef USE_SWITCH + this->display_switch_->publish_state(state); +#endif + this->airton_rtc_.save(&this->settings_); + } +} + +bool AirtonClimate::get_display_state() const { return this->settings_.display_state; } + +#ifdef USE_SWITCH +void AirtonClimate::set_sleep_switch(switch_::Switch *sw) { + this->sleep_switch_ = sw; + if (this->sleep_switch_ != nullptr) { + this->sleep_switch_->publish_state(this->get_sleep_state()); + } +} +void AirtonClimate::set_display_switch(switch_::Switch *sw) { + this->display_switch_ = sw; + if (this->display_switch_ != nullptr) { + this->display_switch_->publish_state(this->get_display_state()); + } +} +#endif // USE_SWITCH + uint8_t AirtonClimate::get_previous_mode_() { return previous_mode_; } void AirtonClimate::set_previous_mode_(uint8_t mode) { previous_mode_ = mode; } @@ -115,7 +154,6 @@ bool AirtonClimate::turbo_control_() { } uint8_t AirtonClimate::temperature_() { - // Force 20C degrees in Fan only mode switch (this->mode) { case climate::CLIMATE_MODE_HEAT_COOL: // Fixed 25C setpoint in Auto mode @@ -146,7 +184,13 @@ uint8_t AirtonClimate::operation_settings_() { if (this->mode == climate::CLIMATE_MODE_HEAT) { // Set heating bit if on the corresponding mode settings |= (1 << 4); } - settings |= 0b11000100; // Set Light, Health and NotAutoOn bits as per default + if (this->get_display_state()) { // Set LED display + settings |= (1 << 7); + } + if (this->get_sleep_state()) { // Set sleep mode + settings |= (1 << 1); + } + settings |= 0b01000100; // Set Health and NotAutoOn bits as per default return settings; } @@ -217,6 +261,12 @@ bool AirtonClimate::parse_state_frame_(uint8_t const frame[]) { break; } + uint8_t display_light = frame[5] & 0b10000000; // Mask anything but the MSB + this->set_display_state(display_light != 0); + + uint8_t sleep_mode = frame[5] & 0b00000010; // Mask anything but the second bit + this->set_sleep_state(sleep_mode != 0); + this->publish_state(); return true; } diff --git a/esphome/components/airton/airton.h b/esphome/components/airton/airton.h index 98832e9719..0c33f297e8 100644 --- a/esphome/components/airton/airton.h +++ b/esphome/components/airton/airton.h @@ -2,6 +2,10 @@ #include "esphome/components/climate_ir/climate_ir.h" +#ifdef USE_SWITCH +#include "esphome/components/switch/switch.h" +#endif + namespace esphome { namespace airton { @@ -37,13 +41,32 @@ const uint32_t AIRTON_MESSAGE_SPACE = 100000; // State Frame size const uint8_t AIRTON_STATE_FRAME_SIZE = 7; +// Specific internal unit settings +struct AirtonSettings { + bool sleep_state; + bool display_state; +}; + class AirtonClimate : public climate_ir::ClimateIR { +#ifdef USE_SWITCH + public: + void set_sleep_switch(switch_::Switch *sw); + void set_display_switch(switch_::Switch *sw); + + protected: + switch_::Switch *sleep_switch_{nullptr}; + switch_::Switch *display_switch_{nullptr}; +#endif public: AirtonClimate() : climate_ir::ClimateIR(AIRTON_TEMP_MIN, AIRTON_TEMP_MAX, 1.0f, true, true, {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {} + void set_sleep_state(bool state); + bool get_sleep_state() const; + void set_display_state(bool state); + bool get_display_state() const; private: // Save the previous operation mode inside instance @@ -52,6 +75,8 @@ class AirtonClimate : public climate_ir::ClimateIR { protected: uint8_t get_previous_mode_(); void set_previous_mode_(uint8_t mode); + AirtonSettings settings_; + ESPPreferenceObject airton_rtc_; // IR transmission payload builder void transmit_state() override; diff --git a/esphome/components/airton/climate.py b/esphome/components/airton/climate.py index a16fe61079..132777b6c0 100644 --- a/esphome/components/airton/climate.py +++ b/esphome/components/airton/climate.py @@ -1,3 +1,4 @@ +from esphome import automation import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv @@ -8,12 +9,49 @@ AUTO_LOAD = ["climate_ir"] airton_ns = cg.esphome_ns.namespace("airton") AirtonClimate = airton_ns.class_("AirtonClimate", climate_ir.ClimateIR) +CONF_AIRTON_ID = "airton_id" + CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(AirtonClimate), } ) +DisplayOnAction = airton_ns.class_("DisplayOnAction", automation.Action) +DisplayOffAction = airton_ns.class_("DisplayOffAction", automation.Action) +SleepOnAction = airton_ns.class_("SleepOnAction", automation.Action) +SleepOffAction = airton_ns.class_("SleepOffAction", automation.Action) + +AIRTON_ACTION_SCHEMA = automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(AirtonClimate), + } +) + + +@automation.register_action( + "climate_ir.airton.display_on", DisplayOnAction, AIRTON_ACTION_SCHEMA +) +@automation.register_action( + "climate_ir.airton.display_off", DisplayOffAction, AIRTON_ACTION_SCHEMA +) +async def display_action_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + return var + + +@automation.register_action( + "climate_ir.airton.sleep_on", SleepOnAction, AIRTON_ACTION_SCHEMA +) +@automation.register_action( + "climate_ir.airton.sleep_off", SleepOffAction, AIRTON_ACTION_SCHEMA +) +async def sleep_action_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + return var + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) diff --git a/esphome/components/airton/switch/__init__.py b/esphome/components/airton/switch/__init__.py new file mode 100644 index 0000000000..a9b7e053a3 --- /dev/null +++ b/esphome/components/airton/switch/__init__.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import CONF_DISPLAY, ENTITY_CATEGORY_CONFIG + +from ..climate import CONF_AIRTON_ID, AirtonClimate, airton_ns + +CODEOWNERS = ["@procsiab"] + +CONF_SLEEP_MODE = "sleep" + +SleepSwitch = airton_ns.class_("SleepSwitch", switch.Switch) +DisplaySwitch = airton_ns.class_("DisplaySwitch", switch.Switch) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_AIRTON_ID): cv.use_id(AirtonClimate), + cv.Optional(CONF_DISPLAY): switch.switch_schema( + DisplaySwitch, + entity_category=ENTITY_CATEGORY_CONFIG, + default_restore_mode="DISABLED", + ), + cv.Optional(CONF_SLEEP_MODE): switch.switch_schema( + SleepSwitch, + entity_category=ENTITY_CATEGORY_CONFIG, + default_restore_mode="DISABLED", + ), + } +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_AIRTON_ID]) + + for switch_type in [CONF_DISPLAY, CONF_SLEEP_MODE]: + if conf := config.get(switch_type): + sw_var = await switch.new_switch(conf) + await cg.register_parented(sw_var, parent) + cg.add(getattr(parent, f"set_{switch_type}_switch")(sw_var)) diff --git a/esphome/components/airton/switch/display.cpp b/esphome/components/airton/switch/display.cpp new file mode 100644 index 0000000000..1322d40c95 --- /dev/null +++ b/esphome/components/airton/switch/display.cpp @@ -0,0 +1,14 @@ +#include "display.h" + +namespace esphome { +namespace airton { + +void DisplaySwitch::write_state(bool state) { + if (this->parent_->get_display_state() != state) { + this->parent_->set_display_state(state); + } + this->publish_state(state); +} + +} // namespace airton +} // namespace esphome diff --git a/esphome/components/airton/switch/display.h b/esphome/components/airton/switch/display.h new file mode 100644 index 0000000000..82d193a8ec --- /dev/null +++ b/esphome/components/airton/switch/display.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "../airton.h" + +namespace esphome { +namespace airton { + +class DisplaySwitch : public switch_::Switch, public Parented { + public: + DisplaySwitch() = default; + + protected: + void write_state(bool state) override; +}; + +} // namespace airton +} // namespace esphome diff --git a/esphome/components/airton/switch/sleep.cpp b/esphome/components/airton/switch/sleep.cpp new file mode 100644 index 0000000000..6e7f08f5fb --- /dev/null +++ b/esphome/components/airton/switch/sleep.cpp @@ -0,0 +1,14 @@ +#include "sleep.h" + +namespace esphome { +namespace airton { + +void SleepSwitch::write_state(bool state) { + if (this->parent_->get_sleep_state() != state) { + this->parent_->set_sleep_state(state); + } + this->publish_state(state); +} + +} // namespace airton +} // namespace esphome diff --git a/esphome/components/airton/switch/sleep.h b/esphome/components/airton/switch/sleep.h new file mode 100644 index 0000000000..ae897b1c45 --- /dev/null +++ b/esphome/components/airton/switch/sleep.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esphome/components/switch/switch.h" +#include "../airton.h" + +namespace esphome { +namespace airton { + +class SleepSwitch : public switch_::Switch, public Parented { + public: + SleepSwitch() = default; + + protected: + void write_state(bool state) override; +}; + +} // namespace airton +} // namespace esphome diff --git a/tests/components/airton/test.esp32-ard.yaml b/tests/components/airton/test.esp32-ard.yaml index 6b8d2fb3d7..26abc96c83 100644 --- a/tests/components/airton/test.esp32-ard.yaml +++ b/tests/components/airton/test.esp32-ard.yaml @@ -23,3 +23,10 @@ sensor: - platform: template id: airton_sensor lambda: "return 21;" + +switch: + - platform: airton + sleep: + name: "sleep mode" + display: + name: "display light" diff --git a/tests/components/airton/test.esp32-c3-ard.yaml b/tests/components/airton/test.esp32-c3-ard.yaml index 6b8d2fb3d7..26abc96c83 100644 --- a/tests/components/airton/test.esp32-c3-ard.yaml +++ b/tests/components/airton/test.esp32-c3-ard.yaml @@ -23,3 +23,10 @@ sensor: - platform: template id: airton_sensor lambda: "return 21;" + +switch: + - platform: airton + sleep: + name: "sleep mode" + display: + name: "display light" diff --git a/tests/components/airton/test.esp32-c3-idf.yaml b/tests/components/airton/test.esp32-c3-idf.yaml index 6b8d2fb3d7..26abc96c83 100644 --- a/tests/components/airton/test.esp32-c3-idf.yaml +++ b/tests/components/airton/test.esp32-c3-idf.yaml @@ -23,3 +23,10 @@ sensor: - platform: template id: airton_sensor lambda: "return 21;" + +switch: + - platform: airton + sleep: + name: "sleep mode" + display: + name: "display light" diff --git a/tests/components/airton/test.esp32-idf.yaml b/tests/components/airton/test.esp32-idf.yaml index 6b8d2fb3d7..26abc96c83 100644 --- a/tests/components/airton/test.esp32-idf.yaml +++ b/tests/components/airton/test.esp32-idf.yaml @@ -23,3 +23,10 @@ sensor: - platform: template id: airton_sensor lambda: "return 21;" + +switch: + - platform: airton + sleep: + name: "sleep mode" + display: + name: "display light" diff --git a/tests/components/airton/test.esp8266-ard.yaml b/tests/components/airton/test.esp8266-ard.yaml index 6b8d2fb3d7..26abc96c83 100644 --- a/tests/components/airton/test.esp8266-ard.yaml +++ b/tests/components/airton/test.esp8266-ard.yaml @@ -23,3 +23,10 @@ sensor: - platform: template id: airton_sensor lambda: "return 21;" + +switch: + - platform: airton + sleep: + name: "sleep mode" + display: + name: "display light"