Modbus fixes and adjustments
This commit is contained in:
parent
bcdf0393a3
commit
a8380f18e0
@ -20,7 +20,6 @@ import {
|
|||||||
ModbusBasicConfig_v3_5_2,
|
ModbusBasicConfig_v3_5_2,
|
||||||
ModbusLegacyBasicConfig,
|
ModbusLegacyBasicConfig,
|
||||||
ModbusLegacySlave,
|
ModbusLegacySlave,
|
||||||
ModbusMasterConfig,
|
|
||||||
ModbusSlave,
|
ModbusSlave,
|
||||||
} from '../gateway-widget.models';
|
} from '../gateway-widget.models';
|
||||||
import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract';
|
import { GatewayConnectorVersionProcessor } from './gateway-connector-version-processor.abstract';
|
||||||
@ -40,9 +39,9 @@ export class ModbusVersionProcessor extends GatewayConnectorVersionProcessor<any
|
|||||||
return {
|
return {
|
||||||
...this.connector,
|
...this.connector,
|
||||||
configurationJson: {
|
configurationJson: {
|
||||||
master: configurationJson.master
|
master: configurationJson.master?.slaves
|
||||||
? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(configurationJson.master)
|
? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(configurationJson.master)
|
||||||
: {} as ModbusMasterConfig,
|
: { slaves: [] },
|
||||||
slave: configurationJson.slave
|
slave: configurationJson.slave
|
||||||
? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(configurationJson.slave as ModbusLegacySlave)
|
? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(configurationJson.slave as ModbusLegacySlave)
|
||||||
: {} as ModbusSlave,
|
: {} as ModbusSlave,
|
||||||
|
|||||||
@ -62,11 +62,6 @@ export abstract class ModbusBasicConfigDirective<BasicConfig>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override onBasicFormGroupChange(value: ModbusBasicConfig_v3_5_2): void {
|
|
||||||
super.onBasicFormGroupChange(value);
|
|
||||||
this.basicFormGroup.get('slave').updateValueAndValidity({ emitEvent: !!this.onChange });
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateSlaveEnabling(isEnabled: boolean): void {
|
private updateSlaveEnabling(isEnabled: boolean): void {
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
this.basicFormGroup.get('slave').enable({ emitEvent: false });
|
this.basicFormGroup.get('slave').enable({ emitEvent: false });
|
||||||
|
|||||||
@ -58,10 +58,10 @@ import {
|
|||||||
})
|
})
|
||||||
export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective<ModbusBasicConfig_v3_5_2> {
|
export class ModbusBasicConfigComponent extends ModbusBasicConfigDirective<ModbusBasicConfig_v3_5_2> {
|
||||||
|
|
||||||
protected override mapConfigToFormValue(config: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 {
|
protected override mapConfigToFormValue({ master, slave }: ModbusBasicConfig_v3_5_2): ModbusBasicConfig_v3_5_2 {
|
||||||
return {
|
return {
|
||||||
master: config.master ?? {} as ModbusMasterConfig,
|
master: master?.slaves ? master : { slaves: [] } as ModbusMasterConfig,
|
||||||
slave: config.slave ?? {} as ModbusSlave,
|
slave: slave ?? {} as ModbusSlave,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -62,7 +62,9 @@ export class ModbusLegacyBasicConfigComponent extends ModbusBasicConfigDirective
|
|||||||
|
|
||||||
protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig_v3_5_2 {
|
protected override mapConfigToFormValue(config: ModbusLegacyBasicConfig): ModbusBasicConfig_v3_5_2 {
|
||||||
return {
|
return {
|
||||||
master: config.master ? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(config.master) : {} as ModbusMasterConfig,
|
master: config.master?.slaves
|
||||||
|
? ModbusVersionMappingUtil.mapMasterToUpgradedVersion(config.master)
|
||||||
|
: { slaves: [] } as ModbusMasterConfig,
|
||||||
slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave) : {} as ModbusSlave,
|
slave: config.slave ? ModbusVersionMappingUtil.mapSlaveToUpgradedVersion(config.slave) : {} as ModbusSlave,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,9 +31,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<ng-template #tagName>
|
<ng-template #tagName>
|
||||||
<div class="tb-flex">
|
<div class="tb-flex">
|
||||||
<div class="title-container" tbTruncateWithTooltip>{{ 'gateway.key' | translate }}: {{ keyControl.get('tag').value }}</div>
|
<div class="title-container tb-flex">{{ 'gateway.key' | translate }}:
|
||||||
<div class="title-container">{{ 'gateway.address' | translate }}: {{ keyControl.get('address').value }}</div>
|
<span class="key-label" tbTruncateWithTooltip>{{ keyControl.get('tag').value }}</span>
|
||||||
<div class="title-container">{{ 'gateway.type' | translate }}: {{ keyControl.get('type').value }}</div>
|
</div>
|
||||||
|
<div class="title-container">{{ 'gateway.address' | translate }}:
|
||||||
|
<span class="key-label">{{ keyControl.get('address').value }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="title-container">{{ 'gateway.type' | translate }}:
|
||||||
|
<span class="key-label">{{ keyControl.get('type').value }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</mat-panel-title>
|
</mat-panel-title>
|
||||||
|
|||||||
@ -23,6 +23,10 @@
|
|||||||
width: 180px;
|
width: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.key-label {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
.key-panel {
|
.key-panel {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|||||||
@ -197,6 +197,16 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
|
||||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{ 'gateway.hints.modbus.word-order' | translate }}" translate>gateway.word-order</div>
|
||||||
|
<div class="tb-flex no-gap">
|
||||||
|
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
|
||||||
|
<mat-select formControlName="wordOrder">
|
||||||
|
<mat-option *ngFor="let order of modbusOrderType" [value]="order">{{ order }}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div *ngIf="protocolType !== ModbusProtocolType.Serial" class="tb-form-panel stroked tb-slide-toggle">
|
<div *ngIf="protocolType !== ModbusProtocolType.Serial" class="tb-form-panel stroked tb-slide-toggle">
|
||||||
<mat-expansion-panel class="tb-settings" [expanded]="showSecurityControl.value">
|
<mat-expansion-panel class="tb-settings" [expanded]="showSecurityControl.value">
|
||||||
<mat-expansion-panel-header fxLayout="row wrap">
|
<mat-expansion-panel-header fxLayout="row wrap">
|
||||||
|
|||||||
@ -113,6 +113,7 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat
|
|||||||
pollPeriod: [5000, [Validators.required]],
|
pollPeriod: [5000, [Validators.required]],
|
||||||
sendDataToThingsBoard: [false],
|
sendDataToThingsBoard: [false],
|
||||||
byteOrder:[ModbusOrderType.BIG],
|
byteOrder:[ModbusOrderType.BIG],
|
||||||
|
wordOrder: [ModbusOrderType.BIG],
|
||||||
security: [],
|
security: [],
|
||||||
identity: this.fb.group({
|
identity: this.fb.group({
|
||||||
vendorName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]],
|
vendorName: ['', [Validators.pattern(noLeadTrailSpacesRegex)]],
|
||||||
@ -243,6 +244,7 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat
|
|||||||
pollPeriod = 5000,
|
pollPeriod = 5000,
|
||||||
sendDataToThingsBoard = false,
|
sendDataToThingsBoard = false,
|
||||||
byteOrder = ModbusOrderType.BIG,
|
byteOrder = ModbusOrderType.BIG,
|
||||||
|
wordOrder = ModbusOrderType.BIG,
|
||||||
security = {},
|
security = {},
|
||||||
identity = {
|
identity = {
|
||||||
vendorName: '',
|
vendorName: '',
|
||||||
@ -266,6 +268,7 @@ export class ModbusSlaveConfigComponent implements ControlValueAccessor, Validat
|
|||||||
pollPeriod,
|
pollPeriod,
|
||||||
sendDataToThingsBoard: !!sendDataToThingsBoard,
|
sendDataToThingsBoard: !!sendDataToThingsBoard,
|
||||||
byteOrder,
|
byteOrder,
|
||||||
|
wordOrder,
|
||||||
security,
|
security,
|
||||||
identity,
|
identity,
|
||||||
values,
|
values,
|
||||||
|
|||||||
@ -260,7 +260,7 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="protocolType !== ModbusProtocolType.Serial" class="tb-form-row column-xs" fxLayoutAlign="space-between center">
|
<div class="tb-form-row column-xs" fxLayoutAlign="space-between center">
|
||||||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{ 'gateway.hints.modbus.word-order' | translate }}" translate>gateway.word-order</div>
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{ 'gateway.hints.modbus.word-order' | translate }}" translate>gateway.word-order</div>
|
||||||
<div class="tb-flex no-gap">
|
<div class="tb-flex no-gap">
|
||||||
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
|
<mat-form-field class="tb-flex no-gap" appearance="outline" subscriptSizing="dynamic">
|
||||||
|
|||||||
@ -101,7 +101,7 @@ export class ModbusSlaveDialogComponent extends DialogComponent<ModbusSlaveDialo
|
|||||||
helpBaseUrl + '/docs/iot-gateway/config/modbus/#section-master-description-and-configuration-parameters';
|
helpBaseUrl + '/docs/iot-gateway/config/modbus/#section-master-description-and-configuration-parameters';
|
||||||
|
|
||||||
private readonly serialSpecificControlKeys = ['serialPort', 'baudrate', 'stopbits', 'bytesize', 'parity', 'strict'];
|
private readonly serialSpecificControlKeys = ['serialPort', 'baudrate', 'stopbits', 'bytesize', 'parity', 'strict'];
|
||||||
private readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host', 'wordOrder'];
|
private readonly tcpUdpSpecificControlKeys = ['port', 'security', 'host'];
|
||||||
|
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
|||||||
@ -48,6 +48,8 @@
|
|||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
|
matTooltip="{{ 'action.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
#attributesButton
|
#attributesButton
|
||||||
(click)="manageKeys($event, attributesButton, ModbusValueKey.ATTRIBUTES, register)">
|
(click)="manageKeys($event, attributesButton, ModbusValueKey.ATTRIBUTES, register)">
|
||||||
<tb-icon matButtonIcon>edit</tb-icon>
|
<tb-icon matButtonIcon>edit</tb-icon>
|
||||||
@ -69,6 +71,8 @@
|
|||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
|
matTooltip="{{ 'action.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
#telemetryButton
|
#telemetryButton
|
||||||
(click)="manageKeys($event, telemetryButton, ModbusValueKey.TIMESERIES, register)">
|
(click)="manageKeys($event, telemetryButton, ModbusValueKey.TIMESERIES, register)">
|
||||||
<tb-icon matButtonIcon>edit</tb-icon>
|
<tb-icon matButtonIcon>edit</tb-icon>
|
||||||
@ -90,6 +94,8 @@
|
|||||||
mat-icon-button
|
mat-icon-button
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
matTooltip="{{ 'action.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
#attributesUpdatesButton
|
#attributesUpdatesButton
|
||||||
(click)="manageKeys($event, attributesUpdatesButton, ModbusValueKey.ATTRIBUTES_UPDATES, register)">
|
(click)="manageKeys($event, attributesUpdatesButton, ModbusValueKey.ATTRIBUTES_UPDATES, register)">
|
||||||
<tb-icon matButtonIcon>edit</tb-icon>
|
<tb-icon matButtonIcon>edit</tb-icon>
|
||||||
@ -111,6 +117,8 @@
|
|||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
|
matTooltip="{{ 'action.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
#rpcRequestsButton
|
#rpcRequestsButton
|
||||||
(click)="manageKeys($event, rpcRequestsButton, ModbusValueKey.RPC_REQUESTS, register)">
|
(click)="manageKeys($event, rpcRequestsButton, ModbusValueKey.RPC_REQUESTS, register)">
|
||||||
<tb-icon matButtonIcon>edit</tb-icon>
|
<tb-icon matButtonIcon>edit</tb-icon>
|
||||||
|
|||||||
@ -110,6 +110,8 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
matTooltip="{{ 'action.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
#attributesButton
|
#attributesButton
|
||||||
(click)="manageKeys($event, attributesButton, MappingKeysType.ATTRIBUTES)">
|
(click)="manageKeys($event, attributesButton, MappingKeysType.ATTRIBUTES)">
|
||||||
<tb-icon matButtonIcon>edit</tb-icon>
|
<tb-icon matButtonIcon>edit</tb-icon>
|
||||||
@ -130,6 +132,8 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
matTooltip="{{ 'action.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
#telemetryButton
|
#telemetryButton
|
||||||
(click)="manageKeys($event, telemetryButton, MappingKeysType.TIMESERIES)">
|
(click)="manageKeys($event, telemetryButton, MappingKeysType.TIMESERIES)">
|
||||||
<tb-icon matButtonIcon>edit</tb-icon>
|
<tb-icon matButtonIcon>edit</tb-icon>
|
||||||
@ -175,6 +179,8 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
matTooltip="{{ 'action.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
#keysButton
|
#keysButton
|
||||||
(click)="manageKeys($event, keysButton, MappingKeysType.CUSTOM)">
|
(click)="manageKeys($event, keysButton, MappingKeysType.CUSTOM)">
|
||||||
<tb-icon matButtonIcon>edit</tb-icon>
|
<tb-icon matButtonIcon>edit</tb-icon>
|
||||||
@ -666,6 +672,8 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
matTooltip="{{ 'action.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
#opcAttributesButton
|
#opcAttributesButton
|
||||||
(click)="manageKeys($event, opcAttributesButton, MappingKeysType.ATTRIBUTES)">
|
(click)="manageKeys($event, opcAttributesButton, MappingKeysType.ATTRIBUTES)">
|
||||||
<tb-icon matButtonIcon>edit</tb-icon>
|
<tb-icon matButtonIcon>edit</tb-icon>
|
||||||
@ -686,6 +694,8 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
matTooltip="{{ 'action.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
#opcTelemetryButton
|
#opcTelemetryButton
|
||||||
(click)="manageKeys($event, opcTelemetryButton, MappingKeysType.TIMESERIES)">
|
(click)="manageKeys($event, opcTelemetryButton, MappingKeysType.TIMESERIES)">
|
||||||
<tb-icon matButtonIcon>edit</tb-icon>
|
<tb-icon matButtonIcon>edit</tb-icon>
|
||||||
@ -706,6 +716,8 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
matTooltip="{{ 'action.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
#attributesUpdatesButton
|
#attributesUpdatesButton
|
||||||
(click)="manageKeys($event, attributesUpdatesButton, MappingKeysType.ATTRIBUTES_UPDATES)">
|
(click)="manageKeys($event, attributesUpdatesButton, MappingKeysType.ATTRIBUTES_UPDATES)">
|
||||||
<tb-icon matButtonIcon>edit</tb-icon>
|
<tb-icon matButtonIcon>edit</tb-icon>
|
||||||
@ -726,6 +738,8 @@
|
|||||||
<button type="button"
|
<button type="button"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
color="primary"
|
color="primary"
|
||||||
|
matTooltip="{{ 'action.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
#rpcMethodsButton
|
#rpcMethodsButton
|
||||||
(click)="manageKeys($event, rpcMethodsButton, MappingKeysType.RPC_METHODS)">
|
(click)="manageKeys($event, rpcMethodsButton, MappingKeysType.RPC_METHODS)">
|
||||||
<tb-icon matButtonIcon>edit</tb-icon>
|
<tb-icon matButtonIcon>edit</tb-icon>
|
||||||
|
|||||||
@ -778,7 +778,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
|
|||||||
JSON.parse(clientConnectorData.value) : clientConnectorData.value;
|
JSON.parse(clientConnectorData.value) : clientConnectorData.value;
|
||||||
|
|
||||||
if (this.isConnectorSynced(clientConnectorData) && clientConnectorData.value.configurationJson) {
|
if (this.isConnectorSynced(clientConnectorData) && clientConnectorData.value.configurationJson) {
|
||||||
this.setFormValue(clientConnectorData.value);
|
this.setFormValue({...clientConnectorData.value, mode: this.connectorForm.get('mode').value ?? clientConnectorData.value.mode});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1163,14 +1163,15 @@ export interface ModbusSlave {
|
|||||||
pollPeriod: number;
|
pollPeriod: number;
|
||||||
sendDataToThingsBoard: boolean;
|
sendDataToThingsBoard: boolean;
|
||||||
byteOrder: ModbusOrderType;
|
byteOrder: ModbusOrderType;
|
||||||
|
wordOrder: ModbusOrderType;
|
||||||
identity: ModbusIdentity;
|
identity: ModbusIdentity;
|
||||||
values: ModbusValuesState;
|
values?: ModbusValuesState;
|
||||||
port: string | number;
|
port: string | number;
|
||||||
security: ModbusSecurity;
|
security: ModbusSecurity;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModbusLegacySlave extends Omit<ModbusSlave, 'values'> {
|
export interface ModbusLegacySlave extends Omit<ModbusSlave, 'values'> {
|
||||||
values: ModbusLegacyRegisterValues;
|
values?: ModbusLegacyRegisterValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModbusValuesState = ModbusRegisterValues | ModbusValues;
|
export type ModbusValuesState = ModbusRegisterValues | ModbusValues;
|
||||||
|
|||||||
@ -38,6 +38,9 @@ export class ModbusVersionMappingUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static mapSlaveToDowngradedVersion(slave: ModbusSlave): ModbusLegacySlave {
|
static mapSlaveToDowngradedVersion(slave: ModbusSlave): ModbusLegacySlave {
|
||||||
|
if (!slave?.values) {
|
||||||
|
return slave as Omit<ModbusLegacySlave, 'values'>;
|
||||||
|
}
|
||||||
const values = Object.keys(slave.values).reduce((acc, valueKey) => {
|
const values = Object.keys(slave.values).reduce((acc, valueKey) => {
|
||||||
acc = {
|
acc = {
|
||||||
...acc,
|
...acc,
|
||||||
@ -54,6 +57,9 @@ export class ModbusVersionMappingUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static mapSlaveToUpgradedVersion(slave: ModbusLegacySlave): ModbusSlave {
|
static mapSlaveToUpgradedVersion(slave: ModbusLegacySlave): ModbusSlave {
|
||||||
|
if (!slave?.values) {
|
||||||
|
return slave as Omit<ModbusSlave, 'values'>;
|
||||||
|
}
|
||||||
const values = Object.keys(slave.values).reduce((acc, valueKey) => {
|
const values = Object.keys(slave.values).reduce((acc, valueKey) => {
|
||||||
acc = {
|
acc = {
|
||||||
...acc,
|
...acc,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user