-
Notifications
You must be signed in to change notification settings - Fork 13
feat: Add remote temperature sensor support #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
dba4a27
4dd6a7e
b28fba8
ab90b65
c6df3ab
0b4d4e2
1ded4fe
db5413b
cb029fe
35535c0
f3ed2b6
2808db5
b778ae4
c18b58e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,12 +11,16 @@ | |||||||||||||
| from homeassistant.core import HomeAssistant | ||||||||||||||
| from homeassistant.data_entry_flow import AbortFlow | ||||||||||||||
| from homeassistant.exceptions import HomeAssistantError | ||||||||||||||
| from homeassistant.helpers import selector | ||||||||||||||
| from pymitsubishi import MitsubishiAPI, MitsubishiController | ||||||||||||||
|
|
||||||||||||||
| from .const import ( | ||||||||||||||
| CONF_ADMIN_PASSWORD, | ||||||||||||||
| CONF_ADMIN_USERNAME, | ||||||||||||||
| CONF_ENCRYPTION_KEY, | ||||||||||||||
| CONF_EXPERIMENTAL_FEATURES, | ||||||||||||||
| CONF_EXTERNAL_TEMP_ENTITY, | ||||||||||||||
| CONF_REMOTE_TEMP_MODE, | ||||||||||||||
| CONF_SCAN_INTERVAL, | ||||||||||||||
| DEFAULT_ADMIN_PASSWORD, | ||||||||||||||
| DEFAULT_ADMIN_USERNAME, | ||||||||||||||
|
|
@@ -25,6 +29,30 @@ | |||||||||||||
| DOMAIN, | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def _get_experimental_schema(suggested_value: str | None = None) -> vol.Schema: | ||||||||||||||
| """Get the schema for experimental features configuration.""" | ||||||||||||||
| entity_selector = selector.EntitySelector( | ||||||||||||||
| selector.EntitySelectorConfig( | ||||||||||||||
| domain=["sensor", "input_number", "number"], | ||||||||||||||
| ) | ||||||||||||||
| ) | ||||||||||||||
| if suggested_value: | ||||||||||||||
| return vol.Schema( | ||||||||||||||
| { | ||||||||||||||
| vol.Optional( | ||||||||||||||
| CONF_EXTERNAL_TEMP_ENTITY, | ||||||||||||||
| description={"suggested_value": suggested_value}, | ||||||||||||||
| ): entity_selector, | ||||||||||||||
| } | ||||||||||||||
| ) | ||||||||||||||
| return vol.Schema( | ||||||||||||||
| { | ||||||||||||||
| vol.Optional(CONF_EXTERNAL_TEMP_ENTITY): entity_selector, | ||||||||||||||
| } | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| _LOGGER = logging.getLogger(__name__) | ||||||||||||||
|
|
||||||||||||||
| STEP_USER_DATA_SCHEMA = vol.Schema( | ||||||||||||||
|
|
@@ -36,6 +64,7 @@ | |||||||||||||
| vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): vol.All( | ||||||||||||||
| vol.Coerce(int), vol.Range(min=10, max=300) | ||||||||||||||
| ), | ||||||||||||||
| vol.Optional(CONF_EXPERIMENTAL_FEATURES, default=False): bool, | ||||||||||||||
| } | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -80,6 +109,13 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | |||||||||||||
| VERSION = 1 | ||||||||||||||
| MINOR_VERSION = 1 | ||||||||||||||
|
|
||||||||||||||
| def __init__(self) -> None: | ||||||||||||||
| """Initialize the config flow.""" | ||||||||||||||
| super().__init__() | ||||||||||||||
| self._connection_data: dict[str, Any] = {} | ||||||||||||||
| self._experimental_features: bool = False | ||||||||||||||
| self._device_info: dict[str, Any] = {} | ||||||||||||||
|
|
||||||||||||||
| @staticmethod | ||||||||||||||
| def async_get_options_flow( | ||||||||||||||
| config_entry: config_entries.ConfigEntry, | ||||||||||||||
|
|
@@ -95,6 +131,9 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Any | |||||||||||||
| if user_input is not None: | ||||||||||||||
| _LOGGER.debug("Processing user input for config flow") | ||||||||||||||
| try: | ||||||||||||||
| # Extract experimental features flag | ||||||||||||||
| self._experimental_features = user_input.pop(CONF_EXPERIMENTAL_FEATURES, False) | ||||||||||||||
|
|
||||||||||||||
| _LOGGER.debug("Calling validate_input") | ||||||||||||||
| info = await validate_input(self.hass, user_input) | ||||||||||||||
| _LOGGER.debug("Validation successful, info: %s", info) | ||||||||||||||
|
|
@@ -103,8 +142,16 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Any | |||||||||||||
| await self.async_set_unique_id(info["unique_id"]) | ||||||||||||||
| self._abort_if_unique_id_configured() | ||||||||||||||
|
|
||||||||||||||
| _LOGGER.debug("Creating config entry") | ||||||||||||||
| return self.async_create_entry(title=info["title"], data=user_input) | ||||||||||||||
| # Store for later | ||||||||||||||
| self._connection_data = user_input | ||||||||||||||
| self._device_info = info | ||||||||||||||
|
|
||||||||||||||
| # If experimental features enabled, go to step 2 | ||||||||||||||
| if self._experimental_features: | ||||||||||||||
| return await self.async_step_experimental() | ||||||||||||||
|
|
||||||||||||||
| # Otherwise, create entry directly | ||||||||||||||
| return self._create_entry(external_temp_entity=None) | ||||||||||||||
|
Comment on lines
+149
to
+154
|
||||||||||||||
|
|
||||||||||||||
| except CannotConnect: | ||||||||||||||
| _LOGGER.error("Cannot connect to device") | ||||||||||||||
|
|
@@ -123,6 +170,31 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Any | |||||||||||||
| step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| async def async_step_experimental(self, user_input: dict[str, Any] | None = None) -> Any: | ||||||||||||||
| """Step 2: Configure experimental features (external temperature sensor).""" | ||||||||||||||
| if user_input is not None: | ||||||||||||||
| external_temp_entity = user_input.get(CONF_EXTERNAL_TEMP_ENTITY) | ||||||||||||||
| return self._create_entry(external_temp_entity=external_temp_entity) | ||||||||||||||
|
|
||||||||||||||
| return self.async_show_form(step_id="experimental", data_schema=_get_experimental_schema()) | ||||||||||||||
|
|
||||||||||||||
| def _create_entry(self, external_temp_entity: str | None) -> Any: | ||||||||||||||
| """Create the config entry with options.""" | ||||||||||||||
| _LOGGER.debug("Creating config entry") | ||||||||||||||
|
|
||||||||||||||
| # Build options | ||||||||||||||
| options: dict[str, Any] = { | ||||||||||||||
| CONF_EXPERIMENTAL_FEATURES: self._experimental_features, | ||||||||||||||
| } | ||||||||||||||
| if self._experimental_features and external_temp_entity: | ||||||||||||||
| options[CONF_EXTERNAL_TEMP_ENTITY] = external_temp_entity | ||||||||||||||
|
|
||||||||||||||
| return self.async_create_entry( | ||||||||||||||
| title=self._device_info["title"], | ||||||||||||||
| data=self._connection_data, | ||||||||||||||
| options=options, | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class OptionsFlowHandler(config_entries.OptionsFlow): | ||||||||||||||
| """Handle options flow for the Mitsubishi integration.""" | ||||||||||||||
|
|
@@ -131,26 +203,38 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None: | |||||||||||||
| """Initialize options flow.""" | ||||||||||||||
| super().__init__() | ||||||||||||||
|
niobos marked this conversation as resolved.
|
||||||||||||||
| self._config_entry = config_entry | ||||||||||||||
| self._connection_data: dict[str, Any] = {} | ||||||||||||||
| self._experimental_features: bool = False | ||||||||||||||
|
|
||||||||||||||
| @property | ||||||||||||||
| def config_entry(self) -> config_entries.ConfigEntry: | ||||||||||||||
| """Return the config entry.""" | ||||||||||||||
| return self._config_entry | ||||||||||||||
|
|
||||||||||||||
| async def async_step_init(self, user_input: dict[str, Any] | None = None) -> Any: | ||||||||||||||
| """Manage the options.""" | ||||||||||||||
| """Step 1: Connection settings and experimental features toggle.""" | ||||||||||||||
| errors: dict[str, str] = {} | ||||||||||||||
|
|
||||||||||||||
| if user_input is not None: | ||||||||||||||
| _LOGGER.debug("Processing options user input: %s", user_input) | ||||||||||||||
| _LOGGER.debug("Processing options step init: %s", user_input) | ||||||||||||||
| try: | ||||||||||||||
| # Validate the new configuration | ||||||||||||||
| # Extract experimental features flag | ||||||||||||||
| self._experimental_features = user_input.pop(CONF_EXPERIMENTAL_FEATURES, False) | ||||||||||||||
|
|
||||||||||||||
| # Validate the connection configuration | ||||||||||||||
| _LOGGER.debug("Validating new configuration") | ||||||||||||||
| await validate_input(self.hass, user_input) | ||||||||||||||
| _LOGGER.debug("Validation successful for options") | ||||||||||||||
|
|
||||||||||||||
| # Update the config entry with new data | ||||||||||||||
| self.hass.config_entries.async_update_entry(self._config_entry, data=user_input) | ||||||||||||||
| # Store connection data for later | ||||||||||||||
| self._connection_data = user_input | ||||||||||||||
|
|
||||||||||||||
| # Trigger reload of the integration to apply changes | ||||||||||||||
| await self.hass.config_entries.async_reload(self._config_entry.entry_id) | ||||||||||||||
| # If experimental features enabled, go to step 2 for entity selection | ||||||||||||||
| if self._experimental_features: | ||||||||||||||
| return await self.async_step_experimental() | ||||||||||||||
|
|
||||||||||||||
| return self.async_create_entry(title="", data={}) | ||||||||||||||
| # Otherwise, save and finish | ||||||||||||||
| return await self._async_save_options(external_temp_entity=None) | ||||||||||||||
|
|
||||||||||||||
| except CannotConnect: | ||||||||||||||
| _LOGGER.error("Cannot connect to device with new settings") | ||||||||||||||
|
|
@@ -160,19 +244,20 @@ async def async_step_init(self, user_input: dict[str, Any] | None = None) -> Any | |||||||||||||
| errors["base"] = "unknown" | ||||||||||||||
|
|
||||||||||||||
| # Create options schema with current values as defaults | ||||||||||||||
| current_host = self._config_entry.data.get(CONF_HOST, "") | ||||||||||||||
| current_encryption_key = self._config_entry.data.get( | ||||||||||||||
| current_host = self.config_entry.data.get(CONF_HOST, "") | ||||||||||||||
| current_encryption_key = self.config_entry.data.get( | ||||||||||||||
| CONF_ENCRYPTION_KEY, DEFAULT_ENCRYPTION_KEY | ||||||||||||||
| ) | ||||||||||||||
| current_admin_username = self._config_entry.data.get( | ||||||||||||||
| current_admin_username = self.config_entry.data.get( | ||||||||||||||
| CONF_ADMIN_USERNAME, DEFAULT_ADMIN_USERNAME | ||||||||||||||
| ) | ||||||||||||||
| current_admin_password = self._config_entry.data.get( | ||||||||||||||
| current_admin_password = self.config_entry.data.get( | ||||||||||||||
| CONF_ADMIN_PASSWORD, DEFAULT_ADMIN_PASSWORD | ||||||||||||||
| ) | ||||||||||||||
| current_scan_interval = self._config_entry.data.get( | ||||||||||||||
| current_scan_interval = self.config_entry.data.get( | ||||||||||||||
| CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL | ||||||||||||||
| ) | ||||||||||||||
| current_experimental = self.config_entry.options.get(CONF_EXPERIMENTAL_FEATURES, False) | ||||||||||||||
|
|
||||||||||||||
| options_schema = vol.Schema( | ||||||||||||||
| { | ||||||||||||||
|
|
@@ -183,11 +268,54 @@ async def async_step_init(self, user_input: dict[str, Any] | None = None) -> Any | |||||||||||||
| vol.Optional(CONF_SCAN_INTERVAL, default=current_scan_interval): vol.All( | ||||||||||||||
| vol.Coerce(int), vol.Range(min=10, max=300) | ||||||||||||||
| ), | ||||||||||||||
| vol.Optional(CONF_EXPERIMENTAL_FEATURES, default=current_experimental): bool, | ||||||||||||||
| } | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| return self.async_show_form(step_id="init", data_schema=options_schema, errors=errors) | ||||||||||||||
|
|
||||||||||||||
| async def async_step_experimental(self, user_input: dict[str, Any] | None = None) -> Any: | ||||||||||||||
| """Step 2: Configure experimental features (external temperature sensor).""" | ||||||||||||||
| if user_input is not None: | ||||||||||||||
| external_temp_entity = user_input.get(CONF_EXTERNAL_TEMP_ENTITY) | ||||||||||||||
| return await self._async_save_options(external_temp_entity=external_temp_entity) | ||||||||||||||
|
|
||||||||||||||
| current_external_temp_entity = self.config_entry.options.get(CONF_EXTERNAL_TEMP_ENTITY, "") | ||||||||||||||
|
|
||||||||||||||
| return self.async_show_form( | ||||||||||||||
| step_id="experimental", | ||||||||||||||
| data_schema=_get_experimental_schema(current_external_temp_entity or None), | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| async def _async_save_options(self, external_temp_entity: str | None) -> Any: | ||||||||||||||
| """Save connection data and options.""" | ||||||||||||||
| # Update the config entry data (connection settings) | ||||||||||||||
| self.hass.config_entries.async_update_entry( | ||||||||||||||
| self.config_entry, | ||||||||||||||
| data=self._connection_data, | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| # Trigger reload of the integration to apply changes | ||||||||||||||
| await self.hass.config_entries.async_reload(self.config_entry.entry_id) | ||||||||||||||
|
|
||||||||||||||
| # Build new options | ||||||||||||||
| new_options: dict[str, Any] = { | ||||||||||||||
| CONF_EXPERIMENTAL_FEATURES: self._experimental_features, | ||||||||||||||
| } | ||||||||||||||
| if self._experimental_features: | ||||||||||||||
| # Only preserve experimental settings when experimental features are enabled | ||||||||||||||
| if external_temp_entity: | ||||||||||||||
| new_options[CONF_EXTERNAL_TEMP_ENTITY] = external_temp_entity | ||||||||||||||
| # Preserve remote_temp_mode from existing options | ||||||||||||||
| if CONF_REMOTE_TEMP_MODE in self.config_entry.options: | ||||||||||||||
| new_options[CONF_REMOTE_TEMP_MODE] = self.config_entry.options[ | ||||||||||||||
| CONF_REMOTE_TEMP_MODE | ||||||||||||||
| ] | ||||||||||||||
| # When experimental features are disabled, don't preserve remote_temp_mode | ||||||||||||||
| # This ensures a clean state when the feature is turned off | ||||||||||||||
|
Comment on lines
+314
to
+315
|
||||||||||||||
| # When experimental features are disabled, don't preserve remote_temp_mode | |
| # This ensures a clean state when the feature is turned off | |
| else: | |
| # When experimental features are disabled, explicitly disable remote_temp_mode | |
| # This ensures a clean state when the feature is turned off | |
| new_options[CONF_REMOTE_TEMP_MODE] = False |
Uh oh!
There was an error while loading. Please reload this page.