From c8d2c3691a1a9fd45f958e64c89b27fcbba0ba80 Mon Sep 17 00:00:00 2001 From: Boris Krivonog Date: Tue, 11 Aug 2026 00:58:37 +0200 Subject: [PATCH] [mitsubishi_cn105] Add vertical vane direction select (#16723) Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- .../mitsubishi_cn105_climate.cpp | 4 +- .../mitsubishi_cn105_component.h | 6 + .../mitsubishi_cn105/select/__init__.py | 47 ++++++++ .../mitsubishi_cn105_vane_select_vertical.cpp | 39 ++++++ .../mitsubishi_cn105_vane_select_vertical.h | 21 ++++ .../climate/mitsubishi_cn105_tests.cpp | 60 +++++----- tests/components/mitsubishi_cn105/common.yaml | 6 + ...bishi_cn105_vane_select_vertical_tests.cpp | 111 ++++++++++++++++++ 8 files changed, 261 insertions(+), 33 deletions(-) create mode 100644 esphome/components/mitsubishi_cn105/select/__init__.py create mode 100644 esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.cpp create mode 100644 esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.h create mode 100644 tests/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical_tests.cpp diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp index 13e02668d1..197e1e1bb5 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp @@ -133,9 +133,7 @@ void MitsubishiCN105Climate::control(const climate::ClimateCall &call) { } } - if (this->parent_->is_status_initialized()) { - this->apply_values_(); - } + this->parent_->publish_status(); } void MitsubishiCN105Climate::apply_values_() { diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.h b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.h index 2319ea7c54..1caf779f40 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.h +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.h @@ -38,6 +38,12 @@ class MitsubishiCN105Component : public Component, public uart::UARTDevice { this->status_callback_.add(std::forward(callback)); } + void publish_status() { + if (this->is_status_initialized()) { + this->status_callback_.call(); + } + } + protected: MitsubishiCN105 hp_; CallbackManager status_callback_; diff --git a/esphome/components/mitsubishi_cn105/select/__init__.py b/esphome/components/mitsubishi_cn105/select/__init__.py new file mode 100644 index 0000000000..a2e0353f85 --- /dev/null +++ b/esphome/components/mitsubishi_cn105/select/__init__.py @@ -0,0 +1,47 @@ +import esphome.codegen as cg +from esphome.components import select +import esphome.config_validation as cv +from esphome.const import CONF_ID +from esphome.types import ConfigType + +from .. import ( + MITSUBISHI_CN105_DEVICE_SCHEMA, + MitsubishiCN105Component, + mitsubishi_ns, + register_mitsubishi_cn105_device, +) + +DEPENDENCIES = ["mitsubishi_cn105"] + +CONF_VERTICAL_VANE_DIRECTION = "vertical_vane_direction" + +# The insertion order must match VALUES in mitsubishi_cn105_vane_select_vertical.cpp. +VERTICAL_VANE_DIRECTIONS = ["Auto", "1", "2", "3", "4", "5", "Swing"] + +MitsubishiCN105VerticalVaneDirectionSelect = mitsubishi_ns.class_( + "MitsubishiCN105VerticalVaneDirectionSelect", + select.Select, + cg.Component, + cg.Parented.template(MitsubishiCN105Component), +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.Optional(CONF_VERTICAL_VANE_DIRECTION): select.select_schema( + MitsubishiCN105VerticalVaneDirectionSelect, + icon="mdi:arrow-up-down", + ), + } +).extend(MITSUBISHI_CN105_DEVICE_SCHEMA) + + +async def to_code(config: ConfigType) -> None: + if vertical_vane_direction := config.get(CONF_VERTICAL_VANE_DIRECTION): + var = cg.new_Pvariable(vertical_vane_direction[CONF_ID]) + await cg.register_component(var, vertical_vane_direction) + await select.register_select( + var, + vertical_vane_direction, + options=VERTICAL_VANE_DIRECTIONS, + ) + await register_mitsubishi_cn105_device(var, config) diff --git a/esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.cpp b/esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.cpp new file mode 100644 index 0000000000..0f9142fe5e --- /dev/null +++ b/esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.cpp @@ -0,0 +1,39 @@ +#include "mitsubishi_cn105_vane_select_vertical.h" + +#include + +namespace esphome::mitsubishi_cn105 { + +// NOTE: This order must match VERTICAL_VANE_DIRECTIONS in select.py. +// MitsubishiCN105VerticalVaneDirectionSelect uses the preferred index-based +// Select API, so Python option order and this array must stay aligned. +static constexpr std::array VALUES{ + MitsubishiCN105::VaneMode::AUTO, MitsubishiCN105::VaneMode::POSITION_1, MitsubishiCN105::VaneMode::POSITION_2, + MitsubishiCN105::VaneMode::POSITION_3, MitsubishiCN105::VaneMode::POSITION_4, MitsubishiCN105::VaneMode::POSITION_5, + MitsubishiCN105::VaneMode::SWING, +}; + +void MitsubishiCN105VerticalVaneDirectionSelect::setup() { + this->parent_->add_on_status_callback([this]() { this->publish_vane_state(this->parent_->status().vane_mode); }); + if (this->parent_->is_status_initialized()) { + this->publish_vane_state(this->parent_->status().vane_mode); + } +} + +void MitsubishiCN105VerticalVaneDirectionSelect::control(size_t index) { + if (index < VALUES.size()) { + this->parent_->set_vane_mode(VALUES[index]); + this->parent_->publish_status(); + } +} + +void MitsubishiCN105VerticalVaneDirectionSelect::publish_vane_state(MitsubishiCN105::VaneMode mode) { + for (size_t i = 0; i < VALUES.size(); ++i) { + if (VALUES[i] == mode) { + this->publish_state(i); + return; + } + } +} + +} // namespace esphome::mitsubishi_cn105 diff --git a/esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.h b/esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.h new file mode 100644 index 0000000000..76977d59d7 --- /dev/null +++ b/esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../mitsubishi_cn105_component.h" + +#include "esphome/components/select/select.h" +#include "esphome/core/component.h" + +namespace esphome::mitsubishi_cn105 { + +class MitsubishiCN105VerticalVaneDirectionSelect : public select::Select, + public Component, + public Parented { + public: + void setup() override; + void publish_vane_state(MitsubishiCN105::VaneMode mode); + + protected: + void control(size_t index) override; +}; + +} // namespace esphome::mitsubishi_cn105 diff --git a/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp b/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp index 7703b02fcd..28fdfbb313 100644 --- a/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp +++ b/tests/components/mitsubishi_cn105/climate/mitsubishi_cn105_tests.cpp @@ -2,16 +2,16 @@ namespace esphome::mitsubishi_cn105::testing { -struct TestContext { +struct MitsubishiCN105TestsContext { MockUARTComponent uart; uart::UARTDevice device{&uart}; TestableMitsubishiCN105 sut{device}; - TestContext() { this->sut.set_current_time(0); } + MitsubishiCN105TestsContext() { this->sut.set_current_time(0); } }; TEST(MitsubishiCN105Tests, InitSendsConnectPacket) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_current_time(123); EXPECT_EQ(ctx.sut.state_, TestableMitsubishiCN105::State::NOT_CONNECTED); @@ -26,7 +26,7 @@ TEST(MitsubishiCN105Tests, InitSendsConnectPacket) { } TEST(MitsubishiCN105Tests, ConnectAndUpdateStatus) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.initialize(); ctx.uart.tx.clear(); // Remove first connect packet bytes @@ -106,7 +106,7 @@ TEST(MitsubishiCN105Tests, ConnectAndUpdateStatus) { } TEST(MitsubishiCN105Tests, NoResponseTriggersReconnect) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.initialize(); ctx.uart.tx.clear(); // Remove first connect packet bytes @@ -133,7 +133,7 @@ TEST(MitsubishiCN105Tests, NoResponseTriggersReconnect) { } TEST(MitsubishiCN105Tests, RxWatchdogLimitsProcessingPerUpdate) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.initialize(); ctx.uart.tx.clear(); // Remove first connect packet bytes @@ -164,7 +164,7 @@ TEST(MitsubishiCN105Tests, RxWatchdogLimitsProcessingPerUpdate) { } TEST(MitsubishiCN105Tests, ParserHandlesMixedRxStream) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.initialize(); ctx.uart.tx.clear(); // Remove first connect packet bytes @@ -228,7 +228,7 @@ TEST(MitsubishiCN105Tests, ParserHandlesMixedRxStream) { } TEST(MitsubishiCN105Tests, NextStatusUpdateAfterUpdateIntervalMilliseconds) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_update_interval(2000); ctx.sut.set_current_time(80000); @@ -258,7 +258,7 @@ TEST(MitsubishiCN105Tests, NextStatusUpdateAfterUpdateIntervalMilliseconds) { } TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedA) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.uart.push_rx( {0xFC, 0x62, 0x01, 0x30, 0x0C, 0x02, 0x00, 0x00, 0x01, 0x03, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55}); @@ -273,7 +273,7 @@ TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedA) { } TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedB) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.uart.push_rx( {0xFC, 0x62, 0x01, 0x30, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xAD}); @@ -288,7 +288,7 @@ TEST(MitsubishiCN105Tests, DecodeStatusSettingsPackageTempEncodedB) { } TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedA) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x07, 0x03, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x5D}); @@ -298,7 +298,7 @@ TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedA) { } TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedB) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0xA7}); @@ -308,7 +308,7 @@ TEST(MitsubishiCN105Tests, DecodeStatusRoomTempPackageTempEncodedB) { } TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitNotSet) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58}); @@ -320,7 +320,7 @@ TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitNotSet) { } TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitSet) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.uart.push_rx({0xFC, 0x62, 0x01, 0x30, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD8}); @@ -332,7 +332,7 @@ TEST(MitsubishiCN105Tests, DecodeWideVanePackageHighBitSet) { } TEST(MitsubishiCN105Tests, ApplySettingsPowerOn) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_power(true); ctx.sut.apply_settings(); @@ -342,7 +342,7 @@ TEST(MitsubishiCN105Tests, ApplySettingsPowerOn) { } TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedA) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_target_temperature(23.0f); ctx.sut.apply_settings(); @@ -352,7 +352,7 @@ TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedA) { } TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedB) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.use_temperature_encoding_b_ = true; ctx.sut.set_target_temperature(26.0f); @@ -363,7 +363,7 @@ TEST(MitsubishiCN105Tests, ApplySettingsTemperatureEncodedB) { } TEST(MitsubishiCN105Tests, ApplySettingsHalfDegreeTemperatureEncodedB) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.use_temperature_encoding_b_ = true; ctx.sut.set_target_temperature(26.5f); @@ -374,7 +374,7 @@ TEST(MitsubishiCN105Tests, ApplySettingsHalfDegreeTemperatureEncodedB) { } TEST(MitsubishiCN105Tests, ApplyModeCool) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_mode(MitsubishiCN105::Mode::COOL); ctx.sut.apply_settings(); @@ -384,7 +384,7 @@ TEST(MitsubishiCN105Tests, ApplyModeCool) { } TEST(MitsubishiCN105Tests, ApplyFanModeSpeed1) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_fan_mode(MitsubishiCN105::FanMode::SPEED_1); ctx.sut.apply_settings(); @@ -394,7 +394,7 @@ TEST(MitsubishiCN105Tests, ApplyFanModeSpeed1) { } TEST(MitsubishiCN105Tests, ApplyVaneModeSwing) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_vane_mode(MitsubishiCN105::VaneMode::SWING); ctx.sut.apply_settings(); @@ -404,7 +404,7 @@ TEST(MitsubishiCN105Tests, ApplyVaneModeSwing) { } TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitNotSet) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_wide_vane_mode(MitsubishiCN105::WideVaneMode::LEFT); ctx.sut.apply_settings(); @@ -414,7 +414,7 @@ TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitNotSet) { } TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitSet) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_wide_vane_high_bit_ = true; ctx.sut.set_wide_vane_mode(MitsubishiCN105::WideVaneMode::LEFT); @@ -425,7 +425,7 @@ TEST(MitsubishiCN105Tests, ApplyWideVaneModeLeftAndHighBitSet) { } TEST(MitsubishiCN105Tests, WriteInterruptsWaitingForNextStatusUpdate) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_update_interval(2000); ctx.sut.set_current_time(5000); @@ -470,7 +470,7 @@ TEST(MitsubishiCN105Tests, WriteInterruptsWaitingForNextStatusUpdate) { } TEST(MitsubishiCN105Tests, SetAndClearRemoteRoomTemp) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; // Set remote temperature ctx.sut.set_remote_temperature(28.5f); @@ -505,7 +505,7 @@ TEST(MitsubishiCN105Tests, SetAndClearRemoteRoomTemp) { } TEST(MitsubishiCN105Tests, ApplyQueuedSettingsThenRemoteRoomTempInSecondWrite) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; // Queue normal settings plus remote temperature together. ctx.sut.use_temperature_encoding_b_ = true; @@ -545,7 +545,7 @@ TEST(MitsubishiCN105Tests, ApplyQueuedSettingsThenRemoteRoomTempInSecondWrite) { } TEST(MitsubishiCN105Tests, WriteTimeoutClearsStatusUpdateWaitCreditOnReconnect) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_update_interval(2000); ctx.sut.set_current_time(5000); @@ -578,7 +578,7 @@ TEST(MitsubishiCN105Tests, WriteTimeoutClearsStatusUpdateWaitCreditOnReconnect) } TEST(MitsubishiCN105Tests, SetOutOfRangeRemoteRoomTempIsIgnored) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_remote_temperature(7.0f); EXPECT_FALSE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE)); @@ -591,13 +591,13 @@ TEST(MitsubishiCN105Tests, SetOutOfRangeRemoteRoomTempIsIgnored) { } TEST(MitsubishiCN105Tests, SetMinRemoteRoomTemp) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_remote_temperature(8.0f); EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE)); } TEST(MitsubishiCN105Tests, SetMaxRemoteRoomTemp) { - auto ctx = TestContext{}; + MitsubishiCN105TestsContext ctx; ctx.sut.set_remote_temperature(39.5f); EXPECT_TRUE(ctx.sut.pending_updates_.contains(TestableMitsubishiCN105::UpdateFlag::REMOTE_TEMPERATURE)); } diff --git a/tests/components/mitsubishi_cn105/common.yaml b/tests/components/mitsubishi_cn105/common.yaml index 5966523b34..12a3b8ce9d 100644 --- a/tests/components/mitsubishi_cn105/common.yaml +++ b/tests/components/mitsubishi_cn105/common.yaml @@ -10,6 +10,12 @@ climate: name: "AC Test" supported_swing_modes: BOTH +select: + - platform: mitsubishi_cn105 + mitsubishi_cn105_id: ac + vertical_vane_direction: + name: "Vertical Vane" + esphome: on_boot: then: diff --git a/tests/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical_tests.cpp b/tests/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical_tests.cpp new file mode 100644 index 0000000000..4c980d69d8 --- /dev/null +++ b/tests/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical_tests.cpp @@ -0,0 +1,111 @@ +#include "../common.h" +#include "esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.h" + +namespace esphome::mitsubishi_cn105::testing { + +class TestableMitsubishiCN105Component : public MitsubishiCN105Component { + public: + MitsubishiCN105::Status &mutable_status() { return const_cast(this->status()); } + + void notify_status() { this->status_callback_.call(); } +}; + +class TestableMitsubishiCN105VerticalVaneDirectionSelect : public MitsubishiCN105VerticalVaneDirectionSelect { + public: + using MitsubishiCN105VerticalVaneDirectionSelect::control; +}; + +struct VerticalVaneDirectionSelectTestContext { + TestableMitsubishiCN105Component hub; + TestableMitsubishiCN105VerticalVaneDirectionSelect select; + + VerticalVaneDirectionSelectTestContext() { + this->select.traits.set_options({"Auto", "1", "2", "3", "4", "5", "Swing"}); + this->select.set_parent(&this->hub); + this->select.setup(); + } +}; + +TEST(MitsubishiCN105VerticalVaneDirectionSelectTests, MapsIndexesToVaneModes) { + VerticalVaneDirectionSelectTestContext ctx; + + constexpr std::array expected_modes{ + MitsubishiCN105::VaneMode::AUTO, MitsubishiCN105::VaneMode::POSITION_1, + MitsubishiCN105::VaneMode::POSITION_2, MitsubishiCN105::VaneMode::POSITION_3, + MitsubishiCN105::VaneMode::POSITION_4, MitsubishiCN105::VaneMode::POSITION_5, + MitsubishiCN105::VaneMode::SWING, + }; + + for (size_t i = 0; i < expected_modes.size(); ++i) { + SCOPED_TRACE(i); + ctx.select.control(i); + EXPECT_EQ(ctx.hub.status().vane_mode, expected_modes[i]); + } +} + +TEST(MitsubishiCN105VerticalVaneDirectionSelectTests, PublishesIncomingVaneModes) { + VerticalVaneDirectionSelectTestContext ctx; + + constexpr std::array modes{ + MitsubishiCN105::VaneMode::AUTO, MitsubishiCN105::VaneMode::POSITION_1, + MitsubishiCN105::VaneMode::POSITION_2, MitsubishiCN105::VaneMode::POSITION_3, + MitsubishiCN105::VaneMode::POSITION_4, MitsubishiCN105::VaneMode::POSITION_5, + MitsubishiCN105::VaneMode::SWING, + }; + + for (size_t i = 0; i < modes.size(); ++i) { + SCOPED_TRACE(i); + ctx.hub.mutable_status().vane_mode = modes[i]; + ctx.hub.notify_status(); + EXPECT_EQ(ctx.select.active_index(), std::optional{i}); + } + + ctx.hub.mutable_status().vane_mode = MitsubishiCN105::VaneMode::UNKNOWN; + ctx.hub.notify_status(); + EXPECT_EQ(ctx.select.active_index(), std::optional{modes.size() - 1}); +} + +TEST(MitsubishiCN105VerticalVaneDirectionSelectTests, ControlPublishesSelectAndClimateThroughHub) { + VerticalVaneDirectionSelectTestContext ctx; + MitsubishiCN105Climate climate_entity; + climate_entity.set_parent(&ctx.hub); + climate_entity.set_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); + + ctx.hub.mutable_status().room_temperature = 20.0f; + climate_entity.setup(); + + ctx.select.control(6); + EXPECT_EQ(ctx.select.active_index(), std::optional{6}); + EXPECT_EQ(climate_entity.swing_mode, climate::CLIMATE_SWING_VERTICAL); + + ctx.select.control(3); + EXPECT_EQ(ctx.select.active_index(), std::optional{3}); + EXPECT_EQ(climate_entity.swing_mode, climate::CLIMATE_SWING_OFF); +} + +TEST(MitsubishiCN105VerticalVaneDirectionSelectTests, ClimateControlPublishesSelectThroughHub) { + VerticalVaneDirectionSelectTestContext ctx; + MitsubishiCN105Climate climate_entity; + climate_entity.set_parent(&ctx.hub); + climate_entity.set_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); + + ctx.hub.mutable_status().room_temperature = 20.0f; + climate_entity.setup(); + + climate_entity.make_call().set_swing_mode(climate::CLIMATE_SWING_VERTICAL).perform(); + EXPECT_EQ(ctx.select.active_index(), std::optional{6}); + + climate_entity.make_call().set_swing_mode(climate::CLIMATE_SWING_OFF).perform(); + EXPECT_EQ(ctx.select.active_index(), std::optional{0}); +} + +TEST(MitsubishiCN105VerticalVaneDirectionSelectTests, BeforeInitializationDoesNotPublishSelectState) { + VerticalVaneDirectionSelectTestContext ctx; + + ctx.select.control(3); + + EXPECT_EQ(ctx.hub.status().vane_mode, MitsubishiCN105::VaneMode::POSITION_3); + EXPECT_FALSE(ctx.select.has_state()); +} + +} // namespace esphome::mitsubishi_cn105::testing