Чтобы понять «попал или нет», приходилось переводить взгляд на плитки метрик
под графиком. Теперь это читается в самой лупе:
- Полоса приёма показывает захват начертанием: в поиске три линии пунктирные,
на локе — сплошные и стянуты сверху скобой. Новых цветов не добавлено, та же
семантика $0020D0FF, что и на главном спектре.
- Строка захвата под частотой центра: LOCK · 14.2 dB (зелёная) / SEARCH (янтарь)
/ BEACON OFF. Состояние берётся из BeaconState — это гистерезисный
FBeaconLocked, которому доверяет сам контур коррекции, а не мгновенный
CarrierLock: тот мерцал бы на границе.
- Δ от опорной рядом: расхождение измеренной частоты маяка с опорной, то есть
расстояние между оранжевым и зелёным маркерами числом. Это невязка, которую
лок стекает в LOError; в пределах дедбэнда (20 Гц) — зелёная, иначе янтарная.
Знак у Δ ставится вручную: флага '+' в Format Object Pascal нет (это printf'изм),
'%+.0f' молча съедается и в строку попадает голый хвост спецификатора — на
экране было «Δ .0f Hz» вместо значения.
Состояние и SNR снимаются раз в тик в PollZoom и кладутся в поля: копия
TBeaconScope в отрисовку не лезет, а SNR сравнивается с допуском 0.2 дБ —
иначе дрожание в сотых долях дБ пересобирало бы кадр каждый тик и dirty-флаг
потерял бы смысл.
Сборка ewsdr (--ws=qt6) — ОК.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ширина, положение и состояние FOLLOW уезжали в дефолт при каждом открытии
окна: настроил 5 кГц вокруг маяка, закрыл — в следующий раз снова 40 кГц.
Положение хранится СДВИГОМ от опорной частоты маяка, а не абсолютом. Абсолют
протух бы от любой правки опорной частоты, а главное — калибровка LOError
копится от сеанса к сеансу и двигает шкалу, так что сохранённая абсолютная
частота через неделю указывала бы уже не туда.
- Settings: BeaconZoomSpanHz/OffsetHz/Follow в TGlobalSettings (ключи
beacon_zoom_span/offset/follow), дефолты и чтение/запись по общему пути.
- RadioController: FBcnZoom* с клампом при загрузке (span 600..200000 Гц,
сдвиг ±500 кГц) — руками испорченный JSON окно не сломает.
- BeaconScopeForm: RestoreZoomState в DoShow, StoreZoomState каждый тик из
PollZoom. Раз состояние пишется в одном месте, оно не разъедется, каким бы
путём ни менялось — колесом, drag'ом, кнопками или автоподтяжкой FOLLOW.
Наведение декодера за краем восстановленного окна подтянет сам FOLLOW на
первом тике, отдельной логики не потребовалось.
Сборка ewsdr (--ws=qt6) и ewsdrd — ОК.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Перерисовку спектра лупы гейтил не тот сигнал: возврат GetBeaconZoomSpectrum
(«в кэше лежит непрочитанный кадр») игнорировался — функция звалась как
процедура, — а FZoomDirty ставился только по смене границ окна и по сдвигу
маркеров. Пока никто не панорамирует, границы не меняются, а до клика все три
маркерные частоты нулевые и неподвижные: кадр собирался один раз и застывал.
Клик задаёт наведение декодера, дальше BeaconDecodeFreqHz непрерывно едет за
несущей в ServiceBeaconLock — сравнение с FLastDecHz начинает срабатывать
каждый тик, и спектр «оживает». Водопад работал с самого начала: он всегда шёл
по своему честному флагу свежести, хотя слой тот же самый и анализатор один.
Теперь свежесть кадра — основной триггер (трасса идёт 15/с, как и даёт
анализатор); проверки окна и маркеров оставлены как дополнительные, они дают
отклик на зум/пан не дожидаясь следующего кадра.
Сборка ewsdr (--ws=qt6) — ОК.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
На спектрограмме 576к центральный маяк — пара пикселей, ткнуть в него для
наведения декодера почти нельзя. В окне Beacon появилась карточка BEACON
TUNING: узкий кусок спектра вокруг маяка в высоком разрешении + мини-водопад,
клик по нему = BeaconSeedAtHz, ровно как клик по главному спектру. Наведение
с главной спектрограммы не тронуто.
Увеличение делает сам WDSP, а не растяжка бинов главного дисплея: второй
analyzer BCN_DISP_ID=4 на том же IQ главного тракта, со своей FFT и своим
span-clip. Клипы fscLin/fscHin считаются прямо из желаемых границ окна в
герцах, а не из зум-слайдера — анализатор умеет ассиметричный клип, поэтому
окно ставится в любое место полосы захвата, а не только вокруг центра DDC.
- WDSPEngine: SetBeaconZoom + GetBeaconZoom{Spectrum,Waterfall}. Analyzer живёт
только пока окно маяка открыто; весь доступ к disp'у под FBcnLock (SetAnalyzer
из UI, Spectrum0 из DSP-потока, GetPixels из display-потока). Кадр (1024
точки) фиксирован по размеру — ресайз окна не переармирует analyzer и не
морозит спектр на наполнение FFT-окна.
- Span-clip НЕ удешевляет FFT — она по всей полосе захвата. Отсюда потолок
262144 и своя пониженная частота кадров BCN_ZOOM_FPS=15 через ovrlp: на 60 fps
это было бы ядро под нагрузкой ради картинки, в которой ничего не меняется.
Переарм по смене sample rate, детектор/усреднение — без переарма.
- RadioController: SetBeaconZoomWindow / GetBeaconZoom* / GetCaptureWindow /
BeaconZoomBinHz. Наружу — абсолютные display-Гц, те же, в которых живут
BeaconSeedAtHz и маркеры главного спектра.
- BeaconScopeForm: клик = наведение, колесо = зум вокруг курсора, drag = пан,
двойной клик = центрировать, кнопки -/+/RESET/FOLLOW (FOLLOW подтягивает окно
только когда маркер ушёл за край, а не возит картинку постоянно). Маркеры —
теми же цветами и с той же семантикой, что DrawBeaconMarkersRaw/GL: опорная
пунктиром, центроид, три линии полосы приёма ±450 Гц, без полупрозрачной
заливки. Водопад — TWaterfallView с палитрой/гаммой из контроллера.
- Рендер по конвенции проекта: кадр лупы собирается в офскрин только по
FZoomDirty (новый кадр анализатора, сдвиг маркеров/окна, ресайз, тема), Paint
= блит + курсор. Констелляция с метриками переведена туда же: статичная
обвязка (карточки, оси, рамки и подписи плиток) кэшируется в FScopeChrome и
пересобирается лишь на ресайз/смену темы — раньше десяток RoundRect и TextOut
рисовались заново 25 раз в секунду прямо в обработчике Paint.
Лупа рисуется на CPU: GL-путь в проекте есть только у главного панафолла и
только под ключом, все прочие панели — обычные TPaintBox.
Сборка ewsdr (--ws=qt6) и ewsdrd — ОК. На железе не проверено.
Co-Authored-By: Claude Opus 5 <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>