Клик по «NNN kHz · DDCn» в шапке пана — кроме сетки rate теперь есть пункты
«→ DDC N»: пан переезжает на другой аппаратный DDC (MovePanDDC), при этом
движок, анализатор и слайсы не пересоздаются — перепрошивается только
hw-индекс. Пригодилось при отладке: конкретный DDC платы может вести себя
хуже соседей, и пан можно просто увести на другой.
Диапазон ограничен реальными DDC железа: MaxDDCIndex = 7 (DDC8+ openHPSDR
enable-бит принимает, но поток не отдаёт). Ограничение действует и в меню,
и в DDCFree, и в самом MovePanDDC.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ApplyAntPorts брал оба разъёма по центру приёма, поэтому при мультислайс-TX
(или split на другой диапазон) передача могла уйти в разъём RX-диапазона.
Теперь RX-разъём — по FCenterFreq, TX — по ActiveTXFreqHz (TX-слайс/TX-VFO);
TX вне VU-плана падает обратно на диапазон приёма. Пересчёт добавлен в
SetTxSlice, SetSplit и в SetMOX(True) как страховка перед выходом в эфир.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Второй разъём у AD936x-плат — это второй КАНАЛ чипа (не порт B первого), и в
1R1T-образе он выбирается мультиплексором AD9361: debug-параметры
adi,1rx-1tx-mode-use-rx-num/-tx-num + биты RX1/RX2, TX1/TX2 регистров 0x3/0x2
(как switch_rfinput.sh/switch_rfoutput.sh прошивки). rf_port_select тут ни при
чём — прошивка его к тому же блокирует.
- IIOBindings: debug-атрибуты + прямой доступ к регистрам.
- PlutoBackend: детект второго разъёма (2R2T-каналы либо мультиплексор + модель
платы), SetAntPorts(RxPort,TxPort), ApplyRxMux/ApplyTxMux; Caps.HasAntSwitch.
⚠ApplyGain(Force): драйвер пропускает запись gain_control_mode, если считает
режим неизменным, — а у нового канала AGC-регистры не запрограммированы (в
железе крутится auto-AGC, hardwaregain отвергается EOPNOTSUPP). Форсируем
перепрог через другой режим после смены разъёма и после ad9361_set_bb_rate.
- RadioBackend: TBackendCaps.HasAntSwitch + virtual SetAntPorts (HPSDR — no-op,
там антеннами рулит Alex).
- Settings: секция ant936x (разъём RX/TX на диапазон и на слот трансвертера);
hw-AGC (режим + manual gain) переехал в TBandSettings/TXvtrEntry — теперь он
свой на каждом диапазоне и трансвертере.
- RadioController: FAnt936x + ApplyAntPorts (RestoreBand/ActivateXvtr/
StartRunning); ApplyBandDSP восстанавливает hw-AGC диапазона.
- SettingsForm: вкладка Antenna — Alex-таблица (openHPSDR) либо таблица
разъёмов AD936x по диапазонам/трансвертерам.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- RadioController: FTxSliceId (0=главный VFO, >0=слайс), SetTxSlice
эксклюзивно; ActiveTXFreqHz/ActiveTXMode с TX-слайса, режим применяется
в SetMOX(True) перед SetTXRun; TxBandIdx для PA-кал и safety DoNotTx
при кросс-банде; RemoveSlice сбрасывает TX-слайс.
- HPSDRNetwork: TX-антенна выбиралась по RX-бэнду → по TX-частоте
(TxAnt[FreqToBandIndex(TXFreqHz)]).
- MainForm/PanafallPanel: клик TX на флаге слайса → SetTxSlice, TX-бейджи
(янтарь=выбран, красный=передаёт), RefreshTxIndicators по rfTransmitting.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Сохранение и восстановление панадаптеров, слайсов и раскладки per-device,
раздельно по контексту: КВ ('hf') и каждый трансвертер ('xvtr_N') — паны
одного контекста не имеют смысла в другом.
Settings: секция "pans"/<ctx> под MAC (grid-режим/доли, на пан —
freq/rate/adc/zoom/pan/доли стека+колонки+сплиттер спектр/водопад, на слайс —
freq/mode/фильтр/agc/vol/mute/squelch/аудиоустройство). TSliceCfg/TPanCfg/
TPansConfig + Default/Save/LoadPans(Ctx).
MainForm: CreatePanCore (вынос ядра из OnAddPanClick), SavePanWorkspaceCtx/
RestorePanWorkspaceCtx/TearDownWorkspace, PanContextKey. Хук контроллера
OnBeforeStop (снимок до гашения DDC в StopRunning). Переходы ActivateXvtrBand/
DeactivateXvtr обёрнуты save-старого/teardown/restore-нового (гейт FRunning);
restore XVTR-панов после ActivateXvtr (FCurrentXvtr целевой ⇒ XvtrTranslate
верный). PanafallPanel.DestroyAllSliceFlags. Смена КВ-банда контекст не меняет.
Round-trip сериализации и разделения контекстов проверены автономно; на
экране не проверено.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Частота пана хранится ВИДИМОЙ (как FVfoA/FCenterFreq) и транслируется в
IF через XvtrTranslate только на границе с железом (AddPanDDC,
SetPanDDCFreq, SetPanDDCRate, SetPanADC) — раньше DDC пана получал
видимую частоту сырьём, а drag водопада пана N клампился пределами АЦП
(~61 МГц) вместо диапазона трансвертера.
Новое: controller.VisibleFreqBounds (диапазон активного XVTR | пределы
железа) для UI-клампа драга; RefreshPanDDCs — на входе/выходе XVTR и
правке LO/LOError клампит видимые центры живых панов, перешивает
freq-слова DDC и прижимает выпавшие слайсы к краям полосы захвата.
Подтверждено юзером на трансвертере.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Движок: TPanChan.ZoomFactor/PanSlider, span-clip в ApplyPanAnalyzerSettings
(математика главного, ZOOM_LIMIT 100), SetPanZoomPan с no-op guard (SetAnalyzer
сбрасывает FFT-историю). Зум переживает смену rate/ADC пана.
Контроллер: формула окна вынесена в ViewWindowFor (главный GetViewWindow тоже
через неё), GetPanViewWindow (PanId=0 делегирует), PanZoomIn/Out по лестнице
кратностей с якорем на центре окна, PanZoomDefault, геттеры.
UI: TPanZoomEvent получил Sender; у панов N включён ряд зума, бар
диспетчеризуется по объекту, кнопки −/⌂/+ общие через PanOfZoomButton (матч по
идентичности — MakeFlatBtn создаёт кнопки с Owner=контейнер, PanFromSender их
не видит); SyncPanZoomUI/RefreshPanZoom, вся мышь панов зум-корректна через
GetPanViewWindow. Темизация баров панов N.
Смоук panzoomtest ALL PASS (пик тона совпадает с формулой окна ±0.6%),
подтверждено на экране.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ChangeSampleRate больше не делает полный Close/Open движка (стоп DSP-потока,
разрушение всех анализаторов, сброс общей IQ-очереди): гейт FMainGated+FMainLock
дропает только main-пакеты, пересоздаются только RXA_CHAN и слайсы пана 0,
анализаторы панов живут. Контроллер не сбрасывает FWDSPReady на время смены
(OnDDCIQ продолжает кормить паны). Общий код RXA — Open/CloseMainRXChannel.
Бонус: SetChannelState dmode=1 на idle-каналах зря ждал флаш ~130мс → dmode=0
в CloseSliceChannel/CloseMainRXChannel (каналы всегда idle по локам).
Итог: смена 331→120мс, пауза аудио пан-слайса 137→40мс. Смоук ratetest ALL PASS,
подтверждено на экране.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Создание пана: rate наследуется от главного с клампом ≤768к
(вне сетки → 384к); за сменой rate главного паны не следуют
- Клик по «NNN kHz» в шапке пана N → меню полной сетки железа
(PanRateAllowed = Caps.RatePresets ∩ кратные 48 кГц; захардкоженный
PAN_DDC_RATES удалён)
- WDSPEngine.SetPanRate: под FSliceLock пересбор аккумулятора/анализатора
пана + переоткрытие слайс-каналов на новом rate (state сохраняется)
- RadioController.SetPanDDCRate: движок → реконфиг DDC (SetPanDDC) →
кламп слайсов к новой полосе; FPanADC[] хранится (база под 3.4)
- Шапка: rate отдельным лейблом (у панов N кликабелен, акцент/рука)
Смоук-тест без железа ALL PASS (тон: спектр+аудио слайса живы через
384→192→768). Проверено юзером на экране.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- общая IQ-очередь с TIQQueueItem.PanId (64→256 слотов); DSP-поток
диспетчеризует пан-пакеты в PushPanItemToDSP: декод, feed анализатора
PAN_DISP_BASE+PanId, аккумулятор, ProcessSlicesFor(PanId) под FSliceLock
- пан-анализаторы: Create/DestroyPanAnalyzer + ApplyPanAnalyzerSettings
(rate пана, без зума — пан-зум в 3.3+), живут в OpenAnalyzer/CloseAnalyzer,
общие настройки дисплея перекатываются (ReapplyPanDisplaySettings)
- слайсы в панах: TSliceChan.PanId, AddSlice(..., APanId), OpenSliceChannel
от rate/блока своего пана, SliceDSPRateFor (WFM от rate пана); блок пана
= FAudioBufSize*Rate/FAudioRate => аудио-блок слайсов неизменен
- выдача пикселей: OnPanSpectrum/OnPanWaterfall(PanId,...) из display-потока,
GetPixels пана под FSliceLock, колбэки вне лока
- MAX_PANS=4 переехал в WDSPEngine (единый движок/контроллер/UI)
- контроллер: AddPanDDC создаёт пан в движке до включения DDC, RemovePanDDC
убирает слайсы (со звуковухами)+движок+DDC, гейт/NCO слайсов от центра
своего пана, ретюн пана двигает его слайсы
- смоук-тест на libwdsp без железа ALL PASS: пик спектра пана точно на месте,
слайс пана выдал 1 кГц RMS 0.68, главный тракт жив, RemovePan чисто
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- TBackendCaps: MaxPans/IndependentPanFreq (HPSDR 4/True, Pluto 1/False);
SetPanDDC/SetPanDDCFreq — виртуальные no-op в TRadioBackend
- HPSDRNetwork: ConfigureDDCs → кэш главного DDC + RebuildDDCSpecific
(главный + активные пан-DDC, каждый со своим rate/ADC); SendFullHP пишет
частотные слова всех активных пан-DDC; хелпер DDCBaseIndex; сброс
пан-состояния в Connect
- RadioController: MAX_PANS=4, PAN_DDC_RATES 192/384/768; AddPanDDC/
RemovePanDDC/SetPanDDCFreq/PanDDC* (аллокатор hw-индексов от FActiveDDC+1,
обратный маппинг FDDCPanMap); OnDDCIQ роутит пан-DDC → PushPanIQ;
паны гасятся в StopRunning, пул сбрасывается в StartRunning
- WDSPEngine: PushPanIQ — заглушка-дроп (аккумулятор/анализатор — этап 3.2)
- фикс: StartRunning не включал Saturn (BoardType 10) в список плат с
DDCBase=2 — на Saturn гейт FActiveDDC ждал бы IQ с DDC0
- doc/SLICES_PLAN.md: статус 3.1 + B1 проверена на экране
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WFM использует новые wfmd/wfmmod из нашего форка WDSP. Демодулятор —
квадратурный дискриминатор на dsp_rate, поэтому при штатных 48 кГц
девиация 75 кГц не помещается в полосу. В WFM цепь RXA поднимается до
WFMDSPRate (наибольший FAudioRate*2^k <= 192 кГц, делящий SampleRate
нацело), rsmpin децимирует вход сам. Выше 192 кГц смысла нет: энергия
демодулированного тона выходит на полку (33.5k при 192 кГц против 31.8k
при 576 кГц), а FFT дорожает.
Две ловушки WDSP, найденные по дороге:
* firmin.c: nfor = nc / size, idxmask = nfor - 1. Все fircore создаются
с nc = max(2048, dsp_size), поэтому смена dsp_size без RXASetNC даёт
nfor = 0 — пустой массив планов и падение в fftw_execute.
* fir.c/bandpass.c планируют с FFTW_PATIENT, а wisdom.c пишет в
wdspWisdom00 только степени двойки. При SampleRate 576 кГц наивный
dsp_size = in_size = 12288 требовал FFT на 24576 точек — FFTW мерил
план 25 секунд. Отсюда потолок 192 кГц и dsp_size = степень двойки.
RXA и TXA гейтятся раздельно: рейты TXA (48 кГц вход, 192 кГц выход) от
SampleRate не зависят, поэтому wfmmod уходит на широкий dsp_rate даже
когда приёмник остался на 48 кГц.
Заодно исправлен WDSP_SAM: было 12, а RXA_SAM = 10. Раньше 12 было вне
диапазона и WDSP молча игнорировал (SAM работал как DSB); после появления
RXA_WFM = 12 это включало бы WFM-демодулятор вместо SAM. txaMode
расходится с rxaMode после DRM (WFM: RXA 12, TXA 14) — отсюда ModeToWDSPTX.
DIGI-U/DIGI-L получили свою таблицу фильтров (3.0k…300) и полосу от нуля,
а не от среза на 100 Гц, как у SSB. В CAT они замаплены на коды Kenwood 6
(FSK) и 9 (FSK-R) — раньше эти коды сваливались в USB/LSB.
Новые индексы дописаны в конец (DIGU=8, DIGL=9, WFM=10): индекс режима
уходит в settings.json, band-cache, CAT и web-API. В VfoOverlay пришлось
развести хит-коды — режимы кодировались как -(I+1) и налезли на HIT_NR/
HIT_NB/HIT_SNB, а полосы кодировались значением BW, и WFM-овские
120000/160000/200000 налезли на HIT_AGC_PICK_BASE/HIT_DEV_BASE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В fly-out DSP появляется ряд SQL (тумблер + порог 0..100) только в режиме FM.
Squelch работает per-slice: у слайсов свои WDSP-каналы, поэтому добавлены
поля/сеттеры FMSQ в TSliceChan/TCtrlSlice, ApplyFMSQToChan и восстановление
при переоткрытии канала. Роутинг события по SliceId (0=RXA, иначе слайс).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Две сохраняемые громкости: FVolume (RX) и FTxMonVolume (self-monitor
даунлинка на передаче, дефолт 40). Контекст монитора = TX + DUP + RX MUTE
off (тот же гейт, что аудио в движке). В этом контексте слайдер/SetVolume
правят FTxMonVolume, иначе FVolume; ActiveVolume — активная под контекст.
Автопереключение громкости RX<->TX-монитор на T/R (SetMOX) и при смене
RX MUTE / DUP во время передачи (ApplyActiveVolume). Обе персистятся
(tx_mon_volume в конфиге). Веб выровнен на ActiveVolume.
Фикс: слайдер громкости не синхронизировался с загруженным значением после
подключения (в rfDevice синкался только drive, volume пропущен) → слайдер
оставался на 70, движок брал сохранённое → «прыжок» при первом касании.
Добавлен TrkVolume.Position := ActiveVolume в rfDevice.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RX MUTE (self-monitor даунлинка на передаче): кнопка теперь доступна на
ВСЕХ бэкендах, а не только Pluto/QO-100. Активна только в DUP; без DUP
кнопка disabled и звук на TX не проходит (движок гейтит по
FKeepRXDuringTX=FDisplayDuplex — двойная защита: аудио-гейт + RXA не
кормится). Добавлен RadioController.DisplayDuplex. TFlatButton теперь
рисует disabled-состояние приглушённым (текст/рамка к фону).
TX pre-roll 8144->6096 (2 блока подушки вместо 3): обоснование «нужно 3
блока» опиралось на щелчок ~0.5с после старта, который на деле оказался
bfo-глитчом тона (устранён f2d0519), а не underrun'ом подушки. Латентность
старта TX ~-10мс; startup-щелчков под нагрузкой DUP не слышно (подтверждено).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Диагностика временным TXLog (снят) вскрыла цепочку проблем TX-тракта:
- Задержка ~200мс своего сигнала в DUP: за время приёма ринг PortAudio-
микрофона наполнялся до упора (9600 сэмплов) и на старте TX бёрстом
оседал стоячим запасом в DUC-очереди. Фикс: TAudioInput.Flush на RX→TX
(заодно убрана передача в эфир аудио, записанного до нажатия PTT).
- Всплески на TUN: (1) старт — радио начинало с пустого DUC-FIFO; (2) по
ходу — пейсинг со строгой каденцией держал запас FIFO у нуля, джиттер
3-6мс давал underrun; (3) клип: голос из бэклога складывался с PostGen-
тоном (стоит после ALC) поверх шкалы, а сам тон при mag=1.0 давал
выброс Гиббса x1.18 на старте. Фиксы: pre-roll PrimeDUCIQ (THROTTLE +
3 блока подушки в очереди — sender метрит непрерывно, FIFO радио прижат
2000..2400), анти-старв доливка нулями до REANCHOR при просадке,
TUN_TONE_MAG=0.83, мьют mic-входа TXA при активном тоне.
- Огрызок TUN-тона в эфир на первом блоке следующего TX: стоп канала
dmode=1 ждал фейд, который некому прокрутить (продюсер уже остановлен) —
канал стопился по таймауту Sleep(1)x100 с недобранным сигналом в
буферах. Это же было и «остаточным фризом ~106мс» GUI на отпускании
PTT: чистый таймаут, фейд никогда не исполнялся. Фикс: dmode=0 +
синхронный слив/продувка канала 4 нулевыми fexchange0 на стопе и
старте (<1мс, штатные down-slew и flush).
- Дисплей: ResetPixelBuffers(TX_DISP_ID) на старте TX — усредняющая
история TX-анализатора показывала спектр тона прошлой сессии.
- StopRunning при активном TUN не чистил DUC-очередь и не сбрасывал
FTuning/тон — закрыто.
Латентность тракта mic→радио теперь ~43мс (WDSP 11 + подушка 21 + FIFO
10.4). Проверено на эфире: q=0..30, fill/drops/underrun=0, clip=0,
клоки радио/ПК без дрейфа (micRx 48000/с).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- SPECTRUM_PIXELS 1024→4096 + настройка display_pixels (лимит точек
анализатора, SetDisplayResLimit); web-зеркало остаётся 1024 —
max-децимация в SnapshotPixels
- палитры водопада Classic/Inferno/Turbo + регулятор gamma (в LUT),
настройки wf_palette/wf_gamma, страница Waterfall → группа Rendering
- GL-водопад переведён на общий LUT/AGC-уровни базового класса
(убраны дубли ColorForDB/UpdateLevels)
- явные glViewport в GL-вьюхах умножаются на PixelScale (HiDPI),
текстура водопада — в физической ширине
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Independent RX slices sharing one panadapter: each has its own
freq/mode/filter/AGC/NR-NB-SNB-ANF, S-meter, volume and sound card.
VFO A = main receiver; B+ are soft fan-out WDSP channels within the
captured span (ideal for Pluto/QO-100; HW DDC path is Stage 3).
- WDSPEngine: soft fan-out (TSliceChan, ProcessSlices in DSP thread,
per-slice NCO/mode/filter/AGC/NR/NB/SNB/ANF + S-meter, OnSliceAudio),
FSliceLock serialises add/remove/set vs DSP; reopened on rate change.
- RadioController: TCtrlSlice with its own TAudioOutput/device,
Add/Remove/SetSlice*, RefreshSliceShifts keeps slices on target Hz
across centre changes. Slice letters start at 'B' (A = main receiver).
- UI: per-slice flags (VfoOverlay) with letter badge/close, filter-band
markers in BOTH GL and CPU renderers, drag-retune, wheel; flags placed
beside the filter band with flip, like the main flag. Ctrl+click adds.
- Main flag (slice A) visible whenever the left panel is hidden OR any
slice exists; synced with panel-driven DSP/mode/AGC changes.
- Perf: S-meter decoupled from flag chrome cache (FMeterDirty +
meter-only RefreshMeter via EnsureRendered); InvalidateVfoOverlay
avoids global overlay-cache churn on meter ticks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Зум был 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>
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>
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>
Реализованы команды, ранее бывшие заглушками — функционал уже был в
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- Status bar / web now show Pluto/LibreSDR model + serial instead of
"Unknown Board" (BoardDisplayName in controller; UI just renders it).
- Field 3 shows AD936x chip temperature + RX RSSI for Pluto (no PA →
Supply only for openHPSDR). Polled via backend.ReadTelemetry (~2 Hz),
filtered for sensor glitches (plausibility window + slew-limit, with
anti-stick) — fixes rare bogus -15°C readings.
- Add libiio binding iio_channel_attr_read_double.
- Remove stale root pluto_integration.MD (superseded by
doc/PLUTO_INTEGRATION_PLAN.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Builds on the beacon-lock baseline: keeps the proven display-FFT control
path (hardware-LO trim folded into the active transverter LOError), and
hardens it. The narrowband NCO/FLL scaffold (BeaconLock.pas) is removed —
it was never in the control path.
- RadioController:
* Persist live: each correction folds straight into the active xvtr
LOError (visible in Settings, survives restart as LNB calibration);
disk writes throttled. FBeaconLockHz is now only a session indicator.
* Robust tracking vs nearby signals: narrow ±2 kHz gate while locked
(±6 kHz only for acquisition), slew-rate outlier rejection (a sudden
centroid jump = interferer, not slow LNB drift → ignored), and lobe
shape validation in MeasureBeaconFFT (width + power symmetry) so a CW
carrier / one-sided neighbour isn't mistaken for the BPSK beacon.
Prominence measured over the in-window noise floor (gate-width robust).
* Fast convergence: feed-forward (predict post-retune position so the
window follows the beacon through the jump) + near-deadbeat correction
on the manual click → one click locks instead of repeated tapping.
* Single central BPSK beacon (10489.750); dead CoarseBeaconOffset and
unused throttle counters removed; stale NCO/FLL/4 Hz comments fixed.
- WDSPEngine: drop the per-sample TBeaconTracker RX-IQ tap (tracker gone).
- MainForm: BEACON is now a plain button, shown only on Pluto in a
transverter. Lock status (off / click / search / LOCK + correction +
prominence) moves to status-bar field 7 in place of PLL on Pluto;
openHPSDR keeps PLL there.
- SettingsForm/StatusBar: widen LO Error field to ±10 MHz (QO-100 LNBs
drift hundreds of kHz) and widen status field 7 for the beacon text.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Click the beacon on the spectrum; the controller tracks it and continuously
trims the transverter LO so the whole downlink stays put as the LNB drifts.
- BeaconLock.pas: TBeaconTracker scaffold (narrowband NCO/FLL) — kept but no
longer in the control path (FLL diverged on the suppressed-carrier BPSK
beacon + Pluto DC offset). Measurement now uses the proven display-FFT path.
- RadioController: FBeaconLockHz folded into XvtrTranslate (on top of LOError).
MeasureBeaconFFT finds the beacon by POWER-WEIGHTED CENTROID of the lobe
(BPSK has a suppressed carrier → broad symmetric ~2.4 kHz lobe, not a line;
a peak-finder jitters, the centroid sits on the carrier). Track position in
ABSOLUTE display Hz (FBeaconTrackHz) so scrolling the waterfall/centre never
moves the search window off the beacon. Closed loop: e = tracked - ref,
d(e)/d(LockHz) = -1 -> LockHz += gain*e; settle cooldown after each correction
(display-FFT lags the LO retune -> avoids overshoot); deadband 75 Hz +
centroid/prominence smoothing + lock hysteresis for a steady lock.
Correction applied only after the user clicks (manual seed), so a crowded
band can't pull a neighbour to the reference.
- WDSPEngine: SetBeaconTracker + RX-IQ tap (tracker idle now, ~1 check/sample).
- SpectrumView + GL: beacon markers — green = reference (10489.750), orange =
tracked centroid.
- MainForm: BEACON button (RX block, XVTR-only). Flow: BEACON -> "CLICK BCN"
-> click beacon -> "L<corr> /<prom>". ServiceBeaconLock driven by MeterTimer.
Reference defaults to the middle BPSK beacon 10489.750. Tested on-air against
Es'hail-2: locks and holds, survives waterfall drag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CTun position was lost on band restore / restart, and VHF/UHF bands were
never saved on Pluto.
- SaveCurrentBand: the >61 MHz guard (HPSDR HF-only) skipped saving native
Pluto VHF/UHF bands → 2m/70cm reverted to defaults. Now HPSDR-only; Pluto
saves VU-plan bands (FreqToBandIdx guards cross-band pollution).
- Persist DDC center for CTun: new TBandSettings.CenterHz (+ JSON center_hz)
and TXvtrEntry.LastCenterHz (+ last_center_hz). RestoreBand / ActivateXvtr
restore the saved center when CTun is on and the offset fits the span,
instead of forcing center := VFO (which re-centered the receiver).
- UpdateFreqDisplayMax: freq-display upper bound from active transverter's
FreqEnd (lets QO-100 show 10 GHz past the 6 GHz Pluto cap).
- QO-100 transverter template (slot 2, disabled): downlink 10489.5–10490.0,
LO 9750. MigrateXvtrTemplate injects it into the first free slot for
devices that already have a saved xvtr config (template was otherwise
masked by the loaded slots).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- RX hw-gain (Pluto/AD936x): backend SetRxGain(ModeIdx, GainDb) — gain_control_mode
(manual/fast_attack/slow_attack/hybrid) + hardwaregain. Controller FRxGainMode/
FRxGainDb, commands SetRxGainMode/SetRxGain/RxGainBy, rfRxGain event, snapshot,
applied in StartRunning. Persisted per-device (rx_gain_mode/rx_gain_db). HPSDR
unaffected (backend no-op).
- UI: separate "RF AGC" panel below the (unchanged) WDSP AGC block, shown only for
Pluto — FAST/SLOW/HYB/MAN mode buttons + manual GAIN slider. LayoutLeftPanel
reflows lower panels via RelayoutBelowBands; no layout shift on HPSDR.
- Cold-init fix: direct sampling_frequency write doesn't load the AD9361 FIR
decimation filter, so a cold-booted Pluto showed a wide centre spike that didn't
track tuning (worked only after SDR Console pre-initialised it). Add optional
libad9361 binding (dynamic) and call ad9361_set_bb_rate() in ApplySampleRate,
which configures BBPLL + loads the FIR; falls back to direct write if absent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ADALM-Pluto / AD9361 support as a second hardware backend alongside
openHPSDR, sharing the WDSP DSP pipeline and the existing controller API
(UI stays decoupled from logic).
- RadioBackend.pas: abstract TRadioBackend + TBackendCaps + TRadioDevice
(Kind/URI/Serial). THPSDRNetwork now derives from it (state via virtual
getters); TRadioController.FNetwork is the base type.
- IIOBindings.pas: dynamic libiio loader (runs without libiio present).
- PlutoBackend.pas: scan/probe-by-URI, connect, LO/rate/bandwidth/gain
control, RX streaming thread (int16->24bit BE -> OnDDCIQ), Q conjugated
to match WDSP IQ convention. Verified on LibreSDR (AD9361) over network.
- Unified discovery: TDiscoverThread scans both backends; network Plutos
found via direct ProbeURI (no mDNS needed). ConnectDevice dispatches by
Dev.Kind (EnsureBackend swaps backend, preserving callbacks).
- DeviceStore/DeviceForm: persist Kind/URI/Serial; save Pluto via
AddSavedPluto so saved/autostart devices reconnect across restarts.
- VHF/UHF band plan (BoardUtils, kind-aware): 6m..ADS-B for Pluto; fixes
HF clamps (band detect, mouse-wheel 60 MHz cap, freq-display max).
- SampleRateOverlay: configurable presets (Pluto 576k..5760k, >520 ksps),
auto-width to fit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1. StartRunning applied AGC mode via a raw enum cast (TWDSPAGCMode(FAGCMode))
instead of the index→enum table, inverting Fast↔Off on every connect/go-live
(GUI and daemon). Now uses ApplyAGCToEngine like the rest of the unit.
2. SetFilterIdx indexed the per-mode BW/deviation tables with an unchecked Idx
(FM tables hold only 2 entries) — out-of-bounds when driven from CAT/web.
Now clamps Idx per mode (EnsureRange).
3. ConnectDevice loaded FActiveVfo / FCurrentBand straight from config without
range validation; a corrupt/old blob could leave FCurrentBand invalid and
make later SetMode/SetFilterIdx write past FBandCache. Both now clamped.
4. VfoSwap did not run the bandstack DSP restore that SetActiveVfo does, so a
swap to a VFO on another band kept the old mode/filter/AGC while the band
button highlighted the new band. Now mirrors SetActiveVfo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ConnectAndRun (ConnectDevice + RestoreBand + XVTR restore + active-VFO retune +
StartRunning) and StopAndDisconnect (StopRunning + settings persist + Disconnect)
move into TRadioController, making the full connect/disconnect lifecycle callable
headless. MainForm.DoConnectDevice/DoStopAndDisconnect become thin wrappers that
only add UI-extras (status strings, AGCTop mirror, spectrum reset/resize,
offline status, A-VFO highlight). Behavior-preserving: status/highlight now run
after bring-up, consistent with the batch-29 precedent (no packets yet, safe).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>