diff --git a/deno.json b/deno.json index 0247afd..05b95bc 100644 --- a/deno.json +++ b/deno.json @@ -2,6 +2,7 @@ "name": "@serve.zone/nupst", "version": "5.0.5", "exports": "./mod.ts", + "nodeModulesDir": "auto", "tasks": { "dev": "deno run --allow-all mod.ts", "compile": "deno task compile:all", diff --git a/package.json b/package.json index 85e9030..b50a264 100644 --- a/package.json +++ b/package.json @@ -58,5 +58,6 @@ "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" - } + }, + "packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34" } diff --git a/ts/snmp/manager.ts b/ts/snmp/manager.ts index 5a5d063..e40b637 100644 --- a/ts/snmp/manager.ts +++ b/ts/snmp/manager.ts @@ -1,4 +1,4 @@ -import * as snmp from 'npm:net-snmp@3.20.0'; +import * as snmp from 'npm:net-snmp@3.26.0'; import { Buffer } from 'node:buffer'; import type { IOidSet, ISnmpConfig, IUpsStatus, TUpsModel } from './types.ts'; import { UpsOidSets } from './oid-sets.ts'; @@ -304,6 +304,10 @@ export class NupstSnmp { console.log(' Power Status:', this.activeOIDs.POWER_STATUS); console.log(' Battery Capacity:', this.activeOIDs.BATTERY_CAPACITY); console.log(' Battery Runtime:', this.activeOIDs.BATTERY_RUNTIME); + console.log(' Output Load:', this.activeOIDs.OUTPUT_LOAD); + console.log(' Output Power:', this.activeOIDs.OUTPUT_POWER); + console.log(' Output Voltage:', this.activeOIDs.OUTPUT_VOLTAGE); + console.log(' Output Current:', this.activeOIDs.OUTPUT_CURRENT); console.log('---------------------------------------'); } @@ -324,20 +328,65 @@ export class NupstSnmp { config, ) || 0; + // Get power draw metrics + const outputLoad = await this.getSNMPValueWithRetry( + this.activeOIDs.OUTPUT_LOAD, + 'output load', + config, + ) || 0; + const outputPower = await this.getSNMPValueWithRetry( + this.activeOIDs.OUTPUT_POWER, + 'output power', + config, + ) || 0; + const outputVoltage = await this.getSNMPValueWithRetry( + this.activeOIDs.OUTPUT_VOLTAGE, + 'output voltage', + config, + ) || 0; + const outputCurrent = await this.getSNMPValueWithRetry( + this.activeOIDs.OUTPUT_CURRENT, + 'output current', + config, + ) || 0; + // Determine power status - handle different values for different UPS models const powerStatus = this.determinePowerStatus(config.upsModel, powerStatusValue); // Convert to minutes for UPS models with different time units const processedRuntime = this.processRuntimeValue(config.upsModel, batteryRuntime); + // Process power metrics with vendor-specific scaling + const processedVoltage = this.processVoltageValue(config.upsModel, outputVoltage); + const processedCurrent = this.processCurrentValue(config.upsModel, outputCurrent); + + // Calculate power from voltage × current if not provided by UPS + let processedPower = outputPower; + if (outputPower === 0 && processedVoltage > 0 && processedCurrent > 0) { + processedPower = Math.round(processedVoltage * processedCurrent); + if (this.debug) { + console.log( + `Calculated power from V×I: ${processedVoltage}V × ${processedCurrent}A = ${processedPower}W`, + ); + } + } + const result = { powerStatus, batteryCapacity, batteryRuntime: processedRuntime, + outputLoad, + outputPower: processedPower, + outputVoltage: processedVoltage, + outputCurrent: processedCurrent, raw: { powerStatus: powerStatusValue, batteryCapacity, batteryRuntime, + outputLoad, + outputPower, + outputVoltage, + outputCurrent, }, }; @@ -347,6 +396,10 @@ export class NupstSnmp { console.log(' Power Status:', result.powerStatus); console.log(' Battery Capacity:', result.batteryCapacity + '%'); console.log(' Battery Runtime:', result.batteryRuntime, 'minutes'); + console.log(' Output Load:', result.outputLoad + '%'); + console.log(' Output Power:', result.outputPower, 'watts'); + console.log(' Output Voltage:', result.outputVoltage, 'volts'); + console.log(' Output Current:', result.outputCurrent, 'amps'); console.log('---------------------------------------'); } @@ -602,4 +655,69 @@ export class NupstSnmp { return batteryRuntime; } + + /** + * Process voltage value based on UPS model + * @param upsModel UPS model + * @param outputVoltage Raw output voltage value + * @returns Processed voltage in volts + */ + private processVoltageValue( + upsModel: TUpsModel | undefined, + outputVoltage: number, + ): number { + if (this.debug) { + console.log('Raw voltage value:', outputVoltage); + } + + if (upsModel === 'cyberpower' && outputVoltage > 0) { + // CyberPower: Voltage is in 0.1V, convert to volts + const volts = outputVoltage / 10; + if (this.debug) { + console.log( + `Converting CyberPower voltage from ${outputVoltage} (0.1V) to ${volts} volts`, + ); + } + return volts; + } + + return outputVoltage; + } + + /** + * Process current value based on UPS model + * @param upsModel UPS model + * @param outputCurrent Raw output current value + * @returns Processed current in amps + */ + private processCurrentValue( + upsModel: TUpsModel | undefined, + outputCurrent: number, + ): number { + if (this.debug) { + console.log('Raw current value:', outputCurrent); + } + + if (upsModel === 'cyberpower' && outputCurrent > 0) { + // CyberPower: Current is in 0.1A, convert to amps + const amps = outputCurrent / 10; + if (this.debug) { + console.log( + `Converting CyberPower current from ${outputCurrent} (0.1A) to ${amps} amps`, + ); + } + return amps; + } else if ((upsModel === 'eaton' || upsModel === 'tripplite' || upsModel === 'liebert') && outputCurrent > 0) { + // RFC 1628 standard: Current is in 0.1A, convert to amps + const amps = outputCurrent / 10; + if (this.debug) { + console.log( + `Converting RFC 1628 current from ${outputCurrent} (0.1A) to ${amps} amps`, + ); + } + return amps; + } + + return outputCurrent; + } } diff --git a/ts/snmp/oid-sets.ts b/ts/snmp/oid-sets.ts index 65e8848..0348a7f 100644 --- a/ts/snmp/oid-sets.ts +++ b/ts/snmp/oid-sets.ts @@ -14,6 +14,10 @@ export class UpsOidSets { POWER_STATUS: '1.3.6.1.4.1.3808.1.1.1.4.1.1.0', // upsBaseOutputStatus BATTERY_CAPACITY: '1.3.6.1.4.1.3808.1.1.1.2.2.1.0', // upsAdvanceBatteryCapacity (percentage) BATTERY_RUNTIME: '1.3.6.1.4.1.3808.1.1.1.2.2.4.0', // upsAdvanceBatteryRunTimeRemaining (TimeTicks) + OUTPUT_LOAD: '1.3.6.1.4.1.3808.1.1.1.4.2.3.0', // upsAdvanceOutputLoad (percentage) + OUTPUT_POWER: '1.3.6.1.4.1.3808.1.1.1.4.2.5.0', // upsAdvanceOutputPower (watts) + OUTPUT_VOLTAGE: '1.3.6.1.4.1.3808.1.1.1.4.2.1.0', // upsAdvanceOutputVoltage (0.1V scale) + OUTPUT_CURRENT: '1.3.6.1.4.1.3808.1.1.1.4.2.4.0', // upsAdvanceOutputCurrent (0.1A scale) POWER_STATUS_VALUES: { online: 2, // upsBaseOutputStatus: 2=onLine onBattery: 3, // upsBaseOutputStatus: 3=onBattery @@ -25,6 +29,10 @@ export class UpsOidSets { POWER_STATUS: '1.3.6.1.4.1.318.1.1.1.4.1.1.0', // upsBasicOutputStatus BATTERY_CAPACITY: '1.3.6.1.4.1.318.1.1.1.2.2.1.0', // Battery capacity in percentage BATTERY_RUNTIME: '1.3.6.1.4.1.318.1.1.1.2.2.3.0', // Remaining runtime in minutes + OUTPUT_LOAD: '1.3.6.1.4.1.318.1.1.1.4.2.3.0', // upsAdvOutputLoad (percentage) + OUTPUT_POWER: '', // Not readily available, load returns VA on some models + OUTPUT_VOLTAGE: '1.3.6.1.4.1.318.1.1.1.4.2.1.0', // upsAdvOutputVoltage + OUTPUT_CURRENT: '1.3.6.1.4.1.318.1.1.1.4.2.4.0', // upsAdvOutputCurrent POWER_STATUS_VALUES: { online: 2, // upsBasicOutputStatus: 2=onLine onBattery: 3, // upsBasicOutputStatus: 3=onBattery @@ -36,6 +44,10 @@ export class UpsOidSets { POWER_STATUS: '1.3.6.1.4.1.534.1.4.4.0', // xupsOutputSource BATTERY_CAPACITY: '1.3.6.1.4.1.534.1.2.4.0', // xupsBatCapacity (percentage) BATTERY_RUNTIME: '1.3.6.1.4.1.534.1.2.1.0', // xupsBatTimeRemaining (seconds) + OUTPUT_LOAD: '1.3.6.1.2.1.33.1.4.4.1.5.1', // RFC 1628: upsOutputPercentLoad + OUTPUT_POWER: '1.3.6.1.2.1.33.1.4.4.1.4.1', // RFC 1628: upsOutputPower (watts) + OUTPUT_VOLTAGE: '1.3.6.1.2.1.33.1.4.4.1.2.1', // RFC 1628: upsOutputVoltage + OUTPUT_CURRENT: '1.3.6.1.2.1.33.1.4.4.1.3.1', // RFC 1628: upsOutputCurrent (0.1A scale) POWER_STATUS_VALUES: { online: 3, // xupsOutputSource: 3=normal (mains power) onBattery: 5, // xupsOutputSource: 5=battery @@ -47,6 +59,10 @@ export class UpsOidSets { POWER_STATUS: '1.3.6.1.4.1.850.1.1.3.1.1.1.0', // tlUpsOutputSource BATTERY_CAPACITY: '1.3.6.1.4.1.850.1.1.3.2.4.1.0', // Battery capacity in percentage BATTERY_RUNTIME: '1.3.6.1.4.1.850.1.1.3.2.2.1.0', // Remaining runtime in minutes + OUTPUT_LOAD: '1.3.6.1.2.1.33.1.4.4.1.5.1', // RFC 1628: upsOutputPercentLoad + OUTPUT_POWER: '1.3.6.1.2.1.33.1.4.4.1.4.1', // RFC 1628: upsOutputPower (watts) + OUTPUT_VOLTAGE: '1.3.6.1.2.1.33.1.4.4.1.2.1', // RFC 1628: upsOutputVoltage + OUTPUT_CURRENT: '1.3.6.1.2.1.33.1.4.4.1.3.1', // RFC 1628: upsOutputCurrent (0.1A scale) POWER_STATUS_VALUES: { online: 2, // tlUpsOutputSource: 2=normal (mains power) onBattery: 3, // tlUpsOutputSource: 3=onBattery @@ -58,6 +74,10 @@ export class UpsOidSets { POWER_STATUS: '1.3.6.1.4.1.476.1.42.3.9.20.1.20.1.2.1.2.1', // lgpPwrOutputSource BATTERY_CAPACITY: '1.3.6.1.4.1.476.1.42.3.9.20.1.20.1.2.1.4.1', // Battery capacity in percentage BATTERY_RUNTIME: '1.3.6.1.4.1.476.1.42.3.9.20.1.20.1.2.1.5.1', // Remaining runtime in minutes + OUTPUT_LOAD: '1.3.6.1.2.1.33.1.4.4.1.5.1', // RFC 1628: upsOutputPercentLoad + OUTPUT_POWER: '1.3.6.1.2.1.33.1.4.4.1.4.1', // RFC 1628: upsOutputPower (watts) + OUTPUT_VOLTAGE: '1.3.6.1.2.1.33.1.4.4.1.2.1', // RFC 1628: upsOutputVoltage + OUTPUT_CURRENT: '1.3.6.1.2.1.33.1.4.4.1.3.1', // RFC 1628: upsOutputCurrent (0.1A scale) POWER_STATUS_VALUES: { online: 2, // lgpPwrOutputSource: 2=normal (mains power) onBattery: 3, // lgpPwrOutputSource: 3=onBattery @@ -69,6 +89,10 @@ export class UpsOidSets { POWER_STATUS: '', BATTERY_CAPACITY: '', BATTERY_RUNTIME: '', + OUTPUT_LOAD: '', + OUTPUT_POWER: '', + OUTPUT_VOLTAGE: '', + OUTPUT_CURRENT: '', }, }; @@ -90,6 +114,10 @@ export class UpsOidSets { 'power status': '1.3.6.1.2.1.33.1.4.1.0', // upsOutputSource 'battery capacity': '1.3.6.1.2.1.33.1.2.4.0', // upsEstimatedChargeRemaining 'battery runtime': '1.3.6.1.2.1.33.1.2.3.0', // upsEstimatedMinutesRemaining + 'output load': '1.3.6.1.2.1.33.1.4.4.1.5.1', // upsOutputPercentLoad (indexed by line) + 'output power': '1.3.6.1.2.1.33.1.4.4.1.4.1', // upsOutputPower in watts (indexed by line) + 'output voltage': '1.3.6.1.2.1.33.1.4.4.1.2.1', // upsOutputVoltage (indexed by line) + 'output current': '1.3.6.1.2.1.33.1.4.4.1.3.1', // upsOutputCurrent in 0.1A (indexed by line) }; } } diff --git a/ts/snmp/types.ts b/ts/snmp/types.ts index 3c3fe38..cdee78c 100644 --- a/ts/snmp/types.ts +++ b/ts/snmp/types.ts @@ -14,6 +14,14 @@ export interface IUpsStatus { batteryCapacity: number; /** Remaining runtime in minutes */ batteryRuntime: number; + /** Output load percentage (0-100) */ + outputLoad: number; + /** Output power in watts */ + outputPower: number; + /** Output voltage in volts */ + outputVoltage: number; + /** Output current in amps */ + outputCurrent: number; /** Raw values from SNMP responses */ raw: Record; } @@ -28,6 +36,14 @@ export interface IOidSet { BATTERY_CAPACITY: string; /** OID for battery runtime */ BATTERY_RUNTIME: string; + /** OID for output load percentage */ + OUTPUT_LOAD: string; + /** OID for output power in watts */ + OUTPUT_POWER: string; + /** OID for output voltage */ + OUTPUT_VOLTAGE: string; + /** OID for output current */ + OUTPUT_CURRENT: string; /** Power status value mappings */ POWER_STATUS_VALUES?: { /** SNMP value that indicates UPS is online (on AC power) */