1
0
mirror of https://github.com/esphome/esphome.git synced 2025-03-14 06:38:17 +00:00

Add vertical_direction select comp. for platform airton

A select component called vertical_direction will be available as a
config for the airton climate component; additionally, now changing the
display or sleep switches will immediately send the IR command.
This commit is contained in:
Lorenzo Prosseda 2025-02-19 16:21:44 +01:00
parent 3d324e3534
commit 236f02f573
No known key found for this signature in database
GPG Key ID: 316B7756E0101C16
9 changed files with 248 additions and 31 deletions

View File

@ -6,30 +6,62 @@ namespace airton {
static const char *const TAG = "airton.climate";
void AirtonClimate::set_sleep_mode_state(bool state) {
void AirtonClimate::set_sleep_mode_state(bool state, bool send_ir = false) {
if (state != this->settings_.sleep_state) {
this->settings_.sleep_state = state;
#ifdef USE_SWITCH
this->sleep_mode_switch_->publish_state(state);
#endif
this->airton_rtc_.save(&this->settings_);
if (send_ir)
this->transmit_state();
}
}
bool AirtonClimate::get_sleep_mode_state() const { return this->settings_.sleep_state; }
void AirtonClimate::set_display_state(bool state) {
void AirtonClimate::set_display_state(bool state, bool send_ir = false) {
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_);
if (send_ir)
this->transmit_state();
}
}
bool AirtonClimate::get_display_state() const { return this->settings_.display_state; }
void AirtonClimate::set_vertical_direction_state(VerticalDirection state) {
if (state.toUint8() != this->settings_.vertical_direction_state.toUint8()) {
this->settings_.vertical_direction_state = state;
#ifdef USE_SELECT
this->vertical_direction_select_->publish_state(state.toString());
#endif
this->airton_rtc_.save(&this->settings_);
}
}
void AirtonClimate::set_vertical_direction_state(const std::string state) {
if (state != this->settings_.vertical_direction_state.toString()) {
VerticalDirection new_state;
new_state.setDirection(state);
this->settings_.vertical_direction_state = new_state.getDirection();
#ifdef USE_SELECT
this->vertical_direction_select_->publish_state(state);
#endif
this->airton_rtc_.save(&this->settings_);
// This overloaded function is called only from the select component, upon changing selection
// Therefore, we transmit the updated state after saving it
this->transmit_state();
}
}
VerticalDirection AirtonClimate::get_vertical_direction_state() const {
return this->settings_.vertical_direction_state;
}
#ifdef USE_SWITCH
void AirtonClimate::set_sleep_mode_switch(switch_::Switch *sw) {
this->sleep_mode_switch_ = sw;
@ -45,6 +77,15 @@ void AirtonClimate::set_display_switch(switch_::Switch *sw) {
}
#endif // USE_SWITCH
#ifdef USE_SELECT
void AirtonClimate::set_vertical_direction_select(select::Select *sel) {
this->vertical_direction_select_ = sel;
if (this->vertical_direction_select_ != nullptr) {
this->vertical_direction_select_->publish_state(this->get_vertical_direction_state().toString());
}
}
#endif // USE_SELECT
uint8_t AirtonClimate::get_previous_mode_() { return previous_mode_; }
void AirtonClimate::set_previous_mode_(uint8_t mode) { previous_mode_ = mode; }
@ -68,7 +109,7 @@ void AirtonClimate::transmit_state() {
remote_state[3] |= this->temperature_();
remote_state[4] = 0;
remote_state[4] |= this->swing_mode_();
remote_state[4] |= this->get_vertical_direction_state().toUint8();
remote_state[5] = this->operation_settings_();
@ -165,18 +206,6 @@ uint8_t AirtonClimate::temperature_() {
}
}
uint8_t AirtonClimate::swing_mode_() {
uint8_t swing_control = 0b01100000;
switch (this->swing_mode) {
case climate::CLIMATE_SWING_VERTICAL:
swing_control |= 1;
break;
default:
break;
}
return swing_control;
}
// The bits of this packet's byte have the following meanings (from MSB to LSB)
// Light, Health, Unknown, HeatOn, Unknown, NotAutoOn, Sleep, Econo
uint8_t AirtonClimate::operation_settings_() {
@ -231,19 +260,8 @@ bool AirtonClimate::parse_state_frame_(uint8_t const frame[]) {
} else {
this->mode = climate::CLIMATE_MODE_OFF;
}
uint8_t temperature = frame[3];
this->target_temperature =
(temperature & 0b00001111) + 16; // Mask the higher half of the byte (unused), add back the offset
uint8_t fan_mode = (frame[2] & 0b01110000) >> 4; // Mask anything but bits 5-7, then shift them to the right
uint8_t swing_mode = frame[4] & 0b00000001; // Mask anything but the LSB
if (swing_mode) {
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
} else {
this->swing_mode = climate::CLIMATE_SWING_OFF;
}
switch (fan_mode) {
case AIRTON_FAN_1:
case AIRTON_FAN_2:
@ -261,6 +279,18 @@ bool AirtonClimate::parse_state_frame_(uint8_t const frame[]) {
break;
}
uint8_t temperature = frame[3];
this->target_temperature =
(temperature & 0b00001111) + 16; // Mask the higher half of the byte (unused), add back the offset
uint8_t swing_mode = frame[4] & 0b00001111; // Mask the higher nibble
if (swing_mode == (uint8_t) VerticalDirection::VERTICAL_DIRECTION_OFF) {
this->swing_mode = climate::CLIMATE_SWING_OFF;
} else {
this->swing_mode = climate::CLIMATE_SWING_VERTICAL;
}
this->set_vertical_direction_state(static_cast<VerticalDirection::Direction>(swing_mode));
uint8_t display_light = frame[5] & 0b10000000; // Mask anything but the MSB
this->set_display_state(display_light != 0);

View File

@ -1,11 +1,16 @@
#pragma once
#include "esphome/components/climate_ir/climate_ir.h"
#include "quirks.h"
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
#ifdef USE_SELECT
#include "esphome/components/select/select.h"
#endif
namespace esphome {
namespace airton {
@ -45,8 +50,18 @@ const uint8_t AIRTON_STATE_FRAME_SIZE = 7;
struct AirtonSettings {
bool sleep_state;
bool display_state;
VerticalDirection vertical_direction_state;
};
// Local vertical direction constants
static const VerticalDirection VERTICAL_DIRECTION_OFF = VerticalDirection::VERTICAL_DIRECTION_OFF;
static const VerticalDirection VERTICAL_DIRECTION_SWING = VerticalDirection::VERTICAL_DIRECTION_SWING;
static const VerticalDirection VERTICAL_DIRECTION_UP = VerticalDirection::VERTICAL_DIRECTION_UP;
static const VerticalDirection VERTICAL_DIRECTION_MIDDLE_UP = VerticalDirection::VERTICAL_DIRECTION_MIDDLE_UP;
static const VerticalDirection VERTICAL_DIRECTION_MIDDLE = VerticalDirection::VERTICAL_DIRECTION_MIDDLE;
static const VerticalDirection VERTICAL_DIRECTION_MIDDLE_DOWN = VerticalDirection::VERTICAL_DIRECTION_MIDDLE_DOWN;
static const VerticalDirection VERTICAL_DIRECTION_DOWN = VerticalDirection::VERTICAL_DIRECTION_DOWN;
class AirtonClimate : public climate_ir::ClimateIR {
#ifdef USE_SWITCH
public:
@ -56,6 +71,13 @@ class AirtonClimate : public climate_ir::ClimateIR {
protected:
switch_::Switch *sleep_mode_switch_{nullptr};
switch_::Switch *display_switch_{nullptr};
#endif
#ifdef USE_SELECT
public:
void set_vertical_direction_select(select::Select *sel);
protected:
select::Select *vertical_direction_select_{nullptr};
#endif
public:
AirtonClimate()
@ -63,10 +85,13 @@ class AirtonClimate : public climate_ir::ClimateIR {
{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_mode_state(bool state);
void set_sleep_mode_state(bool state, bool send_ir);
bool get_sleep_mode_state() const;
void set_display_state(bool state);
void set_display_state(bool state, bool send_ir);
bool get_display_state() const;
void set_vertical_direction_state(VerticalDirection state);
void set_vertical_direction_state(std::string state);
VerticalDirection get_vertical_direction_state() const;
private:
// Save the previous operation mode inside instance
@ -85,7 +110,6 @@ class AirtonClimate : public climate_ir::ClimateIR {
uint16_t fan_speed_();
bool turbo_control_();
uint8_t temperature_();
uint8_t swing_mode_();
uint8_t operation_settings_();
uint8_t sum_bytes_(const uint8_t *start, uint16_t length);

View File

@ -11,10 +11,25 @@ AirtonClimate = airton_ns.class_("AirtonClimate", climate_ir.ClimateIR)
CONF_AIRTON_ID = "airton_id"
CONF_SLEEP_MODE = "sleep_mode"
CONF_VERTICAL_DIRECTION = "vertical_direction"
VerticalDirections = airton_ns.enum("VerticalDirections")
VERTICAL_DIRECTIONS = {
"off": VerticalDirections.VERTICAL_DIRECTION_OFF,
"swing": VerticalDirections.VERTICAL_DIRECTION_SWING,
"up": VerticalDirections.VERTICAL_DIRECTION_UP,
"middle-up": VerticalDirections.VERTICAL_DIRECTION_MIDDLE_UP,
"middle": VerticalDirections.VERTICAL_DIRECTION_MIDDLE,
"middle-down": VerticalDirections.VERTICAL_DIRECTION_MIDDLE_DOWN,
"down": VerticalDirections.VERTICAL_DIRECTION_DOWN,
}
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(AirtonClimate),
cv.Optional(CONF_VERTICAL_DIRECTION, default="off"): cv.enum(
VERTICAL_DIRECTIONS
),
}
)
@ -57,3 +72,4 @@ async def sleep_action_to_code(config, action_id, template_arg, args):
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)
cg.add(var.set_vertical_direction_state(config[CONF_VERTICAL_DIRECTION]))

View File

@ -0,0 +1,76 @@
#ifndef AIRTON_QUIRKS_H
#define AIRTON_QUIRKS_H
#include <cstdint>
#include <string>
#include "esphome/core/log.h"
namespace esphome {
namespace airton {
// Vertical direction for air outlet flap
class VerticalDirection {
public:
enum Direction {
VERTICAL_DIRECTION_OFF = 0x00,
VERTICAL_DIRECTION_SWING = 0x01,
VERTICAL_DIRECTION_UP = 0x02,
VERTICAL_DIRECTION_MIDDLE_UP = 0x03,
VERTICAL_DIRECTION_MIDDLE = 0x04,
VERTICAL_DIRECTION_MIDDLE_DOWN = 0x05,
VERTICAL_DIRECTION_DOWN = 0x06,
};
VerticalDirection(Direction direction = Direction::VERTICAL_DIRECTION_OFF) : _direction(direction) {}
std::string toString() const {
switch (_direction) {
case Direction::VERTICAL_DIRECTION_SWING:
return "swing";
case Direction::VERTICAL_DIRECTION_UP:
return "up";
case Direction::VERTICAL_DIRECTION_MIDDLE_UP:
return "middle-up";
case Direction::VERTICAL_DIRECTION_MIDDLE:
return "middle";
case Direction::VERTICAL_DIRECTION_MIDDLE_DOWN:
return "middle-down";
case Direction::VERTICAL_DIRECTION_DOWN:
return "down";
case Direction::VERTICAL_DIRECTION_OFF:
default:
return "off";
}
}
uint8_t toUint8() const { return static_cast<uint8_t>(_direction); }
void setDirection(Direction direction) { _direction = direction; }
void setDirection(std::string direction) {
if (direction == "swing") {
_direction = Direction::VERTICAL_DIRECTION_SWING;
} else if (direction == "up") {
_direction = Direction::VERTICAL_DIRECTION_UP;
} else if (direction == "middle-up") {
_direction = Direction::VERTICAL_DIRECTION_MIDDLE_UP;
} else if (direction == "middle") {
_direction = Direction::VERTICAL_DIRECTION_MIDDLE;
} else if (direction == "middle-down") {
_direction = Direction::VERTICAL_DIRECTION_MIDDLE_DOWN;
} else if (direction == "down") {
_direction = Direction::VERTICAL_DIRECTION_DOWN;
} else {
_direction = Direction::VERTICAL_DIRECTION_OFF;
}
}
Direction getDirection() const { return _direction; }
private:
Direction _direction;
};
} // namespace airton
} // namespace esphome
#endif // AIRTON_QUIRKS_H

View File

@ -0,0 +1,38 @@
import esphome.codegen as cg
from esphome.components import select
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_CONFIG
from ..climate import (
CONF_AIRTON_ID,
CONF_VERTICAL_DIRECTION,
VERTICAL_DIRECTIONS,
AirtonClimate,
airton_ns,
)
CODEOWNERS = ["@procsiab"]
VerticalDirectionSelect = airton_ns.class_("VerticalDirectionSelect", select.Select)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_AIRTON_ID): cv.use_id(AirtonClimate),
cv.Optional(CONF_VERTICAL_DIRECTION): select.select_schema(
VerticalDirectionSelect,
entity_category=ENTITY_CATEGORY_CONFIG,
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_AIRTON_ID])
for select_type in [CONF_VERTICAL_DIRECTION]:
if conf := config.get(select_type):
sel_var = await select.new_select(
conf, options=list(VERTICAL_DIRECTIONS.keys())
)
await cg.register_parented(sel_var, parent)
cg.add(getattr(parent, f"set_{select_type}_select")(sel_var))

View File

@ -0,0 +1,15 @@
#include "direction.h"
namespace esphome {
namespace airton {
void VerticalDirectionSelect::control(const std::string &value) {
if (this->parent_->get_vertical_direction_state().toString() != value) {
ESP_LOGD("vertical_direction", "Select received value: %s", value.c_str());
this->parent_->set_vertical_direction_state(value);
}
this->publish_state(value);
}
} // namespace airton
} // namespace esphome

View File

@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../airton.h"
namespace esphome {
namespace airton {
class VerticalDirectionSelect : public select::Select, public Parented<AirtonClimate> {
public:
VerticalDirectionSelect() = default;
protected:
void control(const std::string &value) override;
};
} // namespace airton
} // namespace esphome

View File

@ -5,7 +5,7 @@ namespace airton {
void DisplaySwitch::write_state(bool state) {
if (this->parent_->get_display_state() != state) {
this->parent_->set_display_state(state);
this->parent_->set_display_state(state, true);
}
this->publish_state(state);
}

View File

@ -5,7 +5,7 @@ namespace airton {
void SleepSwitch::write_state(bool state) {
if (this->parent_->get_sleep_mode_state() != state) {
this->parent_->set_sleep_mode_state(state);
this->parent_->set_sleep_mode_state(state, true);
}
this->publish_state(state);
}