Commit Graph
349 Commits
Author SHA1 Message Date
ew8bakandClaude Opus 4.8 6ad5f29657 feat(overlay): flip VFO overlay to left side when it doesn't fit on right
PositionVfoOverlay считает обе кромки полосы фильтра и ставит накладку справа
от неё по умолчанию; если справа не влезает (маркер у правого края) — рисует
слева от левой кромки. Возврат на правую сторону автоматически, когда снова
помещается. Убран прежний clamp-оверлап с маркером.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:18:29 +03:00
ew8bakandClaude Opus 4.8 43dbd2b829 fix(overlay): S-meter scale + header badge polish
- S-метр: убрана метка +60 (вылезала за правый край), шрифт подписей
  5→7 (читаемость), подписи сдвинуты ниже тика. Бар оставлен компактным,
  ряду добавлено 3px под крупные метки.
- Градиент S-метра привязан к уровню dBm (S1→S7→S9→S9+18), а не к длине
  бара — сильные сигналы уходят в красный раньше.
- Бейджи A/SPLIT/TX: высота 15→12px (верх на месте, низ приподнят) —
  текст по центру, больше зазор до S-метра.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:14:16 +03:00
ew8bakandClaude Opus 4.8 4a92256e72 feat(overlay): redesign VfoOverlay in SmartSDR slice-flag style
Полный редизайн накладки поверх спектра: шапка (слайс-бейдж A/B, ширина
фильтра, кликабельный SPLIT, индикатор TX), метр-ряд (индикаторы NR/NB/ANF/
SNB + сигнал-бар), крупная частота + S, нижний бар с fly-out пикерами.

- Fly-out движок MODE/DSP/AUD (раскрываются под баром, карточка растёт):
  MODE — сетка режимов 4×2 + фильтры; DSP — NR/NB/SNB/ANF + AGC; AUD —
  выбор аудио-устройства вывода (задел под слайсы).
- Слайдер громкости (click+drag) + mute; SPLIT-toggle; live TX-индикатор.
- Все контролы подключены к RadioController (SetVolume/ToggleMute/SetSplit/
  EnumOutputDevices→ApplyAudioDevice); wire в MainForm + HandleMouseUp, чтобы
  клик по накладке не уходил в спектр.
- FormatFreq на Int64 (double накапливал ошибку в десятки Гц на ~10 ГГц QO-100).
- S-метр ниже по высоте + плавный градиент по уровню dBm: тёмно-зелёный→
  зелёный→жёлтый→красный (красный к ~S9+18).
- Кэш+dirty рендер сохранён (CPU-паритет со старой накладкой), GL-путь через
  DrawOverlayBitmap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:00:59 +03:00
ew8bakandClaude Opus 4.8 5b7eda291e fix(spectrum): сохранение/восстановление зума для трансверторов (XVTR)
Зум был per-band только для HF (TBandSettings). XVTR-слоты хранятся отдельно
(TXvtrEntry) и зум туда не попадал — при входе в трансвертер не восстанавливался.

- TXvtrEntry: LastZoomFactor/LastPanSlider + Default/Load/Save (JSON).
- SaveCurrentBand (XVTR-ветка): пишет зум/пан в слот.
- ActivateXvtr: восстанавливает зум слота + SetZoomPan (re-arm анализатора).
  Покрывает все входы (кнопка, старт по LastXvtr, CAT/web).
- ApplyZoom: кэширует в активный слот — XVTR при активном трансверторе,
  иначе HF-band.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:27:08 +03:00
ew8bakandClaude Opus 4.8 371b2858bd feat(spectrum): zoom + pan для спектра и водопада (WDSP span-clip)
Зум через нативный span-clip WDSP (fscLin/fscHin в SetAnalyzer): clp=0,
видимое окно сужается клипом, при зуме 0 полоса == sample rate (старое
поведение сохранено). Конвенция как в Thetis: ZoomFactor 0=без зума..1=макс.

- WDSPEngine: SetZoomPan + расчёт span-clip, ViewLowHz/ViewHighHz.
- RadioController: GetViewWindow (единая истина видимого окна для оверлеев и
  мыши), ApplyZoom, ZoomIn/Out по лестнице кратностей, ZoomDefault. Якорь
  зума кнопками — на маркере активного VFO. rfZoom.
- Per-band персист зума/пана (TBandSettings + Save/Load/Restore).
- MainForm: маршрутизация оверлеев/кликов через GetViewWindow (покрывает CPU
  и OpenGL), резерв строки PANZOOM_H в layout + сплиттере, rfZoom-рендер.
- WebAdapter: web-спектр/наведение на view-окно (делит те же WDSP-пиксели).
- PanZoomBar (новый): нижняя полоса = линейка полного диапазона (частоты +
  деления), поверх окно-селектор «стекло» (полупрозрачное, объёмная фаска).
  Середина=пан, края=зум, клик мимо=центрировать, двойной клик=сброс.

Сборка ewsdr (--ws=qt6) и ewsdrd — ОК. На железе не проверено.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:18:24 +03:00
ew8bakandClaude Opus 4.8 0e550dd9f9 fix(cat): align Andromeda panel mapping with real G2 hardware (Thetis)
Button/encoder indices follow Thetis MakeNewAndromedaDataset (our 0-based
index = Thetis Number - 1), verified against andromeda_test/AndDecode.pas.
The old map assumed sequential 0,1,2.. and truncated at 32; the real panel
sends sparse indices up to 47.

