mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Add a new "Ambient Weather Network" integration (#105779)
* Adding a new "Ambient Weather Network" integration. * Rebase and update code coverage. * Addressed some reviewer comments. * Remove mnemonics and replace with station names. * Remove climate-utils * Remove support for virtual stations. * Rebase * Address feedback * Remove redundant errors * Reviewer feedback * Add icons.json * More icons * Reviewer feedback * Fix test * Make sensor tests more robust * Make coordinator more robust * Change update coordinator to raise UpdateFailed * Recover from no station found error * Dynamically set device name * Address feedback * Disable some sensors by default * Reviewer feedback * Change from hub to service * Rebase * Address reviewer feedback * Reviewer feedback * Manually rerun ruff on all files
This commit is contained in:
parent
f62fb76765
commit
0dd8ffd1f5
@ -66,6 +66,7 @@ homeassistant.components.alpha_vantage.*
|
|||||||
homeassistant.components.amazon_polly.*
|
homeassistant.components.amazon_polly.*
|
||||||
homeassistant.components.amberelectric.*
|
homeassistant.components.amberelectric.*
|
||||||
homeassistant.components.ambiclimate.*
|
homeassistant.components.ambiclimate.*
|
||||||
|
homeassistant.components.ambient_network.*
|
||||||
homeassistant.components.ambient_station.*
|
homeassistant.components.ambient_station.*
|
||||||
homeassistant.components.amcrest.*
|
homeassistant.components.amcrest.*
|
||||||
homeassistant.components.ampio.*
|
homeassistant.components.ampio.*
|
||||||
|
@ -90,6 +90,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/amberelectric/ @madpilot
|
/tests/components/amberelectric/ @madpilot
|
||||||
/homeassistant/components/ambiclimate/ @danielhiversen
|
/homeassistant/components/ambiclimate/ @danielhiversen
|
||||||
/tests/components/ambiclimate/ @danielhiversen
|
/tests/components/ambiclimate/ @danielhiversen
|
||||||
|
/homeassistant/components/ambient_network/ @thomaskistler
|
||||||
|
/tests/components/ambient_network/ @thomaskistler
|
||||||
/homeassistant/components/ambient_station/ @bachya
|
/homeassistant/components/ambient_station/ @bachya
|
||||||
/tests/components/ambient_station/ @bachya
|
/tests/components/ambient_station/ @bachya
|
||||||
/homeassistant/components/amcrest/ @flacjacket
|
/homeassistant/components/amcrest/ @flacjacket
|
||||||
|
35
homeassistant/components/ambient_network/__init__.py
Normal file
35
homeassistant/components/ambient_network/__init__.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
"""The Ambient Weather Network integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from aioambient.open_api import OpenAPI
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import AmbientNetworkDataUpdateCoordinator
|
||||||
|
|
||||||
|
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up the Ambient Weather Network from a config entry."""
|
||||||
|
|
||||||
|
api = OpenAPI()
|
||||||
|
coordinator = AmbientNetworkDataUpdateCoordinator(hass, api)
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
|
||||||
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
return unload_ok
|
152
homeassistant/components/ambient_network/config_flow.py
Normal file
152
homeassistant/components/ambient_network/config_flow.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
"""Config flow for the Ambient Weather Network integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from aioambient import OpenAPI
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_LATITUDE,
|
||||||
|
CONF_LOCATION,
|
||||||
|
CONF_LONGITUDE,
|
||||||
|
CONF_MAC,
|
||||||
|
CONF_RADIUS,
|
||||||
|
UnitOfLength,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.selector import (
|
||||||
|
LocationSelector,
|
||||||
|
LocationSelectorConfig,
|
||||||
|
SelectOptionDict,
|
||||||
|
SelectSelector,
|
||||||
|
SelectSelectorConfig,
|
||||||
|
)
|
||||||
|
from homeassistant.util.unit_conversion import DistanceConverter
|
||||||
|
|
||||||
|
from .const import API_STATION_INDOOR, API_STATION_INFO, API_STATION_MAC_ADDRESS, DOMAIN
|
||||||
|
from .helper import get_station_name
|
||||||
|
|
||||||
|
CONF_USER = "user"
|
||||||
|
CONF_STATION = "station"
|
||||||
|
|
||||||
|
# One mile
|
||||||
|
CONF_RADIUS_DEFAULT = 1609.34
|
||||||
|
|
||||||
|
|
||||||
|
class AmbientNetworkConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle the config flow for the Ambient Weather Network integration."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Construct the config flow."""
|
||||||
|
|
||||||
|
self._longitude = 0.0
|
||||||
|
self._latitude = 0.0
|
||||||
|
self._radius = 0.0
|
||||||
|
self._stations: dict[str, dict[str, Any]] = {}
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self,
|
||||||
|
user_input: dict[str, Any] | None = None,
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle the initial step to select the location."""
|
||||||
|
|
||||||
|
errors: dict[str, str] | None = None
|
||||||
|
if user_input:
|
||||||
|
self._latitude = user_input[CONF_LOCATION][CONF_LATITUDE]
|
||||||
|
self._longitude = user_input[CONF_LOCATION][CONF_LONGITUDE]
|
||||||
|
self._radius = user_input[CONF_LOCATION][CONF_RADIUS]
|
||||||
|
|
||||||
|
client: OpenAPI = OpenAPI()
|
||||||
|
self._stations = {
|
||||||
|
x[API_STATION_MAC_ADDRESS]: x
|
||||||
|
for x in await client.get_devices_by_location(
|
||||||
|
self._latitude,
|
||||||
|
self._longitude,
|
||||||
|
radius=DistanceConverter.convert(
|
||||||
|
self._radius,
|
||||||
|
UnitOfLength.METERS,
|
||||||
|
UnitOfLength.MILES,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Filter out indoor stations
|
||||||
|
self._stations = dict(
|
||||||
|
filter(
|
||||||
|
lambda item: not item[1]
|
||||||
|
.get(API_STATION_INFO, {})
|
||||||
|
.get(API_STATION_INDOOR, False),
|
||||||
|
self._stations.items(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._stations:
|
||||||
|
return await self.async_step_station()
|
||||||
|
|
||||||
|
errors = {"base": "no_stations_found"}
|
||||||
|
|
||||||
|
schema: vol.Schema = self.add_suggested_values_to_schema(
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_LOCATION,
|
||||||
|
): LocationSelector(LocationSelectorConfig(radius=True)),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{
|
||||||
|
CONF_LOCATION: {
|
||||||
|
CONF_LATITUDE: self.hass.config.latitude,
|
||||||
|
CONF_LONGITUDE: self.hass.config.longitude,
|
||||||
|
CONF_RADIUS: CONF_RADIUS_DEFAULT,
|
||||||
|
}
|
||||||
|
if not errors
|
||||||
|
else {
|
||||||
|
CONF_LATITUDE: self._latitude,
|
||||||
|
CONF_LONGITUDE: self._longitude,
|
||||||
|
CONF_RADIUS: self._radius,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id=CONF_USER, data_schema=schema, errors=errors if errors else {}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_station(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle the second step to select the station."""
|
||||||
|
|
||||||
|
if user_input:
|
||||||
|
mac_address = user_input[CONF_STATION]
|
||||||
|
await self.async_set_unique_id(mac_address)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=get_station_name(self._stations[mac_address]),
|
||||||
|
data={CONF_MAC: mac_address},
|
||||||
|
)
|
||||||
|
|
||||||
|
options: list[SelectOptionDict] = [
|
||||||
|
SelectOptionDict(
|
||||||
|
label=get_station_name(station),
|
||||||
|
value=mac_address,
|
||||||
|
)
|
||||||
|
for mac_address, station in self._stations.items()
|
||||||
|
]
|
||||||
|
|
||||||
|
schema: vol.Schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_STATION): SelectSelector(
|
||||||
|
SelectSelectorConfig(options=options, multiple=False, sort=True),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id=CONF_STATION,
|
||||||
|
data_schema=schema,
|
||||||
|
)
|
16
homeassistant/components/ambient_network/const.py
Normal file
16
homeassistant/components/ambient_network/const.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""Constants for the Ambient Weather Network integration."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
DOMAIN = "ambient_network"
|
||||||
|
|
||||||
|
API_LAST_DATA = "lastData"
|
||||||
|
API_STATION_COORDS = "coords"
|
||||||
|
API_STATION_INDOOR = "indoor"
|
||||||
|
API_STATION_INFO = "info"
|
||||||
|
API_STATION_LOCATION = "location"
|
||||||
|
API_STATION_NAME = "name"
|
||||||
|
API_STATION_MAC_ADDRESS = "macAddress"
|
||||||
|
API_STATION_TYPE = "stationtype"
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__package__)
|
65
homeassistant/components/ambient_network/coordinator.py
Normal file
65
homeassistant/components/ambient_network/coordinator.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"""DataUpdateCoordinator for the Ambient Weather Network integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
|
from aioambient import OpenAPI
|
||||||
|
from aioambient.errors import RequestError
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_MAC
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
|
from .const import API_LAST_DATA, DOMAIN, LOGGER
|
||||||
|
from .helper import get_station_name
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(minutes=5)
|
||||||
|
|
||||||
|
|
||||||
|
class AmbientNetworkDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
|
"""The Ambient Network Data Update Coordinator."""
|
||||||
|
|
||||||
|
config_entry: ConfigEntry
|
||||||
|
station_name: str
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, api: OpenAPI) -> None:
|
||||||
|
"""Initialize the coordinator."""
|
||||||
|
super().__init__(hass, LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
|
||||||
|
self.api = api
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
|
"""Fetch the latest data from the Ambient Network."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await self.api.get_device_details(
|
||||||
|
self.config_entry.data[CONF_MAC]
|
||||||
|
)
|
||||||
|
except RequestError as ex:
|
||||||
|
raise UpdateFailed("Cannot connect to Ambient Network") from ex
|
||||||
|
|
||||||
|
self.station_name = get_station_name(response)
|
||||||
|
|
||||||
|
if (last_data := response.get(API_LAST_DATA)) is None:
|
||||||
|
raise UpdateFailed(
|
||||||
|
f"Station '{self.config_entry.title}' did not report any data"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Eliminate data if the station hasn't been updated for a while.
|
||||||
|
if (created_at := last_data.get("created_at")) is None:
|
||||||
|
raise UpdateFailed(
|
||||||
|
f"Station '{self.config_entry.title}' did not report a time stamp"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Eliminate data that has been generated more than an hour ago. The station is
|
||||||
|
# probably offline.
|
||||||
|
if int(created_at / 1000) < int(
|
||||||
|
(datetime.now() - timedelta(hours=1)).timestamp()
|
||||||
|
):
|
||||||
|
raise UpdateFailed(
|
||||||
|
f"Station '{self.config_entry.title}' reported stale data"
|
||||||
|
)
|
||||||
|
|
||||||
|
return cast(dict[str, Any], last_data)
|
50
homeassistant/components/ambient_network/entity.py
Normal file
50
homeassistant/components/ambient_network/entity.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
"""Base entity class for the Ambient Weather Network integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||||
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import AmbientNetworkDataUpdateCoordinator
|
||||||
|
|
||||||
|
|
||||||
|
class AmbientNetworkEntity(CoordinatorEntity[AmbientNetworkDataUpdateCoordinator]):
|
||||||
|
"""Entity class for Ambient network devices."""
|
||||||
|
|
||||||
|
_attr_attribution = "Data provided by ambientnetwork.net"
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: AmbientNetworkDataUpdateCoordinator,
|
||||||
|
description: EntityDescription,
|
||||||
|
mac_address: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the Ambient network entity."""
|
||||||
|
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self.entity_description = description
|
||||||
|
self._attr_unique_id = f"{mac_address}_{description.key}"
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
|
name=coordinator.station_name,
|
||||||
|
identifiers={(DOMAIN, mac_address)},
|
||||||
|
manufacturer="Ambient Weather",
|
||||||
|
)
|
||||||
|
self._update_attrs()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _update_attrs(self) -> None:
|
||||||
|
"""Update state attributes."""
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
"""Get the latest data and updates the state."""
|
||||||
|
|
||||||
|
self._update_attrs()
|
||||||
|
super()._handle_coordinator_update()
|
31
homeassistant/components/ambient_network/helper.py
Normal file
31
homeassistant/components/ambient_network/helper.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"""Helper class for the Ambient Weather Network integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
API_LAST_DATA,
|
||||||
|
API_STATION_COORDS,
|
||||||
|
API_STATION_INFO,
|
||||||
|
API_STATION_LOCATION,
|
||||||
|
API_STATION_NAME,
|
||||||
|
API_STATION_TYPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_station_name(station: dict[str, Any]) -> str:
|
||||||
|
"""Pick a station name.
|
||||||
|
|
||||||
|
Station names can be empty, in which case we construct the name from
|
||||||
|
the location and device type.
|
||||||
|
"""
|
||||||
|
if name := station.get(API_STATION_INFO, {}).get(API_STATION_NAME):
|
||||||
|
return str(name)
|
||||||
|
location = (
|
||||||
|
station.get(API_STATION_INFO, {})
|
||||||
|
.get(API_STATION_COORDS, {})
|
||||||
|
.get(API_STATION_LOCATION)
|
||||||
|
)
|
||||||
|
station_type = station.get(API_LAST_DATA, {}).get(API_STATION_TYPE)
|
||||||
|
return f"{location}{'' if location is None or station_type is None else ' '}{station_type}"
|
21
homeassistant/components/ambient_network/icons.json
Normal file
21
homeassistant/components/ambient_network/icons.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"last_rain": {
|
||||||
|
"default": "mdi:water"
|
||||||
|
},
|
||||||
|
"lightning_strikes_per_day": {
|
||||||
|
"default": "mdi:lightning-bolt"
|
||||||
|
},
|
||||||
|
"lightning_strikes_per_hour": {
|
||||||
|
"default": "mdi:lightning-bolt"
|
||||||
|
},
|
||||||
|
"lightning_distance": {
|
||||||
|
"default": "mdi:lightning-bolt"
|
||||||
|
},
|
||||||
|
"wind_direction": {
|
||||||
|
"default": "mdi:compass-outline"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
homeassistant/components/ambient_network/manifest.json
Normal file
11
homeassistant/components/ambient_network/manifest.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"domain": "ambient_network",
|
||||||
|
"name": "Ambient Weather Network",
|
||||||
|
"codeowners": ["@thomaskistler"],
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/ambient_network",
|
||||||
|
"integration_type": "service",
|
||||||
|
"iot_class": "cloud_polling",
|
||||||
|
"loggers": ["aioambient"],
|
||||||
|
"requirements": ["aioambient==2024.01.0"]
|
||||||
|
}
|
315
homeassistant/components/ambient_network/sensor.py
Normal file
315
homeassistant/components/ambient_network/sensor.py
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
"""Support for Ambient Weather Network sensors."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
|
SensorStateClass,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
|
CONF_MAC,
|
||||||
|
DEGREE,
|
||||||
|
PERCENTAGE,
|
||||||
|
UnitOfIrradiance,
|
||||||
|
UnitOfLength,
|
||||||
|
UnitOfPrecipitationDepth,
|
||||||
|
UnitOfPressure,
|
||||||
|
UnitOfSpeed,
|
||||||
|
UnitOfTemperature,
|
||||||
|
UnitOfVolumetricFlux,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import AmbientNetworkDataUpdateCoordinator
|
||||||
|
from .entity import AmbientNetworkEntity
|
||||||
|
|
||||||
|
TYPE_AQI_PM25 = "aqi_pm25"
|
||||||
|
TYPE_AQI_PM25_24H = "aqi_pm25_24h"
|
||||||
|
TYPE_BAROMABSIN = "baromabsin"
|
||||||
|
TYPE_BAROMRELIN = "baromrelin"
|
||||||
|
TYPE_CO2 = "co2"
|
||||||
|
TYPE_DAILYRAININ = "dailyrainin"
|
||||||
|
TYPE_DEWPOINT = "dewPoint"
|
||||||
|
TYPE_EVENTRAININ = "eventrainin"
|
||||||
|
TYPE_FEELSLIKE = "feelsLike"
|
||||||
|
TYPE_HOURLYRAININ = "hourlyrainin"
|
||||||
|
TYPE_HUMIDITY = "humidity"
|
||||||
|
TYPE_LASTRAIN = "lastRain"
|
||||||
|
TYPE_LIGHTNING_DISTANCE = "lightning_distance"
|
||||||
|
TYPE_LIGHTNING_PER_DAY = "lightning_day"
|
||||||
|
TYPE_LIGHTNING_PER_HOUR = "lightning_hour"
|
||||||
|
TYPE_MAXDAILYGUST = "maxdailygust"
|
||||||
|
TYPE_MONTHLYRAININ = "monthlyrainin"
|
||||||
|
TYPE_PM25 = "pm25"
|
||||||
|
TYPE_PM25_24H = "pm25_24h"
|
||||||
|
TYPE_SOLARRADIATION = "solarradiation"
|
||||||
|
TYPE_TEMPF = "tempf"
|
||||||
|
TYPE_UV = "uv"
|
||||||
|
TYPE_WEEKLYRAININ = "weeklyrainin"
|
||||||
|
TYPE_WINDDIR = "winddir"
|
||||||
|
TYPE_WINDGUSTMPH = "windgustmph"
|
||||||
|
TYPE_WINDSPEEDMPH = "windspeedmph"
|
||||||
|
TYPE_YEARLYRAININ = "yearlyrainin"
|
||||||
|
|
||||||
|
|
||||||
|
SENSOR_DESCRIPTIONS = (
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_AQI_PM25,
|
||||||
|
translation_key="pm25_aqi",
|
||||||
|
device_class=SensorDeviceClass.AQI,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=0,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_AQI_PM25_24H,
|
||||||
|
translation_key="pm25_aqi_24h_average",
|
||||||
|
device_class=SensorDeviceClass.AQI,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=0,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_BAROMABSIN,
|
||||||
|
translation_key="absolute_pressure",
|
||||||
|
native_unit_of_measurement=UnitOfPressure.INHG,
|
||||||
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_BAROMRELIN,
|
||||||
|
translation_key="relative_pressure",
|
||||||
|
native_unit_of_measurement=UnitOfPressure.INHG,
|
||||||
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_CO2,
|
||||||
|
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||||
|
device_class=SensorDeviceClass.CO2,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_DAILYRAININ,
|
||||||
|
translation_key="daily_rain",
|
||||||
|
native_unit_of_measurement=UnitOfPrecipitationDepth.INCHES,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_DEWPOINT,
|
||||||
|
translation_key="dew_point",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_FEELSLIKE,
|
||||||
|
translation_key="feels_like",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_HOURLYRAININ,
|
||||||
|
translation_key="hourly_rain",
|
||||||
|
native_unit_of_measurement=UnitOfVolumetricFlux.INCHES_PER_HOUR,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_HUMIDITY,
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_LASTRAIN,
|
||||||
|
translation_key="last_rain",
|
||||||
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_LIGHTNING_PER_DAY,
|
||||||
|
translation_key="lightning_strikes_per_day",
|
||||||
|
native_unit_of_measurement="strikes",
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_LIGHTNING_PER_HOUR,
|
||||||
|
translation_key="lightning_strikes_per_hour",
|
||||||
|
native_unit_of_measurement="strikes/hour",
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_LIGHTNING_DISTANCE,
|
||||||
|
translation_key="lightning_distance",
|
||||||
|
native_unit_of_measurement=UnitOfLength.MILES,
|
||||||
|
device_class=SensorDeviceClass.DISTANCE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_MAXDAILYGUST,
|
||||||
|
translation_key="max_daily_gust",
|
||||||
|
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
||||||
|
device_class=SensorDeviceClass.WIND_SPEED,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_MONTHLYRAININ,
|
||||||
|
translation_key="monthly_rain",
|
||||||
|
native_unit_of_measurement=UnitOfPrecipitationDepth.INCHES,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_PM25_24H,
|
||||||
|
translation_key="pm25_24h_average",
|
||||||
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
device_class=SensorDeviceClass.PM25,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_PM25,
|
||||||
|
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
device_class=SensorDeviceClass.PM25,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_SOLARRADIATION,
|
||||||
|
native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
|
||||||
|
device_class=SensorDeviceClass.IRRADIANCE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_TEMPF,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_UV,
|
||||||
|
translation_key="uv_index",
|
||||||
|
native_unit_of_measurement="index",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_WEEKLYRAININ,
|
||||||
|
translation_key="weekly_rain",
|
||||||
|
native_unit_of_measurement=UnitOfPrecipitationDepth.INCHES,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_WINDDIR,
|
||||||
|
translation_key="wind_direction",
|
||||||
|
native_unit_of_measurement=DEGREE,
|
||||||
|
suggested_display_precision=0,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_WINDGUSTMPH,
|
||||||
|
translation_key="wind_gust",
|
||||||
|
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
||||||
|
device_class=SensorDeviceClass.WIND_SPEED,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_WINDSPEEDMPH,
|
||||||
|
native_unit_of_measurement=UnitOfSpeed.MILES_PER_HOUR,
|
||||||
|
device_class=SensorDeviceClass.WIND_SPEED,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key=TYPE_YEARLYRAININ,
|
||||||
|
translation_key="yearly_rain",
|
||||||
|
native_unit_of_measurement=UnitOfPrecipitationDepth.INCHES,
|
||||||
|
device_class=SensorDeviceClass.PRECIPITATION,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
suggested_display_precision=2,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up the Ambient Network sensor entities."""
|
||||||
|
|
||||||
|
coordinator: AmbientNetworkDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
if coordinator.config_entry is not None:
|
||||||
|
async_add_entities(
|
||||||
|
AmbientNetworkSensor(
|
||||||
|
coordinator,
|
||||||
|
description,
|
||||||
|
coordinator.config_entry.data[CONF_MAC],
|
||||||
|
)
|
||||||
|
for description in SENSOR_DESCRIPTIONS
|
||||||
|
if coordinator.data.get(description.key) is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AmbientNetworkSensor(AmbientNetworkEntity, SensorEntity):
|
||||||
|
"""A sensor implementation for an Ambient Weather Network sensor."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: AmbientNetworkDataUpdateCoordinator,
|
||||||
|
description: SensorEntityDescription,
|
||||||
|
mac_address: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize a sensor object."""
|
||||||
|
|
||||||
|
super().__init__(coordinator, description, mac_address)
|
||||||
|
|
||||||
|
def _update_attrs(self) -> None:
|
||||||
|
"""Update sensor attributes."""
|
||||||
|
|
||||||
|
value = self.coordinator.data.get(self.entity_description.key)
|
||||||
|
|
||||||
|
# Treatments for special units.
|
||||||
|
if value is not None and self.device_class == SensorDeviceClass.TIMESTAMP:
|
||||||
|
value = datetime.fromtimestamp(value / 1000, tz=dt_util.DEFAULT_TIME_ZONE)
|
||||||
|
|
||||||
|
self._attr_available = value is not None
|
||||||
|
self._attr_native_value = value
|
87
homeassistant/components/ambient_network/strings.json
Normal file
87
homeassistant/components/ambient_network/strings.json
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Select region",
|
||||||
|
"description": "Choose the region you want to survey in order to locate Ambient personal weather stations."
|
||||||
|
},
|
||||||
|
"station": {
|
||||||
|
"title": "Select station",
|
||||||
|
"description": "Select the weather station you want to add to Home Assistant.",
|
||||||
|
"data": {
|
||||||
|
"station": "Station"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"no_stations_found": "Did not find any stations in the selected region."
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"pm25_24h_average": {
|
||||||
|
"name": "PM2.5 (24 hour average)"
|
||||||
|
},
|
||||||
|
"pm25_aqi": {
|
||||||
|
"name": "PM2.5 AQI"
|
||||||
|
},
|
||||||
|
"pm25_aqi_24h_average": {
|
||||||
|
"name": "PM2.5 AQI (24 hour average)"
|
||||||
|
},
|
||||||
|
"absolute_pressure": {
|
||||||
|
"name": "Absolute pressure"
|
||||||
|
},
|
||||||
|
"relative_pressure": {
|
||||||
|
"name": "Relative pressure"
|
||||||
|
},
|
||||||
|
"daily_rain": {
|
||||||
|
"name": "Daily rain"
|
||||||
|
},
|
||||||
|
"dew_point": {
|
||||||
|
"name": "Dew point"
|
||||||
|
},
|
||||||
|
"feels_like": {
|
||||||
|
"name": "Feels like"
|
||||||
|
},
|
||||||
|
"hourly_rain": {
|
||||||
|
"name": "Hourly rain"
|
||||||
|
},
|
||||||
|
"last_rain": {
|
||||||
|
"name": "Last rain"
|
||||||
|
},
|
||||||
|
"lightning_strikes_per_day": {
|
||||||
|
"name": "Lightning strikes per day"
|
||||||
|
},
|
||||||
|
"lightning_strikes_per_hour": {
|
||||||
|
"name": "Lightning strikes per hour"
|
||||||
|
},
|
||||||
|
"lightning_distance": {
|
||||||
|
"name": "Lightning distance"
|
||||||
|
},
|
||||||
|
"max_daily_gust": {
|
||||||
|
"name": "Max daily gust"
|
||||||
|
},
|
||||||
|
"monthly_rain": {
|
||||||
|
"name": "Monthly rain"
|
||||||
|
},
|
||||||
|
"uv_index": {
|
||||||
|
"name": "UV index"
|
||||||
|
},
|
||||||
|
"weekly_rain": {
|
||||||
|
"name": "Weekly rain"
|
||||||
|
},
|
||||||
|
"wind_direction": {
|
||||||
|
"name": "Wind direction"
|
||||||
|
},
|
||||||
|
"wind_gust": {
|
||||||
|
"name": "Wind gust"
|
||||||
|
},
|
||||||
|
"yearly_rain": {
|
||||||
|
"name": "Yearly rain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -42,6 +42,7 @@ FLOWS = {
|
|||||||
"alarmdecoder",
|
"alarmdecoder",
|
||||||
"amberelectric",
|
"amberelectric",
|
||||||
"ambiclimate",
|
"ambiclimate",
|
||||||
|
"ambient_network",
|
||||||
"ambient_station",
|
"ambient_station",
|
||||||
"analytics_insights",
|
"analytics_insights",
|
||||||
"android_ip_webcam",
|
"android_ip_webcam",
|
||||||
|
@ -244,6 +244,12 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "cloud_polling"
|
"iot_class": "cloud_polling"
|
||||||
},
|
},
|
||||||
|
"ambient_network": {
|
||||||
|
"name": "Ambient Weather Network",
|
||||||
|
"integration_type": "service",
|
||||||
|
"config_flow": true,
|
||||||
|
"iot_class": "cloud_polling"
|
||||||
|
},
|
||||||
"ambient_station": {
|
"ambient_station": {
|
||||||
"name": "Ambient Weather Station",
|
"name": "Ambient Weather Station",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
|
10
mypy.ini
10
mypy.ini
@ -421,6 +421,16 @@ disallow_untyped_defs = true
|
|||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unreachable = true
|
warn_unreachable = true
|
||||||
|
|
||||||
|
[mypy-homeassistant.components.ambient_network.*]
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unreachable = true
|
||||||
|
|
||||||
[mypy-homeassistant.components.ambient_station.*]
|
[mypy-homeassistant.components.ambient_station.*]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
|
@ -190,6 +190,7 @@ aioairzone-cloud==0.5.1
|
|||||||
# homeassistant.components.airzone
|
# homeassistant.components.airzone
|
||||||
aioairzone==0.7.6
|
aioairzone==0.7.6
|
||||||
|
|
||||||
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
aioambient==2024.01.0
|
aioambient==2024.01.0
|
||||||
|
|
||||||
|
@ -169,6 +169,7 @@ aioairzone-cloud==0.5.1
|
|||||||
# homeassistant.components.airzone
|
# homeassistant.components.airzone
|
||||||
aioairzone==0.7.6
|
aioairzone==0.7.6
|
||||||
|
|
||||||
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
aioambient==2024.01.0
|
aioambient==2024.01.0
|
||||||
|
|
||||||
|
1
tests/components/ambient_network/__init__.py
Normal file
1
tests/components/ambient_network/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Ambient Weather Network integration."""
|
91
tests/components/ambient_network/conftest.py
Normal file
91
tests/components/ambient_network/conftest.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
"""Common fixtures for the Ambient Weather Network integration tests."""
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
|
from aioambient import OpenAPI
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components import ambient_network
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from tests.common import (
|
||||||
|
MockConfigEntry,
|
||||||
|
load_json_array_fixture,
|
||||||
|
load_json_object_fixture,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||||
|
"""Override async_setup_entry."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.ambient_network.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup_entry:
|
||||||
|
yield mock_setup_entry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="devices_by_location", scope="package")
|
||||||
|
def devices_by_location_fixture() -> list[dict[str, Any]]:
|
||||||
|
"""Return result of OpenAPI get_devices_by_location() call."""
|
||||||
|
return load_json_array_fixture(
|
||||||
|
"devices_by_location_response.json", "ambient_network"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def mock_device_details_callable(mac_address: str) -> dict[str, Any]:
|
||||||
|
"""Return result of OpenAPI get_device_details() call."""
|
||||||
|
return load_json_object_fixture(
|
||||||
|
f"device_details_response_{mac_address[0].lower()}.json", "ambient_network"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="open_api")
|
||||||
|
def mock_open_api() -> OpenAPI:
|
||||||
|
"""Mock OpenAPI object."""
|
||||||
|
return Mock(
|
||||||
|
get_device_details=AsyncMock(side_effect=mock_device_details_callable),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="aioambient")
|
||||||
|
async def mock_aioambient(open_api: OpenAPI):
|
||||||
|
"""Mock aioambient library."""
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.ambient_network.config_flow.OpenAPI",
|
||||||
|
return_value=open_api,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.ambient_network.OpenAPI",
|
||||||
|
return_value=open_api,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="config_entry")
|
||||||
|
def config_entry_fixture(request) -> MockConfigEntry:
|
||||||
|
"""Mock config entry."""
|
||||||
|
return MockConfigEntry(
|
||||||
|
domain=ambient_network.DOMAIN,
|
||||||
|
title=f"Station {request.param[0]}",
|
||||||
|
data={"mac": request.param},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_platform(
|
||||||
|
expected_outcome: bool,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
):
|
||||||
|
"""Load the Ambient Network integration with the provided OpenAPI and config entry."""
|
||||||
|
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
assert (
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id) == expected_outcome
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
return
|
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"macAddress": "AA:AA:AA:AA:AA:AA",
|
||||||
|
"lastData": {
|
||||||
|
"stationtype": "AMBWeatherPro_V5.0.6",
|
||||||
|
"dateutc": 1699474320000,
|
||||||
|
"tempf": 82.9,
|
||||||
|
"dewPoint": 82.0,
|
||||||
|
"feelsLike": 85.0,
|
||||||
|
"humidity": 60,
|
||||||
|
"windspeedmph": 8.72,
|
||||||
|
"windgustmph": 9.17,
|
||||||
|
"maxdailygust": 22.82,
|
||||||
|
"winddir": 11,
|
||||||
|
"uv": 0,
|
||||||
|
"solarradiation": 37.64,
|
||||||
|
"hourlyrainin": 0,
|
||||||
|
"dailyrainin": 0,
|
||||||
|
"weeklyrainin": 0,
|
||||||
|
"monthlyrainin": 0,
|
||||||
|
"totalrainin": 26.402,
|
||||||
|
"baromrelin": 29.586,
|
||||||
|
"baromabsin": 28.869,
|
||||||
|
"batt_co2": 1,
|
||||||
|
"type": "weather-data",
|
||||||
|
"created_at": 1699474320914,
|
||||||
|
"dateutc5": 1699474200000,
|
||||||
|
"lastRain": 1698659100000,
|
||||||
|
"tz": "America/Chicago"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"name": "Station A"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"_id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||||
|
"macAddress": "BB:BB:BB:BB:BB:BB",
|
||||||
|
"info": {
|
||||||
|
"name": "Station B"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"_id": "cccccccccccccccccccccccccccccccc",
|
||||||
|
"macAddress": "CC:CC:CC:CC:CC:CC",
|
||||||
|
"lastData": {
|
||||||
|
"stationtype": "AMBWeatherPro_V5.0.6",
|
||||||
|
"dateutc": 1699474320000,
|
||||||
|
"tempf": 82.9,
|
||||||
|
"dewPoint": 82.0,
|
||||||
|
"feelsLike": 85.0,
|
||||||
|
"humidity": 60,
|
||||||
|
"windspeedmph": 8.72,
|
||||||
|
"windgustmph": 9.17,
|
||||||
|
"maxdailygust": 22.82,
|
||||||
|
"winddir": 11,
|
||||||
|
"uv": 0,
|
||||||
|
"solarradiation": 37.64,
|
||||||
|
"hourlyrainin": 0,
|
||||||
|
"dailyrainin": 0,
|
||||||
|
"weeklyrainin": 0,
|
||||||
|
"monthlyrainin": 0,
|
||||||
|
"totalrainin": 26.402,
|
||||||
|
"baromrelin": 29.586,
|
||||||
|
"baromabsin": 28.869,
|
||||||
|
"batt_co2": 1,
|
||||||
|
"type": "weather-data",
|
||||||
|
"dateutc5": 1699474200000,
|
||||||
|
"lastRain": 1698659100000,
|
||||||
|
"tz": "America/Chicago"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"name": "Station C"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,364 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"_id": "aaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
"macAddress": "AA:AA:AA:AA:AA:AA",
|
||||||
|
"lastData": {
|
||||||
|
"stationtype": "AMBWeatherPro_V5.0.6",
|
||||||
|
"dateutc": 1699474320000,
|
||||||
|
"tempf": 82.9,
|
||||||
|
"humidity": 60,
|
||||||
|
"windspeedmph": 8.72,
|
||||||
|
"windgustmph": 9.17,
|
||||||
|
"maxdailygust": 22.82,
|
||||||
|
"winddir": 11,
|
||||||
|
"uv": 0,
|
||||||
|
"solarradiation": 37.64,
|
||||||
|
"hourlyrainin": 0,
|
||||||
|
"dailyrainin": 0,
|
||||||
|
"weeklyrainin": 0,
|
||||||
|
"monthlyrainin": 0,
|
||||||
|
"totalrainin": 26.402,
|
||||||
|
"baromrelin": 29.586,
|
||||||
|
"baromabsin": 28.869,
|
||||||
|
"batt_co2": 1,
|
||||||
|
"type": "weather-data",
|
||||||
|
"created_at": 1699474320914,
|
||||||
|
"dateutc5": 1699474200000,
|
||||||
|
"lastRain": 1698659100000,
|
||||||
|
"tz": "America/Chicago"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"name": "Station A",
|
||||||
|
"coords": {
|
||||||
|
"geo": {
|
||||||
|
"coordinates": [-97.0, 32.0],
|
||||||
|
"type": "Point"
|
||||||
|
},
|
||||||
|
"elevation": 237.0,
|
||||||
|
"location": "Location A",
|
||||||
|
"coords": {
|
||||||
|
"lon": -97.0,
|
||||||
|
"lat": 32.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indoor": false,
|
||||||
|
"slug": "aaaaaaaaaaaaaaaaaaaaaaaa"
|
||||||
|
},
|
||||||
|
"tz": {
|
||||||
|
"name": "America/Chicago"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "bbbbbbbbbbbbbbbbbbbbbbbb",
|
||||||
|
"macAddress": "BB:BB:BB:BB:BB:BB",
|
||||||
|
"lastData": {
|
||||||
|
"stationtype": "AMBWeatherV4.2.6",
|
||||||
|
"dateutc": 1700716980000,
|
||||||
|
"baromrelin": 29.342,
|
||||||
|
"baromabsin": 29.342,
|
||||||
|
"tempf": 35.8,
|
||||||
|
"humidity": 88,
|
||||||
|
"winddir": 237,
|
||||||
|
"winddir_avg10m": 221,
|
||||||
|
"windspeedmph": 0,
|
||||||
|
"windspdmph_avg10m": 0,
|
||||||
|
"windgustmph": 1.3,
|
||||||
|
"maxdailygust": 12.3,
|
||||||
|
"hourlyrainin": 0,
|
||||||
|
"eventrainin": 0,
|
||||||
|
"dailyrainin": 0,
|
||||||
|
"weeklyrainin": 0.024,
|
||||||
|
"monthlyrainin": 0.331,
|
||||||
|
"yearlyrainin": 12.382,
|
||||||
|
"solarradiation": 0,
|
||||||
|
"uv": 0,
|
||||||
|
"soilhum2": 0,
|
||||||
|
"type": "weather-data",
|
||||||
|
"created_at": 1700717004020,
|
||||||
|
"dateutc5": 1700716800000,
|
||||||
|
"lastRain": 1700445000000,
|
||||||
|
"discreets": {
|
||||||
|
"humidity1": [41, 42, 43]
|
||||||
|
},
|
||||||
|
"tz": "America/Chicago"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"name": "Station B",
|
||||||
|
"coords": {
|
||||||
|
"coords": {
|
||||||
|
"lat": 32.0,
|
||||||
|
"lon": -97.0
|
||||||
|
},
|
||||||
|
"location": "Location B",
|
||||||
|
"elevation": 226.0,
|
||||||
|
"geo": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [-97.0, 32.0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indoor": false,
|
||||||
|
"slug": "bbbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
|
},
|
||||||
|
"tz": {
|
||||||
|
"name": "America/Chicago"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "cccccccccccccccccccccccc",
|
||||||
|
"macAddress": "CC:CC:CC:CC:CC:CC",
|
||||||
|
"lastData": {},
|
||||||
|
"info": {
|
||||||
|
"name": "Station C",
|
||||||
|
"coords": {
|
||||||
|
"geo": {
|
||||||
|
"coordinates": [-97.0, 32.0],
|
||||||
|
"type": "Point"
|
||||||
|
},
|
||||||
|
"elevation": 242.0,
|
||||||
|
"location": "Location C",
|
||||||
|
"coords": {
|
||||||
|
"lon": -97.0,
|
||||||
|
"lat": 32.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indoor": false,
|
||||||
|
"slug": "cccccccccccccccccccccccc"
|
||||||
|
},
|
||||||
|
"tz": {
|
||||||
|
"name": "America/Chicago"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "dddddddddddddddddddddddd",
|
||||||
|
"macAddress": "DD:DD:DD:DD:DD:DD",
|
||||||
|
"lastData": {
|
||||||
|
"stationtype": "AMBWeatherPro_V5.1.3",
|
||||||
|
"dateutc": 1700716920000,
|
||||||
|
"tempf": 38.1,
|
||||||
|
"humidity": 85,
|
||||||
|
"windspeedmph": 0,
|
||||||
|
"windgustmph": 0,
|
||||||
|
"maxdailygust": 0,
|
||||||
|
"winddir": 89,
|
||||||
|
"uv": 0,
|
||||||
|
"solarradiation": 0,
|
||||||
|
"hourlyrainin": 0,
|
||||||
|
"eventrainin": 0,
|
||||||
|
"dailyrainin": 0,
|
||||||
|
"weeklyrainin": 0.028,
|
||||||
|
"monthlyrainin": 0.327,
|
||||||
|
"yearlyrainin": 12.76,
|
||||||
|
"totalrainin": 12.76,
|
||||||
|
"baromrelin": 29.731,
|
||||||
|
"baromabsin": 29.338,
|
||||||
|
"type": "weather-data",
|
||||||
|
"created_at": 1700716969446,
|
||||||
|
"dateutc5": 1700716800000,
|
||||||
|
"lastRain": 1700449500000,
|
||||||
|
"tz": "America/Chicago"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"name": "Station D",
|
||||||
|
"coords": {
|
||||||
|
"coords": {
|
||||||
|
"lat": 32.0,
|
||||||
|
"lon": -97.0
|
||||||
|
},
|
||||||
|
"address": "",
|
||||||
|
"location": "Location D",
|
||||||
|
"elevation": 221.0,
|
||||||
|
"address_components": [
|
||||||
|
{
|
||||||
|
"long_name": "1234",
|
||||||
|
"short_name": "1234",
|
||||||
|
"types": ["street_number"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"long_name": "D Street",
|
||||||
|
"short_name": "D St.",
|
||||||
|
"types": ["route"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"long_name": "D Town",
|
||||||
|
"short_name": "D Town",
|
||||||
|
"types": ["locality", "political"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"long_name": "D County",
|
||||||
|
"short_name": "D County",
|
||||||
|
"types": ["administrative_area_level_2", "political"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"long_name": "Delaware",
|
||||||
|
"short_name": "DE",
|
||||||
|
"types": ["administrative_area_level_1", "political"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"long_name": "United States",
|
||||||
|
"short_name": "US",
|
||||||
|
"types": ["country", "political"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"long_name": "12345",
|
||||||
|
"short_name": "12345",
|
||||||
|
"types": ["postal_code"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"geo": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [-97.0, 32.0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indoor": false,
|
||||||
|
"slug": "dddddddddddddddddddddddd"
|
||||||
|
},
|
||||||
|
"tz": {
|
||||||
|
"name": "America/Chicago"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "eeeeeeeeeeeeeeeeeeeeeeee",
|
||||||
|
"macAddress": "EE:EE:EE:EE:EE:EE",
|
||||||
|
"lastData": {
|
||||||
|
"stationtype": "AMBWeatherV4.3.4",
|
||||||
|
"dateutc": 1700716920000,
|
||||||
|
"baromrelin": 29.238,
|
||||||
|
"baromabsin": 29.238,
|
||||||
|
"tempf": 45,
|
||||||
|
"humidity": 55,
|
||||||
|
"winddir": 98,
|
||||||
|
"winddir_avg10m": 185,
|
||||||
|
"windspeedmph": 1.1,
|
||||||
|
"windspdmph_avg10m": 1.3,
|
||||||
|
"windgustmph": 3.4,
|
||||||
|
"maxdailygust": 12.5,
|
||||||
|
"hourlyrainin": 0,
|
||||||
|
"eventrainin": 0,
|
||||||
|
"dailyrainin": 0,
|
||||||
|
"weeklyrainin": 0.059,
|
||||||
|
"monthlyrainin": 0.39,
|
||||||
|
"yearlyrainin": 31.268,
|
||||||
|
"lightning_day": 1,
|
||||||
|
"lightning_time": 1700700515000,
|
||||||
|
"lightning_distance": 8.7,
|
||||||
|
"batt_lightning": 0,
|
||||||
|
"solarradiation": 0,
|
||||||
|
"uv": 0,
|
||||||
|
"batt_co2": 1,
|
||||||
|
"type": "weather-data",
|
||||||
|
"created_at": 1700716954726,
|
||||||
|
"dateutc5": 1700716800000,
|
||||||
|
"lastRain": 1700445300000,
|
||||||
|
"lightnings": [
|
||||||
|
[1700713320000, 0],
|
||||||
|
[1700713380000, 0],
|
||||||
|
[1700713440000, 0],
|
||||||
|
[1700713500000, 0],
|
||||||
|
[1700713560000, 0],
|
||||||
|
[1700713620000, 0],
|
||||||
|
[1700713680000, 0],
|
||||||
|
[1700713740000, 0],
|
||||||
|
[1700713800000, 0],
|
||||||
|
[1700713860000, 0],
|
||||||
|
[1700713920000, 0],
|
||||||
|
[1700713980000, 0],
|
||||||
|
[1700714040000, 0],
|
||||||
|
[1700714100000, 0],
|
||||||
|
[1700714160000, 0],
|
||||||
|
[1700714220000, 0],
|
||||||
|
[1700714280000, 0],
|
||||||
|
[1700714340000, 0],
|
||||||
|
[1700714400000, 0],
|
||||||
|
[1700714460000, 0],
|
||||||
|
[1700714520000, 0],
|
||||||
|
[1700714580000, 0],
|
||||||
|
[1700714640000, 0],
|
||||||
|
[1700714700000, 0],
|
||||||
|
[1700714760000, 0],
|
||||||
|
[1700714820000, 0],
|
||||||
|
[1700714880000, 0],
|
||||||
|
[1700714940000, 0],
|
||||||
|
[1700715000000, 0],
|
||||||
|
[1700715060000, 0],
|
||||||
|
[1700715120000, 0],
|
||||||
|
[1700715180000, 0],
|
||||||
|
[1700715240000, 0],
|
||||||
|
[1700715300000, 0],
|
||||||
|
[1700715360000, 0],
|
||||||
|
[1700715420000, 0],
|
||||||
|
[1700715480000, 0],
|
||||||
|
[1700715540000, 0],
|
||||||
|
[1700715600000, 0],
|
||||||
|
[1700715660000, 0],
|
||||||
|
[1700715720000, 0],
|
||||||
|
[1700715780000, 0],
|
||||||
|
[1700715840000, 0],
|
||||||
|
[1700715900000, 0],
|
||||||
|
[1700715960000, 0],
|
||||||
|
[1700716020000, 0],
|
||||||
|
[1700716080000, 0],
|
||||||
|
[1700716140000, 0],
|
||||||
|
[1700716200000, 0],
|
||||||
|
[1700716260000, 0],
|
||||||
|
[1700716320000, 0],
|
||||||
|
[1700716380000, 0],
|
||||||
|
[1700716440000, 0],
|
||||||
|
[1700716500000, 0],
|
||||||
|
[1700716560000, 0],
|
||||||
|
[1700716620000, 0],
|
||||||
|
[1700716680000, 0],
|
||||||
|
[1700716740000, 0],
|
||||||
|
[1700716800000, 0],
|
||||||
|
[1700716860000, 0],
|
||||||
|
[1700716920000, 0]
|
||||||
|
],
|
||||||
|
"lightning_hour": 0,
|
||||||
|
"tz": "America/Chicago"
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"name": "Station E",
|
||||||
|
"coords": {
|
||||||
|
"coords": {
|
||||||
|
"lat": 32.0,
|
||||||
|
"lon": -97.0
|
||||||
|
},
|
||||||
|
"location": "Location E",
|
||||||
|
"elevation": 236.0,
|
||||||
|
"geo": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [-97.0, 32.0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indoor": false,
|
||||||
|
"slug": "eeeeeeeeeeeeeeeeeeeeeeee"
|
||||||
|
},
|
||||||
|
"tz": {
|
||||||
|
"name": "America/Chicago"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "ffffffffffffffffffffffff",
|
||||||
|
"macAddress": "FF:FF:FF:FF:FF:FF",
|
||||||
|
"lastData": {},
|
||||||
|
"info": {
|
||||||
|
"name": "",
|
||||||
|
"coords": {
|
||||||
|
"coords": {
|
||||||
|
"lat": 32.0,
|
||||||
|
"lon": -97.0
|
||||||
|
},
|
||||||
|
"location": "Location F",
|
||||||
|
"elevation": 242.0,
|
||||||
|
"geo": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [-97.0, 32.0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indoor": false,
|
||||||
|
"slug": "ffffffffffffffffffffffff"
|
||||||
|
},
|
||||||
|
"tz": {
|
||||||
|
"name": "America/Chicago"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
856
tests/components/ambient_network/snapshots/test_sensor.ambr
Normal file
856
tests/components/ambient_network/snapshots/test_sensor.ambr
Normal file
@ -0,0 +1,856 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_absolute_pressure-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_absolute_pressure',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfPressure.HPA: 'hPa'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.PRESSURE: 'pressure'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Absolute pressure',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'absolute_pressure',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_baromabsin',
|
||||||
|
'unit_of_measurement': <UnitOfPressure.HPA: 'hPa'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_absolute_pressure-state]
|
||||||
|
None
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_daily_rain-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_daily_rain',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfLength.MILLIMETERS: 'mm'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.PRECIPITATION: 'precipitation'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Daily rain',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'daily_rain',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_dailyrainin',
|
||||||
|
'unit_of_measurement': <UnitOfLength.MILLIMETERS: 'mm'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_daily_rain-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by ambientnetwork.net',
|
||||||
|
'device_class': 'precipitation',
|
||||||
|
'friendly_name': 'Station A Daily rain',
|
||||||
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
|
'unit_of_measurement': <UnitOfLength.MILLIMETERS: 'mm'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_a_daily_rain',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '0.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_dew_point-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_dew_point',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Dew point',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'dew_point',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_dewPoint',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_dew_point-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by ambientnetwork.net',
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'Station A Dew point',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_a_dew_point',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '27.7777777777778',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_feels_like-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_feels_like',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Feels like',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'feels_like',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_feelsLike',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_feels_like-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by ambientnetwork.net',
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'Station A Feels like',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_a_feels_like',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '29.4444444444444',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_hourly_rain-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_hourly_rain',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR: 'mm/h'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.PRECIPITATION_INTENSITY: 'precipitation_intensity'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Hourly rain',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'hourly_rain',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_hourlyrainin',
|
||||||
|
'unit_of_measurement': <UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR: 'mm/h'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_hourly_rain-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by ambientnetwork.net',
|
||||||
|
'device_class': 'precipitation_intensity',
|
||||||
|
'friendly_name': 'Station A Hourly rain',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR: 'mm/h'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_a_hourly_rain',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '0.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_humidity-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_humidity',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Humidity',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_humidity',
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_humidity-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by ambientnetwork.net',
|
||||||
|
'device_class': 'humidity',
|
||||||
|
'friendly_name': 'Station A Humidity',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_a_humidity',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '60',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_irradiance-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_irradiance',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.IRRADIANCE: 'irradiance'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Irradiance',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_solarradiation',
|
||||||
|
'unit_of_measurement': <UnitOfIrradiance.WATTS_PER_SQUARE_METER: 'W/m²'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_irradiance-state]
|
||||||
|
None
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_last_rain-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_last_rain',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Last rain',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'last_rain',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_lastRain',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_last_rain-state]
|
||||||
|
None
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_max_daily_gust-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_max_daily_gust',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.WIND_SPEED: 'wind_speed'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Max daily gust',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'max_daily_gust',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_maxdailygust',
|
||||||
|
'unit_of_measurement': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_max_daily_gust-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by ambientnetwork.net',
|
||||||
|
'device_class': 'wind_speed',
|
||||||
|
'friendly_name': 'Station A Max daily gust',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_a_max_daily_gust',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '36.72523008',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_monthly_rain-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_monthly_rain',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfLength.MILLIMETERS: 'mm'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.PRECIPITATION: 'precipitation'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Monthly rain',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'monthly_rain',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_monthlyrainin',
|
||||||
|
'unit_of_measurement': <UnitOfLength.MILLIMETERS: 'mm'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_monthly_rain-state]
|
||||||
|
None
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_relative_pressure-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_relative_pressure',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfPressure.HPA: 'hPa'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.PRESSURE: 'pressure'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Relative pressure',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'relative_pressure',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_baromrelin',
|
||||||
|
'unit_of_measurement': <UnitOfPressure.HPA: 'hPa'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_relative_pressure-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by ambientnetwork.net',
|
||||||
|
'device_class': 'pressure',
|
||||||
|
'friendly_name': 'Station A Relative pressure',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfPressure.HPA: 'hPa'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_a_relative_pressure',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '1001.89694313129',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_temperature-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_temperature',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Temperature',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_tempf',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_temperature-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by ambientnetwork.net',
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'Station A Temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_a_temperature',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '28.2777777777778',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_uv_index-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_uv_index',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'UV index',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'uv_index',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_uv',
|
||||||
|
'unit_of_measurement': 'index',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_uv_index-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by ambientnetwork.net',
|
||||||
|
'friendly_name': 'Station A UV index',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': 'index',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_a_uv_index',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_weekly_rain-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_weekly_rain',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfLength.MILLIMETERS: 'mm'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.PRECIPITATION: 'precipitation'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Weekly rain',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'weekly_rain',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_weeklyrainin',
|
||||||
|
'unit_of_measurement': <UnitOfLength.MILLIMETERS: 'mm'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_weekly_rain-state]
|
||||||
|
None
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_wind_direction-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_wind_direction',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Wind direction',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'wind_direction',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_winddir',
|
||||||
|
'unit_of_measurement': '°',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_wind_direction-state]
|
||||||
|
None
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_wind_gust-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_wind_gust',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.WIND_SPEED: 'wind_speed'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Wind gust',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'wind_gust',
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_windgustmph',
|
||||||
|
'unit_of_measurement': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_wind_gust-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by ambientnetwork.net',
|
||||||
|
'device_class': 'wind_speed',
|
||||||
|
'friendly_name': 'Station A Wind gust',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_a_wind_gust',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '14.75768448',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_wind_speed-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_a_wind_speed',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.WIND_SPEED: 'wind_speed'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Wind speed',
|
||||||
|
'platform': 'ambient_network',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': 'AA:AA:AA:AA:AA:AA_windspeedmph',
|
||||||
|
'unit_of_measurement': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[AA:AA:AA:AA:AA:AA][sensor.station_a_wind_speed-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by ambientnetwork.net',
|
||||||
|
'device_class': 'wind_speed',
|
||||||
|
'friendly_name': 'Station A Wind speed',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_a_wind_speed',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '14.03347968',
|
||||||
|
})
|
||||||
|
# ---
|
85
tests/components/ambient_network/test_config_flow.py
Normal file
85
tests/components/ambient_network/test_config_flow.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
"""Test the Ambient Weather Network config flow."""
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
|
from aioambient import OpenAPI
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.ambient_network.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import SOURCE_USER, ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("config_entry", ["AA:AA:AA:AA:AA:AA"], indirect=True)
|
||||||
|
async def test_happy_path(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_setup_entry: AsyncMock,
|
||||||
|
open_api: OpenAPI,
|
||||||
|
aioambient: AsyncMock,
|
||||||
|
devices_by_location: list[dict[str, Any]],
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test the happy path."""
|
||||||
|
|
||||||
|
setup_result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert setup_result["type"] == FlowResultType.FORM
|
||||||
|
assert setup_result["step_id"] == "user"
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
open_api,
|
||||||
|
"get_devices_by_location",
|
||||||
|
AsyncMock(return_value=devices_by_location),
|
||||||
|
):
|
||||||
|
user_result = await hass.config_entries.flow.async_configure(
|
||||||
|
setup_result["flow_id"],
|
||||||
|
{"location": {"latitude": 10.0, "longitude": 20.0, "radius": 1.0}},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert user_result["type"] == FlowResultType.FORM
|
||||||
|
assert user_result["step_id"] == "station"
|
||||||
|
|
||||||
|
stations_result = await hass.config_entries.flow.async_configure(
|
||||||
|
user_result["flow_id"],
|
||||||
|
{
|
||||||
|
"station": "AA:AA:AA:AA:AA:AA",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert stations_result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
|
assert stations_result["title"] == config_entry.title
|
||||||
|
assert stations_result["data"] == config_entry.data
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_station_found(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
aioambient: AsyncMock,
|
||||||
|
open_api: OpenAPI,
|
||||||
|
) -> None:
|
||||||
|
"""Test that we abort when we cannot find a station in the area."""
|
||||||
|
|
||||||
|
setup_result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert setup_result["type"] == FlowResultType.FORM
|
||||||
|
assert setup_result["step_id"] == "user"
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
open_api,
|
||||||
|
"get_devices_by_location",
|
||||||
|
AsyncMock(return_value=[]),
|
||||||
|
):
|
||||||
|
user_result = await hass.config_entries.flow.async_configure(
|
||||||
|
setup_result["flow_id"],
|
||||||
|
{"location": {"latitude": 10.0, "longitude": 20.0, "radius": 1.0}},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert user_result["type"] == FlowResultType.FORM
|
||||||
|
assert user_result["step_id"] == "user"
|
||||||
|
assert user_result["errors"] == {"base": "no_stations_found"}
|
123
tests/components/ambient_network/test_sensor.py
Normal file
123
tests/components/ambient_network/test_sensor.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
"""Test Ambient Weather Network sensors."""
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from aioambient import OpenAPI
|
||||||
|
from aioambient.errors import RequestError
|
||||||
|
from freezegun import freeze_time
|
||||||
|
import pytest
|
||||||
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from .conftest import setup_platform
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2023-11-08")
|
||||||
|
@pytest.mark.parametrize("config_entry", ["AA:AA:AA:AA:AA:AA"], indirect=True)
|
||||||
|
async def test_sensors(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
open_api: OpenAPI,
|
||||||
|
aioambient,
|
||||||
|
config_entry,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
) -> None:
|
||||||
|
"""Test all sensors under normal operation."""
|
||||||
|
await setup_platform(True, hass, config_entry)
|
||||||
|
|
||||||
|
entity_entries = er.async_entries_for_config_entry(
|
||||||
|
entity_registry, config_entry.entry_id
|
||||||
|
)
|
||||||
|
|
||||||
|
assert entity_entries
|
||||||
|
for entity_entry in entity_entries:
|
||||||
|
assert hass.states.get(entity_entry.entity_id) == snapshot(
|
||||||
|
name=f"{entity_entry.entity_id}-state"
|
||||||
|
)
|
||||||
|
assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry")
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2023-11-09")
|
||||||
|
@pytest.mark.parametrize("config_entry", ["AA:AA:AA:AA:AA:AA"], indirect=True)
|
||||||
|
async def test_sensors_with_stale_data(
|
||||||
|
hass: HomeAssistant, open_api: OpenAPI, aioambient, config_entry
|
||||||
|
) -> None:
|
||||||
|
"""Test that the sensors are not populated if the data is stale."""
|
||||||
|
await setup_platform(False, hass, config_entry)
|
||||||
|
|
||||||
|
sensor = hass.states.get("sensor.station_a_absolute_pressure")
|
||||||
|
assert sensor is None
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2023-11-08")
|
||||||
|
@pytest.mark.parametrize("config_entry", ["BB:BB:BB:BB:BB:BB"], indirect=True)
|
||||||
|
async def test_sensors_with_no_data(
|
||||||
|
hass: HomeAssistant, open_api: OpenAPI, aioambient, config_entry
|
||||||
|
) -> None:
|
||||||
|
"""Test that the sensors are not populated if the last data is absent."""
|
||||||
|
await setup_platform(False, hass, config_entry)
|
||||||
|
|
||||||
|
sensor = hass.states.get("sensor.station_b_absolute_pressure")
|
||||||
|
assert sensor is None
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2023-11-08")
|
||||||
|
@pytest.mark.parametrize("config_entry", ["CC:CC:CC:CC:CC:CC"], indirect=True)
|
||||||
|
async def test_sensors_with_no_update_time(
|
||||||
|
hass: HomeAssistant, open_api: OpenAPI, aioambient, config_entry
|
||||||
|
) -> None:
|
||||||
|
"""Test that the sensors are not populated if the update time is missing."""
|
||||||
|
await setup_platform(False, hass, config_entry)
|
||||||
|
|
||||||
|
sensor = hass.states.get("sensor.station_c_absolute_pressure")
|
||||||
|
assert sensor is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("config_entry", ["AA:AA:AA:AA:AA:AA"], indirect=True)
|
||||||
|
async def test_sensors_disappearing(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
open_api: OpenAPI,
|
||||||
|
aioambient,
|
||||||
|
config_entry,
|
||||||
|
caplog,
|
||||||
|
) -> None:
|
||||||
|
"""Test that we log errors properly."""
|
||||||
|
|
||||||
|
initial_datetime = datetime(year=2023, month=11, day=8)
|
||||||
|
with freeze_time(initial_datetime) as frozen_datetime:
|
||||||
|
# Normal state, sensor is available.
|
||||||
|
await setup_platform(True, hass, config_entry)
|
||||||
|
sensor = hass.states.get("sensor.station_a_relative_pressure")
|
||||||
|
assert sensor is not None
|
||||||
|
assert float(sensor.state) == pytest.approx(1001.89694313129)
|
||||||
|
|
||||||
|
# Sensor becomes unavailable if the network is unavailable. Log message
|
||||||
|
# should only show up once.
|
||||||
|
for _ in range(5):
|
||||||
|
with patch.object(
|
||||||
|
open_api, "get_device_details", side_effect=RequestError()
|
||||||
|
):
|
||||||
|
frozen_datetime.tick(timedelta(minutes=10))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
sensor = hass.states.get("sensor.station_a_relative_pressure")
|
||||||
|
assert sensor is not None
|
||||||
|
assert sensor.state == "unavailable"
|
||||||
|
assert caplog.text.count("Cannot connect to Ambient Network") == 1
|
||||||
|
|
||||||
|
# Network comes back. Sensor should start reporting again. Log message
|
||||||
|
# should only show up once.
|
||||||
|
for _ in range(5):
|
||||||
|
frozen_datetime.tick(timedelta(minutes=10))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
sensor = hass.states.get("sensor.station_a_relative_pressure")
|
||||||
|
assert sensor is not None
|
||||||
|
assert float(sensor.state) == pytest.approx(1001.89694313129)
|
||||||
|
assert caplog.text.count("Fetching ambient_network data recovered") == 1
|
Loading…
x
Reference in New Issue
Block a user