23 changed files with 1341 additions and 502 deletions
@ -1,191 +0,0 @@ |
|||
///
|
|||
/// 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 { ChangeDetectionStrategy, Component, forwardRef, Input, OnDestroy, TemplateRef } from '@angular/core'; |
|||
import { |
|||
ControlValueAccessor, |
|||
FormBuilder, |
|||
FormGroup, |
|||
NG_VALIDATORS, |
|||
NG_VALUE_ACCESSOR, |
|||
ValidationErrors, |
|||
Validator, |
|||
} from '@angular/forms'; |
|||
import { |
|||
MappingType, |
|||
MQTTBasicConfig, |
|||
RequestMappingData, |
|||
RequestType, |
|||
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
|||
import { SharedModule } from '@shared/shared.module'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { takeUntil } from 'rxjs/operators'; |
|||
import { Subject } from 'rxjs'; |
|||
import { isObject } from 'lodash'; |
|||
import { |
|||
SecurityConfigComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; |
|||
import { |
|||
WorkersConfigControlComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; |
|||
import { |
|||
BrokerConfigControlComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/broker-config-control/broker-config-control.component'; |
|||
import { |
|||
MappingTableComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; |
|||
import { isDefinedAndNotNull } from '@core/utils'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-mqtt-basic-config', |
|||
templateUrl: './mqtt-basic-config.component.html', |
|||
changeDetection: ChangeDetectionStrategy.OnPush, |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => MqttBasicConfigComponent), |
|||
multi: true |
|||
}, |
|||
{ |
|||
provide: NG_VALIDATORS, |
|||
useExisting: forwardRef(() => MqttBasicConfigComponent), |
|||
multi: true |
|||
} |
|||
], |
|||
standalone: true, |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
SecurityConfigComponent, |
|||
WorkersConfigControlComponent, |
|||
BrokerConfigControlComponent, |
|||
MappingTableComponent, |
|||
], |
|||
styleUrls: ['./mqtt-basic-config.component.scss'] |
|||
}) |
|||
|
|||
export class MqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { |
|||
|
|||
@Input() |
|||
generalTabContent: TemplateRef<any>; |
|||
|
|||
mappingTypes = MappingType; |
|||
basicFormGroup: FormGroup; |
|||
|
|||
private onChange: (value: MQTTBasicConfig) => void; |
|||
private onTouched: () => void; |
|||
|
|||
private destroy$ = new Subject<void>(); |
|||
|
|||
constructor(private fb: FormBuilder) { |
|||
this.basicFormGroup = this.fb.group({ |
|||
dataMapping: [], |
|||
requestsMapping: [], |
|||
broker: [], |
|||
workers: [], |
|||
}); |
|||
|
|||
this.basicFormGroup.valueChanges |
|||
.pipe(takeUntil(this.destroy$)) |
|||
.subscribe(value => { |
|||
this.onChange(this.getMappedMQTTConfig(value)); |
|||
this.onTouched(); |
|||
}); |
|||
} |
|||
|
|||
ngOnDestroy(): void { |
|||
this.destroy$.next(); |
|||
this.destroy$.complete(); |
|||
} |
|||
|
|||
registerOnChange(fn: (value: MQTTBasicConfig) => void): void { |
|||
this.onChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: () => void): void { |
|||
this.onTouched = fn; |
|||
} |
|||
|
|||
writeValue(basicConfig: MQTTBasicConfig): void { |
|||
const { broker, dataMapping = [], requestsMapping } = basicConfig; |
|||
const editedBase = { |
|||
workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { |
|||
maxNumberOfWorkers: broker.maxNumberOfWorkers, |
|||
maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, |
|||
} : {}, |
|||
dataMapping: dataMapping || [], |
|||
broker: broker || {}, |
|||
requestsMapping: Array.isArray(requestsMapping) |
|||
? requestsMapping |
|||
: this.getRequestDataArray(requestsMapping), |
|||
}; |
|||
|
|||
this.basicFormGroup.setValue(editedBase, {emitEvent: false}); |
|||
} |
|||
|
|||
private getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig { |
|||
let { broker, workers, dataMapping, requestsMapping } = basicConfig || {}; |
|||
|
|||
if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { |
|||
broker = { |
|||
...broker, |
|||
...workers, |
|||
}; |
|||
} |
|||
|
|||
if ((requestsMapping as RequestMappingData[])?.length) { |
|||
requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingData[]); |
|||
} |
|||
|
|||
return { broker, workers, dataMapping, requestsMapping }; |
|||
} |
|||
|
|||
validate(): ValidationErrors | null { |
|||
return this.basicFormGroup.valid ? null : { |
|||
basicFormGroup: {valid: false} |
|||
}; |
|||
} |
|||
|
|||
private getRequestDataArray(value: Record<RequestType, RequestMappingData[]>): RequestMappingData[] { |
|||
const mappingConfigs = []; |
|||
|
|||
if (isObject(value)) { |
|||
Object.keys(value).forEach((configKey: string) => { |
|||
for (const mapping of value[configKey]) { |
|||
mappingConfigs.push({ |
|||
requestType: configKey, |
|||
requestValue: mapping |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
return mappingConfigs; |
|||
} |
|||
|
|||
private getRequestDataObject(array: RequestMappingData[]): Record<RequestType, RequestMappingData[]> { |
|||
return array.reduce((result, { requestType, requestValue }) => { |
|||
result[requestType].push(requestValue); |
|||
return result; |
|||
}, { |
|||
connectRequests: [], |
|||
disconnectRequests: [], |
|||
attributeRequests: [], |
|||
attributeUpdates: [], |
|||
serverSideRpc: [], |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
///
|
|||
/// 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 { ControlValueAccessor, FormBuilder, FormGroup, ValidationErrors, Validator } from '@angular/forms'; |
|||
import { Subject } from 'rxjs'; |
|||
import { takeUntil } from 'rxjs/operators'; |
|||
import { isObject } from 'lodash'; |
|||
import { |
|||
MappingType, |
|||
MQTTBasicConfig, |
|||
MQTTLegacyBasicConfig, |
|||
RequestMappingData, |
|||
RequestMappingValue, |
|||
RequestType |
|||
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
|||
import { Directive, OnDestroy } from '@angular/core'; |
|||
|
|||
@Directive() |
|||
export abstract class AbstractMqttBasicConfigComponent implements ControlValueAccessor, Validator, OnDestroy { |
|||
|
|||
basicFormGroup: FormGroup; |
|||
mappingTypes = MappingType; |
|||
private destroy$ = new Subject<void>(); |
|||
private onChange: (value: MQTTBasicConfig | MQTTLegacyBasicConfig) => void; |
|||
private onTouched: () => void; |
|||
|
|||
constructor(protected fb: FormBuilder) { |
|||
this.basicFormGroup = this.fb.group({ |
|||
mapping: [], |
|||
requestsMapping: [], |
|||
broker: [], |
|||
workers: [], |
|||
}); |
|||
|
|||
this.basicFormGroup.valueChanges |
|||
.pipe(takeUntil(this.destroy$)) |
|||
.subscribe(value => { |
|||
this.onChange(this.getMappedMQTTConfig(value)); |
|||
this.onTouched(); |
|||
}); |
|||
} |
|||
|
|||
ngOnDestroy(): void { |
|||
this.destroy$.next(); |
|||
this.destroy$.complete(); |
|||
} |
|||
|
|||
registerOnChange(fn: (value: MQTTBasicConfig | MQTTLegacyBasicConfig) => void): void { |
|||
this.onChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: () => void): void { |
|||
this.onTouched = fn; |
|||
} |
|||
|
|||
validate(): ValidationErrors | null { |
|||
return this.basicFormGroup.valid ? null : { |
|||
basicFormGroup: {valid: false} |
|||
}; |
|||
} |
|||
|
|||
protected getRequestDataArray(value: Record<RequestType, RequestMappingData[]>): RequestMappingData[] { |
|||
const mappingConfigs = []; |
|||
|
|||
if (isObject(value)) { |
|||
Object.keys(value).forEach((configKey: string) => { |
|||
for (const mapping of value[configKey]) { |
|||
mappingConfigs.push({ |
|||
requestType: configKey, |
|||
requestValue: mapping |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
return mappingConfigs; |
|||
} |
|||
|
|||
protected getRequestDataObject(array: RequestMappingValue[]): Record<RequestType, RequestMappingValue[]> { |
|||
return array.reduce((result, { requestType, requestValue }) => { |
|||
result[requestType].push(requestValue); |
|||
return result; |
|||
}, { |
|||
connectRequests: [], |
|||
disconnectRequests: [], |
|||
attributeRequests: [], |
|||
attributeUpdates: [], |
|||
serverSideRpc: [], |
|||
}); |
|||
} |
|||
|
|||
abstract writeValue(basicConfig: MQTTBasicConfig | MQTTLegacyBasicConfig): void; |
|||
protected abstract getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig | MQTTLegacyBasicConfig; |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
///
|
|||
/// 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 { Component, forwardRef, Input, TemplateRef, ChangeDetectionStrategy } from '@angular/core'; |
|||
import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; |
|||
import { |
|||
MQTTBasicConfig, |
|||
RequestMappingData, |
|||
RequestMappingValue, |
|||
RequestType |
|||
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
|||
import { |
|||
AbstractMqttBasicConfigComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; |
|||
import { isDefinedAndNotNull } from '@core/utils'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { SharedModule } from '@shared/shared.module'; |
|||
import { |
|||
SecurityConfigComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; |
|||
import { |
|||
WorkersConfigControlComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; |
|||
import { |
|||
BrokerConfigControlComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; |
|||
import { |
|||
MappingTableComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-mqtt-basic-config', |
|||
templateUrl: './mqtt-basic-config.component.html', |
|||
changeDetection: ChangeDetectionStrategy.OnPush, |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => MqttBasicConfigComponent), |
|||
multi: true |
|||
}, |
|||
{ |
|||
provide: NG_VALIDATORS, |
|||
useExisting: forwardRef(() => MqttBasicConfigComponent), |
|||
multi: true |
|||
} |
|||
], |
|||
styleUrls: ['./mqtt-basic-config.component.scss'], |
|||
standalone: true, |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
SecurityConfigComponent, |
|||
WorkersConfigControlComponent, |
|||
BrokerConfigControlComponent, |
|||
MappingTableComponent, |
|||
], |
|||
}) |
|||
export class MqttBasicConfigComponent extends AbstractMqttBasicConfigComponent { |
|||
|
|||
@Input() |
|||
generalTabContent: TemplateRef<any>; |
|||
|
|||
writeValue(basicConfig: MQTTBasicConfig): void { |
|||
const { broker, mapping = [], requestsMapping } = basicConfig; |
|||
const editedBase = { |
|||
workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { |
|||
maxNumberOfWorkers: broker.maxNumberOfWorkers, |
|||
maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, |
|||
} : {}, |
|||
mapping: mapping || [], |
|||
broker: broker || {}, |
|||
requestsMapping: this.getRequestDataArray(requestsMapping as Record<RequestType, RequestMappingData[]>), |
|||
}; |
|||
|
|||
this.basicFormGroup.setValue(editedBase, {emitEvent: false}); |
|||
} |
|||
|
|||
protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTBasicConfig { |
|||
let { broker, workers, mapping, requestsMapping } = basicConfig || {}; |
|||
|
|||
if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { |
|||
broker = { |
|||
...broker, |
|||
...workers, |
|||
}; |
|||
} |
|||
|
|||
if ((requestsMapping as RequestMappingData[])?.length) { |
|||
requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingValue[]); |
|||
} |
|||
|
|||
return { broker, mapping, requestsMapping }; |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
///
|
|||
/// 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 { Component, forwardRef, Input, TemplateRef, ChangeDetectionStrategy } from '@angular/core'; |
|||
import { NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms'; |
|||
import { |
|||
MQTTBasicConfig, |
|||
MQTTLegacyBasicConfig, |
|||
RequestMappingData, |
|||
RequestMappingValue, |
|||
RequestType |
|||
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
|||
import { MqttVersionMappingUtil } from '@home/components/widget/lib/gateway/utils/mqtt-version-mapping.util'; |
|||
import { |
|||
AbstractMqttBasicConfigComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/basic-config/mqtt-basic-config.abstract'; |
|||
import { isDefinedAndNotNull } from '@core/utils'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { SharedModule } from '@shared/shared.module'; |
|||
import { |
|||
SecurityConfigComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/security-config/security-config.component'; |
|||
import { |
|||
WorkersConfigControlComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/workers-config-control/workers-config-control.component'; |
|||
import { |
|||
BrokerConfigControlComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/mqtt/broker-config-control/broker-config-control.component'; |
|||
import { |
|||
MappingTableComponent |
|||
} from '@home/components/widget/lib/gateway/connectors-configuration/mapping-table/mapping-table.component'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-mqtt-legacy-basic-config', |
|||
templateUrl: './mqtt-basic-config.component.html', |
|||
changeDetection: ChangeDetectionStrategy.OnPush, |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => MqttLegacyBasicConfigComponent), |
|||
multi: true |
|||
}, |
|||
{ |
|||
provide: NG_VALIDATORS, |
|||
useExisting: forwardRef(() => MqttLegacyBasicConfigComponent), |
|||
multi: true |
|||
} |
|||
], |
|||
styleUrls: ['./mqtt-basic-config.component.scss'], |
|||
standalone: true, |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
SecurityConfigComponent, |
|||
WorkersConfigControlComponent, |
|||
BrokerConfigControlComponent, |
|||
MappingTableComponent, |
|||
], |
|||
}) |
|||
export class MqttLegacyBasicConfigComponent extends AbstractMqttBasicConfigComponent { |
|||
|
|||
@Input() |
|||
generalTabContent: TemplateRef<any>; |
|||
|
|||
writeValue(basicConfig: any): void { |
|||
const { |
|||
broker, |
|||
mapping = [], |
|||
connectRequests = [], |
|||
disconnectRequests = [], |
|||
attributeRequests = [], |
|||
attributeUpdates = [], |
|||
serverSideRpc = [] |
|||
} = basicConfig; |
|||
const updatedRequestMapping = MqttVersionMappingUtil.mapRequestsToNewestVersion({ |
|||
connectRequests, |
|||
disconnectRequests, |
|||
attributeRequests, |
|||
attributeUpdates, |
|||
serverSideRpc |
|||
}); |
|||
const editedBase = { |
|||
workers: broker && (broker.maxNumberOfWorkers || broker.maxMessageNumberPerWorker) ? { |
|||
maxNumberOfWorkers: broker.maxNumberOfWorkers, |
|||
maxMessageNumberPerWorker: broker.maxMessageNumberPerWorker, |
|||
} : {}, |
|||
mapping: MqttVersionMappingUtil.mapMappingToNewestVersion(mapping) || [], |
|||
broker: broker || {}, |
|||
requestsMapping: this.getRequestDataArray(updatedRequestMapping), |
|||
}; |
|||
|
|||
this.basicFormGroup.setValue(editedBase, {emitEvent: false}); |
|||
} |
|||
|
|||
protected getMappedMQTTConfig(basicConfig: MQTTBasicConfig): MQTTLegacyBasicConfig { |
|||
let { broker, workers, mapping, requestsMapping } = basicConfig || {}; |
|||
|
|||
if (isDefinedAndNotNull(workers.maxNumberOfWorkers) || isDefinedAndNotNull(workers.maxMessageNumberPerWorker)) { |
|||
broker = { |
|||
...broker, |
|||
...workers, |
|||
}; |
|||
} |
|||
|
|||
if ((requestsMapping as RequestMappingData[])?.length) { |
|||
requestsMapping = this.getRequestDataObject(requestsMapping as RequestMappingValue[]); |
|||
} |
|||
|
|||
return { |
|||
broker, |
|||
mapping: MqttVersionMappingUtil.mapMappingToLegacyVersion(mapping), |
|||
...(MqttVersionMappingUtil.mapRequestsToLegacyVersion(requestsMapping as Record<RequestType, RequestMappingData[]>)) |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
///
|
|||
/// 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 { |
|||
ConnectorType, |
|||
GatewayConnector, |
|||
LegacyGatewayConnector, |
|||
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
|||
import { MqttVersionMappingUtil } from './mqtt-version-mapping.util'; |
|||
|
|||
export class GatewayConnectorVersionMappingUtil { |
|||
|
|||
static getMappedByVersion(connector: GatewayConnector, gatewayVersion: string): GatewayConnector { |
|||
switch (connector.type) { |
|||
case ConnectorType.MQTT: |
|||
return this.getMappedMQTTByVersion(connector, gatewayVersion); |
|||
default: |
|||
return connector; |
|||
} |
|||
} |
|||
|
|||
private static getMappedMQTTByVersion( |
|||
connector: GatewayConnector | LegacyGatewayConnector, |
|||
gatewayVersion: string |
|||
): GatewayConnector | LegacyGatewayConnector { |
|||
if (this.isVersionUpdateNeeded(gatewayVersion, connector.configVersion)) { |
|||
return this.isGatewayOutdated(gatewayVersion, connector.configVersion) |
|||
? MqttVersionMappingUtil.getLegacyVersion(connector) |
|||
: MqttVersionMappingUtil.getNewestVersion(connector); |
|||
} |
|||
return connector; |
|||
} |
|||
|
|||
private static isGatewayOutdated(gatewayVersion: string, configVersion: string): boolean { |
|||
if (!gatewayVersion || !configVersion) { |
|||
return false; |
|||
} |
|||
|
|||
return this.parseVersion(configVersion) > this.parseVersion(gatewayVersion); |
|||
} |
|||
|
|||
private static isVersionUpdateNeeded(configVersion: string, gatewayVersion: string): boolean { |
|||
if (!gatewayVersion || !configVersion) { |
|||
return false; |
|||
} |
|||
|
|||
return this.parseVersion(configVersion) !== this.parseVersion(gatewayVersion); |
|||
} |
|||
|
|||
private static parseVersion(version: string): number { |
|||
return Number(version?.replace(/\./g, '')); |
|||
} |
|||
} |
|||
@ -0,0 +1,279 @@ |
|||
///
|
|||
/// 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 { deleteNullProperties, isEqual } from '@core/utils'; |
|||
import { |
|||
AttributeRequest, |
|||
ConnectorDeviceInfo, |
|||
Converter, |
|||
ConverterConnectorMapping, |
|||
ConvertorType, |
|||
CurrentGatewayConnector, |
|||
GatewayConnector, |
|||
LegacyConverter, |
|||
LegacyConverterConnectorMapping, |
|||
LegacyGatewayConnector, |
|||
LegacyRequestMappingData, |
|||
MQTTBasicConfig, |
|||
MQTTLegacyBasicConfig, |
|||
RequestMappingData, |
|||
RequestType, |
|||
ServerSideRpc, |
|||
ServerSideRpcType, |
|||
SourceType |
|||
} from '@home/components/widget/lib/gateway/gateway-widget.models'; |
|||
|
|||
export class MqttVersionMappingUtil { |
|||
|
|||
static readonly mqttRequestTypeKeys = Object.values(RequestType); |
|||
static readonly mqttRequestMappingOldFields = |
|||
['attributeNameJsonExpression', 'deviceNameJsonExpression', 'deviceNameTopicExpression', 'extension-config']; |
|||
static readonly mqttRequestMappingNewFields = |
|||
['attributeNameExpressionSource', 'responseTopicQoS', 'extensionConfig']; |
|||
|
|||
static getNewestVersion(mqttConnector: GatewayConnector): CurrentGatewayConnector { |
|||
const { |
|||
connectRequests, |
|||
disconnectRequests, |
|||
attributeRequests, |
|||
attributeUpdates, |
|||
serverSideRpc |
|||
} = mqttConnector.configurationJson as MQTTLegacyBasicConfig; |
|||
let configurationJson = { |
|||
...mqttConnector.configurationJson, |
|||
requestsMapping: this.mapRequestsToNewestVersion({ |
|||
connectRequests, |
|||
disconnectRequests, |
|||
attributeRequests, |
|||
attributeUpdates, |
|||
serverSideRpc |
|||
}), |
|||
mapping: this.mapMappingToNewestVersion((mqttConnector.configurationJson as MQTTLegacyBasicConfig).mapping), |
|||
}; |
|||
|
|||
this.mqttRequestTypeKeys.forEach((key: RequestType) => { |
|||
const { [key]: removedKey, ...rest } = configurationJson as MQTTLegacyBasicConfig; |
|||
configurationJson = { ...rest } as any; |
|||
}); |
|||
|
|||
this.cleanUpConfigJson(configurationJson as MQTTBasicConfig); |
|||
|
|||
return { |
|||
...mqttConnector, |
|||
configurationJson |
|||
} as CurrentGatewayConnector; |
|||
} |
|||
|
|||
static getLegacyVersion(mqttConnector: GatewayConnector): LegacyGatewayConnector { |
|||
const { requestsMapping, mapping, ...restConfig } = mqttConnector.configurationJson as MQTTBasicConfig; |
|||
|
|||
const updatedRequestsMapping = this.mapRequestsToLegacyVersion(requestsMapping as Record<RequestType, RequestMappingData[]>); |
|||
const updatedMapping = this.mapMappingToLegacyVersion(mapping); |
|||
|
|||
return { |
|||
...mqttConnector, |
|||
configurationJson: { |
|||
...restConfig, |
|||
...updatedRequestsMapping, |
|||
mapping: updatedMapping, |
|||
}, |
|||
} as LegacyGatewayConnector; |
|||
} |
|||
|
|||
static mapMappingToNewestVersion(mapping: LegacyConverterConnectorMapping[] | ConverterConnectorMapping[]): ConverterConnectorMapping[] { |
|||
return mapping?.map(({ converter, topicFilter, subscriptionQos = 1 }) => { |
|||
const deviceInfo = converter.deviceInfo ?? this.extractConverterDeviceInfo(converter); |
|||
|
|||
const newConverter = { |
|||
...converter, |
|||
deviceInfo, |
|||
extensionConfig: converter.extensionConfig || converter['extension-config'] || null |
|||
}; |
|||
|
|||
this.cleanUpOldFields(newConverter); |
|||
|
|||
return { converter: newConverter, topicFilter, subscriptionQos }; |
|||
}); |
|||
} |
|||
|
|||
static mapRequestsToNewestVersion( |
|||
requestMapping: Record<RequestType, |
|||
RequestMappingData[] | LegacyRequestMappingData[]> |
|||
): Record<RequestType, RequestMappingData[]> { |
|||
return this.mqttRequestTypeKeys.reduce((acc, key: RequestType) => { |
|||
if (!requestMapping[key]) { |
|||
return acc; |
|||
} |
|||
|
|||
acc[key] = requestMapping[key].map(value => { |
|||
const newValue = this.mapRequestToNewest(value as LegacyRequestMappingData, key); |
|||
|
|||
this.cleanUpOldFields(newValue as {}); |
|||
|
|||
return newValue; |
|||
}); |
|||
|
|||
return acc; |
|||
}, {}) as Record<RequestType, RequestMappingData[]>; |
|||
} |
|||
|
|||
static mapRequestsToLegacyVersion( |
|||
requestsMapping: Record<RequestType, RequestMappingData[]> |
|||
): Record<RequestType, LegacyRequestMappingData[]> { |
|||
return this.mqttRequestTypeKeys.reduce((acc, key) => { |
|||
if (!requestsMapping[key]) { |
|||
return acc; |
|||
} |
|||
|
|||
acc[key] = requestsMapping[key].map((value: RequestMappingData) => { |
|||
if (key === RequestType.SERVER_SIDE_RPC) { |
|||
delete (value as ServerSideRpc).type; |
|||
} |
|||
|
|||
const { attributeNameExpression, deviceInfo, ...rest } = value as AttributeRequest; |
|||
|
|||
const newValue = { |
|||
...rest, |
|||
attributeNameJsonExpression: attributeNameExpression || null, |
|||
deviceNameJsonExpression: deviceInfo?.deviceNameExpressionSource !== SourceType.TOPIC ? deviceInfo?.deviceNameExpression : null, |
|||
deviceNameTopicExpression: deviceInfo?.deviceNameExpressionSource === SourceType.TOPIC ? deviceInfo?.deviceNameExpression : null, |
|||
}; |
|||
|
|||
this.cleanUpNewFields(newValue); |
|||
|
|||
return newValue; |
|||
}); |
|||
|
|||
return acc; |
|||
}, {}) as Record<RequestType, LegacyRequestMappingData[]>; |
|||
} |
|||
|
|||
static mapMappingToLegacyVersion( |
|||
mapping: ConverterConnectorMapping[] |
|||
): LegacyConverterConnectorMapping[] { |
|||
return mapping?.map((converterMapping: ConverterConnectorMapping) => { |
|||
const converter = this.mapConverterToLegacy(converterMapping.converter); |
|||
|
|||
this.cleanUpNewFields(converter as {}); |
|||
|
|||
return { converter, topicFilter: converterMapping.topicFilter }; |
|||
}); |
|||
} |
|||
|
|||
private static mapConverterToLegacy(converter: Converter): LegacyConverter { |
|||
const { deviceInfo, ...restConverter } = converter; |
|||
|
|||
return converter.type !== ConvertorType.BYTES ? { |
|||
...restConverter, |
|||
deviceNameJsonExpression: deviceInfo?.deviceNameExpressionSource === SourceType.MSG ? deviceInfo.deviceNameExpression : null, |
|||
deviceTypeJsonExpression: |
|||
deviceInfo?.deviceProfileExpressionSource === SourceType.MSG ? deviceInfo.deviceProfileExpression : null, |
|||
deviceNameTopicExpression: |
|||
deviceInfo?.deviceNameExpressionSource !== SourceType.MSG |
|||
? deviceInfo?.deviceNameExpression |
|||
: null, |
|||
deviceTypeTopicExpression: deviceInfo?.deviceProfileExpressionSource !== SourceType.MSG |
|||
? deviceInfo?.deviceProfileExpression |
|||
: null, |
|||
} : { |
|||
...restConverter, |
|||
deviceNameExpression: deviceInfo.deviceNameExpression, |
|||
deviceTypeExpression: deviceInfo.deviceProfileExpression, |
|||
['extension-config']: converter.extensionConfig, |
|||
}; |
|||
} |
|||
|
|||
private static cleanUpOldFields(obj: Record<string, unknown>): void { |
|||
this.mqttRequestMappingOldFields.forEach(field => delete obj[field]); |
|||
deleteNullProperties(obj); |
|||
} |
|||
|
|||
private static cleanUpNewFields(obj: Record<string, unknown>): void { |
|||
this.mqttRequestMappingNewFields.forEach(field => delete obj[field]); |
|||
deleteNullProperties(obj); |
|||
} |
|||
|
|||
private static getTypeSourceByValue(value: string): SourceType { |
|||
if (value.includes('${')) { |
|||
return SourceType.MSG; |
|||
} |
|||
if (value.includes(`/`)) { |
|||
return SourceType.TOPIC; |
|||
} |
|||
return SourceType.CONST; |
|||
} |
|||
|
|||
private static cleanUpConfigJson(configurationJson: MQTTBasicConfig): void { |
|||
if (isEqual(configurationJson.requestsMapping, {})) { |
|||
delete configurationJson.requestsMapping; |
|||
} |
|||
|
|||
if (isEqual(configurationJson.mapping, [])) { |
|||
delete configurationJson.mapping; |
|||
} |
|||
} |
|||
|
|||
private static extractConverterDeviceInfo(converter: LegacyConverter): ConnectorDeviceInfo { |
|||
const deviceNameExpression = converter.deviceNameExpression |
|||
|| converter.deviceNameJsonExpression |
|||
|| converter.deviceNameTopicExpression |
|||
|| null; |
|||
const deviceNameExpressionSource = converter.deviceNameExpressionSource |
|||
? converter.deviceNameExpressionSource as SourceType |
|||
: deviceNameExpression ? this.getTypeSourceByValue(deviceNameExpression) : null; |
|||
const deviceProfileExpression = converter.deviceProfileExpression |
|||
|| converter.deviceTypeTopicExpression |
|||
|| converter.deviceTypeJsonExpression |
|||
|| 'default'; |
|||
const deviceProfileExpressionSource = converter.deviceProfileExpressionSource |
|||
? converter.deviceProfileExpressionSource as SourceType |
|||
: deviceProfileExpression ? this.getTypeSourceByValue(deviceProfileExpression) : null; |
|||
|
|||
return deviceNameExpression || deviceProfileExpression ? { |
|||
deviceNameExpression, |
|||
deviceNameExpressionSource, |
|||
deviceProfileExpression, |
|||
deviceProfileExpressionSource |
|||
} : null; |
|||
} |
|||
|
|||
private static mapRequestToNewest(value, key: RequestType): RequestMappingData { |
|||
const deviceNameExpression = value.deviceNameJsonExpression || value.deviceNameTopicExpression || null; |
|||
const deviceProfileExpression = value.deviceTypeTopicExpression || value.deviceTypeJsonExpression || 'default'; |
|||
const deviceProfileExpressionSource = deviceProfileExpression ? this.getTypeSourceByValue(deviceProfileExpression) : null; |
|||
const attributeNameExpression = value.attributeNameExpressionSource || value.attributeNameJsonExpression || null; |
|||
const responseTopicQoS = key === RequestType.SERVER_SIDE_RPC ? 1 : null; |
|||
const type = key === RequestType.SERVER_SIDE_RPC |
|||
? (value as ServerSideRpc).responseTopicExpression |
|||
? ServerSideRpcType.WithResponse |
|||
: ServerSideRpcType.WithoutResponse |
|||
: null; |
|||
|
|||
return { |
|||
...value, |
|||
attributeNameExpression, |
|||
attributeNameExpressionSource: attributeNameExpression ? this.getTypeSourceByValue(attributeNameExpression) : null, |
|||
deviceInfo: value.deviceInfo ? value.deviceInfo : deviceNameExpression ? { |
|||
deviceNameExpression, |
|||
deviceNameExpressionSource: this.getTypeSourceByValue(deviceNameExpression), |
|||
deviceProfileExpression, |
|||
deviceProfileExpressionSource |
|||
} : null, |
|||
responseTopicQoS, |
|||
type |
|||
}; |
|||
} |
|||
} |
|||
@ -1,218 +1,399 @@ |
|||
{ |
|||
"broker": { |
|||
"name": "Default Local Broker", |
|||
"host": "127.0.0.1", |
|||
"port": 1883, |
|||
"clientId": "ThingsBoard_gateway", |
|||
"version": 5, |
|||
"maxMessageNumberPerWorker": 10, |
|||
"maxNumberOfWorkers": 100, |
|||
"sendDataOnlyOnChange": false, |
|||
"security": { |
|||
"type": "anonymous" |
|||
} |
|||
}, |
|||
"dataMapping": [ |
|||
{ |
|||
"topicFilter": "sensor/data", |
|||
"subscriptionQos": 1, |
|||
"converter": { |
|||
"type": "json", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "message", |
|||
"deviceNameExpression": "${serialNumber}", |
|||
"deviceProfileExpressionSource": "message", |
|||
"deviceProfileExpression": "${sensorType}" |
|||
"current": { |
|||
"broker": { |
|||
"name": "Default Local Broker", |
|||
"host": "127.0.0.1", |
|||
"port": 1883, |
|||
"clientId": "ThingsBoard_gateway", |
|||
"version": 5, |
|||
"maxMessageNumberPerWorker": 10, |
|||
"maxNumberOfWorkers": 100, |
|||
"sendDataOnlyOnChange": false, |
|||
"security": { |
|||
"type": "anonymous" |
|||
} |
|||
}, |
|||
"sendDataOnlyOnChange": false, |
|||
"timeout": 60000, |
|||
"attributes": [ |
|||
{ |
|||
"type": "string", |
|||
"key": "model", |
|||
"value": "${sensorModel}" |
|||
}, |
|||
{ |
|||
"type": "string", |
|||
"key": "${sensorModel}", |
|||
"value": "on" |
|||
} |
|||
"mapping": [ |
|||
{ |
|||
"topicFilter": "sensor/data", |
|||
"subscriptionQos": 1, |
|||
"converter": { |
|||
"type": "json", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "message", |
|||
"deviceNameExpression": "${serialNumber}", |
|||
"deviceProfileExpressionSource": "message", |
|||
"deviceProfileExpression": "${sensorType}" |
|||
}, |
|||
"sendDataOnlyOnChange": false, |
|||
"timeout": 60000, |
|||
"attributes": [ |
|||
{ |
|||
"type": "string", |
|||
"key": "model", |
|||
"value": "${sensorModel}" |
|||
}, |
|||
{ |
|||
"type": "string", |
|||
"key": "${sensorModel}", |
|||
"value": "on" |
|||
} |
|||
], |
|||
"timeseries": [ |
|||
{ |
|||
"type": "string", |
|||
"key": "temperature", |
|||
"value": "${temp}" |
|||
}, |
|||
{ |
|||
"type": "double", |
|||
"key": "humidity", |
|||
"value": "${hum}" |
|||
}, |
|||
{ |
|||
"type": "string", |
|||
"key": "combine", |
|||
"value": "${hum}:${temp}" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/+/data", |
|||
"subscriptionQos": 1, |
|||
"converter": { |
|||
"type": "json", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "topic", |
|||
"deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/data)", |
|||
"deviceProfileExpressionSource": "constant", |
|||
"deviceProfileExpression": "Thermometer" |
|||
}, |
|||
"sendDataOnlyOnChange": false, |
|||
"timeout": 60000, |
|||
"attributes": [ |
|||
{ |
|||
"type": "string", |
|||
"key": "model", |
|||
"value": "${sensorModel}" |
|||
} |
|||
], |
|||
"timeseries": [ |
|||
{ |
|||
"type": "double", |
|||
"key": "temperature", |
|||
"value": "${temp}" |
|||
}, |
|||
{ |
|||
"type": "string", |
|||
"key": "humidity", |
|||
"value": "${hum}" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/raw_data", |
|||
"subscriptionQos": 1, |
|||
"converter": { |
|||
"type": "bytes", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "message", |
|||
"deviceNameExpression": "[0:4]", |
|||
"deviceProfileExpressionSource": "constant", |
|||
"deviceProfileExpression": "default" |
|||
}, |
|||
"sendDataOnlyOnChange": false, |
|||
"timeout": 60000, |
|||
"attributes": [ |
|||
{ |
|||
"type": "raw", |
|||
"key": "rawData", |
|||
"value": "[:]" |
|||
} |
|||
], |
|||
"timeseries": [ |
|||
{ |
|||
"type": "raw", |
|||
"key": "temp", |
|||
"value": "[4:]" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "custom/sensors/+", |
|||
"subscriptionQos": 1, |
|||
"converter": { |
|||
"type": "custom", |
|||
"extension": "CustomMqttUplinkConverter", |
|||
"cached": true, |
|||
"extensionConfig": { |
|||
"temperature": 2, |
|||
"humidity": 2, |
|||
"batteryLevel": 1 |
|||
} |
|||
} |
|||
} |
|||
], |
|||
"timeseries": [ |
|||
{ |
|||
"type": "string", |
|||
"key": "temperature", |
|||
"value": "${temp}" |
|||
}, |
|||
{ |
|||
"type": "double", |
|||
"key": "humidity", |
|||
"value": "${hum}" |
|||
}, |
|||
{ |
|||
"type": "string", |
|||
"key": "combine", |
|||
"value": "${hum}:${temp}" |
|||
} |
|||
] |
|||
} |
|||
"requestsMapping": { |
|||
"connectRequests": [ |
|||
{ |
|||
"topicFilter": "sensor/connect", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "message", |
|||
"deviceNameExpression": "${serialNumber}", |
|||
"deviceProfileExpressionSource": "constant", |
|||
"deviceProfileExpression": "Thermometer" |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/+/connect", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "topic", |
|||
"deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)", |
|||
"deviceProfileExpressionSource": "constant", |
|||
"deviceProfileExpression": "Thermometer" |
|||
} |
|||
} |
|||
], |
|||
"disconnectRequests": [ |
|||
{ |
|||
"topicFilter": "sensor/disconnect", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "message", |
|||
"deviceNameExpression": "${serialNumber}" |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/+/disconnect", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "topic", |
|||
"deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)" |
|||
} |
|||
} |
|||
], |
|||
"attributeRequests": [ |
|||
{ |
|||
"retain": false, |
|||
"topicFilter": "v1/devices/me/attributes/request", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "message", |
|||
"deviceNameExpression": "${serialNumber}" |
|||
}, |
|||
"attributeNameExpressionSource": "message", |
|||
"attributeNameExpression": "${versionAttribute}, ${pduAttribute}", |
|||
"topicExpression": "devices/${deviceName}/attrs", |
|||
"valueExpression": "${attributeKey}: ${attributeValue}" |
|||
} |
|||
], |
|||
"attributeUpdates": [ |
|||
{ |
|||
"retain": true, |
|||
"deviceNameFilter": ".*", |
|||
"attributeFilter": "firmwareVersion", |
|||
"topicExpression": "sensor/${deviceName}/${attributeKey}", |
|||
"valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" |
|||
} |
|||
], |
|||
"serverSideRpc": [ |
|||
{ |
|||
"type": "twoWay", |
|||
"deviceNameFilter": ".*", |
|||
"methodFilter": "echo", |
|||
"requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", |
|||
"responseTopicExpression": "sensor/${deviceName}/response/${methodName}/${requestId}", |
|||
"responseTopicQoS": 1, |
|||
"responseTimeout": 10000, |
|||
"valueExpression": "${params}" |
|||
}, |
|||
{ |
|||
"type": "oneWay", |
|||
"deviceNameFilter": ".*", |
|||
"methodFilter": "no-reply", |
|||
"requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", |
|||
"valueExpression": "${params}" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/+/data", |
|||
"subscriptionQos": 1, |
|||
"converter": { |
|||
"type": "json", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "topic", |
|||
"deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/data)", |
|||
"deviceProfileExpressionSource": "constant", |
|||
"deviceProfileExpression": "Thermometer" |
|||
"legacy": { |
|||
"broker": { |
|||
"name": "Default Local Broker", |
|||
"host": "127.0.0.1", |
|||
"port": 1883, |
|||
"clientId": "ThingsBoard_gateway", |
|||
"version": 5, |
|||
"maxMessageNumberPerWorker": 10, |
|||
"maxNumberOfWorkers": 100, |
|||
"sendDataOnlyOnChange": false, |
|||
"security": { |
|||
"type": "basic", |
|||
"username": "user", |
|||
"password": "password" |
|||
} |
|||
}, |
|||
"sendDataOnlyOnChange": false, |
|||
"timeout": 60000, |
|||
"attributes": [ |
|||
{ |
|||
"type": "string", |
|||
"key": "model", |
|||
"value": "${sensorModel}" |
|||
} |
|||
"mapping": [ |
|||
{ |
|||
"topicFilter": "sensor/data", |
|||
"converter": { |
|||
"type": "json", |
|||
"deviceNameJsonExpression": "${serialNumber}", |
|||
"deviceTypeJsonExpression": "${sensorType}", |
|||
"sendDataOnlyOnChange": false, |
|||
"timeout": 60000, |
|||
"attributes": [ |
|||
{ |
|||
"type": "string", |
|||
"key": "model", |
|||
"value": "${sensorModel}" |
|||
}, |
|||
{ |
|||
"type": "string", |
|||
"key": "${sensorModel}", |
|||
"value": "on" |
|||
} |
|||
], |
|||
"timeseries": [ |
|||
{ |
|||
"type": "double", |
|||
"key": "temperature", |
|||
"value": "${temp}" |
|||
}, |
|||
{ |
|||
"type": "double", |
|||
"key": "humidity", |
|||
"value": "${hum}" |
|||
}, |
|||
{ |
|||
"type": "string", |
|||
"key": "combine", |
|||
"value": "${hum}:${temp}" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/+/data", |
|||
"converter": { |
|||
"type": "json", |
|||
"deviceNameTopicExpression": "(?<=sensor\/)(.*?)(?=\/data)", |
|||
"deviceTypeTopicExpression": "Thermometer", |
|||
"sendDataOnlyOnChange": false, |
|||
"timeout": 60000, |
|||
"attributes": [ |
|||
{ |
|||
"type": "string", |
|||
"key": "model", |
|||
"value": "${sensorModel}" |
|||
} |
|||
], |
|||
"timeseries": [ |
|||
{ |
|||
"type": "double", |
|||
"key": "temperature", |
|||
"value": "${temp}" |
|||
}, |
|||
{ |
|||
"type": "double", |
|||
"key": "humidity", |
|||
"value": "${hum}" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/raw_data", |
|||
"converter": { |
|||
"type": "bytes", |
|||
"deviceNameExpression": "[0:4]", |
|||
"deviceTypeExpression": "default", |
|||
"sendDataOnlyOnChange": false, |
|||
"timeout": 60000, |
|||
"attributes": [ |
|||
{ |
|||
"type": "raw", |
|||
"key": "rawData", |
|||
"value": "[:]" |
|||
} |
|||
], |
|||
"timeseries": [ |
|||
{ |
|||
"type": "raw", |
|||
"key": "temp", |
|||
"value": "[4:]" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "custom/sensors/+", |
|||
"converter": { |
|||
"type": "custom", |
|||
"extension": "CustomMqttUplinkConverter", |
|||
"cached": true, |
|||
"extension-config": { |
|||
"temperatureBytes": 2, |
|||
"humidityBytes": 2, |
|||
"batteryLevelBytes": 1 |
|||
} |
|||
} |
|||
} |
|||
], |
|||
"timeseries": [ |
|||
{ |
|||
"type": "double", |
|||
"key": "temperature", |
|||
"value": "${temp}" |
|||
}, |
|||
{ |
|||
"type": "string", |
|||
"key": "humidity", |
|||
"value": "${hum}" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/raw_data", |
|||
"subscriptionQos": 1, |
|||
"converter": { |
|||
"type": "bytes", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "message", |
|||
"deviceNameExpression": "[0:4]", |
|||
"deviceProfileExpressionSource": "constant", |
|||
"deviceProfileExpression": "default" |
|||
}, |
|||
"sendDataOnlyOnChange": false, |
|||
"timeout": 60000, |
|||
"attributes": [ |
|||
{ |
|||
"type": "raw", |
|||
"key": "rawData", |
|||
"value": "[:]" |
|||
} |
|||
"connectRequests": [ |
|||
{ |
|||
"topicFilter": "sensor/connect", |
|||
"deviceNameJsonExpression": "${serialNumber}" |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/+/connect", |
|||
"deviceNameTopicExpression": "(?<=sensor\/)(.*?)(?=\/connect)" |
|||
} |
|||
], |
|||
"disconnectRequests": [ |
|||
{ |
|||
"topicFilter": "sensor/disconnect", |
|||
"deviceNameJsonExpression": "${serialNumber}" |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/+/disconnect", |
|||
"deviceNameTopicExpression": "(?<=sensor\/)(.*?)(?=\/disconnect)" |
|||
} |
|||
], |
|||
"attributeRequests": [ |
|||
{ |
|||
"retain": false, |
|||
"topicFilter": "v1/devices/me/attributes/request", |
|||
"deviceNameJsonExpression": "${serialNumber}", |
|||
"attributeNameJsonExpression": "${versionAttribute}, ${pduAttribute}", |
|||
"topicExpression": "devices/${deviceName}/attrs", |
|||
"valueExpression": "${attributeKey}: ${attributeValue}" |
|||
} |
|||
], |
|||
"timeseries": [ |
|||
{ |
|||
"type": "raw", |
|||
"key": "temp", |
|||
"value": "[4:]" |
|||
} |
|||
"attributeUpdates": [ |
|||
{ |
|||
"retain": true, |
|||
"deviceNameFilter": ".*", |
|||
"attributeFilter": "firmwareVersion", |
|||
"topicExpression": "sensor/${deviceName}/${attributeKey}", |
|||
"valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" |
|||
} |
|||
], |
|||
"serverSideRpc": [ |
|||
{ |
|||
"deviceNameFilter": ".*", |
|||
"methodFilter": "echo", |
|||
"requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", |
|||
"responseTopicExpression": "sensor/${deviceName}/response/${methodName}/${requestId}", |
|||
"responseTimeout": 10000, |
|||
"valueExpression": "${params}" |
|||
}, |
|||
{ |
|||
"deviceNameFilter": ".*", |
|||
"methodFilter": "no-reply", |
|||
"requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", |
|||
"valueExpression": "${params}" |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "custom/sensors/+", |
|||
"subscriptionQos": 1, |
|||
"converter": { |
|||
"type": "custom", |
|||
"extension": "CustomMqttUplinkConverter", |
|||
"cached": true, |
|||
"extensionConfig": { |
|||
"temperature": 2, |
|||
"humidity": 2, |
|||
"batteryLevel": 1 |
|||
} |
|||
} |
|||
} |
|||
], |
|||
"requestsMapping": { |
|||
"connectRequests": [ |
|||
{ |
|||
"topicFilter": "sensor/connect", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "message", |
|||
"deviceNameExpression": "${serialNumber}", |
|||
"deviceProfileExpressionSource": "constant", |
|||
"deviceProfileExpression": "Thermometer" |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/+/connect", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "topic", |
|||
"deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)", |
|||
"deviceProfileExpressionSource": "constant", |
|||
"deviceProfileExpression": "Thermometer" |
|||
} |
|||
} |
|||
], |
|||
"disconnectRequests": [ |
|||
{ |
|||
"topicFilter": "sensor/disconnect", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "message", |
|||
"deviceNameExpression": "${serialNumber}" |
|||
} |
|||
}, |
|||
{ |
|||
"topicFilter": "sensor/+/disconnect", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "topic", |
|||
"deviceNameExpression": "(?<=sensor\/)(.*?)(?=\/connect)" |
|||
} |
|||
} |
|||
], |
|||
"attributeRequests": [ |
|||
{ |
|||
"retain": false, |
|||
"topicFilter": "v1/devices/me/attributes/request", |
|||
"deviceInfo": { |
|||
"deviceNameExpressionSource": "message", |
|||
"deviceNameExpression": "${serialNumber}" |
|||
}, |
|||
"attributeNameExpressionSource": "message", |
|||
"attributeNameExpression": "${versionAttribute}, ${pduAttribute}", |
|||
"topicExpression": "devices/${deviceName}/attrs", |
|||
"valueExpression": "${attributeKey}: ${attributeValue}" |
|||
} |
|||
], |
|||
"attributeUpdates": [ |
|||
{ |
|||
"retain": true, |
|||
"deviceNameFilter": ".*", |
|||
"attributeFilter": "firmwareVersion", |
|||
"topicExpression": "sensor/${deviceName}/${attributeKey}", |
|||
"valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" |
|||
} |
|||
], |
|||
"serverSideRpc": [ |
|||
{ |
|||
"type": "twoWay", |
|||
"deviceNameFilter": ".*", |
|||
"methodFilter": "echo", |
|||
"requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", |
|||
"responseTopicExpression": "sensor/${deviceName}/response/${methodName}/${requestId}", |
|||
"responseTopicQoS": 1, |
|||
"responseTimeout": 10000, |
|||
"valueExpression": "${params}" |
|||
}, |
|||
{ |
|||
"type": "oneWay", |
|||
"deviceNameFilter": ".*", |
|||
"methodFilter": "no-reply", |
|||
"requestTopicExpression": "sensor/${deviceName}/request/${methodName}/${requestId}", |
|||
"valueExpression": "${params}" |
|||
} |
|||
] |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue