mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 05:37:44 +00:00
Migrate mobile_app to RestoreEntity (#46391)
This commit is contained in:
parent
70e23402a9
commit
26e7916367
@ -17,11 +17,9 @@ from .const import (
|
||||
ATTR_MODEL,
|
||||
ATTR_OS_VERSION,
|
||||
CONF_CLOUDHOOK_URL,
|
||||
DATA_BINARY_SENSOR,
|
||||
DATA_CONFIG_ENTRIES,
|
||||
DATA_DELETED_IDS,
|
||||
DATA_DEVICES,
|
||||
DATA_SENSOR,
|
||||
DATA_STORE,
|
||||
DOMAIN,
|
||||
STORAGE_KEY,
|
||||
@ -40,18 +38,14 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
||||
app_config = await store.async_load()
|
||||
if app_config is None:
|
||||
app_config = {
|
||||
DATA_BINARY_SENSOR: {},
|
||||
DATA_CONFIG_ENTRIES: {},
|
||||
DATA_DELETED_IDS: [],
|
||||
DATA_SENSOR: {},
|
||||
}
|
||||
|
||||
hass.data[DOMAIN] = {
|
||||
DATA_BINARY_SENSOR: app_config.get(DATA_BINARY_SENSOR, {}),
|
||||
DATA_CONFIG_ENTRIES: {},
|
||||
DATA_DELETED_IDS: app_config.get(DATA_DELETED_IDS, []),
|
||||
DATA_DEVICES: {},
|
||||
DATA_SENSOR: app_config.get(DATA_SENSOR, {}),
|
||||
DATA_STORE: store,
|
||||
}
|
||||
|
||||
|
@ -2,18 +2,25 @@
|
||||
from functools import partial
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||
from homeassistant.const import CONF_WEBHOOK_ID
|
||||
from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID, STATE_ON
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import (
|
||||
ATTR_DEVICE_NAME,
|
||||
ATTR_SENSOR_ATTRIBUTES,
|
||||
ATTR_SENSOR_DEVICE_CLASS,
|
||||
ATTR_SENSOR_ICON,
|
||||
ATTR_SENSOR_NAME,
|
||||
ATTR_SENSOR_STATE,
|
||||
ATTR_SENSOR_TYPE,
|
||||
ATTR_SENSOR_TYPE_BINARY_SENSOR as ENTITY_TYPE,
|
||||
ATTR_SENSOR_UNIQUE_ID,
|
||||
DATA_DEVICES,
|
||||
DOMAIN,
|
||||
)
|
||||
from .entity import MobileAppEntity, sensor_id
|
||||
from .entity import MobileAppEntity, unique_id
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
@ -22,13 +29,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
|
||||
webhook_id = config_entry.data[CONF_WEBHOOK_ID]
|
||||
|
||||
for config in hass.data[DOMAIN][ENTITY_TYPE].values():
|
||||
if config[CONF_WEBHOOK_ID] != webhook_id:
|
||||
entity_registry = await er.async_get_registry(hass)
|
||||
entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)
|
||||
for entry in entries:
|
||||
if entry.domain != ENTITY_TYPE or entry.disabled_by:
|
||||
continue
|
||||
|
||||
device = hass.data[DOMAIN][DATA_DEVICES][webhook_id]
|
||||
|
||||
entities.append(MobileAppBinarySensor(config, device, config_entry))
|
||||
config = {
|
||||
ATTR_SENSOR_ATTRIBUTES: {},
|
||||
ATTR_SENSOR_DEVICE_CLASS: entry.device_class,
|
||||
ATTR_SENSOR_ICON: entry.original_icon,
|
||||
ATTR_SENSOR_NAME: entry.original_name,
|
||||
ATTR_SENSOR_STATE: None,
|
||||
ATTR_SENSOR_TYPE: entry.domain,
|
||||
ATTR_SENSOR_UNIQUE_ID: entry.unique_id,
|
||||
}
|
||||
entities.append(MobileAppBinarySensor(config, entry.device_id, config_entry))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
@ -37,14 +52,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
if data[CONF_WEBHOOK_ID] != webhook_id:
|
||||
return
|
||||
|
||||
unique_id = sensor_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID])
|
||||
|
||||
entity = hass.data[DOMAIN][ENTITY_TYPE][unique_id]
|
||||
|
||||
if "added" in entity:
|
||||
return
|
||||
|
||||
entity["added"] = True
|
||||
data[CONF_UNIQUE_ID] = unique_id(
|
||||
data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID]
|
||||
)
|
||||
data[
|
||||
CONF_NAME
|
||||
] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}"
|
||||
|
||||
device = hass.data[DOMAIN][DATA_DEVICES][data[CONF_WEBHOOK_ID]]
|
||||
|
||||
@ -64,3 +77,10 @@ class MobileAppBinarySensor(MobileAppEntity, BinarySensorEntity):
|
||||
def is_on(self):
|
||||
"""Return the state of the binary sensor."""
|
||||
return self._config[ATTR_SENSOR_STATE]
|
||||
|
||||
@callback
|
||||
def async_restore_last_state(self, last_state):
|
||||
"""Restore previous state."""
|
||||
|
||||
super().async_restore_last_state(last_state)
|
||||
self._config[ATTR_SENSOR_STATE] = last_state.state == STATE_ON
|
||||
|
@ -3,7 +3,7 @@ import uuid
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import person
|
||||
from homeassistant.helpers import entity_registry
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .const import ATTR_APP_ID, ATTR_DEVICE_ID, ATTR_DEVICE_NAME, CONF_USER_ID, DOMAIN
|
||||
|
||||
@ -36,8 +36,8 @@ class MobileAppFlowHandler(config_entries.ConfigFlow):
|
||||
user_input[ATTR_DEVICE_ID] = str(uuid.uuid4()).replace("-", "")
|
||||
|
||||
# Register device tracker entity and add to person registering app
|
||||
ent_reg = await entity_registry.async_get_registry(self.hass)
|
||||
devt_entry = ent_reg.async_get_or_create(
|
||||
entity_registry = await er.async_get_registry(self.hass)
|
||||
devt_entry = entity_registry.async_get_or_create(
|
||||
"device_tracker",
|
||||
DOMAIN,
|
||||
user_input[ATTR_DEVICE_ID],
|
||||
|
@ -9,11 +9,9 @@ CONF_REMOTE_UI_URL = "remote_ui_url"
|
||||
CONF_SECRET = "secret"
|
||||
CONF_USER_ID = "user_id"
|
||||
|
||||
DATA_BINARY_SENSOR = "binary_sensor"
|
||||
DATA_CONFIG_ENTRIES = "config_entries"
|
||||
DATA_DELETED_IDS = "deleted_ids"
|
||||
DATA_DEVICES = "devices"
|
||||
DATA_SENSOR = "sensor"
|
||||
DATA_STORE = "store"
|
||||
DATA_NOTIFY = "notify"
|
||||
|
||||
|
@ -1,57 +1,70 @@
|
||||
"""A entity class for mobile_app."""
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_WEBHOOK_ID
|
||||
from homeassistant.const import ATTR_ICON, CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from .const import (
|
||||
ATTR_DEVICE_NAME,
|
||||
ATTR_SENSOR_ATTRIBUTES,
|
||||
ATTR_SENSOR_DEVICE_CLASS,
|
||||
ATTR_SENSOR_ICON,
|
||||
ATTR_SENSOR_NAME,
|
||||
ATTR_SENSOR_STATE,
|
||||
ATTR_SENSOR_TYPE,
|
||||
ATTR_SENSOR_UNIQUE_ID,
|
||||
DOMAIN,
|
||||
SIGNAL_SENSOR_UPDATE,
|
||||
)
|
||||
from .helpers import device_info
|
||||
|
||||
|
||||
def sensor_id(webhook_id, unique_id):
|
||||
def unique_id(webhook_id, sensor_unique_id):
|
||||
"""Return a unique sensor ID."""
|
||||
return f"{webhook_id}_{unique_id}"
|
||||
return f"{webhook_id}_{sensor_unique_id}"
|
||||
|
||||
|
||||
class MobileAppEntity(Entity):
|
||||
class MobileAppEntity(RestoreEntity):
|
||||
"""Representation of an mobile app entity."""
|
||||
|
||||
def __init__(self, config: dict, device: DeviceEntry, entry: ConfigEntry):
|
||||
"""Initialize the sensor."""
|
||||
"""Initialize the entity."""
|
||||
self._config = config
|
||||
self._device = device
|
||||
self._entry = entry
|
||||
self._registration = entry.data
|
||||
self._sensor_id = sensor_id(
|
||||
self._registration[CONF_WEBHOOK_ID], config[ATTR_SENSOR_UNIQUE_ID]
|
||||
)
|
||||
self._unique_id = config[CONF_UNIQUE_ID]
|
||||
self._entity_type = config[ATTR_SENSOR_TYPE]
|
||||
self.unsub_dispatcher = None
|
||||
self._name = f"{entry.data[ATTR_DEVICE_NAME]} {config[ATTR_SENSOR_NAME]}"
|
||||
self._name = config[CONF_NAME]
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
self.unsub_dispatcher = async_dispatcher_connect(
|
||||
self.hass, SIGNAL_SENSOR_UPDATE, self._handle_update
|
||||
)
|
||||
state = await self.async_get_last_state()
|
||||
|
||||
if state is None:
|
||||
return
|
||||
|
||||
self.async_restore_last_state(state)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Disconnect dispatcher listener when removed."""
|
||||
if self.unsub_dispatcher is not None:
|
||||
self.unsub_dispatcher()
|
||||
|
||||
@callback
|
||||
def async_restore_last_state(self, last_state):
|
||||
"""Restore previous state."""
|
||||
self._config[ATTR_SENSOR_STATE] = last_state.state
|
||||
self._config[ATTR_SENSOR_ATTRIBUTES] = {
|
||||
**last_state.attributes,
|
||||
**self._config[ATTR_SENSOR_ATTRIBUTES],
|
||||
}
|
||||
if ATTR_ICON in last_state.attributes:
|
||||
self._config[ATTR_SENSOR_ICON] = last_state.attributes[ATTR_ICON]
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Declare that this entity pushes its state to HA."""
|
||||
@ -80,27 +93,19 @@ class MobileAppEntity(Entity):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID of this sensor."""
|
||||
return self._sensor_id
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device registry information for this entity."""
|
||||
return device_info(self._registration)
|
||||
|
||||
async def async_update(self):
|
||||
"""Get the latest state of the sensor."""
|
||||
data = self.hass.data[DOMAIN]
|
||||
try:
|
||||
self._config = data[self._entity_type][self._sensor_id]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
@callback
|
||||
def _handle_update(self, data):
|
||||
"""Handle async event updates."""
|
||||
incoming_id = sensor_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID])
|
||||
if incoming_id != self._sensor_id:
|
||||
incoming_id = unique_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID])
|
||||
if incoming_id != self._unique_id:
|
||||
return
|
||||
|
||||
self._config = data
|
||||
self._config = {**self._config, **data}
|
||||
self.async_write_ha_state()
|
||||
|
@ -25,9 +25,7 @@ from .const import (
|
||||
ATTR_SUPPORTS_ENCRYPTION,
|
||||
CONF_SECRET,
|
||||
CONF_USER_ID,
|
||||
DATA_BINARY_SENSOR,
|
||||
DATA_DELETED_IDS,
|
||||
DATA_SENSOR,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
@ -138,9 +136,7 @@ def safe_registration(registration: Dict) -> Dict:
|
||||
def savable_state(hass: HomeAssistantType) -> Dict:
|
||||
"""Return a clean object containing things that should be saved."""
|
||||
return {
|
||||
DATA_BINARY_SENSOR: hass.data[DOMAIN][DATA_BINARY_SENSOR],
|
||||
DATA_DELETED_IDS: hass.data[DOMAIN][DATA_DELETED_IDS],
|
||||
DATA_SENSOR: hass.data[DOMAIN][DATA_SENSOR],
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,19 +1,26 @@
|
||||
"""Sensor platform for mobile_app."""
|
||||
from functools import partial
|
||||
|
||||
from homeassistant.const import CONF_WEBHOOK_ID
|
||||
from homeassistant.const import CONF_NAME, CONF_UNIQUE_ID, CONF_WEBHOOK_ID
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import (
|
||||
ATTR_DEVICE_NAME,
|
||||
ATTR_SENSOR_ATTRIBUTES,
|
||||
ATTR_SENSOR_DEVICE_CLASS,
|
||||
ATTR_SENSOR_ICON,
|
||||
ATTR_SENSOR_NAME,
|
||||
ATTR_SENSOR_STATE,
|
||||
ATTR_SENSOR_TYPE,
|
||||
ATTR_SENSOR_TYPE_SENSOR as ENTITY_TYPE,
|
||||
ATTR_SENSOR_UNIQUE_ID,
|
||||
ATTR_SENSOR_UOM,
|
||||
DATA_DEVICES,
|
||||
DOMAIN,
|
||||
)
|
||||
from .entity import MobileAppEntity, sensor_id
|
||||
from .entity import MobileAppEntity, unique_id
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
@ -22,13 +29,22 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
|
||||
webhook_id = config_entry.data[CONF_WEBHOOK_ID]
|
||||
|
||||
for config in hass.data[DOMAIN][ENTITY_TYPE].values():
|
||||
if config[CONF_WEBHOOK_ID] != webhook_id:
|
||||
entity_registry = await er.async_get_registry(hass)
|
||||
entries = er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)
|
||||
for entry in entries:
|
||||
if entry.domain != ENTITY_TYPE or entry.disabled_by:
|
||||
continue
|
||||
|
||||
device = hass.data[DOMAIN][DATA_DEVICES][webhook_id]
|
||||
|
||||
entities.append(MobileAppSensor(config, device, config_entry))
|
||||
config = {
|
||||
ATTR_SENSOR_ATTRIBUTES: {},
|
||||
ATTR_SENSOR_DEVICE_CLASS: entry.device_class,
|
||||
ATTR_SENSOR_ICON: entry.original_icon,
|
||||
ATTR_SENSOR_NAME: entry.original_name,
|
||||
ATTR_SENSOR_STATE: None,
|
||||
ATTR_SENSOR_TYPE: entry.domain,
|
||||
ATTR_SENSOR_UNIQUE_ID: entry.unique_id,
|
||||
ATTR_SENSOR_UOM: entry.unit_of_measurement,
|
||||
}
|
||||
entities.append(MobileAppSensor(config, entry.device_id, config_entry))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
@ -37,14 +53,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
if data[CONF_WEBHOOK_ID] != webhook_id:
|
||||
return
|
||||
|
||||
unique_id = sensor_id(data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID])
|
||||
|
||||
entity = hass.data[DOMAIN][ENTITY_TYPE][unique_id]
|
||||
|
||||
if "added" in entity:
|
||||
return
|
||||
|
||||
entity["added"] = True
|
||||
data[CONF_UNIQUE_ID] = unique_id(
|
||||
data[CONF_WEBHOOK_ID], data[ATTR_SENSOR_UNIQUE_ID]
|
||||
)
|
||||
data[
|
||||
CONF_NAME
|
||||
] = f"{config_entry.data[ATTR_DEVICE_NAME]} {data[ATTR_SENSOR_NAME]}"
|
||||
|
||||
device = hass.data[DOMAIN][DATA_DEVICES][data[CONF_WEBHOOK_ID]]
|
||||
|
||||
|
@ -36,6 +36,7 @@ from homeassistant.exceptions import HomeAssistantError, ServiceNotFound
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
template,
|
||||
)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
@ -79,7 +80,6 @@ from .const import (
|
||||
CONF_SECRET,
|
||||
DATA_CONFIG_ENTRIES,
|
||||
DATA_DELETED_IDS,
|
||||
DATA_STORE,
|
||||
DOMAIN,
|
||||
ERR_ENCRYPTION_ALREADY_ENABLED,
|
||||
ERR_ENCRYPTION_NOT_AVAILABLE,
|
||||
@ -95,7 +95,6 @@ from .helpers import (
|
||||
error_response,
|
||||
registration_context,
|
||||
safe_registration,
|
||||
savable_state,
|
||||
supports_encryption,
|
||||
webhook_response,
|
||||
)
|
||||
@ -415,7 +414,10 @@ async def webhook_register_sensor(hass, config_entry, data):
|
||||
device_name = config_entry.data[ATTR_DEVICE_NAME]
|
||||
|
||||
unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}"
|
||||
existing_sensor = unique_store_key in hass.data[DOMAIN][entity_type]
|
||||
entity_registry = await er.async_get_registry(hass)
|
||||
existing_sensor = entity_registry.async_get_entity_id(
|
||||
entity_type, DOMAIN, unique_store_key
|
||||
)
|
||||
|
||||
data[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID]
|
||||
|
||||
@ -424,16 +426,7 @@ async def webhook_register_sensor(hass, config_entry, data):
|
||||
_LOGGER.debug(
|
||||
"Re-register for %s of existing sensor %s", device_name, unique_id
|
||||
)
|
||||
entry = hass.data[DOMAIN][entity_type][unique_store_key]
|
||||
data = {**entry, **data}
|
||||
|
||||
hass.data[DOMAIN][entity_type][unique_store_key] = data
|
||||
|
||||
hass.data[DOMAIN][DATA_STORE].async_delay_save(
|
||||
lambda: savable_state(hass), DELAY_SAVE
|
||||
)
|
||||
|
||||
if existing_sensor:
|
||||
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, data)
|
||||
else:
|
||||
register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register"
|
||||
@ -485,7 +478,10 @@ async def webhook_update_sensor_states(hass, config_entry, data):
|
||||
|
||||
unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}"
|
||||
|
||||
if unique_store_key not in hass.data[DOMAIN][entity_type]:
|
||||
entity_registry = await er.async_get_registry(hass)
|
||||
if not entity_registry.async_get_entity_id(
|
||||
entity_type, DOMAIN, unique_store_key
|
||||
):
|
||||
_LOGGER.error(
|
||||
"Refusing to update %s non-registered sensor: %s",
|
||||
device_name,
|
||||
@ -498,7 +494,7 @@ async def webhook_update_sensor_states(hass, config_entry, data):
|
||||
}
|
||||
continue
|
||||
|
||||
entry = hass.data[DOMAIN][entity_type][unique_store_key]
|
||||
entry = {CONF_WEBHOOK_ID: config_entry.data[CONF_WEBHOOK_ID]}
|
||||
|
||||
try:
|
||||
sensor = sensor_schema_full(sensor)
|
||||
@ -518,16 +514,10 @@ async def webhook_update_sensor_states(hass, config_entry, data):
|
||||
|
||||
new_state = {**entry, **sensor}
|
||||
|
||||
hass.data[DOMAIN][entity_type][unique_store_key] = new_state
|
||||
|
||||
async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state)
|
||||
|
||||
resp[unique_id] = {"success": True}
|
||||
|
||||
hass.data[DOMAIN][DATA_STORE].async_delay_save(
|
||||
lambda: savable_state(hass), DELAY_SAVE
|
||||
)
|
||||
|
||||
return webhook_response(resp, registration=config_entry.data)
|
||||
|
||||
|
||||
|
@ -7,14 +7,6 @@ from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import REGISTER, REGISTER_CLEARTEXT
|
||||
|
||||
from tests.common import mock_device_registry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def registry(hass):
|
||||
"""Return a configured device registry."""
|
||||
return mock_device_registry(hass)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def create_registrations(hass, authed_api_client):
|
||||
|
271
tests/components/mobile_app/test_binary_sensor.py
Normal file
271
tests/components/mobile_app/test_binary_sensor.py
Normal file
@ -0,0 +1,271 @@
|
||||
"""Entity tests for mobile_app."""
|
||||
from homeassistant.const import STATE_OFF
|
||||
from homeassistant.helpers import device_registry
|
||||
|
||||
|
||||
async def test_sensor(hass, create_registrations, webhook_client):
|
||||
"""Test that sensors can be registered and updated."""
|
||||
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": {
|
||||
"attributes": {"foo": "bar"},
|
||||
"device_class": "plug",
|
||||
"icon": "mdi:power-plug",
|
||||
"name": "Is Charging",
|
||||
"state": True,
|
||||
"type": "binary_sensor",
|
||||
"unique_id": "is_charging",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert reg_resp.status == 201
|
||||
|
||||
json = await reg_resp.json()
|
||||
assert json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity = hass.states.get("binary_sensor.test_1_is_charging")
|
||||
assert entity is not None
|
||||
|
||||
assert entity.attributes["device_class"] == "plug"
|
||||
assert entity.attributes["icon"] == "mdi:power-plug"
|
||||
assert entity.attributes["foo"] == "bar"
|
||||
assert entity.domain == "binary_sensor"
|
||||
assert entity.name == "Test 1 Is Charging"
|
||||
assert entity.state == "on"
|
||||
|
||||
update_resp = await webhook_client.post(
|
||||
webhook_url,
|
||||
json={
|
||||
"type": "update_sensor_states",
|
||||
"data": [
|
||||
{
|
||||
"icon": "mdi:battery-unknown",
|
||||
"state": False,
|
||||
"type": "binary_sensor",
|
||||
"unique_id": "is_charging",
|
||||
},
|
||||
# This invalid data should not invalidate whole request
|
||||
{
|
||||
"type": "binary_sensor",
|
||||
"unique_id": "invalid_state",
|
||||
"invalid": "data",
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
assert update_resp.status == 200
|
||||
|
||||
json = await update_resp.json()
|
||||
assert json["invalid_state"]["success"] is False
|
||||
|
||||
updated_entity = hass.states.get("binary_sensor.test_1_is_charging")
|
||||
assert updated_entity.state == "off"
|
||||
assert "foo" not in updated_entity.attributes
|
||||
|
||||
dev_reg = await device_registry.async_get_registry(hass)
|
||||
assert len(dev_reg.devices) == len(create_registrations)
|
||||
|
||||
# Reload to verify state is restored
|
||||
config_entry = hass.config_entries.async_entries("mobile_app")[1]
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
unloaded_entity = hass.states.get("binary_sensor.test_1_is_charging")
|
||||
assert unloaded_entity.state == "unavailable"
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
restored_entity = hass.states.get("binary_sensor.test_1_is_charging")
|
||||
assert restored_entity.state == updated_entity.state
|
||||
assert restored_entity.attributes == updated_entity.attributes
|
||||
|
||||
|
||||
async def test_sensor_must_register(hass, create_registrations, webhook_client):
|
||||
"""Test that sensors must be registered before updating."""
|
||||
webhook_id = create_registrations[1]["webhook_id"]
|
||||
webhook_url = f"/api/webhook/{webhook_id}"
|
||||
resp = await webhook_client.post(
|
||||
webhook_url,
|
||||
json={
|
||||
"type": "update_sensor_states",
|
||||
"data": [
|
||||
{"state": True, "type": "binary_sensor", "unique_id": "battery_state"}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
assert resp.status == 200
|
||||
|
||||
json = await resp.json()
|
||||
assert json["battery_state"]["success"] is False
|
||||
assert json["battery_state"]["error"]["code"] == "not_registered"
|
||||
|
||||
|
||||
async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client, caplog):
|
||||
"""Test that a duplicate unique ID in registration updates the sensor."""
|
||||
webhook_id = create_registrations[1]["webhook_id"]
|
||||
webhook_url = f"/api/webhook/{webhook_id}"
|
||||
|
||||
payload = {
|
||||
"type": "register_sensor",
|
||||
"data": {
|
||||
"attributes": {"foo": "bar"},
|
||||
"device_class": "plug",
|
||||
"icon": "mdi:power-plug",
|
||||
"name": "Is Charging",
|
||||
"state": True,
|
||||
"type": "binary_sensor",
|
||||
"unique_id": "is_charging",
|
||||
},
|
||||
}
|
||||
|
||||
reg_resp = await webhook_client.post(webhook_url, json=payload)
|
||||
|
||||
assert reg_resp.status == 201
|
||||
|
||||
reg_json = await reg_resp.json()
|
||||
assert reg_json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert "Re-register" not in caplog.text
|
||||
|
||||
entity = hass.states.get("binary_sensor.test_1_is_charging")
|
||||
assert entity is not None
|
||||
|
||||
assert entity.attributes["device_class"] == "plug"
|
||||
assert entity.attributes["icon"] == "mdi:power-plug"
|
||||
assert entity.attributes["foo"] == "bar"
|
||||
assert entity.domain == "binary_sensor"
|
||||
assert entity.name == "Test 1 Is Charging"
|
||||
assert entity.state == "on"
|
||||
|
||||
payload["data"]["state"] = False
|
||||
dupe_resp = await webhook_client.post(webhook_url, json=payload)
|
||||
|
||||
assert dupe_resp.status == 201
|
||||
dupe_reg_json = await dupe_resp.json()
|
||||
assert dupe_reg_json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert "Re-register" in caplog.text
|
||||
|
||||
entity = hass.states.get("binary_sensor.test_1_is_charging")
|
||||
assert entity is not None
|
||||
|
||||
assert entity.attributes["device_class"] == "plug"
|
||||
assert entity.attributes["icon"] == "mdi:power-plug"
|
||||
assert entity.attributes["foo"] == "bar"
|
||||
assert entity.domain == "binary_sensor"
|
||||
assert entity.name == "Test 1 Is Charging"
|
||||
assert entity.state == "off"
|
||||
|
||||
|
||||
async def test_register_sensor_no_state(hass, create_registrations, webhook_client):
|
||||
"""Test that sensors can be registered, when there is no (unknown) state."""
|
||||
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": "Is Charging",
|
||||
"state": None,
|
||||
"type": "binary_sensor",
|
||||
"unique_id": "is_charging",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert reg_resp.status == 201
|
||||
|
||||
json = await reg_resp.json()
|
||||
assert json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity = hass.states.get("binary_sensor.test_1_is_charging")
|
||||
assert entity is not None
|
||||
|
||||
assert entity.domain == "binary_sensor"
|
||||
assert entity.name == "Test 1 Is Charging"
|
||||
assert entity.state == STATE_OFF # Binary sensor defaults to off
|
||||
|
||||
reg_resp = await webhook_client.post(
|
||||
webhook_url,
|
||||
json={
|
||||
"type": "register_sensor",
|
||||
"data": {
|
||||
"name": "Backup Is Charging",
|
||||
"type": "binary_sensor",
|
||||
"unique_id": "backup_is_charging",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert reg_resp.status == 201
|
||||
|
||||
json = await reg_resp.json()
|
||||
assert json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity = hass.states.get("binary_sensor.test_1_backup_is_charging")
|
||||
assert entity
|
||||
|
||||
assert entity.domain == "binary_sensor"
|
||||
assert entity.name == "Test 1 Backup Is Charging"
|
||||
assert entity.state == STATE_OFF # Binary sensor defaults to off
|
||||
|
||||
|
||||
async def test_update_sensor_no_state(hass, create_registrations, webhook_client):
|
||||
"""Test that sensors can be updated, when there is no (unknown) state."""
|
||||
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": "Is Charging",
|
||||
"state": True,
|
||||
"type": "binary_sensor",
|
||||
"unique_id": "is_charging",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert reg_resp.status == 201
|
||||
|
||||
json = await reg_resp.json()
|
||||
assert json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity = hass.states.get("binary_sensor.test_1_is_charging")
|
||||
assert entity is not None
|
||||
assert entity.state == "on"
|
||||
|
||||
update_resp = await webhook_client.post(
|
||||
webhook_url,
|
||||
json={
|
||||
"type": "update_sensor_states",
|
||||
"data": [
|
||||
{"state": None, "type": "binary_sensor", "unique_id": "is_charging"}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
assert update_resp.status == 200
|
||||
|
||||
json = await update_resp.json()
|
||||
assert json == {"is_charging": {"success": True}}
|
||||
|
||||
updated_entity = hass.states.get("binary_sensor.test_1_is_charging")
|
||||
assert updated_entity.state == STATE_OFF # Binary sensor defaults to off
|
@ -66,10 +66,24 @@ async def test_sensor(hass, create_registrations, webhook_client):
|
||||
|
||||
updated_entity = hass.states.get("sensor.test_1_battery_state")
|
||||
assert updated_entity.state == "123"
|
||||
assert "foo" not in updated_entity.attributes
|
||||
|
||||
dev_reg = await device_registry.async_get_registry(hass)
|
||||
assert len(dev_reg.devices) == len(create_registrations)
|
||||
|
||||
# Reload to verify state is restored
|
||||
config_entry = hass.config_entries.async_entries("mobile_app")[1]
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
unloaded_entity = hass.states.get("sensor.test_1_battery_state")
|
||||
assert unloaded_entity.state == "unavailable"
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
restored_entity = hass.states.get("sensor.test_1_battery_state")
|
||||
assert restored_entity.state == updated_entity.state
|
||||
assert restored_entity.attributes == updated_entity.attributes
|
||||
|
||||
|
||||
async def test_sensor_must_register(hass, create_registrations, webhook_client):
|
||||
"""Test that sensors must be registered before updating."""
|
@ -109,7 +109,7 @@ async def test_webhook_handle_fire_event(hass, create_registrations, webhook_cli
|
||||
|
||||
@callback
|
||||
def store_event(event):
|
||||
"""Helepr to store events."""
|
||||
"""Help store events."""
|
||||
events.append(event)
|
||||
|
||||
hass.bus.async_listen("test_event", store_event)
|
||||
|
Loading…
x
Reference in New Issue
Block a user