diff --git a/custom_components/eversolo/api.py b/custom_components/eversolo/api.py index 0bbd76e..d153388 100644 --- a/custom_components/eversolo/api.py +++ b/custom_components/eversolo/api.py @@ -43,6 +43,12 @@ async def async_get_data(self): "knob_brightness": await self.async_get_knob_brightness(), "music_control_state": await self.async_get_music_control_state(), "vu_mode_state": await self.async_get_vu_mode_state(), + "vu_mode_options": { + "VU-Meter 1": "VU-Meter 1", + "VU-Meter 2": "VU-Meter 2", + "VU-Meter 3": "VU-Meter 3", + "VU-Meter 4": "VU-Meter 4", + }, } LOGGER.debug("Fetched data from API: %s", result) return result @@ -55,12 +61,44 @@ async def async_get_music_control_state(self): ) return result + def transform_sources(self, input_output_state: dict) -> dict: + """Return available input sources.""" + sources = input_output_state.get("inputData", None) + + if sources is None: + return None + + transformed_sources = {} + + for source in list(sources): + transformed_sources[source["tag"].replace("/", "")] = source["name"] + + return transformed_sources + + def transform_outputs(self, input_output_state: dict) -> dict: + """Return available input sources.""" + outputs = input_output_state.get("outputData", None) + + if outputs is None: + return None + + transformed_sources = {} + + for source in [output for output in outputs if output["enable"]]: + transformed_sources[source["tag"].replace("/", "")] = source["name"] + + return transformed_sources + async def async_get_input_output_state(self): """Return input/output state.""" result = await self._api_wrapper( method="get", url=f"http://{self._host}:{self._port}/ZidooMusicControl/v2/getInputAndOutputList", ) + + result["transformed_sources"] = self.transform_sources(result) + result["transformed_outputs"] = self.transform_outputs(result) + return result async def async_get_vu_mode_state(self): @@ -150,7 +188,7 @@ async def async_trigger_cycle_screen_mode(self) -> any: parseJson=False, ) - async def async_select_vu_mode_option(self, index) -> any: + async def async_select_vu_mode_option(self, index, tag) -> any: """Select the VU meter style.""" await self._api_wrapper( method="get", diff --git a/custom_components/eversolo/manifest.json b/custom_components/eversolo/manifest.json index 81fb8bd..a38ba0e 100644 --- a/custom_components/eversolo/manifest.json +++ b/custom_components/eversolo/manifest.json @@ -8,5 +8,5 @@ "documentation": "https://github.com/hchris1/Eversolo", "iot_class": "local_polling", "issue_tracker": "https://github.com/hchris1/Eversolo/issues", - "version": "0.2.1" + "version": "0.3.0" } \ No newline at end of file diff --git a/custom_components/eversolo/media_player.py b/custom_components/eversolo/media_player.py index 846aa42..8b3289c 100644 --- a/custom_components/eversolo/media_player.py +++ b/custom_components/eversolo/media_player.py @@ -27,14 +27,6 @@ | MediaPlayerEntityFeature.SEEK ) -AVAILABLE_SOURCES = { - "XMOS": "Internal Player", - "BT": "Bluetooth", - "USB": "USB-C", - "SPDIF": "SPDIF", - "RCA": "Coaxial", -} - async def async_setup_entry(hass, entry, async_add_devices): """Set up the Media Player platform.""" @@ -130,17 +122,32 @@ def source(self): if input_output_state is None: return None + sources = self.coordinator.data.get("input_output_state", {}).get( + "transformed_sources", None + ) + + if sources is None: + return None + input_index = input_output_state.get("inputIndex", -1) - if input_index < 0 or input_index >= len(AVAILABLE_SOURCES): + if input_index < 0 or input_index >= len(sources): LOGGER.debug("Input index %s is out of range", input_index) return None - return list(AVAILABLE_SOURCES.values())[input_index] + return list(sources.values())[input_index] @property def source_list(self): """List of available input sources.""" - return list(AVAILABLE_SOURCES.values()) + # NoneType object has no values + sources = self.coordinator.data.get("input_output_state", {}).get( + "transformed_sources", None + ) + + if sources is None: + return None + + return list(sources.values()) @property def media_title(self): @@ -316,12 +323,18 @@ async def async_mute_volume(self, mute): async def async_select_source(self, source): """Set the input source.""" - index = 0 - for key, value in AVAILABLE_SOURCES.items(): - if value == source: - await self.coordinator.client.async_set_input(index, key) - break - index += 1 + sources = self.coordinator.data.get("input_output_state", {}).get( + "transformed_sources", None + ) + + if sources is None: + return + + index, tag = next( + (index, key) for index, key in enumerate(sources) if sources[key] == source + ) + + await self.coordinator.client.async_set_input(index, tag) async def async_media_play_pause(self): """Simulate play pause Media Player.""" diff --git a/custom_components/eversolo/select.py b/custom_components/eversolo/select.py index c969d15..310217a 100644 --- a/custom_components/eversolo/select.py +++ b/custom_components/eversolo/select.py @@ -20,9 +20,10 @@ class EversoloSelectDescriptionMixin(Generic[_EversoloDataUpdateCoordinatorT]): """Mixin to describe a Select entity.""" - available_options: list[str] + get_selected_option: Callable[[_EversoloDataUpdateCoordinatorT], int] + get_available_options: Callable[[_EversoloDataUpdateCoordinatorT], list[str]] select_option: Callable[ - [_EversoloDataUpdateCoordinatorT], Coroutine[Any, Any, None] + [_EversoloDataUpdateCoordinatorT, int, str], Coroutine[Any, Any, None] ] @@ -39,11 +40,30 @@ class EversoloSelectDescription( key="vu_style", name="Eversolo VU Style", icon="mdi:gauge-low", - available_options=["VU-Meter 1", "VU-Meter 2", "VU-Meter 3", "VU-Meter 4"], - select_option=lambda coordinator, option: coordinator.client.async_select_vu_mode_option( - option + get_selected_option=lambda coordinator: coordinator.data.get( + "vu_mode_state", {} + ).get("currentIndex", -1), + get_available_options=lambda coordinator: coordinator.data.get( + "vu_mode_options", [] ), - ) + select_option=lambda coordinator, index, tag: coordinator.client.async_select_vu_mode_option( + index, tag + ), + ), + EversoloSelectDescription[EversoloDataUpdateCoordinator]( + key="output_mode", + name="Eversolo Output Mode", + icon="mdi:transmission-tower", + get_selected_option=lambda coordinator: coordinator.data.get( + "input_output_state", {} + ).get("outputIndex", -1), + get_available_options=lambda coordinator: coordinator.data.get( + "input_output_state", {} + ).get("transformed_outputs", None), + select_option=lambda coordinator, index, tag: coordinator.client.async_set_output( + index, tag + ), + ), ] @@ -71,31 +91,48 @@ def __init__( """Initialize the Select class.""" super().__init__(coordinator) self.entity_description = entity_description - self._attr_options = self.entity_description.available_options self._attr_unique_id = ( f"{coordinator.config_entry.entry_id}_{entity_description.key}" ) + @property + def options(self) -> list[str]: + """Return the list of available options.""" + return list( + self.entity_description.get_available_options(self.coordinator).values() + ) + @property def current_option(self) -> str: - """Return current VU style.""" - vu_mode_state = self.coordinator.data.get("vu_mode_state", None) + """Return current state.""" + current_index = self.entity_description.get_selected_option(self.coordinator) - if vu_mode_state is None: - return None + options = self.entity_description.get_available_options(self.coordinator) - current_index = int(vu_mode_state.get("currentIndex", -1)) + if options is None: + LOGGER.debug("No options found") + return None - if current_index < 0 or current_index >= len(self._attr_options): + if current_index < 0 or current_index >= len(options): LOGGER.debug("Current index %s is out of range", current_index) return None - return self._attr_options[current_index] + return list(options.values())[current_index] async def async_select_option(self, option: str) -> None: - """Change the selected option.""" + """Change to selected option.""" - await self.entity_description.select_option( - self.coordinator, self.entity_description.available_options.index(option) - ) + options = self.entity_description.get_available_options(self.coordinator) + + index, tag = None, None + for i, (key, value) in enumerate(options.items()): + if value == option: + index, tag = i, key + break + + if index is None or tag is None: + LOGGER.debug("Option %s not found", option) + return + + await self.entity_description.select_option(self.coordinator, index, tag) self._attr_current_option = option diff --git a/hacs.json b/hacs.json index bc7dbfb..f93cae2 100644 --- a/hacs.json +++ b/hacs.json @@ -2,7 +2,7 @@ "name": "Eversolo Integration", "filename": "eversolo.zip", "hide_default_branch": true, - "homeassistant": "2023.10.0", + "homeassistant": "2023.12.3", "render_readme": true, "zip_release": true } \ No newline at end of file