From 992f999bcff472b69fbcb9e033978b6ef80aef32 Mon Sep 17 00:00:00 2001 From: Sammy1Am <467704+Sammy1Am@users.noreply.github.com> Date: Thu, 8 Aug 2024 20:51:11 +0000 Subject: [PATCH 1/9] No default name, use time_id, don't depend on climate --- esphome/components/mitsubishi_itp/climate.py | 22 +++++--------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/esphome/components/mitsubishi_itp/climate.py b/esphome/components/mitsubishi_itp/climate.py index 0d1a22c07e3f..c6f1d8691e5d 100644 --- a/esphome/components/mitsubishi_itp/climate.py +++ b/esphome/components/mitsubishi_itp/climate.py @@ -13,11 +13,11 @@ from esphome.const import ( CONF_CUSTOM_FAN_MODES, CONF_ID, - CONF_NAME, CONF_OUTDOOR_TEMPERATURE, CONF_SENSORS, CONF_SUPPORTED_FAN_MODES, CONF_SUPPORTED_MODES, + CONF_TIME_ID, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_FREQUENCY, DEVICE_CLASS_HUMIDITY, @@ -43,12 +43,10 @@ ] DEPENDENCIES = [ "uart", - "climate", ] CONF_UART_HEATPUMP = "uart_heatpump" CONF_UART_THERMOSTAT = "uart_thermostat" -CONF_TIME_SOURCE = "time_source" CONF_THERMOSTAT_TEMPERATURE = "thermostat_temperature" CONF_THERMOSTAT_HUMIDITY = "thermostat_humidity" @@ -109,17 +107,7 @@ cv.GenerateID(CONF_ID): cv.declare_id(MitsubishiUART), cv.Required(CONF_UART_HEATPUMP): cv.use_id(uart.UARTComponent), cv.Optional(CONF_UART_THERMOSTAT): cv.use_id(uart.UARTComponent), - cv.Optional(CONF_TIME_SOURCE): cv.use_id(time.RealTimeClock), - # Overwrite name from ENTITY_BASE_SCHEMA with "Climate" as default - cv.Optional(CONF_NAME, default="Climate"): cv.Any( - cv.All( - cv.none, - cv.requires_friendly_name( - "Name cannot be None when esphome->friendly_name is not set!" - ), - ), - cv.string, - ), + cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), cv.Optional( CONF_SUPPORTED_MODES, default=DEFAULT_CLIMATE_MODES ): cv.ensure_list(climate.validate_climate_mode), @@ -361,12 +349,12 @@ async def to_code(config): SELECTS[CONF_TEMPERATURE_SOURCE_SELECT][2].append("Thermostat") # If RTC defined - if CONF_TIME_SOURCE in config: - rtc_component = await cg.get_variable(config[CONF_TIME_SOURCE]) + if CONF_TIME_ID in config: + rtc_component = await cg.get_variable(config[CONF_TIME_ID]) cg.add(getattr(muart_component, "set_time_source")(rtc_component)) elif CONF_UART_THERMOSTAT in config and config.get(CONF_ENHANCED_MHK_SUPPORT): raise cv.RequiredFieldInvalid( - f"{CONF_TIME_SOURCE} is required if {CONF_ENHANCED_MHK_SUPPORT} is set." + f"{CONF_TIME_ID} is required if {CONF_ENHANCED_MHK_SUPPORT} is set." ) # Traits From f083124e4b91c64e2a4756b9a2942778de3212d0 Mon Sep 17 00:00:00 2001 From: Sammy1Am <467704+Sammy1Am@users.noreply.github.com> Date: Thu, 8 Aug 2024 23:15:18 +0000 Subject: [PATCH 2/9] Break out sensors into sensor.py --- esphome/components/mitsubishi_itp/climate.py | 156 +------------------ esphome/components/mitsubishi_itp/sensor.py | 147 +++++++++++++++++ 2 files changed, 149 insertions(+), 154 deletions(-) create mode 100644 esphome/components/mitsubishi_itp/sensor.py diff --git a/esphome/components/mitsubishi_itp/climate.py b/esphome/components/mitsubishi_itp/climate.py index c6f1d8691e5d..6c2bf2e68892 100644 --- a/esphome/components/mitsubishi_itp/climate.py +++ b/esphome/components/mitsubishi_itp/climate.py @@ -5,28 +5,17 @@ uart, time, sensor, - binary_sensor, button, - text_sensor, select, ) from esphome.const import ( CONF_CUSTOM_FAN_MODES, CONF_ID, - CONF_OUTDOOR_TEMPERATURE, - CONF_SENSORS, CONF_SUPPORTED_FAN_MODES, CONF_SUPPORTED_MODES, CONF_TIME_ID, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_FREQUENCY, - DEVICE_CLASS_HUMIDITY, ENTITY_CATEGORY_CONFIG, ENTITY_CATEGORY_NONE, - STATE_CLASS_MEASUREMENT, - UNIT_CELSIUS, - UNIT_HERTZ, - UNIT_PERCENT, ) from esphome.core import coroutine @@ -48,12 +37,6 @@ CONF_UART_HEATPUMP = "uart_heatpump" CONF_UART_THERMOSTAT = "uart_thermostat" -CONF_THERMOSTAT_TEMPERATURE = "thermostat_temperature" -CONF_THERMOSTAT_HUMIDITY = "thermostat_humidity" -CONF_THERMOSTAT_BATTERY = "thermostat_battery" -CONF_ERROR_CODE = "error_code" -CONF_ISEE_STATUS = "isee_status" - CONF_SELECTS = "selects" CONF_TEMPERATURE_SOURCE_SELECT = "temperature_source_select" # This is to create a Select object for selecting a source CONF_VANE_POSITION_SELECT = "vane_position_select" @@ -77,6 +60,7 @@ MitsubishiUART = mitsubishi_itp_ns.class_( "MitsubishiUART", cg.PollingComponent, climate.Climate ) +CONF_MITSUBISHI_IPT_ID = "mitsuibishi_itp_id" TemperatureSourceSelect = mitsubishi_itp_ns.class_( "TemperatureSourceSelect", select.Select @@ -125,112 +109,6 @@ } ).extend(cv.polling_component_schema(DEFAULT_POLLING_INTERVAL)) -# TODO Storing the registration function here seems weird, but I can't figure out how to determine schema type later -SENSORS = dict[str, tuple[str, cv.Schema, callable]]( - { - CONF_THERMOSTAT_TEMPERATURE: ( - "Thermostat Temperature", - sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - accuracy_decimals=1, - ), - sensor.register_sensor, - ), - CONF_OUTDOOR_TEMPERATURE: ( - "Outdoor Temperature", - sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - accuracy_decimals=1, - icon="mdi:sun-thermometer-outline", - ), - sensor.register_sensor, - ), - CONF_THERMOSTAT_HUMIDITY: ( - "Thermostat Humidity", - sensor.sensor_schema( - unit_of_measurement=UNIT_PERCENT, - device_class=DEVICE_CLASS_HUMIDITY, - state_class=STATE_CLASS_MEASUREMENT, - accuracy_decimals=0, - ), - sensor.register_sensor, - ), - CONF_THERMOSTAT_BATTERY: ( - "Thermostat Battery", - text_sensor.text_sensor_schema( - icon="mdi:battery", - ), - text_sensor.register_text_sensor, - ), - "compressor_frequency": ( - "Compressor Frequency", - sensor.sensor_schema( - unit_of_measurement=UNIT_HERTZ, - device_class=DEVICE_CLASS_FREQUENCY, - state_class=STATE_CLASS_MEASUREMENT, - ), - sensor.register_sensor, - ), - "actual_fan": ( - "Actual Fan Speed", - text_sensor.text_sensor_schema( - icon="mdi:fan", - ), - text_sensor.register_text_sensor, - ), - "filter_status": ( - "Filter Status", - binary_sensor.binary_sensor_schema( - device_class="problem", icon="mdi:air-filter" - ), - binary_sensor.register_binary_sensor, - ), - "defrost": ( - "Defrost", - binary_sensor.binary_sensor_schema(icon="mdi:snowflake-melt"), - binary_sensor.register_binary_sensor, - ), - "preheat": ( - "Preheat", - binary_sensor.binary_sensor_schema(icon="mdi:heating-coil"), - binary_sensor.register_binary_sensor, - ), - "standby": ( - "Standby", - binary_sensor.binary_sensor_schema(icon="mdi:pause-circle-outline"), - binary_sensor.register_binary_sensor, - ), - CONF_ISEE_STATUS: ( - "i-see Status", - binary_sensor.binary_sensor_schema(icon="mdi:eye"), - binary_sensor.register_binary_sensor, - ), - CONF_ERROR_CODE: ( - "Error Code", - text_sensor.text_sensor_schema(icon="mdi:alert-circle-outline"), - text_sensor.register_text_sensor, - ), - } -) - -SENSORS_SCHEMA = cv.All( - { - cv.Optional( - sensor_designator, - default={"name": f"{sensor_name}", "disabled_by_default": "true"}, - ): sensor_schema - for sensor_designator, ( - sensor_name, - sensor_schema, - registration_function, - ) in SENSORS.items() - } -) - SELECTS = { CONF_TEMPERATURE_SOURCE_SELECT: ( "Temperature Source", @@ -269,7 +147,7 @@ for select_designator, ( select_name, select_schema, - select_options, + _, ) in SELECTS.items() } ) @@ -297,7 +175,6 @@ CONFIG_SCHEMA = BASE_SCHEMA.extend( { - cv.Optional(CONF_SENSORS, default={}): SENSORS_SCHEMA, cv.Optional(CONF_SELECTS, default={}): SELECTS_SCHEMA, cv.Optional(CONF_BUTTONS, default={}): BUTTONS_SCHEMA, } @@ -369,35 +246,6 @@ async def to_code(config): if CONF_CUSTOM_FAN_MODES in config: cg.add(traits.set_supported_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) - # Sensors - - for sensor_designator, ( - _, - _, - registration_function, - ) in SENSORS.items(): - # Only add the thermostat temp if we have a TS_UART - if (CONF_UART_THERMOSTAT not in config) and ( - sensor_designator - in [ - CONF_THERMOSTAT_TEMPERATURE, - CONF_THERMOSTAT_HUMIDITY, - CONF_THERMOSTAT_BATTERY, - ] - ): - continue - - sensor_conf = config[CONF_SENSORS][sensor_designator] - sensor_component = cg.new_Pvariable(sensor_conf[CONF_ID]) - - await registration_function(sensor_component, sensor_conf) - - cg.add( - getattr(muart_component, f"set_{sensor_designator}_sensor")( - sensor_component - ) - ) - # Selects # Add additional configured temperature sensors to the select menu diff --git a/esphome/components/mitsubishi_itp/sensor.py b/esphome/components/mitsubishi_itp/sensor.py new file mode 100644 index 000000000000..50fbf8a34d04 --- /dev/null +++ b/esphome/components/mitsubishi_itp/sensor.py @@ -0,0 +1,147 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import ( + sensor, + binary_sensor, + text_sensor, +) +from esphome.const import ( + CONF_ID, + CONF_OUTDOOR_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_FREQUENCY, + DEVICE_CLASS_HUMIDITY, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HERTZ, + UNIT_PERCENT, +) +from esphome.components.mitsubishi_itp.climate import ( + CONF_MITSUBISHI_IPT_ID, + MitsubishiUART, +) +from esphome.core import coroutine + +CONF_THERMOSTAT_BATTERY = "thermostat_battery" +CONF_THERMOSTAT_HUMIDITY = "thermostat_humidity" +CONF_THERMOSTAT_TEMPERATURE = "thermostat_temperature" + + +CONF_ERROR_CODE = "error_code" +CONF_ISEE_STATUS = "isee_status" + +# TODO Storing the registration function here seems weird, but I can't figure out how to determine schema type later +SENSORS = dict[str, tuple[cv.Schema, callable]]( + { + "actual_fan": ( + text_sensor.text_sensor_schema( + icon="mdi:fan", + ), + text_sensor.register_text_sensor, + ), + "compressor_frequency": ( + sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + device_class=DEVICE_CLASS_FREQUENCY, + state_class=STATE_CLASS_MEASUREMENT, + ), + sensor.register_sensor, + ), + "defrost": ( + binary_sensor.binary_sensor_schema(icon="mdi:snowflake-melt"), + binary_sensor.register_binary_sensor, + ), + CONF_ERROR_CODE: ( + text_sensor.text_sensor_schema(icon="mdi:alert-circle-outline"), + text_sensor.register_text_sensor, + ), + "filter_status": ( + binary_sensor.binary_sensor_schema( + device_class="problem", icon="mdi:air-filter" + ), + binary_sensor.register_binary_sensor, + ), + CONF_ISEE_STATUS: ( + binary_sensor.binary_sensor_schema(icon="mdi:eye"), + binary_sensor.register_binary_sensor, + ), + CONF_OUTDOOR_TEMPERATURE: ( + sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + icon="mdi:sun-thermometer-outline", + ), + sensor.register_sensor, + ), + "preheat": ( + binary_sensor.binary_sensor_schema(icon="mdi:heating-coil"), + binary_sensor.register_binary_sensor, + ), + "standby": ( + binary_sensor.binary_sensor_schema(icon="mdi:pause-circle-outline"), + binary_sensor.register_binary_sensor, + ), + CONF_THERMOSTAT_BATTERY: ( + text_sensor.text_sensor_schema( + icon="mdi:battery", + ), + text_sensor.register_text_sensor, + ), + CONF_THERMOSTAT_HUMIDITY: ( + sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=0, + ), + sensor.register_sensor, + ), + CONF_THERMOSTAT_TEMPERATURE: ( + sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=1, + ), + sensor.register_sensor, + ), + } +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MITSUBISHI_IPT_ID): cv.use_id(MitsubishiUART), + } +).extend( + { + cv.Optional(sensor_designator): sensor_schema + for sensor_designator, ( + sensor_schema, + _, + ) in SENSORS.items() + } +) + + +@coroutine +async def to_code(config): + muart_component = await cg.get_variable(config[CONF_MITSUBISHI_IPT_ID]) + + # Sensors + + for sensor_designator, ( + _, + registration_function, + ) in SENSORS.items(): + if sensor_conf := config.get(sensor_designator): + sensor_component = cg.new_Pvariable(sensor_conf[CONF_ID]) + + await registration_function(sensor_component, sensor_conf) + + cg.add( + getattr(muart_component, f"set_{sensor_designator}_sensor")( + sensor_component + ) + ) From 371f77c5ebd8329dc6232b66fadf8cbff08effae Mon Sep 17 00:00:00 2001 From: Sammy1Am <467704+Sammy1Am@users.noreply.github.com> Date: Thu, 8 Aug 2024 23:24:25 +0000 Subject: [PATCH 3/9] Check for sensor nullptrs if not defined --- .../mitsubishi_uart-packetprocessing.cpp | 34 +++++++++++-------- .../mitsubishi_itp/mitsubishi_uart.cpp | 20 ++++++++--- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/esphome/components/mitsubishi_itp/mitsubishi_uart-packetprocessing.cpp b/esphome/components/mitsubishi_itp/mitsubishi_uart-packetprocessing.cpp index f0b641887f83..acd4d72d98bf 100644 --- a/esphome/components/mitsubishi_itp/mitsubishi_uart-packetprocessing.cpp +++ b/esphome/components/mitsubishi_itp/mitsubishi_uart-packetprocessing.cpp @@ -228,7 +228,7 @@ void MitsubishiUART::process_packet(const CurrentTempGetResponsePacket &packet) publish_on_update_ |= (old_current_temperature != current_temperature); - if (!std::isnan(packet.get_outdoor_temp())) { + if (outdoor_temperature_sensor_ && !std::isnan(packet.get_outdoor_temp())) { const float old_outdoor_temperature = outdoor_temperature_sensor_->raw_state; outdoor_temperature_sensor_->raw_state = packet.get_outdoor_temp(); publish_on_update_ |= (old_outdoor_temperature != outdoor_temperature_sensor_->raw_state); @@ -336,23 +336,27 @@ void MitsubishiUART::process_packet(const ErrorStateGetResponsePacket &packet) { ESP_LOGV(TAG, "Processing %s", packet.to_string().c_str()); route_packet_(packet); - std::string old_error_code = error_code_sensor_->raw_state; - - // TODO: Include friendly text from JSON, somehow. - if (!packet.error_present()) { - error_code_sensor_->raw_state = "No Error Reported"; - } else if (auto raw_code = packet.get_raw_short_code() != 0x00) { - // Not that it matters, but good for validation I guess. - if ((raw_code & 0x1F) > 0x15) { - ESP_LOGW(TAG, "Error short code %x had invalid low bits. This is an IT protocol violation!", raw_code); + // Only worry about this if we have error_code_sensor_ defined + // TODO: Should we log a warning with error codes even if no sensor? + if (error_code_sensor_) { + std::string old_error_code = error_code_sensor_->raw_state; + + // TODO: Include friendly text from JSON, somehow. + if (!packet.error_present()) { + error_code_sensor_->raw_state = "No Error Reported"; + } else if (auto raw_code = packet.get_raw_short_code() != 0x00) { + // Not that it matters, but good for validation I guess. + if ((raw_code & 0x1F) > 0x15) { + ESP_LOGW(TAG, "Error short code %x had invalid low bits. This is an IT protocol violation!", raw_code); + } + + error_code_sensor_->raw_state = "Error " + packet.get_short_code(); + } else { + error_code_sensor_->raw_state = "Error " + to_string(packet.get_error_code()); } - error_code_sensor_->raw_state = "Error " + packet.get_short_code(); - } else { - error_code_sensor_->raw_state = "Error " + to_string(packet.get_error_code()); + publish_on_update_ |= (old_error_code != error_code_sensor_->raw_state); } - - publish_on_update_ |= (old_error_code != error_code_sensor_->raw_state); } void MitsubishiUART::process_packet(const SettingsSetRequestPacket &packet) { diff --git a/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp b/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp index 8738b2675d15..22353a9970b1 100644 --- a/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp +++ b/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp @@ -230,11 +230,21 @@ void MitsubishiUART::do_publish_() { } // Binary sensors automatically dedup publishes (I think) and so will only actually publish on change - filter_status_sensor_->publish_state(filter_status_sensor_->state); - defrost_sensor_->publish_state(defrost_sensor_->state); - preheat_sensor_->publish_state(preheat_sensor_->state); - standby_sensor_->publish_state(standby_sensor_->state); - isee_status_sensor_->publish_state(isee_status_sensor_->state); + if (filter_status_sensor_) { + filter_status_sensor_->publish_state(filter_status_sensor_->state); + } + if (defrost_sensor_) { + defrost_sensor_->publish_state(defrost_sensor_->state); + } + if (preheat_sensor_) { + preheat_sensor_->publish_state(preheat_sensor_->state); + } + if (standby_sensor_) { + standby_sensor_->publish_state(standby_sensor_->state); + } + if (isee_status_sensor_) { + isee_status_sensor_->publish_state(isee_status_sensor_->state); + } } bool MitsubishiUART::select_temperature_source(const std::string &state) { From 71a32965a116ffd06ab72dbf6a6e6cfd588e7c91 Mon Sep 17 00:00:00 2001 From: Sammy1Am <467704+Sammy1Am@users.noreply.github.com> Date: Fri, 9 Aug 2024 03:51:51 +0000 Subject: [PATCH 4/9] Breakout button into its own py file and directory --- .../mitsubishi_itp/button/__init__.py | 49 +++++++++++++++++++ .../{ => button}/muart_button.h | 2 +- esphome/components/mitsubishi_itp/climate.py | 40 +-------------- 3 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 esphome/components/mitsubishi_itp/button/__init__.py rename esphome/components/mitsubishi_itp/{ => button}/muart_button.h (94%) diff --git a/esphome/components/mitsubishi_itp/button/__init__.py b/esphome/components/mitsubishi_itp/button/__init__.py new file mode 100644 index 000000000000..f9005cb83272 --- /dev/null +++ b/esphome/components/mitsubishi_itp/button/__init__.py @@ -0,0 +1,49 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.const import ( + ENTITY_CATEGORY_CONFIG, +) +from esphome.components.mitsubishi_itp.climate import ( + CONF_MITSUBISHI_IPT_ID, + mitsubishi_itp_ns, + MitsubishiUART, +) +from esphome.core import coroutine + +CONF_FILTER_RESET_BUTTON = "filter_reset_button" + +FilterResetButton = mitsubishi_itp_ns.class_( + "FilterResetButton", button.Button, cg.Component +) + +BUTTONS = { + CONF_FILTER_RESET_BUTTON: button.button_schema( + FilterResetButton, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:restore", + ), +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MITSUBISHI_IPT_ID): cv.use_id(MitsubishiUART), + } +).extend( + { + cv.Optional(button_designator): button_schema + for button_designator, button_schema in BUTTONS.items() + } +) + + +@coroutine +async def to_code(config): + muart_component = await cg.get_variable(config[CONF_MITSUBISHI_IPT_ID]) + + # Buttons + for button_designator, _ in BUTTONS.items(): + button_conf = config[button_designator] + button_component = await button.new_button(button_conf) + await cg.register_component(button_component, button_conf) + await cg.register_parented(button_component, muart_component) diff --git a/esphome/components/mitsubishi_itp/muart_button.h b/esphome/components/mitsubishi_itp/button/muart_button.h similarity index 94% rename from esphome/components/mitsubishi_itp/muart_button.h rename to esphome/components/mitsubishi_itp/button/muart_button.h index e95e1b8ce784..1884bc2f0341 100644 --- a/esphome/components/mitsubishi_itp/muart_button.h +++ b/esphome/components/mitsubishi_itp/button/muart_button.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/components/button/button.h" -#include "mitsubishi_uart.h" +#include "../mitsubishi_uart.h" namespace esphome { namespace mitsubishi_itp { diff --git a/esphome/components/mitsubishi_itp/climate.py b/esphome/components/mitsubishi_itp/climate.py index 6c2bf2e68892..6c80fb7b7c7b 100644 --- a/esphome/components/mitsubishi_itp/climate.py +++ b/esphome/components/mitsubishi_itp/climate.py @@ -3,10 +3,9 @@ from esphome.components import ( climate, uart, - time, - sensor, - button, select, + sensor, + time, ) from esphome.const import ( CONF_CUSTOM_FAN_MODES, @@ -42,9 +41,6 @@ CONF_VANE_POSITION_SELECT = "vane_position_select" CONF_HORIZONTAL_VANE_POSITION_SELECT = "horizontal_vane_position_select" -CONF_BUTTONS = "buttons" -CONF_FILTER_RESET_BUTTON = "filter_reset_button" - CONF_TEMPERATURE_SOURCES = ( "temperature_sources" # This is for specifying additional sources ) @@ -70,10 +66,6 @@ "HorizontalVanePositionSelect", select.Select ) -FilterResetButton = mitsubishi_itp_ns.class_( - "FilterResetButton", button.Button, cg.Component -) - DEFAULT_CLIMATE_MODES = ["OFF", "HEAT", "DRY", "COOL", "FAN_ONLY", "HEAT_COOL"] DEFAULT_FAN_MODES = ["AUTO", "QUIET", "LOW", "MEDIUM", "HIGH"] CUSTOM_FAN_MODES = {"VERYHIGH": mitsubishi_itp_ns.FAN_MODE_VERYHIGH} @@ -152,31 +144,10 @@ } ) -BUTTONS = { - CONF_FILTER_RESET_BUTTON: ( - "Filter Reset", - button.button_schema( - FilterResetButton, - entity_category=ENTITY_CATEGORY_CONFIG, - icon="mdi:restore", - ), - ) -} - -BUTTONS_SCHEMA = cv.All( - { - cv.Optional( - button_designator, default={"name": f"{button_name}"} - ): button_schema - for button_designator, (button_name, button_schema) in BUTTONS.items() - } -) - CONFIG_SCHEMA = BASE_SCHEMA.extend( { cv.Optional(CONF_SELECTS, default={}): SELECTS_SCHEMA, - cv.Optional(CONF_BUTTONS, default={}): BUTTONS_SCHEMA, } ) @@ -281,13 +252,6 @@ async def to_code(config): select_component, select_conf, options=select_options ) - # Buttons - for button_designator, (_, _) in BUTTONS.items(): - button_conf = config[CONF_BUTTONS][button_designator] - button_component = await button.new_button(button_conf) - await cg.register_component(button_component, button_conf) - await cg.register_parented(button_component, muart_component) - # Debug Settings if dam_conf := config.get(CONF_DISABLE_ACTIVE_MODE): cg.add(getattr(muart_component, "set_active_mode")(not dam_conf)) From 2f33cdb1ace378dad3a875172d8a1a8ac73c9628 Mon Sep 17 00:00:00 2001 From: Sammy1Am <467704+Sammy1Am@users.noreply.github.com> Date: Fri, 9 Aug 2024 06:32:30 +0000 Subject: [PATCH 5/9] Breakout select into directory/py file (temp sources not yet working) --- .../mitsubishi_itp/button/__init__.py | 6 +- esphome/components/mitsubishi_itp/climate.py | 122 ++---------------- .../mitsubishi_uart-packetprocessing.cpp | 118 +++++++++-------- .../mitsubishi_itp/mitsubishi_uart.cpp | 77 ++++++----- .../mitsubishi_itp/mitsubishi_uart.h | 10 +- .../mitsubishi_itp/select/__init__.py | 97 ++++++++++++++ .../{ => select}/muart_select.h | 2 +- 7 files changed, 229 insertions(+), 203 deletions(-) create mode 100644 esphome/components/mitsubishi_itp/select/__init__.py rename esphome/components/mitsubishi_itp/{ => select}/muart_select.h (96%) diff --git a/esphome/components/mitsubishi_itp/button/__init__.py b/esphome/components/mitsubishi_itp/button/__init__.py index f9005cb83272..798f052441da 100644 --- a/esphome/components/mitsubishi_itp/button/__init__.py +++ b/esphome/components/mitsubishi_itp/button/__init__.py @@ -1,14 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import button -from esphome.const import ( - ENTITY_CATEGORY_CONFIG, -) from esphome.components.mitsubishi_itp.climate import ( CONF_MITSUBISHI_IPT_ID, mitsubishi_itp_ns, MitsubishiUART, ) +from esphome.const import ( + ENTITY_CATEGORY_CONFIG, +) from esphome.core import coroutine CONF_FILTER_RESET_BUTTON = "filter_reset_button" diff --git a/esphome/components/mitsubishi_itp/climate.py b/esphome/components/mitsubishi_itp/climate.py index 6c80fb7b7c7b..126333404ec3 100644 --- a/esphome/components/mitsubishi_itp/climate.py +++ b/esphome/components/mitsubishi_itp/climate.py @@ -3,7 +3,6 @@ from esphome.components import ( climate, uart, - select, sensor, time, ) @@ -13,8 +12,6 @@ CONF_SUPPORTED_FAN_MODES, CONF_SUPPORTED_MODES, CONF_TIME_ID, - ENTITY_CATEGORY_CONFIG, - ENTITY_CATEGORY_NONE, ) from esphome.core import coroutine @@ -36,11 +33,6 @@ CONF_UART_HEATPUMP = "uart_heatpump" CONF_UART_THERMOSTAT = "uart_thermostat" -CONF_SELECTS = "selects" -CONF_TEMPERATURE_SOURCE_SELECT = "temperature_source_select" # This is to create a Select object for selecting a source -CONF_VANE_POSITION_SELECT = "vane_position_select" -CONF_HORIZONTAL_VANE_POSITION_SELECT = "horizontal_vane_position_select" - CONF_TEMPERATURE_SOURCES = ( "temperature_sources" # This is for specifying additional sources ) @@ -58,27 +50,13 @@ ) CONF_MITSUBISHI_IPT_ID = "mitsuibishi_itp_id" -TemperatureSourceSelect = mitsubishi_itp_ns.class_( - "TemperatureSourceSelect", select.Select -) -VanePositionSelect = mitsubishi_itp_ns.class_("VanePositionSelect", select.Select) -HorizontalVanePositionSelect = mitsubishi_itp_ns.class_( - "HorizontalVanePositionSelect", select.Select -) - DEFAULT_CLIMATE_MODES = ["OFF", "HEAT", "DRY", "COOL", "FAN_ONLY", "HEAT_COOL"] DEFAULT_FAN_MODES = ["AUTO", "QUIET", "LOW", "MEDIUM", "HIGH"] CUSTOM_FAN_MODES = {"VERYHIGH": mitsubishi_itp_ns.FAN_MODE_VERYHIGH} -VANE_POSITIONS = ["Auto", "1", "2", "3", "4", "5", "Swing"] -HORIZONTAL_VANE_POSITIONS = ["Auto", "<<", "<", "|", ">", ">>", "<>", "Swing"] - -INTERNAL_TEMPERATURE_SOURCE_OPTIONS = [ - mitsubishi_itp_ns.TEMPERATURE_SOURCE_INTERNAL -] # These will always be available validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) -BASE_SCHEMA = climate.CLIMATE_SCHEMA.extend( +CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( { cv.GenerateID(CONF_ID): cv.declare_id(MitsubishiUART), cv.Required(CONF_UART_HEATPUMP): cv.use_id(uart.UARTComponent), @@ -101,56 +79,6 @@ } ).extend(cv.polling_component_schema(DEFAULT_POLLING_INTERVAL)) -SELECTS = { - CONF_TEMPERATURE_SOURCE_SELECT: ( - "Temperature Source", - select.select_schema( - TemperatureSourceSelect, - entity_category=ENTITY_CATEGORY_CONFIG, - icon="mdi:thermometer-check", - ), - INTERNAL_TEMPERATURE_SOURCE_OPTIONS, - ), - CONF_VANE_POSITION_SELECT: ( - "Vane Position", - select.select_schema( - VanePositionSelect, - entity_category=ENTITY_CATEGORY_NONE, - icon="mdi:arrow-expand-vertical", - ), - VANE_POSITIONS, - ), - CONF_HORIZONTAL_VANE_POSITION_SELECT: ( - "Horizontal Vane Position", - select.select_schema( - HorizontalVanePositionSelect, - entity_category=ENTITY_CATEGORY_NONE, - icon="mdi:arrow-expand-horizontal", - ), - HORIZONTAL_VANE_POSITIONS, - ), -} - -SELECTS_SCHEMA = cv.All( - { - cv.Optional( - select_designator, default={"name": f"{select_name}"} - ): select_schema - for select_designator, ( - select_name, - select_schema, - _, - ) in SELECTS.items() - } -) - - -CONFIG_SCHEMA = BASE_SCHEMA.extend( - { - cv.Optional(CONF_SELECTS, default={}): SELECTS_SCHEMA, - } -) - def final_validate(config): schema = uart.final_validate_device_schema( @@ -193,8 +121,6 @@ async def to_code(config): # Register thermostat with MUART ts_uart_component = await cg.get_variable(config[CONF_UART_THERMOSTAT]) cg.add(getattr(muart_component, "set_thermostat_uart")(ts_uart_component)) - # Add sensor as source - SELECTS[CONF_TEMPERATURE_SOURCE_SELECT][2].append("Thermostat") # If RTC defined if CONF_TIME_ID in config: @@ -217,40 +143,18 @@ async def to_code(config): if CONF_CUSTOM_FAN_MODES in config: cg.add(traits.set_supported_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) - # Selects - - # Add additional configured temperature sensors to the select menu - for ts_id in config[CONF_TEMPERATURE_SOURCES]: - ts = await cg.get_variable(ts_id) - SELECTS[CONF_TEMPERATURE_SOURCE_SELECT][2].append(ts.get_name()) - cg.add( - getattr(ts, "add_on_state_callback")( - # TODO: Is there anyway to do this without a raw expression? - cg.RawExpression( - f"[](float v){{{getattr(muart_component, 'temperature_source_report')}({ts.get_name()}, v);}}" - ) - ) - ) - - # Register selects - for select_designator, ( - _, - _, - select_options, - ) in SELECTS.items(): - select_conf = config[CONF_SELECTS][select_designator] - select_component = cg.new_Pvariable(select_conf[CONF_ID]) - cg.add(getattr(muart_component, f"set_{select_designator}")(select_component)) - await cg.register_parented(select_component, muart_component) - - # For temperature source select, skip registration if there are less than two sources - if select_designator == CONF_TEMPERATURE_SOURCE_SELECT: - if len(SELECTS[CONF_TEMPERATURE_SOURCE_SELECT][2]) < 2: - continue - - await select.register_select( - select_component, select_conf, options=select_options - ) + # # Add additional configured temperature sensors to the select menu + # for ts_id in config[CONF_TEMPERATURE_SOURCES]: + # ts = await cg.get_variable(ts_id) + # TEMPERATURE_SOURCE_OPTIONS.append(ts.get_name()) + # cg.add( + # getattr(ts, "add_on_state_callback")( + # # TODO: Is there anyway to do this without a raw expression? + # cg.RawExpression( + # f"[](float v){{{getattr(muart_component, 'temperature_source_report')}({ts.get_name()}, v);}}" + # ) + # ) + # ) # Debug Settings if dam_conf := config.get(CONF_DISABLE_ACTIVE_MODE): diff --git a/esphome/components/mitsubishi_itp/mitsubishi_uart-packetprocessing.cpp b/esphome/components/mitsubishi_itp/mitsubishi_uart-packetprocessing.cpp index acd4d72d98bf..ad62be071ff5 100644 --- a/esphome/components/mitsubishi_itp/mitsubishi_uart-packetprocessing.cpp +++ b/esphome/components/mitsubishi_itp/mitsubishi_uart-packetprocessing.cpp @@ -158,65 +158,71 @@ void MitsubishiUART::process_packet(const SettingsGetResponsePacket &packet) { publish_on_update_ |= fan_changed; - // TODO: It would probably be nice to have the enum->string mapping defined somewhere to avoid typos/errors - const std::string old_vane_position = vane_position_select_->state; - switch (packet.get_vane()) { - case SettingsSetRequestPacket::VANE_AUTO: - vane_position_select_->state = "Auto"; - break; - case SettingsSetRequestPacket::VANE_1: - vane_position_select_->state = "1"; - break; - case SettingsSetRequestPacket::VANE_2: - vane_position_select_->state = "2"; - break; - case SettingsSetRequestPacket::VANE_3: - vane_position_select_->state = "3"; - break; - case SettingsSetRequestPacket::VANE_4: - vane_position_select_->state = "4"; - break; - case SettingsSetRequestPacket::VANE_5: - vane_position_select_->state = "5"; - break; - case SettingsSetRequestPacket::VANE_SWING: - vane_position_select_->state = "Swing"; - break; - default: - ESP_LOGW(TAG, "Vane in unknown position %x", packet.get_vane()); + // If we don't have a vane position select, there is no where to report this data, and no need to parse it + if (vane_position_select_) { + // TODO: It would probably be nice to have the enum->string mapping defined somewhere to avoid typos/errors + const std::string old_vane_position = vane_position_select_->state; + switch (packet.get_vane()) { + case SettingsSetRequestPacket::VANE_AUTO: + vane_position_select_->state = "Auto"; + break; + case SettingsSetRequestPacket::VANE_1: + vane_position_select_->state = "1"; + break; + case SettingsSetRequestPacket::VANE_2: + vane_position_select_->state = "2"; + break; + case SettingsSetRequestPacket::VANE_3: + vane_position_select_->state = "3"; + break; + case SettingsSetRequestPacket::VANE_4: + vane_position_select_->state = "4"; + break; + case SettingsSetRequestPacket::VANE_5: + vane_position_select_->state = "5"; + break; + case SettingsSetRequestPacket::VANE_SWING: + vane_position_select_->state = "Swing"; + break; + default: + ESP_LOGW(TAG, "Vane in unknown position %x", packet.get_vane()); + } + publish_on_update_ |= (old_vane_position != vane_position_select_->state); } - publish_on_update_ |= (old_vane_position != vane_position_select_->state); - const std::string old_horizontal_vane_position = horizontal_vane_position_select_->state; - switch (packet.get_horizontal_vane()) { - case SettingsSetRequestPacket::HV_AUTO: - horizontal_vane_position_select_->state = "Auto"; - break; - case SettingsSetRequestPacket::HV_LEFT_FULL: - horizontal_vane_position_select_->state = "<<"; - break; - case SettingsSetRequestPacket::HV_LEFT: - horizontal_vane_position_select_->state = "<"; - break; - case SettingsSetRequestPacket::HV_CENTER: - horizontal_vane_position_select_->state = "|"; - break; - case SettingsSetRequestPacket::HV_RIGHT: - horizontal_vane_position_select_->state = ">"; - break; - case SettingsSetRequestPacket::HV_RIGHT_FULL: - horizontal_vane_position_select_->state = ">>"; - break; - case SettingsSetRequestPacket::HV_SPLIT: - horizontal_vane_position_select_->state = "<>"; - break; - case SettingsSetRequestPacket::HV_SWING: - horizontal_vane_position_select_->state = "Swing"; - break; - default: - ESP_LOGW(TAG, "Vane in unknown horizontal position %x", packet.get_horizontal_vane()); + // If we don't have a horizontal vane position select, there is no where to report this data, and no need to parse it + if (horizontal_vane_position_select_) { + const std::string old_horizontal_vane_position = horizontal_vane_position_select_->state; + switch (packet.get_horizontal_vane()) { + case SettingsSetRequestPacket::HV_AUTO: + horizontal_vane_position_select_->state = "Auto"; + break; + case SettingsSetRequestPacket::HV_LEFT_FULL: + horizontal_vane_position_select_->state = "<<"; + break; + case SettingsSetRequestPacket::HV_LEFT: + horizontal_vane_position_select_->state = "<"; + break; + case SettingsSetRequestPacket::HV_CENTER: + horizontal_vane_position_select_->state = "|"; + break; + case SettingsSetRequestPacket::HV_RIGHT: + horizontal_vane_position_select_->state = ">"; + break; + case SettingsSetRequestPacket::HV_RIGHT_FULL: + horizontal_vane_position_select_->state = ">>"; + break; + case SettingsSetRequestPacket::HV_SPLIT: + horizontal_vane_position_select_->state = "<>"; + break; + case SettingsSetRequestPacket::HV_SWING: + horizontal_vane_position_select_->state = "Swing"; + break; + default: + ESP_LOGW(TAG, "Vane in unknown horizontal position %x", packet.get_horizontal_vane()); + } + publish_on_update_ |= (old_horizontal_vane_position != horizontal_vane_position_select_->state); } - publish_on_update_ |= (old_horizontal_vane_position != horizontal_vane_position_select_->state); } void MitsubishiUART::process_packet(const CurrentTempGetResponsePacket &packet) { diff --git a/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp b/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp index 22353a9970b1..2508c43b1cdf 100644 --- a/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp +++ b/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp @@ -22,9 +22,17 @@ MitsubishiUART::MitsubishiUART(uart::UARTComponent *hp_uart_comp) // Used to restore state of previous MUART-specific settings (like temperature source or pass-thru mode) // Most other climate-state is preserved by the heatpump itself and will be retrieved after connection void MitsubishiUART::setup() { - // Populate select map - for (size_t index = 0; index < temperature_source_select_->traits.get_options().size(); index++) { - temp_select_map_[temperature_source_select_->traits.get_options()[index]] = index; + if (temperature_source_select_) { + if (ts_uart_) { + auto options = temperature_source_select_->traits.get_options(); + options.push_back(TEMPERATURE_SOURCE_THERMOSTAT); + temperature_source_select_->traits.set_options(options); + } + + // Populate select map for preferences + for (size_t index = 0; index < temperature_source_select_->traits.get_options().size(); index++) { + temp_select_map_[temperature_source_select_->traits.get_options()[index]] = index; + } } // Using App.get_compilation_time() means these will get reset each time the firmware is updated, but this @@ -52,22 +60,27 @@ void MitsubishiUART::restore_preferences_() { MUARTPreferences prefs; if (preferences_.load(&prefs)) { // currentTemperatureSource - if (prefs.currentTemperatureSourceIndex.has_value() && - temperature_source_select_->has_index(prefs.currentTemperatureSourceIndex.value()) && - temperature_source_select_->at(prefs.currentTemperatureSourceIndex.value()).has_value()) { - current_temperature_source_ = temperature_source_select_->at(prefs.currentTemperatureSourceIndex.value()).value(); - temperature_source_select_->publish_state(current_temperature_source_); - ESP_LOGCONFIG(TAG, "Preferences loaded."); - } else { - ESP_LOGCONFIG(TAG, "Preferences loaded, but unsuitable values."); - current_temperature_source_ = TEMPERATURE_SOURCE_INTERNAL; - temperature_source_select_->publish_state(TEMPERATURE_SOURCE_INTERNAL); + if (temperature_source_select_) { + if (prefs.currentTemperatureSourceIndex.has_value() && + temperature_source_select_->has_index(prefs.currentTemperatureSourceIndex.value()) && + temperature_source_select_->at(prefs.currentTemperatureSourceIndex.value()).has_value()) { + current_temperature_source_ = + temperature_source_select_->at(prefs.currentTemperatureSourceIndex.value()).value(); + temperature_source_select_->publish_state(current_temperature_source_); + ESP_LOGCONFIG(TAG, "Preferences loaded."); + } else { + ESP_LOGCONFIG(TAG, "Preferences loaded, but unsuitable values."); + current_temperature_source_ = TEMPERATURE_SOURCE_INTERNAL; + temperature_source_select_->publish_state(TEMPERATURE_SOURCE_INTERNAL); + } } } else { // TODO: Shouldn't need to define setting all these defaults twice ESP_LOGCONFIG(TAG, "Preferences not loaded."); current_temperature_source_ = TEMPERATURE_SOURCE_INTERNAL; - temperature_source_select_->publish_state(TEMPERATURE_SOURCE_INTERNAL); + if (temperature_source_select_) { + temperature_source_select_->publish_state(TEMPERATURE_SOURCE_INTERNAL); + } } } @@ -95,19 +108,20 @@ void MitsubishiUART::loop() { if (ts_bridge_) ts_bridge_->loop(); - // If it's been too long since we received a temperature update (and we're not set to Internal) - if (((millis() - last_received_temperature_) > TEMPERATURE_SOURCE_TIMEOUT_MS) && - (temperature_source_select_->has_option(TEMPERATURE_SOURCE_INTERNAL)) && - (temperature_source_select_->state != TEMPERATURE_SOURCE_INTERNAL)) { - ESP_LOGW(TAG, "No temperature received from %s for %lu milliseconds, reverting to Internal source", - current_temperature_source_.c_str(), (unsigned long) TEMPERATURE_SOURCE_TIMEOUT_MS); - // Set the select to show Internal (but do not change currentTemperatureSource) - temperature_source_select_->publish_state(TEMPERATURE_SOURCE_INTERNAL); - // Send a packet to the heat pump to tell it to switch to internal temperature sensing - IFACTIVE(hp_bridge_.send_packet(RemoteTemperatureSetRequestPacket().use_internal_temperature());) + // If no temperature_source_select_ has been defined, Internal will always be used, no need to check on this + if (temperature_source_select_) { + // If it's been too long since we received a temperature update (and we're not set to Internal) + if (((millis() - last_received_temperature_) > TEMPERATURE_SOURCE_TIMEOUT_MS) && + (temperature_source_select_->has_option(TEMPERATURE_SOURCE_INTERNAL)) && + (temperature_source_select_->state != TEMPERATURE_SOURCE_INTERNAL)) { + ESP_LOGW(TAG, "No temperature received from %s for %lu milliseconds, reverting to Internal source", + current_temperature_source_.c_str(), (unsigned long) TEMPERATURE_SOURCE_TIMEOUT_MS); + // Set the select to show Internal (but do not change currentTemperatureSource) + temperature_source_select_->publish_state(TEMPERATURE_SOURCE_INTERNAL); + // Send a packet to the heat pump to tell it to switch to internal temperature sensing + IFACTIVE(hp_bridge_.send_packet(RemoteTemperatureSetRequestPacket().use_internal_temperature());) + } } - // - // Send packet to HP to tell it to use internal temp sensor } void MitsubishiUART::dump_config() { @@ -189,8 +203,13 @@ void MitsubishiUART::update() { void MitsubishiUART::do_publish_() { publish_state(); - vane_position_select_->publish_state(vane_position_select_->state); - horizontal_vane_position_select_->publish_state(horizontal_vane_position_select_->state); + if (vane_position_select_) { + vane_position_select_->publish_state(vane_position_select_->state); + } + + if (horizontal_vane_position_select_) { + horizontal_vane_position_select_->publish_state(horizontal_vane_position_select_->state); + } save_preferences_(); // We can save this every time we publish as writes to flash are by default collected and // delayed @@ -346,7 +365,7 @@ void MitsubishiUART::temperature_source_report(const std::string &temperature_so hp_bridge_.send_packet(pkt);) // If we've changed the select to reflect a temporary reversion to a different source, change it back. - if (temperature_source_select_->state != temperature_source) { + if (temperature_source_select_ && temperature_source_select_->state != temperature_source) { ESP_LOGI(TAG, "Temperature received, switching back to %s as source.", temperature_source.c_str()); temperature_source_select_->publish_state(temperature_source); } diff --git a/esphome/components/mitsubishi_itp/mitsubishi_uart.h b/esphome/components/mitsubishi_itp/mitsubishi_uart.h index a80092430483..d2431c11fa8d 100644 --- a/esphome/components/mitsubishi_itp/mitsubishi_uart.h +++ b/esphome/components/mitsubishi_itp/mitsubishi_uart.h @@ -25,10 +25,10 @@ const float MUART_TEMPERATURE_STEP = 0.5; const std::string FAN_MODE_VERYHIGH = "Very High"; const std::string TEMPERATURE_SOURCE_INTERNAL = "Internal"; -const uint32_t TEMPERATURE_SOURCE_TIMEOUT_MS = 420000; // (7min) The heatpump will revert on its own in ~10min - const std::string TEMPERATURE_SOURCE_THERMOSTAT = "Thermostat"; +const uint32_t TEMPERATURE_SOURCE_TIMEOUT_MS = 420000; // (7min) The heatpump will revert on its own in ~10min + // These are named to match with set fan speeds where possible. "Very Low" is a special speed // for e.g. preheating or thermal off const std::array ACTUAL_FAN_SPEED_NAMES = {"Off", "Very Low", "Low", "Medium", @@ -198,9 +198,9 @@ class MitsubishiUART : public PollingComponent, public climate::Climate, public text_sensor::TextSensor *thermostat_battery_sensor_ = nullptr; // Selects - select::Select *temperature_source_select_; - select::Select *vane_position_select_; - select::Select *horizontal_vane_position_select_; + select::Select *temperature_source_select_ = nullptr; + select::Select *vane_position_select_ = nullptr; + select::Select *horizontal_vane_position_select_ = nullptr; // Temperature select extras std::map temp_select_map_; // Used to map strings to indexes for preference storage diff --git a/esphome/components/mitsubishi_itp/select/__init__.py b/esphome/components/mitsubishi_itp/select/__init__.py new file mode 100644 index 000000000000..13abcce0f6a1 --- /dev/null +++ b/esphome/components/mitsubishi_itp/select/__init__.py @@ -0,0 +1,97 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import ( + select, +) +from esphome.components.mitsubishi_itp.climate import ( + CONF_MITSUBISHI_IPT_ID, + mitsubishi_itp_ns, + MitsubishiUART, +) +from esphome.const import ( + CONF_ID, + ENTITY_CATEGORY_CONFIG, + ENTITY_CATEGORY_NONE, +) +from esphome.core import coroutine + +CONF_TEMPERATURE_SOURCE = ( + "temperature_source" # This is to create a Select object for selecting a source +) +CONF_VANE_POSITION = "vane_position" +CONF_HORIZONTAL_VANE_POSITION = "horizontal_vane_position" + +VANE_POSITIONS = ["Auto", "1", "2", "3", "4", "5", "Swing"] +HORIZONTAL_VANE_POSITIONS = ["Auto", "<<", "<", "|", ">", ">>", "<>", "Swing"] + +TemperatureSourceSelect = mitsubishi_itp_ns.class_( + "TemperatureSourceSelect", select.Select +) +VanePositionSelect = mitsubishi_itp_ns.class_("VanePositionSelect", select.Select) +HorizontalVanePositionSelect = mitsubishi_itp_ns.class_( + "HorizontalVanePositionSelect", select.Select +) + +SELECTS = { + CONF_TEMPERATURE_SOURCE: ( + select.select_schema( + TemperatureSourceSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:thermometer-check", + ), + [mitsubishi_itp_ns.TEMPERATURE_SOURCE_INTERNAL], + ), + CONF_VANE_POSITION: ( + select.select_schema( + VanePositionSelect, + entity_category=ENTITY_CATEGORY_NONE, + icon="mdi:arrow-expand-vertical", + ), + VANE_POSITIONS, + ), + CONF_HORIZONTAL_VANE_POSITION: ( + select.select_schema( + HorizontalVanePositionSelect, + entity_category=ENTITY_CATEGORY_NONE, + icon="mdi:arrow-expand-horizontal", + ), + HORIZONTAL_VANE_POSITIONS, + ), +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_MITSUBISHI_IPT_ID): cv.use_id(MitsubishiUART), + } +).extend( + { + cv.Optional(select_designator): select_schema + for select_designator, ( + select_schema, + _, + ) in SELECTS.items() + } +) + + +@coroutine +async def to_code(config): + muart_component = await cg.get_variable(config[CONF_MITSUBISHI_IPT_ID]) + + # Register selects + for select_designator, ( + _, + select_options, + ) in SELECTS.items(): + if select_conf := config.get(select_designator): + select_component = cg.new_Pvariable(select_conf[CONF_ID]) + cg.add( + getattr(muart_component, f"set_{select_designator}_select")( + select_component + ) + ) + await cg.register_parented(select_component, muart_component) + + await select.register_select( + select_component, select_conf, options=select_options + ) diff --git a/esphome/components/mitsubishi_itp/muart_select.h b/esphome/components/mitsubishi_itp/select/muart_select.h similarity index 96% rename from esphome/components/mitsubishi_itp/muart_select.h rename to esphome/components/mitsubishi_itp/select/muart_select.h index 41d16550e97a..b886629a06dd 100644 --- a/esphome/components/mitsubishi_itp/muart_select.h +++ b/esphome/components/mitsubishi_itp/select/muart_select.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/components/select/select.h" -#include "mitsubishi_uart.h" +#include "../mitsubishi_uart.h" namespace esphome { namespace mitsubishi_itp { From a2794b336d2b39a419031dacc4202d0d82ded23c Mon Sep 17 00:00:00 2001 From: Sammy1Am <467704+Sammy1Am@users.noreply.github.com> Date: Fri, 9 Aug 2024 07:13:32 +0000 Subject: [PATCH 6/9] Fix temperature sources --- esphome/components/mitsubishi_itp/climate.py | 26 ++++++++++--------- .../mitsubishi_itp/mitsubishi_uart.cpp | 25 +++++++++--------- .../mitsubishi_itp/mitsubishi_uart.h | 5 +++- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/esphome/components/mitsubishi_itp/climate.py b/esphome/components/mitsubishi_itp/climate.py index 126333404ec3..2978e814fc2d 100644 --- a/esphome/components/mitsubishi_itp/climate.py +++ b/esphome/components/mitsubishi_itp/climate.py @@ -143,18 +143,20 @@ async def to_code(config): if CONF_CUSTOM_FAN_MODES in config: cg.add(traits.set_supported_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES])) - # # Add additional configured temperature sensors to the select menu - # for ts_id in config[CONF_TEMPERATURE_SOURCES]: - # ts = await cg.get_variable(ts_id) - # TEMPERATURE_SOURCE_OPTIONS.append(ts.get_name()) - # cg.add( - # getattr(ts, "add_on_state_callback")( - # # TODO: Is there anyway to do this without a raw expression? - # cg.RawExpression( - # f"[](float v){{{getattr(muart_component, 'temperature_source_report')}({ts.get_name()}, v);}}" - # ) - # ) - # ) + # Add additional configured temperature sensors to the select menu + for ts_id in config[CONF_TEMPERATURE_SOURCES]: + ts = await cg.get_variable(ts_id) + cg.add( + getattr(muart_component, "register_temperature_source")(ts.get_name().str()) + ) + cg.add( + getattr(ts, "add_on_state_callback")( + # TODO: Is there anyway to do this without a raw expression? + cg.RawExpression( + f"[](float v){{{getattr(muart_component, 'temperature_source_report')}({ts.get_name()}, v);}}" + ) + ) + ) # Debug Settings if dam_conf := config.get(CONF_DISABLE_ACTIVE_MODE): diff --git a/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp b/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp index 2508c43b1cdf..812247376575 100644 --- a/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp +++ b/esphome/components/mitsubishi_itp/mitsubishi_uart.cpp @@ -22,17 +22,12 @@ MitsubishiUART::MitsubishiUART(uart::UARTComponent *hp_uart_comp) // Used to restore state of previous MUART-specific settings (like temperature source or pass-thru mode) // Most other climate-state is preserved by the heatpump itself and will be retrieved after connection void MitsubishiUART::setup() { - if (temperature_source_select_) { - if (ts_uart_) { - auto options = temperature_source_select_->traits.get_options(); - options.push_back(TEMPERATURE_SOURCE_THERMOSTAT); - temperature_source_select_->traits.set_options(options); - } + if (ts_uart_) { + temp_select_options_.push_back(TEMPERATURE_SOURCE_THERMOSTAT); + } - // Populate select map for preferences - for (size_t index = 0; index < temperature_source_select_->traits.get_options().size(); index++) { - temp_select_map_[temperature_source_select_->traits.get_options()[index]] = index; - } + if (temperature_source_select_) { + temperature_source_select_->traits.set_options(temp_select_options_); } // Using App.get_compilation_time() means these will get reset each time the firmware is updated, but this @@ -47,9 +42,9 @@ void MitsubishiUART::save_preferences_() { // currentTemperatureSource // Save the index of the value stored in currentTemperatureSource just in case we're temporarily using Internal - auto index = temp_select_map_.find(current_temperature_source_); - if (index != temp_select_map_.end()) { - prefs.currentTemperatureSourceIndex = index->second; + auto index = find(temp_select_options_.begin(), temp_select_options_.end(), current_temperature_source_); + if (index != temp_select_options_.end()) { + prefs.currentTemperatureSourceIndex = index - temp_select_options_.begin(); } preferences_.save(&prefs); @@ -344,6 +339,10 @@ bool MitsubishiUART::select_horizontal_vane_position(const std::string &state) { return true; } +void MitsubishiUART::register_temperature_source(std::string temperature_source_name) { + temp_select_options_.push_back(temperature_source_name); +} + // Called by temperature_source sensors to report values. Will only take action if the currentTemperatureSource // matches the incoming source. Specifically this means that we are not storing any values // for sensors other than the current source, and selecting a different source won't have any diff --git a/esphome/components/mitsubishi_itp/mitsubishi_uart.h b/esphome/components/mitsubishi_itp/mitsubishi_uart.h index d2431c11fa8d..81e741223ee5 100644 --- a/esphome/components/mitsubishi_itp/mitsubishi_uart.h +++ b/esphome/components/mitsubishi_itp/mitsubishi_uart.h @@ -95,6 +95,8 @@ class MitsubishiUART : public PollingComponent, public climate::Climate, public bool select_vane_position(const std::string &state); bool select_horizontal_vane_position(const std::string &state); + // Adds an option to temperature_source_select_ + void register_temperature_source(std::string temperature_source_name); // Used by external sources to report a temperature void temperature_source_report(const std::string &temperature_source, const float &v); @@ -203,7 +205,8 @@ class MitsubishiUART : public PollingComponent, public climate::Climate, public select::Select *horizontal_vane_position_select_ = nullptr; // Temperature select extras - std::map temp_select_map_; // Used to map strings to indexes for preference storage + std::vector temp_select_options_ = { + TEMPERATURE_SOURCE_INTERNAL}; // Used to map strings to indexes for preference storage std::string current_temperature_source_ = TEMPERATURE_SOURCE_INTERNAL; uint32_t last_received_temperature_ = millis(); From 0845cb681205c441640269a8766a81012f494166 Mon Sep 17 00:00:00 2001 From: Sammy1Am <467704+Sammy1Am@users.noreply.github.com> Date: Fri, 9 Aug 2024 22:30:35 +0000 Subject: [PATCH 7/9] Fix some formatting and test issues --- .../components/mitsubishi_itp/button/__init__.py | 14 +++++++------- esphome/components/mitsubishi_itp/climate.py | 2 +- .../components/mitsubishi_itp/select/__init__.py | 14 +++++++------- esphome/components/mitsubishi_itp/sensor.py | 12 ++++++------ tests/components/mitsubishi_itp/common.yaml | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/esphome/components/mitsubishi_itp/button/__init__.py b/esphome/components/mitsubishi_itp/button/__init__.py index 798f052441da..f30ff71f8569 100644 --- a/esphome/components/mitsubishi_itp/button/__init__.py +++ b/esphome/components/mitsubishi_itp/button/__init__.py @@ -1,15 +1,15 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import button -from esphome.components.mitsubishi_itp.climate import ( - CONF_MITSUBISHI_IPT_ID, - mitsubishi_itp_ns, - MitsubishiUART, -) from esphome.const import ( ENTITY_CATEGORY_CONFIG, ) from esphome.core import coroutine +from ...mitsubishi_itp.climate import ( + CONF_MITSUBISHI_ITP_ID, + mitsubishi_itp_ns, + MitsubishiUART, +) CONF_FILTER_RESET_BUTTON = "filter_reset_button" @@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.Schema( { - cv.GenerateID(CONF_MITSUBISHI_IPT_ID): cv.use_id(MitsubishiUART), + cv.GenerateID(CONF_MITSUBISHI_ITP_ID): cv.use_id(MitsubishiUART), } ).extend( { @@ -39,7 +39,7 @@ @coroutine async def to_code(config): - muart_component = await cg.get_variable(config[CONF_MITSUBISHI_IPT_ID]) + muart_component = await cg.get_variable(config[CONF_MITSUBISHI_ITP_ID]) # Buttons for button_designator, _ in BUTTONS.items(): diff --git a/esphome/components/mitsubishi_itp/climate.py b/esphome/components/mitsubishi_itp/climate.py index 2978e814fc2d..44380db9a6a1 100644 --- a/esphome/components/mitsubishi_itp/climate.py +++ b/esphome/components/mitsubishi_itp/climate.py @@ -48,7 +48,7 @@ MitsubishiUART = mitsubishi_itp_ns.class_( "MitsubishiUART", cg.PollingComponent, climate.Climate ) -CONF_MITSUBISHI_IPT_ID = "mitsuibishi_itp_id" +CONF_MITSUBISHI_ITP_ID = "mitsubishi_itp_id" DEFAULT_CLIMATE_MODES = ["OFF", "HEAT", "DRY", "COOL", "FAN_ONLY", "HEAT_COOL"] DEFAULT_FAN_MODES = ["AUTO", "QUIET", "LOW", "MEDIUM", "HIGH"] diff --git a/esphome/components/mitsubishi_itp/select/__init__.py b/esphome/components/mitsubishi_itp/select/__init__.py index 13abcce0f6a1..fc133fe3a3b0 100644 --- a/esphome/components/mitsubishi_itp/select/__init__.py +++ b/esphome/components/mitsubishi_itp/select/__init__.py @@ -3,17 +3,17 @@ from esphome.components import ( select, ) -from esphome.components.mitsubishi_itp.climate import ( - CONF_MITSUBISHI_IPT_ID, - mitsubishi_itp_ns, - MitsubishiUART, -) from esphome.const import ( CONF_ID, ENTITY_CATEGORY_CONFIG, ENTITY_CATEGORY_NONE, ) from esphome.core import coroutine +from ...mitsubishi_itp.climate import ( + CONF_MITSUBISHI_ITP_ID, + mitsubishi_itp_ns, + MitsubishiUART, +) CONF_TEMPERATURE_SOURCE = ( "temperature_source" # This is to create a Select object for selecting a source @@ -61,7 +61,7 @@ CONFIG_SCHEMA = cv.Schema( { - cv.GenerateID(CONF_MITSUBISHI_IPT_ID): cv.use_id(MitsubishiUART), + cv.GenerateID(CONF_MITSUBISHI_ITP_ID): cv.use_id(MitsubishiUART), } ).extend( { @@ -76,7 +76,7 @@ @coroutine async def to_code(config): - muart_component = await cg.get_variable(config[CONF_MITSUBISHI_IPT_ID]) + muart_component = await cg.get_variable(config[CONF_MITSUBISHI_ITP_ID]) # Register selects for select_designator, ( diff --git a/esphome/components/mitsubishi_itp/sensor.py b/esphome/components/mitsubishi_itp/sensor.py index 50fbf8a34d04..c401e8f7d5b5 100644 --- a/esphome/components/mitsubishi_itp/sensor.py +++ b/esphome/components/mitsubishi_itp/sensor.py @@ -5,6 +5,10 @@ binary_sensor, text_sensor, ) +from esphome.components.mitsubishi_itp.climate import ( + CONF_MITSUBISHI_ITP_ID, + MitsubishiUART, +) from esphome.const import ( CONF_ID, CONF_OUTDOOR_TEMPERATURE, @@ -16,10 +20,6 @@ UNIT_HERTZ, UNIT_PERCENT, ) -from esphome.components.mitsubishi_itp.climate import ( - CONF_MITSUBISHI_IPT_ID, - MitsubishiUART, -) from esphome.core import coroutine CONF_THERMOSTAT_BATTERY = "thermostat_battery" @@ -112,7 +112,7 @@ CONFIG_SCHEMA = cv.Schema( { - cv.GenerateID(CONF_MITSUBISHI_IPT_ID): cv.use_id(MitsubishiUART), + cv.GenerateID(CONF_MITSUBISHI_ITP_ID): cv.use_id(MitsubishiUART), } ).extend( { @@ -127,7 +127,7 @@ @coroutine async def to_code(config): - muart_component = await cg.get_variable(config[CONF_MITSUBISHI_IPT_ID]) + muart_component = await cg.get_variable(config[CONF_MITSUBISHI_ITP_ID]) # Sensors diff --git a/tests/components/mitsubishi_itp/common.yaml b/tests/components/mitsubishi_itp/common.yaml index 50a539f0897b..63f1997c2593 100644 --- a/tests/components/mitsubishi_itp/common.yaml +++ b/tests/components/mitsubishi_itp/common.yaml @@ -36,7 +36,7 @@ climate: - platform: mitsubishi_itp uart_heatpump: hp_uart uart_thermostat: tstat_uart - time_source: homeassistant_time + time_id: homeassistant_time update_interval: 12s temperature_sources: - fake_temp From 3f5a13573960faf336f2390af9c787981d072478 Mon Sep 17 00:00:00 2001 From: Sammy1Am <467704+Sammy1Am@users.noreply.github.com> Date: Sat, 10 Aug 2024 08:34:07 +0000 Subject: [PATCH 8/9] Fix ci-custom test error --- esphome/components/mitsubishi_itp/sensor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/mitsubishi_itp/sensor.py b/esphome/components/mitsubishi_itp/sensor.py index c401e8f7d5b5..34521728bcd1 100644 --- a/esphome/components/mitsubishi_itp/sensor.py +++ b/esphome/components/mitsubishi_itp/sensor.py @@ -5,10 +5,6 @@ binary_sensor, text_sensor, ) -from esphome.components.mitsubishi_itp.climate import ( - CONF_MITSUBISHI_ITP_ID, - MitsubishiUART, -) from esphome.const import ( CONF_ID, CONF_OUTDOOR_TEMPERATURE, @@ -21,6 +17,10 @@ UNIT_PERCENT, ) from esphome.core import coroutine +from .climate import ( + CONF_MITSUBISHI_ITP_ID, + MitsubishiUART, +) CONF_THERMOSTAT_BATTERY = "thermostat_battery" CONF_THERMOSTAT_HUMIDITY = "thermostat_humidity" From 4542d4f9e2533017cb464ca607d017bbb108a5bf Mon Sep 17 00:00:00 2001 From: Sammy1Am <467704+Sammy1Am@users.noreply.github.com> Date: Sat, 10 Aug 2024 08:39:55 +0000 Subject: [PATCH 9/9] Fix test file --- tests/components/mitsubishi_itp/common.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/mitsubishi_itp/common.yaml b/tests/components/mitsubishi_itp/common.yaml index 63f1997c2593..1247f3a31d15 100644 --- a/tests/components/mitsubishi_itp/common.yaml +++ b/tests/components/mitsubishi_itp/common.yaml @@ -34,6 +34,7 @@ uart: climate: - platform: mitsubishi_itp + name: Climate uart_heatpump: hp_uart uart_thermostat: tstat_uart time_id: homeassistant_time