mirror of
https://github.com/home-assistant/core.git
synced 2025-04-26 10:17:51 +00:00
Fire events for hue remote buttons pressed (#33277)
* Add remote platform to hue integration supporting ZGPSwitch, ZLLSwitch and ZLLRotary switches. * Ported from custom component Hue-remotes-HASS from @robmarkcole * Add options flow for hue, to toggle handling of sensors and remotes * Sensors are enabled by default, and remotes are disabled, to not generate any breaking change for existent users. Also, when linking a new bridge these defaults are used, so unless going explicitly to the Options menu, the old behavior is preserved. * SensorManager stores the enabled platforms and ignores everything else. * Bridge is created with flags for `add_sensors` and `add_remotes`, and uses them to forward entry setup to only the enabled platforms. * Update listener removes disabled kinds of devices when options are changed, so device list is in sync with options, and disabled kinds disappear from HA, leaving the enable/disable entity option for individual devices. * Fix hue bridge mock with new parameters * Revert changes in hue bridge mock * Remove OptionsFlow and platform flags * Extract `GenericHueDevice` from `GenericHueSensor` to use it as base class for all hue devices, including those without any entity, like remotes without battery. * Add `HueBattery` sensor for battery powered remotes and generate entities for TYPE_ZLL_ROTARY and TYPE_ZLL_SWITCH remotes. * Remove remote platform * Add HueEvent class to fire events for button presses * Use `sensor.lastupdated` string to control state changes * Event data includes: - "id", as pretty name of the remote - "unique_id" of the remote device - "event", with the raw code of the pressed button ('buttonevent' or 'rotaryevent' property) - "last_updated", with the bridge timestamp for the button press * Register ZGP_SWITCH, ZLL_SWITCH, ZLL_ROTARY remotes * fix removal * Exclude W0611 * Extract GenericHueDevice to its own module and solve import tree, also fixing lint in CI * Store registered events to do not repeat device reg * Minor cleaning * Add tests for hue_event and battery entities for hue remotes
This commit is contained in:
parent
dd1608db0d
commit
f5cbc9d208
93
homeassistant/components/hue/hue_event.py
Normal file
93
homeassistant/components/hue/hue_event.py
Normal file
@ -0,0 +1,93 @@
|
||||
"""Representation of a Hue remote firing events for button presses."""
|
||||
import logging
|
||||
|
||||
from aiohue.sensors import TYPE_ZGP_SWITCH, TYPE_ZLL_ROTARY, TYPE_ZLL_SWITCH
|
||||
|
||||
from homeassistant.const import CONF_EVENT, CONF_ID
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .sensor_device import GenericHueDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_HUE_EVENT = "hue_event"
|
||||
CONF_LAST_UPDATED = "last_updated"
|
||||
CONF_UNIQUE_ID = "unique_id"
|
||||
|
||||
EVENT_NAME_FORMAT = "{}"
|
||||
|
||||
|
||||
class HueEvent(GenericHueDevice):
|
||||
"""When you want signals instead of entities.
|
||||
|
||||
Stateless sensors such as remotes are expected to generate an event
|
||||
instead of a sensor entity in hass.
|
||||
"""
|
||||
|
||||
def __init__(self, sensor, name, bridge, primary_sensor=None):
|
||||
"""Register callback that will be used for signals."""
|
||||
super().__init__(sensor, name, bridge, primary_sensor)
|
||||
|
||||
self.event_id = slugify(self.sensor.name)
|
||||
# Use the 'lastupdated' string to detect new remote presses
|
||||
self._last_updated = self.sensor.lastupdated
|
||||
|
||||
# Register callback in coordinator and add job to remove it on bridge reset.
|
||||
self.bridge.sensor_manager.coordinator.async_add_listener(
|
||||
self.async_update_callback
|
||||
)
|
||||
self.bridge.reset_jobs.append(self.async_will_remove_from_hass)
|
||||
_LOGGER.debug("Hue event created: %s", self.event_id)
|
||||
|
||||
@callback
|
||||
def async_will_remove_from_hass(self):
|
||||
"""Remove listener on bridge reset."""
|
||||
self.bridge.sensor_manager.coordinator.async_remove_listener(
|
||||
self.async_update_callback
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_update_callback(self):
|
||||
"""Fire the event if reason is that state is updated."""
|
||||
if self.sensor.lastupdated == self._last_updated:
|
||||
return
|
||||
|
||||
# Extract the press code as state
|
||||
if hasattr(self.sensor, "rotaryevent"):
|
||||
state = self.sensor.rotaryevent
|
||||
else:
|
||||
state = self.sensor.buttonevent
|
||||
|
||||
self._last_updated = self.sensor.lastupdated
|
||||
|
||||
# Fire event
|
||||
data = {
|
||||
CONF_ID: self.event_id,
|
||||
CONF_UNIQUE_ID: self.unique_id,
|
||||
CONF_EVENT: state,
|
||||
CONF_LAST_UPDATED: self.sensor.lastupdated,
|
||||
}
|
||||
self.bridge.hass.bus.async_fire(CONF_HUE_EVENT, data)
|
||||
|
||||
async def async_update_device_registry(self):
|
||||
"""Update device registry."""
|
||||
device_registry = (
|
||||
await self.bridge.hass.helpers.device_registry.async_get_registry()
|
||||
)
|
||||
|
||||
entry = device_registry.async_get_or_create(
|
||||
config_entry_id=self.bridge.config_entry.entry_id, **self.device_info
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"Event registry with entry_id: %s and device_id: %s",
|
||||
entry.id,
|
||||
self.device_id,
|
||||
)
|
||||
|
||||
|
||||
EVENT_CONFIG_MAP = {
|
||||
TYPE_ZGP_SWITCH: {"name_format": EVENT_NAME_FORMAT, "class": HueEvent},
|
||||
TYPE_ZLL_SWITCH: {"name_format": EVENT_NAME_FORMAT, "class": HueEvent},
|
||||
TYPE_ZLL_ROTARY: {"name_format": EVENT_NAME_FORMAT, "class": HueEvent},
|
||||
}
|
@ -1,17 +1,25 @@
|
||||
"""Hue sensor entities."""
|
||||
from aiohue.sensors import TYPE_ZLL_LIGHTLEVEL, TYPE_ZLL_TEMPERATURE
|
||||
from aiohue.sensors import (
|
||||
TYPE_ZLL_LIGHTLEVEL,
|
||||
TYPE_ZLL_ROTARY,
|
||||
TYPE_ZLL_SWITCH,
|
||||
TYPE_ZLL_TEMPERATURE,
|
||||
)
|
||||
|
||||
from homeassistant.const import (
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
TEMP_CELSIUS,
|
||||
UNIT_PERCENTAGE,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN as HUE_DOMAIN
|
||||
from .sensor_base import SENSOR_CONFIG_MAP, GenericZLLSensor
|
||||
from .sensor_base import SENSOR_CONFIG_MAP, GenericHueSensor, GenericZLLSensor
|
||||
|
||||
LIGHT_LEVEL_NAME_FORMAT = "{} light level"
|
||||
REMOTE_NAME_FORMAT = "{} battery level"
|
||||
TEMPERATURE_NAME_FORMAT = "{} temperature"
|
||||
|
||||
|
||||
@ -79,6 +87,30 @@ class HueTemperature(GenericHueGaugeSensorEntity):
|
||||
return self.sensor.temperature / 100
|
||||
|
||||
|
||||
class HueBattery(GenericHueSensor):
|
||||
"""Battery class for when a batt-powered device is only represented as an event."""
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this device."""
|
||||
return f"{self.sensor.uniqueid}-battery"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the battery."""
|
||||
return self.sensor.battery
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the sensor."""
|
||||
return DEVICE_CLASS_BATTERY
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this entity."""
|
||||
return UNIT_PERCENTAGE
|
||||
|
||||
|
||||
SENSOR_CONFIG_MAP.update(
|
||||
{
|
||||
TYPE_ZLL_LIGHTLEVEL: {
|
||||
@ -91,5 +123,15 @@ SENSOR_CONFIG_MAP.update(
|
||||
"name_format": TEMPERATURE_NAME_FORMAT,
|
||||
"class": HueTemperature,
|
||||
},
|
||||
TYPE_ZLL_SWITCH: {
|
||||
"platform": "sensor",
|
||||
"name_format": REMOTE_NAME_FORMAT,
|
||||
"class": HueBattery,
|
||||
},
|
||||
TYPE_ZLL_ROTARY: {
|
||||
"platform": "sensor",
|
||||
"name_format": REMOTE_NAME_FORMAT,
|
||||
"class": HueBattery,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@ -10,8 +10,10 @@ from homeassistant.core import callback
|
||||
from homeassistant.helpers import debounce, entity
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN as HUE_DOMAIN, REQUEST_REFRESH_DELAY
|
||||
from .const import REQUEST_REFRESH_DELAY
|
||||
from .helpers import remove_devices
|
||||
from .hue_event import EVENT_CONFIG_MAP
|
||||
from .sensor_device import GenericHueDevice
|
||||
|
||||
SENSOR_CONFIG_MAP = {}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -38,6 +40,9 @@ class SensorManager:
|
||||
self.bridge = bridge
|
||||
self._component_add_entities = {}
|
||||
self.current = {}
|
||||
self.current_events = {}
|
||||
|
||||
self._enabled_platforms = ("binary_sensor", "sensor")
|
||||
self.coordinator = DataUpdateCoordinator(
|
||||
bridge.hass,
|
||||
_LOGGER,
|
||||
@ -66,7 +71,8 @@ class SensorManager:
|
||||
"""Register async_add_entities methods for components."""
|
||||
self._component_add_entities[platform] = async_add_entities
|
||||
|
||||
if len(self._component_add_entities) < 2:
|
||||
if len(self._component_add_entities) < len(self._enabled_platforms):
|
||||
_LOGGER.debug("Aborting start with %s, waiting for the rest", platform)
|
||||
return
|
||||
|
||||
# We have all components available, start the updating.
|
||||
@ -81,7 +87,7 @@ class SensorManager:
|
||||
"""Update sensors from the bridge."""
|
||||
api = self.bridge.api.sensors
|
||||
|
||||
if len(self._component_add_entities) < 2:
|
||||
if len(self._component_add_entities) < len(self._enabled_platforms):
|
||||
return
|
||||
|
||||
to_add = {}
|
||||
@ -110,12 +116,24 @@ class SensorManager:
|
||||
# Iterate again now we have all the presence sensors, and add the
|
||||
# related sensors with nice names where appropriate.
|
||||
for item_id in api:
|
||||
existing = current.get(api[item_id].uniqueid)
|
||||
if existing is not None:
|
||||
uniqueid = api[item_id].uniqueid
|
||||
if current.get(uniqueid, self.current_events.get(uniqueid)) is not None:
|
||||
continue
|
||||
|
||||
primary_sensor = None
|
||||
sensor_config = SENSOR_CONFIG_MAP.get(api[item_id].type)
|
||||
sensor_type = api[item_id].type
|
||||
|
||||
# Check for event generator devices
|
||||
event_config = EVENT_CONFIG_MAP.get(sensor_type)
|
||||
if event_config is not None:
|
||||
base_name = api[item_id].name
|
||||
name = event_config["name_format"].format(base_name)
|
||||
new_event = event_config["class"](api[item_id], name, self.bridge)
|
||||
self.bridge.hass.async_create_task(
|
||||
new_event.async_update_device_registry()
|
||||
)
|
||||
self.current_events[uniqueid] = new_event
|
||||
|
||||
sensor_config = SENSOR_CONFIG_MAP.get(sensor_type)
|
||||
if sensor_config is None:
|
||||
continue
|
||||
|
||||
@ -125,13 +143,11 @@ class SensorManager:
|
||||
base_name = primary_sensor.name
|
||||
name = sensor_config["name_format"].format(base_name)
|
||||
|
||||
current[api[item_id].uniqueid] = sensor_config["class"](
|
||||
current[uniqueid] = sensor_config["class"](
|
||||
api[item_id], name, self.bridge, primary_sensor=primary_sensor
|
||||
)
|
||||
|
||||
to_add.setdefault(sensor_config["platform"], []).append(
|
||||
current[api[item_id].uniqueid]
|
||||
)
|
||||
to_add.setdefault(sensor_config["platform"], []).append(current[uniqueid])
|
||||
|
||||
self.bridge.hass.async_create_task(
|
||||
remove_devices(
|
||||
@ -143,53 +159,23 @@ class SensorManager:
|
||||
self._component_add_entities[platform](to_add[platform])
|
||||
|
||||
|
||||
class GenericHueSensor(entity.Entity):
|
||||
class GenericHueSensor(GenericHueDevice, entity.Entity):
|
||||
"""Representation of a Hue sensor."""
|
||||
|
||||
should_poll = False
|
||||
|
||||
def __init__(self, sensor, name, bridge, primary_sensor=None):
|
||||
"""Initialize the sensor."""
|
||||
self.sensor = sensor
|
||||
self._name = name
|
||||
self._primary_sensor = primary_sensor
|
||||
self.bridge = bridge
|
||||
|
||||
async def _async_update_ha_state(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def primary_sensor(self):
|
||||
"""Return the primary sensor entity of the physical device."""
|
||||
return self._primary_sensor or self.sensor
|
||||
|
||||
@property
|
||||
def device_id(self):
|
||||
"""Return the ID of the physical device this sensor is part of."""
|
||||
return self.unique_id[:23]
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the ID of this Hue sensor."""
|
||||
return self.sensor.uniqueid
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return a friendly name for the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return if sensor is available."""
|
||||
return self.bridge.sensor_manager.coordinator.last_update_success and (
|
||||
self.bridge.allow_unreachable or self.sensor.config["reachable"]
|
||||
self.bridge.allow_unreachable
|
||||
# remotes like Hue Tap (ZGPSwitchSensor) have no _reachability_
|
||||
or self.sensor.config.get("reachable", True)
|
||||
)
|
||||
|
||||
@property
|
||||
def swupdatestate(self):
|
||||
"""Return detail of available software updates for this device."""
|
||||
return self.primary_sensor.raw.get("swupdate", {}).get("state")
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""When entity is added to hass."""
|
||||
self.bridge.sensor_manager.coordinator.async_add_listener(
|
||||
@ -209,21 +195,6 @@ class GenericHueSensor(entity.Entity):
|
||||
"""
|
||||
await self.bridge.sensor_manager.coordinator.async_request_refresh()
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device info.
|
||||
|
||||
Links individual entities together in the hass device registry.
|
||||
"""
|
||||
return {
|
||||
"identifiers": {(HUE_DOMAIN, self.device_id)},
|
||||
"name": self.primary_sensor.name,
|
||||
"manufacturer": self.primary_sensor.manufacturername,
|
||||
"model": (self.primary_sensor.productname or self.primary_sensor.modelid),
|
||||
"sw_version": self.primary_sensor.swversion,
|
||||
"via_device": (HUE_DOMAIN, self.bridge.api.config.bridgeid),
|
||||
}
|
||||
|
||||
|
||||
class GenericZLLSensor(GenericHueSensor):
|
||||
"""Representation of a Hue-brand, physical sensor."""
|
||||
|
53
homeassistant/components/hue/sensor_device.py
Normal file
53
homeassistant/components/hue/sensor_device.py
Normal file
@ -0,0 +1,53 @@
|
||||
"""Support for the Philips Hue sensor devices."""
|
||||
from .const import DOMAIN as HUE_DOMAIN
|
||||
|
||||
|
||||
class GenericHueDevice:
|
||||
"""Representation of a Hue device."""
|
||||
|
||||
def __init__(self, sensor, name, bridge, primary_sensor=None):
|
||||
"""Initialize the sensor."""
|
||||
self.sensor = sensor
|
||||
self._name = name
|
||||
self._primary_sensor = primary_sensor
|
||||
self.bridge = bridge
|
||||
|
||||
@property
|
||||
def primary_sensor(self):
|
||||
"""Return the primary sensor entity of the physical device."""
|
||||
return self._primary_sensor or self.sensor
|
||||
|
||||
@property
|
||||
def device_id(self):
|
||||
"""Return the ID of the physical device this sensor is part of."""
|
||||
return self.unique_id[:23]
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the ID of this Hue sensor."""
|
||||
return self.sensor.uniqueid
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return a friendly name for the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def swupdatestate(self):
|
||||
"""Return detail of available software updates for this device."""
|
||||
return self.primary_sensor.raw.get("swupdate", {}).get("state")
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device info.
|
||||
|
||||
Links individual entities together in the hass device registry.
|
||||
"""
|
||||
return {
|
||||
"identifiers": {(HUE_DOMAIN, self.device_id)},
|
||||
"name": self.primary_sensor.name,
|
||||
"manufacturer": self.primary_sensor.manufacturername,
|
||||
"model": (self.primary_sensor.productname or self.primary_sensor.modelid),
|
||||
"sw_version": self.primary_sensor.swversion,
|
||||
"via_device": (HUE_DOMAIN, self.bridge.api.config.bridgeid),
|
||||
}
|
@ -11,6 +11,7 @@ import pytest
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import hue
|
||||
from homeassistant.components.hue import sensor_base as hue_sensor_base
|
||||
from homeassistant.components.hue.hue_event import CONF_HUE_EVENT
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -241,6 +242,33 @@ UNSUPPORTED_SENSOR = {
|
||||
"uniqueid": "arbitrary",
|
||||
"recycle": True,
|
||||
}
|
||||
HUE_TAP_REMOTE_1 = {
|
||||
"state": {"buttonevent": 17, "lastupdated": "2019-06-22T14:43:50"},
|
||||
"swupdate": {"state": "notupdatable", "lastinstall": None},
|
||||
"config": {"on": True},
|
||||
"name": "Hue Tap",
|
||||
"type": "ZGPSwitch",
|
||||
"modelid": "ZGPSWITCH",
|
||||
"manufacturername": "Philips",
|
||||
"productname": "Hue tap switch",
|
||||
"diversityid": "d8cde5d5-0eef-4b95-b0f0-71ddd2952af4",
|
||||
"uniqueid": "00:00:00:00:00:44:23:08-f2",
|
||||
"capabilities": {"certified": True, "primary": True, "inputs": []},
|
||||
}
|
||||
HUE_DIMMER_REMOTE_1 = {
|
||||
"state": {"buttonevent": 4002, "lastupdated": "2019-12-28T21:58:02"},
|
||||
"swupdate": {"state": "noupdates", "lastinstall": "2019-10-13T13:16:15"},
|
||||
"config": {"on": True, "battery": 100, "reachable": True, "pending": []},
|
||||
"name": "Hue dimmer switch 1",
|
||||
"type": "ZLLSwitch",
|
||||
"modelid": "RWL021",
|
||||
"manufacturername": "Philips",
|
||||
"productname": "Hue dimmer switch",
|
||||
"diversityid": "73bbabea-3420-499a-9856-46bf437e119b",
|
||||
"swversion": "6.1.1.28573",
|
||||
"uniqueid": "00:17:88:01:10:3e:3a:dc-02-fc00",
|
||||
"capabilities": {"certified": True, "primary": True, "inputs": []},
|
||||
}
|
||||
SENSOR_RESPONSE = {
|
||||
"1": PRESENCE_SENSOR_1_PRESENT,
|
||||
"2": LIGHT_LEVEL_SENSOR_1,
|
||||
@ -248,6 +276,8 @@ SENSOR_RESPONSE = {
|
||||
"4": PRESENCE_SENSOR_2_NOT_PRESENT,
|
||||
"5": LIGHT_LEVEL_SENSOR_2,
|
||||
"6": TEMPERATURE_SENSOR_2,
|
||||
"7": HUE_TAP_REMOTE_1,
|
||||
"8": HUE_DIMMER_REMOTE_1,
|
||||
}
|
||||
|
||||
|
||||
@ -341,8 +371,8 @@ async def test_sensors_with_multiple_bridges(hass, mock_bridge):
|
||||
|
||||
assert len(mock_bridge.mock_requests) == 1
|
||||
assert len(mock_bridge_2.mock_requests) == 1
|
||||
# 3 "physical" sensors with 3 virtual sensors each
|
||||
assert len(hass.states.async_all()) == 9
|
||||
# 3 "physical" sensors with 3 virtual sensors each + 1 battery sensor
|
||||
assert len(hass.states.async_all()) == 10
|
||||
|
||||
|
||||
async def test_sensors(hass, mock_bridge):
|
||||
@ -351,7 +381,7 @@ async def test_sensors(hass, mock_bridge):
|
||||
await setup_bridge(hass, mock_bridge)
|
||||
assert len(mock_bridge.mock_requests) == 1
|
||||
# 2 "physical" sensors with 3 virtual sensors each
|
||||
assert len(hass.states.async_all()) == 6
|
||||
assert len(hass.states.async_all()) == 7
|
||||
|
||||
presence_sensor_1 = hass.states.get("binary_sensor.living_room_sensor_motion")
|
||||
light_level_sensor_1 = hass.states.get("sensor.living_room_sensor_light_level")
|
||||
@ -377,6 +407,11 @@ async def test_sensors(hass, mock_bridge):
|
||||
assert temperature_sensor_2.state == "18.75"
|
||||
assert temperature_sensor_2.name == "Kitchen sensor temperature"
|
||||
|
||||
battery_remote_1 = hass.states.get("sensor.hue_dimmer_switch_1_battery_level")
|
||||
assert battery_remote_1 is not None
|
||||
assert battery_remote_1.state == "100"
|
||||
assert battery_remote_1.name == "Hue dimmer switch 1 battery level"
|
||||
|
||||
|
||||
async def test_unsupported_sensors(hass, mock_bridge):
|
||||
"""Test that unsupported sensors don't get added and don't fail."""
|
||||
@ -385,8 +420,8 @@ async def test_unsupported_sensors(hass, mock_bridge):
|
||||
mock_bridge.mock_sensor_responses.append(response_with_unsupported)
|
||||
await setup_bridge(hass, mock_bridge)
|
||||
assert len(mock_bridge.mock_requests) == 1
|
||||
# 2 "physical" sensors with 3 virtual sensors each
|
||||
assert len(hass.states.async_all()) == 6
|
||||
# 2 "physical" sensors with 3 virtual sensors each + 1 battery sensor
|
||||
assert len(hass.states.async_all()) == 7
|
||||
|
||||
|
||||
async def test_new_sensor_discovered(hass, mock_bridge):
|
||||
@ -395,14 +430,14 @@ async def test_new_sensor_discovered(hass, mock_bridge):
|
||||
|
||||
await setup_bridge(hass, mock_bridge)
|
||||
assert len(mock_bridge.mock_requests) == 1
|
||||
assert len(hass.states.async_all()) == 6
|
||||
assert len(hass.states.async_all()) == 7
|
||||
|
||||
new_sensor_response = dict(SENSOR_RESPONSE)
|
||||
new_sensor_response.update(
|
||||
{
|
||||
"7": PRESENCE_SENSOR_3_PRESENT,
|
||||
"8": LIGHT_LEVEL_SENSOR_3,
|
||||
"9": TEMPERATURE_SENSOR_3,
|
||||
"9": PRESENCE_SENSOR_3_PRESENT,
|
||||
"10": LIGHT_LEVEL_SENSOR_3,
|
||||
"11": TEMPERATURE_SENSOR_3,
|
||||
}
|
||||
)
|
||||
|
||||
@ -413,7 +448,7 @@ async def test_new_sensor_discovered(hass, mock_bridge):
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_bridge.mock_requests) == 2
|
||||
assert len(hass.states.async_all()) == 9
|
||||
assert len(hass.states.async_all()) == 10
|
||||
|
||||
presence = hass.states.get("binary_sensor.bedroom_sensor_motion")
|
||||
assert presence is not None
|
||||
@ -429,7 +464,7 @@ async def test_sensor_removed(hass, mock_bridge):
|
||||
|
||||
await setup_bridge(hass, mock_bridge)
|
||||
assert len(mock_bridge.mock_requests) == 1
|
||||
assert len(hass.states.async_all()) == 6
|
||||
assert len(hass.states.async_all()) == 7
|
||||
|
||||
mock_bridge.mock_sensor_responses.clear()
|
||||
keys = ("1", "2", "3")
|
||||
@ -466,3 +501,121 @@ async def test_update_unauthorized(hass, mock_bridge):
|
||||
assert len(mock_bridge.mock_requests) == 0
|
||||
assert len(hass.states.async_all()) == 0
|
||||
assert len(mock_bridge.handle_unauthorized_error.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_hue_events(hass, mock_bridge):
|
||||
"""Test that hue remotes fire events when pressed."""
|
||||
mock_bridge.mock_sensor_responses.append(SENSOR_RESPONSE)
|
||||
|
||||
mock_listener = Mock()
|
||||
unsub = hass.bus.async_listen(CONF_HUE_EVENT, mock_listener)
|
||||
|
||||
await setup_bridge(hass, mock_bridge)
|
||||
assert len(mock_bridge.mock_requests) == 1
|
||||
assert len(hass.states.async_all()) == 7
|
||||
assert len(mock_listener.mock_calls) == 0
|
||||
|
||||
new_sensor_response = dict(SENSOR_RESPONSE)
|
||||
new_sensor_response["7"]["state"] = {
|
||||
"buttonevent": 18,
|
||||
"lastupdated": "2019-12-28T22:58:02",
|
||||
}
|
||||
mock_bridge.mock_sensor_responses.append(new_sensor_response)
|
||||
|
||||
# Force updates to run again
|
||||
await mock_bridge.sensor_manager.coordinator.async_refresh()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_bridge.mock_requests) == 2
|
||||
assert len(hass.states.async_all()) == 7
|
||||
assert len(mock_listener.mock_calls) == 1
|
||||
assert mock_listener.mock_calls[0][1][0].data == {
|
||||
"id": "hue_tap",
|
||||
"unique_id": "00:00:00:00:00:44:23:08-f2",
|
||||
"event": 18,
|
||||
"last_updated": "2019-12-28T22:58:02",
|
||||
}
|
||||
|
||||
new_sensor_response = dict(new_sensor_response)
|
||||
new_sensor_response["8"]["state"] = {
|
||||
"buttonevent": 3002,
|
||||
"lastupdated": "2019-12-28T22:58:01",
|
||||
}
|
||||
mock_bridge.mock_sensor_responses.append(new_sensor_response)
|
||||
|
||||
# Force updates to run again
|
||||
await mock_bridge.sensor_manager.coordinator.async_refresh()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_bridge.mock_requests) == 3
|
||||
assert len(hass.states.async_all()) == 7
|
||||
assert len(mock_listener.mock_calls) == 2
|
||||
assert mock_listener.mock_calls[1][1][0].data == {
|
||||
"id": "hue_dimmer_switch_1",
|
||||
"unique_id": "00:17:88:01:10:3e:3a:dc-02-fc00",
|
||||
"event": 3002,
|
||||
"last_updated": "2019-12-28T22:58:01",
|
||||
}
|
||||
|
||||
# Add a new remote. In discovery the new event is registered **but not fired**
|
||||
new_sensor_response = dict(new_sensor_response)
|
||||
new_sensor_response["21"] = {
|
||||
"state": {
|
||||
"rotaryevent": 2,
|
||||
"expectedrotation": 208,
|
||||
"expectedeventduration": 400,
|
||||
"lastupdated": "2020-01-31T15:56:19",
|
||||
},
|
||||
"swupdate": {"state": "noupdates", "lastinstall": "2019-11-26T03:35:21"},
|
||||
"config": {"on": True, "battery": 100, "reachable": True, "pending": []},
|
||||
"name": "Lutron Aurora 1",
|
||||
"type": "ZLLRelativeRotary",
|
||||
"modelid": "Z3-1BRL",
|
||||
"manufacturername": "Lutron",
|
||||
"productname": "Lutron Aurora",
|
||||
"diversityid": "2c3a75ff-55c4-4e4d-8c44-82d330b8eb9b",
|
||||
"swversion": "3.4",
|
||||
"uniqueid": "ff:ff:00:0f:e7:fd:bc:b7-01-fc00-0014",
|
||||
"capabilities": {
|
||||
"certified": True,
|
||||
"primary": True,
|
||||
"inputs": [
|
||||
{
|
||||
"repeatintervals": [400],
|
||||
"events": [
|
||||
{"rotaryevent": 1, "eventtype": "start"},
|
||||
{"rotaryevent": 2, "eventtype": "repeat"},
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
mock_bridge.mock_sensor_responses.append(new_sensor_response)
|
||||
|
||||
# Force updates to run again
|
||||
await mock_bridge.sensor_manager.coordinator.async_refresh()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_bridge.mock_requests) == 4
|
||||
assert len(hass.states.async_all()) == 8
|
||||
assert len(mock_listener.mock_calls) == 2
|
||||
|
||||
# A new press fires the event
|
||||
new_sensor_response["21"]["state"]["lastupdated"] = "2020-01-31T15:57:19"
|
||||
mock_bridge.mock_sensor_responses.append(new_sensor_response)
|
||||
|
||||
# Force updates to run again
|
||||
await mock_bridge.sensor_manager.coordinator.async_refresh()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_bridge.mock_requests) == 5
|
||||
assert len(hass.states.async_all()) == 8
|
||||
assert len(mock_listener.mock_calls) == 3
|
||||
assert mock_listener.mock_calls[2][1][0].data == {
|
||||
"id": "lutron_aurora_1",
|
||||
"unique_id": "ff:ff:00:0f:e7:fd:bc:b7-01-fc00-0014",
|
||||
"event": 2,
|
||||
"last_updated": "2020-01-31T15:57:19",
|
||||
}
|
||||
|
||||
unsub()
|
||||
|
Loading…
x
Reference in New Issue
Block a user