mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 16:27:08 +00:00
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:
parent
7810dc213a
commit
de18be235d
@ -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:
|
||||||
|
@ -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]:
|
||||||
|
38
homeassistant/components/weatherflow_cloud/entity.py
Normal file
38
homeassistant/components/weatherflow_cloud/entity.py
Normal 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]
|
42
homeassistant/components/weatherflow_cloud/icons.json
Normal file
42
homeassistant/components/weatherflow_cloud/icons.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
208
homeassistant/components/weatherflow_cloud/sensor.py
Normal file
208
homeassistant/components/weatherflow_cloud/sensor.py
Normal 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])
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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."""
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
4783
tests/components/weatherflow_cloud/fixtures/forecast.json
Normal file
4783
tests/components/weatherflow_cloud/fixtures/forecast.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||||
|
}
|
132
tests/components/weatherflow_cloud/fixtures/stations.json
Normal file
132
tests/components/weatherflow_cloud/fixtures/stations.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
1556
tests/components/weatherflow_cloud/snapshots/test_sensor.ambr
Normal file
1556
tests/components/weatherflow_cloud/snapshots/test_sensor.ambr
Normal file
File diff suppressed because it is too large
Load Diff
@ -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',
|
||||||
|
})
|
||||||
|
# ---
|
29
tests/components/weatherflow_cloud/test_sensor.py
Normal file
29
tests/components/weatherflow_cloud/test_sensor.py
Normal 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)
|
29
tests/components/weatherflow_cloud/test_weather.py
Normal file
29
tests/components/weatherflow_cloud/test_weather.py
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user