diff --git a/server/sql/tables.sql b/server/sql/tables.sql index 96e9328..4a45726 100644 --- a/server/sql/tables.sql +++ b/server/sql/tables.sql @@ -7,8 +7,6 @@ CREATE TABLE IF NOT EXISTS lp_ttn_end_device_uplinks ( dev_addr VARCHAR(255), received_at_utc DATE, battery NUMERIC, - latitude DOUBLE, - longitude DOUBLE, created_at_utc TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at_utc TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); diff --git a/server/src/controller/ttnController.ts b/server/src/controller/ttnController.ts index 4c68393..606d9de 100644 --- a/server/src/controller/ttnController.ts +++ b/server/src/controller/ttnController.ts @@ -1,12 +1,8 @@ import express, { Request, Response } from "express"; import { container } from "tsyringe"; -import { domainEventEmitter } from "../config/eventEmitter"; -import { - TtnMessageReceivedEvent, - TtnMessageReceivedEventName, -} from "../event/ttnMessageReceivedEvent"; import { validateData } from "../middleware/validationMiddleware"; import { TtnMessage } from "../models/ttnMessage"; +import { LocationService } from "../services/locationService"; import { LpTtnEndDeviceUplinksService } from "../services/lpTtnEndDeviceUplinksService"; import { TtnGatewayReceptionService } from "../services/ttnGatewayReceptionService"; import { WifiScanService } from "../services/wifiScanService"; @@ -20,6 +16,8 @@ const ttnGatewayReceptionService = container.resolve( ); const wifiScanService = container.resolve(WifiScanService); +const locationService = container.resolve(LocationService); + const router = express.Router(); router.post( @@ -28,8 +26,8 @@ router.post( async (req: Request, res: Response) => { try { const message = req.body as TtnMessage; - // Create uplink record - const { lp_ttn_end_device_uplinks_id, latitude, longitude } = + + const { lp_ttn_end_device_uplinks_id } = await lpTtnEndDeviceUplinksService.createUplink({ device_id: message.end_device_ids.device_id, application_ids: @@ -41,14 +39,17 @@ router.post( battery: message.uplink_message.decoded_payload?.messages[0].find( (e) => e.type === "Battery" )?.measurementValue, - latitude: message.uplink_message.decoded_payload?.messages[0].find( - (e) => e.type === "Latitude" - )?.measurementValue, - longitude: message.uplink_message.decoded_payload?.messages[0].find( - (e) => e.type === "Longitude" - )?.measurementValue, }); + const gnnsLocation = { + latitude: message.uplink_message.decoded_payload?.messages[0].find( + (e) => e.type === "Latitude" + )?.measurementValue, + longitude: message.uplink_message.decoded_payload?.messages[0].find( + (e) => e.type === "Longitude" + )?.measurementValue, + }; + const wifiScans = message.uplink_message.decoded_payload?.messages[0] .find((e) => e.type === "Wi-Fi Scan") @@ -58,39 +59,50 @@ router.post( rssi: w.rssi, })) ?? []; + console.log(wifiScans); + const ttnGatewayReceptions = message.uplink_message.rx_metadata.map( (g) => ({ lp_ttn_end_device_uplinks_id, gateway_id: g.gateway_ids.gateway_id, eui: g.gateway_ids.eui, rssi: g.rssi, - latitude: g.location.latitude, - longitude: g.location.longitude, - altitude: g.location.altitude, + latitude: g.location?.latitude, + longitude: g.location?.longitude, + altitude: g.location?.altitude, }) ); - const event: TtnMessageReceivedEvent = { - lp_ttn_end_device_uplinks_id, - wifis: wifiScans.map((w) => ({ mac: w.mac, rssi: w.rssi })), - ttnGateways: ttnGatewayReceptions.map((g) => ({ - rssi: g.rssi, - altitude: g.altitude, - latitude: g.latitude, - longitude: g.longitude, - })), - gnssLocation: { latitude, longitude } + const createDatabaseEntries = async () => { + const [wifiResults, gatewayResults] = await Promise.all([ + wifiScanService.createWifiScans(wifiScans), + ttnGatewayReceptionService.filterAndInsertGatewayReception( + ttnGatewayReceptions + ), + ]); + + locationService.createLocationFromTriangulation({ + lp_ttn_end_device_uplinks_id, + wifi: wifiResults.map(({ latitude, longitude, rssi }) => ({ + latitude, + longitude, + rssi, + })), + ttn_gw: gatewayResults.map(({ latitude, longitude, rssi }) => ({ + latitude, + longitude, + rssi, + })), + gnss: + gnnsLocation.latitude && gnnsLocation.longitude + ? { + latitude: gnnsLocation.latitude, + longitude: gnnsLocation.longitude, + } + : undefined, + }); }; - - domainEventEmitter.emit(TtnMessageReceivedEventName, event); - - await Promise.all([ - wifiScanService.createWifiScans(wifiScans), - ttnGatewayReceptionService.createGatewayReceptions( - ttnGatewayReceptions - ), - ]); - + createDatabaseEntries().then(); res.status(200); } catch (error) { console.log(error); diff --git a/server/src/controller/ttnGatewayReceptionController.ts b/server/src/controller/ttnGatewayReceptionController.ts index fe06b1f..b9c0623 100644 --- a/server/src/controller/ttnGatewayReceptionController.ts +++ b/server/src/controller/ttnGatewayReceptionController.ts @@ -1,6 +1,6 @@ import express, { Request, Response } from "express"; -import { TtnGatewayReceptionService } from "../services/ttnGatewayReceptionService"; import { container } from "tsyringe"; +import { TtnGatewayReceptionService } from "../services/ttnGatewayReceptionService"; const ttnGatewayReceptionService = container.resolve( TtnGatewayReceptionService @@ -35,7 +35,7 @@ router.get("/:id", async (req: Request, res: Response) => { router.post("/", async (req: Request, res: Response) => { try { const newGatewayReception = - await ttnGatewayReceptionService.createGatewayReception(req.body); + await ttnGatewayReceptionService.createTtnGatewayReception(req.body); res.status(201).json(newGatewayReception); } catch (error) { res.status(500).json({ error: "Error creating gateway reception" }); @@ -46,7 +46,10 @@ router.put("/:id", async (req: Request, res: Response) => { try { const { id } = req.params; const updatedGatewayReception = - await ttnGatewayReceptionService.updateGatewayReception(id, req.body); + await ttnGatewayReceptionService.updateGatewayReception({ + ...req.body, + ttn_gateway_reception_id: id, + }); if (!updatedGatewayReception) { res.status(404).json({ error: "Gateway reception not found" }); return; diff --git a/server/src/controller/wifiScanController.ts b/server/src/controller/wifiScanController.ts index 23a52f6..111cc7d 100644 --- a/server/src/controller/wifiScanController.ts +++ b/server/src/controller/wifiScanController.ts @@ -40,7 +40,10 @@ router.post("/", async (req: Request, res: Response) => { router.put("/:id", async (req: Request, res: Response) => { try { const { id } = req.params; - const updatedWifiScan = await wifiScanService.updateWifiScan(id, req.body); + const updatedWifiScan = await wifiScanService.updateWifiScan({ + ...req.body, + wifi_scan_id: id, + }); if (!updatedWifiScan) { res.status(404).json({ error: "Wifi scan not found" }); return; diff --git a/server/src/event/ttnMessageReceivedEvent.ts b/server/src/event/ttnMessageReceivedEvent.ts deleted file mode 100644 index cbf068f..0000000 --- a/server/src/event/ttnMessageReceivedEvent.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const TtnMessageReceivedEventName = "TtnMessageReceived"; -export type TtnMessageReceivedEvent = { - lp_ttn_end_device_uplinks_id: string; - wifis: { - mac: string; - rssi: number; - }[]; - ttnGateways: { - rssi: number; - latitude: number; - longitude: number; - altitude: number; - }[]; - gnssLocation: { - latitude?: number; - longitude?: number; - } -}; diff --git a/server/src/eventHandler/ttnMessageReceivedEventHandler.ts b/server/src/eventHandler/ttnMessageReceivedEventHandler.ts deleted file mode 100644 index 771da9c..0000000 --- a/server/src/eventHandler/ttnMessageReceivedEventHandler.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { container } from "tsyringe"; -import { domainEventEmitter } from "../config/eventEmitter"; -import { - TtnMessageReceivedEvent, - TtnMessageReceivedEventName, -} from "../event/ttnMessageReceivedEvent"; -import { getLocationForWifiMemoized } from "../proxy/wigle"; -import { LocationService } from "../services/locationService"; -import { WifiScanService } from "../services/wifiScanService"; - -const locationService = container.resolve(LocationService); -const wifiScanService = container.resolve(WifiScanService); - -const CalculateTtnGatewayLocation = (event: TtnMessageReceivedEvent) => { - // Get location based on TTN Gateways - const virtualLocation = { - latitude: undefined as number | undefined, - longitude: undefined as number | undefined, - }; - - if (!event.ttnGateways || event.ttnGateways.length === 0) { - console.log("No TTN Gateway location received!"); - } else { - let totalWeight = 0; - let weightedLatitude = 0; - let weightedLongitude = 0; - - event.ttnGateways.forEach((gw) => { - const weight = 1 / Math.abs(gw.rssi); // Higher RSSI (closer to 0) gives more weight - totalWeight += weight; - weightedLatitude += gw.latitude * weight; - weightedLongitude += gw.longitude * weight; - }); - - // Calculate the weighted average to get the virtual location - const virtualLocation = { - latitude: weightedLatitude / totalWeight, - longitude: weightedLongitude / totalWeight, - }; - - console.log( - "Tracker location based on TTN Gateway location:", - virtualLocation - ); - } - return { - ttn_latitude: virtualLocation.latitude, - ttn_longitude: virtualLocation.longitude, - }; -}; - -const CalculateWifiLocation = async (event: TtnMessageReceivedEvent) => { - // Get location based on WiFi Scans - const virtualLocation = { - latitude: undefined as number | undefined, - longitude: undefined as number | undefined, - }; - - if (!event.wifis || event.wifis.length === 0) { - console.log("No WiFi scans received!"); - } else { - // Process Wi-Fi data to compute weighted location - let wifiScans = await Promise.all( - event.wifis.map(async (wifi) => { - // Create new WiFi Scan entry if wigle.net reported location - const apiResponse = await getLocationForWifiMemoized(wifi.mac);; - // Only return valid data wifiScans (location for MAC was found) - if ((apiResponse?.success == true) && (apiResponse.totalResults > 0)) { - return { - lp_ttn_end_device_uplinks_id: event.lp_ttn_end_device_uplinks_id, - mac: wifi.mac, - rssi: wifi.rssi, - latitude: apiResponse?.results[0].trilat, - longitude: apiResponse?.results[0].trilong, - } - } - return undefined; - }) - ); - - const wifiScansFiltered = wifiScans.filter(w => (w !== undefined)); - - // Store valid wifiScans into DB - const locatedWifiScans = await wifiScanService.createWifiScans(wifiScansFiltered); - - if (locatedWifiScans.length !== 0) { - const { totalWeight, weightedLatitude, weightedLongitude } = - locatedWifiScans.reduce( - (acc, { latitude, longitude, rssi }) => { - const weight = 1 / Math.abs(rssi); - acc.totalWeight += weight; - acc.weightedLatitude += latitude * weight; - acc.weightedLongitude += longitude * weight; - return acc; - }, - { - totalWeight: 0, - weightedLatitude: 0, - weightedLongitude: 0, - } - ); - - // Calculate the weighted average to get the virtual location - virtualLocation.latitude = weightedLatitude / totalWeight; - virtualLocation.longitude = weightedLongitude / totalWeight; - - console.log( - "Tracker location based on WiFi Scan location:", - virtualLocation - ); - } - return { - wifi_latitude: virtualLocation.latitude, - wifi_longitude: virtualLocation.longitude, - }; -}; - -const CalculateGnssLocation = (event: TtnMessageReceivedEvent) => { - // Get location based on reported GNSS - if ( - event.gnssLocation.latitude === undefined || - event.gnssLocation.longitude === undefined - ) { - console.log("No valid GNSS location received!"); - } - - return { - gnss_latitude: event.gnssLocation.latitude, - gnss_longitude: event.gnssLocation.longitude, - }; -}; - -domainEventEmitter.on( - TtnMessageReceivedEventName, - async (event: TtnMessageReceivedEvent) => { - console.log(event); - - var wifi_based_latitude: number | undefined = undefined; - var wifi_based_longitude: number | undefined = undefined; - var gnss_based_latitude: number | undefined = undefined; - var gnss_based_longitude: number | undefined = undefined; - var ttn_gw_based_latitude: number | undefined = undefined; - var ttn_gw_based_longitude: number | undefined = undefined; - - if (event.ttnGateways && event.ttnGateways.length > 0) { - const virtualLocation = CalculateTtnGatewayLocation(event); - console.log(virtualLocation); - ttn_gw_based_latitude = virtualLocation.ttn_latitude; - ttn_gw_based_longitude = virtualLocation.ttn_longitude; - } - - if (event.wifis && event.wifis.length > 0) { - const virtualLocation = await CalculateWifiLocation(event); - wifi_based_latitude = virtualLocation.wifi_latitude; - wifi_based_longitude = virtualLocation.wifi_longitude; - } - - const virtualLocation = CalculateGnssLocation(event); - gnss_based_latitude = virtualLocation.gnss_latitude; - gnss_based_longitude = virtualLocation.gnss_longitude; - - console.log({ - lp_ttn_end_device_uplinks_id: event.lp_ttn_end_device_uplinks_id, - ttn_gw_latitude: ttn_gw_based_latitude, - ttn_gw_longitude: ttn_gw_based_longitude, - gnss_latitude: gnss_based_latitude, - gnss_longitude: gnss_based_longitude, - wifi_latitude: wifi_based_latitude, - wifi_longitude: wifi_based_longitude, - }); - - const newLocation = await locationService.createLocation({ - lp_ttn_end_device_uplinks_id: event.lp_ttn_end_device_uplinks_id, - ttn_gw_latitude: ttn_gw_based_latitude, - ttn_gw_longitude: ttn_gw_based_longitude, - gnss_latitude: gnss_based_latitude, - gnss_longitude: gnss_based_longitude, - wifi_latitude: wifi_based_latitude, - wifi_longitude: wifi_based_longitude, - }); - - console.log(newLocation); - } -); diff --git a/server/src/index.ts b/server/src/index.ts index dcc61d9..a73af84 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,7 +1,6 @@ import dotenv from "dotenv"; import express from "express"; import "reflect-metadata"; -import "./eventHandler/ttnMessageReceivedEventHandler"; const cors = require("cors"); import locationRoutes from "./controller/locationController"; diff --git a/server/src/models/location.ts b/server/src/models/location.ts index 4b3e708..86e8d21 100644 --- a/server/src/models/location.ts +++ b/server/src/models/location.ts @@ -20,35 +20,27 @@ Location.init( type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true, - allowNull: false, }, lp_ttn_end_device_uplinks_id: { type: DataTypes.UUID, - allowNull: false, }, wifi_latitude: { type: DataTypes.NUMBER, - allowNull: true, }, wifi_longitude: { type: DataTypes.NUMBER, - allowNull: true, }, gnss_latitude: { type: DataTypes.NUMBER, - allowNull: true, }, gnss_longitude: { type: DataTypes.NUMBER, - allowNull: true, }, ttn_gw_latitude: { type: DataTypes.NUMBER, - allowNull: true, }, ttn_gw_longitude: { type: DataTypes.NUMBER, - allowNull: true, }, created_at_utc: { type: DataTypes.DATE, diff --git a/server/src/models/lpTtnEndDeviceUplinks.ts b/server/src/models/lpTtnEndDeviceUplinks.ts index 0603ea6..515644c 100644 --- a/server/src/models/lpTtnEndDeviceUplinks.ts +++ b/server/src/models/lpTtnEndDeviceUplinks.ts @@ -10,8 +10,6 @@ export class LpTtnEndDeviceUplinks extends Model { public dev_addr!: string; public received_at_utc!: Date; public battery!: number; - public latitude!: number; - public longitude!: number; public created_at_utc!: Date; public updated_at_utc!: Date; } @@ -30,35 +28,21 @@ LpTtnEndDeviceUplinks.init( }, application_ids: { type: DataTypes.STRING, - allowNull: true, }, dev_eui: { type: DataTypes.STRING, - allowNull: true, }, join_eui: { type: DataTypes.STRING, - allowNull: true, }, dev_addr: { type: DataTypes.STRING, - allowNull: true, }, received_at_utc: { type: DataTypes.DATE, - allowNull: true, }, battery: { type: DataTypes.NUMBER, - allowNull: true, - }, - latitude: { - type: DataTypes.NUMBER, - allowNull: true, - }, - longitude: { - type: DataTypes.NUMBER, - allowNull: true, }, created_at_utc: { type: DataTypes.DATE, diff --git a/server/src/models/ttnGatewayReception.ts b/server/src/models/ttnGatewayReception.ts index bbb8652..17bad55 100644 --- a/server/src/models/ttnGatewayReception.ts +++ b/server/src/models/ttnGatewayReception.ts @@ -32,23 +32,18 @@ TtnGatewayReception.init( }, eui: { type: DataTypes.STRING, - allowNull: false, }, rssi: { type: DataTypes.NUMBER, - allowNull: true, }, latitude: { type: DataTypes.NUMBER, - allowNull: true, }, longitude: { type: DataTypes.NUMBER, - allowNull: true, }, altitude: { type: DataTypes.NUMBER, - allowNull: true, }, created_at_utc: { type: DataTypes.DATE, diff --git a/server/src/models/ttnMessage.ts b/server/src/models/ttnMessage.ts index aa3a93d..1cf3d6f 100644 --- a/server/src/models/ttnMessage.ts +++ b/server/src/models/ttnMessage.ts @@ -6,14 +6,14 @@ export interface TtnMessage { }; dev_eui: string; join_eui: string; - dev_addr: string; + dev_addr?: string; }; correlation_ids: string[]; received_at: string; uplink_message: { - session_key_id: string; + session_key_id?: string; f_port?: number; - f_cnt: number; + f_cnt?: number; frm_payload?: string; decoded_payload?: { err: number; @@ -22,8 +22,8 @@ export interface TtnMessage { { measurementId: "4200"; measurementValue: any[]; - motionId: number; - timestamp: number; + motionId?: number; + timestamp?: number; type: "Event Status"; }, { @@ -32,29 +32,29 @@ export interface TtnMessage { mac: string; rssi: number; }[]; - motionId: number; - timestamp: number; + motionId?: number; + timestamp?: number; type: "Wi-Fi Scan"; }, { measurementId: "3000"; measurementValue: number; - motionId: number; - timestamp: number; + motionId?: number; + timestamp?: number; type: "Battery"; }, { measurementId: "4197"; measurementValue: number; - motionId: number; - timestamp: number; + motionId?: number; + timestamp?: number; type: "Longitude"; }, { measurementId: "4198"; measurementValue: number; - motionId: number; - timestamp: number; + motionId?: number; + timestamp?: number; type: "Latitude"; } ] @@ -67,44 +67,44 @@ export interface TtnMessage { gateway_id: string; eui?: string; }; - time: string; + time?: string; timestamp?: number; rssi: number; channel_rssi: number; - snr: number; - location: { + snr?: number; + location?: { latitude: number; longitude: number; - altitude: number; + altitude?: number; source?: string; }; - uplink_token: string; + uplink_token?: string; channel_index?: number; - received_at: string; + received_at?: string; }[]; settings: { data_rate: { lora: { bandwidth: number; spreading_factor: number; - coding_rate: string; + coding_rate?: string; }; }; frequency: string; timestamp?: number; time?: Date; }; - received_at: Date; + received_at?: Date; confirmed?: boolean; - consumed_airtime: string; - version_ids: { + consumed_airtime?: string; + version_ids?: { brand_id: string; model_id: string; hardware_version: string; firmware_version: string; band_id: string; }; - network_ids: { + network_ids?: { net_id: string; ns_id: string; tenant_id: string; diff --git a/server/src/models/wifiScan.ts b/server/src/models/wifiScan.ts index 5b3e2d4..d558718 100644 --- a/server/src/models/wifiScan.ts +++ b/server/src/models/wifiScan.ts @@ -30,15 +30,15 @@ WifiScan.init( }, rssi: { type: DataTypes.NUMBER, - allowNull: true, + allowNull: false, }, latitude: { type: DataTypes.NUMBER, - allowNull: true, + allowNull: false, }, longitude: { type: DataTypes.NUMBER, - allowNull: true, + allowNull: false, }, created_at_utc: { type: DataTypes.DATE, diff --git a/server/src/proxy/wigle.ts b/server/src/proxy/wigle.ts index 705c180..68155d8 100644 --- a/server/src/proxy/wigle.ts +++ b/server/src/proxy/wigle.ts @@ -47,6 +47,7 @@ export const getLocationForWifi = async ( try { const url = `${process.env.WIGLE_BASE_URL!}${process.env .WIGLE_NETWORK_SEARCH!}?netid=${encodeURIComponent(mac)}`; + const response = await fetch(url, { method: "GET", headers: { @@ -54,12 +55,12 @@ export const getLocationForWifi = async ( Authorization: `Basic ${process.env.WIGLE_TOKEN}`, }, }); + if (response.ok) { return await response.json(); } console.log(response.status); return undefined; - } catch (error) { console.error("Error during call of API wigle.net:", error); } diff --git a/server/src/services/locationService.ts b/server/src/services/locationService.ts index 05bf190..8fa6d24 100644 --- a/server/src/services/locationService.ts +++ b/server/src/services/locationService.ts @@ -2,6 +2,39 @@ import { inject, injectable } from "tsyringe"; import { Location } from "../models/location"; import { LocationRepository } from "../repositories/locationRepository"; +interface CreateLocationParams { + lp_ttn_end_device_uplinks_id: string; + wifi?: Coordinates; + gnss?: Coordinates; + ttn_gw?: Coordinates; +} + +interface CreateLocationTriangulationParams { + lp_ttn_end_device_uplinks_id: string; + wifi: LocationSignal[]; + ttn_gw: LocationSignal[]; + gnss?: Coordinates; +} + +interface LocationSignal extends Coordinates { + rssi: number; +} + +interface Coordinates { + latitude: number; + longitude: number; +} + +interface UpdateTtnGatewayReceptionParams { + ttn_gateway_reception_id: string; + gateway_id?: string; + eui?: string; + rssi?: number; + latitude?: number; + longitude?: number; + altitude?: number; +} + @injectable() export class LocationService { constructor( @@ -17,8 +50,30 @@ export class LocationService { return this.repository.findById(id); } - public async createLocation(data: Partial) { - return this.repository.create(data); + public async createLocation(data: CreateLocationParams) { + return this.repository.create({ + lp_ttn_end_device_uplinks_id: data.lp_ttn_end_device_uplinks_id, + wifi_latitude: data.wifi?.latitude, + wifi_longitude: data.wifi?.longitude, + ttn_gw_latitude: data.ttn_gw?.latitude, + ttn_gw_longitude: data.ttn_gw?.longitude, + gnss_latitude: data.gnss?.latitude, + gnss_longitude: data.gnss?.longitude, + }); + } + + public async createLocationFromTriangulation( + data: CreateLocationTriangulationParams + ) { + const wifi_location = this.calculateVirtualLocation(data.wifi); + const gateway_location = this.calculateVirtualLocation(data.ttn_gw); + + return this.createLocation({ + lp_ttn_end_device_uplinks_id: data.lp_ttn_end_device_uplinks_id, + wifi: wifi_location, + ttn_gw: gateway_location, + gnss: data.gnss, + }); } public async updateLocation(id: string, data: Partial) { @@ -28,4 +83,25 @@ export class LocationService { public async deleteLocation(id: string) { return this.repository.delete(id); } + + private calculateVirtualLocation(locations: LocationSignal[]) { + if (locations.length === 0) return undefined; + + const { totalWeight, weightedLatitude, weightedLongitude } = + locations.reduce( + (acc, { latitude, longitude, rssi }) => { + const weight = 1 / Math.abs(rssi); + acc.totalWeight += weight; + acc.weightedLatitude += latitude * weight; + acc.weightedLongitude += longitude * weight; + return acc; + }, + { totalWeight: 0, weightedLatitude: 0, weightedLongitude: 0 } + ); + + return { + latitude: weightedLatitude / totalWeight, + longitude: weightedLongitude / totalWeight, + }; + } } diff --git a/server/src/services/ttnGatewayReceptionService.ts b/server/src/services/ttnGatewayReceptionService.ts index 55b6111..ff971b2 100644 --- a/server/src/services/ttnGatewayReceptionService.ts +++ b/server/src/services/ttnGatewayReceptionService.ts @@ -1,7 +1,26 @@ import { inject, injectable } from "tsyringe"; -import { TtnGatewayReception } from "../models/ttnGatewayReception"; import { TtnGatewayReceptionRepository } from "../repositories/ttnGatewayReceptionRepository"; +interface CreateTtnGatewayReceptionParams { + lp_ttn_end_device_uplinks_id: string; + gateway_id: string; + eui?: string; + rssi?: number; + latitude?: number; + longitude?: number; + altitude?: number; +} + +interface UpdateTtnGatewayReceptionParams { + ttn_gateway_reception_id: string; + gateway_id?: string; + eui?: string; + rssi?: number; + latitude?: number; + longitude?: number; + altitude?: number; +} + @injectable() export class TtnGatewayReceptionService { constructor( @@ -17,19 +36,24 @@ export class TtnGatewayReceptionService { return this.repository.findById(id); } - public async createGatewayReception(data: Partial) { - return this.repository.create(data); - } - - public async createGatewayReceptions(data: Partial[]) { - return this.repository.createMany(data); - } - - public async updateGatewayReception( - id: string, - data: Partial + public async createTtnGatewayReception( + data: CreateTtnGatewayReceptionParams ) { - return this.repository.update(id, data); + if (data.latitude !== undefined && data.longitude !== undefined) + return this.repository.create(data); + } + + public async filterAndInsertGatewayReception( + data: CreateTtnGatewayReceptionParams[] + ) { + const result = await Promise.all( + data.map(async (gateway) => await this.createTtnGatewayReception(gateway)) + ); + return result.filter((gateway) => gateway !== undefined); + } + + public async updateGatewayReception(data: UpdateTtnGatewayReceptionParams) { + return this.repository.update(data.ttn_gateway_reception_id, data); } public async deleteGatewayReception(id: string) { diff --git a/server/src/services/wifiScanService.ts b/server/src/services/wifiScanService.ts index 050145c..78b585e 100644 --- a/server/src/services/wifiScanService.ts +++ b/server/src/services/wifiScanService.ts @@ -1,7 +1,21 @@ import { inject, injectable } from "tsyringe"; -import { WifiScan } from "../models/wifiScan"; +import { getLocationForWifiMemoized } from "../proxy/wigle"; import { WifiScanRepository } from "../repositories/wifiScanRepository"; +interface CreateWifiScanParams { + lp_ttn_end_device_uplinks_id: string; + mac: string; + rssi: number; +} + +interface UpdateWifiScanParams { + wifi_scan_id: string; + mac?: string; + rssi?: number; + latitude?: number; + longitude?: number; +} + @injectable() export class WifiScanService { constructor( @@ -16,16 +30,28 @@ export class WifiScanService { return this.repository.findById(id); } - public async createWifiScan(data: Partial) { - return this.repository.create(data); + public async createWifiScan(data: CreateWifiScanParams) { + const apiResponse = await getLocationForWifiMemoized(data.mac); + + if (apiResponse !== undefined && apiResponse.results.length > 0) + return this.repository.create({ + ...data, + latitude: apiResponse.results[0].trilat, + longitude: apiResponse.results[0].trilong, + }); } - public async createWifiScans(data: Partial[]) { - return this.repository.createMany(data); + public async createWifiScans(data: CreateWifiScanParams[]) { + let wifiScans = await Promise.all( + data.map(async (wifi) => { + return await this.createWifiScan(wifi); + }) + ); + return wifiScans.filter((wifi) => wifi !== undefined); } - public async updateWifiScan(id: string, data: Partial) { - return this.repository.update(id, data); + public async updateWifiScan(data: UpdateWifiScanParams) { + return this.repository.update(data.wifi_scan_id, data); } public async deleteWifiScan(id: string) { diff --git a/server/src/validation/ttn/ttnMessageValidation.ts b/server/src/validation/ttn/ttnMessageValidation.ts index c321dc9..a167448 100644 --- a/server/src/validation/ttn/ttnMessageValidation.ts +++ b/server/src/validation/ttn/ttnMessageValidation.ts @@ -8,14 +8,14 @@ export const ttnMessageValidator = z.object({ }), dev_eui: z.string(), join_eui: z.string(), - dev_addr: z.string(), + dev_addr: z.string().optional(), }), correlation_ids: z.array(z.string()), received_at: z.string(), uplink_message: z.object({ - session_key_id: z.string(), + session_key_id: z.string().optional(), f_port: z.number().optional(), - f_cnt: z.number(), + f_cnt: z.number().optional(), frm_payload: z.string().optional(), decoded_payload: z .object({ @@ -25,8 +25,8 @@ export const ttnMessageValidator = z.object({ z.object({ measurementId: z.string(), measurementValue: z.union([z.array(z.any()), z.number()]), - motionId: z.number(), - timestamp: z.number(), + motionId: z.number().optional(), + timestamp: z.number().optional(), type: z.string(), }) ) @@ -41,20 +41,22 @@ export const ttnMessageValidator = z.object({ gateway_id: z.string(), eui: z.string().optional(), }), - time: z.string(), + time: z.string().optional(), timestamp: z.number().optional(), rssi: z.number(), channel_rssi: z.number(), - snr: z.number(), - location: z.object({ - latitude: z.number(), - longitude: z.number(), - altitude: z.number(), - source: z.string().optional(), - }), - uplink_token: z.string(), + snr: z.number().optional(), + location: z + .object({ + latitude: z.number(), + longitude: z.number(), + altitude: z.number().optional(), + source: z.string().optional(), + }) + .optional(), + uplink_token: z.string().optional(), channel_index: z.number().optional(), - received_at: z.string(), + received_at: z.string().optional(), }) ), settings: z.object({ @@ -62,29 +64,33 @@ export const ttnMessageValidator = z.object({ lora: z.object({ bandwidth: z.number(), spreading_factor: z.number(), - coding_rate: z.string(), + coding_rate: z.string().optional(), }), }), frequency: z.string(), timestamp: z.number().optional(), time: z.string().optional(), }), - received_at: z.string(), + received_at: z.string().optional(), confirmed: z.boolean().optional(), - consumed_airtime: z.string(), - version_ids: z.object({ - brand_id: z.string(), - model_id: z.string(), - hardware_version: z.string(), - firmware_version: z.string(), - band_id: z.string(), - }), - network_ids: z.object({ - net_id: z.string(), - ns_id: z.string(), - tenant_id: z.string(), - cluster_id: z.string(), - cluster_address: z.string(), - }), + consumed_airtime: z.string().optional(), + version_ids: z + .object({ + brand_id: z.string(), + model_id: z.string(), + hardware_version: z.string(), + firmware_version: z.string(), + band_id: z.string(), + }) + .optional(), + network_ids: z + .object({ + net_id: z.string(), + ns_id: z.string(), + tenant_id: z.string(), + cluster_id: z.string(), + cluster_address: z.string(), + }) + .optional(), }), });