mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-27 02:37:32 +00:00
feat: Pluto board name + hardware telemetry in status bar
- 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>
This commit is contained in:
@@ -7,6 +7,8 @@ unit BoardUtils;
|
|||||||
interface
|
interface
|
||||||
|
|
||||||
function BoardTypeName(BoardType: Integer): string;
|
function BoardTypeName(BoardType: Integer): string;
|
||||||
|
// Короткое человекочитаемое имя AD936x-платы по hw_model (Pluto / LibreSDR / прочие).
|
||||||
|
function PlutoModelName(const HwModel: string): string;
|
||||||
// Индекс диапазона по частоте. Pluto=True → VHF/UHF план, иначе HF (openHPSDR).
|
// Индекс диапазона по частоте. Pluto=True → VHF/UHF план, иначе HF (openHPSDR).
|
||||||
function FreqToBandIdx(Hz: Double; Pluto: Boolean = False): Integer;
|
function FreqToBandIdx(Hz: Double; Pluto: Boolean = False): Integer;
|
||||||
// Короткое имя диапазона для кнопки (зависит от плана).
|
// Короткое имя диапазона для кнопки (зависит от плана).
|
||||||
@@ -61,6 +63,19 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function PlutoModelName(const HwModel: string): string;
|
||||||
|
var
|
||||||
|
L: string;
|
||||||
|
begin
|
||||||
|
// hw_model клонов AD936x обычно содержит маркер платы. Узнаём Pluto/LibreSDR,
|
||||||
|
// иначе показываем сырой hw_model, а при его отсутствии — обобщённое имя.
|
||||||
|
L := LowerCase(HwModel);
|
||||||
|
if Pos('libre', L) > 0 then Result := 'LibreSDR'
|
||||||
|
else if Pos('pluto', L) > 0 then Result := 'PlutoSDR'
|
||||||
|
else if HwModel <> '' then Result := HwModel
|
||||||
|
else Result := 'AD936x SDR';
|
||||||
|
end;
|
||||||
|
|
||||||
function FreqToBandIdx(Hz: Double; Pluto: Boolean): Integer;
|
function FreqToBandIdx(Hz: Double; Pluto: Boolean): Integer;
|
||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ var
|
|||||||
// ---- Атрибуты ----
|
// ---- Атрибуты ----
|
||||||
iio_channel_attr_read_longlong: function(chn: Piio_channel; attr: PAnsiChar;
|
iio_channel_attr_read_longlong: function(chn: Piio_channel; attr: PAnsiChar;
|
||||||
out val: clonglong): cint; cdecl;
|
out val: clonglong): cint; cdecl;
|
||||||
|
iio_channel_attr_read_double: function(chn: Piio_channel; attr: PAnsiChar;
|
||||||
|
out val: cdouble): cint; cdecl;
|
||||||
iio_channel_attr_write_longlong: function(chn: Piio_channel; attr: PAnsiChar;
|
iio_channel_attr_write_longlong: function(chn: Piio_channel; attr: PAnsiChar;
|
||||||
val: clonglong): cint; cdecl;
|
val: clonglong): cint; cdecl;
|
||||||
iio_channel_attr_write: function(chn: Piio_channel; attr: PAnsiChar;
|
iio_channel_attr_write: function(chn: Piio_channel; attr: PAnsiChar;
|
||||||
@@ -161,6 +163,7 @@ begin
|
|||||||
iio_channel_enable := Sym('iio_channel_enable');
|
iio_channel_enable := Sym('iio_channel_enable');
|
||||||
iio_channel_disable := Sym('iio_channel_disable');
|
iio_channel_disable := Sym('iio_channel_disable');
|
||||||
iio_channel_attr_read_longlong := Sym('iio_channel_attr_read_longlong');
|
iio_channel_attr_read_longlong := Sym('iio_channel_attr_read_longlong');
|
||||||
|
iio_channel_attr_read_double := Sym('iio_channel_attr_read_double');
|
||||||
iio_channel_attr_write_longlong := Sym('iio_channel_attr_write_longlong');
|
iio_channel_attr_write_longlong := Sym('iio_channel_attr_write_longlong');
|
||||||
iio_channel_attr_write := Sym('iio_channel_attr_write');
|
iio_channel_attr_write := Sym('iio_channel_attr_write');
|
||||||
iio_channel_attr_write_bool := Sym('iio_channel_attr_write_bool');
|
iio_channel_attr_write_bool := Sym('iio_channel_attr_write_bool');
|
||||||
|
|||||||
+30
-1
@@ -353,6 +353,7 @@ type
|
|||||||
// Public UI update (called from sync objects)
|
// Public UI update (called from sync objects)
|
||||||
procedure UpdateTXMeters;
|
procedure UpdateTXMeters;
|
||||||
procedure UpdateStatusField7; // поле 7: PLL (HPSDR) или beacon (Pluto)
|
procedure UpdateStatusField7; // поле 7: PLL (HPSDR) или beacon (Pluto)
|
||||||
|
procedure UpdateStatusField3; // поле 3: Supply (HPSDR) или Temp/RSSI (Pluto)
|
||||||
function BeaconStatusText: string; // компактный статус QO-100 beacon lock
|
function BeaconStatusText: string; // компактный статус QO-100 beacon lock
|
||||||
procedure DoUpdateDDCSeq(DDCIdx: Integer; Seq: LongWord);
|
procedure DoUpdateDDCSeq(DDCIdx: Integer; Seq: LongWord);
|
||||||
|
|
||||||
@@ -2972,6 +2973,8 @@ begin
|
|||||||
|
|
||||||
// QO-100 beacon lock: одна итерация контура (no-op если лок выключен).
|
// QO-100 beacon lock: одна итерация контура (no-op если лок выключен).
|
||||||
FController.ServiceBeaconLock;
|
FController.ServiceBeaconLock;
|
||||||
|
// Pluto: опрос температуры/RSSI (троттлинг внутри; no-op для HPSDR).
|
||||||
|
FController.ServicePlutoTelemetry;
|
||||||
|
|
||||||
// Сглаженное среднее (EMA)
|
// Сглаженное среднее (EMA)
|
||||||
FSMeterAvg := FSMeterAvg * (1.0 - AVG_ALPHA) + FController.FLastSMeter * AVG_ALPHA;
|
FSMeterAvg := FSMeterAvg * (1.0 - AVG_ALPHA) + FController.FLastSMeter * AVG_ALPHA;
|
||||||
@@ -3179,6 +3182,32 @@ procedure TMainForm.UpdateTXMeters;
|
|||||||
// в TRadioController.ApplyHPStatus.
|
// в TRadioController.ApplyHPStatus.
|
||||||
begin
|
begin
|
||||||
UpdateStatusField7; // PLL (openHPSDR) или статус beacon lock (Pluto)
|
UpdateStatusField7; // PLL (openHPSDR) или статус beacon lock (Pluto)
|
||||||
|
UpdateStatusField3; // Supply (openHPSDR) или Temp/RSSI (Pluto)
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.UpdateStatusField3;
|
||||||
|
// Поле 3 статус-бара. openHPSDR — supply V/A (HP status). Pluto — температура
|
||||||
|
// чипа и RSSI приёмника (нет PA → supply неприменим). Значения берём из
|
||||||
|
// контроллера (FLastPlutoTempC/RSSIdB / FLastSupplyV/A) — логика опроса в ядре.
|
||||||
|
var
|
||||||
|
S: string;
|
||||||
|
begin
|
||||||
|
// Pluto: нет PA/supply — показываем аппаратную телеметрию AD936x.
|
||||||
|
if FController.IsPluto then
|
||||||
|
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]);
|
||||||
|
if FController.FLastPlutoRSSIdB > TELEMETRY_NONE then
|
||||||
|
begin
|
||||||
|
if S <> '' then S := S + ' ';
|
||||||
|
S := S + Format('RSSI %.0fdB', [FController.FLastPlutoRSSIdB]);
|
||||||
|
end;
|
||||||
|
if S = '' then S := 'Pluto --';
|
||||||
|
SetStatusText(3, S);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
if not FController.FRunning then
|
if not FController.FRunning then
|
||||||
begin
|
begin
|
||||||
@@ -3674,7 +3703,7 @@ begin
|
|||||||
UpdateVfoDisplay;
|
UpdateVfoDisplay;
|
||||||
|
|
||||||
SetStatusText(1, 'IP: ' + FController.FNetwork.Device.IPAddress);
|
SetStatusText(1, 'IP: ' + FController.FNetwork.Device.IPAddress);
|
||||||
SetStatusText(0, 'Board: ' + BoardTypeName(FController.FNetwork.Device.BoardType));
|
SetStatusText(0, 'Board: ' + FController.BoardDisplayName);
|
||||||
FSpecView.AGCTop := FController.FAGCTop;
|
FSpecView.AGCTop := FController.FAGCTop;
|
||||||
SetStatusText(6, 'SEQ OK');
|
SetStatusText(6, 'SEQ OK');
|
||||||
SetStatusText(2, 'Running');
|
SetStatusText(2, 'Running');
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ type
|
|||||||
procedure SendFullHP; override;
|
procedure SendFullHP; override;
|
||||||
procedure SendPTT(Active: Boolean); override;
|
procedure SendPTT(Active: Boolean); override;
|
||||||
procedure SetRxGain(ModeIdx: Integer; GainDb: Integer); override;
|
procedure SetRxGain(ModeIdx: Integer; GainDb: Integer); override;
|
||||||
|
function ReadTelemetry(out TempC, RSSIdB: Double): Boolean; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Стабильный 6-байтовый ключ (synthetic MAC) из serial/URI — для per-device
|
// Стабильный 6-байтовый ключ (synthetic MAC) из serial/URI — для per-device
|
||||||
@@ -578,4 +579,39 @@ begin
|
|||||||
if FConnected then ApplyGain;
|
if FConnected then ApplyGain;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TPlutoBackend.ReadTelemetry(out TempC, RSSIdB: Double): Boolean;
|
||||||
|
// Опрос аппаратной телеметрии ad9361-phy (вызывается контроллером, не из RX-потока):
|
||||||
|
// temp0/input — температура чипа в миллиградусах C (longlong);
|
||||||
|
// voltage0/rssi — RSSI входного тракта в дБ (double; строка вида '12.34 dB',
|
||||||
|
// libiio парсит ведущее число). Валиден только во время приёма.
|
||||||
|
var
|
||||||
|
Ch: Piio_channel;
|
||||||
|
raw: clonglong;
|
||||||
|
d: cdouble;
|
||||||
|
begin
|
||||||
|
TempC := 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);
|
||||||
|
if (Ch <> nil) and (iio_channel_attr_read_longlong(Ch, 'input', raw) = 0) then
|
||||||
|
begin
|
||||||
|
TempC := raw / 1000.0;
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// RSSI имеет смысл только при активном RX-стриме (иначе чтение даёт ошибку/мусор).
|
||||||
|
if FStreaming then
|
||||||
|
begin
|
||||||
|
Ch := iio_device_find_channel(FPhy, 'voltage0', 0);
|
||||||
|
if (Ch <> nil) and Assigned(iio_channel_attr_read_double)
|
||||||
|
and (iio_channel_attr_read_double(Ch, 'rssi', d) = 0) then
|
||||||
|
begin
|
||||||
|
RSSIdB := d;
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -73,6 +73,13 @@ type
|
|||||||
PRadioDevice = ^TRadioDevice;
|
PRadioDevice = ^TRadioDevice;
|
||||||
TRadioDeviceArray = array of TRadioDevice;
|
TRadioDeviceArray = array of TRadioDevice;
|
||||||
|
|
||||||
|
const
|
||||||
|
// Признак «значение телеметрии недоступно» (ReadTelemetry). Заведомо вне
|
||||||
|
// физического диапазона температур/RSSI.
|
||||||
|
TELEMETRY_NONE = -1.0E9;
|
||||||
|
|
||||||
|
type
|
||||||
|
|
||||||
// Колбэки данных (вызываются из рабочих потоков бэкенда).
|
// Колбэки данных (вызываются из рабочих потоков бэкенда).
|
||||||
TOnDeviceFound = procedure(const Dev: TRadioDevice) of object;
|
TOnDeviceFound = procedure(const Dev: TRadioDevice) of object;
|
||||||
TOnDDCIQPacket = procedure(DDCIndex: Integer;
|
TOnDDCIQPacket = procedure(DDCIndex: Integer;
|
||||||
@@ -143,6 +150,11 @@ type
|
|||||||
procedure SendSpeakerAudio(const Left, Right: array of Single; Count: Integer); virtual;
|
procedure SendSpeakerAudio(const Left, Right: array of Single; Count: Integer); virtual;
|
||||||
procedure SetSpeakerAudio(AEnabled: Boolean); virtual;
|
procedure SetSpeakerAudio(AEnabled: Boolean); virtual;
|
||||||
|
|
||||||
|
// Аппаратная телеметрия, не приходящая в потоке (опрашивается контроллером).
|
||||||
|
// Pluto/AD936x: температура чипа (°C) и RSSI приёмника (дБ). HPSDR — нет такой
|
||||||
|
// (использует HP-status), возвращает False. Недоступные значения = TELEMETRY_NONE.
|
||||||
|
function ReadTelemetry(out TempC, RSSIdB: Double): Boolean; virtual;
|
||||||
|
|
||||||
// ---- Свойства состояния (полиморфные через виртуальные геттеры) ----
|
// ---- Свойства состояния (полиморфные через виртуальные геттеры) ----
|
||||||
property Connected: Boolean read GetConnected;
|
property Connected: Boolean read GetConnected;
|
||||||
property Running: Boolean read GetRunning;
|
property Running: Boolean read GetRunning;
|
||||||
@@ -231,4 +243,11 @@ procedure TRadioBackend.SetSpeakerAudio(AEnabled: Boolean);
|
|||||||
begin
|
begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TRadioBackend.ReadTelemetry(out TempC, RSSIdB: Double): Boolean;
|
||||||
|
begin
|
||||||
|
TempC := TELEMETRY_NONE;
|
||||||
|
RSSIdB := TELEMETRY_NONE;
|
||||||
|
Result := False;
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -272,6 +272,11 @@ type
|
|||||||
// ---- Телеметрия (наполняется в Фазе 3/4 вместе с сетевыми колбэками) ----
|
// ---- Телеметрия (наполняется в Фазе 3/4 вместе с сетевыми колбэками) ----
|
||||||
FLastSMeter, FLastFwdW, FLastSWR, FLastSupplyV, FLastSupplyA: Double;
|
FLastSMeter, FLastFwdW, FLastSWR, FLastSupplyV, FLastSupplyA: Double;
|
||||||
FLastPLLLock: Boolean;
|
FLastPLLLock: Boolean;
|
||||||
|
// Pluto-телеметрия (нет HP-status): температура чипа °C / RSSI приёмника дБ.
|
||||||
|
// Опрашиваются ServicePlutoTelemetry с UI-таймера; TELEMETRY_NONE = нет данных.
|
||||||
|
FLastPlutoTempC, FLastPlutoRSSIdB: Double;
|
||||||
|
FPlutoTeleTick: QWord; // GetTickCount64 последнего опроса (троттлинг)
|
||||||
|
FPlutoTempBad: Integer; // подряд отброшенных выбросов температуры (анти-залипание)
|
||||||
|
|
||||||
// ---- RX/seq телеметрия data-path (пишется из сетевого колбэка OnDDCIQ) ----
|
// ---- RX/seq телеметрия data-path (пишется из сетевого колбэка OnDDCIQ) ----
|
||||||
FRXPacketCount: LongWord; // счётчик принятых IQ пакетов
|
FRXPacketCount: LongWord; // счётчик принятых IQ пакетов
|
||||||
@@ -515,6 +520,13 @@ type
|
|||||||
function BackendCaps: TBackendCaps;
|
function BackendCaps: TBackendCaps;
|
||||||
// True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF).
|
// True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF).
|
||||||
function IsPluto: Boolean;
|
function IsPluto: Boolean;
|
||||||
|
// Человекочитаемое имя подключённой платы для статус-строки (UI/Web).
|
||||||
|
// HPSDR → тип платы; Pluto/LibreSDR → hw_model + серийник (если есть).
|
||||||
|
function BoardDisplayName: string;
|
||||||
|
// Опрос аппаратной телеметрии Pluto (температура/RSSI) с троттлингом — зовётся
|
||||||
|
// с UI-таймера (MeterTimer @10 Гц), как ServiceBeaconLock. No-op для HPSDR.
|
||||||
|
// Результат — в FLastPlutoTempC/FLastPlutoRSSIdB (UI читает и форматирует).
|
||||||
|
procedure ServicePlutoTelemetry;
|
||||||
// Список URI для probe сетевых Pluto (mDNS недоступен) — задаёт фронтенд
|
// Список URI для probe сетевых Pluto (mDNS недоступен) — задаёт фронтенд
|
||||||
// перед Discover (сохранённые Pluto + ручной ввод IP).
|
// перед Discover (сохранённые Pluto + ручной ввод IP).
|
||||||
procedure SetPlutoProbeURIs(const URIs: array of string);
|
procedure SetPlutoProbeURIs(const URIs: array of string);
|
||||||
@@ -551,6 +563,8 @@ begin
|
|||||||
FWfAGCEnabled := True; FDitherEnabled := True; FRandomEnabled := True;
|
FWfAGCEnabled := True; FDitherEnabled := True; FRandomEnabled := True;
|
||||||
FFMStepOn := True;
|
FFMStepOn := True;
|
||||||
FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1;
|
FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1;
|
||||||
|
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoRSSIdB := TELEMETRY_NONE;
|
||||||
|
FPlutoTeleTick := 0; FPlutoTempBad := 0;
|
||||||
FillChar(FDevMAC, SizeOf(FDevMAC), 0);
|
FillChar(FDevMAC, SizeOf(FDevMAC), 0);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -2727,6 +2741,8 @@ begin
|
|||||||
end;
|
end;
|
||||||
FRXPacketCount := 0;
|
FRXPacketCount := 0;
|
||||||
FActiveDDC := 0;
|
FActiveDDC := 0;
|
||||||
|
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoRSSIdB := TELEMETRY_NONE;
|
||||||
|
FPlutoTeleTick := 0; FPlutoTempBad := 0;
|
||||||
Changed(rfRunning); // безусловный сброс run-кнопки/MOX.Enabled/sleep (как старый хвост STOP)
|
Changed(rfRunning); // безусловный сброс run-кнопки/MOX.Enabled/sleep (как старый хвост STOP)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -3021,6 +3037,70 @@ begin
|
|||||||
Result := (FNetwork <> nil) and (FNetwork.Caps.Kind = bkPluto);
|
Result := (FNetwork <> nil) and (FNetwork.Caps.Kind = bkPluto);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TRadioController.BoardDisplayName: string;
|
||||||
|
var
|
||||||
|
Dev: THPSDRDevice;
|
||||||
|
begin
|
||||||
|
if FNetwork = nil then Exit('');
|
||||||
|
Dev := FNetwork.Device;
|
||||||
|
if Dev.Kind = bkPluto then
|
||||||
|
begin
|
||||||
|
Result := PlutoModelName(Dev.Model);
|
||||||
|
// Серийник дописываем, только если это реальный hw_serial, а не URI-fallback
|
||||||
|
// ('usb:1.5.5' / 'ip:…'), который PlutoBackend кладёт при отсутствии серийника.
|
||||||
|
if (Dev.Serial <> '') and (Pos('usb:', Dev.Serial) = 0)
|
||||||
|
and (Pos('ip:', Dev.Serial) = 0) then
|
||||||
|
Result := Result + ' SN:' + Dev.Serial;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Result := BoardTypeName(Dev.BoardType);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.ServicePlutoTelemetry;
|
||||||
|
const
|
||||||
|
POLL_MS = 500; // опрос ~2 Гц: чтение attr — USB round-trip, чаще ни к чему
|
||||||
|
TEMP_MIN_C = 0.0; // работающий AD936x всегда теплее окружающей (само-нагрев)
|
||||||
|
TEMP_MAX_C = 120.0; // абсолютный потолок (датчик исправен)
|
||||||
|
TEMP_STEP_C = 12.0; // макс. физичный шаг между опросами (тепловая инерция чипа)
|
||||||
|
TEMP_FORCE = 3; // принять «выброс», если держится N раз подряд (не залипаем)
|
||||||
|
var
|
||||||
|
t, 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;
|
||||||
|
|
||||||
|
// Температура: датчик AD9361 иногда отдаёт мусор (мид-конверсия / контеншн на
|
||||||
|
// USB-контексте с RX-потоком) — наблюдалось редкое -15 °C. Фильтруем: жёсткое
|
||||||
|
// окно правдоподобия + slew-limit по тепловой инерции; устойчивый «выброс»
|
||||||
|
// (реальный скачок) всё же принимаем через TEMP_FORCE опросов, чтобы не залипнуть.
|
||||||
|
if t > TELEMETRY_NONE then
|
||||||
|
begin
|
||||||
|
if (t >= TEMP_MIN_C) and (t <= TEMP_MAX_C) then
|
||||||
|
begin
|
||||||
|
if (FLastPlutoTempC <= TELEMETRY_NONE) or (Abs(t - FLastPlutoTempC) <= TEMP_STEP_C) then
|
||||||
|
begin
|
||||||
|
FLastPlutoTempC := t; FPlutoTempBad := 0;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
Inc(FPlutoTempBad);
|
||||||
|
if FPlutoTempBad >= TEMP_FORCE then
|
||||||
|
begin FLastPlutoTempC := t; FPlutoTempBad := 0; end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
// вне окна правдоподобия — молча отбрасываем, держим прошлое валидное значение
|
||||||
|
end;
|
||||||
|
|
||||||
|
// RSSI быстро меняется с сигналом — без сглаживания; обновляем, только если валиден.
|
||||||
|
if r > TELEMETRY_NONE then FLastPlutoRSSIdB := r;
|
||||||
|
|
||||||
|
Changed(rfSupply); // поле телеметрии статус-бара (поле 3); web-адаптер тоже
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetPlutoProbeURIs(const URIs: array of string);
|
procedure TRadioController.SetPlutoProbeURIs(const URIs: array of string);
|
||||||
var i: Integer;
|
var i: Integer;
|
||||||
begin
|
begin
|
||||||
|
|||||||
+16
-3
@@ -19,7 +19,7 @@ interface
|
|||||||
|
|
||||||
uses
|
uses
|
||||||
Classes, SysUtils,
|
Classes, SysUtils,
|
||||||
RadioController, WebServer, WDSPEngine, HPSDRNetwork, HPSDRProtocol, BoardUtils, Settings;
|
RadioController, WebServer, WDSPEngine, HPSDRNetwork, HPSDRProtocol, RadioBackend, BoardUtils, Settings;
|
||||||
|
|
||||||
type
|
type
|
||||||
// UI/оркестрационные операции, которые адаптер дёргает у хозяина (пока не
|
// UI/оркестрационные операции, которые адаптер дёргает у хозяина (пока не
|
||||||
@@ -394,7 +394,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
if FController.FRunning then StatusText := 'Running'
|
if FController.FRunning then StatusText := 'Running'
|
||||||
else StatusText := 'Connected';
|
else StatusText := 'Connected';
|
||||||
BoardText := 'Board: ' + BoardTypeName(FController.FNetwork.Device.BoardType);
|
BoardText := 'Board: ' + FController.BoardDisplayName;
|
||||||
IPText := 'IP: ' + FController.FNetwork.Device.IPAddress;
|
IPText := 'IP: ' + FController.FNetwork.Device.IPAddress;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -414,7 +414,20 @@ begin
|
|||||||
if FController.FLastPLLLock then PLLText := 'PLL OK'
|
if FController.FLastPLLLock then PLLText := 'PLL OK'
|
||||||
else PLLText := 'PLL?';
|
else PLLText := 'PLL?';
|
||||||
|
|
||||||
if not BoardSupportsSupplyVoltage(FController.FNetwork.Device.BoardType) then
|
if FController.IsPluto then
|
||||||
|
begin
|
||||||
|
// Pluto: нет PA/supply — температура чипа + RSSI приёмника (telemetry-поле).
|
||||||
|
SupplyText := '';
|
||||||
|
if FController.FLastPlutoTempC > TELEMETRY_NONE then
|
||||||
|
SupplyText := Format('Temp %.0fC', [FController.FLastPlutoTempC]);
|
||||||
|
if FController.FLastPlutoRSSIdB > TELEMETRY_NONE then
|
||||||
|
begin
|
||||||
|
if SupplyText <> '' then SupplyText := SupplyText + ' ';
|
||||||
|
SupplyText := SupplyText + Format('RSSI %.0fdB', [FController.FLastPlutoRSSIdB]);
|
||||||
|
end;
|
||||||
|
if SupplyText = '' then SupplyText := 'Pluto --';
|
||||||
|
end
|
||||||
|
else if not BoardSupportsSupplyVoltage(FController.FNetwork.Device.BoardType) then
|
||||||
SupplyText := 'Supply n/a'
|
SupplyText := 'Supply n/a'
|
||||||
else if FController.FLastSupplyV >= 0 then
|
else if FController.FLastSupplyV >= 0 then
|
||||||
begin
|
begin
|
||||||
|
|||||||
@@ -1,518 +0,0 @@
|
|||||||
# Интеграция ADALM-PLUTO (libiio) в EWSDR — полный план работ
|
|
||||||
|
|
||||||
> Цель: добавить поддержку **PlutoSDR** (AD9363/AD9364) через **libiio** как
|
|
||||||
> второй тип «железа» наряду с трансиверами **openHPSDR**. Discovery-форма
|
|
||||||
> остаётся прежней, но дополняется board-специфическими параметрами Pluto.
|
|
||||||
> В UI скрываются функции, которых у Pluto нет, и появляются те, что есть
|
|
||||||
> только у Pluto. Весь DSP по-прежнему идёт через **WDSP**. Платформенно-
|
|
||||||
> специфичный код выносится в отдельные файлы, чтобы не загромождать
|
|
||||||
> существующую логику openHPSDR.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. Краткое резюме решения
|
|
||||||
|
|
||||||
1. Ввести **тонкий слой абстракции backend'а** (`SDRBackend.pas`,
|
|
||||||
`TSDRBackend`), описывающий ровно тот API, которым `MainForm` сегодня
|
|
||||||
пользуется у `THPSDRNetwork`. Существующий `THPSDRNetwork` обворачивается
|
|
||||||
адаптером `THPSDRBackend` (код самого `HPSDRNetwork.pas` не трогаем).
|
|
||||||
2. Новый backend Pluto — `PlutoBackend.pas` (`TPlutoBackend`), и низкоуровневые
|
|
||||||
биндинги libiio — `IIO.pas` (по образцу `WDSP.pas`).
|
|
||||||
3. Поток IQ от Pluto (int16 LE) **переупаковывается в тот же 24-битный
|
|
||||||
big-endian формат**, что и openHPSDR, и подаётся в существующий
|
|
||||||
`TWDSPEngine.PushDDCPacket` — DSP-конвейер не меняется.
|
|
||||||
4. Возможности железа описываются записью **`TBoardCaps`** (`BoardCaps.pas`).
|
|
||||||
`MainForm`/`SettingsForm` гейтят UI по `Caps`, а не по жёстким спискам
|
|
||||||
`BoardType in [...]`.
|
|
||||||
5. Discovery-форма расширяется панелью Pluto-параметров (URI, gain mode,
|
|
||||||
gain, RF bandwidth, sample rate, диапазон LO) — раскладка прежняя.
|
|
||||||
6. Минимальный sample rate Pluto = **≈521 кS/s** (без программируемого FIR)
|
|
||||||
учитывается в `SampleRateOverlay` и в валидации.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Текущая архитектура (контекст)
|
|
||||||
|
|
||||||
Стек: **Lazarus / Free Pascal** (FPC 3.2 / Lazarus 2.x), LCL, кросс-платформенно
|
|
||||||
(Windows / Linux / macOS). DSP — внешняя **WDSP** (`wdsp.dll` / `libwdsp.so` /
|
|
||||||
`libwdsp.dylib`). Аудио — PortAudio (`AudioInput.pas` / `AudioOutput.pas`).
|
|
||||||
|
|
||||||
### 1.1 Путь данных RX
|
|
||||||
```
|
|
||||||
THPSDRNetwork (UDP, HPSDRNetwork.pas)
|
|
||||||
→ receive thread → MainForm.OnDDCIQCB(DDCIndex, TDDCIQPacket)
|
|
||||||
→ FDSPEngine.PushDDCPacket(IQData, 0, SamplesPerFrame) // сырые 24-bit BE байты
|
|
||||||
→ WDSP DSP-поток: декод 24-bit BE → double (scale 1/8388608),
|
|
||||||
RXA-цепь → OnAudio (AudioOutput) + OnSpectrum/OnWaterfall
|
|
||||||
```
|
|
||||||
Декод IQ зашит в `TWDSPEngine.PushIQItemToDSP` (`WDSPEngine.pas:1199-1209`):
|
|
||||||
3 байта I + 3 байта Q, big-endian, `SCALE = 1/8388608.0`.
|
|
||||||
|
|
||||||
### 1.2 Путь данных TX
|
|
||||||
```
|
|
||||||
Mic: OnMicPacketCB (HW-mic) или AudioInput (звуковая карта)
|
|
||||||
→ FDSPEngine.PushTXMicSamples*
|
|
||||||
→ WDSP TXA-цепь → OnTXIQReady(double IQ @ 192k, MainForm.pas:6795)
|
|
||||||
→ батчинг по 240 пар → FNetwork.SendDUCIQ → UDP (порт 1029)
|
|
||||||
```
|
|
||||||
Масштаб TX: `V * 8388607.0`, кламп в 24-bit (`MainForm.pas:6801`).
|
|
||||||
|
|
||||||
### 1.3 Discovery / устройство
|
|
||||||
- `DeviceForm.pas` (`TDeviceDialog`): слева сохранённые устройства
|
|
||||||
(`TSavedDevice`: Name / IPAddress / BoardType / AutoStart → `hpsdr_devices.ini`),
|
|
||||||
справа найденные. Кнопка DISCOVER → callback `OnDiscover` →
|
|
||||||
`MainForm.BtnDiscoverFromDialog` → `TDiscoverThread` → `FNetwork.Discover`
|
|
||||||
→ `OnDeviceFound` → `FDeviceDialog.AddDiscovered(IP, name, BoardType)`.
|
|
||||||
- Устройство в сети идентифицируется по **IP**, а настройки хранятся по **MAC**
|
|
||||||
(`Settings.pas`, JSON по `MacToStr`).
|
|
||||||
|
|
||||||
### 1.4 Поверхность API `THPSDRNetwork`, которой пользуется MainForm
|
|
||||||
(`HPSDRNetwork.pas:204-249`) — именно это станет интерфейсом backend'а:
|
|
||||||
- `Discover`, `Connect(THPSDRDevice)`, `Disconnect`
|
|
||||||
- `ConfigureDDCs(NumDDCs, SampleRate, ADCSource, Dither, Random)`
|
|
||||||
- `SetRunAndFreq(Run, RXFreq, TXFreq, Drive)`
|
|
||||||
- `UpdateState(RXFreq, TXFreq, Drive, TX, PAEnabled, AlexEnabled)`
|
|
||||||
- `SendDUCIQ(I, Q)`, `SendDDCAudio`, `SendDUCSpecific`, `SetStepAtten`,
|
|
||||||
`SetAlexConfig`, `SetXvtrMode`, `SendPTT`, `SendFullHP`
|
|
||||||
- Свойства: `Connected`, `Running`, `Device`, `LastError`
|
|
||||||
- Колбэки: `OnDeviceFound`, `OnDDCIQ`, `OnMicPacket`, `OnHPStatus`, `OnWideband`
|
|
||||||
|
|
||||||
### 1.5 Board-специфичные места (по `BoardType`)
|
|
||||||
- `BoardUtils.BoardTypeName`, `FreqToBandIdx`.
|
|
||||||
- `HPSDRProtocol`: `BoardSupportsSupplyVoltage/Current`, `ADCToWatts*`,
|
|
||||||
`ADCToSupplyVolts/Current`.
|
|
||||||
- `HPSDRNetwork.ConfigureDDCs`: число ADC и базовый DDC (`BoardType in [3,4,5,10]`).
|
|
||||||
- `MainForm`: телеметрия (supply V/A, fwd/rev power, SWR) в `OnHPStatusCB`,
|
|
||||||
step-attenuator (`FAtten`), Alex/XVTR, wideband-панель, dither/random.
|
|
||||||
- `SettingsForm`: вкладки Antenna/Alex, Transverter, Tip/Ring (Orion),
|
|
||||||
ADC dither/random, PA-калибровка.
|
|
||||||
|
|
||||||
### 1.6 Линковка внешних библиотек
|
|
||||||
- `WDSP.pas`: `WDSP_LIB` по платформе; на macOS `{$linklib wdsp}`; weak —
|
|
||||||
если библиотеки нет, указатели `nil`, проверяется `Assigned(@WDSPwisdom)`.
|
|
||||||
- `ewsdr.lpi`: `CustomOptions = -k-L/usr/local/lib`.
|
|
||||||
- Сборка: `lazbuild --ws=qt6 ewsdr.lpr` (`BUILD.MD`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Pluto SDR / libiio — факты и ограничения
|
|
||||||
|
|
||||||
**Железо:** ADALM-PLUTO на AD9363 (заводской), прошивкой разблокируется до
|
|
||||||
возможностей AD9364/AD9361. **Один RX и один TX** канал (один ADC / один DAC).
|
|
||||||
|
|
||||||
**libiio API (нужный минимум):**
|
|
||||||
- Контекст: `iio_create_context_from_uri("ip:192.168.2.1" | "ip:pluto.local"
|
|
||||||
| "usb:1.x.x" | "local:")`.
|
|
||||||
- Discovery: `iio_create_scan_context(NULL,0)` →
|
|
||||||
`iio_scan_context_get_info_list` → `iio_context_info_get_uri/_description`.
|
|
||||||
- Устройства: `ad9361-phy` (управление), `cf-ad9361-lpc` (RX-стрим),
|
|
||||||
`cf-ad9361-dds-core-lpc` (TX-стрим).
|
|
||||||
- Каналы phy: `voltage0` (RX, input) / `voltage0` (TX, output) — gain,
|
|
||||||
rf_bandwidth, sampling_frequency; `altvoltage0` (RX LO), `altvoltage1` (TX LO).
|
|
||||||
- Стрим: `iio_device_create_buffer`, `iio_buffer_refill` (RX),
|
|
||||||
`iio_buffer_push` (TX); каналы `voltage0`(I)+`voltage1`(Q), формат **int16 LE**,
|
|
||||||
интерливинг I/Q. Полезных бит — 12 (знаковые, в младших битах).
|
|
||||||
- Атрибуты (строки): `frequency` (LO, Hz), `sampling_frequency` (Hz),
|
|
||||||
`rf_bandwidth` (Hz), `hardwaregain` (dB), `gain_control_mode`
|
|
||||||
(`manual|slow_attack|fast_attack|hybrid`), `rssi`, `temp0` (xadc, mC).
|
|
||||||
|
|
||||||
**Платформенные ограничения Pluto (важно для UI/валидации):**
|
|
||||||
| Параметр | Значение |
|
|
||||||
|---|---|
|
|
||||||
| Sample rate (min) | **≈520.83 кS/s** без FIR; ~65 кS/s только с загруженным FIR |
|
|
||||||
| Sample rate (max) | 61.44 МS/s (по USB 2.0 реально ~5–6 МS/s непрерывно) |
|
|
||||||
| Частота LO (AD9363) | 325 МГц – 3.8 ГГц |
|
|
||||||
| Частота LO (AD9364, разблок.) | 70 МГц – 6 ГГц |
|
|
||||||
| RX gain | 0–73 дБ (manual) + режимы AGC |
|
|
||||||
| TX «gain» | аттенюатор −89.75…0 дБ |
|
|
||||||
| RF bandwidth | 200 кГц – 56 МГц |
|
|
||||||
| Транспорт | USB / Ethernet-gadget; интерфейс — URI, не broadcast |
|
|
||||||
|
|
||||||
**Чего у Pluto НЕТ:** нескольких DDC/ADC, dither/random, Alex-фильтров/реле
|
|
||||||
антенн, аппаратного wideband-ADC, PureSignal, измерения напряжения/тока питания,
|
|
||||||
fwd/rev power и КСВ, аппаратного step-attenuator, HW-CW-keyer. XVTR-логика
|
|
||||||
возможна только как программный сдвиг частоты (без реле).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. openHPSDR vs Pluto — матрица возможностей
|
|
||||||
|
|
||||||
| Возможность | openHPSDR | Pluto | Действие в UI |
|
|
||||||
|---|---|---|---|
|
|
||||||
| Несколько DDC | да | нет (1 RX) | скрыть мульти-DDC |
|
|
||||||
| Dither / Random ADC | да | нет | скрыть в Advanced |
|
|
||||||
| Step attenuator (RX) | да | нет (есть RX gain dB) | заменить контрол |
|
|
||||||
| RX gain / AGC mode | косвенно | да (manual/AGC) | показать для Pluto |
|
|
||||||
| Drive level (TX) | 0–255 | TX atten dB | переинтерпретировать |
|
|
||||||
| Alex антенны/фильтры | да | нет | скрыть вкладку |
|
|
||||||
| XVTR (реле) | да | только сдвиг | показать упрощённо |
|
|
||||||
| Wideband ADC панель | да | нет | скрыть |
|
|
||||||
| Supply V / A | часть плат | нет (есть temp) | заменить на temp |
|
|
||||||
| Fwd/Rev power, SWR | да | нет | скрыть SWR/PWR |
|
|
||||||
| RF bandwidth | фикс. | настраиваемый | показать для Pluto |
|
|
||||||
| Диапазон частот | 0–61.44М (HF/6м) | 70М–6Г | расширить ввод/бэнд-план |
|
|
||||||
| Sample rates | 48к–1536к | 521к–~6М | свой набор в overlay |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Архитектурное решение
|
|
||||||
|
|
||||||
### 4.1 Слой абстракции backend'а — `SDRBackend.pas` (НОВЫЙ)
|
|
||||||
Абстрактный класс `TSDRBackend`, повторяющий публичный контракт
|
|
||||||
`THPSDRNetwork` (раздел 1.4). MainForm работает с `FBackend: TSDRBackend`
|
|
||||||
вместо прямого `FNetwork`. Колбэки переносятся на backend.
|
|
||||||
|
|
||||||
```pascal
|
|
||||||
type
|
|
||||||
TSDRKind = (sdrHPSDR, sdrPluto);
|
|
||||||
|
|
||||||
TSDRBackend = class
|
|
||||||
protected
|
|
||||||
FOnDDCIQ: TOnDDCIQPacket; // (повторно используем типы из HPSDRNetwork)
|
|
||||||
FOnMic: TOnMicPacket;
|
|
||||||
FOnHPStatus: TOnHPStatus; // для Pluto заполняем синтетически (RSSI/temp)
|
|
||||||
FOnWideband: TOnWidebandFrame;// Pluto: не вызывается
|
|
||||||
FOnDeviceFound: TOnDeviceFound;
|
|
||||||
public
|
|
||||||
function Kind: TSDRKind; virtual; abstract;
|
|
||||||
function Caps: TBoardCaps; virtual; abstract;
|
|
||||||
function Discover(TimeoutMs: Integer): THPSDRDeviceArray; virtual; abstract;
|
|
||||||
function Connect(const Dev: THPSDRDevice): Boolean; virtual; abstract;
|
|
||||||
procedure Disconnect; virtual; abstract;
|
|
||||||
procedure ConfigureDDCs(NumDDCs: Byte; SampleRate: Word; ...); virtual; abstract;
|
|
||||||
procedure SetRunAndFreq(Run: Boolean; RXFreqHz, TXFreqHz: Double; Drive: Byte); virtual; abstract;
|
|
||||||
procedure UpdateState(...); virtual; abstract;
|
|
||||||
procedure SendDUCIQ(const I, Q: array of Integer); virtual; abstract;
|
|
||||||
// ... остальные методы из 1.4; для неподдерживаемых — пустая реализация в Pluto
|
|
||||||
property Connected: Boolean read FConnected;
|
|
||||||
property Running: Boolean read FRunning;
|
|
||||||
property Device: THPSDRDevice read FDevice;
|
|
||||||
property LastError: string read FLastError;
|
|
||||||
property OnDDCIQ: ... ; // + остальные колбэки
|
|
||||||
end;
|
|
||||||
```
|
|
||||||
|
|
||||||
**Почему не интерфейс (`interface`/IUnknown):** класс проще для FPC-кода без
|
|
||||||
ARC-сюрпризов, повторяет существующий стиль и не требует менять
|
|
||||||
`HPSDRNetwork.pas`.
|
|
||||||
|
|
||||||
### 4.2 `THPSDRBackend` (адаптер) — в `SDRBackend.pas` или отдельно
|
|
||||||
Тонкая обёртка: внутри `FNet: THPSDRNetwork`, делегирует все методы и
|
|
||||||
пробрасывает колбэки. `Caps` возвращает capabilities по `BoardType`.
|
|
||||||
`HPSDRNetwork.pas` остаётся **без изменений**.
|
|
||||||
|
|
||||||
### 4.3 `TPlutoBackend` — `PlutoBackend.pas` (НОВЫЙ)
|
|
||||||
Реализует `TSDRBackend` поверх libiio. Внутри:
|
|
||||||
- RX-поток: `iio_buffer_refill` → int16→24-bit BE repack → синтез
|
|
||||||
`TDDCIQPacket` (seq, SamplesPerFrame) → `FOnDDCIQ(0, pkt)`.
|
|
||||||
- TX-поток: очередь как в HPSDR (`DUC_TX_QUEUE_SIZE`), пары I/Q (24-bit)
|
|
||||||
→ int16 → `iio_buffer_push`.
|
|
||||||
- Телеметрия-поток (~5–10 Гц): читает `rssi`/`temp0` → синтетический
|
|
||||||
`THighPriorityStatus` → `FOnHPStatus` (S-метр + температура).
|
|
||||||
- `Discover` → `iio_create_scan_context`; маппинг URI/serial → `THPSDRDevice`
|
|
||||||
(см. 7.3 про идентификацию).
|
|
||||||
|
|
||||||
### 4.4 `IIO.pas` — биндинги libiio (НОВЫЙ)
|
|
||||||
По образцу `WDSP.pas`. **Рекомендация: динамическая загрузка через `dynlibs`**
|
|
||||||
(`LoadLibrary`/`GetProcedureAddress`) с `function IIOAvailable: Boolean` —
|
|
||||||
тогда приложение запускается даже без установленной libiio, а Pluto просто
|
|
||||||
недоступен (как сейчас weak-WDSP). Имена: `libiio.so.0`/`libiio.so` (Linux),
|
|
||||||
`iio.dll` (Windows), `libiio.dylib`/framework (macOS).
|
|
||||||
|
|
||||||
### 4.5 `BoardCaps.pas` — описание возможностей (НОВЫЙ)
|
|
||||||
```pascal
|
|
||||||
type
|
|
||||||
TBoardCaps = record
|
|
||||||
Kind: TSDRKind;
|
|
||||||
DisplayName: string;
|
|
||||||
HasMultiDDC, HasDitherRandom, HasStepAtten, HasRXGain,
|
|
||||||
HasAGCModes, HasAlex, HasXvtrRelays, HasWideband,
|
|
||||||
HasSupplyVolts, HasSupplyCurrent, HasPower, HasSWR,
|
|
||||||
HasRFBandwidth, HasHWMic, HasPureSignal: Boolean;
|
|
||||||
MinSampleRate, MaxSampleRate: Integer;
|
|
||||||
MinFreqHz, MaxFreqHz: Double;
|
|
||||||
SampleRates: array of Integer; // что показывать в overlay
|
|
||||||
end;
|
|
||||||
|
|
||||||
function BoardCapsFor(Kind: TSDRKind; BoardType: Integer): TBoardCaps;
|
|
||||||
```
|
|
||||||
Единый источник правды для гейтинга UI — заменяет разбросанные
|
|
||||||
`if BoardType in [...]`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Список новых и изменяемых файлов
|
|
||||||
|
|
||||||
**Новые:**
|
|
||||||
- `IIO.pas` — биндинги libiio (динамическая загрузка).
|
|
||||||
- `PlutoBackend.pas` — `TPlutoBackend` (RX/TX/telemetry потоки, repack).
|
|
||||||
- `SDRBackend.pas` — `TSDRBackend` + `THPSDRBackend`-адаптер + `TSDRKind`.
|
|
||||||
- `BoardCaps.pas` — `TBoardCaps` + `BoardCapsFor`.
|
|
||||||
- `PlutoUtils.pas` (опц.) — частотные бэнд-планы/пресеты для VHF/UHF/SHF.
|
|
||||||
|
|
||||||
**Изменяемые:**
|
|
||||||
- `MainForm.pas` — `FNetwork: THPSDRNetwork` → `FBackend: TSDRBackend`
|
|
||||||
(+ фабрика по `Kind`); гейтинг UI по `FBackend.Caps`; телеметрия;
|
|
||||||
drive/gain; START/STOP.
|
|
||||||
- `DeviceForm.pas` — `TSavedDevice` += поля Pluto; панель Pluto-параметров;
|
|
||||||
`AddDiscovered` += URI/Kind.
|
|
||||||
- `SettingsForm.pas` — показывать/прятать вкладки и контролы по Caps;
|
|
||||||
новые контролы Pluto (RX gain, AGC mode, RF bandwidth).
|
|
||||||
- `BoardUtils.pas` — имя для Pluto, бэнд-план VHF/UHF (или вынести в PlutoUtils).
|
|
||||||
- `Settings.pas` — ключ устройства (MAC vs serial), новые поля (gain mode,
|
|
||||||
gain, rf_bandwidth, pluto sample rate), set частот для Pluto.
|
|
||||||
- `SampleRateOverlay.pas` — набор скоростей зависит от backend/Caps.
|
|
||||||
- `ewsdr.lpr` — добавить новые units в `uses`.
|
|
||||||
- `ewsdr.lpi` — пути/линковка libiio (если link-time), новые файлы в проекте.
|
|
||||||
- `bundle_app.sh` — включать libiio в macOS-бандл.
|
|
||||||
- `BUILD.MD` — отметить зависимость libiio (опциональная).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Детальный разбор по подсистемам
|
|
||||||
|
|
||||||
### 6.1 libiio биндинги (`IIO.pas`)
|
|
||||||
Объявить нужные типы-указатели (`p_iio_context`, `p_iio_device`,
|
|
||||||
`p_iio_channel`, `p_iio_buffer`, `p_iio_scan_context`, `p_iio_context_info`)
|
|
||||||
и функции:
|
|
||||||
- контекст: `iio_create_context_from_uri`, `iio_context_destroy`,
|
|
||||||
`iio_context_get_device`, `iio_context_find_device`.
|
|
||||||
- scan: `iio_create_scan_context`, `iio_scan_context_get_info_list`,
|
|
||||||
`iio_context_info_get_uri`, `iio_context_info_get_description`,
|
|
||||||
`iio_context_info_list_free`, `iio_scan_context_destroy`.
|
|
||||||
- каналы: `iio_device_find_channel`, `iio_channel_enable`,
|
|
||||||
`iio_channel_attr_write_longlong/_read_longlong`, `..._write/_read` (строки),
|
|
||||||
`iio_channel_attr_write_double` (для gain).
|
|
||||||
- буферы: `iio_device_create_buffer`, `iio_buffer_destroy`,
|
|
||||||
`iio_buffer_refill`, `iio_buffer_push`, `iio_buffer_first`,
|
|
||||||
`iio_buffer_step`, `iio_buffer_end`, `iio_channel_read/_write` (или прямой
|
|
||||||
доступ к памяти буфера).
|
|
||||||
- `iio_strerror`.
|
|
||||||
|
|
||||||
Все `cdecl`. Использовать `TIIO_*` typedef'ы. Реализовать `InitIIO/IIOAvailable`
|
|
||||||
(грузим библиотеку, резолвим символы; при отсутствии — `False`).
|
|
||||||
|
|
||||||
### 6.2 Backend-абстракция и подключение в MainForm
|
|
||||||
- Завести фабрику: `function CreateBackend(Kind: TSDRKind): TSDRBackend;`.
|
|
||||||
- В `MainForm` заменить тип поля `FNetwork` на `FBackend: TSDRBackend`
|
|
||||||
(или оставить имя `FNetwork`, сменив тип — меньше правок в тексте).
|
|
||||||
- Колбэки (`OnDeviceFound`, `OnDDCIQCB`, `OnHPStatusCB`, `OnMicPacketCB`,
|
|
||||||
`OnWidebandCB`) переключить на backend.
|
|
||||||
- При выборе устройства в discovery узнаём `Kind` (Pluto vs HPSDR) и создаём
|
|
||||||
нужный backend перед `Connect`.
|
|
||||||
- `BoardTypeName`-вывод в статус-баре и Web (`MainForm:3172`,
|
|
||||||
`3257`) — через `FBackend.Caps.DisplayName`.
|
|
||||||
|
|
||||||
### 6.3 Discovery-форма (`DeviceForm.pas`)
|
|
||||||
- `TSavedDevice` расширить: `Kind: TSDRKind; URI: string;
|
|
||||||
PlutoGainMode: Integer; PlutoGainDB: Double; PlutoRFBW: Integer;
|
|
||||||
PlutoSampleRate: Integer;` (BoardType остаётся для HPSDR).
|
|
||||||
- INI (`hpsdr_devices.ini`): дописать поля; обратная совместимость —
|
|
||||||
отсутствующие читать с дефолтами, `Kind` по умолчанию `sdrHPSDR`.
|
|
||||||
- DISCOVER запускает **обе** ветки: HPSDR broadcast и libiio-scan (если
|
|
||||||
`IIOAvailable`). В списке найденных помечать тип: `[HPSDR]` / `[PLUTO]`.
|
|
||||||
`AddDiscovered` получает доп. параметры (URI, Kind, отображаемое имя).
|
|
||||||
- Раскладка прежняя; снизу/справа добавить **сворачиваемую панель
|
|
||||||
«Pluto parameters»**, активную только когда выбранное/добавляемое
|
|
||||||
устройство — Pluto: поля URI, Gain mode (combo), Gain (dB), RF bandwidth,
|
|
||||||
Sample rate. Для HPSDR-устройств панель скрыта/неактивна.
|
|
||||||
- `BtnConnect` отдаёт в результат не только IP, но и `Kind`/URI/Pluto-параметры.
|
|
||||||
|
|
||||||
### 6.4 Идентификация устройства и Settings
|
|
||||||
- HPSDR: ключ настроек — **MAC**. Pluto: MAC нет → использовать **serial
|
|
||||||
number** Pluto (атрибут контекста `hw_serial`/usb serial) как ключ.
|
|
||||||
В `Settings.pas` обобщить `MacToStr` до строкового device-id, либо завести
|
|
||||||
отдельную ветку ключей `pluto:<serial>`.
|
|
||||||
- `THPSDRDevice` дополнить (или ввести `TSDRDeviceInfo`) полями `Kind`,
|
|
||||||
`URI`, `Serial`. Чтобы не плодить типы, можно положить serial в поле `MAC`
|
|
||||||
как хэш — но честнее расширить запись. (Решить в Фазе 1, см. открытые вопросы.)
|
|
||||||
- Новые per-device поля в `TGlobalSettings` (или отдельная секция `pluto`):
|
|
||||||
`PlutoGainMode`, `PlutoGainDB`, `PlutoRFBW`, `PlutoLOOffset`.
|
|
||||||
|
|
||||||
### 6.5 Sample rate (`SampleRateOverlay.pas` + валидация)
|
|
||||||
- Текущий `SPAN_RATES = (48k..1536k)` сделать зависимым от `Caps.SampleRates`.
|
|
||||||
- Для Pluto предложить набор: `521000, 768000, 1000000, 1536000, 2000000,
|
|
||||||
3000000` (верх — с оглядкой на USB-троттлинг; пометить «USB limit»).
|
|
||||||
- Жёсткая валидация: для Pluto `SampleRate >= MinSampleRate(521000)`.
|
|
||||||
При попытке выставить ниже — клампить + статус-предупреждение.
|
|
||||||
- `FSpanHz` = выбранный rate (как сейчас). WDSP rate-agnostic; пересоздание
|
|
||||||
движка через существующий `RecreateDSPEngine/ChangeSampleRate`.
|
|
||||||
- **CPU/латентность:** при 521к–3М `FBufSize = BufSize*Rate/48000` большой
|
|
||||||
(≈5.5k–32k). Проверить нагрузку; при необходимости — уменьшить базовый
|
|
||||||
`BufSize` для Pluto или включить программируемый FIR Pluto для понижения
|
|
||||||
rate. (Замер в Фазе 4.)
|
|
||||||
|
|
||||||
### 6.6 RX-поток IQ и формат сэмплов (ключевой момент)
|
|
||||||
- Pluto отдаёт **int16 LE** (12 значащих бит). openHPSDR/WDSP ждёт **24-bit BE**,
|
|
||||||
scale `1/8388608`.
|
|
||||||
- **Решение (минимальная инвазивность):** в `TPlutoBackend` RX-потоке
|
|
||||||
переупаковывать каждый int16-сэмпл в 3-байтовый big-endian с тем же
|
|
||||||
динамическим диапазоном: `v24 = v16 << 12` (12→24 бита, знак сохраняется),
|
|
||||||
затем разложить в 3 байта BE. Это даёт корректный масштаб для существующего
|
|
||||||
декодера `PushIQItemToDSP` без изменений в WDSPEngine.
|
|
||||||
- Альтернатива: добавить в `TWDSPEngine` метод `PushIQDoubles(constI,Q)` и
|
|
||||||
кормить double напрямую (чище, но трогает WDSPEngine — оставить как опцию).
|
|
||||||
- Размер «пакета»: эмулируем `TDDCIQPacket` — заполняем `Seq` (монотонно),
|
|
||||||
`SamplesPerFrame` (например 238, как у HPSDR, чтобы переиспользовать ветку
|
|
||||||
`MainForm.OnDDCIQCB`), и режем буфер `iio_buffer_refill` на такие кадры.
|
|
||||||
- `FActiveDDC`/seq-контроль в `OnDDCIQCB` продолжают работать (DDCIndex=0).
|
|
||||||
|
|
||||||
### 6.7 TX-поток
|
|
||||||
- `OnTXIQReady` (MainForm:6795) уже даёт 24-bit-клампленные `I/Q` пары →
|
|
||||||
`FBackend.SendDUCIQ`. В `TPlutoBackend.SendDUCIQ` конвертировать
|
|
||||||
24-bit → int16 (`v16 = v24 >> 12` или scale), писать в TX-буфер,
|
|
||||||
`iio_buffer_push` в отдельном потоке (равномерная подача, как DUC-sender).
|
|
||||||
- TX-частота: Pluto имеет отдельный TX LO (`altvoltage1`). Поддержать
|
|
||||||
split/duplex; при не-дуплексе LO RX=TX.
|
|
||||||
- «Drive level» (0–100 слайдер) маппить в TX `hardwaregain` (аттенюатор
|
|
||||||
0…−89.75 дБ): 100%→0 дБ, 0%→−89.75 дБ (или разумный практический диапазон,
|
|
||||||
напр. 0…−40 дБ). См. `CalcDriveByte`/`TrkDriveChange` в MainForm.
|
|
||||||
|
|
||||||
### 6.8 Gain / AGC модель
|
|
||||||
- HPSDR: step-attenuator (`FAtten`, 0/10/20 дБ) + WDSP-AGC.
|
|
||||||
- Pluto: RX `gain_control_mode` (manual/slow/fast/hybrid) + `hardwaregain` (дБ).
|
|
||||||
- В UI: если `Caps.HasRXGain` — показать RX-gain слайдер (0–73 дБ) и combo
|
|
||||||
AGC-mode вместо step-attenuator. Когда mode≠manual — слайдер gain disable.
|
|
||||||
- WDSP-AGC RXA оставить включённым для пост-обработки (как у HPSDR), но
|
|
||||||
основной gain — аппаратный Pluto.
|
|
||||||
|
|
||||||
### 6.9 Телеметрия (S-meter / temp вместо supply/SWR)
|
|
||||||
- `OnHPStatusCB` (MainForm:3286) и панель статуса завязаны на supply V/A,
|
|
||||||
fwd/rev power, SWR.
|
|
||||||
- Pluto: `Caps.HasSupplyVolts/Current/Power/SWR = False`. В этих местах
|
|
||||||
показывать прочерк/скрывать. Вместо supply показать `temp0` (°C) Pluto.
|
|
||||||
- S-метр: WDSP `GetSMeterDBm` остаётся основным (по IQ); RSSI Pluto можно
|
|
||||||
показывать как доп. индикатор.
|
|
||||||
- TX-метр (см. memory `project_smeter_tx`: мощность+КСВ): для Pluto скрыть КСВ
|
|
||||||
и показывать только относительный уровень / TX gain (нет датчиков мощности).
|
|
||||||
|
|
||||||
### 6.10 Частотный диапазон / LO / XVTR
|
|
||||||
- Расширить допустимый ввод частоты до 70 МГц–6 ГГц (по `Caps.MinFreqHz/Max`).
|
|
||||||
- Бэнд-план: `BoardUtils.FreqToBandIdx` сейчас только HF/6м. Добавить
|
|
||||||
VHF/UHF/SHF бэнды (2м, 70см, 23см, и т.д.) для Pluto (в `PlutoUtils.pas`
|
|
||||||
или расширить `BoardUtils`), чтобы per-band настройки/калибровка работали.
|
|
||||||
- XVTR-реле у Pluto нет → вкладка Transverter в упрощённом виде (только
|
|
||||||
программный сдвиг частоты), либо скрыть, оставив встроенную перестройку LO.
|
|
||||||
|
|
||||||
### 6.11 Гейтинг UI (`MainForm` + `SettingsForm`)
|
|
||||||
- Везде, где сейчас `BoardType in [...]` или прямые предположения о HPSDR —
|
|
||||||
заменить на `FBackend.Caps.*`.
|
|
||||||
- `SettingsForm`: вкладки `Antenna/Alex`, `Transverter`, контролы Tip/Ring,
|
|
||||||
ADC dither/random, PA-калибровка показывать только при соответствующих Caps.
|
|
||||||
Для Pluto добавить секцию «Pluto RF» (gain mode, gain, RF bandwidth, LO offset).
|
|
||||||
- Главная форма: кнопки/оверлеи Wideband, ATT, SWR-индикатор — скрывать по Caps.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Сборка и зависимости
|
|
||||||
|
|
||||||
- **Линковка libiio:** предпочтительно **рантайм-загрузка** (`dynlibs`) —
|
|
||||||
приложение работает без libiio (Pluto просто не появляется в discovery).
|
|
||||||
Имена: Linux `libiio.so.0`, Windows `iio.dll` (libiio MSVC/MinGW сборка),
|
|
||||||
macOS `libiio.dylib` или `/Library/Frameworks/iio.framework`.
|
|
||||||
- Если выбрать link-time (как WDSP `{$linklib}`) — добавить путь в `ewsdr.lpi`
|
|
||||||
(`-k-L...`) и `{$linklib iio}` под `{$IFDEF DARWIN}`/нужные платформы.
|
|
||||||
- `ewsdr.lpr`: добавить `IIO, SDRBackend, PlutoBackend, BoardCaps` в `uses`.
|
|
||||||
- `ewsdr.lpi`: внести новые `.pas` в список файлов проекта.
|
|
||||||
- `bundle_app.sh`: при сборке macOS-бандла копировать `libiio.dylib` и его
|
|
||||||
зависимости (как уже делается для прочих не-системных dylib).
|
|
||||||
- `BUILD.MD`: отметить опциональную зависимость и где взять libiio
|
|
||||||
(пакет `libiio` / Analog Devices релизы).
|
|
||||||
- Документация: dev-права на USB (Linux udev-правило для Pluto
|
|
||||||
`0456:b673`), доступ к `ip:192.168.2.1`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. План работ по фазам
|
|
||||||
|
|
||||||
### Фаза 0 — Подготовка и каркас абстракции (без поведения Pluto)
|
|
||||||
1. `BoardCaps.pas`: `TBoardCaps`, `BoardCapsFor` (заполнить для всех HPSDR-плат).
|
|
||||||
2. `SDRBackend.pas`: `TSDRBackend` (абстракт), `THPSDRBackend` (обёртка над
|
|
||||||
существующим `THPSDRNetwork`), фабрика, `TSDRKind`.
|
|
||||||
3. `MainForm`: перевести `FNetwork` на `FBackend: TSDRBackend` (= `THPSDRBackend`),
|
|
||||||
все вызовы — через backend. Колбэки — через backend.
|
|
||||||
4. **Критерий приёмки:** поведение с openHPSDR полностью идентично текущему
|
|
||||||
(регресс-проверка RX/TX/discovery/settings). Pluto ещё нет.
|
|
||||||
|
|
||||||
### Фаза 1 — libiio биндинги и discovery Pluto
|
|
||||||
1. `IIO.pas`: рантайм-загрузка + нужные функции; `IIOAvailable`.
|
|
||||||
2. `TPlutoBackend.Discover` (scan-context) → список Pluto-URI.
|
|
||||||
3. `DeviceForm`: тип устройства в списке найденных; расширить `TSavedDevice`
|
|
||||||
и INI; панель Pluto-параметров; результат с `Kind`/URI.
|
|
||||||
4. Идентификация Pluto по serial; ключ Settings.
|
|
||||||
5. **Критерий:** Pluto находится в discovery, сохраняется/загружается,
|
|
||||||
подключение `iio_create_context_from_uri` успешно (без стрима).
|
|
||||||
|
|
||||||
### Фаза 2 — RX-стрим Pluto → WDSP
|
|
||||||
1. `TPlutoBackend`: конфигурация phy (LO, sampling_frequency, rf_bandwidth,
|
|
||||||
gain mode/gain), создание RX-буфера, RX-поток.
|
|
||||||
2. Repack int16→24-bit BE → синтез `TDDCIQPacket` → `OnDDCIQ`.
|
|
||||||
3. `SampleRateOverlay`/валидация по `Caps` (min 521к).
|
|
||||||
4. MainForm START/STOP-ветка для Pluto (вместо `ConfigureDDCs`/`SetRunAndFreq`
|
|
||||||
— вызовы backend, которые у Pluto означают set-rate + start-stream).
|
|
||||||
5. **Критерий:** живой спектр/водопад и аудио с Pluto на FM/AM/SSB.
|
|
||||||
|
|
||||||
### Фаза 3 — TX-стрим Pluto
|
|
||||||
1. TX-буфер, TX-поток (`iio_buffer_push`), 24-bit→int16.
|
|
||||||
2. `SendDUCIQ`/`SendPTT` для Pluto; drive→TX hardwaregain.
|
|
||||||
3. Split/duplex LO RX/TX.
|
|
||||||
4. **Критерий:** передача SSB/FM, корректный уровень, отсутствие underrun.
|
|
||||||
|
|
||||||
### Фаза 4 — UI-гейтинг и Pluto-специфика
|
|
||||||
1. Заменить все `BoardType in [...]` на `Caps`.
|
|
||||||
2. Скрыть Alex/XVTR/Wideband/Dither/SWR для Pluto; показать RX gain / AGC mode /
|
|
||||||
RF bandwidth; temp вместо supply.
|
|
||||||
3. VHF/UHF/SHF бэнд-план; расширенный ввод частоты до 6 ГГц.
|
|
||||||
4. Замер CPU/латентности на высоких rate; тюнинг буферов/FIR.
|
|
||||||
5. **Критерий:** UI согласован с возможностями железа для обоих типов.
|
|
||||||
|
|
||||||
### Фаза 5 — Полировка, сборка, документация
|
|
||||||
1. `bundle_app.sh`/`lpi`/`BUILD.MD`/udev.
|
|
||||||
2. Обработка ошибок libiio (`iio_strerror`), реконнект, выдёргивание USB.
|
|
||||||
3. Регресс-тесты openHPSDR + Pluto.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Риски и открытые вопросы
|
|
||||||
|
|
||||||
1. **WDSP на высоких sample rate (521к–3М):** проверить CPU/латентность;
|
|
||||||
возможно нужен меньший `BufSize` для Pluto или FIR-децимация Pluto.
|
|
||||||
2. **Идентификация Pluto (нет MAC):** окончательно выбрать ключ Settings —
|
|
||||||
расширять `THPSDRDevice` (поле `Serial`/`URI`) или вводить новый device-info
|
|
||||||
тип. Влияет на форму и Settings — решить в Фазе 1.
|
|
||||||
3. **USB-троттлинг:** верхняя граница реального непрерывного rate по USB 2.0
|
|
||||||
(~5–6 МS/s); пометить «USB limit» в overlay.
|
|
||||||
4. **libiio на Windows/macOS:** наличие готовых сборок `iio.dll`/`libiio.dylib`;
|
|
||||||
рантайм-загрузка снижает риск отсутствия.
|
|
||||||
5. **Масштаб/нормировка IQ:** подтвердить, что `<<12` даёт правильный уровень
|
|
||||||
S-метра (калибровка по WDSP); при необходимости — поправочный коэффициент.
|
|
||||||
6. **TX gain маппинг:** диапазон слайдера drive → TX atten подобрать на железе.
|
|
||||||
7. **Discovery-таймаут:** libiio-scan может блокировать; запускать в потоке
|
|
||||||
(как `TDiscoverThread`) и не вешать UI.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Тест-план
|
|
||||||
|
|
||||||
- **Регресс openHPSDR:** discovery, connect, RX все режимы, TX SSB/CW/FM,
|
|
||||||
Alex/XVTR/wideband, supply/SWR — без изменений после Фазы 0.
|
|
||||||
- **Pluto RX:** discovery (USB и IP), connect, спектр/водопад/аудио на разных
|
|
||||||
rate (521к/768к/1.5М), смена LO в пределах диапазона, gain manual/AGC.
|
|
||||||
- **Pluto TX:** SSB/FM, уровень по drive, split RX/TX, отсутствие
|
|
||||||
drop/underrun (логи seq/drops).
|
|
||||||
- **UI-гейтинг:** при Pluto скрыты Alex/XVTR/Wideband/SWR/dither; показаны
|
|
||||||
RX gain/AGC/RF bandwidth/temp.
|
|
||||||
- **Устойчивость:** выдёргивание USB / разрыв сети → корректный Disconnect,
|
|
||||||
reconnect, отсутствие крэшей; запуск без установленной libiio.
|
|
||||||
- **Кросс-платформа:** Linux (qt6) обязательно; Windows/macOS — по мере
|
|
||||||
доступности libiio.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. Принципы, которых держимся
|
|
||||||
|
|
||||||
- `HPSDRNetwork.pas`, `WDSPEngine.pas`, `WDSP.pas` — **не меняем** (или
|
|
||||||
минимально: опц. `PushIQDoubles`). Pluto-логика — в новых файлах.
|
|
||||||
- Один источник правды о возможностях железа — `TBoardCaps`.
|
|
||||||
- Pluto переиспользует существующий DSP-конвейер за счёт repack в 24-bit BE.
|
|
||||||
- Discovery-форма сохраняет раскладку; Pluto-параметры — дополнительная панель.
|
|
||||||
Reference in New Issue
Block a user