mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
feat: QO-100 beacon bulletin text display (Phase C)
Decode and show the AO-40 frame payload as text in the beacon scope window. - RadioController.GetBeaconFrame exposes the latest 256-byte frame. - BeaconScopeForm: poll the frame counter; on each new frame, parse the payload (drop trailing 2-byte CRC, printable ASCII, 64-char lines, as in gr-satellites qo100.parse) and append to a scrolling read-only memo. - Window widened/heightened; constellation size capped to leave room. Frame format verified offline against a real on-air capture: the decoder produces the live QO-100 AMSAT bulletin text (RS errors=0). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+73
-10
@@ -16,18 +16,22 @@ interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, Math, StrUtils,
|
||||
Forms, Controls, Graphics, ExtCtrls,
|
||||
AppTheme, BeaconDecoder, RadioController;
|
||||
Forms, Controls, Graphics, ExtCtrls, StdCtrls,
|
||||
AppTheme, BeaconDecoder, BeaconFEC, RadioController;
|
||||
|
||||
type
|
||||
TBeaconScopeForm = class(TForm)
|
||||
private
|
||||
FCtl: TRadioController;
|
||||
FBox: TPaintBox;
|
||||
FTimer: TTimer;
|
||||
FTheme: TAppTheme;
|
||||
FCtl: TRadioController;
|
||||
FBox: TPaintBox;
|
||||
FMemo: TMemo; // декодированный текст бюллетеня (AO-40 кадры)
|
||||
FTimer: TTimer;
|
||||
FTheme: TAppTheme;
|
||||
FLastFrames: Int64; // счётчик кадров на прошлом тике (детект нового)
|
||||
procedure DoTick(Sender: TObject);
|
||||
procedure DoPaint(Sender: TObject);
|
||||
procedure PollFrame;
|
||||
function FrameToText(const Frame: TBeaconFrame): string;
|
||||
public
|
||||
constructor CreateWith(AOwner: TComponent; ACtl: TRadioController);
|
||||
procedure ApplyTheme(const T: TAppTheme);
|
||||
@@ -41,12 +45,25 @@ begin
|
||||
FCtl := ACtl;
|
||||
FTheme := DarkTheme;
|
||||
|
||||
Caption := 'QO-100 Beacon — Constellation';
|
||||
Caption := 'QO-100 Beacon — Constellation + Bulletin';
|
||||
BorderStyle := bsSizeable;
|
||||
Width := 360;
|
||||
Height := 560;
|
||||
Width := 600;
|
||||
Height := 680;
|
||||
Position := poScreenCenter;
|
||||
|
||||
// Текст бюллетеня (декодированные AO-40 кадры) — снизу, моноширинный, ReadOnly.
|
||||
FMemo := TMemo.Create(Self);
|
||||
FMemo.Parent := Self;
|
||||
FMemo.Align := alBottom;
|
||||
FMemo.Height := 230;
|
||||
FMemo.ReadOnly := True;
|
||||
FMemo.ScrollBars := ssVertical;
|
||||
FMemo.WordWrap := False;
|
||||
FMemo.Font.Name := 'Courier New';
|
||||
FMemo.Font.Size := 9;
|
||||
FMemo.Color := FTheme.BG;
|
||||
FMemo.Font.Color := FTheme.Text;
|
||||
|
||||
FBox := TPaintBox.Create(Self);
|
||||
FBox.Parent := Self;
|
||||
FBox.Align := alClient;
|
||||
@@ -61,12 +78,57 @@ end;
|
||||
procedure TBeaconScopeForm.ApplyTheme(const T: TAppTheme);
|
||||
begin
|
||||
FTheme := T;
|
||||
if FMemo <> nil then
|
||||
begin
|
||||
FMemo.Color := FTheme.BG;
|
||||
FMemo.Font.Color := FTheme.Text;
|
||||
end;
|
||||
if FBox <> nil then FBox.Invalidate;
|
||||
end;
|
||||
|
||||
procedure TBeaconScopeForm.DoTick(Sender: TObject);
|
||||
begin
|
||||
if (FBox <> nil) and Visible then FBox.Invalidate;
|
||||
if not Visible then Exit;
|
||||
PollFrame;
|
||||
if FBox <> nil then FBox.Invalidate;
|
||||
end;
|
||||
|
||||
function TBeaconScopeForm.FrameToText(const Frame: TBeaconFrame): string;
|
||||
// AO-40 кадр QO-100 = ASCII-текст (space-padded), последние 2 байта = CRC-16.
|
||||
// Как gr-satellites qo100.parse: отбрасываем CRC, печатные ASCII, строки по 64.
|
||||
var
|
||||
i, b: Integer;
|
||||
line: string;
|
||||
begin
|
||||
Result := '';
|
||||
i := 0;
|
||||
while i <= 253 do
|
||||
begin
|
||||
line := '';
|
||||
for b := i to Min(i + 63, 253) do
|
||||
if (Frame[b] >= 32) and (Frame[b] < 127) then line := line + Chr(Frame[b])
|
||||
else line := line + ' ';
|
||||
Result := Result + TrimRight(line) + LineEnding;
|
||||
Inc(i, 64);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TBeaconScopeForm.PollFrame;
|
||||
// Раз/тик: если декодер выдал НОВЫЙ кадр (S.Frames вырос) — печатаем бюллетень.
|
||||
var
|
||||
S: TBeaconScope;
|
||||
Frame: TBeaconFrame;
|
||||
rs: Integer;
|
||||
begin
|
||||
if FCtl = nil then Exit;
|
||||
if not FCtl.GetBeaconScope(S) then Exit;
|
||||
if S.Frames <= FLastFrames then Exit;
|
||||
FLastFrames := S.Frames;
|
||||
if not FCtl.GetBeaconFrame(Frame, rs) then Exit;
|
||||
FMemo.Append(Format('--- frame %d (RS err %d) ---', [S.Frames, rs]));
|
||||
FMemo.Append(FrameToText(Frame));
|
||||
while FMemo.Lines.Count > 400 do FMemo.Lines.Delete(0); // ограничиваем рост
|
||||
FMemo.SelStart := Length(FMemo.Text); // автоскролл вниз
|
||||
end;
|
||||
|
||||
procedure TBeaconScopeForm.DoPaint(Sender: TObject);
|
||||
@@ -89,6 +151,7 @@ begin
|
||||
// область констелляции — квадрат сверху, текст снизу
|
||||
r := (Min(W, H - 70) - 20) div 2;
|
||||
if r < 20 then r := 20;
|
||||
if r > 110 then r := 110; // кап: оставляем место метрикам над memo
|
||||
cx := W div 2;
|
||||
cy := 10 + r;
|
||||
|
||||
|
||||
+7
-1
@@ -49,7 +49,7 @@ interface
|
||||
uses
|
||||
Classes, SysUtils, Math,
|
||||
HPSDRProtocol, HPSDRNetwork, RadioBackend, PlutoBackend, IIOBindings,
|
||||
WDSPEngine, AudioOutput, AudioInput, BeaconDecoder,
|
||||
WDSPEngine, AudioOutput, AudioInput, BeaconDecoder, BeaconFEC,
|
||||
Settings, ChannelStore, FMRepeater, BoardUtils, DeviceStore;
|
||||
|
||||
type
|
||||
@@ -503,6 +503,7 @@ type
|
||||
procedure SetBeaconDecodeInvert(On_: Boolean); // зеркальный baseband вкл/выкл
|
||||
function BeaconDecodeInvert: Boolean;
|
||||
function GetBeaconScope(out S: TBeaconScope): Boolean;
|
||||
function GetBeaconFrame(out Frame: TBeaconFrame; out RSErrors: Integer): Boolean;
|
||||
|
||||
// Отображение / приём
|
||||
procedure SetCTun(On_: Boolean);
|
||||
@@ -1473,6 +1474,11 @@ begin
|
||||
else FillChar(S, SizeOf(S), 0);
|
||||
end;
|
||||
|
||||
function TRadioController.GetBeaconFrame(out Frame: TBeaconFrame; out RSErrors: Integer): Boolean;
|
||||
begin
|
||||
Result := Assigned(FBeaconDec) and FBeaconDec.GetLastFrame(Frame, RSErrors);
|
||||
end;
|
||||
|
||||
procedure TRadioController.ApplyModeFilter;
|
||||
var
|
||||
Lo, Hi, Half: Integer;
|
||||
|
||||
Reference in New Issue
Block a user