Skip to content

Commit fd2f126

Browse files
authored
Feature/weather code language (#14)
* load weather codes from file * Rewrite as python class and add support for multiple weather codes, add German as 1st translation * add feature to define the translation language for weather codes
1 parent bc5a82b commit fd2f126

File tree

7 files changed

+146
-66
lines changed

7 files changed

+146
-66
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
FROM alpine:3.23.2
22

33
LABEL maintainer="Michael Oberdorf <info@oberdorf-itc.de>"
4-
LABEL site.local.program.version="1.2.0"
4+
LABEL site.local.program.version="1.3.0"
55

66
ENV TZ="UTC" \
77
REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ Container image: [DockerHub](https://hub.docker.com/r/oitc/weather2mqtt)
2626

2727
# Supported tags and respective `Dockerfile` links
2828

29-
* [`latest`, `1.2.0`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.2.0/Dockerfile)
29+
* [`latest`, `1.3.0`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.3.0/Dockerfile)
30+
* [`1.2.0`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.2.0/Dockerfile)
3031
* [`1.1.1`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.1.1/Dockerfile)
3132
* [`1.1.0`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.1.0/Dockerfile)
3233
* [`1.0.2`](https://github.com/cybcon/docker.weather2mqtt/blob/v1.0.2/Dockerfile)
@@ -135,6 +136,7 @@ The container grab some configuration via environment variables.
135136
| `LONGITUDE` | The geo coordinate longitude from where we want to have the weather. | optional | `9.11446` |
136137
| `ELEVATION` | The ground elevation from where we want to have the weather. | optional | |
137138
| `WEATHER_MODELS` | The weather model to use. | optional | |
139+
| `WEATHE_CODE_LANGUAGE` | Translation of the numeric weather code into the defined language ('en' or 'de') | optional | `en` |
138140
| `TZ` | The time zone to use to provide timestamps. | optional | `UTC` |
139141
| `MQTT_CLIENT_ID` | A MQTT client identifier. | optional | |
140142
| `MQTT_PROTOCOL_VERSION` | The MQTT protocol version to use. Currently supported `3` (means 3.1.1) and `5`. | optional | `3` |
@@ -173,6 +175,7 @@ export MQTT_PROTOCOL_VERSION="5"
173175
export MQTT_TOPIC="github.com/cybcon/docker.weather2mqtt.git/weather"
174176
export CACHE_DIR="../cache"
175177
export CACHE_EXPIRY_AFTER_SEC="600"
178+
export WEATHE_CODE_LANGUAGE="de"
176179
```
177180

178181
### Configuration files
Lines changed: 49 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,60 @@
11
"""
22
###############################################################################
3-
# Library to tranlate weather codes to human readable description text\n
4-
# seeAlso: https://github.com/open-meteo/open-meteo/issues/287\n
5-
#------------------------------------------------------------------------------\n
6-
# Author: Michael Oberdorf\n
7-
# Date: 2025-04-11\n
8-
# Last modified by: Michael Oberdorf\n
9-
# Last modified at: 2025-07-01\n
3+
# Library to translate weather codes to human readable description text
4+
# seeAlso: https://github.com/open-meteo/open-meteo/issues/287
5+
#------------------------------------------------------------------------------
6+
# Author: Michael Oberdorf
7+
# Date: 2025-04-11
8+
# Last modified by: Michael Oberdorf
9+
# Last modified at: 2026-01-11
1010
###############################################################################\n
1111
"""
1212

1313
__author__ = "Michael Oberdorf <info@oberdorf-itc.de>"
1414
__status__ = "production"
15-
__date__ = "2025-07-01"
16-
__version_info__ = ("0", "1", "1")
15+
__date__ = "2026-01-11"
16+
__version_info__ = ("1", "0", "0")
1717
__version__ = ".".join(__version_info__)
1818

19-
__all__ = ["translate_weather_code"]
20-
21-
_WEATHER_CODES = {
22-
0: "Clear sky",
23-
1: "Mainly clear",
24-
2: "Partly cloudy",
25-
3: "Cloudy",
26-
45: "Fog",
27-
48: "Freezing Fog and depositing rime fog",
28-
51: "Drizzle: Light intensity",
29-
53: "Drizzle: Moderate intensity",
30-
55: "Drizzle: Dense intensity",
31-
56: "Freezing Drizzle: Light intensity",
32-
57: "Freezing Drizzle: Dense intensity",
33-
61: "Rain: Slight intensity",
34-
63: "Rain: Moderate intensity",
35-
65: "Rain: Heavy intensity",
36-
66: "Freezing Rain: Light intensity",
37-
67: "Freezing Rain: Heavy intensity",
38-
71: "Snow fall: Slight intensity",
39-
73: "Snow fall: Moderate intensity",
40-
75: "Snow fall: Heavy intensity",
41-
77: "Snow Grains",
42-
80: "Rain showers: Slight intensity",
43-
81: "Rain showers: Moderate intensity",
44-
82: "Rain showers: Heavy intensity",
45-
85: "Snow showers: Slight intensity",
46-
86: "Snow showers: Heavy intensity",
47-
95: "Thunderstorm: Slight or moderate",
48-
96: "Thunderstorm with slight hail",
49-
99: "Thunderstorm with hail",
50-
100: "is not used",
51-
101: "Tornado",
52-
102: "Tropical storm",
53-
103: "Hurricane",
54-
}
55-
56-
57-
def translate_weather_code(code: int) -> str:
19+
__all__ = ["WeatherCodes"]
20+
21+
import json
22+
import logging
23+
import os
24+
25+
26+
class WeatherCodes:
5827
"""
59-
Translate weather code to human readable description\n
60-
:param code: Weather code\n
61-
:return: Human readable description\n
28+
Class to translate weather codes to human readable description
6229
"""
63-
return _WEATHER_CODES.get(code, "Unknown weather code")
30+
31+
__translations_path = os.path.join(__path__[0], "translations")
32+
33+
def __init__(self, language: str = "en"):
34+
self.logger = logging.getLogger(__name__)
35+
36+
# identify the weather code translation file
37+
weather_code_translation_file = os.path.join(self.__translations_path, f"{language}.json")
38+
if not os.path.isfile(weather_code_translation_file):
39+
self.logger.warning(
40+
f"Weather code translation file for language '{language}' not found. Falling back to default language 'en'."
41+
)
42+
weather_code_translation_file = os.path.join(self.__translations_path, "en.json")
43+
44+
# load the weather code translations
45+
with open(weather_code_translation_file, encoding="utf-8") as f:
46+
self.weather_codes = json.load(f)
47+
48+
def translate(self, code: int) -> str:
49+
"""
50+
Translate weather code to human readable description
51+
52+
:param code: Weather code
53+
:return: Human readable description
54+
"""
55+
# transform the code to int and str to fit to the json keys
56+
code = str(int(code))
57+
self.logger.debug(f"Translating weather code {code}")
58+
translation = self.weather_codes.get(code, "Unknown weather code")
59+
self.logger.debug(f"Weather code {code} translated to '{translation}'")
60+
return translation
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"0": "Klarer Himmel",
3+
"1": "Überwiegend klar",
4+
"2": "Teilweise bewölkt",
5+
"3": "Bewölkt",
6+
"45": "Nebel",
7+
"48": "Gefrierender Nebel und sich absetzender Raureifnebel",
8+
"51": "Leichter Nieselregen",
9+
"53": "Nieselregen",
10+
"55": "Dichter Nieselregen",
11+
"56": "Leichter überfrierender Nieselregen",
12+
"57": "Dichter überfrierender Nieselregen",
13+
"61": "Leichter Regen",
14+
"63": "Regen",
15+
"65": "Starker Regen",
16+
"66": "Leichter Eisregen",
17+
"67": "Starker Eisregen",
18+
"71": "Leichter Schneefall",
19+
"73": "Schneefall",
20+
"75": "Starker Schneefall",
21+
"77": "Graupel",
22+
"80": "Leichte Regenschauer",
23+
"81": "Regenschauer",
24+
"82": "Starke Regenschauer",
25+
"85": "Leichte Schneeschauer",
26+
"86": "Starke Schneeschauer",
27+
"95": "Leicht bis mäßiges Gewitter",
28+
"96": "Gewitter mit leichtem Hagel",
29+
"99": "Gewitter mit Hagel",
30+
"101": "Tornado",
31+
"102": "Tropischer Sturm",
32+
"103": "Hurrikan"
33+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"0": "Clear sky",
3+
"1": "Mainly clear",
4+
"2": "Partly cloudy",
5+
"3": "Cloudy",
6+
"45": "Fog",
7+
"48": "Freezing Fog and depositing rime fog",
8+
"51": "Drizzle: Light intensity",
9+
"53": "Drizzle: Moderate intensity",
10+
"55": "Drizzle: Dense intensity",
11+
"56": "Freezing Drizzle: Light intensity",
12+
"57": "Freezing Drizzle: Dense intensity",
13+
"61": "Rain: Slight intensity",
14+
"63": "Rain: Moderate intensity",
15+
"65": "Rain: Heavy intensity",
16+
"66": "Freezing Rain: Light intensity",
17+
"67": "Freezing Rain: Heavy intensity",
18+
"71": "Snow fall: Slight intensity",
19+
"73": "Snow fall: Moderate intensity",
20+
"75": "Snow fall: Heavy intensity",
21+
"77": "Snow Grains",
22+
"80": "Rain showers: Slight intensity",
23+
"81": "Rain showers: Moderate intensity",
24+
"82": "Rain showers: Heavy intensity",
25+
"85": "Snow showers: Slight intensity",
26+
"86": "Snow showers: Heavy intensity",
27+
"95": "Thunderstorm: Slight or moderate",
28+
"96": "Thunderstorm with slight hail",
29+
"99": "Thunderstorm with hail",
30+
"100": "is not used",
31+
"101": "Tornado",
32+
"102": "Tropical storm",
33+
"103": "Hurricane"
34+
}

src/app/bin/weather2mqtt.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# Author: Michael Oberdorf
1212
# Date: 2025-04-11
1313
# Last modified by: Michael Oberdorf
14-
# Last modified at: 2026-01-06
14+
# Last modified at: 2026-01-11
1515
###############################################################################
1616
"""
1717

@@ -26,10 +26,10 @@
2626
import paho.mqtt.client as mqtt
2727
import pytz
2828
import requests_cache # seeAlso: https://pypi.org/project/requests-cache/
29-
from lib.weather_codes import translate_weather_code
29+
from lib.weather_codes import WeatherCodes
3030
from retry_requests import retry # seeAlso: https://pypi.org/project/retry-requests/
3131

32-
__version__ = "1.2.0"
32+
__version__ = "1.3.0"
3333
__script_path__ = os.path.dirname(__file__)
3434
__config_path__ = os.path.join(os.path.dirname(__script_path__), "etc")
3535
__local_tz__ = pytz.timezone("UTC")
@@ -273,7 +273,10 @@ def parse_current_weather(data: any, fields: list = []) -> dict:
273273

274274
# Translate weather code
275275
if "weather_code" in parsed_data.keys():
276-
parsed_data["weather_code_text"] = translate_weather_code(parsed_data["weather_code"])
276+
weather_code_language = os.environ.get("WEATHER_CODE_LANGUAGE", "en")[0:2].lower()
277+
parsed_data["weather_code_text"] = WeatherCodes(language=weather_code_language).translate(
278+
code=parsed_data["weather_code"]
279+
)
277280
log.debug(f"Translated weather code: {parsed_data['weather_code_text']}")
278281

279282
log.debug(f"Parsed current weather: {parsed_data}")
@@ -317,9 +320,10 @@ def parse_daily_weather(data: any, fields: list = []) -> dict:
317320

318321
# Translate weather code
319322
if "weather_code" in parsed_data.keys():
323+
weather_code_language = os.environ.get("WEATHER_CODE_LANGUAGE", "en")[0:2].lower()
320324
parsed_data["weather_code_text"] = dict()
321325
for k, v in parsed_data["weather_code"].items():
322-
parsed_data["weather_code_text"][k] = translate_weather_code(v)
326+
parsed_data["weather_code_text"][k] = WeatherCodes(language=weather_code_language).translate(code=v)
323327

324328
log.debug(f"Parsed current weather: {parsed_data}")
325329
return parsed_data

test/test_weather_codes.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,25 @@
1919

2020
import unittest
2121

22-
from src.app.bin.lib.weather_codes import translate_weather_code
22+
from src.app.bin.lib.weather_codes import WeatherCodes
2323

2424

2525
class TestWeatherCodes(unittest.TestCase):
26-
def test_translate_weather_code(self):
27-
# Example test cases for translate_weather_code
28-
self.assertEqual(translate_weather_code(0), "Clear sky")
29-
self.assertEqual(translate_weather_code(1), "Mainly clear")
30-
self.assertEqual(translate_weather_code(3), "Cloudy")
31-
self.assertEqual(translate_weather_code(199), "Unknown weather code")
26+
def test_translate(self):
27+
# Test cases for default language (English)
28+
self.assertEqual(WeatherCodes().translate(code=0), "Clear sky")
29+
self.assertEqual(WeatherCodes().translate(code=1), "Mainly clear")
30+
31+
# Test cases for specific languages (English and German)
32+
self.assertEqual(WeatherCodes(language="en").translate(code=3), "Cloudy")
33+
self.assertEqual(WeatherCodes(language="en").translate(code=199), "Unknown weather code")
34+
self.assertEqual(WeatherCodes(language="de").translate(code=45), "Nebel")
35+
self.assertEqual(WeatherCodes(language="de").translate(code=66), "Leichter Eisregen")
36+
self.assertEqual(WeatherCodes(language="de").translate(code=81), "Regenschauer")
37+
self.assertEqual(WeatherCodes(language="de").translate(code=199), "Unknown weather code")
38+
39+
# Test case for unsupported language (fallback to English)
40+
self.assertEqual(WeatherCodes(language="foo").translate(code=77), "Snow Grains")
3241

3342

3443
if __name__ == "__main__":

0 commit comments

Comments
 (0)