From 4153cd7361b08bb9cbebf7236cc7df2d84702c94 Mon Sep 17 00:00:00 2001 From: adamzend <14905147+adamzend@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:03:34 +0200 Subject: [PATCH] Release v1.0.0: configurable Modbus weather station publisher for Lemmy --- .gitignore | 17 +++ KONFIGURACJA.txt | 85 +++++++++++ README.md | 218 ++++++++++++++++++++++++++++ README.txt | 197 +++++++++++++++++++++++++ VERSION | 1 + charts.py | 165 +++++++++++++++++++++ config.example.json | 51 +++++++ docs/images/live-weather-post.jpg | Bin 0 -> 213889 bytes install.sh | 10 ++ install_cron.py | 51 +++++++ meteo.py | 183 ++++++++++++++++++++++++ publish_lemmy.py | 229 ++++++++++++++++++++++++++++++ requirements.lock.txt | 11 ++ run_cycle.py | 40 ++++++ settings.py | 61 ++++++++ static.example.txt | 5 + test_charts.py | 68 +++++++++ test_generic.py | 89 ++++++++++++ test_meteo.py | 90 ++++++++++++ test_publish.py | 51 +++++++ test_support.py | 10 ++ 21 files changed, 1632 insertions(+) create mode 100644 .gitignore create mode 100644 KONFIGURACJA.txt create mode 100644 README.md create mode 100644 README.txt create mode 100644 VERSION create mode 100644 charts.py create mode 100644 config.example.json create mode 100644 docs/images/live-weather-post.jpg create mode 100755 install.sh create mode 100644 install_cron.py create mode 100644 meteo.py create mode 100644 publish_lemmy.py create mode 100644 requirements.lock.txt create mode 100644 run_cycle.py create mode 100644 settings.py create mode 100644 static.example.txt create mode 100644 test_charts.py create mode 100644 test_generic.py create mode 100644 test_meteo.py create mode 100644 test_publish.py create mode 100644 test_support.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67dc438 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +config.json +static.txt +.venv/ +.cache/ +__pycache__/ +*.pyc +data/ +logs/ +wykresy/ +tmp/ +*.sqlite3* +*.log +*.bak +*.tar.gz +crontab*.txt +secrets.json +*token*.json diff --git a/KONFIGURACJA.txt b/KONFIGURACJA.txt new file mode 100644 index 0000000..6629935 --- /dev/null +++ b/KONFIGURACJA.txt @@ -0,0 +1,85 @@ +OPIS CONFIG.JSON + +Utwórz config.json przez install.sh lub skopiuj config.example.json. +Plik jest JSON: klucze i tekst w podwójnych cudzysłowach, kropka dziesiętna, +true/false małymi literami, null dla nieustawionego ID. Bez komentarzy +wewnątrz JSON i bez przecinka po ostatnim polu. + +timezone + Strefa IANA, domyślnie UTC. Wpisz strefę swojej stacji. Steruje opisami dat + i wyborem miesiąca, nie zmienia strefy systemowego CRON-a. +stale_after_minutes + Domyślnie 90. Maksymalny wiek odczytu, by przy publikacji uznać go za świeży. + Także granica odstępu między punktami, po której wykres przerywa linię. + Dobierz do harmonogramu: dla pomiaru co godzinę 90 daje zapas 30 minut. + +modbus.host + Adres IP lub nazwa urządzenia dostępnego z serwera. W szablonie pusty: + musisz go uzupełnić. Sam host, bez przedrostka protokołu i bez portu. +modbus.port + Port TCP urządzenia, domyślnie standardowe 502; dopuszczalne 1..65535. +modbus.unit_id + ID urządzenia za bramką, 0..255. Wpisz wartość z dokumentacji urządzenia. +modbus.function_code + 3 = Holding Registers (FC03), 4 = Input Registers (FC04). + Jeden typ odczytu obowiązuje wszystkie trzy czujniki. +modbus.timeout_seconds + Limit oczekiwania na połączenie/operację odczytu; domyślnie 5. +modbus.attempts + Maksymalna liczba prób całego zestawu pomiarów, 1..10, domyślnie 3. +modbus.retry_delay_seconds + Przerwa między nieudanymi próbami, domyślnie 2 sekundy. + +sensors.temperature / sensors.humidity / sensors.pressure + Każda z trzech sekcji ma WŁASNE pola: + address: adres rejestru przesyłany w protokole, 0..65535. null trzeba + zastąpić liczbą. Program nie odejmuje automatycznie 1 ani 40001. + Oznaczenia producenta typu 40001/LW nie są uniwersalne; sprawdź, + czy instrukcja urządzenia stosuje adresy zerowe czy numerację od 1. + signed: true dla int16 ze znakiem; false dla uint16. Dla temperatury + ujemnej najczęściej potrzebne true. Zależy od urządzenia. + multiplier: osobny mnożnik. Przykład: 0.1 daje dzielenie przez 10. + offset: korekta dodawana po przemnożeniu, zwykle 0. + min / max: dopuszczalny zakres już PRZELICZONEGO wyniku. Poza zakresem + oznacza błąd całej próby, a nie automatyczne ograniczenie wartości. + +Wzór: wynik = wartość_po_dekodowaniu_znaku * multiplier + offset. +Przykłady: + 234 * 0.1 + 0 = 23.4 -> we wpisie „23,4 °C”. + 65481 przy signed=true -> -55 -> * 0.1 = -5.5 °C. + 456 * 0.1 = 45.6 %. + 10132 * 0.1 = 1013.2 hPa. +Temperatura jest prezentowana w °C, wilgotność w %, ciśnienie w hPa. +Jeśli urządzenie daje inne jednostki, dobierz multiplier i offset, aby +wynik był właśnie w tych jednostkach. Nie zmieniaj tylko etykiety. +Nie ma obsługi dodatkowych czujników ani dowolnej listy parametrów. + +lemmy.enabled + false: bez publikowania; true: włącz API po uzupełnieniu wymaganych pól. +lemmy.instance + Pełny bazowy adres HTTPS własnej instancji, bez /post/... lub /api/v3, + bez loginu/hasła w URL. Użyj adresu docelowego bez przekierowań HTTP. +lemmy.username / lemmy.password + Login (lub e-mail) i hasło konta autora Lemmy. To nie są dane do systemu, + Modbus, bazy PostgreSQL ani SMTP. Hasło jest lokalnie w config.json; + dostęp ogranicza chmod 600. To plik tajny, nie zaszyfrowany sejf. +lemmy.community_id + Dodatni liczbowy ID społeczności na tej instancji, nie jej nazwa. +lemmy.post_id + null: wyszukaj/utwórz wpis. Dodatnie ID: aktualizuj wskazany własny wpis. + Stan z data/lemmy-state.json ma utrzymać ciągłość; sprzeczne ID powoduje błąd. +lemmy.title + Tytuł nowego wpisu i kryterium rozpoznawania po niepewnym utworzeniu. +lemmy.heading + Nagłówek wyświetlany nad temperaturą w treści wpisu. +lemmy.pin_in_community + true: próbuj przypiąć wpis w społeczności. Konto musi mieć uprawnienia. + false nie odpina wpisu już przypiętego; wyłącza wymuszanie przypięcia. +lemmy.update_note + Tekst pod wartościami, np. opis częstotliwości. Sam nie zmienia CRON-a. + +PAMIĘTAJ +Po edycji: .venv/bin/python settings.py. Nie wyświetla haseł. +Zmiany wczytywane są podczas kolejnego uruchomienia; usługa stale działająca +nie jest potrzebna, a serwera nie trzeba restartować. +W razie niepewności sprawdź jeden odczyt z lemmy.enabled=false. diff --git a/README.md b/README.md new file mode 100644 index 0000000..492a3f8 --- /dev/null +++ b/README.md @@ -0,0 +1,218 @@ +# Uploading server data (meteo) to Lemmy instance + +A Python application that reads a physical weather station over **Modbus TCP** and publishes its measurements to a **single, regularly updated Lemmy post**. + +It collects temperature, relative humidity and atmospheric pressure, stores the history in SQLite, generates three monthly charts, and uploads the results through Lemmy's API. A standard Linux CRON job runs the complete cycle every hour. + +## Features + +- Configurable Modbus host, TCP port, device ID and register addresses. +- Holding registers (FC03) or input registers (FC04), with signed or unsigned 16-bit values. +- A separate **multiplier and offset for each measurement**. +- Validation, retries and a short station status: working correctly or station error. +- SQLite history, including failed readings without replacing them with zeroes. +- Three monthly PNG charts: temperature, humidity and pressure. +- Updates to one existing post, preserving the static text outside the managed section. +- Optional community pinning when the publishing account has moderator permissions. +- User-level CRON scheduling, isolated Python dependencies and file locks against overlapping cycles. + +## How it works + +```text +Modbus TCP device + | + v +Read registers -> apply scaling -> validate -> SQLite + latest status + | + v + Three monthly charts + | + v + Upload images and update one Lemmy post +``` + +Each value is calculated on the collection server: + +```text +measurement = decoded_register * multiplier + offset +``` + +For example, a register value of `234` with a multiplier of `0.1` becomes `23.4 °C`. Lemmy receives formatted Markdown text and uploaded PNG images, not raw Modbus registers. The current presentation uses Polish labels and decimal commas. + +The charts cover the current calendar month in the configured time zone. Missing readings break the plotted lines. Old measurements remain in the database when a new month begins. + +## Requirements + +- Linux with Python 3.11 or later; validated on Debian 12 with Python 3.11.2. +- Network access to the Modbus device and HTTPS access to the Lemmy instance. +- An active Lemmy account allowed to post in the selected community and upload images. +- **Lemmy API v3**, based on the working Lemmy 0.19.11 deployment. API v4 is not implemented. +- Moderator or administrator permissions only if community pinning is enabled. + +The application supports one 16-bit register per measurement. Multi-register floats and custom word order are not implemented. The three registers are read separately, so they do not need to be adjacent; the readings are not a single atomic hardware sample. The program performs no Modbus writes. + +No Docker container or incoming web server is required on the collection machine. Installation does not require rebooting the server. + +## Installation + +### 1. Download the application + +Download and extract the package from [Releases](../../releases), or clone this repository: + +```bash +git clone https://github.com/adamzend/Uploading-server-data-meteo-to-Lemmy-instance.git meteo +cd meteo +``` + +### 2. Install the system dependencies and Python environment + +```bash +sudo apt-get update +sudo apt-get install python3 python3-venv cron ca-certificates mc +sh install.sh +``` + +Run `install.sh` as the ordinary Linux user who will run the station. It creates `.venv`, installs the pinned dependencies and copies `config.example.json` to `config.json` if needed. It does **not** enable CRON or publish anything. + +### 3. Configure and test the measurements + +```bash +mcedit config.json +chmod 600 config.json +.venv/bin/python settings.py +.venv/bin/python -m unittest discover -v +.venv/bin/python meteo.py +.venv/bin/python charts.py +``` + +Fill in the Modbus host, port, device ID, function code and all three register addresses. Choose the correct signedness and multiplier for each sensor. Leave `lemmy.enabled` set to `false` while testing. + +`settings.py` validates configuration without connecting to external services. The test suite uses simulated data and API responses. `meteo.py` performs a real reading and adds it to the database. Check `data/status.json` and the three images in `wykresy/`. + +### 4. Configure Lemmy + +In `config.json`, enter the instance's canonical HTTPS origin, the publishing account's username and password, and the numeric community ID. Set the post title and heading. + +- Leave `post_id` as `null` to find or create the weather post. +- Set `post_id` to update an existing post owned by this account in this community. +- Enable `pin_in_community` only when the account has the required permissions. +- The program authenticates through the API and caches a JWT locally. It does not create accounts or grant permissions. TOTP login is not implemented. + +For a new post, you can prepare a static description of your sensors: + +```bash +cp static.example.txt static.txt +mcedit static.txt +``` + +The content of `static.txt` is appended **only when a new post is created**. Edit the static section directly in Lemmy afterward. + +For an existing post, place these markers on separate lines with blank lines around the managed content: + +```markdown +[meteo-start]: # + +Measurements and charts will be inserted here. + +[meteo-end]: # + +Your permanent sensor description goes here. +``` + +The program replaces only the section between the markers. Missing, duplicated or reversed markers cause publication to stop instead of replacing the entire post. + +Set `lemmy.enabled` to `true`, then run the first complete cycle: + +```bash +.venv/bin/python settings.py +.venv/bin/python run_cycle.py +``` + +Check the resulting post. Its URL is recorded in `logs/meteo.log`, and its ID is saved in `data/lemmy-state.json`. + +### 5. Enable hourly updates + +```bash +.venv/bin/python install_cron.py +crontab -l +systemctl is-active cron +``` + +If CRON is disabled: + +```bash +sudo systemctl enable --now cron +``` + +The installer schedules a cycle at the beginning of every hour, preserves unrelated jobs and saves a backup of the previous crontab. It manages one `# meteo-collector` job per Linux user. The schedule follows the server's CRON time zone; chart dates use `timezone` from the application configuration. + +To edit the schedule or remove this job: + +```bash +EDITOR=mcedit crontab -e +.venv/bin/python install_cron.py --remove +``` + +When changing the interval, also adjust `stale_after_minutes` and `lemmy.update_note`. Running the installer again restores the hourly schedule. + +## Configuration reference + +The complete template is [config.example.json](config.example.json). + +| Setting | Purpose | +| --- | --- | +| `timezone` | IANA time zone for dates and month boundaries; default `UTC`. | +| `stale_after_minutes` | Maximum acceptable reading age at publication, also the chart gap threshold; default 90. | +| `modbus.host`, `port`, `unit_id` | Device network address, TCP port and Modbus unit ID. | +| `modbus.function_code` | `3` for holding registers or `4` for input registers. | +| `modbus.timeout_seconds`, `attempts`, `retry_delay_seconds` | Timeout and retry controls. | +| `sensors..address` | Protocol register address, from 0 to 65535. All three must be filled in. | +| `sensors..signed` | `true` for int16, `false` for uint16. | +| `sensors..multiplier`, `offset` | Per-sensor conversion applied after decoding the sign. | +| `sensors..min`, `max` | Valid range of the converted measurement. | +| `lemmy.enabled` | Enables publishing; disabled in the template. | +| `lemmy.instance`, `username`, `password` | Instance origin and publishing account credentials. | +| `lemmy.community_id`, `post_id` | Local community ID and optional existing post ID. | +| `lemmy.title`, `heading`, `update_note` | New post title, in-post heading and update description. | +| `lemmy.pin_in_community` | Requests community pinning; does not grant moderation rights. | + +Sensor names are `temperature`, `humidity` and `pressure`. Converted values must be in **°C, %, and hPa**, respectively. Addresses are sent as configured: the application does not automatically subtract 1 or 40001 from a manufacturer's register numbering. + +Detailed Polish documentation is also included: [installation and operation](README.txt) and [configuration](KONFIGURACJA.txt). + +## Operation and data + +| File or directory | Contents | +| --- | --- | +| `run_cycle.py` | Measurement, chart generation and publication in order. | +| `meteo.py` | Modbus reads, conversion, validation and storage. | +| `charts.py` | Monthly charts and their manifest. | +| `publish_lemmy.py` | Authentication, image upload and post updates. | +| `data/` | SQLite history, latest status, JWT, post state and upload history. | +| `wykresy/` | Three PNG charts and their manifest. | +| `logs/` | Application and CRON logs. | +| `.venv/`, `.cache/` | Local Python environment and plotting cache. | + +A failed reading is stored as an error. The post can show the last successful values with their timestamp and an error status. If the entire server, network connection or Lemmy API becomes unavailable, the post cannot update itself: check the last measurement time. This status is not an independent external uptime monitor. + +Old uploaded chart images are not automatically deleted from pict-rs. Application logs rotate, but `logs/cron.log` does not. Plan storage and backups accordingly. + +To migrate, disable the old CRON job and let its current cycle finish, then copy the application, your private configuration and the complete `data/` directory. Recreate the environment with `sh install.sh`, test one cycle and enable CRON on the new server. Do not run two publishers against the same post. For a live SQLite backup, use the SQLite backup API instead of copying only the database file while WAL writes are active. + +## Credentials and verification + +`config.json` contains your password in plain text and must remain private. It and the generated data, logs and tokens are excluded by `.gitignore`. Do not publish them. The public template has no device address, account credentials or deployment-specific register map. + +The first release passed **18 automated tests on Debian 12 / Python 3.11.2**, covering Modbus parsing and retries, scaling, month boundaries, missing measurements, PNG generation and preservation of static post content. Tests use simulated network responses; compatibility with every Lemmy deployment is not implied. + +## A real working weather station + +This project is not just a theoretical idea. It is based on a **physical, operating weather station that sends real measurements to an active Lemmy instance**. The configurable application in this repository was prepared from that working deployment. + +**Live weather post:** [Stacja meteo — Szymbark](https://szymbark.net/post/171) + +The screenshot below was captured from the actual public post on 13 October 2026. It shows the measured values, station status and three monthly charts; it is not a mockup. + +![The live Szymbark weather post showing temperature, humidity, pressure, station status and monthly charts](docs/images/live-weather-post.jpg) + +**Lemmy instance homepage:** [szymbark.net](http://szymbark.net) diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..2ce7dca --- /dev/null +++ b/README.txt @@ -0,0 +1,197 @@ +MODBUS METEO -> LEMMY +Wersja przenośna, przygotowana do publikacji kodu źródłowego + +CO ROBI PROGRAM +Odczytuje temperaturę, wilgotność i ciśnienie z urządzenia Modbus TCP. +Przelicza każdy rejestr według osobnego mnożnika, zapisuje historię w SQLite, +tworzy trzy wykresy bieżącego miesiąca i aktualizuje jeden wpis Lemmy. +Wpis zawiera wartości, krótki status stacji, czas pomiaru i wykresy. +Użytkownik może zachować stały opis pod wykresami, np. parametry czujników. + +Wszystkie adresy urządzeń, adres instancji i dane konta podaje użytkownik. +Szablon config.example.json nie zawiera danych wdrożenia ani sekretów. +Konfiguracja rzeczywista config.json jest ignorowana przez Git. + +WYMAGANIA I ZAKRES +- Debian/Linux z Pythonem 3.11 lub nowszym; sprawdzono na Debianie 12 / 3.11.2. +- Pakiety systemowe: python3, python3-venv, cron, ca-certificates; opcjonalnie mc. +- Dostęp sieciowy do urządzenia Modbus i HTTPS do własnej instancji Lemmy. +- Aktywne konto Lemmy mogące publikować w wybranej społeczności. +- Przypinanie wymaga uprawnień moderatora społeczności lub administratora. +- API Lemmy v3, oparte na działającym wdrożeniu 0.19.11. API v4 nie jest + zaimplementowane; przed inną wersją serwera sprawdź zgodność. +- Instancja musi pozwalać temu kontu wysyłać obrazy przez /pictrs/image. +- Obsługiwane są pojedyncze rejestry 16-bitowe int16/uint16, FC03/FC04. + Float32, pary rejestrów, kolejność słów i inne jednostki wymagają rozbudowy. +- Program wykonuje wyłącznie odczyt Modbus, nie zapisuje rejestrów. +- Windows służy do przechowywania/edycji plików; uruchomienie wymaga Linuxa + (blokady fcntl, CRON). Nie potrzeba Dockera ani serwera WWW dla klienta. + +INSTALACJA KROK PO KROKU + +1. Na docelowym Debianie przygotuj pakiety: + sudo apt-get update + sudo apt-get install python3 python3-venv cron ca-certificates mc + Nie jest potrzebny restart serwera. + +2. Skopiuj zawartość tego katalogu do wybranego folderu, np. ~/meteo. + Dalsze polecenia wykonuj jako zwykły użytkownik, właściciel tego folderu: + cd ~/meteo + sh install.sh + Instalator tworzy .venv, instaluje zależności, tworzy katalogi robocze + i config.json, jeśli jeszcze nie istnieje. Nie włącza CRON-a ani publikacji. + +3. Uzupełnij konfigurację: + mcedit config.json + Wpisz host/port/unit_id, adres każdego rejestru i mnożnik; sprawdź signed. + Ustaw własną strefę czasową IANA. Szczegóły w KONFIGURACJA.txt. + Na początku pozostaw lemmy.enabled=false. + chmod 600 config.json + .venv/bin/python settings.py + To wyłącznie walidacja składni i pól; bez połączeń sieciowych. + +4. Uruchom testy i rzeczywisty odczyt: + .venv/bin/python -m unittest discover -v + .venv/bin/python meteo.py + .venv/bin/python charts.py + Testy wykorzystują fikcyjne dane i mock API, nie kontaktują się z czujnikami + ani instancją. Odczyt meteo.py kontaktuje się z urządzeniem i dopisuje próbę + do historii. Obejrzyj data/status.json oraz trzy PNG w wykresy/. + +5. Przygotuj konto i społeczność w Lemmy. + Zwykłe hasło tego konta jest używane do uzyskania JWT przez API. + Hasło aplikacji Google/SMTP nie jest hasłem konta Lemmy. + Program nie tworzy użytkowników, nie zmienia bazy Lemmy ani uprawnień. + Jeżeli konto wymaga 2FA/TOTP, ten klient nie obsługuje formularza TOTP. + W config.json uzupełnij sekcję lemmy: instance (pełny adres HTTPS), + username, password, community_id, title, heading i opcjonalne przypięcie. + Community_id musi być lokalnym ID na docelowej instancji. Możesz uzyskać + je z JSON jej API GET /api/v3/community?name=NAZWA_SPOLECZNOSCI: + pole community_view.community.id. Nie myl nazwy widocznej z identyfikatorem. + +6. Wybierz nowy lub istniejący wpis. + Dla nowego pozostaw post_id=null. Opcjonalnie: + cp static.example.txt static.txt + mcedit static.txt + Wpisz stały opis czujników. Będzie dołączony tylko podczas tworzenia wpisu. + Późniejsze poprawki stałej części wykonuj już w edytorze Lemmy. + Dla istniejącego własnego wpisu ustaw post_id i umieść w jego treści: + + [meteo-start]: # + + Tutaj program wstawi wyniki i wykresy. + + [meteo-end]: # + + Tutaj możesz dopisać stały opis czujników. + + Oba znaczniki muszą wystąpić dokładnie raz, w tej kolejności i jako + osobne wiersze. Puste wiersze są istotne dla renderowania Markdown. + Program zachowuje treść przed i po sekcji. Przy braku znaczników odmawia + edycji. Nie stosuj widocznych komentarzy HTML jako nowych znaczników. + +7. Włącz lemmy.enabled=true. Wykonaj świadomie pierwszą publikację: + .venv/bin/python settings.py + .venv/bin/python run_cycle.py + Polecenie odczyta pomiar, utworzy wykresy i utworzy/zaktualizuje wpis. + Adres aktualizowanego wpisu znajdziesz w logs/meteo.log. + Stan i ID wpisu zostaną zapisane w data/lemmy-state.json. + Zweryfikuj wygląd wpisu oraz uprawnienia do przypięcia, jeśli je włączono. + +8. Dopiero po udanym teście zainstaluj harmonogram: + .venv/bin/python install_cron.py + crontab -l + systemctl is-active cron + Jeśli usługa jest wyłączona: sudo systemctl enable --now cron + Skrypt dodaje zadanie co pełną godzinę dla bieżącego użytkownika, + z bezwzględną ścieżką do tej instalacji. Zachowuje inne zadania i zapisuje + kopię poprzedniego crontab w data/. Zastępuje linię # meteo-collector: + zakłada jedną stację dla tego użytkownika systemowego. + +JAK DZIAŁA CYKL +run_cycle.py -> meteo.py -> SQLite/status -> charts.py -> publish_lemmy.py. +Odczyty trzech rejestrów są osobnymi zapytaniami, dzięki czemu mogą być +oddalone i nie trzeba czytać niedostępnych rejestrów między nimi. Nie jest +to atomowa próbka sprzętowa; wartości powstają w krótkich odstępach czasu. +Przy niepowodzeniu dowolnego rejestru cała próba jest błędna i powtarzana. +Błędy zapisywane są jako NULL, nie jako zerowe pomiary. Program pokazuje +ostatnie dobre wartości i ich datę, jeśli aktualny odczyt zawiedzie. + +Lemmy otrzymuje tekst Markdown, a wykresy pliki PNG (16:9, 1680x945). +Status zielony oznacza poprawny, wystarczająco świeży odczyt wszystkich +parametrów w chwili publikacji. Nie stanowi pełnej diagnostyki sprzętu. +Gdy zatrzyma się cały program, internet lub Lemmy, post nie zmieni statusu +samodzielnie: zostanie stara data. Niezależny alarm wymaga zewnętrznego nadzoru. + +Stan publikacji zapobiega tworzeniu nowego posta co godzinę. Po niepewnym +wyniku tworzenia program szuka własnego wpisu o tym samym tytule, społeczności +i znacznikach w ostatnich 50 wpisach autora; jeśli wynik jest niejednoznaczny, +odmawia tworzenia kolejnego. Administrator musi wskazać właściwy post_id. +Zmiana konta/instancji/społeczności przy starym stanie jest blokowana. + +Wykresy wybierają miesiąc według timezone. Baza przechowuje UTC. Przy zmianie +miesiąca wykresy zaczynają się od nowa, ale historyczne pomiary zostają w bazie. +Braki i zbyt długie odstępy pomiarów przerywają linie. Obrazy wysyłane są na +instancję Lemmy. Starych obrazów program automatycznie nie usuwa; trzeba +uwzględnić miejsce w pict-rs. Historia uploadów zawiera tokeny do usuwania. + +OBSŁUGA I ZMIANY +- Pełny cykl ręcznie: .venv/bin/python run_cycle.py +- Sam pomiar: .venv/bin/python meteo.py +- Bieżące wykresy: .venv/bin/python charts.py +- Archiwalny miesiąc: .venv/bin/python charts.py --month RRRR-MM + To nadpisuje lokalne PNG, więc przed publikacją przywróć bieżące wykresy. +- Sama publikacja: .venv/bin/python publish_lemmy.py + Wymaga istniejących, spójnych plików pomiarów i wykresów. Zwykle używaj cyklu. +- Log: tail -n 60 logs/meteo.log +- CRON: EDITOR=mcedit crontab -e +- Wyłączenie zadania: .venv/bin/python install_cron.py --remove + Nie usuwa plików ani wpisu. Nie uruchamiaj z sudo. +- Przerwa tylko w publikowaniu: lemmy.enabled=false; pomiary i wykresy zostają. +- Inna częstotliwość: edytuj crontab, stale_after_minutes i lemmy.update_note. + Ponowne install_cron.py przywraca harmonogram godzinowy. +- Tytuł jest używany przy tworzeniu/szukaniu wpisu; zmiana title w config + nie przemianuje istniejącego posta. Zrób to w Lemmy i uaktualnij config. + +PLIKI +settings.py wczytanie i kontrola konfiguracji +config.example.json pusty szablon ustawień, przeznaczony do publikacji +config.json dane użytkownika, powstaje przy instalacji, NIE publikuj +meteo.py Modbus, skalowanie, SQLite, status +charts.py trzy wykresy i manifest zgodności danych +publish_lemmy.py JWT, przesyłanie obrazów, edycja jednego wpisu +run_cycle.py kolejność i blokada cyklu +install.sh venv, zależności, katalogi; bez CRON-a +install_cron.py harmonogram i kopia poprzedniego crontab +static.example.txt wzór stałej sekcji posta +static.txt własny opis, używany przy pierwszym tworzeniu +requirements.lock.txt zamrożone wersje bibliotek wykresów +test_*.py testy bez korzystania z serwerów produkcyjnych +data/ SQLite, status, JWT, ID wpisu, historia uploadów +wykresy/ PNG i manifest.json +logs/ log aplikacji i wyjście CRON-a +.cache/, .venv/, tmp/ lokalne cache, środowisko i pliki pomocnicze + +PRZENIESIENIE NA KOLEJNY SERWER +1. Wyłącz stare zadanie CRON i poczekaj na zakończenie aktualnego cyklu. +2. Skopiuj kod, config.json, static.txt (jeśli istnieje), cały data/ oraz + opcjonalnie wykresy/ i logs/. Zatrzymanej aplikacji nie uruchamiaj podczas + kopiowania. Kopiując bazę na żywo użyj sqlite3.Connection.backup(), nie + kopiuj samego pliku sqlite3 z pominięciem aktywnego WAL. +3. Na nowym serwerze wykonaj sh install.sh. Zachowuje istniejący config.json. +4. Sprawdź właściciela plików i chmod 600 config.json; folder data powinien + być prywatny. Uruchom test, jeden pełny cykl i dopiero potem nowy CRON. +5. Nie uruchamiaj dwóch serwerów publikujących do jednego wpisu równocześnie. + Dla innej instancji/konta odłóż stare pliki data/lemmy-* do prywatnego backupu, + uzupełnij nowe ustawienia i post_id. Nie mieszaj stanów dwóch instalacji. + +PRZED PUBLIKACJĄ W GITHUBIE +Publikuj zawartość tylko tego katalogu. Nie dołączaj konfiguracji rzeczywistej, +SQLite, tokenów, logów, wykresów, archiwów ani plików prywatnych z katalogu +nadrzędnego. .gitignore ogranicza przypadkowe dodanie, ale nie usuwa sekretów +z istniejącej historii Git. Przed git add/commit sprawdź git status i diff. +Nie dodano licencji bez decyzji właściciela kodu. Przed udostępnieniem jako +projekt open source wybierz licencję i dodaj jej treść, jeśli tego chcesz. + +Wersja nie instaluje SMTP ani hooków YunoHost — to konfiguracja instancji, +a klient korzysta wyłącznie z Modbus, SQLite i uwierzytelnionego API Lemmy. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..cbaf3b3 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/charts.py b/charts.py new file mode 100644 index 0000000..35a5ecf --- /dev/null +++ b/charts.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +"""Generate three monthly PNG charts from the recorded observations.""" +import argparse +import calendar +from datetime import datetime, timedelta, timezone +import json +import math +import os +from pathlib import Path +import sqlite3 +from zoneinfo import ZoneInfo + +from settings import load as load_config + +BASE = Path(__file__).resolve().parent +os.environ['MPLCONFIGDIR'] = str(BASE / '.cache/matplotlib') +os.environ['XDG_CACHE_HOME'] = str(BASE / '.cache') +MONTHS = ('styczeń', 'luty', 'marzec', 'kwiecień', 'maj', 'czerwiec', + 'lipiec', 'sierpień', 'wrzesień', 'październik', 'listopad', 'grudzień') +SPECS = ( + ('temperatura', 'Temperatura', '°C', '#C15E29', 2), + ('wilgotnosc', 'Wilgotność powietrza', '%', '#2176B5', 3), + ('cisnienie', 'Ciśnienie atmosferyczne', 'hPa', '#18847B', 4), +) + + +def month_bounds(year, month, tz): + start = datetime(year, month, 1, tzinfo=tz) + end = datetime(year + (month == 12), 1 if month == 12 else month + 1, 1, tzinfo=tz) + return start, end + + +def load_rows(db_path, start, end, now): + with sqlite3.connect(db_path.resolve().as_uri() + '?mode=ro', uri=True) as db: + return db.execute('''SELECT checked_at,status,temperature_c,humidity_percent,pressure_hpa + FROM readings WHERE checked_at >= ? AND checked_at < ? AND checked_at <= ? + ORDER BY checked_at,id''', tuple( + value.astimezone(timezone.utc).isoformat(timespec='seconds') + for value in (start, end, now))).fetchall() + + +def series(rows, column, tz, max_gap_minutes=90): + x, y = [], [] + previous = None + for row in rows: + stamp = datetime.fromisoformat(row[0]) + if previous is not None and (stamp - previous).total_seconds() > max_gap_minutes * 60: + x.append((previous + timedelta(seconds=1)).astimezone(tz)) + y.append(float('nan')) + x.append(stamp.astimezone(tz)) + value = row[column] + y.append(float(value) if row[1] == 'ok' and value is not None else float('nan')) + previous = stamp + return x, y + + +def number(value): + return f'{value:.1f}'.replace('.', ',') + + +def generate(month=None, *, db_path=None, output=None, now=None): + import matplotlib + matplotlib.use('Agg') + import matplotlib.pyplot as plt + import matplotlib.dates as mdates + from matplotlib.ticker import FuncFormatter, MaxNLocator + + os.umask(0o077) + config = load_config() + tz = ZoneInfo(config['timezone']) + now = now or datetime.now(tz) + local_now = now.astimezone(tz) + year, month_number = map(int, month.split('-')) if month else (local_now.year, local_now.month) + start, end = month_bounds(year, month_number, tz) + rows = load_rows(db_path or BASE / 'data/meteo.sqlite3', start, end, now) + output = output or BASE / 'wykresy' + output.mkdir(parents=True, exist_ok=True) + month_label = f'{MONTHS[month_number - 1]} {year}' + plt.rcParams.update({'font.family': 'DejaVu Sans', 'font.size': 12, + 'axes.unicode_minus': True, 'axes.formatter.useoffset': False}) + manifest = {'month': f'{year:04d}-{month_number:02d}', + 'generated_at': local_now.isoformat(timespec='seconds'), + 'timezone': config['timezone'], 'charts': {}} + for slug, title, unit, color, column in SPECS: + x, y = series(rows, column, tz, config["stale_after_minutes"]) + valid = [(stamp, value) for stamp, value in zip(x, y) if math.isfinite(value)] + # Use a 16:9 canvas compatible with common image preview crops. + fig, ax = plt.subplots(figsize=(14, 7.875), dpi=120) + fig.patch.set_facecolor('#F5F7FA') + ax.set_facecolor('white') + fig.subplots_adjust(left=.09, right=.96, bottom=.22, top=.69) + fig.text(.09, .92, 'STACJA METEO / ' + month_label.upper(), + color='#64748B', fontsize=11, weight='bold') + fig.text(.09, .845, title, fontsize=25, weight='bold', color='#172B42') + if valid: + last_time, last_value = valid[-1] + vals = [v for _, v in valid] + fig.text(.96, .845, f'{number(last_value)} {unit}', ha='right', + fontsize=29, weight='bold', color=color) + fig.text(.96, .79, 'Ostatni pomiar: ' + last_time.strftime('%d.%m, %H:%M'), + ha='right', fontsize=10, color='#64748B') + fig.text(.09, .775, + f'Minimum: {number(min(vals))} {unit} • Maksimum: {number(max(vals))} {unit}', + fontsize=12, color='#475569') + ax.plot(x, y, color=color, linewidth=1.7, marker='o', + markersize=4 if len(valid) < 15 else 2, zorder=3) + ax.scatter([last_time], [last_value], s=55, facecolor=color, + edgecolor='white', linewidth=1.3, zorder=4) + if slug == 'wilgotnosc': + ax.set_ylim(0, 100) + else: + padding = max((max(vals) - min(vals)) * .15, 1 if slug == 'temperatura' else 2) + ax.set_ylim(min(vals) - padding, max(vals) + padding) + else: + fig.text(.96, .845, 'Brak pomiarów', ha='right', fontsize=19, color='#64748B') + ax.text(.5, .5, 'Brak poprawnych pomiarów w tym miesiącu', transform=ax.transAxes, + ha='center', va='center', color='#64748B', fontsize=14) + ax.set_ylim(0, 100 if slug == 'wilgotnosc' else 1) + ax.set_yticks([]) + ax.set_xlim(start, end - timedelta(seconds=1)) + if start <= local_now < end: + ax.axvspan(local_now, end, color='#F1F5F9', zorder=0) + days = [1, 5, 10, 15, 20, 25, calendar.monthrange(year, month_number)[1]] + ax.set_xticks([datetime(year, month_number, day, tzinfo=tz) for day in days]) + ax.xaxis.set_major_formatter(mdates.DateFormatter('%d', tz=tz)) + ax.set_xlabel('Dzień miesiąca', labelpad=10, color='#475569') + ax.set_ylabel(unit, labelpad=12, color='#475569') + if valid: + ax.yaxis.set_major_locator(MaxNLocator(nbins=5)) + ax.yaxis.set_major_formatter(FuncFormatter(lambda value, _: f'{value:g}'.replace('.', ','))) + ax.grid(axis='y', color='#E2E8F0', linewidth=.8) + ax.grid(axis='x', color='#EDF1F5', linewidth=.6) + ax.set_axisbelow(True) + ax.tick_params(axis='both', length=0, pad=9, colors='#475569') + for spine in ax.spines.values(): + spine.set_visible(False) + footer = f'Liczba poprawnych pomiarów: {len(valid)}' + if valid: + footer += ' • Dane od ' + valid[0][0].strftime('%d.%m, %H:%M') + fig.text(.09, .095, footer, fontsize=10, color='#64748B') + fig.text(.09, .052, 'Przerwy oznaczają brak danych. Czas lokalny: ' + config['timezone'], + fontsize=9, color='#64748B') + fig.text(.96, .052, 'Aktualizacja: ' + local_now.strftime('%d.%m.%Y, %H:%M'), + ha='right', fontsize=9, color='#64748B') + target = output / f'{slug}.png' + temporary = target.with_suffix('.tmp') + try: + fig.savefig(temporary, format='png', dpi=120, facecolor=fig.get_facecolor(), + metadata={'Title': f'{title} — {month_label}'}) + temporary.replace(target) + finally: + plt.close(fig) + manifest['charts'][slug] = {'file': target.name, 'count': len(valid), + 'last_success_at': valid[-1][0].isoformat() if valid else None} + temp_manifest = output / 'manifest.tmp' + temp_manifest.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') + temp_manifest.replace(output / 'manifest.json') + return manifest + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--month', help='Optional month YYYY-MM; defaults to current local month') + args = parser.parse_args() + print(json.dumps(generate(args.month), ensure_ascii=False, indent=2)) diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..8f64700 --- /dev/null +++ b/config.example.json @@ -0,0 +1,51 @@ +{ + "timezone": "UTC", + "stale_after_minutes": 90, + "modbus": { + "host": "", + "port": 502, + "unit_id": 1, + "function_code": 3, + "timeout_seconds": 5, + "attempts": 3, + "retry_delay_seconds": 2 + }, + "sensors": { + "temperature": { + "address": null, + "signed": true, + "multiplier": 0.1, + "offset": 0, + "min": -100, + "max": 100 + }, + "humidity": { + "address": null, + "signed": false, + "multiplier": 0.1, + "offset": 0, + "min": 0, + "max": 100 + }, + "pressure": { + "address": null, + "signed": false, + "multiplier": 0.1, + "offset": 0, + "min": 100, + "max": 1200 + } + }, + "lemmy": { + "enabled": false, + "instance": "", + "username": "", + "password": "", + "community_id": null, + "post_id": null, + "title": "Stacja meteo", + "heading": "Stacja meteo", + "pin_in_community": false, + "update_note": "Aktualizacja co godzinę. Wykresy obejmują bieżący miesiąc." + } +} diff --git a/docs/images/live-weather-post.jpg b/docs/images/live-weather-post.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a994f6cb9c1116c6c162f22b98596148a47d49c GIT binary patch literal 213889 zcmeFZ1yGzp*Dg2&4-zE!44&WwXJC*J+=F|7;O-6yk__$^Y=XN4861K;3~qzFyM+X@ ze0TqUzwiFLUv1smd$($G?bVcMU+GAR{jWKtTZjP@X=3 zzes>200Rvj104+m104es69elx!Sm;`1x~UW=c{@=KtaHw*x?kh4mHf92JEg z@Qe@zl@R4`4*>L!X`-Y2W4`}c&{3YDqG4cRVm~#T5&%$8QJ+0SLw`<)^9%z8fP(^f zYDFi+Afn?Yekq}bNw01iPs+omq2-d0I5~wyLe8)4>gFDloKsy>TQ{*z#=tB1PDaz? zOMdsqBu0VY+|I67l4s{3ufb;KOj3DIDyW{6|L^5LCjj`z1fJIFsZosZNgoOdItB&` z8p<;?4AiG(C45GQO2jR3re^Ar(>Z}g928%@PLKYQ=lmT;7Xz>QBnheHN7tI%+Ks=9 z09@3k{)DK60CB(_5j`zE{j>k9(y}L;q3cRR={R_crCE>ly3AXCMyCW^b(OPaU(+om zy|T-)laE(swchPz8uW+ZqlTjHv1K~cyQLgikZr&@_%wE<4^rwH_siz3(Gsj*^`^AH5htwXVD`=deW>=WqFMk0M#IUzA-8_)BL@3lG^V8w?n8TirQ*ta-lO z%dH$Eydw5O3^(LGckwCE&8Kx5Y*$h0?b-&bQcfkj0-9#jf$iEtv5DWk%0UY@{pc6J zj~91+d@5Vog@==+OusY}OrY{orT+=0#2#BB48%Sb5$i2I;+v((*%(;#UG&|zJ;f_6 zPEXHPrI9m@Z_!`hf=Al`oh3GO>OHB%zh*)C{}hQC-L_SkBL#&FBBnmP@K7me@c1oS zGCAS2*K?r^WOr*xH+S>;gj)=`hBbG371tYO@3EB(5XrVxEy|&}*wCIa#2ZW18}gkr zP6}`c`8A7md)3v;Ss$2N-peoES`nE=f)-j*cSvnS`H-KgQ)P|a12|SLr?#fW+O5qZ zII>S4?F02z=Duyj#;%6YX8TwfKdCanh;qd4 zvN*opL2cr>8?g;1X;E2jrIIW;63{R&DQb@N9~Y=pfGW(%wnE-)U~PAYig!Af7g4SA z-=q~R!>Gm7K8I43T|InAp4RfT`w}OwrCjTm?ZOWEHWx*Nvb8>+Gq>8xsAJuCXINan z;y&HLa!^oYp=DE; zJ^g-0O$(uARnpjxdv#4=9_1I_cLaoRAfoWSVJPOX;5j3K-28$Df!KXJ8g_oXv=f*4 zhk~jlMU|to4m2}3tM7>pdA%*OEz3TOt!1n|iNx4fWY7|&v$q@2Z|);fxuCfs-C=fP z&0r#ZCHX<%ViegiHkv0=e?c*{5;5mk*|4ub8IE>DFs-t`>6Q+hC2N|19xu5%^G2-~ zp+>0U#MGr-S*{97BPSQj$$ZNK_=q+7wSU20#jH-HG}Cq+#e8%2G>E|j`Y?O5marYt z6=hE)L38C5&0>qPIy){f`+>5b-d-}fW)pf%?hv5aU1yU~`~^2kd9jUb?F>v-fe|a&h-~iK+uP zF+uj@jL|2UL+&p(58m=0Q~cEkX6|VPS5$0kA(xF8z$mLGS8U&jXUn;DhGjYK(*pNH z-_ku(yK!LSel?g`Hk|FN?36KVS~#qh4J#}Zv4_{?^)r zM^Y}5Iuo|;QaiytX-WB-N4~qj`x+sXNM&&9A_C?RrIMA@(Qx$`%9R- z?E}YluY~PRc+O_LYI(1QWU}CDXjhn19~q$D6_5!VNwfBG!C!Ak1InTo;0m-H9v!U)dk40W+>H{%-y1|~gF0*lT8-j#2*J*B6LQjl60N2EU8@VKI72( z7Tu6vl(xtnO0PSI+mL_WFD7)N4)NVR60?ph@epmElCa<>?Q~9P$`Ywckf(y4a^S|A z1tRdRMC1&-hB+`Y3X7AczbhFY%uV&qQlHJw&xZ7yI)3o-+Uyk}4mDO)=RZ8yn4PIv z)p9F2xZ-xNb#S{m!CS+SA7rTZBs&DvU@WgH%6ue1?w8Wr#E-GNEiLymp0D|7vDf1S zNRi5FswW(zvo;kBF9`~LMZORt2e|I!k4hn8t7?pT`wc~xc<5P6!0rz_-f8Rtkze9P zF!_%Peef~k(eUYH5ijGE?uw$%hr0B4MDvC6Jqdg`Q@Z+eD`frx-lLSoHM6-}4FN&i zpwTS|qP4!Rt@3%w_^?l5uDi!Igo0n`(!KWCTY7>1fso9mKiG8oO~s0nOSYU{g{uzm z{e%70cSFs^Ckbk@jFTdOj-@SVqxspNGTTerso6m`j%#A|8eN+|@)E`T_!-vD1IP&n zkspv3{K6OTT@|ryr_%aipjNfJYJ$MfL?eX5i@=+tv^_{H3ofCiqkhv366HYWYkPT; z)VKeNoYTe9I0%6Xbm>7a917sP-?`G56xL%2Bov*hPp&;djU0G^#}G>#=#$ALe13f+ zml5{;g{l{6Lns=Fh@#=`xNP>eRN-Y zI!ozm_Zr7n3j@~p04t!k=UG>6UwC?WnzLDW*Fw{{CX&rOzcMzrIF>4{5kdiop^YE- z$|2!cl&bvUVCzd-$!<`*IHLo;TLb>)fSc}h1)gYw#+C&@(X ziD3wKhD?RNglp;kPK$bkUyE&vMe#}%QOGM%{JnLgxO`ic+0t86a+85h05~cEa2I{3 zzQX_ci$WRZSAz$cj5>`Wa}P9H)5@hOpu{LS6F!>UpFj-1RMWCnE+oG+G(5)RX4JF5 zJF7C=Uezog6m%+NEg~c7iu$vTXYQ@d@4tW$2%BxC2dTl#Ux50UG4DZTRHRv<_wbL| zpLpmCbV$SX#GVV|n7cP$@7hv`zxS*QS#?uJX8OOj@?>bIUx}*e``qVlqndv$9`fdS zs_-bLQ}8dL*C5{!06xybKjZRktvMxrM_@7z)cZ`e;&gNt>;pHl&QN z4jfhx)-<{`M@+Ge2ojN3Yc}X|*jO##2ccWc*1(Qk$_|}pC#K$cHvQW7dPar7L#1x0 zrE)f{(oey5*UC`TFKK3d&H~clLzwBkQW_h;cXpJGm}(FjM6C^^QMEezNs=;$jWna0 z9}`S+$^7`*^0Ag=cd=(X+Byzyo5chwh2o5Tg|^oxXW(fHQm}mKAd>K-oW$Pj*=54X zE2looF6%cOaK=6_lU zBR(Qoq*B#HvXoewlFw5aqI8=6izP_k>CJ5Tf}yY(dnzcxBea|swY6qb^zX~2>C zCPCeH5}h5M*jp}5F79M@$?fT-u;6t)MOE$PWI{>aZwndyAst}n4WHZO{BjF!Zt%t2 z;oJ-2=v6^0i#ZxaevHzlhd2A5VCpj(?g3&eA%&jD3yWT6EvRA^c^Jietu)Go!Z2UO zL`RTS!FJe<{X%Sa<|FzjeSCoq>qW*Nc$@flaLGl0?f$xYr`z=CG=DQGg%d%FFt+mG zdf*Q{s0@bb7U9l%&_Xh?(^>q>o0id^+6!V?60VH_JT4K{)0O7)og0)#4dfjnKLy7o zgb$ApnA7=Z)h|i|-gp#DO-xCAEJ#QYmOB`PrYh?gq?)EvHB{!K-=BZnlWHRteve?v@B8CZI~?4Jx3i9&%{AT3E74Mx z5{Sd&>b)5wFLJp&D4Po`VG_MrXIt}50UMz~b?;l*Ltm_Kng*Jm?PxFg)}Mo9m0zCm zD77XG$>{@)qI&{Ih+Ia_cULr@)2$>4E*=e-@r_@_wPRkrjF)bT>09%xvx?gs@nqM$ zjkveoTToTsL&`D~gjfTr$NE%U5;O5JgU6+a;FQp9NDe1V0ITp2&z?(`SSeG)i@yS; zKa@9F{`8hb2~A~QipYH;!aEJ?#l?>f6fxJ_2?iCk!(P5)HBX+aisA^tyRC2gxt1H} zp+uAu39>dDIch{fioL}r&b`F+DWt;8_srW4eDAac$vYAq&pR&1G~dxK2h3L7^u3p7 zpi$^2;}U*N#+8@|-*Tj22t#+oQ}ZDc;(XTh;40^fDGzVe3+g8C*vg4Vi*CL)aw_r= z6^Z|b)6)hVudxgbitZ30z=yo6SM2q#p5xI^9ax;>zozPV{cbAdTtr4>Sb{k0TdKOD zWiYuVpKCwvN9KD4a-eSKz%#wLdOn9)h^SUy&ZRp~GgNoYbMk1wrOs7!xs^;TjM4T~ z=XsHO8WUlJ#y2W7hp*L{yBu}^z-~kOuugHMhcZWs_acZnZG)L1GT&PW|I=)AzvwSD z<}Zi6Y&yl9T1GVT5ORt)Drj6awB{EBCKO(o+qQ;fyG>fnh2?#gUX|`HJfi(VgLnYq z{_p&yVL1FGhFAP9nv)89MNubq$PtpS1WLgtjbXfZ^ZJh&S+5QLkonGZVd z{~}DQz9RV&Y5V1B%<;G&#%>WfS9iwYEP^FEtVQ7NuAh}e9Byqty9Zn|;07^7&U*`G zH}zZ7!gN{j+os3L)27)IggzD$+nMLtrC|)1*_q*! z)A9+D_h$q{rHGQqen;c+y_ev#Wd2|trikYHEz>83j4$N^6AC_8Cl%hk7V4QBvUTH^ zm1ifJt}L;O5znVE>pNleq!Hekz*H;dmzuH_jzAh#?u|T4b)SGg@B#p-@Q#oK-e5?I z--oxJJ>ae~LKvC$`>%!(q`-97OV==PwWRH{#6GAI3mIfQBOG{7p%uCSL5f0a^(Lf07 znHPI#WWe)y;)9QZehG2xRe4-jIJ`0n5aIsTOsv5VeuIlTn?~9F8X}G_#4IJ*Ho0=m znESwY6TUptgPV^bw`jmiIX=}CgGM07gyva%bAS_K?$@9}aDz2snLtcEt{8}mtaD*J z0}e$$7?*8hUI z@qcz4`aeG2%jFiOCA;GU?N0JNWN)+xi0RXKFd;$kqy!paM{pQcq$mSwru^m?azc4} ztZ8+*VE&g0w9(RxVGL^W918UqjfCCIT=XmkA7IVPbiD5;+U0`!E_Ui@yc2kWSxA?a z>ub_0ZEZ@nh`R*kW}}fZyooU*o)uDP(kTfrh0@}Zb-*B72vuTGf^!HF`}I}A`0S{n z*h&f$X^YJOqW#Bza3Ae*7U5Cu@omv@7GFnc!Co=fAOC|A8I@HOwUDGO2)`J4#50qT zm-zY;j!;>4a9{q|9M_94>E&eC_}x31=pXDwTk z6%)l>xihgr(7qBeGnJb9B*v z>YP@1mGu8BdRlKi`Nq=S_HG?TkK}80fpoofm`1bQG8OJOd=Y0!IfhgMKQhK<2guRl z_f|G>%Xl}|V0z~N>jx;raq?d7gzvPG8&QBi679r8Ho52VLciAV(1y;AE`Qk6lzadW zGe+o5Qbnm>w2>%q`iIJn#^?hB2G==Wb=E{TE<%R*_s`@DSS-qUUjyto{2s0Xn0neek^0u%0RYTR!5m z%jM%AP6<^tqhmD*K1a>YuD3QM6MQ^|QxHk3Ht~X3FE#X&LQ|Yeuhyg9Ez<;mkV|f4bg4 z-bQNHINRp}?^PxEb804p&G^)=Lk07|eb_*wUoTH#2(80G7QKQJP|13smAtCV!G{Jm z)CQqIbJCE05kK$ZdPVa_L*^124%Na{Eh@!P$?uagDp(9ZF1E_V$}^XX%l1q1zD}P# z_ma)5KHh{K;fu5mJ5Di@8NkUVGq4L6w1Blr3m{r+Oz;NR1Y*3ByWC?02J&QgD9vh4 z-&ID>_1(+yoL@IDh_=c??4G*l!joaJRrKaU*tbdHoJt~%t2Sm+MNktMWmc0BGO3I5h$*_*ieyGsa_DAtPvPX5~ zq~=Q!_f((C_wCwKZy6o?Q{oZeqoZSk$xf4dN%SoP3+sI)j5tk*-7?};f7tp`cKPk+ zf;t`6Pa*k-aaRTnn^s~XjhB~f#vaQ>X7DTU!h5TIFSqq5Gn!O3jk(nYhN^~^yhNq( z*#jJR_m6VSr6tlDdCRGMUe7*biVL@In&#hY>8Ky-HVi6B&aX7S+DF1@*D`@#Ud5j^ zFJ<-Yii?8EJxIVs9BvKRvJ5a(3jJHQteRYywwj(G8b#+UFnb6+lu!s<+ ze&t^PNVzZura-`ELpdv{xgfWQf;qd1nd*zsDK2KCwzWNSR&=Iy%qVm=%f#dvlbuXN z97r-}*X!9&&v+^57_zd$&IWq=1h^}}W3oiru%ZTajc%vPdieR2cHakjzXtLpFd=NB zm-)?j{T!(M(9t45gp)J$*A!J6%#7iiek!u%q~;wOdr@JLl`Jm15H9s!fX!uoTm^dY zwsWC_G$XFRR2+6%^!5%TN$r8k%idIR6Vkd{0QQWB*XJ-K06+%jL4SpEkSYlDzzoiRl|zHvMv} z;o(g-ye1|>9xd^Q(uaL6S-c+S?Og*M4n@1hdO8hL#wWL#mJK-j!za)hTLf#P*$%|v ze}nB%O}>8!9ZUxr!H>nniTrB!^!Bao6J?8S?=!L#Wp1!22MzIgBsS!CWF1{#&wukv zYbDC+4C)c*zWKDs%^>HwD~Rumums=h2syS~Lqvx{vRCI&WghMp>ULAW`Ilh_4`JIp z-^+tL(Q~0^90@7YWU@2D-Su0W?sR|Wr|doI6U){6fD5?79zZR)E<+9 z@UCCdla}P?PzJuXRN9Zcf4kxrN~@aYVAI0SZ~ij6_=n0}K}Udj@@p|Bc=^D7~yO{Vx~IQtG42%`ADrcolyEMdX->UbR|T zYqnY>$;=DejW)p9GNifiF(gA-~&5 z{aP01_L)H+cJ%k7_$KZGo5ncfVjP7;L{D(u(a#QbGk9?k7(%+@e;VdA&qNgO_41w2 zARIpF-}<~Y3*FX#IeA4~W0Zqban_)|8g60$hnJV)LpjRv@Hk8{IVmu|<1!a8+L=ZN z%dL;pU}Ez!#1^&5RA$J>sCY|V5)!(({~LJqpGM^WUc!`jxknVWUwl61EuMMxPkA~{ zN(xQt?NcRPrKUN*GLUCiSQ2+^t`^nQ--6v?0kd93+WU(m6bVBe`wLjg^=H4MteoOO_)JW+S5C_NPcuZWpK_eyY-~P!UaPtL zew@eQ=VNSfjsKmEwPA$NB)>GGBrWQ$J0#tk!2eMAx5~%=Or-f|O3go{q^b7NlHXmm zH#THE|7ybjjs1RdCUNp6r%8tna5~E@q>{(ghDB&W)wR~-F0|YIOsN2FBn-HbP-7&P zeEk=&_jDhJh`d}(E8rFeBVANk_0cTPp5`4z<=X>P;^63ppJ(`9e5b87v9zU7^K?tTp-_p zGCAi(0nIy-$#0cbkifaFF0v5wlwDLX??JI7AU!=ThwbXo zCz2X?+2`uJ_jdGeB)s{i?%x>MWH@BijTJR%gen_rH3pL}IXn6rX2yWmgZY)2&&OR0oQMzfG4(m^Cb3K_ZB-YPpAe3Ze@6uYZuoziNKEWb zT|?Nu_+EZBldL%spESe!ZV)V5-C74(?dRuqfR7{48&&R85c>P%z{%z6IA?tB#D#8# zRRud`kV|d=+YXe%n2mA&!=!Uxs5ub8^TLVqqK*5kX~fW@dqubb_K68At^GslF*BNx zGD-U~iJ_yL@cTQaWla5uk6p7wpr-5?hE$Yb-d?+jg}v>9q7)lnZmm{0qAWZMM}n4& zfBww|c=P$w&JRnTGd>n??gC%;BNq_#nML(#m`_BLvP4ORwVQeLF>b62T`QMjV-$=| z5(5Qn9)oA?IS0nwj1?E&h7Gf`{(5X%c3#}fn(Wbl3oL4DPxY%RYN_FlxiR>$ev@jI z!E!c5uIS6iyi>HxD{~*I6Sit+F^js2#mC1#_rm3K#_zZdWBZJ>;#KQK{LbV{Tc4(l zcXvT@ym4m~t2jsK9_TL3H3U*QUv8XloER;)g1745Pu8qE7U%O?LPBg-CkZ6agMk24 zCx-6W_J-Ilixf37%DF7RF4bN$LSBuzdpE4Y`{-9@G_R;wZ)M*Pz$PW5f)+N$0iA2! zL_U{m^%-XQ3AxK5j4qn;jIR7}!ZDfIU@SKoh!08BCn^zm%&Bm!*7xvkYEhfdp?rZv zZ&4X5Vb2w8D@3O-#)gD_w&F+dU&P&&lcNDJsh3QAew$beWF(7?ODc_aFp2Qg*3FL* z6Y}=8E-oDBL8b!tXv2=vy|6L4sZawi&EraH5|O)i@fRuNef#>LeohLFlX~?BE*o4? zok&G58sip6UPJ>6bj)oXC&e$*0)L)1=@{xo@q&&4RpQc|2p&coCxfLkg!^V1 zRT-4x?L;eo74k)z98T9qcspt(R-c;J9!33VD|fb&3}*lwlOqUR`XkyF zD~goWjM=*?u`EgwVr*@jGT72@>cB!(b_OXajn0c)vW}YHiklAg|5Wm}%^7B1(kH0u zQ7!jVGc!eeFP_kkfc1k`?35L)KRDAggn_nOJ1*negVx@)`|$0QHXzta_UN7xxJtsy zi6q~=AsHjz3>uZRHF79tY@cq=6*|5($P#fso0>Xtj?ZYo<=cZ;zTE ze@wLuTVZ&PqcT+-)jYWMIozg^`(HfjuK7_W#RN44l;e*$ZNMg-BInDe zXO(fe;c)GUWyC&XSppe8Ii9^qFy`kdKdq?O_lN0D*4t|CNdhfKUEutn8E5VpkmgmD zUCk}ux8=Brp!nWaZ)#hC-M2%}g}IsJ*eqW7M=^y^wb;pUkF589XJ-MA*j5WfON$pp zgKLcO3@rTV;VZP0eJVexD^yS1XXwd6tK@Jhw+U}_| z6fx!%NL0mPs`!R}A+$^f9ane3oUz9o7Ug0@Y7foJ3Olp;Gr<=x6TEM~ zHpv&Ql{achs?9dE=X!4zCzJ|UIXZiS9piS>9A7S{MaDGO`qB;1b0*i{Qz|a=*9kYl zkJDeLf7`ax2P(S3TU@zbU^4PEhC)TA1&PrIPQvf@WytoW&`Y(nAg#j~y22R|ies9EfPn6sGppkWNd{-!Zz z!GoX#)(CG~R@+x8;JqC07==RVgk3NO{B&(L5WDZiwvMas{qNU*Jg#nCt($!uG~>EI z%q}rHP+1f%PYf;SkN?^IG=}fyeeoBtt?u`V!PeY6rXY!{>(?++4Y&-^Uz?~twgzlP z1!FsHX?QB{@K@GsF>tI5dm<-yL%9Xo{)iKbewXK7iZ*uz!H|jr zp)4mJcg^V^yM>L)$7(mnV}g)cFa;xXrEV~Q5lAgf=2!J5fy%CYhF4ZpFDp?Im;7M% ztgoq}=nFmt$_ga(*;}9->{xNKxgT=VkWF zWiOTUOIkFf85AXz64E|&P>%z-X1Nk!{xxCgO^2z*KI3U&>Hec+Govwcifco;CD&TS zdU&(?&~Ii)wfB8H%(8?~K3H68qq7LAJotUTEK>P84s$kw*}m{lUsxuM?lsTN4^Z}J zTe=>X!R`3)q5JhuTgum~D(GaB__0$*A*IcB87KDD85NQl3~26%y;~@0ip9dPw>e>a z&o1;h7~weiL{7w7*w1C~S6nA*Qlt-g7bS2}5LkBY5|~>rx0;7^hf;CtEbo8nQJyn` zo&8eh9Pa!h(0KerSomfq)KDtW4m>>|$Np7>QI2MTUPO{D0x-;)kNY|0H+#m?8oDM1Q#ghpW$Di+a>R<;^3KN5jYBI}*+&e#W7qAVb&QhZFmD zh5b7mQ&=%}HbC??@yQ*^k||;Pmy7&hdRlftmV7v3V#)f@#xIKx-^BTpVux(%5Bfec zT%hcbBquhz!6994`7dCf<{@RX1*+N>GjmXq6JbfJOvPxrGsj2pllQ^kF|Ysr(~1E3 zdYi~~>CrYPQZ(j|!R6Hl=7671Y99uM^Lgdy3(RMDT0-<*nkU|owY-}2^|VS=G^vx@ zTs!~|K9CgfuPW@<&(;VcOK4=tQ@m~Rto^7~bSJ(Kw5t6D=*alm-|+Ho-rN25-F(Ag zBaWVq)EelX#!FZ^HB!^ZF^;|08+lX>@TKbf87wNA>p9&|QPo1YsXZNN_~L;f@!hEd zeEOCZi;X#-Xli8qM#M9?W?Gem??rm9j5IOEuIMgGUTgBN(*yCjPrRE>R!JwD3)HR4 zKNiel%D)8isG@-xFvHje+bQ89(_V*d)LCt#qJ?YoyKc1Q))6caXDpEc%pe@`kQX?d z`9DWpW=Kdpq7IgE9o@SmNV%b002DXG4h7riKR+9?LJ?g~iluX@#!|nk-S0ntsd^@0 zq`{X;CHa`RqDsup@#rY|7hv%D7tR`GnWP;a?d^kL?XZsAidNFwfiB1ZP<9=~?+MlL zE3CG;FYER8vnk2l&&Q*nHI+DU2(YQvw3*-WPJm43#I;qf&5~}3{A%*maoYbp+b+Jo z!PO^tJ|}+?ng0F7ON3R_Vdn<)1M_m{WD4~z-&eoNI%S}h(;B*8#=c70wEJ;+K+TkE zgwlo{OJ1rczEGSN%avtnp|girW=+rfKn;{u_HpJ${8zUXrA76(?=(~2Z!Q`(j^f_W7-*D(D2K<*(7XKO?eyg8fHL|E^6tJwWTNuW1H41_SM9R!) zfk^sbxzSEJB0&n#HhWyrR!<}x#pW|6fS}!Xci;9fdtBDDjxx2|S;2i#JZDm2r)mXl!EJPS<%Y@e5{rm@k3>K{*X6PriRE0vRk0bc8Zkd#K z4Bj>(H`_%{tz32SVwM3l{^bz5HdW8^qM#qW%y^cP*wFVbQcdY|CNDvE&YfCZ+IiDIqWw-*i6Kf_V=jUcj9|X z_{893bUZxmbJ7t?Edrh1w zCUcDQG5FfRzIoW#;afL*#^Rbj^&I!Yu;JXZIlhn{f_KrC2rOK-l(`FhXpyL11lRvwt}!pA+EPC`jE!cJlyS2=SBI|-(= zJ{3&YXz2`iG|h)IP?bHUO7OAd=dYWULX}TxzcO(sREFZu7g|6d2&-Mu&xtvzFNL;H zF>K0@$>nrVt`~CnDw~@@3o*UtUyK>-9%!s!NRD=)G2T?lDhCjQ4ZmvkoxzNDwA3Mp zBRWwS$AZB|>EZ|Xq#0_e*NdS>>%&sBq8O}gH-?xGX&K(6KLO^{0YP@T_cSirh#H$~0pGw9R1&6EYgO1p8v$ zf~`C!kDE9UreL*!Hma$`(RbQ;?+vX=Eflu`HESf8(~>5*x4YX$t(jIt2o7*k<&|#6 z21a>D;=uw(PrA1}*w2pH<;}~F?%h(K>YHl3gvgA`cTk9E&9QAu zI|K~n^Ks4A$sA>-{2+ur`R)17a@7R$3$1Mnp)hZTPxP65a!W6$Fd0@h>c8%^Vg!j} zT&vAHZUgvzQ{4HtwJk8wnCjh#q`9Uy!WZS7e%q zUpMURR)Q*Xax>Mz#qlj83^kM}mylBd+G}B+8~{kPw&xaj&EFa#d&>CTzv5?6170rzn9tVLG59HC*{#=_-tdg&A30#6>~$B46fxQYLRepePVxE# zMFd^?FnKEOyB zCY3Mc5!Yn{zF%5cXg~V(x75V9{55=iIq>Ew`d6ZDn?_`of#IBhA!&zAXoPqkZSaaw z&-qS`q)%m7UA=Zw#TSX{bM9kyOj^=7iO$U)<}1adVilZWlkCshOjXK!ZgSEP`WUfT zh@}1sCgau_unAb9;a|1Ckz7*^6=;e!cdm{<_Nqh=#K^qaa#1U;yh_H_Ha@}ST)Yp+ zhj(Ua)6b>^qu6<_7lm&C^k;5`n{STGGV(!eJJ;FXr+u3po@*R@>N^TyWHuhN&cZ3l z_4?R0tQ)y8krj&Ka(nZ$>l?2FQE_56jC$_+$%i&3q)AVw6cG8V3SZ_Mt{%*LC^5{D z4KCop^@U6~eYp_FIp=ibd&?pVwiSa06259pK^i*-L*8x}PeDv=yNFgq#5dLqEkWJn zWcRqSl|;woQM-9bl@)4EFaSa(68`Z6NZu0)6ZSqhM`baVnvb&Q*!!@t%NNWg?=q$e zT-yya@SvRi3s7@Pk5JgTBJLXUi8P6>N)ZU8q&^$pH36V7W}16uUKS?`_C$j{X5M`) zQeQ5$t>ov`+VT6sQB>j`9sMTCq1*e#OYi9IsLhClidnCNL;EX!{-^*^DVJU`mJPcd zd+&v|^_EPTiC}q=-$T3c1`I0LI37FA%aTU2ii{sq;5oJZjO=j`icYGa#V2;L6Lk1V z*~lmU%2l49_kaaNoKx)a62ru-Z)CXEuR5qBG1KKeR>36;{o!)wG-qCb%)OVE#~k;O zU4wFzBV!Z{?MwuGq3+(i`F?UxgOPt^RZZQFdUs0d;xz+?jHAu`5zwO1aFJ2j`LOiM zxA4t5ZG^FoXn=>bg*j9|JsiYdMT8d%kT^*irhj=z`J^Rb3`XD zX*I+_k~8)!(x$QRVo12jKwf+4jKigaX$34E>njF`QsCh1qUXI zN>%$|+6bnBqho7M@Z}q-JFjWY``|*hg4|ILICwX|*j<#$3UF8H(id=)e3TQ4f2Q+l zomUQ`x9`J#Hq$S~=%xtULkkRA2pRlDb}!g>^!Q?fK{P+W>5JPW+_(PQnA-cTN2r4= z`ei4apN*m>ck!f((rPm`CY!cQ8k)SwC->~ueXTLiuN|LVl2ow#wRU|WarPhODj(e;`1C#G7 zVyKi;yDdU{&QH?P){$xg>-<92*KZ~;i%T}nEYuuUSSrb6s-uHHqHe}|e%QtDK$W|l zOFh*Ou37u-w;MktFR!!+v6uagiwdFxr5!!uIH}uFMn0C8m9*9Cu^g<>CFmJ6m`R)i zfRg%4R!&a9ZHZ-Tq+@B4qV^njPl)y${3I=V^Vg3_XMCZ=->I?pI#+vl97*<p#*+M-&SQ%H0$&pE%RM`Y$gA;olLK!f zRR$KDeliHdtCLF;H<3Z|4=s&avXYW&THJUMSj!CIM*xdYl8?FbGKkG0w?{SN>%-Ai zGr`Cm7?nS>Y_7GQ6CA4gxy?j=EH0BOZXzT+$gms?li>{R$H$TQcuvtBbn-Ln1c_WHKM(Y+;TDQ@G-!xIeKxkZzJ1*Jt>AkZfR23E4K_(!=uk4=LyfSgj*~EfFBix zSc}P3Pz%CG(QeJZ7B{EsD@F@s{G^H&HDq^x52cvj2|h_{twlzaxi3wQ??*layo`my zV~V~e$|!#5d*U7!WeKygnb?3`oRLP*j1l5Bf!x9b>;p;+ZnkeYF%azC)8w%tXZEBs zMGXjDVT634Pq!YGV&>)!YWm8QW|G$J0i{8Oj%H5E+XbruDymZ$>Z(ZU^q#}Gjf2wb z@g&suO{04|akJbzxR)l#=+-u$fz6mJ8Y(}-{-#(PyL4v>a@tZB2R$7<;}T91K~_^m zJ;tK^FM^I;U^AMK?@s(r>0Hf<$~DXK(u&3buQn=>HYEmhQ9?OLr3COZ{SLPQ^R-*r z*;qxRt$h3ALZ@gNrFo?hVxezi>(pL^cQ5Jh@uc}h^WOItvswAObW!kWo|Ici!HHrd zV$l+5P&UE%KrS7`(@AN!FjOWmFiwq-zyZlIld4~>=vb6cm>s+hG~=Xo5g{#0*WZ|X z2E@k_jclIM0vXlSs!Q^5ys=J~dquFG|4iN!U)FME1+1n&t)IotJK=uZsGK_eqYmqC z6CV{7eaTP;u-SpHI7nqw=g`30RShHVZ-_PYH)?0Y6`<%F<5_{7G&j02hqXDflsa(u zmjnxC*H4QHQBsfKb7F?kx(U4>v$2ab#Bv7HUgGR?Aq!IeAfuV2SWBc?Y?73A;#l(5 z!#2G>i+21<6|JHkN;|$@{=m%2O7~fL!l`>{(3tv^rf(3Bf+_rXBP8WNS=#?b)&5Va zBqj|%)$X6Re*dj>z%TzVGUJmSv;J^7jckD2%N+NA(!O7wtS_>~^C@z`b=(=&Ua+9K z;yUr#LU%(tFj&+M+oWQJCxZMucFE9@M{7TXimunPM^Or8rmAoIDI+%7FQfR&Ad1`05AC%$)I6X zF`ONpW46+|ZM#*s1|1DW1xlsPB0Ee=AlTGee~-3Fr%o*ALZ}nrO-OB;zsb=0^$2++ z@R_7`4$>WUa(VvE^0iCS>K9QXgUZwBiV6oo!#Ex~5HF5dboAJ`pbHbH3sdT=BJUJd z{cmPaUojX~262I%5kmvM_t;z7Geky^j)u~Zg0|$_TlMLymU~Z!#JCPTu}05r`|$-^ zyGpKJBO?&I6Wv&Xbp}4l@t{GUfFHXV`4rGw_PyPkntnCO_d~?42d|mSMg0qXuVt;(h5jnlN>nPW#+}B{m4to0R|Ng%(f;IjHI@+)MC#IDBZ$PB~*xK2z zV)0xsq1tE?j1l|<^7PSCL~?m*XbH^I5{z)x72p~skR2N6oH|gL`585nfsgqj`M2jEEJO z!GJqaLh@!BcaqXGeEYLoGQ*Sca&%epx zrz#M?+~NCDop!0!u(a}4IO6`7utM5$(@CF5#|tiU9A!cj)yWly3xj=@DuD&tWf>e3 ze=QKHuAx!&Bg?=b69Z;MpcEAuWo3&2|63?ITFgKMse>7&35s3r6S{`i$jQ$okCT4p zOOJz^jzz9}c<|jwk3%UZ+COGV5(99Xf@Z{vbP$Nw+veR!lesat?2(clHIn4lRIvl8 z*{_1l$bT`ErO-Y^Sa&YyKvyG-S;(uLpGSgLyY07@K+$#H5`~B{CmC50K#Cb`f<;OxNjUuMjbC)Z#i>l}) zi#_JTI5)htw$E#?yO5Y6IDF_CmN2o}H$HB6Rb!uLXvi+ao;$|~jRt^+>-(vOR^NWm zKHHWAHgB}!y^;5*_&Lg80V}2WnNdiW4K;BgNA)5Sx~bBvAYJ4*E-5du0OA&xTU$s) zo8Tv*1OjEnaL$aUK0$ti%8zZY)=!tNtzxb;1|Bi5@N-&_hu2yuzeG7s61@8El+4@T zQ?;P#MnOjh`R-HIaT6JW|~_6hKEIREVrIc)6eA)c7-y z>3?JIy~CRNwzbh9RcRs}0-}J3H0c2Xib$8@7wUi{?6oUE3aeUATI z)x^7626PHctLDU>HXMK&Ff)eFpx?^r%NT9{*P8C%fK@BEnkCAu)k<(hQ+}?ki1crO z>Ws)hT6nDSbBjXVn)o_97-047Nm}=66z%aFF6iFe5bWHTCAEL-$EIF;o?BQSw5YFL z#GpRx(R?zDMb*NEv!YCj9pyXPdJ?y1W1MN{JSwYUO!Gy|_fy=3qT)T=%dI`E@zkKXXi zyZp*8Z+{Yk84#QXE>?C#xMDMwDRtI))E*@6UD5J?OskmWu`}JM%U@o0?mQ7gKdTz-Yy!b=aDnJgYkT*>C>rUYU;YYCE~FPfPDwn6sB?0aXQpD(QQ zzD??&-+09Yvk&`P=;%u8ykIRuQcdh3>w)4u9`CzKZRZz9tHYw^^v*fm7oSuxQ*Uhu*$t*GiXZRZp39bmh5d6Vd9+?c2E^`F9BVD|^Lwe^)S`oa^25o; z=$6h1FGtc=dT;Cned3iLiC@58Qz`D6*lnLiZeOR}J<;&h!L^E&*H3|jXaDUne2{x; zs}fEnysDwtK2S+fPDPKK&F%MR^VYAiQ$Z?{u0thns`6M+JI1mRpC~+D{O)pC!rY$l zq3MYsZiflVlUEW?{}eAotD&%#5j#JLb0JRiXr=;Fj6jWx#%aarM@B)SKSCIc7nC&M zmnR7TdMe!MT0pI$U)|V7xKWm}hE!G#GS(VZ`S`Xmx0?$L&a6aw^cz6<8_-tc2NwJJ zBKIx1wUi&qZR?_L`J1rjpKFq zfK`C%%%HP5r&N(&pj90n_uOus&$>u=^r%w_{A{E%8U#F9<@Ee? z_+-49>u&g2<4N6K((tqmd`FPNqRowwD|qJV6s&BxC-rCFcSMj{O@@rhb*PN1Fu%z6 zPUa}EUtBeB?RD5)24NZDrsb7eFYB4S7Y~eXTxDFbmk&~g41(*dBm-FAhNbi-X;Hl? zZ~1#iS(ZCDAVuf&gU!|mzm+@2YpVK*xBi>{`zxCN@OJ!bQ%U;2IiN4> zB_&>{!ENVI{lv^6KmY*@D8V|OA6~c;BP@Yb8Zn`n&WI|3>&wxYsUPaBblN~7lUdL3Y#Qr6Zl4vTwMr%11jKBjV*Ezk|iEOd{fWh?RKIjE&BVsm1bMhWWai4WLI^nuE+qFOzQ zE^1*H650NV7JdWJ*ipce}Tc*(-uhJ7|(J)Y!C5WdL8dpNgGkZyN z!M1d~?h++8&&Y8?^blGEj@U(DdH2&@%mnp09W13&$o_S%N7wy}Z-G(7FnjSn*tcOB zRf$)(B`QNgyIF!r>}KY$bu%{EP8jQ@$sL*p>5uX`HX`qcJv#}d|mHq*liNRe)I={rgXduy6HUuPUb3ZOtuI$;uk; z+`#L@SAB{fw!Ltxl9o!ipWoX4E}s1KjAetj*0@Icu-(V%wj|_Bi}+$Ya*#k@*g+^+ zY{grSHwHUna98hNm2gdvxf&(g zsLqOlM>{OCL^T4x9=rv05(W2t3L|lOmbso+#h+bL)$W#ABa<0!C;4!WrU>R%>xdiL zIwXb*PWOd3pMMdRho^Iw=zq+D8-!sj+&KH90FT>TlySnCVx?uoF)eZtq9%RKDlb8% z<0V;z&`ZZe*mSa)yDWBS%z#Q_yL}K^O%NfX8$(- z9<&%Uq#7XrpqN`RiO(K<{roy(o<)t_z6d0G&#Fl6TS)+W#@DHD%v0a)+Wp%TTirsa z#vja&If}TB>zT(f6BpwLa-;)bKo#8?zIEF%h{s}UXHn(!a&c*8cdu>8YEx|2gW*{@ z6Ls(zE=yzw(YOJEa#m^`BeX>%-A2k&@CKw|jK{eNu4zqekKl;R8lh%+xQdOqfP z+bQV$-Gbiun1}vF?x?}2nyTgxr*PXoR%}E>@OKw8g*~6_QUr@z&SuHU>By*_`g3)^ z>ALjDF2XN6K&h6Ojl7smXH)te5xRP>F}?m_A_XkjZ-B_`XbYu;K1z}1xtn{g*Wev zd5o5~Do-M=0KtY-xBm$=_SE>|iO$>+OaF1bwPzxyM0&WBpz@g%Wh%^w1hwr6ax6{EOcJ2*H~V=l!);TziK7^TJURGib4Mx#`bLtEwg>G)6&Fg5cms zOmEM1hICssg$ahcr6zF(t3S6zG_HLwHop!B%|y;C9i%Usm0k8Y z#;o(2_u6-LUW*vL`6&U(l{{>lb**R2%tj(@9UOh2#tirXiXGCFI&XuaBf_=-WoLe8 zv91S)YmBBM1=)yZ)8tKK0|>VSf4cVWKtZ1eyR-w@B|Fci!?(4o43-+SwO`gOc6EX4 zv$UG)1zQy-n>3*n#3J;>9O-qp4R>R{nsS)M&;S^{E-iD0oevo|XnfTi5V^XS&}m5a z;y9+f`S4j&088kVz#cO<>SJGLK>o*Rt4G&3s#in5=Qn+2h-=D((l0fZewN@Lv|99Y zT|k$xE{43&dsK9Rf3IvJK>jbkHGcFzIkbPWGzssiZV?4sCv{3HkpA17{U$Rfrl!X| zXx<@hNbi~JL{u-yHZdxoL2c8R@_jyAx&5YyBT8kY+x|Wy;baeH*QC82am^^eHXGu0 zHI3bmXOYG;wd?VOY7)7zK#@g$Xl1C2Ozn?`2J5;#n0cW%GetsO&C-ysuy6=E4*LQ= zz5A{<=ec^>kMA>GG^Ig5rAOvYpl@xr-4Zxd+0vR(o-)1i# z=xS~#MRIr05*B9Ms6L{)GVaUFiF23uv+r44@r*|%kg?Pk?> za`R~M!|ZR~a&Tzr3jzdMY2EkP?v{C-Ic$Dm59d<*wQ6`g;P$v{KQ-x>FOE90+i~RP zUd?!ypW^fOK73`&&-ylEHyvD38efXA3u`Clk|~;laG@9)^iJhV) zY&V8nG$1|+atASfU{hEx$&lXczAk!0J>C$L{I?Zuaxg&|u@lh$uJ$*5K zDO7)c_mdcZ=>XF4;)Zj!WA35xE5UWIqZH<0i=4SZPahvt&Y0cI`G-Ul9HqLJT>7G; zw3JCeb3{S1*v$wtJV39ho6^`9VmdFQyyxolGy==6j~t0vn)F z5L#wtU0S-AM2qIe%=3X;s!L!?^9j*{E5JH|#5IWj6?c9KZDg(}9pYN=6>G90pO9Cn|#Wl}b+>0~^ zJ&qZ%i0*_9Mv1Z`Ey`xW0yCvzN%Z2~?OIDjF9fwDT`YxxwP_(~ziO+~1*gcc#w!3YS z^Zejj2jlY}FS@*z4bGm9{VMElUyrx-&hO4%hYT-zN=m1e!-0*L6M9F=!>trKHF}*x zQyX?+2`mkemJAt+CpWscopCc0QMooROx~n8ua_f^AOhgU?CZ>u(}QrKW}lL3HPg~> zJ#NL&*UZmh&qiaYiCnNJhaBmLXLMYbu&&CfW(jpjo{D7BTEW-+?D)O0XSlPaSFO;{ z`}CNjiT6L93LQm1K``cd!l389NMUv#+)Zb(yBEoC0cJeI;>l2=ob0*@t#HiC_Gw;X zRwTj7W0NlT2m*@8lC674?cqId{unJP-*#%2RFmEJ z(!vH`MdPkjG!P5+;t0vacJ`D9q-7SIjT*O~Tm#Ju`-ZrQ)=T%}+FRlMdTw?S$u`R4 zouHUl7v{c!-Nz=y-`XH04Q5ZY?fn9DQZz`ygVGblWa;myrkWVXecwMk51Jj59BeK5 zifW$ssx;epl#Z|ZsRcms6i@6LGV#C`E}m_fotxP#zYQ!H)J;~^w!76MuFR{Rp4zyy zj2*NTAP`#JtDorPEyL5YFxc}Eh!P<7SjcFrCad?nDbF$P1P7V= zzrz1WAoX!+7Cgu~96%*rqicUlm4%x;x3;WVp*BI~7r3#%tBpJ3?0c8A%v=^j5mHL@ z=daO|>0(I68Vp`ABm5;FyEe^30c_a_x=CDyOXqQJbwZ+ga4DJ-<6}JrdQQTUHy;5_ z71$}Sk>^%3nKeU{#?oZF~@ng*@>7(f!3&{67lh~&+A3Dz1&^6nPdhW672T2LG2PG7Z8uWg<>c|9y9ofqTrP&-e~gd1(B zw*QDrgw@?4;cI2@U|US=ymRJt?N(%&N>|?9KE6xnuB9CymLu7b9IedYHg6~3VA3k} zDJ^xizsL27Txw8qf_^s>@V+VuDK^~Jt;S-pjs_9sh0T=Gue$(lagbc z8hqE@z2YqZvX$zW$rwjZ7h7-!NQ->fb$CioRRsq`OZiPW(|=GH_Es%z*6h&UiK&__z%BV zU1Hkz#CC9q)Cy|Sgm^QY@#Qx3{(Xwr#p4W(fTiC6jEz=-%71F)v;A$*pq_cleMt!g zqn%g!52tAyEzOCl%n4opOr-FyE7-HS1KthRe>)Ua%)e!3F1%BE*)Qw*A29WF)wsO8 zdsabuQoLrXFo*vg3x_YaRn=v2KCV3Vf#(p{?RZF;UcXQ@<;+sv^e9{vhNIIzwWK-uO%YH7t0gzRr9b0h%=3QR@NgBdhWQc#A4@ z+OCCMFJ6_b3OALVu2x?26DX2iHBd4|{q>iaXSo$*CP-Us!NfYrMbZy5%?3 zHE)DYA*9~AziPs>ffrrY^$Z=y0PMT&yxF6X@ZeHH7a|%oXNl537Md=KYc?SH0b#I> zDr*44C5kEx`Gh6UHg0ZYhX>tWksh$4e539|!G}XGjh7rjB;;#dYsZak97N0AwIza{ ziW6Jq#^Cf1`CJ;5d))csip27sUMp3du&WFMZ*OfJThbXi>pC`QyqvPijf|w(c0!Y- z^K;E@v3%01OoCs5=eZ;^8dh!V>(d0uZqLrIX9om)bBF-EoA~}zqEk2;ydky<4GQUS z%=63H9QVtfim5J#D>4jdP>d&azUIs8<)lC-fJ7UWL8R8pvO1219-qnhEUV`{e`j{;eEJ2Cn4`C_y<-AVkl)&8nxD&Mp3 zXh0>H!R?U|`q-;3ViEis@JL&a6#^z-K|oL!qT=|6jZ z1F8nHbmRY1L;Lv*e4CK4U{_U`(>*?hNZ}5dLV!@3iLL&I(7IT%QF78iap)+SCk0_H`A)^GKMH-0Mb^Q^~b_kDP2 z;$1v=w;g^=n(I_k_$vJCZhe>jgw=Mg+jH%RSo}bVL)st3g^t&S3pCekGuN%i1^BKD z7r23y3|6eXZlb7YHXdHrjfgSH0E>j#Y%uHaO^|d z&)gF6TlT&6#!<=|%uM*C_?BHjkWE6P0nNult|%ks=*=y9l}{z8O(Nc!N_W@qpg=mT z`86azRA|y`yu7g~QtHQdvhjM-SBOZ?Uq3L~n>rSC!Q3A}PBT^&&mVphZpuWYLFU$} zhUKXQ zd#JwZ*`dnBk+q2n#g0+$?hAS5@{Zw8P0fzNs&aZP#!%b78T^ImvRGGfn<$x`Wt5g* zxe3o}l!+bBht{t-`)w;n+7gW)2dTRGEj7oI>Gi`*?Tmg!^uh6F`V0*RdB(+GjmhUH zvF{$B6@DQW>AJt0-yR?!TY@vRjZ{OvWRITOGcs}~ms+o2Ik?fL&F3LyO7Z)dvdzA2k+ z#tS@$VrW9eAV&D;2D#<)KPQN)zjUNI*46W3PCbxwMz%wS0j8&{YEIr>zPgBvFEs{c zi{YaNZb8)e*aK6v3dvSCL+dS>R<9i7m!g^(HiQvVEbs*fad~?T<2<%z>(oy={hURO zMzr(uvlUQZRrl)M$83Q5&Ke=Zk;Azyt+NXVsq z%2AujEA=%aYwy@+*LL)G0Jt4x-1(?}bmoCj%4W*5eY>10Qc-Mt?d$b+6q5tHq2EmYY%kHsI2>0sGnPK^|6&pY24 zO5qVxE~cLEy?vA{*^J?*9;ZZ(xbI7ZNU)9qEdwKq2bZtbFFiI}-y*`ikG5zZzvRsJ z@2DOW9IOj2?+3%$>p=U1oaHh}nIYRIqar&sa*F81XpoaP`?~?b@2lm|Ne>#aO~0)Mv)Ox zdNz)twO=lLj1AT7s(6NbtgcwRxU$`bWeSgmX5>l(uMB!H;=cikwFubYK<%Z#&fr05 zmJyFzTGndbCau)8(Y@O@pCohP!)Jei$A0*V#aB}e>56SYS%1_cL2fU)s8XpTVpe;D z9XIIK3&M$Nv@|aUDkjVw@$m;!_ja_$jSy3&CwhAIhvfQ0cWHU&%NnEnmu!AyJGt## z@*7X&zFq#Zsrq$f+!=xN>>+<^An2a)V+lYS*7ZcPKd(q^T2v~_8`Yd^&YL$mTXM<@ zV)+(tIwgqvWyb|Q8H8blTumMech$x$(ITe>?tqM+hMH6mEV667Ms^h^H|vCTVZ@sFR2u7^F`PM z!q|RZ()B}Ddb}K^ia3K|dr3OOhwGg553O?e?|rTyQWR77 zck6#SGdmIV_R+5FhLp|7r+&5D(cP2Xn1#nmY*ZO4kr3&HU0W2^Y20mOtm&|`RYjS6 z4a*#4f_LaM@wo1XPBuwcX4u~do&TzYoBOZsjn>Aqgh{fWg}(v9njXI>SS%6eg!NaJ z7d8_c_ky@3r{_1$VH65mmfW?CL%wANU>3sQuzKC7mE=+N1 z2yu)R>q6giW30M8ai{%zx=lj{XI+H7OmqCkkGaK@ccHC%Cw|{O&mI-KezYTZ5Y-x3 zl@rFZl`>(D^?&sPZ`E)L7k95hah+T^5$OiAF z#h{wn*0hWCUAr&29<(l~vK|c`WwA3=1=5c&{C5PvMYy-+o|$sl9G_9p@I33bh-lEL z&MgYjeYC_DC#PCEo}&~iMU#=Oy#Ey`}gkEOmCE#*KNq+Vq_`t6O-e2{{I@E|dV z3r^qXyN0DtyP&Y3@Z!0fn>TM`P0b9Uv`1#eqp7(Um}L844y|r<0$t;FH^^>@k{|Sv z2h1`yG|TK*JSJ(Mo@Fun3()|5&Bw=VhS-D6L`yxD?yD}^D|Zao7}dr!$?7R;Xs;uK z3X-GE9o&qE5dc@x21wr{eC5q)wL*gpzZ{6PpGCc-oW^xAf_{0BTMzE;!ajk`$A#gR z{3sT`-vFu&?9Vqg!?CTnbRSVYLc*QpXMD$+$zi!!`2=HC6q*U*o$P$q@#UC!>byo4 zJ>z`m+FMfbH3c&ci3&S#*K<%EU^HaWr5h{s*tOn%_vT6HhV(s`+}!$p@^p#UdDkE{ z;^_&|&P+q6*9~=8Y?*BF(Yu_n8dSc(K-DZKE$3q9dJ4CM3%-<`D>FO*WgwY1vwOL| zlsI@W5fY{Zrk#7xFf^T{=eWN4u;1OX`|9hvz~BYnT?U`VO?)s*@5Z{_-Tb2>9R=p- zpKzfCrAe@v3CRL@-ZNsa*xb6P#$+KARS}DrykKYTqz|PRiSWKuU>*!SSBo6D@zXsZ0BdxEipUC|94d9!-dcwN)(Vuzl zUj8>(%?(h7b#PICvo=9_w36W7V)W=BOBbOVT}IAA8FO8sNMH$PRf8OM%R7r7H=JsT z!+Fvku~-y|AdpR_PP8?BpDR?Kb=#7Ly$UgBSp2!QLUoz$5EV+J-Bk8e@=! zlFbu^BUlBI=>0XI%n1|1a&UzSK+ zqt5~8`}_GjKR2EF(P>f;LoH+V6=tiN;3EBs_q}LU!RYFkPV2rF32PJCB^L>P>qWQP z@nOk_yt?k9EbJyd`75bsbrN>I)@2ZxbgqnN%@s`CC5$7UGZu#Z)%); z{NKm+|9SZ4pMUzV26qS`>Q>uWguXfz{kYvtkC zj4)c&?Rvg3xg66^hVQh`oJ^!Vef33=wZmHS!fgJ&bIHYJ-Fbd)5GX@LsI$koUR?S; zF?>(s@#nG8an<3GcHR;51_uXz5OCneCT?`QR17($KUSYq)1|L8^}v=KZ^_}6CsWHc zr4Fdl?^rhc=>t!m6Sa;qpR`;~czQi|c0@Vq?to%DG;sNyH!YWH^KXFJ>j#_7joADA zNL3bUkxRXDd1Iqh*=Ch+yO$QZpW{N5Sx0byt_Y<8Z(ajGtCA@_zT*BmU6aI?S%kQV zf)(gqrZkG3m~kf7k2;M?yf8(6Vy0?bgznMCWLADoddz+>t^5mZRKHIPwiB?H);&M9 zV-oc6--4C}BSyISmf+~TFb=1Q!Vl9~7q7I}-@mDHB*)(a+<-q<=lZX1$Nz|1Lq7zj zv)eSy&Kw)7F1yJO({dl<7m{cI;s=5V@V{548z}1GPB4Q6+l4{#gJavd;Wf`#uV#Qz zAZFoObnsLLtx-l-+E31o36rMz{qJTzC8J`W@L;uT{DxuS%;7TFHvv)r5Z? zkrGn6W%3%Q2zsiF7c-9%0163D?kwj*aG{#oim(SIqasII?gP2u zUkBp1I=w_2+~5@WpdPy}Ys0sUiRsD_yDt9A^d6Rb7C!aHYxdbiRnz@%nUr|!JykUy zQ@k9a@cH@6;gy=v9k#7?jpmvf%Os#E6#M5 zZJYDIUT1cZ)AL6OwXF1>jvCLn=3u}^%Q8#VUy zvc}+;^3S{b!O2Q84*_^B*QpV1OYr^}SElv{wh4T-{X|F`kQw5=edAP1v_%J9)yMAp z8T@!luqAQ|HoH~j&`Dc?;YCBACy+cO`Vy|%_*oEj&?s0gjK+Omi9HRDR#&9JbApvL zO80HvljI{wH> zr^9R#%FP{hio3}Oe}XK`pPK_*4VN1m*AJgV-h8N%hh?K)^eHtNJUe4%p;WmRP>8MG zK7ZT>goV_m6J4gO=;*GK6uVF8IM(PJ)GN<$-0AFL?UmGzTdb=MgI+q-#8pf8$T1-Kr`lufaREkZcr&x!ScMTcW31!dZqaTPNs@^`33qRea{e z3uWSY;UsjHeOqa!tl=GV%_`~co4-xCXT!c@hp+M%`Uy6r!dVU%e{Go-FkR?tYUcXV84ed6hne>~w) zpA}uxqjCi`0{GSY}_W!4j*9nzxB|n>1vy9z#%!S9k1P-&`XMTB77jiM& z0*vV!XntBLsP-K5X9~@{%>Mwn{z0kv$8O>qat{$-4SgN#h9U3guTkOF&+*)n=O=IC zr&Cf-HiSR9g;=}1X8?JSQzXWnXDH7iY+<8>@1mfvCrngYO;_Ql)fF(&oj-=5*Zxl(jKwj!s%Awtum@R0Zq@YF?MRs+gyK7 z5$@GBZUM68xa|dWWJ!fxrS6?W2DV`hGU7A*v>v4qx4U$Itdq)aF@J0u)_!1LKd)$7 znqyR*6Fer081&4ozcsC9X7u5xO_+J$V7>u1$VK%|ikOz0@_mD_C_1Z&2^y+bH!PO? zfMlwvgxt0-Y7b~$=c-FGrsnn@-jWhFq()rwf z+!d}MNTpGrQQ}_nb@ZraM$5i#q#``$U{q+#VrsZ9mch#caDy0cDiy8@Z$jIlj*iLn zKRH4~=PR1Vg_Zf&ux;w-+RiOzKVtq_XsUeDcwV@t*yb`4N|-+9mc0J#r((OZDm)IbbhPR_tCY$)ZN9D;2>L=x;0+)==1qNI>m?XfN$s|i~> z7S{|ID9Jp-L0BAKSh|oeFHcdzVsLxJT>Zx#XdQrUM*4Z)vz zr7~y$PI17LSOq-r%@tzK>FMKTj*4LeQ)VN~1ry4CF+8UD_8=;-*rEdWl8Ff&gx_cq zA&{C`qb>;6gY|F$Y|0+pfA}Xm<{viDf4j-|*Rb%E(S(3iMwQJP0aJI_pG$6kdIeHm z<{O-DUT1t{y0~+``*+PvAh2TL{{6gvo7O$H8_F##d0EvgMW#g_HH(6wMGA%^M4^tS z7$5jPMu;Yzi5I3np>F?Q)L+%h0Ip`=V$HB+4B$CQa_#Nw9eMC21E!r8I9($_l zkc>=5CVsphA7R^<3>3mjIlaUw^7CtZ)Ji7q*1t}dXbGvVl(fdN{Cth26k?o|hOn67eAt!#U2tmeh>2b;hNWA&h%MmaYa{8lL5(|b1Z zJS>{VRk7l`Qgyd+!hyBvz9gGFk2eN_b|DaXU^VAw&6;%a5#6q`=-FtIrV1C1Pzh95 zIEnUyo^X^0-Jku?p$3uoqlQmxwH*>)k+tqou|nvhc)y%CBv^|3Kn2A+TM-d3Otnd= zh3bg?t-_+p@@kH;(Xjhe$1tA6#ny%j=1}TX?^IN_+Jui_2iAmVy?9uW&oYgfBR*PW zn@hki*a$>1^3<4T)wdqmJg;aVB3xeeqftvoi?|awbfVsJnra@?h*)GtyL9UJZ!CM3 z^?7E~D;JjA{PQr)wk_2B_g#(B!#w| z>vlNA^#{^QOg0w~RN+(v3HdeHZmj@!Eq3`Un^{MtM&GW9ck|94H3gN5~|KZQySZU5Md z{X=Rn#cr$9=dWX;;*y)#3ZY@U;38cy0W&Tk=G|8%1E5n2i96_9Gkm^d=z1xlsF3s` z^bIuHK$%zgJHur)F4R$`Y7dI?HTP%UWH`!!xa*BUJjY=XvIZm|&snhITVknDveiT# zy26tWWoE?_Je(|7BXWAzMB{q)3ce`9B661=&i><>$kjnfxFGnO!{FKS^o6={Rn~mn z2DD#~F8NE?xT?Iubfl6ahmd8cM6tUiIKun}lf>S9V83xA!%RsPP3;4dEtrn}z}bEO zcp@>>{EjVo`(rPBIK`~TXM_1LKH$|tPlqx_K z5Ji=CGj6J|DkJ*>+m~obq#dR5brLpr(|ZH5#3__-UBXjZ9WQvGtNtgm^}S9to921f z+On|9&6?|yIQ@e`YfjD7e6(t42%)IA$9(xM$Nfrj-UyN1eUzoAO;B9@nMGDso0Z@( z8S(BN6Zrh_fFO_D0tppKTbgzf8dU{FR_x^ZxVh|@v`EZjo8yZbA5HIi+D|^*s3s87 z6D+Tf+#5M|8GU!Mi?Lh~H(!;IPEF|dh*S$HIItY4(i73&iOkB(^IUb_mO)`9Jd3P) zQ{Xpa?8{YgEA^}Lw9p;hL!AzeaZ0?e16UJ=Yw3W^yM+bit_S(d0f;VD*HL|`4x04#s3xoM(#)|`9q6^c%rGXK) zKRudjwPkam4$)&&9OZiGkIQ!yh_<@1Ai=L+@E1j=T6-1)<61l8zt+z!rtj<^u5r3- z3(k-d0~e@q5J`(v5Aa=={deb+k4J|Oty-p@EP~zQql{<2G@Gf( zlX8U5kqM4!cd+68h>Qf>z++F>kQt9~cD?VtJ^|StK$*Bqlr+Q{y&k=mk$AjG_8J;h zC5OoHJbJ0|e1s*M)M#}d#9%8E7$4i8vrs)@#_WsCMu{a4If;}wRFGKpB~|09%D{%0%C-a!CxbE)Oe~1r6$d1H^$`-vS88C{9Qi(u@2PR@Yw(6V2*cuAnoBL+ zJI4|2n1C!xyfh8DD*9hcA%ilR@|d*<_U;(6Qz$6lfS@1}HsA*Vq^Oi#i8xc=jGBM^ zvB(UK_=7+#trwS3j15@OHX#a2LL8{S`IqwcoK?wog&Qo23fwvA4J;fbl(3oBfz>)U zumr60MPlA)$rEJxYJkhtRcgT>I}rbE`{y49_-~p+XLCVn26A>G$SJq@sx5?RGB;$? z>oy+2iSKaNoSFKu>g&_L5JWug?n3UnyY-5y-v9WYu4(Aj2zVA}oNE0#cI4&_=Njkx zG*f7EHTJeiT6?kyQ}cF4@#jeIpY=Jai0w0-S0rzXRk4pgq~1@9l@z~0!WJs>muC`G z74WwquFDzzbzBiq{a}shybR&RaKr3mnU4p0W6IhGgY52b82NRAQb|H@{k;dubHfP^{5=B;LKIR#m{01{uj`J7fxC%H>&a90R~0Nl3OVq%@o{|9oCT)etIgC_9JQA zO&nO~M9i6;&L=_K5HwLgBsquVta(|Cd=p<90*bhxTG{^8%`&8~Qu z@wZiWU5WB<>rwU37u@qRz2AUtgKJfs#|2tIy0r96cl-pC!huL}eD2{ou#$9_>N?|b zw$ua#!4ie3#{pqPUG4vn-9{P{K-zMWRBbQ0nLDb~Zza$9L{-h1`bgSt((A^KIeu*z zJ0s(K%aP|crS%vFNorYRAIZ%r%;BNOvoXU_%?`5o>UIDeGTMq3jIfaCu5K02yg$bz zkF9O}4bbo@%?#FFuj>G}Jz(72v+C|9Z}*N#Wd$D4T&JFSu~8Vg6QwoLt}IGmW9c%0 zpXx$tC1LjqN}Mj73TMz3{bV?**h}Sc?0aib1Dah|Q!}lls%Li8%+<=14!jz0+S+cl z30Ie0hTl>;<8D$ka1SqN9#qzF;&mAJpyh|N4BEN?5UX)1Kk&M$DRvs}%E*tKxT|^4 zr588d+Jxv4@)_s}AoJEG6aceL2V4muYe&>W8yzeH)s*V3mzf3Z?j@ZVz1S1^v78t+fW;(P2lE{rAz84Ul zOp^b_Qe?m4Vd8a>2y?t${=032C6DavN_6`jtnDNjF|&9}K}K6%d!2r6D`t1+-xgcc zVmf{wL4G)Ls5cabJ`L|0o)kZ{W)twN#ubUB;Cf`Do|{$y3Dx#~@DX~RePk?E?u z(yH-^R=?`7{Cm{8qT6|PQDFR=f{y%L?*OT(i|papF;6=)uW+8vC(KXbK{vggO))ZK zaAIf0(A0Gb*DZ12P32K_)KAXilT%oCb0kvaeYF0@Bj$It{l5Y87ah{ZSTb@-H&RYY z7iMppkAOpyan=Mx%^dj1fXDs#v37FL!^zX5;t2D&w3M5Gr79_Kr_kfL>??r*i{aPK zp(fJU1MUFfSA}C<^uwAmL_a3Y#1982M;L?@eDHU1UeUGIBeW&Rth{R7;hC=m`5TTN z%o&Iu3UVeKP^Io3Vg{Udp)zb@rayBXS`{fT&Dmye{GlGV3A<78$e^+Lwz|0Y4_h}` zoGK{_KEUH!*t#7c-3hft8Clg=u%=aM^&WCWPB(OL1~!z(Rn@UFc0PWwYr4wtDYI~- za>S5t;!(pM|(h zz?$1Xh>79AAw>#%bX5YalOFx^tGk|7Ar)I|i?_AW9^C9$U$t{Uv|tb(n+>>ku5d_C zSNFJ@_7p15C(UW59jn;G9++V332?AX_g-!Zo)pT4wwJK(pBex^RV1jJY>PE%DT?w{ zSSWJ}bH?`zBszP(GLi6EYCP48-I;n9}8*nZ}fRDv1$Ks_4)bB z3~?^`@zs+gnVagRPpW$MR93ZQ^tt+bhCO_)kQzZcg@fBd{&!Gg8u1?wUELw3Z|&l6 zpZ|RVB_7nvul@f&G-%uoG|}Y*k-qS_*r+DoAmNoCl)6C zEzsYQCe2<-x^U0H%=ogo&<>5!CKpw;D$Z(ZPM(H|R;fpH)GJ)mJyeu&{;x@Fw{W`E z;vY!Kd~4QysW=l#iPqhGUCin`sS>IsqdS$vnmK zqOrbG<7zg__>+70EJ3!dwroxyj}{{-1YeDnYX&BrZqPQ8IKK zCI?fC2;uj8iKUNh?A|*HrV61ST+XRykJZ>GO6p_nvKac3M1sV{=j;~f?-n_in{ovu z2hMQPP^`1?gOo0B~Dj1Gzq_?MpMul^qo1c{S*krTuvq>`gnT zPz#$-W9W7@UX3F*)BYkmlIlyJe8oICKZd_k>K#$->A_E$nW!8T=2!vF6y@0GK{j}Ckmt0vcy+nd zmzJqRx^RO@2g7|JBnSE1{EL z^*#iyVVGT0AA}y0R4e;<>K#R|Ocer72dGx=0=x`~A^lj$F3`|k8EkuI?eV5uAERL| z`PBTkps#|@$uLxMyI4=aCj)nd)El=Ue=V+R0!VQA0UM9bMx^cje3N`b_QvK}jsnhm z{mVMg$eh4+R~Wq=dh-bF;)eVb$}jUo@A&cd%cOX+<}-(7-0P!vJ`_=`v(^lReF+Ml zDvX9psIp5e_Ej!h+KBt)NQf@e>2dfw5|=J*^zT zi>eLGqKxm6u2JV(enYEsR-f2mMCrM^?AZf)-@p$}SAs#%F3afHykwnODXqobnl9c$ z!5Y_MhASUp!&&D-Tgv8%rj~44LUu3>TXCg)KSTfzAc3mKP4Lpa)4!u(U^Bl9RKHMB zgt%Ryv#u0radgy(_NJDuU>Lyo#iT_^4HFDFyIsy(jzWnZ;rsAk{V6fcRLaIY{9aF- znlQlsUSwCh<9vGh$m=hP{mGX4ia+he`_Fz88!bf}*$@4v5g%{s{3rWLYR~k)KPmkC zh46W&)BDD4I&|Z;)#O<7JSLzr;*h-b-Wz$|MHb@r`B13*+AWxC;X{nYHH|5y>>|~g z#!A&Zh0F+qe)DY>5;B}@sbh~fsiPF;x!8*wg1AOR{6ztaGVdL`(_|s@<`LsP zwaCNRo(^RrBi2d67lHT-PIM1NJA^-(>DFz-OiGwSCl_&@p*!vn7sz-AF89SYJ?)2m zyZI~}-8_bm(PgLBy5CE1+#MM3TM5rX2x6?uGuGU@hoQZ#C%>DC9+xo@OU^QE!O~M@ zKQ4H%`j+j=NO$VSSR8<2SyCL)cwkE>=vQ8e*_zF!KI&pK;L9nN(xUL7qq2@{N_PlM z>n%FWgjUppTsOgi<)xomBwL3(eF9T+n`gbc!gs1E;1t)~1GMp@Zsk4OX{a-Y7GTCb zhqzdV00sskRTI55`T1+&MmAc-D-X0o7P3PU&pc=wg~js6ea-1>ZA+lVBF|xJp-Lo9 zBLdcLU%mq|6oA$@9c>Q{Ml^OA>TB|+?uS=@S+XnD1!`4Q^?)a(kZl_yGlg!c;9Y-iUX+;nYLGsND>J2RR z9{V(oPU>2_HlqGWCt6JGrsX~d)Z_w=&wovSxiG4qYzfx+{MgSiR9jFJvo<5w#B}*)t8?*+CTj* zX36cZp3@K@)n@V3lH4`|hSiKta_S{P6cX|g3<_eM@CA0y5uTo$N0$}DFH)y~qO``7 zL95;;XbF9u$453?S*vH^o4vth;595TKw^>&rfY1@%>bEJ)Y-Hd7qNH0gVk>W*fry% zdbGNhUX1gUvlxCtBgYM)zU`@%3_jh-UClFo6i1#NIo5kiPIvDW*ZrCOHAeV|X}Wkt zTWd@XMEYvIcaIZw5ofe!0_*RM`F?O-c$ zZi+udPA8Hy>?Fho@LFEu@!$K{{LUXpurU#?{pL3~U72Wh8@Vh;6i(R^a)XUU0#%!j zGjES!;^h&7PX6@YdX;xxq<8#2D*(P&a*RJsq+4`IWrWU4Bvbi0BwoSE(;u5ye!S{K z5M}XdY`NMQQ0g;Q8(Oj1eN(@+K5Jv@dsE+%t};J}yYwWq+tl)eHAq`cDM%LX9T)61 z!bR=e`X&a|-ZWAlkP;gD2yRsT*p{~e4An2SB@!13m+z_c92qt>LVHP!qRP?0#%vjL zw@GK?VHFWfgw@i+Xq?2mN9@awmF4IUpE(}Ff|9xX(5-VWZhY0Ur3BCeA#j~HX=ABU zFkFxUK(}&G6%T?6H0=Zl5npGPiP5KS{3+I<`?baNE1zTM-EL;a7q6YNVoTG-h~M;+ z@0S~fMPbTXcDmd^DZQ`PIVweQl74H`%PPx06*=@Ca{8eRW~pf(0;^VZFT;!m+-N9= zI4MUSy&8^tSg>OVF0M~_b8Rx%1#aplXHrY^{p)r&t8_%bJ}m_m*`$iLNO-nYb^(!5 zE%<~Dm9C+;#FLKs@M5&SZ9K*4@tXXLoMuIKhecK>rmJ+aT7 zn5oRPnbPt-wyS#EMPm(RF$F`nyyU~EaPw3N&z0MDPyC+I$?9=Ar4*MoPMN8WizMr( z)p^7Ls4`bP`4GyU2bDV?-wF$8wj)szFH1w@tje=xJTx{G;sd-%*^f>V*9AlkIqBn8#xOwHRsFvu`R%$tZn{2)Xm)+D^^S`X&Tj~g;*jg0Umv~ z3GJLXs45Mm_lQZtH+|{LYOWX{(j8D;v7Xc{!c^fIfRIqrkTq zO}B6Hr$-n)gNPp!-n z$AKtt2zBqQvx*bBpCR>@vyNjJgHKInvSl2f;%jy3<30PLY(~|fQL5s7*}7AAvA%RimNKYj7<$=Kx4P^h9|CDa5IU?D7o~gEPd2^L_ktnmU9Bj9W_H}>(;QEiAWMS zFE>0PqCuf3IPUFIp4`4CG&WorG_}If%UU^q8ej~iOKR-V?*ODFRsCq3t(x+yOU86j2Rl_|eGdzN zfNax^X27K-^>Js1+cTS;Y0FM21<1l9pD@g)&bO~$Bgj1{YZ{wDP{Zx!w6Y<7SoC{* zd)cyJdf^)kTIC_Fk6rE_yQu090ry5L(4P;cZc;2R6E{q*XmD}5E{Hp82|S#`av}Wo z)>Zu7rgDy7d^7|SpQBr5b2o_>(@VKXBg`Yob7dTU{q+t=i$-eLrulT1Fx$&-`_Xv8 z_7S99HdSJ)90G-z`78dcKyKQjE4IJdbMAer@X+Nd2u2!TLdYH-h1+n0Z*!Y{O=dPa zYkl`bjhkbHb@6CvD1l|>sT-Hb1nYednsxqKgJY`FeEXJe$Ddu0=9PTj!~VIhd0e$5upPnj4gm*yR&J7Nc57Q!O2Y&@K&eJ9C>o^Ca^LT(SxNfskE zcTiT-gvcjT4T3jpwBt75eU#x{(aLG)cLgw?-PaSN*j_k{vE=<}#WZ+p~~mnZ30 zf*KAT**0$V(8R(*(z$!v`qL#|I?@>{(g87GRl!)RKDjN%(ijBlcV0lfK|`Zo+ECCk zbQ}oF*2@#`tybT&p2j>27 zYzDdZk8VOTk@LSYu{5=JdAGzKS)ZKG8~-lyvggW`kxcl1HUs-xmj{+hh~)x z;vF(zsP>TTS*z72t8|xKMA1pj9MsW#iy?y+&`{|hXA=m_um`=Q&%EMXuOsO6J%O0g z)q0~zNazB^YA%^(7+)IId7sF*ev=NM&B*Iz?Fv6VX=oX8XJLdfSi7PCmjEEI>r74Hd14oxeP&|Gf80!F(_M%K`4Et$utZ-neTr>2`{$s`I zpZFXFd$j2gp8Gwg*CgK5%wRe{QW)Tim$V$T=12R@;ij*31w^QO4%J12c8l>QHup!Y z@@}f;SPU>T=oB!oUKz?-XG4=;eF5AlV6B5ijYMN1!D^Rwdp;{y6V-R(4U4S0rlU;mxPL#O;@LQuA&tF0a3F z-lcbS;!4=t=iiXGsR%-DaevVpdpa(ifrC0m=$_=Jc_29;i`8R^rc zAt1k$c-Y~9)o^D56hzqz*yn%l_}Ep?8|;Tnd}?CB5r|6HnR+tTGa``SK_O|RSJM)? zI!LKCkmvA(k0RK0<>raQYFz=t!t0cz9Nwe>`#Rh5ir)MZRo2r;ACqWa?iqeR@8Oo~KB@*Q=G=%r%bj58QN z^9aXrnS~p)%^}s8oR3s2q8ZmSaOcW*rIrg@>h}bgFZ7o8;qE<>qEk{X3fL-s-&fxf z2@+Ycl+yoThY;P24@~|2NXPnf0LM(@aAP`P&80`(y`igksSMN54*o!m|N6th4r=qQ zWBRO#KF%SX)>DX^n0L4hq%8Be(n9%A{k8$$H$@2cXeOT2|^f)1?N=w9DFn7B>J09=SjTu#{_g=$AAL&=xmKs5y(u7nGph%Gf&{Yd)I5 z#wf%Hzz^+LY-SlMgEbp#jw^05+A1hU-81AEij4#vf|C~BR?_5S)dI=KIR{vvtFwL> z0xwZ7$4&*HprVSDcpEjs=s6j@J#Xdg&pdg`_~mEO?!;bI6S#Cvw8_Nh*4~zle%Xa7 zt2W>x=o!++0yEg<;TUnk9}$tW;qD+NF>WkfTb~}OP@Dcr+Bp7q-19j9xBb86t1g}n zIa|L1I{bF5c^*(}&lSG$UFt0sT!uca$Cw>YXpT*W3CWsj^chB6dFTzg7R8sTurY6* zb(oQ@NJdYbMM-qYXApk7Zmn-`i?YgXo7zQ8^qEKXK1l52mYUbzeH8Q;^WHig+A3-G{rJlzA#Y1j_F^uZ zMWHXf3#2f zXBo>gGSkgMmzgxcu8vyIVomCQJ#8NK`bB#~*^=1>>LPQy>~=hUQBx= z&cQp$XxNSBHxBYXNvkiJ5(`h-T11t~xS#Rdrk{K|4z>+AaOjqA4V>lZRCzlz7TFi% z8M|^IaXSbx0>icyGq!$|3FhV^4RC_avJ}wlyyq;TGRl*twO6eQP|jJB@>zk+!Q~+6dj?UFYn;o)BmJG7>#?m7$S!kD zueLs8O?};|9swB@_f)a?eE9M?d!uq|_|TpF1Xa4rjF~x0&scKDrY8C~hxu7*hi?gr z3hY9EJ%L~;vHjFM_zL&+z!VI!kEQ`uPd3ATzh7d%<>utqKhq>9xFCNGT{jN?gxZtY z@6u&ykFe%E{822KNvHIHxZclc%ouD;lWZo7%rC@3xk5!DNA{nlrg(Z-2I| z`i2SHC^jO|3AMyqyEhQsnq1z_b8eS*OPZ0C1!JG<1VZ)nUg zV%&eOf==w@%G!H7EE%xWE*1h;CeW3CD#*jCE@-L?+%Nd!dXRxF%Y^WxB4EuIoiHiM@`%imxSX30h$Uj!#7O5Wz6Tiud1=eDFtzm_S@h2nm%GQ4b zV>V&`17+*qR?t1D3oYFSDSLm7Vcvy5 zhR#@rE1HVzL5Rj(Q5boTLCQ&RxA+q7ISaLKj3oJ|i*o%%aU=_8kxpw7+Q~{Be|&hv zw|vvyn{$d>D~6!!82J#BFcG9%&(iW{7P__VvL{J1kbK0;_`Y{4t0uPzfs^;YS;Oqf zOe8>4G*y2~DFZ=8WtF)_#ZBYwg~>yFVe#?&+#S-%x&SKm=zfT7Jbo{KLV6urVzYOe zMnuH~+Pj8$h}ypstiQ3|Rmcf(MZK7^cdUIkl)17lE$c2)sqvo)>$|*% zhy>|bbD1lNHPpe1ka7dIx9>!;T};_Kef#rLj1=ejCE@e*wtKCq4_;kG#q3E?PzJ!j zTl_38OpTT|f|A2i$b_^h={1kg!0vFLbvhJln z6+ZAMOGaf#2bVCcQ|2^Gi}{_Wke3O4L|eDmTi@(brK&sCv@ zF%|BdInT_)6Z3VW7;&RR_PxSxdkW@6`7;)_X3p56#;vQU648UwCUD8Urlb7gvf%C% z`bDK4``2p9l!0(WuzV>hRN?lwU>h;U+(wJB`8Z~?Nvh^t&DZQA(5wTlnq1_(y3kh> zaG2)Aa31{DDF!%cHrl0aOeF-d-2!aVdz>O&q58;`4hqttGV2^oRuj0eMJ9u!6gvSq_6ogg$ImTuNxz$nxx@J zYMyYtCYS9JS3cCnGmi#U%V7Y}yln~3j=UW))tuiuq_r#=_(0JU0vHsl-GHKkpw#9b zwP3qY$`Y8qdWP-rK1Vu5^EYyn?CUO`5IaT5WqAEWqM^35%&j~OCO)x&lJ=~?6T#>L zzrq`LxEde~B0yY{YLy7ii$EMIVZp1*7XXXD--U?OHu&g>SOYHtsJ4KvA;w)e3KqW64J~bzG+#N9Z;}Pb*Xh2$-ha+H^{M}wE=6*2t!jSs$4t}5M()>s-+m&?r^y*~e zKw*1r3;gK9Okk6F7n^x-(XHL+&SNF&$Z8oZr&M@%7_Go?W!Z;Na~T!^nAg0x)d=#vMpysgO<%gMtveJyM#Y%Z z6cv$&NkW!Wp$2$1Xujic6x3^d;qn}dk{A=`2g@4vz0{8-5v+4l3+8TBX)*0$F-0OC zPK&z^m*gX~@7^jc2d_?OX#Q(VrGERb6EME+;^JfTt8=$Gn+B47=YrjrkkC8-i3w)O zX7}#~RHhrZ{?XG|a{5o8NQoju_*G1&T3`qe-eUfQ806o6PoJ3`lIbt=gO-*A!zrp|${gxhU$V1%;44<$ zumA_zIu(~9Pj$_>nV{gYiN}Ps2xG490D$DE+OJa6l3gj1ue$BdNBqytErW>c2vDF# z8{cCO-FJ_PJw$}^0Dt65dFq(!8ot3!_L)t9lPQ#GuuN(XBU+rE> zjYKYL1u(~D zclm|}+p>Q*nocXOMdWR;c={GCjA~7F3~;(p&9ApcoUOe^$oKOjJ4aZmfFRf@o9hPz zR<*tD(=gVTy+>2|1M>IlBGtBnh{g3JlI7J{Q#rYnbqzDicj>fv4>FtHFQ+K^Q=|5* zTV=J0zUh7FHdtEB`@=msyTz35n{)%q5`CA`^kJ;XNhzRgUf%0aD#{9|z06HIebVSu zGDU~7A2avfMZwwt@p`Y0J8}L`%Zd$&^HBo2g)pZBtQC@!Q7@REIrtKF7bCgEuYaCxqAo4zK|5= z70h?4xt!L5n)GPUmU0oTX3tMXP$xIz#De>&BUf`6s23UCC~x}SeQ+}O2KJ5 zUxeDZRYfkjl^0b+jED-Th)UPc3@C?E3M6>zLLpf1T1xg2sxD!Xpm(}VMXqk2T1M2I z9eO$swG6%~EDa9@8Mxzg;lYtcy3dp~+T7=mjPs(}@Z#nL;R1AvO&L7xBF1?@h3}4N z(oer{sq+snDTz~1@O+(U>0My0!k=5Jo4M}!FqwQEojUN&=(_}xd!E4VeFveF4up80 z-xK?JaT03O8IpEX%;#VUQFmh%@>eWuAh1d1@g%Zl(Kg!mE?;YCqL4JEf0#oO$jymC zS$(wmS&@)5N$w-@=1zAkEG96=?F(YG@1|4`yBfxbUvm129LIe;rEFAAfHxUrC$r`BSL77a1T$b0I_!1@6OeK4k5 z4skuc3(&7DZ-+=dJx%AZ7X53mfW`)LQ--1!F{Yw$1>W4+Tx;n{*f&- zVSZ_hNu49q7rTw@%JPTHzvfuzIHYUPZ{yYW(+P5Y#d-h5)s`5` z{w}UL`nPYtDiaX_36fJLzUY?U6|SMa7k=*TR%rT~3EK~z$XWuk>m0X;dx)<@uh$62 z2#r+t>(041_Q}&F6KAoL$Q|8amJ^Ijy0guU0KY+M+>mGeJC%ZCmHg@-=~to^PYEwH zEFpgFDXgCh-7ynp2Q9Oe=)8!S&ZIe4BV&46Q|&S4TdI^h#1YZSdg0l^Uq)?`Qh1h4?+` zV|@X1ZQurgBAB0n-E~Bn>MLVUcb;t{f$LX+JrfJz>MzxBkCvIcbwhSFv4w`3zJoa~ z3qUH4Fv4>whTz(Ol1C<1!9S2<6EZ9_A7-h9FKDbE&lXUprQFglV8)$T&4iOpVRs4RoJiCH9BFW4x zqwg8?D(1%5iPx;aImv?IP}0hQ7;34)=ldjvm_r&WN1HreS2I)wHpuE&j~zU`&L2j~ z#s;2Mq3+@&uyOV?e^A*@3+fHfGLX*Z10vk)NEQbhH#Sfgh*9G!a8Fw5;ssoygR2Q& zzL!_m@>A0?xOrQ++sw21u%#wG(P#Dh=iy8vc5%GcUli=fjwWePGj^xaB(E`OjR(0s zTe4?n$yEqOvw>nRz9VPzOYB*0)w0p2$rbVSx*|vb{5(5^wc#^qo-=&@mTye-)YtCIGWxx&ThNjl1i07QR^T<8v`>88bRduR+NjjAFs2nE#!A{*{o^S{TRAU#8# zH+e&Au%d{8FPW3+-$gAiuIe{6Pnizc-;zlg)HXWnU;dNyCYdZZ<0Y?+e`KTHFN~6yixYE}imTK!v`hjaKP65R-EBe|wN0PPj3 z?(?zYB$W@Q!B7;;#mw2s&KoI{YnRWbdY`*(4FW8MPfC29*#`YVk!9ZFYjOkZy;Rw8 zP4W5B7Z-ZN6LZP+fh zPhL!*%Ul032Uw1CHUwm;&#^mZLH7*UAUMpY;l9-d^=rDh&+Cvfl=L*oW~pIw@I$D~ z^PI9Z;oDN|1e#7R$|GIg035$FT#IWM`gFUzjfGp3pXCNB$davN#x*Y`&r^YfUr%N< zR)vDUuN3;d9QPK%?4W{9$so`scq(Jjg_C{N_EX6~z?9Zp_>Y%g@Q~MyWWtvGuJ!u3 z#m~1Q6&Spt&_$V0@LOr4)V_Si#9A%-V~-n4Ir4ipsVsliwA?Nf!};9i+WpM524?x{ zvT@K(>WxMYOSO{yQCq_)p&2e)kTrf0ARPx|=%whRpy*hNvwI=cmE~)DfsuD6?wm5_ z)S74_nxB4gz|N#*)_lfZ-^XG1BHTG7yI(!c*X|CbPk*j5&?RIv?PW{cJUdf{{O&HB~FW#q8;+ z?gmol;ttKZ8b^9Q?Vapp$B+DRyRo4J4VKj9&-tTF@C~iAhB0mH&O#m5k?0)G zfK`-X{PhH}xp$YerA8FUV97_1%1e{ch2w+MANXpLZW%x)n%!8vhqo3d*~!5@z(KYKkNg(1T9aaC&aX}Gb9{TwNdTXBh#Zpc6m5NMP~dqy zi&cvCp7(zJ6~Gb`YfMBZPnQQ_@0V^r5E2r4FvI^ADW1}wJwi#6ic6&IV;LAI5%YTT z{iA8dg5IDUV|=Tf>kxRT`t10)-1UH<=utRlTY2;$(6dO?g+`@MYR^N6!l zf$PtYx(Rhmb-1RTiv&w+ZP)R1-7V^e#i0f&(v#9t^EC}tnH&L~84mJEjp53S0iteP zp$$KZ)=FY#F1J^0y;Tu*jI_4tAZ(}#llC9I!=}4yGJHc9?LGOie*=73X^!7}94{%` zyR4AbJ%*KcQNGUjN6+MEXq2ld)G<`lSL0MPdmWil-o*^M!p_B!o*aAS$egD?qE3KpQsNaOAPr6#ohoiDV4-`c_{Na*liRuD<;l^1_SL% z%3PUcD$=yNxExb+DX3N%t_Litcq)8}wE32iIYzjWLGFm^dUBUH@t;#X$l}#A)ANUa z_Q2(7vvC1RyYj{f1^+PwxV-z{)i(U&ZDQdWmFo822^pS$>Rgxm|Jh>xA0)B=v*7ms z!X^L6mHs~lQt^TIc0nGEWLSBuNxH|2@lY(eJ>3j++IJ;jBd(!j@6p42A6v=7~ zDij1i6G8e7AK&o0q99O7lnS3Www~Ey4zXXD>6JyTF%Q0yUIB()CDW$n%6<0C(vB~y zcz}?3X$sG(Gcps`Z|X;S6OC_R?%!<~anEfvC6;cjMQ#t8GCQVzO+vyKA?X_kz6@T# zHw#%QPw&1tK^PN2kucN!Hc?H23ZzCXHK%1uHhKnC1@$x8Aj0F z09}eK#%rSJ*&?_vWH8mC&QAZY0vt!8zkHz?#m;R!_Gu6{KS00P@?@nPuukFC!pWOO3 z{7jnLq5+gHnO&2VCNmnL-*mA7cur5%UEsd{;y^RI(9pnb5vcQQrLiZl%n{{m>~Z~% zlQwyjP+sH#G&AbC&0KCs*U%D(nV_$(8nM3x? z*@7X@^Kx6~?!g4OV8B{hiF3L&rYBogx2vyw&I)cyl<2=?9Q60y0yZivEzDigZ&0w3 zW24~d{Y;S~&(-9NR5$_+Im>$*$R>Sc9QqZU3uN+k|F4YLP1b)tZ}diXwqLIOMKM?I z>ox0{KpXmw?&##re*zNQK;Ha&RQA84zwAh>@FbV*{2P?{ApZHMas1dB_V2V zIE5Gx^rP`RRNxStUwX??av9YK+KesahM|HdQVo!;WcFBma5dvTz%%i&Ao9w-i9~6f zM|O~u!e0~|gU#TP{x&v$0*1Bf+H#jR`5dAc6J=NSO8eLDNH9o_&AFO2&i`^%z!XnG ztDsu3h{YghaL6f>Z!KTfT1df7HQF%BE-LQq!ET5#)KrR%biDjfa|TvFHIm2Lu6X~$ zEtApASau3Z90QIvEA9^qgX3YQ#oWtTYT3anyWWCKADbGKq zOaSNkXjJp=Sz{M4=(e#7R+IJ(1Y$jE;u~zwmzE~Y|T*hvBitLACpydfi-Lk^`Y}0jla{!YykDS?MvO} zH($J}^vS-%Ao!JTWbr9V=tnt5bpJG@9t68vo2v;(9dC3u>x}Q^YD!5KH)bA6=Z~7Q zd#Im-3GwtzZIRV8rmY7Rp2mrkm27et@F`F|aXEiD$ckmGDBIZFWz zMtz#*9xu75M4z;4&}jERX1hp)Ex=J1SEk4`Q-33FTo zqRChWnWFZm$Eb((^F^V?qdVHl84IG&oL#R0)DsKghksEFz1=wY{DaS=%q#jYiiDjx zu?_aU-#=AlW5-yC*(m)^+2m$zO$JEUCwH?p@gY8ecMs)P`t8F3gh#pMn0Rta#GYer z-Du4AfRJE|`S(idm2M|5zt=f|0255cuFl|59QGNT=J_jzTVJr9J$VNR--e?7!6fQb zzjKfHmFxYCeLSK7B}U6Hx3cfq*R
oqjg{{0gE|Ie;77?31XvREF8f+S69j~U3m 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

utX@3H~68y9> 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

_Dw}S)QjW|qe&Q0^rjoR$m<~sF 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 ' + + shlex.quote(str(base / 'logs/cron.log')) + ' 2>&1 ' + MARKER) + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--remove', action='store_true') + args = parser.parse_args() + os.umask(0o077) + if not args.remove: + load() + if not (BASE / '.venv/bin/python').exists(): + raise SystemExit('Run install.sh first') + result = subprocess.run(['crontab', '-l'], capture_output=True, text=True, + env={'PATH': '/usr/bin:/bin', 'LC_ALL': 'C'}) + if result.returncode == 0: + previous = result.stdout + elif result.returncode == 1 and 'no crontab for' in result.stderr: + previous = '' + else: + raise SystemExit('Cannot read crontab') + (BASE / 'data').mkdir(exist_ok=True) + (BASE / 'logs').mkdir(exist_ok=True) + backup = BASE / 'data' / ('crontab-before-' + datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S%fZ') + '.txt') + backup.write_text(previous, encoding='utf-8') + lines = [line for line in previous.splitlines() if not line.rstrip().endswith(MARKER)] + if not args.remove: + lines.append(entry(BASE)) + updated = '\n'.join(lines) + '\n' + subprocess.run(['crontab', '-'], input=updated, text=True, check=True) + if subprocess.check_output(['crontab', '-l'], text=True) != updated: + raise SystemExit('Crontab verification failed') + print('CRON zaktualizowany; kopia poprzedniej konfiguracji w data/.') + +if __name__ == '__main__': + main() diff --git a/meteo.py b/meteo.py new file mode 100644 index 0000000..91255df --- /dev/null +++ b/meteo.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +"""Read-only Modbus TCP weather collector. Python standard library only.""" +import argparse +import fcntl +import json +import logging +from logging.handlers import RotatingFileHandler +import math +import os +from pathlib import Path +import socket +import sqlite3 +import struct +import sys +import time +from datetime import datetime, timezone +from zoneinfo import ZoneInfo + +from settings import load as load_config + +BASE = Path(__file__).resolve().parent +FIELDS = ("temperature", "humidity", "pressure") + + +def utc_now(): + return datetime.now(timezone.utc).isoformat(timespec="seconds") + + +def read_exact(sock, size): + data = bytearray() + while len(data) < size: + chunk = sock.recv(size - len(data)) + if not chunk: + raise OSError("Urządzenie zamknęło połączenie przed końcem odpowiedzi") + data.extend(chunk) + return bytes(data) + + +def read_registers(settings, start, count): + unit = settings["unit_id"] + function = settings["function_code"] + request = struct.pack(">HHHBBHH", 1, 0, 6, unit, function, start, count) + with socket.create_connection( + (settings["host"], settings["port"]), timeout=settings["timeout_seconds"] + ) as sock: + sock.settimeout(settings["timeout_seconds"]) + sock.sendall(request) + transaction, protocol, length, response_unit = struct.unpack( + ">HHHB", read_exact(sock, 7) + ) + if (transaction, protocol, response_unit) != (1, 0, unit): + raise ValueError("Nieprawidłowy nagłówek odpowiedzi Modbus") + if not 2 <= length <= 254: + raise ValueError("Nieprawidłowa długość odpowiedzi Modbus") + payload = read_exact(sock, length - 1) + if payload[0] == (function | 0x80): + code = payload[1] if len(payload) > 1 else "brak kodu" + raise ValueError(f"Wyjątek Modbus: {code}") + if len(payload) != 2 + count * 2 or payload[:2] != bytes((function, count * 2)): + raise ValueError("Nieprawidłowa funkcja lub liczba bajtów Modbus") + return struct.unpack(">" + "H" * count, payload[2:]) + + +def decode(registers, start, sensors): + values, raw = {}, {} + for name in FIELDS: + sensor = sensors[name] + number = registers[sensor["address"] - start] + raw[name] = number + if sensor["signed"] and number >= 32768: + number -= 65536 + value = number * sensor["multiplier"] + sensor["offset"] + if not math.isfinite(value) or not sensor["min"] <= value <= sensor["max"]: + raise ValueError(f"{name}: wynik {value} poza zakresem konfiguracji") + values[name] = value + return values, raw + + +def collect(config): + settings = config["modbus"] + attempts = settings["attempts"] + if not 1 <= attempts <= 10: + raise ValueError("Liczba prób musi wynosić od 1 do 10") + for attempt in range(1, attempts + 1): + try: + # Read only configured registers, including widely separated addresses. + registers = {} + for name in FIELDS: + address = config['sensors'][name]['address'] + if address not in registers: + registers[address] = read_registers(settings, address, 1)[0] + values, raw = decode(registers, 0, config['sensors']) + return utc_now(), values, raw, attempt, None + except (OSError, ValueError) as exc: + logging.warning("Próba %s/%s: %s", attempt, attempts, exc) + if attempt < attempts: + time.sleep(settings["retry_delay_seconds"]) + else: + return utc_now(), None, None, attempt, str(exc) + + +def save_result(db_path, checked_at, values, raw, attempts, error): + with sqlite3.connect(db_path, timeout=10) as db: + db.execute("PRAGMA journal_mode=WAL") + db.execute("""CREATE TABLE IF NOT EXISTS readings ( + id INTEGER PRIMARY KEY, + checked_at TEXT NOT NULL, + status TEXT NOT NULL CHECK(status IN ('ok', 'error')), + temperature_c REAL, humidity_percent REAL, pressure_hpa REAL, + raw_json TEXT, attempts INTEGER NOT NULL, error TEXT + )""") + db.execute("CREATE INDEX IF NOT EXISTS readings_time ON readings(checked_at)") + db.execute("""INSERT INTO readings + (checked_at,status,temperature_c,humidity_percent,pressure_hpa,raw_json,attempts,error) + VALUES (?,?,?,?,?,?,?,?)""", ( + checked_at, "ok" if values is not None else "error", + *((values[name] if values is not None else None) for name in FIELDS), + json.dumps(raw) if raw is not None else None, attempts, error + )) + last_good = db.execute("""SELECT checked_at,temperature_c,humidity_percent,pressure_hpa + FROM readings WHERE status='ok' ORDER BY id DESC LIMIT 1""").fetchone() + return last_good + + +def write_status(path, checked_at, values, last_good, local_timezone): + status = { + "status": "ok" if values is not None else "error", + "label": "Działa poprawnie" if values is not None else "Błąd stacji", + "checked_at": checked_at, + "checked_at_local": datetime.fromisoformat(checked_at).astimezone( + ZoneInfo(local_timezone)).isoformat(), + "values": values, + "last_success_at": last_good[0] if last_good else None, + "last_success_values": dict(zip(FIELDS, last_good[1:])) if last_good else None, + } + temporary = path.with_suffix(".tmp") + temporary.write_text(json.dumps(status, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + temporary.replace(path) + return status + + +def main(): + os.umask(0o077) + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--quiet", action="store_true", help="Log to file without terminal output") + args = parser.parse_args() + (BASE / "data").mkdir(exist_ok=True) + (BASE / "logs").mkdir(exist_ok=True) + handler = RotatingFileHandler(BASE / "logs/meteo.log", maxBytes=2_000_000, + backupCount=5, encoding="utf-8") + logging.basicConfig(level=logging.INFO, handlers=[handler], + format="%(asctime)s %(levelname)s %(message)s") + with (BASE / "data/meteo.lock").open("a") as lock: + try: + fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + logging.info("Poprzedni odczyt jeszcze trwa; pomijam uruchomienie") + return 0 + try: + config = load_config() + ZoneInfo(config["timezone"]) + checked_at, values, raw, attempts, error = collect(config) + last_good = save_result(BASE / "data/meteo.sqlite3", checked_at, + values, raw, attempts, error) + status = write_status(BASE / "data/status.json", checked_at, + values, last_good, config["timezone"]) + if values is not None: + logging.info("Pomiar: temperatura=%s °C; wilgotność=%s %%; ciśnienie=%s hPa", + values["temperature"], values["humidity"], values["pressure"]) + else: + logging.error("Błąd stacji: %s", error) + if not args.quiet: + print(json.dumps(status, ensure_ascii=False, indent=2)) + return 0 if values is not None else 1 + except Exception: + logging.exception("Błąd programu lub zapisu danych") + if not args.quiet: + print("Błąd programu. Szczegóły: " + str(BASE / "logs/meteo.log"), file=sys.stderr) + return 2 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/publish_lemmy.py b/publish_lemmy.py new file mode 100644 index 0000000..1d48f35 --- /dev/null +++ b/publish_lemmy.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +"""Publish a single weather post and uploaded charts through Lemmy API v3.""" +from datetime import datetime, timezone +import fcntl +import hashlib +import json +import logging +import os +from pathlib import Path +import uuid +from urllib.error import HTTPError +from urllib.parse import quote, urlsplit +from urllib.request import Request, build_opener, HTTPRedirectHandler +from zoneinfo import ZoneInfo + +from settings import load as load_config + +BASE = Path(__file__).resolve().parent +START = '[meteo-start]: #' +END = '[meteo-end]: #' +LEGACY_START = '' +LEGACY_END = '' +CHARTS = [('temperatura', 'Temperatura'), ('wilgotnosc', 'Wilgotność powietrza'), + ('cisnienie', 'Ciśnienie atmosferyczne')] + + +def read_json(path): + return json.loads(path.read_text(encoding='utf-8')) + + +def save_json(path, data): + temporary = path.with_suffix('.tmp') + temporary.write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') + temporary.chmod(0o600) + temporary.replace(path) + + +class ApiError(Exception): + def __init__(self, status, code): + super().__init__(f'Lemmy HTTP {status}: {code}') + self.status = status + self.code = code + + +class NoRedirect(HTTPRedirectHandler): + def redirect_request(self, req, fp, code, msg, headers, newurl): + raise ApiError(code, 'redirect_refused; configure canonical instance address') + + +class Client: + def __init__(self): + self.secrets = load_config()['lemmy'] + self.base = self.secrets['instance'].rstrip('/') + self.token_path = BASE / 'data/lemmy-token.json' + self.token = None + if self.token_path.exists(): + cached = read_json(self.token_path) + if cached.get('instance') == self.base and cached.get('username') == self.secrets['username']: + self.token = cached.get('jwt') + + def request(self, path, data=None, method=None, content_type='application/json', auth=True, retry=True): + if auth and not self.token: + self.login() + headers = {'User-Agent': 'MeteoStation/1.0', 'Accept': 'application/json'} + if auth: + headers['Authorization'] = 'Bearer ' + self.token + if data is not None: + headers['Content-Type'] = content_type + if not isinstance(data, bytes): + data = json.dumps(data).encode('utf-8') + request = Request(self.base + path, data=data, headers=headers, method=method) + try: + with build_opener(NoRedirect()).open(request, timeout=45) as response: + return json.load(response) + except HTTPError as exc: + try: + code = json.loads(exc.read()).get('error', 'api_error') + except Exception: + code = 'non_json_error' + if auth and retry and (exc.code == 401 or code in ('not_logged_in', 'incorrect_login')): + self.login() + return self.request(path, data, method, content_type, auth=True, retry=False) + raise ApiError(exc.code, code) from None + + def login(self): + result = self.request('/api/v3/user/login', { + 'username_or_email': self.secrets['username'], 'password': self.secrets['password']}, + method='POST', auth=False) + if not result.get('jwt'): + raise ValueError('Login did not return a token') + self.token = result['jwt'] + save_json(self.token_path, {'jwt': self.token, 'instance': self.base, 'username': self.secrets['username']}) + + def upload(self, filename): + boundary = 'meteo-' + uuid.uuid4().hex + header = (f'--{boundary}\r\nContent-Disposition: form-data; name="images[]"; ' + f'filename="{filename.name}"\r\nContent-Type: image/png\r\n\r\n').encode() + body = header + filename.read_bytes() + f'\r\n--{boundary}--\r\n'.encode() + result = self.request('/pictrs/image', body, 'POST', 'multipart/form-data; boundary=' + boundary) + files = result.get('files', []) + if len(files) != 1: + raise ValueError('Image upload did not return exactly one image') + entry = files[0] + return {'url': self.base + '/pictrs/image/' + quote(entry['file'], safe=''), + 'file': entry['file'], 'delete_token': entry.get('delete_token')} + + +def merge_body(existing, section): + start, end = (LEGACY_START, LEGACY_END) if LEGACY_START in existing else (START, END) + if existing.count(start) != 1 or existing.count(end) != 1: + raise ValueError('Weather markers missing or duplicated; post left unchanged') + before, rest = existing.split(start) + middle, after = rest.split(end) + return before + START + '\n\n' + section + '\n\n' + END + after + + +def render_section(status, images, now=None, config=None): + config = config or load_config() + now = now or datetime.now(timezone.utc) + checked = datetime.fromisoformat(status['checked_at']) + age = (now - checked).total_seconds() + healthy = status['status'] == 'ok' and 0 <= age <= config['stale_after_minutes'] * 60 + values = status.get('values') if healthy else status.get('last_success_values') + def value(name, unit): + return ('—' if not values else f"{values[name]:.1f}".replace('.', ',') + ' ' + unit) + # Non-breaking-space paragraphs retain vertical spacing in Lemmy Markdown. + lines = ['## ' + config['lemmy']['heading'], '', '\u00a0', '', '\u00a0', '', + f"**Temperatura:** {value('temperature', '°C')} ", + f"**Wilgotność:** {value('humidity', '%')} ", + f"**Ciśnienie:** {value('pressure', 'hPa')} ", + '**Status:** ' + ('🟢 Działa poprawnie' if healthy else '🔴 Błąd stacji') + ' '] + stamp = status.get('last_success_at') + shown = datetime.fromisoformat(stamp).astimezone(ZoneInfo(config['timezone'])).strftime('%d.%m.%Y, %H:%M') if stamp else 'brak' + lines.append('**Ostatni poprawny pomiar:** ' + shown) + if not healthy and values: + lines.append('\nPodane wartości pochodzą z ostatniego poprawnego pomiaru.') + lines.append('\n' + config['lemmy']['update_note']) + for slug, title in CHARTS: + lines.extend(['', f'### {title}', '', f"![{title} — bieżący miesiąc]({images[slug]['url']})"]) + return '\n'.join(lines) + + +def publish(): + os.umask(0o077) + config = load_config() + settings = config['lemmy'] + if not settings.get('enabled'): + return None + (BASE / 'data').mkdir(exist_ok=True) + with (BASE / 'data/publish.lock').open('a') as lock: + fcntl.flock(lock, fcntl.LOCK_EX) + client = Client() + identity = client.request('/api/v3/site')['my_user']['local_user_view']['person']['id'] + state_path = BASE / 'data/lemmy-state.json' + state = read_json(state_path) if state_path.exists() else {'images': {}} + destination = {'instance': client.base, 'author_id': identity, 'community_id': settings['community_id']} + if state.get('destination') not in (None, destination): + raise ValueError('Destination changed: archive local publication state before using a different account or community') + if settings['post_id'] and state.get('post_id') and settings['post_id'] != state['post_id']: + raise ValueError('Configured post_id differs from saved state') + state['destination'] = destination + status = read_json(BASE / 'data/status.json') + manifest = read_json(BASE / 'wykresy/manifest.json') + if manifest['month'] != datetime.now(ZoneInfo(config['timezone'])).strftime('%Y-%m'): + raise ValueError('Chart month is not current; publication deferred') + for info in manifest['charts'].values(): + if info['last_success_at'] and status['last_success_at'] and datetime.fromisoformat(info['last_success_at']) != datetime.fromisoformat(status['last_success_at']): + raise ValueError('Chart and measurement timestamps differ; publication deferred') + post_id = state.get('post_id') or settings['post_id'] + if not post_id: + result = client.request('/api/v3/user?person_id=' + str(identity) + '&limit=50&sort=New') + candidates = [p['post'] for p in result.get('posts', []) if p['post']['community_id'] == settings['community_id'] + and p['post']['name'] == settings['title'] and any(marker in (p['post'].get('body') or '') for marker in (START, LEGACY_START))] + if len(candidates) > 1: + raise ValueError('Multiple matching weather posts; manual selection required') + if candidates: + post_id = state['post_id'] = candidates[0]['id'] + state.pop('creation_pending', None) + save_json(state_path, state) + elif state.get('creation_pending'): + raise ValueError('Previous creation outcome uncertain; refusing duplicate post') + if post_id: + current = client.request('/api/v3/post?id=' + str(post_id))['post_view']['post'] + if current['creator_id'] != identity or current['community_id'] != settings['community_id']: + raise ValueError('Post ownership or community mismatch') + if current['deleted'] or current['removed']: + raise ValueError('Post has been deleted or removed') + merge_body(current.get('body') or '', '') # Validate markers before uploading. + for slug, title in CHARTS: + path = BASE / 'wykresy' / (slug + '.png') + digest = hashlib.sha256(path.read_bytes()).hexdigest() + if state['images'].get(slug, {}).get('sha256') != digest: + uploaded = client.upload(path) + uploaded['sha256'] = digest + with (BASE / 'data/lemmy-uploads.jsonl').open('a', encoding='utf-8') as archive: + archive.write(json.dumps({'uploaded_at': datetime.now(timezone.utc).isoformat(), + 'chart': slug, **uploaded}) + '\n') + state['images'][slug] = uploaded + save_json(state_path, state) + section = render_section(status, state['images'], config=config) + if not post_id: + state['creation_pending'] = True + save_json(state_path, state) + appendix = (BASE / 'static.txt').read_text(encoding='utf-8') if (BASE / 'static.txt').exists() else '' + result = client.request('/api/v3/post', {'name': settings['title'], 'community_id': settings['community_id'], + 'body': START + '\n\n' + section + '\n\n' + END + '\n\n' + appendix, 'nsfw': False}, 'POST') + post_id = state['post_id'] = result['post_view']['post']['id'] + state.pop('creation_pending', None) + save_json(state_path, state) + else: + # Fetch again to preserve edits made while uploading images. + current = client.request('/api/v3/post?id=' + str(post_id))['post_view']['post'] + body = merge_body(current.get('body') or '', section) + if body != current.get('body'): + client.request('/api/v3/post', {'post_id': post_id, 'body': body}, 'PUT') + post = client.request('/api/v3/post?id=' + str(post_id))['post_view']['post'] + if settings.get('pin_in_community') and not post['featured_community']: + client.request('/api/v3/post/feature', {'post_id': post_id, 'featured': True, 'feature_type': 'Community'}, 'POST') + state['post_id'] = post_id + state['last_published_at'] = datetime.now(timezone.utc).isoformat(timespec='seconds') + state['last_measurement_at'] = status['checked_at'] + save_json(state_path, state) + url = client.base + '/post/' + str(post_id) + logging.info('Zaktualizowano wpis Lemmy: %s', url) + return url + + +if __name__ == '__main__': + print(publish()) diff --git a/requirements.lock.txt b/requirements.lock.txt new file mode 100644 index 0000000..29f7fa3 --- /dev/null +++ b/requirements.lock.txt @@ -0,0 +1,11 @@ +contourpy==1.3.3 +cycler==0.12.1 +fonttools==4.66.1 +kiwisolver==1.5.1 +matplotlib==3.11.2 +numpy==2.4.6 +packaging==26.3 +pillow==12.3.0 +pyparsing==3.3.3 +python-dateutil==2.9.0.post0 +six==1.17.0 diff --git a/run_cycle.py b/run_cycle.py new file mode 100644 index 0000000..13fe21c --- /dev/null +++ b/run_cycle.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Scheduled measurement followed by charts, including after a failed reading.""" +import fcntl +import logging +import os +import sys +from pathlib import Path + +BASE = Path(__file__).resolve().parent + + +def main(): + os.umask(0o077) + (BASE / 'data').mkdir(exist_ok=True) + with (BASE / 'data/cycle.lock').open('a') as lock: + try: + fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + return 0 + import meteo + result = meteo.main() + try: + from charts import generate + manifest = generate() + logging.info('Wygenerowano 3 wykresy dla miesiąca %s', manifest['month']) + except Exception: + logging.exception('Nie udało się wygenerować wykresów') + return 3 + if result != 2: + try: + from publish_lemmy import publish + publish() + except Exception: + logging.exception('Nie udało się zaktualizować wpisu Lemmy') + return 4 + return result + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..44ebea1 --- /dev/null +++ b/settings.py @@ -0,0 +1,61 @@ +"""Configuration validation; never display credentials.""" +import json +import math +from pathlib import Path +from urllib.parse import urlsplit +from zoneinfo import ZoneInfo + +BASE = Path(__file__).resolve().parent +FIELDS = ('temperature', 'humidity', 'pressure') + +def validate(config): + ZoneInfo(config['timezone']) + if not isinstance(config['stale_after_minutes'], (int, float)) or not 1 <= config['stale_after_minutes'] <= 10080: + raise ValueError('stale_after_minutes: expected 1..10080') + m = config['modbus'] + if not isinstance(m['host'], str) or not m['host'].strip(): + raise ValueError('modbus.host: enter device address') + for key, lo, hi in [('port', 1, 65535), ('unit_id', 0, 255), ('function_code', 3, 4), ('attempts', 1, 10)]: + if type(m[key]) is not int or not lo <= m[key] <= hi: + raise ValueError('modbus.' + key + ': invalid value') + for key, lo, hi in [('timeout_seconds', .1, 120), ('retry_delay_seconds', 0, 60)]: + if not isinstance(m[key], (int, float)) or not lo <= m[key] <= hi: + raise ValueError('modbus.' + key + ': invalid value') + for name in FIELDS: + s = config['sensors'][name] + if type(s['address']) is not int or not 0 <= s['address'] <= 65535: + raise ValueError(name + '.address: enter a zero-based register address') + if type(s['signed']) is not bool: + raise ValueError(name + '.signed: expected boolean') + for key in ('multiplier', 'offset', 'min', 'max'): + if type(s[key]) not in (int, float) or not math.isfinite(s[key]): + raise ValueError(name + '.' + key + ': expected finite number') + if s['multiplier'] == 0 or s['min'] > s['max']: + raise ValueError(name + ': invalid multiplier or range') + l = config['lemmy'] + if type(l['enabled']) is not bool: + raise ValueError('lemmy.enabled: expected boolean') + if l['enabled']: + u = urlsplit(l['instance']) + if u.scheme != 'https' or not u.hostname or u.username or u.password or u.path not in ('', '/') or u.query or u.fragment: + raise ValueError('lemmy.instance: expected HTTPS origin without credentials or path') + for key in ('username', 'password', 'title', 'heading'): + if not isinstance(l[key], str) or not l[key].strip(): + raise ValueError('lemmy.' + key + ': required') + if type(l['community_id']) is not int or l['community_id'] <= 0: + raise ValueError('lemmy.community_id: expected positive integer') + if l['post_id'] is not None and (type(l['post_id']) is not int or l['post_id'] <= 0): + raise ValueError('lemmy.post_id: expected null or positive integer') + if type(l['pin_in_community']) is not bool: + raise ValueError('lemmy.pin_in_community: expected boolean') + return config + +def load(): + return validate(json.loads((BASE / 'config.json').read_text(encoding='utf-8'))) + +if __name__ == '__main__': + try: + load() + print('Konfiguracja poprawna. Nie wykonano połączeń sieciowych.') + except Exception as exc: + raise SystemExit('Błąd konfiguracji: ' + str(exc)) diff --git a/static.example.txt b/static.example.txt new file mode 100644 index 0000000..2a71adb --- /dev/null +++ b/static.example.txt @@ -0,0 +1,5 @@ +### Zastosowane czujniki + +| Czujnik temperatury i wilgotności | Czujnik ciśnienia | +| --- | --- | +| Wpisz model, zakresy i dokładność swojego czujnika. | Wpisz model, zakresy i dokładność swojego czujnika. | diff --git a/test_charts.py b/test_charts.py new file mode 100644 index 0000000..28044f1 --- /dev/null +++ b/test_charts.py @@ -0,0 +1,68 @@ +from datetime import datetime, timezone +import math +from pathlib import Path +import tempfile +import unittest +from zoneinfo import ZoneInfo + +from unittest.mock import patch +from test_support import config +import charts +import meteo + + +class ChartTests(unittest.TestCase): + def setUp(self): + self.mock_config = patch("charts.load_config", return_value=config()) + self.mock_config.start() + self.addCleanup(self.mock_config.stop) + def test_month_bounds_dst_leap_and_year_end(self): + tz = ZoneInfo('Europe/Warsaw') + start, end = charts.month_bounds(2026, 10, tz) + self.assertEqual(start.astimezone(timezone.utc).isoformat(), '2026-09-30T22:00:00+00:00') + self.assertEqual(end.astimezone(timezone.utc).isoformat(), '2026-10-31T23:00:00+00:00') + self.assertEqual((charts.month_bounds(2024, 2, tz)[1] - charts.month_bounds(2024, 2, tz)[0]).days, 29) + self.assertEqual(charts.month_bounds(2026, 12, tz)[1].year, 2027) + + def test_gaps_and_failed_readings(self): + rows = [('2026-10-12T10:00:00+00:00', 'ok', 7.0), + ('2026-10-12T11:00:00+00:00', 'error', None), + ('2026-10-12T12:00:00+00:00', 'ok', 8.0), + ('2026-10-12T15:00:00+00:00', 'ok', 9.0)] + x, y = charts.series(rows, 2, ZoneInfo('Europe/Warsaw')) + self.assertEqual(len(y), 5) + self.assertTrue(math.isnan(y[1])) + self.assertTrue(math.isnan(y[3])) + self.assertEqual([v for v in y if math.isfinite(v)], [7, 8, 9]) + + def test_local_month_selection_and_future_exclusion(self): + with tempfile.TemporaryDirectory() as temporary: + db = Path(temporary) / 'test.sqlite3' + for stamp in ['2026-09-30T21:59:59+00:00', '2026-09-30T22:00:00+00:00', + '2026-10-12T10:00:00+00:00', '2026-10-31T23:00:00+00:00']: + meteo.save_result(db, stamp, {'temperature': 7, 'humidity': 90, 'pressure': 1010}, {}, 1, None) + tz = ZoneInfo('Europe/Warsaw') + start, end = charts.month_bounds(2026, 10, tz) + rows = charts.load_rows(db, start, end, datetime(2026, 10, 10, tzinfo=tz)) + self.assertEqual([row[0] for row in rows], ['2026-09-30T22:00:00+00:00']) + + def test_render_empty_and_single_reading(self): + from PIL import Image + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + db = root / 'test.sqlite3' + meteo.save_result(db, '2026-10-12T10:00:00+00:00', + {'temperature': -5, 'humidity': 100, 'pressure': 1010}, {}, 1, None) + for month, expected in [('2026-09', 0), ('2026-10', 1)]: + output = root / month + manifest = charts.generate(month, db_path=db, output=output, + now=datetime(2026, 10, 12, 12, tzinfo=timezone.utc)) + for chart in manifest['charts'].values(): + self.assertEqual(chart['count'], expected) + with Image.open(output / chart['file']) as img: + self.assertEqual(img.size, (1680, 945)) + img.verify() + + +if __name__ == '__main__': + unittest.main() diff --git a/test_generic.py b/test_generic.py new file mode 100644 index 0000000..852e0d0 --- /dev/null +++ b/test_generic.py @@ -0,0 +1,89 @@ +import json +from pathlib import Path +import struct +import tempfile +import unittest +from unittest.mock import patch + +import meteo +import settings +import publish_lemmy as publisher +from install_cron import entry +from test_meteo import FakeSocket +from test_support import config + +class GenericTests(unittest.TestCase): + def test_example_requires_real_addresses(self): + c = json.loads((settings.BASE / 'config.example.json').read_text()) + with self.assertRaises(ValueError): + settings.validate(c) + c = config() + settings.validate(c) + c['sensors']['pressure']['address'] = None + with self.assertRaises(ValueError): + settings.validate(c) + + def test_signed_multiplier_offset_and_distant_addresses(self): + c = config() + c['sensors']['pressure']['address'] = 60000 + c['sensors']['temperature'].update(multiplier=.2, offset=1) + values, raw = meteo.decode({20:65526, 23:450, 60000:10132}, 0, c['sensors']) + self.assertEqual(values['temperature'], -1) + self.assertAlmostEqual(values['pressure'], 1013.2) + self.assertEqual(raw['temperature'], 65526) + with patch('meteo.read_registers', side_effect=[(65526,), (450,), (10132,)]) as reader: + result = meteo.collect(c) + self.assertIsNone(result[-1]) + self.assertEqual([call.args[1:] for call in reader.call_args_list], [(20,1),(23,1),(60000,1)]) + + def test_input_registers_fc04(self): + c = config()['modbus'] + c['function_code'] = 4 + fake = FakeSocket(struct.pack('>HHHBBBH', 1, 0, 5, 8, 4, 2, 234)) + with patch('meteo.socket.create_connection', return_value=fake): + self.assertEqual(meteo.read_registers(c, 10, 1), (234,)) + self.assertEqual(fake.sent, struct.pack('>HHHBBHH', 1, 0, 6, 8, 4, 10, 1)) + + def test_cron_quotes_paths_and_rejects_percent(self): + self.assertIn("'/tmp/weather station/run_cycle.py'", entry(Path('/tmp/weather station'))) + with self.assertRaises(ValueError): + entry(Path('/tmp/weather%station')) + + def test_publish_updates_existing_post_preserving_static_footer(self): + c = config() + c['lemmy'].update(enabled=True, instance='https://localhost', username='fixture', password='fixture', community_id=12, post_id=42) + requests = [] + post = {'id':42,'creator_id':99,'community_id':12,'deleted':False,'removed':False, + 'featured_community':False,'body':'Intro\n'+publisher.START+'\n\nold\n\n'+publisher.END+'\n\nSensor specifications'} + class FakeClient: + base = 'https://localhost' + def request(self, path, data=None, method=None): + requests.append((path,data,method)) + if path == '/api/v3/site': + return {'my_user':{'local_user_view':{'person':{'id':99}}}} + if method == 'PUT': + post['body'] = data['body'] + return {'post_view':{'post':post.copy()}} + def upload(self, filename): + return {'url':'/fixture.png', 'file':filename.name, 'delete_token':'fixture'} + with tempfile.TemporaryDirectory() as tmp: + base = Path(tmp) + (base/'data').mkdir() + (base/'wykresy').mkdir() + from datetime import datetime + from zoneinfo import ZoneInfo + stamp = datetime.now(ZoneInfo(c['timezone'])).isoformat() + values = dict(temperature=23.4,humidity=45,pressure=1013.2) + (base/'data/status.json').write_text(json.dumps(dict(status='ok',checked_at=stamp,last_success_at=stamp,values=values,last_success_values=values))) + (base/'wykresy/manifest.json').write_text(json.dumps({'month':stamp[:7], 'charts':{s:{'last_success_at':stamp} for s,_ in publisher.CHARTS}})) + for slug,_ in publisher.CHARTS: + (base/'wykresy'/ (slug+'.png')).write_bytes(b'fixture') + with patch.object(publisher,'BASE',base), patch.object(publisher,'load_config',return_value=c), patch.object(publisher,'Client',FakeClient): + publisher.publish() + self.assertTrue(post['body'].endswith('Sensor specifications')) + self.assertIn('23,4 °C',post['body']) + self.assertFalse(any(method=='POST' and path=='/api/v3/post' for path,_,method in requests)) + self.assertEqual(json.loads((base/'data/lemmy-state.json').read_text())['post_id'],42) + +if __name__ == '__main__': + unittest.main() diff --git a/test_meteo.py b/test_meteo.py new file mode 100644 index 0000000..5481833 --- /dev/null +++ b/test_meteo.py @@ -0,0 +1,90 @@ +import json +from pathlib import Path +import sqlite3 +import struct +import tempfile +import unittest +from unittest.mock import patch + +import meteo + +from test_support import config +CONFIG = config() + + +class FakeSocket: + def __init__(self, response): + self.response = response + self.sent = None + + def __enter__(self): + return self + + def __exit__(self, *args): + pass + + def settimeout(self, timeout): + pass + + def sendall(self, request): + self.sent = request + + def recv(self, size): + # Simulate fragmented TCP replies. + size = min(size, 2) + data, self.response = self.response[:size], self.response[size:] + return data + + +class CollectorTests(unittest.TestCase): + def test_fragmented_response_and_read_only_request(self): + response = struct.pack(">HHHBBB6H", 1, 0, 15, 8, 3, 12, + 65481, 67, 0, 951, 0, 10202) + fake = FakeSocket(response) + with patch("meteo.socket.create_connection", return_value=fake): + registers = meteo.read_registers(CONFIG["modbus"], 20, 6) + self.assertEqual(fake.sent, struct.pack(">HHHBBHH", 1, 0, 6, 8, 3, 20, 6)) + values, _ = meteo.decode(registers, 20, CONFIG["sensors"]) + for key, expected in {"temperature": -5.5, "humidity": 95.1, "pressure": 1020.2}.items(): + self.assertAlmostEqual(values[key], expected) + + def test_modbus_exception(self): + fake = FakeSocket(struct.pack(">HHHBBB", 1, 0, 3, 8, 0x83, 2)) + with patch("meteo.socket.create_connection", return_value=fake): + with self.assertRaisesRegex(ValueError, "Wyjątek Modbus: 2"): + meteo.read_registers(CONFIG["modbus"], 20, 6) + + def test_closed_connection(self): + with self.assertRaises(OSError): + meteo.read_exact(FakeSocket(b"\x00"), 7) + + def test_invalid_humidity(self): + with self.assertRaisesRegex(ValueError, "humidity"): + meteo.decode((73, 0, 0, 1001, 0, 10202), 20, CONFIG["sensors"]) + + def test_failure_retries_and_preserves_last_good(self): + with patch("meteo.read_registers", side_effect=TimeoutError("test timeout")) as reader: + with patch("meteo.time.sleep"): + checked_at, values, raw, attempts, error = meteo.collect(CONFIG) + self.assertEqual(reader.call_count, 3) + self.assertIsNone(values) + with tempfile.TemporaryDirectory(dir=meteo.BASE) as temporary: + db = Path(temporary) / "test.sqlite3" + good_time = "2026-10-12T10:00:00+00:00" + meteo.save_result(db, good_time, + {"temperature": 7.3, "humidity": 95.1, "pressure": 1020.2}, + {}, 1, None) + last_good = meteo.save_result(db, checked_at, values, raw, attempts, error) + status = meteo.write_status(Path(temporary) / "status.json", checked_at, + values, last_good, CONFIG["timezone"]) + self.assertEqual(status["status"], "error") + self.assertIsNone(status["values"]) + self.assertEqual(status["last_success_at"], good_time) + with sqlite3.connect(db) as connection: + self.assertEqual(connection.execute( + "SELECT temperature_c,humidity_percent,pressure_hpa FROM readings WHERE status='error'" + ).fetchone(), (None, None, None)) + + +if __name__ == "__main__": + unittest.main() diff --git a/test_publish.py b/test_publish.py new file mode 100644 index 0000000..b488f11 --- /dev/null +++ b/test_publish.py @@ -0,0 +1,51 @@ +from datetime import datetime, timezone +import unittest +from unittest.mock import patch +from test_support import config + +from publish_lemmy import START, END, merge_body, render_section + + +class PublishTests(unittest.TestCase): + def setUp(self): + self.mock_config = patch("publish_lemmy.load_config", return_value=config()) + self.mock_config.start() + self.addCleanup(self.mock_config.stop) + def test_preserves_description(self): + old = 'Opis użytkownika\n' + START + '\nstary pomiar\n' + END + '\nDalszy opis' + self.assertEqual(merge_body(old, 'nowy pomiar'), + 'Opis użytkownika\n' + START + '\n\nnowy pomiar\n\n' + END + '\nDalszy opis') + + def test_migrates_html_comments(self): + text = 'Opis\n\nstare\n\nStopka' + result = merge_body(text, 'nowe') + self.assertEqual(result, 'Opis\n' + START + '\n\nnowe\n\n' + END + '\nStopka') + self.assertNotIn('