/** * Auto-Recording Manager * * This module handles automatic recording start/stop based on MQTT machine state changes. * It monitors MQTT events and triggers camera recording when machines turn on/off. */ import { visionApi, type MqttEvent, type CameraConfig } from './visionApi' export interface AutoRecordingState { cameraName: string machineState: 'on' | 'off' isRecording: boolean autoRecordEnabled: boolean lastStateChange: Date } export class AutoRecordingManager { private cameras: Map = new Map() private mqttPollingInterval: NodeJS.Timeout | null = null private lastProcessedEventNumber = 0 private isRunning = false constructor(private pollingIntervalMs: number = 2000) {} /** * Start the auto-recording manager */ async start(): Promise { if (this.isRunning) { console.warn('Auto-recording manager is already running') return } console.log('Starting auto-recording manager...') this.isRunning = true // Initialize camera configurations await this.initializeCameras() // Start polling for MQTT events this.startMqttPolling() } /** * Stop the auto-recording manager */ stop(): void { if (!this.isRunning) { return } console.log('Stopping auto-recording manager...') this.isRunning = false if (this.mqttPollingInterval) { clearInterval(this.mqttPollingInterval) this.mqttPollingInterval = null } this.cameras.clear() } /** * Initialize camera configurations and states */ private async initializeCameras(): Promise { try { const cameras = await visionApi.getCameras() for (const [cameraName, cameraStatus] of Object.entries(cameras)) { try { const config = await visionApi.getCameraConfig(cameraName) this.cameras.set(cameraName, { cameraName, machineState: 'off', // Default to off isRecording: cameraStatus.is_recording, autoRecordEnabled: config.auto_record_on_machine_start, lastStateChange: new Date() }) console.log(`Initialized camera ${cameraName}: auto-record=${config.auto_record_on_machine_start}, machine=${config.machine_topic}`) } catch (error) { console.error(`Failed to initialize camera ${cameraName}:`, error) } } } catch (error) { console.error('Failed to initialize cameras:', error) } } /** * Start polling for MQTT events */ private startMqttPolling(): void { this.mqttPollingInterval = setInterval(async () => { if (!this.isRunning) { return } try { await this.processMqttEvents() } catch (error) { console.error('Error processing MQTT events:', error) } }, this.pollingIntervalMs) } /** * Process new MQTT events and trigger recording actions */ private async processMqttEvents(): Promise { try { const mqttResponse = await visionApi.getMqttEvents(50) // Get recent events // Filter for new events we haven't processed yet const newEvents = mqttResponse.events.filter( event => event.message_number > this.lastProcessedEventNumber ) if (newEvents.length === 0) { return } // Update last processed event number this.lastProcessedEventNumber = Math.max( ...newEvents.map(event => event.message_number) ) // Process each new event for (const event of newEvents) { await this.handleMqttEvent(event) } } catch (error) { console.error('Failed to fetch MQTT events:', error) } } /** * Handle a single MQTT event and trigger recording if needed */ private async handleMqttEvent(event: MqttEvent): Promise { const { machine_name, normalized_state } = event // Find cameras that are configured for this machine const affectedCameras = await this.getCamerasForMachine(machine_name) for (const cameraName of affectedCameras) { const cameraState = this.cameras.get(cameraName) if (!cameraState || !cameraState.autoRecordEnabled) { continue } const newMachineState = normalized_state as 'on' | 'off' // Skip if state hasn't changed if (cameraState.machineState === newMachineState) { continue } console.log(`Machine ${machine_name} changed from ${cameraState.machineState} to ${newMachineState} - Camera: ${cameraName}`) // Update camera state cameraState.machineState = newMachineState cameraState.lastStateChange = new Date() // Trigger recording action if (newMachineState === 'on' && !cameraState.isRecording) { await this.startAutoRecording(cameraName, machine_name) } else if (newMachineState === 'off' && cameraState.isRecording) { await this.stopAutoRecording(cameraName, machine_name) } } } /** * Get cameras that are configured for a specific machine */ private async getCamerasForMachine(machineName: string): Promise { const cameras: string[] = [] // Define the correct machine-to-camera mapping const machineToCamera: Record = { 'blower_separator': 'camera1', // camera1 is for blower separator 'vibratory_conveyor': 'camera2' // camera2 is for conveyor } const expectedCamera = machineToCamera[machineName] if (!expectedCamera) { console.warn(`No camera mapping found for machine: ${machineName}`) return cameras } try { const allCameras = await visionApi.getCameras() // Check if the expected camera exists and has auto-recording enabled if (allCameras[expectedCamera]) { try { const config = await visionApi.getCameraConfig(expectedCamera) if (config.auto_record_on_machine_start) { cameras.push(expectedCamera) console.log(`Found camera ${expectedCamera} configured for machine ${machineName}`) } else { console.log(`Camera ${expectedCamera} exists but auto-recording is disabled`) } } catch (error) { console.error(`Failed to get config for camera ${expectedCamera}:`, error) } } else { console.warn(`Expected camera ${expectedCamera} not found for machine ${machineName}`) } } catch (error) { console.error('Failed to get cameras for machine:', error) } return cameras } /** * Start auto-recording for a camera */ private async startAutoRecording(cameraName: string, machineName: string): Promise { try { const timestamp = new Date().toISOString().replace(/[:.]/g, '-') const filename = `auto_${machineName}_${timestamp}.mp4` const result = await visionApi.startRecording(cameraName, { filename }) if (result.success) { const cameraState = this.cameras.get(cameraName) if (cameraState) { cameraState.isRecording = true } console.log(`✅ Auto-recording started for ${cameraName}: ${result.filename}`) } else { console.error(`❌ Failed to start auto-recording for ${cameraName}:`, result.message) } } catch (error) { console.error(`❌ Error starting auto-recording for ${cameraName}:`, error) } } /** * Stop auto-recording for a camera */ private async stopAutoRecording(cameraName: string, machineName: string): Promise { try { const result = await visionApi.stopRecording(cameraName) if (result.success) { const cameraState = this.cameras.get(cameraName) if (cameraState) { cameraState.isRecording = false } console.log(`⏹️ Auto-recording stopped for ${cameraName} (${result.duration_seconds}s)`) } else { console.error(`❌ Failed to stop auto-recording for ${cameraName}:`, result.message) } } catch (error) { console.error(`❌ Error stopping auto-recording for ${cameraName}:`, error) } } /** * Get current auto-recording states for all cameras */ getStates(): AutoRecordingState[] { return Array.from(this.cameras.values()) } /** * Refresh camera configurations (call when configs are updated) */ async refreshConfigurations(): Promise { await this.initializeCameras() } } // Global instance export const autoRecordingManager = new AutoRecordingManager()