Add support for OpenUV binary sensors and sensors (#15769)
* Initial commit * Adjusted ownership and coverage * Member-requested changes * Updated Ozone to a value, not an index * Verbiage update
This commit is contained in:
parent
2f8d66ef2b
commit
bdea9e1333
6 changed files with 414 additions and 0 deletions
|
@ -215,6 +215,9 @@ omit =
|
|||
homeassistant/components/opencv.py
|
||||
homeassistant/components/*/opencv.py
|
||||
|
||||
homeassistant/components/openuv.py
|
||||
homeassistant/components/*/openuv.py
|
||||
|
||||
homeassistant/components/pilight.py
|
||||
homeassistant/components/*/pilight.py
|
||||
|
||||
|
|
|
@ -98,6 +98,8 @@ homeassistant/components/konnected.py @heythisisnate
|
|||
homeassistant/components/*/konnected.py @heythisisnate
|
||||
homeassistant/components/matrix.py @tinloaf
|
||||
homeassistant/components/*/matrix.py @tinloaf
|
||||
homeassistant/components/openuv.py @bachya
|
||||
homeassistant/components/*/openuv.py @bachya
|
||||
homeassistant/components/qwikswitch.py @kellerza
|
||||
homeassistant/components/*/qwikswitch.py @kellerza
|
||||
homeassistant/components/rainmachine/* @bachya
|
||||
|
|
103
homeassistant/components/binary_sensor/openuv.py
Normal file
103
homeassistant/components/binary_sensor/openuv.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
"""
|
||||
This platform provides binary sensors for OpenUV data.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.openuv/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.components.openuv import (
|
||||
BINARY_SENSORS, DATA_PROTECTION_WINDOW, DOMAIN, TOPIC_UPDATE,
|
||||
TYPE_PROTECTION_WINDOW, OpenUvEntity)
|
||||
from homeassistant.util.dt import as_local, parse_datetime, utcnow
|
||||
|
||||
DEPENDENCIES = ['openuv']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_PROTECTION_WINDOW_STARTING_TIME = 'start_time'
|
||||
ATTR_PROTECTION_WINDOW_STARTING_UV = 'start_uv'
|
||||
ATTR_PROTECTION_WINDOW_ENDING_TIME = 'end_time'
|
||||
ATTR_PROTECTION_WINDOW_ENDING_UV = 'end_uv'
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the OpenUV binary sensor platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
openuv = hass.data[DOMAIN]
|
||||
|
||||
binary_sensors = []
|
||||
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
|
||||
name, icon = BINARY_SENSORS[sensor_type]
|
||||
binary_sensors.append(
|
||||
OpenUvBinarySensor(openuv, sensor_type, name, icon))
|
||||
|
||||
async_add_devices(binary_sensors, True)
|
||||
|
||||
|
||||
class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
|
||||
"""Define a binary sensor for OpenUV."""
|
||||
|
||||
def __init__(self, openuv, sensor_type, name, icon):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(openuv)
|
||||
|
||||
self._icon = icon
|
||||
self._latitude = openuv.client.latitude
|
||||
self._longitude = openuv.client.longitude
|
||||
self._name = name
|
||||
self._sensor_type = sensor_type
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the status of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Disable polling."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique, HASS-friendly identifier for this entity."""
|
||||
return '{0}_{1}_{2}'.format(
|
||||
self._latitude, self._longitude, self._sensor_type)
|
||||
|
||||
@callback
|
||||
def _update_data(self):
|
||||
"""Update the state."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, TOPIC_UPDATE, self._update_data)
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the state."""
|
||||
data = self.openuv.data[DATA_PROTECTION_WINDOW]['result']
|
||||
if self._sensor_type == TYPE_PROTECTION_WINDOW:
|
||||
self._state = parse_datetime(
|
||||
data['from_time']) <= utcnow() <= parse_datetime(
|
||||
data['to_time'])
|
||||
self._attrs.update({
|
||||
ATTR_PROTECTION_WINDOW_ENDING_TIME:
|
||||
as_local(parse_datetime(data['to_time'])),
|
||||
ATTR_PROTECTION_WINDOW_ENDING_UV: data['to_uv'],
|
||||
ATTR_PROTECTION_WINDOW_STARTING_UV: data['from_uv'],
|
||||
ATTR_PROTECTION_WINDOW_STARTING_TIME:
|
||||
as_local(parse_datetime(data['from_time'])),
|
||||
})
|
182
homeassistant/components/openuv.py
Normal file
182
homeassistant/components/openuv.py
Normal file
|
@ -0,0 +1,182 @@
|
|||
"""
|
||||
Support for data from openuv.io.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/openuv/
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, CONF_API_KEY, CONF_BINARY_SENSORS, CONF_ELEVATION,
|
||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_MONITORED_CONDITIONS,
|
||||
CONF_SCAN_INTERVAL, CONF_SENSORS)
|
||||
from homeassistant.helpers import (
|
||||
aiohttp_client, config_validation as cv, discovery)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
|
||||
REQUIREMENTS = ['pyopenuv==1.0.1']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'openuv'
|
||||
|
||||
DATA_PROTECTION_WINDOW = 'protection_window'
|
||||
DATA_UV = 'uv'
|
||||
|
||||
DEFAULT_ATTRIBUTION = 'Data provided by OpenUV'
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(minutes=30)
|
||||
|
||||
NOTIFICATION_ID = 'openuv_notification'
|
||||
NOTIFICATION_TITLE = 'OpenUV Component Setup'
|
||||
|
||||
TOPIC_UPDATE = '{0}_data_update'.format(DOMAIN)
|
||||
|
||||
TYPE_CURRENT_OZONE_LEVEL = 'current_ozone_level'
|
||||
TYPE_CURRENT_UV_INDEX = 'current_uv_index'
|
||||
TYPE_MAX_UV_INDEX = 'max_uv_index'
|
||||
TYPE_PROTECTION_WINDOW = 'uv_protection_window'
|
||||
TYPE_SAFE_EXPOSURE_TIME_1 = 'safe_exposure_time_type_1'
|
||||
TYPE_SAFE_EXPOSURE_TIME_2 = 'safe_exposure_time_type_2'
|
||||
TYPE_SAFE_EXPOSURE_TIME_3 = 'safe_exposure_time_type_3'
|
||||
TYPE_SAFE_EXPOSURE_TIME_4 = 'safe_exposure_time_type_4'
|
||||
TYPE_SAFE_EXPOSURE_TIME_5 = 'safe_exposure_time_type_5'
|
||||
TYPE_SAFE_EXPOSURE_TIME_6 = 'safe_exposure_time_type_6'
|
||||
|
||||
BINARY_SENSORS = {
|
||||
TYPE_PROTECTION_WINDOW: ('Protection Window', 'mdi:sunglasses')
|
||||
}
|
||||
|
||||
BINARY_SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)):
|
||||
vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)])
|
||||
})
|
||||
|
||||
SENSORS = {
|
||||
TYPE_CURRENT_OZONE_LEVEL: (
|
||||
'Current Ozone Level', 'mdi:vector-triangle', 'du'),
|
||||
TYPE_CURRENT_UV_INDEX: ('Current UV Index', 'mdi:weather-sunny', 'index'),
|
||||
TYPE_MAX_UV_INDEX: ('Max UV Index', 'mdi:weather-sunny', 'index'),
|
||||
TYPE_SAFE_EXPOSURE_TIME_1: (
|
||||
'Skin Type 1 Safe Exposure Time', 'mdi:timer', 'minutes'),
|
||||
TYPE_SAFE_EXPOSURE_TIME_2: (
|
||||
'Skin Type 2 Safe Exposure Time', 'mdi:timer', 'minutes'),
|
||||
TYPE_SAFE_EXPOSURE_TIME_3: (
|
||||
'Skin Type 3 Safe Exposure Time', 'mdi:timer', 'minutes'),
|
||||
TYPE_SAFE_EXPOSURE_TIME_4: (
|
||||
'Skin Type 4 Safe Exposure Time', 'mdi:timer', 'minutes'),
|
||||
TYPE_SAFE_EXPOSURE_TIME_5: (
|
||||
'Skin Type 5 Safe Exposure Time', 'mdi:timer', 'minutes'),
|
||||
TYPE_SAFE_EXPOSURE_TIME_6: (
|
||||
'Skin Type 6 Safe Exposure Time', 'mdi:timer', 'minutes'),
|
||||
}
|
||||
|
||||
SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSORS)])
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_API_KEY): cv.string,
|
||||
vol.Optional(CONF_ELEVATION): float,
|
||||
vol.Optional(CONF_LATITUDE): cv.latitude,
|
||||
vol.Optional(CONF_LONGITUDE): cv.longitude,
|
||||
vol.Optional(CONF_BINARY_SENSORS, default={}): BINARY_SENSOR_SCHEMA,
|
||||
vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
|
||||
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL):
|
||||
cv.time_period,
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the OpenUV component."""
|
||||
from pyopenuv import Client
|
||||
from pyopenuv.errors import OpenUvError
|
||||
|
||||
conf = config[DOMAIN]
|
||||
api_key = conf[CONF_API_KEY]
|
||||
elevation = conf.get(CONF_ELEVATION, hass.config.elevation)
|
||||
latitude = conf.get(CONF_LATITUDE, hass.config.latitude)
|
||||
longitude = conf.get(CONF_LONGITUDE, hass.config.longitude)
|
||||
|
||||
try:
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
openuv = OpenUV(
|
||||
Client(
|
||||
api_key, latitude, longitude, websession, altitude=elevation),
|
||||
conf[CONF_BINARY_SENSORS][CONF_MONITORED_CONDITIONS] +
|
||||
conf[CONF_SENSORS][CONF_MONITORED_CONDITIONS])
|
||||
await openuv.async_update()
|
||||
hass.data[DOMAIN] = openuv
|
||||
except OpenUvError as err:
|
||||
_LOGGER.error('An error occurred: %s', str(err))
|
||||
hass.components.persistent_notification.create(
|
||||
'Error: {0}<br />'
|
||||
'You will need to restart hass after fixing.'
|
||||
''.format(err),
|
||||
title=NOTIFICATION_TITLE,
|
||||
notification_id=NOTIFICATION_ID)
|
||||
return False
|
||||
|
||||
for component, schema in [
|
||||
('binary_sensor', conf[CONF_BINARY_SENSORS]),
|
||||
('sensor', conf[CONF_SENSORS]),
|
||||
]:
|
||||
hass.async_create_task(
|
||||
discovery.async_load_platform(
|
||||
hass, component, DOMAIN, schema, config))
|
||||
|
||||
async def refresh_sensors(event_time):
|
||||
"""Refresh OpenUV data."""
|
||||
_LOGGER.debug('Refreshing OpenUV data')
|
||||
await openuv.async_update()
|
||||
async_dispatcher_send(hass, TOPIC_UPDATE)
|
||||
|
||||
async_track_time_interval(hass, refresh_sensors, conf[CONF_SCAN_INTERVAL])
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class OpenUV:
|
||||
"""Define a generic OpenUV object."""
|
||||
|
||||
def __init__(self, client, monitored_conditions):
|
||||
"""Initialize."""
|
||||
self._monitored_conditions = monitored_conditions
|
||||
self.client = client
|
||||
self.data = {}
|
||||
|
||||
async def async_update(self):
|
||||
"""Update sensor/binary sensor data."""
|
||||
if TYPE_PROTECTION_WINDOW in self._monitored_conditions:
|
||||
data = await self.client.uv_protection_window()
|
||||
self.data[DATA_PROTECTION_WINDOW] = data
|
||||
|
||||
if any(c in self._monitored_conditions for c in SENSORS):
|
||||
data = await self.client.uv_index()
|
||||
self.data[DATA_UV] = data
|
||||
|
||||
|
||||
class OpenUvEntity(Entity):
|
||||
"""Define a generic OpenUV entity."""
|
||||
|
||||
def __init__(self, openuv):
|
||||
"""Initialize."""
|
||||
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
||||
self._name = None
|
||||
self.openuv = openuv
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return self._attrs
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the entity."""
|
||||
return self._name
|
121
homeassistant/components/sensor/openuv.py
Normal file
121
homeassistant/components/sensor/openuv.py
Normal file
|
@ -0,0 +1,121 @@
|
|||
"""
|
||||
This platform provides sensors for OpenUV data.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.openuv/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.components.openuv import (
|
||||
DATA_UV, DOMAIN, SENSORS, TOPIC_UPDATE, TYPE_CURRENT_OZONE_LEVEL,
|
||||
TYPE_CURRENT_UV_INDEX, TYPE_MAX_UV_INDEX, TYPE_SAFE_EXPOSURE_TIME_1,
|
||||
TYPE_SAFE_EXPOSURE_TIME_2, TYPE_SAFE_EXPOSURE_TIME_3,
|
||||
TYPE_SAFE_EXPOSURE_TIME_4, TYPE_SAFE_EXPOSURE_TIME_5,
|
||||
TYPE_SAFE_EXPOSURE_TIME_6, OpenUvEntity)
|
||||
from homeassistant.util.dt import as_local, parse_datetime
|
||||
|
||||
DEPENDENCIES = ['openuv']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_MAX_UV_TIME = 'time'
|
||||
|
||||
EXPOSURE_TYPE_MAP = {
|
||||
TYPE_SAFE_EXPOSURE_TIME_1: 'st1',
|
||||
TYPE_SAFE_EXPOSURE_TIME_2: 'st2',
|
||||
TYPE_SAFE_EXPOSURE_TIME_3: 'st3',
|
||||
TYPE_SAFE_EXPOSURE_TIME_4: 'st4',
|
||||
TYPE_SAFE_EXPOSURE_TIME_5: 'st5',
|
||||
TYPE_SAFE_EXPOSURE_TIME_6: 'st6'
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the OpenUV binary sensor platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
openuv = hass.data[DOMAIN]
|
||||
|
||||
sensors = []
|
||||
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
|
||||
name, icon, unit = SENSORS[sensor_type]
|
||||
sensors.append(OpenUvSensor(openuv, sensor_type, name, icon, unit))
|
||||
|
||||
async_add_devices(sensors, True)
|
||||
|
||||
|
||||
class OpenUvSensor(OpenUvEntity):
|
||||
"""Define a binary sensor for OpenUV."""
|
||||
|
||||
def __init__(self, openuv, sensor_type, name, icon, unit):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(openuv)
|
||||
|
||||
self._icon = icon
|
||||
self._latitude = openuv.client.latitude
|
||||
self._longitude = openuv.client.longitude
|
||||
self._name = name
|
||||
self._sensor_type = sensor_type
|
||||
self._state = None
|
||||
self._unit = unit
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Disable polling."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the status of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique, HASS-friendly identifier for this entity."""
|
||||
return '{0}_{1}_{2}'.format(
|
||||
self._latitude, self._longitude, self._sensor_type)
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit the value is expressed in."""
|
||||
return self._unit
|
||||
|
||||
@callback
|
||||
def _update_data(self):
|
||||
"""Update the state."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(self.hass, TOPIC_UPDATE, self._update_data)
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the state."""
|
||||
data = self.openuv.data[DATA_UV]['result']
|
||||
if self._sensor_type == TYPE_CURRENT_OZONE_LEVEL:
|
||||
self._state = data['ozone']
|
||||
elif self._sensor_type == TYPE_CURRENT_UV_INDEX:
|
||||
self._state = data['uv']
|
||||
elif self._sensor_type == TYPE_MAX_UV_INDEX:
|
||||
self._state = data['uv_max']
|
||||
self._attrs.update({
|
||||
ATTR_MAX_UV_TIME: as_local(
|
||||
parse_datetime(data['uv_max_time']))
|
||||
})
|
||||
elif self._sensor_type in (TYPE_SAFE_EXPOSURE_TIME_1,
|
||||
TYPE_SAFE_EXPOSURE_TIME_2,
|
||||
TYPE_SAFE_EXPOSURE_TIME_3,
|
||||
TYPE_SAFE_EXPOSURE_TIME_4,
|
||||
TYPE_SAFE_EXPOSURE_TIME_5,
|
||||
TYPE_SAFE_EXPOSURE_TIME_6):
|
||||
self._state = data['safe_exposure_time'][EXPOSURE_TYPE_MAP[
|
||||
self._sensor_type]]
|
|
@ -962,6 +962,9 @@ pynut2==2.1.2
|
|||
# homeassistant.components.binary_sensor.nx584
|
||||
pynx584==0.4
|
||||
|
||||
# homeassistant.components.openuv
|
||||
pyopenuv==1.0.1
|
||||
|
||||
# homeassistant.components.iota
|
||||
pyota==2.0.5
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue