import type { IActionConfig } from './actions/base-action.ts'; import { NETWORK } from './constants.ts'; import type { IUpsStatus as IProtocolUpsStatus } from './snmp/types.ts'; import { createInitialUpsStatus, type IUpsIdentity, type IUpsStatus } from './ups-status.ts'; export interface ISuccessfulUpsPollSnapshot { updatedStatus: IUpsStatus; transition: 'none' | 'recovered' | 'powerStatusChange'; previousStatus?: IUpsStatus; downtimeSeconds?: number; } export interface IFailedUpsPollSnapshot { updatedStatus: IUpsStatus; transition: 'none' | 'unreachable'; failures: number; previousStatus?: IUpsStatus; } export function ensureUpsStatus( currentStatus: IUpsStatus | undefined, ups: IUpsIdentity, now: number = Date.now(), ): IUpsStatus { return currentStatus || createInitialUpsStatus(ups, now); } export function buildSuccessfulUpsPollSnapshot( ups: IUpsIdentity, polledStatus: IProtocolUpsStatus, currentStatus: IUpsStatus | undefined, currentTime: number, ): ISuccessfulUpsPollSnapshot { const previousStatus = ensureUpsStatus(currentStatus, ups, currentTime); const updatedStatus: IUpsStatus = { id: ups.id, name: ups.name, powerStatus: polledStatus.powerStatus, batteryCapacity: polledStatus.batteryCapacity, batteryRuntime: polledStatus.batteryRuntime, outputLoad: polledStatus.outputLoad, outputPower: polledStatus.outputPower, outputVoltage: polledStatus.outputVoltage, outputCurrent: polledStatus.outputCurrent, lastCheckTime: currentTime, lastStatusChange: previousStatus.lastStatusChange || currentTime, consecutiveFailures: 0, unreachableSince: 0, }; if (previousStatus.powerStatus === 'unreachable') { updatedStatus.lastStatusChange = currentTime; return { updatedStatus, transition: 'recovered', previousStatus, downtimeSeconds: Math.round((currentTime - previousStatus.unreachableSince) / 1000), }; } if (previousStatus.powerStatus !== polledStatus.powerStatus) { updatedStatus.lastStatusChange = currentTime; return { updatedStatus, transition: 'powerStatusChange', previousStatus, }; } return { updatedStatus, transition: 'none', previousStatus: currentStatus, }; } export function buildFailedUpsPollSnapshot( ups: IUpsIdentity, currentStatus: IUpsStatus | undefined, currentTime: number, ): IFailedUpsPollSnapshot { const previousStatus = ensureUpsStatus(currentStatus, ups, currentTime); const failures = Math.min( previousStatus.consecutiveFailures + 1, NETWORK.MAX_CONSECUTIVE_FAILURES, ); if ( failures >= NETWORK.CONSECUTIVE_FAILURE_THRESHOLD && previousStatus.powerStatus !== 'unreachable' ) { return { updatedStatus: { ...previousStatus, consecutiveFailures: failures, powerStatus: 'unreachable', unreachableSince: currentTime, lastStatusChange: currentTime, }, transition: 'unreachable', failures, previousStatus, }; } return { updatedStatus: { ...previousStatus, consecutiveFailures: failures, }, transition: 'none', failures, previousStatus: currentStatus, }; } export function hasThresholdViolation( powerStatus: IProtocolUpsStatus['powerStatus'], batteryCapacity: number, batteryRuntime: number, actions: IActionConfig[] | undefined, ): boolean { return getActionThresholdStates(powerStatus, batteryCapacity, batteryRuntime, actions).some( Boolean, ); } export function isActionThresholdExceeded( actionConfig: IActionConfig, powerStatus: IProtocolUpsStatus['powerStatus'], batteryCapacity: number, batteryRuntime: number, ): boolean { if (powerStatus !== 'onBattery' || !actionConfig.thresholds) { return false; } return ( batteryCapacity < actionConfig.thresholds.battery || batteryRuntime < actionConfig.thresholds.runtime ); } export function getActionThresholdStates( powerStatus: IProtocolUpsStatus['powerStatus'], batteryCapacity: number, batteryRuntime: number, actions: IActionConfig[] | undefined, ): boolean[] { if (!actions || actions.length === 0) { return []; } return actions.map((actionConfig) => isActionThresholdExceeded(actionConfig, powerStatus, batteryCapacity, batteryRuntime) ); } export function getEnteredThresholdIndexes( previousStates: boolean[] | undefined, currentStates: boolean[], ): number[] { const enteredIndexes: number[] = []; for (let index = 0; index < currentStates.length; index++) { if (currentStates[index] && !previousStates?.[index]) { enteredIndexes.push(index); } } return enteredIndexes; }