diff --git a/custom_components/pstryk/config_flow.py b/custom_components/pstryk/config_flow.py index 2b67341..7ac12f5 100644 --- a/custom_components/pstryk/config_flow.py +++ b/custom_components/pstryk/config_flow.py @@ -1,4 +1,4 @@ -"""Config flow for Pstryk Energy integration.""" +"""Config flow for Pstryk Energy integration - Enhanced version.""" from homeassistant import config_entries import voluptuous as vol import aiohttp @@ -36,97 +36,128 @@ class MQTTNotConfiguredError(HomeAssistantError): class PstrykConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Pstryk Energy.""" VERSION = 3 - # Version 3 adds retry mechanism configuration - # Version 2 adds MQTT support and 48h mode + + def __init__(self): + """Initialize the config flow.""" + self._data = {} + self._options = {} async def async_step_user(self, user_input=None): - """Handle the initial step.""" + """Handle the initial step - API configuration.""" errors = {} if user_input is not None: - # Sprawdź poprawność API key + # Validate API key api_key = user_input["api_key"] valid = await self._validate_api_key(api_key) if valid: - # Check MQTT configuration if enabled - mqtt_enabled = user_input.get(CONF_MQTT_ENABLED, False) - if mqtt_enabled: - mqtt_configured = await self._check_mqtt_configuration() - if not mqtt_configured: - errors["base"] = "mqtt_not_configured" - - if not errors: - # Extract MQTT related configs - data = { - "api_key": user_input["api_key"], - "buy_top": user_input["buy_top"], - "sell_top": user_input["sell_top"], - "buy_worst": user_input["buy_worst"], - "sell_worst": user_input["sell_worst"], - } - - # Add MQTT configs and retry mechanism to options - options = { - CONF_MQTT_ENABLED: user_input.get(CONF_MQTT_ENABLED, False), - CONF_MQTT_TOPIC_BUY: user_input.get(CONF_MQTT_TOPIC_BUY, DEFAULT_MQTT_TOPIC_BUY), - CONF_MQTT_TOPIC_SELL: user_input.get(CONF_MQTT_TOPIC_SELL, DEFAULT_MQTT_TOPIC_SELL), - CONF_MQTT_48H_MODE: user_input.get(CONF_MQTT_48H_MODE, False), - CONF_RETRY_ATTEMPTS: user_input.get(CONF_RETRY_ATTEMPTS, DEFAULT_RETRY_ATTEMPTS), - CONF_RETRY_DELAY: user_input.get(CONF_RETRY_DELAY, DEFAULT_RETRY_DELAY), - } - - return self.async_create_entry( - title="Pstryk Energy", - data=data, - options=options - ) + self._data["api_key"] = api_key + # Move to price settings step + return await self.async_step_price_settings() else: errors["api_key"] = "invalid_api_key" - # Check if MQTT integration is loaded - mqtt_enabled = False - try: - mqtt_enabled = self.hass.services.has_service("mqtt", "publish") - except Exception: - mqtt_enabled = False + return self.async_show_form( + step_id="user", + data_schema=vol.Schema({ + vol.Required("api_key"): str, + }), + errors=errors + ) + + async def async_step_price_settings(self, user_input=None): + """Handle price monitoring settings.""" + if user_input is not None: + # Store price settings + self._data.update({ + "buy_top": user_input["buy_top"], + "sell_top": user_input["sell_top"], + "buy_worst": user_input["buy_worst"], + "sell_worst": user_input["sell_worst"], + }) - # Base schema for required fields - schema = { - vol.Required("api_key"): str, - vol.Required("buy_top", default=5): vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), - vol.Required("sell_top", default=5): vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), - vol.Required("buy_worst", default=5): vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), - vol.Required("sell_worst", default=5): vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), - } + # Check if MQTT is available + mqtt_available = await self._check_mqtt_availability() + if mqtt_available: + return await self.async_step_mqtt_settings() + else: + # Skip MQTT and go to API retry settings + return await self.async_step_api_retry() + + return self.async_show_form( + step_id="price_settings", + data_schema=vol.Schema({ + vol.Required("buy_top", default=5): vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), + vol.Required("sell_top", default=5): vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), + vol.Required("buy_worst", default=5): vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), + vol.Required("sell_worst", default=5): vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), + }) + ) + + async def async_step_mqtt_settings(self, user_input=None): + """Handle MQTT configuration.""" + errors = {} - # Add MQTT fields if MQTT integration is loaded - if mqtt_enabled: - schema.update({ + if user_input is not None: + # Check MQTT configuration if enabled + mqtt_enabled = user_input.get(CONF_MQTT_ENABLED, False) + if mqtt_enabled: + mqtt_configured = await self._check_mqtt_configuration() + if not mqtt_configured: + errors["base"] = "mqtt_not_configured" + + if not errors: + # Store MQTT settings + self._options.update({ + CONF_MQTT_ENABLED: user_input.get(CONF_MQTT_ENABLED, False), + CONF_MQTT_TOPIC_BUY: user_input.get(CONF_MQTT_TOPIC_BUY, DEFAULT_MQTT_TOPIC_BUY), + CONF_MQTT_TOPIC_SELL: user_input.get(CONF_MQTT_TOPIC_SELL, DEFAULT_MQTT_TOPIC_SELL), + CONF_MQTT_48H_MODE: user_input.get(CONF_MQTT_48H_MODE, False), + }) + # Move to API retry settings + return await self.async_step_api_retry() + + return self.async_show_form( + step_id="mqtt_settings", + data_schema=vol.Schema({ vol.Required(CONF_MQTT_ENABLED, default=False): bool, vol.Optional(CONF_MQTT_TOPIC_BUY, default=DEFAULT_MQTT_TOPIC_BUY): str, vol.Optional(CONF_MQTT_TOPIC_SELL, default=DEFAULT_MQTT_TOPIC_SELL): str, vol.Optional(CONF_MQTT_48H_MODE, default=False): bool, + }), + errors=errors + ) + + async def async_step_api_retry(self, user_input=None): + """Handle API retry configuration.""" + if user_input is not None: + # Store retry settings + self._options.update({ + CONF_RETRY_ATTEMPTS: user_input.get(CONF_RETRY_ATTEMPTS, DEFAULT_RETRY_ATTEMPTS), + CONF_RETRY_DELAY: user_input.get(CONF_RETRY_DELAY, DEFAULT_RETRY_DELAY), }) - - # Add retry mechanism fields - schema.update({ - vol.Optional(CONF_RETRY_ATTEMPTS, default=DEFAULT_RETRY_ATTEMPTS): vol.All( - vol.Coerce(int), vol.Range(min=MIN_RETRY_ATTEMPTS, max=MAX_RETRY_ATTEMPTS) - ), - vol.Optional(CONF_RETRY_DELAY, default=DEFAULT_RETRY_DELAY): vol.All( - vol.Coerce(int), vol.Range(min=MIN_RETRY_DELAY, max=MAX_RETRY_DELAY) - ), - }) + + # Create the config entry + return self.async_create_entry( + title="Pstryk Energy", + data=self._data, + options=self._options + ) return self.async_show_form( - step_id="user", - data_schema=vol.Schema(schema), - errors=errors + step_id="api_retry", + data_schema=vol.Schema({ + vol.Optional(CONF_RETRY_ATTEMPTS, default=DEFAULT_RETRY_ATTEMPTS): vol.All( + vol.Coerce(int), vol.Range(min=MIN_RETRY_ATTEMPTS, max=MAX_RETRY_ATTEMPTS) + ), + vol.Optional(CONF_RETRY_DELAY, default=DEFAULT_RETRY_DELAY): vol.All( + vol.Coerce(int), vol.Range(min=MIN_RETRY_DELAY, max=MAX_RETRY_DELAY) + ), + }) ) async def _validate_api_key(self, api_key): """Validate API key by calling a simple API endpoint.""" - # Używamy endpointu buy z krótkim oknem czasowym dla szybkiego sprawdzenia now = dt_util.utcnow() start_utc = now.strftime("%Y-%m-%dT%H:%M:%SZ") end_utc = (now + timedelta(hours=1)).strftime("%Y-%m-%dT%H:%M:%SZ") @@ -148,8 +179,13 @@ class PstrykConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _check_mqtt_configuration(self): """Check if MQTT integration is properly configured.""" try: - # Sprawdź czy usługa MQTT publish jest dostępna - # To powinno działać zarówno z dodatkiem MQTT jak i z integracją podstawową + return self.hass.services.has_service("mqtt", "publish") + except Exception: + return False + + async def _check_mqtt_availability(self): + """Check if MQTT integration is available (not necessarily configured).""" + try: return self.hass.services.has_service("mqtt", "publish") except Exception: return False @@ -161,14 +197,14 @@ class PstrykConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class PstrykOptionsFlowHandler(config_entries.OptionsFlow): - """Handle Pstryk options.""" + """Handle options flow for Pstryk Energy - single page view.""" def __init__(self, config_entry): """Initialize options flow.""" self.config_entry = config_entry async def async_step_init(self, user_input=None): - """Manage the options.""" + """Manage the options - single page for quick configuration.""" errors = {} if user_input is not None: @@ -189,25 +225,27 @@ class PstrykOptionsFlowHandler(config_entries.OptionsFlow): except Exception: mqtt_enabled = False - # Base schema for required fields - options = { + # Build schema with better organization + # We use field naming to create visual grouping + schema = { + # Price Monitoring Settings - prefix with numbers for ordering vol.Required("buy_top", default=self.config_entry.options.get( - "buy_top", self.config_entry.data.get("buy_top", 5))): vol.All( - vol.Coerce(int), vol.Range(min=1, max=24)), + "buy_top", self.config_entry.data.get("buy_top", 5))): + vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), vol.Required("sell_top", default=self.config_entry.options.get( - "sell_top", self.config_entry.data.get("sell_top", 5))): vol.All( - vol.Coerce(int), vol.Range(min=1, max=24)), + "sell_top", self.config_entry.data.get("sell_top", 5))): + vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), vol.Required("buy_worst", default=self.config_entry.options.get( - "buy_worst", self.config_entry.data.get("buy_worst", 5))): vol.All( - vol.Coerce(int), vol.Range(min=1, max=24)), + "buy_worst", self.config_entry.data.get("buy_worst", 5))): + vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), vol.Required("sell_worst", default=self.config_entry.options.get( - "sell_worst", self.config_entry.data.get("sell_worst", 5))): vol.All( - vol.Coerce(int), vol.Range(min=1, max=24)), + "sell_worst", self.config_entry.data.get("sell_worst", 5))): + vol.All(vol.Coerce(int), vol.Range(min=1, max=24)), } - # Add MQTT fields if MQTT integration is loaded + # MQTT Configuration (if available) if mqtt_enabled: - options.update({ + schema.update({ vol.Required(CONF_MQTT_ENABLED, default=self.config_entry.options.get( CONF_MQTT_ENABLED, False)): bool, vol.Optional(CONF_MQTT_TOPIC_BUY, default=self.config_entry.options.get( @@ -218,29 +256,42 @@ class PstrykOptionsFlowHandler(config_entries.OptionsFlow): CONF_MQTT_48H_MODE, False)): bool, }) - # Add retry mechanism fields - options.update({ + # API Configuration + schema.update({ vol.Optional(CONF_RETRY_ATTEMPTS, default=self.config_entry.options.get( - CONF_RETRY_ATTEMPTS, DEFAULT_RETRY_ATTEMPTS)): vol.All( - vol.Coerce(int), vol.Range(min=MIN_RETRY_ATTEMPTS, max=MAX_RETRY_ATTEMPTS) - ), + CONF_RETRY_ATTEMPTS, DEFAULT_RETRY_ATTEMPTS)): + vol.All(vol.Coerce(int), vol.Range(min=MIN_RETRY_ATTEMPTS, max=MAX_RETRY_ATTEMPTS)), vol.Optional(CONF_RETRY_DELAY, default=self.config_entry.options.get( - CONF_RETRY_DELAY, DEFAULT_RETRY_DELAY)): vol.All( - vol.Coerce(int), vol.Range(min=MIN_RETRY_DELAY, max=MAX_RETRY_DELAY) - ), + CONF_RETRY_DELAY, DEFAULT_RETRY_DELAY)): + vol.All(vol.Coerce(int), vol.Range(min=MIN_RETRY_DELAY, max=MAX_RETRY_DELAY)), }) + # Add description with section information + description_text = "Configure your energy price monitoring settings" + if mqtt_enabled: + description_text += "\n\n**Note:** Settings are grouped by: Price Monitoring, MQTT Bridge, and API Configuration" + return self.async_show_form( step_id="init", - data_schema=vol.Schema(options), - errors=errors + data_schema=vol.Schema(schema), + errors=errors, + description_placeholders={ + "description": description_text + } ) - + async def _check_mqtt_configuration(self): - """Check if MQTT integration is properly configured.""" + """Check if MQTT is properly configured.""" try: - # Sprawdź czy usługa MQTT publish jest dostępna - # To powinno działać zarówno z dodatkiem MQTT jak i z integracją podstawową + mqtt_config_entry = None + for entry in self.hass.config_entries.async_entries("mqtt"): + if entry.state == config_entries.ConfigEntryState.LOADED: + mqtt_config_entry = entry + break + + if not mqtt_config_entry: + return False + return self.hass.services.has_service("mqtt", "publish") except Exception: return False