diff --git a/src/entities/dto/list-all-entities.dto.ts b/src/entities/dto/list-all-entities.dto.ts index 5d9f7956..3a50d968 100644 --- a/src/entities/dto/list-all-entities.dto.ts +++ b/src/entities/dto/list-all-entities.dto.ts @@ -37,5 +37,8 @@ export class ListAllEntitiesDto { | "owner" | "contactPerson" | "personalData" - | "openDataDkEnabled"; + | "openDataDkEnabled" + | "deviceModel" + | "devices" + | "dataTargets"; } diff --git a/src/entities/lorawan-device.entity.ts b/src/entities/lorawan-device.entity.ts index 00249a9a..48c9f0fe 100644 --- a/src/entities/lorawan-device.entity.ts +++ b/src/entities/lorawan-device.entity.ts @@ -6,14 +6,17 @@ import { IoTDeviceType } from "@enum/device-type.enum"; @ChildEntity(IoTDeviceType.LoRaWAN) export class LoRaWANDevice extends IoTDevice { - /** - * This is used to identify the LoRaWAN device in Chirpstack, - * the remaining information is only stored in Chirpstack. - */ @Column({ nullable: true }) @Length(16, 16, { message: "Must be 16 characters" }) deviceEUI: string; + @Column({ nullable: true }) + @Length(32, 32, { message: "Must be 32 characters" }) + OTAAapplicationKey: string; + + @Column({ nullable: true }) + deviceProfileName: string; + @Column({ nullable: true }) chirpstackApplicationId: number; diff --git a/src/migration/1700748970060-added-chirpstack-data-to-iot-device.ts b/src/migration/1700748970060-added-chirpstack-data-to-iot-device.ts new file mode 100644 index 00000000..eb2290aa --- /dev/null +++ b/src/migration/1700748970060-added-chirpstack-data-to-iot-device.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddedChirpstackDataToIotDevice1700748970060 implements MigrationInterface { + name = 'AddedChirpstackDataToIotDevice1700748970060' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "iot_device" ADD "OTAAapplicationKey" character varying`); + await queryRunner.query(`ALTER TABLE "iot_device" ADD "deviceProfileName" character varying`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "iot_device" DROP COLUMN "deviceProfileName"`); + await queryRunner.query(`ALTER TABLE "iot_device" DROP COLUMN "OTAAapplicationKey"`); + } + +} diff --git a/src/modules/device-integrations/chirpstack-administration.module.ts b/src/modules/device-integrations/chirpstack-administration.module.ts index 1c56486f..f0ff2a88 100644 --- a/src/modules/device-integrations/chirpstack-administration.module.ts +++ b/src/modules/device-integrations/chirpstack-administration.module.ts @@ -18,7 +18,7 @@ import { NetworkServerController } from "@admin-controller/chirpstack/network-se ChirpstackGatewayController, ServiceProfileController, DeviceProfileController, - NetworkServerController + NetworkServerController, ], imports: [HttpModule, ConfigModule.forRoot({ load: [configuration] })], providers: [ @@ -29,6 +29,6 @@ import { NetworkServerController } from "@admin-controller/chirpstack/network-se DeviceProfileService, ChirpstackDeviceService, ], - exports: [ChirpstackDeviceService, ChirpstackGatewayService], + exports: [ChirpstackDeviceService, ChirpstackGatewayService, DeviceProfileService], }) export class ChirpstackAdministrationModule {} diff --git a/src/modules/device-management/iot-device.module.ts b/src/modules/device-management/iot-device.module.ts index f23107d7..3ced280b 100644 --- a/src/modules/device-management/iot-device.module.ts +++ b/src/modules/device-management/iot-device.module.ts @@ -18,6 +18,7 @@ import { ReceiveDataModule } from "@modules/device-integrations/receive-data.mod import { InternalMqttListenerModule } from "@modules/device-integrations/internal-mqtt-listener.module"; import { EncryptionHelperService } from "@services/encryption-helper.service"; import { CsvGeneratorService } from "@services/csv-generator.service"; +import { LorawanDeviceDatabaseEnrichJob } from "@services/device-management/lorawan-device-database-enrich-job"; @Module({ imports: [ @@ -38,6 +39,7 @@ import { CsvGeneratorService } from "@services/csv-generator.service"; PeriodicSigFoxCleanupService, IoTDeviceDownlinkService, SigFoxMessagesService, + LorawanDeviceDatabaseEnrichJob, MqttService, IoTDeviceService, EncryptionHelperService, diff --git a/src/services/chirpstack/chirpstack-device.service.ts b/src/services/chirpstack/chirpstack-device.service.ts index cb1e50c7..44424bf4 100644 --- a/src/services/chirpstack/chirpstack-device.service.ts +++ b/src/services/chirpstack/chirpstack-device.service.ts @@ -32,15 +32,18 @@ import { groupBy } from "lodash"; import { LoRaWANStatsResponseDto } from "@dto/chirpstack/device/lorawan-stats.response.dto"; import { ConfigService } from "@nestjs/config"; import { HttpService } from "@nestjs/axios"; +import { DeviceProfileService } from "@services/chirpstack/device-profile.service"; @Injectable() export class ChirpstackDeviceService extends GenericChirpstackConfigurationService { - constructor(internalHttpService: HttpService, private configService: ConfigService) { + constructor( + internalHttpService: HttpService, + private configService: ConfigService, + private deviceProfileService: DeviceProfileService + ) { super(internalHttpService); - this.deviceStatsIntervalInDays = configService.get( - "backend.deviceStatsIntervalInDays" - ); + this.deviceStatsIntervalInDays = configService.get("backend.deviceStatsIntervalInDays"); } private readonly logger = new Logger(ChirpstackDeviceService.name); @@ -65,17 +68,12 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi // if default exist use it let applicationId = applications.result.find( element => - element.serviceProfileID.toLowerCase() === - dto.device.serviceProfileID.toLowerCase() && + element.serviceProfileID.toLowerCase() === dto.device.serviceProfileID.toLowerCase() && element.name.startsWith(this.defaultApplicationName) )?.id; // otherwise create default if (!applicationId) { - applicationId = await this.createDefaultApplication( - applicationId, - dto, - organizationID - ); + applicationId = await this.createDefaultApplication(applicationId, dto, organizationID); } return +applicationId; @@ -88,9 +86,7 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi ) { applicationId = await this.createApplication({ application: { - name: `${ - this.defaultApplicationName - }-${dto.device.serviceProfileID.toLowerCase()}`.substring(0, 50), + name: `${this.defaultApplicationName}-${dto.device.serviceProfileID.toLowerCase()}`.substring(0, 50), description: this.DEFAULT_DESCRIPTION, organizationID: organizationID, serviceProfileID: dto.device.serviceProfileID, @@ -99,10 +95,7 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi return applicationId; } - makeCreateChirpstackDeviceDto( - dto: CreateLoRaWANSettingsDto, - name: string - ): CreateChirpstackDeviceDto { + makeCreateChirpstackDeviceDto(dto: CreateLoRaWANSettingsDto, name: string): CreateChirpstackDeviceDto { const csDto = new ChirpstackDeviceContentsDto(); csDto.name = `${this.DEVICE_NAME_PREFIX}${name}`.toLowerCase(); csDto.description = this.DEFAULT_DESCRIPTION; @@ -116,9 +109,7 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi return { device: csDto }; } - async overwriteDownlink( - dto: CreateChirpstackDeviceQueueItemDto - ): Promise { + async overwriteDownlink(dto: CreateChirpstackDeviceQueueItemDto): Promise { await this.deleteDownlinkQueue(dto.deviceQueueItem.devEUI); try { const res = await this.post( @@ -127,12 +118,9 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi ); return res.data; } catch (err) { - const fcntError = - "enqueue downlink payload error: get next downlink fcnt for deveui error"; + const fcntError = "enqueue downlink payload error: get next downlink fcnt for deveui error"; if (err?.response?.data?.error?.startsWith(fcntError)) { - throw new BadRequestException( - ErrorCodes.DeviceIsNotActivatedInChirpstack - ); + throw new BadRequestException(ErrorCodes.DeviceIsNotActivatedInChirpstack); } throw err; @@ -148,9 +136,7 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi } async getDownlinkQueue(deviceEUI: string): Promise { - const res = await this.get( - `devices/${deviceEUI}/queue` - ); + const res = await this.get(`devices/${deviceEUI}/queue`); return res; } @@ -177,20 +163,14 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi isUpdate ); if (res.status != 200) { - this.logger.warn( - `Could not ABP activate Chirpstack Device using body: ${JSON.stringify( - dto - )}` - ); + this.logger.warn(`Could not ABP activate Chirpstack Device using body: ${JSON.stringify(dto)}`); return false; } return res.status == 200; } async getAllDevicesStatus(): Promise { - return await this.get( - `devices?limit=10000&offset=0` - ); + return await this.get(`devices?limit=10000&offset=0`); } private async createOrUpdateABPActivation( @@ -223,11 +203,7 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi return { res, dto }; } - async activateDeviceWithOTAA( - deviceEUI: string, - nwkKey: string, - isUpdate: boolean - ): Promise { + async activateDeviceWithOTAA(deviceEUI: string, nwkKey: string, isUpdate: boolean): Promise { // http://localhost:8080/api/devices/0011223344557188/keys // {"deviceKeys":{"nwkKey":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","devEUI":"0011223344557188"}} @@ -244,9 +220,7 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi res = await this.post(`devices/${deviceEUI}/keys`, dto); } if (res.status != 200) { - this.logger.warn( - `Could not activate Chirpstack Device using body: ${JSON.stringify(dto)}` - ); + this.logger.warn(`Could not activate Chirpstack Device using body: ${JSON.stringify(dto)}`); return false; } return res.status == 200; @@ -264,9 +238,7 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi } if (res.status !== 200) { - this.logger.warn( - `Could not create Chirpstack Device using body: ${JSON.stringify(dto)}` - ); + this.logger.warn(`Could not create Chirpstack Device using body: ${JSON.stringify(dto)}`); return false; } @@ -274,12 +246,8 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi return true; } - async getChirpstackApplication( - id: string - ): Promise { - return await this.get( - `applications/${id}` - ); + async getChirpstackApplication(id: string): Promise { + return await this.get(`applications/${id}`); } async getChirpstackDevice(id: string): Promise { @@ -291,9 +259,7 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi async getKeys(deviceId: string): Promise { try { - const res = await this.get( - `devices/${deviceId}/keys` - ); + const res = await this.get(`devices/${deviceId}/keys`); return res.deviceKeys; } catch (err) { // Chirpstack returns 404 if keys are not saved .. @@ -301,13 +267,9 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi } } - async getActivation( - deviceId: string - ): Promise { + async getActivation(deviceId: string): Promise { try { - const res = await this.get( - `devices/${deviceId}/activation` - ); + const res = await this.get(`devices/${deviceId}/activation`); return res.deviceActivation; } catch (err) { return new ChirpstackDeviceActivationContentsDto(); @@ -336,17 +298,17 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi loraDevice.lorawanSettings.deviceStatusBattery = csData.deviceStatusBattery; loraDevice.lorawanSettings.deviceStatusMargin = csData.deviceStatusMargin; + const deviceProfile = await this.deviceProfileService.findOneDeviceProfileById(csData.deviceProfileID); + loraDevice.deviceProfileName = deviceProfile.deviceProfile.name; + const appMatch = applications.find(app => app.id === csData.applicationID); loraDevice.lorawanSettings.serviceProfileID = appMatch ? appMatch.serviceProfileID : loraDevice.lorawanSettings.serviceProfileID; if (!loraDevice.lorawanSettings.serviceProfileID) { - const csAppliation = await this.getChirpstackApplication( - csData.applicationID - ); - loraDevice.lorawanSettings.serviceProfileID = - csAppliation.application.serviceProfileID; + const csAppliation = await this.getChirpstackApplication(csData.applicationID); + loraDevice.lorawanSettings.serviceProfileID = csAppliation.application.serviceProfileID; } return loraDevice; @@ -358,6 +320,7 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi // OTAA loraDevice.lorawanSettings.activationType = ActivationType.OTAA; loraDevice.lorawanSettings.OTAAapplicationKey = keys.nwkKey; + loraDevice.OTAAapplicationKey = keys.nwkKey; } else { const activation = await this.getActivation(loraDevice.deviceEUI); if (activation.devAddr != null) { @@ -374,25 +337,16 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi } } - async isDeviceAlreadyCreated( - deviceEUI: string, - chirpstackIds: ChirpstackDeviceId[] = null - ): Promise { - const devices = !chirpstackIds - ? await this.getAllChirpstackDevices() - : chirpstackIds; - const alreadyExists = devices.some( - x => x.devEUI.toLowerCase() === deviceEUI.toLowerCase() - ); + async isDeviceAlreadyCreated(deviceEUI: string, chirpstackIds: ChirpstackDeviceId[] = null): Promise { + const devices = !chirpstackIds ? await this.getAllChirpstackDevices() : chirpstackIds; + const alreadyExists = devices.some(x => x.devEUI.toLowerCase() === deviceEUI.toLowerCase()); return alreadyExists; } getStats(deviceEUI: string): Promise { const now = new Date(); const to_time = now.toISOString(); - const from_time = new Date( - new Date().setDate(now.getDate() - this.deviceStatsIntervalInDays) - ).toISOString(); + const from_time = new Date(new Date().setDate(now.getDate() - this.deviceStatsIntervalInDays)).toISOString(); return this.get( `devices/${deviceEUI}/stats?interval=DAY&startTimestamp=${from_time}&endTimestamp=${to_time}` @@ -408,10 +362,7 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi public async getLoRaWANApplications( devices: LoRaWANDeviceWithChirpstackDataDto[] ): Promise { - const loraDevicesByAppId = groupBy( - devices, - device => device.chirpstackApplicationId - ); + const loraDevicesByAppId = groupBy(devices, device => device.chirpstackApplicationId); const res: ChirpstackSingleApplicationResponseDto[] = []; @@ -423,16 +374,11 @@ export class ChirpstackDeviceService extends GenericChirpstackConfigurationServi return res; } - private async getAllChirpstackDevices( - limit = 1000 - ): Promise { - return (await this.get(`devices?limit=${limit}`)) - .result; + private async getAllChirpstackDevices(limit = 1000): Promise { + return (await this.get(`devices?limit=${limit}`)).result; } - private async createApplication( - dto: CreateChirpstackApplicationDto - ): Promise { + private async createApplication(dto: CreateChirpstackApplicationDto): Promise { return (await this.post("applications", dto)).data.id; } } diff --git a/src/services/device-management/application.service.ts b/src/services/device-management/application.service.ts index 7aff9754..c6d34209 100644 --- a/src/services/device-management/application.service.ts +++ b/src/services/device-management/application.service.ts @@ -100,6 +100,16 @@ export class ApplicationService { order: sorting, }); + this.externalSortResult(query, result); + + return { + data: result, + count: total, + }; + } + + // Some sorting fields can't be done in the database + private externalSortResult(query: ListAllEntitiesDto, result: Application[]) { // Since openDataDkEnabled is not a database attribute sorting has to be done manually after reading if (query.orderOn === "openDataDkEnabled") { result.sort( @@ -109,11 +119,16 @@ export class ApplicationService { Number(!!b.dataTargets.find(t => t.type === DataTargetType.OpenDataDK))) ); } - - return { - data: result, - count: total, - }; + if (query.orderOn === "devices") { + result.sort( + (a, b) => (query.sort.toLowerCase() === "asc" ? 1 : -1) * (a.iotDevices.length - b.iotDevices.length) + ); + } + if (query.orderOn === "dataTargets") { + result.sort( + (a, b) => (query.sort.toLowerCase() === "asc" ? 1 : -1) * (a.dataTargets.length - b.dataTargets.length) + ); + } } async getApplicationsOnPermissionId( @@ -202,7 +217,7 @@ export class ApplicationService { } async findManyByIds(ids: number[]): Promise { - if (ids === null || ids?.length === 0) { + if (ids == null || ids?.length === 0) { return []; } return await this.applicationRepository.findBy({ id: In(ids) }); @@ -372,13 +387,22 @@ export class ApplicationService { .where('"iot_device"."applicationId" = :id', { id: appId }) .leftJoinAndSelect("iot_device.latestReceivedMessage", "metadata") .leftJoinAndSelect("iot_device.deviceModel", "deviceModel") + .leftJoinAndSelect("iot_device.connections", "connections") .skip(query?.offset ? +query.offset : 0) .take(query?.limit ? +query.limit : 100) .orderBy(orderByColumn, direction) + .addOrderBy("name", "ASC") .getManyAndCount(); + if (query.orderOn === "dataTargets") { + data.sort( + (a, b) => (query.sort.toLowerCase() === "asc" ? 1 : -1) * (a.connections.length - b.connections.length) + ); + } + // Fetch LoRa details one by one to get battery status. The LoRa API doesn't support query by multiple deveui's to reduce the calls. // Reduce calls by pre-fetching service profile ids by application id. The applications is usually the same + // TODO: Remove const loraDevices = data.filter( device => device.type === IoTDeviceType.LoRaWAN ) as LoRaWANDeviceWithChirpstackDataDto[]; @@ -402,12 +426,18 @@ export class ApplicationService { query.orderOn === "name" || query.orderOn === "active" || query.orderOn === "rssi" || - query.orderOn === "snr" + query.orderOn === "snr" || + query.orderOn === "deviceModel" || + query.orderOn === "dataTargets" ) { if (query.orderOn === "active") { orderBy = `metadata.sentTime`; } else if (query.orderOn === "rssi" || query.orderOn === "snr") { orderBy = `metadata.${query.orderOn}`; + } else if (query.orderOn === "deviceModel") { + orderBy = "deviceModel.body"; + } else if (query.orderOn === "dataTargets") { + orderBy = "connections.id"; } else { orderBy = `iot_device.${query.orderOn}`; } diff --git a/src/services/device-management/iot-device.service.ts b/src/services/device-management/iot-device.service.ts index ad6ae808..7cdfedf5 100644 --- a/src/services/device-management/iot-device.service.ts +++ b/src/services/device-management/iot-device.service.ts @@ -16,10 +16,7 @@ import { import { LoRaWANDeviceWithChirpstackDataDto } from "@dto/lorawan-device-with-chirpstack-data.dto"; import { SigFoxDeviceWithBackendDataDto } from "@dto/sigfox-device-with-backend-data.dto"; import { CreateSigFoxApiDeviceRequestDto } from "@dto/sigfox/external/create-sigfox-api-device-request.dto"; -import { - SigFoxApiDeviceContent, - SigFoxApiDeviceResponse, -} from "@dto/sigfox/external/sigfox-api-device-response.dto"; +import { SigFoxApiDeviceContent, SigFoxApiDeviceResponse } from "@dto/sigfox/external/sigfox-api-device-response.dto"; import { UpdateSigFoxApiDeviceRequestDto } from "@dto/sigfox/external/update-sigfox-api-device-request.dto"; import { UpdateIoTDeviceDto } from "@dto/update-iot-device.dto"; import { Application } from "@entities/application.entity"; @@ -56,14 +53,7 @@ import { SigFoxApiDeviceTypeService } from "@services/sigfox/sigfox-api-device-t import { SigFoxApiDeviceService } from "@services/sigfox/sigfox-api-device.service"; import { SigFoxGroupService } from "@services/sigfox/sigfox-group.service"; import { SigFoxMessagesService } from "@services/sigfox/sigfox-messages.service"; -import { - DeleteResult, - EntityManager, - ILike, - In, - Repository, - SelectQueryBuilder, -} from "typeorm"; +import { DeleteResult, EntityManager, ILike, In, Repository, SelectQueryBuilder } from "typeorm"; import { v4 as uuidv4 } from "uuid"; import { DeviceModelService } from "./device-model.service"; import { IoTLoRaWANDeviceService } from "./iot-lorawan-device.service"; @@ -79,6 +69,7 @@ import { EncryptionHelperService } from "@services/encryption-helper.service"; import { CsvGeneratorService } from "@services/csv-generator.service"; import * as fs from "fs"; import { caCertPath } from "@resources/resource-paths"; +import { DeviceProfileService } from "@services/chirpstack/device-profile.service"; type IoTDeviceOrSpecialized = | IoTDevice @@ -113,7 +104,8 @@ export class IoTDeviceService { private mqttService: MqttService, private internalMqttClientListenerService: InternalMqttClientListenerService, private encryptionHelperService: EncryptionHelperService, - private csvGeneratorService: CsvGeneratorService + private csvGeneratorService: CsvGeneratorService, + private deviceProfileService: DeviceProfileService ) {} private readonly logger = new Logger(IoTDeviceService.name); @@ -139,10 +131,7 @@ export class IoTDeviceService { }); } - async findOneWithApplicationAndMetadata( - id: number, - enrich?: boolean - ): Promise { + async findOneWithApplicationAndMetadata(id: number, enrich?: boolean): Promise { // Repository syntax doesn't yet support ordering by relation: https://github.com/typeorm/typeorm/issues/2620 // Therefore we use the QueryBuilder ... const iotDevice = await this.queryDatabaseForIoTDevice(id); @@ -167,37 +156,25 @@ export class IoTDeviceService { return iotDevice; } - async findManyWithApplicationAndMetadata( - ids: number[] - ): Promise { + async findManyWithApplicationAndMetadata(ids: number[]): Promise { return this.queryDatabaseForIoTDevices(ids); } - async enrichSigFoxDevice( - iotDevice: IoTDevice - ): Promise { + async enrichSigFoxDevice(iotDevice: IoTDevice): Promise { const sigfoxDevice = iotDevice as SigFoxDeviceWithBackendDataDto; - const application = await this.applicationService.findOneWithOrganisation( - iotDevice.application.id - ); + const application = await this.applicationService.findOneWithOrganisation(iotDevice.application.id); const sigfoxGroup = await this.sigfoxGroupService.findOneByGroupId( sigfoxDevice.groupId, application.belongsTo.id ); - const thisDevice = await this.getDataFromSigFoxAboutDevice( - sigfoxGroup, - sigfoxDevice - ); + const thisDevice = await this.getDataFromSigFoxAboutDevice(sigfoxGroup, sigfoxDevice); if (!thisDevice) { throw new NotFoundException(ErrorCodes.SigfoxError); } - sigfoxDevice.sigfoxSettings = await this.mapSigFoxBackendDataToDto( - thisDevice, - sigfoxGroup - ); + sigfoxDevice.sigfoxSettings = await this.mapSigFoxBackendDataToDto(thisDevice, sigfoxGroup); return sigfoxDevice; } @@ -225,23 +202,16 @@ export class IoTDeviceService { limit: number, offset: number ): Promise { - const data: Promise< - IoTDeviceMinimalRaw[] - > = this.getQueryForFindAllByPayloadDecoder(payloadDecoderId) + const data: Promise = this.getQueryForFindAllByPayloadDecoder(payloadDecoderId) .addSelect('"application"."id"', "applicationId") .addSelect('"application"."belongsToId"', "organizationId") .limit(limit) .offset(offset) .getRawMany(); - const count = this.getQueryForFindAllByPayloadDecoder( - payloadDecoderId - ).getCount(); + const count = this.getQueryForFindAllByPayloadDecoder(payloadDecoderId).getCount(); - const transformedData: IoTDeviceMinimal[] = await this.mapToIoTDeviceMinimal( - data, - req - ); + const transformedData: IoTDeviceMinimal[] = await this.mapToIoTDeviceMinimal(data, req); return { data: transformedData, @@ -283,9 +253,7 @@ export class IoTDeviceService { return false; } - private getQueryForFindAllByPayloadDecoder( - payloadDecoderId: number - ): SelectQueryBuilder { + private getQueryForFindAllByPayloadDecoder(payloadDecoderId: number): SelectQueryBuilder { return this.iotDeviceRepository .createQueryBuilder("device") .innerJoin("device.application", "application") @@ -300,14 +268,8 @@ export class IoTDeviceService { * Avoid calling the endpoint /devices/:id at SigFox * https://support.sigfox.com/docs/api-rate-limiting */ - private async getDataFromSigFoxAboutDevice( - sigfoxGroup: SigFoxGroup, - sigfoxDevice: SigFoxDeviceWithBackendDataDto - ) { - const allDevices = await this.sigfoxApiDeviceService.getAllByGroupIds( - sigfoxGroup, - [sigfoxDevice.groupId] - ); + private async getDataFromSigFoxAboutDevice(sigfoxGroup: SigFoxGroup, sigfoxDevice: SigFoxDeviceWithBackendDataDto) { + const allDevices = await this.sigfoxApiDeviceService.getAllByGroupIds(sigfoxGroup, [sigfoxDevice.groupId]); const thisDevice = allDevices.data.find(x => x.id == sigfoxDevice.deviceId); return thisDevice; @@ -317,49 +279,30 @@ export class IoTDeviceService { return this.iotDeviceRepository .createQueryBuilder("iot_device") .loadAllRelationIds({ relations: ["createdBy", "updatedBy"] }) - .innerJoinAndSelect( - "iot_device.application", - "application", - 'application.id = iot_device."applicationId"' - ) - .leftJoinAndSelect( - "iot_device.receivedMessagesMetadata", - "metadata", - 'metadata."deviceId" = iot_device.id' - ) + .innerJoinAndSelect("iot_device.application", "application", 'application.id = iot_device."applicationId"') + .leftJoinAndSelect("iot_device.receivedMessagesMetadata", "metadata", 'metadata."deviceId" = iot_device.id') .leftJoinAndSelect( "iot_device.latestReceivedMessage", "receivedMessage", '"receivedMessage"."deviceId" = iot_device.id' ) - .leftJoinAndSelect( - "iot_device.deviceModel", - "device_model", - 'device_model.id = iot_device."deviceModelId"' - ) + .leftJoinAndSelect("iot_device.deviceModel", "device_model", 'device_model.id = iot_device."deviceModelId"') .orderBy('metadata."sentTime"', "DESC"); } private async queryDatabaseForIoTDevice(id: number) { - return await this.buildIoTDeviceWithRelationsQuery() - .where("iot_device.id = :id", { id: id }) - .getOne(); + return await this.buildIoTDeviceWithRelationsQuery().where("iot_device.id = :id", { id: id }).getOne(); } private queryDatabaseForIoTDevices(ids: number[]) { - return this.buildIoTDeviceWithRelationsQuery() - .where("iot_device.id IN (:...ids)", { ids }) - .getMany(); + return this.buildIoTDeviceWithRelationsQuery().where("iot_device.id IN (:...ids)", { ids }).getMany(); } async findGenericHttpDeviceByApiKey(key: string): Promise { return await this.genericHTTPDeviceRepository.findOneBy({ apiKey: key }); } - async findSigFoxDeviceByDeviceIdAndDeviceTypeId( - deviceId: string, - apiKey: string - ): Promise { + async findSigFoxDeviceByDeviceIdAndDeviceTypeId(deviceId: string, apiKey: string): Promise { return await this.sigfoxRepository.findOneBy({ deviceId, deviceTypeId: apiKey, @@ -372,6 +315,18 @@ export class IoTDeviceService { }); } + async findNonEnrichedLoRaWANDevices(): Promise { + return await this.loRaWANDeviceRepository + .createQueryBuilder("iot_device") + .where('"OTAAapplicationKey" is null') + .take(25) + .getMany(); + } + + async updateLocalLoRaWANDevices(devices: LoRaWANDevice[]): Promise { + await this.loRaWANDeviceRepository.save(devices); + } + async findMQTTDevice(id: number): Promise { return await this.mqttInternalBrokerDeviceRepository.findOne({ where: { id }, @@ -390,10 +345,7 @@ export class IoTDeviceService { }); } - async create( - createIoTDeviceDto: CreateIoTDeviceDto, - userId: number - ): Promise { + async create(createIoTDeviceDto: CreateIoTDeviceDto, userId: number): Promise { // Reuse the same logic for creating multiple devices. const iotDevice = await this.createMany([createIoTDeviceDto], userId); @@ -402,10 +354,7 @@ export class IoTDeviceService { return iotDevice[0].data; } - async createMany( - createIoTDeviceDtos: CreateIoTDeviceDto[], - userId: number - ): Promise { + async createMany(createIoTDeviceDtos: CreateIoTDeviceDto[], userId: number): Promise { const iotDevicesMaps: CreateIoTDeviceMapDto[] = []; // Translate each generic device to the specific type @@ -435,12 +384,8 @@ export class IoTDeviceService { } // Store or update valid devices on the database - const validIotDevices = validProcessedDevices.map( - iotDeviceMap => iotDeviceMap.iotDevice - ); - const dbIotDevices = validIotDevices.length - ? await this.iotDeviceRepository.save(validIotDevices) - : []; + const validIotDevices = validProcessedDevices.map(iotDeviceMap => iotDeviceMap.iotDevice); + const dbIotDevices = validIotDevices.length ? await this.iotDeviceRepository.save(validIotDevices) : []; // Set deviceId related values on new mqtt devices await this.handleNewMQTTDevices(dbIotDevices); @@ -454,16 +399,12 @@ export class IoTDeviceService { } async removeDownlink(sigfoxDevice: SigFoxDevice): Promise { - this.logger.log( - `Removing downlink from device(${sigfoxDevice.id}) sigfoxId(${sigfoxDevice.deviceId})` - ); + this.logger.log(`Removing downlink from device(${sigfoxDevice.id}) sigfoxId(${sigfoxDevice.deviceId})`); sigfoxDevice.downlinkPayload = null; return await this.iotDeviceRepository.save(sigfoxDevice); } - async getDownlinkForSigfox( - device: SigFoxDevice - ): Promise { + async getDownlinkForSigfox(device: SigFoxDevice): Promise { if (device.downlinkPayload != null) { return { totalCount: 1, @@ -480,15 +421,9 @@ export class IoTDeviceService { }; } - async update( - id: number, - updateDto: UpdateIoTDeviceDto, - userId: number - ): Promise { + async update(id: number, updateDto: UpdateIoTDeviceDto, userId: number): Promise { const existingIoTDevice = await this.iotDeviceRepository.findOneByOrFail({ id }); - const iotDeviceDtoMap: CreateIoTDeviceMapDto[] = [ - { iotDevice: existingIoTDevice, iotDeviceDto: updateDto }, - ]; + const iotDeviceDtoMap: CreateIoTDeviceMapDto[] = [{ iotDevice: existingIoTDevice, iotDeviceDto: updateDto }]; await this.validateDtoAndCreateIoTDevice(iotDeviceDtoMap, true); const mappedIoTDevice = iotDeviceDtoMap[0].iotDevice; @@ -498,22 +433,15 @@ export class IoTDeviceService { return res; } - async updateMany( - updateDto: UpdateIoTDeviceBatchDto, - userId: number - ): Promise { + async updateMany(updateDto: UpdateIoTDeviceBatchDto, userId: number): Promise { // Fetch existing devices from db and map them const existingDevices = await this.iotDeviceRepository.findBy({ id: In(updateDto.data.map(device => device.id)), }); - const iotDeviceMaps: CreateIoTDeviceMapDto[] = updateDto.data.map( - updateDevice => ({ - iotDeviceDto: updateDevice, - iotDevice: existingDevices.find( - existingDevice => existingDevice.id === updateDevice.id - ), - }) - ); + const iotDeviceMaps: CreateIoTDeviceMapDto[] = updateDto.data.map(updateDevice => ({ + iotDeviceDto: updateDevice, + iotDevice: existingDevices.find(existingDevice => existingDevice.id === updateDevice.id), + })); await this.validateDtoAndCreateIoTDevice(iotDeviceMaps, true); const validDevices = iotDeviceMaps.reduce((res: IoTDevice[], currentMap) => { @@ -559,9 +487,7 @@ export class IoTDeviceService { await this.chirpstackDeviceService.deleteDevice(lorawanDevice.deviceEUI); } else if (device.type === IoTDeviceType.MQTTExternalBroker) { - this.internalMqttClientListenerService.removeMQTTClient( - device as MQTTExternalBrokerDevice - ); + this.internalMqttClientListenerService.removeMQTTClient(device as MQTTExternalBrokerDevice); } }); @@ -589,10 +515,7 @@ export class IoTDeviceService { // Remove device from the mappings. It's important that each existing mapping has been fetched // as the new mappings will replace the existing ones. multicastsWithDevice.forEach( - multicast => - (multicast.iotDevices = multicast.iotDevices?.filter( - iotDevice => iotDevice.id !== device.id - )) + multicast => (multicast.iotDevices = multicast.iotDevices?.filter(iotDevice => iotDevice.id !== device.id)) ); await multicastRepository.save(multicastsWithDevice); } @@ -607,9 +530,7 @@ export class IoTDeviceService { switch (device.type) { case IoTDeviceType.LoRaWAN: - const loraData = await this.chirpstackDeviceService.getStats( - (device as LoRaWANDevice).deviceEUI - ); + const loraData = await this.chirpstackDeviceService.getStats((device as LoRaWANDevice).deviceEUI); return loraData.result.map(loraStat => ({ timestamp: loraStat.timestamp, @@ -618,11 +539,7 @@ export class IoTDeviceService { rxPacketsPerDr: loraStat.rxPacketsPerDr, })); case IoTDeviceType.SigFox: - const sigFoxData = await this.sigfoxMessagesService.getMessageSignals( - device.id, - fromDate, - toDate - ); + const sigFoxData = await this.sigfoxMessagesService.getMessageSignals(device.id, fromDate, toDate); // SigFox data might contain data points on the same day. They have to be averaged const sortedStats = sigFoxData @@ -631,11 +548,7 @@ export class IoTDeviceService { rssi: data.rssi, snr: data.snr, })) - .sort( - (a, b) => - new Date(a.timestamp).getTime() - - new Date(b.timestamp).getTime() - ); + .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); const averagedStats = this.averageStatsForSameDay(sortedStats); return averagedStats; default: @@ -645,13 +558,7 @@ export class IoTDeviceService { private averageStatsForSameDay(stats: DeviceStatsResponseDto[]) { const statsSummed = stats.reduce( - ( - res: Record< - string, - { timestamp: string; count: number; rssi: number; snr: number } - >, - item - ) => { + (res: Record, item) => { // Assume that the date is ISO formatted and extract only the date. const dateWithoutTime = item.timestamp.split("T")[0]; res[dateWithoutTime] = res.hasOwnProperty(dateWithoutTime) @@ -672,13 +579,11 @@ export class IoTDeviceService { {} ); - const averagedStats: DeviceStatsResponseDto[] = Object.entries(statsSummed).map( - ([_key, item]) => ({ - timestamp: item.timestamp, - rssi: item.rssi / item.count, - snr: item.snr / item.count, - }) - ); + const averagedStats: DeviceStatsResponseDto[] = Object.entries(statsSummed).map(([_key, item]) => ({ + timestamp: item.timestamp, + rssi: item.rssi / item.count, + snr: item.snr / item.count, + })); return averagedStats; } @@ -707,9 +612,7 @@ export class IoTDeviceService { for (const map of iotDeviceMaps) { const { iotDevice, iotDeviceDto } = map; try { - const application = applications.find( - app => app.id === iotDeviceDto.applicationId - ); + const application = applications.find(app => app.id === iotDeviceDto.applicationId); iotDevice.name = iotDeviceDto.name; iotDevice.application = application; @@ -734,10 +637,7 @@ export class IoTDeviceService { // Filter devices whose properties couldn't be set await this.mapDeviceModels(filterValidIotDeviceMaps(iotDeviceMaps)); // Filter devices which didn't have a valid device model - await this.mapChildDtoToIoTDevice( - filterValidIotDeviceMaps(iotDeviceMaps), - isUpdate - ); + await this.mapChildDtoToIoTDevice(filterValidIotDeviceMaps(iotDeviceMaps), isUpdate); } async mapDeviceModels(iotDevicesDtoMap: CreateIoTDeviceMapDto[]): Promise { @@ -750,9 +650,7 @@ export class IoTDeviceService { return ids; }, []); - const deviceModels = await this.deviceModelService.getByIdsWithRelations( - deviceModelIds - ); + const deviceModels = await this.deviceModelService.getByIdsWithRelations(deviceModelIds); const applicationIds = iotDevicesDtoMap.reduce((ids: number[], dto) => { if (dto.iotDeviceDto.applicationId) { @@ -761,9 +659,7 @@ export class IoTDeviceService { return ids; }, []); - const applications = await this.applicationService.findManyWithOrganisation( - applicationIds - ); + const applications = await this.applicationService.findManyWithOrganisation(applicationIds); // Ensure that each device model is assignable this.setDeviceModel(iotDevicesDtoMap, applications, deviceModels); @@ -775,9 +671,7 @@ export class IoTDeviceService { deviceModels: DeviceModel[] ) { for (const map of iotDevicesDtoMap) { - const applicationMatch = applications.find( - application => application.id === map.iotDevice.application.id - ); + const applicationMatch = applications.find(application => application.id === map.iotDevice.application.id); if (!applicationMatch) { map.error = { @@ -788,9 +682,7 @@ export class IoTDeviceService { // Validate DeviceModel if set if (map.iotDeviceDto.deviceModelId) { - const deviceModelMatch = deviceModels.find( - model => model.id === map.iotDeviceDto.deviceModelId - ); + const deviceModelMatch = deviceModels.find(model => model.id === map.iotDeviceDto.deviceModelId); if (!deviceModelMatch) { map.error = { message: ErrorCodes.DeviceModelDoesNotExist }; @@ -809,23 +701,16 @@ export class IoTDeviceService { } } - resetHttpDeviceApiKey( - httpDevice: GenericHTTPDevice - ): Promise { + resetHttpDeviceApiKey(httpDevice: GenericHTTPDevice): Promise { httpDevice.apiKey = uuidv4(); return this.iotDeviceRepository.save(httpDevice); } private async getApplicationsByIds(applicationIds: number[]) { - return applicationIds.length - ? await this.applicationService.findManyByIds(applicationIds) - : []; + return applicationIds.length ? await this.applicationService.findManyByIds(applicationIds) : []; } - private async mapChildDtoToIoTDevice( - iotDevicesDtoMap: CreateIoTDeviceMapDto[], - isUpdate: boolean - ): Promise { + public async mapChildDtoToIoTDevice(iotDevicesDtoMap: CreateIoTDeviceMapDto[], isUpdate: boolean): Promise { // Pre-fetch lorawan settings, if any const loraDeviceEuis = await this.getLorawanDeviceEuis(iotDevicesDtoMap); const loraOrganizationId = await this.chirpstackDeviceService.getDefaultOrganizationId(); @@ -838,7 +723,7 @@ export class IoTDeviceService { try { if (map.iotDevice.constructor.name === LoRaWANDevice.name) { const cast = map.iotDevice as LoRaWANDevice; - map.iotDevice = await this.mapLoRaWANDevice( + map.iotDevice = await this.mapAndCreateLoRaWANDevice( map.iotDeviceDto, cast, isUpdate, @@ -848,29 +733,16 @@ export class IoTDeviceService { } else if (map.iotDevice.constructor.name === SigFoxDevice.name) { const cast = map.iotDevice as SigFoxDevice; map.iotDevice = await this.mapSigFoxDevice(map.iotDeviceDto, cast); - } else if ( - map.iotDevice.constructor.name === MQTTInternalBrokerDevice.name - ) { + } else if (map.iotDevice.constructor.name === MQTTInternalBrokerDevice.name) { const cast = map.iotDevice as MQTTInternalBrokerDevice; - map.iotDevice = await this.mapMQTTInternalBrokerDevice( - map.iotDeviceDto, - cast - ); - } else if ( - map.iotDevice.constructor.name === MQTTExternalBrokerDevice.name - ) { + map.iotDevice = await this.mapMQTTInternalBrokerDevice(map.iotDeviceDto, cast); + } else if (map.iotDevice.constructor.name === MQTTExternalBrokerDevice.name) { const cast = map.iotDevice as MQTTExternalBrokerDevice; - map.iotDevice = await this.mapMQTTExternalBrokerDevice( - map.iotDeviceDto, - cast, - isUpdate - ); + map.iotDevice = await this.mapMQTTExternalBrokerDevice(map.iotDeviceDto, cast, isUpdate); } } catch (error) { map.error = { - message: - (error as Error)?.message ?? - ErrorCodes.FailedToCreateOrUpdateIotDevice, + message: (error as Error)?.message ?? ErrorCodes.FailedToCreateOrUpdateIotDevice, }; } } @@ -879,19 +751,13 @@ export class IoTDeviceService { private async getLorawanDeviceEuis( iotDevicesDtoMap: CreateIoTDeviceMapDto[] ): Promise { - const iotLorawanDevices = iotDevicesDtoMap.reduce( - (res: string[], { iotDevice, iotDeviceDto }) => { - if ( - iotDevice.constructor.name === LoRaWANDevice.name && - iotDeviceDto.lorawanSettings - ) { - res.push(iotDeviceDto.lorawanSettings.devEUI); - } + const iotLorawanDevices = iotDevicesDtoMap.reduce((res: string[], { iotDevice, iotDeviceDto }) => { + if (iotDevice.constructor.name === LoRaWANDevice.name && iotDeviceDto.lorawanSettings) { + res.push(iotDeviceDto.lorawanSettings.devEUI); + } - return res; - }, - [] - ); + return res; + }, []); const loraDeviceEuis = !iotLorawanDevices.length ? [] @@ -901,32 +767,20 @@ export class IoTDeviceService { return loraDeviceEuis.map(loraDevice => ({ devEUI: loraDevice.deviceEUI })); } - private async mapSigFoxDevice( - dto: CreateIoTDeviceDto, - cast: SigFoxDevice - ): Promise { + private async mapSigFoxDevice(dto: CreateIoTDeviceDto, cast: SigFoxDevice): Promise { cast.deviceId = dto?.sigfoxSettings?.deviceId; cast.deviceTypeId = dto?.sigfoxSettings?.deviceTypeId; - const sigfoxGroup = await this.sigfoxGroupService.findOneWithPassword( - dto.sigfoxSettings.groupId - ); + const sigfoxGroup = await this.sigfoxGroupService.findOneWithPassword(dto.sigfoxSettings.groupId); cast.groupId = sigfoxGroup.sigfoxGroupId; await this.createOrUpdateSigFoxDevice(dto, sigfoxGroup, cast); - await this.sigfoxApiDeviceTypeService.addOrUpdateCallback( - sigfoxGroup, - cast.deviceTypeId - ); + await this.sigfoxApiDeviceTypeService.addOrUpdateCallback(sigfoxGroup, cast.deviceTypeId); return cast; } - private async createOrUpdateSigFoxDevice( - dto: CreateIoTDeviceDto, - sigfoxGroup: SigFoxGroup, - cast: SigFoxDevice - ) { + private async createOrUpdateSigFoxDevice(dto: CreateIoTDeviceDto, sigfoxGroup: SigFoxGroup, cast: SigFoxDevice) { if (dto?.sigfoxSettings?.connectToExistingDeviceInBackend == false) { // Create device in sigfox backend const res = await this.createInSigfoxBackend(dto, sigfoxGroup); @@ -934,10 +788,7 @@ export class IoTDeviceService { } else { // Ensure that the device exists try { - const res = await this.sigfoxApiDeviceService.getByIdSimple( - sigfoxGroup, - cast.deviceId - ); + const res = await this.sigfoxApiDeviceService.getByIdSimple(sigfoxGroup, cast.deviceId); cast.deviceId = res.id; cast.deviceTypeId = res.deviceType.id; await this.doEditInSigFoxBackend(res, dto, sigfoxGroup, cast); @@ -945,20 +796,13 @@ export class IoTDeviceService { if (err?.status == 429) { throw err; } - throw new BadRequestException( - ErrorCodes.DeviceDoesNotExistInSigFoxForGroup - ); + throw new BadRequestException(ErrorCodes.DeviceDoesNotExistInSigFoxForGroup); } } } - async getAllSigfoxDevicesByGroup( - group: SigFoxGroup, - removeExisting: boolean - ): Promise { - const devices = await this.sigfoxApiDeviceService.getAllByGroupIds(group, [ - group.sigfoxGroupId, - ]); + async getAllSigfoxDevicesByGroup(group: SigFoxGroup, removeExisting: boolean): Promise { + const devices = await this.sigfoxApiDeviceService.getAllByGroupIds(group, [group.sigfoxGroupId]); if (removeExisting) { const sigfoxDeviceIdsInUse = await this.sigfoxRepository.find({ @@ -999,18 +843,8 @@ export class IoTDeviceService { sigfoxDevice: SigFoxDevice ) { await Promise.all([ - this.updateSigFoxDevice( - currentSigFoxSettings, - dto, - sigfoxGroup, - sigfoxDevice - ), - this.changeDeviceTypeIfNeeded( - currentSigFoxSettings, - dto, - sigfoxGroup, - sigfoxDevice - ), + this.updateSigFoxDevice(currentSigFoxSettings, dto, sigfoxGroup, sigfoxDevice), + this.changeDeviceTypeIfNeeded(currentSigFoxSettings, dto, sigfoxGroup, sigfoxDevice), ]); } @@ -1028,11 +862,7 @@ export class IoTDeviceService { name: dto.name, }; - await this.sigfoxApiDeviceService.update( - sigfoxGroup, - sigfoxDevice.deviceId, - updateDto - ); + await this.sigfoxApiDeviceService.update(sigfoxGroup, sigfoxDevice.deviceId, updateDto); } private async changeDeviceTypeIfNeeded( @@ -1056,10 +886,7 @@ export class IoTDeviceService { } } - private async createInSigfoxBackend( - dto: CreateIoTDeviceDto, - sigfoxGroup: SigFoxGroup - ) { + private async createInSigfoxBackend(dto: CreateIoTDeviceDto, sigfoxGroup: SigFoxGroup) { const sigfoxDto: CreateSigFoxApiDeviceRequestDto = this.mapToSigFoxDto(dto); try { @@ -1091,7 +918,7 @@ export class IoTDeviceService { return sigfoxDto; } - private async mapLoRaWANDevice( + private async mapAndCreateLoRaWANDevice( dto: CreateIoTDeviceDto, lorawanDevice: LoRaWANDevice, isUpdate: boolean, @@ -1102,10 +929,7 @@ export class IoTDeviceService { if ( !isUpdate && - (await this.chirpstackDeviceService.isDeviceAlreadyCreated( - dto.lorawanSettings.devEUI, - lorawanDeviceEuis - )) + (await this.chirpstackDeviceService.isDeviceAlreadyCreated(dto.lorawanSettings.devEUI, lorawanDeviceEuis)) ) { throw new BadRequestException(ErrorCodes.IdInvalidOrAlreadyInUse); } @@ -1124,12 +948,14 @@ export class IoTDeviceService { chirpstackDeviceDto.device.applicationID = applicationId.toString(); // Create or update the LoRa device against Chirpstack API - await this.chirpstackDeviceService.createOrUpdateDevice( - chirpstackDeviceDto, - lorawanDeviceEuis - ); + await this.chirpstackDeviceService.createOrUpdateDevice(chirpstackDeviceDto, lorawanDeviceEuis); lorawanDeviceEuis.push(chirpstackDeviceDto.device); await this.doActivation(dto, isUpdate); + lorawanDevice.OTAAapplicationKey = dto.lorawanSettings.OTAAapplicationKey; + const deviceProfile = await this.deviceProfileService.findOneDeviceProfileById( + dto.lorawanSettings.deviceProfileID + ); + lorawanDevice.deviceProfileName = deviceProfile.deviceProfile.name; } catch (err) { this.logger.error(err); @@ -1143,10 +969,7 @@ export class IoTDeviceService { return lorawanDevice; } - private async doActivation( - dto: CreateIoTDeviceDto, - isUpdate: boolean - ): Promise { + private async doActivation(dto: CreateIoTDeviceDto, isUpdate: boolean): Promise { if (dto.lorawanSettings.activationType == ActivationType.OTAA) { // OTAA Activate if key is provided await this.doActivationByOTAA(dto, isUpdate); @@ -1198,19 +1021,13 @@ export class IoTDeviceService { cast.authenticationType = settings.authenticationType; switch (cast.authenticationType) { case AuthenticationType.PASSWORD: - cast.mqttpasswordhash = this.mqttService.hashPassword( - settings.mqttpassword - ); - cast.mqttpassword = this.encryptionHelperService.basicEncrypt( - settings.mqttpassword - ); + cast.mqttpasswordhash = this.mqttService.hashPassword(settings.mqttpassword); + cast.mqttpassword = this.encryptionHelperService.basicEncrypt(settings.mqttpassword); cast.mqttusername = settings.mqttusername; break; case AuthenticationType.CERTIFICATE: if (!cast.deviceCertificate) { - const certificateDetails = await this.mqttService.generateCertificate( - cast.name - ); + const certificateDetails = await this.mqttService.generateCertificate(cast.name); cast.deviceCertificate = certificateDetails.deviceCertificate; cast.deviceCertificateKey = this.encryptionHelperService.basicEncrypt( certificateDetails.deviceCertificateKey @@ -1242,17 +1059,13 @@ export class IoTDeviceService { cast.authenticationType = settings.authenticationType; switch (cast.authenticationType) { case AuthenticationType.PASSWORD: - cast.mqttpassword = this.encryptionHelperService.basicEncrypt( - settings.mqttpassword - ); + cast.mqttpassword = this.encryptionHelperService.basicEncrypt(settings.mqttpassword); cast.mqttusername = settings.mqttusername; break; case AuthenticationType.CERTIFICATE: cast.caCertificate = settings.caCertificate; cast.deviceCertificate = settings.deviceCertificate; - cast.deviceCertificateKey = this.encryptionHelperService.basicEncrypt( - settings.deviceCertificateKey - ); + cast.deviceCertificateKey = this.encryptionHelperService.basicEncrypt(settings.deviceCertificateKey); break; } @@ -1275,9 +1088,7 @@ export class IoTDeviceService { authenticationType: device.authenticationType, caCertificate: device.caCertificate ?? fs.readFileSync(caCertPath).toString(), deviceCertificate: device.deviceCertificate, - deviceCertificateKey: this.encryptionHelperService.basicDecrypt( - device.deviceCertificateKey - ), + deviceCertificateKey: this.encryptionHelperService.basicDecrypt(device.deviceCertificateKey), mqtttopicname: device.mqtttopicname, mqttURL: device.mqttURL, mqttPort: device.mqttPort, @@ -1294,9 +1105,7 @@ export class IoTDeviceService { authenticationType: device.authenticationType, caCertificate: device.caCertificate, deviceCertificate: device.deviceCertificate, - deviceCertificateKey: this.encryptionHelperService.basicDecrypt( - device.deviceCertificateKey - ), + deviceCertificateKey: this.encryptionHelperService.basicDecrypt(device.deviceCertificateKey), mqtttopicname: device.mqtttopicname, mqttURL: device.mqttURL, mqttPort: device.mqttPort, @@ -1316,8 +1125,7 @@ export class IoTDeviceService { private async fixMQTTInternalBrokerTopics(dbIotDevices: IoTDevice[]) { const newMQTTInternalBrokers = dbIotDevices.filter( (d: MQTTInternalBrokerDevice) => - d.type === IoTDeviceType.MQTTInternalBroker && - d.mqtttopicname.includes("undefined") + d.type === IoTDeviceType.MQTTInternalBroker && d.mqtttopicname.includes("undefined") ); const remappedMQTT = []; for (const iotDevice of newMQTTInternalBrokers) { diff --git a/src/services/device-management/lorawan-device-database-enrich-job.ts b/src/services/device-management/lorawan-device-database-enrich-job.ts new file mode 100644 index 00000000..e80fd00d --- /dev/null +++ b/src/services/device-management/lorawan-device-database-enrich-job.ts @@ -0,0 +1,23 @@ +import { Injectable } from "@nestjs/common"; +import { Cron, CronExpression } from "@nestjs/schedule"; +import { ChirpstackDeviceService } from "@services/chirpstack/chirpstack-device.service"; +import { IoTDeviceService } from "@services/device-management/iot-device.service"; + +@Injectable() +export class LorawanDeviceDatabaseEnrichJob { + constructor(private chirpstackDeviceService: ChirpstackDeviceService, private iotDeviceService: IoTDeviceService) {} + + @Cron(CronExpression.EVERY_DAY_AT_5AM) // TODO: Finalize when to run + async enrichLoRaWANDeviceDatabase() { + // Select up to 25 lora devices without appId in the database + const devices = await this.iotDeviceService.findNonEnrichedLoRaWANDevices(); + + // Enrich from chirpstack + const enrichedDevices = await Promise.all( + devices.map(async device => await this.chirpstackDeviceService.enrichLoRaWANDevice(device)) + ); + + // Save to database + await this.iotDeviceService.updateLocalLoRaWANDevices(enrichedDevices); + } +}