From 87f585b6c0c7a5072512bd330684a5760639da19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20S=C3=B6derberg?= Date: Thu, 19 Mar 2026 17:30:04 +0100 Subject: [PATCH] Initial commit --- .env | 11 + README.md | 3 + constants.py | 46 + display/__init__.py | 49 + display/epd4in2.py | 1213 +++++++++++++++++ display/epdconfig.py | 118 ++ display/image_maker.py | 290 ++++ environment/__init__.py | 35 + environment/local_sensors.py | 46 + environment/pysky.py | 152 +++ environment/smhi.py | 163 +++ environment/wireless_sensor.py | 42 + fonts/__init__.py | 15 + fonts/arial.otf | Bin 0 -> 52940 bytes fonts/chakra.ttf | Bin 0 -> 71600 bytes icons/__init__.py | 128 ++ icons/default/dew.bmp | Bin 0 -> 160 bytes icons/default/humidity.bmp | Bin 0 -> 160 bytes icons/default/indoor.bmp | Bin 0 -> 160 bytes icons/default/pressure.bmp | Bin 0 -> 160 bytes icons/default/sunrise.bmp | Bin 0 -> 160 bytes icons/default/sunset.bmp | Bin 0 -> 160 bytes icons/default/temp_high.bmp | Bin 0 -> 192 bytes icons/default/temp_low.bmp | Bin 0 -> 192 bytes icons/default/temp_mid.bmp | Bin 0 -> 192 bytes icons/default/uv.bmp | Bin 0 -> 160 bytes icons/default/wind.bmp | Bin 0 -> 160 bytes icons/default/wind_gust.bmp | Bin 0 -> 160 bytes icons/forecast/day/blizzard.png | Bin 0 -> 1561 bytes icons/forecast/day/breezy.png | Bin 0 -> 1596 bytes icons/forecast/day/breezy_snow.png | Bin 0 -> 1665 bytes icons/forecast/day/clear.png | Bin 0 -> 1564 bytes icons/forecast/day/drizzle.png | Bin 0 -> 1522 bytes icons/forecast/day/fog.png | Bin 0 -> 1265 bytes icons/forecast/day/heavy_rain.png | Bin 0 -> 1608 bytes icons/forecast/day/mix_rainfall.png | Bin 0 -> 1615 bytes icons/forecast/day/mostly_cloudy.png | Bin 0 -> 993 bytes icons/forecast/day/partly_cloudy.png | Bin 0 -> 1519 bytes icons/forecast/day/rain.png | Bin 0 -> 1432 bytes icons/forecast/day/scattered_showers.png | Bin 0 -> 1842 bytes icons/forecast/day/scattered_thunderstorm.png | Bin 0 -> 1952 bytes icons/forecast/day/severe_thunderstorm.png | Bin 0 -> 1363 bytes icons/forecast/day/sleet.png | Bin 0 -> 1727 bytes icons/forecast/day/snow.png | Bin 0 -> 1623 bytes icons/forecast/day/windy_mostly_cloudy.png | Bin 0 -> 2040 bytes icons/forecast/night/blizzard.png | Bin 0 -> 1944 bytes icons/forecast/night/breezy.png | Bin 0 -> 1596 bytes icons/forecast/night/breezy_snow.png | Bin 0 -> 1665 bytes icons/forecast/night/clear.png | Bin 0 -> 1039 bytes icons/forecast/night/drizzle.png | Bin 0 -> 1874 bytes icons/forecast/night/fog.png | Bin 0 -> 1649 bytes icons/forecast/night/heavy_rain.png | Bin 0 -> 1962 bytes icons/forecast/night/mix_rainfall.png | Bin 0 -> 1965 bytes icons/forecast/night/mostly_cloudy.png | Bin 0 -> 1391 bytes icons/forecast/night/partly_cloudy.png | Bin 0 -> 1572 bytes icons/forecast/night/rain.png | Bin 0 -> 1806 bytes icons/forecast/night/scattered_showers.png | Bin 0 -> 1713 bytes .../forecast/night/scattered_thunderstorm.png | Bin 0 -> 1879 bytes icons/forecast/night/severe_thunderstorm.png | Bin 0 -> 1768 bytes icons/forecast/night/sleet.png | Bin 0 -> 2074 bytes icons/forecast/night/snow.png | Bin 0 -> 2070 bytes icons/forecast/night/windy_mostly_cloudy.png | Bin 0 -> 2315 bytes icons/moon/1.bmp | Bin 0 -> 168 bytes icons/moon/2.bmp | Bin 0 -> 168 bytes icons/moon/3.bmp | Bin 0 -> 168 bytes icons/moon/4.bmp | Bin 0 -> 168 bytes icons/moon/5.bmp | Bin 0 -> 168 bytes icons/moon/6.bmp | Bin 0 -> 168 bytes icons/moon/7.bmp | Bin 0 -> 168 bytes icons/warnings/ORANGE.png | Bin 0 -> 2777 bytes icons/warnings/RED.png | Bin 0 -> 2808 bytes icons/warnings/YELLOW.png | Bin 0 -> 2775 bytes icons/warnings/fire.png | Bin 0 -> 1729 bytes icons/warnings/no_wifi.bmp | Bin 0 -> 1524 bytes icons/warnings/temperature_high.png | Bin 0 -> 1510 bytes icons/warnings/water_shortage.png | Bin 0 -> 1503 bytes icons/warnings/wind.bmp | Bin 0 -> 384 bytes icons/wind/E.bmp | Bin 0 -> 182 bytes icons/wind/ENE.bmp | Bin 0 -> 3654 bytes icons/wind/ESE.bmp | Bin 0 -> 182 bytes icons/wind/N.bmp | Bin 0 -> 182 bytes icons/wind/NE.bmp | Bin 0 -> 182 bytes icons/wind/NNE.bmp | Bin 0 -> 182 bytes icons/wind/NNW.bmp | Bin 0 -> 3654 bytes icons/wind/NW.bmp | Bin 0 -> 182 bytes icons/wind/S.bmp | Bin 0 -> 184 bytes icons/wind/SE.bmp | Bin 0 -> 182 bytes icons/wind/SSE.bmp | Bin 0 -> 3654 bytes icons/wind/SSW.bmp | Bin 0 -> 184 bytes icons/wind/SW.bmp | Bin 0 -> 184 bytes icons/wind/W.bmp | Bin 0 -> 182 bytes icons/wind/WNW.bmp | Bin 0 -> 182 bytes icons/wind/WSW.bmp | Bin 0 -> 3654 bytes main.py | 100 ++ tools/geometry.py | 186 +++ tools/julian.py | 58 + tools/mqtt.py | 47 + tools/weather_logger.py | 44 + 98 files changed, 2746 insertions(+) create mode 100644 .env create mode 100644 README.md create mode 100644 constants.py create mode 100644 display/__init__.py create mode 100644 display/epd4in2.py create mode 100644 display/epdconfig.py create mode 100644 display/image_maker.py create mode 100644 environment/__init__.py create mode 100644 environment/local_sensors.py create mode 100644 environment/pysky.py create mode 100644 environment/smhi.py create mode 100644 environment/wireless_sensor.py create mode 100644 fonts/__init__.py create mode 100644 fonts/arial.otf create mode 100644 fonts/chakra.ttf create mode 100644 icons/__init__.py create mode 100644 icons/default/dew.bmp create mode 100644 icons/default/humidity.bmp create mode 100644 icons/default/indoor.bmp create mode 100644 icons/default/pressure.bmp create mode 100644 icons/default/sunrise.bmp create mode 100644 icons/default/sunset.bmp create mode 100644 icons/default/temp_high.bmp create mode 100644 icons/default/temp_low.bmp create mode 100644 icons/default/temp_mid.bmp create mode 100644 icons/default/uv.bmp create mode 100644 icons/default/wind.bmp create mode 100644 icons/default/wind_gust.bmp create mode 100644 icons/forecast/day/blizzard.png create mode 100644 icons/forecast/day/breezy.png create mode 100644 icons/forecast/day/breezy_snow.png create mode 100644 icons/forecast/day/clear.png create mode 100644 icons/forecast/day/drizzle.png create mode 100644 icons/forecast/day/fog.png create mode 100644 icons/forecast/day/heavy_rain.png create mode 100644 icons/forecast/day/mix_rainfall.png create mode 100644 icons/forecast/day/mostly_cloudy.png create mode 100644 icons/forecast/day/partly_cloudy.png create mode 100644 icons/forecast/day/rain.png create mode 100644 icons/forecast/day/scattered_showers.png create mode 100644 icons/forecast/day/scattered_thunderstorm.png create mode 100644 icons/forecast/day/severe_thunderstorm.png create mode 100644 icons/forecast/day/sleet.png create mode 100644 icons/forecast/day/snow.png create mode 100644 icons/forecast/day/windy_mostly_cloudy.png create mode 100644 icons/forecast/night/blizzard.png create mode 100644 icons/forecast/night/breezy.png create mode 100644 icons/forecast/night/breezy_snow.png create mode 100644 icons/forecast/night/clear.png create mode 100644 icons/forecast/night/drizzle.png create mode 100644 icons/forecast/night/fog.png create mode 100644 icons/forecast/night/heavy_rain.png create mode 100644 icons/forecast/night/mix_rainfall.png create mode 100644 icons/forecast/night/mostly_cloudy.png create mode 100644 icons/forecast/night/partly_cloudy.png create mode 100644 icons/forecast/night/rain.png create mode 100644 icons/forecast/night/scattered_showers.png create mode 100644 icons/forecast/night/scattered_thunderstorm.png create mode 100644 icons/forecast/night/severe_thunderstorm.png create mode 100644 icons/forecast/night/sleet.png create mode 100644 icons/forecast/night/snow.png create mode 100644 icons/forecast/night/windy_mostly_cloudy.png create mode 100644 icons/moon/1.bmp create mode 100644 icons/moon/2.bmp create mode 100644 icons/moon/3.bmp create mode 100644 icons/moon/4.bmp create mode 100644 icons/moon/5.bmp create mode 100644 icons/moon/6.bmp create mode 100644 icons/moon/7.bmp create mode 100644 icons/warnings/ORANGE.png create mode 100644 icons/warnings/RED.png create mode 100644 icons/warnings/YELLOW.png create mode 100644 icons/warnings/fire.png create mode 100644 icons/warnings/no_wifi.bmp create mode 100644 icons/warnings/temperature_high.png create mode 100644 icons/warnings/water_shortage.png create mode 100644 icons/warnings/wind.bmp create mode 100644 icons/wind/E.bmp create mode 100644 icons/wind/ENE.bmp create mode 100644 icons/wind/ESE.bmp create mode 100644 icons/wind/N.bmp create mode 100644 icons/wind/NE.bmp create mode 100644 icons/wind/NNE.bmp create mode 100644 icons/wind/NNW.bmp create mode 100644 icons/wind/NW.bmp create mode 100644 icons/wind/S.bmp create mode 100644 icons/wind/SE.bmp create mode 100644 icons/wind/SSE.bmp create mode 100644 icons/wind/SSW.bmp create mode 100644 icons/wind/SW.bmp create mode 100644 icons/wind/W.bmp create mode 100644 icons/wind/WNW.bmp create mode 100644 icons/wind/WSW.bmp create mode 100644 main.py create mode 100644 tools/geometry.py create mode 100644 tools/julian.py create mode 100644 tools/mqtt.py create mode 100644 tools/weather_logger.py diff --git a/.env b/.env new file mode 100644 index 0000000..4407919 --- /dev/null +++ b/.env @@ -0,0 +1,11 @@ +# .env + +# Network & Sensor +SENSOR_IP=192.168.0.188 +MQTT_USERNAME=weather_station +MQTT_PASSWORD=SensorData12345! +MQTT_FALLBACK_IP=192.168.0.168 + +# Location (Defaulting to your Skåne coordinates) +LATITUDE=56.2006 +LONGITUDE=12.5553 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b086ce --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Weather Station + +Code for the indoor Weather Station diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..15d67ad --- /dev/null +++ b/constants.py @@ -0,0 +1,46 @@ +# constants.py + +import os +from dotenv import load_dotenv +from tools.geometry import Coordinate + +load_dotenv() + +# --- Network & Credentials --- +SENSOR_IP = os.getenv("SENSOR_IP", "192.168.0.188") +SENSOR_HTTP = f"http://{SENSOR_IP}" + +MQTT_USERNAME = os.getenv("MQTT_USERNAME", "weather_station") +MQTT_PASSWORD = os.getenv("MQTT_PASSWORD", "SensorData12345!") +MQTT_FALLBACK_IP = os.getenv("MQTT_FALLBACK_IP", "192.168.0.168") + +# --- Location --- +LAT = float(os.getenv("LATITUDE", "56.2006")) +LONG = float(os.getenv("LONGITUDE", "12.5553")) +HOME_LOCATION = Coordinate(LAT, LONG) + +# --- Localization --- +WEEK_DAYS = { + 0: "Måndag", + 1: "Tisdag", + 2: "Onsdag", + 3: "Torsdag", + 4: "Fredag", + 5: "Lördag", + 6: "Söndag", +} + +MONTH_NAMES = { + 1: "jan.", + 2: "feb.", + 3: "mars", + 4: "apr.", + 5: "maj", + 6: "juni", + 7: "juli", + 8: "aug.", + 9: "sep.", + 10: "okt.", + 11: "nov.", + 12: "dec.", +} diff --git a/display/__init__.py b/display/__init__.py new file mode 100644 index 0000000..2c469a8 --- /dev/null +++ b/display/__init__.py @@ -0,0 +1,49 @@ +# display/__init__.py + +import logging +from datetime import datetime +from PIL import Image + +from .epd4in2 import EPD, epdconfig +from .image_maker import eInkImage + +logging.getLogger(__name__) + + +class Display: + + def __init__(self) -> None: + self.image = eInkImage() + self.e_Paper = self._start_up() + + def _start_up(self) -> EPD: + try: + epd = EPD() + epd.init() + epd.Clear() + return epd + except Exception as e: + logging.error(f"eInk Display not found! | {e}") + + @staticmethod + def _wait_until(timestamp: str, absolute_date: datetime) -> None: + while ( + not timestamp in datetime.today().time().strftime("%H:%M:%S") + and absolute_date >= datetime.today() + ): + pass + return + + @staticmethod + def exit(): + epdconfig.module_exit() + + def refresh(self, image: Image.Image, date: datetime, first: bool) -> None: + try: + self.e_Paper.Init_4Gray() + if not first: + self._wait_until(":00", date) + self.e_Paper.display_4Gray(self.e_Paper.getbuffer_4Gray(image)) + self.e_Paper.sleep() + except Exception as e: + logging.error(f"Failed displaying image - ERROR: {e}") diff --git a/display/epd4in2.py b/display/epd4in2.py new file mode 100644 index 0000000..2ea8342 --- /dev/null +++ b/display/epd4in2.py @@ -0,0 +1,1213 @@ +# display/epd4in2.py + +from . import epdconfig + +GRAY1 = 0xFF # white +GRAY2 = 0xC0 +GRAY3 = 0x80 # gray +GRAY4 = 0x00 # Blackest + +# Display resolution +EPD_WIDTH = 400 +EPD_HEIGHT = 300 + + +class EPD: + def __init__(self): + self.reset_pin = epdconfig.RST_PIN + self.dc_pin = epdconfig.DC_PIN + self.busy_pin = epdconfig.BUSY_PIN + self.cs_pin = epdconfig.CS_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + self.GRAY1 = GRAY1 # white + self.GRAY2 = GRAY2 + self.GRAY3 = GRAY3 # gray + self.GRAY4 = GRAY4 # Blackest + self.DATA = [0x00] * 15000 + + lut_vcom0 = [ + 0x00, + 0x08, + 0x08, + 0x00, + 0x00, + 0x02, + 0x00, + 0x0F, + 0x0F, + 0x00, + 0x00, + 0x01, + 0x00, + 0x08, + 0x08, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + lut_ww = [ + 0x50, + 0x08, + 0x08, + 0x00, + 0x00, + 0x02, + 0x90, + 0x0F, + 0x0F, + 0x00, + 0x00, + 0x01, + 0xA0, + 0x08, + 0x08, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + lut_bw = [ + 0x50, + 0x08, + 0x08, + 0x00, + 0x00, + 0x02, + 0x90, + 0x0F, + 0x0F, + 0x00, + 0x00, + 0x01, + 0xA0, + 0x08, + 0x08, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + lut_wb = [ + 0xA0, + 0x08, + 0x08, + 0x00, + 0x00, + 0x02, + 0x90, + 0x0F, + 0x0F, + 0x00, + 0x00, + 0x01, + 0x50, + 0x08, + 0x08, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + lut_bb = [ + 0x20, + 0x08, + 0x08, + 0x00, + 0x00, + 0x02, + 0x90, + 0x0F, + 0x0F, + 0x00, + 0x00, + 0x01, + 0x10, + 0x08, + 0x08, + 0x00, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + # ******************************partial screen update LUT*********************************/ + EPD_4IN2_Partial_lut_vcom1 = [ + 0x00, + 0x01, + 0x20, + 0x01, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + + EPD_4IN2_Partial_lut_ww1 = [ + 0x00, + 0x01, + 0x20, + 0x01, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + + EPD_4IN2_Partial_lut_bw1 = [ + 0x20, + 0x01, + 0x20, + 0x01, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + + EPD_4IN2_Partial_lut_wb1 = [ + 0x10, + 0x01, + 0x20, + 0x01, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + + EPD_4IN2_Partial_lut_bb1 = [ + 0x00, + 0x01, + 0x20, + 0x01, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + + # ******************************gray*********************************/ + # 0~3 gray + EPD_4IN2_4Gray_lut_vcom = [ + 0x00, + 0x0A, + 0x00, + 0x00, + 0x00, + 0x01, + 0x60, + 0x14, + 0x14, + 0x00, + 0x00, + 0x01, + 0x00, + 0x14, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x13, + 0x0A, + 0x01, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + # R21 + EPD_4IN2_4Gray_lut_ww = [ + 0x40, + 0x0A, + 0x00, + 0x00, + 0x00, + 0x01, + 0x90, + 0x14, + 0x14, + 0x00, + 0x00, + 0x01, + 0x10, + 0x14, + 0x0A, + 0x00, + 0x00, + 0x01, + 0xA0, + 0x13, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + # R22H r + EPD_4IN2_4Gray_lut_bw = [ + 0x40, + 0x0A, + 0x00, + 0x00, + 0x00, + 0x01, + 0x90, + 0x14, + 0x14, + 0x00, + 0x00, + 0x01, + 0x00, + 0x14, + 0x0A, + 0x00, + 0x00, + 0x01, + 0x99, + 0x0C, + 0x01, + 0x03, + 0x04, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + # R23H w + EPD_4IN2_4Gray_lut_wb = [ + 0x40, + 0x0A, + 0x00, + 0x00, + 0x00, + 0x01, + 0x90, + 0x14, + 0x14, + 0x00, + 0x00, + 0x01, + 0x00, + 0x14, + 0x0A, + 0x00, + 0x00, + 0x01, + 0x99, + 0x0B, + 0x04, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + # R24H b + EPD_4IN2_4Gray_lut_bb = [ + 0x80, + 0x0A, + 0x00, + 0x00, + 0x00, + 0x01, + 0x90, + 0x14, + 0x14, + 0x00, + 0x00, + 0x01, + 0x20, + 0x14, + 0x0A, + 0x00, + 0x00, + 0x01, + 0x50, + 0x13, + 0x01, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ] + + # Hardware reset + def reset(self): + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(10) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(10) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(10) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(10) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(10) + epdconfig.digital_write(self.reset_pin, 0) + epdconfig.delay_ms(10) + epdconfig.digital_write(self.reset_pin, 1) + epdconfig.delay_ms(10) + + def send_command(self, command): + epdconfig.digital_write(self.dc_pin, 0) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([command]) + epdconfig.digital_write(self.cs_pin, 1) + + def send_data(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte([data]) + epdconfig.digital_write(self.cs_pin, 1) + + # send a lot of data + def send_data2(self, data): + epdconfig.digital_write(self.dc_pin, 1) + epdconfig.digital_write(self.cs_pin, 0) + epdconfig.spi_writebyte2(data) + epdconfig.digital_write(self.cs_pin, 1) + + def ReadBusy(self): + self.send_command(0x71) + while epdconfig.digital_read(self.busy_pin) == 0: # 0: idle, 1: busy + self.send_command(0x71) + epdconfig.delay_ms(100) + + def set_lut(self): + self.send_command(0x20) # vcom + self.send_data2(self.lut_vcom0) + + self.send_command(0x21) # ww -- + self.send_data2(self.lut_ww) + + self.send_command(0x22) # bw r + self.send_data2(self.lut_bw) + + self.send_command(0x23) # wb w + self.send_data2(self.lut_bb) + + self.send_command(0x24) # bb b + self.send_data2(self.lut_wb) + + def Partial_SetLut(self): + self.send_command(0x20) + self.send_data2(self.EPD_4IN2_Partial_lut_vcom1) + + self.send_command(0x21) + self.send_data2(self.EPD_4IN2_Partial_lut_ww1) + + self.send_command(0x22) + self.send_data2(self.EPD_4IN2_Partial_lut_bw1) + + self.send_command(0x23) + self.send_data2(self.EPD_4IN2_Partial_lut_wb1) + + self.send_command(0x24) + self.send_data2(self.EPD_4IN2_Partial_lut_bb1) + + def Gray_SetLut(self): + self.send_command(0x20) # vcom + self.send_data2(self.EPD_4IN2_4Gray_lut_vcom) + + self.send_command(0x21) # red not use + self.send_data2(self.EPD_4IN2_4Gray_lut_ww) + + self.send_command(0x22) # bw r + self.send_data2(self.EPD_4IN2_4Gray_lut_bw) + + self.send_command(0x23) # wb w + self.send_data2(self.EPD_4IN2_4Gray_lut_wb) + + self.send_command(0x24) # bb b + self.send_data2(self.EPD_4IN2_4Gray_lut_bb) + + self.send_command(0x25) # vcom + self.send_data2(self.EPD_4IN2_4Gray_lut_ww) + + def init(self): + if epdconfig.module_init() != 0: + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x01) # POWER SETTING + self.send_data(0x03) # VDS_EN, VDG_EN + self.send_data(0x00) # VCOM_HV, VGHL_LV[1], VGHL_LV[0] + self.send_data(0x2B) # VDH + self.send_data(0x2B) # VDL + + self.send_command(0x06) # boost soft start + self.send_data(0x17) + self.send_data(0x17) + self.send_data(0x17) + + self.send_command(0x04) # POWER_ON + self.ReadBusy() + + self.send_command(0x00) # panel setting + self.send_data(0xBF) # KW-BF KWR-AF BWROTP 0f + + self.send_command(0x30) # PLL setting + self.send_data(0x3C) # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ + + self.send_command(0x61) # resolution setting + self.send_data(0x01) + self.send_data(0x90) # 128 + self.send_data(0x01) + self.send_data(0x2C) + + self.send_command(0x82) # vcom_DC setting + self.send_data(0x12) + + self.send_command(0x50) # VCOM AND DATA INTERVAL SETTING + self.send_data( + 0x97 + ) # 97white border 77black border VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7 + + self.set_lut() + # EPD hardware init end + return 0 + + def init_Partial(self): + if epdconfig.module_init() != 0: + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x01) # POWER SETTING + self.send_data(0x03) # VDS_EN, VDG_EN + self.send_data(0x00) # VCOM_HV, VGHL_LV[1], VGHL_LV[0] + self.send_data(0x2B) # VDH + self.send_data(0x2B) # VDL + + self.send_command(0x06) # boost soft start + self.send_data(0x17) + self.send_data(0x17) + self.send_data(0x17) + + self.send_command(0x04) # POWER_ON + self.ReadBusy() + + self.send_command(0x00) # panel setting + self.send_data(0xBF) # KW-BF KWR-AF BWROTP 0f + + self.send_command(0x30) # PLL setting + self.send_data(0x3C) # 3A 100HZ 29 150Hz 39 200HZ 31 171HZ + + self.send_command(0x61) # resolution setting + self.send_data(0x01) + self.send_data(0x90) # 128 + self.send_data(0x01) + self.send_data(0x2C) + + self.send_command(0x82) # vcom_DC setting + self.send_data(0x12) + + self.send_command(0x50) # VCOM AND DATA INTERVAL SETTING + self.send_data( + 0x07 + ) # 97white border 77black border VBDF 17|D7 VBDW 97 VBDB 57 VBDF F7 VBDW 77 VBDB 37 VBDR B7 + + self.Partial_SetLut() + # EPD hardware init end + return 0 + + def Init_4Gray(self): + if epdconfig.module_init() != 0: + return -1 + # EPD hardware init start + self.reset() + + self.send_command(0x01) # POWER SETTING + self.send_data(0x03) + self.send_data(0x00) # VGH=20V,VGL=-20V + self.send_data(0x2B) # VDH=15V + self.send_data(0x2B) # VDL=-15V + self.send_data(0x13) + + self.send_command(0x06) # booster soft start + self.send_data(0x17) # A + self.send_data(0x17) # B + self.send_data(0x17) # C + + self.send_command(0x04) + self.ReadBusy() + + self.send_command(0x00) # panel setting + self.send_data(0x3F) # KW-3f KWR-2F BWROTP 0f BWOTP 1f + + self.send_command(0x30) # PLL setting + self.send_data(0x3C) # 100hz + + self.send_command(0x61) # resolution setting + self.send_data(0x01) # 400 + self.send_data(0x90) + self.send_data(0x01) # 300 + self.send_data(0x2C) + + self.send_command(0x82) # vcom_DC setting + self.send_data(0x12) + + self.send_command(0x50) # VCOM AND DATA INTERVAL SETTING + self.send_data(0x97) + + def getbuffer(self, image): + # logger.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width / 8) * self.height) + image_monocolor = image.convert("1") + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight) + if imwidth == self.width and imheight == self.height: + for y in range(imheight): + for x in range(imwidth): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0: + buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8)) + elif imwidth == self.height and imheight == self.width: + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] == 0: + buf[int((newx + newy * self.width) / 8)] &= ~(0x80 >> (y % 8)) + return buf + + def getbuffer_4Gray(self, image): + # logger.debug("bufsiz = ",int(self.width/8) * self.height) + buf = [0xFF] * (int(self.width / 4) * self.height) + image_monocolor = image.convert("L") + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + i = 0 + # logger.debug("imwidth = %d, imheight = %d",imwidth,imheight) + if imwidth == self.width and imheight == self.height: + for y in range(imheight): + for x in range(imwidth): + # Set the bits for the column of pixels at the current position. + if pixels[x, y] == 0xC0: + pixels[x, y] = 0x80 + elif pixels[x, y] == 0x80: + pixels[x, y] = 0x40 + i = i + 1 + if i % 4 == 0: + buf[int((x + (y * self.width)) / 4)] = ( + (pixels[x - 3, y] & 0xC0) + | (pixels[x - 2, y] & 0xC0) >> 2 + | (pixels[x - 1, y] & 0xC0) >> 4 + | (pixels[x, y] & 0xC0) >> 6 + ) + + elif imwidth == self.height and imheight == self.width: + for x in range(imwidth): + for y in range(imheight): + newx = y + newy = x + if pixels[x, y] == 0xC0: + pixels[x, y] = 0x80 + elif pixels[x, y] == 0x80: + pixels[x, y] = 0x40 + i = i + 1 + if i % 4 == 0: + buf[int((newx + (newy * self.width)) / 4)] = ( + (pixels[x, y - 3] & 0xC0) + | (pixels[x, y - 2] & 0xC0) >> 2 + | (pixels[x, y - 1] & 0xC0) >> 4 + | (pixels[x, y] & 0xC0) >> 6 + ) + + return buf + + def display(self, image): + if self.width % 8 == 0: + linewidth = int(self.width / 8) + else: + linewidth = int(self.width / 8) + 1 + + self.send_command(0x92) + self.set_lut() + self.send_command(0x10) + self.send_data2([0xFF] * int(self.width * linewidth)) + + self.send_command(0x13) + self.send_data2(image) + + self.send_command(0x12) + self.ReadBusy() + + def EPD_4IN2_PartialDisplay(self, X_start, Y_start, X_end, Y_end, Image): + # EPD_WIDTH = 400 + # EPD_HEIGHT = 300 + + if EPD_WIDTH % 8 != 0: + Width = int(EPD_WIDTH / 8) + 1 + else: + Width = int(EPD_WIDTH / 8) + Height = EPD_HEIGHT + + if X_start % 8 != 0: + X_start = int(X_start / 8) + 1 + else: + X_start = int(X_start / 8) + if X_end % 8 != 0: + X_end = int(X_end / 8) + 1 + else: + X_end = int(X_end / 8) + + buf = [0x00] * (Y_end - Y_start) * (X_end - X_start) + + self.send_command(0x91) # This command makes the display enter partial mode + self.send_command(0x90) # resolution setting + self.send_data(int(X_start * 8 / 256)) + self.send_data(int(X_start * 8 % 256)) # x-start + + self.send_data(int(X_end * 8 / 256)) + self.send_data(int(X_end * 8 % 256) - 1) # x-end + + self.send_data(int(Y_start / 256)) + self.send_data(int(Y_start % 256)) # y-start + + self.send_data(int(Y_end / 256)) + self.send_data(int(Y_end % 256) - 1) # y-end + self.send_data(0x28) + + self.send_command(0x10) # writes Old data to SRAM for programming + for j in range(0, Y_end - Y_start): + for i in range(0, X_end - X_start): + buf[j * (X_end - X_start) + i] = self.DATA[ + (Y_start + j) * Width + X_start + i + ] + self.send_data2(buf) + + self.send_command(0x13) # writes New data to SRAM. + for j in range(0, Y_end - Y_start): + for i in range(0, X_end - X_start): + buf[j * (X_end - X_start) + i] = ~Image[ + (Y_start + j) * Width + X_start + i + ] + index123 = int((Y_start + j) * Width + X_start + i) + self.DATA[(Y_start + j) * Width + X_start + i] = ~Image[index123] + self.send_data2(buf) + + self.send_command(0x12) # DISPLAY REFRESH + epdconfig.delay_ms(200) # The delay here is necessary, 200uS at least!!! + self.ReadBusy() + + def display_4Gray(self, image): + self.send_command(0x92) + self.set_lut() + self.send_command(0x10) + + if self.width % 8 == 0: + linewidth = int(self.width / 8) + else: + linewidth = int(self.width / 8) + 1 + + buf = [0x00] * self.height * linewidth + + for i in range( + 0, int(EPD_WIDTH * EPD_HEIGHT / 8) + ): # EPD_WIDTH * EPD_HEIGHT / 4 + temp3 = 0 + for j in range(0, 2): + temp1 = image[i * 2 + j] + for k in range(0, 2): + temp2 = temp1 & 0xC0 + if temp2 == 0xC0: + temp3 |= 0x01 # white + elif temp2 == 0x00: + temp3 |= 0x00 # black + elif temp2 == 0x80: + temp3 |= 0x01 # gray1 + else: # 0x40 + temp3 |= 0x00 # gray2 + temp3 <<= 1 + + temp1 <<= 2 + temp2 = temp1 & 0xC0 + if temp2 == 0xC0: # white + temp3 |= 0x01 + elif temp2 == 0x00: # black + temp3 |= 0x00 + elif temp2 == 0x80: + temp3 |= 0x01 # gray1 + else: # 0x40 + temp3 |= 0x00 # gray2 + if j != 1 or k != 1: + temp3 <<= 1 + temp1 <<= 2 + buf[i] = temp3 + self.send_data2(buf) + + self.send_command(0x13) + + for i in range(0, int(EPD_WIDTH * EPD_HEIGHT / 8)): # 5808*4 46464 + temp3 = 0 + for j in range(0, 2): + temp1 = image[i * 2 + j] + for k in range(0, 2): + temp2 = temp1 & 0xC0 + if temp2 == 0xC0: + temp3 |= 0x01 # white + elif temp2 == 0x00: + temp3 |= 0x00 # black + elif temp2 == 0x80: + temp3 |= 0x00 # gray1 + else: # 0x40 + temp3 |= 0x01 # gray2 + temp3 <<= 1 + + temp1 <<= 2 + temp2 = temp1 & 0xC0 + if temp2 == 0xC0: # white + temp3 |= 0x01 + elif temp2 == 0x00: # black + temp3 |= 0x00 + elif temp2 == 0x80: + temp3 |= 0x00 # gray1 + else: # 0x40 + temp3 |= 0x01 # gray2 + if j != 1 or k != 1: + temp3 <<= 1 + temp1 <<= 2 + buf[i] = temp3 + self.send_data2(buf) + + self.Gray_SetLut() + self.send_command(0x12) + epdconfig.delay_ms(200) + self.ReadBusy() + # pass + + def Clear(self): + if self.width % 8 == 0: + linewidth = int(self.width / 8) + else: + linewidth = int(self.width / 8) + 1 + + self.send_command(0x10) + self.send_data2([0xFF] * int(self.height * linewidth)) + + self.send_command(0x13) + self.send_data2([0xFF] * int(self.height * linewidth)) + + self.send_command(0x12) + self.ReadBusy() + + def sleep(self): + self.send_command(0x02) # POWER_OFF + self.ReadBusy() + self.send_command(0x07) # DEEP_SLEEP + self.send_data(0xA5) + + epdconfig.delay_ms(2000) + epdconfig.module_exit() + + +### END OF FILE ### diff --git a/display/epdconfig.py b/display/epdconfig.py new file mode 100644 index 0000000..7292746 --- /dev/null +++ b/display/epdconfig.py @@ -0,0 +1,118 @@ +# display/epdconfig.py + +import logging +import time + +logger = logging.getLogger(__name__) + + +class DummyGPIO: + BCM = OUT = IN = 0 + + @staticmethod + def setmode(*args): + pass + + @staticmethod + def setwarnings(*args): + pass + + @staticmethod + def setup(*args): + pass + + @staticmethod + def output(*args): + pass + + @staticmethod + def input(*args): + return 0 + + @staticmethod + def cleanup(*args): + pass + + +class DummySPI: + max_speed_hz = 4000000 + mode = 0b00 + + @staticmethod + def open(*args): + pass + + @staticmethod + def writebytes(*args): + pass + + @staticmethod + def writebytes2(*args): + pass + + @staticmethod + def close(): + pass + + +try: + import RPi.GPIO as GPIO + import spidev + + SPI = spidev.SpiDev() +except ImportError: + logger.warning( + "RPi.GPIO or spidev not found. Using Dummy hardware drivers (Test Mode)." + ) + GPIO = DummyGPIO() + SPI = DummySPI() + +RST_PIN = 17 +DC_PIN = 25 +CS_PIN = 8 +BUSY_PIN = 24 +PWR_PIN = 18 + + +def digital_write(pin, value): + GPIO.output(pin, value) + + +def digital_read(pin): + return GPIO.input(pin) + + +def delay_ms(delaytime): + time.sleep(delaytime / 1000.0) + + +def spi_writebyte(data): + SPI.writebytes(data) + + +def spi_writebyte2(data): + SPI.writebytes2(data) + + +def module_init(): + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(RST_PIN, GPIO.OUT) + GPIO.setup(DC_PIN, GPIO.OUT) + GPIO.setup(CS_PIN, GPIO.OUT) + GPIO.setup(PWR_PIN, GPIO.OUT) + GPIO.setup(BUSY_PIN, GPIO.IN) + GPIO.output(PWR_PIN, 1) + + SPI.open(0, 0) + SPI.max_speed_hz = 4000000 + SPI.mode = 0b00 + return 0 + + +def module_exit(): + SPI.close() + GPIO.output(RST_PIN, 0) + GPIO.output(DC_PIN, 0) + GPIO.output(PWR_PIN, 0) + GPIO.cleanup([RST_PIN, DC_PIN, CS_PIN, BUSY_PIN, PWR_PIN]) diff --git a/display/image_maker.py b/display/image_maker.py new file mode 100644 index 0000000..f83b92b --- /dev/null +++ b/display/image_maker.py @@ -0,0 +1,290 @@ +# display/image_maker.py + +from PIL import Image, ImageDraw, ImageFont, ImageOps + +from fonts import Font +from icons import Icon + +from .epd4in2 import EPD_HEIGHT as HEIGHT +from .epd4in2 import EPD_WIDTH as WIDTH + + +def closest(num_list: list, K): + return num_list[min(range(len(num_list)), key=lambda i: abs(num_list[i] - K))] + + +def draw_temp_block( + d: ImageDraw.ImageDraw, + coords: tuple[int, int], + content: list[tuple[str, int, ImageFont.FreeTypeFont]], +): + x, y = coords + for text, color, font in content: + d.text((x, y), text, fill=color, font=font) + text_width = int(font.getlength(text)) + x += text_width + + +class eInkImage: + def __init__(self): + self.info_font_y_offset = 1 + self.info_y_offset = 14 + + def make_image( + self, date: tuple, weather_data: dict, network_status: bool + ) -> Image.Image: + forecast = weather_data.get("forecast", {}) + image = Image.new("1", (WIDTH, HEIGHT), 255) + draw = ImageDraw.Draw(image) + + # Top + draw.text((6, 2), date[0], font=Font.f20, fill=0) + draw.text((6, 20), date[1], font=Font.f60, fill=0) + + # --- Left-side --- + image.paste(Icon.default["indoor"], (6, 78 + self.info_y_offset)) + draw.text( + (36, 78 - self.info_font_y_offset + self.info_y_offset), + "Inomhus", + font=Font.f20, + fill=0, + ) + + # Temp Logic + indoor_temp = weather_data.get("indoor_temp", 20) + if indoor_temp >= 24.0: + indoor_temp_icon = Icon.default["temp_high"] + elif indoor_temp <= 18.0: + indoor_temp_icon = Icon.default["temp_low"] + else: + indoor_temp_icon = Icon.default["temp_mid"] + + image.paste(indoor_temp_icon, (6, 116 + self.info_y_offset)) + + draw_temp_block( + draw, + (38, 93 + self.info_y_offset), + [ + (str(weather_data.get("indoor_temp", "--")), 0, Font.f55), + ("°C", 0, Font.f25), + ], + ) + # ---------- + image.paste(Icon.default["humidity"], (6, 160 + self.info_y_offset)) + draw.text( + (36, 160 - self.info_font_y_offset + self.info_y_offset), + f"{weather_data.get('indoor_humidity', '--')}%", + font=Font.f20, + fill=0, + ) + # ---------- + image.paste(Icon.default["pressure"], (6, 190 + self.info_y_offset)) + draw.text( + (36, 190 - self.info_font_y_offset + self.info_y_offset), + f"{weather_data.get('pressure', '--')}hPa", + font=Font.f20, + fill=0, + ) + + # Sun + image.paste(Icon.default["sunrise"], (6, 220 + self.info_y_offset)) + draw.text( + (36, 220 - self.info_font_y_offset + self.info_y_offset), + weather_data.get("sunrise", "--"), + font=Font.f20, + fill=0, + ) + # ---------- + image.paste(Icon.default["sunset"], (101, 220 + self.info_y_offset)) + draw.text( + (131, 220 - self.info_font_y_offset + self.info_y_offset), + weather_data.get("sunset", "--"), + font=Font.f20, + fill=0, + ) + + # Moon + moon_value = closest( + [direction for direction, _ in Icon.moon.items()], + weather_data.get("moon_illumination", 0.167) * 100, + ) + moon_icon = Icon.moon[moon_value] + + if not weather_data.get("moon_to_full", True): + image.paste(moon_icon, (6, 250 + self.info_y_offset)) + else: + image.paste(ImageOps.mirror(moon_icon), (6, 250 + self.info_y_offset)) + draw.text( + (42, 250 + self.info_y_offset), + weather_data.get("moon_phase", "----"), + font=Font.f20, + fill=0, + ) + + # Right-side + right_side_x = 204 + if weather_data.get("temperature", 10) >= 24.0: + outdoor_temp_icon = Icon.default["temp_high"] + elif weather_data.get("temperature", 10) < 0.0: + outdoor_temp_icon = Icon.default["temp_low"] + else: + outdoor_temp_icon = Icon.default["temp_mid"] + image.paste(outdoor_temp_icon, (right_side_x, 116 + self.info_y_offset)) + draw_temp_block( + draw, + (right_side_x + 30, 93 + self.info_y_offset), + [ + (str(weather_data.get("temperature", "--")), 0, Font.f55), + ("°C", 0, Font.f25), + ], + ) + # ---------- + image.paste(Icon.default["humidity"], (right_side_x, 160 + self.info_y_offset)) + draw.text( + (right_side_x + 30, 160 - self.info_font_y_offset + self.info_y_offset), + f"{weather_data.get('humidity', '--')}%", + font=Font.f20, + fill=0, + ) + # ---------- + image.paste(Icon.default["dew"], (right_side_x + 96, 160 + self.info_y_offset)) + draw.text( + (right_side_x + 126, 160 - self.info_font_y_offset + self.info_y_offset), + f"{weather_data.get('dew', '--')}°C", + font=Font.f20, + fill=0, + ) + + # Wind + wind_offset = 20 + wind_deg = forecast.get("wind", {}).get("deg", 0) + wind_speed = forecast.get("wind", {}).get("speed", "--") + wind_gust = forecast.get("wind", {}).get("gust", "--") + has_wind = bool(forecast.get("wind")) + + wind_value = closest( + [direction for direction, _ in Icon.wind.items()], wind_deg + ) + + wind_direction, wind_icon = ( + Icon.wind[wind_value] if has_wind else ("--", Icon.wind[180][1]) + ) + + direction_width = int(Font.f14.getlength(wind_direction)) + wind_direction_text_x = ( + right_side_x + wind_offset + ((30 - direction_width) / 2) + ) + + image.paste(wind_icon, (right_side_x + wind_offset, 192 + self.info_y_offset)) + image.paste( + Icon.default["wind"], + (right_side_x + 40 + wind_offset, 190 + self.info_y_offset), + ) + image.paste( + Icon.default["wind_gust"], + (right_side_x + 40 + wind_offset, 210 + self.info_y_offset), + ) + + draw.text( + (wind_direction_text_x, 222 + self.info_y_offset), + wind_direction, + font=Font.f14, + fill=0, + ) + draw.text( + ( + right_side_x + 70 + wind_offset, + 190 - self.info_font_y_offset + self.info_y_offset, + ), + f"{wind_speed}{'m/s' if has_wind else ''}", + font=Font.f20, + fill=0, + ) + draw.text( + ( + right_side_x + 70 + wind_offset, + 210 - self.info_font_y_offset + self.info_y_offset, + ), + f"{wind_gust}{'m/s' if has_wind else ''}", + font=Font.f20, + fill=0, + ) + + # UV + image.paste(Icon.default["uv"], (right_side_x + 60, 240 + self.info_y_offset)) + draw.text( + ( + right_side_x + 60 + 30, + 240 - self.info_font_y_offset + self.info_y_offset, + ), + str(weather_data.get("uv_index", "--")), + font=Font.f20, + fill=0, + ) + + # Warnings + if not network_status: + image.paste( + Icon.warning["no_wifi"], + (WIDTH - Icon.warning["no_wifi"].width - 4, 80), + ) + + if ( + forecast.get("wind", {}).get("speed", 0) >= 14.0 + or forecast.get("wind", {}).get("gust", 0) >= 16.0 + ): + image.paste( + Icon.warning["wind"], (250 - Icon.warning["wind"].width - 4, 80) + ) + + if weather_data.get("fire_index", 0) > 3: + image.paste( + Icon.warning["fire"], (WIDTH - Icon.warning["fire"].width - 4, 4) + ) + + alerts = weather_data.get("weather_alerts") + + if alerts and isinstance(alerts, list) and len(alerts) > 0: + warning_code = alerts[0].get("type") + + if warning_code in ["RED", "ORANGE", "YELLOW"]: + image.paste( + Icon.warning[warning_code], + (WIDTH - Icon.warning[warning_code].width - 4, 80), + ) + elif warning_code == "MESSAGE": + if warning_code == "WATER_SHORTAGE": + image.paste( + Icon.warning["water_shortage"], + (WIDTH - Icon.warning["water_shortage"].width - 4, 80), + ) + elif warning_code == "HIGH_TEMPERATURES": + image.paste( + Icon.warning["temperature_high"], + (WIDTH - Icon.warning["temperature_high"].width - 4, 80), + ) + else: + image.paste( + Icon.warning["YELLOW"], + (WIDTH - Icon.warning["YELLOW"].width - 4, 80), + ) + + # Weather-icon + image = image.convert("L") + if ( + weather_data.get("sunrise", "06:00") + < date[1] + < weather_data.get("sunset", "20:00") + ): + time_of_day = "day" + else: + time_of_day = "night" + weather_icon = Icon.get_forecast( + forecast.get("weather_symbol", 0), + time_of_day, + forecast.get("wind", {}).get("speed", 0), + ) + weather_icon_y_offset = int(((120 - weather_icon.size[1]) / 2)) + image.paste(weather_icon, (250, weather_icon_y_offset), weather_icon) + + return image diff --git a/environment/__init__.py b/environment/__init__.py new file mode 100644 index 0000000..4c919d0 --- /dev/null +++ b/environment/__init__.py @@ -0,0 +1,35 @@ +# environment/__init__.py + +import logging +from datetime import datetime + +import requests + +from constants import HOME_LOCATION +from tools.geometry import Coordinate + +from .local_sensors import Sensors +from .pysky import Moon, Sun +from .smhi import SMHI +from .wireless_sensor import WirelessSensor + +logger = logging.getLogger(__name__) + + +class Weather: + def __init__(self, location: Coordinate = HOME_LOCATION) -> None: + self.location = location + self.session = requests.Session() + self.smhi_api = SMHI(self.location, self.session) + self.sensor_data = Sensors() + self.wireless_sensor = WirelessSensor(self.session) + + def get(self, date: datetime) -> dict: + logger.debug("Collecting weather data from all sensors...") + return ( + self.sensor_data.get() + | self.wireless_sensor.get() + | Moon.get_illumination(date) + | Sun.get_sunset_sunrise(self.location, date) + | self.smhi_api.get() + ) diff --git a/environment/local_sensors.py b/environment/local_sensors.py new file mode 100644 index 0000000..b3de71b --- /dev/null +++ b/environment/local_sensors.py @@ -0,0 +1,46 @@ +# environment/local_sensors.py + +import logging + +logger = logging.getLogger(__name__) + +try: + import board + from adafruit_bme280 import basic as adafruit_bme280 +except ImportError as e: + logger.warning( + f"BME280 dependencies not installed (Normal for test environments) | {e}" + ) +except Exception as e: + logger.error(f"Error importing Raspberry Pi specific dependencies | {e}") + + +class Sensors: + def __init__(self): + self._bme280 = None + self._data = {} + try: + if "adafruit_bme280" in globals(): + self._bme280 = adafruit_bme280.Adafruit_BME280_I2C( + board.I2C(), address=0x76 + ) + except Exception as e: + logger.error(f"BME280 hardware not found on I2C bus! | {e}") + + def _get_data(self): + if self._bme280 is None: + return + + try: + self._data = { + "indoor_temp": round(self._bme280.temperature, 1), + "indoor_humidity": round(self._bme280.relative_humidity, 1), + "pressure": round(self._bme280.pressure, 1), + } + except Exception as e: + logger.error(f"Failed getting indoor sensor data: {e}") + + def get(self) -> dict: + logger.debug("Measuring environment indoors...") + self._get_data() + return self._data diff --git a/environment/pysky.py b/environment/pysky.py new file mode 100644 index 0000000..6f9b6a1 --- /dev/null +++ b/environment/pysky.py @@ -0,0 +1,152 @@ +# environment/pysky.py + +import logging +from datetime import datetime, timedelta +from math import acos, asin, ceil, cos, degrees, fmod +from math import pi as PI +from math import radians, sin, sqrt + +from constants import Coordinate +from tools.julian import from_julian, julian_date + +logger = logging.getLogger(__name__) + + +class Sun: + + @staticmethod + def _calc(location: Coordinate, date: datetime, elevation: float = 0.0) -> dict: + J_date = julian_date(date) + n = ceil(J_date - (2451545.0 + 0.0009) + 69.184 / 86400.0) - 1 + J_ = n + 0.0009 - location.long / 360.0 + M_degrees = fmod(357.5291 + 0.98560028 * J_, 360) + M_radians = radians(M_degrees) + C_degrees = ( + 1.9148 * sin(M_radians) + + 0.02 * sin(2 * M_radians) + + 0.0003 * sin(3 * M_radians) + ) + L_degrees = fmod(M_degrees + C_degrees + 180.0 + 102.9372, 360) + Lambda_radians = radians(L_degrees) + J_transit = ( + 2451545.0 + J_ + 0.0053 * sin(M_radians) - 0.0069 * sin(2 * Lambda_radians) + ) + sin_d = sin(Lambda_radians) * sin(radians(23.4397)) + cos_d = cos(asin(sin_d)) + some_cos = ( + sin(radians(-0.833 - 2.076 * sqrt(elevation) / 60.0)) + - sin(radians(location.lat)) * sin_d + ) / (cos(radians(location.lat)) * cos_d) + + try: + w0_radians = acos(some_cos) + except ValueError: + return {"sunrise": "--:--", "sunset": "--:--"} + + w0_degrees = degrees(w0_radians) + j_rise = J_transit - w0_degrees / 360 + j_set = J_transit + w0_degrees / 360 + + sunset_time = from_julian(j_set) + sunrise_time = from_julian(j_rise) + + return { + "sunrise": f"{sunrise_time.hour:02d}:{sunrise_time.minute:02d}", + "sunset": f"{sunset_time.hour:02d}:{sunset_time.minute:02d}", + } + + @staticmethod + def get_sunset_sunrise(location: Coordinate, date: datetime | None = None) -> dict: + if date is None: + date = datetime.now() + logger.debug("Calculating Sun trajectory...") + return Sun._calc(location, date) + + +class Moon: + + @staticmethod + def _illumination_name(illumination: float, waxing: bool = True) -> str: + if illumination > 0.996: + return "Fullmåne" + elif illumination > 0.57: + return "Tilltagande halvmåne" if waxing else "Avtagande halvmåne" + elif illumination > 0.43: + return "Halvmåne (tilltagande)" if waxing else "Halvmåne (avtagande)" + elif illumination > 0.02: + return "Tilltagande skära" if waxing else "Avtagande skära" + else: + return "Nymåne" + + @staticmethod + def _constrain(d: float) -> float: + t = d % 360 + if t < 0: + t += 360 + return t + + @staticmethod + def _get_illuminated_fraction(jd: float) -> float: + toRad = PI / 180.0 + T = (jd - 2451545) / 36525.0 + D = ( + Moon._constrain( + 297.8501921 + + 445267.1114034 * T + - 0.0018819 * T * T + + 1.0 / 545868.0 * T * T * T + - 1.0 / 113065000.0 * T * T * T * T + ) + * toRad + ) + M = ( + Moon._constrain( + 357.5291092 + + 35999.0502909 * T + - 0.0001536 * T * T + + 1.0 / 24490000.0 * T * T * T + ) + * toRad + ) + Mp = ( + Moon._constrain( + 134.9633964 + + 477198.8675055 * T + + 0.0087414 * T * T + + 1.0 / 69699.0 * T * T * T + - 1.0 / 14712000.0 * T * T * T * T + ) + * toRad + ) + i = ( + Moon._constrain( + 180 + - D * 180 / PI + - 6.289 * sin(Mp) + + 2.1 * sin(M) + - 1.274 * sin(2 * D - Mp) + - 0.658 * sin(2 * D) + - 0.214 * sin(2 * Mp) + - 0.11 * sin(D) + ) + * toRad + ) + return (1 + cos(i)) / 2 + + @staticmethod + def get_illumination(date: datetime | None = None) -> dict: + if date is None: + date = datetime.now() + logger.debug("Calculating Moon illumination...") + + i = Moon._get_illuminated_fraction(julian_date(date)) + i_future = Moon._get_illuminated_fraction( + julian_date(date + timedelta(seconds=1)) + ) + waxing = i_future > i + + return { + "moon_illumination": round(i, 4), + "moon_phase": Moon._illumination_name(i, waxing), + "waxing": waxing, + } diff --git a/environment/smhi.py b/environment/smhi.py new file mode 100644 index 0000000..d8433e3 --- /dev/null +++ b/environment/smhi.py @@ -0,0 +1,163 @@ +# environment/smhi.py + +import logging +from datetime import datetime + +import requests +from tools.geometry import Coordinate, LineString, MultiPolygon, Polygon + +logger = logging.getLogger(__name__) + + +class SMHI: + + fire_warning_api = "https://opendata-download-metfcst.smhi.se/api/category/fwif1g/version/1/daily/geotype/point/lon/{lon}/lat/{lat}/data.json" + forecast_api = "https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/{lon}/lat/{lat}/data.json" + alerts_api = ( + "https://opendata-download-warnings.smhi.se/ibww/api/version/1/warning.json" + ) + + def __init__(self, location: Coordinate, session: requests.Session) -> None: + self.location = location + self._session = session + self._data = {"fire_index": {}, "weather_alerts": {}, "forecast": {}} + self._api = { + "fire_index": self._get_fire_warning, + "weather_alerts": self._get_alerts, + "forecast": self._get_forecast, + } + + def _calculate_area(self, area_type: str, area_list: list) -> bool: + """Helper to check if our home coordinates fall within an SMHI warning zone.""" + point = self.location.to_Point(reversed=True) + match area_type: + case "Polygon": + return Polygon(area_list[0]).contains(point) + case "LineString": + return LineString(area_list).distance_to(point) <= 0.045 + case "MultiPolygon": + return MultiPolygon(area_list).contains(point) + return False + + def _get_fire_warning(self) -> bool: + try: + resp = self._session.get( + self.fire_warning_api.format( + lon=self.location.long, lat=self.location.lat + ), + timeout=10, + ) + resp.raise_for_status() + + for param in resp.json()["timeSeries"][0]["parameters"]: + if param["name"] == "fwiindex": + self._data["fire_index"] = { + "data": param["values"][0], + "valid_time": datetime.now(), + } + return True + except requests.RequestException as e: + logger.error(f"Network error getting SMHI Fire index: {e}") + except Exception as e: + logger.error(f"Parsing error for SMHI Fire index: {e}") + return False + + def _get_alerts(self) -> bool: + try: + resp = self._session.get(self.alerts_api, timeout=10) + resp.raise_for_status() + + area_list = [] + for warning in resp.json(): + for areas in warning["warningAreas"]: + is_in_area = False + if areas["area"]["type"] == "FeatureCollection": + for features in areas["area"]["features"]: + if self._calculate_area( + features["geometry"]["type"], + features["geometry"]["coordinates"], + ): + is_in_area = True + break + elif self._calculate_area( + areas["area"]["geometry"]["type"], + areas["area"]["geometry"]["coordinates"], + ): + is_in_area = True + + if is_in_area: + area_list.append( + { + "areaName": areas["areaName"]["sv"], + "type": areas["warningLevel"]["code"], + "description": areas["eventDescription"]["code"], + } + ) + + self._data["weather_alerts"] = { + "data": area_list, + "valid_time": datetime.now(), + } + return True + except requests.RequestException as e: + logger.error(f"Network error getting SMHI Alerts: {e}") + except Exception as e: + logger.error(f"Parsing error for SMHI Alerts: {e}") + return False + + def _get_forecast(self) -> bool: + try: + resp = self._session.get( + self.forecast_api.format(lon=self.location.long, lat=self.location.lat), + timeout=10, + ) + resp.raise_for_status() + + data = resp.json()["timeSeries"][1] + w_data = {x["name"]: x["values"][0] for x in data["parameters"]} + + self._data["forecast"] = { + "data": { + "wind": { + "deg": w_data["wd"], + "speed": round(w_data["ws"], 1), + "gust": round(w_data["gust"], 1), + }, + "weather_symbol": w_data["Wsymb2"] - 1, + }, + "valid_time": datetime.now(), + } + return True + except requests.RequestException as e: + logger.error(f"Network error getting SMHI Forecast: {e}") + except Exception as e: + logger.error(f"Parsing error for SMHI Forecast: {e}") + return False + + def get(self) -> dict: + """Returns valid SMHI data, utilizing cached data if recently fetched.""" + valid_data = {} + for api_name, fetch_func in self._api.items(): + cache = self._data[api_name] + valid_time = cache.get("valid_time", datetime.fromtimestamp(0)) + age_seconds = (datetime.now() - valid_time).total_seconds() + + if age_seconds <= 300: + logger.debug(f'Using cached data for "{api_name}"') + valid_data[api_name] = cache["data"] + else: + logger.debug(f'Fetching new data for "{api_name}"...') + if fetch_func(): + logger.debug(f'Successfully fetched new data for "{api_name}"') + valid_data[api_name] = self._data[api_name]["data"] + elif age_seconds <= 600: + logger.warning( + f'Failed fetching "{api_name}", falling back to 10-min cache.' + ) + valid_data[api_name] = cache["data"] + else: + logger.error( + f'SMHI data "{api_name}" is too old and cannot be refreshed.' + ) + + return valid_data diff --git a/environment/wireless_sensor.py b/environment/wireless_sensor.py new file mode 100644 index 0000000..4d147c6 --- /dev/null +++ b/environment/wireless_sensor.py @@ -0,0 +1,42 @@ +# environment/wireless_sensor.py + +import logging +from datetime import datetime + +import requests +from constants import SENSOR_HTTP + +logger = logging.getLogger(__name__) + + +class WirelessSensor: + + def __init__(self, session: requests.Session) -> None: + self._sensor_data = {} + self._last_poll = None + self._session = session + + def _poll_sensor(self) -> None: + try: + resp = self._session.get(SENSOR_HTTP, timeout=6) + resp.raise_for_status() + + self._last_poll = datetime.now() + self._sensor_data = resp.json() + except requests.RequestException as e: + logger.error(f"Network error connecting to wireless sensor: {e}") + except Exception as e: + logger.error(f"Error parsing wireless sensor data: {e}") + + def get(self) -> dict: + logger.debug("Polling Wireless Sensor...") + self._poll_sensor() + + if self._last_poll: + age_seconds = abs((datetime.now() - self._last_poll).total_seconds()) + if age_seconds < 600: + return self._sensor_data + else: + logger.warning("Wireless sensor data is too old (over 10 mins).") + + return {} diff --git a/fonts/__init__.py b/fonts/__init__.py new file mode 100644 index 0000000..5e7b38c --- /dev/null +++ b/fonts/__init__.py @@ -0,0 +1,15 @@ +# fonts/__init__.py + +from pathlib import Path +from PIL import ImageFont + + +class Font: + Chakra = str(Path(__file__).with_name("chakra.ttf")) + Arial = str(Path(__file__).with_name("arial.otf")) + + f60 = ImageFont.truetype(Chakra, 60) # Time + f55 = ImageFont.truetype(Chakra, 55) # Temp + f25 = ImageFont.truetype(Chakra, 25) # Temp C + f14 = ImageFont.truetype(Chakra, 14) # Wind + f20 = ImageFont.truetype(Chakra, 20) # Date / Info diff --git a/fonts/arial.otf b/fonts/arial.otf new file mode 100644 index 0000000000000000000000000000000000000000..748e240917665a520ca9d63db86d1939e615ebc2 GIT binary patch literal 52940 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

