2019-02-03 21:03:35 +00:00
|
|
|
"""Test zha sensor."""
|
2020-01-18 01:53:31 +00:00
|
|
|
from unittest import mock
|
|
|
|
|
2020-01-11 01:30:58 +00:00
|
|
|
import pytest
|
2019-10-21 23:30:56 +00:00
|
|
|
import zigpy.zcl.clusters.general as general
|
|
|
|
import zigpy.zcl.clusters.homeautomation as homeautomation
|
|
|
|
import zigpy.zcl.clusters.measurement as measurement
|
|
|
|
import zigpy.zcl.clusters.smartenergy as smartenergy
|
|
|
|
import zigpy.zcl.foundation as zcl_f
|
2019-10-21 17:14:17 +00:00
|
|
|
|
2019-02-03 21:03:35 +00:00
|
|
|
from homeassistant.components.sensor import DOMAIN
|
2020-01-11 01:30:58 +00:00
|
|
|
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
|
2019-10-21 17:14:17 +00:00
|
|
|
|
2019-02-03 21:03:35 +00:00
|
|
|
from .common import (
|
2019-10-21 17:14:17 +00:00
|
|
|
async_enable_traffic,
|
2019-07-31 19:25:30 +00:00
|
|
|
async_init_zigpy_device,
|
2019-10-21 17:14:17 +00:00
|
|
|
async_test_device_join,
|
2019-10-31 16:31:06 +00:00
|
|
|
find_entity_id,
|
2019-07-31 19:25:30 +00:00
|
|
|
make_attribute,
|
2019-10-21 17:14:17 +00:00
|
|
|
make_zcl_header,
|
2019-02-03 21:03:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_sensor(hass, config_entry, zha_gateway):
|
|
|
|
"""Test zha sensor platform."""
|
|
|
|
|
|
|
|
# list of cluster ids to create devices and sensor entities for
|
|
|
|
cluster_ids = [
|
2019-10-21 23:30:56 +00:00
|
|
|
measurement.RelativeHumidity.cluster_id,
|
|
|
|
measurement.TemperatureMeasurement.cluster_id,
|
|
|
|
measurement.PressureMeasurement.cluster_id,
|
|
|
|
measurement.IlluminanceMeasurement.cluster_id,
|
|
|
|
smartenergy.Metering.cluster_id,
|
|
|
|
homeautomation.ElectricalMeasurement.cluster_id,
|
2019-02-03 21:03:35 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
# devices that were created from cluster_ids list above
|
|
|
|
zigpy_device_infos = await async_build_devices(
|
2019-07-31 19:25:30 +00:00
|
|
|
hass, zha_gateway, config_entry, cluster_ids
|
|
|
|
)
|
2019-02-03 21:03:35 +00:00
|
|
|
|
|
|
|
# ensure the sensor entity was created for each id in cluster_ids
|
2019-02-07 08:14:19 +00:00
|
|
|
for cluster_id in cluster_ids:
|
|
|
|
zigpy_device_info = zigpy_device_infos[cluster_id]
|
2020-01-11 01:30:58 +00:00
|
|
|
entity_id = zigpy_device_info[ATTR_ENTITY_ID]
|
2019-02-07 08:14:19 +00:00
|
|
|
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
|
|
|
|
|
|
|
# allow traffic to flow through the gateway and devices
|
2019-07-31 19:25:30 +00:00
|
|
|
await async_enable_traffic(
|
|
|
|
hass,
|
|
|
|
zha_gateway,
|
|
|
|
[
|
|
|
|
zigpy_device_info["zha_device"]
|
|
|
|
for zigpy_device_info in zigpy_device_infos.values()
|
|
|
|
],
|
|
|
|
)
|
2019-02-07 08:14:19 +00:00
|
|
|
|
|
|
|
# test that the sensors now have a state of unknown
|
2019-02-03 21:03:35 +00:00
|
|
|
for cluster_id in cluster_ids:
|
|
|
|
zigpy_device_info = zigpy_device_infos[cluster_id]
|
2020-01-11 01:30:58 +00:00
|
|
|
entity_id = zigpy_device_info[ATTR_ENTITY_ID]
|
2019-02-03 21:03:35 +00:00
|
|
|
assert hass.states.get(entity_id).state == STATE_UNKNOWN
|
|
|
|
|
|
|
|
# get the humidity device info and test the associated sensor logic
|
2019-10-21 23:30:56 +00:00
|
|
|
device_info = zigpy_device_infos[measurement.RelativeHumidity.cluster_id]
|
2019-02-03 21:03:35 +00:00
|
|
|
await async_test_humidity(hass, device_info)
|
|
|
|
|
|
|
|
# get the temperature device info and test the associated sensor logic
|
2019-10-21 23:30:56 +00:00
|
|
|
device_info = zigpy_device_infos[measurement.TemperatureMeasurement.cluster_id]
|
2019-02-03 21:03:35 +00:00
|
|
|
await async_test_temperature(hass, device_info)
|
|
|
|
|
|
|
|
# get the pressure device info and test the associated sensor logic
|
2019-10-21 23:30:56 +00:00
|
|
|
device_info = zigpy_device_infos[measurement.PressureMeasurement.cluster_id]
|
2019-02-03 21:03:35 +00:00
|
|
|
await async_test_pressure(hass, device_info)
|
|
|
|
|
|
|
|
# get the illuminance device info and test the associated sensor logic
|
2019-10-21 23:30:56 +00:00
|
|
|
device_info = zigpy_device_infos[measurement.IlluminanceMeasurement.cluster_id]
|
2019-02-03 21:03:35 +00:00
|
|
|
await async_test_illuminance(hass, device_info)
|
|
|
|
|
|
|
|
# get the metering device info and test the associated sensor logic
|
2019-10-21 23:30:56 +00:00
|
|
|
device_info = zigpy_device_infos[smartenergy.Metering.cluster_id]
|
2019-02-03 21:03:35 +00:00
|
|
|
await async_test_metering(hass, device_info)
|
|
|
|
|
|
|
|
# get the electrical_measurement device info and test the associated
|
|
|
|
# sensor logic
|
2019-10-21 23:30:56 +00:00
|
|
|
device_info = zigpy_device_infos[homeautomation.ElectricalMeasurement.cluster_id]
|
2019-02-03 21:03:35 +00:00
|
|
|
await async_test_electrical_measurement(hass, device_info)
|
|
|
|
|
|
|
|
# test joining a new temperature sensor to the network
|
|
|
|
await async_test_device_join(
|
2019-10-31 16:31:06 +00:00
|
|
|
hass, zha_gateway, measurement.TemperatureMeasurement.cluster_id, entity_id
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-02-03 21:03:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_build_devices(hass, zha_gateway, config_entry, cluster_ids):
|
|
|
|
"""Build a zigpy device for each cluster id.
|
|
|
|
|
|
|
|
This will build devices for all cluster ids that exist in cluster_ids.
|
|
|
|
They get added to the network and then the sensor component is loaded
|
2020-01-31 16:33:00 +00:00
|
|
|
which will cause sensor entities to get created for each device.
|
2019-02-03 21:03:35 +00:00
|
|
|
A dict containing relevant device info for testing is returned. It contains
|
|
|
|
the entity id, zigpy device, and the zigbee cluster for the sensor.
|
|
|
|
"""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2019-02-03 21:03:35 +00:00
|
|
|
device_infos = {}
|
|
|
|
counter = 0
|
|
|
|
for cluster_id in cluster_ids:
|
|
|
|
# create zigpy device
|
|
|
|
device_infos[cluster_id] = {"zigpy_device": None}
|
2019-07-31 19:25:30 +00:00
|
|
|
device_infos[cluster_id]["zigpy_device"] = await async_init_zigpy_device(
|
|
|
|
hass,
|
2019-10-21 23:30:56 +00:00
|
|
|
[cluster_id, general.Basic.cluster_id],
|
2019-07-31 19:25:30 +00:00
|
|
|
[],
|
|
|
|
None,
|
|
|
|
zha_gateway,
|
2020-01-03 13:47:06 +00:00
|
|
|
ieee=f"00:15:8d:00:02:32:4f:0{counter}",
|
|
|
|
manufacturer=f"Fake{cluster_id}",
|
|
|
|
model=f"FakeModel{cluster_id}",
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2019-02-03 21:03:35 +00:00
|
|
|
|
|
|
|
counter += 1
|
|
|
|
|
|
|
|
# load up sensor domain
|
2019-07-31 19:25:30 +00:00
|
|
|
await hass.config_entries.async_forward_entry_setup(config_entry, DOMAIN)
|
2019-02-03 21:03:35 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# put the other relevant info in the device info dict
|
|
|
|
for cluster_id in cluster_ids:
|
|
|
|
device_info = device_infos[cluster_id]
|
|
|
|
zigpy_device = device_info["zigpy_device"]
|
2019-07-31 19:25:30 +00:00
|
|
|
device_info["cluster"] = zigpy_device.endpoints.get(1).in_clusters[cluster_id]
|
2019-10-31 16:31:06 +00:00
|
|
|
zha_device = zha_gateway.get_device(zigpy_device.ieee)
|
|
|
|
device_info["zha_device"] = zha_device
|
2020-01-11 01:30:58 +00:00
|
|
|
device_info[ATTR_ENTITY_ID] = await find_entity_id(DOMAIN, zha_device, hass)
|
2019-10-31 16:31:06 +00:00
|
|
|
await hass.async_block_till_done()
|
2019-02-03 21:03:35 +00:00
|
|
|
return device_infos
|
|
|
|
|
|
|
|
|
|
|
|
async def async_test_humidity(hass, device_info):
|
|
|
|
"""Test humidity sensor."""
|
|
|
|
await send_attribute_report(hass, device_info["cluster"], 0, 1000)
|
2019-07-31 19:25:30 +00:00
|
|
|
assert_state(hass, device_info, "10.0", "%")
|
2019-02-03 21:03:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_test_temperature(hass, device_info):
|
|
|
|
"""Test temperature sensor."""
|
|
|
|
await send_attribute_report(hass, device_info["cluster"], 0, 2900)
|
2019-07-31 19:25:30 +00:00
|
|
|
assert_state(hass, device_info, "29.0", "°C")
|
2019-02-03 21:03:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_test_pressure(hass, device_info):
|
|
|
|
"""Test pressure sensor."""
|
|
|
|
await send_attribute_report(hass, device_info["cluster"], 0, 1000)
|
2019-07-31 19:25:30 +00:00
|
|
|
assert_state(hass, device_info, "1000", "hPa")
|
2019-02-03 21:03:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_test_illuminance(hass, device_info):
|
|
|
|
"""Test illuminance sensor."""
|
|
|
|
await send_attribute_report(hass, device_info["cluster"], 0, 10)
|
2019-07-31 19:25:30 +00:00
|
|
|
assert_state(hass, device_info, "1.0", "lx")
|
2019-02-03 21:03:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_test_metering(hass, device_info):
|
|
|
|
"""Test metering sensor."""
|
2019-08-27 03:16:54 +00:00
|
|
|
await send_attribute_report(hass, device_info["cluster"], 1024, 12345)
|
|
|
|
assert_state(hass, device_info, "12345.0", "unknown")
|
2019-02-03 21:03:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def async_test_electrical_measurement(hass, device_info):
|
|
|
|
"""Test electrical measurement sensor."""
|
2020-01-18 01:53:31 +00:00
|
|
|
with mock.patch(
|
|
|
|
(
|
|
|
|
"homeassistant.components.zha.core.channels.homeautomation"
|
|
|
|
".ElectricalMeasurementChannel.divisor"
|
|
|
|
),
|
|
|
|
new_callable=mock.PropertyMock,
|
|
|
|
) as divisor_mock:
|
|
|
|
divisor_mock.return_value = 1
|
|
|
|
await send_attribute_report(hass, device_info["cluster"], 1291, 100)
|
|
|
|
assert_state(hass, device_info, "100", "W")
|
|
|
|
|
|
|
|
await send_attribute_report(hass, device_info["cluster"], 1291, 99)
|
|
|
|
assert_state(hass, device_info, "99", "W")
|
|
|
|
|
|
|
|
divisor_mock.return_value = 10
|
|
|
|
await send_attribute_report(hass, device_info["cluster"], 1291, 1000)
|
|
|
|
assert_state(hass, device_info, "100", "W")
|
|
|
|
|
|
|
|
await send_attribute_report(hass, device_info["cluster"], 1291, 99)
|
|
|
|
assert_state(hass, device_info, "9.9", "W")
|
2019-02-03 21:03:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def send_attribute_report(hass, cluster, attrid, value):
|
|
|
|
"""Cause the sensor to receive an attribute report from the network.
|
|
|
|
|
|
|
|
This is to simulate the normal device communication that happens when a
|
|
|
|
device is paired to the zigbee network.
|
|
|
|
"""
|
|
|
|
attr = make_attribute(attrid, value)
|
2019-10-21 23:30:56 +00:00
|
|
|
hdr = make_zcl_header(zcl_f.Command.Report_Attributes)
|
2019-10-21 17:14:17 +00:00
|
|
|
cluster.handle_message(hdr, [[attr]])
|
2019-02-03 21:03:35 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
|
|
|
|
def assert_state(hass, device_info, state, unit_of_measurement):
|
|
|
|
"""Check that the state is what is expected.
|
|
|
|
|
|
|
|
This is used to ensure that the logic in each sensor class handled the
|
|
|
|
attribute report it received correctly.
|
|
|
|
"""
|
2020-01-11 01:30:58 +00:00
|
|
|
hass_state = hass.states.get(device_info[ATTR_ENTITY_ID])
|
2019-02-03 21:03:35 +00:00
|
|
|
assert hass_state.state == state
|
2020-01-11 01:30:58 +00:00
|
|
|
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
|