From 6375092741fc91cd14624e8a90f74f77813cbcaa Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 23 Jun 2026 19:10:07 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20pluto=20telemetry=20=E2=80=94=20show=20?= =?UTF-8?q?both=20temperatures=20(transceiver=20+=20Zynq=20SoC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- MainForm.pas | 10 ++++++++-- PlutoBackend.pas | 48 +++++++++++++++++++++++++++++++++------------ RadioBackend.pas | 16 ++++++++------- RadioController.pas | 22 ++++++++++++++------- StatusBar.pas | 3 ++- WebAdapter.pas | 12 +++++++++--- 6 files changed, 79 insertions(+), 32 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index b991f53..48dbaec 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -3240,8 +3240,14 @@ begin begin if not FController.FRunning then begin SetStatusText(3, 'Pluto --'); Exit; end; S := ''; - if FController.FLastPlutoTempC > TELEMETRY_NONE then - S := Format('Temp %.0fC', [FController.FLastPlutoTempC]); + // Temp X/YC: X — трансивер AD9361, Y — Zynq SoC/FPGA (xadc). + if (FController.FLastPlutoTempC > TELEMETRY_NONE) + and (FController.FLastPlutoSocTempC > TELEMETRY_NONE) then + S := Format('Temp %.0f/%.0fC', [FController.FLastPlutoTempC, FController.FLastPlutoSocTempC]) + else if FController.FLastPlutoTempC > TELEMETRY_NONE then + S := Format('Temp %.0fC', [FController.FLastPlutoTempC]) + else if FController.FLastPlutoSocTempC > TELEMETRY_NONE then + S := Format('Temp %.0fC', [FController.FLastPlutoSocTempC]); if FController.FLastPlutoRSSIdB > TELEMETRY_NONE then begin if S <> '' then S := S + ' '; diff --git a/PlutoBackend.pas b/PlutoBackend.pas index ca99191..6222a22 100644 --- a/PlutoBackend.pas +++ b/PlutoBackend.pas @@ -128,7 +128,7 @@ type procedure SetTxAtten(Db: Double); override; procedure SendDUCIQ(const IData, QData: array of Integer); override; procedure ClearDUCIQQueue; override; - function ReadTelemetry(out TempC, RSSIdB: Double): Boolean; override; + function ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; override; end; // Стабильный 6-байтовый ключ (synthetic MAC) из serial/URI — для per-device @@ -904,19 +904,23 @@ begin LeaveCriticalSection(FTxLock); end; -function TPlutoBackend.ReadTelemetry(out TempC, RSSIdB: Double): Boolean; -// Опрос аппаратной телеметрии ad9361-phy (вызывается контроллером, не из RX-потока): -// temp0/input — температура чипа в миллиградусах C (longlong); -// voltage0/rssi — RSSI входного тракта в дБ (double; строка вида '12.34 dB', -// libiio парсит ведущее число). Валиден только во время приёма. +function TPlutoBackend.ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; +// Опрос аппаратной телеметрии (вызывается контроллером, не из RX-потока): +// ad9361-phy temp0/input — температура трансивера в миллиградусах C (longlong); +// xadc temp0 — температура Zynq SoC/FPGA; сырой raw + offset (longlong) +// + scale (double), C = (raw + offset) * scale / 1000; +// ad9361-phy voltage0/rssi — RSSI входного тракта в дБ (double; строка '12.34 dB', +// libiio парсит ведущее число). Валиден только во время приёма. var - Ch: Piio_channel; - raw: clonglong; - d: cdouble; + Ch, ChX: Piio_channel; + XDev: Piio_device; + raw, offs: clonglong; + scale, d: cdouble; begin - TempC := TELEMETRY_NONE; - RSSIdB := TELEMETRY_NONE; - Result := False; + TempC := TELEMETRY_NONE; + SocTempC := TELEMETRY_NONE; + RSSIdB := TELEMETRY_NONE; + Result := False; if (not FConnected) or (FPhy = nil) or (not IIOLoaded) then Exit; Ch := iio_device_find_channel(FPhy, 'temp0', 0); @@ -926,6 +930,26 @@ begin Result := True; end; + // Температура Zynq SoC/FPGA — отдельное IIO-устройство 'xadc' (всегда присутствует). + // Канал отдаёт сырой код + offset + scale (в отличие от ad9361, дающего милли-°C): + // T(°C) = (raw + offset) * scale / 1000. + if FCtx <> nil then + begin + XDev := iio_context_find_device(FCtx, 'xadc'); + if XDev <> nil then + begin + ChX := iio_device_find_channel(XDev, 'temp0', 0); + if (ChX <> nil) and Assigned(iio_channel_attr_read_double) + and (iio_channel_attr_read_longlong(ChX, 'raw', raw) = 0) + and (iio_channel_attr_read_longlong(ChX, 'offset', offs) = 0) + and (iio_channel_attr_read_double(ChX, 'scale', scale) = 0) then + begin + SocTempC := (raw + offs) * scale / 1000.0; + Result := True; + end; + end; + end; + // RSSI имеет смысл только при активном RX-стриме (иначе чтение даёт ошибку/мусор). if FStreaming then begin diff --git a/RadioBackend.pas b/RadioBackend.pas index f8a89f3..f0a2101 100644 --- a/RadioBackend.pas +++ b/RadioBackend.pas @@ -154,9 +154,10 @@ type procedure SetSpeakerAudio(AEnabled: Boolean); virtual; // Аппаратная телеметрия, не приходящая в потоке (опрашивается контроллером). - // Pluto/AD936x: температура чипа (°C) и RSSI приёмника (дБ). HPSDR — нет такой - // (использует HP-status), возвращает False. Недоступные значения = TELEMETRY_NONE. - function ReadTelemetry(out TempC, RSSIdB: Double): Boolean; virtual; + // Pluto/AD936x: температура трансивера (TempC) и Zynq SoC/FPGA (SocTempC) в °C, + // RSSI приёмника (дБ). HPSDR — нет такой (использует HP-status), возвращает False. + // Недоступные значения = TELEMETRY_NONE. + function ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; virtual; // ---- Свойства состояния (полиморфные через виртуальные геттеры) ---- property Connected: Boolean read GetConnected; @@ -246,11 +247,12 @@ procedure TRadioBackend.SetSpeakerAudio(AEnabled: Boolean); begin end; -function TRadioBackend.ReadTelemetry(out TempC, RSSIdB: Double): Boolean; +function TRadioBackend.ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; begin - TempC := TELEMETRY_NONE; - RSSIdB := TELEMETRY_NONE; - Result := False; + TempC := TELEMETRY_NONE; + SocTempC := TELEMETRY_NONE; + RSSIdB := TELEMETRY_NONE; + Result := False; end; procedure TRadioBackend.SetTxAtten(Db: Double); diff --git a/RadioController.pas b/RadioController.pas index bd3e7f0..dd318ad 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -279,9 +279,10 @@ type // ---- Телеметрия (наполняется в Фазе 3/4 вместе с сетевыми колбэками) ---- FLastSMeter, FLastFwdW, FLastSWR, FLastSupplyV, FLastSupplyA: Double; FLastPLLLock: Boolean; - // Pluto-телеметрия (нет HP-status): температура чипа °C / RSSI приёмника дБ. - // Опрашиваются ServicePlutoTelemetry с UI-таймера; TELEMETRY_NONE = нет данных. - FLastPlutoTempC, FLastPlutoRSSIdB: Double; + // Pluto-телеметрия (нет HP-status): температура трансивера °C / Zynq SoC °C / + // RSSI приёмника дБ. Опрашиваются ServicePlutoTelemetry с UI-таймера; + // TELEMETRY_NONE = нет данных. + FLastPlutoTempC, FLastPlutoSocTempC, FLastPlutoRSSIdB: Double; FPlutoTeleTick: QWord; // GetTickCount64 последнего опроса (троттлинг) FPlutoTempBad: Integer; // подряд отброшенных выбросов температуры (анти-залипание) @@ -602,7 +603,8 @@ begin FFMStepOn := True; FLocalAudio := True; // GUI: звук на локальную карту; демон выключит явно FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1; - FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoRSSIdB := TELEMETRY_NONE; + FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoSocTempC := TELEMETRY_NONE; + FLastPlutoRSSIdB := TELEMETRY_NONE; FPlutoTeleTick := 0; FPlutoTempBad := 0; FillChar(FDevMAC, SizeOf(FDevMAC), 0); end; @@ -2809,7 +2811,8 @@ begin end; FRXPacketCount := 0; FActiveDDC := 0; - FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoRSSIdB := TELEMETRY_NONE; + FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoSocTempC := TELEMETRY_NONE; + FLastPlutoRSSIdB := TELEMETRY_NONE; FPlutoTeleTick := 0; FPlutoTempBad := 0; Changed(rfRunning); // безусловный сброс run-кнопки/MOX.Enabled/sleep (как старый хвост STOP) end; @@ -3201,14 +3204,14 @@ const TEMP_STEP_C = 12.0; // макс. физичный шаг между опросами (тепловая инерция чипа) TEMP_FORCE = 3; // принять «выброс», если держится N раз подряд (не залипаем) var - t, r: Double; + t, s, r: Double; now_: QWord; begin if (FNetwork = nil) or (not FRunning) or (FNetwork.Caps.Kind <> bkPluto) then Exit; now_ := GetTickCount64; if (FPlutoTeleTick <> 0) and (now_ - FPlutoTeleTick < POLL_MS) then Exit; FPlutoTeleTick := now_; - if not FNetwork.ReadTelemetry(t, r) then Exit; + if not FNetwork.ReadTelemetry(t, s, r) then Exit; // Температура: датчик AD9361 иногда отдаёт мусор (мид-конверсия / контеншн на // USB-контексте с RX-потоком) — наблюдалось редкое -15 °C. Фильтруем: жёсткое @@ -3232,6 +3235,11 @@ begin // вне окна правдоподобия — молча отбрасываем, держим прошлое валидное значение end; + // Температура Zynq SoC (xadc): стабильный локальный датчик (не подвержен мусору + // мид-конверсии AD9361) — достаточно окна правдоподобия без slew-limit. + if (s > TELEMETRY_NONE) and (s >= TEMP_MIN_C) and (s <= TEMP_MAX_C) then + FLastPlutoSocTempC := s; + // RSSI быстро меняется с сигналом — без сглаживания; обновляем, только если валиден. if r > TELEMETRY_NONE then FLastPlutoRSSIdB := r; diff --git a/StatusBar.pas b/StatusBar.pas index 5616fe4..1b031a8 100644 --- a/StatusBar.pas +++ b/StatusBar.pas @@ -34,7 +34,8 @@ const SEQ_MIN_W = 90; STATUS_ORDER: array[0..7] of Integer = (2, 0, 1, 3, 7, 4, 5, 6); // поле 7: PLL (openHPSDR) ИЛИ статус QO-100 beacon lock (Pluto) — шире под "BCN LOCK …". - BASE_W: array[0..7] of Integer = (280, 160, 170, 150, 190, 180, 130, 200); + // поле 3: Supply (HPSDR) ИЛИ две температуры Pluto "Temp 37/53C RSSI -89dB" — шире. + BASE_W: array[0..7] of Integer = (280, 160, 170, 190, 190, 180, 130, 200); INITIAL_TEXT: array[0..7] of string = ( 'Board --', 'IP --', diff --git a/WebAdapter.pas b/WebAdapter.pas index 37d9057..88e32eb 100644 --- a/WebAdapter.pas +++ b/WebAdapter.pas @@ -460,10 +460,16 @@ begin if FController.IsPluto then begin - // Pluto: нет PA/supply — температура чипа + RSSI приёмника (telemetry-поле). + // Pluto: нет PA/supply — температуры (трансивер/SoC) + RSSI (telemetry-поле). + // Temp X/YC: X — трансивер AD9361, Y — Zynq SoC/FPGA (xadc). SupplyText := ''; - if FController.FLastPlutoTempC > TELEMETRY_NONE then - SupplyText := Format('Temp %.0fC', [FController.FLastPlutoTempC]); + if (FController.FLastPlutoTempC > TELEMETRY_NONE) + and (FController.FLastPlutoSocTempC > TELEMETRY_NONE) then + SupplyText := Format('Temp %.0f/%.0fC', [FController.FLastPlutoTempC, FController.FLastPlutoSocTempC]) + else if FController.FLastPlutoTempC > TELEMETRY_NONE then + SupplyText := Format('Temp %.0fC', [FController.FLastPlutoTempC]) + else if FController.FLastPlutoSocTempC > TELEMETRY_NONE then + SupplyText := Format('Temp %.0fC', [FController.FLastPlutoSocTempC]); if FController.FLastPlutoRSSIdB > TELEMETRY_NONE then begin if SupplyText <> '' then SupplyText := SupplyText + ' ';