Files
drone-detector/server/routes/detectors.js
2025-08-28 06:34:30 +02:00

249 lines
7.6 KiB
JavaScript

const express = require('express');
const router = express.Router();
const Joi = require('joi');
const { validateRequest } = require('../middleware/validation');
const { Heartbeat, Device, DroneDetection } = require('../models');
const AlertService = require('../services/alertService');
const DroneTrackingService = require('../services/droneTrackingService');
// Initialize services
const alertService = new AlertService();
const droneTracker = new DroneTrackingService();
// Handle movement alerts from the tracking service
droneTracker.on('movement_alert', (alertData) => {
const { io } = require('../index');
if (io) {
// Emit to dashboard with detailed movement information
io.emitToDashboard('drone_movement_alert', {
...alertData,
timestamp: new Date().toISOString()
});
// Emit to specific device room
io.emitToDevice(alertData.deviceId, 'drone_movement_alert', {
...alertData,
timestamp: new Date().toISOString()
});
console.log(`🚨 Movement Alert: ${alertData.analysis.description} (Drone ${alertData.droneId})`);
}
});
// Unified schema that accepts both heartbeat and detection payloads
const detectorSchema = Joi.object({
// Heartbeat fields
type: Joi.string().valid('heartbeat').when('device_id', {
not: Joi.exist(),
then: Joi.required()
}),
key: Joi.string().when('type', {
is: 'heartbeat',
then: Joi.required()
}),
// Optional heartbeat fields (from original heartbeat route)
signal_strength: Joi.number().integer().optional(),
battery_level: Joi.number().integer().min(0).max(100).optional(),
temperature: Joi.number().optional(),
uptime: Joi.number().integer().min(0).optional(),
memory_usage: Joi.number().integer().min(0).max(100).optional(),
firmware_version: Joi.string().optional(),
// Detection fields
device_id: Joi.number().integer().when('type', {
not: 'heartbeat',
then: Joi.required()
}),
geo_lat: Joi.number().min(-90).max(90).when('device_id', {
is: Joi.exist(),
then: Joi.required()
}),
geo_lon: Joi.number().min(-180).max(180).when('device_id', {
is: Joi.exist(),
then: Joi.required()
}),
device_timestamp: Joi.number().integer().min(0).when('device_id', {
is: Joi.exist(),
then: Joi.required()
}),
drone_type: Joi.number().integer().min(0).when('device_id', {
is: Joi.exist(),
then: Joi.required()
}),
rssi: Joi.number().when('device_id', {
is: Joi.exist(),
then: Joi.required()
}),
freq: Joi.number().when('device_id', {
is: Joi.exist(),
then: Joi.required()
}),
drone_id: Joi.number().integer().when('device_id', {
is: Joi.exist(),
then: Joi.required()
}),
// Optional detection fields
confidence_level: Joi.number().min(0).max(1).optional(),
signal_duration: Joi.number().integer().min(0).optional()
}).or('type', 'device_id'); // Must have either type (heartbeat) or device_id (detection)
// POST /api/detectors - Unified endpoint for heartbeats and detections
router.post('/', validateRequest(detectorSchema), async (req, res) => {
try {
// Determine if this is a heartbeat or detection based on payload
if (req.body.type === 'heartbeat') {
return await handleHeartbeat(req, res);
} else if (req.body.device_id) {
return await handleDetection(req, res);
} else {
return res.status(400).json({
success: false,
error: 'Invalid payload: must be either heartbeat or detection format'
});
}
} catch (error) {
console.error('Error in detectors endpoint:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
});
// Handle heartbeat payload
async function handleHeartbeat(req, res) {
const { type, key, device_id, ...heartbeatData } = req.body;
console.log(`💓 Heartbeat received from device key: ${key}`);
// If device_id is not provided, try to find device by key
let deviceId = device_id;
if (!deviceId) {
// Try to extract device ID from key or use key as identifier
const keyMatch = key.match(/device[_-]?(\d+)/i);
deviceId = keyMatch ? parseInt(keyMatch[1]) : key.hashCode();
}
// Ensure device exists or create it
const [device] = await Device.findOrCreate({
where: { id: deviceId },
defaults: {
id: deviceId,
name: `Device ${deviceId}`,
last_heartbeat: new Date()
}
});
// Update device's last heartbeat
await device.update({ last_heartbeat: new Date() });
// Create heartbeat record with all optional fields
const heartbeat = await Heartbeat.create({
device_id: deviceId,
device_key: key,
...heartbeatData,
received_at: new Date()
});
// Emit real-time update via Socket.IO (from original heartbeat route)
req.io.emit('device_heartbeat', {
device_id: deviceId,
device_key: key,
timestamp: heartbeat.received_at,
status: 'online',
...heartbeatData
});
console.log(`✅ Heartbeat recorded for device ${deviceId}`);
return res.status(201).json({
success: true,
data: heartbeat,
message: 'Heartbeat recorded successfully'
});
}
// Handle detection payload
async function handleDetection(req, res) {
const detectionData = req.body;
console.log(`🚁 Drone detection received from device ${detectionData.device_id}: drone_id=${detectionData.drone_id}, type=${detectionData.drone_type}, rssi=${detectionData.rssi}`);
// Ensure device exists or create it (from original detection route)
const [device] = await Device.findOrCreate({
where: { id: detectionData.device_id },
defaults: {
id: detectionData.device_id,
geo_lat: detectionData.geo_lat || 0,
geo_lon: detectionData.geo_lon || 0,
last_heartbeat: new Date()
}
});
// Create detection record
const detection = await DroneDetection.create({
...detectionData,
server_timestamp: new Date()
});
// Process detection through tracking service for movement analysis (from original)
const movementAnalysis = droneTracker.processDetection({
...detectionData,
server_timestamp: detection.server_timestamp
});
// Emit real-time update via Socket.IO with movement analysis (from original)
req.io.emit('drone_detection', {
id: detection.id,
device_id: detection.device_id,
drone_id: detection.drone_id,
drone_type: detection.drone_type,
rssi: detection.rssi,
freq: detection.freq,
geo_lat: detection.geo_lat,
geo_lon: detection.geo_lon,
server_timestamp: detection.server_timestamp,
confidence_level: detection.confidence_level,
signal_duration: detection.signal_duration,
movement_analysis: movementAnalysis,
device: {
id: device.id,
name: device.name,
geo_lat: device.geo_lat,
geo_lon: device.geo_lon
}
});
// Process alerts asynchronously (from original)
alertService.processAlert(detection, req.io).catch(error => {
console.error('Alert processing error:', error);
});
console.log(`✅ Detection recorded and alert processing initiated for detection ${detection.id}`);
return res.status(201).json({
success: true,
data: {
...detection.toJSON(),
movement_analysis: movementAnalysis
},
message: 'Drone detection recorded successfully'
});
}
// Helper function for string hashing (if key is not numeric)
String.prototype.hashCode = function() {
let hash = 0;
if (this.length === 0) return hash;
for (let i = 0; i < this.length; i++) {
const char = this.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return Math.abs(hash);
};
module.exports = router;