mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Add water level sensors provided by UK Environment Agency (#31954)
This commit is contained in:
parent
f1fd8aa51f
commit
f82f815304
@ -107,6 +107,7 @@ homeassistant/components/dunehd/* @bieniu
|
||||
homeassistant/components/dweet/* @fabaff
|
||||
homeassistant/components/dynalite/* @ziv1234
|
||||
homeassistant/components/dyson/* @etheralm
|
||||
homeassistant/components/eafm/* @Jc2k
|
||||
homeassistant/components/ecobee/* @marthoc
|
||||
homeassistant/components/ecovacs/* @OverloadUT
|
||||
homeassistant/components/edl21/* @mtdcr
|
||||
|
23
homeassistant/components/eafm/__init__.py
Normal file
23
homeassistant/components/eafm/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""UK Environment Agency Flood Monitoring Integration."""
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up devices."""
|
||||
hass.data[DOMAIN] = {}
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry):
|
||||
"""Set up flood monitoring sensors for this config entry."""
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, "sensor")
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
"""Unload flood monitoring sensors."""
|
||||
return await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
|
61
homeassistant/components/eafm/config_flow.py
Normal file
61
homeassistant/components/eafm/config_flow.py
Normal file
@ -0,0 +1,61 @@
|
||||
"""Config flow to configure flood monitoring gauges."""
|
||||
import logging
|
||||
|
||||
from aioeafm import get_stations
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UKFloodsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a UK Environment Agency flood monitoring config flow."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
def __init__(self):
|
||||
"""Handle a UK Floods config flow."""
|
||||
self.stations = {}
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow start."""
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
station = self.stations[user_input["station"]]
|
||||
await self.async_set_unique_id(station, raise_on_progress=False)
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
title=user_input["station"], data={"station": station},
|
||||
)
|
||||
|
||||
session = async_get_clientsession(hass=self.hass)
|
||||
stations = await get_stations(session)
|
||||
|
||||
self.stations = {}
|
||||
for station in stations:
|
||||
label = station["label"]
|
||||
|
||||
# API annoyingly sometimes returns a list and some times returns a string
|
||||
# E.g. L3121 has a label of ['Scurf Dyke', 'Scurf Dyke Dyke Level']
|
||||
if isinstance(label, list):
|
||||
label = label[-1]
|
||||
|
||||
self.stations[label] = station["stationReference"]
|
||||
|
||||
if not self.stations:
|
||||
return self.async_abort(reason="no_stations")
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
errors=errors,
|
||||
data_schema=vol.Schema(
|
||||
{vol.Required("station"): vol.In(sorted(self.stations))}
|
||||
),
|
||||
)
|
3
homeassistant/components/eafm/const.py
Normal file
3
homeassistant/components/eafm/const.py
Normal file
@ -0,0 +1,3 @@
|
||||
"""Constants for the eafm component."""
|
||||
|
||||
DOMAIN = "eafm"
|
8
homeassistant/components/eafm/manifest.json
Normal file
8
homeassistant/components/eafm/manifest.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"domain": "eafm",
|
||||
"name": "Environment Agency Flood Gauges",
|
||||
"documentation": "https://www.home-assistant.io/integrations/eafm",
|
||||
"config_flow": true,
|
||||
"codeowners": ["@Jc2k"],
|
||||
"requirements": ["aioeafm==0.1.2"]
|
||||
}
|
183
homeassistant/components/eafm/sensor.py
Normal file
183
homeassistant/components/eafm/sensor.py
Normal file
@ -0,0 +1,183 @@
|
||||
"""Support for guages from flood monitoring API."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from aioeafm import get_station
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.const import ATTR_ATTRIBUTION, LENGTH_METERS
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
UNIT_MAPPING = {
|
||||
"http://qudt.org/1.1/vocab/unit#Meter": LENGTH_METERS,
|
||||
}
|
||||
|
||||
|
||||
def get_measures(station_data):
|
||||
"""Force measure key to always be a list."""
|
||||
if "measures" not in station_data:
|
||||
return []
|
||||
if isinstance(station_data["measures"], dict):
|
||||
return [station_data["measures"]]
|
||||
return station_data["measures"]
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up UK Flood Monitoring Sensors."""
|
||||
station_key = config_entry.data["station"]
|
||||
session = async_get_clientsession(hass=hass)
|
||||
|
||||
measurements = set()
|
||||
|
||||
async def async_update_data():
|
||||
# DataUpdateCoordinator will handle aiohttp ClientErrors and timouts
|
||||
async with async_timeout.timeout(30):
|
||||
data = await get_station(session, station_key)
|
||||
|
||||
measures = get_measures(data)
|
||||
entities = []
|
||||
|
||||
# Look to see if payload contains new measures
|
||||
for measure in measures:
|
||||
if measure["@id"] in measurements:
|
||||
continue
|
||||
|
||||
if "latestReading" not in measure:
|
||||
# Don't create a sensor entity for a gauge that isn't available
|
||||
continue
|
||||
|
||||
entities.append(Measurement(hass.data[DOMAIN][station_key], measure["@id"]))
|
||||
measurements.add(measure["@id"])
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
# Turn data.measures into a dict rather than a list so easier for entities to
|
||||
# find themselves.
|
||||
data["measures"] = {measure["@id"]: measure for measure in measures}
|
||||
|
||||
return data
|
||||
|
||||
hass.data[DOMAIN][station_key] = coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name="sensor",
|
||||
update_method=async_update_data,
|
||||
update_interval=timedelta(seconds=15 * 60),
|
||||
)
|
||||
|
||||
# Fetch initial data so we have data when entities subscribe
|
||||
await coordinator.async_refresh()
|
||||
|
||||
|
||||
class Measurement(Entity):
|
||||
"""A gauge at a flood monitoring station."""
|
||||
|
||||
attribution = "This uses Environment Agency flood and river level data from the real-time data API"
|
||||
|
||||
def __init__(self, coordinator, key):
|
||||
"""Initialise the gauge with a data instance and station."""
|
||||
self.coordinator = coordinator
|
||||
self.key = key
|
||||
|
||||
@property
|
||||
def station_name(self):
|
||||
"""Return the station name for the measure."""
|
||||
return self.coordinator.data["label"]
|
||||
|
||||
@property
|
||||
def station_id(self):
|
||||
"""Return the station id for the measure."""
|
||||
return self.coordinator.data["measures"][self.key]["stationReference"]
|
||||
|
||||
@property
|
||||
def qualifier(self):
|
||||
"""Return the qualifier for the station."""
|
||||
return self.coordinator.data["measures"][self.key]["qualifier"]
|
||||
|
||||
@property
|
||||
def parameter_name(self):
|
||||
"""Return the parameter name for the station."""
|
||||
return self.coordinator.data["measures"][self.key]["parameterName"]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the gauge."""
|
||||
return f"{self.station_name} {self.parameter_name} {self.qualifier}"
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Stations are polled as a group - the entity shouldn't poll by itself."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique id of the gauge."""
|
||||
return self.key
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device info."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, "measure-id", self.station_id)},
|
||||
"name": self.name,
|
||||
"manufacturer": "https://environment.data.gov.uk/",
|
||||
"model": self.parameter_name,
|
||||
"entry_type": "service",
|
||||
}
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
if not self.coordinator.last_update_success:
|
||||
return False
|
||||
|
||||
# If sensor goes offline it will no longer contain a reading
|
||||
if "latestReading" not in self.coordinator.data["measures"][self.key]:
|
||||
return False
|
||||
|
||||
# Sometimes lastestReading key is present but actually a URL rather than a piece of data
|
||||
# This is usually because the sensor has been archived
|
||||
if not isinstance(
|
||||
self.coordinator.data["measures"][self.key]["latestReading"], dict
|
||||
):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""When entity is added to hass."""
|
||||
self.async_on_remove(
|
||||
self.coordinator.async_add_listener(self.async_write_ha_state)
|
||||
)
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return units for the sensor."""
|
||||
measure = self.coordinator.data["measures"][self.key]
|
||||
if "unit" not in measure:
|
||||
return None
|
||||
return UNIT_MAPPING.get(measure["unit"], measure["unitName"])
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the sensor specific state attributes."""
|
||||
return {ATTR_ATTRIBUTION: self.attribution}
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the current sensor value."""
|
||||
return self.coordinator.data["measures"][self.key]["latestReading"]["value"]
|
||||
|
||||
async def async_update(self):
|
||||
"""
|
||||
Update the entity.
|
||||
|
||||
Only used by the generic entity update service.
|
||||
"""
|
||||
await self.coordinator.async_request_refresh()
|
17
homeassistant/components/eafm/strings.json
Normal file
17
homeassistant/components/eafm/strings.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Track a flood monitoring station",
|
||||
"description": "Select the station you want to monitor",
|
||||
"data": {
|
||||
"station": "Station"
|
||||
}
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"no_stations": "No flood monitoring stations found.",
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
}
|
||||
}
|
17
homeassistant/components/eafm/translations/en.json
Normal file
17
homeassistant/components/eafm/translations/en.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Track a flood monitoring station",
|
||||
"description": "Select the station you want to monitor",
|
||||
"data": {
|
||||
"station": "Station"
|
||||
}
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"no_stations": "No flood monitoring stations found.",
|
||||
"already_configured": "This station is already configured."
|
||||
}
|
||||
}
|
||||
}
|
@ -44,6 +44,7 @@ FLOWS = [
|
||||
"doorbird",
|
||||
"dunehd",
|
||||
"dynalite",
|
||||
"eafm",
|
||||
"ecobee",
|
||||
"elgato",
|
||||
"elkm1",
|
||||
|
@ -157,6 +157,9 @@ aiobotocore==0.11.1
|
||||
# homeassistant.components.minecraft_server
|
||||
aiodns==2.0.0
|
||||
|
||||
# homeassistant.components.eafm
|
||||
aioeafm==0.1.2
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==2.6.1
|
||||
|
||||
|
@ -85,6 +85,9 @@ aiobotocore==0.11.1
|
||||
# homeassistant.components.minecraft_server
|
||||
aiodns==2.0.0
|
||||
|
||||
# homeassistant.components.eafm
|
||||
aioeafm==0.1.2
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==2.6.1
|
||||
|
||||
|
1
tests/components/eafm/__init__.py
Normal file
1
tests/components/eafm/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for eafm."""
|
18
tests/components/eafm/conftest.py
Normal file
18
tests/components/eafm/conftest.py
Normal file
@ -0,0 +1,18 @@
|
||||
"""eafm fixtures."""
|
||||
|
||||
from asynctest import patch
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_get_stations():
|
||||
"""Mock aioeafm.get_stations."""
|
||||
with patch("homeassistant.components.eafm.config_flow.get_stations") as patched:
|
||||
yield patched
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_get_station():
|
||||
"""Mock aioeafm.get_station."""
|
||||
with patch("homeassistant.components.eafm.sensor.get_station") as patched:
|
||||
yield patched
|
59
tests/components/eafm/test_config_flow.py
Normal file
59
tests/components/eafm/test_config_flow.py
Normal file
@ -0,0 +1,59 @@
|
||||
"""Tests for eafm config flow."""
|
||||
from asynctest import patch
|
||||
import pytest
|
||||
from voluptuous.error import MultipleInvalid
|
||||
|
||||
from homeassistant.components.eafm import const
|
||||
|
||||
|
||||
async def test_flow_no_discovered_stations(hass, mock_get_stations):
|
||||
"""Test config flow discovers no station."""
|
||||
mock_get_stations.return_value = []
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": "user"}
|
||||
)
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "no_stations"
|
||||
|
||||
|
||||
async def test_flow_invalid_station(hass, mock_get_stations):
|
||||
"""Test config flow errors on invalid station."""
|
||||
mock_get_stations.return_value = [
|
||||
{"label": "My station", "stationReference": "L12345"}
|
||||
]
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": "user"}
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
|
||||
with pytest.raises(MultipleInvalid):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={"station": "My other station"}
|
||||
)
|
||||
|
||||
|
||||
async def test_flow_works(hass, mock_get_stations, mock_get_station):
|
||||
"""Test config flow discovers no station."""
|
||||
mock_get_stations.return_value = [
|
||||
{"label": "My station", "stationReference": "L12345"}
|
||||
]
|
||||
mock_get_station.return_value = [
|
||||
{"label": "My station", "stationReference": "L12345"}
|
||||
]
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN, context={"source": "user"}
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
|
||||
with patch("homeassistant.components.eafm.async_setup_entry", return_value=True):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={"station": "My station"}
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "My station"
|
||||
assert result["data"] == {
|
||||
"station": "L12345",
|
||||
}
|
431
tests/components/eafm/test_sensor.py
Normal file
431
tests/components/eafm/test_sensor.py
Normal file
@ -0,0 +1,431 @@
|
||||
"""Tests for polling measures."""
|
||||
import datetime
|
||||
|
||||
import aiohttp
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
DUMMY_REQUEST_INFO = aiohttp.client.RequestInfo(
|
||||
url="http://example.com", method="GET", headers={}, real_url="http://example.com"
|
||||
)
|
||||
|
||||
CONNECTION_EXCEPTIONS = [
|
||||
aiohttp.ClientConnectionError("Mock connection error"),
|
||||
aiohttp.ClientResponseError(DUMMY_REQUEST_INFO, [], message="Mock response error"),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_test_fixture(hass, mock_get_station, initial_value):
|
||||
"""Create a dummy config entry for testing polling."""
|
||||
mock_get_station.return_value = initial_value
|
||||
|
||||
entry = MockConfigEntry(
|
||||
version=1,
|
||||
domain="eafm",
|
||||
entry_id="VikingRecorder1234",
|
||||
data={"station": "L1234"},
|
||||
title="Viking Recorder",
|
||||
connection_class=config_entries.CONN_CLASS_CLOUD_PUSH,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
assert await async_setup_component(hass, "eafm", {})
|
||||
assert entry.state == config_entries.ENTRY_STATE_LOADED
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async def poll(value):
|
||||
mock_get_station.reset_mock(return_value=True, side_effect=True)
|
||||
|
||||
if isinstance(value, Exception):
|
||||
mock_get_station.side_effect = value
|
||||
else:
|
||||
mock_get_station.return_value = value
|
||||
|
||||
next_update = dt_util.utcnow() + datetime.timedelta(60 * 15)
|
||||
async_fire_time_changed(hass, next_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return entry, poll
|
||||
|
||||
|
||||
async def test_reading_measures_not_list(hass, mock_get_station):
|
||||
"""
|
||||
Test that a measure can be a dict not a list.
|
||||
|
||||
E.g. https://environment.data.gov.uk/flood-monitoring/id/stations/751110
|
||||
"""
|
||||
_ = await async_setup_test_fixture(
|
||||
hass,
|
||||
mock_get_station,
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": {
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 5},
|
||||
"stationReference": "L1234",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "5"
|
||||
|
||||
|
||||
async def test_reading_no_unit(hass, mock_get_station):
|
||||
"""
|
||||
Test that a sensor functions even if its unit is not known.
|
||||
|
||||
E.g. https://environment.data.gov.uk/flood-monitoring/id/stations/L0410
|
||||
"""
|
||||
_ = await async_setup_test_fixture(
|
||||
hass,
|
||||
mock_get_station,
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 5},
|
||||
"stationReference": "L1234",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "5"
|
||||
|
||||
|
||||
async def test_ignore_invalid_latest_reading(hass, mock_get_station):
|
||||
"""
|
||||
Test that a sensor functions even if its unit is not known.
|
||||
|
||||
E.g. https://environment.data.gov.uk/flood-monitoring/id/stations/L0410
|
||||
"""
|
||||
_ = await async_setup_test_fixture(
|
||||
hass,
|
||||
mock_get_station,
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": "http://environment.data.gov.uk/flood-monitoring/data/readings/L0410-level-stage-i-15_min----/2017-02-22T10-30-00Z",
|
||||
"stationReference": "L0410",
|
||||
},
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Other",
|
||||
"latestReading": {"value": 5},
|
||||
"stationReference": "L0411",
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state is None
|
||||
|
||||
state = hass.states.get("sensor.my_station_other_stage")
|
||||
assert state.state == "5"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exception", CONNECTION_EXCEPTIONS)
|
||||
async def test_reading_unavailable(hass, mock_get_station, exception):
|
||||
"""Test that a sensor is marked as unavailable if there is a connection error."""
|
||||
_, poll = await async_setup_test_fixture(
|
||||
hass,
|
||||
mock_get_station,
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 5},
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "5"
|
||||
|
||||
await poll(exception)
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "unavailable"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exception", CONNECTION_EXCEPTIONS)
|
||||
async def test_recover_from_failure(hass, mock_get_station, exception):
|
||||
"""Test that a sensor recovers from failures."""
|
||||
_, poll = await async_setup_test_fixture(
|
||||
hass,
|
||||
mock_get_station,
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 5},
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "5"
|
||||
|
||||
await poll(exception)
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "unavailable"
|
||||
|
||||
await poll(
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 56},
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "56"
|
||||
|
||||
|
||||
async def test_reading_is_sampled(hass, mock_get_station):
|
||||
"""Test that a sensor is added and polled."""
|
||||
await async_setup_test_fixture(
|
||||
hass,
|
||||
mock_get_station,
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 5},
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "5"
|
||||
assert state.attributes["unit_of_measurement"] == "m"
|
||||
|
||||
|
||||
async def test_multiple_readings_are_sampled(hass, mock_get_station):
|
||||
"""Test that multiple sensors are added and polled."""
|
||||
await async_setup_test_fixture(
|
||||
hass,
|
||||
mock_get_station,
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 5},
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
},
|
||||
{
|
||||
"@id": "really-long-unique-id-2",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Second Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 4},
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "5"
|
||||
assert state.attributes["unit_of_measurement"] == "m"
|
||||
|
||||
state = hass.states.get("sensor.my_station_water_level_second_stage")
|
||||
assert state.state == "4"
|
||||
assert state.attributes["unit_of_measurement"] == "m"
|
||||
|
||||
|
||||
async def test_ignore_no_latest_reading(hass, mock_get_station):
|
||||
"""Test that a measure is ignored if it has no latest reading."""
|
||||
await async_setup_test_fixture(
|
||||
hass,
|
||||
mock_get_station,
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 5},
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
},
|
||||
{
|
||||
"@id": "really-long-unique-id-2",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Second Stage",
|
||||
"parameterName": "Water Level",
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "5"
|
||||
assert state.attributes["unit_of_measurement"] == "m"
|
||||
|
||||
state = hass.states.get("sensor.my_station_water_level_second_stage")
|
||||
assert state is None
|
||||
|
||||
|
||||
async def test_mark_existing_as_unavailable_if_no_latest(hass, mock_get_station):
|
||||
"""Test that a measure is marked as unavailable if it has no latest reading."""
|
||||
_, poll = await async_setup_test_fixture(
|
||||
hass,
|
||||
mock_get_station,
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 5},
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "5"
|
||||
assert state.attributes["unit_of_measurement"] == "m"
|
||||
|
||||
await poll(
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "unavailable"
|
||||
|
||||
await poll(
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 5},
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "5"
|
||||
|
||||
|
||||
async def test_unload_entry(hass, mock_get_station):
|
||||
"""Test being able to unload an entry."""
|
||||
entry, _ = await async_setup_test_fixture(
|
||||
hass,
|
||||
mock_get_station,
|
||||
{
|
||||
"label": "My station",
|
||||
"measures": [
|
||||
{
|
||||
"@id": "really-long-unique-id",
|
||||
"label": "York Viking Recorder - level-stage-i-15_min----",
|
||||
"qualifier": "Stage",
|
||||
"parameterName": "Water Level",
|
||||
"latestReading": {"value": 5},
|
||||
"stationReference": "L1234",
|
||||
"unit": "http://qudt.org/1.1/vocab/unit#Meter",
|
||||
"unitName": "m",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
# And there should be an entity
|
||||
state = hass.states.get("sensor.my_station_water_level_stage")
|
||||
assert state.state == "5"
|
||||
|
||||
assert await entry.async_unload(hass)
|
||||
|
||||
# And the entity should be gone
|
||||
assert not hass.states.get("sensor.my_station_water_level_stage")
|
Loading…
x
Reference in New Issue
Block a user