UI: Device profile transport configuration

This commit is contained in:
Igor Kulikov 2020-09-03 12:51:54 +03:00
parent 0759c135a3
commit 3adbb481a1
14 changed files with 402 additions and 12 deletions

View File

@ -181,8 +181,10 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
switch (deviceProfile.getTransportType()){
case DEFAULT:
deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration());
break;
case MQTT:
deviceData.setTransportConfiguration(new MqttDeviceTransportConfiguration());
break;
case LWM2M:
deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration());
break;

View File

@ -92,6 +92,8 @@ import { DefaultDeviceProfileConfigurationComponent } from './profile/device/def
import { DeviceProfileConfigurationComponent } from './profile/device/device-profile-configuration.component';
import { DeviceProfileDataComponent } from './profile/device-profile-data.component';
import { DeviceProfileComponent } from './profile/device-profile.component';
import { DefaultDeviceProfileTransportConfigurationComponent } from './profile/device/default-device-profile-transport-configuration.component';
import { DeviceProfileTransportConfigurationComponent } from './profile/device/device-profile-transport-configuration.component';
@NgModule({
declarations:
@ -165,6 +167,8 @@ import { DeviceProfileComponent } from './profile/device-profile.component';
TenantProfileDialogComponent,
DefaultDeviceProfileConfigurationComponent,
DeviceProfileConfigurationComponent,
DefaultDeviceProfileTransportConfigurationComponent,
DeviceProfileTransportConfigurationComponent,
DeviceProfileDataComponent,
DeviceProfileComponent
],
@ -229,6 +233,8 @@ import { DeviceProfileComponent } from './profile/device-profile.component';
TenantProfileDialogComponent,
DefaultDeviceProfileConfigurationComponent,
DeviceProfileConfigurationComponent,
DefaultDeviceProfileTransportConfigurationComponent,
DeviceProfileTransportConfigurationComponent,
DeviceProfileDataComponent,
DeviceProfileComponent
],

View File

@ -34,7 +34,10 @@
<div translate>device-profile.transport-configuration</div>
</mat-panel-title>
</mat-expansion-panel-header>
TODO
<tb-device-profile-transport-configuration
formControlName="transportConfiguration"
required>
</tb-device-profile-transport-configuration>
</mat-expansion-panel>
</mat-accordion>
</div>

View File

