Add support for device configuration URL (#57539)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
addb91d49e
commit
e27e4c3561
9 changed files with 83 additions and 5 deletions
|
@ -68,6 +68,7 @@ def _entry_dict(entry):
|
||||||
"""Convert entry to API format."""
|
"""Convert entry to API format."""
|
||||||
return {
|
return {
|
||||||
"area_id": entry.area_id,
|
"area_id": entry.area_id,
|
||||||
|
"configuration_url": entry.configuration_url,
|
||||||
"config_entries": list(entry.config_entries),
|
"config_entries": list(entry.config_entries),
|
||||||
"connections": list(entry.connections),
|
"connections": list(entry.connections),
|
||||||
"disabled_by": entry.disabled_by,
|
"disabled_by": entry.disabled_by,
|
||||||
|
|
|
@ -399,6 +399,7 @@ class BlockDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
||||||
manufacturer="Shelly",
|
manufacturer="Shelly",
|
||||||
model=aioshelly.const.MODEL_NAMES.get(self.model, self.model),
|
model=aioshelly.const.MODEL_NAMES.get(self.model, self.model),
|
||||||
sw_version=sw_version,
|
sw_version=sw_version,
|
||||||
|
configuration_url=f"http://{self.entry.data[CONF_HOST]}",
|
||||||
)
|
)
|
||||||
self.device_id = entry.id
|
self.device_id = entry.id
|
||||||
self.device.subscribe_updates(self.async_set_updated_data)
|
self.device.subscribe_updates(self.async_set_updated_data)
|
||||||
|
@ -635,6 +636,7 @@ class RpcDeviceWrapper(update_coordinator.DataUpdateCoordinator):
|
||||||
manufacturer="Shelly",
|
manufacturer="Shelly",
|
||||||
model=aioshelly.const.MODEL_NAMES.get(self.model, self.model),
|
model=aioshelly.const.MODEL_NAMES.get(self.model, self.model),
|
||||||
sw_version=sw_version,
|
sw_version=sw_version,
|
||||||
|
configuration_url=f"http://{self.entry.data[CONF_HOST]}",
|
||||||
)
|
)
|
||||||
self.device_id = entry.id
|
self.device_id = entry.id
|
||||||
self.device.subscribe_updates(self.async_set_updated_data)
|
self.device.subscribe_updates(self.async_set_updated_data)
|
||||||
|
|
|
@ -55,6 +55,7 @@ class DeviceEntry:
|
||||||
|
|
||||||
area_id: str | None = attr.ib(default=None)
|
area_id: str | None = attr.ib(default=None)
|
||||||
config_entries: set[str] = attr.ib(converter=set, factory=set)
|
config_entries: set[str] = attr.ib(converter=set, factory=set)
|
||||||
|
configuration_url: str | None = attr.ib(default=None)
|
||||||
connections: set[tuple[str, str]] = attr.ib(converter=set, factory=set)
|
connections: set[tuple[str, str]] = attr.ib(converter=set, factory=set)
|
||||||
disabled_by: str | None = attr.ib(
|
disabled_by: str | None = attr.ib(
|
||||||
default=None,
|
default=None,
|
||||||
|
@ -244,6 +245,7 @@ class DeviceRegistry:
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
config_entry_id: str,
|
config_entry_id: str,
|
||||||
|
configuration_url: str | None | UndefinedType = UNDEFINED,
|
||||||
connections: set[tuple[str, str]] | None = None,
|
connections: set[tuple[str, str]] | None = None,
|
||||||
default_manufacturer: str | None | UndefinedType = UNDEFINED,
|
default_manufacturer: str | None | UndefinedType = UNDEFINED,
|
||||||
default_model: str | None | UndefinedType = UNDEFINED,
|
default_model: str | None | UndefinedType = UNDEFINED,
|
||||||
|
@ -302,6 +304,7 @@ class DeviceRegistry:
|
||||||
device = self._async_update_device(
|
device = self._async_update_device(
|
||||||
device.id,
|
device.id,
|
||||||
add_config_entry_id=config_entry_id,
|
add_config_entry_id=config_entry_id,
|
||||||
|
configuration_url=configuration_url,
|
||||||
disabled_by=disabled_by,
|
disabled_by=disabled_by,
|
||||||
entry_type=entry_type,
|
entry_type=entry_type,
|
||||||
manufacturer=manufacturer,
|
manufacturer=manufacturer,
|
||||||
|
@ -326,6 +329,7 @@ class DeviceRegistry:
|
||||||
*,
|
*,
|
||||||
add_config_entry_id: str | UndefinedType = UNDEFINED,
|
add_config_entry_id: str | UndefinedType = UNDEFINED,
|
||||||
area_id: str | None | UndefinedType = UNDEFINED,
|
area_id: str | None | UndefinedType = UNDEFINED,
|
||||||
|
configuration_url: str | None | UndefinedType = UNDEFINED,
|
||||||
disabled_by: str | None | UndefinedType = UNDEFINED,
|
disabled_by: str | None | UndefinedType = UNDEFINED,
|
||||||
manufacturer: str | None | UndefinedType = UNDEFINED,
|
manufacturer: str | None | UndefinedType = UNDEFINED,
|
||||||
model: str | None | UndefinedType = UNDEFINED,
|
model: str | None | UndefinedType = UNDEFINED,
|
||||||
|
@ -342,6 +346,7 @@ class DeviceRegistry:
|
||||||
device_id,
|
device_id,
|
||||||
add_config_entry_id=add_config_entry_id,
|
add_config_entry_id=add_config_entry_id,
|
||||||
area_id=area_id,
|
area_id=area_id,
|
||||||
|
configuration_url=configuration_url,
|
||||||
disabled_by=disabled_by,
|
disabled_by=disabled_by,
|
||||||
manufacturer=manufacturer,
|
manufacturer=manufacturer,
|
||||||
model=model,
|
model=model,
|
||||||
|
@ -361,6 +366,7 @@ class DeviceRegistry:
|
||||||
*,
|
*,
|
||||||
add_config_entry_id: str | UndefinedType = UNDEFINED,
|
add_config_entry_id: str | UndefinedType = UNDEFINED,
|
||||||
area_id: str | None | UndefinedType = UNDEFINED,
|
area_id: str | None | UndefinedType = UNDEFINED,
|
||||||
|
configuration_url: str | None | UndefinedType = UNDEFINED,
|
||||||
disabled_by: str | None | UndefinedType = UNDEFINED,
|
disabled_by: str | None | UndefinedType = UNDEFINED,
|
||||||
entry_type: str | None | UndefinedType = UNDEFINED,
|
entry_type: str | None | UndefinedType = UNDEFINED,
|
||||||
manufacturer: str | None | UndefinedType = UNDEFINED,
|
manufacturer: str | None | UndefinedType = UNDEFINED,
|
||||||
|
@ -424,6 +430,7 @@ class DeviceRegistry:
|
||||||
changes["identifiers"] = new_identifiers
|
changes["identifiers"] = new_identifiers
|
||||||
|
|
||||||
for attr_name, value in (
|
for attr_name, value in (
|
||||||
|
("configuration_url", configuration_url),
|
||||||
("disabled_by", disabled_by),
|
("disabled_by", disabled_by),
|
||||||
("entry_type", entry_type),
|
("entry_type", entry_type),
|
||||||
("manufacturer", manufacturer),
|
("manufacturer", manufacturer),
|
||||||
|
@ -514,6 +521,8 @@ class DeviceRegistry:
|
||||||
name_by_user=device.get("name_by_user"),
|
name_by_user=device.get("name_by_user"),
|
||||||
# Introduced in 0.119
|
# Introduced in 0.119
|
||||||
disabled_by=device.get("disabled_by"),
|
disabled_by=device.get("disabled_by"),
|
||||||
|
# Introduced in 2021.11
|
||||||
|
configuration_url=device.get("configuration_url"),
|
||||||
)
|
)
|
||||||
# Introduced in 0.111
|
# Introduced in 0.111
|
||||||
for device in data.get("deleted_devices", []):
|
for device in data.get("deleted_devices", []):
|
||||||
|
@ -556,6 +565,7 @@ class DeviceRegistry:
|
||||||
"area_id": entry.area_id,
|
"area_id": entry.area_id,
|
||||||
"name_by_user": entry.name_by_user,
|
"name_by_user": entry.name_by_user,
|
||||||
"disabled_by": entry.disabled_by,
|
"disabled_by": entry.disabled_by,
|
||||||
|
"configuration_url": entry.configuration_url,
|
||||||
}
|
}
|
||||||
for entry in self.devices.values()
|
for entry in self.devices.values()
|
||||||
]
|
]
|
||||||
|
|
|
@ -158,6 +158,7 @@ def get_unit_of_measurement(hass: HomeAssistant, entity_id: str) -> str | None:
|
||||||
class DeviceInfo(TypedDict, total=False):
|
class DeviceInfo(TypedDict, total=False):
|
||||||
"""Entity device information for device registry."""
|
"""Entity device information for device registry."""
|
||||||
|
|
||||||
|
configuration_url: str | None
|
||||||
connections: set[tuple[str, str]]
|
connections: set[tuple[str, str]]
|
||||||
default_manufacturer: str
|
default_manufacturer: str
|
||||||
default_model: str
|
default_model: str
|
||||||
|
|
|
@ -473,6 +473,17 @@ class EntityPlatform:
|
||||||
if key in device_info:
|
if key in device_info:
|
||||||
processed_dev_info[key] = device_info[key] # type: ignore[misc]
|
processed_dev_info[key] = device_info[key] # type: ignore[misc]
|
||||||
|
|
||||||
|
if "configuration_url" in device_info:
|
||||||
|
try:
|
||||||
|
processed_dev_info["configuration_url"] = cv.url(
|
||||||
|
device_info["configuration_url"]
|
||||||
|
)
|
||||||
|
except vol.Invalid:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Ignoring invalid device configuration_url '%s'",
|
||||||
|
device_info["configuration_url"],
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
device = device_registry.async_get_or_create(**processed_dev_info) # type: ignore[arg-type]
|
device = device_registry.async_get_or_create(**processed_dev_info) # type: ignore[arg-type]
|
||||||
device_id = device.id
|
device_id = device.id
|
||||||
|
|
|
@ -58,6 +58,7 @@ async def test_list_devices(hass, client, registry):
|
||||||
"area_id": None,
|
"area_id": None,
|
||||||
"name_by_user": None,
|
"name_by_user": None,
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
|
"configuration_url": None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"config_entries": ["1234"],
|
"config_entries": ["1234"],
|
||||||
|
@ -72,6 +73,7 @@ async def test_list_devices(hass, client, registry):
|
||||||
"area_id": None,
|
"area_id": None,
|
||||||
"name_by_user": None,
|
"name_by_user": None,
|
||||||
"disabled_by": None,
|
"disabled_by": None,
|
||||||
|
"configuration_url": None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ async def coap_wrapper(hass):
|
||||||
|
|
||||||
config_entry = MockConfigEntry(
|
config_entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
data={"sleep_period": 0, "model": "SHSW-25"},
|
data={"sleep_period": 0, "model": "SHSW-25", "host": "1.2.3.4"},
|
||||||
unique_id="12345678",
|
unique_id="12345678",
|
||||||
)
|
)
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
@ -140,7 +140,7 @@ async def rpc_wrapper(hass):
|
||||||
|
|
||||||
config_entry = MockConfigEntry(
|
config_entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
data={"sleep_period": 0, "model": "SNSW-001P16EU", "gen": 2},
|
data={"sleep_period": 0, "model": "SNSW-001P16EU", "gen": 2, "host": "1.2.3.4"},
|
||||||
unique_id="12345678",
|
unique_id="12345678",
|
||||||
)
|
)
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
|
@ -109,7 +109,7 @@ async def test_get_triggers_button(hass):
|
||||||
|
|
||||||
config_entry = MockConfigEntry(
|
config_entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
data={"sleep_period": 43200, "model": "SHBTN-1"},
|
data={"sleep_period": 43200, "model": "SHBTN-1", "host": "1.2.3.4"},
|
||||||
unique_id="12345678",
|
unique_id="12345678",
|
||||||
)
|
)
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
|
@ -832,6 +832,7 @@ async def test_device_info_called(hass):
|
||||||
unique_id="qwer",
|
unique_id="qwer",
|
||||||
device_info={
|
device_info={
|
||||||
"identifiers": {("hue", "1234")},
|
"identifiers": {("hue", "1234")},
|
||||||
|
"configuration_url": "http://192.168.0.100/config",
|
||||||
"connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")},
|
"connections": {(dr.CONNECTION_NETWORK_MAC, "abcd")},
|
||||||
"manufacturer": "test-manuf",
|
"manufacturer": "test-manuf",
|
||||||
"model": "test-model",
|
"model": "test-model",
|
||||||
|
@ -860,13 +861,14 @@ async def test_device_info_called(hass):
|
||||||
device = registry.async_get_device({("hue", "1234")})
|
device = registry.async_get_device({("hue", "1234")})
|
||||||
assert device is not None
|
assert device is not None
|
||||||
assert device.identifiers == {("hue", "1234")}
|
assert device.identifiers == {("hue", "1234")}
|
||||||
|
assert device.configuration_url == "http://192.168.0.100/config"
|
||||||
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, "abcd")}
|
assert device.connections == {(dr.CONNECTION_NETWORK_MAC, "abcd")}
|
||||||
|
assert device.entry_type == "service"
|
||||||
assert device.manufacturer == "test-manuf"
|
assert device.manufacturer == "test-manuf"
|
||||||
assert device.model == "test-model"
|
assert device.model == "test-model"
|
||||||
assert device.name == "test-name"
|
assert device.name == "test-name"
|
||||||
assert device.sw_version == "test-sw"
|
|
||||||
assert device.suggested_area == "Heliport"
|
assert device.suggested_area == "Heliport"
|
||||||
assert device.entry_type == "service"
|
assert device.sw_version == "test-sw"
|
||||||
assert device.via_device_id == via.id
|
assert device.via_device_id == via.id
|
||||||
|
|
||||||
|
|
||||||
|
@ -916,6 +918,55 @@ async def test_device_info_not_overrides(hass):
|
||||||
assert device2.model == "test-model"
|
assert device2.model == "test-model"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_device_info_invalid_url(hass, caplog):
|
||||||
|
"""Test device info is forwarded correctly."""
|
||||||
|
registry = dr.async_get(hass)
|
||||||
|
registry.async_get_or_create(
|
||||||
|
config_entry_id="123",
|
||||||
|
connections=set(),
|
||||||
|
identifiers={("hue", "via-id")},
|
||||||
|
manufacturer="manufacturer",
|
||||||
|
model="via",
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Mock setup entry method."""
|
||||||
|
async_add_entities(
|
||||||
|
[
|
||||||
|
# Valid device info, but invalid url
|
||||||
|
MockEntity(
|
||||||
|
unique_id="qwer",
|
||||||
|
device_info={
|
||||||
|
"identifiers": {("hue", "1234")},
|
||||||
|
"configuration_url": "foo://192.168.0.100/config",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
platform = MockPlatform(async_setup_entry=async_setup_entry)
|
||||||
|
config_entry = MockConfigEntry(entry_id="super-mock-id")
|
||||||
|
entity_platform = MockEntityPlatform(
|
||||||
|
hass, platform_name=config_entry.domain, platform=platform
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await entity_platform.async_setup_entry(config_entry)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(hass.states.async_entity_ids()) == 1
|
||||||
|
|
||||||
|
device = registry.async_get_device({("hue", "1234")})
|
||||||
|
assert device is not None
|
||||||
|
assert device.identifiers == {("hue", "1234")}
|
||||||
|
assert device.configuration_url is None
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"Ignoring invalid device configuration_url 'foo://192.168.0.100/config'"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_entity_disabled_by_integration(hass):
|
async def test_entity_disabled_by_integration(hass):
|
||||||
"""Test entity disabled by integration."""
|
"""Test entity disabled by integration."""
|
||||||
component = EntityComponent(_LOGGER, DOMAIN, hass, timedelta(seconds=20))
|
component = EntityComponent(_LOGGER, DOMAIN, hass, timedelta(seconds=20))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue