Mobile app to update entity registry on re-register sensors (#58378)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Paulus Schoutsen 2021-10-31 20:21:46 -07:00 committed by GitHub
parent 4e419d8c6f
commit a122cbab61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 39 deletions

View file

@ -9,6 +9,7 @@ from .const import (
ATTR_DEVICE_NAME, ATTR_DEVICE_NAME,
ATTR_SENSOR_ATTRIBUTES, ATTR_SENSOR_ATTRIBUTES,
ATTR_SENSOR_DEVICE_CLASS, ATTR_SENSOR_DEVICE_CLASS,
ATTR_SENSOR_ENTITY_CATEGORY,
ATTR_SENSOR_ICON, ATTR_SENSOR_ICON,
ATTR_SENSOR_NAME, ATTR_SENSOR_NAME,
ATTR_SENSOR_STATE, ATTR_SENSOR_STATE,
@ -40,6 +41,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
ATTR_SENSOR_STATE: None, ATTR_SENSOR_STATE: None,
ATTR_SENSOR_TYPE: entry.domain, ATTR_SENSOR_TYPE: entry.domain,
ATTR_SENSOR_UNIQUE_ID: entry.unique_id, ATTR_SENSOR_UNIQUE_ID: entry.unique_id,
ATTR_SENSOR_ENTITY_CATEGORY: entry.entity_category,
} }
entities.append(MobileAppBinarySensor(config, entry.device_id, config_entry)) entities.append(MobileAppBinarySensor(config, entry.device_id, config_entry))

View file

@ -11,6 +11,7 @@ from .const import (
ATTR_DEVICE_NAME, ATTR_DEVICE_NAME,
ATTR_SENSOR_ATTRIBUTES, ATTR_SENSOR_ATTRIBUTES,
ATTR_SENSOR_DEVICE_CLASS, ATTR_SENSOR_DEVICE_CLASS,
ATTR_SENSOR_ENTITY_CATEGORY,
ATTR_SENSOR_ICON, ATTR_SENSOR_ICON,
ATTR_SENSOR_NAME, ATTR_SENSOR_NAME,
ATTR_SENSOR_STATE, ATTR_SENSOR_STATE,
@ -45,6 +46,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
ATTR_SENSOR_TYPE: entry.domain, ATTR_SENSOR_TYPE: entry.domain,
ATTR_SENSOR_UNIQUE_ID: entry.unique_id, ATTR_SENSOR_UNIQUE_ID: entry.unique_id,
ATTR_SENSOR_UOM: entry.unit_of_measurement, ATTR_SENSOR_UOM: entry.unit_of_measurement,
ATTR_SENSOR_ENTITY_CATEGORY: entry.entity_category,
} }
entities.append(MobileAppSensor(config, entry.device_id, config_entry)) entities.append(MobileAppSensor(config, entry.device_id, config_entry))

View file

@ -446,6 +446,26 @@ async def webhook_register_sensor(hass, config_entry, data):
"Re-register for %s of existing sensor %s", device_name, unique_id "Re-register for %s of existing sensor %s", device_name, unique_id
) )
entry = entity_registry.async_get(existing_sensor)
changes = {}
if (
new_name := f"{device_name} {data[ATTR_SENSOR_NAME]}"
) != entry.original_name:
changes["original_name"] = new_name
for ent_reg_key, data_key in (
("device_class", ATTR_SENSOR_DEVICE_CLASS),
("unit_of_measurement", ATTR_SENSOR_UOM),
("entity_category", ATTR_SENSOR_ENTITY_CATEGORY),
("original_icon", ATTR_SENSOR_ICON),
):
if data_key in data and getattr(entry, ent_reg_key) != data[data_key]:
changes[ent_reg_key] = data[data_key]
if changes:
entity_registry.async_update_entity(existing_sensor, **changes)
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, data) async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, data)
else: else:
register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register" register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register"

View file

@ -38,7 +38,6 @@ from homeassistant.core import CALLBACK_TYPE, Context, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError, NoEntitySpecifiedError from homeassistant.exceptions import HomeAssistantError, NoEntitySpecifiedError
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import EntityPlatform from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.entity_registry import RegistryEntry
from homeassistant.helpers.event import Event, async_track_entity_registry_updated_event from homeassistant.helpers.event import Event, async_track_entity_registry_updated_event
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
@ -227,7 +226,7 @@ class Entity(ABC):
parallel_updates: asyncio.Semaphore | None = None parallel_updates: asyncio.Semaphore | None = None
# Entry in the entity registry # Entry in the entity registry
registry_entry: RegistryEntry | None = None registry_entry: er.RegistryEntry | None = None
# Hold list for functions to call on remove. # Hold list for functions to call on remove.
_on_remove: list[CALLBACK_TYPE] | None = None _on_remove: list[CALLBACK_TYPE] | None = None
@ -806,7 +805,7 @@ class Entity(ABC):
if data["action"] != "update": if data["action"] != "update":
return return
ent_reg = await self.hass.helpers.entity_registry.async_get_registry() ent_reg = er.async_get(self.hass)
old = self.registry_entry old = self.registry_entry
self.registry_entry = ent_reg.async_get(data["entity_id"]) self.registry_entry = ent_reg.async_get(data["entity_id"])
assert self.registry_entry is not None assert self.registry_entry is not None

