1
mirror of https://github.com/home-assistant/core synced 2024-09-03 08:14:07 +02:00

Mobile app to notify when sensor is disabled (#71561)

* Mobile app to notify when sensor is disabled

* Add entity status to get_config

* Allow overriding enabled/disabled
This commit is contained in:
Paulus Schoutsen 2022-05-17 09:05:49 -07:00 committed by GitHub
parent e4573273dc
commit 0b09376360
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 162 additions and 13 deletions

View File

@ -67,7 +67,7 @@ ERR_INVALID_FORMAT = "invalid_format"
ATTR_SENSOR_ATTRIBUTES = "attributes"
ATTR_SENSOR_DEVICE_CLASS = "device_class"
ATTR_SENSOR_DEFAULT_DISABLED = "default_disabled"
ATTR_SENSOR_DISABLED = "disabled"
ATTR_SENSOR_ENTITY_CATEGORY = "entity_category"
ATTR_SENSOR_ICON = "icon"
ATTR_SENSOR_NAME = "name"

View File

@ -9,8 +9,8 @@ from homeassistant.helpers.restore_state import RestoreEntity
from .const import (
ATTR_SENSOR_ATTRIBUTES,
ATTR_SENSOR_DEFAULT_DISABLED,
ATTR_SENSOR_DEVICE_CLASS,
ATTR_SENSOR_DISABLED,
ATTR_SENSOR_ENTITY_CATEGORY,
ATTR_SENSOR_ICON,
ATTR_SENSOR_STATE,
@ -64,7 +64,7 @@ class MobileAppEntity(RestoreEntity):
@property
def entity_registry_enabled_default(self) -> bool:
"""Return if entity should be enabled by default."""
return not self._config.get(ATTR_SENSOR_DEFAULT_DISABLED)
return not self._config.get(ATTR_SENSOR_DISABLED)
@property
def device_class(self):

View File

@ -64,8 +64,8 @@ from .const import (
ATTR_NO_LEGACY_ENCRYPTION,
ATTR_OS_VERSION,
ATTR_SENSOR_ATTRIBUTES,
ATTR_SENSOR_DEFAULT_DISABLED,
ATTR_SENSOR_DEVICE_CLASS,
ATTR_SENSOR_DISABLED,
ATTR_SENSOR_ENTITY_CATEGORY,
ATTR_SENSOR_ICON,
ATTR_SENSOR_NAME,
@ -439,6 +439,11 @@ def _gen_unique_id(webhook_id, sensor_unique_id):
return f"{webhook_id}_{sensor_unique_id}"
def _extract_sensor_unique_id(webhook_id, unique_id):
"""Return a unique sensor ID."""
return unique_id[len(webhook_id) + 1 :]
@WEBHOOK_COMMANDS.register("register_sensor")
@validate_schema(
vol.All(
@ -457,7 +462,7 @@ def _gen_unique_id(webhook_id, sensor_unique_id):
vol.Optional(ATTR_SENSOR_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon,
vol.Optional(ATTR_SENSOR_STATE_CLASS): vol.In(SENSOSR_STATE_CLASSES),
vol.Optional(ATTR_SENSOR_DEFAULT_DISABLED): bool,
vol.Optional(ATTR_SENSOR_DISABLED): bool,
},
_validate_state_class_sensor,
)
@ -490,6 +495,15 @@ async def webhook_register_sensor(hass, config_entry, data):
) != entry.original_name:
changes["original_name"] = new_name
if (
should_be_disabled := data.get(ATTR_SENSOR_DISABLED)
) is None or should_be_disabled == entry.disabled:
pass
elif should_be_disabled:
changes["disabled_by"] = er.RegistryEntryDisabler.INTEGRATION
else:
changes["disabled_by"] = None
for ent_reg_key, data_key in (
("device_class", ATTR_SENSOR_DEVICE_CLASS),
("unit_of_measurement", ATTR_SENSOR_UOM),
@ -551,6 +565,7 @@ async def webhook_update_sensor_states(hass, config_entry, data):
device_name = config_entry.data[ATTR_DEVICE_NAME]
resp = {}
entity_registry = er.async_get(hass)
for sensor in data:
entity_type = sensor[ATTR_SENSOR_TYPE]
@ -559,9 +574,10 @@ async def webhook_update_sensor_states(hass, config_entry, data):
unique_store_key = _gen_unique_id(config_entry.data[CONF_WEBHOOK_ID], unique_id)
entity_registry = er.async_get(hass)
if not entity_registry.async_get_entity_id(
entity_type, DOMAIN, unique_store_key
if not (
entity_id := entity_registry.async_get_entity_id(
entity_type, DOMAIN, unique_store_key
)
):
_LOGGER.debug(
"Refusing to update %s non-registered sensor: %s",
@ -601,6 +617,12 @@ async def webhook_update_sensor_states(hass, config_entry, data):
resp[unique_id] = {"success": True}
# Check if disabled
entry = entity_registry.async_get(entity_id)
if entry.disabled_by:
resp[unique_id]["is_disabled"] = True
return webhook_response(resp, registration=config_entry.data)
@ -637,6 +659,21 @@ async def webhook_get_config(hass, config_entry, data):
with suppress(hass.components.cloud.CloudNotAvailable):
resp[CONF_REMOTE_UI_URL] = cloud.async_remote_ui_url(hass)
webhook_id = config_entry.data[CONF_WEBHOOK_ID]
entities = {}
for entry in er.async_entries_for_config_entry(
er.async_get(hass), config_entry.entry_id
):
if entry.domain in ("binary_sensor", "sensor"):
unique_id = _extract_sensor_unique_id(webhook_id, entry.unique_id)
else:
unique_id = entry.unique_id
entities[unique_id] = {"disabled": entry.disabled}
resp["entities"] = entities
return webhook_response(resp, registration=config_entry.data)

View File

@ -355,7 +355,7 @@ async def test_default_disabling_entity(hass, create_registrations, webhook_clie
"name": "Battery State",
"type": "sensor",
"unique_id": "battery_state",
"default_disabled": True,
"disabled": True,
},
},
)
@ -373,3 +373,70 @@ async def test_default_disabling_entity(hass, create_registrations, webhook_clie
er.async_get(hass).async_get("sensor.test_1_battery_state").disabled_by
== er.RegistryEntryDisabler.INTEGRATION
)
async def test_updating_disabled_sensor(hass, create_registrations, webhook_client):
"""Test that sensors return error if disabled in instance."""
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": None,
"type": "sensor",
"unique_id": "battery_state",
},
},
)
assert reg_resp.status == HTTPStatus.CREATED
update_resp = await webhook_client.post(
webhook_url,
json={
"type": "update_sensor_states",
"data": [
{
"icon": "mdi:battery-unknown",
"state": 123,
"type": "sensor",
"unique_id": "battery_state",
},
],
},
)
assert update_resp.status == HTTPStatus.OK
json = await update_resp.json()
assert json["battery_state"]["success"] is True
assert "is_disabled" not in json["battery_state"]
er.async_get(hass).async_update_entity(
"sensor.test_1_battery_state", disabled_by=er.RegistryEntryDisabler.USER
)
update_resp = await webhook_client.post(
webhook_url,
json={
"type": "update_sensor_states",
"data": [
{
"icon": "mdi:battery-unknown",
"state": 123,
"type": "sensor",
"unique_id": "battery_state",
},
],
},
)
assert update_resp.status == HTTPStatus.OK
json = await update_resp.json()
assert json["battery_state"]["success"] is True
assert json["battery_state"]["is_disabled"] is True

View File

@ -254,10 +254,30 @@ async def test_webhook_handle_get_zones(hass, create_registrations, webhook_clie
async def test_webhook_handle_get_config(hass, create_registrations, webhook_client):
"""Test that we can get config properly."""
resp = await webhook_client.post(
"/api/webhook/{}".format(create_registrations[1]["webhook_id"]),
json={"type": "get_config"},
)
webhook_id = create_registrations[1]["webhook_id"]
webhook_url = f"/api/webhook/{webhook_id}"
# Create two entities
for sensor in (
{
"name": "Battery State",
"type": "sensor",
"unique_id": "battery-state-id",
},
{
"name": "Battery Charging",
"type": "sensor",
"unique_id": "battery-charging-id",
"disabled": True,
},
):
reg_resp = await webhook_client.post(
webhook_url,
json={"type": "register_sensor", "data": sensor},
)
assert reg_resp.status == HTTPStatus.CREATED
resp = await webhook_client.post(webhook_url, json={"type": "get_config"})
assert resp.status == HTTPStatus.OK
@ -279,6 +299,11 @@ async def test_webhook_handle_get_config(hass, create_registrations, webhook_cli
"components": hass_config["components"],
"version": hass_config["version"],
"theme_color": "#03A9F4", # Default frontend theme color
"entities": {
"mock-device-id": {"disabled": False},
"battery-state-id": {"disabled": False},
"battery-charging-id": {"disabled": True},
},
}
assert expected_dict == json
@ -902,6 +927,7 @@ async def test_reregister_sensor(hass, create_registrations, webhook_client):
assert entry.unit_of_measurement is None
assert entry.entity_category is None
assert entry.original_icon == "mdi:cellphone"
assert entry.disabled_by is None
reg_resp = await webhook_client.post(
webhook_url,
@ -917,6 +943,7 @@ async def test_reregister_sensor(hass, create_registrations, webhook_client):
"entity_category": "diagnostic",
"icon": "mdi:new-icon",
"unit_of_measurement": "%",
"disabled": True,
},
},
)
@ -928,3 +955,21 @@ async def test_reregister_sensor(hass, create_registrations, webhook_client):
assert entry.unit_of_measurement == "%"
assert entry.entity_category == "diagnostic"
assert entry.original_icon == "mdi:new-icon"
assert entry.disabled_by == er.RegistryEntryDisabler.INTEGRATION
reg_resp = await webhook_client.post(
webhook_url,
json={
"type": "register_sensor",
"data": {
"name": "New Name",
"type": "sensor",
"unique_id": "abcd",
"disabled": False,
},
},
)
assert reg_resp.status == HTTPStatus.CREATED
entry = ent_reg.async_get("sensor.test_1_battery_state")
assert entry.disabled_by is None