@ -62,7 +62,8 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit
ngOnInit() {
this.deviceProfileDataFormGroup = this.fb.group({
configuration: [null, Validators.required]
configuration: [null, Validators.required],
transportConfiguration: [null, Validators.required]
});
this.deviceProfileDataFormGroup.valueChanges.subscribe(() => {
this.updateModel();
@ -80,6 +81,7 @@ export class DeviceProfileDataComponent implements ControlValueAccessor, OnInit
writeValue(value: DeviceProfileData | null): void {
this.deviceProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false});
this.deviceProfileDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false});
}
private updateModel() {

View File

@ -65,6 +65,17 @@
{{ 'device-profile.type-required' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>device-profile.transport-type</mat-label>
<mat-select formControlName="transportType" required>
<mat-option *ngFor="let type of deviceTransportTypes" [value]="type">
{{deviceTransportTypeTranslations.get(type) | translate}}
</mat-option>
</mat-select>
<mat-error *ngIf="entityForm.get('transportType').hasError('required')">
{{ 'device-profile.transport-type-required' | translate }}
</mat-error>
</mat-form-field>
<tb-device-profile-data
formControlName="profileData"
required>

View File

@ -27,7 +27,10 @@ import {
DeviceProfile,
DeviceProfileData,
DeviceProfileType,
deviceProfileTypeTranslationMap
deviceProfileTypeTranslationMap,
DeviceTransportType,
deviceTransportTypeTranslationMap,
createDeviceProfileTransportConfiguration
} from '@shared/models/device.models';
import { EntityType } from '@shared/models/entity-type.models';
import { RuleChainId } from '@shared/models/id/rule-chain-id';
@ -48,6 +51,10 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
deviceProfileTypeTranslations = deviceProfileTypeTranslationMap;
deviceTransportTypes = Object.keys(DeviceTransportType);
deviceTransportTypeTranslations = deviceTransportTypeTranslationMap;
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Optional() @Inject('entity') protected entityValue: DeviceProfile,
@ -68,7 +75,8 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
const form = this.fb.group(
{
name: [entity ? entity.name : '', [Validators.required]],
type: [entity ? entity.type : '', [Validators.required]],
type: [entity ? entity.type : null, [Validators.required]],
transportType: [entity ? entity.transportType : null, [Validators.required]],
profileData: [entity && !this.isAdd ? entity.profileData : {}, []],
defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []],
description: [entity ? entity.description : '', []],
@ -77,6 +85,9 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
form.get('type').valueChanges.subscribe(() => {
this.deviceProfileTypeChanged(form);
});
form.get('transportType').valueChanges.subscribe(() => {
this.deviceProfileTransportTypeChanged(form);
});
this.checkIsNewDeviceProfile(entity, form);
return form;
}
@ -84,6 +95,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
private checkIsNewDeviceProfile(entity: DeviceProfile, form: FormGroup) {
if (entity && !entity.id) {
form.get('type').patchValue(DeviceProfileType.DEFAULT, {emitEvent: true});
form.get('transportType').patchValue(DeviceTransportType.DEFAULT, {emitEvent: true});
}
}
@ -92,16 +104,31 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
let profileData: DeviceProfileData = form.getRawValue().profileData;
if (!profileData) {
profileData = {
configuration: null
configuration: null,
transportConfiguration: null
};
}
profileData.configuration = createDeviceProfileConfiguration(deviceProfileType);
form.patchValue({profileData});
}
private deviceProfileTransportTypeChanged(form: FormGroup) {
const deviceTransportType: DeviceTransportType = form.get('transportType').value;
let profileData: DeviceProfileData = form.getRawValue().profileData;
if (!profileData) {
profileData = {
configuration: null,
transportConfiguration: null
};
}
profileData.transportConfiguration = createDeviceProfileTransportConfiguration(deviceTransportType);
form.patchValue({profileData});
}
updateForm(entity: DeviceProfile) {
this.entityForm.patchValue({name: entity.name});
this.entityForm.patchValue({type: entity.type});
this.entityForm.patchValue({transportType: entity.transportType});
this.entityForm.patchValue({profileData: entity.profileData});
this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null});
this.entityForm.patchValue({description: entity.description});

View File

@ -0,0 +1,24 @@
<!--
Copyright © 2016-2020 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.
-->
<form [formGroup]="defaultDeviceProfileTransportConfigurationFormGroup" style="padding-bottom: 16px;">
<tb-json-object-edit
[required]="required"
label="{{ 'device-profile.transport-type-default' | translate }}"
formControlName="configuration">
</tb-json-object-edit>
</form>

View File

@ -0,0 +1,97 @@
///
/// Copyright © 2016-2020 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, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import {
DefaultDeviceProfileTransportConfiguration,
DeviceProfileTransportConfiguration,
DeviceTransportType
} from '@shared/models/device.models';
@Component({
selector: 'tb-default-device-profile-transport-configuration',
templateUrl: './default-device-profile-transport-configuration.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultDeviceProfileTransportConfigurationComponent),
multi: true
}]
})
export class DefaultDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit {
defaultDeviceProfileTransportConfigurationFormGroup: FormGroup;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
private fb: FormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.defaultDeviceProfileTransportConfigurationFormGroup = this.fb.group({
configuration: [null, Validators.required]
});
this.defaultDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.defaultDeviceProfileTransportConfigurationFormGroup.disable({emitEvent: false});
} else {
this.defaultDeviceProfileTransportConfigurationFormGroup.enable({emitEvent: false});
}
}
writeValue(value: DefaultDeviceProfileTransportConfiguration | null): void {
this.defaultDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false});
}
private updateModel() {
let configuration: DeviceProfileTransportConfiguration = null;
if (this.defaultDeviceProfileTransportConfigurationFormGroup.valid) {
configuration = this.defaultDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration;
configuration.type = DeviceTransportType.DEFAULT;
}
this.propagateChange(configuration);
}
}

View File

