Add Sensors to Weatherflow Cloud (#111651)

* continue

* Rebase dev

* signle function to generate attr_entity info

* rewrite icon determination as an if block

* handling PR

* Removing wind sensors for now - separate future PR

* ruff

* Update coordinator.py

Thought i already did this

* Update sensor.py

* Update icons.json

* Update sensor.py

* Update homeassistant/components/weatherflow_cloud/entity.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* working on a unified entity

* working on a unified entity

* sensor refactor

* addressing entity comment

* Update homeassistant/components/weatherflow_cloud/entity.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update homeassistant/components/weatherflow_cloud/sensor.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* doc

* pr comments again

* fixing PR

* fixing PR

* applying entity class in sensor

* Update homeassistant/components/weatherflow_cloud/sensor.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Cleaning up weather.py

* station id cleanup for weather class

* rewrite adding sensors the correct way

* Adding snapshot testing

* snapshot update

* added total class

* updated snapshots

* minor tweak

* snapshot away

* adding more coverage

* switch back to total

* Apply suggestions from code review

* Apply suggestions from code review

* Apply suggestions from code review

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Jeef 2024-07-19 02:30:01 -06:00 committed by GitHub
parent 7810dc213a
commit de18be235d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 7116 additions and 20 deletions

View File

@ -9,7 +9,7 @@ from homeassistant.core import HomeAssistant
from .const import DOMAIN from .const import DOMAIN
from .coordinator import WeatherFlowCloudDataUpdateCoordinator from .coordinator import WeatherFlowCloudDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.WEATHER] PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.WEATHER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

View File

@ -21,12 +21,11 @@ class WeatherFlowCloudDataUpdateCoordinator(
def __init__(self, hass: HomeAssistant, api_token: str) -> None: def __init__(self, hass: HomeAssistant, api_token: str) -> None:
"""Initialize global WeatherFlow forecast data updater.""" """Initialize global WeatherFlow forecast data updater."""
self.weather_api = WeatherFlowRestAPI(api_token=api_token) self.weather_api = WeatherFlowRestAPI(api_token=api_token)
super().__init__( super().__init__(
hass, hass,
LOGGER, LOGGER,
name=DOMAIN, name=DOMAIN,
update_interval=timedelta(minutes=15), update_interval=timedelta(seconds=60),
) )
async def _async_update_data(self) -> dict[int, WeatherFlowDataREST]: async def _async_update_data(self) -> dict[int, WeatherFlowDataREST]:

View File

@ -0,0 +1,38 @@
"""Base entity class for WeatherFlow Cloud integration."""
from weatherflow4py.models.rest.unified import WeatherFlowDataREST
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import ATTR_ATTRIBUTION, DOMAIN, MANUFACTURER
from .coordinator import WeatherFlowCloudDataUpdateCoordinator
class WeatherFlowCloudEntity(CoordinatorEntity[WeatherFlowCloudDataUpdateCoordinator]):
"""Base entity class to use for everything."""
_attr_attribution = ATTR_ATTRIBUTION
_attr_has_entity_name = True
def __init__(
self,
coordinator: WeatherFlowCloudDataUpdateCoordinator,
station_id: int,
) -> None:
"""Class initializer."""
super().__init__(coordinator)
self.station_id = station_id
self._attr_device_info = DeviceInfo(
name=self.station.station.name,
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, str(station_id))},
manufacturer=MANUFACTURER,
configuration_url=f"https://tempestwx.com/station/{station_id}/grid",
)
@property
def station(self) -> WeatherFlowDataREST:
"""Individual Station data."""
return self.coordinator.data[self.station_id]

View File

