Enable config flow for Luftdaten (#17700)
* Move file to new location * Update requirement * Enable config flow * Add luftdaten * Add tests * Update * Add constants * Changes according to the review comments * Remove wrong entry from flows * Fix dict handling * Add callback and use OrderedDict * Remve leftover * Fix * Remove await
This commit is contained in:
parent
7933bd7f91
commit
2e517ab6bc
13 changed files with 508 additions and 108 deletions
|
@ -109,7 +109,6 @@ homeassistant/components/sensor/gpsd.py @fabaff
|
|||
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
|
||||
homeassistant/components/sensor/jewish_calendar.py @tsvi
|
||||
homeassistant/components/sensor/linux_battery.py @fabaff
|
||||
homeassistant/components/sensor/luftdaten.py @fabaff
|
||||
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
|
||||
homeassistant/components/sensor/min_max.py @fabaff
|
||||
homeassistant/components/sensor/moon.py @fabaff
|
||||
|
@ -189,6 +188,8 @@ homeassistant/components/*/konnected.py @heythisisnate
|
|||
# L
|
||||
homeassistant/components/lifx.py @amelchio
|
||||
homeassistant/components/*/lifx.py @amelchio
|
||||
homeassistant/components/luftdaten/* @fabaff
|
||||
homeassistant/components/*/luftdaten.py @fabaff
|
||||
|
||||
# M
|
||||
homeassistant/components/matrix.py @tinloaf
|
||||
|
|
170
homeassistant/components/luftdaten/__init__.py
Normal file
170
homeassistant/components/luftdaten/__init__.py
Normal file
|
@ -0,0 +1,170 @@
|
|||
"""
|
||||
Support for Luftdaten stations.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/luftdaten/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import (
|
||||
CONF_MONITORED_CONDITIONS, CONF_SCAN_INTERVAL, CONF_SENSORS,
|
||||
CONF_SHOW_ON_MAP, TEMP_CELSIUS)
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
|
||||
from .config_flow import configured_sensors
|
||||
from .const import CONF_SENSOR_ID, DEFAULT_SCAN_INTERVAL, DOMAIN
|
||||
|
||||
REQUIREMENTS = ['luftdaten==0.3.4']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_LUFTDATEN = 'luftdaten'
|
||||
DATA_LUFTDATEN_CLIENT = 'data_luftdaten_client'
|
||||
DATA_LUFTDATEN_LISTENER = 'data_luftdaten_listener'
|
||||
DEFAULT_ATTRIBUTION = "Data provided by luftdaten.info"
|
||||
|
||||
SENSOR_HUMIDITY = 'humidity'
|
||||
SENSOR_PM10 = 'P1'
|
||||
SENSOR_PM2_5 = 'P2'
|
||||
SENSOR_PRESSURE = 'pressure'
|
||||
SENSOR_TEMPERATURE = 'temperature'
|
||||
|
||||
TOPIC_UPDATE = '{0}_data_update'.format(DOMAIN)
|
||||
|
||||
VOLUME_MICROGRAMS_PER_CUBIC_METER = 'µg/m3'
|
||||
|
||||
SENSORS = {
|
||||
SENSOR_TEMPERATURE: ['Temperature', 'mdi:thermometer', TEMP_CELSIUS],
|
||||
SENSOR_HUMIDITY: ['Humidity', 'mdi:water-percent', '%'],
|
||||
SENSOR_PRESSURE: ['Pressure', 'mdi:arrow-down-bold', 'Pa'],
|
||||
SENSOR_PM10: ['PM10', 'mdi:thought-bubble',
|
||||
VOLUME_MICROGRAMS_PER_CUBIC_METER],
|
||||
SENSOR_PM2_5: ['PM2.5', 'mdi:thought-bubble-outline',
|
||||
VOLUME_MICROGRAMS_PER_CUBIC_METER]
|
||||
}
|
||||
|
||||
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_SENSOR_ID): cv.positive_int,
|
||||
vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
|
||||
vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean,
|
||||
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 Luftdaten component."""
|
||||
hass.data[DOMAIN] = {}
|
||||
hass.data[DOMAIN][DATA_LUFTDATEN_CLIENT] = {}
|
||||
hass.data[DOMAIN][DATA_LUFTDATEN_LISTENER] = {}
|
||||
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
conf = config[DOMAIN]
|
||||
station_id = conf.get(CONF_SENSOR_ID)
|
||||
|
||||
if station_id not in configured_sensors(hass):
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={'source': SOURCE_IMPORT},
|
||||
data={
|
||||
CONF_SENSORS: conf[CONF_SENSORS],
|
||||
CONF_SENSOR_ID: conf[CONF_SENSOR_ID],
|
||||
CONF_SHOW_ON_MAP: conf[CONF_SHOW_ON_MAP],
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][CONF_SCAN_INTERVAL] = conf[CONF_SCAN_INTERVAL]
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up Luftdaten as config entry."""
|
||||
from luftdaten import Luftdaten
|
||||
from luftdaten.exceptions import LuftdatenError
|
||||
|
||||
session = async_get_clientsession(hass)
|
||||
|
||||
try:
|
||||
luftdaten = LuftDatenData(
|
||||
Luftdaten(
|
||||
config_entry.data[CONF_SENSOR_ID], hass.loop, session),
|
||||
config_entry.data.get(CONF_SENSORS, {}).get(
|
||||
CONF_MONITORED_CONDITIONS, list(SENSORS)))
|
||||
await luftdaten.async_update()
|
||||
hass.data[DOMAIN][DATA_LUFTDATEN_CLIENT][config_entry.entry_id] = \
|
||||
luftdaten
|
||||
except LuftdatenError:
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
|
||||
config_entry, 'sensor'))
|
||||
|
||||
async def refresh_sensors(event_time):
|
||||
"""Refresh Luftdaten data."""
|
||||
await luftdaten.async_update()
|
||||
async_dispatcher_send(hass, TOPIC_UPDATE)
|
||||
|
||||
hass.data[DOMAIN][DATA_LUFTDATEN_LISTENER][
|
||||
config_entry.entry_id] = async_track_time_interval(
|
||||
hass, refresh_sensors,
|
||||
hass.data[DOMAIN].get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
"""Unload an Luftdaten config entry."""
|
||||
remove_listener = hass.data[DOMAIN][DATA_LUFTDATEN_LISTENER].pop(
|
||||
config_entry.entry_id)
|
||||
remove_listener()
|
||||
|
||||
for component in ('sensor', ):
|
||||
await hass.config_entries.async_forward_entry_unload(
|
||||
config_entry, component)
|
||||
|
||||
hass.data[DOMAIN][DATA_LUFTDATEN_CLIENT].pop(config_entry.entry_id)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class LuftDatenData:
|
||||
"""Define a generic Luftdaten object."""
|
||||
|
||||
def __init__(self, client, sensor_conditions):
|
||||
"""Initialize the Luftdata object."""
|
||||
self.client = client
|
||||
self.data = {}
|
||||
self.sensor_conditions = sensor_conditions
|
||||
|
||||
async def async_update(self):
|
||||
"""Update sensor/binary sensor data."""
|
||||
from luftdaten.exceptions import LuftdatenError
|
||||
|
||||
try:
|
||||
await self.client.get_data()
|
||||
|
||||
self.data[DATA_LUFTDATEN] = self.client.values
|
||||
self.data[DATA_LUFTDATEN].update(self.client.meta)
|
||||
|
||||
except LuftdatenError:
|
||||
_LOGGER.error("Unable to retrieve data from luftdaten.info")
|
75
homeassistant/components/luftdaten/config_flow.py
Normal file
75
homeassistant/components/luftdaten/config_flow.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
"""Config flow to configure the Luftdaten component."""
|
||||
from collections import OrderedDict
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_SCAN_INTERVAL, CONF_SHOW_ON_MAP
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .const import CONF_SENSOR_ID, DEFAULT_SCAN_INTERVAL, DOMAIN
|
||||
|
||||
|
||||
@callback
|
||||
def configured_sensors(hass):
|
||||
"""Return a set of configured Luftdaten sensors."""
|
||||
return set(
|
||||
'{0}'.format(entry.data[CONF_SENSOR_ID])
|
||||
for entry in hass.config_entries.async_entries(DOMAIN))
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class LuftDatenFlowHandler(config_entries.ConfigFlow):
|
||||
"""Handle a Luftdaten config flow."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
@callback
|
||||
def _show_form(self, errors=None):
|
||||
"""Show the form to the user."""
|
||||
data_schema = OrderedDict()
|
||||
data_schema[vol.Required(CONF_SENSOR_ID)] = str
|
||||
data_schema[vol.Optional(CONF_SHOW_ON_MAP, default=False)] = bool
|
||||
|
||||
return self.async_show_form(
|
||||
step_id='user',
|
||||
data_schema=vol.Schema(data_schema),
|
||||
errors=errors or {}
|
||||
)
|
||||
|
||||
async def async_step_import(self, import_config):
|
||||
"""Import a config entry from configuration.yaml."""
|
||||
return await self.async_step_user(import_config)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the start of the config flow."""
|
||||
from luftdaten import Luftdaten, exceptions
|
||||
|
||||
if not user_input:
|
||||
return self._show_form()
|
||||
|
||||
sensor_id = user_input[CONF_SENSOR_ID]
|
||||
|
||||
if sensor_id in configured_sensors(self.hass):
|
||||
return self._show_form({CONF_SENSOR_ID: 'sensor_exists'})
|
||||
|
||||
session = aiohttp_client.async_get_clientsession(self.hass)
|
||||
luftdaten = Luftdaten(
|
||||
user_input[CONF_SENSOR_ID], self.hass.loop, session)
|
||||
try:
|
||||
await luftdaten.get_data()
|
||||
valid = await luftdaten.validate_sensor()
|
||||
except exceptions.LuftdatenConnectionError:
|
||||
return self._show_form(
|
||||
{CONF_SENSOR_ID: 'communication_error'})
|
||||
|
||||
if not valid:
|
||||
return self._show_form({CONF_SENSOR_ID: 'invalid_sensor'})
|
||||
|
||||
scan_interval = user_input.get(
|
||||
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
||||
user_input.update({CONF_SCAN_INTERVAL: scan_interval.seconds})
|
||||
|
||||
return self.async_create_entry(title=sensor_id, data=user_input)
|
10
homeassistant/components/luftdaten/const.py
Normal file
10
homeassistant/components/luftdaten/const.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
"""Define constants for the Luftdaten component."""
|
||||
from datetime import timedelta
|
||||
|
||||
ATTR_SENSOR_ID = 'sensor_id'
|
||||
|
||||
CONF_SENSOR_ID = 'sensor_id'
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(minutes=10)
|
||||
|
||||
DOMAIN = 'luftdaten'
|
20
homeassistant/components/luftdaten/strings.json
Normal file
20
homeassistant/components/luftdaten/strings.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"config": {
|
||||
"title": "Luftdaten",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Define Luftdaten",
|
||||
"data": {
|
||||
"station_id": "Luftdaten Sensor ID",
|
||||
"show_on_map": "Show on map"
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"sensor_exists": "Sensor already registered",
|
||||
"invalid_sensor": "Sensor not available or invalid",
|
||||
"communication_error": "Unable to communicate with the Luftdaten API"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,152 +4,120 @@ Support for Luftdaten sensors.
|
|||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.luftdaten/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.components.luftdaten import (
|
||||
DATA_LUFTDATEN, DATA_LUFTDATEN_CLIENT, DEFAULT_ATTRIBUTION, DOMAIN,
|
||||
SENSORS, TOPIC_UPDATE)
|
||||
from homeassistant.components.luftdaten.const import ATTR_SENSOR_ID
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_MONITORED_CONDITIONS,
|
||||
CONF_NAME, CONF_SHOW_ON_MAP, TEMP_CELSIUS)
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
ATTR_ATTRIBUTION, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_SHOW_ON_MAP)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['luftdaten==0.2.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_SENSOR_ID = 'sensor_id'
|
||||
|
||||
CONF_ATTRIBUTION = "Data provided by luftdaten.info"
|
||||
|
||||
|
||||
VOLUME_MICROGRAMS_PER_CUBIC_METER = 'µg/m3'
|
||||
|
||||
SENSOR_TEMPERATURE = 'temperature'
|
||||
SENSOR_HUMIDITY = 'humidity'
|
||||
SENSOR_PM10 = 'P1'
|
||||
SENSOR_PM2_5 = 'P2'
|
||||
SENSOR_PRESSURE = 'pressure'
|
||||
|
||||
SENSOR_TYPES = {
|
||||
SENSOR_TEMPERATURE: ['Temperature', TEMP_CELSIUS],
|
||||
SENSOR_HUMIDITY: ['Humidity', '%'],
|
||||
SENSOR_PRESSURE: ['Pressure', 'Pa'],
|
||||
SENSOR_PM10: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER],
|
||||
SENSOR_PM2_5: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER]
|
||||
}
|
||||
|
||||
DEFAULT_NAME = 'Luftdaten'
|
||||
|
||||
CONF_SENSORID = 'sensorid'
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_SENSORID): cv.positive_int,
|
||||
vol.Required(CONF_MONITORED_CONDITIONS):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean,
|
||||
})
|
||||
DEPENDENCIES = ['luftdaten']
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Luftdaten sensor."""
|
||||
from luftdaten import Luftdaten
|
||||
"""Set up an Luftdaten sensor based on existing config."""
|
||||
pass
|
||||
|
||||
name = config.get(CONF_NAME)
|
||||
show_on_map = config.get(CONF_SHOW_ON_MAP)
|
||||
sensor_id = config.get(CONF_SENSORID)
|
||||
|
||||
session = async_get_clientsession(hass)
|
||||
luftdaten = LuftdatenData(Luftdaten(sensor_id, hass.loop, session))
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up a Luftdaten sensor based on a config entry."""
|
||||
luftdaten = hass.data[DOMAIN][DATA_LUFTDATEN_CLIENT][entry.entry_id]
|
||||
|
||||
await luftdaten.async_update()
|
||||
sensors = []
|
||||
for sensor_type in luftdaten.sensor_conditions:
|
||||
name, icon, unit = SENSORS[sensor_type]
|
||||
sensors.append(
|
||||
LuftdatenSensor(
|
||||
luftdaten, sensor_type, name, icon, unit,
|
||||
entry.data[CONF_SHOW_ON_MAP])
|
||||
)
|
||||
|
||||
if luftdaten.data is None:
|
||||
_LOGGER.error("Sensor is not available: %s", sensor_id)
|
||||
return
|
||||
|
||||
devices = []
|
||||
for variable in config[CONF_MONITORED_CONDITIONS]:
|
||||
if luftdaten.data.values[variable] is None:
|
||||
_LOGGER.warning("It might be that sensor %s is not providing "
|
||||
"measurements for %s", sensor_id, variable)
|
||||
devices.append(
|
||||
LuftdatenSensor(luftdaten, name, variable, sensor_id, show_on_map))
|
||||
|
||||
async_add_entities(devices)
|
||||
async_add_entities(sensors, True)
|
||||
|
||||
|
||||
class LuftdatenSensor(Entity):
|
||||
"""Implementation of a Luftdaten sensor."""
|
||||
|
||||
def __init__(self, luftdaten, name, sensor_type, sensor_id, show):
|
||||
def __init__(
|
||||
self, luftdaten, sensor_type, name, icon, unit, show):
|
||||
"""Initialize the Luftdaten sensor."""
|
||||
self._async_unsub_dispatcher_connect = None
|
||||
self.luftdaten = luftdaten
|
||||
self._icon = icon
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._sensor_id = sensor_id
|
||||
self._data = None
|
||||
self.sensor_type = sensor_type
|
||||
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
||||
self._unit_of_measurement = unit
|
||||
self._show_on_map = show
|
||||
self._attrs = {}
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return '{} {}'.format(self._name, SENSOR_TYPES[self.sensor_type][0])
|
||||
def icon(self):
|
||||
"""Return the icon."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self.luftdaten.data.values[self.sensor_type]
|
||||
return self._data[self.sensor_type]
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this entity, if any."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Disable polling."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique, friendly identifier for this entity."""
|
||||
return '{0}_{1}'.format(self._data['sensor_id'], self.sensor_type)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
onmap = ATTR_LATITUDE, ATTR_LONGITUDE
|
||||
nomap = 'lat', 'long'
|
||||
lat_format, lon_format = onmap if self._show_on_map else nomap
|
||||
self._attrs[ATTR_SENSOR_ID] = self._data['sensor_id']
|
||||
self._attrs[ATTR_ATTRIBUTION] = DEFAULT_ATTRIBUTION
|
||||
|
||||
on_map = ATTR_LATITUDE, ATTR_LONGITUDE
|
||||
no_map = 'lat', 'long'
|
||||
lat_format, lon_format = on_map if self._show_on_map else no_map
|
||||
try:
|
||||
attr = {
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||
ATTR_SENSOR_ID: self._sensor_id,
|
||||
lat_format: self.luftdaten.data.meta['latitude'],
|
||||
lon_format: self.luftdaten.data.meta['longitude'],
|
||||
}
|
||||
return attr
|
||||
self._attrs[lon_format] = self._data['longitude']
|
||||
self._attrs[lat_format] = self._data['latitude']
|
||||
return self._attrs
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
@callback
|
||||
def update():
|
||||
"""Update the state."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
|
||||
self.hass, TOPIC_UPDATE, update)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Disconnect dispatcher listener when removed."""
|
||||
if self._async_unsub_dispatcher_connect:
|
||||
self._async_unsub_dispatcher_connect()
|
||||
|
||||
async def async_update(self):
|
||||
"""Get the latest data from luftdaten.info and update the state."""
|
||||
await self.luftdaten.async_update()
|
||||
|
||||
|
||||
class LuftdatenData:
|
||||
"""Class for handling the data retrieval."""
|
||||
|
||||
def __init__(self, data):
|
||||
"""Initialize the data object."""
|
||||
self.data = data
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
async def async_update(self):
|
||||
"""Get the latest data from luftdaten.info."""
|
||||
from luftdaten.exceptions import LuftdatenError
|
||||
|
||||
"""Get the latest data and update the state."""
|
||||
try:
|
||||
await self.data.async_get_data()
|
||||
except LuftdatenError:
|
||||
_LOGGER.error("Unable to retrieve data from luftdaten.info")
|
||||
self._data = self.luftdaten.data[DATA_LUFTDATEN]
|
||||
except KeyError:
|
||||
return
|
||||
|
|
|
@ -145,6 +145,7 @@ FLOWS = [
|
|||
'ios',
|
||||
'lifx',
|
||||
'mailgun',
|
||||
'luftdaten',
|
||||
'mqtt',
|
||||
'nest',
|
||||
'openuv',
|
||||
|
|
|
@ -590,8 +590,8 @@ locationsharinglib==3.0.7
|
|||
# homeassistant.components.logi_circle
|
||||
logi_circle==0.1.7
|
||||
|
||||
# homeassistant.components.sensor.luftdaten
|
||||
luftdaten==0.2.0
|
||||
# homeassistant.components.luftdaten
|
||||
luftdaten==0.3.4
|
||||
|
||||
# homeassistant.components.light.lw12wifi
|
||||
lw12==0.9.2
|
||||
|
|
|
@ -112,6 +112,9 @@ libpurecoollink==0.4.2
|
|||
# homeassistant.components.media_player.soundtouch
|
||||
libsoundtouch==0.7.2
|
||||
|
||||
# homeassistant.components.luftdaten
|
||||
luftdaten==0.3.4
|
||||
|
||||
# homeassistant.components.sensor.mfi
|
||||
# homeassistant.components.switch.mfi
|
||||
mficlient==0.3.0
|
||||
|
|
|
@ -50,12 +50,12 @@ TEST_REQUIREMENTS = (
|
|||
'evohomeclient',
|
||||
'feedparser',
|
||||
'foobot_async',
|
||||
'gTTS-token',
|
||||
'geojson_client',
|
||||
'georss_client',
|
||||
'gTTS-token',
|
||||
'ha-ffmpeg',
|
||||
'hangups',
|
||||
'HAP-python',
|
||||
'ha-ffmpeg',
|
||||
'haversine',
|
||||
'hbmqtt',
|
||||
'hdate',
|
||||
|
@ -65,6 +65,7 @@ TEST_REQUIREMENTS = (
|
|||
'influxdb',
|
||||
'libpurecoollink',
|
||||
'libsoundtouch',
|
||||
'luftdaten',
|
||||
'mficlient',
|
||||
'numpy',
|
||||
'paho-mqtt',
|
||||
|
|
1
tests/components/luftdaten/__init__.py
Normal file
1
tests/components/luftdaten/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Define tests for the Luftdaten component."""
|
114
tests/components/luftdaten/test_config_flow.py
Normal file
114
tests/components/luftdaten/test_config_flow.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
"""Define tests for the Luftdaten config flow."""
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.luftdaten import DOMAIN, config_flow
|
||||
from homeassistant.components.luftdaten.const import CONF_SENSOR_ID
|
||||
from homeassistant.const import CONF_SCAN_INTERVAL, CONF_SHOW_ON_MAP
|
||||
|
||||
from tests.common import MockConfigEntry, mock_coro
|
||||
|
||||
|
||||
async def test_duplicate_error(hass):
|
||||
"""Test that errors are shown when duplicates are added."""
|
||||
conf = {
|
||||
CONF_SENSOR_ID: '12345abcde',
|
||||
}
|
||||
|
||||
MockConfigEntry(domain=DOMAIN, data=conf).add_to_hass(hass)
|
||||
flow = config_flow.LuftDatenFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
result = await flow.async_step_user(user_input=conf)
|
||||
assert result['errors'] == {CONF_SENSOR_ID: 'sensor_exists'}
|
||||
|
||||
|
||||
async def test_communication_error(hass):
|
||||
"""Test that no sensor is added while unable to communicate with API."""
|
||||
conf = {
|
||||
CONF_SENSOR_ID: '12345abcde',
|
||||
}
|
||||
|
||||
flow = config_flow.LuftDatenFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
with patch('luftdaten.Luftdaten.get_data', return_value=mock_coro(None)):
|
||||
result = await flow.async_step_user(user_input=conf)
|
||||
assert result['errors'] == {CONF_SENSOR_ID: 'invalid_sensor'}
|
||||
|
||||
|
||||
async def test_invalid_sensor(hass):
|
||||
"""Test that an invalid sensor throws an error."""
|
||||
conf = {
|
||||
CONF_SENSOR_ID: '12345abcde',
|
||||
}
|
||||
|
||||
flow = config_flow.LuftDatenFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
with patch('luftdaten.Luftdaten.get_data', return_value=mock_coro(False)),\
|
||||
patch('luftdaten.Luftdaten.validate_sensor',
|
||||
return_value=mock_coro(False)):
|
||||
result = await flow.async_step_user(user_input=conf)
|
||||
assert result['errors'] == {CONF_SENSOR_ID: 'invalid_sensor'}
|
||||
|
||||
|
||||
async def test_show_form(hass):
|
||||
"""Test that the form is served with no input."""
|
||||
flow = config_flow.LuftDatenFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
result = await flow.async_step_user(user_input=None)
|
||||
|
||||
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result['step_id'] == 'user'
|
||||
|
||||
|
||||
async def test_step_import(hass):
|
||||
"""Test that the import step works."""
|
||||
conf = {
|
||||
CONF_SENSOR_ID: '12345abcde',
|
||||
CONF_SHOW_ON_MAP: False,
|
||||
}
|
||||
|
||||
flow = config_flow.LuftDatenFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
with patch('luftdaten.Luftdaten.get_data', return_value=mock_coro(True)), \
|
||||
patch('luftdaten.Luftdaten.validate_sensor',
|
||||
return_value=mock_coro(True)):
|
||||
result = await flow.async_step_import(import_config=conf)
|
||||
|
||||
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result['title'] == '12345abcde'
|
||||
assert result['data'] == {
|
||||
CONF_SENSOR_ID: '12345abcde',
|
||||
CONF_SHOW_ON_MAP: False,
|
||||
CONF_SCAN_INTERVAL: 600,
|
||||
}
|
||||
|
||||
|
||||
async def test_step_user(hass):
|
||||
"""Test that the user step works."""
|
||||
conf = {
|
||||
CONF_SENSOR_ID: '12345abcde',
|
||||
CONF_SHOW_ON_MAP: False,
|
||||
CONF_SCAN_INTERVAL: timedelta(minutes=5),
|
||||
}
|
||||
|
||||
flow = config_flow.LuftDatenFlowHandler()
|
||||
flow.hass = hass
|
||||
|
||||
with patch('luftdaten.Luftdaten.get_data', return_value=mock_coro(True)), \
|
||||
patch('luftdaten.Luftdaten.validate_sensor',
|
||||
return_value=mock_coro(True)):
|
||||
result = await flow.async_step_user(user_input=conf)
|
||||
|
||||
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result['title'] == '12345abcde'
|
||||
assert result['data'] == {
|
||||
CONF_SENSOR_ID: '12345abcde',
|
||||
CONF_SHOW_ON_MAP: False,
|
||||
CONF_SCAN_INTERVAL: 300,
|
||||
}
|
36
tests/components/luftdaten/test_init.py
Normal file
36
tests/components/luftdaten/test_init.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
"""Test the Luftdaten component setup."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components import luftdaten
|
||||
from homeassistant.components.luftdaten.const import CONF_SENSOR_ID, DOMAIN
|
||||
from homeassistant.const import CONF_SCAN_INTERVAL, CONF_SHOW_ON_MAP
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
||||
async def test_config_with_sensor_passed_to_config_entry(hass):
|
||||
"""Test that configured options for a sensor are loaded."""
|
||||
conf = {
|
||||
CONF_SENSOR_ID: '12345abcde',
|
||||
CONF_SHOW_ON_MAP: False,
|
||||
CONF_SCAN_INTERVAL: 600,
|
||||
}
|
||||
|
||||
with patch.object(hass, 'config_entries') as mock_config_entries, \
|
||||
patch.object(luftdaten, 'configured_sensors', return_value=[]):
|
||||
assert await async_setup_component(hass, DOMAIN, conf) is True
|
||||
|
||||
assert len(mock_config_entries.flow.mock_calls) == 0
|
||||
|
||||
|
||||
async def test_config_already_registered_not_passed_to_config_entry(hass):
|
||||
"""Test that an already registered sensor does not initiate an import."""
|
||||
conf = {
|
||||
CONF_SENSOR_ID: '12345abcde',
|
||||
}
|
||||
|
||||
with patch.object(hass, 'config_entries') as mock_config_entries, \
|
||||
patch.object(luftdaten, 'configured_sensors',
|
||||
return_value=['12345abcde']):
|
||||
assert await async_setup_component(hass, DOMAIN, conf) is True
|
||||
|
||||
assert len(mock_config_entries.flow.mock_calls) == 0
|
Loading…
Add table
Add a link
Reference in a new issue