@ -20,7 +20,7 @@ import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { DeviceProfileConfiguration, DeviceProfileType } from '@shared/models/device.models';
import { deepClone } from '../../../../../core/utils';
import { deepClone } from '@core/utils';
@Component({
selector: 'tb-device-profile-configuration',

View File

@ -0,0 +1,27 @@
<!--
Copyright © 2016-2020 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.
-->
<div [formGroup]="deviceProfileTransportConfigurationFormGroup">
<div [ngSwitch]="transportType">
<ng-template [ngSwitchCase]="deviceTransportType.DEFAULT">
<tb-default-device-profile-configuration
[required]="required"
formControlName="configuration">
</tb-default-device-profile-configuration>
</ng-template>
</div>
</div>

View File

@ -0,0 +1,103 @@
///
/// Copyright © 2016-2020 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, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { DeviceProfileTransportConfiguration, DeviceTransportType } from '@shared/models/device.models';
import { deepClone } from '@core/utils';
@Component({
selector: 'tb-device-profile-transport-configuration',
templateUrl: './device-profile-transport-configuration.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DeviceProfileTransportConfigurationComponent),
multi: true
}]
})
export class DeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit {
deviceTransportType = DeviceTransportType;
deviceProfileTransportConfigurationFormGroup: FormGroup;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
transportType: DeviceTransportType;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
private fb: FormBuilder) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.deviceProfileTransportConfigurationFormGroup = this.fb.group({
configuration: [null, Validators.required]
});
this.deviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.deviceProfileTransportConfigurationFormGroup.disable({emitEvent: false});
} else {
this.deviceProfileTransportConfigurationFormGroup.enable({emitEvent: false});
}
}
writeValue(value: DeviceProfileTransportConfiguration | null): void {
this.transportType = value?.type;
const configuration = deepClone(value);
if (configuration) {
delete configuration.type;
}
this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false});
}
private updateModel() {
let configuration: DeviceProfileTransportConfiguration = null;
if (this.deviceProfileTransportConfigurationFormGroup.valid) {
configuration = this.deviceProfileTransportConfigurationFormGroup.getRawValue().configuration;
configuration.type = this.transportType;
}
this.propagateChange(configuration);
}
}

View File

