Skip to content

Commit

Permalink
Fix circular dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
Quentame committed Oct 11, 2019
1 parent 14dfef1 commit 9f8f846
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 175 deletions.
176 changes: 1 addition & 175 deletions homeassistant/components/hue/sensor_base.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,17 @@
"""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.exceptions import NoEntitySpecifiedError
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_manager import SensorManager

CURRENT_SENSORS = "current_sensors"
SENSOR_MANAGER_FORMAT = "{}_sensor_manager"

_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


async def async_setup_entry(hass, config_entry, async_add_entities, binary=False):
"""Set up the Hue sensors from a config entry."""
bridge = hass.data[hue.DOMAIN][config_entry.data["host"]]
Expand All @@ -49,158 +27,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities, binary=False
await manager.start()


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)


class GenericHueSensor:
"""Representation of a Hue sensor."""

Expand Down
184 changes: 184 additions & 0 deletions homeassistant/components/hue/sensor_manager.py
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)

0 comments on commit 9f8f846

Please sign in to comment.