Quellcode für lib.env

#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2023-       Martin Sinn                         m.sinn@gmx.de
#########################################################################
#  This file is part of SmartHomeNG.
#
#  SmartHomeNG is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  SmartHomeNG is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with SmartHomeNG. If not, see <http://www.gnu.org/licenses/>.
#########################################################################

"""
Diese lib implementiert Funktionen zum Umgang mit Environment Daten in SmartHomeNG.

Hierzu gehören

  - Umrechnungen der folgenden Maßeinheiten für Geschwindigkeiten:

    - mps  - Miles Per Second
    - mph
    - km/h - Kilometer pro Stunde (kmh)
    - m/s  - Meter je Sekunde (ms)
    - nm/h - Nautical Miles per Hour
    - kn   - Knoten (1 nm pro Stunde)
    - bft  - Beaufort

  - Umrechnungen der folgenden Maßeinheiten für Längen / Entfernungenn:

    - meter   - Meter
    - miles   - Meilen
    - nm      - Nautische Meilen

  - Umrechnungen der folgenden Maßeinheiten für Temperaturen:

    - °F   - Grad Fahrenheit
    - °C   - Grad Celsius

  - weitere Umrechnungen

    - Grad zu Himmelsrichtung

"""

import logging

from lib.translation import translate
#from lib.whocalledme import log_who_called_me

_logger = logging.getLogger('lib.env')


_mile = 1609.344       # 1 mile is 1609.344 meters long
_nautical_mile = 1852  # 1 nm is 1852 meters long


"""
Umrechnungen von Geschwindigkeiten  (m/s, km/h, mph, Knoten, mps, Bft)

  https://www.einheiten-umrechnen.de
"""

[Doku]def kn_to_kmh(speed: float) -> float: """ Umrechnung Knoten (nautische Meilen pro Stunde) in Kilometer pro Stunde :param speed: Geschwindigkeit in Knoten :return: Geschwindigkeit in km/h """ if not isinstance(speed,(int, float)): _logger.error("kn_to_kmh: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(speed)})) return -1 return speed * _nautical_mile
[Doku]def kmh_to_kn(speed: float) -> float: """ Umrechnung Kilometer pro Stunde in Knoten (nautische Meilen pro Stunde) :param speed: Geschwindigkeit in km/h :return: Geschwindigkeit in Knoten """ if not isinstance(speed,(int, float)): _logger.error("kmh_to_kn: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(speed)})) return -1 return speed / _nautical_mile
[Doku]def ms_to_kmh(speed: float) -> float: """ Umrechnung Meter pro Sekunde in Kilometer pro Stunde :param speed: Geschwindigkeit in m/s :return: Geschwindigkeit in km/h """ if not isinstance(speed,(int, float)): _logger.error("ms_to_kmh: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(speed)})) return -1 return speed * 3.6
[Doku]def kmh_to_ms(speed: float) -> float: """ Umrechnung Kilometer pro Stunde in Meter pro Sekunde :param speed: Geschwindigkeit in km/h :return: Geschwindigkeit in m/s """ if not isinstance(speed,(int, float)): _logger.error("kmh_to_ms: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(speed)})) return -1 return speed / 3.6
[Doku]def mps_to_kmh(speed: float) -> float: """ Umrechnung Miles per Second in Kilometer pro Stunde :param speed: Geschwindigkeit in mps :return: Geschwindigkeit in km/h """ if not isinstance(speed,(int, float)): _logger.error("mps_to_kmh: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(speed)})) return -1 return speed * 3.6 * _mile
[Doku]def kmh_to_mps(speed: float) -> float: """ Umrechnung Kilometer pro Stunde in Miles per Second :param speed: Geschwindigkeit in km/h :return: Geschwindigkeit in mps """ if not isinstance(speed,(int, float)): _logger.error("kmh_to_mps: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(speed)})) return -1 return speed / 3.6 / _mile
[Doku]def mph_to_kmh(speed: float) -> float: """ Umrechnung Miles per Hour in Kilometer pro Stunde :param speed: Geschwindigkeit in mph :return: Geschwindigkeit in km/h """ if not isinstance(speed,(int, float)): _logger.error("mph_to_kmh: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(speed)})) return -1 return speed * _mile / 1000
[Doku]def kmh_to_mph(speed: float) -> float: """ Umrechnung Kilometer pro Stunde in Miles per Hour :param speed: Geschwindigkeit in km/h :return: Geschwindigkeit in mph """ if not isinstance(speed,(int, float)): _logger.error("kmh_to_mph: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(speed)})) return -1 return speed / _mile * 1000
[Doku]def ms_to_bft(speed: float) -> int: """ Umrechnung Windgeschwindigkeit von Meter pro Sekunde in Beaufort Beaufort gibt die Windgeschwindigkeit durch eine Zahl zwischen 0 und 12 an :param speed: Windgeschwindigkeit in m/s :return: Windgeschwindigkeit in bft """ if not isinstance(speed,(int, float)): _logger.error("ms_to_bft: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(speed)})) return -1 # Origin of table: https://www.smarthomeng.de/vom-winde-verweht table = [ (0.3, 0), (1.6, 1), (3.4, 2), (5.5, 3), (8.0, 4), (10.8, 5), (13.9, 6), (17.2, 7), (20.8, 8), (24.5, 9), (28.5, 10), (32.7, 11), (999, 12)] return min(filter(lambda x: x[0] >= speed, table))[1]
[Doku]def kmh_to_bft(speed: float) -> int: """ Umrechnung Windgeschwindigkeit von Kilometer pro Stunde in Beaufort Beaufort gibt die Windgeschwindigkeit durch eine Zahl zwischen 0 und 12 an :param speed: Windgeschwindigkeit in km/h :return: Windgeschwindigkeit in bft """ if not isinstance(speed,(int, float)): _logger.error("kmh_to_bft: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(speed)})) return -1 return ms_to_bft(kmh_to_ms(speed))
[Doku]def bft_to_text(bft: int, language: str='de') -> str: """ Umwandlung Windgeschwindigkeit in bft in beschreibenden Text :param bft: Wind speed in beaufort (bft) :return: Text Beschreibung der Windgeschwindigkeit """ if not isinstance(bft,(int, float)): _logger.error("bft_to_text: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(bft)})) return '' bft = int(bft) if (bft < 0) or (bft > 12): _logger.error("bft_to_text: " + translate("Beaufort is out of scale: '{bft}'", {'bft': bft})) return '' return translate("bft_"+str(bft))
""" Umrechnung von Längen / Entfernungen """
[Doku]def miles_to_meter(distance): """ Umterchnung Meilen zu Metern :param distance: Strecke in Meilen :return: Strecke in Metern """ if not isinstance(distance,(int, float)): _logger.error("miles_to_meter: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(distance)})) return -1 return miles * _mile
[Doku]def nauticalmiles_to_meter(distance): """ Umterchnung nautische Meilen zu Metern :param distance: Strecke in nautischen Meilen :return: Strecke in Metern """ if not isinstance(distance,(int, float)): _logger.error("nauticalmiles_to_meter: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(distance)})) return -1 return miles * _nautical_mile
[Doku]def meter_to_miles(distance): """ Umterchnung Meter zu Meilen :param distance: Strecke in Metern :return: Strecke in Meilen """ if not isinstance(distance,(int, float)): _logger.error("meter_to_miles: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(distance)})) return -1 return meter / _mile
[Doku]def meter_to_nauticalmiles(distance): """ Umterchnung Meter zu nautische Meilen :param distance: Strecke in Metern :return: Strecke in nautischen Meilen """ if not isinstance(distance,(int, float)): _logger.error("meter_to_nauticalmiles: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(distance)})) return -1 return meter / _nautical_mile
""" Umrechnung von Temperaturen """
[Doku]def f_to_c(grad: float) -> float: """ Umrechnung von Grad Fahrenheit in Grad Celsius :param grad: Temperatur in °F :return: Temperatur in °C """ if not isinstance(grad,(int, float)): _logger.error("f_to_c: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(grad)})) return -999 return (grad - 32) * 5 / 9
[Doku]def c_to_f(grad: float) -> float: """ Umrechnung von Grad Celsius in Grad Fahrenheit :param grad: Temperatur in °C :return: Temperatur in °F """ if not isinstance(grad,(int, float)): _logger.error("c_to_f: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(grad)})) return -999 return grad * 9 / 5 + 32
""" Die folgenden Funktionen dienen der Umrechnung einer Himmelsrichtung von Grad in die gebräuchlichen Abkürzungen """
[Doku]def degrees_to_direction_8(deg: float) -> str: """ Umrechnung Gradzahl in Himmelsrichtung (Abkürzung) Diese Funktion teilt die Himmelsrichtungen in 8 Sektoren :param deg: Kompass Gradzahl :return: Himmelsrichtung (Abkürzung) """ if deg is None: return '?' if not isinstance(deg,(int, float)): _logger.error("degrees_to_direction_8: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(deg)})) return '' direction_array = ['N', 'NO', 'O', 'SO', 'S', 'SW', 'W', 'NW', 'N'] index = int( (deg % 360 + 22.5) / 45) return direction_array[index]
[Doku]def degrees_to_direction_16(deg: float) -> str: """ Umrechnung Gradzahl in Himmelsrichtung (Abkürzung) Diese Funktion teilt die Himmelsrichtungen in 16 Sektoren :param deg: Kompass Gradzahl :return: Himmelsrichtung (Abkürzung) """ if deg is None: return '?' if not isinstance(deg,(int, float)): _logger.error("degrees_to_direction_16: " + translate("Parameter must be of type float or int but is of type {typ}", {'typ': type(deg)})) return '' direction_array = ['N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N'] index = int( (deg % 360 + 11.25) / 22.5) return direction_array[index]
LOCATION_NAME = '' LAT_LON = '' from typing import Union # Ab Python 3.10 auch: def location_name(lat: float | str, lon: float | str) -> str:
[Doku]def location_name(lat: Union[float, str], lon: Union[float, str]) -> str: """ Lokationsname (Stadt, Stadtteil oder Ort) einer Lokation, die über Latitude und Longitude gewählt wird. Die Informationen werden von OpenWeatherMap abgerufen. :param lat: Latitude :param lon: Longitude :return: Lokationsname """ #log_who_called_me(with_localvars=False) import requests # api documentation: https://nominatim.org/release-docs/develop/api/Reverse/ request_str = f"https://nominatim.openstreetmap.org/reverse?lat={lat}&lon={lon}&format=jsonv2" global LOCATION_NAME, LAT_LON if LOCATION_NAME != '' and LAT_LON == str(lat)+'/'+str(lon): _logger.info(f"-> location_name: returning stored name for {lat} / {lon}") return LOCATION_NAME try: response = requests.get(request_str) except Exception as e: _logger.warning("location_name: " + translate("Exception when sending GET request: {e}", {'e': e})) return '' if response.ok: try: json_obj = response.json() except Exception as e: _logger.warning("location_name: " + translate("Response '{response}' is not in valid json format: {e}", {'response': response, 'e': e})) return '' else: _logger.warning(f"location_name: openstreetmap.org responded with '{response.status_code}' when trying to get locatio name for {lat=} / {lon=}") return '' if json_obj.get('address', None) is None: result = '' _logger.notice(f"No 'address' in json response '{json_obj}' for request '{request_str}'") else: if json_obj['address'].get('city', None) is not None: result = json_obj['address']['city'] elif json_obj['address'].get('town', None) is not None: result = json_obj['address']['town'] elif json_obj['address'].get('village', None) is not None: result = json_obj['address']['village'] else: result = '' if json_obj['address'].get('suburb', None) is not None: if result != '': result += ', ' result += json_obj['address']['suburb'] LAT_LON = str(lat) + '/' + str(lon) LOCATION_NAME = result return result
[Doku]def location_address(lat: Union[float, str], lon: Union[float, str]) -> dict: """ Address-Information einer Lokation, die über Latitude und Longitude gewählt wird. Die Informationen werden von OpenWeatherMap abgerufen. :param lat: Latitude :param lon: Longitude :return: Address Information """ import requests # api documentation: https://nominatim.org/release-docs/develop/api/Reverse/ request_str = f"https://nominatim.openstreetmap.org/reverse?lat={lat}&lon={lon}&format=jsonv2" _logger.warning(f"-> location_address: getting name for {lat} / {lon}") try: response = requests.get(request_str) except Exception as e: _logger.warning("location_address: " + translate("Exception when sending GET request: {e}", {'e': e})) return '' try: json_obj = response.json() except Exception as e: _logger.warning("location_address: " + translate("Response '{response}' is not in valid json format: {e}", {'response': response, 'e': e})) return '' if response.status_code >= 500: _logger.warning(f"location_address: {location_name(response.status_code)}") return '' #self._logger.notice(f"{json_obj['display_name']}") return json_obj['address']