# HAMCTRL ## Komunikacja / Communication Odpowiadaj w języku, w którym pisze użytkownik — jeśli pisze po polsku, odpowiadaj wyłącznie po polsku (cała wypowiedź, bez angielskich wtrąceń typu „Fix:”/„commit” użytych zamiast polskiego odpowiednika); jeśli pisze w innym języku, odpowiadaj w tym języku. Nazwy własne z kodu (funkcje, zmienne, typy wiadomości WS jak `wsjtx_decode`, `is_tx`, nazwy plików) zawsze zostają w oryginalnej formie — to cytowanie kodu, nie wtrącanie obcego języka. *(This project's own instructions used to hardcode "always respond in Polish" — wrong for a public repo where contributors may not read Polish at all. Mirror whatever language the user writes in instead.)* ## Co to za projekt Webapp do zdalnego sterowania radiostacją amatorską Icom IC-7300 przez CI-V: strojenie, tryby, PTT, rotory, DX cluster, log QSO — oraz własny, kompletny tor FT8/FT4 (dekoder + enkoder), **bez WSJT-X/JTDX**. - Backend: **Python** (`aiohttp`), jeden duży plik `webapp.py` (~7000 linii) — serwer HTTP/WS, silnik automatyki FT8, sterowanie radiem (`civ.py`), audio (`audio.py`, `audio_stream.py`, `audio_rust_bridge.py`). - Frontend: **vanilla JS** (bez frameworka/buildu) w `public/js/`, jeden `public/index.html`. Kluczowe dla FT8/FT4: `public/js/wsjtx.js` (okno RX/panel FT8) i `public/js/wsjtx_scope.js` (wodospad/scope). - Audio real-time: osobny serwer w **Rust** w podfolderze `ham_audio/` (`ham_audio.exe`), komunikacja z Pythonem przez TCP (patrz `ft8_rust_receiver.py`). Rozróżnienie RX/TX jest istotne przy szukaniu błędów: - **RX (odbiór):** dekodowanie FT8/FT4 dzieje się w Rust (`ham_audio/src/decode/`), Python tylko odbiera gotowe wyniki przez TCP (`ft8_rust_receiver.py`). - **TX (nadawanie):** enkodowanie i generowanie sygnału FT8/FT4 jest po stronie Pythona — `ft8_encoder.py` i `ft4_encoder.py` tworzą PCM do nadawania. Cały tor TX (Fake Split, wybór częstotliwości, mnożenie poziomu `txVolume`, synchronizacja okna) jest w Pythonie. Szukając błędu w nadawaniu — zaczynać od Pythona, nie od Rusta. - Produkcja działa jako **EXE spakowane PyInstallerem** (`hamctrl.spec`, punkt wejścia `launcher.py`) — **po każdej zmianie pliku .py trzeba przebudować EXE**, sama zmiana źródła nic nie daje na żywca. - Sekrety i dane per-instalacja (`config.json`, `users.json`, certyfikaty TLS, `qso.db`) leżą w `%APPDATA%\HAMCTRL`, **nie w repo** — chroni je `.gitignore`, nie ruszać. ## Silnik FT8/FT4 (skąd zaczynać przy błędach automatyki/QSO) Łańcuch: **Rust dekoduje → `ft8_rust_receiver.py` odbiera przez TCP → `webapp.py::_ft8_rx_loop` → `_process_auto_qso` woła `qso_engine.py` (czysta maszyna stanów QSO, bez asyncio/sieci) → broadcast WS (`auto_qso_status`, `auto_seq_status`, `wsjtx_decode`, ...) → front (`wsjtx.js`) renderuje okno RX i panel automatyki**. - `qso_engine.py` — silnik stanu pojedynczego QSO + kolejka "Call 1st". Ma pełne pokrycie testami w `test_qso_engine.py` (uruchamiane jako skrypt, nie pytest: `python test_qso_engine.py`, wymaga `PYTHONIOENCODING=utf-8` w tej powłoce Windows, inaczej pada na polskich znakach w konsoli). **Każda naprawa w tym silniku powinna dostać swój test w tym pliku.** - TX FT8/FT4 (`_ft8_tx_sequence` / `_ft8_tx_sequence_inner` w `webapp.py`) jest **wspólną ścieżką** dla nadawania ręcznego i automatycznego — synchronizacja do okna 15s/7.5s UTC, PTT, mutex `_ft8_tx_lock` (serializuje wszystkie transmisje, ręczne i auto). - W kodzie są polskie komentarze opisujące wcześniejsze naprawy (np. „automatyka wariuje, backend i UI się rozjeżdżają") — czytać je, to ślady jak dany mechanizm miał działać i jakie regresje już raz naprawiono. - Częsty wzorzec błędu w tym projekcie: front (`wsjtx.js`) wysyła/obsługuje typ wiadomości WS, dla którego backend **nigdy nie miał handlera** (albo handler zniknął) — objawia się jako „przycisk nic nie robi". Zawsze sprawdzać obie strony (`WS.send({type: ...})` we froncie vs `elif t == "..."` w `_ws_msg` w `webapp.py`). - Świeżo połączony klient (nowa karta, F5, reconnect) musi dostać snapshot aktualnego stanu backendu zaraz po wiadomości `init` — bez tego panel automatyki/Fake Split pokazuje domyślne/nieaktualne wartości do czasu pierwszego kolejnego broadcastu. - **Fake Split** (przesunięcie VFO na czas TX tak, by emisja trafiała w środek pasma odbiorczego korespondenta) był w torze TX i zniknął po serii zmian w torze audio — przywrócony 2026-08-10. Nazwy funkcji, po których szukać w historii Gita / śladach w kodzie, gdyby znów zniknął: `_compute_fake_split`, `_apply_fake_split_before_tx`, `_restore_fake_split_after_tx`, oraz handler WS `ft8_toggle_fake_split`. Domyślnie wyłączony, sterowany z panelu FT8 we froncie. VFO musi być zawsze przywrócony po TX (blok `finally`), inaczej radio zostaje na przesuniętej częstotliwości. ## Weryfikacja buildu i cache - **Znacznik wersji EXE:** `webapp.py` wypisuje przy starcie linię `[build] webapp.py wersja BUILD-RRRR-MM-DD-...`. To narzędzie diagnostyczne: po przebudowie EXE sprawdzić w logu, czy znacznik jest aktualny. Jeśli widać stary znacznik mimo zmiany kodu — PyInstaller spakował niewłaściwy `webapp.py` (zły katalog/import), i żadna poprawka .py nie zadziała, dopóki to się nie naprawi. Przy istotnej zmianie warto podbić ten znacznik, żeby jednoznacznie potwierdzić, że nowy kod wszedł do EXE. - **Cache przeglądarki (frontend):** pliki JS/CSS ładują się z `?v=...` (cache-busting) w `index.html`. Po zmianie w plikach `public/js/*.js` lub `public/css/style.css` podbić ten parametr wersji — inaczej przeglądarka poda stary plik z cache i test pokaże nieaktualny kod (objaw: „zmiana nie działa", choć plik na dysku jest poprawny). Alternatywnie twardy refresh (Ctrl+Shift+R) albo okno incognito do sprawdzenia. To częste źródło fałszywego „dalej to samo". ## Warsztat pracy - Naprawiać **jedną rzecz naraz**. Po każdej zmianie w `qso_engine.py` lub w logice automatyki w `webapp.py`: uruchomić `PYTHONIOENCODING=utf-8 python test_qso_engine.py` i commitować dopiero gdy testy przechodzą. - Nie dodawać abstrakcji/refaktoringu wykraczającego poza zgłoszony problem — ten plik jest już duży i gęsty, punktowe poprawki są tu cenniejsze niż porządkowanie. ## Kontekst użytkownika Rozmówca jest operatorem radioamatorskim (znak SQ3MZM), nie programistą etatowym — testuje zmiany na żywym sprzęcie (IC-7300 podłączony do anteny). Konsekwencje: - Instrukcje dawać konkretnie, krok po kroku (jaki plik, jaka komenda, co sprawdzić w logu). - Szczególna ostrożność przy zmianach dotykających nadawania — sygnał idzie w eter. Przy strojeniu poziomów/mocy zaczynać od małej mocy. - Objawy zgłaszane są w kategoriach działania radia („automat nie widzi wołających stacji", „ALC za wysokie"), nie w kategoriach kodu — trzeba je przełożyć na miejsce w kodzie.