x<5MyBwo6Ls%e~I3LArRr6Owc{7A`?)6?R z7oCL0s8_?UVu`)Zs|{YrgctD12(1r_Uk8McNPz8Z#sXo8|I^<9)T+HT_P^7@51!=x z*SxdGQSH@!=%4e8|0;=&dbPvYDJKuf9lzgkaA!u)O*5ePn z7WekInw{Hmph0naXHW0HT6L3byIBb99{2xw%wsu>m~Oy-vpx(wgYPrjGjNlFG^waJ zywm|*hC|tD6pZcOb}x_)Kk$MUdTnu?dPsRFr`}Q(N4CXnnz=5R6b2Oxc7Ilm_f6;R z^u8s~M>BLc2#09^IM!zvxXy zH{k!Y^4~Gi{9pP0CHrvP-~1B`zW<%tc}Bd{_uqE1|0}h_w#(Z2CmjC&PQ49ne*6C> z$p0oUsAGGb@AZZSC^+AL<_=q%N>^?qQEeJcnv4|d3-16`(AB>Joz1;@$S3fTC5U6@FaL<(vN$> z#X0;l2@W#2dhkZMp@EOQ$sa&Aqwzs8NU$yt;E2Rvr%ZBGqaDJ!>DeTeGK+`%#O|*t zoJ5&=hl_r{;D+ThV!3X7Zw_9rWW!moAj+SB7jqyD-kcN2C}+5bd8O*xB)`wmAd9Nmy+qrKd-%x4w6`^0rEhmSV!`$rEVqXxly3OlN} zJ?YSLUlhyEnR~&l(=7og+No`J-+x$;B z=>JLDSSYmdpRne~H{kszztN5N`;)7A;BA-Rm&&$?`RNbdm#W_Rg!iSw3%~GvsSV2e zQa^Xip76fZQ0Mzn)h7qxeW@Ls?@NWZj>{9ElacyZ6^rihg$2Lc`-AjFBt@#Ek`&;ZFXXUixNP>z5@;N(ZrXo!YD z+Lsxo zN%qEUvN3y2OcLr=;g7`nXT3`k_!C`pc&6y-*eWi(y+67Hm4F#-W$Lz6n#P}~c0)#X zHpyP2D@arJ5IxQQGN+8>l;u{g$Lj$pLW0X>Ud0r)Z@F1R%!$X>I&7#*#qjNVzb7y{WiiUfim7G*i}QY;LF~4OOirT}sAsKZGe~{L_5>P%9g+cV@SIDc)`hHFsNB{`&!h#cO=D&SSg6X0(XY-V$-1;vS#cyY zErKWDzSHx8Db+JQ(zK!n5Z`PA0 z{mv9H8n@RgP2U>#`4V`t1t1$~w?w?@luZi}CSj#MC0VgLi(MU+t5%whqKVtJbwpdY zuE_~=y1uxkqKX)*8Vqg9TUlAruhrsIMNE8q8ct};w>JAo1#YDvG*U2?#dr(2K6dG4 zab$o@i-rBD6Eg61??_o8*ZNNn`b}trEFHlD+XAeeL4WZBb*R~><$7D8KA#e_s%mspXen8qiTXo@t-fdG)oUX1mh!H*S^;JXw7XlL>auzc^3 zp1a{k{`3{S!vS)b7l^L|&Qa&hK63M~4#enHl)FIvI8}T+RD6nrh~BS)HL$K-6XR+`EdLJ6#%!`3O9*yuf=E|u}{J$Nud_cqUqxB>zD6st4ds@L~1~(BAlrzN!&~M}887F>gR<5QMSE1;Iln2d{~O;Q1qzUz5vN zIpldXIp|qEsLSgN6eAA|$U_O21;Oit+!nT{gfO6ivT?v4^RO-Ck7l4Zi?J*hbG0Vp^1ZP<>jpM`U?d6yri+P+M=0h6$ z#dsorL8ck=mYo*HL73pwKGN6&(r{j$SAgbkdl}ThXOCpyF&*LrxjvH%$JQXz@n;tQ zA?J;Qx;$Wi*%&#GxjjvwTYUWZ*kR1afXBQ(%6NCcu~p{8xecee;IiHwv$1s=3trCK WU5v#>gww1>gw&B z6Cs5VA!3m*#MB`Lg+-1>9m|A}?*hAJ$mmgHKAF+3R*1ZpgvehpWX!m{iH{_$6C%1m z2>-fKV=i)Bb@%dapq~Z$sUG(3piO!8g6Y-2Zn;B< zTZRj9Z)rtYacS7N59*LV8|j%9K=>W=n}qu)+;^>*SwH{R?{jVxBK8#_0$;DLEh#Rw z-?mZ+8`7;oGmGcXGNQ%Jp#244uPL5cHaq$L1|igKLRjvYRXe9%=3jBC5IyD#(Z0Ws zLb}}|6rnKuM1io0LBbC?eg7Fkar$A`KJeIcw!-zjWhG&J|EWWa0k-3($DCJ%_WxDb zjBkkI!-Z4d=kOJW|JjPO?^{+{KE}NbT+M$Lm8PzsQq>&@g}B5FMc3H;1audLx?lVFl9TeGVM~{N96LP+J?Z6RwVj#!igZ1X;tgg%R;GFkuKHiY6q?# zsnfvxu3U(_4GXB0h!z%6jl4DDHeBxzkKnpWe2MGV(5)1wWRj4wyX=K)A2|ruTsaBX zD`gF?v*a3FpOR~Zk}t?t5r0i0uNsFMN;OeU#&wFCf@`rV#kE|O(nDQ2C?ag%>(~BaJOrUYJ?@YuK|4-U(Y~oPzp>5MN34(N1)bJ&N%=n9?C>!je&?cWwv%Qn>5Ud@X;0^BgA_r(ekh-8YC6hn zKv;_!RzfPWl4R(gS}Id?=g^ZQA2MBn^nNYu^zX{Q4DytNKec3Ci(J0eO_tSCZqm#w zu>g50k)K-1E*y|LRb(SZmQ6NszTQH->%gH3R3)4@m+M#oTpjm_WN&-U(S531+gAfD z&eP@>p*_=)qXgxapK*l= z`a=Dv&KNeMosnQ%Wb`wJ7-Nj9j2Xr}<3{5i;}N6LIA9z$jvJ>f7E73=lO^3U(K6jq zXIW^u)AE4jNz03t*DP;a-m`pe`QGxIpWzqk*U>M@Z-C!0zY@P%zw7<(^n1YXNxv8U zUh{j$??b;6)=+ClYr1u?b-48kYpHdXb*Xiw^%?7K>jCRw>v8L8o5dDov)g*vvTX&n z(YDF93fsN5M{UpAHrxJT`={+A+gEMcx9Qp@rOj1su4(g7o7dYMZgafNX@84 zm;ZSG68~EN_5QE=zv=&P|4##~0lfkS1dI zgkKYWYxsTPkA*)U{&M)P@K3|t5rGlyA`&9{MGTD?8!6HBROIByipcuNMUhJ)AB=n^@}QL0zQ75BSbVzhebhl_n^uXwgqc4xXKKjn+2cn;helhyB z=(nTaYb)9YwQb*aOxt;FA8EV2?Z4Z8+VBN|c2~7~q}|i)HnrQ) z?wxjL+DEm|XrJ4@y8Zn2H??2der5a3?f=pKpY1`s?=x~tQ^PG>u3cD}Xq zi=97@O^7XyeLD7+E{-m>tG?#@!M3TAVBXviL{ioe9>2%!IOpTN8FA z{Lr;S*CAaSx<1wQ{Y1aSyu<~GYZ5=`X6rVfTWz<;ySz&fOZ|{QMBYR)bds^@6-gA2|>U~G=<-OPReyMj;?*qNR?EOn0 zOP{DdUHhc>8Pn(1KD+vyzNqI#voCt=qEAxVrd*mbEu|r4S<1$gohhHBoKB5REleGi zIyH51>W0)EsryoorvB~-b#!wSIm#XL9k)B~b3EsG+0o?qx8pO%_m0zPere%p9nuoh zhNO*4t4y1dc2C;6wC!pCO8YqNyL5Z{nDoce-%bC6ei>;QB^gUH_GY*<`)Af>KAyQV z^Hf&Xtl?Q#XRXcpEZdr$l6`sh)!BDuKa{;bdu#TA?B8+%a(d(p%(*mYM$XMSkLSFb z^L@^lzM*{w^c~l?qVLUpAMU%O@1Olf_bcmnO}~5kt?Bntzkl`nwBPsre(!JXAK5>- z|A79N_Al#yb^p!%kM(yC7%*V&fb|28473gGIxux$!N5xgmJGam;FAO282Ia;^g#m$ zT{h^dLDhp64|;IWQ-d}P+CJ#epyPvnA8Z}mVQ|mE0|t*7JZtcsgC88cX>ikE=iq~b zj}HDbH#E0HZbEKKZc*;E+=aQ% z`PKOg@|Wa4o8OfGbwQhgr~-RI?}Ezgv zhi=4ZKr?rmp+?cHk3&nF5QGP@YO)HN!v|wr$q@_1OLC#SM=q0(%SQQ$JSN?$qsmg3 zstIbF+M)KTPYsI^WOOvT8p%c+Boto9$ce+v5V_BIDY{+2i8l`oxWmn-X_lyowKvkBEfWZFs!Iqg3+x-&16V$NO(vhyzuCdiLpVk_SmkmJz_Is^I|8)R>v-iy*qXp z*K9S{EW+O2-pOv~nmOz_T(f%nb@oN@2voDR_UG*zP_tKC)oe`Mq`0MAvvAa`gSTd- zsM*HEEs5I_-{6{kh?>y~P`ZD`nrR2uP`E?HJ7&zhx=y;5yEge^+>g3fxbJr_b>Ho3 z>r!WbI{TlW*9-A8)BOClFQsq%=x0YR^-iJel_~e*} z=O;Tp!OGA7UMp5$j5~nqPjbFoO?O5wv_^_C(im+_q@%FkLE~%VwBfR}u>=skr43)VShiW-ur#&gVA<+>Z$S?tX#LNE z5|FaS1T)pL3U{k4t1V9u9&x5Y#8BPTaMc?t$?MfJH9|SChVG@RRJ`h|;#42Zv%0F; zsz%LH$?9ILWMf4aktlj%?lw>qii^=lCt@u}v)EZ!+b$7ziRD<`K81PQ>tdH^68}`0 zn5ku{`6^evC-!4r_XAeFf5>1NBBN!DOqM;d>dlflG9N46tK?K!jy3Omxm4aOSI7tC zYL$)|UWS^h#;PPWUQI`jFISJM^DTFgZ2u}Z&MRf}6h zqgX4R70-z`#htQNd?DTwAB*oWGyO(zoFqr6 znR0@>Q7(|z%IoBVa*BLdw8LuODh^lN0A^-imu{kY(@MelEf*jDSi`urN8Jc z{uG(gPxO-km}j3AxiVY~lA$7BMv6QcA%@EKqDZzCTUA`K0)ld_pXgmx_(@X6&0B5+h}-m?MkCGx8eo zg1kXIFBgh+@_MmeE)s)fn1~VIiq6>8x>ZgT56NorgsfL<)RUOyKdpAdA3m?1QqQWj z>N)ifwI8$o57a?*K)s+|R2$T0wMA`G>uF~OYl23#QErp3%Qxgs`H?(?apP0@nfzQH zmPh0l@=JA@{7QWUk9$lVS3jtq)Jb(loyG3WIQfnIR(>bHm&fIQFvfhVK31QpBkD`_ zwfaSUuSThnYBW|er$rC(yC}ep+A!Hc43X_bu}l-wWV$Gk8DhH37Bl2PQH?#F8ksBZ zm8D{tEED(1>EdBIQ#^wGohRj7u}01lJLUaihg>H9A(xBY@)7ZG?D|BBqoOVLi9*CD z*bDhggp1EbggA^nwoio(Yb1a1p$Nc887MvyZNvv6OIk&aY$N*1K#VJH?0HF%CKdKz z43P>4HdZEx%VeCmMvfHM$GE;RQc*LwOaL5*(ygqt4gp}Ri>t@o74?zvAR)Rt7fPQb)A}{>eOv&iMn0gg+05w z)t%}dX7XwF(8g3UoE*mw%4qcj*7Fb~f?mD9HC25=iEr!EK#Gr3t&y_hUlpBUJIzbSs2tgLY~MyG;O3k@0_jt*$ID zmYptetu3jRZLyPEEyMU4P+M18BQ1Y_ozpOFj&NP*I%|$NJ*VOCuF!#m#^_4Bj1pR= zE9pj}ZRkoPn?x(oH4s-!Y@kCwzNeWWffUi!lh#bXFa35~{=kgKVp!nO?@*=azsuxE z86$RMFY{W!NO*9v4&sa5OJR{u%V!9HvEVmL?3el6MnoaeVhXJpK&kMCO7&p6sXC^_ zWZ4H855G<#P#sdAs>9e-aI0_Bcj`abT{xkB22CLLd7C}Lw;u1|%`J^r5O3zyO@y7B z7Ze~3Wf+D9)M4l53Xk)vI;DQY&a#IiX_feg^Z4oUa34ZLM||Y^oxGV({Tktn z)kgWN02Qc$$Y1$T#fl*LyZl3*k$=jw(uLIrRHT$jQK_h(GV4ckC+LVQ-lHvwMPVG5 ztSJHiMD0d<-;@%=SkjtKVVlG$0Asv%Egr%cSS{;;wTK5W3Rhtqrcwf7?{{F{VZj{Y z6|oY%q5^x8owzL`p!ICf5O2g`g<2^d;Ay3B#H~p2wP7o4wb|NnW`i+dA?S{4 zZqP7ukQjSR{?vkgy03hr+?;+f)0pX@sH=n(lMPT_s-H&!Ixz}5pXDRXkWTro+>d<3 zYdARVg@>kE(TM}$vH?^>!k|yI;`P0!cedz9QXXMR?3~w>l5+bs_a91WzJrFo!^nRe zUf>7#fuG#kC7s3TVPoIM8VetO~ zCH(@)$i9gid0qn2u9U|phoqS>rT1ZvLR5os7X{ljHAMA!MPaQ&YkO!A%#h-%L$W72 zeIq=4m826SPie^*$zKr9h$qk^RzQPyh?`))^P$CR?B-9!dSxue4)UHqYLDAMe=Jk))w?mF&>Kk0Yf>kSZRDFT#=jt%7pFr12rNT>)7s$p{DVz$zD+J;yQ~>01 zD}P*F3geSJt8h*!|5R37&nQ1!|4?Wr`8$+JwKjWuf#|{`81?w4Wk>9NKSMoVZfASi zMHVHG`u5M}5k&o={-kq9%vNaZgP*|oN9(J6@)y2#hEc+&$2a#2ZIwcefR0EcjM@b1 zI<)Zri(qUNegtZTC{nkI{^x{ekoKVnRlXs`FaJM+Z17o$vd;$_k?unI6l%Z9zqcknFoI>BSGh&nG& z{r*dUA1pc-oRiOD5o%P4*w&z~h3!|vHZKH2aUR|pvV=qZU%@o!@Ep91+l7NbT`F=_ zH_)65#sGh%$byH@Ql$X8Cm8&H1`ezXqr7}Q{C&fVAzul6356QqTlxboF#-L<=H9R2 zpqb|W9-$5H6m;${5RXQ9M7DJ|$td?u0`!kuhG956K8sNjfcYzh7h(T_!hYhc`!fQ} zQvHl^B9{PdWx_Y2r#J4w4fjbO1|9uxU3z~UFfiFRp@g!)+piF|{SjXRsT^|!J zLYNN7G{OB5X1>XP3pbRU1RxLPYlcp+y-uKi7rtRJe8~c)G5JzicY`(u zki1VKPWXEe?$&TA>P4WC zryc0px&5(QSA;qi$?M!7$OZ0$a;EzOPNV*I6Uvz@V$t@=u%~3z4|TsBJf}nN*wIjP zK{Eq3_J-&!V?+n+>BP#XMS^?~x?Rb-?JXM+-^B5R|0$iygYUdS^fqn*{0P4WQ6TPsvl}n(K zQNLvDl!8X~MLi~h?=FPD!N1VV@+YMIB!VTL^8rL)pDh6pfjzDwK!i7aE%E@+M!nTB zz;wv?EW(q(JHh9O=n0(=^q>&;JqQPX(*e+1WJzpxwUK@I1m5z*8E=A=IU}xW_$4 z^};03Q}9fdCnUNYKpp^cqYZi+Pa)qC_bHxFi~w#5Y;!2^lOX>dq^$$) z&E`;5;65DyeXCV~$6AGJ^gV@%=qvpY-i^9lf-nYr2O&%_L!$@yPDLGlK)3{<3w|1a zRk~0ipaU44;1_d|wjb?(0z2c!MIQF$mH<`)e$?^32nogkwgV~v{{-Nq#XU;HG=!9I z3ScS#E#iI^K=hRFHNaDVCjmq|6=Oyx`7CVEyeAw3+Bpt;ymktK^YG^oyWyRIBYw^B zqsV}*wUrC7dvG~c>+R5|(nUAyo5l;_y7lbC?qTjLT(`Ssy7ruX-WBV*`D}yhd$)9L zcKztea&=2#Aa!zpwNwtUDx}r$6Y7gneI&2$N2Si^*wva)g3?U*(0uwuv-Pd zKlf(gFk>yR26o&emMB;=b;a(jfl~}WX_YqF2ImiN%K#ZDgCx#pu#+8%UHLFPg%g1t z?MSSumSV3vQFMcb+&E8&#yLci_*S-)?PUjelVnkbHA^S#V0V_WbSfg_utOX#6Rxhh^PBxKb%|)kOSo)IT(BDNAa{_9(K3$aq?0qi*WifR1U)`&w-PNi?Of$Z#i6! zkRx$2Gg_464B|a`nH(d>%5m~?IbL3YQ<{m`m!BkF#A#0&&Zg2uhMbIZs409BRg7_W z8g}5%$P!s9%j9&NO;x~mR$@gp1M9Mx=!x0lfUK3XupeI`XUjUQwCdqMKfu#U^Kgo_ zKwd4c;ZvZ6@_M-lryV)4<;6G~x(TNt{opsghX;O5-YRdCw_`8oPB8#I;%j*qPE-nT zN^=jNgDsQy$>lg9n87Dv56Xvdo>Yxff-1REJ}Otq$3%@>jc2h|W5$EA0q0Ue)XFDt z_A*3#C)Y^x`O%@+r=#7`I{7S~qk3Mhlk0H~^8!ZF**H_*AUDcQ@+C10`*Od_&2o!; zS-yhjO)rsK@xmAkIw&t6Y_*@)ez&&>6~5oM2w8F2M=y2%I^MQloJi zb(tEY#^U+k%W)cdg_?kq+evD&x)LWVSE;FZ3V51`#9m6YXp8fR_Bg$W!AVCaRia99 z@-iJ~rxmIa=esjhwVH`@-CCUN&Q^6efvHywm>p~rzo>Z_5q}k@@NC`H>Kb(|&Sw_l zba;_Iv$;v#tZotEI0fk}V#V{=ZFmy?>Q;>Lfg(su!G6m?F&O7M zcVK_}PO(vJ!YR*P7#Z?#HvFb|No*Ed#LIln`nuSGUEu(n{47=X;w<_;obld|lidfz zP3l3MC);qs`>IC=qiPkOD?N_qly4R*a5}gKJ4`>q+rA>~;vV?tE_j}Bxwua} zEsC*|mMfNFH|SyPq&_7c6m7&?I73{E^TcP=v*J~pBtDPbr1gA;*oafajcOClas0(? z*nPWG+=2Dt5^*D)EsCqeHyEuKi=Eh?dR1*zuVII3J3af2Gm3wxoobhAQg7mn@@T zBVWG0*ZFd@^?j}mHJ@CEEq7*dNnLG?Ew{G3wx(=GKyF=SO?h!iLw%Vow~)iY{F2JL zl7^YntIOsG(E#zug3z^7F&C|8X*HYzc zDe`q~^3wwfz3g=sEqGq8txyYEXbP&+^Q}X;guo%qLQ+yns>2a5q!l+uie>1u;yN>{ zBR6np%RH%tj&wEbV(Tz(3qZ3)CUw5nTzVMjnWdzrWm<=` z0qs-}NT00f8D_3@-OA~iMqy34#${Va`G{SR zYN89YiVJiB1zObwnni)9DRjf-7y6B=sI9BfxwSZjo?M!$KsRJzO7N(P2AE4-!_4a9 zhI-p5Q#FC3=2REYsW5ABnGSp^lA%k@(EKwr-;6@*7^V&!(@L3+R9%5gU7<`ZQVxrf zlA2v)8H=X1j`bS8i=F`0$dalX1rquQpV7G&u#M~8Vj)M_u(IxEcd8`rF( zLM>R47Oc>tGhJ9=Veq&XIvS_VXPjw1e&eXaS;w&vS;w2zAKzSk|M8`jWp!n9D(6_o zm)8}~Eeo8`oJazu7G~?_C`z|ZU=kBuq_td>ZJo$$117fW4>=BNu~!lB8rs(LSP`j( zdD@8bSV6dFMW9dU@^ZCeay{Nh=gVaUf*&goq2`loD%z2eYAe=CDAr!5xY_Fzmzg>! zX|85KiC1uIskebs3sZE#DMi*Yu7|bECpFL0b+uUeT8w;Mo&1b|GB00KWqDfiygXZ( zmawdayWs*;92q&*axN*byje=qS{!Ks<*m3nQY{tDZYM9WqGcYqoeXQGw;f=J9>1vd zm!G35t8A58pi1p_D%oPVP)Ax;U{y<|a5Py#Gs^4A%4({MYf39itktYmYqeKHAj!8> zLsqlyX$^8;xlz%(GGFg-)ZGbzs94E`o`LG1*%YkK0TPj%UxIT~LWURs-9 zQCwQg33JdI+^N8^D7?OQba8$0jMgcsDPi>$wPP!aYg#c&)s;)tl}pu?OVyQ2)xxCe z%BAYcnImLMp=pr_b>&Q}!@X8sDjNds&5^S()l_z&gS`$augiDn@*TQ-M|!YUP9L8N zIs7VtvDfIZ|>nt+UGJfL^0@ zbLVOC&E|4wcaAW_R$NzCJFj6@0Ecy?cz=$T*3PRjW7TET>zQ3fey0EQ%IfN}(rLBx zZS{4P#pMmN%oMEL_`Z~<)Q${u&Ev?>_K;!DwQz3=l3`9#in5E$%BH4fS!Y((&_Fe( ztfaQ4l)0v+rumo6smF|A(zz+S`$D_BcnrzBeaDDGqZjhIF$r9Qmeq zaA18y`O>tjOiRh~XBS>lJ9C;pdu@to8k1+5cEf4r>JNF%hD=Mz_2*H#YN z?k!N$u}ERfW0slG7s0usd?>sue5sgEh-WY~xisJM@ZWrE3gjjB47oJeq@{*%&rD10 zQ^N+v$}LRKHXUkucAq&ji>s?mBHXng%F#qQEr@bj66I>5+!jQ+Es63pQJzVpg~->~ z{1#dBTV~DDvSqbIdpvf!u3vhK6%Elqj;it;p!50$4Gd{7?N zcz%eUw;n0h>gTP~`FZQEG)7`q(LyH)csd%-ieay=jn6%v8^cq+&FS=99G;b)QC3$Y zM*3c77T3)X1ud>KX3h}VGkvZo#Dd&5lt9lC(-UCkbHWDh@Whu0$PJ0Lb{^d^#D;fm z)KA#Yo{IfWD-J&RX*0D=I`FjFN$fy&lXg4>6@|A=+ThINPrPaI6W-$ZlJ?wiwzN;Y zg&o|j*oX9eVvL>~JI_;Nu76o(;vJ_r%Ur(Vxk%T0mI7QqH7ap^1J930_gbde$~0Ro z*8}q$(YRkC8h5aPT<)!W?QC4fak_TKnJMAvy3lw4S3l!ETp#9Ztno17+bz3sy_@5k zEjJ^+iA(f1<{-Y+ayPEeay-(Ya^JR4uB{Yz{ca3rnkyNDxa)vX#PK3%l%5a8^O^kB z3Ok-l)S8qooN!3F51O>%+{cdHl$P-Rc@oS9d1(N$f)69j)dz zk;!5xT4xG4h2Tj)^PPFmwFlpK#eQ-uSs+eA>58|>aQ&07+xWT+oDK8IKkTYH@#N^c zIGqosbFPkE|4YCVs2%Z)dPfRzYJA=}D&eJRP3y@Ie9Rvv-T6Z1LHRWh-8rDSe$*a8 z+#bD}+XG)9pr_vZb9=;aYlPs+&k(!miFV;SOjmbbT&>r%(big@k zhn6A9LSHu#{B;uxx2To-IQKH+fNFx>90KPo?3u5_j{0g2AI8r8-FTm4G0qO=s#%d0 zIBzJ$`NS1CyBLmhjlnqcNXL0dGR{gm0osB-4B!a^@a~oia2ii*AIBNcVR=Y-c%mGf zA~|u=v=gUNTR7f`{=Ww2U=QLAfhF=5oU>hxQ@9#gA*bOq?{YaBXMqJcGt34!)D+oE zEtlN@@C?M4y#wLlA=<`>KLK7Z5xqZpA8a`cz64kDJ}R8!;T)gC@d`Bx_+cC$#`r5a zekH|m28O&i@1nfv?qz)KgKG?9?2NH9z11KnT zVUb3Ern!S@?%=#1m{-Ec4_qICXS%xsU0pwbp5-)HX2ap$0?c*H=Q^sd(T(HXm}eK} znauGZj^nKz@QL9#PT0Z!48`RJ^UA5?Idwe8GdZ5g@pO)-b3Bma^wx<~u^f*@92U-B z)e-zvogkd5je~C*C}i6Y{wok|(R@UIiLGAh*P>JTowLc_%hs6 zxa0!iEl6MFLr3?NuXT8}4gQxHfeNQ3LdG5C&{u1B?z%zg+bhr{SK_6ihqW<1+ zMJxPLeeVf9^ml~a6t)mwOQEpt0wH}>Wx5X?-BZ5S;Z#l6n(stS?~5mXzW2n>Lw7!T zTGN9r3U*-n2Q$Q1U~UQ5M8lU15Qo6n;eo@dTg<(CK!?3QpUut|< zY)iU`0P{YqJ@Q2We8V8zTQy#oVdx*N!e2sv2wNEXO(=zR7YL{7^yUibdpCM@=w3h*;B_6o(kk5ObH5Jpwb831(O)9bUnmS+-70*z z`95^{Iq2^8Nxu#G7Xv7~_8j3{Gd=9j&{?5X&^?8vt->jwxxxpJd!}n0{%`YLruloy zAFlZqwUWb=K3L=XHm8TCgGV0#g~_lv2f}!S7Xu2+Ftk&17}{3bEz*7QlrO4RRJYb) zXqbujh5@0L5Et@Nc=`h2$>`M~$9?E<&-q%1UpMn%`qumpH>Y!cpY*UlLk>~NMGcW&qIH; zna|5l<6GRH&3Y`mj4;|f8 z{?_3>P1l<5TbkY%PyBrEiJyngQ%>u!H9gXU-+&Ib0$u{V0C)!Vg&hSy0a&TS6+Yoo zjlaVuq;${yO^7c9P&nTwtTX$k7hm0qZo0moig04^*x=UTNXifVP@nWX-1pb_o-i{w zCAdd$*I)``FA%oZ=@EYu1~u~wIj!leI`l2)@954ahbO<#{6H7n$(+dYC$i)bEO{bR z&J+PiO=3xsSdzDya}SoJ2h$`mW)st_VGMoANy<=)i^tUgx)Me#WN8-~s}MiVsS7!E zA=Ae)|8TVzd1;+4#l2jkdztb9#@xvJgEumt8xXD+KV^XbfGbvE&w_YCJvSVs!xfDCMRFvyaq}rL@&V1}lGl)|Mu{Qd0>Tu3Aobd&WDPa2HOh1L=Q#d}6 z>w<4;f%8PpHPM((amt1LM3QGB)30L7oXEK*Fy#d1KY=kz7_)>amk>s*=J;it>flrd z(ThdISuEmIb8l@J^BKYz-r18oSmqs!;r%FaGs!7#CO(KmT3kuO=ZOYyfK!P#Q(Vwj z(GcfyCorE$T>3)hw#dYDd=kgUa_JW_hWGvCVAjbcoOdv5rhqvNX3Y#X@k9^ppe$Ly z*O5#!l2fl^Zr7Q3j*sN{2yUaxm}WHNM-yHJ5(Zzy08e?8DF<1WB0h*MWDv_Uh$R`s zr3_+81~H#OlnZY&(LP%s_K&I&qpx6B;&kN^aT9+9dxfB{V1LSA!9I?+LVDrN5qt$3 z-V$HImg#sGqXutp;49epF4bUsd0^mw1%{E0<0G`C4@jH?dG3|b3;enPD761gJjREn zHLWK<;iEL&`9h*;24Uc9hejJf%h2RQJX(Up+Zm)ygkD(mjmX~-x=)VCa(IapMbuPfz3|^f86t?}FIKFh^rD;v;$xnD1 zDgQ2{v}VAE*->8?WNR6ke27;oz!@8)bZ;dsxjU2g+`OlalfD}e@5q>~bOi;`e2Vw8 z5eLRRU-F#k;A<(^+l>U4zR^M7QA*)`-sc2)Gt4X8C)}UncLKjt2wm>u?lT%kUm;VF z?HjzC2iEchyzPdsUcmn7pFes$o!`>E6~9n@4Mi*zXUFt4KAa9Rt$C(oL5Zfk^!27t zPN7prqM^UWs25^SR)~z1h8o zYfJS#=Ej?d7yjp_u^9i*8ar4&|D#-H9$d-x|EK@{Ld$jg)FXk(1}${VeTr*w1~Dha zFYxcLd>?Q>;6B(gC-@#EjiBwRggqYZAokwh@ppgv*Hmxb=iSe{o1nEF%{0*bduRpv z?oJ#|1-s*f(1CNp%vNtmlo_U;Tz>ms_sJ_h2Axlp|6H36+Z3-5r1!pkke zbvecw3&xr?;QSOkVHn;e+77FFUF^hLOuO*2VaB%){PyFyfI!T+zk+RkjW>D&`CFC2 z7@LCdHZguU@d%Moh$qV)A_Q+H=HX50d^s7ID<$4g$6JF}AwE@3N4y+w2S#J$nuj-! z=i~j*NPN-qew;V0zXUft9XOHJKoi810D1c z{?LO1r@85P1J{5)GI2`NR}IG4TA&Ir78EHy!MF55J%!&g3%#Ay1?_dl-3X0tMXQ~n zR>M7h&HDcP81g)iT+gG`kHfxUS@@k0QMjIIX&>k2gC2Ddfb#xo2WD>pU*bvtjxBkj z-1pp#$a~adA(ZX`#C-Z%%bb+vUW?po!T%^~PyA1~;iJrQNLn+usS96DoJL;t9RJK( zo6@+CfyWutk3!;19+3Y*W0+0jdJxwWD7(r1yeAD9wsy`%_4d}m69dmiaD5&*Pv{mS zo%=}M%P^*Cz{SLn}|ioUH! z{!YNt{frsFDg5HFvk@cW;oXw)y;;1Ojc=Q#$P|3pGz;%f55>FFHFzg{7QO{~y}T2i zYdPLhEfg={J=G!NMLgLt0<%qp=TpclOZLj)?3KgWD~GXH4ri|%&R#i!y|QAj+?lWeeU0|2Jj}Bx@x5=Cw2AJNC`(**ABxfZ@pqU9eUC9kvfLKdw;%K!h?!Fm#utmv_~MT@ z>pNhih(T-6Sd)yghFU{$YxtpVeUaJ^U-0$A+wgga=c9crXrD>APR4jde?4dl;xrx^ zTxToS*@imL#^_Tg>yUR2#-=u?|4sNl)twlj{C%_#z*-1nE%>t*0=b5MTth#uA--Z} zMY;H6O8c!h;khu(uGw>~gzb_4WgiC*ycKsehj<<~2*1#>4_x?;`UY~6pVhsDEZJkv z2x;`dy!67H&PQ?XyywRLm4`{!JO+3uJpGOOCwU*p?Arr9WdC)jJpy?{=K6OGpl=$Z zFxK#oy#elNMvpliN#5KZ?xV!X#Ga2Qb6JM#4$Kp&O;6F7M!p{7)k?0TcSc7g)4vPp zkv$Z6nbgf!vz{ywON$uIKrg66&5!Iw(=rAc@bLE74&7f!I#7@XY4t-ZAGR{CCtD=t zXQ{A0LY%LjQqC2ELTd%oE1^X^mxI_2^bM^y4-VMP(D7#UqvvTIgHk|)(x~mo<}uQq ztAxMew0zXo7t|goy!{BRdQ^{|v9WW!wC>qYux3n6d+>kNiw9%s zvDrF2`2S6acJa0f^-e7V>8ly9=PPE3G|!`wh_=}_n#+d#)YEB2$359hY39ay;$@Otw88@PJF|TXpqh(AsR>6rO|D zO#3^Z#VdNO+S$ukbgGmaRZaY&wVU_T!@70);fo^e=s#u3Uh4nLl8MDvWp$}^5A9=UBiFrIPv^Nhov$Luyd@9^i*I)P^&u{`_e!m|&5o_%!Tk=UO{;x0TAcjVbe5YIk> zdEAZQ*+&SEyB&Dk4d!vz&g;WAJO}BFFP8pfJgK|9?|jbQp~A3Vg9E0S?o}) z(?oo=$A&Lp^#^_ceqpR-gImeMy0+k(p_4FAn~WB-a*Nr}Vz(jJ?f5CQ-5pq!(u&-M zHoFfyH`E^f+#UgFj}2IDZ^G(4kXt2!TP28FC6Zeum|Gn3#v#^(oxVQ1+XKS*p!f&_@ zf7(a?=>BhiexLYl^;@TZi~Sb+&HC%lw#qNhFS*4p#?R+xdDqCqcS&m0RCO7?IFpI5 z()7YkgSn5=5jv%(@7v=k{~q{i47GABw{I8hs7w%cv~DR@2(!_qJ-IdW#ocJf0(@uX z5$sCS{>r7?c30q>V;6FKkNz?R-(>k2Utl?ddG2)dmJob7s4eEU3$V}MTU>`Hva_+4 z=!-e*BKqzU{sKh>));rR^jVm+W}4Pm2xS*B6%O$ z1nzcVWw8p^$Kc~u10DxF0eBLy2CyA-r`ItK!jFnK0RI5&1T=xqn+V?mybah5pdEp| zfOi1<03E^gAh;d`*Ms1C5L^#}>p^gJf};~0o#5yMM<=*7f?FfFHG*3sxHW=XBe*q! zTch|D`Az|T1Dpo@?mmcbk=s-TAPbt9F8okyE5HV51Mmj~00IF)fM7rfAQTV=2nR#} zA^}l=Xh2&)J3xCt2S5y^@1K*>}2Rs1XkVK7MiP~JSt*?Nbr~ZG~;s?><2hrjO(c%Zu;s?><2hrjO z(c%Zu;s?><2hrjO(c<`WEuam+9}oZt1cU;@0O5cLKqMdvPyi?d6aj_+h608GE(Tlz z7!DW#7zr2!7!9}-a2a3>U@Tx9;Bvrtz!iWAfQf)f0JI9M?Ri*RBU>A_2>C428lqrh zEnE8nenvhBo(};Y20Q{-33wE+3P3&I-`nE5V0o|>czo=H1D3+}?{yz%yLe34K^q5% z2P6Qx0tNvF19Aa*09cWLe#C4*9bgWi9?$@o3z!F(4_E-W8gLEZTEKOHg@Ee;ivTwO z76Wbs+yuB8a0}p8z-<7`ec@r8@Gwqz7$-c86CTD1595S~al*qm;bENcFivxg|VVv+VPIwq6Jd6__#t9GOgokm$!#Lq#oHW9~%Q)d>obWPEco`?W zj1ykQ3E%C6?{>m>JK?*X@G?%=oD(+Zgv~i&b57Wt6E^3B%{gInPS~6iHs^%RIbm~7 z*qjqK=Y-8UVRKH{RugQi3AWV)+iHSsHNm!;U|UVFttQx36Ktyqw$%jNYJzPw!M2)U zTTQU7CfHUJY^w>j)dbsWf^9XywwhpDO|Y#dIU6ln2bcq>2Q&cY0_Fi00ImjH1GpA& z9bh5gdcY#U4S>af8v!=~ZU)=}xD{|4;C8?rfI9(80CxfI2HXQ!>V8iyL;dapEC<{V zc+kBScDWUHxfOP~6?VB5cDWUHxfOP~)n_aWz*q4s7^}LVo4X{#{O zR$-*A!bn?%k+upWZ52k^DvY#M7-_38(pF)lt-?rKg^{)jBW)E%+A55+RTycjFw$0G zq^-h8TZNIf>OB3Y3H_%D{ig~2rwRS13H_%DqjD4akQ05#i9X~+A9A7(Injrl=tEBQ zAt(Bf6Me{uKIB9na-t78(TAMqLr(M|C;E^ReaMME>5?o#@|A^lvBnw-f!_iT>?G|8}B(JJG+L=-*EC zZzuY<6aCwX{_RBncA|ef(Z8MO-%j*zC;GP&{o9HD?L_}}qJKNlzo};({7e6)UiKj1 zA;80cM*u4Uj{;T!9s@j%d`|$L1Uv;;3-|%!UYz@J^vuUG{;gDfFyoB$_J8X8t@}Oo z`78kS_o?7ej&MGJq`DWd0`LF;>jCcXlCO9_9X@0`tR4GcJnx~|lsU?FheyDC3;;`l zM`(mcz$^*?OM*veghyzEM`(mcXoN>-ghyzEM`(mcXoN>-ghyzEM`(mcXoN>-ghyzE zM`(mcXoN>-ghy!PUcMdsHoLGMc@r|c1$Y~<8?Xnk7w`^XA1rhQzWW^KegKj`ptZ6G z^F@p&@P_94gJj>u@;fnK?1Fp)0kZ(B@V+Xopk7Cr<|=9>^wJ2OG%AcTIGM(nLotQ% zMPYoQvuJRJDOI`~OUzn2kb) zQ_$D3R#x;W){@lXSD*&Xvu=9!YCHB%ci~T=u0R-pn$zfF)|%FyPvL$${P*jKH@R;+ z=gb*%8eMabR$)2N`&!J~*J9qj7W4MCn76ORynQX^?Q5Z%HYoWU?8DGg1f6()WH8=| zSHOBt-(eRLPZG5J+H8R;!j8(%$U)E3(DOBTrU1FC76mNoC+x_50{B|&HYdyYS zf$vk`>%c#v8Yi(QCQ)__BeeL{pAfa(M!ZU-A_51-Hlq22v0O0*-lRGr;F@bws9@ps68 z`4)Ql+5H}Die~1|tMOO}=ZiwE!Sg)b>L^WToYGv{pw;~g*kOCEZz*D8%qQj9n4K_2kh!*$+#n<*dlI6TLC z2g(V>7%OCSocYU0fbqpe%kTt4b(O|5h)2GQRg%O%F6c;B)TY-y-SLIz4sO327cNMdp1PoBbW3d?q+KQ%Wr zv@~Y+Y!XYJXqKb9V@!HpXy=gl0#ib-T+QWgpz^)=2CrP}SuVwzrDuEn$Y(&W;kLNM ztnj3`@FKaPp}{puF38Dool)BwTzwi!v*k_ME*fj_lqSB%y$$PrYH{QVM-9T`+MyZJ zq7$Oy@V7jWclvaEt^v6pdrbP}s3G7WdzZ?dE3@A&-IEQOs8y@EAJbb5HvHAzk!(-@ zN8Hi7sAlna8UqFjFEo;^zsOzM0>6XsXd#lO59nDB9wUy9E0W*1mdNq0XJx&sz1mi? zd)mGdEqfns|DL-22eY1;3;RG5ze)Pysa;I`#`EAe_~6H}&ob%vc<`uIAJ!S+$!CE- z*RH^<9o51o-(F8X;Kx|VXPNxo@xk}8kk2yl`+V{2vrPQD7I?BF6TjYrC%IVLgePtL z$m64@7x32^$R15SkuNj(yzJpa@*CvKO#BuP9=>dp5C84*%jWvxYf2va%|x$Tvn%NR z$*RrP3{NPMpSf1ZzOJ|G(U2dM;57^F3#vdF{a0U*kP!#lhBp`qZ*VXt=V10jGGMmr zC0~D#b$zMp=h?c4RdLJH`!+U=e$73vid)RYvq$jKlSP<#Z%=i-itpx--4fnVuSq;t z@1-ZroAlg+y?D}?iQl3<4)-qb34{jFH^bAy6L3u^s+R-n>s>pc0@r)8_t^s3)Ac@- z&VBkv&_`mYM92h_n))?dVjLjN20f}DucW+JUP)Z8{5Lsn`LipSc*vi}*WuszQpi-~ z?y37&A=lw`JfTE&V3}AKgtx(ZCz#gY?W-pJt3;2sxdc~w=f>bVBwSJtsm~S=C#2w; z_@pIzGs3_en4bG!ylltzMtIC+@ZJsVjn1cK0=JHd-=y&efX`%!O#H_4;5TUeA<(CA zzcA_7nRuL53$fdv)-mzxJ$UypvAbDLyk~YZ$?5(N)yzlE7c@QWRZ1S?O+GK1c=uPp zCvv|q@mn-~d%VX-Z~d9&Dz&`2~{@Cf2oNsCM~A`=W4Oi}%H>u-4H zlbc?V7K7n=F$5^7Jfm>Aa6 zWR~GYvB>K`Mya>K&%{58)#4Yr9O7r24J z>yNqn*Q3iulR0r6-$NdZdy&v4dMlKPX*;R3_5=07tXkDL{6+l9j%}`3c@#bNfa_*? zjjI>e3vyE4cu|J($?q;*R+!IXt^&Wai>P!DB(Npc+-FR+?w@xsV z;F%D6pEO9VYNoHQNk&1nRdR$<+x~oa`n2Q;@Hlu4)&=QJ4V1RvyQ761G%}cSY$iG2 z=V*pDA9CAB-#q)%a~_%i!q-onJ=?Wb#^&T4&B-B0Kik!9HqU%$_6LjM{H&*0T2B+% zB27H4eoXu({EZ>c%xgaD&%|#$4}OC$o=0$#evbza{WbHUkstiI#nGcYa{1)j>&Zv; z<&ody_f88ujr=BlpD&(0oQYrO!BhF%-h?Mh^C{OyA1`?5sa*DCCZCrHZ|dI&!28G{ z^sbJ?+!yNSSudu*Ha!bB0w!gl6)@qQSQ(zQhMMANj(V$lvTF`-hh|rH8?iQ<+9O-n z9Dmt_)~^d&wupNemt^8wNZf+nM`GYdEMbsp~CSwN8>jG7@?ROSuC&AgBT(iSj8(a!&$bS)1kZx)x#{Juck1QbcL=v(Un2tV#UKkS zINX+mg$;Fbb@!0Pu5ZY=W)Cane%^P;4Z5oj>raauk{*Jy^b^4;h&UzkDv{ z(V|mLtRMPiy?k!^FxPs>0cnmNeQOl0JIg;DHaPkIWEx?>Xd<`AUAjFAyd&5|_6sI{ zBWBDzGlsjPGUM^Ql<8N8n|>Aj&lmry^uhB;Mf4b{h!6KqPkL#Kd^;!~{4V~^oytc! zXmvdkK!uVrysP2pc2v)}th#Q8l9ODg%FAVXd0o=GNl7xGywcTFenm#NiyNG4NO8rjVKE0dn)cxD~ed+5#?V$uje!D<_Ta z*}3xru~)tsfA$?Uaavgu*&~fH%fKP6DhFZN_65I%~WB^5}>IBkGsPflHQP zdTtx#S~hBlG_DyjBI`9-`I+>dQIzQ|-Mx6HZ@HFaFaBRG@Xhq^w!r&)>HqD)LvN%9vs@bUCVOXPzV&@U zmig3|#ynF_bCl*esS$-yns@Z{7_HSBje**TnsfCf*!LC5=>i|6R}HjfBwouc}w)pQdH2Q#r77 zm3X>R7{k#*pf+dwbfww8kr3N3#kl{v zI^E3eXyVB}P5cJwizhoY@#|XPNk=CB1;RtR0Mwz*>_?GCTDvS>+ZhRGzg_&yGmF1D zxp?Eo#rQjzV}AS1^{nd=*~W!47GCp|BM`C`1KwP@T*~w`G9!+hVKgQs$#RzKVflk=DZ)0z@;5cD z*yXRLQH?nV*Z7~h#@)HbCZ1|+;x|fPJk`v^ulL|lGb-7{Q_T{=PtjMf`Rlk5G}2^Y zH1G@yY3<;!F~Q8jUVL;r#{_&X=F!C3>Az1eAN}eJW8SgHPMCYe#fgdbzUAevh7lts zjPB88KrBhj9MxWwv>zoH@Q$5XhbI0tFMg9R-k9#i@ATl^!?4@np-;e`82EtStDba{ z+$Z0z7WfrjeoZa#6TSF1eew7*hDY9w=fQ7i#w&+0*h|01gJ*d?d=jw6I+wi7`SyD9 zp&3$)30{8hG~-dfNnZRuU%YzOi(lu#Q~7wt-&2nkdh%(H7i6#I_AtV{^e+=1SOm>f zt^gliV_}V()(+eA7{QZFYoJ*}yR4Eug;hn@Ty**C>nDy_+@nXS>`^*kfUA0nBYtf0 z#N~NCKI!4LguA>YZ1GCkOVRf`Jd$pg8+;^^Ui?cQdT!|h8lQl(C6biJ-OhNr#$$1t zy!keBxZoLNVjA7ssCrmMiKK@UXHWm5XvpUi zJf{C^R_4ia*R#ix9!i?+w8!3j$@`;{65|G!4H@F?3YnSZTgDW1>-Grx!X>6;#(38K z_qs3aWgX{2$1%YB<6N5O91sArFmyInZ|>|%HW4ptKlz zEqRiryWC0!>eliU%2UI*tZC9#^8Zx!C4f;CN&nMtfT%#okW3~Cxh9jzOcFvuCduT^ z88rg3L`Nhh(zvORo^JK2K4alXds z?r_&F3*tU3tzj-RXnD`r?Ci^Ir~HxA`C3u*66+$rt*0yv)~$6{Tdj{; zB}cuy&guMzGpivbr6eh+E+e@_-c@P6*J4rl|57>){=b7feQ^KmT)Yn;`0ob)B8Db6 z8QRH*9XZU%ovW0k?i#eBtLtRp_1BNoJWY+caEX-QdZVU>#$V>jMdju4%haMqE-zSo zl8c~6b~9GU5!pv?_z8k5eE6b&mH2>SIfMl8*JxFfuRH4PU2H~=d`rxO(Ze0`S8S5y zUP~8?@9uuII>sFC=wOy&tF>6|D_>6gUy5#-JbmcPZMuLTWZrPYY;SK>%so1`B*Idcl3U4Hod1yy4si3;3Q1aMDo$ z->brjFFtkz{HO{CU2i$<^M-TVEXp7DhD&5o1^f{e4lmRj|6{DvOWtG|MfpQi9(@hV zD4nnNHLbx~;Ea%hQ#{CcuS@5{0Clt8m

69n4?;1)a^Vk>B+6w)_t7ZCD1xZEOuz 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 JJH-9P{|B_`^34DM literal 0 HcmV?d00001 diff --git a/icons/__init__.py b/icons/__init__.py new file mode 100644 index 0000000..d9a4f46 --- /dev/null +++ b/icons/__init__.py @@ -0,0 +1,128 @@ +# icons/__init__.py + +from pathlib import Path +from PIL import Image + + +def _open_icon(*files_folder) -> Image.Image: + """Helper function to load images cleanly.""" + return Image.open(Path(__file__).parent.joinpath(*files_folder)) + + +class Icon: + + wind = { + 0.0: ("N", _open_icon("wind", "N.bmp")), + 22.5: ("NNE", _open_icon("wind", "NNE.bmp")), + 45.0: ("NE", _open_icon("wind", "NE.bmp")), + 67.5: ("ENE", _open_icon("wind", "ENE.bmp")), + 90.0: ("E", _open_icon("wind", "E.bmp")), + 112.5: ("ESE", _open_icon("wind", "ESE.bmp")), + 135.0: ("SE", _open_icon("wind", "SE.bmp")), + 157.5: ("SSE", _open_icon("wind", "SSE.bmp")), + 180.0: ("S", _open_icon("wind", "S.bmp")), + 202.5: ("SSW", _open_icon("wind", "SSW.bmp")), + 225.0: ("SW", _open_icon("wind", "SW.bmp")), + 247.5: ("WSW", _open_icon("wind", "WSW.bmp")), + 270.0: ("W", _open_icon("wind", "W.bmp")), + 292.5: ("WNW", _open_icon("wind", "WNW.bmp")), + 315.0: ("NW", _open_icon("wind", "NW.bmp")), + 337.5: ("NNW", _open_icon("wind", "NNW.bmp")), + } + + moon = { + 0.0: _open_icon("moon", "1.bmp"), + 16.7: _open_icon("moon", "2.bmp"), + 33.3: _open_icon("moon", "3.bmp"), + 50.0: _open_icon("moon", "4.bmp"), + 66.7: _open_icon("moon", "5.bmp"), + 83.3: _open_icon("moon", "6.bmp"), + 100.0: _open_icon("moon", "7.bmp"), + } + + warning = { + "fire": _open_icon("warnings", "fire.png"), + "wind": _open_icon("warnings", "wind.bmp"), + "water_shortage": _open_icon("warnings", "water_shortage.png"), + "temperature_high": _open_icon("warnings", "temperature_high.png"), + "no_wifi": _open_icon("warnings", "no_wifi.bmp"), + "RED": _open_icon("warnings", "RED.png"), + "YELLOW": _open_icon("warnings", "YELLOW.png"), + "ORANGE": _open_icon("warnings", "ORANGE.png"), + } + + default = { + "indoor": _open_icon("default", "indoor.bmp"), + "temp_low": _open_icon("default", "temp_low.bmp"), + "temp_mid": _open_icon("default", "temp_mid.bmp"), + "temp_high": _open_icon("default", "temp_high.bmp"), + "humidity": _open_icon("default", "humidity.bmp"), + "pressure": _open_icon("default", "pressure.bmp"), + "sunrise": _open_icon("default", "sunrise.bmp"), + "sunset": _open_icon("default", "sunset.bmp"), + "dew": _open_icon("default", "dew.bmp"), + "wind": _open_icon("default", "wind.bmp"), + "wind_gust": _open_icon("default", "wind_gust.bmp"), + "uv": _open_icon("default", "uv.bmp"), + } + + _weather_id_map = { + 0: ("clear", "breezy"), + 1: ("partly_cloudy", "breezy"), + 2: ("partly_cloudy", "breezy"), + 3: ("partly_cloudy", "windy_mostly_cloudy"), + 4: ("mostly_cloudy", "windy_mostly_cloudy"), + 5: ("mostly_cloudy", "windy_mostly_cloudy"), + 6: ("fog", "fog"), + 7: ("scattered_showers", "scattered_showers"), + 8: ("scattered_showers", "scattered_showers"), + 9: ("heavy_rain", "heavy_rain"), + 10: ("mix_rainfall", "mix_rainfall"), + 11: ("sleet", "sleet"), + 12: ("sleet", "sleet"), + 13: ("sleet", "sleet"), + 14: ("snow", "breezy_snow"), + 15: ("snow", "breezy_snow"), + 16: ("blizzard", "blizzard"), + 17: ("drizzle", "drizzle"), + 18: ("rain", "rain"), + 19: ("heavy_rain", "heavy_rain"), + 20: ("scattered_thunderstorm", "scattered_thunderstorm"), + 21: ("sleet", "sleet"), + 22: ("sleet", "sleet"), + 23: ("sleet", "sleet"), + 24: ("snow", "breezy_snow"), + 25: ("snow", "breezy_snow"), + 26: ("blizzard", "blizzard"), + } + + _forecast_icons = {"day": {}, "night": {}} + + for forecast_pic in Path(__file__).parent.joinpath("forecast", "day").glob("*.png"): + _forecast_icons["day"][forecast_pic.stem] = _open_icon( + "forecast", "day", forecast_pic.name + ) + + for forecast_pic in ( + Path(__file__).parent.joinpath("forecast", "night").glob("*.png") + ): + _forecast_icons["night"][forecast_pic.stem] = _open_icon( + "forecast", "night", forecast_pic.name + ) + + @classmethod + def get_forecast( + cls, + id: int, + time_of_day: str, + wind_strength: float, + wind_speed_limit: float = 12.0, + ) -> Image.Image: + """Fetches the correct forecast icon instantly without looping.""" + icon_names = cls._weather_id_map.get(id, ("clear", "breezy")) + + selected_icon_name = ( + icon_names[1] if wind_strength >= wind_speed_limit else icon_names[0] + ) + + return cls._forecast_icons[time_of_day][selected_icon_name] diff --git a/icons/default/dew.bmp b/icons/default/dew.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e3e390194f0e9fe153e8b6bd16476908f8f59aee GIT binary patch literal 160 zcmZ?rUBCbVc0fu4h$Wzyk%18`kpv{Sb3-s#07(4*{~yL=V6Xu)>lhdq|2Hr&|3ARM q{{I1143Yz3kU9`%02u+oK)nn$7C@W>wA=-Vb5a->fP4l9APoR1Eg}a1 literal 0 HcmV?d00001 diff --git a/icons/default/humidity.bmp b/icons/default/humidity.bmp new file mode 100644 index 0000000000000000000000000000000000000000..bda633de6aa39dcb1dcf1eb6195646ec9c32ee7c GIT binary patch literal 160 zcmZ?rUBCbVc0fu4h$Wzyk%18`kpv{Sb3-s#07(4*{~yAv08;-L7?{rkF>?b0nb!Dj13O+Fkr?1f#P*Q z%)rC&fq|c)fPs&}fJQ3$L54vwx$<0|VpXhOqj^ ze+>2vf8c5!Ft9VcU=Z)%U=UycVHO4j5RPVIaA4qUoDeNjXTe~>AhTOQf&<|I28P`X F3;-#-6)OM$ literal 0 HcmV?d00001 diff --git a/icons/default/temp_high.bmp b/icons/default/temp_high.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e5ca852f547adee448c0d02d5232586ec92d8b00 GIT binary patch literal 192 zcmZ?rJ-`3~c0fu4h$Vnn0f-ra5Coclcq=yqg9U)Z|Ns9XOy&a&3@i!^44e}f7=%wS zFo?f^U=SN5#=C)mL3KF;gXsbW3DafMWmp$5@RTlRU~FB$z{I)?h!-%hur3GU1^Fec o%SuWRe7HPg>vA=o(gh4Mtjp{rfaWlms5dYeIRG8l@QZ-~04v2KR{#J2 literal 0 HcmV?d00001 diff --git a/icons/default/temp_low.bmp b/icons/default/temp_low.bmp new file mode 100644 index 0000000000000000000000000000000000000000..595af74e469a7cb4e3811692c40ebb1a8b361619 GIT binary patch literal 192 zcmXYrEe--P5QSe$R}eta1Y}j0Qve}VBsdNPoB;jQicPQ>{7Me8f?JeV%1pj_^P9}! zG@#m6vJq<$gpn7!iPx<1YbfNLYdRSK7l`Q{SiXtX2ZG9>9z!z*)CS?Es literal 0 HcmV?d00001 diff --git a/icons/default/temp_mid.bmp b/icons/default/temp_mid.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b5fb9327a0e362abc4d00024461060fc529a05ad GIT binary patch literal 192 zcmZ?rJ-`3~c0fu4h$Vnn0f-ra5Coclcq=yqg9U)Z|Ns9XOy&a&3@i%v44e}f7=%yQ zGl;)n0AhO}n*k_h&%nEZfq`|oJp=0kAiWGqlS(>i`JR= wq?vda?H~OB2{H2j0V%5k$}Y8n48lAL-7FFCa@7sB;1<7m#FK zAo2xNQzwv5P<7c9l|&%jq(Cg!MH_dKNDf-Ntrz0L+Ngkw$27Fz z^678R@V)=M@q@d&JCMm>XzqD#KdV;CW2r$gkg0%!p{W4?3bqdbIslYHhMxfd3fZq# z%KglOWF!*-=_zXhz*h+`VWE%!(5_a>6T#tek*a|7#PtB6Eby`h2{{vZ&BUZ8;9zLt z6ZdsWaZdrDR;`prDP2D{!UY@*%`E^p27rT9Bmcl*wNgIL;68x}6_CWkr(#EVq`{n2 zE9F{-_q#_y@b$754>=eN&34B3Tp~=s!O-kU5YOTHU})~kxUrBaOu>I6B%qNrB0F zQLykND?=jm2_)g#870Lcsbw`Y0@pw)#opymJRr9x6MUyI<#c%z&dnoCY3hxZd(B2B zU85r`)mahnV-RmXfc(8j)2nqG#+rx!)YrR6ft%hRG`{1YfVJ%$2m zSw_VQK5;0X1Y&K)7F1sU&VD|ZC7w;a(Tbd%4}KVo`Yan+T6^Kv*zBFtI=M8bUu0bP=zq!Fk1>pO*u;Vf?BDRhiN>;g!<-nz(9~V0jI^wM z#Kq!Ym`tbe*Y$PQ&NPCVec#v4l6_V#?}+Zw|KG6|kdw85oU8@pWGx^kYXLb~3&;uc zIT011T{0)ZpU_v|DYw#uIgzHO8;v%aLwEFO=L$dA7JZ5|d zzumsal(i!EpjpSWxIuc1F^OP(0`WI_2mnU_PzhUGXzkKm<8`-><}-dwAM}WRM$6(Y z%1dtTta_ux68K@)=%lh1<~fl*VHhj_Evz8zm>XY)$iszRaw{N-(u@%CB+2nD7KE&U zlmxdab%{IyNxjET8q!O8(9d)4eNPg8?6-#@8g2op;_#lc3#(8xI>_zjQJT{%O+-v# z-RGQLRK*eJd$~Y_fPT4JSpLR3UTL@mqy*TVQdklFhCNSX`=NhlGjL#damOj2+pVL3_ZMzp{QF*oi zA}#tn!eZ%bF7bep=fHG@o;vpyz)oKOHbWRJQ`qldc9?DN=K`l~^Jfl=r9YzHU^iho zoY$fU70>1I!uk@~qpAb9QO8fx?4riX00000 LNkvXXu0mjfA<*^S literal 0 HcmV?d00001 diff --git a/icons/forecast/day/breezy.png b/icons/forecast/day/breezy.png new file mode 100644 index 0000000000000000000000000000000000000000..9fa9883a5d244f0212bc93a6be1fd28e01785015 GIT binary patch literal 1596 zcmV-C2E+M@P)?l= z0yZli5z(CxVkLyQJn2YF&__$ZBBG5!UQGByM3b(`O(!f}%4qC*u2PDf=2$5u@`H#z z5z)Qj^pC9Zen;yLDWh1*=t{rfRHEu%zs`b+z`XnCLO3A|^odlzSAoo`_?-!3;%hs0$jV*CDV$BWX6ZJ|*(S-_ckRR^S2@|Q zCPV{k@Q*sg!%^Dqn;BkPj?|W%m^3&Tlu|KkIuVUA5z`Vs!^Cqq>f&#fn5;^0)E1O< z%>D#_t_<@eS^IRNIVwv4S|qc8Rhx)g=CV)*ILI5Tf z9&C{h2u~q#_f>>5E}(a&{$iiNkMe&+^nu%UbsEvAt;fh#zZg!5tL_I$`CTB~>a;UQ z$^*p@J2!|sf_;^f#DBVNFw|cT>t}MOTPZ0o#?{wlbvBeZ#5xGJd}DAKX#q{1+V4uK zt5EA@25af`WbHeLj#Z8WqLeuFV$`9No;N6}tbPyQ&{}bB(4==2L)P@p;*InZpmq}j zp;+juoeS7Wrd1J!hp|55H~jW+S<_t)bVzt0szb*H@${?z0VnBWtn6g3>Tp$c)Fpk= zF~W<-vW2nEPC@#(!=x)n&Sl`SHYUFQ1y363>#+@Sq=Co!h$?1#q<7(fZ>b_Q_96pM zzhUxWt0wpDb#jD^b7^S89AJ%&YbR*(q*^X8dIZR8K4o{rFQuaV=lK*(^fWyQ{n93Pr=ms;OP8VC!6HP)^S!m__w zPMbGv=k;D*K$K~j#WB02Z4y;`nVt!~C7o;KkOI@9&HE#*vCKJg@^$TN$8~Qs9lAX$ zBt2ytWE+(LkZI<=AV?FVPo%r7EIy#25n{KVH}r{g+d#85;9V!2dKSHGOC;q0r&;>K zG~O`FlA~{*#5-B0g^gP52hxDsEHm`U(y#C$g%<>IkJP5$QbrrB$erheN*Ptj6+bnL zBw71@UqObmSZq|-xBn4^vDEV7gdF^I#k>vC=XlO$g%^llm8?Ox;URyjd|3b4Evti$ zJH0%?0DfCX`ygYaWN7h}v$%tHvR zA=d|MZK2I7c5ud+n5|YZ&BrSudJxB6y$!7i#bPkV@WW@~v_=lDpA9)m;kTmz;T0Su zx&3U&&g2t*=F@x}B?wN82{?ClzgTNK&N&<__nhPNCgI!Bk=xIP19)i{|7f9*gfV6W z?_Rl<2?Vh}l*?Z5%1_`plW~kTR|}(h%03OVz_q?M&M_b>BrC}{Msi=-HVIWe;^@eo=HtSNVR`#J&Bt}LgN69b4ylYPxZQQ+`g@p=IN0W%(uNso zoo%K)Ka!F{oLg?(MkmHw>*HJ(h!`%!ZgtjMYgg74vU58FHZ|NU>$fuYY0_g|hDtxtL)x@t`{Dm?u*U zY7?y|7c=&!a%h3@Yj0@FN<;;S3fKval<0uq{gaac`aSz4g6XiATy(H-1d_IZiS5;} zwI#H{2q!PT02nEy7C;~K#xt#3H)(=xeHPFC+$4$E#X8e_!gnUA?lL>x;F*vrDM`-= zjMk_nv$cR3sGa7+6v!D{z5$ooHUIX%{(3WldM*^8t(HRG0lY#tB8ZrL*i~k0iCY2ui6i#h1*X_ohS`*Dwv`Lg?URmevn}Q#1>2hCk%)5mB4l&xRZC z8e0TJCey?dH^p;m_SLYjPpS0_UYm>hQ#R-yuxl~R$Lgvll8yTUeFTTd)$(#*I=uH<*9Q0`o~2RYvj=LSNHEStiR1t(&?Hps2BrRx zGq{U-FQ2&ot6UUyx!VX*ht#Ixl+<|3hdXy#PLH2th8xsU_TFh^f`djvNj$SkjtP@H)x zszYSeJzpL>k91nj1X+TV3{N`j>uXO*F1-Cjf+Eka-0#dYx**(&gGA zc-=5T(Po}XF6^rtVJyp}iJc?QRQ_Pa81saWxaF)}$Ty|cV!7hYqtODKTE~66_r`lyTiP{dM%+(J~5EJN`xf@+tbhlKby^g9LGzh7A+4*#3= z+d~mcDRFN3YNvb^4v}V~b*6|d$me-m#hjlHVkMX5#;w}fIf&?gkIC-F3^h;U00000 LNkvXXu0mjfLBbObOmt-qC21~h};2Hf$0vU z3UaO>WCv6SR0W%Xvhg&`#9m7fiy#jzycqhi`@kvVweg5U=c!y=@3HLAmU*P zRhtY+0I&%m+?{;%5(z`Zt)f^gm;^}xunZx5zwoCWBF+^=V#SU33J^XgKL1@@k_wm# zNizd7RKrwAnlu_o4$w$)fJTx7G?E;kk>mi4BnN0DIY1-H0X}eu2mnz(Hbca2>J63y z0GN?BKK||yaX%`Se$6e$Qx{?c+cyqt`T28~v8*sTx7AP+DyFB?nF- zV@YbDp|#;sisi1OOoeq>_wp3c`b#dn$Hu+7QnYAqG8gXL)D_AyqxEztp>`4_sgT&% zApPFUojU6~O|xDVwBE$g^HHeC2ShZv<1|SU0NjTV{!%D<7dpgEFO)$G*j z;9jd>O_Ed>;{wFdl!_No6)Gf=UQS9U#|f2=r8SaNdL#q>)F`x(bR}pO5QR8Nw5#(t z)g3W?k`lHEo**v&FOuTHGM@ec01MJSDo8W+T;55_(&fdSAOLiQ?Rfxz$6uOwQVVUE zuB5jaSK{}TufwaSj5@BEWH^w0$LkNyihXV*u^P;*Z;nhc!hNJh25@Rp<~JNhiaofG zdCGui#=N#Sbo2PYlwpi{n(|+LPvFw?#oOWatC?{$Wr{nBk!OHyrf{Y#aSHHr=qu6W znA&rUmVZz2ANJyxesp60TeMJKJRzc}#zf;V>wV1b^y(SH4pFCf|X$1wCh1TwyZiywA1^dEk^=73sZ9QPfDZtC-p(@B|nUab9Bix zp|LmRj%kFb<}*74BI>JB@jHstVoFnQ@)-m~jM|7#!m6Wh@g+@*Z_}E7!t}(4sivDu zylGvTeveit{LC$FWjp$PRq!YJ{<=bJp{v8yP>1PCV-pp%p8!Gqc-=>%zpEoibYxvyyxt6W$yfDIuNIVWfS|x( zlu7CuDV5DzmQ;5D5NZ)sWp|fBP@o>}2}2u;JJOo)qtY6bvp%?rW=wV4X4H#)jnX2> zYh3V|22qV52Shc391ztAazNBrK^K+?Iyo;Dg_O2R)L0>{y+n3euGC9pxkMGW!x-V_ zh6qZH=uW8o9HNS!F&%T59lEuw`!v8gQYZb?>z%A1bmi4BnN0DIY1-H0UAjT&`5HCsgN{jZO2qd zN*PEFhCTCuNsvTZ_rCMli^E6oFT%9z%e5eT?Rb3>r@u#wzD8FggzzsK1S}2E(GFYy O0000I_3QR%ak1CHyiNhf|OrQr0 zY{273&LjWl|DT*8K@fmRxSqcY0Q-2+-}8*-UjRTK0D1t>DHifQqn(TaV*p3X?8SjcxZ zaL2-i4pP_ibLhi#hzB^EJ=gPVn)rpGTtM`7bY^7qb$f(8S5{AX zFIVG9v5Ov;){t#X_>Q16K)$aTOJP40*pe5fH?XOO?RGwHm?50kXS&> zEDlX}dI{+2Trt_8^df>Iox=|63Rh%6gY8{Yf%c~8jkriSD4I++oynsD0Lgfu3Tf8puVWBB#> zIF5LLv(qb;t5S>3YzAUBVM{<93sk(LqazrP$4RN^R>FR(Sr?8Wr!x>&T%!@ZDku~W zFddCm5D87V@~N8ctCJH|;)TeU%2naYPAFh1wX~%8`}zaCdHq@k^>V zHhhd=G#*X&01Z;?pyfk-aSk7x`ijLX8r#_dKvJ{b1lt4`;YUBX<@;V_>hV>b92JS+whQ)gGEN; zVt)L11LqeP(=xuns`Na5rjbf?13t<5K;y@0Lkup5cSobqU6FeylgU4Chr{zE?JYaC zVEkRV&kb5$v{?(d##%sASPPgCYXK8tEnq?jzvd7HA%v!S^jKU!2!cc#Q?_ymfyRMx zFRWnGU+6{4U^y8c4O|UXT*7TibaH>Lli}5od|)fN;#@L5khOmz`+^_qT(S*Z!iXgx zA*8}7B*fm7LN3-Dp0VFKCWJT~F8$wBh+%G7^ea&_=}DL_kE@?1mu?fTan4c898TwP z$!sU^&Uc$RG%q+~a*Pp{a?*Gnk;TU%s!P|Qb>}CJdOscgpdP5 zNEfd-AAwllLcZD@-^$aXh$e?CM+oU4SDO%`W`&lF*XT-_Wte8vnL7gRV1Wzot3~b` zG1|;U+GKL!z5NJZTZIY7^uEN1k7Jg|FP<0l7 z;Rox1Yl;UWwkyyjAj>&Rn}^MV!Ml=Hq*t*NxaIGjP(ZAK=tQPk_s3-;Xh88 z1w>DT(ZP`YA{?t4P5rXkFB&QEfnoede5lU9wTUQXk Ye+^^QZ^_F1KL7v#07*qoM6N<$f>Kt-hyVZp literal 0 HcmV?d00001 diff --git a/icons/forecast/day/fog.png b/icons/forecast/day/fog.png new file mode 100644 index 0000000000000000000000000000000000000000..4a388135f47d0132b8debf97b2e2e2216f211850 GIT binary patch literal 1265 zcmV1GBf-+ zhjY$+Kq#f4nsTKU1%PdAtN7QU2>?C(TTR4cJ>?VRK$(CjS`q+K0IBz)}Rns2F6x@WPgk=q&8Q=(=rbGZN>q$~;kq1sao+Axh`NWr5{NZI`7D zV>K)z7TE0kqs$5yXFX7^)R<6NY`TI3FRbR?S256?E{tEw?2~*sN8$l4SSZ2Oh0*G& z21Hg7l(_LN0X0+;LGi+}-U5DKN3j9EH<#py+yun}yC#@D-eEst4 z>gvzE8IQ9CiTz?BU-CQNq=3zNcH5)l6Zq6>`FSZ5GF2?(xfdm_gNPy3gfB*;-_U5h z_bVRY`>v)lxg_rkZ41=A5FkvwegNHWS5|lNVj=Iju68VNo^re!9DevHQ#^*1&g7yV zp0jZTRF*f%MXYU&$4O7V zU0%XyG*U6$td})L;{{|9_UQOH2v?oOb1x4Cx~(Y+6R%#c>k!YziyIp)V58B1e!s7C z3fXif$J*3QEAp;ft(v%|Cs0)c#W`E7oh~-9s$7XbS76yXL8PBvK>T> zJN1Mfm|Wf25>=}gXC0e=pM2Fzk6_U4tdn9xBVHv-9v13~iC z2_mL#P0kBZTfjs-R>CkH!pHV_8k03ey!&fw2F$_}&1(S@@tB22XtncTecf2aCg|Kq zq!aO23ik)Jva+%a4>vY+Ph%G6Y!Ah)5@7{&P511cY;MBR(vt3J+{M2%#*K2N#*Rj0 zbk4BX@54d;z{IMgRxIT0m71xwa3UUKK_#x!8doC`*m<@C%gf8Urr^dQp1ra0bUF){ z{j**d8D+y37e1e!+6xRCt+9`r@HcX1?gN%6=J@e8rJS`p(QdcvdDo0F91J?WUQgs0 zhkfm=SjgX6c+qlK?tjAQ73p~(6i|>*KtVzQ1qlTdBot7PP(VSr1SEv45<;4U5X#wH zPk*uh`{-+0^L{8O}sH6+rUq)@~)l_@VVAj=}oc7EPvkU0Bue zk0c>va^E8*`94cT$^|1%3tSZ}=Y%W+Zx9((_W7wqCVwzdHXTao)EzWJh?ILRlv44s b4*>oG%HWW*=?z@S00000NkvXXu0mjf7xYMr literal 0 HcmV?d00001 diff --git a/icons/forecast/day/heavy_rain.png b/icons/forecast/day/heavy_rain.png new file mode 100644 index 0000000000000000000000000000000000000000..07fc5050e4d620263c1ce6c1e734ebb2168774cd GIT binary patch literal 1608 zcmV-O2DkZ%P)-Z)BK#9hRW<-%~%%B}>|Mo~$a;;B#WSRpw>4u_-?eEUkcqfo7|-NeD?1LN@279}q&i&~n-Iz>07ZFh*dr)mbBiLrYm%cdI|#fyws32e4HK-_Lb;^xtTFWXiNrWgtl~m0 z7J)W|79foaY78qf6QPM@u!f!0v9?Wj9wn5~2m*r$1_-Do3J#LR!~I+~eQIJNp$TIt z27-LQK;#izY$ITru%S{cPUO*)W5;_0T1<#_V!H6#JH<`()Bf}ZEUH_pmbGj~-0^`Q z0(sZhH>7uVMox}jzZ;Jyp9#A5FoUSns%7Pk+gk!1sU{-f)zPoy&B+NFjYcxV43R`7 zllK$Ya3mp?#%MGq`}_N(-|t5m2KE{Avf4N$5R=cA*0(YQAa8GPj|_)H9TUQ?cSeT9 zSsG&`V#R@jg9BaU5ez<3l~n>$mb8hN)s5cSZ{*_QLiezbw|9!6^T-6|Obdx$4iDoJ zJG4d8pPKFVB4{9_C$Eo>$<^hhPLmbkX{}lgH0Kq@!M0O?Ao1$xXquiFMm~%`TyZ&R z)*NAF5h|qv6Si}=BLJ4ko8ddSYEr9~+q@3W68Q4xLmOtx!w@%DO0{bF&Ni9WV@+Lv zfZ-5%1l`;dD0R1Yig&V>1hVTCAlf<#Ao2%4i3!|5a0>6kdc}4A^+RB@l1KXIvmLwXs2x$)s&Df-tq%JCz6q6ze1= zlPlz_M~`fZ5C+rh#qX7JJ`#I2rTg-Whh+WUJ)0tk@O-wxz^sLlMA#>++LZF!Z??$& z2M=tDz(S{1E#GeWhq+vo@?9>n5*~m5qffdoUf8ZsnCHh*SA6mK(|^6qDEgOIpPZkc zfBNUuRp^DBOeWXk@%XRN=%2xOJo#He-T{`ob=GmGwQuD<6gasbxJXzM7!gYXBVtKl zL@Wu6h$Vp$u_Q1elnJDimMEodN@;^q8hAYf`e0vpCe(Mnl+q0LhZeZ$>yrtZnQuiP zk(O<^gAISo8f55eym8=v*vJ1&Ni;83I`hvI1pRjzGNn}BPo}AmmzooF)-r-;<_U+^ z4w$FGc%~t-Kth>xH5C))nIH4ZAWCTpKh&8A_ZlslAM0`t?+m-d+31M>XHz7#FT>Hk zx}+vS+Y~(0W%Osd34uj0wbiN#%e+_s;T6Fg1Wz63+8w zr=0$Ij!eP;Wt>3S7xUOrF%01qnE)lbKsE`KeK8fndA^JptvQk16Qw#<2$X#>6~aA9 zUnW?fu1WI35-8=QRidIG*f4z=UjCL^AS(naMp5agDC5iUw2(DEHJ#8B`kqF?UPMgM1=Ev znJCz>%0i=QOIQL~rXqSV>8PmU%P^=7e0{B%ZV-WMb9HY+;K!t+Vw4gL)1-n;vkfHB zM`AuGw^Gz3=X{xJ;CJ%aC`IF$3`BBdchK-Fyv(m?Z=cs2>BP%2&~EL%uoFQ0000ae`A$ z0iX#0_05g-x`8fA%Lu@R=OeUALTGV9m^A>XZf>mi_4YWqv;riHt1Sp_RtU1}ibZMz z*zo)+0F<;`{69S>kXEfxIY0^yXdUZHFC48`=ud4zR0@#bq0@h=?4t8!72=ZO>zRfJ zbiXD&JmXRZu;KYN3-HEzO2NvwlmKja{x$$Sv+)H&+G==y+4M~p_JK5X+KN+^w7LX< zoJoJE!UD^Vz(cKIqHXeqQWhARzZU49vNSbY(bmIs`plFSW|A}_ABUi}^mj>#il!`4 z2#}^?Rg2Lzt&9PT9l{2tV!HLEiE`LS01Z#Ybn2UW{jfgcvr!1ObFjbHRB`VpfX(|3 z@AT{(etq}ubTAl*vV%)9l3dh7)Nkb4eBY`Iong!ULKNGK6G_z21!180v)hGUucr%KSY;{@3$;Xvrqe<{_u|F(L7?b%yC&cPS>aq?m~kE8 zPsc4dKRdH^`}{mBRJ|smRIgs2*o22F&hv%rhep%MsTlPyto!YTDHFVoCtO{ug~+MVQyOSB;5lrAp1uEHDZ*-mK}tMmat zC3P{C`se+7*SZv?U?>OGn1<(fT$&5Ne0dZk%lANQ;~-*mDe!-!{1{-3Dzr{$d3pK1 zAkGBs=pSdJ_psUJS%oNsIH)PK{{APJlc z+BY@AtEd7uK4g7hTv8hu{U{hzBL2%4+@nTAete(Ph{b=|ttePQDXIz_Qa)>kwEFA+e2zlrUnEN}^U**VtNU(~-U1t>^c z+A4ud02Bp}w$5OY7GPXb8~Kz4z+)q|r1eEzo6nowSOJ(Oipm04WPMc|;F180ilTi% z+7cJ37mkL49Mo8_kOG({ipm0)1UPHieH;Ex24GaA>j=`8xYR;L+A`}(v?s?40Tc{H zQQ8t^eX+nL0fw)ww34C=U{vrBM$u5MFMQ0c6DEKcewbyKz{LWL08&p-@5_f$jJ~Qb zOZV=pco@2l*fkNxSnv)V=RDDC5LCLtb1=zPvbaMFOKAOW{xx3!_!ku1R>1NG#ODA2 N002ovPDHLkV1m}5|Iz>e literal 0 HcmV?d00001 diff --git a/icons/forecast/day/mostly_cloudy.png b/icons/forecast/day/mostly_cloudy.png new file mode 100644 index 0000000000000000000000000000000000000000..c0b963f8ecdd36afdbe8605c8593edeb3e804196 GIT binary patch literal 993 zcmV<710MW|P)IB?7vhpC|Dqr7!*$ z+%H!g+X_T4P(eoaniR6zip2w3zee{nhExxvhuJdlFz+1Osv5YjJOL?ex1}tW9%JDw zML;?*0u5o?i$fAo>%k&x4^(|%h_FcMl2(THK*OQijXB4*PIcW(h6ip)SVJaSy-?T# zUC&>lQY@}xO^0kRw0YrpzaLPDt}3c1R8i37iT@Q)aVe&nye0}-_vU}cb7{c!E?x5lU zJ@zCK`=eR+)nZdo0dZ={Q@fc z9<|6&z*~+9^tXcsT%4aXu%v?uc6pLzUnVik!-l)N7Dw@r5;h@uYW%{mWup^05@q)Tx`2sN)^Avs}*)$Te9|t?rD1fXH0jOknkD zGMCFSutJF0NI=K7uorm7#i|h!u%n@BbWAMCv*~Frru5}OKpLA}<6zYDnHdIF2oZFQ zb~v_$C(9SpR!vOgU~Yciv>E-_!HVS3Y>Z=D1r8#cn(ZZUYTi(rZv7T;Ke9a(Q|AD$UJ*o}3KZaSFOlE&#wEb=!kzD0T6R P00000NkvXXu0mjf=PAUF literal 0 HcmV?d00001 diff --git a/icons/forecast/day/partly_cloudy.png b/icons/forecast/day/partly_cloudy.png new file mode 100644 index 0000000000000000000000000000000000000000..c2dd46d2620442943750d712649c7c3f0ba0ac68 GIT binary patch literal 1519 zcmVP&Din>bsM*A{N1+n;I z8D(~Y((JXEWKkG`FS>sqn8Fl&vlJ95Wtb^4s5AEl+?!&xeY5^pnQ4Pdm#)x26D=(q zIrT}sNt1hT?>*<&>#0c_vG8>Jm)#jIk$vT3QsbGkPZNdQc6di{Fp6Iw5tD+ z1c38|kWEVI=T1G;$ z?~zehnZ&IlYRJ@mvI;AMxK>4FP5L4X#@P}4cAQdb7~e1+_5wid1ej%wQmUG?!LIAU zA6=%DnveN1du%<=HSn%bjfd$qHktM7*c-Ltsr&aggRuBPS!GJq5n2_e1cQSBVf&Y|~i+BM|3uxTa3yex>53;Wt% z=duh=G%Q}G1_J)9_LqW0tuRL^?X_#f5n7-Lg!4(v_zOzADt z_MwOHC2d;CMA7?h*ocKacz^cePp+l~i^>=wgaqvv5azLICDV&(5|4?cioYJ_;k$3Y zf!nu#;%!>X3Z>Mr*wP3g!lso>2#2nS7`R9I?|@Q1ujABeF=+eWN3y!G~==ts<&xJEUd z6`+VgEFN=D`g!uj_3J#nHbW^r^p3G%D8LkI)PZFrH(r0EkEgJPyAAbdD6GgOsc6o1 zck_J0p>tShly0Gys?8`WV1_U__zo{~mOS=AjCE`E#GA>&3diU46u(Pvc+xV$dKDcmUh<$mDQfg))(2$D9a@+QG8aDiS zh+5&<=bnd;!dGmG%JASuUsHN3o2cvze;sTu(>+$}jc!p`e+>8L7xo&P{RPn#j>K%m z*p_-_G1=JJ*@gSd%kbxe2V94}0Z}_OW{>Q`vKe1d6j)kZbW&7CPdE~@yh|(f%NBui>Rh6Yst%VK$si`Tay|k7U%~7Z1SR!sZTaZ@Rncx3_-0G?XJ*k47 zDGQDz;)aLjqznZt>6cJcKnKi*BQe7e;W4?@;3yCCdD*(8g)RG2;YdvPZE`7veYCbF zK`(UaGFq*A?7mobgwYpvAmC?#_|{QZ$pY=+NUUKIyfS%7wT0b8G+aRJ2|Ez*tC&hi z!HL$^W|3B3SR{lm^SVxFXQvF^Fhj$Q#(iP&1qvzZvd{Q@;PrYX=!qc>x5ld$2m=8> ztL9vGySH9%3!FZ4#_etxkr_+G%?S$#w9J+_%aN8z|&6Q8cs$BQx=l6=-l5k}#5jRXv4|Ccuq&aQqxX5fvCYQ^> zT0ZYqDRG<)f4#V>#nQ_i=*h0_JS;3Oo!#Ev{{P&N4yCQFwL+m_tN36v><<6{{{o1; VblzQDCu;xz002ovPDHLkV1la6?2rHe literal 0 HcmV?d00001 diff --git a/icons/forecast/day/rain.png b/icons/forecast/day/rain.png new file mode 100644 index 0000000000000000000000000000000000000000..39139f293a30384afaa4d31a49eb47c731bbc102 GIT binary patch literal 1432 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 literal 0 HcmV?d00001 diff --git a/icons/forecast/night/blizzard.png b/icons/forecast/night/blizzard.png new file mode 100644 index 0000000000000000000000000000000000000000..37923dd8662b3107645dcdeb9d95de16b1bcd837 GIT binary patch literal 1944 zcmV;J2WR++P)YirijxpB37#m}Z%>zJ% zF;Ooih8beA@88e_Vw?v6V;(N)mc5415LhUd z2MWcqR|y&OsJhZ6uqVZ1^^O1Fi_bsfB~jR+NLAr_lZV*<_+#+)yYIoVV=wbu zULt424cRu(s*L0-G!i*%B8EUksvUAc8iC|6WI78^KoZA4{D}ARPqA7}MF~%kLLh}I zydi}&^d0JjlW)GoJ01S`bBwVZV=V6WA}LO!P%M{%ls-MsshoV{1XQb4(Ww+&CIs|( zfIbgUCNO2uM9I0`R#?)ew$b*r<16Qxk%DU55MQ4W!Hv@TyRYid_r%%I^2M$0a^2Cns6=fCfEic36%a>qz`L5UL zAC<(+2n9H5 zUu}~-4SFv;9>|kRz}4TbNg)q$#yj@Q=leXP_(gNwwlZUE`9@b~VjEFmJK(&$m3pHkbxdL;4{1L!kh{r7HUyd{>j`CBAWi+pp-|I$q%FD@>^Qnd=TTCLu<$C6eq zn@Mj=I)oc6InK99!?o)-`W`%Zu(NN2P^;Dc{c~~grr`aivzfGIZn@vly(qC+kVVvGihITABE#U+*qNHwgD%N?a)s9 zWHV{UeHhO1Ig3*Tm`RMuCQ^vNL^3t4YevTed%&uji5^bhyuQ`(X+=6ok^XH&Q`t<~ z@?8fe^P9nTjXdDCfh2kQ#kM<& znd9BQi6fdwrld7gi4chDYpfO8{kq2Q6gr@71!G&;jFlZEZ3yHAJlq75?Lw9MfU4I+ zc7~qp&L99}=r3kxi?G^D1>GraJnvdfAWZ_Bq8dXg>E(!q?T_sRO%X_wBrmueuE^|2 zYo<)Z?Vk38Q@rX0O+z5r%#6b@j3uQWDDuSyl1RujBjCEpz*?LhJm+VIj{ylRCv@7leIepxyJCMOZRpRA^1%m z_(WjCbVtg79|Y#9P@q9^k)h}_8q16$jPT2ebm$5Nc^74_ZW_PphoScqt$krZv`YGd zXb~ug7J-5g+El`GV|4ThXwM;T=P(kYK&aRdnw0?l$;yAr)waBTZRhem+twkE2eR);G&m5NA>H8sbo eOJfrV0RICZeASz%Izo#80000?l= z0yZli5z(CxVkLyQJn2YF&__$ZBBG5!UQGByM3b(`O(!f}%4qC*u2PDf=2$5u@`H#z z5z)Qj^pC9Zen;yLDWh1*=t{rfRHEu%zs`b+z`XnCLO3A|^odlzSAoo`_?-!3;%hs0$jV*CDV$BWX6ZJ|*(S-_ckRR^S2@|Q zCPV{k@Q*sg!%^Dqn;BkPj?|W%m^3&Tlu|KkIuVUA5z`Vs!^Cqq>f&#fn5;^0)E1O< z%>D#_t_<@eS^IRNIVwv4S|qc8Rhx)g=CV)*ILI5Tf z9&C{h2u~q#_f>>5E}(a&{$iiNkMe&+^nu%UbsEvAt;fh#zZg!5tL_I$`CTB~>a;UQ z$^*p@J2!|sf_;^f#DBVNFw|cT>t}MOTPZ0o#?{wlbvBeZ#5xGJd}DAKX#q{1+V4uK zt5EA@25af`WbHeLj#Z8WqLeuFV$`9No;N6}tbPyQ&{}bB(4==2L)P@p;*InZpmq}j zp;+juoeS7Wrd1J!hp|55H~jW+S<_t)bVzt0szb*H@${?z0VnBWtn6g3>Tp$c)Fpk= zF~W<-vW2nEPC@#(!=x)n&Sl`SHYUFQ1y363>#+@Sq=Co!h$?1#q<7(fZ>b_Q_96pM zzhUxWt0wpDb#jD^b7^S89AJ%&YbR*(q*^X8dIZR8K4o{rFQuaV=lK*(^fWyQ{n93Pr=ms;OP8VC!6HP)^S!m__w zPMbGv=k;D*K$K~j#WB02Z4y;`nVt!~C7o;KkOI@9&HE#*vCKJg@^$TN$8~Qs9lAX$ zBt2ytWE+(LkZI<=AV?FVPo%r7EIy#25n{KVH}r{g+d#85;9V!2dKSHGOC;q0r&;>K zG~O`FlA~{*#5-B0g^gP52hxDsEHm`U(y#C$g%<>IkJP5$QbrrB$erheN*Ptj6+bnL zBw71@UqObmSZq|-xBn4^vDEV7gdF^I#k>vC=XlO$g%^llm8?Ox;URyjd|3b4Evti$ zJH0%?0DfCX`ygYaWN7h}v$%tHvR zA=d|MZK2I7c5ud+n5|YZ&BrSudJxB6y$!7i#bPkV@WW@~v_=lDpA9)m;kTmz;T0Su zx&3U&&g2t*=F@x}B?wN82{?ClzgTNK&N&<__nhPNCgI!Bk=xIP19)i{|7f9*gfV6W z?_Rl<2?Vh}l*?Z5%1_`plW~kTR|}(h%03OVz_q?M&M_b>BrC}{Msi=-HVIWe;^@eo=HtSNVR`#J&Bt}LgN69b4ylYPxZQQ+`g@p=IN0W%(uNso zoo%K)Ka!F{oLg?(MkmHw>*HJ(h!`%!ZgtjMYgg74vU58FHZ|NU>$fuYY0_g|hDtxtL)x@t`{Dm?u*U zY7?y|7c=&!a%h3@Yj0@FN<;;S3fKval<0uq{gaac`aSz4g6XiATy(H-1d_IZiS5;} zwI#H{2q!PT02nEy7C;~K#xt#3H)(=xeHPFC+$4$E#X8e_!gnUA?lL>x;F*vrDM`-= zjMk_nv$cR3sGa7+6v!D{z5$ooHUIX%{(3WldM*^8t(HRG0lY#tB8ZrL*i~k0iCY2ui6i#h1*X_ohS`*Dwv`Lg?URmevn}Q#1>2hCk%)5mB4l&xRZC z8e0TJCey?dH^p;m_SLYjPpS0_UYm>hQ#R-yuxl~R$Lgvll8yTUeFTTd)$(#*I=uH<*9Q0`o~2RYvj=LSNHEStiR1t(&?Hps2BrRx zGq{U-FQ2&ot6UUyx!VX*ht#Ixl+<|3hdXy#PLH2th8xsU_TFh^f`djvNj$SkjtP@H)x zszYSeJzpL>k91nj1X+TV3{N`j>uXO*F1-Cjf+Eka-0#dYx**(&gGA zc-=5T(Po}XF6^rtVJyp}iJc?QRQ_Pa81saWxaF)}$Ty|cV!7hYqtODKTE~66_r`lyTiP{dM%+(J~5EJN`xf@+tbhlKby^g9LGzh7A+4*#3= z+d~mcDRFN3YNvb^4v}V~b*6|d$me-m#hjlHVkMX5#;w}fIf&?gkIC-F3^h;U00000 LNkvXXu0mjfVbbb^o#+zH}LP)6_u zH3J0f37`{{Y|u`iK7|t%#=^on+k)?Vdb}5m(1$zSoz4ylA;1@{*3}FE$|Mf|a4hN0 zBd`DfegeRpYff+A0AN9{8G$-$bhliss|kTUX@C9zfHaB2!e#DJ;a2Nv3ILlkmn@Sw zoOPXlRJTmxa0>vB5~B9jUjTS<-GAOh%hkHd0pOXyA_8}kPXQ&R*Ng)Ue1fCgCS5V- znv>Nn5m5#R831+wPzWJdl5tmkah(7W1KRk~tsb+WDj=}Z77PF? z%j>#GdQNUrT(`o}{@S$cuR%mb_E%acM+T_?<;3@E6Op@{6uL`caSE z5)q{bzZsWXy3_GvtS;%nxAXDugc70k3xvS8b@~nx8TYia-UH{x6|*31RUdD!C-)6-3XC@8$@Kg|7}vx zV*Z=NaCtO852%WU7K-3D!8o&fI8%a1Mc%Aj)=vD{fZ&9q}F0-n;R)eMRb3# zRB*C-_~98`T@_ij4mzc{Q9o$EDY&*rCazJ-&s0EW|F|)@QqOZf-4ozYuD2N`o7t?H;i307`mM;~wh_ zr$MG8y6N_kQ~Jel;(wV|4s5OT;&pa`(;%asA)OjWPH-BedPa=PUAI&vId#L>|F3|a z@{0)A_5p$q0xIdj?~_NJI<18p?&J&hHy({QTZ9n(JW&e({{T%I?`#n}J=002ov JPDHLkV1m(W;Mo8G literal 0 HcmV?d00001 diff --git a/icons/forecast/night/drizzle.png b/icons/forecast/night/drizzle.png new file mode 100644 index 0000000000000000000000000000000000000000..55b5e95d5e5243fabfba2707ac6b6dda51380933 GIT binary patch literal 1874 zcmV-Y2d(&tP)k*dcCd)+n?1gUXoRM8XpDRBS?Y>DKONP}j1 zj@Ikh_3r$5*H%7h54LyTjK6tr-p|Y|p_D==gAft}fFuA!0U%2$T~+yayE~OTByI`- zE?d54{My2LJ;A`W$Xq z)6s614xz1v2l7CN`9dcUNC=6dV{ju+8#+M)r<~?I8{#?|Z)+z)2uT1y-s}Ax=|pn* zLW1*kacp#?;^FZrKW#C>DW!&=XPU}vr3U8mg(zkP3A_efZm*(aVAtH(=t!Ltz>wuP zpIyjMN>gIJ=c6SA=JJJFR*;mK>6|tKbNND2XLvC`KM$XO_NmCDO0`f+7?aBvn7MwhGd3F^ zABXhJS1>Rz;P=r%%$1cuxd!I)g{)$eb;a+SCHVNGt57PH{60FW!Xqoyz+AqddcJFl z$Bz%d7hg_`XyP2EfKpIKU@l*n5b%mhz(|}-edt#dn_dhI#)YATKnzpTs)_M~;o+y@ z(p$cjpOKiCnG;oMU|`9-6&K#PC}PSC*9al2gpdip4@wh=so03D92Mv}@({fH-ut4G zSWG#KM5f(Lo6VhskR;27e0yOGj^nsU6UXerOEsirxb)^l5wGloHA^iqW((dw@l;Ge z(kAc<61kEs23<0qimL`9aZ^EgS5a*L{STaf{k2v!b;@xL#8hmx-N<8j z^pT@5G;~IHGheecX08ciOgF2$2RbqQ%vo4kx&x6&1O^9B)wOi*-inHr&CsX8ggb$} zot<5{b?X*vZmj78T}&0+A+jpo}Ha~}&&HcKV=V{6O&tb@E7o_+55;^N|uO)J*q zhn(TGmo7A5#I3BXs4Nh8#nG9}wCe(YH&#_b3%2G_Tv9vF`9K2q8#ZrW@nHFyhzy_rpRSU_FpkrHUU+UabrH zx^VMOsK9bsKlyA6!J?H}D~VmYw!X?>v-RHAFBpmDnn@@%uu}1|H860P%eZDWCYLX) z3hs2$g4?$jpj8BQT5Qno!_KPfxp08Z`;T{m9C;e95A>w>K5dxGR896Fip6j{0nv`^3! z%`FY_2zE&!ZfQa>ZZ7BQVl&?5sJ$j69E~*-Q zyuqkQ47yTBqnbRc8A*?Ds|G_PWYR^T%8VlFFc?L_zNJ)!Us?!Modu{&$y5)9E&_RR zm}o}9^OQ1!;iP@i`LW81H1Eh*+U=yH3wUu^Rbmq;uW#3t6j>E&pnbv@4h<-O!WW*a zIHAz0s{B|PffiF?qblSjZL(O9&E`QiHAw$iB$3Ht11;=z>n=*+Kb*RbcOy)t!~g&Q M07*qoM6N<$f-g#GRR910 literal 0 HcmV?d00001 diff --git a/icons/forecast/night/fog.png b/icons/forecast/night/fog.png new file mode 100644 index 0000000000000000000000000000000000000000..0be991394bc872d32832674d98451461477872f8 GIT binary patch literal 1649 zcmV-%29EiOP)lMHv3aO(ZVVMoL2rqNE(?so)?1hf*9HXjO4& zkSJ+I1?B?c5=S|KK&rVQ!6C#s@Vek5fkVRqRpsD^%7scab}20Z@}LMODVAzCaVSZo zL9_Zd*v_uAv-|HOjz4KG@$5Xl{Qvx~*=3w_*ezg;^>WVVskiHEb^aJ*0mfK@F($~H zV~kBw&(T3WpjRNs1At*{emj@dk|Z+5LI98gfJZfc&Qs6Va_Y<&ivd7Zhd{vJNYPS~ zhy;G&^t>%2iKxP3Zp_^hB%%?SwA+B_p;aU?0RRsg>>Hq-+?6vioyiH+>j!{d00?Px zDWeq?EgOx5%X(u82O0q464QrJYRTYO#FEbBdeLVakdPV`--wMzBjHrz4~#MKw^t>` zDsaxV{&A*ZJBdhMLUN;e_(P1Pl8I;GEfeD$pbYl1OXT^EUIoCaFWk7D7(P(6CCQ!hC?AX!6 zZ@vAFsb}$sApxl*;`x_%+F!eP@q0*|eb>yRFL2H+TWT^%tWjad9o{2Ho`Tokc+1p`$ z#tlRL7Z(>Gm(9S*Ji~8%dN+aC!>A{XPS~PEW)7`nuG*Ha0e3er^`dpZ`|o zvCVYaCW+|2oZFG?0l<%!rwmJ<9)whW{neL7y4GdKYpt^IRmL7EPHqNy^W(L zg$iqB!}mNLjs)DedSL}O|1Lpwbrr4@3b3-WLO#CF=Yy`UF1YsdRlwqyNZx*`*S|xj z?&~>}D;1cTnI%=-X6Wwj-UtSR@nW&~@pgSX8V^Y?qHyIWn46t-bY(u@o{kq!o+=vZ zYyWbrL0Dd=39Nx3E%o%(Ix5%20%AjPCCMv{$>ex)RI}jkSxFSe!({>i-M+H`@R3N? z4#h`OL-COgxG zWo4b6ogP?)A=IfeU|F={g_Z5w*X4m#7=mJyH%Y|H<(^211^j;S`*%czR&fvOob6~N zEQ(aGxwfPlJbdt=7nZRd6ob4aV!=!0t`jK=@DJ?Y@Afj9A*sgFnqeOj+2^+Z_{gC{ zURCQt?RDj*cX6-|?Z`XBdJrXmYuB&4nGS1&QfC=Yj&7C-4BZMtGGo|WDeQ}^jv#Wu zy^yei{`6Qafnet1I}#c~A4&Q`q*YW`|G8^vX{o1FDjAyl-#d2}*Vfj`%gf7)CZ|?j zksD8r>b@tvt3IS*7h3*98cv{1A_3YY5}-{Y0oo)IpiLqHOx2wkV^X&!#FgA8V}*#m z%P?=pcfbrm_=z$N3p8A^rL^Y22uOA;y(XOEknDaank<217B)H7Fjk0=K(z@X170UF zgbO8+5|dKhti{}rMCq^(&oJ2x_@uaHuQT2-(V3X1cmdoD+OabM+Aa zBoaV6V!;nR#%x~Ku$DC^{zjq*0JeO4%oHU3M9K(5%CEHGquTCQ%Br(duWR@<>NmYM vR2*sEoRAK2=%Wn75<;vAn(@4J;~f42{W%zr`nq}500000NkvXXu0mjf1A7yC literal 0 HcmV?d00001 diff --git a/icons/forecast/night/heavy_rain.png b/icons/forecast/night/heavy_rain.png new file mode 100644 index 0000000000000000000000000000000000000000..6377d433025a9b64f6e5bea48173e3a1a37627fa GIT binary patch literal 1962 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 literal 0 HcmV?d00001 diff --git a/icons/forecast/night/mostly_cloudy.png b/icons/forecast/night/mostly_cloudy.png new file mode 100644 index 0000000000000000000000000000000000000000..0270da65f56f4daa77208f77e870194cdaf69edd GIT binary patch literal 1391 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 W4{T;`Cb|p&0000WfYLR)iYe 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 Hu0mjfJBT`X literal 0 HcmV?d00001 diff --git a/icons/forecast/night/scattered_thunderstorm.png b/icons/forecast/night/scattered_thunderstorm.png new file mode 100644 index 0000000000000000000000000000000000000000..771d1ab1b47a9409ec0a9ef4d959fd6c57fe79db GIT binary patch literal 1879 zcmV-d2dMaoP)9?MHI*1SgBOngApyIIn_;jX+dg>i1?ut$G%rJ zs!*Vpx)%_a#?6HbQqzARt#g6Gf#gQuK%xp2MZ!N+ZiU7cp-_I+DWXZ+NW`rgAtlnF zw>+E8dhOkro!K2n`A9j$*`1wy{pP(lZ)TQp&SA5{7)t;^4gfL$P~x0tRsXA-Qa}=J z6adcHKj)k?nf40BGJXNIbShbK{w)@yqb9-_LyN08#RGtHfbL!fP9;~a1BT3qe%=V=~S}m4%%9vO}scc&HpG} zza_dbsd_r+Cy1b*siad$>ETOhK~8YU*{M@Ma2VAXW7yu8l5OqhoYy4pZ9!`uSSXeU zX-+CDoD)mPk@LCj)oSH^$u?#<=NTW@`9hn3g<=^kW>`nt^+%8Y`Q*w++pk^wO16nN zIOo$ouJc5>fG)X{Hm0Vg;NvSF$~2Fbi~&Vzer!kSOe9S4kfEU=c<0^sWNyXaRiZ>Z z5XuA$Tf7DeL=;Z2?WnZC>9ECXfFhnc^`^|NC}I)k)07$sqeQ?$v8*!EN-^~IY3S|k zmA(A}R-CaID=K8*AVSIUoj{E!;`;S(Vf4ZWDjJ(1cZ5M^1%nLIc@#6z4TWQ@5lrC=RWSU|Ek{Aqk6ovOg0GxXMJ6$1L9j`M0? z0@9Q&OLN$LCJE-GzyBpwR_KM5h<6mwHW!XGK#1~saxe@5E!WjnK%G-D-5h@Y1>KLx zHwqFh0!Eb(tK{H;zGiEC_B;o>cR$Aw0t*@_JLjE{E54Ud3i#DIK7iFCS`dJ<{mX&%P=6{%n<>h7HU98tv;JX_) z{0e!NbG~71s41Y!B63%8>!+XLm)p0cyIi~T2VA{6BP3o^K+5eZ9wl~T`Tph{{CW3o z;O;FgEy5R{f9CuceO|U*{jf0?9_?1~(DGD>hwR`c?V-z@)mAJzM zeO{1Z>qcUKAN2RXT%}-<@mfELk$7EceHbuw@XD*Nt@vI3WZx7@6Q<2)+ws@EMJvKS ze)Pz?LL}NskQ&dAd5YL0v*jUdP&mxlR##WUd~%j@;*Ru~UK~&rVI}Yt7mVKC7NK}Z z{M_(J!s{aRfky7I{Pnlrtzl~jn9fX(=f_$TX%hupTU&?O*;!ayTQi_H8^H7?GoBx7 zrZz?n;o8E2iQ)kjSt{p-N1E%&v4GqEwQzrF$+RxniPQF9i8f1PN~?^JN)-0+CC zO)h3va|*2n!$y)z-mb~(R3Q^|SXpeVOM!FRdHfRQ@qwp%m`bXcIyy@tvTN~=Og5b@g4B)foB zr5w?yWmug-q_Hul=A2nD#?bkHD$wL44O`=K#K_*>#u)Sa;Paewkykv0N+fSCA`A4Q zOQ?8~tR)G=Jwt=)1G%eSe+AE7Q>eP)~rwoB7kXzY)zuq5c0uix>8w}qNbP1%Td&1_uXH-leuznySHUYqjz#}=vSd;8| z1X%(Tg7-HfqI4NBe#4Um4YhZ*iHsI<99G-_4 z1SV7ICLUJB9sFqEgpS9ek)pxi(MHO_IC=Wh7-L%wp0$Kh0+Xq94OjHCe6$?>5iiC% zX(@VQob*H8lkvy#JotVMsSl((<+$JFTdj0sEVK%mzR`4m7$nwJoWTx z)vrh!2F6kXxlea3h~yEPo10Y*pXXVnB92I)*U^LughL_d>A6rG`9w`EghK!R%tE06 z`K?XZ+}woQx2Iuyds}n{1B|i#{Nw>E%q7pa3YS~#?G@mgg#~!9x@vo*_4PIQ;>PE8 zi6p}jH-E5CpbpW58~pO?2F%UQ!rtDV@F*meU(e0L+}upL+m4V227%m7$&^U1YmeOO z1DL;i*Yz~-FWiGvDrxmq$t0{{Lu^i$RnJ7rUlyDpErL*dBbSA47QoTz+roP z2Qu^XQm6fF`*ToNcj^x=bxoC$3sqeA1=3wvTv~FZ^U8rN_N(W6l6LDRRXjWk1jx#g zzWesO#o{GmZ3WSFssFOA8gfWg%Y#hNQYC3qNSGDkc2rx4r*52Y}8kyu-*8d@fE-)Dp>6`HW3!re%OrSPWSY2JM!7OZ{ zrbu8c8rd=zO8mxSCr|21*a3`=3{CI*fm|0(=rFN{h6W90Q4y4EJ?KcsqLH``B-TBC zve;dqIf}mEkw`=r65HC^ARG?+J&jVx8-~|1l;b4!`e|6g;ShA3?a-CPP9q*J+nr_F zrr=xJpR&+1Gc&NUvEj$-4!n_>UdBf&R3^?sj<%9~A z0>;M1KKr{+IA!PFheDyB`}+FMm->Ej>H8;lcXuDPGuF!TvfEfcYn2By;K^1mL`(?o zEQ*TuD*FZH1oCaE8G&VgoiP@eN}(4CRnZ>mKo$9w63BN|inPjIF}IEd0ZNxJB9b19 z2s~0Nb1SMBP$q$h7#AcjkWeCleE*q~cDbCX-eBb_)tDLq-d1s#m?%`8T;#M}u2I0< zakWWM7Nkp{gVQvez_MG^52(UB3C|>u$H%1(r_ds< zbpkJS;D7~rd9LpQk-oQrS2Yoll_ffmcN(b9P4hDUyD| z3ms@S4!uEejzyrlCPE2?d0jIpGN4)qQdWiz^`d>Z$BsMzfPVnWh{-cS6F4#e0000< KMNUMnLSTX+eoMgs literal 0 HcmV?d00001 diff --git a/icons/forecast/night/sleet.png b/icons/forecast/night/sleet.png new file mode 100644 index 0000000000000000000000000000000000000000..eaace768eef45a3a7b79060c203b4d22e4a51773 GIT binary patch literal 2074 zcmV+#2<7*QP)k$aR3+wfGp>HQs?6~ErE=&JphoR ztDU}{Eyw7%>gGB|5F_gA(061ux(xUDuHXVVCF(fW2`MIR~eoaLnV+_}~vo=k% z6Sz(HfMLM}d5DL)r`Hq&GR9Ia$O{AwPatCqwdy68%{z79zZoQOT%oOY0z+3KxqP8t z@>6MWAZEK}F~+cOOyPF2dwE9P4e4CIFia1ESC2#UG5}1|l{FK}7|Q~{Da9%5<(z9% zCUqe&moEsfAZ}sYx8?!*10gV%FZ9y7rBmu^ zu=>wq`1YGA`24d^RJ-DG(5YiB)J|Z%$TQ;FwQF8T)DoG|Y6pfv9^%ZIGw}WgAF6ii zA@|Q0v=L}n^Xf{Tz{$5x!SUlKRJ)x;Q;1@Ro}g0#Cw-VGzR|jK2aLS?9&~hcs1Aj8 zWQmM&WoRLgya`o>nW8{N4e)!+vtZ=glP^pXhyRqgLVINF9EDfa39lYK8pNpv+sRRb?iUp0XuEX`X z&A26jVHw0a(RKKL0=v6k-Zb*c$|ES3eP=kYa?TfPT)Ac-Z*A>g_@h#RAAgvEojY5l z?H(-u1$Xb>g_}2jR=vfBFTIICdTpm&1Ue&&@^8Px?OV5CZEel<6j367x^V;M=Wo01 za+-6F%Ug{dIN7M=q163SoQ2t9QHeZ6TiY&p?e!rzdD4}RJSWHlV>E_ryS(zuBLF0C zW@ZMe)vE8wEG<>wt1rKh{hZ;Pr;J}1C2%y-EZLw~ENUSSv44MuEGRKzCJzLGq&OPV zT){Q3QmJSi^Mw~*f_?j*7a!*UpidUT77`fOI)gd^UtLrfLEw!OZ$2ie>*btFYifiY zs6AgA1cZ6ARH>{hisj|y!0xqs_ftDQN}O{nMiK-@Uh~f1`4txK-&fYKJ`tyMtQ6a6 z{fjJgYFH0&d^2$k^c>C(8X=R1=t(5gwgJ{i7g)~Uxf3RN0t<;`8b}+DPh`e6Y2(ON zXFaS|*VnuV0rkX{L^546O69h}f!K7EW+`y*UZlt~0*OQt*Oxvaf#pa{&44A%#<6C! zQ>=7@K<&lXez3B#T5og33u1|6IxYyz>K)r3R#sP|G#xt(uM;>p5SyeR;TF`;Z`8sn zwZ?&>tyLaHju_g_1V%!cw6(QDOG`@xX5x$u0;&EuGEtfR>NPH5U~^uWY{GLtU|d`gtq`a(6MDwW%iLi8yp3NHooc>#(pz zpigWO=o4E6`otE2KCwlhPiztB6Tb4ohC{v9jZCNQqqGYsMSDvojfILbQ1)x%@UV%@ zm~G&f+K-BROk~CaO;>o9K*ktOQfz&=;FMRP4Sgj#mI?cOO=ny=AxoQHu>9^)BAIrI zoNu}ZCVsp=qZ@c4kR%C7Gs3C9FlFKPxp_GAxY9m}WV+vC*HrWfRL!4A2nUx(F`1KH zc73dV(|+8O5y}M8wot2TY`Y+=Y%`*pvSM?2nY7$&A~S}s?^VNB)u2n4m4gY4u|!V!kd}EaS0jyiiDS%g*?{;()wdXURD?3je5CKB8;(ViJf-{ zcPv`8$3=YBqWu)-TxvX%7a{iQp<+szD?&}}d&$r~NL3ZXq>1YWM>B@J1uOC{T9HSP z*@;eCcifP;YNv`&yBcoeNg)Trs4{b72U?L)m67C;Z~?1=Zo6Fs<}6{|&u*|Kko;Y% zNu_K82TFvSU@kYVIuw7JbJlgwVM_@#oCI#hoNV8iN5YK|WhGTgUiUi~!YJ)i)P5?I z;wT#}I;v>4bV|&Y>1NWp=1bKV#&ZH>RsExqBzwbdqjd!Z+i#luU8}bJ5^0@mX6fTj zb|8hJ;^eML?J@-AHX(3>*OPAGpbeXl&KzWB>pF07*qoM6N<$ Eg3f-;YybcN literal 0 HcmV?d00001 diff --git a/icons/forecast/night/snow.png b/icons/forecast/night/snow.png new file mode 100644 index 0000000000000000000000000000000000000000..4a6d6d88cfcabc4199910ba207855b4a05930ac6 GIT binary patch literal 2070 zcmV+x2lMHv3KO4SlqvgxS-K`eSJmEs`U-mo22JcTeS zAR!@|UJ=;A73ZcGgg6-6AP`&{jzJt!p;C~*52+leL~TQX7NO<^acR=lR-4c&kp}(N zzh<-D_s%}->?cK4yOWvOuQT(1|6^8QjKNlcAP7;$*qjruyKO6QCrRZ>cSPbj2mmtxFrw0=;>5Up zMI@1kD=zHz*nMMcXgkk%K_c{-)t=JW&W@umh$QQ*WLT6CabgbrvLf>)+QyuiK+hA2 zV%wxnjMI6_6|P(DQn`vRfm<;_t3&NvYmfy-xiT}BjH`Jh;})ioqBE5nT=TU{+)fuKFp4e+ zf+bnRg-DV(86JJv!GQyh!;vE|+vx%qLRk=m3iuQ30^bjf$8aK`0Ue9+#GV$Fp;HlHA;SM$}g)= z4Sz2+;q}*Eb!)hJ##mH0XhAD4&)GY16li!*4%rauX3)`~n#P7)yjm}jDB_7Y*O-JF z+M=texFX$h@tnlINB6riOr9~;3Gdx=M)m`Vh^zlxgD=0BhWmH#hEQnNre_-)8_;gu zgl2OI&Y%AgR##W84AEeWb))T@i0lUvuPv{@AD1t~%F2qN_DJeqiwjUHeP@PH+|n^= z9g&>KydQ+MRs8n*r4ET!aQE&V`1YH#Fh8$bV}&Fp7-O3%Ho8QTcT)BdFQ)L*g&Hg_ zEZFJ;SLfGfzS1I7kwlPLZ7y*+FTjID#K6D+Jp0`9FgzSpdfcGxtXMnLA(FIW&q*90 z@y&xo5lH4!Pd@{pP)PizHU)rVTD6tpinJY%^8)MZ8}RGJi`+-vv12D3KKx{dK=i4N zMA18EWT{-8bEAfk2U_hmEG;!*eSIBnT)z(OcH60;u;Xa4kTX=6IzW=g_ax#L#?`mB zw&vE5VKe#f0jEkhc!HNPv4DgbB7Kc>=YB9Ot{o!M5QOhb72oQ|#?QaN?3F8CPh%bc z=BV~^rdY@+T5jJKQLV17`GG_r`+?H%11yh8XR?YG+_ywtvgqo$fD`FV_MUTdo3tp& zPq^A_`jtq;QTfUgS7gJR`6vS;IH(hS4qTaCUs5E}m#eY|Fd`SW6AiW zU$*!od-wWa7P{yLd6fit$`5jA_~D0O(EnS!E6nd9lCEJS{ID_)9y$oyw{Q1)8frLQ zEabYfQPD+2GRKmgQUy=X%t$y4vC&aKlG%`F6?BWJsCWv`&ZADxP7N3puU)7)84WiB z+^H#f==67=J`S)E^4?D*hKZ~Smvh52G3rx@t9RcI=yk#rsN3yXVNt=1mB zaaWd?|GahU)@>W}YZVJQ-Oo2{sn69oi7oND8aEIS$q50GoDdMn2?3Fu5D>`;0g;?w ztI{H!$%=x!Zlu^Koym%o3%c#onXGuj3Z%gS1MLk7Xx^~ABGZ|ycy$D;H0Fp@t@aga zi$8UGakaK$EqkYZI+N8ThQ^+w_BExvv(vCdB&8vxwdP^1ieFlZ(n8i@oz%v->_Wgu zI+NA9WH~AG{f|mhrEb9Lid0%nJbiAZzO;xj+nzft5lLr|Jc_P$j@7Af(`bA7vo;km zsX@M#8%EAmp0H!Jak~~MK6k8G$f<5^GA!8sbNYTyQtL(OS(4i0PeQEuw2#Dl_fJja-RE+6+LT6VtSk;iIMhENk(o;%c#Kr;yXp( zn3ytpeZy(!8XNKwq#4AMS{9nT5UH3}wa`SO^y9*vTc>JdlCYQ7yQ9qU#jOk@TU(xn zRlOxfFDk#HtmVO^WFARa=N6j$*@(n{_Y_vnNT;`wFSe>#7$LGC`@3pUDs5q@$J5ri zbvq^Vc-lG_gz~cXu6rcLN`2{Ed!SQ;m5KO_0ncA;3vsp?TlYGQ<}`h;55 zm{YKtYKYKht3@$Aq9u#St&^rE;yS`!{=M>yj_}S)908ousa{DnD7j^3Qvq7338iOR zL@JHl6RC!ZLh3+EUn*%yEq$r03Tl^hNWoQwDTN^zvvAyPb%^d*nA4re-8TGf24FCWD07*qoM6N<$g599x AH2?qr literal 0 HcmV?d00001 diff --git a/icons/forecast/night/windy_mostly_cloudy.png b/icons/forecast/night/windy_mostly_cloudy.png new file mode 100644 index 0000000000000000000000000000000000000000..65d829daa31a9660ab98ca424a6cf0fe17d0709f GIT binary patch literal 2315 zcmV+m3H0`fP)G3EHkG6N!mXZQv zzIC>_-P^mJeebpYk_o-N-I=-He)G-um>D=i2n=R$9G3$C$%K#^vuPNVLB3;-nn zPy_(u=;d5Oh|kP+63nbD9>8%N5?T%bNwV$+&F=o7kz6XRRREB$^gSVjGrP?}A-R-T z9P-nm+`?PY?hq>~brTqfwG0EET&IYIfle;RaivZX3j=vV<2WuI09qJt4-rC=&F;fM zmfR{0_a@PoVJcj0EqJ^gUor9fV8c0lbpN>sMS=5jQ?5LLKJEqJ_q0sWCVD!Q^6 z0Q_{as>#dEmN)<$$Dxx@tg(5X5R#*>=gM%LT#uJe4|7H3UK0yKNH74Dic-ySTp-M? z)q)H{i0a5-g=6H>Az!LP!eXI%_wLr zS}4h?rW(g_f>&v&wLm?teU>$#5eE+*gstzsV^(#|G&LleZKC=R-R^a!#}+(Z9^aZc z5tN!;zY#q8*yCnaC%WfRV;F`dmwH+Sv?%7M+=M-Q_Q2<#eQHOKGIUEWCAEYW$8{FU~2Xswn)P2RM)Z6hyEMm&!NsyYFYIQS$b)=bGYO{K+hzr<-xlcUV-_q#O z_rk=9lZBF)m}o{1s>RrZCY?)(-Q^T1W}v&f2Y8-`YuBzqQc|P?cI(zH2n9Q#t?e>+ zy&kPCp2y(67?)rsH{OZu>$?RfPo096bLW_!xp}h}ni?CSq2UP2?HKUMA%ytF3ryrv zFUucq`c*_Q6oR8ijzUjQkFMw2uU~`O+I`U7-5u=~B(^{vs$|u=`)0(i6KN5{CV~-IC1>ARo%>}tSD1WWU6^+rly)%(9zKmK`f%F zsZl8N=P!We&pi)g)5b`?M-%IETN`wBb-|6E9ted(R(7+5Wkjoyo2l}>=}1dU6FjK4 zwl>A8k@)83CfNG^hXTP!>gh9Q;Npb~c6PIfg$%Yjt*x#7pHH4R0pEZ3 zjf8M~0UI`MhSp1$Ox3FzkXu<%7Bfri%C%y54Ic*MAGp82uS&7EGBsRd50<21W_JYm3*fs;npzR!gc$uA=ARBsx1gVfO4SNe_B^ zdxd|0tgVUg|1s#Y^Na1!(!w6FF^KZD#p}~q$TfA-n;Gb6Z->MYcS~wQ+ZeGZzaVN% z_UE5!;rKDV>+>`ri78l~mz(XQfeF_jmUVzbAHBGz7v>Av*PSORr<)DO-BS%TDUcGw7j-SDnTBjR23Y?tWkl2s6RYoKY$3Hkh^;wkOaD+>~ zh43+ZXnDEWej0+bm0%8d=Yi7PcV^=www zoM}Ub46$Vo+uprDC|<`Up<~jZ?;V5(>3sx`urB*e@a(;+1#1^i9Q zxiYk8PoFvwp<_pl9&JU}QC*gZaRm@Vj<~4T?)?g^z@T{E+<7o%%G8Mb8Rpy2^}T)j zHe9`O1v)y~`_nsrcXh#`Lv;~0E$V4>GE77>4LO#}LymE~Yh9~4qi80C*p|AA%U$a_ ziRJB1xW?;|00CN9fO=vbU1MNtd^(=hf_U1JlJ6U&v1q>)hq19@(4UBBB?iL*LXP2o za;*fag;<5^HR3?=)3!5}`bXCd1U2?6NNv&_wcl+i3Jb_?|%G@)XODIqz^p+s8+MO))|U4*go$Wut0Jo*1ws<`6G- zu3RS?jcnNxV~7iKqwSV40J)@ElCd0g9vZcN;#Fq+3#WNF`BOlo?R*oIGIJxZ>!�};Qd}1- zr{|0pL~&I^loU-mGVFge3DB2g%X%hPk*kv8k2uf3?Qk2p(K>^f+md)97F=lOZVCGv lB^BM?XK3a&u80Ky_!kl>%VW2V;3=@Z`1L*;lLm{m^ynP8>AOx2FNUsnIN+nfOar2005eu8Q}l` literal 0 HcmV?d00001 diff --git a/icons/moon/3.bmp b/icons/moon/3.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2c80cd786e697d4c52740c8726ecdefc190817b5 GIT binary patch literal 168 zcmZ?rUBLhWc0fu4h^3&Ik%18`kp(2Tazij!07(4*{~y9kU|?YUz`(!`#QZ=k55)E$ r{D6U>9*X}1>Hh~%FbABEP-hR+BL~t8G=mRh7RXGH*$fE`d<+Z#pJQ3v`j%HdKO`VbgaVy?3T`)l28)qe3oPXe%$JBLn|x;leLeN o`plg+phQG(i)2+kRmpL}8G|ki3Jkyg_VTz(m@JX$WJ^UZy+@)e3IG5A literal 0 HcmV?d00001 diff --git a/icons/moon/5.bmp b/icons/moon/5.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e05e7e0bfe8385a0b628d0342d2de62478dfd6e0 GIT binary patch literal 168 zcmZ?rUBLhWc0fu4h^3&Ik%18`kp(2Tazij!07(4*{~yAvXJBCb-yq5U{{RC&5X%Fx z{r?AGT>t-pb^ZSjK(!ytffz)iV{4FD{r?X}K(%mvATvN_0nM}qn$5sa&!EM?006`O BKzaZG literal 0 HcmV?d00001 diff --git a/icons/moon/6.bmp b/icons/moon/6.bmp new file mode 100644 index 0000000000000000000000000000000000000000..aebdfdf8704d10396ef8b0230e1181ffa326acee GIT binary patch literal 168 zcmZ?rUBLhWc0fu4h^3&Ik%18`kp(2Tazij!07(4*{~yAvZ(wBn|A2-4{|^TK|9=?d xf!O~4KQONU|DPF%!D>P3abchuU64AUK3$Mrpc&R6vp{A7&1PVzZ?I-y004-BL}35` literal 0 HcmV?d00001 diff --git a/icons/moon/7.bmp b/icons/moon/7.bmp new file mode 100644 index 0000000000000000000000000000000000000000..a168647f1d29910c1e3c5355c9eb869c9656a6a6 GIT binary patch literal 168 zcmZ?rUBLhWc0fu4h^3&Ik%18`kp(2Tazij!07(4*{~yAvZ(wBn|A3kO{|^TK|9=?d s|NmpK2g-qP{r~?BApHOT{{#3iOr1ScAIJ=lSs*ikW-~Ch($ literal 0 HcmV?d00001 diff --git a/icons/warnings/ORANGE.png b/icons/warnings/ORANGE.png new file mode 100644 index 0000000000000000000000000000000000000000..af7bd10e46d0a41d832e0f0e73225ee2ef4c4c1b GIT binary patch literal 2777 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 T1J`a9E?<*zv0;BmLc#w4m@E}d literal 0 HcmV?d00001 diff --git a/icons/warnings/YELLOW.png b/icons/warnings/YELLOW.png new file mode 100644 index 0000000000000000000000000000000000000000..3fb97f2a1ce51b61382052190899fcd31e460eba GIT binary patch literal 2775 zcmb_d3se(V8jj^iEtG0u5nXq6h(dWdnM_`g!HAFqi5PhZf+EOdW&*<_6EXt~CpGjSm z?QVLjdgs8x6H7Dvhg6D)({$$FZ(O)(Wqq=_?84jY`#-f0wAAfNEZsh5|L?wtP4>K{ zD?DF#^#S$3!};#K;RT%gr@}J7AJu2#HCpn|X=mm#;(sBdzvry2HZdzf`gB&HXAN?_9c0GsJHEwYFo5hv|3GkW80oyw^7IjM+f+> ztoNk!|7hp-J^yA~+sXQy9_-B@hJWyPQn;V>zb4pvq&XH&iv z9&Gv`;w=36)*^;#skxf5_4>U;rXKI{E^SXd{dQ*0*$e64jP-uFz2ly9&v;)-$I+^_ z-TOzuTi^Bvk+yB`jhz}}AB`^)xCrLUZZ)0i22=8!Ieaq<#;1#a z=Y;zFDNi79+(XAEa0&YL7{O$TaFQ`lqP#3KKU9KM!Fgt!$e=jLK&8?qm2l*fb3%wF zRl<0=UaU8(sWdvgz(Q>-*bqw;WDrVH7`z%f=DDVK)`IiiNo_?6T5uc zf`(!V3vK3T#su*eaRZaXsf2*)WDHs68M7vKDo-F|NFHuRP?4CAX#z+RGdOdOC3C_# zNgz}vl|`927GTjCtT~P07&eW0g6Wy<#~gs%>h&`|X62HVHRFQiw7DRRsf5gmW@Gcs z6cR(ROpb-1w7G!hay}X}rnXQx$5>(+CUd${QPY$miClzAAfI^JL^4*^_a_>Z2InZ1 zkS`l7R>G(}7DX{}Ach9|izS#?JPFk^ByG%}4Jr-5r1Hn0plL{)!+#Y_5}1*(WZ@uU zItx#w5VI*&2+iaXQ!|;21vm!m&uVSyhcE54J_+%NRK;ZlOX_LAHLqpO7c9XYt%$<}L-bQUoX^umqJT zV5w9lhXZk321{h}0IAY|N&=L!>DXuEEHioF!k?0pO@{ou|0ec+a-xD#PAq|Anipp-2=Fm;#ZV@YC=0ki3M*IB;PUCz zX+mNH_tup~xHdxbG)wligs@uU!5x<_Ry6&1$?m_#AopE(u*wh`;rCa!MYZlZ<`fTs z?zl|`m9Fe}h&k_A;}-5dc>EawQy&!Wc>gQS_kT!hU2^iihbFlezU%AwU44*l_17oP zq^y4%YcFFTn=_B{I_a_Fn$i;Sa|t(~X#Ige~PV1z=2Eh3up~t0`b^tl{%+N2H)uW#O)35#4dMn4 literal 0 HcmV?d00001 diff --git a/icons/warnings/fire.png b/icons/warnings/fire.png new file mode 100644 index 0000000000000000000000000000000000000000..d199a68ed713a86609f666ed6d2bbffdb5e9e192 GIT binary patch literal 1729 zcmbVNTWB0r7@nk-#>Aqn3PnN3$xF1I>&{;0&@oLi(Ouh(F5EkmY@xN5Lcz8|9)v1=DAWf*#iu@r4D(XZFL115>D>NoSt|QuxaJfW z0zADiQ9%o{NVB-%r`g>NKnYwNLAK%6y%02XuEh)JI(aQ}Yzq?2>fA`;kew+{vSTE` ztR~1jk|c%AYXZ!{Tt>_7ViieIMJXf73NPh>S^!Fh?ff|EF0gDcWjxbyMxS)97DYY~ z#W;?II3tjtD$1IsiIO5J3QrMy*z_XU;Jt9L%V6LT1&$v%#A6diXp*@|=cv?n46dKj zdSNF|G-F}|`l2jINlY!Eg;Jb97t~wMEhOSPcCiGm22G`Hn) z>SJ9kuA90Dqw#qfMkgWbqQgql$Kn(Y$y|W&_&k+4m_*})u>iw}1QkN+-AYY%C9_Ia zkQH`lzvEdX4xhZq0vj;GI+v7j;Xq~igFp0yfA_u!X-oJXwZV=Sim$3 za!xX3DnE~S(^f5BQa6Mx+iC*eA`}D yiuZrdH4BvS1&vWG62M2y%esA^D0rB{Oy(jv$ug)bOnbP=#adPy~>;C~z%_+hF literal 0 HcmV?d00001 diff --git a/icons/warnings/no_wifi.bmp b/icons/warnings/no_wifi.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5073745c8d9b3836e495a8133b36278ffc4d4949 GIT binary patch literal 1524 zcmbVMTWB0r7@lB2ZK)da5--Sj3YuW&y0^@+V-k1L?qVhl$+lggqG!*X?2g%)Go6`a zXY*iUN(3<=K1E9qv84qc3YNarODKwcDI$nLN(BWG5o@aWs-D@)&`Pw}ftfj%@4tNi z?X{`NgL`&8w2R}oJ;j1C&HjV&+rvKG>DARu_Pf_BJQr}>?*91g;ZAN0a9r=UT`GrV za{^-Oh{&Q9B1VoEOL6;0A`js?67m&NwOt+jdhrv$+m;TFW=z@i@}y=L7JV|aI9bAr zb6B&$$kY7(2r>l@2@xMTbvJ;K4qAF4TgS%|;9HPzP6uPLLB4EG@pzO zAx~07Ip)*?S~$UZ^M1W$+`4>g4(s11z?tnb_FT7sp9V z1mXAsqtOY-j_9D&^oTS~0y^(wGQPmv+#9p;VBRMvq<)FgdN)&3U6*+^BPuFCc+_?+ z+6abjk{|{Oi4Nkl39=?AnUa!*=`75oo|IK6%WbGhEqk`P6EvejW%w2-D;f)h=(b=B z!&&M(h%vSuR3(zGW&wT&8KO8+C>ZCF5O!v^xnSd~TV_Vpw& z@1?jC+eD;)a%|n*)U{nW8wK$)8A5E6j4|S}+2yk#wQ>Hf8XIu$7mcyfJ#RZ-eBfLf z(TX6-$Jo(=!wU@V*h7rn@0Ju6Yow`%FBI~*TInR<-kwdrzY~h|M7yn%)Qie zy?3HGdhYlc_4?+(O8(`OAD{ni{Z~hR+S|wFL8z1*rYIY9uAA)c96KgyX4@=u(vYk)DniemIlDW}&YW@P zBs&RMp%Ar?`XGoE1T83qYPFO?A6oH2>JE^{t67XD0Iy~xVPB=-L-qcD&=2oVw zcx3^bt~B->*qcC3;1h;G;fqRLGl!XMmv@BdV5Hbwk#QaHIr{Jm@_VR~NZ;IBW0kDdRg!YBCl=Rc*K1N;|L6 zsG;hnX{wr`8ivdfa=aWcl*mE6r_W%M7)M^nJQ@JOh-!3^SrRw3>w_Prv_ag96OWjh zpitEnP59ISx;Vv!i&3ki+{G$s5uXGs=2$(&hE2+7+@$|PonGIgfXCKxQXQMx;`^zJ zn3b2f8@+&RijJ$xAyH>ZOcx_e%1hkLJ;Iw1PDTVV8dWK6^)ofoHyIeCif(}1VJ~oL zJ08AEg4l==OA=|5HB;6{tGW&~lc()z&48NLg*w#r>dTu!b)I4K9w;vw7cq2Su!~`x zMn2*mdp>FqH4GXONaF}6X^Tc&F}JR3h2y|V5Hl2DQn4+GJFR%03-h>!%_1qura|)D zhmn$8Bl)6SFOs^Bjaq@!Q+b=>MUmRRy!$`p4@DmDE!28o9?@~4Cg7A8^K32mT4Ij8 z(p$AW&?y6muqba!!XmH4m3qhC14p1s+int0`XA};!eUxyZ4{A-29MVL>q+I-tD+OT zj;R0iv2%A<*Y@Fj6o_Ryg!m>MV1;?lukW6t8dM|SuwT@f?DB9?w!x~XWw{g_%*Nm*3j-9JHG$LJG1f4!7u!2{fU#m z9X*$~#(t=+oPPZJC%Z3(pRIlT=jw3o)lYA%oqdE|yZrw6`e0$;+jb7I)35Uv7HNpueS{R{EZW*p1ggeeC6$p%!k9HUv7DE R>AaZ4mGZQGX5W$He*+b3>f8VT literal 0 HcmV?d00001 diff --git a/icons/warnings/water_shortage.png b/icons/warnings/water_shortage.png new file mode 100644 index 0000000000000000000000000000000000000000..cb5456a42b8562590e0bf8cc57866a7fc469c5d3 GIT binary patch literal 1503 zcmbVMTWAzl7+!7bEfF7tK8P*D*oTPDbuZaDI7-}2+!ZH@$x@S-+OsofcZc1XGtQjN zOaiT7@u8MhsEUfBh(Q!2K3Hgp&ocCSEuBA*He|!gp_*No@zMCq zaLJsho0`S%+R5#R09D{10=dX(xB-ZCzNHt?H9MAgt_2}=ogZL?+=Ma8<*<)9O;iL^ zmQ}7_6JZ8sl3Hdvr^;$dl9Q663UWV4Wr3RHIuB3je5(S+@@HGmtz`X88?Z}Q&ZbMI9{yQ4LWh6 z5tAb5Ns1^lpISi6jB#GmZ?woQQ$h{oAeRIbtHfBZh6xU8_+M1V*Uu2p*cwL6v7{}I z6H^3aaEiLo3CNP@pfv3vX$%Fp>6>V9iki8FdE7}4b3Z&A{Kxxrfkih4H zEfZ9*??CFY?Z7IMJh#eoaU4MoH?U8Msda5D36RB%K99{NOKn%)`k(S6zD;`zHkPf2bsVb+P_P4< zt?5onjH5%HRm0|5WdNYb%3J46mRDr)onw315ooIoYltfSk94j^qxJlH zlBo3(>%_Jr(m#D{-96Q{T{s;DY#9$Bx{1daa_Q{y>5zK#_|p&QfIC&p50uuNHEt_= zzf5c%dUWuwwI3BO0r2g)?=MHk=B9$h!oYbw_fnWFuCaH1eD`PL!51fbFZ5jh5$yf# z<%O@WoS8dzZ*tYiF!|op{*CW%+HmumhbvFNb`4)W{>$s*Ki%1!c`I2Dk3aeS{{7y^ zH-6o-b$;mH+wkJ-!dG{C7gsO#G_}irygHxzaQ>Z{PYw-_)PH#8<{M95=t<{B{_HuN QWh1yaIGmr`{pOLs0TB@EWB>pF literal 0 HcmV?d00001 diff --git a/icons/warnings/wind.bmp b/icons/warnings/wind.bmp new file mode 100644 index 0000000000000000000000000000000000000000..9dddfe858ccd8c55c68c140f7240e2e812c807b6 GIT binary patch literal 384 zcmYk0v1$TQ5Jm6HDyxN&G!_BH(oVLsiGr~eZPYKYSX@!m)?~p#kdRNv59B8TDQqoM zrnA)2LVVsgvw|09&Rq8H%xkt|15us5a#sfWp-F@rElmQ}+S_=~f|wB&WR2BgT_E0A zbeuO^xI-{2*C{U+z{B%k%h5*)2hZ9I7LzG?#^(D;B7p`mmizV;A>un2P?Zw;WS+k( zt&nFv{46&+Mo|c?J|A7){I0+H2w?W>^9*xtHYcF>eteT{%cB5|f8|TU;txIFDICt` bxtN|xyVgbEZaY0Fl3sn4;V+}UvnvBvMa4li literal 0 HcmV?d00001 diff --git a/icons/wind/E.bmp b/icons/wind/E.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ac7b38721e80746c3b29875acf403eae055bc986 GIT binary patch literal 182 zcmZ?r-Npa`c0fu4h~=P|k%18+0VKC_LogGN4Z{C{07L;XHXs2M{K3G${(*tP{s9By u{|5~G{|_+K|8HOhYLf@z`v3pH7{msNf#kqyKx#pHzIIl}*j+)$cblqRw zn87`rwYA3hhO)J40yTk}fFJ?)FR@$;Y@(|;0%8%l{&E}Xe zr_L#zfp^MX%JTZ$lCX1`pvz%{R(gNJma?!um5p^;>lj~)L;3ubO`TP9#(lRYgYzoI ZF2(gZP;4$A?Kx&^`-lAQ?9jfi-|hLZID`NI literal 0 HcmV?d00001 diff --git a/icons/wind/ESE.bmp b/icons/wind/ESE.bmp new file mode 100644 index 0000000000000000000000000000000000000000..6dd197804c41b6051117fa2eafacdbfe48189279 GIT binary patch literal 182 zcma*eu?>JA6olc278Vw4AgxnKV((0_l-7=9X<o^bNzM?QX3BohDt literal 0 HcmV?d00001 diff --git a/icons/wind/N.bmp b/icons/wind/N.bmp new file mode 100644 index 0000000000000000000000000000000000000000..15635215b5626be0dff8f63a5c2d7f3f476a754d GIT binary patch literal 182 zcmXxdF$#b%429vp2JGPA4eaVEEVz3ny_C)##Upfd5NovN!IuOA$vAD44BC3t{T!|= xw60B2>HNx?w<(aHU%<3Gm~eoJ3Cw(g$(LR$T)z2SHp>?}MS7(uK`1cJJ4a+^-u<0-^S2)srD=%JA5QX8x)*YmE1Ph~yy*oI9rG-(K(wfdS7=e<)h(CeEmwdcS$PtF|l9Z0B zQDsV0y5Ky^q+pH$iG@ECwrr>S0|pi@^CJ4e3!c)!z{0_czCC+jKYlK1(TIGa C2qH89 literal 0 HcmV?d00001 diff --git a/icons/wind/NNW.bmp b/icons/wind/NNW.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5bd76262e84bb89b421c4bc73ed045150f2b2f59 GIT binary patch literal 3654 zcmeH{!3}^Q3`Oy5;t0m06F7heZ;tDH2r5ZK3I!~n#wOKD@xOjenAfEfM@;HEs_d_9 z#GtA(wpIw8CgdNl`vU6UNu4FbFiUbFvlQ26mf&K{uX0^pbNYQ6GnZGNPt)T*GmI{E z{{olt2>V=qp(a(=VCtDTg7Sv-?{G1_(6v-OM8_00V6% ATmS$7 literal 0 HcmV?d00001 diff --git a/icons/wind/S.bmp b/icons/wind/S.bmp new file mode 100644 index 0000000000000000000000000000000000000000..78ba4b61954672bc993a725536461f895275296f GIT binary patch literal 184 zcmYMpF%E+;429v}SRz9QZcx^qqLI4yLM-G`l{#`1j*yW7;X0uaPx_uM`}O{E=IP_% z?tg;R$K?IFo$A5M%-Twiq<|(}26P?K9FhG{v4aBJFY^XX%n-9U7}rJ^mVzoI2&r?R Ibgsnv2brHG<^TWy literal 0 HcmV?d00001 diff --git a/icons/wind/SE.bmp b/icons/wind/SE.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1d4b913c3a38c503f6a8ee85cff22223a7117140 GIT binary patch literal 182 zcmZvUu?<2o3`EbQa6!Qa&@u&)e$NhU5~hMSqc8$xiU|J~^2>kH^Z8`?mitRnH|z@M z4U^3S^L?Bg*av}zNe}EhG2EJzHXQdZouWe1CHkMtp5vnuPn91nO-3|gB93>b$}7;k FrxkkKDSiL| literal 0 HcmV?d00001 diff --git a/icons/wind/SSE.bmp b/icons/wind/SSE.bmp new file mode 100644 index 0000000000000000000000000000000000000000..998856585cbce421bb8f656b077f5d63e6cf811f GIT binary patch literal 3654 zcmeH@K@P$&3zNf6YdkD?IZvJ8?d>YZ>Uw#_=cyj9hX?OkE_~j;hU;a) zzXh|fRW8mM)o12L-H~&C-IukIbBi0v;jAr~h5wDqXB#ok#2s-zn`!TB_eZ@mcNC+R N3vO-i|84F+@C1YGID`NI literal 0 HcmV?d00001 diff --git a/icons/wind/SSW.bmp b/icons/wind/SSW.bmp new file mode 100644 index 0000000000000000000000000000000000000000..7e7802f14bffc7b4225923d142c89f054f6aab73 GIT binary patch literal 184 zcmZ?r-N66>c0fu4h~=P|k%18`Q3WKoazij!07(4*{~yXM0MZ{A7#N|L`2#}@`v->l z{s#>8{~s{e1F<|1^8+z65HkWX0|?gx)!Rd{{0AlmejppDkAe9I&@3os0MZNJQ3NN2sQn>kgXv3lfUNd-tXrusXfU8s}13nFr9SD{WkrOCZ literal 0 HcmV?d00001 diff --git a/icons/wind/WSW.bmp b/icons/wind/WSW.bmp new file mode 100644 index 0000000000000000000000000000000000000000..bc5fb7583894f1a024a2a9637dafa6b0133a8d26 GIT binary patch literal 3654 zcmeH@%?*Gc3`X&6;t0m06F7heZ;tDHh(Ar5hJr;vVrWumOZgsUUYAatHkoxa-rwG+ z!94MC>NrUHn7BFGY#ReYUbFeWF$En$vh2zxRLm%^SoSKbUG)~E;ESmq3 zO=slt*t2sSTRj%rrtPb}W&8A9=b>zu=F>9?`>Cd+&&QW&NqafAR41h0#tzX5xkK5U c2JNFcch6O)8Zq;)xG`s_TJ;C&50v=>I{{}nga7~l literal 0 HcmV?d00001 diff --git a/main.py b/main.py new file mode 100644 index 0000000..977a28e --- /dev/null +++ b/main.py @@ -0,0 +1,100 @@ +# main.py + +import logging +import time +import traceback +from datetime import datetime, timedelta + +import schedule +from icmplib import ping as _ping + +from tools.weather_logger import set_up_logger + +set_up_logger() + +from constants import MONTH_NAMES, WEEK_DAYS +from display import Display +from environment import Weather +from tools.mqtt import MQTT + +logger = logging.getLogger(__name__) +logger.info("Setting up components...") + +weather = Weather() +display = Display() +mqtt = MQTT() + + +def is_network_up(retries: int = 2, timeout: int = 2) -> bool: + """Checks if the internet is accessible by pinging Cloudflare.""" + try: + host = _ping("1.1.1.1", count=retries, timeout=timeout, privileged=False) + return host.is_alive + except Exception as e: + logger.warning(f"Ping failed: {e}") + return False + + +def date_string(date: datetime) -> tuple[str, str]: + """Formats the date and time strings for the display.""" + date_line = ( + f"{WEEK_DAYS[date.weekday()]}, {date.day} {MONTH_NAMES[date.month]} {date.year}" + ) + time_line = f"{date.hour:02d}:{date.minute:02d}" + return date_line, time_line + + +def update(first: bool = False) -> None: + start_timestamp = time.time() + logger.debug("Running scheduled update") + date = datetime.now() if first else datetime.now() + timedelta(seconds=20) + logger.debug(f"Target display time: {date}") + + weather_data = weather.get(date) + mqtt.send(weather_data) + + str_date = date_string(date) + + logger.debug("Constructing the Image...") + image = display.image.make_image(str_date, weather_data, is_network_up(timeout=1)) + + logger.debug("Refreshing the E-ink display...") + display.refresh(image, date, first) + + logger.debug( + f"Update completed in {round(time.time() - start_timestamp, 2)} seconds" + ) + + +def main(): + logger.info("Checking network connection...") + is_network_up() + + try: + logger.info("Starting weather station") + update(first=True) + schedule.every().minute.at(":40").do(update) + logger.info("Schedule started") + + while True: + schedule.run_pending() + time.sleep(1) + + except KeyboardInterrupt: + logger.info("Manual exit requested (Ctrl+C). Shutting down...") + except Exception as e: + logger.error(f"Schedule failed - ERROR: {e}") + logger.error(traceback.format_exc()) + finally: + try: + Display.exit() + logger.info("E-ink display safely shut down.") + except Exception as display_error: + logger.error( + f"Failed to shutdown the E-ink Display cleanly: {display_error}" + ) + exit() + + +if __name__ == "__main__": + main() diff --git a/tools/geometry.py b/tools/geometry.py new file mode 100644 index 0000000..5d2f8cc --- /dev/null +++ b/tools/geometry.py @@ -0,0 +1,186 @@ +# tools/geometry.py + +from dataclasses import dataclass + + +@dataclass +class Point: + """Represents a 2D point in a Cartesian coordinate system.""" + + x: float + y: float + + +class Line: + """Represents a finite line segment between two Points.""" + + def __init__(self, p1: Point, p2: Point) -> None: + self.p1 = p1 + self.p2 = p2 + + def distance_to_point(self, point: Point) -> float: + """ + Calculates the shortest distance from this line segment to a given point. + Uses vector projection to find the closest point on the segment. + """ + x1, y1 = self.p1.x, self.p1.y + x2, y2 = self.p2.x, self.p2.y + x3, y3 = point.x, point.y + + # If the line is actually just a single point + if x1 == x2 and y1 == y2: + return ((x1 - x3) ** 2 + (y1 - y3) ** 2) ** 0.5 + + px, py = x2 - x1, y2 - y1 + norm = px * px + py * py + + # Calculate the projection scalar (u) of the point onto the line + u = ((x3 - x1) * px + (y3 - y1) * py) / float(norm) + + # Clamp u to the [0, 1] range to ensure we stay on the line segment + u = max(0.0, min(1.0, u)) + + # Find the exact coordinates of the closest point on the segment + closest_x = x1 + u * px + closest_y = y1 + u * py + + # Return distance from the target point to the closest point + dx, dy = closest_x - x3, closest_y - y3 + return (dx * dx + dy * dy) ** 0.5 + + +class LineString: + """Represents a path formed by a sequence of connected line segments.""" + + def __init__(self, lines: list[list[float]]) -> None: + if len(lines) < 2: + raise ValueError("A LineString requires at least 2 coordinate pairs.") + + self.lineList: list[Line] = [] + for i in range(len(lines) - 1): + p1 = Point(lines[i][0], lines[i][1]) + p2 = Point(lines[i + 1][0], lines[i + 1][1]) + self.lineList.append(Line(p1, p2)) + + def distance_to(self, point: Point) -> float: + """Calculates the minimum distance from the given point to the LineString.""" + # Cleanly check the distance to all segments and return the smallest one + return min(line.distance_to_point(point) for line in self.lineList) + + def is_intersecting(self, point: Point) -> bool: + """Checks if a given point lies exactly on the LineString.""" + return self.distance_to(point) == 0.0 + + +class Polygon: + """Represents a 2D shape enclosed by a series of connected points.""" + + def __init__(self, points: list[list[float]]) -> None: + self.listPoints: list[Point] = [Point(p[0], p[1]) for p in points] + + def on_line(self, l1: Line, p: Point) -> bool: + """Checks if collinear point 'p' lies strictly on the line segment 'l1'.""" + return min(l1.p1.x, l1.p2.x) <= p.x <= max(l1.p1.x, l1.p2.x) and min( + l1.p1.y, l1.p2.y + ) <= p.y <= max(l1.p1.y, l1.p2.y) + + def direction(self, a: Point, b: Point, c: Point) -> int: + """ + Finds the orientation of an ordered triplet (a, b, c). + Returns: + 0 : Collinear + 1 : Clockwise + 2 : Counterclockwise + """ + val = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y) + if val == 0: + return 0 + return 2 if val < 0 else 1 + + def is_intersect(self, l1: Line, l2: Line) -> bool: + """Checks if line segment l1 intersects with line segment l2.""" + dir1 = self.direction(l1.p1, l1.p2, l2.p1) + dir2 = self.direction(l1.p1, l1.p2, l2.p2) + dir3 = self.direction(l2.p1, l2.p2, l1.p1) + dir4 = self.direction(l2.p1, l2.p2, l1.p2) + + # General case intersection + if dir1 != dir2 and dir3 != dir4: + return True + + # Special collinear cases + if dir1 == 0 and self.on_line(l1, l2.p1): + return True + if dir2 == 0 and self.on_line(l1, l2.p2): + return True + if dir3 == 0 and self.on_line(l2, l1.p1): + return True + if dir4 == 0 and self.on_line(l2, l1.p2): + return True + + return False + + def contains(self, p: Point) -> bool: + """ + Determines if a point is strictly inside the Polygon using the Ray-Casting algorithm. + Draws a horizontal line to the right of the point and counts edge intersections. + """ + n = len(self.listPoints) + if n < 3: + return False + + # Create a horizontal ray starting from the point and going infinitely right + exline = Line(p, Point(99999.0, p.y)) + count = 0 + + for i in range(n): + side = Line(self.listPoints[i], self.listPoints[(i + 1) % n]) + + if self.is_intersect(side, exline): + # If the point is collinear with the side, check if it's strictly on the side + if self.direction(side.p1, p, side.p2) == 0: + return self.on_line(side, p) + count += 1 + + # If the number of intersections is odd, the point is inside the polygon + return bool(count & 1) + + +class MultiPolygon: + """Represents a collection of multiple separate Polygons.""" + + def __init__(self, polygons: list) -> None: + self.listPolygons: list[Polygon] = [Polygon(polygon[0]) for polygon in polygons] + + def contains(self, p: Point) -> bool: + """Returns True if the point is inside ANY of the contained Polygons.""" + for polygon in self.listPolygons: + if polygon.contains(p): + return True + return False + + +@dataclass +class Coordinate: + """ + Custom type for the management of geographical coordinates. + Provides utility to switch between lat/long and x/y point spaces. + """ + + lat: float + long: float + + @property + def tuple(self) -> tuple[float, float]: + """Returns the coordinates as a (Latitude, Longitude) tuple.""" + return (self.lat, self.long) + + def to_Point(self, reversed: bool = False) -> Point: + """ + Returns Coordinates mapped to a Cartesian Point. + + Args: + reversed (bool): If True, switches orientation so x=long, y=lat. + Defaults to False (x=lat, y=long). + """ + return Point(self.long, self.lat) if reversed else Point(self.lat, self.long) diff --git a/tools/julian.py b/tools/julian.py new file mode 100644 index 0000000..7f0d9be --- /dev/null +++ b/tools/julian.py @@ -0,0 +1,58 @@ +# tools/julian.py + +from datetime import datetime, timedelta +import math + + +def julian_date(date: datetime | None = None): + """Given any date in the future or past, return julian date""" + if date is None: + date = datetime.now() + + time = date.timestamp() * 1000 + + offset = date.utcoffset() + tzoffset = offset.total_seconds() // 60 if offset is not None else 0 + + return (time / 86400000) - (tzoffset / 1440) + 2440587.5 + + +def CJDN(date: datetime | None = None): + return round(julian_date(date)) + + +def current_julian_date(): + """Returns current julian date""" + return julian_date() + + +def tomorrow_julian_date(): + """returns tomorrow's julian date""" + date = datetime.now() + timedelta(days=1) + return julian_date(date) + + +def future_julian_date(hoursadd=0, minutesadd=0, secondsadd=0): + """defaults to current if no specifications made. HOURS, MINUTES, AND SECONDS CANNOT BE NEGATIVE""" + date = datetime.now() + timedelta( + hours=hoursadd, minutes=minutesadd, seconds=secondsadd + ) + return julian_date(date) + + +def epoch_days(date: datetime | None = None): + """returns days since Jan 1st, 2000. Negative if before this date""" + return julian_date(date) - 2451545 + + +def day_percent(date: datetime | None = None): + """Returns decimal portion of Julian Date""" + whole = julian_date(date) + return whole - math.floor(whole) + + +def from_julian(j): + """Returns datetime.datetime object given julian date J""" + J1970 = 2440588 + dayMs = 24 * 60 * 60 * 1000 + return datetime.fromtimestamp((j + 0.5 - J1970) * dayMs / 1000.0) diff --git a/tools/mqtt.py b/tools/mqtt.py new file mode 100644 index 0000000..82a5409 --- /dev/null +++ b/tools/mqtt.py @@ -0,0 +1,47 @@ +# tools/mqtt.py + +import json +import socket +import logging +from typing import Any + +from paho.mqtt.publish import single as mqtt_publish + +from constants import MQTT_USERNAME, MQTT_PASSWORD, MQTT_FALLBACK_IP + +logger = logging.getLogger(__name__) + + +class MQTT: + def __init__(self): + self._username = MQTT_USERNAME + self._password = MQTT_PASSWORD + self.IP = self._resolve_ip() + + def _resolve_ip(self) -> str: + """Attempts to resolve the Home Assistant IP, falls back to static on failure/timeout.""" + socket.setdefaulttimeout(2.0) + try: + return socket.gethostbyname("homeassistant.local") + except (socket.gaierror, socket.timeout, OSError) as e: + logger.warning( + f"Could not resolve homeassistant.local ({e}). Falling back to static IP." + ) + return MQTT_FALLBACK_IP + finally: + socket.setdefaulttimeout(None) + + def send(self, data: dict[str, Any], topic: str = "weather_station") -> None: + logger.debug("Sending MQTT payload to server...") + try: + mqtt_publish( + topic, + json.dumps(data), + retain=True, + hostname=self.IP, + keepalive=120, + client_id="weather-station", + auth={"password": self._password, "username": self._username}, + ) + except Exception as e: + logger.error(f"MQTT data couldn't be sent: {e}") diff --git a/tools/weather_logger.py b/tools/weather_logger.py new file mode 100644 index 0000000..4e2205e --- /dev/null +++ b/tools/weather_logger.py @@ -0,0 +1,44 @@ +# tools/weather_logger.py + +import logging +import logging.handlers +from pathlib import Path + + +def set_up_logger(): + logger = logging.getLogger() + + if logger.hasHandlers(): + return + + logger.setLevel(logging.DEBUG) + logging.getLogger("PIL").setLevel(logging.WARNING) + formatter = logging.Formatter( + "{asctime} - {levelname:>7}: {message}", "%y-%m-%d %H:%M:%S", style="{" + ) + log_dir = Path(__file__).parent.parent.joinpath("logs") + log_dir.mkdir(parents=True, exist_ok=True) + + debugFileHandler = logging.handlers.RotatingFileHandler( + filename=log_dir.joinpath("debug.log"), + maxBytes=1_048_576, + backupCount=5, + ) + debugFileHandler.setFormatter(formatter) + debugFileHandler.setLevel(logging.DEBUG) + + infoFileHandler = logging.handlers.RotatingFileHandler( + filename=log_dir.joinpath("info.log"), + maxBytes=1_048_576, + backupCount=3, + ) + infoFileHandler.setFormatter(formatter) + infoFileHandler.setLevel(logging.INFO) + + console = logging.StreamHandler() + console.setFormatter(formatter) + console.setLevel(logging.INFO) + + logger.addHandler(debugFileHandler) + logger.addHandler(infoFileHandler) + logger.addHandler(console)