- AndromedaMap: AND_MAX_BUTTONS 32->48, renumbered button/encoder tables;
  new actions abVFOAtoB/abVFOBtoA/abVFOLock/abStartStop/abShift.
- RadioController: VFO Lock (FVfoLock, rfVfoLock, SetVfoLock, gate in
  TuneActiveBy).
- AndromedaPanel: wire A>B/B>A/SDR-ON/Lock; Shift band-keypad layer
  (#28 latch, #29..39 -> SetBand 160m..6m, non-sticky); aiVFOLock/aiShift
  indicators.
- Filter High/Low (#4/#5) left as width approximation per decision.
- doc/ANDROMEDA.md updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 16:11:12 +03:00
ew8bakandClaude Opus 4.8 2d7290e44d feat(cat): Andromeda/G2 front panel support (serial, LED indicators)
Bidirectional Apache Labs Andromeda/G2 front-panel protocol on top of CAT.
Panel logic lives in dedicated units; existing files get only thin hooks.

New units:
- AndromedaMap.pas   — data only: action enums + default button/encoder
  layout tables and LED (ZZZI) numbers (mirroring the Thetis defaults)
- AndromedaPanel.pas — TAndromedaPanel: inbound panel events -> controller
  actions (marshalled via Invoke), state subscription -> ZZZI push via SendProc

Hooks:
- CATEngine: parse ZZZD/ZZZU/ZZZE/ZZZP/ZZZS into generic TCATContext callbacks
- CATAdapter: creates the panel, routes engine callbacks to it, supplies the
  SendProc to the Andromeda port, AddStateListener, ZZZS handshake
- CATSerial: Andromeda flag in port config + HasAndromeda/SendToAndromeda
- RadioController: AddStateListener (multicast, does not steal MainForm's
  OnStateChanged)
- Settings/SettingsForm/MainForm: CATSerialAndromeda[0..3] + persistence + checkbox

First version: serial transport, hardcoded default mapping, LED indicators
(MOX/Tune/CTUN/VFO A-B/Split/NB/NR/SNB/ANF/Squelch). Doc: doc/ANDROMEDA.md.

TODO: default button mapping does not match the physical panel (hardcoded) —
to be fixed later; also ZZMF/LCD text, TCP G2V2 transport, configurable mapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:40:10 +03:00
ew8bakandClaude Opus 4.8 4a92624e9b feat(cat): wire split/FM/CTUN/AGC-T CAT commands to controller
Реализованы команды, ранее бывшие заглушками — функционал уже был в
TRadioController, но не пробрасывался в CAT:

- Split TX: ZZSP/FT/FR + split-байт в IF (новый RadioController.SetSplit)
- FM Squelch: SQ (0–255), ZZSQ (0–100%), ZZSO (on/off)
- FM CTCSS: CT/ZZTA (on), CN (1-based)/ZZTB (0-based тон)
- FM-репитер: OF (offset), OS (направление, с трансляцией Kenwood↔RPT_*)
- FM step: ZZST (00–03)
- CTUN: ZZCN; AGC-T/RF gain: ZZAR
- ANF: ZZNT (раньше ошибочно алиас NB)
- Шаги VFO: ZZAU/ZZBP (исправлен баг — были алиасы ZZFA/ZZAP), ZZSU

TCATContext расширен callback'ами (split, squelch, CTCSS, repeater, step,
AGC-T, CTUN); CATAdapter — геттеры/сеттеры/sync с маршалингом через Invoke.
Добавлен doc/CAT_STATUS.md — полный статус CAT и план работ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 10:35:35 +03:00
ew8bak 7938e800f3 iss file for libad9361.dll 2026-06-25 16:02:27 +03:00
Uladzimir Karpenka e99dc7266f fix create macos bundle 2026-06-25 15:30:02 +03:00
ew8bak ec8e707354 iss file for libiio 2026-06-25 13:02:24 +03:00
ew8bak 437501bbc0 fix libfec import 2026-06-25 12:58:07 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 4b0e6c2d35 fix: link libfec via {$LINKLIB fec} so symbols resolve on Darwin
external FEC_LIB on the function decls alone does not register the
library with ld on FPC/Darwin — viterbi27/rs_8 symbols stayed
undefined at link time. Mirror the working WDSP pattern ({$linklib
wdsp}). This is not static linking: ld prefers libfec.dylib over .a
when both are in the search path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 17:28:50 +03:00
ew8bakandClaude Opus 4.8 3612f32199 fix: web connect on GUI resolves device Kind/URI (Pluto vs HPSDR)
SyncWebConnect (web-коннект на десктопной GUI) собирал устройство сам
только из IP+BoardType, оставляя Kind=0 (=bkHPSDR) и пустые URI/Serial.
Из-за этого сохранённый Pluto подключался как openHPSDR → UDP discovery
→ Hardware Timeout (web при этом оптимистично показывал connected /
Unknown Board). Десктоп-путь и демон уже шли через ResolveDevice.

Теперь SyncWebConnect тоже зовёт FController.ResolveDevice(IP) —
единый резолв (восстанавливает Kind/URI/Serial/BoardType/MAC из стора
по IP или URI) для всех трёх входов.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:59:11 +03:00
ew8bakandClaude Opus 4.8 fa6a804331 feat: web RF-AGC as button + popup overlay (Pluto)
Заменил дропдаун+слайдер на одну кнопку RF AGC (с подписью текущего
режима). По клику — оверлей (стиль как FNP-попап) с кнопками
FAST/SLOW/HYBRID/MANUAL + ползунок GAIN 0–73 дБ, активный только в
MANUAL. Крестик/клик мимо закрывают; выбор режима оставляет оверлей
открытым (чтобы сразу подвинуть слайдер). Бэкенд не тронут — те же
команды set_rx_gain_mode/set_rx_gain и state-поля. Только WebPageHtml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:52:14 +03:00
ew8bakandClaude Opus 4.8 a885488243 feat: web RF-AGC + QO-100 RX MUTE controls (Pluto)
Прокинул в web-интерфейс управление, которого там не хватало (раньше
только отображение):

- RF-AGC (Pluto hw-gain): дропдаун режима FAST/SLOW/HYB/MAN + слайдер
  GAIN 0–73 дБ (активен только в MAN, драг переводит в manual). Виден
  по BackendCaps.HasHWGain (= Pluto подключён). Команды set_rx_gain_mode
  / set_rx_gain → SetRxGainMode / SetRxGain.
- RX MUTE (QO-100 self-monitor): toggle-кнопка рядом с BCN, видна по
  InQO100, подсветка по состоянию. Команда set_rx_mute → SetRxMuteOnTx.

Гейтинг зеркалит десктоп. Работает в GUI-web-сервере и headless-демоне.
Проверено на железе пользователем.

WebServer: команды + state-поля (rf_agc_visible/rx_gain_mode/rx_gain_db,
rx_mute_visible/rx_mute_on) + SetRxGainStatus/SetRxMuteStatus.
WebAdapter: колбэки OnRxGain*/OnRxMute (Invoke на поток контроллера) +
публикация в PushState. MainForm + ewsdrd.lpr: привязка колбэков.
WebPageHtml: виджеты, JS-обработчики, updRfAgc/updRxMute, PEND_MAP.

Также актуализирован doc/PLUTO_INTEGRATION_PLAN.md: TX-тракт и режим
QO-100 помечены проверенными на железе; раскрыт TODO «XO ppm-калибровка»
(ручной TxLOOffset как текущий обходной путь, xo_correction/GPSDO как
постоянное решение).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:02:34 +03:00
ew8bak 8bb8714a25 delete test*.lpr 2026-06-24 10:05:30 +03:00
ew8bakandClaude Opus 4.8 5c17fdcb7e fix: center ruler labels vertically (short ticks + TextHeight centering)
After tying the ruler font to the ruler height, the labels (drawn at a
fixed H/2-1 top) sat below center because the taller font extended
downward. Shorten the ticks (major H*0.34, minor H*0.20) and center the
label in the area beneath them using TextHeight, so the digits are
vertically centered at any DPI. Applies to both the normal grid and the
FM-step grid branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:33:19 +03:00
ew8bakandClaude Opus 4.8 fd85b532ee fix: ruler text scales with DPI (font height tied to ruler height)
The ruler drew labels at a fixed Font.Size := 7, so on Windows display
scaling (125/150/200%) the text no longer matched the DPI-scaled ruler
height — labels came out too small or clipped. Tie the font height to
the ruler height (-Max(8, Round(H*0.45))) so labels scale with DPI,
matching the approach used by the new bandplan overlay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:04:44 +03:00
ew8bakandClaude Opus 4.8 432c0285f0 feat: QO-100 NB transponder bandplan overlay on spectrum
Add a thin colored strip along the bottom of the spectrum (above the
ruler) showing the QO-100 narrow-band transponder bandplan: mode
segments (CW ONLY / NB DIGI / DIGI / SSB / MIXED MODES) plus the three
beacons (CW / PSK / EXP) as muted-red cells at their ranges.

- New unit BandPlanOverlay.pas (TBandPlanOverlay), modelled on
  VfoOverlay: content rendered once into a cache bitmap, per-frame cost
  is just a constant-alpha composite (CPU) / one textured quad (GL).
  Cache invalidates only on view change (center/span/width) so it idles
  while the QO-100 downlink display is static.
- Wired into both render paths: SpectrumView (CPU composite) and
  SpectrumViewOpengl (texture, re-uploaded only when dirty).
- MainForm: SetView fed from SyncSpecViewFreq; visibility gated by
  InQO100 (Pluto + full-duplex transponder), like RX MUTE / BEACON.
- DPI-robust: strip height scales with Screen.PixelsPerInch and the font
  height is tied to the strip height (not point size), so labels always
  fit at any Windows scaling.
- Segment/beacon frequencies and colors are a single editable table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:02:50 +03:00
ew8bakandClaude Opus 4.8 3bd37b956e feat: QO-100 self-monitor (RX MUTE) + gate beacon/RX-mute buttons to QO-100
Add a per-operator toggle to hear your own downlink off the satellite
while transmitting (full-duplex self-monitor), and restrict QO-100-only
controls to the QO-100 mode.

- WDSPEngine: new MonitorRXDuringTX flag; audio gate becomes
  `not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX)` so RX audio
  can pass to speakers during TX in full-duplex.
- Controller: FRxMuteOnTx (default True = mute RX on TX), SetRxMuteOnTx/
  ApplyRxMuteToEngine, rfRxMuteOnTx notify; new InQO100 predicate
  (Pluto + full-duplex XVTR slot) as the single gate for QO-100 UI.
- Settings: global RxMuteOnTx (rx_mute_on_tx, default True), persisted.
- MainForm: new RX MUTE button in TX panel next to DUP, visible only in
  QO-100; BEACON button visibility tightened from "any transverter" to
  InQO100 so it no longer shows in plain VHF/UHF modes.
- doc: plan updated (RF-AGC done, two temps, TX/RX locked split,
  self-monitor, phase 6 done; remaining TODOs incl. web wiring).

Web intentionally left unchanged for now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 19:50:30 +03:00
ew8bakandClaude Opus 4.8 6375092741 feat: pluto telemetry — show both temperatures (transceiver + Zynq SoC)
Pluto exposes two temperature sensors: the AD9361 transceiver
(ad9361-phy temp0/input, milli-°C) and the Zynq SoC/FPGA (xadc temp0,
raw+offset+scale → C = (raw+offset)*scale/1000). Only the former was
read. Now both are read and shown as "Temp 37/53C" (chip/SoC) in the
desktop status bar and web UI.

- ReadTelemetry gains an out SocTempC param (base + Pluto override);
  Pluto reads xadc via the IIO context.
- Controller stores FLastPlutoSocTempC; SoC temp validated with a
  plausibility window (no slew-limit — stable local sensor, unlike the
  AD9361 sensor that spikes during mid-conversion).
- Status formatting handles one or both temps in MainForm and WebAdapter.
- Widen desktop status-bar field 3 (150→190) so both temps + RSSI fit;
  web layout unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 19:10:07 +03:00
ew8bakandClaude Opus 4.8 63c8c91152 fix: widen web status-bar PLL/BCN cell so beacon lock text fits
The PLL column (also used for QO-100 beacon status on Pluto) was
minmax(70px,90px), clipping "BCN LOCK 105Hz /7dB". Widen to
minmax(120px,185px), matching the desktop field-7 width. Frontend only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:21:43 +03:00
ew8bakandClaude Opus 4.8 2ecc0c93e2 feat: web beacon marker as two-half filter (Manchester lobes)
Replace the single tracked-beacon line with a filter band spanning the
beacon width, split into two halves (left/right Manchester lobes at
±400 Hz) with a central divider (carrier null) and band edges at
±450 Hz, matching the desktop dec-marker width. Reference frequency is
now a green dashed line. Frontend only (WebPageHtml).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:17:09 +03:00
ew8bakandClaude Opus 4.8 0fbe5d5edd feat: QO-100 beacon lock in web UI + headless daemon
Mirror the desktop QO-100 beacon lock into the web interface and make it
work in the headless daemon.

Backend:
- WebServer: set_beacon/beacon_seed commands (+OnBeacon/OnBeaconSeed
  events), beacon state fields, SetBeaconStatus, and beacon_visible/on/
  text/ref_hz/track_hz in BuildStateJson. beacon_seed carries 'frac'
  (0..1 click position); absolute Hz is computed controller-side from
  live FCenterFreq/FSpanHz (mirrors desktop PixelToFreq).
- WebAdapter: OnBeacon->SetBeaconLock, OnBeaconSeed->BeaconSeedAtHz;
  PushState mirrors compact status (matches MainForm.BeaconStatusText)
  into the PLL status cell when visible (Pluto + active XVTR).
- MainForm/ewsdrd: wire the two callbacks.
- ewsdrd: call ServiceBeaconLock in the main loop @~10Hz (FRunning+
  FWDSPReady gated). Without this the lock loop never ran headless.

Frontend (WebPageHtml):
- BCN button (visible only on Pluto+XVTR), highlighted while locked.
- Seed-on-mousedown with immediate return (like desktop FormMouseDown):
  no drag/set_center so the gesture never moves the center / tears IQ.
  Armed (first click after BCN) or Shift, mirroring desktop arm/Shift.
- PLL status cell shows beacon status (field-7 parity with desktop).
- Ref (green) + tracked (orange) beacon markers on spectrum/waterfall.

Known issue (unresolved): after lock the beacon can slowly drift off and
drop to "BCN sync". Suspected residual-sign anti-drift or retune-timing
on the web/daemon path; needs on-air diagnostics. See memory
project_web_beacon for the investigation state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 16:53:02 +03:00
ew8bakandClaude Opus 4.8 8472c8e7b8 fix: daemon mirrors freq_mhz_digits to web (VFO format)
The headless daemon never set the web server's FreqMhzDigits, so it
stayed at the constructor default (3 = up to 999 MHz) and VHF/UHF/SHF
frequencies didn't fit the web VFO display. The GUI mirrors this per-
device setting in ApplyFreqMhzDigits; the daemon now does the same on
connect (rfDevice) via PushFreqDigits, reading the value the controller
loaded into FLoadedGlobal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:54:21 +03:00
ew8bakandClaude Opus 4.8 3cedc6225d fix: web spectrum tuning range from backend caps; remove dead code
Fix: dragging/wheeling the spectrum in the web UI clamped frequency to a
hardcoded HF ceiling (vfoMax=60 MHz), so on Pluto VHF/UHF it snapped down
to 60 MHz and couldn't pan up (only the VFO wheel worked, via a different
limit). Push the backend's tuning range (BackendCaps.MinFreqHz/MaxFreqHz)
to the web alongside the band plan; JS vfoMin/vfoMax use freq_min/freq_max
from state. The transverter branch (XVTR_CUR>=0) is unchanged — it keeps
the wide-open range since the displayed RF freq is translated to IF and
clamped server-side.

