mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Correct restoring of mobile_app sensors (#76886)
This commit is contained in:
parent
b4323108b1
commit
0ed265e2be
@ -75,9 +75,8 @@ class MobileAppBinarySensor(MobileAppEntity, BinarySensorEntity):
|
||||
"""Return the state of the binary sensor."""
|
||||
return self._config[ATTR_SENSOR_STATE]
|
||||
|
||||
@callback
|
||||
def async_restore_last_state(self, last_state):
|
||||
async def async_restore_last_state(self, last_state):
|
||||
"""Restore previous state."""
|
||||
|
||||
super().async_restore_last_state(last_state)
|
||||
await super().async_restore_last_state(last_state)
|
||||
self._config[ATTR_SENSOR_STATE] = last_state.state == STATE_ON
|
||||
|
@ -43,10 +43,9 @@ class MobileAppEntity(RestoreEntity):
|
||||
if (state := await self.async_get_last_state()) is None:
|
||||
return
|
||||
|
||||
self.async_restore_last_state(state)
|
||||
await self.async_restore_last_state(state)
|
||||
|
||||
@callback
|
||||
def async_restore_last_state(self, last_state):
|
||||
async def async_restore_last_state(self, last_state):
|
||||
"""Restore previous state."""
|
||||
self._config[ATTR_SENSOR_STATE] = last_state.state
|
||||
self._config[ATTR_SENSOR_ATTRIBUTES] = {
|
||||
|
@ -3,9 +3,9 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
||||
from homeassistant.components.sensor import RestoreSensor, SensorDeviceClass
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_WEBHOOK_ID, STATE_UNKNOWN
|
||||
from homeassistant.const import CONF_WEBHOOK_ID, STATE_UNKNOWN, TEMP_CELSIUS
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
@ -27,6 +27,7 @@ from .const import (
|
||||
DOMAIN,
|
||||
)
|
||||
from .entity import MobileAppEntity
|
||||
from .webhook import _extract_sensor_unique_id
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@ -73,9 +74,30 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
class MobileAppSensor(MobileAppEntity, SensorEntity):
|
||||
class MobileAppSensor(MobileAppEntity, RestoreSensor):
|
||||
"""Representation of an mobile app sensor."""
|
||||
|
||||
async def async_restore_last_state(self, last_state):
|
||||
"""Restore previous state."""
|
||||
|
||||
await super().async_restore_last_state(last_state)
|
||||
|
||||
if not (last_sensor_data := await self.async_get_last_sensor_data()):
|
||||
# Workaround to handle migration to RestoreSensor, can be removed
|
||||
# in HA Core 2023.4
|
||||
self._config[ATTR_SENSOR_STATE] = None
|
||||
webhook_id = self._entry.data[CONF_WEBHOOK_ID]
|
||||
sensor_unique_id = _extract_sensor_unique_id(webhook_id, self.unique_id)
|
||||
if (
|
||||
self.device_class == SensorDeviceClass.TEMPERATURE
|
||||
and sensor_unique_id == "battery_temperature"
|
||||
):
|
||||
self._config[ATTR_SENSOR_UOM] = TEMP_CELSIUS
|
||||
return
|
||||
|
||||
self._config[ATTR_SENSOR_STATE] = last_sensor_data.native_value
|
||||
self._config[ATTR_SENSOR_UOM] = last_sensor_data.native_unit_of_measurement
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
|
@ -1,15 +1,34 @@
|
||||
"""Entity tests for mobile_app."""
|
||||
from http import HTTPStatus
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.const import PERCENTAGE, STATE_UNAVAILABLE, STATE_UNKNOWN
|
||||
from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
|
||||
|
||||
|
||||
async def test_sensor(hass, create_registrations, webhook_client):
|
||||
@pytest.mark.parametrize(
|
||||
"unit_system, state_unit, state1, state2",
|
||||
(
|
||||
(METRIC_SYSTEM, TEMP_CELSIUS, "100", "123"),
|
||||
(IMPERIAL_SYSTEM, TEMP_FAHRENHEIT, "212", "253"),
|
||||
),
|
||||
)
|
||||
async def test_sensor(
|
||||
hass, create_registrations, webhook_client, unit_system, state_unit, state1, state2
|
||||
):
|
||||
"""Test that sensors can be registered and updated."""
|
||||
hass.config.units = unit_system
|
||||
|
||||
webhook_id = create_registrations[1]["webhook_id"]
|
||||
webhook_url = f"/api/webhook/{webhook_id}"
|
||||
|
||||
@ -19,15 +38,15 @@ async def test_sensor(hass, create_registrations, webhook_client):
|
||||
"type": "register_sensor",
|
||||
"data": {
|
||||
"attributes": {"foo": "bar"},
|
||||
"device_class": "battery",
|
||||
"device_class": "temperature",
|
||||
"icon": "mdi:battery",
|
||||
"name": "Battery State",
|
||||
"name": "Battery Temperature",
|
||||
"state": 100,
|
||||
"type": "sensor",
|
||||
"entity_category": "diagnostic",
|
||||
"unique_id": "battery_state",
|
||||
"unique_id": "battery_temp",
|
||||
"state_class": "total",
|
||||
"unit_of_measurement": PERCENTAGE,
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
},
|
||||
},
|
||||
)
|
||||
@ -38,20 +57,23 @@ async def test_sensor(hass, create_registrations, webhook_client):
|
||||
assert json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity = hass.states.get("sensor.test_1_battery_state")
|
||||
entity = hass.states.get("sensor.test_1_battery_temperature")
|
||||
assert entity is not None
|
||||
|
||||
assert entity.attributes["device_class"] == "battery"
|
||||
assert entity.attributes["device_class"] == "temperature"
|
||||
assert entity.attributes["icon"] == "mdi:battery"
|
||||
assert entity.attributes["unit_of_measurement"] == PERCENTAGE
|
||||
# unit of temperature sensor is automatically converted to the system UoM
|
||||
assert entity.attributes["unit_of_measurement"] == state_unit
|
||||
assert entity.attributes["foo"] == "bar"
|
||||
assert entity.attributes["state_class"] == "total"
|
||||
assert entity.domain == "sensor"
|
||||
assert entity.name == "Test 1 Battery State"
|
||||
assert entity.state == "100"
|
||||
assert entity.name == "Test 1 Battery Temperature"
|
||||
assert entity.state == state1
|
||||
|
||||
assert (
|
||||
er.async_get(hass).async_get("sensor.test_1_battery_state").entity_category
|
||||
er.async_get(hass)
|
||||
.async_get("sensor.test_1_battery_temperature")
|
||||
.entity_category
|
||||
== "diagnostic"
|
||||
)
|
||||
|
||||
@ -64,7 +86,7 @@ async def test_sensor(hass, create_registrations, webhook_client):
|
||||
"icon": "mdi:battery-unknown",
|
||||
"state": 123,
|
||||
"type": "sensor",
|
||||
"unique_id": "battery_state",
|
||||
"unique_id": "battery_temp",
|
||||
},
|
||||
# This invalid data should not invalidate whole request
|
||||
{"type": "sensor", "unique_id": "invalid_state", "invalid": "data"},
|
||||
@ -77,8 +99,8 @@ async def test_sensor(hass, create_registrations, webhook_client):
|
||||
json = await update_resp.json()
|
||||
assert json["invalid_state"]["success"] is False
|
||||
|
||||
updated_entity = hass.states.get("sensor.test_1_battery_state")
|
||||
assert updated_entity.state == "123"
|
||||
updated_entity = hass.states.get("sensor.test_1_battery_temperature")
|
||||
assert updated_entity.state == state2
|
||||
assert "foo" not in updated_entity.attributes
|
||||
|
||||
dev_reg = dr.async_get(hass)
|
||||
@ -88,16 +110,120 @@ async def test_sensor(hass, create_registrations, webhook_client):
|
||||
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")
|
||||
unloaded_entity = hass.states.get("sensor.test_1_battery_temperature")
|
||||
assert unloaded_entity.state == 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")
|
||||
restored_entity = hass.states.get("sensor.test_1_battery_temperature")
|
||||
assert restored_entity.state == updated_entity.state
|
||||
assert restored_entity.attributes == updated_entity.attributes
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"unique_id, unit_system, state_unit, state1, state2",
|
||||
(
|
||||
("battery_temperature", METRIC_SYSTEM, TEMP_CELSIUS, "100", "123"),
|
||||
("battery_temperature", IMPERIAL_SYSTEM, TEMP_FAHRENHEIT, "212", "253"),
|
||||
# The unique_id doesn't match that of the mobile app's battery temperature sensor
|
||||
("battery_temp", IMPERIAL_SYSTEM, TEMP_FAHRENHEIT, "212", "123"),
|
||||
),
|
||||
)
|
||||
async def test_sensor_migration(
|
||||
hass,
|
||||
create_registrations,
|
||||
webhook_client,
|
||||
unique_id,
|
||||
unit_system,
|
||||
state_unit,
|
||||
state1,
|
||||
state2,
|
||||
):
|
||||
"""Test migration to RestoreSensor."""
|
||||
hass.config.units = unit_system
|
||||
|
||||
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": "temperature",
|
||||
"icon": "mdi:battery",
|
||||
"name": "Battery Temperature",
|
||||
"state": 100,
|
||||
"type": "sensor",
|
||||
"entity_category": "diagnostic",
|
||||
"unique_id": unique_id,
|
||||
"state_class": "total",
|
||||
"unit_of_measurement": TEMP_CELSIUS,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert reg_resp.status == HTTPStatus.CREATED
|
||||
|
||||
json = await reg_resp.json()
|
||||
assert json == {"success": True}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity = hass.states.get("sensor.test_1_battery_temperature")
|
||||
assert entity is not None
|
||||
|
||||
assert entity.attributes["device_class"] == "temperature"
|
||||
assert entity.attributes["icon"] == "mdi:battery"
|
||||
# unit of temperature sensor is automatically converted to the system UoM
|
||||
assert entity.attributes["unit_of_measurement"] == state_unit
|
||||
assert entity.attributes["foo"] == "bar"
|
||||
assert entity.attributes["state_class"] == "total"
|
||||
assert entity.domain == "sensor"
|
||||
assert entity.name == "Test 1 Battery Temperature"
|
||||
assert entity.state == state1
|
||||
|
||||
# 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_temperature")
|
||||
assert unloaded_entity.state == STATE_UNAVAILABLE
|
||||
|
||||
# Simulate migration to RestoreSensor
|
||||
with patch(
|
||||
"homeassistant.helpers.restore_state.RestoreEntity.async_get_last_extra_data",
|
||||
return_value=None,
|
||||
):
|
||||
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_temperature")
|
||||
assert restored_entity.state == "unknown"
|
||||
assert restored_entity.attributes == entity.attributes
|
||||
|
||||
# Test unit conversion is working
|
||||
update_resp = await webhook_client.post(
|
||||
webhook_url,
|
||||
json={
|
||||
"type": "update_sensor_states",
|
||||
"data": [
|
||||
{
|
||||
"icon": "mdi:battery-unknown",
|
||||
"state": 123,
|
||||
"type": "sensor",
|
||||
"unique_id": unique_id,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
assert update_resp.status == HTTPStatus.OK
|
||||
|
||||
updated_entity = hass.states.get("sensor.test_1_battery_temperature")
|
||||
assert updated_entity.state == state2
|
||||
assert "foo" not in 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"]
|
||||
|
Loading…
x
Reference in New Issue
Block a user