@ -0,0 +1,42 @@
{
"entity": {
"sensor": {
"air_temperature": {
"default": "mdi:thermometer"
},
"air_density": {
"default": "mdi:format-line-weight"
},
"feels_like": {
"default": "mdi:thermometer"
},
"heat_index": {
"default": "mdi:sun-thermometer"
},
"wet_bulb_temperature": {
"default": "mdi:thermometer-water"
},
"wet_bulb_globe_temperature": {
"default": "mdi:thermometer-water"
},
"lightning_strike_count": {
"default": "mdi:lightning-bolt"
},
"lightning_strike_count_last_1hr": {
"default": "mdi:lightning-bolt"
},
"lightning_strike_count_last_3hr": {
"default": "mdi:lightning-bolt"
},
"lightning_strike_last_distance": {
"default": "mdi:lightning-bolt"
},
"lightning_strike_last_epoch": {
"default": "mdi:lightning-bolt"
},
"wind_chill": {
"default": "mdi:snowflake-thermometer"
}
}
}
}

View File

@ -5,5 +5,6 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud", "documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["weatherflow4py"],
"requirements": ["weatherflow4py==0.2.21"] "requirements": ["weatherflow4py==0.2.21"]
} }

View File

@ -0,0 +1,208 @@
"""Sensors for cloud based weatherflow."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from datetime import UTC, datetime
from weatherflow4py.models.rest.observation import Observation
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfLength, UnitOfPressure, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
from .coordinator import WeatherFlowCloudDataUpdateCoordinator
from .entity import WeatherFlowCloudEntity
@dataclass(frozen=True, kw_only=True)
class WeatherFlowCloudSensorEntityDescription(
SensorEntityDescription,
):
"""Describes a weatherflow sensor."""
value_fn: Callable[[Observation], StateType | datetime]
WF_SENSORS: tuple[WeatherFlowCloudSensorEntityDescription, ...] = (
# Air Sensors
WeatherFlowCloudSensorEntityDescription(
key="air_density",
translation_key="air_density",
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=5,
value_fn=lambda data: data.air_density,
native_unit_of_measurement="kg/m³",
),
# Temp Sensors
WeatherFlowCloudSensorEntityDescription(
key="air_temperature",
translation_key="air_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
value_fn=lambda data: data.air_temperature,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
WeatherFlowCloudSensorEntityDescription(
key="dew_point",
translation_key="dew_point",
value_fn=lambda data: data.dew_point,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
),
WeatherFlowCloudSensorEntityDescription(
key="feels_like",
translation_key="feels_like",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
value_fn=lambda data: data.feels_like,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
WeatherFlowCloudSensorEntityDescription(
key="heat_index",
translation_key="heat_index",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
value_fn=lambda data: data.heat_index,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
WeatherFlowCloudSensorEntityDescription(
key="wind_chill",
translation_key="wind_chill",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
value_fn=lambda data: data.wind_chill,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
WeatherFlowCloudSensorEntityDescription(
key="wet_bulb_temperature",
translation_key="wet_bulb_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
value_fn=lambda data: data.wet_bulb_temperature,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
WeatherFlowCloudSensorEntityDescription(
key="wet_bulb_globe_temperature",
translation_key="wet_bulb_globe_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
value_fn=lambda data: data.wet_bulb_globe_temperature,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
# Pressure Sensors
WeatherFlowCloudSensorEntityDescription(
key="barometric_pressure",
translation_key="barometric_pressure",
value_fn=lambda data: data.barometric_pressure,
native_unit_of_measurement=UnitOfPressure.MBAR,
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=3,
),
WeatherFlowCloudSensorEntityDescription(
key="sea_level_pressure",
translation_key="sea_level_pressure",
value_fn=lambda data: data.sea_level_pressure,
native_unit_of_measurement=UnitOfPressure.MBAR,
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=3,
),
# Lightning Sensors
WeatherFlowCloudSensorEntityDescription(
key="lightning_strike_count",
translation_key="lightning_strike_count",
state_class=SensorStateClass.TOTAL,
value_fn=lambda data: data.lightning_strike_count,
),
WeatherFlowCloudSensorEntityDescription(
key="lightning_strike_count_last_1hr",
translation_key="lightning_strike_count_last_1hr",
state_class=SensorStateClass.TOTAL,
value_fn=lambda data: data.lightning_strike_count_last_1hr,
),
WeatherFlowCloudSensorEntityDescription(
key="lightning_strike_count_last_3hr",
translation_key="lightning_strike_count_last_3hr",
state_class=SensorStateClass.TOTAL,
value_fn=lambda data: data.lightning_strike_count_last_3hr,
),
WeatherFlowCloudSensorEntityDescription(
key="lightning_strike_last_distance",
translation_key="lightning_strike_last_distance",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.DISTANCE,
native_unit_of_measurement=UnitOfLength.KILOMETERS,
value_fn=lambda data: data.lightning_strike_last_distance,
),
WeatherFlowCloudSensorEntityDescription(
key="lightning_strike_last_epoch",
translation_key="lightning_strike_last_epoch",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda data: datetime.fromtimestamp(
data.lightning_strike_last_epoch, tz=UTC
),
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up WeatherFlow sensors based on a config entry."""
coordinator: WeatherFlowCloudDataUpdateCoordinator = hass.data[DOMAIN][
entry.entry_id
]
stations = coordinator.data.keys()
async_add_entities(
WeatherFlowCloudSensor(coordinator, sensor_description, station_id)
for station_id in stations
for sensor_description in WF_SENSORS
)
class WeatherFlowCloudSensor(WeatherFlowCloudEntity, SensorEntity):
"""Implementation of a WeatherFlow sensor."""
entity_description: WeatherFlowCloudSensorEntityDescription
def __init__(
self,
coordinator: WeatherFlowCloudDataUpdateCoordinator,
description: WeatherFlowCloudSensorEntityDescription,
station_id: int,
) -> None:
"""Initialize the sensor."""
# Initialize the Entity Class
super().__init__(coordinator, station_id)
self.entity_description = description
self._attr_unique_id = f"{station_id}_{description.key}"
@property
def native_value(self) -> StateType | datetime:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.station.observation.obs[0])

