Gateway fixes and added support for newer Gateway versions
This commit is contained in:
parent
97243cad43
commit
38b0e4fb02
@ -15,15 +15,17 @@
|
||||
///
|
||||
|
||||
import { GatewayConnector, GatewayVersion } from '@home/components/widget/lib/gateway/gateway-widget.models';
|
||||
import { isNumber, isString } from '@core/utils';
|
||||
import {
|
||||
GatewayConnectorVersionMappingUtil
|
||||
} from '@home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util';
|
||||
|
||||
export abstract class GatewayConnectorVersionProcessor<BasicConfig> {
|
||||
gatewayVersion: number;
|
||||
configVersion: number;
|
||||
|
||||
protected constructor(protected gatewayVersionIn: string | number, protected connector: GatewayConnector<BasicConfig>) {
|
||||
this.gatewayVersion = this.parseVersion(this.gatewayVersionIn);
|
||||
this.configVersion = this.parseVersion(this.connector.configVersion);
|
||||
this.gatewayVersion = GatewayConnectorVersionMappingUtil.parseVersion(this.gatewayVersionIn);
|
||||
this.configVersion = GatewayConnectorVersionMappingUtil.parseVersion(this.connector.configVersion);
|
||||
}
|
||||
|
||||
getProcessedByVersion(): GatewayConnector<BasicConfig> {
|
||||
@ -53,19 +55,13 @@ export abstract class GatewayConnectorVersionProcessor<BasicConfig> {
|
||||
}
|
||||
|
||||
private isVersionUpgradeNeeded(): boolean {
|
||||
return this.gatewayVersionIn === GatewayVersion.Current && (!this.configVersion || this.configVersion < this.gatewayVersion);
|
||||
return this.gatewayVersion >= GatewayConnectorVersionMappingUtil.parseVersion(GatewayVersion.Current)
|
||||
&& (!this.configVersion || this.configVersion < this.gatewayVersion);
|
||||
}
|
||||
|
||||
private isVersionDowngradeNeeded(): boolean {
|
||||
return this.configVersion && this.connector.configVersion === GatewayVersion.Current && (this.configVersion > this.gatewayVersion);
|
||||
}
|
||||
|
||||
private parseVersion(version: string | number): number {
|
||||
if (isNumber(version)) {
|
||||
return version as number;
|
||||
}
|
||||
|
||||
return isString(version) ? parseFloat((version as string).replace(/\./g, '').slice(0, 3)) / 100 : 0;
|
||||
return this.configVersion && this.configVersion >= GatewayConnectorVersionMappingUtil.parseVersion(GatewayVersion.Current)
|
||||
&& (this.configVersion > this.gatewayVersion);
|
||||
}
|
||||
|
||||
protected abstract getDowngradedVersion(): GatewayConnector<BasicConfig>;
|
||||
|
||||
@ -126,6 +126,7 @@ export class SecurityConfigComponent implements ControlValueAccessor, OnInit, On
|
||||
if (!securityInfo.type) {
|
||||
securityInfo.type = SecurityType.ANONYMOUS;
|
||||
}
|
||||
this.updateValidators(securityInfo.type);
|
||||
this.securityFormGroup.reset(securityInfo, {emitEvent: false});
|
||||
}
|
||||
this.cdr.markForCheck();
|
||||
|
||||
@ -37,6 +37,7 @@ import { Observable, Subject } from 'rxjs';
|
||||
import { ResourcesService } from '@core/services/resources.service';
|
||||
import { takeUntil, tap } from 'rxjs/operators';
|
||||
import { helpBaseUrl } from '@shared/models/constants';
|
||||
import { LatestVersionConfigPipe } from '@home/components/widget/lib/gateway/pipes/latest-version-config.pipe';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-add-connector-dialog',
|
||||
@ -63,6 +64,7 @@ export class AddConnectorDialogComponent
|
||||
@Inject(MAT_DIALOG_DATA) public data: AddConnectorConfigData,
|
||||
public dialogRef: MatDialogRef<AddConnectorDialogComponent, CreatedConnectorConfigData>,
|
||||
private fb: FormBuilder,
|
||||
private isLatestVersionConfig: LatestVersionConfigPipe,
|
||||
private resourcesService: ResourcesService) {
|
||||
super(store, router, dialogRef);
|
||||
this.connectorForm = this.fb.group({
|
||||
@ -103,9 +105,9 @@ export class AddConnectorDialogComponent
|
||||
if (gatewayVersion) {
|
||||
value.configVersion = gatewayVersion;
|
||||
}
|
||||
value.configurationJson = (gatewayVersion === GatewayVersion.Current
|
||||
? defaultConfig[this.data.gatewayVersion]
|
||||
: defaultConfig.legacy)
|
||||
value.configurationJson = (this.isLatestVersionConfig.transform(gatewayVersion)
|
||||
? defaultConfig[GatewayVersion.Current]
|
||||
: defaultConfig[GatewayVersion.Legacy])
|
||||
?? defaultConfig;
|
||||
if (this.connectorForm.valid) {
|
||||
this.dialogRef.close(value);
|
||||
|
||||
@ -178,7 +178,7 @@
|
||||
<ng-container [ngSwitch]="initialConnector.type">
|
||||
<ng-container *ngSwitchCase="ConnectorType.MQTT">
|
||||
<tb-mqtt-basic-config
|
||||
*ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy"
|
||||
*ngIf="connectorForm.get('configVersion').value | isLatestVersionConfig else legacy"
|
||||
formControlName="basicConfig"
|
||||
[generalTabContent]="generalTabContent"
|
||||
(initialized)="basicConfigInitSubject.next()"
|
||||
@ -193,7 +193,7 @@
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="ConnectorType.OPCUA">
|
||||
<tb-opc-ua-basic-config
|
||||
*ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy"
|
||||
*ngIf="connectorForm.get('configVersion').value | isLatestVersionConfig else legacy"
|
||||
formControlName="basicConfig"
|
||||
[generalTabContent]="generalTabContent"
|
||||
(initialized)="basicConfigInitSubject.next()"
|
||||
@ -208,7 +208,7 @@
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="ConnectorType.MODBUS">
|
||||
<tb-modbus-basic-config
|
||||
*ngIf="connectorForm.get('configVersion').value === GatewayVersion.Current else legacy"
|
||||
*ngIf="connectorForm.get('configVersion').value | isLatestVersionConfig else legacy"
|
||||
formControlName="basicConfig"
|
||||
[generalTabContent]="generalTabContent"
|
||||
(initialized)="basicConfigInitSubject.next()"
|
||||
@ -316,7 +316,7 @@
|
||||
</div>
|
||||
<tb-report-strategy
|
||||
[defaultValue]="ReportStrategyDefaultValue.Connector"
|
||||
*ngIf="connectorForm.get('type').value === ConnectorType.MODBUS && connectorForm.get('configVersion').value === GatewayVersion.Current"
|
||||
*ngIf="connectorForm.get('type').value === ConnectorType.MODBUS && (connectorForm.get('configVersion').value | isLatestVersionConfig)"
|
||||
formControlName="reportStrategy"
|
||||
/>
|
||||
</section>
|
||||
|
||||
@ -59,7 +59,6 @@ import {
|
||||
GatewayConnectorDefaultTypesTranslatesMap,
|
||||
GatewayLogLevel,
|
||||
noLeadTrailSpacesRegex,
|
||||
GatewayVersion,
|
||||
ReportStrategyDefaultValue,
|
||||
ReportStrategyType,
|
||||
} from './gateway-widget.models';
|
||||
@ -71,6 +70,7 @@ import { PageData } from '@shared/models/page/page-data';
|
||||
import {
|
||||
GatewayConnectorVersionMappingUtil
|
||||
} from '@home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util';
|
||||
import { LatestVersionConfigPipe } from '@home/components/widget/lib/gateway/pipes/latest-version-config.pipe';
|
||||
|
||||
export class ForceErrorStateMatcher implements ErrorStateMatcher {
|
||||
isErrorState(control: FormControl | null): boolean {
|
||||
@ -104,7 +104,6 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
|
||||
readonly displayedColumns = ['enabled', 'key', 'type', 'syncStatus', 'errors', 'actions'];
|
||||
readonly GatewayConnectorTypesTranslatesMap = GatewayConnectorDefaultTypesTranslatesMap;
|
||||
readonly ConnectorConfigurationModes = ConfigurationModes;
|
||||
readonly GatewayVersion = GatewayVersion;
|
||||
readonly ReportStrategyDefaultValue = ReportStrategyDefaultValue;
|
||||
|
||||
pageLink: PageLink;
|
||||
@ -149,6 +148,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
|
||||
private telemetryWsService: TelemetryWebsocketService,
|
||||
private zone: NgZone,
|
||||
private utils: UtilsService,
|
||||
private isLatestVersionConfig: LatestVersionConfigPipe,
|
||||
private cd: ChangeDetectorRef) {
|
||||
super(store);
|
||||
|
||||
@ -255,7 +255,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
|
||||
delete value.class;
|
||||
}
|
||||
|
||||
if (value.type === ConnectorType.MODBUS && value.configVersion === GatewayVersion.Current) {
|
||||
if (value.type === ConnectorType.MODBUS && this.isLatestVersionConfig.transform(value.configVersion)) {
|
||||
if (!value.reportStrategy) {
|
||||
value.reportStrategy = {
|
||||
type: ReportStrategyType.OnReportPeriod,
|
||||
@ -508,6 +508,9 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
|
||||
if (!connector.configurationJson) {
|
||||
connector.configurationJson = {} as ConnectorBaseConfig;
|
||||
}
|
||||
if (this.gatewayVersion && !connector.configVersion) {
|
||||
connector.configVersion = this.gatewayVersion;
|
||||
}
|
||||
connector.basicConfig = connector.configurationJson;
|
||||
this.initialConnector = connector;
|
||||
|
||||
@ -517,7 +520,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
|
||||
|
||||
this.saveConnector(this.getUpdatedConnectorData(connector));
|
||||
|
||||
if (!previousType || previousType === connector.type || !this.allowBasicConfig.has(connector.type)) {
|
||||
if (previousType === connector.type || !this.allowBasicConfig.has(connector.type)) {
|
||||
this.patchBasicConfigConnector(connector);
|
||||
} else {
|
||||
this.basicConfigInitSubject.pipe(take(1)).subscribe(() => {
|
||||
@ -527,24 +530,26 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
|
||||
}
|
||||
|
||||
private setInitialConnectorValues(connector: GatewayConnector): void {
|
||||
const {basicConfig, mode, ...initialConnector} = connector;
|
||||
this.toggleReportStrategy(connector.type);
|
||||
this.connectorForm.get('mode').setValue(this.allowBasicConfig.has(connector.type)
|
||||
? connector.mode ?? ConfigurationModes.BASIC
|
||||
: null, {emitEvent: false}
|
||||
);
|
||||
this.connectorForm.get('configVersion').setValue(connector.configVersion, {emitEvent: false});
|
||||
this.connectorForm.get('type').setValue(connector.type, {emitEvent: false});
|
||||
this.connectorForm.patchValue(initialConnector, {emitEvent: false});
|
||||
}
|
||||
|
||||
private openAddConnectorDialog(): Observable<GatewayConnector> {
|
||||
return this.dialog.open<AddConnectorDialogComponent, AddConnectorConfigData>(AddConnectorDialogComponent, {
|
||||
disableClose: true,
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
||||
data: {
|
||||
dataSourceData: this.dataSource.data,
|
||||
gatewayVersion: this.gatewayVersion,
|
||||
}
|
||||
}).afterClosed();
|
||||
return this.ctx.ngZone.run(() =>
|
||||
this.dialog.open<AddConnectorDialogComponent, AddConnectorConfigData>(AddConnectorDialogComponent, {
|
||||
disableClose: true,
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
|
||||
data: {
|
||||
dataSourceData: this.dataSource.data,
|
||||
gatewayVersion: this.gatewayVersion,
|
||||
}
|
||||
}).afterClosed()
|
||||
);
|
||||
}
|
||||
|
||||
uniqNameRequired(): ValidatorFn {
|
||||
@ -650,13 +655,8 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
|
||||
private observeModeChange(): void {
|
||||
this.connectorForm.get('mode').valueChanges
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((mode) => {
|
||||
.subscribe(() => {
|
||||
this.connectorForm.get('mode').markAsPristine();
|
||||
if (mode === ConfigurationModes.BASIC) {
|
||||
this.basicConfigInitSubject.pipe(take(1)).subscribe(() => {
|
||||
this.patchBasicConfigConnector({...this.initialConnector, mode: ConfigurationModes.BASIC});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -827,12 +827,17 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
|
||||
...connector,
|
||||
}, this.gatewayVersion);
|
||||
|
||||
if (this.gatewayVersion && !connectorState.configVersion) {
|
||||
connectorState.configVersion = this.gatewayVersion;
|
||||
}
|
||||
|
||||
connectorState.basicConfig = connectorState.configurationJson;
|
||||
this.initialConnector = connectorState;
|
||||
this.updateConnector(connectorState);
|
||||
}
|
||||
|
||||
private updateConnector(connector: GatewayConnector): void {
|
||||
this.jsonConfigSub?.unsubscribe();
|
||||
switch (connector.type) {
|
||||
case ConnectorType.MQTT:
|
||||
case ConnectorType.OPCUA:
|
||||
@ -842,18 +847,21 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
|
||||
default:
|
||||
this.connectorForm.patchValue({...connector, mode: null});
|
||||
this.connectorForm.markAsPristine();
|
||||
this.createJsonConfigWatcher();
|
||||
}
|
||||
this.createJsonConfigWatcher();
|
||||
}
|
||||
|
||||
private updateBasicConfigConnector(connector: GatewayConnector): void {
|
||||
this.basicConfigSub?.unsubscribe();
|
||||
const previousType = this.connectorForm.get('type').value;
|
||||
this.setInitialConnectorValues(connector);
|
||||
if ((!connector.mode || connector.mode === ConfigurationModes.BASIC) && this.connectorForm.get('type').value !== connector.type) {
|
||||
|
||||
if (previousType === connector.type || !this.allowBasicConfig.has(connector.type)) {
|
||||
this.patchBasicConfigConnector(connector);
|
||||
} else {
|
||||
this.basicConfigInitSubject.asObservable().pipe(take(1)).subscribe(() => {
|
||||
this.patchBasicConfigConnector(connector);
|
||||
});
|
||||
} else {
|
||||
this.patchBasicConfigConnector(connector);
|
||||
}
|
||||
}
|
||||
|
||||
@ -861,6 +869,7 @@ export class GatewayConnectorComponent extends PageComponent implements AfterVie
|
||||
this.connectorForm.patchValue(connector, {emitEvent: false});
|
||||
this.connectorForm.markAsPristine();
|
||||
this.createBasicConfigWatcher();
|
||||
this.createJsonConfigWatcher();
|
||||
}
|
||||
|
||||
private toggleReportStrategy(type: ConnectorType): void {
|
||||
|
||||
@ -33,6 +33,8 @@ export class GatewayHelpLinkPipe implements PipeTransform {
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (field === 'attributes' || field === 'timeseries') {
|
||||
return 'widget/lib/gateway/attributes_timeseries_expressions_fn';
|
||||
}
|
||||
return 'widget/lib/gateway/expressions_fn';
|
||||
}
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
///
|
||||
/// Copyright © 2016-2024 The Thingsboard Authors
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { GatewayVersion } from '@home/components/widget/lib/gateway/gateway-widget.models';
|
||||
import {
|
||||
GatewayConnectorVersionMappingUtil
|
||||
} from '@home/components/widget/lib/gateway/utils/gateway-connector-version-mapping.util';
|
||||
|
||||
@Pipe({
|
||||
name: 'isLatestVersionConfig',
|
||||
standalone: true,
|
||||
})
|
||||
export class LatestVersionConfigPipe implements PipeTransform {
|
||||
transform(configVersion: number | string): boolean {
|
||||
return GatewayConnectorVersionMappingUtil.parseVersion(configVersion)
|
||||
>= GatewayConnectorVersionMappingUtil.parseVersion(GatewayVersion.Current);
|
||||
}
|
||||
}
|
||||
@ -24,6 +24,7 @@ import {
|
||||
import { MqttVersionProcessor } from '@home/components/widget/lib/gateway/abstract/mqtt-version-processor.abstract';
|
||||
import { OpcVersionProcessor } from '@home/components/widget/lib/gateway/abstract/opc-version-processor.abstract';
|
||||
import { ModbusVersionProcessor } from '@home/components/widget/lib/gateway/abstract/modbus-version-processor.abstract';
|
||||
import { isNumber, isString } from '@core/utils';
|
||||
|
||||
export abstract class GatewayConnectorVersionMappingUtil {
|
||||
|
||||
@ -39,4 +40,12 @@ export abstract class GatewayConnectorVersionMappingUtil {
|
||||
return connector;
|
||||
}
|
||||
}
|
||||
|
||||
static parseVersion(version: string | number): number {
|
||||
if (isNumber(version)) {
|
||||
return version as number;
|
||||
}
|
||||
|
||||
return isString(version) ? parseFloat((version as string).replace(/\./g, '').slice(0, 3)) / 100 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ export class OpcVersionMappingUtil {
|
||||
static mapMappingToUpgradedVersion(mapping: LegacyDeviceConnectorMapping[]): DeviceConnectorMapping[] {
|
||||
return mapping.map((legacyMapping: LegacyDeviceConnectorMapping) => ({
|
||||
...legacyMapping,
|
||||
deviceNodeSource: this.getTypeSourceByValue(legacyMapping.deviceNodePattern),
|
||||
deviceNodeSource: this.getDeviceNodeSourceByValue(legacyMapping.deviceNodePattern),
|
||||
deviceInfo: {
|
||||
deviceNameExpression: legacyMapping.deviceNamePattern,
|
||||
deviceNameExpressionSource: this.getTypeSourceByValue(legacyMapping.deviceNamePattern),
|
||||
@ -122,6 +122,14 @@ export class OpcVersionMappingUtil {
|
||||
return OPCUaSourceType.CONST;
|
||||
}
|
||||
|
||||
private static getDeviceNodeSourceByValue(value: string): OPCUaSourceType {
|
||||
if (value.includes('${')) {
|
||||
return OPCUaSourceType.IDENTIFIER;
|
||||
} else {
|
||||
return OPCUaSourceType.PATH;
|
||||
}
|
||||
}
|
||||
|
||||
private static getArgumentType(arg: unknown): string {
|
||||
switch (typeof arg) {
|
||||
case 'boolean':
|
||||
|
||||
@ -170,6 +170,7 @@ import { RpcTemplateArrayViewPipe } from '@home/components/widget/lib/gateway/pi
|
||||
import {
|
||||
ReportStrategyComponent
|
||||
} from '@home/components/widget/lib/gateway/connectors-configuration/report-strategy/report-strategy.component';
|
||||
import { LatestVersionConfigPipe } from '@home/components/widget/lib/gateway/pipes/latest-version-config.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -268,6 +269,7 @@ import {
|
||||
ModbusRpcParametersComponent,
|
||||
RpcTemplateArrayViewPipe,
|
||||
ReportStrategyComponent,
|
||||
LatestVersionConfigPipe,
|
||||
],
|
||||
exports: [
|
||||
EntitiesTableWidgetComponent,
|
||||
@ -337,7 +339,8 @@ import {
|
||||
ScadaSymbolWidgetComponent
|
||||
],
|
||||
providers: [
|
||||
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule}
|
||||
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule},
|
||||
{provide: LatestVersionConfigPipe}
|
||||
]
|
||||
})
|
||||
export class WidgetComponentsModule {
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
### Expressions
|
||||
#### JSON Path:
|
||||
|
||||
The expression field is used to extract data from the MQTT message. There are various available options for different parts of the messages:
|
||||
|
||||
- The JSONPath format can be used to extract data from the message body.
|
||||
|
||||
- The regular expression format can be used to extract data from the topic where the message will arrive.
|
||||
|
||||
- Slices can only be used in the expression fields of bytes converters.
|
||||
|
||||
JSONPath expressions specify the items within a JSON structure (which could be an object, array, or nested combination of both) that you want to access. These expressions can select elements from JSON data on specific criteria. Here's a basic overview of how JSONPath expressions are structured:
|
||||
|
||||
- `$`: The root element of the JSON document;
|
||||
|
||||
- `.`: Child operator used to select child elements. For example, $.store.book ;
|
||||
|
||||
- `[]`: Child operator used to select child elements. $['store']['book'] accesses the book array within a store object;
|
||||
|
||||
##### Examples:
|
||||
|
||||
For example, if we want to extract the device name from the following message, we can use the expression below:
|
||||
|
||||
MQTT message:
|
||||
|
||||
```
|
||||
{
|
||||
"sensorModelInfo": {
|
||||
"sensorName": "AM-123",
|
||||
"sensorType": "myDeviceType"
|
||||
},
|
||||
"data": {
|
||||
"temp": 12.2,
|
||||
"hum": 56,
|
||||
"status": "ok"
|
||||
}
|
||||
}
|
||||
{:copy-code}
|
||||
```
|
||||
|
||||
Expression:
|
||||
|
||||
`${sensorModelInfo.sensorName}`
|
||||
|
||||
Converted data:
|
||||
|
||||
`AM-123`
|
||||
|
||||
If we want to extract all data from the message above, we can use the following expression:
|
||||
|
||||
`${data}`
|
||||
|
||||
Converted data:
|
||||
|
||||
`{"temp": 12.2, "hum": 56, "status": "ok"}`
|
||||
|
||||
Or if we want to extract specific data (for example “temperature”), you can use the following expression:
|
||||
|
||||
`${data.temp}`
|
||||
|
||||
And as a converted data we will get:
|
||||
|
||||
`12.2`
|
||||
|
||||
<br/>
|
||||
@ -20,13 +20,21 @@
|
||||
"connectAttemptTimeMs": 5000,
|
||||
"connectAttemptCount": 5,
|
||||
"waitAfterFailedAttemptsMs": 300000,
|
||||
"reportStrategy": {
|
||||
"type": "ON_REPORT_PERIOD",
|
||||
"reportPeriod": 30000
|
||||
},
|
||||
"attributes": [
|
||||
{
|
||||
"tag": "string_read",
|
||||
"type": "string",
|
||||
"functionCode": 4,
|
||||
"objectsCount": 4,
|
||||
"address": 1
|
||||
"address": 1,
|
||||
"reportStrategy": {
|
||||
"type": "ON_REPORT_PERIOD",
|
||||
"reportPeriod": 15000
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "bits_read",
|
||||
@ -86,7 +94,11 @@
|
||||
"type": "8uint",
|
||||
"functionCode": 4,
|
||||
"objectsCount": 1,
|
||||
"address": 17
|
||||
"address": 17,
|
||||
"reportStrategy": {
|
||||
"type": "ON_REPORT_PERIOD",
|
||||
"reportPeriod": 15000
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "16uint_read",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user