Fix ZHA temperature sensor restoration (#30661)

* Add test for restoring state for zha temp.

* Don't restore unit of measurement for ZHA sensors.

Properly restore ZHA temperature sensor state.
This commit is contained in:
Alexei Chetroi 2020-01-10 20:30:58 -05:00 committed by David F. Mulcahey
parent 605b0ceb5f
commit 008dddb17c
2 changed files with 135 additions and 8 deletions

View File

@ -12,9 +12,15 @@ from homeassistant.components.sensor import (
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DOMAIN, DOMAIN,
) )
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, POWER_WATT, TEMP_CELSIUS from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT,
POWER_WATT,
STATE_UNKNOWN,
TEMP_CELSIUS,
)
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.util.temperature import fahrenheit_to_celsius
from .core.const import ( from .core.const import (
CHANNEL_ELECTRICAL_MEASUREMENT, CHANNEL_ELECTRICAL_MEASUREMENT,
@ -160,7 +166,6 @@ class Sensor(ZhaEntity):
def async_restore_last_state(self, last_state): def async_restore_last_state(self, last_state):
"""Restore previous state.""" """Restore previous state."""
self._state = last_state.state self._state = last_state.state
self._unit = last_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
@callback @callback
async def async_state_attr_provider(self): async def async_state_attr_provider(self):
@ -277,3 +282,14 @@ class Temperature(Sensor):
_device_class = DEVICE_CLASS_TEMPERATURE _device_class = DEVICE_CLASS_TEMPERATURE
_divisor = 100 _divisor = 100
_unit = TEMP_CELSIUS _unit = TEMP_CELSIUS
@callback
def async_restore_last_state(self, last_state):
"""Restore previous state."""
if last_state.state == STATE_UNKNOWN:
return
if last_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) != TEMP_CELSIUS:
ftemp = float(last_state.state)
self._state = round(fahrenheit_to_celsius(ftemp), 1)
return
self._state = last_state.state

View File

@ -1,4 +1,5 @@
"""Test zha sensor.""" """Test zha sensor."""
import pytest
import zigpy.zcl.clusters.general as general import zigpy.zcl.clusters.general as general
import zigpy.zcl.clusters.homeautomation as homeautomation import zigpy.zcl.clusters.homeautomation as homeautomation
import zigpy.zcl.clusters.measurement as measurement import zigpy.zcl.clusters.measurement as measurement
@ -6,7 +7,20 @@ import zigpy.zcl.clusters.smartenergy as smartenergy
import zigpy.zcl.foundation as zcl_f import zigpy.zcl.foundation as zcl_f
from homeassistant.components.sensor import DOMAIN from homeassistant.components.sensor import DOMAIN
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN import homeassistant.config as config_util
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_UNIT_OF_MEASUREMENT,
CONF_UNIT_SYSTEM,
CONF_UNIT_SYSTEM_IMPERIAL,
CONF_UNIT_SYSTEM_METRIC,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.helpers import restore_state
from homeassistant.util import dt as dt_util
from .common import ( from .common import (
async_enable_traffic, async_enable_traffic,
@ -39,7 +53,7 @@ async def test_sensor(hass, config_entry, zha_gateway):
# ensure the sensor entity was created for each id in cluster_ids # ensure the sensor entity was created for each id in cluster_ids
for cluster_id in cluster_ids: for cluster_id in cluster_ids:
zigpy_device_info = zigpy_device_infos[cluster_id] zigpy_device_info = zigpy_device_infos[cluster_id]
entity_id = zigpy_device_info["entity_id"] entity_id = zigpy_device_info[ATTR_ENTITY_ID]
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
# allow traffic to flow through the gateway and devices # allow traffic to flow through the gateway and devices
@ -55,7 +69,7 @@ async def test_sensor(hass, config_entry, zha_gateway):
# test that the sensors now have a state of unknown # test that the sensors now have a state of unknown
for cluster_id in cluster_ids: for cluster_id in cluster_ids:
zigpy_device_info = zigpy_device_infos[cluster_id] zigpy_device_info = zigpy_device_infos[cluster_id]
entity_id = zigpy_device_info["entity_id"] entity_id = zigpy_device_info[ATTR_ENTITY_ID]
assert hass.states.get(entity_id).state == STATE_UNKNOWN assert hass.states.get(entity_id).state == STATE_UNKNOWN
# get the humidity device info and test the associated sensor logic # get the humidity device info and test the associated sensor logic
@ -128,7 +142,7 @@ async def async_build_devices(hass, zha_gateway, config_entry, cluster_ids):
device_info["cluster"] = zigpy_device.endpoints.get(1).in_clusters[cluster_id] device_info["cluster"] = zigpy_device.endpoints.get(1).in_clusters[cluster_id]
zha_device = zha_gateway.get_device(zigpy_device.ieee) zha_device = zha_gateway.get_device(zigpy_device.ieee)
device_info["zha_device"] = zha_device device_info["zha_device"] = zha_device
device_info["entity_id"] = await find_entity_id(DOMAIN, zha_device, hass) device_info[ATTR_ENTITY_ID] = await find_entity_id(DOMAIN, zha_device, hass)
await hass.async_block_till_done() await hass.async_block_till_done()
return device_infos return device_infos
@ -187,6 +201,103 @@ def assert_state(hass, device_info, state, unit_of_measurement):
This is used to ensure that the logic in each sensor class handled the This is used to ensure that the logic in each sensor class handled the
attribute report it received correctly. attribute report it received correctly.
""" """
hass_state = hass.states.get(device_info["entity_id"]) hass_state = hass.states.get(device_info[ATTR_ENTITY_ID])
assert hass_state.state == state assert hass_state.state == state
assert hass_state.attributes.get("unit_of_measurement") == unit_of_measurement assert hass_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == unit_of_measurement
@pytest.fixture
def hass_ms(hass):
"""Hass instance with measurement system."""
async def _hass_ms(meas_sys):
await config_util.async_process_ha_core_config(
hass, {CONF_UNIT_SYSTEM: meas_sys}
)
await hass.async_block_till_done()
return hass
return _hass_ms
@pytest.fixture
def core_rs(hass_storage):
"""Core.restore_state fixture."""
def _storage(entity_id, uom, state):
now = dt_util.utcnow().isoformat()
hass_storage[restore_state.STORAGE_KEY] = {
"version": restore_state.STORAGE_VERSION,
"key": restore_state.STORAGE_KEY,
"data": [
{
"state": {
"entity_id": entity_id,
"state": str(state),
"attributes": {ATTR_UNIT_OF_MEASUREMENT: uom},
"last_changed": now,
"last_updated": now,
"context": {
"id": "3c2243ff5f30447eb12e7348cfd5b8ff",
"user_id": None,
},
},
"last_seen": now,
}
],
}
return
return _storage
@pytest.mark.parametrize(
"uom, raw_temp, expected, restore",
[
(TEMP_CELSIUS, 2900, 29, False),
(TEMP_CELSIUS, 2900, 29, True),
(TEMP_FAHRENHEIT, 2900, 84, False),
(TEMP_FAHRENHEIT, 2900, 84, True),
],
)
async def test_temp_uom(
uom, raw_temp, expected, restore, hass_ms, config_entry, zha_gateway, core_rs
):
"""Test zha temperature sensor unit of measurement."""
entity_id = "sensor.fake1026_fakemodel1026_004f3202_temperature"
if restore:
core_rs(entity_id, uom, state=(expected - 2))
hass = await hass_ms(
CONF_UNIT_SYSTEM_METRIC if uom == TEMP_CELSIUS else CONF_UNIT_SYSTEM_IMPERIAL
)
# list of cluster ids to create devices and sensor entities for
temp_cluster = measurement.TemperatureMeasurement
cluster_ids = [temp_cluster.cluster_id]
# devices that were created from cluster_ids list above
zigpy_device_infos = await async_build_devices(
hass, zha_gateway, config_entry, cluster_ids
)
zigpy_device_info = zigpy_device_infos[temp_cluster.cluster_id]
zha_device = zigpy_device_info["zha_device"]
if not restore:
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
# allow traffic to flow through the gateway and devices
await async_enable_traffic(hass, zha_gateway, [zha_device])
# test that the sensors now have a state of unknown
if not restore:
assert hass.states.get(entity_id).state == STATE_UNKNOWN
await send_attribute_report(hass, zigpy_device_info["cluster"], 0, raw_temp)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state is not None
assert round(float(state.state)) == expected
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == uom