View file

@ -243,21 +243,21 @@ class EntityRegistry:
unique_id: str, unique_id: str,
*, *,
# To influence entity ID generation # To influence entity ID generation
suggested_object_id: str | None = None,
known_object_ids: Iterable[str] | None = None, known_object_ids: Iterable[str] | None = None,
suggested_object_id: str | None = None,
# To disable an entity if it gets created # To disable an entity if it gets created
disabled_by: str | None = None, disabled_by: str | None = None,
# Data that we want entry to have # Data that we want entry to have
config_entry: ConfigEntry | None = None,
device_id: str | None = None,
area_id: str | None = None, area_id: str | None = None,
capabilities: Mapping[str, Any] | None = None, capabilities: Mapping[str, Any] | None = None,
supported_features: int | None = None, config_entry: ConfigEntry | None = None,
device_class: str | None = None, device_class: str | None = None,
unit_of_measurement: str | None = None, device_id: str | None = None,
original_name: str | None = None,
original_icon: str | None = None,
entity_category: str | None = None, entity_category: str | None = None,
original_icon: str | None = None,
original_name: str | None = None,
supported_features: int | None = None,
unit_of_measurement: str | None = None,
) -> RegistryEntry: ) -> RegistryEntry:
"""Get entity. Create if it doesn't exist.""" """Get entity. Create if it doesn't exist."""
config_entry_id = None config_entry_id = None
@ -300,20 +300,20 @@ class EntityRegistry:
disabled_by = DISABLED_INTEGRATION disabled_by = DISABLED_INTEGRATION
entity = RegistryEntry( entity = RegistryEntry(
entity_id=entity_id,
config_entry_id=config_entry_id,
device_id=device_id,
area_id=area_id, area_id=area_id,
unique_id=unique_id,
platform=platform,
disabled_by=disabled_by,
capabilities=capabilities, capabilities=capabilities,
supported_features=supported_features or 0, config_entry_id=config_entry_id,
device_class=device_class, device_class=device_class,
unit_of_measurement=unit_of_measurement, device_id=device_id,
original_name=original_name, disabled_by=disabled_by,
original_icon=original_icon,
entity_category=entity_category, entity_category=entity_category,
entity_id=entity_id,
original_icon=original_icon,
original_name=original_name,
platform=platform,
supported_features=supported_features or 0,
unique_id=unique_id,
unit_of_measurement=unit_of_measurement,
) )
self._register_entry(entity) self._register_entry(entity)
_LOGGER.info("Registered new %s.%s entity: %s", domain, platform, entity_id) _LOGGER.info("Registered new %s.%s entity: %s", domain, platform, entity_id)
@ -383,24 +383,34 @@ class EntityRegistry:
self, self,
entity_id: str, entity_id: str,
*, *,
name: str | None | UndefinedType = UNDEFINED,
icon: str | None | UndefinedType = UNDEFINED,
config_entry_id: str | None | UndefinedType = UNDEFINED,
area_id: str | None | UndefinedType = UNDEFINED, area_id: str | None | UndefinedType = UNDEFINED,
config_entry_id: str | None | UndefinedType = UNDEFINED,
device_class: str | None | UndefinedType = UNDEFINED,
disabled_by: str | None | UndefinedType = UNDEFINED,
entity_category: str | None | UndefinedType = UNDEFINED,
icon: str | None | UndefinedType = UNDEFINED,
name: str | None | UndefinedType = UNDEFINED,
new_entity_id: str | UndefinedType = UNDEFINED, new_entity_id: str | UndefinedType = UNDEFINED,
new_unique_id: str | UndefinedType = UNDEFINED, new_unique_id: str | UndefinedType = UNDEFINED,
disabled_by: str | None | UndefinedType = UNDEFINED, original_icon: str | None | UndefinedType = UNDEFINED,
original_name: str | None | UndefinedType = UNDEFINED,
unit_of_measurement: str | None | UndefinedType = UNDEFINED,
) -> RegistryEntry: ) -> RegistryEntry:
"""Update properties of an entity.""" """Update properties of an entity."""
return self._async_update_entity( return self._async_update_entity(
entity_id, entity_id,
name=name,
icon=icon,
config_entry_id=config_entry_id,
area_id=area_id, area_id=area_id,
config_entry_id=config_entry_id,
device_class=device_class,
disabled_by=disabled_by,
entity_category=entity_category,
icon=icon,
name=name,
new_entity_id=new_entity_id, new_entity_id=new_entity_id,
new_unique_id=new_unique_id, new_unique_id=new_unique_id,
disabled_by=disabled_by, original_icon=original_icon,
original_name=original_name,
unit_of_measurement=unit_of_measurement,
) )
@callback @callback
@ -408,21 +418,21 @@ class EntityRegistry:
self, self,
entity_id: str, entity_id: str,
*, *,
name: str | None | UndefinedType = UNDEFINED,
icon: str | None | UndefinedType = UNDEFINED,
config_entry_id: str | None | UndefinedType = UNDEFINED,
new_entity_id: str | UndefinedType = UNDEFINED,
device_id: str | None | UndefinedType = UNDEFINED,
area_id: str | None | UndefinedType = UNDEFINED, area_id: str | None | UndefinedType = UNDEFINED,
new_unique_id: str | UndefinedType = UNDEFINED,
disabled_by: str | None | UndefinedType = UNDEFINED,
capabilities: Mapping[str, Any] | None | UndefinedType = UNDEFINED, capabilities: Mapping[str, Any] | None | UndefinedType = UNDEFINED,
supported_features: int | UndefinedType = UNDEFINED, config_entry_id: str | None | UndefinedType = UNDEFINED,
device_class: str | None | UndefinedType = UNDEFINED, device_class: str | None | UndefinedType = UNDEFINED,
unit_of_measurement: str | None | UndefinedType = UNDEFINED, device_id: str | None | UndefinedType = UNDEFINED,
original_name: str | None | UndefinedType = UNDEFINED, disabled_by: str | None | UndefinedType = UNDEFINED,
original_icon: str | None | UndefinedType = UNDEFINED,
entity_category: str | None | UndefinedType = UNDEFINED, entity_category: str | None | UndefinedType = UNDEFINED,
icon: str | None | UndefinedType = UNDEFINED,
name: str | None | UndefinedType = UNDEFINED,
new_entity_id: str | UndefinedType = UNDEFINED,
new_unique_id: str | UndefinedType = UNDEFINED,
original_icon: str | None | UndefinedType = UNDEFINED,
original_name: str | None | UndefinedType = UNDEFINED,
supported_features: int | UndefinedType = UNDEFINED,
unit_of_measurement: str | None | UndefinedType = UNDEFINED,
) -> RegistryEntry: ) -> RegistryEntry:
"""Private facing update properties method.""" """Private facing update properties method."""
old = self.entities[entity_id] old = self.entities[entity_id]

