Skip to content

Commit f8c5f75

Browse files
committed
Refactor weather data handling and improve visibility unit conversion
1 parent d32ac7e commit f8c5f75

3 files changed

Lines changed: 123 additions & 70 deletions

File tree

README.md

Lines changed: 97 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
# Open-Meteo Enhanced
22

3+
[![HACS](https://img.shields.io/badge/hacs-default-blue)](https://hacs.xyz/)
34
[![HACS Validation](https://github.com/sayurin/open_meteo_local/actions/workflows/validate.yml/badge.svg)](https://github.com/sayurin/open_meteo_local/actions/workflows/validate.yml)
5+
[![License](https://img.shields.io/github/license/sayurin/open_meteo_local)](https://github.com/sayurin/open_meteo_local/blob/master/LICENSE.md)
6+
[![Version](https://img.shields.io/github/v/release/sayurin/open_meteo_local)](https://github.com/sayurin/open_meteo_local/releases/latest)
7+
[![Downloads](https://img.shields.io/github/downloads/sayurin/open_meteo_local/latest/total)](https://github.com/sayurin/open_meteo_local/releases/latest)
8+
[![Stars](https://img.shields.io/github/stars/sayurin/open_meteo_local?style=flat&color=gold)](https://github.com/sayurin/open_meteo_local)
49

5-
A custom [Home Assistant](https://www.home-assistant.io/) integration for [Open-Meteo](https://open-meteo.com/) weather forecasting. Provides more accurate forecasts and richer weather data than the built-in `open_meteo` integration.
10+
A custom [Home Assistant](https://www.home-assistant.io/) integration for [Open-Meteo](https://open-meteo.com/) weather forecasting. Provides more accurate forecasts and richer weather data than the built-in `open_meteo` integration — no cloud account or API key required.
611

712
## Why use this instead of the built-in Open-Meteo integration?
813

@@ -18,43 +23,48 @@ In addition, this integration exposes many more weather attributes and uses more
1823

1924
### Attribute comparison
2025

21-
| Feature | Built-in `open_meteo` | `open_meteo_local` |
22-
|---|---|---|
23-
| **Current: Temperature** | Yes | Yes |
24-
| **Current: Humidity** | No | **Yes** |
25-
| **Current: Dew point** | No | **Yes** |
26-
| **Current: Apparent temperature** | No | **Yes** |
27-
| **Current: Cloud coverage** | No | **Yes** |
28-
| **Current: Pressure (MSL)** | No | **Yes** |
29-
| **Current: Visibility** | No | **Yes** |
30-
| **Current: Wind speed** | Yes | Yes |
31-
| **Current: Wind bearing** | Yes | Yes |
32-
| **Current: Wind gust speed** | No | **Yes** |
33-
| **Current: UV index** | No | **Yes** |
34-
| **Current: Ozone** | No | No |
35-
| **Current: Day/Night awareness** | No | **Yes** |
36-
| **Daily: Temperature max/min** | Yes | Yes |
37-
| **Daily: Apparent temperature** | No | **Yes** |
38-
| **Daily: Precipitation sum** | Yes | Yes |
39-
| **Daily: Precipitation probability** | No | **Yes** |
40-
| **Daily: Wind speed / direction** | Yes | Yes |
41-
| **Daily: Wind gust speed** | No | **Yes** |
42-
| **Daily: UV index** | No | **Yes** |
43-
| **Daily: Humidity** | No | No |
44-
| **Daily: Dew point** | No | No |
45-
| **Daily: Cloud coverage** | No | No |
46-
| **Daily: Pressure** | No | No |
47-
| **Hourly: Temperature** | Yes | Yes |
48-
| **Hourly: Precipitation** | Yes | Yes |
49-
| **Hourly: Humidity** | No | **Yes** |
50-
| **Hourly: Dew point** | No | **Yes** |
51-
| **Hourly: Apparent temperature** | No | **Yes** |
52-
| **Hourly: Precipitation probability** | No | **Yes** |
53-
| **Hourly: Cloud coverage** | No | **Yes** |
54-
| **Hourly: Pressure** | No | **Yes** |
55-
| **Hourly: Wind speed / direction / gusts** | No | **Yes** |
56-
| **Hourly: UV index** | No | **Yes** |
57-
| **Hourly: Day/Night awareness** | No | **Yes** |
26+
| Feature | Built-in `open_meteo` | `open_meteo_local` | Non-obvious details |
27+
|---|---|---|---|
28+
| **Current: Cloud coverage** | No | **Yes** | - |
29+
| **Current: Condition** | Partial | **Yes** | See **Weather condition accuracy** below |
30+
| **Current: Humidity** | No | **Yes** | 2m |
31+
| **Current: Apparent temperature** | No | **Yes** | 2m |
32+
| **Current: Dew point** | No | **Yes** | 2m |
33+
| **Current: Pressure** | No | **Yes** | Sea level pressure |
34+
| **Current: Temperature** | Yes | Yes | 2m |
35+
| **Current: Visibility** | No | **Yes** | - |
36+
| **Current: Wind gust speed** | No | **Yes** | 10m |
37+
| **Current: Wind speed** | Yes | Yes | 10m |
38+
| **Current: Ozone** | No | No | TBD |
39+
| **Current: UV index** | No | **Yes** | - |
40+
| **Current: Wind bearing** | Yes | Yes | 10m |
41+
| **Daily: Cloud coverage** | No | **Yes** | `mean` |
42+
| **Daily: Condition** | Partial | **Yes** | See **Weather condition accuracy** below |
43+
| **Daily: Humidity** | No | **Yes** | 2m `mean` |
44+
| **Daily: Apparent temperature** | No | **Yes** | 2m `mean` |
45+
| **Daily: Dew point** | No | **Yes** | 2m `mean` |
46+
| **Daily: Precipitation** | Yes | Yes | `sum` |
47+
| **Daily: Pressure** | No | **Yes** | sea level pressure `mean` |
48+
| **Daily: Higher temperature** | Yes | Yes | 2m `max` |
49+
| **Daily: Lower temperature** | Yes | Yes | 2m `min` |
50+
| **Daily: Wind gust speed** | No | **Yes** | 10m `max` |
51+
| **Daily: Wind speed** | Yes | Yes | 10m `max` |
52+
| **Daily: Precipitation probability** | No | **Yes** | daily `max` |
53+
| **Daily: UV index** | No | **Yes** | daily `max` |
54+
| **Daily: Wind bearing** | Yes | Yes | 10m `dominant` |
55+
| **Hourly: Cloud coverage** | No | **Yes** | - |
56+
| **Hourly: Condition** | Partial | **Yes** | See **Weather condition accuracy** below |
57+
| **Hourly: Humidity** | No | **Yes** | 2m |
58+
| **Hourly: Apparent temperature** | No | **Yes** | 2m |
59+
| **Hourly: Dew point** | No | **Yes** | 2m |
60+
| **Hourly: Precipitation** | Yes | Yes | - |
61+
| **Hourly: Pressure** | No | **Yes** | sea level pressure |
62+
| **Hourly: Temperature** | Yes | Yes | 2m |
63+
| **Hourly: Wind gust speed** | No | **Yes** | 10m |
64+
| **Hourly: Wind speed** | No | **Yes** | 10m |
65+
| **Hourly: Precipitation probability** | No | **Yes** | - |
66+
| **Hourly: UV index** | No | **Yes** | - |
67+
| **Hourly: Wind bearing** | No | **Yes** | 10m |
5868

5969
### Weather condition accuracy
6070

@@ -73,32 +83,69 @@ This integration provides more accurate condition mapping:
7383

7484
### HACS (recommended)
7585

76-
1. Open HACS in your Home Assistant instance.
77-
2. Add this repository as a custom repository:
78-
- URL: `https://github.com/sayurin/open_meteo_local`
79-
- Category: Integration
80-
3. Search for "Open-Meteo Enhanced" and install it.
81-
4. Restart Home Assistant.
86+
[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=sayurin&repository=open_meteo_local&category=integration)
87+
88+
1. Select the button above, or search for **"Open-Meteo Enhanced"** in HACS.
89+
2. Install the integration.
90+
3. Restart Home Assistant.
8291

8392
### Manual
8493

8594
1. Copy the `custom_components/open_meteo_local` directory to your Home Assistant `config/custom_components/` directory.
8695
2. Restart Home Assistant.
8796

97+
## Prerequisites
98+
99+
- Home Assistant with [HACS](https://hacs.xyz/) installed (for HACS installation) or access to the `custom_components` directory (for manual installation)
100+
- Internet access to reach the [Open-Meteo API](https://open-meteo.com/) — no account or API key required
101+
- At least one zone configured in Home Assistant to use as the weather location (**Settings → Areas, labels & zones**)
102+
88103
## Configuration
89104

90105
1. Go to **Settings****Devices & Services****Add Integration**.
91106
2. Search for **Open-Meteo Enhanced**.
92107
3. Select a zone to use for the weather location.
93108

94-
The integration creates a weather entity bound to the selected zone. The location (latitude/longitude) is read from the zone entity, so updating the zone will update the weather data location.
109+
The integration creates a weather entity bound to the selected zone. The location (latitude/longitude) is read from the zone entity, so updating the zone coordinates will automatically update the weather data location.
110+
111+
Multiple instances can be added to get forecasts for different locations — one instance per zone.
112+
113+
## Known Limitations
114+
115+
- Requires internet access; there is no local or offline mode.
116+
- Each configuration entry covers a single zone. Add multiple instances for multiple locations.
117+
- Ozone is intentionally not fetched to keep all data on a single weather forecast API call.
118+
- Forecast history is not retained — only current and future forecasts are available.
119+
120+
## Troubleshooting
121+
122+
### Weather entity shows as unavailable
123+
124+
The weather entity appears as unavailable after setup.
125+
126+
1. Check that Home Assistant has internet access and can reach `api.open-meteo.com`.
127+
2. Verify the selected zone exists and has valid coordinates set in **Settings → Areas, labels & zones**.
128+
3. Check **Settings → System → Logs** for any error messages related to `open_meteo_local`.
129+
4. Try reloading the integration from **Settings → Devices & Services → Open-Meteo Enhanced → ⋮ → Reload**.
130+
131+
### Daily forecast temperatures look incorrect
132+
133+
Daily high or low temperatures seem wrong, especially around midnight.
134+
135+
This is the exact problem this integration solves — the built-in `open_meteo` integration uses UTC day boundaries. Confirm you are using **Open-Meteo Enhanced** and not the built-in integration. If the issue persists, verify the zone's latitude and longitude are correct.
136+
137+
### Weather condition shows "sunny" at night
138+
139+
Make sure day/night awareness is working by checking that the zone's coordinates are correct. This integration uses the `is_day` flag from the Open-Meteo API based on the zone's location.
140+
141+
## Removing the Integration
142+
143+
To remove the integration, go to **Settings → Devices & Services**, select **Open-Meteo Enhanced**, and select **Delete**.
95144

96-
## Technical details
145+
## Acknowledgments
97146

98-
- Uses the [Open-Meteo API](https://open-meteo.com/) with FlatBuffers format (`openmeteo-sdk`) for efficient binary parsing, instead of the JSON-based `open-meteo` library used by the built-in integration.
99-
- Polls every 15 minutes.
100-
- Provides 48-hour hourly forecasts and 7-day daily forecasts.
101-
- Timestamps use the local timezone (`auto`) for more intuitive forecast times.
147+
- [Open-Meteo](https://open-meteo.com/) for the free, open-source weather API
148+
- [openmeteo-sdk](https://github.com/open-meteo/sdk) for the FlatBuffers parsing library
102149

103150
## License
104151

custom_components/open_meteo_local/coordinator.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,49 +48,53 @@
4848
_CURRENT_MAP: tuple[tuple[str, str | None, type], ...] = (
4949
("weather_code", None, int),
5050
("is_day", None, bool),
51-
("temperature_2m", "temperature", float),
51+
("cloud_cover", "cloud_coverage", int),
5252
("relative_humidity_2m", "humidity", float),
53-
("dew_point_2m", "dew_point", float),
5453
("apparent_temperature", "apparent_temperature", float),
55-
("cloud_cover", "cloud_coverage", float),
54+
("dew_point_2m", "dew_point", float),
5655
("pressure_msl", "pressure", float),
56+
("temperature_2m", "temperature", float),
5757
("visibility", "visibility", float),
58-
("wind_speed_10m", "wind_speed", float),
59-
("wind_direction_10m", "wind_bearing", float),
6058
("wind_gusts_10m", "wind_gust_speed", float),
59+
("wind_speed_10m", "wind_speed", float),
6160
("uv_index", "uv_index", float),
61+
("wind_direction_10m", "wind_bearing", float),
6262
)
6363

6464
# (api_field_name, forecast_ha_key, value_converter)
6565
# ha_key=None: condition computation only (weather_code / is_day)
6666
_DAILY_MAP: tuple[tuple[str, str | None, type], ...] = (
6767
("weather_code", None, int),
68+
("cloud_cover_mean", CLOUD_COVERAGE, int),
69+
("relative_humidity_2m_mean", HUMIDITY, float),
70+
("apparent_temperature_mean", NATIVE_APPARENT_TEMP, float),
71+
("dew_point_2m_mean", NATIVE_DEW_POINT, float),
72+
("precipitation_sum", NATIVE_PRECIPITATION, float),
73+
("pressure_msl_mean", NATIVE_PRESSURE, float),
6874
("temperature_2m_max", NATIVE_TEMP, float),
6975
("temperature_2m_min", NATIVE_TEMP_LOW, float),
70-
("apparent_temperature_max", NATIVE_APPARENT_TEMP, float),
71-
("precipitation_sum", NATIVE_PRECIPITATION, float),
72-
("precipitation_probability_max", PRECIPITATION_PROBABILITY, int),
73-
("wind_speed_10m_max", NATIVE_WIND_SPEED, float),
74-
("wind_direction_10m_dominant", WIND_BEARING, float),
7576
("wind_gusts_10m_max", NATIVE_WIND_GUST_SPEED, float),
77+
("wind_speed_10m_max", NATIVE_WIND_SPEED, float),
78+
("precipitation_probability_max", PRECIPITATION_PROBABILITY, int),
7679
("uv_index_max", UV_INDEX, float),
80+
("wind_direction_10m_dominant", WIND_BEARING, float),
7781
)
7882

7983
_HOURLY_MAP: tuple[tuple[str, str | None, type], ...] = (
8084
("weather_code", None, int),
8185
("is_day", None, bool),
82-
("temperature_2m", NATIVE_TEMP, float),
86+
("cloud_cover", CLOUD_COVERAGE, int),
8387
("relative_humidity_2m", HUMIDITY, float),
84-
("dew_point_2m", NATIVE_DEW_POINT, float),
8588
("apparent_temperature", NATIVE_APPARENT_TEMP, float),
89+
("dew_point_2m", NATIVE_DEW_POINT, float),
8690
("precipitation", NATIVE_PRECIPITATION, float),
87-
("precipitation_probability", PRECIPITATION_PROBABILITY, int),
88-
("cloud_cover", CLOUD_COVERAGE, int),
8991
("pressure_msl", NATIVE_PRESSURE, float),
90-
("wind_speed_10m", NATIVE_WIND_SPEED, float),
91-
("wind_direction_10m", WIND_BEARING, float),
92+
("temperature_2m", NATIVE_TEMP, float),
9293
("wind_gusts_10m", NATIVE_WIND_GUST_SPEED, float),
94+
("wind_speed_10m", NATIVE_WIND_SPEED, float),
95+
("precipitation_probability", PRECIPITATION_PROBABILITY, int),
9396
("uv_index", UV_INDEX, float),
97+
("wind_direction_10m", WIND_BEARING, float),
9498
)
9599

96100

@@ -103,7 +107,7 @@ class OpenMeteoData:
103107
humidity: float | None
104108
dew_point: float | None
105109
apparent_temperature: float | None
106-
cloud_coverage: float | None
110+
cloud_coverage: int | None
107111
pressure: float | None
108112
visibility: float | None
109113
wind_speed: float | None

custom_components/open_meteo_local/weather.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class OpenMeteoWeatherEntity(
4242
_attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
4343
_attr_native_pressure_unit = UnitOfPressure.HPA
4444
_attr_native_temperature_unit = UnitOfTemperature.CELSIUS
45-
_attr_native_visibility_unit = UnitOfLength.METERS
45+
_attr_native_visibility_unit = UnitOfLength.KILOMETERS
4646
_attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
4747
_attr_supported_features = (
4848
WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY
@@ -91,7 +91,7 @@ def native_apparent_temperature(self) -> float | None:
9191
return self.coordinator.data.apparent_temperature
9292

9393
@property
94-
def cloud_coverage(self) -> float | None:
94+
def cloud_coverage(self) -> int | None:
9595
"""Return the cloud coverage."""
9696
return self.coordinator.data.cloud_coverage
9797

@@ -103,7 +103,9 @@ def native_pressure(self) -> float | None:
103103
@property
104104
def native_visibility(self) -> float | None:
105105
"""Return the visibility."""
106-
return self.coordinator.data.visibility
106+
if self.coordinator.data.visibility is None:
107+
return None
108+
return self.coordinator.data.visibility / 1000
107109

108110
@property
109111
def native_wind_speed(self) -> float | None:

0 commit comments

Comments
 (0)