import cron from 'node-cron'; import { scheduleService, PresetActionPayload } from './scheduleService'; import { groupService } from './groupService'; import { WledPlaylist } from '../wled/types'; interface ScheduledTask { startTask: cron.ScheduledTask; endTask?: cron.ScheduledTask; scheduleId: string; } export class SchedulerService { private tasks: Map = new Map(); async initialize(): Promise { console.log('Initializing scheduler...'); const schedules = await scheduleService.getAllSchedules(); const enabledSchedules = schedules.filter((s) => s.enabled); console.log(`Found ${enabledSchedules.length} enabled schedules`); for (const schedule of enabledSchedules) { this.registerSchedule(schedule.id); } } async registerSchedule(scheduleId: string): Promise { // Remove existing task if any this.unregisterSchedule(scheduleId); const schedule = await scheduleService.getScheduleById(scheduleId); if (!schedule || !schedule.enabled) { return; } try { // Register start task const startTask = cron.schedule( schedule.cronExpression, async () => { console.log(`Executing schedule: ${schedule.name} (${schedule.id})`); await this.executeSchedule(scheduleId); }, { timezone: schedule.timezone, scheduled: true, } ); const scheduledTask: ScheduledTask = { startTask, scheduleId }; // Register end task if endCronExpression exists if (schedule.endCronExpression) { const endTask = cron.schedule( schedule.endCronExpression, async () => { console.log(`Executing end schedule (turning off): ${schedule.name} (${schedule.id})`); await this.turnOffGroup(schedule.groupId); }, { timezone: schedule.timezone, scheduled: true, } ); scheduledTask.endTask = endTask; console.log( `Registered schedule: ${schedule.name} with start: ${schedule.cronExpression}, end: ${schedule.endCronExpression} (${schedule.timezone})` ); } else { console.log( `Registered schedule: ${schedule.name} with cron: ${schedule.cronExpression} (${schedule.timezone})` ); } this.tasks.set(scheduleId, scheduledTask); } catch (error) { console.error(`Failed to register schedule ${scheduleId}:`, error); } } unregisterSchedule(scheduleId: string): void { const existing = this.tasks.get(scheduleId); if (existing) { existing.startTask.stop(); if (existing.endTask) { existing.endTask.stop(); } this.tasks.delete(scheduleId); console.log(`Unregistered schedule: ${scheduleId}`); } } private async executeSchedule(scheduleId: string): Promise { try { const schedule = await scheduleService.getScheduleById(scheduleId); if (!schedule) { console.error(`Schedule ${scheduleId} not found`); return; } const actionPayload = scheduleService.parseActionPayload(schedule); if (schedule.type === 'PRESET') { const payload = actionPayload as PresetActionPayload; console.log(`Applying preset ${payload.presetId} to group ${schedule.groupId}`); const result = await groupService.applyPresetToGroup(schedule.groupId, payload.presetId); console.log(`Preset applied. Success: ${result.results.success.length}, Failed: ${result.results.failed.length}`); } else if (schedule.type === 'PLAYLIST') { const payload = actionPayload as WledPlaylist; console.log(`Applying playlist to group ${schedule.groupId}`); const result = await groupService.applyPlaylistToGroup(schedule.groupId, payload); console.log(`Playlist applied. Success: ${result.results.success.length}, Failed: ${result.results.failed.length}`); } } catch (error) { console.error(`Error executing schedule ${scheduleId}:`, error); } } private async turnOffGroup(groupId: string): Promise { try { console.log(`Turning off group ${groupId}`); const result = await groupService.turnOffGroup(groupId); console.log(`Group turned off. Success: ${result.results.success.length}, Failed: ${result.results.failed.length}`); } catch (error) { console.error(`Error turning off group ${groupId}:`, error); } } async shutdown(): Promise { console.log('Shutting down scheduler...'); for (const [scheduleId] of this.tasks) { this.unregisterSchedule(scheduleId); } } } export const schedulerService = new SchedulerService();