/** * Base classes and interfaces for the NUPST action system * * Actions are triggered on: * 1. Power status changes (online ↔ onBattery) * 2. Threshold violations (battery/runtime cross below configured thresholds) */ export type TPowerStatus = 'online' | 'onBattery' | 'unknown'; /** * Context provided to actions when they execute * Contains all relevant UPS state and trigger information */ export interface IActionContext { // UPS identification /** Unique ID of the UPS */ upsId: string; /** Human-readable name of the UPS */ upsName: string; // Current state /** Current power status */ powerStatus: TPowerStatus; /** Current battery capacity percentage (0-100) */ batteryCapacity: number; /** Estimated battery runtime in minutes */ batteryRuntime: number; // State tracking /** Previous power status before this trigger */ previousPowerStatus: TPowerStatus; // Metadata /** Timestamp when this action was triggered (milliseconds since epoch) */ timestamp: number; /** Reason this action was triggered */ triggerReason: 'powerStatusChange' | 'thresholdViolation'; } /** * Action trigger mode - determines when an action executes */ export type TActionTriggerMode = | 'onlyPowerChanges' // Only on power status changes (online ↔ onBattery) | 'onlyThresholds' // Only when action's thresholds are exceeded | 'powerChangesAndThresholds' // On power changes OR threshold violations | 'anyChange'; // On every UPS poll/check (every ~30s) /** * Configuration for an action */ export interface IActionConfig { /** Type of action to execute */ type: 'shutdown' | 'webhook' | 'script'; // Trigger configuration /** * When should this action be triggered? * - onlyPowerChanges: Only on power status changes * - onlyThresholds: Only when thresholds exceeded * - powerChangesAndThresholds: On both (default) * - anyChange: On every check */ triggerMode?: TActionTriggerMode; // Threshold configuration (applies to all action types) /** Threshold settings for this action */ thresholds?: { /** Battery percentage threshold (0-100) */ battery: number; /** Runtime threshold in minutes */ runtime: number; }; // Shutdown action configuration /** Delay before shutdown in minutes (default: 5) */ shutdownDelay?: number; /** Only execute shutdown on threshold violation, not power status changes */ onlyOnThresholdViolation?: boolean; // Webhook action configuration /** URL to call for webhook */ webhookUrl?: string; /** HTTP method to use (default: POST) */ webhookMethod?: 'GET' | 'POST'; /** Timeout for webhook request in milliseconds (default: 10000) */ webhookTimeout?: number; /** Only execute webhook on threshold violation */ webhookOnlyOnThresholdViolation?: boolean; // Script action configuration /** Path to script relative to /etc/nupst (e.g., "myaction.sh") */ scriptPath?: string; /** Timeout for script execution in milliseconds (default: 60000) */ scriptTimeout?: number; /** Only execute script on threshold violation */ scriptOnlyOnThresholdViolation?: boolean; } /** * Abstract base class for all actions * Each action type must extend this class and implement execute() */ export abstract class Action { /** Type identifier for this action */ abstract readonly type: string; /** * Create a new action with the given configuration * @param config Action configuration */ constructor(protected config: IActionConfig) {} /** * Execute this action with the given context * @param context Current UPS state and trigger information */ abstract execute(context: IActionContext): Promise; /** * Helper to check if this action should execute based on trigger mode * @param context Action context with current UPS state * @returns True if action should execute */ protected shouldExecute(context: IActionContext): boolean { const mode = this.config.triggerMode || 'powerChangesAndThresholds'; // Default switch (mode) { case 'onlyPowerChanges': // Only execute on power status changes return context.triggerReason === 'powerStatusChange'; case 'onlyThresholds': // Only execute when this action's thresholds are exceeded if (!this.config.thresholds) return false; // No thresholds = never execute return this.areThresholdsExceeded(context.batteryCapacity, context.batteryRuntime); case 'powerChangesAndThresholds': // Execute on power changes OR when thresholds exceeded if (context.triggerReason === 'powerStatusChange') return true; if (!this.config.thresholds) return false; return this.areThresholdsExceeded(context.batteryCapacity, context.batteryRuntime); case 'anyChange': // Execute on every trigger (power change or threshold check) return true; default: return true; } } /** * Check if current battery/runtime exceeds this action's thresholds * @param batteryCapacity Current battery percentage * @param batteryRuntime Current runtime in minutes * @returns True if thresholds are exceeded */ protected areThresholdsExceeded(batteryCapacity: number, batteryRuntime: number): boolean { if (!this.config.thresholds) { return false; // No thresholds configured } return ( batteryCapacity < this.config.thresholds.battery || batteryRuntime < this.config.thresholds.runtime ); } }