diff --git a/custom_components/vinx/__init__.py b/custom_components/vinx/__init__.py index 42b8d83..eb88c31 100644 --- a/custom_components/vinx/__init__.py +++ b/custom_components/vinx/__init__.py @@ -16,6 +16,7 @@ class DeviceInformation: mac_address: str product_name: str + device_label: str device_info: DeviceInfo @@ -44,7 +45,7 @@ async def get_device_information(lw3: LW3) -> DeviceInformation: configuration_url=f"http://{ip_address}/", ) - return DeviceInformation(mac_address, product_name, device_info) + return DeviceInformation(mac_address, product_name, device_label, device_info) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/custom_components/vinx/config_flow.py b/custom_components/vinx/config_flow.py index 04af8b2..e3ca8fd 100644 --- a/custom_components/vinx/config_flow.py +++ b/custom_components/vinx/config_flow.py @@ -1,38 +1,74 @@ +import logging + from typing import Any import voluptuous as vol +from homeassistant.components.zeroconf import ZeroconfServiceInfo from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.helpers.device_registry import format_mac from .const import CONF_HOST, CONF_PORT, DOMAIN from .lw3 import LW3 -STEP_USER_DATA_SCHEMA = vol.Schema( - { - vol.Required(CONF_HOST): str, - vol.Required(CONF_PORT, default=6107): int, - } -) +_LOGGER = logging.getLogger(__name__) class VinxConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 + def __init__(self): + # Potentially prepopulated values (e.g. during auto-discovery) + self.host: str | None = None + self.port: int = 6107 + + @property + def schema(self): + return vol.Schema( + { + vol.Required(CONF_HOST, default=self.host): str, + vol.Required(CONF_PORT, default=self.port): int, + } + ) + async def async_step_user(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult: """Handle user initiated configuration""" errors: dict[str, str] = {} if user_input is not None: try: - # Query the device for enough information to make an entry title + # Verify that the device is connectable lw3 = LW3(user_input["host"], user_input["port"]) async with lw3.connection(): + # Query information for the entry title and entry unique ID product_name = await lw3.get_property("/.ProductName") device_label = await lw3.get_property("/SYS/MB.DeviceLabel") + mac_address = await lw3.get_property("/.MacAddress") title = f"{device_label} ({product_name})" + + unique_id = format_mac(str(mac_address)) + await self.async_set_unique_id(unique_id) + + # Abort the configuration if the device is already configured + self._abort_if_unique_id_configured() except (BrokenPipeError, ConnectionError, OSError): # all technically OSError errors["base"] = "cannot_connect" else: return self.async_create_entry(title=title, data=user_input) - return self.async_show_form(step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors) + return self.async_show_form(step_id="user", data_schema=self.schema, errors=errors) + + async def async_step_zeroconf(self, discovery_info: ZeroconfServiceInfo) -> ConfigFlowResult: + _LOGGER.info(f"Zeroconf discovery info: {discovery_info}") + + # Abort if the device is already configured + unique_id = format_mac(discovery_info.properties.get("mac")) + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + + # Pre-populate the form + self.host = discovery_info.ip_address + self.port = discovery_info.port + + # Trigger the user configuration flow + return await self.async_step_user() diff --git a/custom_components/vinx/manifest.json b/custom_components/vinx/manifest.json index 026f0dd..8fccf7a 100644 --- a/custom_components/vinx/manifest.json +++ b/custom_components/vinx/manifest.json @@ -13,5 +13,5 @@ "iot_class": "local_polling", "requirements": [], "ssdp": [], - "zeroconf": [] + "zeroconf": ["_lwr3._tcp.local."] } diff --git a/custom_components/vinx/media_player.py b/custom_components/vinx/media_player.py index 55d89eb..54e92e5 100644 --- a/custom_components/vinx/media_player.py +++ b/custom_components/vinx/media_player.py @@ -25,7 +25,7 @@ async def async_setup_entry(hass, entry, async_add_entities): _LOGGER.warning("Unknown device type, no entities will be added") -class AbstractVinxDevice(MediaPlayerEntity): +class AbstractVinxMediaPlayerEntity(MediaPlayerEntity): def __init__(self, lw3: LW3, device_information: DeviceInformation) -> None: self._lw3 = lw3 self._device_information = device_information @@ -53,14 +53,23 @@ def device_info(self) -> DeviceInfo: @property def name(self): - return "Media Player" + # Use increasingly less descriptive names depending on what information is available + device_label = self._device_information.device_label + serial_number = self._device_information.device_info.get("serial_number") + if device_label: + return f"{self._device_information.device_label} media player" + elif serial_number: + return f"VINX {serial_number} media player" + else: + return "VINX media player" -class VinxEncoder(AbstractVinxDevice): + +class VinxEncoder(AbstractVinxMediaPlayerEntity): pass -class VinxDecoder(AbstractVinxDevice): +class VinxDecoder(AbstractVinxMediaPlayerEntity): def __init__(self, lw3: LW3, device_information: DeviceInformation) -> None: super().__init__(lw3, device_information)