mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
Now that SetDrive is a complete command, the web/CAT Drive handler moves into TWebAdapter.OnDrive (the one handler WA1 left behind). CAT context SetDriveLevel repointed to it too. Makes the channel-power-clear reactive: ChannelController.OnDriveChanged fires on the rfDrive event and clears FPreChannelPower for any external drive change (slider/web/encoder), guarded by FRestoringDrive for the unit's own apply/restore. This removes the imperative ClearPreChannelPower coupling so SetDrive callers need no channel knowledge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
237 lines
10 KiB
ObjectPascal
237 lines
10 KiB
ObjectPascal
unit ChannelController;
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// TChannelController — адаптер каналов памяти поверх TRadioController.
|
|
//
|
|
// Каналы концептуально — recall сохранённого конфига (частота/режим/FM/мощность),
|
|
// т.е. клиент радио-ядра, а не часть DSP/сети. Поэтому логика применения каналов
|
|
// живёт здесь, отдельным юнитом, и оркеструет ПУБЛИЧНЫЕ команды контроллера
|
|
// (SetMode/SetFMCTCSS/SetFMRpt/SetVfoA/SetDrive/SaveCurrentBand/…). Каждая
|
|
// команда несёт DSP+сеть+состояние и эмитит Changed(field) — рендер делает UI
|
|
// через OnStateChanged. Юнит держит ТОЛЬКО канальное состояние (активный индекс
|
|
// + мощность «до канала») — это снимает последний UI-блокер для headless-режима.
|
|
//
|
|
// Наружу юнит эмитит собственное событие OnActiveChanged (рендер кнопки Channel).
|
|
// Две UI-композитные XVTR-операции (вход/выход трансвертера с wideband-видом и
|
|
// web-пушем) пока живут в MainForm-обёртках — юнит зовёт их через колбэки
|
|
// OnActivateXvtr/OnDeactivateXvtr (сократятся при переносе xvtr render-extras в
|
|
// OnControllerState).
|
|
// -----------------------------------------------------------------------------
|
|
|
|
{$mode delphi}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, Math,
|
|
RadioController, ChannelStore, Settings, WDSPEngine, FMRepeater, BoardUtils;
|
|
|
|
type
|
|
// Рендер активного канала: Active=True → подсветка + Name; False → сброс.
|
|
TChannelActiveEvent = procedure(Idx: Integer; const Name: string; Active: Boolean) of object;
|
|
TChannelXvtrEvent = procedure(Idx: Integer) of object;
|
|
TChannelNotifyEvent = procedure of object;
|
|
|
|
TChannelController = class
|
|
private
|
|
FRadio: TRadioController;
|
|
FActiveIdx: Integer; // -1 = нет активного канала
|
|
FPreChannelPower: Integer; // drive % до применения канала (-1 = не сохранён)
|
|
FRestoringDrive: Boolean; // True пока юнит сам ставит drive (apply/restore)
|
|
FOnActiveChanged: TChannelActiveEvent;
|
|
FOnActivateXvtr: TChannelXvtrEvent;
|
|
FOnDeactivateXvtr: TChannelNotifyEvent;
|
|
procedure EmitActive(Active: Boolean; const Name: string);
|
|
procedure SetDriveGuarded(V: Integer); // SetDrive, не сбрасывающий память канала
|
|
public
|
|
constructor Create(ARadio: TRadioController);
|
|
|
|
procedure Select(Idx: Integer); // выбор канала из дропдауна
|
|
procedure Apply(const Ch: TChannel); // применить канал к радио
|
|
procedure CheckActive; // деактивировать при уходе с частоты
|
|
procedure OnVfoTuned; // канальная оркестрация после SetVfoA
|
|
procedure OnDriveChanged; // реакция на rfDrive: внешнее изменение снимает память
|
|
|
|
property ActiveIdx: Integer read FActiveIdx write FActiveIdx;
|
|
property OnActiveChanged: TChannelActiveEvent read FOnActiveChanged write FOnActiveChanged;
|
|
property OnActivateXvtr: TChannelXvtrEvent read FOnActivateXvtr write FOnActivateXvtr;
|
|
property OnDeactivateXvtr: TChannelNotifyEvent read FOnDeactivateXvtr write FOnDeactivateXvtr;
|
|
end;
|
|
|
|
implementation
|
|
|
|
constructor TChannelController.Create(ARadio: TRadioController);
|
|
begin
|
|
inherited Create;
|
|
FRadio := ARadio;
|
|
FActiveIdx := -1;
|
|
FPreChannelPower := -1;
|
|
end;
|
|
|
|
procedure TChannelController.EmitActive(Active: Boolean; const Name: string);
|
|
begin
|
|
if Assigned(FOnActiveChanged) then FOnActiveChanged(FActiveIdx, Name, Active);
|
|
end;
|
|
|
|
procedure TChannelController.SetDriveGuarded(V: Integer);
|
|
// Drive, выставляемый самим юнитом (применение мощности канала / восстановление):
|
|
// rfDrive прилетит, но OnDriveChanged НЕ должен трактовать его как ручной → не
|
|
// сбрасывать FPreChannelPower.
|
|
begin
|
|
FRestoringDrive := True;
|
|
try
|
|
FRadio.SetDrive(V);
|
|
finally
|
|
FRestoringDrive := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TChannelController.OnDriveChanged;
|
|
// Вызывается из OnControllerState(rfDrive). Любое ВНЕШНЕЕ изменение drive
|
|
// (слайдер/web/энкодер) снимает сохранённую «до канала» мощность.
|
|
begin
|
|
if not FRestoringDrive then FPreChannelPower := -1;
|
|
end;
|
|
|
|
procedure TChannelController.Select(Idx: Integer);
|
|
var Ch: TChannel;
|
|
begin
|
|
if (FRadio.FChannelStore = nil) or (Idx < 0) or (Idx >= FRadio.FChannelStore.Count) then Exit;
|
|
Ch := FRadio.FChannelStore.GetChannel(Idx);
|
|
FActiveIdx := Idx;
|
|
EmitActive(True, Ch.Name); // рендер кнопки Channel (подсветка + имя)
|
|
Apply(Ch);
|
|
end;
|
|
|
|
procedure TChannelController.Apply(const Ch: TChannel);
|
|
var
|
|
i, NewBand, XvtrIdx: Integer;
|
|
begin
|
|
// Сохраняем текущее состояние до любых изменений
|
|
FRadio.SaveCurrentBand;
|
|
|
|
// Ищем подходящий XVTR-слот по частоте канала
|
|
XvtrIdx := -1;
|
|
for i := 0 to CFG_XVTR_COUNT - 1 do
|
|
if FRadio.FXvtrSettings.Entries[i].Enabled and
|
|
(Ch.RXFreq >= FRadio.FXvtrSettings.Entries[i].FreqBegin) and
|
|
(Ch.RXFreq <= FRadio.FXvtrSettings.Entries[i].FreqEnd) then
|
|
begin
|
|
XvtrIdx := i;
|
|
Break;
|
|
end;
|
|
|
|
NewBand := -1;
|
|
if XvtrIdx >= 0 then
|
|
begin
|
|
// Канал на трансвертерном диапазоне — активируем XVTR если не активен
|
|
if (FRadio.FCurrentXvtr <> XvtrIdx) and Assigned(FOnActivateXvtr) then
|
|
FOnActivateXvtr(XvtrIdx);
|
|
end
|
|
else
|
|
begin
|
|
// Канал на HF (или неизвестная частота). Если были на XVTR — выходим в HF,
|
|
// иначе SetVfoA заклэмпит частоту.
|
|
if (FRadio.FCurrentXvtr >= 0) and Assigned(FOnDeactivateXvtr) then
|
|
FOnDeactivateXvtr;
|
|
NewBand := FreqToBandIdx(Ch.RXFreq);
|
|
if (NewBand >= 0) and (NewBand <> FRadio.FCurrentBand) then
|
|
FRadio.SetCurrentBandIdx(NewBand); // band-индекс + drive-кал + рендер кнопок
|
|
end;
|
|
|
|
// Режим (после XVTR/Band режим мог измениться — сверяем снова). SetMode несёт
|
|
// DSP + дефолтный фильтр под режим + рендер (FM-панели для FM).
|
|
if FRadio.FMode <> Ch.Mode then
|
|
FRadio.SetMode(Ch.Mode);
|
|
|
|
// FM-параметры
|
|
if FRadio.FMode = MODE_FM then
|
|
begin
|
|
FRadio.FFMCTCSSAutoActive := Ch.CTCSSOn; // CTCSS из канала — авто, сбросится при уходе
|
|
FRadio.SetFMCTCSSTone(Ch.CTCSSToneIdx);
|
|
FRadio.SetFMCTCSS(Ch.CTCSSOn);
|
|
FRadio.FFMRptOffsetHz := Ch.RptOffsetHz; // зафиксировать до рендера в SetFMRpt
|
|
FRadio.SetFMRpt(Ch.RptDir); // dir + рендер кнопок/оффсета
|
|
FRadio.FFMRptAutoActive := Ch.RptDir <> RPT_NONE; // RPT из канала — авто
|
|
end;
|
|
|
|
// Мощность из канала. Запоминаем drive «до канала» для восстановления при уходе
|
|
// (не перетираем при переходе канал→канал).
|
|
if Ch.Power >= 0 then
|
|
begin
|
|
if FPreChannelPower < 0 then
|
|
FPreChannelPower := FRadio.FDrivePercent;
|
|
SetDriveGuarded(EnsureRange(Ch.Power, 0, 100));
|
|
end;
|
|
|
|
// Частота VFO A (XVTR-клэмп, CTUN, band-detect, сеть — всё в SetVfoA)
|
|
FRadio.SetVfoA(Round(Ch.RXFreq));
|
|
|
|
// Сохраняем итог в band/XVTR-кэш
|
|
if (XvtrIdx >= 0) or (NewBand >= 0) then
|
|
FRadio.SaveCurrentBand;
|
|
end;
|
|
|
|
procedure TChannelController.CheckActive;
|
|
var Ch: TChannel;
|
|
begin
|
|
if FActiveIdx < 0 then Exit;
|
|
if (FRadio.FChannelStore = nil) or (FActiveIdx >= FRadio.FChannelStore.Count) then
|
|
begin
|
|
FActiveIdx := -1;
|
|
EmitActive(False, '');
|
|
Exit;
|
|
end;
|
|
Ch := FRadio.FChannelStore.GetChannel(FActiveIdx);
|
|
if Round(FRadio.FVfoA) <> Round(Ch.RXFreq) then
|
|
begin
|
|
FActiveIdx := -1;
|
|
EmitActive(False, '');
|
|
|
|
// Сбрасываем CTCSS если он был включён каналом (не вручную)
|
|
if FRadio.FFMCTCSSAutoActive then
|
|
begin
|
|
FRadio.FFMCTCSSAutoActive := False;
|
|
FRadio.SetFMCTCSS(False);
|
|
end;
|
|
|
|
// Сбрасываем RPT если он был включён каналом (не вручную), затем даём
|
|
// UpdateRptAutoState переоценить авто-MINUS для текущей частоты.
|
|
if FRadio.FFMRptAutoActive then
|
|
begin
|
|
FRadio.SetFMRpt(RPT_NONE); // dir NONE + рендер off (снимает авто-флаг)
|
|
FRadio.UpdateRptAutoState;
|
|
end;
|
|
|
|
// Восстанавливаем мощность которая была до применения канала
|
|
if FPreChannelPower >= 0 then
|
|
begin
|
|
SetDriveGuarded(FPreChannelPower);
|
|
FPreChannelPower := -1;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TChannelController.OnVfoTuned;
|
|
// Вызывается из пути тюнинга (MainForm.ApplyVfoA) ПОСЛЕ FController.SetVfoA.
|
|
// Без активного канала: сброс авто-CTCSS + восстановление pre-channel drive.
|
|
// С активным каналом: CheckActive деактивирует при уходе с частоты канала.
|
|
begin
|
|
if FActiveIdx < 0 then
|
|
begin
|
|
if FRadio.FFMCTCSSAutoActive then
|
|
begin
|
|
FRadio.FFMCTCSSAutoActive := False;
|
|
FRadio.SetFMCTCSS(False);
|
|
end;
|
|
if FPreChannelPower >= 0 then
|
|
begin
|
|
SetDriveGuarded(FPreChannelPower); // восстановить + push + рендер слайдера
|
|
FPreChannelPower := -1;
|
|
end;
|
|
end;
|
|
CheckActive; // no-op если FActiveIdx<0
|
|
end;
|
|
|
|
end.
|