View File

@ -23,5 +23,65 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]", "already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
} }
},
"entity": {
"sensor": {
"air_density": {
"name": "Air density"
},
"barometric_pressure": {
"name": "Pressure barometric"
},
"sea_level_pressure": {
"name": "Pressure sea level"
},
"dew_point": {
"name": "Dew point"
},
"lightning_strike_count": {
"name": "Lightning count"
},
"lightning_strike_count_last_1hr": {
"name": "Lightning count last 1 hr"
},
"lightning_strike_count_last_3hr": {
"name": "Lightning count last 3 hr"
},
"lightning_strike_last_distance": {
"name": "Lightning last distance"
},
"lightning_strike_last_epoch": {
"name": "Lightning last strike"
},
"wind_chill": {
"name": "Wind chill"
},
"wind_direction": {
"name": "Wind direction"
},
"wind_direction_cardinal": {
"name": "Wind direction (cardinal)"
},
"wind_gust": {
"name": "Wind gust"
},
"wind_lull": {
"name": "Wind lull"
},
"feels_like": {
"name": "Feels like"
},
"heat_index": {
"name": "Heat index"
},
"wet_bulb_temperature": {
"name": "Wet bulb temperature"
},
"wet_bulb_globe_temperature": {
"name": "Wet bulb globe temperature"
}
}
} }
} }

View File

