Files
nupst/ts/ups-monitoring.ts
T

173 lines
4.6 KiB
TypeScript

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;
}