Skip to content
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
2 changes: 1 addition & 1 deletion homeassistant/components/mobile_app/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@

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"
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/mobile_app/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

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,
Expand Down Expand Up @@ -64,7 +64,7 @@ def name(self):
@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):
Expand Down
47 changes: 42 additions & 5 deletions homeassistant/components/mobile_app/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@
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,
Expand Down Expand Up @@ -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(
Expand 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,
)
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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]
Expand All @@ -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",
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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)


Expand Down
69 changes: 68 additions & 1 deletion tests/components/mobile_app/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
)
Expand All @@ -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
53 changes: 49 additions & 4 deletions tests/components/mobile_app/test_webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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,
},
},
)
Expand All @@ -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