Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM alpine:3.23.2

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

ENV TZ="UTC" \
REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ Container image: [DockerHub](https://hub.docker.com/r/oitc/weather2mqtt)

# Supported tags and respective `Dockerfile` links

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

### Configuration files
Expand Down
101 changes: 49 additions & 52 deletions src/app/bin/lib/weather_codes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,60 @@
"""
###############################################################################
# Library to tranlate weather codes to human readable description text\n
# seeAlso: https://github.com/open-meteo/open-meteo/issues/287\n
#------------------------------------------------------------------------------\n
# Author: Michael Oberdorf\n
# Date: 2025-04-11\n
# Last modified by: Michael Oberdorf\n
# Last modified at: 2025-07-01\n
# Library to translate weather codes to human readable description text
# seeAlso: https://github.com/open-meteo/open-meteo/issues/287
#------------------------------------------------------------------------------
# Author: Michael Oberdorf
# Date: 2025-04-11
# Last modified by: Michael Oberdorf
# Last modified at: 2026-01-11
###############################################################################\n
"""

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

__all__ = ["translate_weather_code"]

_WEATHER_CODES = {
0: "Clear sky",
1: "Mainly clear",
2: "Partly cloudy",
3: "Cloudy",
45: "Fog",
48: "Freezing Fog and depositing rime fog",
51: "Drizzle: Light intensity",
53: "Drizzle: Moderate intensity",
55: "Drizzle: Dense intensity",
56: "Freezing Drizzle: Light intensity",
57: "Freezing Drizzle: Dense intensity",
61: "Rain: Slight intensity",
63: "Rain: Moderate intensity",
65: "Rain: Heavy intensity",
66: "Freezing Rain: Light intensity",
67: "Freezing Rain: Heavy intensity",
71: "Snow fall: Slight intensity",
73: "Snow fall: Moderate intensity",
75: "Snow fall: Heavy intensity",
77: "Snow Grains",
80: "Rain showers: Slight intensity",
81: "Rain showers: Moderate intensity",
82: "Rain showers: Heavy intensity",
85: "Snow showers: Slight intensity",
86: "Snow showers: Heavy intensity",
95: "Thunderstorm: Slight or moderate",
96: "Thunderstorm with slight hail",
99: "Thunderstorm with hail",
100: "is not used",
101: "Tornado",
102: "Tropical storm",
103: "Hurricane",
}


def translate_weather_code(code: int) -> str:
__all__ = ["WeatherCodes"]

import json
import logging
import os


class WeatherCodes:
"""
Translate weather code to human readable description\n
:param code: Weather code\n
:return: Human readable description\n
Class to translate weather codes to human readable description
"""
return _WEATHER_CODES.get(code, "Unknown weather code")

__translations_path = os.path.join(__path__[0], "translations")

def __init__(self, language: str = "en"):
self.logger = logging.getLogger(__name__)

# identify the weather code translation file
weather_code_translation_file = os.path.join(self.__translations_path, f"{language}.json")
if not os.path.isfile(weather_code_translation_file):
self.logger.warning(
f"Weather code translation file for language '{language}' not found. Falling back to default language 'en'."
)
weather_code_translation_file = os.path.join(self.__translations_path, "en.json")

# load the weather code translations
with open(weather_code_translation_file, encoding="utf-8") as f:
self.weather_codes = json.load(f)

def translate(self, code: int) -> str:
"""
Translate weather code to human readable description

:param code: Weather code
:return: Human readable description
"""
# transform the code to int and str to fit to the json keys
code = str(int(code))
self.logger.debug(f"Translating weather code {code}")
translation = self.weather_codes.get(code, "Unknown weather code")
self.logger.debug(f"Weather code {code} translated to '{translation}'")
return translation
33 changes: 33 additions & 0 deletions src/app/bin/lib/weather_codes/translations/de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"0": "Klarer Himmel",
"1": "Überwiegend klar",
"2": "Teilweise bewölkt",
"3": "Bewölkt",
"45": "Nebel",
"48": "Gefrierender Nebel und sich absetzender Raureifnebel",
"51": "Leichter Nieselregen",
"53": "Nieselregen",
"55": "Dichter Nieselregen",
"56": "Leichter überfrierender Nieselregen",
"57": "Dichter überfrierender Nieselregen",
"61": "Leichter Regen",
"63": "Regen",
"65": "Starker Regen",
"66": "Leichter Eisregen",
"67": "Starker Eisregen",
"71": "Leichter Schneefall",
"73": "Schneefall",
"75": "Starker Schneefall",
"77": "Graupel",
"80": "Leichte Regenschauer",
"81": "Regenschauer",
"82": "Starke Regenschauer",
"85": "Leichte Schneeschauer",
"86": "Starke Schneeschauer",
"95": "Leicht bis mäßiges Gewitter",
"96": "Gewitter mit leichtem Hagel",
"99": "Gewitter mit Hagel",
"101": "Tornado",
"102": "Tropischer Sturm",
"103": "Hurrikan"
}
34 changes: 34 additions & 0 deletions src/app/bin/lib/weather_codes/translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"0": "Clear sky",
"1": "Mainly clear",
"2": "Partly cloudy",
"3": "Cloudy",
"45": "Fog",
"48": "Freezing Fog and depositing rime fog",
"51": "Drizzle: Light intensity",
"53": "Drizzle: Moderate intensity",
"55": "Drizzle: Dense intensity",
"56": "Freezing Drizzle: Light intensity",
"57": "Freezing Drizzle: Dense intensity",
"61": "Rain: Slight intensity",
"63": "Rain: Moderate intensity",
"65": "Rain: Heavy intensity",
"66": "Freezing Rain: Light intensity",
"67": "Freezing Rain: Heavy intensity",
"71": "Snow fall: Slight intensity",
"73": "Snow fall: Moderate intensity",
"75": "Snow fall: Heavy intensity",
"77": "Snow Grains",
"80": "Rain showers: Slight intensity",
"81": "Rain showers: Moderate intensity",
"82": "Rain showers: Heavy intensity",
"85": "Snow showers: Slight intensity",
"86": "Snow showers: Heavy intensity",
"95": "Thunderstorm: Slight or moderate",
"96": "Thunderstorm with slight hail",
"99": "Thunderstorm with hail",
"100": "is not used",
"101": "Tornado",
"102": "Tropical storm",
"103": "Hurricane"
}
14 changes: 9 additions & 5 deletions src/app/bin/weather2mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# Author: Michael Oberdorf
# Date: 2025-04-11
# Last modified by: Michael Oberdorf
# Last modified at: 2026-01-06
# Last modified at: 2026-01-11
###############################################################################
"""

Expand All @@ -26,10 +26,10 @@
import paho.mqtt.client as mqtt
import pytz
import requests_cache # seeAlso: https://pypi.org/project/requests-cache/
from lib.weather_codes import translate_weather_code
from lib.weather_codes import WeatherCodes
from retry_requests import retry # seeAlso: https://pypi.org/project/retry-requests/

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

# Translate weather code
if "weather_code" in parsed_data.keys():
parsed_data["weather_code_text"] = translate_weather_code(parsed_data["weather_code"])
weather_code_language = os.environ.get("WEATHER_CODE_LANGUAGE", "en")[0:2].lower()
parsed_data["weather_code_text"] = WeatherCodes(language=weather_code_language).translate(
code=parsed_data["weather_code"]
)
log.debug(f"Translated weather code: {parsed_data['weather_code_text']}")

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

# Translate weather code
if "weather_code" in parsed_data.keys():
weather_code_language = os.environ.get("WEATHER_CODE_LANGUAGE", "en")[0:2].lower()
parsed_data["weather_code_text"] = dict()
for k, v in parsed_data["weather_code"].items():
parsed_data["weather_code_text"][k] = translate_weather_code(v)
parsed_data["weather_code_text"][k] = WeatherCodes(language=weather_code_language).translate(code=v)

log.debug(f"Parsed current weather: {parsed_data}")
return parsed_data
Expand Down
23 changes: 16 additions & 7 deletions test/test_weather_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,25 @@

import unittest

from src.app.bin.lib.weather_codes import translate_weather_code
from src.app.bin.lib.weather_codes import WeatherCodes


class TestWeatherCodes(unittest.TestCase):
def test_translate_weather_code(self):
# Example test cases for translate_weather_code
self.assertEqual(translate_weather_code(0), "Clear sky")
self.assertEqual(translate_weather_code(1), "Mainly clear")
self.assertEqual(translate_weather_code(3), "Cloudy")
self.assertEqual(translate_weather_code(199), "Unknown weather code")
def test_translate(self):
# Test cases for default language (English)
self.assertEqual(WeatherCodes().translate(code=0), "Clear sky")
self.assertEqual(WeatherCodes().translate(code=1), "Mainly clear")

# Test cases for specific languages (English and German)
self.assertEqual(WeatherCodes(language="en").translate(code=3), "Cloudy")
self.assertEqual(WeatherCodes(language="en").translate(code=199), "Unknown weather code")
self.assertEqual(WeatherCodes(language="de").translate(code=45), "Nebel")
self.assertEqual(WeatherCodes(language="de").translate(code=66), "Leichter Eisregen")
self.assertEqual(WeatherCodes(language="de").translate(code=81), "Regenschauer")
self.assertEqual(WeatherCodes(language="de").translate(code=199), "Unknown weather code")

# Test case for unsupported language (fallback to English)
self.assertEqual(WeatherCodes(language="foo").translate(code=77), "Snow Grains")


if __name__ == "__main__":
Expand Down