@ -17,11 +17,11 @@ from homeassistant.const import (
UnitOfTemperature, UnitOfTemperature,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ATTR_ATTRIBUTION, DOMAIN, MANUFACTURER, STATE_MAP from .const import DOMAIN, STATE_MAP
from .coordinator import WeatherFlowCloudDataUpdateCoordinator from .coordinator import WeatherFlowCloudDataUpdateCoordinator
from .entity import WeatherFlowCloudEntity
async def async_setup_entry( async def async_setup_entry(
@ -43,13 +43,11 @@ async def async_setup_entry(
class WeatherFlowWeather( class WeatherFlowWeather(
SingleCoordinatorWeatherEntity[WeatherFlowCloudDataUpdateCoordinator] WeatherFlowCloudEntity,
SingleCoordinatorWeatherEntity[WeatherFlowCloudDataUpdateCoordinator],
): ):
"""Implementation of a WeatherFlow weather condition.""" """Implementation of a WeatherFlow weather condition."""
_attr_attribution = ATTR_ATTRIBUTION
_attr_has_entity_name = True
_attr_native_temperature_unit = UnitOfTemperature.CELSIUS _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
_attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS _attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
_attr_native_pressure_unit = UnitOfPressure.MBAR _attr_native_pressure_unit = UnitOfPressure.MBAR
@ -65,19 +63,9 @@ class WeatherFlowWeather(
station_id: int, station_id: int,
) -> None: ) -> None:
"""Initialise the platform with a data instance and station.""" """Initialise the platform with a data instance and station."""
super().__init__(coordinator) super().__init__(coordinator, station_id)
self.station_id = station_id
self._attr_unique_id = f"weatherflow_forecast_{station_id}" self._attr_unique_id = f"weatherflow_forecast_{station_id}"
self._attr_device_info = DeviceInfo(
name=self.local_data.station.name,
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, f"{station_id}")},
manufacturer=MANUFACTURER,
configuration_url=f"https://tempestwx.com/station/{station_id}/grid",
)
@property @property
def local_data(self) -> WeatherFlowDataREST: def local_data(self) -> WeatherFlowDataREST:
"""Return the local weather data object for this station.""" """Return the local weather data object for this station."""

View File

@ -1 +1,13 @@
"""Tests for the WeatherflowCloud integration.""" """Tests for the WeatherflowCloud integration."""
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Fixture for setting up the component."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

View File

@ -5,6 +5,15 @@ from unittest.mock import AsyncMock, Mock, patch
from aiohttp import ClientResponseError from aiohttp import ClientResponseError
import pytest import pytest
from weatherflow4py.models.rest.forecast import WeatherDataForecastREST
from weatherflow4py.models.rest.observation import ObservationStationREST
from weatherflow4py.models.rest.stations import StationsResponseREST
from weatherflow4py.models.rest.unified import WeatherFlowDataREST
from homeassistant.components.weatherflow_cloud.const import DOMAIN
from homeassistant.const import CONF_API_TOKEN
from tests.common import MockConfigEntry, load_fixture
@pytest.fixture @pytest.fixture
@ -56,3 +65,51 @@ def mock_get_stations_401_error() -> Generator[AsyncMock]:
side_effect=side_effects, side_effect=side_effects,
) as mock_get_stations: ) as mock_get_stations:
yield mock_get_stations yield mock_get_stations
MOCK_API_TOKEN = "1234567890"
@pytest.fixture
async def mock_config_entry() -> MockConfigEntry:
"""Fixture for MockConfigEntry."""
return MockConfigEntry(
domain=DOMAIN,
data={CONF_API_TOKEN: MOCK_API_TOKEN},
version=1,
)
@pytest.fixture
def mock_api():
"""Fixture for Mock WeatherFlowRestAPI."""
get_stations_response_data = StationsResponseREST.from_json(
load_fixture("stations.json", DOMAIN)
)
get_forecast_response_data = WeatherDataForecastREST.from_json(
load_fixture("forecast.json", DOMAIN)
)
get_observation_response_data = ObservationStationREST.from_json(
load_fixture("station_observation.json", DOMAIN)
)
data = {
24432: WeatherFlowDataREST(
weather=get_forecast_response_data,
observation=get_observation_response_data,
station=get_stations_response_data.stations[0],
device_observations=None,
)
}
with patch(
"homeassistant.components.weatherflow_cloud.coordinator.WeatherFlowRestAPI",
autospec=True,
) as mock_api_class:
# Create an instance of AsyncMock for the API
mock_api = AsyncMock()
mock_api.get_all_data.return_value = data
# Patch the class to return our mock_api instance
mock_api_class.return_value = mock_api
yield mock_api

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,100 @@
{
"elevation": 2063.150146484375,
"is_public": true,
"latitude": 43.94962,
"longitude": -102.86831,
"obs": [
{
"air_density": 0.96139,
"air_temperature": 10.5,
"barometric_pressure": 782.8,
"brightness": 757,
"delta_t": 8.4,
"dew_point": -10.4,
"feels_like": 10.5,
"heat_index": 10.5,
"lightning_strike_count": 0,
"lightning_strike_count_last_1hr": 0,
"lightning_strike_count_last_3hr": 0,
"lightning_strike_last_distance": 26,
"lightning_strike_last_epoch": 1707346875,
"precip": 0.0,
"precip_accum_last_1hr": 0.0,
"precip_accum_local_day": 0.0,
"precip_accum_local_day_final": 0.0,
"precip_accum_local_yesterday": 0.0,
"precip_accum_local_yesterday_final": 0.0,
"precip_analysis_type_yesterday": 0,
"precip_minutes_local_day": 0,
"precip_minutes_local_yesterday": 0,
"precip_minutes_local_yesterday_final": 0,
"pressure_trend": "steady",
"relative_humidity": 22,
"sea_level_pressure": 1006.2,
"solar_radiation": 6,
"station_pressure": 782.8,
"timestamp": 1708994629,
"uv": 0.03,
"wet_bulb_globe_temperature": 4.6,
"wet_bulb_temperature": 2.1,
"wind_avg": 1.4,
"wind_chill": 10.5,
"wind_direction": 203,
"wind_gust": 3.2,
"wind_lull": 0.3
}
],
"outdoor_keys": [
"timestamp",
"air_temperature",
"barometric_pressure",
"station_pressure",
"pressure_trend",
"sea_level_pressure",
"relative_humidity",
"precip",
"precip_accum_last_1hr",
"precip_accum_local_day",
"precip_accum_local_day_final",
"precip_accum_local_yesterday_final",
"precip_minutes_local_day",
"precip_minutes_local_yesterday_final",
"wind_avg",
"wind_direction",
"wind_gust",
"wind_lull",
"solar_radiation",
"uv",
"brightness",
"lightning_strike_last_epoch",
"lightning_strike_last_distance",
"lightning_strike_count",
"lightning_strike_count_last_1hr",
"lightning_strike_count_last_3hr",
"feels_like",
"heat_index",
"wind_chill",
"dew_point",
"wet_bulb_temperature",
"wet_bulb_globe_temperature",
"delta_t",
"air_density"
],
"public_name": "My Home Station",
"station_id": 24432,
"station_name": "My Home Station",
"station_units": {
"units_direction": "degrees",
"units_distance": "mi",
"units_other": "metric",
"units_precip": "in",
"units_pressure": "hpa",
"units_temp": "f",
"units_wind": "bft"
},
"status": {
"status_code": 0,
"status_message": "SUCCESS"
},
"timezone": "America/Denver"
}