Dead-code cleanup (audit of this session's work):
- SampleRateMode/TSampleRateMode: orphaned when the sample-rate
  unification replaced the srmContinuous branch in ApplyBackendCapsToUI;
  no readers left. Removed (+ now-unused PLUTO_MAX_SR).
- Pre-existing write-only/unused: TBackendCaps.MaxSampleRate,
  SampleRateOverlay.SPAN_NAMES, MainForm.BAND_FREQ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:35:56 +03:00
ew8bakandClaude Opus 4.8 4749cf0cbc feat: unify band plan across desktop + web UIs
Web band selector was hardcoded to the HF plan, so on Pluto it showed
160m..6m instead of the VHF/UHF plan and highlighted the wrong entry
(band switching itself already worked: web sends idx, controller maps it
via the IsPluto plan). Mirror the sample-rate unification.

Single source of truth = BoardUtils -> controller:
- BoardUtils: TBandInfo/TBandPlanArray + BuildBandPlan(Pluto).
- TRadioController.BandPlan returns BuildBandPlan(IsPluto).

Both UIs derive from it:
- Desktop already plan-aware (RelabelBands + RestoreBand) - unchanged.
- Web: WebServer.SetBands + "bands" in state JSON; hosts push
  BandPlan on connect/startup; JS builds the band selector from
  state.bands (BAND_N/BAND_F now dynamic).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:01:23 +03:00
ew8bakandClaude Opus 4.8 6b695ce756 feat: unify sample-rate presets across desktop + web UIs
Sample-rate (span) presets were duplicated in three places and none was
authoritative: HPSDR set hardcoded in the desktop overlay (SPAN_RATES),
Pluto set in backend caps, and a separate hardcoded list in the web JS.
The web showed HPSDR rates even on Pluto, and the web adapter silently
dropped any rate outside a hardcoded HPSDR whitelist (so Pluto-only rates
like 960k/2304k never switched).

Single source of truth = backend caps -> controller:
- HPSDRNetwork.Caps now fills RatePresets [48k..1536k] (was nil).
- RadioBackend: named type TBackendRateArray.
- TRadioController.SampleRatePresets returns BackendCaps.RatePresets.

Both UIs derive from it:
- Desktop: ApplyBackendCapsToUI always feeds the overlay from
  SampleRatePresets (overlay no longer owns the HPSDR list).
- Web: WebServer.SetRatePresets + rate_presets in state JSON; hosts
  (daemon OnState/startup, GUI ApplyBackendCapsToUI/startup) push
  SampleRatePresets; JS builds span buttons dynamically from it.
- WebAdapter.SyncSpan validates against SampleRatePresets instead of a
  hardcoded whitelist, so any backend rate is accepted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 13:52:03 +03:00
ew8bakandClaude Opus 4.8 d78d835485 feat: Pluto support in headless daemon (connect/autostart/discover/dev_add) + headless audio
The headless daemon (ewsdrd) only ever built HPSDR devices, so Pluto
could not be used: backend is chosen by Dev.Kind and Pluto opens by URI,
neither of which the daemon propagated. Also local audio played on the
host and web Discover never probed network Plutos.

Shared, backend-agnostic helpers in TRadioController (used by GUI + daemon):
- ResolveDevice(IP): discovered->saved lookup restoring Kind/URI/Serial/
  BoardType/MAC. MainForm.ResolveDevice now delegates here.
- AddSavedDevice(name, addr): web dev_add saves Pluto when addr has a URI
  scheme (ip:/usb:/local:), else HPSDR. Both web hosts use it.
- SeedPlutoProbeFromSaved: seed network-probe URIs from saved Plutos
  (no mDNS -> a network Pluto is only found by direct URI probe).
- LocalAudioEnabled flag (GUI=True): when False the local sound card is
  neither opened nor written; RX audio goes only to web (OnAudioConsume).

DeviceStore.AutoStartDevice(out Dev): full autostart record (Kind/URI/
Serial) so a saved Pluto (URI-addressed, empty IPAddress on USB) can
autostart.

Daemon (ewsdrd.lpr): LocalAudioEnabled:=False; SyncConnect via
ResolveDevice; autostart via AutoStartDevice; SyncDiscover seeds probe
URIs then Discover; SyncDevAdd via AddSavedDevice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 13:32:05 +03:00
ew8bakandClaude Opus 4.8 3115b2b5bf feat: add LO Error field to QO-100 settings tab
Expose the QO-100 slot's LOError (Hz) on the QO-100 settings page,
placed right after LNB LO. Maps to Entries[QO100_SLOT].LOError — the
live LNB-drift calibration that the beacon lock folds its correction
into. Group height extended to 8 rows and the info label shifted down.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:06:07 +03:00
ew8bak 0906f854fb Merge feature/qo100-decoder-lock: decoder-based QO-100 beacon lock
Replace the display-FFT centroid beacon lock with a decoder-based loop that
trims the active transverter's LOError from the beacon decoder's precise NCO
carrier estimate, compensating LNB drift so the whole downlink (incl. FT8)
stays put. Single BEACON button (lock = decoder). Also unifies the beacon
decoder filter marker between the CPU and OpenGL spectrum renderers.

Verified on air: lock holds, FT8 decodes cleanly.
2026-06-22 22:14:35 +03:00
ew8bakandClaude Opus 4.8 3f0e4204c2 fix: unify beacon decoder filter marker (CPU matches GL)
The narrow beacon decoder filter marker rendered differently in the two
spectrum views: CPU drew a translucent filled band (BlendBand) plus a centre
line, while OpenGL drew three plain vertical lines (both edges + centre), so
it looked like one band on CPU and "two parts" on GL.

Make the CPU path draw the same three lines as GL — consistent look and
cheaper (drops the per-frame per-pixel alpha blend for this marker; BlendBand
is kept for the RX/TX passband shading).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:06:44 +03:00
ew8bakandClaude Opus 4.8 6246b6bad4 feat: QO-100 decoder-based beacon lock (replace FFT lock)
Replace the display-FFT centroid beacon lock with a decoder-based loop.
The beacon decoder already tracks the carrier precisely via its NCO
(squaring-FFT acquisition + Costas offload); its residual ResidHz+CarFreqHz
is an exact measurement of the beacon's offset from the aim point. Feed that
into LOError of the active transverter so the LO retunes to compensate LNB
drift — the whole downlink stays put, calibration persists.

RadioController:
- Remove MeasureBeaconFFT and all display-FFT lock state/constants
  (lobe/search/track/slew/sym/shape/seed-gain/prom), fields
  FBeaconManualSeed/FBeaconPromDB/FBeaconDecOn, methods
  SetBeaconDecode/SetBeaconDecodeAtHz/BeaconDecodeEnabled.
- SetBeaconLock is now the master switch (lock = decoder); BeaconSeedAtHz
  aims the decoder; ServiceBeaconLock measures beacon freq from the decoder
  scope and trims LOError (gain 0.5, deadband 20 Hz, step clamp 400 Hz,
  settle 8 ticks). Feed-forward of the aim keeps the decoder locked through
  the retune (net baseband ~0). Lock/SNR now come from the decoder.

MainForm: single BEACON button — left-click toggles lock (+ opens the
constellation/bulletin scope, arms the spectrum click for aiming),
right-click shows/hides the scope. Spectrum click (armed or Shift) aims via
BeaconSeedAtHz. Simplified status field and markers.

Both targets build (lazbuild --ws=qt6 + build-ewsdrd.sh). Not yet verified
on air. Risk: BEACON_RESID_SIGN=+1.0 — flip to -1.0 if the loop runs away.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:51:01 +03:00
ew8bak 59338b2d04 Merge feature/qo100-beacon-decode: QO-100 central beacon decoder (BPSK 400 / AO-40 FEC → bulletin text)
M1: stable BPSK demod. M2: AO-40 FEC decode end-to-end (libfec). Phase C:
decode 256-byte frame as ASCII bulletin (CRC), display in BeaconScopeForm.
2026-06-22 11:26:38 +03:00
ew8bakandClaude Opus 4.8 37eb8065d6 chore: remove QO-100 beacon decoder debug instrumentation
After successful on-air validation, drop the temporary diagnostics:
- IQ baseband dump to ~/ewsdr_beacon_iq.f32 (FDbgStream/DbgWrite2).
- BeaconLog file logger (~/ewsdr_beacon.log) and per-second STATE / AIM lines.
- Dead Gardner timing fields (FMidI/FMidQ/FHalf/FPrevI/FPrevQ), superseded by
  the ML signal×slope TED.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:45:52 +03:00
ew8bakandClaude Opus 4.8 8e4106b822 feat: QO-100 beacon bulletin text display (Phase C)
Decode and show the AO-40 frame payload as text in the beacon scope window.

- RadioController.GetBeaconFrame exposes the latest 256-byte frame.
- BeaconScopeForm: poll the frame counter; on each new frame, parse the
  payload (drop trailing 2-byte CRC, printable ASCII, 64-char lines, as in
  gr-satellites qo100.parse) and append to a scrolling read-only memo.
- Window widened/heightened; constellation size capped to leave room.

Frame format verified offline against a real on-air capture: the decoder
produces the live QO-100 AMSAT bulletin text (RS errors=0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:30:36 +03:00
ew8bakandClaude Opus 4.8 afc8775ddb feat: QO-100 beacon AO-40 FEC decode end-to-end (Milestone 2)
Full chain now decodes the QO-100 central beacon to 256-byte AO-40 frames
on-air (frames>0, RS errors=0). Builds on M1 (stable BPSK demod).

FEC backend (BeaconFEC.pas, TBeaconFEC):
- AO-40 FEC: distributed sync (65 bit, step 80) -> 80x65 deinterleave ->
  Viterbi r=1/2 k=7 -> CCSDS descramble -> 2x RS(160,128). Links system
  libfec (Karn) load-time (libfec.so/.dll/.dylib). Viterbi AO-40 [0x4F,-0x6D]
  remapped to libfec [0x6D,0x4F]. Validated against gr-satellites reference
  vectors (fectest2: exact 256-byte frame, 0 mismatches).

DBPSK Manchester frontend (BeaconDecoder.pas):
- Chip-rate (800) processing: RRC matched filter -> ML signal-times-slope
  timing recovery -> Costas -> Manchester combine (block phase select) ->
  differential decode -> soft symbols to FEC.
- Carrier acquisition: squaring-FFT estimates the residual (carrier at +-pi/chip
  defeats decision-directed Costas), one-shot pulls it to DC via residual NCO.
- Costas frequency offload into pre-RRC NCO (with deadband) tracks LNB drift
  without hitting the +-pi Costas clamp.
- ML TED replaces Gardner, which degenerates on the Manchester chip stream.

Temporary on-air diagnostics retained (IQ dump + STATE log) pending wider
signal validation; to be removed before final cleanup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:21:33 +03:00
ew8bakandClaude Opus 4.8 c063d7a197 feat: QO-100 central beacon BPSK decoder (Milestone 1 — stable demod)
Декодер центрального маяка QO-100 (10489.750, AO-40 BPSK 400 бод): фронтенд
до мягких символов + констелляция, с устойчивым захватом несущей/символа.

Цепочка (BeaconDecoder.pas): NCO-снос смещения → boxcar-децим до 9600 →
RRC матч-фильтр → AGC-нормировка → Gardner timing → Costas BPSK с frequency
offload в пред-RRC NCO. squaring-FFT оставлена индикатором несущей (prom).

Ключевые решения по итогам эфирной отладки:
- invert=True для тракта QO-100 (LNB+трансвертер зеркалит спектр).
- Декодер развязан от beacon-lock (тот ретюнил железо, рвал IQ).
- AGC-нормировка петель: сигнал ~-60 dBFS замораживал Costas/Gardner.
- Frequency offload: накопл. снос Costas стекает в FResidInc (без потолка
  ±π/символ) → держит ошибку наведения (1пкс≈300Гц) + дрейф LNB.

Интеграция: тап IQ в WDSPEngine.PushIQItemToDSP; RadioController владеет
декодером, клик-наведение (ПКМ BEACON + ЛКМ/Shift+ЛКМ по спектру); узкий
фильтр-маркер в SpectrumView/GL; окно констелляции BeaconScopeForm; файловый
лог HOME/ewsdr_beacon.log для диагностики.

Milestone 2 (далее): FEC — Viterbi k=7 + RS(160,128) + интерливер + дескремблер.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 18:15:00 +03:00
ew8bakandClaude Opus 4.8 28adf9508a chore: bump build number / widget-type macro count (ewsdr.lpi)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 22:52:17 +03:00
ew8bakandClaude Opus 4.8 c849058d36 fix: DUP button state consistent with QO-100 auto-duplex
The DUP toggle wrote FDisplayDuplex directly, bypassing the controller's
SetDuplex (which also sets KeepRXDuringTX live and fires rfDuplex). And
MainForm had no rfDuplex render case, so QO-100 auto-enabling duplex
(ActivateXvtr → SetDuplex) left the button unlit while FDisplayDuplex=True
— pressing DUP then turned it OFF when the user expected ON.

Single path now: ApplyDUP just calls SetDuplex; a new rfDuplex render case
styles the button and (when transmitting) switches the display source.
Both the button and QO-100 auto-enable go through it, so the button always
reflects state and KeepRXDuringTX stays in sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 22:42:41 +03:00
ew8bakandClaude Opus 4.8 17d0af4910 docs: update Pluto integration plan for recent TX fixes
Document the TX-path work landed this session:
- §4.6: polyphase FIR interpolator + sample-rate-scaled TX buffer +
  SoapyPlutoSDR confirmation of 32768 full-scale.
- §4.8 (new): power-linear drive→atten mapping, full WDSP digital level,
  TX LO powerdown gated by PTT.
- Phase 4 status updated.
- Related fixes: per-band FM settings (SQL/CTCSS/step/repeater) restore on
  startup — was dropped by LoadDevice; affects all backends.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 22:08:45 +03:00
ew8bakandClaude Opus 4.8 ff331fa2a0 feat: Pluto TX LO powerdown gated by PTT
TX LO (altvoltage1) is now powered down on RX and enabled on PTT, instead
of staying on continuously. Removes the TX carrier leakage that showed up
at the VFO frequency in the RX spectrum (and persisted after MOX), and
stops idle TX-LO radiation. ApplyTxLO sets powerdown from FTransmitting,
which also recovers the LO if external software (e.g. SDR Console) left it
powered down. RX LO (altvoltage0) is kept on — receive is continuous; QO-100
full-duplex still works (RX LO separate).

Trade-off: re-enabling the TX LO re-runs the AD9361 synth calibration
(a few ms), so the first syllable on SSB may be slightly clipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 22:07:06 +03:00
ew8bakandClaude Opus 4.8 2ab1f4f9ac fix: restore per-band FM settings on startup (SQL/CTCSS/step/repeater)
LoadDevice (bulk startup band loader) parsed only freq/mode/filter/AGC/
CTun/center/span and skipped the FM fields, while SaveBand writes them and
LoadBand reads them. So FM squelch, CTCSS, step and repeater shift were
saved on exit but reset to defaults on the next start (band switching kept
working — that path goes through the in-memory band cache, not disk).

Added the missing fmsq_on/fmsq_level/ctcss_on/ctcss_idx/fmstep_on/
fmstep_idx/fmrpt_dir/fmrpt_offset reads to LoadDevice, mirroring LoadBand.
Affects all backends, not just Pluto.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:31:38 +03:00
ew8bakandClaude Opus 4.8 09c9109491 fix: Pluto TX drive mapping — power-linear + full digital level
TUN produced no tone: TUNLevel default 10% mapped via the old dB-linear
attenuation curve (−89.75..ceiling) to −81.8 dB → effectively off. Now
power-linear: dB = ceiling + 20*log10(pct/100), so 10%→−30, 50%→−16,
100%→ceiling — usable across the whole slider.

Also keep WDSP digital level at full-scale for Pluto (ApplyWDSPDrive):
power is set once via hardware attenuation, not by scaling the WDSP path.
HPSDR unchanged (drive byte still drives the radio PA).

Note: at a given slider position Pluto TX is now much hotter than before
(50%: −49.9→−16 dB). Ceiling stays PlutoTxMaxAttDb (default −10 dB).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:20:57 +03:00
ew8bakandClaude Opus 4.8 be684545c8 feat: Pluto TX polyphase FIR interpolator (anti-imaging)
Replaced linear interpolation upsampling (192k→device-rate) with a
polyphase FIR interpolator ×L. Prototype: windowed-sinc lowpass (Blackman,
~-58 dB sidelobes), TAPS*L long, cutoff TX_FIR_CUT=40k, normalized to unity
per-phase DC gain (loudness unchanged). Images at multiples of 192k are now
suppressed ~-58 dB instead of only sinc², cleaning up the uplink spectrum.

BuildTxResampler designs coefficients per FTxRatio in StartTX; FTxDlI/Q
delay line persists across SendDUCIQ calls, reset on StartTX/ClearDUCIQQueue.
Removed FTxPrevI/Q.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:25:52 +03:00
ew8bakandClaude Opus 4.8 071ffa476d fix: Pluto TX buffer scaled by sample-rate (SoapyPlutoSDR model)
Fixed 4096-pair TX buffer was too small for full-duplex above ~1 Msps
("not enough ... samples getting dropped" per SoapyPlutoSDR), causing
clicks/dropouts. Now sized as round(rate/60) rounded up to a power of 2,
clamped [4096..65536] (576k→16384, 1536k→32768). FIFO cushion (~4 buffers)
resizes to match in StartTX.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:20:41 +03:00
ew8bakandClaude Opus 4.8 3fe5cf3fad feat: QO-100 mode — separate RX/TX LO + dedicated settings page
QO-100 reuses the transverter path (VFO/band-stack/beacon-lock untouched)
with a split LO for the geostationary transponder:
- TXvtrEntry += FullDuplexTx + TxLOOffset (persisted). Reserve the last
  XVTR slot (QO100_SLOT) for QO-100; migration restores the template when
  the slot is unset or has empty offsets (fixes old configs showing zeros).
- XvtrTranslateTX: TX LO = visible - TxLOOffset (no LNB LOError) for QO-100;
  identical to RX translate for normal transverters. Switched all TX-side
  translate calls (controller + MainForm).
- SetDuplex wired (live KeepRXDuringTX); auto display-duplex on QO-100 entry.
- New "QO-100" settings page (LNB LO / transponder offset / downlink edges /
  RX gain / TX ceiling + live RX-IF/TX readout); QO slot hidden from the
  transverter table. Reuses OnXvtrChange — no MainForm wiring needed.
- doc/PLUTO_INTEGRATION_PLAN.md: phase 5 done.

Builds clean (headless + qt6 GUI). Not yet verified on hardware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:30:23 +03:00
ew8bakandClaude Opus 4.8 c6660891c0 feat: drive slider value label — % (openHPSDR) / dB (Pluto)
Show current drive value next to the slider: percent for openHPSDR,
TX attenuation in dB for Pluto (via controller PlutoTxAttForDrive;
unit flips on backend connect). Refreshed on drive change, device
render, and TX max-att change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 14:50:39 +03:00
ew8bakandClaude Opus 4.8 d08fc17056 feat: Pluto TX path — attenuation-based power, full-duplex IQ stream
TX path for Pluto/AD936x (RX was already working):
- PlutoBackend: continuous full-duplex TX stream (cf-ad9361-dds-core-lpc)
  with FIFO + push thread; SendDUCIQ upsamples WDSP 192k -> device rate
  (integer, linear interp), 24->16 bit, Q inverted; zeros on underrun.
- Power via output attenuator (ADI-recommended): SetTxAtten writes
  hardwaregain on voltage0(out). Controller maps drive% -> dB
  (PlutoTxAttForDrive/ApplyPlutoTxAtten); -89.75 dB on RX (~off),
  ceiling PlutoTxMaxAttDb on 100% drive.
- Configurable max-att (default -10 dB, ADI-linear) for external amp:
  persisted in global settings, UI spin on PA page, routed through
  controller SetPlutoTxMaxAtt (UI stays decoupled from logic).
- RadioBackend: virtual SetTxAtten (no-op for HPSDR).
- Update doc/PLUTO_INTEGRATION_PLAN.md: phases 3/4/5 + risks.

Builds clean (headless + qt6 GUI). Not yet verified on hardware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:27:49 +03:00