diff --git a/BeaconScopeForm.pas b/BeaconScopeForm.pas index df40fc8..a5e3bf1 100644 --- a/BeaconScopeForm.pas +++ b/BeaconScopeForm.pas @@ -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; diff --git a/RadioController.pas b/RadioController.pas index 0e1bf5e..4faba7f 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -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;