View File

@ -0,0 +1,132 @@
{
"stations": [
{
"created_epoch": 1658343273,
"devices": [
{
"device_id": 7654321,
"device_meta": {
"agl": 1.8288,
"environment": "indoor",
"name": "HB-00068123",
"wifi_network_name": ""
},
"device_type": "HB",
"firmware_revision": "177",
"hardware_revision": "1",
"location_id": 24432,
"serial_number": "HB-00068123"
},
{
"device_id": 123456,
"device_meta": {
"agl": 1.8288,
"environment": "outdoor",
"name": "ST-11084623",
"wifi_network_name": ""
},
"device_settings": {
"show_precip_final": true
},
"device_type": "ST",
"firmware_revision": "172",
"hardware_revision": "1",
"location_id": 24432,
"serial_number": "ST-11084623"
}
],
"is_local_mode": false,
"last_modified_epoch": 1658344464,
"latitude": 43.94962,
"location_id": 24432,
"longitude": -102.86831,
"name": "My Home Station",
"public_name": "My Home Station",
"station_id": 24432,
"station_items": [
{
"device_id": 123456,
"item": "air_temperature_humidity",
"location_id": 24432,
"location_item_id": 657904,
"sort": 0,
"station_id": 24432,
"station_item_id": 657904
},
{
"device_id": 123456,
"item": "barometric_pressure",
"location_id": 24432,
"location_item_id": 657906,
"sort": 3,
"station_id": 24432,
"station_item_id": 657906
},
{
"device_id": 7654321,
"item": "diagnostics",
"location_id": 24432,
"location_item_id": 657912,
"station_id": 24432,
"station_item_id": 657912
},
{
"device_id": 123456,
"item": "diagnostics",
"location_id": 24432,
"location_item_id": 657913,
"sort": 6,
"station_id": 24432,
"station_item_id": 657913
},
{
"device_id": 123456,
"item": "light",
"location_id": 24432,
"location_item_id": 657908,
"sort": 2,
"station_id": 24432,
"station_item_id": 657908
},
{
"device_id": 123456,
"item": "lightning",
"location_id": 24432,
"location_item_id": 657905,
"sort": 4,
"station_id": 24432,
"station_item_id": 657905
},
{
"device_id": 123456,
"item": "rain",
"location_id": 24432,
"location_item_id": 657907,
"sort": 5,
"station_id": 24432,
"station_item_id": 657907
},
{
"device_id": 123456,
"item": "wind",
"location_id": 24432,
"location_item_id": 657909,
"sort": 1,
"station_id": 24432,
"station_item_id": 657909
}
],
"station_meta": {
"elevation": 2063.150146484375,
"share_with_wf": true,
"share_with_wu": true
},
"timezone": "America/Denver",
"timezone_offset_minutes": -420
}
],
"status": {
"status_code": 0,
"status_message": "SUCCESS"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
# serializer version: 1
# name: test_weather[weather.my_home_station-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'weather',
'entity_category': None,
'entity_id': 'weather.my_home_station',
'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': None,
'platform': 'weatherflow_cloud',
'previous_unique_id': None,
'supported_features': <WeatherEntityFeature: 3>,
'translation_key': None,
'unique_id': 'weatherflow_forecast_24432',
'unit_of_measurement': None,
})
# ---
# name: test_weather[weather.my_home_station-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Weather data delivered by WeatherFlow/Tempest REST Api',
'dew_point': -13.0,
'friendly_name': 'My Home Station',
'humidity': 27,
'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>,
'pressure': 795.8,
'pressure_unit': <UnitOfPressure.HPA: 'hPa'>,
'supported_features': <WeatherEntityFeature: 3>,
'temperature': 4.0,
'temperature_unit': <UnitOfTemperature.CELSIUS: '°C'>,
'uv_index': 2,
'visibility_unit': <UnitOfLength.KILOMETERS: 'km'>,
'wind_bearing': 40.0,
'wind_gust_speed': 14.4,
'wind_speed': 7.2,
'wind_speed_unit': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
}),
'context': <ANY>,
'entity_id': 'weather.my_home_station',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'sunny',
})
# ---

View File

@ -0,0 +1,29 @@
"""Tests for the WeatherFlow Cloud sensor platform."""
from unittest.mock import AsyncMock, patch
from syrupy import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
async def test_all_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
mock_api: AsyncMock,
) -> None:
"""Test all entities."""
with patch(
"homeassistant.components.weatherflow_cloud.PLATFORMS", [Platform.SENSOR]
):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)

View File

@ -0,0 +1,29 @@
"""Tests for the WeatherFlow Cloud weather platform."""
from unittest.mock import AsyncMock, patch
from syrupy import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
async def test_weather(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
mock_api: AsyncMock,
) -> None:
"""Test all entities."""
with patch(
"homeassistant.components.weatherflow_cloud.PLATFORMS", [Platform.WEATHER]
):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)