initial
This commit is contained in:
471
ts/ups/ups.classes.nutprotocol.ts
Normal file
471
ts/ups/ups.classes.nutprotocol.ts
Normal file
@@ -0,0 +1,471 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
/**
|
||||
* NUT Protocol variable definitions
|
||||
*/
|
||||
export const NUT_VARIABLES = {
|
||||
// Device info
|
||||
deviceMfr: 'device.mfr',
|
||||
deviceModel: 'device.model',
|
||||
deviceSerial: 'device.serial',
|
||||
deviceType: 'device.type',
|
||||
|
||||
// UPS status
|
||||
upsStatus: 'ups.status',
|
||||
upsAlarm: 'ups.alarm',
|
||||
upsTime: 'ups.time',
|
||||
upsLoad: 'ups.load',
|
||||
upsTemperature: 'ups.temperature',
|
||||
|
||||
// Battery
|
||||
batteryCharge: 'battery.charge',
|
||||
batteryRuntime: 'battery.runtime',
|
||||
batteryVoltage: 'battery.voltage',
|
||||
batteryVoltageNominal: 'battery.voltage.nominal',
|
||||
batteryType: 'battery.type',
|
||||
batteryDate: 'battery.date',
|
||||
batteryTemperature: 'battery.temperature',
|
||||
|
||||
// Input
|
||||
inputVoltage: 'input.voltage',
|
||||
inputVoltageNominal: 'input.voltage.nominal',
|
||||
inputFrequency: 'input.frequency',
|
||||
inputFrequencyNominal: 'input.frequency.nominal',
|
||||
inputTransferHigh: 'input.transfer.high',
|
||||
inputTransferLow: 'input.transfer.low',
|
||||
|
||||
// Output
|
||||
outputVoltage: 'output.voltage',
|
||||
outputVoltageNominal: 'output.voltage.nominal',
|
||||
outputFrequency: 'output.frequency',
|
||||
outputCurrent: 'output.current',
|
||||
};
|
||||
|
||||
/**
|
||||
* NUT instant commands
|
||||
*/
|
||||
export const NUT_COMMANDS = {
|
||||
testBatteryStart: 'test.battery.start',
|
||||
testBatteryStartQuick: 'test.battery.start.quick',
|
||||
testBatteryStartDeep: 'test.battery.start.deep',
|
||||
testBatteryStop: 'test.battery.stop',
|
||||
calibrateStart: 'calibrate.start',
|
||||
calibrateStop: 'calibrate.stop',
|
||||
shutdown: 'shutdown.return',
|
||||
shutdownStayOff: 'shutdown.stayoff',
|
||||
shutdownStop: 'shutdown.stop',
|
||||
shutdownReboot: 'shutdown.reboot',
|
||||
beeperEnable: 'beeper.enable',
|
||||
beeperDisable: 'beeper.disable',
|
||||
beeperMute: 'beeper.mute',
|
||||
beeperToggle: 'beeper.toggle',
|
||||
loadOff: 'load.off',
|
||||
loadOn: 'load.on',
|
||||
};
|
||||
|
||||
/**
|
||||
* UPS status flags from NUT
|
||||
*/
|
||||
export type TNutStatusFlag =
|
||||
| 'OL' // Online (on utility power)
|
||||
| 'OB' // On battery
|
||||
| 'LB' // Low battery
|
||||
| 'HB' // High battery
|
||||
| 'RB' // Replace battery
|
||||
| 'CHRG' // Charging
|
||||
| 'DISCHRG' // Discharging
|
||||
| 'BYPASS' // On bypass
|
||||
| 'CAL' // Calibrating
|
||||
| 'OFF' // Offline
|
||||
| 'OVER' // Overloaded
|
||||
| 'TRIM' // Trimming voltage
|
||||
| 'BOOST' // Boosting voltage
|
||||
| 'FSD'; // Forced shutdown
|
||||
|
||||
/**
|
||||
* NUT UPS information
|
||||
*/
|
||||
export interface INutUpsInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* NUT variable
|
||||
*/
|
||||
export interface INutVariable {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* NUT Protocol handler for Network UPS Tools
|
||||
* TCP-based text protocol on port 3493
|
||||
*/
|
||||
export class NutProtocol {
|
||||
private socket: plugins.net.Socket | null = null;
|
||||
private address: string;
|
||||
private port: number;
|
||||
private connected: boolean = false;
|
||||
private responseBuffer: string = '';
|
||||
private responseResolver: ((value: string[]) => void) | null = null;
|
||||
private responseRejecter: ((error: Error) => void) | null = null;
|
||||
|
||||
constructor(address: string, port: number = 3493) {
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to NUT server
|
||||
*/
|
||||
public async connect(): Promise<void> {
|
||||
if (this.connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket = new plugins.net.Socket();
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (this.socket) {
|
||||
this.socket.destroy();
|
||||
this.socket = null;
|
||||
}
|
||||
reject(new Error(`Connection timeout to ${this.address}:${this.port}`));
|
||||
}, 5000);
|
||||
|
||||
this.socket.on('connect', () => {
|
||||
clearTimeout(timeout);
|
||||
this.connected = true;
|
||||
resolve();
|
||||
});
|
||||
|
||||
this.socket.on('error', (err) => {
|
||||
clearTimeout(timeout);
|
||||
this.connected = false;
|
||||
if (this.responseRejecter) {
|
||||
this.responseRejecter(err);
|
||||
this.responseRejecter = null;
|
||||
this.responseResolver = null;
|
||||
}
|
||||
reject(err);
|
||||
});
|
||||
|
||||
this.socket.on('data', (data) => {
|
||||
this.handleData(data);
|
||||
});
|
||||
|
||||
this.socket.on('close', () => {
|
||||
this.connected = false;
|
||||
if (this.responseRejecter) {
|
||||
this.responseRejecter(new Error('Connection closed'));
|
||||
this.responseRejecter = null;
|
||||
this.responseResolver = null;
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.connect(this.port, this.address);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from NUT server
|
||||
*/
|
||||
public async disconnect(): Promise<void> {
|
||||
if (!this.connected || !this.socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.sendCommand('LOGOUT');
|
||||
} catch {
|
||||
// Ignore logout errors
|
||||
}
|
||||
|
||||
this.socket.destroy();
|
||||
this.socket = null;
|
||||
this.connected = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connected
|
||||
*/
|
||||
public get isConnected(): boolean {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming data
|
||||
*/
|
||||
private handleData(data: Buffer): void {
|
||||
this.responseBuffer += data.toString();
|
||||
|
||||
// Check for complete response (ends with newline)
|
||||
const lines = this.responseBuffer.split('\n');
|
||||
|
||||
// Check if we have a complete response
|
||||
if (this.responseBuffer.endsWith('\n')) {
|
||||
const responseLines = lines.filter((l) => l.trim().length > 0);
|
||||
this.responseBuffer = '';
|
||||
|
||||
if (this.responseResolver) {
|
||||
this.responseResolver(responseLines);
|
||||
this.responseResolver = null;
|
||||
this.responseRejecter = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send command and get response
|
||||
*/
|
||||
private async sendCommand(command: string): Promise<string[]> {
|
||||
if (!this.socket || !this.connected) {
|
||||
throw new Error('Not connected to NUT server');
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.responseResolver = resolve;
|
||||
this.responseRejecter = reject;
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
this.responseResolver = null;
|
||||
this.responseRejecter = null;
|
||||
reject(new Error(`Command timeout: ${command}`));
|
||||
}, 10000);
|
||||
|
||||
this.responseResolver = (lines) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(lines);
|
||||
};
|
||||
|
||||
this.responseRejecter = (err) => {
|
||||
clearTimeout(timeout);
|
||||
reject(err);
|
||||
};
|
||||
|
||||
this.socket!.write(`${command}\n`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* List available UPS devices
|
||||
*/
|
||||
public async listUps(): Promise<INutUpsInfo[]> {
|
||||
await this.ensureConnected();
|
||||
const response = await this.sendCommand('LIST UPS');
|
||||
|
||||
const upsList: INutUpsInfo[] = [];
|
||||
|
||||
for (const line of response) {
|
||||
// Format: UPS <name> "<description>"
|
||||
const match = line.match(/^UPS\s+(\S+)\s+"([^"]*)"/);
|
||||
if (match) {
|
||||
upsList.push({
|
||||
name: match[1],
|
||||
description: match[2],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return upsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all variables for a UPS
|
||||
*/
|
||||
public async listVariables(upsName: string): Promise<INutVariable[]> {
|
||||
await this.ensureConnected();
|
||||
const response = await this.sendCommand(`LIST VAR ${upsName}`);
|
||||
|
||||
const variables: INutVariable[] = [];
|
||||
|
||||
for (const line of response) {
|
||||
// Format: VAR <ups> <name> "<value>"
|
||||
const match = line.match(/^VAR\s+\S+\s+(\S+)\s+"([^"]*)"/);
|
||||
if (match) {
|
||||
variables.push({
|
||||
name: match[1],
|
||||
value: match[2],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific variable value
|
||||
*/
|
||||
public async getVariable(upsName: string, varName: string): Promise<string | null> {
|
||||
await this.ensureConnected();
|
||||
const response = await this.sendCommand(`GET VAR ${upsName} ${varName}`);
|
||||
|
||||
for (const line of response) {
|
||||
// Format: VAR <ups> <name> "<value>"
|
||||
const match = line.match(/^VAR\s+\S+\s+\S+\s+"([^"]*)"/);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
// Handle error responses
|
||||
if (line.startsWith('ERR')) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple variables at once
|
||||
*/
|
||||
public async getVariables(upsName: string, varNames: string[]): Promise<Map<string, string>> {
|
||||
const results = new Map<string, string>();
|
||||
|
||||
for (const varName of varNames) {
|
||||
const value = await this.getVariable(upsName, varName);
|
||||
if (value !== null) {
|
||||
results.set(varName, value);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an instant command
|
||||
*/
|
||||
public async runCommand(upsName: string, command: string): Promise<boolean> {
|
||||
await this.ensureConnected();
|
||||
const response = await this.sendCommand(`INSTCMD ${upsName} ${command}`);
|
||||
|
||||
for (const line of response) {
|
||||
if (line === 'OK') {
|
||||
return true;
|
||||
}
|
||||
if (line.startsWith('ERR')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* List available commands for a UPS
|
||||
*/
|
||||
public async listCommands(upsName: string): Promise<string[]> {
|
||||
await this.ensureConnected();
|
||||
const response = await this.sendCommand(`LIST CMD ${upsName}`);
|
||||
|
||||
const commands: string[] = [];
|
||||
|
||||
for (const line of response) {
|
||||
// Format: CMD <ups> <command>
|
||||
const match = line.match(/^CMD\s+\S+\s+(\S+)/);
|
||||
if (match) {
|
||||
commands.push(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse UPS status string into flags
|
||||
*/
|
||||
public parseStatus(statusString: string): TNutStatusFlag[] {
|
||||
return statusString.split(/\s+/).filter((s) => s.length > 0) as TNutStatusFlag[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comprehensive UPS status
|
||||
*/
|
||||
public async getUpsStatus(upsName: string): Promise<{
|
||||
status: TNutStatusFlag[];
|
||||
batteryCharge: number;
|
||||
batteryRuntime: number;
|
||||
inputVoltage: number;
|
||||
outputVoltage: number;
|
||||
load: number;
|
||||
}> {
|
||||
const vars = await this.getVariables(upsName, [
|
||||
NUT_VARIABLES.upsStatus,
|
||||
NUT_VARIABLES.batteryCharge,
|
||||
NUT_VARIABLES.batteryRuntime,
|
||||
NUT_VARIABLES.inputVoltage,
|
||||
NUT_VARIABLES.outputVoltage,
|
||||
NUT_VARIABLES.upsLoad,
|
||||
]);
|
||||
|
||||
return {
|
||||
status: this.parseStatus(vars.get(NUT_VARIABLES.upsStatus) || ''),
|
||||
batteryCharge: parseFloat(vars.get(NUT_VARIABLES.batteryCharge) || '0'),
|
||||
batteryRuntime: parseFloat(vars.get(NUT_VARIABLES.batteryRuntime) || '0'),
|
||||
inputVoltage: parseFloat(vars.get(NUT_VARIABLES.inputVoltage) || '0'),
|
||||
outputVoltage: parseFloat(vars.get(NUT_VARIABLES.outputVoltage) || '0'),
|
||||
load: parseFloat(vars.get(NUT_VARIABLES.upsLoad) || '0'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get device information
|
||||
*/
|
||||
public async getDeviceInfo(upsName: string): Promise<{
|
||||
manufacturer: string;
|
||||
model: string;
|
||||
serial: string;
|
||||
type: string;
|
||||
}> {
|
||||
const vars = await this.getVariables(upsName, [
|
||||
NUT_VARIABLES.deviceMfr,
|
||||
NUT_VARIABLES.deviceModel,
|
||||
NUT_VARIABLES.deviceSerial,
|
||||
NUT_VARIABLES.deviceType,
|
||||
]);
|
||||
|
||||
return {
|
||||
manufacturer: vars.get(NUT_VARIABLES.deviceMfr) || '',
|
||||
model: vars.get(NUT_VARIABLES.deviceModel) || '',
|
||||
serial: vars.get(NUT_VARIABLES.deviceSerial) || '',
|
||||
type: vars.get(NUT_VARIABLES.deviceType) || '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure connected before command
|
||||
*/
|
||||
private async ensureConnected(): Promise<void> {
|
||||
if (!this.connected) {
|
||||
await this.connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a NUT server is reachable
|
||||
*/
|
||||
public static async probe(address: string, port: number = 3493, timeout: number = 3000): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const socket = new plugins.net.Socket();
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
socket.destroy();
|
||||
resolve(false);
|
||||
}, timeout);
|
||||
|
||||
socket.on('connect', () => {
|
||||
clearTimeout(timer);
|
||||
socket.destroy();
|
||||
resolve(true);
|
||||
});
|
||||
|
||||
socket.on('error', () => {
|
||||
clearTimeout(timer);
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
socket.connect(port, address);
|
||||
});
|
||||
}
|
||||
}
|
||||
377
ts/ups/ups.classes.upssnmp.ts
Normal file
377
ts/ups/ups.classes.upssnmp.ts
Normal file
@@ -0,0 +1,377 @@
|
||||
import { SnmpProtocol, SNMP_OIDS, type ISnmpOptions } from '../snmp/snmp.classes.snmpprotocol.js';
|
||||
|
||||
/**
|
||||
* 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),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user