From 102a9572b38e3585a3c0883555fc9febdf6a0a57 Mon Sep 17 00:00:00 2001 From: Anton Tanasenko Date: Wed, 14 Feb 2024 12:58:09 +0200 Subject: [PATCH] Allow configuring alt and swing modes --- components/samsung_ac/__init__.py | 83 +++++++++++++++++++++ components/samsung_ac/conversions.cpp | 76 ++++++++----------- components/samsung_ac/conversions.h | 6 +- components/samsung_ac/protocol.h | 16 ++-- components/samsung_ac/protocol_nasa.cpp | 45 +---------- components/samsung_ac/protocol_non_nasa.cpp | 2 +- components/samsung_ac/samsung_ac_device.cpp | 62 ++++++++++----- components/samsung_ac/samsung_ac_device.h | 60 ++++++++++++++- 8 files changed, 226 insertions(+), 124 deletions(-) diff --git a/components/samsung_ac/__init__.py b/components/samsung_ac/__init__.py index 9db631e..29c84fd 100644 --- a/components/samsung_ac/__init__.py +++ b/components/samsung_ac/__init__.py @@ -50,6 +50,13 @@ CONF_DEVICE_ID = "samsung_ac_device_id" CONF_DEVICE_ADDRESS = "address" +CONF_CAPABILITIES = "capabilities" +CONF_HORIZONTAL_SWING = "horizontal_swing" +CONF_VERTICAL_SWING = "vertical_swing" +CONF_PRESETS = "presets" +CONF_PRESET_NAME = "name" +CONF_PRESET_ENABLED = "enabled" +CONF_PRESET_VALUE = "value" CONF_DEVICE_ROOM_TEMPERATURE = "room_temperature" CONF_DEVICE_TARGET_TEMPERATURE = "target_temperature" CONF_DEVICE_OUTDOOR_TEMPERATURE = "outdoor_temperature" @@ -63,6 +70,36 @@ CONF_DEVICE_ROOM_HUMIDITY = "room_humidity" CONF_DEVICE_WATER_TEMPERATURE = "water_temperature" +def preset_entry( + name: str, + value: int, + displayName: str +): return ( + cv.Optional(name, default=False), cv.Any(cv.boolean, cv.All({ + cv.Optional(CONF_PRESET_ENABLED, default=False): cv.boolean, + cv.Optional(CONF_PRESET_NAME, default=displayName): cv.string, + cv.Optional(CONF_PRESET_VALUE, default=value): cv.int_ + })) +) + +PRESETS = { + "sleep": {"value": 1, "displayName": "Sleep"}, + "quiet": {"value": 2, "displayName": "Quiet"}, + "fast": {"value": 3, "displayName": "Fast"}, + "longreach": {"value": 6, "displayName": "LongReach"}, + "windfree": {"value": 9, "displayName": "WindFree"}, +} + +CAPABILITIES_SCHEMA = ( + cv.Schema({ + cv.Optional(CONF_HORIZONTAL_SWING, default=False): cv.boolean, + cv.Optional(CONF_VERTICAL_SWING, default=False): cv.boolean, + cv.Optional(CONF_PRESETS): cv.Schema(dict( + [preset_entry(name, PRESETS[name]["value"], PRESETS[name]["displayName"]) for name in PRESETS] + )) + }) +) + CUSTOM_SENSOR_SCHEMA = sensor.sensor_schema().extend({ cv.Required(CONF_DEVICE_MESSAGE): cv.hex_int, }) @@ -115,6 +152,7 @@ def humidity_sensor_schema(message: int): cv.Schema( { cv.GenerateID(CONF_DEVICE_ID): cv.declare_id(Samsung_AC_Device), + cv.Optional(CONF_CAPABILITIES): CAPABILITIES_SCHEMA, cv.Required(CONF_DEVICE_ADDRESS): cv.string, cv.Optional(CONF_DEVICE_ROOM_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, @@ -168,6 +206,7 @@ def humidity_sensor_schema(message: int): cv.Optional(CONF_DEBUG_MQTT_PASSWORD, default=""): cv.string, cv.Optional(CONF_DEBUG_LOG_MESSAGES, default=False): cv.boolean, cv.Optional(CONF_DEBUG_LOG_MESSAGES_RAW, default=False): cv.boolean, + cv.Optional(CONF_CAPABILITIES): CAPABILITIES_SCHEMA, cv.Required(CONF_DEVICES): cv.ensure_list(DEVICE_SCHEMA), } ) @@ -186,6 +225,50 @@ async def to_code(config): var_dev = cg.new_Pvariable( device[CONF_DEVICE_ID], device[CONF_DEVICE_ADDRESS], var) + # setup capabilities + if CONF_CAPABILITIES in device and CONF_VERTICAL_SWING in device[CONF_CAPABILITIES]: + cg.add(var_dev.set_supports_vertical_swing(device[CONF_CAPABILITIES][CONF_VERTICAL_SWING])) + elif CONF_CAPABILITIES in config and CONF_VERTICAL_SWING in config[CONF_CAPABILITIES]: + cg.add(var_dev.set_supports_vertical_swing(config[CONF_CAPABILITIES][CONF_VERTICAL_SWING])) + + if CONF_CAPABILITIES in device and CONF_HORIZONTAL_SWING in device[CONF_CAPABILITIES]: + cg.add(var_dev.set_supports_horizontal_swing(device[CONF_CAPABILITIES][CONF_HORIZONTAL_SWING])) + elif CONF_CAPABILITIES in config and CONF_HORIZONTAL_SWING in config[CONF_CAPABILITIES]: + cg.add(var_dev.set_supports_horizontal_swing(config[CONF_CAPABILITIES][CONF_HORIZONTAL_SWING])) + + + none_added = False + for preset in PRESETS: + device_preset_conf = device[CONF_CAPABILITIES][CONF_PRESETS][preset] if ( + CONF_CAPABILITIES in device + and CONF_PRESETS in device[CONF_CAPABILITIES] + and preset in device[CONF_CAPABILITIES][CONF_PRESETS]) else None + global_preset_conf = config[CONF_CAPABILITIES][CONF_PRESETS][preset] if ( + CONF_CAPABILITIES in config + and CONF_PRESETS in config[CONF_CAPABILITIES] + and preset in config[CONF_CAPABILITIES][CONF_PRESETS]) else None + + preset_conf = global_preset_conf if device_preset_conf is None else device_preset_conf + preset_dict = isinstance(preset_conf, dict) + if preset_conf == True or (preset_dict and preset_conf[CONF_PRESET_ENABLED] == True): + if not none_added: + none_added = True + cg.add(var_dev.add_alt_mode("None", 0)) + + cg.add(var_dev.add_alt_mode( + preset_conf[CONF_PRESET_NAME] if preset_dict and CONF_PRESET_NAME in preset_conf else PRESETS[preset]["displayName"], + preset_conf[CONF_PRESET_VALUE] if preset_dict and CONF_PRESET_VALUE in preset_conf else PRESETS[preset]["value"] + )) + +# if CONF_CAPABILITIES in device and CONF_ALT_MODES in device[CONF_CAPABILITIES]: +# cg.add(var_dev.add_alt_mode("None", 0)) +# for alt in device[CONF_CAPABILITIES][CONF_ALT_MODES]: +# cg.add(var_dev.add_alt_mode(alt[CONF_ALT_MODE_NAME], alt[CONF_ALT_MODE_VALUE])) +# elif CONF_CAPABILITIES in config and CONF_ALT_MODES in config[CONF_CAPABILITIES]: +# cg.add(var_dev.add_alt_mode("None", 0)) +# for alt in config[CONF_CAPABILITIES][CONF_ALT_MODES]: +# cg.add(var_dev.add_alt_mode(alt[CONF_ALT_MODE_NAME], alt[CONF_ALT_MODE_VALUE])) + if CONF_DEVICE_POWER in device: conf = device[CONF_DEVICE_POWER] sens = await switch.new_switch(conf) diff --git a/components/samsung_ac/conversions.cpp b/components/samsung_ac/conversions.cpp index eedc172..ba6ee24 100644 --- a/components/samsung_ac/conversions.cpp +++ b/components/samsung_ac/conversions.cpp @@ -128,59 +128,49 @@ namespace esphome return FanMode::Auto; } - AltMode preset_to_altmode(climate::ClimatePreset preset) + AltModeName preset_to_altmodename(climate::ClimatePreset preset) { switch (preset) { + case climate::ClimatePreset::CLIMATE_PRESET_ECO: + return "Eco"; + case climate::ClimatePreset::CLIMATE_PRESET_AWAY: + return "Away"; + case climate::ClimatePreset::CLIMATE_PRESET_BOOST: + return "Boost"; + case climate::ClimatePreset::CLIMATE_PRESET_COMFORT: + return "Comfort"; + case climate::ClimatePreset::CLIMATE_PRESET_HOME: + return "Home"; case climate::ClimatePreset::CLIMATE_PRESET_SLEEP: - return AltMode::Sleep; + return "Sleep"; + case climate::ClimatePreset::CLIMATE_PRESET_ACTIVITY: + return "Activity"; case climate::ClimatePreset::CLIMATE_PRESET_NONE: default: - return AltMode::None; + return "None"; } } - AltMode custompreset_to_altmode(const std::string &value) + optional altmodename_to_preset(const AltModeName& name) { - if (value == "Quiet") - return AltMode::Quiet; - if (value == "Fast") - return AltMode::Fast; - if (value == "Long Reach") - return AltMode::LongReach; - if (value == "WindFree") - return AltMode::Windfree; - return AltMode::Unknown; - } - - optional altmode_to_preset(AltMode mode) - { - switch (mode) - { - case AltMode::None: - return climate::ClimatePreset::CLIMATE_PRESET_NONE; - case AltMode::Sleep: - return climate::ClimatePreset::CLIMATE_PRESET_SLEEP; - default: - return nullopt; - }; - } - - std::string altmode_to_custompreset(AltMode mode) - { - switch (mode) - { - case AltMode::Quiet: - return "Quiet"; - case AltMode::Fast: - return "Fast"; - case AltMode::LongReach: - return "Long Reach"; - case AltMode::Windfree: - return "WindFree"; - default: - return ""; - }; + if (str_equals_case_insensitive(name, "ECO")) + return optional(climate::CLIMATE_PRESET_ECO); + if (str_equals_case_insensitive(name, "AWAY")) + return optional(climate::CLIMATE_PRESET_AWAY); + if (str_equals_case_insensitive(name, "BOOST")) + return optional(climate::CLIMATE_PRESET_BOOST); + if (str_equals_case_insensitive(name, "COMFORT")) + return optional(climate::CLIMATE_PRESET_COMFORT); + if (str_equals_case_insensitive(name, "HOME")) + return optional(climate::CLIMATE_PRESET_HOME); + if (str_equals_case_insensitive(name, "SLEEP")) + return optional(climate::CLIMATE_PRESET_SLEEP); + if (str_equals_case_insensitive(name, "ACTIVITY")) + return optional(climate::CLIMATE_PRESET_ACTIVITY); + if (str_equals_case_insensitive(name, "NONE")) + return optional(climate::CLIMATE_PRESET_NONE); + return nullopt; } climate::ClimateSwingMode swingmode_to_climateswingmode(SwingMode swingMode) diff --git a/components/samsung_ac/conversions.h b/components/samsung_ac/conversions.h index f38c22e..8bfb621 100644 --- a/components/samsung_ac/conversions.h +++ b/components/samsung_ac/conversions.h @@ -19,10 +19,8 @@ namespace esphome FanMode climatefanmode_to_fanmode(climate::ClimateFanMode fanmode); FanMode customfanmode_to_fanmode(const std::string &value); - optional altmode_to_preset(AltMode mode); - std::string altmode_to_custompreset(AltMode mode); - AltMode preset_to_altmode(climate::ClimatePreset preset); - AltMode custompreset_to_altmode(const std::string &value); + AltModeName preset_to_altmodename(climate::ClimatePreset preset); + optional altmodename_to_preset(const AltModeName& name); climate::ClimateSwingMode swingmode_to_climateswingmode(SwingMode swingMode); SwingMode climateswingmode_to_swingmode(climate::ClimateSwingMode swingMode); diff --git a/components/samsung_ac/protocol.h b/components/samsung_ac/protocol.h index f1d977a..cac09ee 100644 --- a/components/samsung_ac/protocol.h +++ b/components/samsung_ac/protocol.h @@ -42,15 +42,13 @@ namespace esphome Off = 5 }; - enum class AltMode + typedef std::string AltModeName; + typedef uint8_t AltMode; + + struct AltModeDesc { - Unknown = -1, - None = 0, - Sleep = 1, - Quiet = 2, - Fast = 3, - LongReach = 4, - Windfree = 5 + AltModeName name; + AltMode value; }; enum class SwingMode : uint8_t @@ -73,7 +71,7 @@ namespace esphome virtual void set_outdoor_temperature(const std::string address, float value) = 0; virtual void set_mode(const std::string address, Mode mode) = 0; virtual void set_fanmode(const std::string address, FanMode fanmode) = 0; - virtual void set_altmode(const std::string address, AltMode fanmode) = 0; + virtual void set_altmode(const std::string address, AltMode altmode) = 0; virtual void set_swing_vertical(const std::string address, bool vertical) = 0; virtual void set_swing_horizontal(const std::string address, bool horizontal) = 0; virtual optional> get_custom_sensors(const std::string address) = 0; diff --git a/components/samsung_ac/protocol_nasa.cpp b/components/samsung_ac/protocol_nasa.cpp index 117a714..970a916 100644 --- a/components/samsung_ac/protocol_nasa.cpp +++ b/components/samsung_ac/protocol_nasa.cpp @@ -367,26 +367,6 @@ namespace esphome } } - int altmode_to_nasa_altmode(AltMode mode) - { - switch (mode) - { - case AltMode::Sleep: - return 1; - case AltMode::Quiet: - return 2; - case AltMode::Fast: - return 3; - case AltMode::LongReach: - return 6; - case AltMode::Windfree: - return 9; - case AltMode::None: - default: - return 0; - } - } - void NasaProtocol::publish_request(MessageTarget *target, const std::string &address, ProtocolRequest &request) { Packet packet = Packet::createa_partial(Address::parse(address), DataType::Request); @@ -424,7 +404,7 @@ namespace esphome if (request.alt_mode) { MessageSet altmode(MessageNumber::ENUM_in_alt_mode); - altmode.value = altmode_to_nasa_altmode(request.alt_mode.value()); + altmode.value = request.alt_mode.value(); packet.messages.push_back(altmode); } @@ -501,27 +481,6 @@ namespace esphome } } - AltMode altmode_to_nasa_altmode(int value) - { - switch (value) - { - case 0: - return AltMode::None; - case 1: - return AltMode::Sleep; - case 2: - return AltMode::Quiet; - case 3: - return AltMode::Fast; - case 6: - return AltMode::LongReach; - case 9: - return AltMode::Windfree; - default: - return AltMode::Unknown; - } - } - void process_messageset(std::string source, std::string dest, MessageSet &message, optional> &custom, MessageTarget *target) { if (debug_mqtt_connected()) @@ -605,7 +564,7 @@ namespace esphome case MessageNumber::ENUM_in_alt_mode: { ESP_LOGW(TAG, "s:%s d:%s ENUM_in_alt_mode %li", source.c_str(), dest.c_str(), message.value); - target->set_altmode(source, altmode_to_nasa_altmode(message.value)); + target->set_altmode(source, message.value); return; } case MessageNumber::ENUM_in_louver_hl_swing: diff --git a/components/samsung_ac/protocol_non_nasa.cpp b/components/samsung_ac/protocol_non_nasa.cpp index cff2907..48cd139 100644 --- a/components/samsung_ac/protocol_non_nasa.cpp +++ b/components/samsung_ac/protocol_non_nasa.cpp @@ -497,7 +497,7 @@ namespace esphome target->set_mode(nonpacket_.src, nonnasa_mode_to_mode(nonpacket_.command20.mode)); target->set_fanmode(nonpacket_.src, nonnasa_fanspeed_to_fanmode(nonpacket_.command20.fanspeed)); // TODO - target->set_altmode(nonpacket_.src, AltMode::None); + target->set_altmode(nonpacket_.src, 0); // TODO target->set_swing_horizontal(nonpacket_.src, false); target->set_swing_vertical(nonpacket_.src, false); diff --git a/components/samsung_ac/samsung_ac_device.cpp b/components/samsung_ac/samsung_ac_device.cpp index df725c8..b3803a3 100644 --- a/components/samsung_ac/samsung_ac_device.cpp +++ b/components/samsung_ac/samsung_ac_device.cpp @@ -4,6 +4,7 @@ #include "conversions.h" #include #include +#include namespace esphome { @@ -49,24 +50,33 @@ namespace esphome customFan.insert("Turbo"); traits.set_supported_custom_fan_modes(customFan); - std::set preset; - preset.insert(climate::ClimatePreset::CLIMATE_PRESET_NONE); - preset.insert(climate::ClimatePreset::CLIMATE_PRESET_SLEEP); - traits.set_supported_presets(preset); - - std::set customPreset; - customPreset.insert("Quiet"); - customPreset.insert("Fast"); - customPreset.insert("Long Reach"); - customPreset.insert("WindFree"); - traits.set_supported_custom_presets(customPreset); - - std::set swingMode; - swingMode.insert(climate::ClimateSwingMode::CLIMATE_SWING_OFF); - swingMode.insert(climate::ClimateSwingMode::CLIMATE_SWING_HORIZONTAL); - swingMode.insert(climate::ClimateSwingMode::CLIMATE_SWING_VERTICAL); - swingMode.insert(climate::ClimateSwingMode::CLIMATE_SWING_BOTH); - traits.set_supported_swing_modes(swingMode); + auto supported = device->get_supported_alt_modes(); + if (supported != nullptr && !supported->empty()) + { + std::set presets; + std::set custom_presets; + for (const AltModeDesc& mode: *supported) { + auto preset = altmodename_to_preset(mode.name); + if (preset) + presets.insert(preset.value()); + else + custom_presets.insert(mode.name); + }; + traits.set_supported_presets(presets); + traits.set_supported_custom_presets(custom_presets); + } + + bool h = device->supports_horizontal_swing(); + bool v = device->supports_vertical_swing(); + if (h || v) + { + std::set swingMode; + swingMode.insert(climate::ClimateSwingMode::CLIMATE_SWING_OFF); + if (h) swingMode.insert(climate::ClimateSwingMode::CLIMATE_SWING_HORIZONTAL); + if (v) swingMode.insert(climate::ClimateSwingMode::CLIMATE_SWING_VERTICAL); + if (h && v) swingMode.insert(climate::ClimateSwingMode::CLIMATE_SWING_BOTH); + traits.set_supported_swing_modes(swingMode); + } return traits; } @@ -109,13 +119,13 @@ namespace esphome auto presetOpt = call.get_preset(); if (presetOpt.has_value()) { - request.alt_mode = preset_to_altmode(presetOpt.value()); + set_alt_mode_by_name(request, preset_to_altmodename(presetOpt.value())); } auto customPresetOpt = call.get_custom_preset(); if (customPresetOpt.has_value()) { - request.alt_mode = custompreset_to_altmode(customPresetOpt.value()); + set_alt_mode_by_name(request, customPresetOpt.value()); } auto swingModeOpt = call.get_swing_mode(); @@ -126,5 +136,17 @@ namespace esphome device->publish_request(request); } + + void Samsung_AC_Climate::set_alt_mode_by_name(ProtocolRequest &request, const AltModeName &name) + { + auto supported = device->get_supported_alt_modes(); + auto mode = std::find_if(supported->begin(), supported->end(), [&name](const AltModeDesc &x) { return x.name == name;}); + if (mode == supported->end()) + { + ESP_LOGW(TAG, "Unsupported alt_mode %s", name); + return; + } + request.alt_mode = mode->value; + } } // namespace samsung_ac } // namespace esphome diff --git a/components/samsung_ac/samsung_ac_device.h b/components/samsung_ac/samsung_ac_device.h index 6b84bb3..3c8c791 100644 --- a/components/samsung_ac/samsung_ac_device.h +++ b/components/samsung_ac/samsung_ac_device.h @@ -2,6 +2,8 @@ #include #include +#include +#include "esphome/core/helpers.h" #include "esphome/components/switch/switch.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/select/select.h" @@ -24,6 +26,9 @@ namespace esphome climate::ClimateTraits traits(); void control(const climate::ClimateCall &call); Samsung_AC_Device *device; + + protected: + void set_alt_mode_by_name(ProtocolRequest &request, const AltModeName &name); }; class Samsung_AC_Number : public number::Number @@ -213,16 +218,24 @@ namespace esphome { if (climate != nullptr) { - auto preset = altmode_to_preset(value); - if (preset.has_value()) + auto supported = get_supported_alt_modes(); + auto mode = std::find_if(supported->begin(), supported->end(), [&value](const AltModeDesc& x) { return x.value == value; }); + if (mode == supported->end()) + { + ESP_LOGW(TAG, "Unsupported alt_mode %d", value); + return; + } + + auto preset = altmodename_to_preset(mode->name); + if (preset) { - climate->preset = preset; + climate->preset = preset.value(); climate->custom_preset.reset(); } else { climate->preset.reset(); - climate->custom_preset = altmode_to_custompreset(value); + climate->custom_preset = mode->name; } climate->publish_state(); } @@ -275,7 +288,46 @@ namespace esphome protocol->publish_request(target, address, request); } + bool supports_horizontal_swing() + { + return supports_horizontal_swing_; + } + + bool supports_vertical_swing() + { + return supports_vertical_swing_; + } + + void set_supports_horizontal_swing(bool value) + { + supports_horizontal_swing_ = value; + } + + void set_supports_vertical_swing(bool value) + { + supports_vertical_swing_ = value; + } + + void add_alt_mode(const AltModeName &name, AltMode value) + { + AltModeDesc desc; + desc.name = name; + desc.value = value; + alt_modes.push_back(std::move(desc)); + } + + const std::vector *get_supported_alt_modes() + { + if (!alt_modes.empty()) + return &alt_modes; + return nullptr; + } + protected: + bool supports_horizontal_swing_{true}; + bool supports_vertical_swing_{true}; + std::vector alt_modes; + Protocol *protocol{nullptr}; MessageTarget *target{nullptr};