diff --git a/backend/src/routes/pulse.routes.ts b/backend/src/routes/pulse.routes.ts index 29b8470..9446da8 100644 --- a/backend/src/routes/pulse.routes.ts +++ b/backend/src/routes/pulse.routes.ts @@ -175,4 +175,157 @@ export async function pulseRoutes(fastify: FastifyInstance) { } } }); + + /** + * GET /pulse/thresholds + * Get current threshold configuration + */ + fastify.get('/thresholds', { + handler: async (request, reply) => { + return { + thresholds: pulseThresholds, + lastUpdated: thresholdsLastUpdated?.toISOString() || null + }; + } + }); + + /** + * POST /pulse/thresholds + * Set threshold alerts for Pulse sensors + */ + fastify.post('/thresholds', { + handler: async (request, reply) => { + const config = request.body as { + temperature?: { min: number; max: number }; + humidity?: { min: number; max: number }; + vpd?: { min: number; max: number }; + co2?: { min: number; max: number }; + }; + + if (config.temperature) { + pulseThresholds.temperature = config.temperature; + } + if (config.humidity) { + pulseThresholds.humidity = config.humidity; + } + if (config.vpd) { + pulseThresholds.vpd = config.vpd; + } + if (config.co2) { + pulseThresholds.co2 = config.co2; + } + + thresholdsLastUpdated = new Date(); + + fastify.log.info({ event: 'pulse_thresholds_updated', thresholds: pulseThresholds }); + + return { + success: true, + thresholds: pulseThresholds, + message: 'Thresholds updated successfully' + }; + } + }); + + /** + * POST /pulse/check + * Check current readings against thresholds and broadcast alerts + */ + fastify.post('/check', { + handler: async (request, reply) => { + const pulse = getPulseService(); + + if (!pulse) { + return reply.status(503).send({ error: 'Pulse not configured' }); + } + + try { + const readings = await pulse.getCurrentReadings(); + const alerts: any[] = []; + + for (const reading of readings) { + // Temperature check + if (reading.temperature !== undefined) { + if (pulseThresholds.temperature.max && reading.temperature > pulseThresholds.temperature.max) { + alerts.push(createAlert(reading, 'TEMPERATURE_HIGH', reading.temperature, pulseThresholds.temperature.max)); + } + if (pulseThresholds.temperature.min && reading.temperature < pulseThresholds.temperature.min) { + alerts.push(createAlert(reading, 'TEMPERATURE_LOW', reading.temperature, pulseThresholds.temperature.min)); + } + } + + // Humidity check + if (reading.humidity !== undefined) { + if (pulseThresholds.humidity.max && reading.humidity > pulseThresholds.humidity.max) { + alerts.push(createAlert(reading, 'HUMIDITY_HIGH', reading.humidity, pulseThresholds.humidity.max)); + } + if (pulseThresholds.humidity.min && reading.humidity < pulseThresholds.humidity.min) { + alerts.push(createAlert(reading, 'HUMIDITY_LOW', reading.humidity, pulseThresholds.humidity.min)); + } + } + + // VPD check + if (reading.vpd !== undefined) { + if (pulseThresholds.vpd.max && reading.vpd > pulseThresholds.vpd.max) { + alerts.push(createAlert(reading, 'VPD_HIGH', reading.vpd, pulseThresholds.vpd.max)); + } + if (pulseThresholds.vpd.min && reading.vpd < pulseThresholds.vpd.min) { + alerts.push(createAlert(reading, 'VPD_LOW', reading.vpd, pulseThresholds.vpd.min)); + } + } + + // CO2 check + if (reading.co2 !== undefined) { + if (pulseThresholds.co2.max && reading.co2 > pulseThresholds.co2.max) { + alerts.push(createAlert(reading, 'CO2_HIGH', reading.co2, pulseThresholds.co2.max)); + } + if (pulseThresholds.co2.min && reading.co2 < pulseThresholds.co2.min) { + alerts.push(createAlert(reading, 'CO2_LOW', reading.co2, pulseThresholds.co2.min)); + } + } + } + + // Broadcast alerts via WebSocket + for (const alert of alerts) { + broadcastAlert(alert); + } + + return { + devicesChecked: readings.length, + alertsTriggered: alerts.length, + alerts, + timestamp: new Date().toISOString() + }; + } catch (error: any) { + fastify.log.error(error); + return reply.status(500).send({ error: error.message }); + } + } + }); } + +// In-memory threshold storage (would be persisted to DB in production) +const pulseThresholds = { + temperature: { min: 65, max: 82 }, // °F + humidity: { min: 40, max: 70 }, // % + vpd: { min: 0.8, max: 1.2 }, // kPa + co2: { min: 400, max: 1500 } // ppm +}; + +let thresholdsLastUpdated: Date | null = null; + +// Import broadcast function +import { broadcastAlert } from '../plugins/websocket'; + +function createAlert(reading: any, type: string, value: number, threshold: number) { + return { + id: `pulse-${Date.now()}-${Math.random().toString(36).slice(2)}`, + type, + sensorName: reading.deviceName || `Device ${reading.deviceId}`, + value, + threshold, + message: `${reading.deviceName || 'Pulse Sensor'}: ${type.replace('_', ' ')} (${value.toFixed(1)} vs threshold ${threshold})`, + timestamp: new Date().toISOString() + }; +} +