View file

@ -10,6 +10,7 @@ from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN
from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.const import CONF_WEBHOOK_ID
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from .const import CALL_SERVICE, FIRE_EVENT, REGISTER_CLEARTEXT, RENDER_TEMPLATE, UPDATE from .const import CALL_SERVICE, FIRE_EVENT, REGISTER_CLEARTEXT, RENDER_TEMPLATE, UPDATE
@ -515,3 +516,58 @@ async def test_register_sensor_limits_state_class(
# This means it was ignored. # This means it was ignored.
assert reg_resp.status == HTTPStatus.OK assert reg_resp.status == HTTPStatus.OK
async def test_reregister_sensor(hass, create_registrations, webhook_client):
"""Test that we can add more info in re-registration."""
webhook_id = create_registrations[1]["webhook_id"]
webhook_url = f"/api/webhook/{webhook_id}"
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "Battery State",
"state": 100,
"type": "sensor",
"unique_id": "abcd",
},
},
)
assert reg_resp.status == HTTPStatus.CREATED
ent_reg = er.async_get(hass)
entry = ent_reg.async_get("sensor.test_1_battery_state")
assert entry.original_name == "Test 1 Battery State"
assert entry.device_class is None
assert entry.unit_of_measurement is None
assert entry.entity_category is None
assert entry.original_icon == "mdi:cellphone"
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "New Name",
"state": 100,
"type": "sensor",
"unique_id": "abcd",
"state_class": "total",
"device_class": "battery",
"entity_category": "diagnostic",
"icon": "mdi:new-icon",
"unit_of_measurement": "%",
},
},
)
assert reg_resp.status == HTTPStatus.CREATED
entry = ent_reg.async_get("sensor.test_1_battery_state")
assert entry.original_name == "Test 1 New Name"
assert entry.device_class == "battery"
assert entry.unit_of_measurement == "%"
assert entry.entity_category == "diagnostic"
assert entry.original_icon == "mdi:new-icon"