Skip to content
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

Improved entity names, duplicate device detection, initial auto-discovery support #5

Merged
merged 6 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion custom_components/vinx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
class DeviceInformation:
mac_address: str
product_name: str
device_label: str
device_info: DeviceInfo


Expand Down Expand Up @@ -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:
Expand Down
52 changes: 44 additions & 8 deletions custom_components/vinx/config_flow.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion custom_components/vinx/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"iot_class": "local_polling",
"requirements": [],
"ssdp": [],
"zeroconf": []
"zeroconf": ["_lwr3._tcp.local."]
}
17 changes: 13 additions & 4 deletions custom_components/vinx/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down