Skip to content

Commit 9424f1e

Browse files
Roadmap features: Mqtt broker/subscriber support (#137)
* Shell for supporting new device type, currently not fully working * Added broker details page * Information bubbles for username/password for MQTT broker * Support added for receiving data from mqtt broker * Bulk import changes and new type * Bulk import of mqtt devices and halfbaked csv export * Disabled most editing on mqtt subscriber * IOT-1469 * Fixes from PR * Added CA to password brokers. Added download button to cert files * Creating a new iotDevice now routes to detail page of device * Added helper text about autogenerated informations for mqtt * Fixed routing when creating iotDevice * Ignore id on bulk import to not risk breaking things * Enabled editing of mqtt devices * Fixed bulk mapping to have validation + error shown when failing * Changed error message handling to handle differnet deeper nestings * Made error check open to all types with children * Added info-text to a hover on MQTT subtype * Changed to checkboxes instead of radio buttons and made logic so when it's checked in can only be unchecked by chosen another iotDevice. * Show message when not able to connect to external broker * Renamed Mqtt device types * Fixed application connection types * Added text about CA certificate when making password internal broker --------- Co-authored-by: August Andersen <[email protected]>
1 parent 7a1cad7 commit 9424f1e

File tree

43 files changed

+1598
-869
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1598
-869
lines changed

src/app/applications/bulk-import/bulk-import.component.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
import { IoTDeviceService } from '@applications/iot-devices/iot-device.service';
1010
import { faDownload, faTrash } from '@fortawesome/free-solid-svg-icons';
1111
import { TranslateService } from '@ngx-translate/core';
12-
import { OrganizationAccessScope } from '@shared/enums/access-scopes';
1312
import { ErrorMessageService } from '@shared/error-message.service';
1413
import { splitList } from '@shared/helpers/array.helper';
1514
import { Download } from '@shared/helpers/download.helper';
@@ -20,7 +19,7 @@ import { Papa } from 'ngx-papaparse';
2019
import { Observable, Subject } from 'rxjs';
2120
import { takeWhile } from 'rxjs/operators';
2221
import { BulkImport } from './bulk-import.model';
23-
import { BulkMapping } from './bulkMapping';
22+
import { BulkMapping } from './bulk-mapping';
2423

2524
@Component({
2625
selector: 'app-bulk-import',
@@ -54,6 +53,14 @@ export class BulkImportComponent implements OnInit {
5453
name: 'lorawan-abp-sample.csv',
5554
url: '../../../assets/docs/iotdevice_lorawan_abp.csv',
5655
},
56+
{
57+
name: 'mqtt-internal-broker-sample.csv',
58+
url: '../../../assets/docs/mqtt_internal_broker_sample.csv',
59+
},
60+
{
61+
name: 'mqtt-external-broker-sample.csv',
62+
url: '../../../assets/docs/mqtt_external_broker_sample.csv',
63+
},
5764
];
5865
download$: Observable<Download>;
5966
private bulkMapper = new BulkMapping();
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { IotDevice } from '@applications/iot-devices/iot-device.model';
2+
import { DeviceType } from '@shared/enums/device-type';
3+
import { Buffer } from 'buffer';
4+
5+
export class BulkMapping {
6+
public dataMapper(data: IotDevice, applicationId: number): IotDevice {
7+
switch (data.type.toUpperCase()) {
8+
case DeviceType.LORAWAN:
9+
return this.lorawanMapper(data, applicationId);
10+
case DeviceType.MQTT_INTERNAL_BROKER:
11+
return this.mqttInternalBrokerMapper(data, applicationId);
12+
case DeviceType.MQTT_EXTERNAL_BROKER:
13+
return this.mqttExternalBrokerMapper(data, applicationId);
14+
case DeviceType.GENERIC_HTTP:
15+
return this.baseMapper(data, applicationId);
16+
default:
17+
break;
18+
}
19+
}
20+
21+
private lorawanMapper(data: any, applicationId): IotDevice {
22+
const newDevice = this.baseMapper(data, applicationId);
23+
newDevice.lorawanSettings = {
24+
devEUI: data.devEUI,
25+
skipFCntCheck: data.skipFCntCheck
26+
? this.convertToBoolean(data.skipFCntCheck)
27+
: undefined,
28+
activationType: data.activationType ? data.activationType : undefined,
29+
OTAAapplicationKey: data.OTAAapplicationKey
30+
? data.OTAAapplicationKey
31+
: undefined,
32+
devAddr: data.devAddr ? data.devAddr : undefined,
33+
networkSessionKey: data.networkSessionKey
34+
? data.networkSessionKey
35+
: undefined,
36+
applicationSessionKey: data.applicationSessionKey
37+
? data.applicationSessionKey
38+
: undefined,
39+
serviceProfileID: data.serviceProfileID
40+
? data.serviceProfileID
41+
: undefined,
42+
deviceProfileID: data.deviceProfileID ? data.deviceProfileID : undefined,
43+
fCntUp: data.fCntUp ? +data.fCntUp : undefined,
44+
nFCntDown: data.nFCntDown ? +data.nFCntDown : undefined,
45+
deviceStatusBattery: undefined,
46+
deviceStatusMargin: undefined,
47+
};
48+
newDevice.type = DeviceType.LORAWAN;
49+
return newDevice;
50+
}
51+
52+
private mqttInternalBrokerMapper(data: any, applicationId: number) {
53+
const newDevice = this.baseMapper(data, applicationId);
54+
newDevice.mqttInternalBrokerSettings = {
55+
authenticationType: data.authenticationType,
56+
caCertificate: undefined,
57+
deviceCertificate: undefined,
58+
deviceCertificateKey: undefined,
59+
mqttPort: undefined,
60+
mqttURL: undefined,
61+
mqtttopicname: undefined,
62+
mqttusername: data.mqttusername,
63+
mqttpassword: data.mqttpassword,
64+
};
65+
newDevice.type = DeviceType.MQTT_INTERNAL_BROKER;
66+
return newDevice;
67+
}
68+
69+
private mqttExternalBrokerMapper(data: any, applicationId: number) {
70+
const newDevice = this.baseMapper(data, applicationId);
71+
newDevice.mqttExternalBrokerSettings = {
72+
authenticationType: data.authenticationType,
73+
caCertificate: this.base64Decode(data.caCertificate),
74+
deviceCertificate: this.base64Decode(data.deviceCertificate),
75+
deviceCertificateKey: this.base64Decode(data.deviceCertificateKey),
76+
mqttPort: data.mqttPort ? Number(data.mqttPort) : undefined,
77+
mqttURL: data.mqttURL,
78+
mqtttopicname: data.mqtttopicname,
79+
mqttpassword: data.mqttpassword,
80+
mqttusername: data.mqttusername,
81+
invalidMqttConfig: data.invalidMqttConfig,
82+
};
83+
newDevice.type = DeviceType.MQTT_EXTERNAL_BROKER;
84+
return newDevice;
85+
}
86+
87+
private base64Decode(input: string) {
88+
if (!input) {
89+
return undefined;
90+
}
91+
return Buffer.from(input, 'base64').toString('binary');
92+
}
93+
94+
private convertToBoolean(text: string): boolean {
95+
if (text.toUpperCase() === 'TRUE') {
96+
return true;
97+
} else {
98+
return false;
99+
}
100+
}
101+
102+
private baseMapper(data: any, applicationId: number): IotDevice {
103+
return {
104+
name: data.name,
105+
application: undefined,
106+
location: undefined,
107+
commentOnLocation: data.commentOnLocation,
108+
comment: data.comment,
109+
type: DeviceType.GENERIC_HTTP,
110+
receivedMessagesMetadata: undefined,
111+
metadata: undefined,
112+
apiKey: undefined,
113+
id: undefined,
114+
createdAt: undefined,
115+
updatedAt: undefined,
116+
applicationId: applicationId,
117+
longitude: data.longitude ? Number(data.longitude) : 0,
118+
latitude: data.latitude ? Number(data.latitude) : 0,
119+
latestReceivedMessage: undefined,
120+
lorawanSettings: undefined,
121+
sigfoxSettings: undefined,
122+
mqttInternalBrokerSettings: undefined,
123+
mqttExternalBrokerSettings: undefined,
124+
createdBy: undefined,
125+
updatedBy: undefined,
126+
updatedByName: undefined,
127+
createdByName: undefined,
128+
deviceModelId: data.deviceModelId != '' ? +data.deviceModelId : undefined,
129+
};
130+
}
131+
}

src/app/applications/bulk-import/bulkMapping.ts

Lines changed: 0 additions & 76 deletions
This file was deleted.

src/app/applications/iot-devices/iot-device-detail/iot-device-detail-generic/iot-device-detail-generic.component.html

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,44 @@
22
<div class="col-md-6 d-flex align-items-stretch">
33
<div class="jumbotron jumbotron--m-right jumbotron--full-width">
44
<h3>{{ 'IOTDEVICE.DETAIL' | translate }}</h3>
5-
<!-- Battery -->
65
<p class="mr-1"><strong>{{ 'IOT-TABLE.APPLICATION' | translate }}</strong>{{device.application.name}}</p>
76
<app-general-details [data]="device"></app-general-details>
7+
<!-- Battery -->
8+
<ng-template [ngIf]="device.type === DeviceType.LORAWAN">
89
<mat-divider></mat-divider>
9-
<div class="d-flex align-items-center">
10+
<div class="d-flex align-items-center">
1011
<p class="mr-1"><strong>{{ 'IOT-TABLE.BATTERY' | translate }}</strong></p>
11-
<app-batteri-status *ngIf="device.type === 'LORAWAN'; else noBatteryStatus" [color]="batteryStatusColor"
12-
[percentage]="batteryStatusPercentage"></app-batteri-status>
13-
<ng-template #noBatteryStatus>
14-
<div>
15-
<p>{{ 'IOTDEVICE-TABLE-ROW.NOT-SUPPORTED-SHORT' | translate }}</p>
16-
</div>
17-
</ng-template>
18-
<ng-template #notAvailable>
19-
<span>{{'IOTDEVICE-TABLE-ROW.NOT-AVAILABLE' | translate}}</span>
20-
</ng-template>
21-
</div>
12+
<app-batteri-status [color]="batteryStatusColor"
13+
[percentage]="batteryStatusPercentage"></app-batteri-status>
14+
</div>
15+
</ng-template>
2216
<mat-divider></mat-divider>
2317

2418
<!-- Unit type -->
2519
<p> <strong>{{ 'IOTDEVICE.TYPE' | translate }}</strong>{{ 'IOT-DEVICE-TYPES.' + device.type | translate}}
2620
</p>
2721
<!-- IF Lora device-->
28-
<ng-container *ngIf="device.type === 'LORAWAN' && device.lorawanSettings">
22+
<ng-container *ngIf="device.type === DeviceType.LORAWAN && device.lorawanSettings">
2923
<app-iot-device-detail-lorawan [device]="device"></app-iot-device-detail-lorawan>
3024
</ng-container>
3125
<!-- SIGFOX DEVICE-->
32-
<ng-container *ngIf="device.type === 'SIGFOX' && device.sigfoxSettings">
26+
<ng-container *ngIf="device.type === DeviceType.SIGFOX && device.sigfoxSettings">
3327
<app-iot-device-detail-sigfox [device]="device"></app-iot-device-detail-sigfox>
3428
</ng-container>
35-
<ng-container *ngIf="device.type === 'GENERIC_HTTP'">
29+
<ng-container *ngIf="device.type === DeviceType.GENERIC_HTTP">
3630
<mat-divider></mat-divider>
3731
<p>
3832
<strong>{{ 'IOTDEVICE.GENERIC_HTTP.APIKEY' | translate }}</strong>
3933
<span class="pre text-break">{{ httpDeviceUrl }}</span>
4034
</p>
4135
</ng-container>
36+
<ng-container *ngIf="device.type === DeviceType.MQTT_INTERNAL_BROKER">
37+
<app-iot-device-details-mqtt-internal-broker [device]="device" ></app-iot-device-details-mqtt-internal-broker>
38+
</ng-container>
39+
<ng-container *ngIf="device.type === DeviceType.MQTT_EXTERNAL_BROKER">
40+
<p class="alarmText" *ngIf="device.mqttExternalBrokerSettings.invalidMqttConfig">{{ 'IOTDEVICE.MQTT.NOCONNECTION' | translate}}</p>
41+
<app-iot-device-details-mqtt-external-broker [device]="device"></app-iot-device-details-mqtt-external-broker>
42+
</ng-container>
4243
<!-- Comment -->
4344
<mat-divider></mat-divider>
4445
<p>

src/app/applications/iot-devices/iot-device-detail/iot-device-detail-generic/iot-device-detail-generic.component.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@
44
white-space: -o-pre-wrap;
55
font-family: monospace;
66
}
7+
8+
.alarmText{
9+
color: #ff0000;
10+
font-size: 1em;
11+
}

src/app/applications/iot-devices/iot-device-detail/iot-device-detail-generic/iot-device-detail-generic.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { environment } from '@environments/environment';
1414
import { TranslateService } from '@ngx-translate/core';
1515
import { jsonToList } from '@shared/helpers/json.helper';
1616
import { KeyValue } from '@shared/types/tuple.type';
17+
import { DeviceType } from '@shared/enums/device-type';
1718

1819
@Component({
1920
selector: 'app-iot-device-detail-generic',
@@ -45,7 +46,6 @@ export class IotDeviceDetailGenericComponent
4546
ngOnChanges(changes: SimpleChanges): void {
4647
this.batteryStatusPercentage = this.getBatteryProcentage();
4748
this.httpDeviceUrl = this.getGenericHttpDeviceUrl();
48-
4949
if (
5050
changes?.device?.previousValue?.metadata !==
5151
changes?.device?.currentValue?.metadata &&
@@ -83,4 +83,6 @@ export class IotDeviceDetailGenericComponent
8383
}
8484

8585
ngOnDestroy(): void {}
86+
87+
protected readonly DeviceType = DeviceType;
8688
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<p><strong>{{ 'IOTDEVICE.MQTT.URL' | translate }}</strong>{{ device.mqttExternalBrokerSettings.mqttURL }}</p>
2+
<p><strong>{{ 'IOTDEVICE.MQTT.PORT' | translate }}</strong>{{ device.mqttExternalBrokerSettings.mqttPort }}</p>
3+
<p><strong>{{ 'IOTDEVICE.MQTT.TOPIC_NAME' | translate }}</strong>{{ device.mqttExternalBrokerSettings.mqtttopicname }}</p>
4+
<ng-container *ngIf="device.mqttExternalBrokerSettings.authenticationType === AuthenticationType.PASSWORD; else certificate">
5+
<p><strong>{{ 'QUESTION.MQTT.USERNAME-LABEL' | translate }}</strong>{{ device.mqttExternalBrokerSettings.mqttusername }}</p>
6+
</ng-container>
7+
<ng-template #certificate>
8+
<p><strong>{{ 'QUESTION.MQTT.CA-CERTIFICATE' | translate }}</strong><textarea class="form-control" [readOnly]="true">{{ device.mqttExternalBrokerSettings.caCertificate }}</textarea></p>
9+
<p><strong>{{ 'QUESTION.MQTT.DEVICE-CERTIFICATE' | translate }}</strong><textarea class="form-control" [readOnly]="true">{{ device.mqttExternalBrokerSettings.deviceCertificate }}</textarea></p>
10+
<p><strong>{{ 'QUESTION.MQTT.DEVICE-CERTIFICATE-KEY' | translate }}</strong><textarea class="form-control" [readOnly]="true">{{ device.mqttExternalBrokerSettings.deviceCertificateKey }}</textarea></p>
11+
</ng-template>

src/app/applications/iot-devices/iot-device-detail/iot-device-details-mqtt-external-broker/iot-device-details-mqtt-external-broker.component.scss

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Component, Input, OnInit } from '@angular/core';
2+
import { IotDevice } from '@applications/iot-devices/iot-device.model';
3+
import { AuthenticationType } from '@shared/enums/authentication-type';
4+
5+
@Component({
6+
selector: 'app-iot-device-details-mqtt-external-broker',
7+
templateUrl: './iot-device-details-mqtt-external-broker.component.html',
8+
styleUrls: ['./iot-device-details-mqtt-external-broker.component.scss'],
9+
})
10+
export class IotDeviceDetailsMqttExternalBrokerComponent implements OnInit {
11+
@Input() device: IotDevice;
12+
13+
constructor() {}
14+
15+
ngOnInit(): void {}
16+
17+
protected readonly AuthenticationType = AuthenticationType;
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<p><strong>{{ 'IOTDEVICE.MQTT.URL' | translate }}</strong>{{ device.mqttInternalBrokerSettings.mqttURL }}</p>
2+
<p><strong>{{ 'IOTDEVICE.MQTT.PORT' | translate }}</strong>{{ device.mqttInternalBrokerSettings.mqttPort }}</p>
3+
<p><strong>{{ 'IOTDEVICE.MQTT.TOPIC_NAME' | translate }}</strong>{{device.mqttInternalBrokerSettings.mqtttopicname}}</p>
4+
5+
<ng-container *ngIf="device.mqttInternalBrokerSettings.authenticationType === AuthenticationType.PASSWORD">
6+
<p><strong>{{ 'QUESTION.MQTT.USERNAME-LABEL' | translate }}</strong>{{ device.mqttInternalBrokerSettings.mqttusername }}</p>
7+
</ng-container>
8+
<p><strong>{{ 'QUESTION.MQTT.CA-CERTIFICATE' | translate }}</strong>
9+
<button [cdkCopyToClipboard]="device.mqttInternalBrokerSettings.caCertificate" class="btn">{{ 'IOTDEVICE.MQTT.COPY-TO-CLIPBOARD' | translate }}</button>
10+
<button class="btn ml-2" (click)="downloadCaCertificate(device.mqttInternalBrokerSettings.caCertificate, 'ca.crt')">{{ 'IOTDEVICE.MQTT.DOWNLOAD' | translate }}</button>
11+
<textarea class="form-control mt-1" [readOnly]="true">{{ device.mqttInternalBrokerSettings.caCertificate }}</textarea></p>
12+
<ng-container *ngIf="device.mqttInternalBrokerSettings.authenticationType === AuthenticationType.CERTIFICATE">
13+
<p><strong>{{ 'QUESTION.MQTT.DEVICE-CERTIFICATE' | translate }}</strong>
14+
<button [cdkCopyToClipboard]="device.mqttInternalBrokerSettings.deviceCertificate" class="btn">{{ 'IOTDEVICE.MQTT.COPY-TO-CLIPBOARD' | translate }}</button>
15+
<button class="btn ml-2" (click)="downloadCaCertificate(device.mqttInternalBrokerSettings.deviceCertificate, device.name +'.crt')">{{ 'IOTDEVICE.MQTT.DOWNLOAD' | translate }}</button>
16+
<textarea class="form-control mt-1" [readOnly]="true">{{ device.mqttInternalBrokerSettings.deviceCertificate }}</textarea>
17+
</p>
18+
<p><strong>{{ 'QUESTION.MQTT.DEVICE-CERTIFICATE-KEY' | translate }}</strong>
19+
<button [cdkCopyToClipboard]="device.mqttInternalBrokerSettings.deviceCertificateKey" class="btn">{{ 'IOTDEVICE.MQTT.COPY-TO-CLIPBOARD' | translate }}</button>
20+
<button class="btn ml-2" (click)="downloadCaCertificate(device.mqttInternalBrokerSettings.deviceCertificateKey, device.name + '.key')">{{ 'IOTDEVICE.MQTT.DOWNLOAD' | translate }}</button>
21+
<textarea class="form-control mt-1" [readOnly]="true">{{ device.mqttInternalBrokerSettings.deviceCertificateKey }}</textarea>
22+
</p>
23+
</ng-container>

0 commit comments

Comments
 (0)