2026-01-09 09:36:43 +00:00
|
|
|
import { SnmpProtocol, SNMP_OIDS, type ISnmpOptions } from '../protocols/index.js';
|
2026-01-09 07:14:39 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extended UPS-MIB OIDs (RFC 1628)
|
|
|
|
|
*/
|
|
|
|
|
export const UPS_SNMP_OIDS = {
|
|
|
|
|
// Identity
|
|
|
|
|
upsIdentManufacturer: '1.3.6.1.2.1.33.1.1.1.0',
|
|
|
|
|
upsIdentModel: '1.3.6.1.2.1.33.1.1.2.0',
|
|
|
|
|
upsIdentUPSSoftwareVersion: '1.3.6.1.2.1.33.1.1.3.0',
|
|
|
|
|
upsIdentAgentSoftwareVersion: '1.3.6.1.2.1.33.1.1.4.0',
|
|
|
|
|
upsIdentName: '1.3.6.1.2.1.33.1.1.5.0',
|
|
|
|
|
upsIdentAttachedDevices: '1.3.6.1.2.1.33.1.1.6.0',
|
|
|
|
|
|
|
|
|
|
// Battery group
|
|
|
|
|
upsBatteryStatus: '1.3.6.1.2.1.33.1.2.1.0',
|
|
|
|
|
upsSecondsOnBattery: '1.3.6.1.2.1.33.1.2.2.0',
|
|
|
|
|
upsEstimatedMinutesRemaining: '1.3.6.1.2.1.33.1.2.3.0',
|
|
|
|
|
upsEstimatedChargeRemaining: '1.3.6.1.2.1.33.1.2.4.0',
|
|
|
|
|
upsBatteryVoltage: '1.3.6.1.2.1.33.1.2.5.0',
|
|
|
|
|
upsBatteryCurrent: '1.3.6.1.2.1.33.1.2.6.0',
|
|
|
|
|
upsBatteryTemperature: '1.3.6.1.2.1.33.1.2.7.0',
|
|
|
|
|
|
|
|
|
|
// Input group
|
|
|
|
|
upsInputLineBads: '1.3.6.1.2.1.33.1.3.1.0',
|
|
|
|
|
upsInputNumLines: '1.3.6.1.2.1.33.1.3.2.0',
|
|
|
|
|
upsInputLineIndex: '1.3.6.1.2.1.33.1.3.3.1.1',
|
|
|
|
|
upsInputFrequency: '1.3.6.1.2.1.33.1.3.3.1.2',
|
|
|
|
|
upsInputVoltage: '1.3.6.1.2.1.33.1.3.3.1.3',
|
|
|
|
|
upsInputCurrent: '1.3.6.1.2.1.33.1.3.3.1.4',
|
|
|
|
|
upsInputTruePower: '1.3.6.1.2.1.33.1.3.3.1.5',
|
|
|
|
|
|
|
|
|
|
// Output group
|
|
|
|
|
upsOutputSource: '1.3.6.1.2.1.33.1.4.1.0',
|
|
|
|
|
upsOutputFrequency: '1.3.6.1.2.1.33.1.4.2.0',
|
|
|
|
|
upsOutputNumLines: '1.3.6.1.2.1.33.1.4.3.0',
|
|
|
|
|
upsOutputLineIndex: '1.3.6.1.2.1.33.1.4.4.1.1',
|
|
|
|
|
upsOutputVoltage: '1.3.6.1.2.1.33.1.4.4.1.2',
|
|
|
|
|
upsOutputCurrent: '1.3.6.1.2.1.33.1.4.4.1.3',
|
|
|
|
|
upsOutputPower: '1.3.6.1.2.1.33.1.4.4.1.4',
|
|
|
|
|
upsOutputPercentLoad: '1.3.6.1.2.1.33.1.4.4.1.5',
|
|
|
|
|
|
|
|
|
|
// Bypass group
|
|
|
|
|
upsBypassFrequency: '1.3.6.1.2.1.33.1.5.1.0',
|
|
|
|
|
upsBypassNumLines: '1.3.6.1.2.1.33.1.5.2.0',
|
|
|
|
|
|
|
|
|
|
// Alarm group
|
|
|
|
|
upsAlarmsPresent: '1.3.6.1.2.1.33.1.6.1.0',
|
|
|
|
|
|
|
|
|
|
// Test group
|
|
|
|
|
upsTestId: '1.3.6.1.2.1.33.1.7.1.0',
|
|
|
|
|
upsTestSpinLock: '1.3.6.1.2.1.33.1.7.2.0',
|
|
|
|
|
upsTestResultsSummary: '1.3.6.1.2.1.33.1.7.3.0',
|
|
|
|
|
upsTestResultsDetail: '1.3.6.1.2.1.33.1.7.4.0',
|
|
|
|
|
upsTestStartTime: '1.3.6.1.2.1.33.1.7.5.0',
|
|
|
|
|
upsTestElapsedTime: '1.3.6.1.2.1.33.1.7.6.0',
|
|
|
|
|
|
|
|
|
|
// Control group
|
|
|
|
|
upsShutdownType: '1.3.6.1.2.1.33.1.8.1.0',
|
|
|
|
|
upsShutdownAfterDelay: '1.3.6.1.2.1.33.1.8.2.0',
|
|
|
|
|
upsStartupAfterDelay: '1.3.6.1.2.1.33.1.8.3.0',
|
|
|
|
|
upsRebootWithDuration: '1.3.6.1.2.1.33.1.8.4.0',
|
|
|
|
|
upsAutoRestart: '1.3.6.1.2.1.33.1.8.5.0',
|
|
|
|
|
|
|
|
|
|
// Config group
|
|
|
|
|
upsConfigInputVoltage: '1.3.6.1.2.1.33.1.9.1.0',
|
|
|
|
|
upsConfigInputFreq: '1.3.6.1.2.1.33.1.9.2.0',
|
|
|
|
|
upsConfigOutputVoltage: '1.3.6.1.2.1.33.1.9.3.0',
|
|
|
|
|
upsConfigOutputFreq: '1.3.6.1.2.1.33.1.9.4.0',
|
|
|
|
|
upsConfigOutputVA: '1.3.6.1.2.1.33.1.9.5.0',
|
|
|
|
|
upsConfigOutputPower: '1.3.6.1.2.1.33.1.9.6.0',
|
|
|
|
|
upsConfigLowBattTime: '1.3.6.1.2.1.33.1.9.7.0',
|
|
|
|
|
upsConfigAudibleStatus: '1.3.6.1.2.1.33.1.9.8.0',
|
|
|
|
|
upsConfigLowVoltageTransferPoint: '1.3.6.1.2.1.33.1.9.9.0',
|
|
|
|
|
upsConfigHighVoltageTransferPoint: '1.3.6.1.2.1.33.1.9.10.0',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Battery status values from UPS-MIB
|
|
|
|
|
*/
|
|
|
|
|
export type TUpsBatteryStatus =
|
|
|
|
|
| 'unknown'
|
|
|
|
|
| 'batteryNormal'
|
|
|
|
|
| 'batteryLow'
|
|
|
|
|
| 'batteryDepleted';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Output source values from UPS-MIB
|
|
|
|
|
*/
|
|
|
|
|
export type TUpsOutputSource =
|
|
|
|
|
| 'other'
|
|
|
|
|
| 'none'
|
|
|
|
|
| 'normal'
|
|
|
|
|
| 'bypass'
|
|
|
|
|
| 'battery'
|
|
|
|
|
| 'booster'
|
|
|
|
|
| 'reducer';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test results summary from UPS-MIB
|
|
|
|
|
*/
|
|
|
|
|
export type TUpsTestResult =
|
|
|
|
|
| 'donePass'
|
|
|
|
|
| 'doneWarning'
|
|
|
|
|
| 'doneError'
|
|
|
|
|
| 'aborted'
|
|
|
|
|
| 'inProgress'
|
|
|
|
|
| 'noTestsInitiated';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* SNMP-based UPS status interface
|
|
|
|
|
*/
|
|
|
|
|
export interface IUpsSnmpStatus {
|
|
|
|
|
batteryStatus: TUpsBatteryStatus;
|
|
|
|
|
secondsOnBattery: number;
|
|
|
|
|
estimatedMinutesRemaining: number;
|
|
|
|
|
estimatedChargeRemaining: number;
|
|
|
|
|
batteryVoltage: number;
|
|
|
|
|
batteryTemperature: number;
|
|
|
|
|
outputSource: TUpsOutputSource;
|
|
|
|
|
outputFrequency: number;
|
|
|
|
|
outputVoltage: number;
|
|
|
|
|
outputCurrent: number;
|
|
|
|
|
outputPower: number;
|
|
|
|
|
outputPercentLoad: number;
|
|
|
|
|
inputFrequency: number;
|
|
|
|
|
inputVoltage: number;
|
|
|
|
|
alarmsPresent: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* UPS SNMP handler for querying UPS devices via SNMP
|
|
|
|
|
*/
|
|
|
|
|
export class UpsSnmpHandler {
|
|
|
|
|
private protocol: SnmpProtocol;
|
|
|
|
|
|
|
|
|
|
constructor(address: string, options?: ISnmpOptions) {
|
|
|
|
|
this.protocol = new SnmpProtocol(address, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Close SNMP session
|
|
|
|
|
*/
|
|
|
|
|
public close(): void {
|
|
|
|
|
this.protocol.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get UPS identity information
|
|
|
|
|
*/
|
|
|
|
|
public async getIdentity(): Promise<{
|
|
|
|
|
manufacturer: string;
|
|
|
|
|
model: string;
|
|
|
|
|
softwareVersion: string;
|
|
|
|
|
name: string;
|
|
|
|
|
}> {
|
|
|
|
|
const varbinds = await this.protocol.getMultiple([
|
|
|
|
|
UPS_SNMP_OIDS.upsIdentManufacturer,
|
|
|
|
|
UPS_SNMP_OIDS.upsIdentModel,
|
|
|
|
|
UPS_SNMP_OIDS.upsIdentUPSSoftwareVersion,
|
|
|
|
|
UPS_SNMP_OIDS.upsIdentName,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const getValue = (oid: string): string => {
|
|
|
|
|
const vb = varbinds.find((v) => v.oid === oid);
|
|
|
|
|
return String(vb?.value || '');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
manufacturer: getValue(UPS_SNMP_OIDS.upsIdentManufacturer),
|
|
|
|
|
model: getValue(UPS_SNMP_OIDS.upsIdentModel),
|
|
|
|
|
softwareVersion: getValue(UPS_SNMP_OIDS.upsIdentUPSSoftwareVersion),
|
|
|
|
|
name: getValue(UPS_SNMP_OIDS.upsIdentName),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get battery status
|
|
|
|
|
*/
|
|
|
|
|
public async getBatteryStatus(): Promise<{
|
|
|
|
|
status: TUpsBatteryStatus;
|
|
|
|
|
secondsOnBattery: number;
|
|
|
|
|
estimatedMinutesRemaining: number;
|
|
|
|
|
estimatedChargeRemaining: number;
|
|
|
|
|
voltage: number;
|
|
|
|
|
temperature: number;
|
|
|
|
|
}> {
|
|
|
|
|
const varbinds = await this.protocol.getMultiple([
|
|
|
|
|
UPS_SNMP_OIDS.upsBatteryStatus,
|
|
|
|
|
UPS_SNMP_OIDS.upsSecondsOnBattery,
|
|
|
|
|
UPS_SNMP_OIDS.upsEstimatedMinutesRemaining,
|
|
|
|
|
UPS_SNMP_OIDS.upsEstimatedChargeRemaining,
|
|
|
|
|
UPS_SNMP_OIDS.upsBatteryVoltage,
|
|
|
|
|
UPS_SNMP_OIDS.upsBatteryTemperature,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const getValue = (oid: string): number => {
|
|
|
|
|
const vb = varbinds.find((v) => v.oid === oid);
|
|
|
|
|
return Number(vb?.value || 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const statusMap: Record<number, TUpsBatteryStatus> = {
|
|
|
|
|
1: 'unknown',
|
|
|
|
|
2: 'batteryNormal',
|
|
|
|
|
3: 'batteryLow',
|
|
|
|
|
4: 'batteryDepleted',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
status: statusMap[getValue(UPS_SNMP_OIDS.upsBatteryStatus)] || 'unknown',
|
|
|
|
|
secondsOnBattery: getValue(UPS_SNMP_OIDS.upsSecondsOnBattery),
|
|
|
|
|
estimatedMinutesRemaining: getValue(UPS_SNMP_OIDS.upsEstimatedMinutesRemaining),
|
|
|
|
|
estimatedChargeRemaining: getValue(UPS_SNMP_OIDS.upsEstimatedChargeRemaining),
|
|
|
|
|
voltage: getValue(UPS_SNMP_OIDS.upsBatteryVoltage) / 10, // Typically in 0.1V units
|
|
|
|
|
temperature: getValue(UPS_SNMP_OIDS.upsBatteryTemperature),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get input status
|
|
|
|
|
*/
|
|
|
|
|
public async getInputStatus(): Promise<{
|
|
|
|
|
frequency: number;
|
|
|
|
|
voltage: number;
|
|
|
|
|
lineBads: number;
|
|
|
|
|
}> {
|
|
|
|
|
const varbinds = await this.protocol.getMultiple([
|
|
|
|
|
UPS_SNMP_OIDS.upsInputFrequency + '.1', // Line 1
|
|
|
|
|
UPS_SNMP_OIDS.upsInputVoltage + '.1', // Line 1
|
|
|
|
|
UPS_SNMP_OIDS.upsInputLineBads,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const getValue = (oid: string): number => {
|
|
|
|
|
const vb = varbinds.find((v) => v.oid === oid);
|
|
|
|
|
return Number(vb?.value || 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
frequency: getValue(UPS_SNMP_OIDS.upsInputFrequency + '.1') / 10, // 0.1 Hz units
|
|
|
|
|
voltage: getValue(UPS_SNMP_OIDS.upsInputVoltage + '.1'),
|
|
|
|
|
lineBads: getValue(UPS_SNMP_OIDS.upsInputLineBads),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get output status
|
|
|
|
|
*/
|
|
|
|
|
public async getOutputStatus(): Promise<{
|
|
|
|
|
source: TUpsOutputSource;
|
|
|
|
|
frequency: number;
|
|
|
|
|
voltage: number;
|
|
|
|
|
current: number;
|
|
|
|
|
power: number;
|
|
|
|
|
percentLoad: number;
|
|
|
|
|
}> {
|
|
|
|
|
const varbinds = await this.protocol.getMultiple([
|
|
|
|
|
UPS_SNMP_OIDS.upsOutputSource,
|
|
|
|
|
UPS_SNMP_OIDS.upsOutputFrequency,
|
|
|
|
|
UPS_SNMP_OIDS.upsOutputVoltage + '.1', // Line 1
|
|
|
|
|
UPS_SNMP_OIDS.upsOutputCurrent + '.1', // Line 1
|
|
|
|
|
UPS_SNMP_OIDS.upsOutputPower + '.1', // Line 1
|
|
|
|
|
UPS_SNMP_OIDS.upsOutputPercentLoad + '.1', // Line 1
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const getValue = (oid: string): number => {
|
|
|
|
|
const vb = varbinds.find((v) => v.oid === oid);
|
|
|
|
|
return Number(vb?.value || 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const sourceMap: Record<number, TUpsOutputSource> = {
|
|
|
|
|
1: 'other',
|
|
|
|
|
2: 'none',
|
|
|
|
|
3: 'normal',
|
|
|
|
|
4: 'bypass',
|
|
|
|
|
5: 'battery',
|
|
|
|
|
6: 'booster',
|
|
|
|
|
7: 'reducer',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
source: sourceMap[getValue(UPS_SNMP_OIDS.upsOutputSource)] || 'other',
|
|
|
|
|
frequency: getValue(UPS_SNMP_OIDS.upsOutputFrequency) / 10, // 0.1 Hz units
|
|
|
|
|
voltage: getValue(UPS_SNMP_OIDS.upsOutputVoltage + '.1'),
|
|
|
|
|
current: getValue(UPS_SNMP_OIDS.upsOutputCurrent + '.1') / 10, // 0.1 A units
|
|
|
|
|
power: getValue(UPS_SNMP_OIDS.upsOutputPower + '.1'),
|
|
|
|
|
percentLoad: getValue(UPS_SNMP_OIDS.upsOutputPercentLoad + '.1'),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get full UPS status
|
|
|
|
|
*/
|
|
|
|
|
public async getFullStatus(): Promise<IUpsSnmpStatus> {
|
|
|
|
|
const [battery, input, output] = await Promise.all([
|
|
|
|
|
this.getBatteryStatus(),
|
|
|
|
|
this.getInputStatus(),
|
|
|
|
|
this.getOutputStatus(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Get alarms separately
|
|
|
|
|
let alarmsPresent = 0;
|
|
|
|
|
try {
|
|
|
|
|
const alarmVb = await this.protocol.get(UPS_SNMP_OIDS.upsAlarmsPresent);
|
|
|
|
|
alarmsPresent = Number(alarmVb.value || 0);
|
|
|
|
|
} catch {
|
|
|
|
|
// Ignore alarm fetch errors
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
batteryStatus: battery.status,
|
|
|
|
|
secondsOnBattery: battery.secondsOnBattery,
|
|
|
|
|
estimatedMinutesRemaining: battery.estimatedMinutesRemaining,
|
|
|
|
|
estimatedChargeRemaining: battery.estimatedChargeRemaining,
|
|
|
|
|
batteryVoltage: battery.voltage,
|
|
|
|
|
batteryTemperature: battery.temperature,
|
|
|
|
|
outputSource: output.source,
|
|
|
|
|
outputFrequency: output.frequency,
|
|
|
|
|
outputVoltage: output.voltage,
|
|
|
|
|
outputCurrent: output.current,
|
|
|
|
|
outputPower: output.power,
|
|
|
|
|
outputPercentLoad: output.percentLoad,
|
|
|
|
|
inputFrequency: input.frequency,
|
|
|
|
|
inputVoltage: input.voltage,
|
|
|
|
|
alarmsPresent,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if UPS-MIB is supported on device
|
|
|
|
|
*/
|
|
|
|
|
public async isUpsDevice(): Promise<boolean> {
|
|
|
|
|
try {
|
|
|
|
|
const vb = await this.protocol.get(UPS_SNMP_OIDS.upsBatteryStatus);
|
|
|
|
|
return vb.value !== null && vb.value !== undefined;
|
|
|
|
|
} catch {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get configuration info
|
|
|
|
|
*/
|
|
|
|
|
public async getConfiguration(): Promise<{
|
|
|
|
|
inputVoltage: number;
|
|
|
|
|
inputFrequency: number;
|
|
|
|
|
outputVoltage: number;
|
|
|
|
|
outputFrequency: number;
|
|
|
|
|
outputVA: number;
|
|
|
|
|
outputPower: number;
|
|
|
|
|
lowBatteryTime: number;
|
|
|
|
|
}> {
|
|
|
|
|
const varbinds = await this.protocol.getMultiple([
|
|
|
|
|
UPS_SNMP_OIDS.upsConfigInputVoltage,
|
|
|
|
|
UPS_SNMP_OIDS.upsConfigInputFreq,
|
|
|
|
|
UPS_SNMP_OIDS.upsConfigOutputVoltage,
|
|
|
|
|
UPS_SNMP_OIDS.upsConfigOutputFreq,
|
|
|
|
|
UPS_SNMP_OIDS.upsConfigOutputVA,
|
|
|
|
|
UPS_SNMP_OIDS.upsConfigOutputPower,
|
|
|
|
|
UPS_SNMP_OIDS.upsConfigLowBattTime,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const getValue = (oid: string): number => {
|
|
|
|
|
const vb = varbinds.find((v) => v.oid === oid);
|
|
|
|
|
return Number(vb?.value || 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
inputVoltage: getValue(UPS_SNMP_OIDS.upsConfigInputVoltage),
|
|
|
|
|
inputFrequency: getValue(UPS_SNMP_OIDS.upsConfigInputFreq) / 10,
|
|
|
|
|
outputVoltage: getValue(UPS_SNMP_OIDS.upsConfigOutputVoltage),
|
|
|
|
|
outputFrequency: getValue(UPS_SNMP_OIDS.upsConfigOutputFreq) / 10,
|
|
|
|
|
outputVA: getValue(UPS_SNMP_OIDS.upsConfigOutputVA),
|
|
|
|
|
outputPower: getValue(UPS_SNMP_OIDS.upsConfigOutputPower),
|
|
|
|
|
lowBatteryTime: getValue(UPS_SNMP_OIDS.upsConfigLowBattTime),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|