Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sensors to Weatherflow Cloud #111651

Merged
merged 38 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b942074
continue
jeeftor Mar 27, 2024
f28a6de
Rebase dev
jeeftor Mar 27, 2024
a0e0be2
signle function to generate attr_entity info
jeeftor May 20, 2024
1ed702d
rewrite icon determination as an if block
jeeftor May 20, 2024
a3f5790
handling PR
jeeftor May 20, 2024
d30b664
Removing wind sensors for now - separate future PR
jeeftor Jun 11, 2024
d2c8c41
ruff
jeeftor Jun 18, 2024
e4ca8bd
Update coordinator.py
jeeftor Jun 20, 2024
d55a83b
Update sensor.py
jeeftor Jun 20, 2024
2252724
Update icons.json
jeeftor Jun 20, 2024
a0f354b
Update sensor.py
jeeftor Jun 20, 2024
f3b7a08
Update homeassistant/components/weatherflow_cloud/entity.py
jeeftor Jul 5, 2024
64a9e2d
working on a unified entity
jeeftor Jul 5, 2024
72cd6db
working on a unified entity
jeeftor Jul 5, 2024
66f7ef4
sensor refactor
jeeftor Jul 5, 2024
0c3d1b4
addressing entity comment
jeeftor Jul 5, 2024
70032cc
Update homeassistant/components/weatherflow_cloud/entity.py
jeeftor Jul 5, 2024
441f274
Update homeassistant/components/weatherflow_cloud/sensor.py
jeeftor Jul 5, 2024
48350d3
doc
jeeftor Jul 5, 2024
54f9b06
pr comments again
jeeftor Jul 7, 2024
4d758da
fixing PR
jeeftor Jul 7, 2024
80f7882
fixing PR
jeeftor Jul 7, 2024
fd8e137
applying entity class in sensor
jeeftor Jul 10, 2024
ec4ccc9
Update homeassistant/components/weatherflow_cloud/sensor.py
jeeftor Jul 10, 2024
5d7cf8c
Cleaning up weather.py
jeeftor Jul 10, 2024
91f2673
station id cleanup for weather class
jeeftor Jul 10, 2024
7abd78e
rewrite adding sensors the correct way
jeeftor Jul 10, 2024
8c2f911
Adding snapshot testing
jeeftor Jul 10, 2024
b1d6a1f
snapshot update
jeeftor Jul 10, 2024
90e03d9
added total class
jeeftor Jul 12, 2024
e9ecdc4
updated snapshots
jeeftor Jul 12, 2024
b35e864
minor tweak
jeeftor Jul 12, 2024
ecac7fe
snapshot away
jeeftor Jul 12, 2024
dede7e7
adding more coverage
jeeftor Jul 15, 2024
97808df
switch back to total
jeeftor Jul 15, 2024
008e6cf
Apply suggestions from code review
joostlek Jul 19, 2024
bec068f
Apply suggestions from code review
joostlek Jul 19, 2024
341b141
Apply suggestions from code review
joostlek Jul 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/weatherflow_cloud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .const import DOMAIN
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:
Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/weatherflow_cloud/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ class WeatherFlowCloudDataUpdateCoordinator(
def __init__(self, hass: HomeAssistant, api_token: str) -> None:
"""Initialize global WeatherFlow forecast data updater."""
self.weather_api = WeatherFlowRestAPI(api_token=api_token)

super().__init__(
hass,
LOGGER,
name=DOMAIN,
update_interval=timedelta(minutes=15),
update_interval=timedelta(seconds=60),
)

async def _async_update_data(self) -> dict[int, WeatherFlowDataREST]:
Expand Down
38 changes: 38 additions & 0 deletions homeassistant/components/weatherflow_cloud/entity.py
Original file line number Diff line number Diff line change
@@ -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]):
jeeftor marked this conversation as resolved.
Show resolved Hide resolved
"""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 changes: 42 additions & 0 deletions homeassistant/components/weatherflow_cloud/icons.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
1 change: 1 addition & 0 deletions homeassistant/components/weatherflow_cloud/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/weatherflow_cloud",
"iot_class": "cloud_polling",
"loggers": ["weatherflow4py"],
"requirements": ["weatherflow4py==0.2.21"]
}
209 changes: 209 additions & 0 deletions homeassistant/components/weatherflow_cloud/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
"""Sensors for cloud based weatherflow."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from datetime import UTC, date, datetime
from decimal import Decimal
joostlek marked this conversation as resolved.
Show resolved Hide resolved

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], int | str | datetime | None]
joostlek marked this conversation as resolved.
Show resolved Hide resolved


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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to access dict.keys() to iterate over just the keys in a dict. Just do:

for key in some_dict:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I could rewrite like this:

    async_add_entities(
        WeatherFlowCloudSensor(coordinator, sensor_description, station_id)
        for station_id in coordinator.data
        for sensor_description in WF_SENSORS
    )

instead of:

    stations = coordinator.data.keys()

    async_add_entities(
        WeatherFlowCloudSensor(coordinator, sensor_description, station_id)
        for station_id in stations
        for sensor_description in WF_SENSORS
    )

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


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 | date | datetime | Decimal | None:
joostlek marked this conversation as resolved.
Show resolved Hide resolved
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.station.observation.obs[0])
60 changes: 60 additions & 0 deletions homeassistant/components/weatherflow_cloud/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,65 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"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"
}
}
}
}
Loading