-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
185 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
"""Support for the Philips Hue sensors as a platform.""" | ||
import asyncio | ||
from datetime import timedelta | ||
import logging | ||
from time import monotonic | ||
|
||
import aiohue | ||
import async_timeout | ||
|
||
from homeassistant.components import hue | ||
from homeassistant.helpers.event import async_track_point_in_utc_time | ||
from homeassistant.util.dt import utcnow | ||
|
||
from .binary_sensor import HuePresence, PRESENCE_NAME_FORMAT | ||
from .sensor import ( | ||
HueLightLevel, | ||
HueTemperature, | ||
LIGHT_LEVEL_NAME_FORMAT, | ||
TEMPERATURE_NAME_FORMAT, | ||
) | ||
|
||
from .sensor_base import CURRENT_SENSORS | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
def _device_id(aiohue_sensor): | ||
# Work out the shared device ID, as described below | ||
device_id = aiohue_sensor.uniqueid | ||
if device_id and len(device_id) > 23: | ||
device_id = device_id[:23] | ||
return device_id | ||
|
||
|
||
class SensorManager: | ||
"""Class that handles registering and updating Hue sensor entities. | ||
Intended to be a singleton. | ||
""" | ||
|
||
SCAN_INTERVAL = timedelta(seconds=5) | ||
sensor_config_map = {} | ||
|
||
def __init__(self, hass, bridge): | ||
"""Initialize the sensor manager.""" | ||
|
||
self.hass = hass | ||
self.bridge = bridge | ||
self._component_add_entities = {} | ||
self._started = False | ||
|
||
self.sensor_config_map.update( | ||
{ | ||
aiohue.sensors.TYPE_ZLL_LIGHTLEVEL: { | ||
"binary": False, | ||
"name_format": LIGHT_LEVEL_NAME_FORMAT, | ||
"class": HueLightLevel, | ||
}, | ||
aiohue.sensors.TYPE_ZLL_TEMPERATURE: { | ||
"binary": False, | ||
"name_format": TEMPERATURE_NAME_FORMAT, | ||
"class": HueTemperature, | ||
}, | ||
aiohue.sensors.TYPE_ZLL_PRESENCE: { | ||
"binary": True, | ||
"name_format": PRESENCE_NAME_FORMAT, | ||
"class": HuePresence, | ||
}, | ||
} | ||
) | ||
|
||
def register_component(self, binary, async_add_entities): | ||
"""Register async_add_entities methods for components.""" | ||
self._component_add_entities[binary] = async_add_entities | ||
|
||
async def start(self): | ||
"""Start updating sensors from the bridge on a schedule.""" | ||
# but only if it's not already started, and when we've got both | ||
# async_add_entities methods | ||
if self._started or len(self._component_add_entities) < 2: | ||
return | ||
|
||
self._started = True | ||
_LOGGER.info( | ||
"Starting sensor polling loop with %s second interval", | ||
self.SCAN_INTERVAL.total_seconds(), | ||
) | ||
|
||
async def async_update_bridge(now): | ||
"""Will update sensors from the bridge.""" | ||
await self.async_update_items() | ||
|
||
async_track_point_in_utc_time( | ||
self.hass, async_update_bridge, utcnow() + self.SCAN_INTERVAL | ||
) | ||
|
||
await async_update_bridge(None) | ||
|
||
async def async_update_items(self): | ||
"""Update sensors from the bridge.""" | ||
|
||
api = self.bridge.api.sensors | ||
|
||
try: | ||
start = monotonic() | ||
with async_timeout.timeout(4): | ||
await api.update() | ||
except (asyncio.TimeoutError, aiohue.AiohueException) as err: | ||
_LOGGER.debug("Failed to fetch sensor: %s", err) | ||
|
||
if not self.bridge.available: | ||
return | ||
|
||
_LOGGER.error("Unable to reach bridge %s (%s)", self.bridge.host, err) | ||
self.bridge.available = False | ||
|
||
return | ||
|
||
finally: | ||
_LOGGER.debug( | ||
"Finished sensor request in %.3f seconds", monotonic() - start | ||
) | ||
|
||
if not self.bridge.available: | ||
_LOGGER.info("Reconnected to bridge %s", self.bridge.host) | ||
self.bridge.available = True | ||
|
||
new_sensors = [] | ||
new_binary_sensors = [] | ||
primary_sensor_devices = {} | ||
current = self.hass.data[hue.DOMAIN][CURRENT_SENSORS] | ||
|
||
# Physical Hue motion sensors present as three sensors in the API: a | ||
# presence sensor, a temperature sensor, and a light level sensor. Of | ||
# these, only the presence sensor is assigned the user-friendly name | ||
# that the user has given to the device. Each of these sensors is | ||
# linked by a common device_id, which is the first twenty-three | ||
# characters of the unique id (then followed by a hyphen and an ID | ||
# specific to the individual sensor). | ||
# | ||
# To set up neat values, and assign the sensor entities to the same | ||
# device, we first, iterate over all the sensors and find the Hue | ||
# presence sensors, then iterate over all the remaining sensors - | ||
# finding the remaining ones that may or may not be related to the | ||
# presence sensors. | ||
for item_id in api: | ||
if api[item_id].type != aiohue.sensors.TYPE_ZLL_PRESENCE: | ||
continue | ||
|
||
primary_sensor_devices[_device_id(api[item_id])] = api[item_id] | ||
|
||
# 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: | ||
self.hass.async_create_task(existing.async_maybe_update_ha_state()) | ||
continue | ||
|
||
primary_sensor = None | ||
sensor_config = self.sensor_config_map.get(api[item_id].type) | ||
if sensor_config is None: | ||
continue | ||
|
||
base_name = api[item_id].name | ||
primary_sensor = primary_sensor_devices.get(_device_id(api[item_id])) | ||
if primary_sensor is not None: | ||
base_name = primary_sensor.name | ||
name = sensor_config["name_format"].format(base_name) | ||
|
||
current[api[item_id].uniqueid] = sensor_config["class"]( | ||
api[item_id], name, self.bridge, primary_sensor=primary_sensor | ||
) | ||
if sensor_config["binary"]: | ||
new_binary_sensors.append(current[api[item_id].uniqueid]) | ||
else: | ||
new_sensors.append(current[api[item_id].uniqueid]) | ||
|
||
async_add_sensor_entities = self._component_add_entities.get(False) | ||
async_add_binary_entities = self._component_add_entities.get(True) | ||
if new_sensors and async_add_sensor_entities: | ||
async_add_sensor_entities(new_sensors) | ||
if new_binary_sensors and async_add_binary_entities: | ||
async_add_binary_entities(new_binary_sensors) |