diff --git a/custom_components/powercalc/config_flow.py b/custom_components/powercalc/config_flow.py index 3b375959c..1b28e66ee 100644 --- a/custom_components/powercalc/config_flow.py +++ b/custom_components/powercalc/config_flow.py @@ -49,6 +49,7 @@ CONF_START_TIME, CONF_STATES_POWER, CONF_SUB_GROUPS, + CONF_SUB_PROFILE, CONF_UPDATE_FREQUENCY, CONF_VALUE, CONF_VALUE_TEMPLATE, @@ -60,7 +61,6 @@ from .errors import StrategyConfigurationError from .power_profile.library import ModelInfo, ProfileLibrary from .power_profile.model_discovery import autodiscover_model -from .power_profile.power_profile import PowerProfile from .sensors.daily_energy import DEFAULT_DAILY_UPDATE_FREQUENCY from .strategy.factory import PowerCalculatorStrategyFactory from .strategy.strategy_interface import PowerCalculationStrategyInterface @@ -385,6 +385,16 @@ async def async_step_lut_model( errors = {} if user_input is not None: self.sensor_config.update({CONF_MODEL: user_input.get(CONF_MODEL)}) + library = ProfileLibrary(self.hass) + profile = await library.get_profile( + ModelInfo( + self.sensor_config.get(CONF_MANUFACTURER), + self.sensor_config.get(CONF_MODEL) + ) + ) + sub_profiles = await library.get_subprofile_listing(profile) + if sub_profiles: + return await self.async_step_lut_subprofile() errors = await self.validate_strategy_config() if not errors: return self.create_config_entry() @@ -397,6 +407,31 @@ async def async_step_lut_model( errors=errors, ) + async def async_step_lut_subprofile( + self, user_input: dict[str, str] = None + ) -> FlowResult: + errors = {} + if user_input is not None: + # Append the sub profile to the model + model = f"{self.sensor_config.get(CONF_MODEL)}/{user_input.get(CONF_SUB_PROFILE)}" + self.sensor_config[CONF_MODEL] = model + errors = await self.validate_strategy_config() + if not errors: + return self.create_config_entry() + + model_info = ModelInfo( + self.sensor_config.get(CONF_MANUFACTURER), + self.sensor_config.get(CONF_MODEL) + ) + return self.async_show_form( + step_id="lut_subprofile", + data_schema=await _create_lut_schema_subprofile( + self.hass, + model_info + ), + errors=errors, + ) + async def validate_strategy_config(self) -> dict: strategy_name = self.sensor_config.get(CONF_MODE) strategy = await _create_strategy_object( @@ -632,6 +667,24 @@ def _create_lut_schema_model(hass: HomeAssistant, manufacturer: str) -> vol.Sche } ) +async def _create_lut_schema_subprofile(hass: HomeAssistant, model_info: ModelInfo) -> vol.Schema: + """Create LUT schema""" + library = ProfileLibrary(hass) + profile = await library.get_profile(model_info) + sub_profiles = [ + selector.SelectOptionDict(value=sub_profile, label=sub_profile) + for sub_profile in await library.get_subprofile_listing(profile) + ] + return vol.Schema( + { + vol.Required(CONF_SUB_PROFILE): selector.SelectSelector( + selector.SelectSelectorConfig( + options=sub_profiles, mode=selector.SelectSelectorMode.DROPDOWN + ) + ) + } + ) + def _build_strategy_config( strategy: str, source_entity_id: str, user_input: dict[str, str] = None diff --git a/custom_components/powercalc/const.py b/custom_components/powercalc/const.py index a65666088..71a3423f7 100644 --- a/custom_components/powercalc/const.py +++ b/custom_components/powercalc/const.py @@ -69,6 +69,7 @@ CONF_ON_TIME = "on_time" CONF_TEMPLATE = "template" CONF_SENSOR_TYPE = "sensor_type" +CONF_SUB_PROFILE = "sub_profile" CONF_UPDATE_FREQUENCY = "update_frequency" CONF_VALUE = "value" CONF_VALUE_TEMPLATE = "value_template" diff --git a/custom_components/powercalc/power_profile/library.py b/custom_components/powercalc/power_profile/library.py index 91917a329..6b2c0a98d 100644 --- a/custom_components/powercalc/power_profile/library.py +++ b/custom_components/powercalc/power_profile/library.py @@ -61,6 +61,14 @@ def get_model_listing(self, manufacturer: str) -> list[str]: continue models.extend(os.listdir(manufacturer_dir)) return sorted(models) + + async def get_subprofile_listing(self, profile: PowerProfile) -> list[str]: + """Get listing op possible sub profiles""" + return sorted(list( + next( + os.walk(profile.get_model_directory()) + )[1] + )) async def get_profile( self, model_info: ModelInfo, custom_directory: str | None = None diff --git a/custom_components/powercalc/power_profile/power_profile.py b/custom_components/powercalc/power_profile/power_profile.py index e06fc7075..c043fca4c 100644 --- a/custom_components/powercalc/power_profile/power_profile.py +++ b/custom_components/powercalc/power_profile/power_profile.py @@ -55,7 +55,7 @@ def load_sub_profile(self, sub_profile: str) -> None: self.sub_profile = sub_profile - def get_lut_directory(self) -> str: + def get_model_directory(self) -> str: if self.linked_lut: return os.path.join(os.path.dirname(__file__), "../data", self.linked_lut) diff --git a/custom_components/powercalc/strategy/lut.py b/custom_components/powercalc/strategy/lut.py index 63e9a2955..530c1d14f 100644 --- a/custom_components/powercalc/strategy/lut.py +++ b/custom_components/powercalc/strategy/lut.py @@ -77,7 +77,7 @@ async def get_lookup_dictionary( return lookup_dict def get_lut_file(self, power_profile: PowerProfile, color_mode: str): - path = os.path.join(power_profile.get_lut_directory(), f"{color_mode}.csv") + path = os.path.join(power_profile.get_model_directory(), f"{color_mode}.csv") gzip_path = f"{path}.gz" if os.path.exists(gzip_path): diff --git a/custom_components/powercalc/strings.json b/custom_components/powercalc/strings.json index c43b48ebe..4d783c461 100644 --- a/custom_components/powercalc/strings.json +++ b/custom_components/powercalc/strings.json @@ -121,6 +121,13 @@ "data": { "model": "Model ID" } + }, + "lut_subprofile": { + "title": "LUT config", + "description": "This model has multiple sub profiles. Select one that suites your device", + "data": { + "sub_profile": "Sub profile" + } } }, "error": { diff --git a/custom_components/powercalc/translations/en.json b/custom_components/powercalc/translations/en.json index 14c0a1565..6f55e27dc 100644 --- a/custom_components/powercalc/translations/en.json +++ b/custom_components/powercalc/translations/en.json @@ -99,6 +99,13 @@ "description": "Select the device model", "title": "LUT config" }, + "lut_subprofile": { + "data": { + "sub_profile": "Sub profile" + }, + "description": "This model has multiple sub profiles. Select one that suites your device", + "title": "LUT config" + }, "user": { "data": { "sensor_type": "Sensor type" diff --git a/custom_components/powercalc/translations/nl.json b/custom_components/powercalc/translations/nl.json index 82d5c9004..73650a3d2 100644 --- a/custom_components/powercalc/translations/nl.json +++ b/custom_components/powercalc/translations/nl.json @@ -99,6 +99,13 @@ "description": "Select het model", "title": "LUT configuratie" }, + "lut_subprofile": { + "data": { + "sub_profile": "Sub profiel" + }, + "description": "Dit model heeft meerdere profielen. Selecteer de juiste voor je apparaat", + "title": "LUT config" + }, "user": { "data": { "sensor_type": "Sensor type" diff --git a/tests/power_profile/test_library.py b/tests/power_profile/test_library.py index 2c4724730..3f600ec90 100644 --- a/tests/power_profile/test_library.py +++ b/tests/power_profile/test_library.py @@ -18,6 +18,17 @@ async def test_model_listing(hass: HomeAssistant): assert "LCT010" in models assert "LCA007" in models +async def test_get_subprofile_listing(hass: HomeAssistant): + library = ProfileLibrary(hass) + profile = await library.get_profile(ModelInfo("yeelight", "YLDL01YL")) + sub_profiles = await library.get_subprofile_listing(profile) + assert sub_profiles == ["ambilight", "downlight"] + +async def test_get_subprofile_listing_empty_list(hass: HomeAssistant): + library = ProfileLibrary(hass) + profile = await library.get_profile(ModelInfo("signify", "LCT010")) + sub_profiles = await library.get_subprofile_listing(profile) + assert sub_profiles == [] async def test_non_existing_manufacturer_returns_empty_model_list(hass: HomeAssistant): library = ProfileLibrary(hass) diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 5d173800d..f91df879b 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -43,6 +43,7 @@ CONF_POWER_TEMPLATE, CONF_SENSOR_TYPE, CONF_STATES_POWER, + CONF_SUB_PROFILE, CONF_UPDATE_FREQUENCY, CONF_VALUE, CONF_VOLTAGE, @@ -280,6 +281,38 @@ async def test_lut_autodiscover_flow_not_confirmed(hass: HomeAssistant): assert result["type"] == data_entry_flow.FlowResultType.FORM assert result["step_id"] == "lut_manufacturer" +async def test_lut_flow_with_sub_profiles(hass: HomeAssistant): + light_entity = MockLight("test", STATE_ON, DEFAULT_UNIQUE_ID) + await create_mock_light_entity(hass, light_entity) + + result = await _goto_virtual_power_strategy_step(hass, CalculationStrategy.LUT) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_MANUFACTURER: "yeelight"} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_MODEL: "YLDL01YL"} + ) + + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "lut_subprofile" + data_schema: vol.Schema = result["data_schema"] + model_select: SelectSelector = data_schema.schema["sub_profile"] + select_options = model_select.config["options"] + assert {"value": "ambilight", "label": "ambilight"} in select_options + assert {"value": "downlight", "label": "downlight"} in select_options + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_SUB_PROFILE: "ambilight"} + ) + + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + _assert_default_virtual_power_entry_data( + CalculationStrategy.LUT, + result["data"], + {CONF_MANUFACTURER: "yeelight", CONF_MODEL: "YLDL01YL/ambilight"}, + ) + async def test_daily_energy_mandatory_fields_not_supplied(hass: HomeAssistant): result = await _select_sensor_type(hass, SensorType.DAILY_ENERGY)