mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-27 01:17:32 +00:00
feat(oc): OC Control — Open Collector выходы Penny/Alex (эталон Thetis)
Byte 1401 High Priority packet: per-band (HF 160m-6m) и per-slot (XVTR) RX/TX-маски на 7 пинов + TX pin action (MOX/TUNE/2TON и комбинации). Только openHPSDR — у Pluto/AD936x такого выхода нет, вкладка Settings скрывается на этом бэкенде. Настройки — вкладка "OC Control" в SettingsForm. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+285
-1
@@ -85,6 +85,7 @@ type
|
||||
const InDev: array of string) of object;
|
||||
TOnAlexChange = procedure(const A: TAlexSettings) of object;
|
||||
TOnXvtrChange = procedure(const X: TXvtrSettings) of object;
|
||||
TOnOCChange = procedure(const O: TOCSettings) of object;
|
||||
|
||||
{ TSettingsForm }
|
||||
TSettingsForm = class(TForm)
|
||||
@@ -320,6 +321,26 @@ type
|
||||
FXvtrConfig: TXvtrSettings;
|
||||
FOnXvtrChange: TOnXvtrChange;
|
||||
|
||||
// ---- OC Control tab (Open Collector выходы Penny/Alex, openHPSDR-only) ----
|
||||
FPageOC: TScrollBox;
|
||||
FNavOC: TFlatButton;
|
||||
FOCSubtitle: TLabel;
|
||||
FOCEnabled: TFlatCheckBox;
|
||||
// HF-группа: 11 диапазонов (0=160m..10=6m) × 7 пинов, RX/TX-маски
|
||||
FOCHFRx: array[0..10, 0..OC_PIN_COUNT-1] of TFlatCheckBox;
|
||||
FOCHFTx: array[0..10, 0..OC_PIN_COUNT-1] of TFlatCheckBox;
|
||||
FOCHFAction: array[0..OC_PIN_COUNT-1] of TFlatComboBox;
|
||||
FOCHFGrp: TPanel;
|
||||
FOCHFActGrp: TPanel;
|
||||
// VHF-группа = 8 слотов XVTR × 7 пинов, RX/TX-маски
|
||||
FOCXvtrRx: array[0..CFG_XVTR_COUNT-1, 0..OC_PIN_COUNT-1] of TFlatCheckBox;
|
||||
FOCXvtrTx: array[0..CFG_XVTR_COUNT-1, 0..OC_PIN_COUNT-1] of TFlatCheckBox;
|
||||
FOCXvtrAction: array[0..OC_PIN_COUNT-1] of TFlatComboBox;
|
||||
FOCXvtrGrp: TPanel;
|
||||
FOCXvtrActGrp: TPanel;
|
||||
FOCConfig: TOCSettings;
|
||||
FOnOCChange: TOnOCChange;
|
||||
|
||||
// ---- QO-100 tab (редактирует зарезервированный слот QO100_SLOT) ----
|
||||
FPageQO: TScrollBox;
|
||||
FNavQO: TFlatButton;
|
||||
@@ -431,6 +452,10 @@ type
|
||||
procedure FireXvtrChange;
|
||||
procedure OnXvtrAnyChange(Sender: TObject);
|
||||
procedure UpdateXvtrRowEnabled(Idx: Integer);
|
||||
procedure BuildOCTab;
|
||||
procedure CollectOCFromUI;
|
||||
procedure FireOCChange;
|
||||
procedure OnOCAnyChange(Sender: TObject);
|
||||
procedure CollectTXFromUI;
|
||||
procedure FireTXChange;
|
||||
procedure OnTXAnyChange(Sender: TObject);
|
||||
@@ -547,6 +572,9 @@ type
|
||||
procedure LoadTXSettings(const T: TTXSettings; BoardType: Integer = 0);
|
||||
procedure LoadAlexSettings(const A: TAlexSettings; BoardType: Integer);
|
||||
procedure LoadXvtrSettings(const X: TXvtrSettings);
|
||||
procedure LoadOCSettings(const O: TOCSettings);
|
||||
// Вкладка OC Control — только openHPSDR (у Pluto/AD936x нет такого выхода).
|
||||
procedure SetOCBackend(IsPluto: Boolean);
|
||||
procedure LoadWfAGCNF(AGC, NF: Boolean);
|
||||
procedure LoadWfRender(Palette: Integer; Gamma: Double;
|
||||
ResLimit, SpeedDiv: Integer);
|
||||
@@ -559,6 +587,7 @@ type
|
||||
property OnSliceCATChange: TOnSliceCATChange read FOnSliceCATChange write FOnSliceCATChange;
|
||||
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
|
||||
property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange;
|
||||
property OnOCChange: TOnOCChange read FOnOCChange write FOnOCChange;
|
||||
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
|
||||
property OnPlutoTxChange: TOnPlutoTxChange read FOnPlutoTxChange write FOnPlutoTxChange;
|
||||
property OnAnt936xChange: TOnAnt936xChange read FOnAnt936xChange write FOnAnt936xChange;
|
||||
@@ -643,6 +672,10 @@ const
|
||||
PA_BAND_NAMES: array[0..10] of string =
|
||||
('160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
|
||||
|
||||
// TX pin action (OC Control) — имена в UI, значения = OCA_* из Settings.pas.
|
||||
OC_ACTION_NAMES: array[0..6] of string = (
|
||||
'MOX', 'TUNE', '2TON', 'MOX+TUNE', 'MOX+2TON', 'TUNE+2TON', 'MOX+TUNE+2TON');
|
||||
|
||||
// Раскладка вкладки Calibration — общая для построения (BuildCalibrationTab)
|
||||
// и перекладки под активное железо (RelayoutCalTab).
|
||||
CAL_MARGIN = 22;
|
||||
@@ -818,7 +851,8 @@ begin
|
||||
FNavSlices := MakeNavButton('Slices', 398);
|
||||
FNavAlex := MakeNavButton('Antenna / Alex', 434);
|
||||
FNavXvtr := MakeNavButton('Transverter', 470);
|
||||
FNavQO := MakeNavButton('QO-100', 506);
|
||||
FNavOC := MakeNavButton('OC Control', 506);
|
||||
FNavQO := MakeNavButton('QO-100', 542);
|
||||
|
||||
FContentPanel := TPanel.Create(Self);
|
||||
FContentPanel.Parent := Self;
|
||||
@@ -838,11 +872,13 @@ begin
|
||||
FPageSlices := MakeScrollPage;
|
||||
FPageAlex := MakeScrollPage;
|
||||
FPageXvtr := MakeScrollPage;
|
||||
FPageOC := MakeScrollPage;
|
||||
FPageQO := MakeScrollPage;
|
||||
FAlexBoardType := 0;
|
||||
TSettingsManager.DefaultAlex(FAlexConfig);
|
||||
TSettingsManager.DefaultXvtr(FXvtrConfig);
|
||||
TSettingsManager.DefaultCalibration(FCalConfig);
|
||||
TSettingsManager.DefaultOC(FOCConfig);
|
||||
|
||||
BuildAudioTab;
|
||||
BuildDisplayTab;
|
||||
@@ -855,6 +891,7 @@ begin
|
||||
BuildSlicesTab;
|
||||
BuildAlexTab;
|
||||
BuildXvtrTab;
|
||||
BuildOCTab;
|
||||
BuildQOTab;
|
||||
|
||||
FBtnClose := TFlatButton.Create(Self);
|
||||
@@ -889,6 +926,7 @@ begin
|
||||
else if Sender = FNavSlices then SelectPage(FPageSlices, FNavSlices)
|
||||
else if Sender = FNavAlex then SelectPage(FPageAlex, FNavAlex)
|
||||
else if Sender = FNavXvtr then SelectPage(FPageXvtr, FNavXvtr)
|
||||
else if Sender = FNavOC then SelectPage(FPageOC, FNavOC)
|
||||
else if Sender = FNavQO then SelectPage(FPageQO, FNavQO);
|
||||
end;
|
||||
|
||||
@@ -906,6 +944,7 @@ begin
|
||||
FPageSlices.Visible := APage = FPageSlices;
|
||||
FPageAlex.Visible := APage = FPageAlex;
|
||||
FPageXvtr.Visible := APage = FPageXvtr;
|
||||
FPageOC.Visible := APage = FPageOC;
|
||||
FPageQO.Visible := APage = FPageQO;
|
||||
|
||||
FNavAudio.Active := ANav = FNavAudio;
|
||||
@@ -920,6 +959,7 @@ begin
|
||||
FNavSlices.Active := ANav = FNavSlices;
|
||||
FNavAlex.Active := ANav = FNavAlex;
|
||||
FNavXvtr.Active := ANav = FNavXvtr;
|
||||
FNavOC.Active := ANav = FNavOC;
|
||||
FNavQO.Active := ANav = FNavQO;
|
||||
end;
|
||||
|
||||
@@ -4658,6 +4698,250 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// ===========================================================================
|
||||
// OC Control tab (Open Collector выходы Penny/Alex, byte 1401 HP-пакета).
|
||||
// Портировано из Thetis (Console/setup.cs UpdateOCBits + Console/HPSDR/
|
||||
// Penny.cs), объём сокращён — см. память project_oc_control: без PA-override/
|
||||
// hot-switching внешнего PA и без split-pins VFO A/B. Только openHPSDR —
|
||||
// вкладка целиком прячется на Pluto/AD936x (SetOCBackend).
|
||||
// HF-группа = 11 диапазонов (как Alex/PA), VHF-группа = 8 слотов XVTR.
|
||||
// ===========================================================================
|
||||
|
||||
procedure TSettingsForm.BuildOCTab;
|
||||
const
|
||||
MARGIN = 22;
|
||||
PAD = 12;
|
||||
HDR1_H = 22;
|
||||
HDR2_H = 20;
|
||||
ROW_H = 24;
|
||||
PIN_W = 24;
|
||||
BAND_X = PAD;
|
||||
RX0_X = PAD + 50;
|
||||
TX0_X = RX0_X + OC_PIN_COUNT * PIN_W + 20;
|
||||
GRP_W = TX0_X + OC_PIN_COUNT * PIN_W + PAD;
|
||||
DATA_Y0 = GRP_TITLE_H + HDR1_H + HDR2_H + 4;
|
||||
HF_GRP_H = GRP_TITLE_H + HDR1_H + HDR2_H + 11 * ROW_H + PAD;
|
||||
XVTR_GRP_H = GRP_TITLE_H + HDR1_H + HDR2_H + CFG_XVTR_COUNT * ROW_H + PAD;
|
||||
ACT_ROW_H = 30;
|
||||
ACT_GRP_W = 260;
|
||||
ACT_GRP_H = GRP_TITLE_H + PAD + OC_PIN_COUNT * ACT_ROW_H;
|
||||
|
||||
HF_BAND_NAMES: array[0..10] of string =
|
||||
('160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
|
||||
|
||||
var
|
||||
Grp, ActGrp: TPanel;
|
||||
b, p: Integer;
|
||||
Y: Integer;
|
||||
Cmb: TFlatComboBox;
|
||||
|
||||
function MkChk(AParent: TWinControl; AX, AY: Integer): TFlatCheckBox;
|
||||
begin
|
||||
Result := TFlatCheckBox.Create(Self);
|
||||
Result.Parent := AParent;
|
||||
Result.Caption := '';
|
||||
Result.SetBounds(AX, AY, PIN_W, 20);
|
||||
Result.Font.Color := CLR_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.OnChange := OnOCAnyChange;
|
||||
end;
|
||||
|
||||
begin
|
||||
FLoading := True;
|
||||
try
|
||||
with TLabel.Create(Self) do
|
||||
begin
|
||||
Parent := FPageOC;
|
||||
Caption := 'OC Control';
|
||||
SetBounds(MARGIN, 18, 500, 28);
|
||||
Font.Name := UI_FONT; Font.Size := 16;
|
||||
Font.Style := [fsBold]; Font.Color := CLR_TEXT;
|
||||
end;
|
||||
FOCSubtitle := TLabel.Create(Self);
|
||||
FOCSubtitle.Parent := FPageOC;
|
||||
FOCSubtitle.Caption :=
|
||||
'Open Collector outputs (Penny/Alex board), per-band and per-transverter. openHPSDR only.';
|
||||
FOCSubtitle.SetBounds(MARGIN, 48, 680, 20);
|
||||
FOCSubtitle.Font.Name := UI_FONT;
|
||||
FOCSubtitle.Font.Size := 9;
|
||||
FOCSubtitle.Font.Color := CLR_TEXTDIM;
|
||||
|
||||
FOCEnabled := TFlatCheckBox.Create(Self);
|
||||
FOCEnabled.Parent := FPageOC;
|
||||
FOCEnabled.Caption := 'Enable OC Control';
|
||||
FOCEnabled.SetBounds(MARGIN, 76, 220, 22);
|
||||
FOCEnabled.Font.Color := CLR_TEXT;
|
||||
FOCEnabled.Font.Name := UI_FONT;
|
||||
FOCEnabled.Font.Size := 9;
|
||||
FOCEnabled.OnChange := OnOCAnyChange;
|
||||
|
||||
// ---- HF band grid ----
|
||||
Grp := MakeGroupPanel(FPageOC, 'HF Bands', MARGIN, 106, GRP_W, HF_GRP_H);
|
||||
FOCHFGrp := Grp;
|
||||
MakeLbl(Grp, 'Band', BAND_X, GRP_TITLE_H + 2, 44);
|
||||
MakeLbl(Grp, 'RX pins', RX0_X, GRP_TITLE_H + 2, OC_PIN_COUNT * PIN_W);
|
||||
MakeLbl(Grp, 'TX pins', TX0_X, GRP_TITLE_H + 2, OC_PIN_COUNT * PIN_W);
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
begin
|
||||
MakeLbl(Grp, IntToStr(p + 1), RX0_X + p * PIN_W + 5, GRP_TITLE_H + HDR1_H + 2, PIN_W);
|
||||
MakeLbl(Grp, IntToStr(p + 1), TX0_X + p * PIN_W + 5, GRP_TITLE_H + HDR1_H + 2, PIN_W);
|
||||
end;
|
||||
for b := 0 to 10 do
|
||||
begin
|
||||
Y := DATA_Y0 + b * ROW_H;
|
||||
MakeLbl(Grp, HF_BAND_NAMES[b], BAND_X, Y + 2, 44);
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
begin
|
||||
FOCHFRx[b, p] := MkChk(Grp, RX0_X + p * PIN_W, Y);
|
||||
FOCHFTx[b, p] := MkChk(Grp, TX0_X + p * PIN_W, Y);
|
||||
end;
|
||||
end;
|
||||
|
||||
// ---- HF TX pin action ----
|
||||
ActGrp := MakeGroupPanel(FPageOC, 'HF TX Pin Action',
|
||||
MARGIN + GRP_W + 14, 106, ACT_GRP_W, ACT_GRP_H);
|
||||
FOCHFActGrp := ActGrp;
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
begin
|
||||
MakeLbl(ActGrp, 'Pin ' + IntToStr(p + 1), PAD, GRP_TITLE_H + PAD + p * ACT_ROW_H + 5, 44);
|
||||
Cmb := MakeCombo(ActGrp, PAD + 50, GRP_TITLE_H + PAD + p * ACT_ROW_H, 190, OnOCAnyChange);
|
||||
for b := 0 to 6 do Cmb.Items.Add(OC_ACTION_NAMES[b]);
|
||||
Cmb.ItemIndex := OCA_MOX_TUNE_TWOTONE;
|
||||
FOCHFAction[p] := Cmb;
|
||||
end;
|
||||
|
||||
// ---- XVTR (VHF) slot grid ----
|
||||
Grp := MakeGroupPanel(FPageOC, 'Transverter Slots', MARGIN, 106 + HF_GRP_H + 14,
|
||||
GRP_W, XVTR_GRP_H);
|
||||
FOCXvtrGrp := Grp;
|
||||
MakeLbl(Grp, 'Slot', BAND_X, GRP_TITLE_H + 2, 44);
|
||||
MakeLbl(Grp, 'RX pins', RX0_X, GRP_TITLE_H + 2, OC_PIN_COUNT * PIN_W);
|
||||
MakeLbl(Grp, 'TX pins', TX0_X, GRP_TITLE_H + 2, OC_PIN_COUNT * PIN_W);
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
begin
|
||||
MakeLbl(Grp, IntToStr(p + 1), RX0_X + p * PIN_W + 5, GRP_TITLE_H + HDR1_H + 2, PIN_W);
|
||||
MakeLbl(Grp, IntToStr(p + 1), TX0_X + p * PIN_W + 5, GRP_TITLE_H + HDR1_H + 2, PIN_W);
|
||||
end;
|
||||
for b := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
Y := DATA_Y0 + b * ROW_H;
|
||||
MakeLbl(Grp, 'XVTR' + IntToStr(b + 1), BAND_X, Y + 2, 48);
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
begin
|
||||
FOCXvtrRx[b, p] := MkChk(Grp, RX0_X + p * PIN_W, Y);
|
||||
FOCXvtrTx[b, p] := MkChk(Grp, TX0_X + p * PIN_W, Y);
|
||||
end;
|
||||
end;
|
||||
|
||||
// ---- XVTR TX pin action ----
|
||||
ActGrp := MakeGroupPanel(FPageOC, 'Transverter TX Pin Action',
|
||||
MARGIN + GRP_W + 14, 106 + HF_GRP_H + 14, ACT_GRP_W, ACT_GRP_H);
|
||||
FOCXvtrActGrp := ActGrp;
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
begin
|
||||
MakeLbl(ActGrp, 'Pin ' + IntToStr(p + 1), PAD, GRP_TITLE_H + PAD + p * ACT_ROW_H + 5, 44);
|
||||
Cmb := MakeCombo(ActGrp, PAD + 50, GRP_TITLE_H + PAD + p * ACT_ROW_H, 190, OnOCAnyChange);
|
||||
for b := 0 to 6 do Cmb.Items.Add(OC_ACTION_NAMES[b]);
|
||||
Cmb.ItemIndex := OCA_MOX_TUNE_TWOTONE;
|
||||
FOCXvtrAction[p] := Cmb;
|
||||
end;
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.CollectOCFromUI;
|
||||
var b, p: Integer; Mask: Byte;
|
||||
begin
|
||||
FOCConfig.Enabled := (FOCEnabled <> nil) and FOCEnabled.Checked;
|
||||
for b := 0 to 10 do
|
||||
begin
|
||||
Mask := 0;
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
if (FOCHFRx[b, p] <> nil) and FOCHFRx[b, p].Checked then Mask := Mask or (1 shl p);
|
||||
FOCConfig.RxMaskHF[b] := Mask;
|
||||
Mask := 0;
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
if (FOCHFTx[b, p] <> nil) and FOCHFTx[b, p].Checked then Mask := Mask or (1 shl p);
|
||||
FOCConfig.TxMaskHF[b] := Mask;
|
||||
end;
|
||||
for b := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
Mask := 0;
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
if (FOCXvtrRx[b, p] <> nil) and FOCXvtrRx[b, p].Checked then Mask := Mask or (1 shl p);
|
||||
FOCConfig.RxMaskXvtr[b] := Mask;
|
||||
Mask := 0;
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
if (FOCXvtrTx[b, p] <> nil) and FOCXvtrTx[b, p].Checked then Mask := Mask or (1 shl p);
|
||||
FOCConfig.TxMaskXvtr[b] := Mask;
|
||||
end;
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
begin
|
||||
if (FOCHFAction[p] <> nil) and (FOCHFAction[p].ItemIndex >= 0) then
|
||||
FOCConfig.ActionHF[p] := Byte(FOCHFAction[p].ItemIndex);
|
||||
if (FOCXvtrAction[p] <> nil) and (FOCXvtrAction[p].ItemIndex >= 0) then
|
||||
FOCConfig.ActionXvtr[p] := Byte(FOCXvtrAction[p].ItemIndex);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.FireOCChange;
|
||||
begin
|
||||
if FLoading then Exit;
|
||||
CollectOCFromUI;
|
||||
if Assigned(FOnOCChange) then
|
||||
FOnOCChange(FOCConfig);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.OnOCAnyChange(Sender: TObject);
|
||||
begin
|
||||
FireOCChange;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.LoadOCSettings(const O: TOCSettings);
|
||||
var b, p: Integer;
|
||||
begin
|
||||
FLoading := True;
|
||||
try
|
||||
FOCConfig := O;
|
||||
if FOCEnabled <> nil then FOCEnabled.Checked := O.Enabled;
|
||||
for b := 0 to 10 do
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
begin
|
||||
if FOCHFRx[b, p] <> nil then
|
||||
FOCHFRx[b, p].Checked := (O.RxMaskHF[b] and (1 shl p)) <> 0;
|
||||
if FOCHFTx[b, p] <> nil then
|
||||
FOCHFTx[b, p].Checked := (O.TxMaskHF[b] and (1 shl p)) <> 0;
|
||||
end;
|
||||
for b := 0 to CFG_XVTR_COUNT - 1 do
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
begin
|
||||
if FOCXvtrRx[b, p] <> nil then
|
||||
FOCXvtrRx[b, p].Checked := (O.RxMaskXvtr[b] and (1 shl p)) <> 0;
|
||||
if FOCXvtrTx[b, p] <> nil then
|
||||
FOCXvtrTx[b, p].Checked := (O.TxMaskXvtr[b] and (1 shl p)) <> 0;
|
||||
end;
|
||||
for p := 0 to OC_PIN_COUNT - 1 do
|
||||
begin
|
||||
if FOCHFAction[p] <> nil then FOCHFAction[p].ItemIndex := EnsureRange(O.ActionHF[p], 0, 6);
|
||||
if FOCXvtrAction[p] <> nil then FOCXvtrAction[p].ItemIndex := EnsureRange(O.ActionXvtr[p], 0, 6);
|
||||
end;
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.SetOCBackend(IsPluto: Boolean);
|
||||
// У Pluto/AD936x нет Open Collector выхода — вкладка целиком скрывается,
|
||||
// а не показывает пустое содержимое (в отличие от Antenna, где Pluto имеет
|
||||
// свою таблицу разъёмов).
|
||||
begin
|
||||
FNavOC.Visible := not IsPluto;
|
||||
if IsPluto and (FPageOC.Visible) then
|
||||
SelectPage(FPageAudio, FNavAudio);
|
||||
end;
|
||||
|
||||
// ── QO-100 page ─────────────────────────────────────────────────────────────
|
||||
|
||||
procedure TSettingsForm.BuildQOTab;
|
||||
|
||||
Reference in New Issue
Block a user