mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
feat: QO-100 self-monitor (RX MUTE) + gate beacon/RX-mute buttons to QO-100
Add a per-operator toggle to hear your own downlink off the satellite while transmitting (full-duplex self-monitor), and restrict QO-100-only controls to the QO-100 mode. - WDSPEngine: new MonitorRXDuringTX flag; audio gate becomes `not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX)` so RX audio can pass to speakers during TX in full-duplex. - Controller: FRxMuteOnTx (default True = mute RX on TX), SetRxMuteOnTx/ ApplyRxMuteToEngine, rfRxMuteOnTx notify; new InQO100 predicate (Pluto + full-duplex XVTR slot) as the single gate for QO-100 UI. - Settings: global RxMuteOnTx (rx_mute_on_tx, default True), persisted. - MainForm: new RX MUTE button in TX panel next to DUP, visible only in QO-100; BEACON button visibility tightened from "any transverter" to InQO100 so it no longer shows in plain VHF/UHF modes. - doc: plan updated (RF-AGC done, two temps, TX/RX locked split, self-monitor, phase 6 done; remaining TODOs incl. web wiring). Web intentionally left unchanged for now. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+35
-1
@@ -237,6 +237,7 @@ type
|
||||
BtnCTun: TFlatButton;
|
||||
BtnBeacon: TFlatButton; // QO-100 beacon lock (видим только на Pluto в трансвертере)
|
||||
BtnDUP: TFlatButton;
|
||||
BtnRxMute: TFlatButton; // QO-100 self-monitor: RX-аудио на TX (видим только Pluto+full-duplex)
|
||||
PanelFMSQ: TPanel;
|
||||
BtnFMSQ: TFlatButton;
|
||||
TrkFMSQ: TFlatSlider;
|
||||
@@ -387,6 +388,8 @@ type
|
||||
procedure UpdateBeaconButton;
|
||||
procedure BtnDUPClick(Sender: TObject);
|
||||
procedure ApplyDUP(Active: Boolean);
|
||||
procedure BtnRxMuteClick(Sender: TObject);
|
||||
procedure UpdateRxMuteButton; // видимость (Pluto+full-duplex) + стиль кнопки RX MUTE
|
||||
procedure BtnTUNClick(Sender: TObject);
|
||||
procedure ApplyTUN(Active: Boolean);
|
||||
procedure UpdateFilterButtons;
|
||||
@@ -1212,9 +1215,12 @@ begin
|
||||
// openHPSDR смысла не имеет (нет дрейфующего LNB → прячем).
|
||||
if BtnBeacon <> nil then
|
||||
begin
|
||||
BtnBeacon.Visible := FController.IsPluto and (FController.FCurrentXvtr >= 0);
|
||||
// Только Pluto + full-duplex транспондер (QO-100) — beacon-lock завязан
|
||||
// на дрейф LNB, в обычных диапазонах/трансвертерах смысла нет.
|
||||
BtnBeacon.Visible := FController.InQO100;
|
||||
UpdateBeaconButton;
|
||||
end;
|
||||
UpdateRxMuteButton; // RX MUTE — только Pluto + full-duplex (QO-100)
|
||||
// Wideband-вид + web-зеркало xvtr (ранее в обёртках Activate/Deactivate).
|
||||
// rfXvtr эмитят ТОЛЬКО ActivateXvtr/DeactivateXvtr — состояние финальное.
|
||||
UpdateWidebandFrequencyView;
|
||||
@@ -1313,6 +1319,8 @@ begin
|
||||
PbWaterfall.Invalidate;
|
||||
end;
|
||||
end;
|
||||
rfRxMuteOnTx:
|
||||
UpdateRxMuteButton; // QO-100 self-monitor toggle: подсветка кнопки RX MUTE
|
||||
rfWfAGC:
|
||||
FSpecView.WfAGCEnabled := FController.FWfAGCEnabled;
|
||||
rfWfNF:
|
||||
@@ -1707,6 +1715,15 @@ begin
|
||||
BtnDUP.Tag := 0;
|
||||
StyleButton(BtnDUP, FController.FDisplayDuplex);
|
||||
|
||||
// QO-100 self-monitor: рядом с DUP, col1. Виден только на Pluto в full-duplex
|
||||
// транспондере. ON (подсвечена, умолч.) = RX-аудио глушится на TX; OFF = слышим
|
||||
// свой downlink со спутника.
|
||||
BtnRxMute := MakeBtn(PanelTX, 'RX MUTE',
|
||||
LeftPanelButtonLeft(LEFT_W, 4, 1), 44,
|
||||
LeftPanelButtonWidth(LEFT_W, 4, 1), BTN_SM, BtnRxMuteClick);
|
||||
BtnRxMute.Visible := False;
|
||||
StyleButton(BtnRxMute, FController.FRxMuteOnTx);
|
||||
|
||||
LblDrv := TLabel.Create(Self);
|
||||
LblDrv.Parent := PanelTX; LblDrv.Left := 4; LblDrv.Top := 77;
|
||||
LblDrv.Caption := 'DRV'; LblDrv.Font.Color := CLR_TEXTDIM;
|
||||
@@ -3697,6 +3714,7 @@ begin
|
||||
// Отдельный Pluto RF-AGC блок под AGC: показываем и сдвигаем панели ниже.
|
||||
LayoutLeftPanel(Caps.HasHWGain);
|
||||
if Caps.HasHWGain then UpdateRxGainUI;
|
||||
UpdateRxMuteButton; // RX MUTE — видимость по Pluto + full-duplex (если QO-100 уже активен)
|
||||
end;
|
||||
|
||||
procedure TMainForm.DoConnectDevice(const Dev: THPSDRDevice);
|
||||
@@ -4032,6 +4050,22 @@ begin
|
||||
ApplyDUP(not FController.FDisplayDuplex);
|
||||
end;
|
||||
|
||||
procedure TMainForm.BtnRxMuteClick(Sender: TObject);
|
||||
begin
|
||||
FController.SetRxMuteOnTx(not FController.FRxMuteOnTx); // рендер через rfRxMuteOnTx
|
||||
end;
|
||||
|
||||
procedure TMainForm.UpdateRxMuteButton;
|
||||
// Кнопка RX MUTE видна только на Pluto в full-duplex транспондере (QO-100).
|
||||
// ON (подсвечена) = RX-аудио глушится на TX; OFF = self-monitor (слышим себя).
|
||||
var Vis: Boolean;
|
||||
begin
|
||||
if BtnRxMute = nil then Exit;
|
||||
Vis := FController.InQO100;
|
||||
BtnRxMute.Visible := Vis;
|
||||
if Vis then StyleButton(BtnRxMute, FController.FRxMuteOnTx);
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TUN — Thetis-style тон: WDSP TXA PostGen + MOX с отдельным уровнем Drive
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
+37
-1
@@ -67,7 +67,7 @@ type
|
||||
rfVolume, rfMute, rfDrive, rfAtten,
|
||||
rfBand, rfXvtr, rfBandRestore, // rfBandRestore: явная смена диапазона (сброс Wf-avg в UI)
|
||||
rfCenterFreq, rfSpan, rfSampleRate,
|
||||
rfCTun, rfDuplex,
|
||||
rfCTun, rfDuplex, rfRxMuteOnTx, // rfRxMuteOnTx: QO-100 self-monitor toggle
|
||||
rfRunning, rfConnected, rfTransmitting, rfTuning,
|
||||
rfNR, rfNB, rfSNB, rfANF,
|
||||
rfWfAGC, rfWfNF, rfDither, rfRandom,
|
||||
@@ -217,6 +217,9 @@ type
|
||||
FTuning: Boolean;
|
||||
FSplitTxB: Boolean;
|
||||
FDisplayDuplex: Boolean;
|
||||
// QO-100 full-duplex self-monitor: True — RX-аудио глушится на TX (по умолч.),
|
||||
// False — слышим свой downlink со спутника при передаче. Кнопка RX MUTE.
|
||||
FRxMuteOnTx: Boolean;
|
||||
FHWPTTActive: Boolean;
|
||||
FHWPTTStartedTX: Boolean;
|
||||
FWebClientActive: Boolean; // выставляет web-слой (connect/disconnect клиента)
|
||||
@@ -496,6 +499,8 @@ type
|
||||
// Отображение / приём
|
||||
procedure SetCTun(On_: Boolean);
|
||||
procedure SetDuplex(On_: Boolean);
|
||||
procedure SetRxMuteOnTx(On_: Boolean); // QO-100 self-monitor (кнопка RX MUTE)
|
||||
procedure ApplyRxMuteToEngine; // FRxMuteOnTx → FDSPEngine.MonitorRXDuringTX
|
||||
procedure SetWfAGC(On_: Boolean);
|
||||
procedure SetWfNF(On_: Boolean);
|
||||
|
||||
@@ -552,6 +557,8 @@ type
|
||||
function BandPlan: TBandPlanArray;
|
||||
// True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF).
|
||||
function IsPluto: Boolean;
|
||||
// True — активен QO-100 (Pluto + full-duplex XVTR-слот); гейт UI beacon/RX MUTE.
|
||||
function InQO100: Boolean;
|
||||
// Человекочитаемое имя подключённой платы для статус-строки (UI/Web).
|
||||
// HPSDR → тип платы; Pluto/LibreSDR → hw_model + серийник (если есть).
|
||||
function BoardDisplayName: string;
|
||||
@@ -593,6 +600,7 @@ begin
|
||||
FMode := 1; FFilter := 5; FFilterBW := 2700;
|
||||
FAGCMode := 1; FAGCTop := 90;
|
||||
FRxGainMode := 2; FRxGainDb := 40; // Pluto: slow_attack по умолчанию
|
||||
FRxMuteOnTx := True; // QO-100 self-monitor выкл по умолчанию (RX заглушён на TX)
|
||||
FVolume := 70; FDrivePercent := 50; FDriveLevel := 0; FAtten := 0;
|
||||
FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000;
|
||||
FCurrentBand := 5; FCurrentXvtr := -1;
|
||||
@@ -1552,6 +1560,7 @@ begin
|
||||
Result.WidebandFill := FWidebandFill;
|
||||
Result.SpectrumFill := FSpectrumFill;
|
||||
Result.DisplayDuplex := FDisplayDuplex;
|
||||
Result.RxMuteOnTx := FRxMuteOnTx;
|
||||
Result.AudioOutDevice := FAudioOutDevName;
|
||||
Result.AudioInDevice := FAudioInDevName;
|
||||
Result.SendAudioToRadio := FSendAudioToRadio;
|
||||
@@ -2371,6 +2380,21 @@ begin
|
||||
Changed(rfDuplex);
|
||||
end;
|
||||
|
||||
procedure TRadioController.ApplyRxMuteToEngine;
|
||||
begin
|
||||
// Монитор имеет смысл только в full-duplex (KeepRXDuringTX); сам гейт в движке
|
||||
// это учитывает. Здесь просто транслируем инверсию флага RX MUTE.
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
FDSPEngine.MonitorRXDuringTX := not FRxMuteOnTx;
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetRxMuteOnTx(On_: Boolean);
|
||||
begin
|
||||
FRxMuteOnTx := On_;
|
||||
ApplyRxMuteToEngine;
|
||||
Changed(rfRxMuteOnTx);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetWfAGC(On_: Boolean);
|
||||
begin FWfAGCEnabled := On_; Changed(rfWfAGC); end;
|
||||
|
||||
@@ -2783,6 +2807,8 @@ begin
|
||||
FNetwork.SetSpeakerAudio(FSendAudioToRadio);
|
||||
// RX hw-gain в бэкенд (Pluto). HPSDR — no-op.
|
||||
ApplyRxGainToBackend;
|
||||
// QO-100 self-monitor: RX-аудио на TX (full-duplex). По умолчанию заглушено.
|
||||
ApplyRxMuteToEngine;
|
||||
SendDUCSpecificFromSettings;
|
||||
// Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...).
|
||||
ApplyTXSettingsToDSP;
|
||||
@@ -2949,6 +2975,7 @@ begin
|
||||
FWidebandFill := FLoadedGlobal.WidebandFill;
|
||||
FSpectrumFill := FLoadedGlobal.SpectrumFill;
|
||||
FDisplayDuplex := FLoadedGlobal.DisplayDuplex;
|
||||
FRxMuteOnTx := FLoadedGlobal.RxMuteOnTx;
|
||||
// Настройки дисплея/сетки + DSP display + аудио-устройства
|
||||
if FLoadedGlobal.FFTSize > 0 then
|
||||
begin
|
||||
@@ -3177,6 +3204,15 @@ begin
|
||||
Result := (FNetwork <> nil) and (FNetwork.Caps.Kind = bkPluto);
|
||||
end;
|
||||
|
||||
function TRadioController.InQO100: Boolean;
|
||||
// True — активен full-duplex транспондер (QO-100): Pluto + выбран XVTR-слот с
|
||||
// флагом FullDuplexTx. Гейт для UI-элементов, осмысленных только в этом режиме
|
||||
// (beacon-lock, RX MUTE self-monitor).
|
||||
begin
|
||||
Result := IsPluto and (FCurrentXvtr >= 0)
|
||||
and FXvtrSettings.Entries[FCurrentXvtr].FullDuplexTx;
|
||||
end;
|
||||
|
||||
function TRadioController.BoardDisplayName: string;
|
||||
var
|
||||
Dev: THPSDRDevice;
|
||||
|
||||
@@ -261,6 +261,7 @@ type
|
||||
WidebandFill: Boolean; // True = fill wideband spectrum with gradient
|
||||
SpectrumFill: Boolean; // True = fill main spectrum under the curve with gradient
|
||||
DisplayDuplex: Boolean; // DUP: при TX показывать RX-водопад, TX-фильтр overlay'ем
|
||||
RxMuteOnTx: Boolean; // QO-100 self-monitor: True=RX-аудио глушится на TX (умолч.)
|
||||
// --- Display FPS ---
|
||||
DisplayFPS: Integer; // spectrum/waterfall timer fps (1..100)
|
||||
// --- Theme ---
|
||||
@@ -448,6 +449,7 @@ begin
|
||||
G.WidebandFill := False;
|
||||
G.SpectrumFill := True;
|
||||
G.DisplayDuplex := False;
|
||||
G.RxMuteOnTx := True;
|
||||
G.DisplayFPS := 60;
|
||||
G.FreqMhzDigits := 3;
|
||||
// CAT defaults
|
||||
@@ -671,6 +673,7 @@ begin
|
||||
G.WidebandFill := JB(GObj,'wideband_fill',False);
|
||||
G.SpectrumFill := JB(GObj,'spectrum_fill',True);
|
||||
G.DisplayDuplex := JB(GObj,'display_duplex',False);
|
||||
G.RxMuteOnTx := JB(GObj,'rx_mute_on_tx',True);
|
||||
G.DisplayFPS := JI(GObj,'display_fps',60);
|
||||
G.LightTheme := JB(GObj,'light_theme',False);
|
||||
G.FreqMhzDigits := EnsureRange(JI(GObj,'freq_mhz_digits',3), 3, 5);
|
||||
@@ -774,6 +777,7 @@ begin
|
||||
JW(O,'wideband_fill',G.WidebandFill);
|
||||
JW(O,'spectrum_fill',G.SpectrumFill);
|
||||
JW(O,'display_duplex',G.DisplayDuplex);
|
||||
JW(O,'rx_mute_on_tx',G.RxMuteOnTx);
|
||||
JW(O,'display_fps',G.DisplayFPS);
|
||||
JW(O,'light_theme',G.LightTheme);
|
||||
JW(O,'freq_mhz_digits',G.FreqMhzDigits);
|
||||
|
||||
+14
-1
@@ -223,6 +223,10 @@ type
|
||||
// (мы выяснили на практике что стоп/старт RXA добавляет задержки на
|
||||
// Windows без видимого выигрыша).
|
||||
FKeepRXDuringTX: Boolean;
|
||||
// Полный дуплекс QO-100: пропускать RX-аудио на колонки и во время TX (слышим
|
||||
// свой downlink через транспондер). Имеет смысл только при FKeepRXDuringTX.
|
||||
// По умолчанию False — обычное поведение (RX-аудио глушится на TX).
|
||||
FMonitorRXDuringTX: Boolean;
|
||||
// Дедлайн (GetTickCount64), до которого RX-аудио не пишется в FOnAudio.
|
||||
// Используется на Windows для маскировки «хвоста» TX leakage (radio
|
||||
// FPGA TX-buffer + PA slew-down → RX1 IQ → audio) после MOX-off.
|
||||
@@ -461,6 +465,10 @@ type
|
||||
// RX-анализатор продолжает получать IQ. Должен ставиться UI ДО SetTXRun.
|
||||
property KeepRXDuringTX: Boolean read FKeepRXDuringTX
|
||||
write FKeepRXDuringTX;
|
||||
// Full-duplex self-monitor: при TX пропускать RX-аудио на колонки (QO-100 —
|
||||
// слышим себя со спутника). Действует только вместе с KeepRXDuringTX.
|
||||
property MonitorRXDuringTX: Boolean read FMonitorRXDuringTX
|
||||
write FMonitorRXDuringTX;
|
||||
// Активный display ID — RX_DISP_ID или TX_DISP_ID (зависит от FTXActive).
|
||||
// SpectrumView/Waterfall должны читать пиксели именно из этого ID.
|
||||
property ActiveDisplayID: Integer read FActiveDisplayID;
|
||||
@@ -788,6 +796,7 @@ begin
|
||||
FTXMicTail := 0;
|
||||
FTXActive := False;
|
||||
FKeepRXDuringTX := False;
|
||||
FMonitorRXDuringTX := False;
|
||||
FPostTXMuteUntil := 0;
|
||||
FActiveDisplayID := RX_DISP_ID;
|
||||
FTXMicSource := txmsRadio;
|
||||
@@ -1302,7 +1311,11 @@ begin
|
||||
// через unsigned-сравнение, GetTickCount64 не зовётся когда дедлайн = 0.
|
||||
if (FPostTXMuteUntil <> 0) and (GetTickCount64 >= FPostTXMuteUntil) then
|
||||
FPostTXMuteUntil := 0;
|
||||
if Assigned(FOnAudio) and not FMuted and not FTXActive
|
||||
// Full-duplex self-monitor (QO-100): при FMonitorRXDuringTX не глушим RX-аудио
|
||||
// на TX — слышим свой downlink со спутника. Иначе (по умолчанию) глушим, как
|
||||
// Thetis/piHPSDR при не-duplex MOX. FPostTXMuteUntil-хвост уважаем в любом случае.
|
||||
if Assigned(FOnAudio) and not FMuted
|
||||
and (not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX))
|
||||
and (FPostTXMuteUntil = 0) then
|
||||
begin
|
||||
for i := 0 to FAudioBufSize - 1 do
|
||||
|
||||
@@ -258,12 +258,16 @@ end;
|
||||
|
||||
### 4.7 Усиление / телеметрия
|
||||
|
||||
- RX gain: контрол manual gain (0–73 дБ) / режим hw-AGC при `Caps.HasHWGain`
|
||||
(можно расширить UI step-atten). WDSP AGC поверх.
|
||||
- ✅ RX gain: блок `RF AGC` (десктоп) — кнопки FAST/SLOW/HYB/MAN (4 режима AD9361,
|
||||
подтверждены на железе `gain_control_mode_available`) + слайдер manual 0–73 дБ при
|
||||
`Caps.HasHWGain`; персист per-device. WDSP AGC поверх. TODO: проброс RF-AGC в web
|
||||
(сейчас в web только отображение, без управления).
|
||||
- Телеметрия: S-meter (WDSP) — для обоих. fwd/SWR/supply/PLL/overload — скрыть при
|
||||
`not Caps.HasPA`. ✅ RSSI/температура Pluto: `ReadTelemetry` (backend) →
|
||||
`ServicePlutoTelemetry` (контроллер, опрос ~2 Гц с фильтром выбросов) → поле 3
|
||||
статус-бара (Temp/RSSI вместо Supply, т.к. у Pluto нет PA).
|
||||
статус-бара (Temp/RSSI вместо Supply, т.к. у Pluto нет PA). ✅ **Две температуры**:
|
||||
трансивер AD9361 (`ad9361-phy temp0`) + Zynq SoC/FPGA (`xadc temp0`,
|
||||
`(raw+offset)*scale/1000`) — формат `Temp 37/53C` в десктопе и web.
|
||||
|
||||
### 4.8 TX-мощность / drive (Pluto)
|
||||
|
||||
@@ -303,7 +307,8 @@ end;
|
||||
|
||||
**Фаза 3 — Управление/калибровка**
|
||||
- ✅ RX gain / hw-AGC, транспорт (USB/IP), persist per-device по synthetic-MAC.
|
||||
- ✅ Имя платы (Pluto/LibreSDR + serial) и телеметрия (температура/RSSI) в статус-бар.
|
||||
- ✅ Имя платы (Pluto/LibreSDR + serial) и телеметрия в статус-бар: RSSI + **две
|
||||
температуры** (трансивер AD9361 + Zynq SoC `xadc`, формат `Temp 37/53C`).
|
||||
- TODO: XO ppm-калибровка, buffer/timeout-контролы.
|
||||
|
||||
**Фаза 4 — TX-тракт Pluto** ✅
|
||||
@@ -329,11 +334,25 @@ end;
|
||||
- ✅ Отдельная страница «QO-100» в SettingsForm (LNB LO / транспондер-offset /
|
||||
границы downlink / RX gain / TX-потолок); зарезервирован последний XVTR-слот
|
||||
(`QO100_SLOT`), не засоряет таблицу трансвертеров openHPSDR.
|
||||
- TODO: проверка на железе.
|
||||
- ✅ **TX↔RX связка с фиксированным офсетом**: TX-VFO в downlink-домене,
|
||||
`XvtrTranslateTX` = `visible − TxLOOffset` (≈8089.5 МГц) — крутишь приём, аплинк
|
||||
едет следом, офсет держится сам (через общий VFO). Split — через VFO-B.
|
||||
- ✅ **Аудио self-monitor** («слышу себя со спутника»): кнопка `RX MUTE` в TX-панели
|
||||
рядом с DUP — видна только Pluto + full-duplex (QO-100). По умолчанию ON (RX-аудио
|
||||
глушится на TX, как раньше); OFF → RX-аудио идёт на колонки и во время TX (свой
|
||||
downlink через транспондер, ~250 мс). Реализация: гейт `WDSPEngine` (`not FTXActive
|
||||
or (FKeepRXDuringTX and FMonitorRXDuringTX)`) + флаг `FRxMuteOnTx` в контроллере,
|
||||
персист в глобале (`rx_mute_on_tx`). Web — не сделано (пока вне области).
|
||||
- TODO: проверка на железе; RIT/XIT-клариффайер (опц., на геостационаре редко нужен);
|
||||
TX-маркер на RX-спектре в DUP (визуальный self-monitor — `FSpecView.TXMode` сейчас
|
||||
в DUP выключен).
|
||||
|
||||
**Фаза 6 — Полировка**
|
||||
- Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон `ewsdrd.lpr`
|
||||
(discovery/connect Pluto headless); кросс-платформенная сборка с/без libiio; доки.
|
||||
**Фаза 6 — Полировка** ✅ (в основном)
|
||||
- ✅ Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон `ewsdrd.lpr`
|
||||
(discovery/connect Pluto headless, beacon-lock @10 Гц); кросс-платформенная сборка
|
||||
с/без libiio.
|
||||
- TODO: проброс в web того, чего там ещё нет — RF-AGC (gain/режимы) и QO-100
|
||||
`RX MUTE` (self-monitor). Доки актуализируются по ходу.
|
||||
|
||||
**Сопутствующие исправления (найдены при тесте Pluto VHF/UHF FM, но баги общие):**
|
||||
- ✅ Восстановление per-band FM-настроек на старте (`Settings.LoadDevice`): bulk-
|
||||
|
||||
Reference in New Issue
Block a user