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;