@ -27,7 +27,11 @@ import { DatePipe } from '@angular/common';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { DialogService } from '@core/services/dialog.service';
import { DeviceProfile, deviceProfileTypeTranslationMap } from '@shared/models/device.models';
import {
DeviceProfile,
deviceProfileTypeTranslationMap,
deviceTransportTypeTranslationMap
} from '@shared/models/device.models';
import { DeviceProfileService } from '@core/http/device-profile.service';
import { DeviceProfileComponent } from '../../components/profile/device-profile.component';
import { DeviceProfileTabsComponent } from './device-profile-tabs.component';
@ -56,7 +60,10 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
new EntityTableColumn<DeviceProfile>('type', 'device-profile.type', '20%', (deviceProfile) => {
return this.translate.instant(deviceProfileTypeTranslationMap.get(deviceProfile.type));
}),
new EntityTableColumn<DeviceProfile>('description', 'device-profile.description', '60%'),
new EntityTableColumn<DeviceProfile>('transportType', 'device-profile.transport-type', '20%', (deviceProfile) => {
return this.translate.instant(deviceTransportTypeTranslationMap.get(deviceProfile.transportType));
}),
new EntityTableColumn<DeviceProfile>('description', 'device-profile.description', '40%'),
new EntityTableColumn<DeviceProfile>('isDefault', 'device-profile.default', '60px',
entity => {
return checkBoxCell(entity.default);

View File

@ -28,12 +28,26 @@ export enum DeviceProfileType {
DEFAULT = 'DEFAULT'
}
export enum DeviceTransportType {
DEFAULT = 'DEFAULT',
MQTT = 'MQTT',
LWM2M = 'LWM2M'
}
export const deviceProfileTypeTranslationMap = new Map<DeviceProfileType, string>(
[
[DeviceProfileType.DEFAULT, 'device-profile.type-default']
]
);
export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, string>(
[
[DeviceTransportType.DEFAULT, 'device-profile.transport-type-default'],
[DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt'],
[DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m']
]
);
export interface DefaultDeviceProfileConfiguration {
[key: string]: any;
}
@ -44,6 +58,26 @@ export interface DeviceProfileConfiguration extends DeviceProfileConfigurations
type: DeviceProfileType;
}
export interface DefaultDeviceProfileTransportConfiguration {
[key: string]: any;
}
export interface MqttDeviceProfileTransportConfiguration {
[key: string]: any;
}
export interface Lwm2mDeviceProfileTransportConfiguration {
[key: string]: any;
}
export type DeviceProfileTransportConfigurations = DefaultDeviceProfileTransportConfiguration &
MqttDeviceProfileTransportConfiguration &
Lwm2mDeviceProfileTransportConfiguration;
export interface DeviceProfileTransportConfiguration extends DeviceProfileTransportConfigurations {
type: DeviceTransportType;
}
export function createDeviceProfileConfiguration(type: DeviceProfileType): DeviceProfileConfiguration {
let configuration: DeviceProfileConfiguration = null;
if (type) {
@ -57,8 +91,30 @@ export function createDeviceProfileConfiguration(type: DeviceProfileType): Devic
return configuration;
}
export function createDeviceProfileTransportConfiguration(type: DeviceTransportType): DeviceProfileTransportConfiguration {
let transportConfiguration: DeviceProfileTransportConfiguration = null;
if (type) {
switch (type) {
case DeviceTransportType.DEFAULT:
const defaultTransportConfiguration: DefaultDeviceProfileTransportConfiguration = {};
transportConfiguration = {...defaultTransportConfiguration, type: DeviceTransportType.DEFAULT};
break;
case DeviceTransportType.MQTT:
const mqttTransportConfiguration: MqttDeviceProfileTransportConfiguration = {};
transportConfiguration = {...mqttTransportConfiguration, type: DeviceTransportType.MQTT};
break;
case DeviceTransportType.LWM2M:
const lwm2mTransportConfiguration: Lwm2mDeviceProfileTransportConfiguration = {};
transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M};
break;
}
}
return transportConfiguration;
}
export interface DeviceProfileData {
configuration: DeviceProfileConfiguration;
transportConfiguration: DeviceProfileTransportConfiguration;
}
export interface DeviceProfile extends BaseData<DeviceProfileId> {
@ -67,29 +123,49 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> {
description?: string;
default: boolean;
type: DeviceProfileType;
transportType: DeviceTransportType;
defaultRuleChainId?: RuleChainId;
profileData: DeviceProfileData;
}
export interface DeviceProfileInfo extends EntityInfoData {
type: DeviceProfileType;
transportType: DeviceTransportType;
}
export interface DefaultDeviceConfiguration {
[key: string]: any;
}
export interface Lwm2mDeviceConfiguration {
[key: string]: any;
}
export type DeviceConfigurations = DefaultDeviceConfiguration & Lwm2mDeviceConfiguration;
export type DeviceConfigurations = DefaultDeviceConfiguration;
export interface DeviceConfiguration extends DeviceConfigurations {
type: DeviceProfileType;
}
export interface DefaultDeviceTransportConfiguration {
[key: string]: any;
}
export interface MqttDeviceTransportConfiguration {
[key: string]: any;
}
export interface Lwm2mDeviceTransportConfiguration {
[key: string]: any;
}
export type DeviceTransportConfigurations = DefaultDeviceTransportConfiguration &
MqttDeviceTransportConfiguration &
Lwm2mDeviceTransportConfiguration;
export interface DeviceTransportConfiguration extends DeviceTransportConfigurations {
type: DeviceTransportType;
}
export interface DeviceData {
configuration: DeviceConfiguration;
transportConfiguration: DeviceTransportConfiguration;
}
export interface Device extends BaseData<DeviceId> {

View File

@ -770,6 +770,11 @@
"type": "Profile type",
"type-required": "Profile type is required.",
"type-default": "Default",
"transport-type": "Transport type",
"transport-type-required": "Transport type is required.",
"transport-type-default": "Default",
"transport-type-mqtt": "MQTT",
"transport-type-lwm2m": "LWM2M",
"description": "Description",
"default": "Default",
"profile-configuration": "Profile configuration",