diff --git a/README.md b/README.md index 9727bc4..e77d4fb 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ TODO ### Database **Change name of database and credentials as you like!** -- Create new database: `CREATE DATABASE locationhub;` +- Create new database: `CREATE DATABASE dev_locationhub;` - Create new user for database: `GRANT ALL PRIVILEGES ON dev_locationhub.* TO 'dbuser'@'localhost' IDENTIFIED BY '1234';` - Import tables: `/usr/bin/mariadb -u dbuser -p1234 dev_locationhub < server/sql/tables.sql` diff --git a/server/.env.template b/server/.env.template index 69702a9..9740f4c 100644 --- a/server/.env.template +++ b/server/.env.template @@ -7,6 +7,6 @@ DB_PORT="" WIGLE_TOKEN="" # Go to account and generate token "Encoded for use" WIGLE_BASE_URL="https://api.wigle.net" WIGLE_NETWORK_SEARCH="/api/v2/network/search" -GET_LOCATION_WIFI_MAX_AGE=1209600000 # 14 Tage in Millisekunden (14 * 24 * 60 * 60 * 1000) +GET_LOCATION_WIFI_MAX_AGE=1209600000 # 14 days in milliseconds (14 * 24 * 60 * 60 * 1000) GET_LOCATION_WIFI_MAX=10000 GET_LOCATION_WIFI_PRIMITIVE=true \ No newline at end of file diff --git a/server/package.json b/server/package.json index a71e8c3..d550a8e 100644 --- a/server/package.json +++ b/server/package.json @@ -13,11 +13,11 @@ "license": "ISC", "devDependencies": { "@types/express": "^5.0.0", + "@types/memoizee": "^0.4.11", "@types/node": "^22.10.2", "nodemon": "^3.1.9", "ts-node": "^10.9.2", - "typescript": "^5.7.2", - "@types/memoizee": "^0.4.11" + "typescript": "^5.7.2" }, "dependencies": { "cors": "^2.8.5", diff --git a/server/sql/tables.sql b/server/sql/tables.sql index babf48b..5788884 100644 --- a/server/sql/tables.sql +++ b/server/sql/tables.sql @@ -46,6 +46,8 @@ CREATE TABLE IF NOT EXISTS location ( wifi_longitude DOUBLE, gnss_latitude DOUBLE, gnss_longitude DOUBLE, + ttn_gw_latitude DOUBLE, + ttn_gw_longitude DOUBLE, created_at_utc TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at_utc TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (lp_ttn_end_device_uplinks_id) REFERENCES lp_ttn_end_device_uplinks(lp_ttn_end_device_uplinks_id) diff --git a/server/src/controller/ttnController.ts b/server/src/controller/ttnController.ts index daabf42..4c68393 100644 --- a/server/src/controller/ttnController.ts +++ b/server/src/controller/ttnController.ts @@ -28,7 +28,8 @@ router.post( async (req: Request, res: Response) => { try { const message = req.body as TtnMessage; - const { lp_ttn_end_device_uplinks_id } = + // Create uplink record + const { lp_ttn_end_device_uplinks_id, latitude, longitude } = await lpTtnEndDeviceUplinksService.createUplink({ device_id: message.end_device_ids.device_id, application_ids: @@ -78,6 +79,7 @@ router.post( latitude: g.latitude, longitude: g.longitude, })), + gnssLocation: { latitude, longitude } }; domainEventEmitter.emit(TtnMessageReceivedEventName, event); diff --git a/server/src/event/ttnMessageReceivedEvent.ts b/server/src/event/ttnMessageReceivedEvent.ts index b6b6945..cbf068f 100644 --- a/server/src/event/ttnMessageReceivedEvent.ts +++ b/server/src/event/ttnMessageReceivedEvent.ts @@ -11,4 +11,8 @@ export type TtnMessageReceivedEvent = { longitude: number; altitude: number; }[]; + gnssLocation: { + latitude?: number; + longitude?: number; + } }; diff --git a/server/src/eventHandler/ttnMessageReceivedEventHandler.ts b/server/src/eventHandler/ttnMessageReceivedEventHandler.ts index 90c0007..e682c01 100644 --- a/server/src/eventHandler/ttnMessageReceivedEventHandler.ts +++ b/server/src/eventHandler/ttnMessageReceivedEventHandler.ts @@ -3,11 +3,156 @@ import { TtnMessageReceivedEvent, TtnMessageReceivedEventName, } from "../event/ttnMessageReceivedEvent"; +import { container } from "tsyringe"; +import { LocationService } from "../services/locationService"; +import { WifiScanService } from "../services/wifiScanService"; +import { getLocationForWifiMemoized } from "../proxy/wigle"; + +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 + virtualLocation.latitude = weightedLatitude / totalWeight; + virtualLocation.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 + const 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); + 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, + }; + }) + ); + + await wifiScanService.createWifiScans(wifiScans); + + const { totalWeight, weightedLatitude, weightedLongitude } = + wifiScans.reduce( + (acc, { latitude, longitude, rssi }) => { + if (latitude && longitude && rssi !== 0) { + 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); - // TODO Hendrik ๐Ÿš€ + + 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); + 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; + + 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 f0202a8..dcc61d9 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -25,5 +25,5 @@ app.use("/api/locations", locationRoutes); app.use("/api/ttn", ttnRoutes); app.listen(PORT, () => { - console.log(`๐Ÿš€ Server lรคuft auf http://localhost:${PORT}`); + console.log(`๐Ÿš€ Server runs here: http://localhost:${PORT}`); }); diff --git a/server/src/models/location.ts b/server/src/models/location.ts index 1a79938..4b3e708 100644 --- a/server/src/models/location.ts +++ b/server/src/models/location.ts @@ -8,6 +8,8 @@ export class Location extends Model { public wifi_longitude!: number; public gnss_latitude!: number; public gnss_longitude!: number; + public ttn_gw_latitude!: number; + public ttn_gw_longitude!: number; public created_at_utc!: Date; public updated_at_utc!: Date; } @@ -40,6 +42,14 @@ Location.init( 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, defaultValue: DataTypes.NOW,