From 887d4c3a8337d4c62c391b90176fe97b9b1ee798 Mon Sep 17 00:00:00 2001 From: Ryan Ludwig <20423360+steamEngineer@users.noreply.github.com> Date: Tue, 13 Oct 2026 20:36:15 -0700 Subject: [PATCH 1/3] [esp32_can] Add NO_ACK mode (#20706) Co-authored-by: Cursor --- esphome/components/esp32_can/canbus.py | 1 + esphome/components/esp32_can/esp32_can.cpp | 7 +++++-- esphome/components/esp32_can/esp32_can.h | 1 + tests/components/esp32_can/common.yaml | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index ae299370cd7..f8f76b2d578 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -47,6 +47,7 @@ CanMode = esp32_can_ns.enum("CanMode") CAN_MODES = { "NORMAL": CanMode.CAN_MODE_NORMAL, "LISTENONLY": CanMode.CAN_MODE_LISTEN_ONLY, + "NO_ACK": CanMode.CAN_MODE_NO_ACK, } # Currently the driver only supports a subset of the bit rates defined in canbus diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index d84b58c5b0c..ee677a08005 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -79,10 +79,13 @@ bool ESP32Can::setup_internal() { } // Select TWAI mode based on configuration - twai_mode_t twai_mode = (this->mode_ == CAN_MODE_LISTEN_ONLY) ? TWAI_MODE_LISTEN_ONLY : TWAI_MODE_NORMAL; - + twai_mode_t twai_mode = TWAI_MODE_NORMAL; if (this->mode_ == CAN_MODE_LISTEN_ONLY) { + twai_mode = TWAI_MODE_LISTEN_ONLY; ESP_LOGI(TAG, "CAN bus configured in LISTEN_ONLY mode (passive, no ACKs)"); + } else if (this->mode_ == CAN_MODE_NO_ACK) { + twai_mode = TWAI_MODE_NO_ACK; + ESP_LOGI(TAG, "CAN bus configured in NO_ACK mode (transmit without requiring ACK)"); } twai_general_config_t g_config = diff --git a/esphome/components/esp32_can/esp32_can.h b/esphome/components/esp32_can/esp32_can.h index f9549bea346..176a3b57f5e 100644 --- a/esphome/components/esp32_can/esp32_can.h +++ b/esphome/components/esp32_can/esp32_can.h @@ -12,6 +12,7 @@ namespace esphome::esp32_can { enum CanMode : uint8_t { CAN_MODE_NORMAL = 0, CAN_MODE_LISTEN_ONLY = 1, + CAN_MODE_NO_ACK = 2, }; class ESP32Can final : public canbus::Canbus { diff --git a/tests/components/esp32_can/common.yaml b/tests/components/esp32_can/common.yaml index a26c5d35a34..01b208995df 100644 --- a/tests/components/esp32_can/common.yaml +++ b/tests/components/esp32_can/common.yaml @@ -18,7 +18,7 @@ canbus: tx_pin: ${tx_pin} can_id: 4 bit_rate: 50kbps - mode: NORMAL + mode: NO_ACK on_frame: - can_id: 500 then: From 60a1d70805b4cc810928ea4174af3c7b3bcc8fde Mon Sep 17 00:00:00 2001 From: Dmitrii Kuminov Date: Tue, 13 Oct 2026 20:46:54 -0700 Subject: [PATCH 2/3] [core] Move deferred include processing to parse_yaml (#16344) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- esphome/yaml_util.py | 14 ++++++++++---- tests/unit_tests/test_yaml_util.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index b5e2ec17e8a..261d8b50e36 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -1120,8 +1120,12 @@ def _load_yaml_internal(fname: Path, *, track_document_range: bool = True) -> An res = parse_yaml(fname, f_handle, track_document_range=track_document_range) except (UnicodeDecodeError, OSError) as err: raise EsphomeError(f"Error reading file {fname}: {err}") from err - # Top-level !include returns a deferred IncludeFile; resolve it so - # callers always receive the final content. + return res + + +def _resolve_include(res: Any) -> Any: + # A document that is just `!include` parses to a deferred IncludeFile; resolve it so + # every parse_yaml caller, including the VS Code and ACE loaders, gets the final content. if isinstance(res, IncludeFile): res = res.load() if res.should_load() else {} return res @@ -1147,7 +1151,7 @@ def parse_yaml( # tracked mode; reject the combination instead of half-applying it. raise ValueError("track_document_range=False requires the default yaml_loader") try: - return _load_yaml_internal_with_type( + res = _load_yaml_internal_with_type( ESPHomeLoader, file_name, file_handle, @@ -1159,13 +1163,15 @@ def parse_yaml( # readable exceptions # Rewind the stream so we can try again file_handle.seek(0, 0) - return _load_yaml_internal_with_type( + res = _load_yaml_internal_with_type( ESPHomePurePythonLoader, file_name, file_handle, yaml_loader, track_document_range=track_document_range, ) + # Resolved after the fallback so a failed first parse never loads the include twice + return _resolve_include(res) def _load_yaml_internal_with_type( diff --git a/tests/unit_tests/test_yaml_util.py b/tests/unit_tests/test_yaml_util.py index c3276823ecc..655fac6bf26 100644 --- a/tests/unit_tests/test_yaml_util.py +++ b/tests/unit_tests/test_yaml_util.py @@ -954,6 +954,23 @@ def test_yaml_merge_chain_include_resolves() -> None: assert config["base"]["existing"] == "value" +def test_parse_yaml_custom_loader_resolves_root_include() -> None: + """A document that is only `!include` is resolved for a custom loader too.""" + parent = Path("/fake/main.yaml") + loader_calls: list[Path] = [] + + def custom_loader(fname: Path) -> dict[str, int]: + loader_calls.append(fname) + return {"x": 1} + + config = yaml_util.parse_yaml( + parent, io.StringIO("!include inner.yaml\n"), custom_loader + ) + + assert config == {"x": 1} + assert loader_calls == [Path("/fake/inner.yaml")] + + def test_yaml_merge_chain_include_depth_exceeded() -> None: """Chain includes in merge keys exceeding depth limit raise a clear error.""" parent = Path("/fake/main.yaml") From 1338902ac5e5f098801e01d79f42d9d0e1422b26 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 13 Oct 2026 18:06:22 -1000 Subject: [PATCH 3/3] [heatpumpir] Link only the configured protocol classes (#20707) --- esphome/components/heatpumpir/climate.py | 4 +- esphome/components/heatpumpir/heatpumpir.cpp | 132 ++++++++++--------- esphome/components/heatpumpir/heatpumpir.h | 32 +++-- 3 files changed, 90 insertions(+), 78 deletions(-) diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index 2583839ca81..42ef79d31d2 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -126,8 +126,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config: ConfigType) -> None: - var = await climate_ir.new_climate_ir(config) - cg.add(var.set_protocol(config[CONF_PROTOCOL])) + tag = heatpumpir_ns.ProtocolTag.template(config[CONF_PROTOCOL]) + var = await climate_ir.new_climate_ir(config, tag()) cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT])) cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT])) cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) diff --git a/esphome/components/heatpumpir/heatpumpir.cpp b/esphome/components/heatpumpir/heatpumpir.cpp index b4597fe4abf..a23d69e3b22 100644 --- a/esphome/components/heatpumpir/heatpumpir.cpp +++ b/esphome/components/heatpumpir/heatpumpir.cpp @@ -5,7 +5,6 @@ #if defined(USE_ARDUINO) || defined(USE_ESP32) #include -#include #include #include #include "esphome/components/remote_base/remote_base.h" @@ -45,71 +44,76 @@ class IRSenderESPHome : public IRSender { ESPHOME_LOG_TAG(TAG, "heatpumpir.climate"); -const std::map> PROTOCOL_CONSTRUCTOR_MAP = { - {PROTOCOL_AUX, []() { return new AUXHeatpumpIR(); }}, // NOLINT - {PROTOCOL_BALLU, []() { return new BalluHeatpumpIR(); }}, // NOLINT - {PROTOCOL_CARRIER_MCA, []() { return new CarrierMCAHeatpumpIR(); }}, // NOLINT - {PROTOCOL_CARRIER_NQV, []() { return new CarrierNQVHeatpumpIR(); }}, // NOLINT - {PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }}, // NOLINT - {PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }}, // NOLINT - {PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }}, // NOLINT - {PROTOCOL_ELECTROLUXYAL, []() { return new ElectroluxYALHeatpumpIR(); }}, // NOLINT - {PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }}, // NOLINT - {PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }}, // NOLINT - {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT - {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }}, // NOLINT - {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }}, // NOLINT - {PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }}, // NOLINT - {PROTOCOL_GREEYT, []() { return new GreeYTHeatpumpIR(); }}, // NOLINT - {PROTOCOL_GREEYAP, []() { return new GreeYAPHeatpumpIR(); }}, // NOLINT - {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }}, // NOLINT - {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }}, // NOLINT - {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }}, // NOLINT - {PROTOCOL_IVT, []() { return new IVTHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MIDEA, []() { return new MideaHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MITSUBISHI_FA, []() { return new MitsubishiFAHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MITSUBISHI_FD, []() { return new MitsubishiFDHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MITSUBISHI_FE, []() { return new MitsubishiFEHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MITSUBISHI_HEAVY_FDTC, []() { return new MitsubishiHeavyFDTCHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MITSUBISHI_HEAVY_ZJ, []() { return new MitsubishiHeavyZJHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MITSUBISHI_HEAVY_ZM, []() { return new MitsubishiHeavyZMHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MITSUBISHI_HEAVY_ZMP, []() { return new MitsubishiHeavyZMPHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MITSUBISHI_KJ, []() { return new MitsubishiKJHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MITSUBISHI_MSC, []() { return new MitsubishiMSCHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MITSUBISHI_MSY, []() { return new MitsubishiMSYHeatpumpIR(); }}, // NOLINT - {PROTOCOL_MITSUBISHI_SEZ, []() { return new MitsubishiSEZKDXXHeatpumpIR(); }}, // NOLINT - {PROTOCOL_PANASONIC_CKP, []() { return new PanasonicCKPHeatpumpIR(); }}, // NOLINT - {PROTOCOL_PANASONIC_DKE, []() { return new PanasonicDKEHeatpumpIR(); }}, // NOLINT - {PROTOCOL_PANASONIC_EKE, []() { return new PanasonicEKEHeatpumpIR(); }}, // NOLINT - {PROTOCOL_PANASONIC_JKE, []() { return new PanasonicJKEHeatpumpIR(); }}, // NOLINT - {PROTOCOL_PANASONIC_LKE, []() { return new PanasonicLKEHeatpumpIR(); }}, // NOLINT - {PROTOCOL_PANASONIC_NKE, []() { return new PanasonicNKEHeatpumpIR(); }}, // NOLINT - {PROTOCOL_SAMSUNG_AQV, []() { return new SamsungAQVHeatpumpIR(); }}, // NOLINT - {PROTOCOL_SAMSUNG_FJM, []() { return new SamsungFJMHeatpumpIR(); }}, // NOLINT - {PROTOCOL_SHARP, []() { return new SharpHeatpumpIR(); }}, // NOLINT - {PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT - {PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT - {PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }}, // NOLINT - {PROTOCOL_NIBE, []() { return new NibeHeatpumpIR(); }}, // NOLINT - {PROTOCOL_QLIMA_1, []() { return new Qlima1HeatpumpIR(); }}, // NOLINT - {PROTOCOL_QLIMA_2, []() { return new Qlima2HeatpumpIR(); }}, // NOLINT - {PROTOCOL_SAMSUNG_AQV12MSAN, []() { return new SamsungAQV12MSANHeatpumpIR(); }}, // NOLINT - {PROTOCOL_ZHJG01, []() { return new ZHJG01HeatpumpIR(); }}, // NOLINT - {PROTOCOL_AIRWAY, []() { return new AIRWAYHeatpumpIR(); }}, // NOLINT - {PROTOCOL_BGH_AUD, []() { return new BGHHeatpumpIR(); }}, // NOLINT - {PROTOCOL_PANASONIC_ALTDKE, []() { return new PanasonicAltDKEHeatpumpIR(); }}, // NOLINT - {PROTOCOL_PHILCO_PHS32, []() { return new PhilcoPHS32HeatpumpIR(); }}, // NOLINT - {PROTOCOL_VAILLANTVAI8, []() { return new VaillantHeatpumpIR(); }}, // NOLINT - {PROTOCOL_R51M, []() { return new R51MHeatpumpIR(); }}, // NOLINT -}; +template struct ProtocolClass; + +template HeatpumpIR *create_heatpump_ir() { + return new typename ProtocolClass

::type(); // NOLINT +} + +// Codegen calls only the configured protocols, so the linker drops the other classes +#define HEATPUMPIR_PROTOCOL(protocol, cls) \ + template<> struct ProtocolClass { using type = cls; }; \ + template HeatpumpIR *create_heatpump_ir(); + +HEATPUMPIR_PROTOCOL(PROTOCOL_AUX, AUXHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_BALLU, BalluHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_CARRIER_MCA, CarrierMCAHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_CARRIER_NQV, CarrierNQVHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_DAIKIN_ARC417, DaikinHeatpumpARC417IR) +HEATPUMPIR_PROTOCOL(PROTOCOL_DAIKIN_ARC480, DaikinHeatpumpARC480A14IR) +HEATPUMPIR_PROTOCOL(PROTOCOL_DAIKIN, DaikinHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_ELECTROLUXYAL, ElectroluxYALHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_FUEGO, FuegoHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_FUJITSU_AWYZ, FujitsuHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_GREE, GreeGenericHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_GREEYAA, GreeYAAHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_GREEYAN, GreeYANHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_GREEYAC, GreeYACHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_GREEYT, GreeYTHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_GREEYAP, GreeYAPHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_HISENSE_AUD, HisenseHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_HITACHI, HitachiHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_HYUNDAI, HyundaiHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_IVT, IVTHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MIDEA, MideaHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_FA, MitsubishiFAHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_FD, MitsubishiFDHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_FE, MitsubishiFEHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_HEAVY_FDTC, MitsubishiHeavyFDTCHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_HEAVY_ZJ, MitsubishiHeavyZJHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_HEAVY_ZM, MitsubishiHeavyZMHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_HEAVY_ZMP, MitsubishiHeavyZMPHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_KJ, MitsubishiKJHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_MSC, MitsubishiMSCHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_MSY, MitsubishiMSYHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_MITSUBISHI_SEZ, MitsubishiSEZKDXXHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_PANASONIC_CKP, PanasonicCKPHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_PANASONIC_DKE, PanasonicDKEHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_PANASONIC_EKE, PanasonicEKEHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_PANASONIC_JKE, PanasonicJKEHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_PANASONIC_LKE, PanasonicLKEHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_PANASONIC_NKE, PanasonicNKEHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_SAMSUNG_AQV, SamsungAQVHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_SAMSUNG_FJM, SamsungFJMHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_SHARP, SharpHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_TOSHIBA_DAISEIKAI, ToshibaDaiseikaiHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_TOSHIBA, ToshibaHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_ZHLT01, ZHLT01HeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_NIBE, NibeHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_QLIMA_1, Qlima1HeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_QLIMA_2, Qlima2HeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_SAMSUNG_AQV12MSAN, SamsungAQV12MSANHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_ZHJG01, ZHJG01HeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_AIRWAY, AIRWAYHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_BGH_AUD, BGHHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_PANASONIC_ALTDKE, PanasonicAltDKEHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_PHILCO_PHS32, PhilcoPHS32HeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_VAILLANTVAI8, VaillantHeatpumpIR) +HEATPUMPIR_PROTOCOL(PROTOCOL_R51M, R51MHeatpumpIR) + +#undef HEATPUMPIR_PROTOCOL void HeatpumpIRClimate::setup() { - auto protocol_constructor = PROTOCOL_CONSTRUCTOR_MAP.find(protocol_); - if (protocol_constructor == PROTOCOL_CONSTRUCTOR_MAP.end()) { - ESP_LOGE(TAG, "Invalid protocol"); - return; - } - this->heatpump_ir_ = protocol_constructor->second(); climate_ir::ClimateIR::setup(); if (this->sensor_) { this->sensor_->add_on_state_callback([this](float state) { diff --git a/esphome/components/heatpumpir/heatpumpir.h b/esphome/components/heatpumpir/heatpumpir.h index d00fa91d05a..dfa5c40366a 100644 --- a/esphome/components/heatpumpir/heatpumpir.h +++ b/esphome/components/heatpumpir/heatpumpir.h @@ -89,25 +89,21 @@ enum VerticalDirection { VERTICAL_DIRECTION_DOWN = 5, }; +/// Creates the HeatpumpIR library class for a protocol; instantiated in heatpumpir.cpp. +template HeatpumpIR *create_heatpump_ir(); + +/// Selects the protocol at compile time so the enum and the library object always match. +template struct ProtocolTag {}; + // Temperature const float TEMP_MIN = 0; // Celsius const float TEMP_MAX = 100; // Celsius class HeatpumpIRClimate final : public climate_ir::ClimateIR { public: - HeatpumpIRClimate() - : climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, true, - {climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, - climate::CLIMATE_FAN_AUTO}, - {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL, - climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_BOTH}) {} + template + explicit HeatpumpIRClimate(ProtocolTag

/*tag*/) : HeatpumpIRClimate(P, create_heatpump_ir

()) {} void setup() override; - void set_protocol(Protocol protocol) { - this->protocol_ = protocol; - if (this->has_fan_byte_presets_()) { - this->presets_ = {climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_BOOST}; - } - } void set_horizontal_default(HorizontalDirection horizontal_direction) { this->default_horizontal_direction_ = horizontal_direction; } @@ -119,6 +115,18 @@ class HeatpumpIRClimate final : public climate_ir::ClimateIR { void set_min_temperature(float temperature) { this->min_temperature_ = temperature; } protected: + HeatpumpIRClimate(Protocol protocol, HeatpumpIR *heatpump_ir) + : climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, + climate::CLIMATE_FAN_AUTO}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL, + climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_BOTH}), + heatpump_ir_(heatpump_ir), + protocol_(protocol) { + if (this->has_fan_byte_presets_()) { + this->presets_ = {climate::CLIMATE_PRESET_NONE, climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_BOOST}; + } + } HeatpumpIR *heatpump_ir_; void control(const climate::ClimateCall &call) override; /// Transmit via IR the state of this climate controller.