mirror of https://github.com/home-assistant/core
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.amberelectric.*
|
||||
homeassistant.components.ambiclimate.*
|
||||
homeassistant.components.ambient_network.*
|
||||
homeassistant.components.ambient_station.*
|
||||
homeassistant.components.amcrest.*
|
||||
homeassistant.components.ampio.*
|
||||
|
|
|
@ -90,6 +90,8 @@ build.json @home-assistant/supervisor
|
|||
/tests/components/amberelectric/ @madpilot
|
||||
/homeassistant/components/ambiclimate/ @danielhiversen
|
||||
/tests/components/ambiclimate/ @danielhiversen
|
||||
/homeassistant/components/ambient_network/ @thomaskistler
|
||||
/tests/components/ambient_network/ @thomaskistler
|
||||
/homeassistant/components/ambient_station/ @bachya
|
||||
/tests/components/ambient_station/ @bachya
|
||||
/homeassistant/components/amcrest/ @flacjacket
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
)
|
|
@ -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__)
|
|
@ -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)
|
|
@ -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()
|
|
@ -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}"
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"]
|
||||
}
|
|
@ -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
|
|
@ -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",
|
||||
"amberelectric",
|
||||
"ambiclimate",
|
||||
"ambient_network",
|
||||
"ambient_station",
|
||||
"analytics_insights",
|
||||
"android_ip_webcam",
|
||||
|
|
|
@ -244,6 +244,12 @@
|
|||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"ambient_network": {
|
||||
"name": "Ambient Weather Network",
|
||||
"integration_type": "service",
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"ambient_station": {
|
||||
"name": "Ambient Weather Station",
|
||||
"integration_type": "hub",
|
||||
|
|
10
mypy.ini
10
mypy.ini
|
@ -421,6 +421,16 @@ disallow_untyped_defs = true
|
|||
warn_return_any = 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.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
|
|
@ -190,6 +190,7 @@ aioairzone-cloud==0.5.1
|
|||
# homeassistant.components.airzone
|
||||
aioairzone==0.7.6
|
||||
|
||||
# homeassistant.components.ambient_network
|
||||
# homeassistant.components.ambient_station
|
||||
aioambient==2024.01.0
|
||||
|
||||
|
|
|
@ -169,6 +169,7 @@ aioairzone-cloud==0.5.1
|
|||
# homeassistant.components.airzone
|
||||
aioairzone==0.7.6
|
||||
|
||||
# homeassistant.components.ambient_network
|
||||
# homeassistant.components.ambient_station
|
||||
aioambient==2024.01.0
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
"""Tests for the Ambient Weather Network integration."""
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
|
@ -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',
|
||||
})
|
||||
# ---
|
|
@ -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"}
|
|
@ -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…
Reference in New Issue