feat(cli,snmp): fix APC runtime unit defaults and add interactive action editing

This commit is contained in:
2026-04-16 09:44:30 +00:00
parent c7b52c48d5
commit c42ebb56d3
22 changed files with 2001 additions and 863 deletions
+13 -48
View File
@@ -2,6 +2,7 @@ import * as snmp from 'npm:net-snmp@3.26.1';
import { Buffer } from 'node:buffer';
import type { IOidSet, ISnmpConfig, IUpsStatus, TUpsModel } from './types.ts';
import { UpsOidSets } from './oid-sets.ts';
import { convertRuntimeValueToMinutes, getDefaultRuntimeUnitForUpsModel } from './runtime-units.ts';
import { SNMP } from '../constants.ts';
import { logger } from '../logger.ts';
import type { INupstAccessor } from '../interfaces/index.ts';
@@ -707,56 +708,20 @@ export class NupstSnmp {
logger.dim(`Raw runtime value: ${batteryRuntime}`);
}
// Explicit runtimeUnit takes precedence over model-based detection
if (config.runtimeUnit) {
if (config.runtimeUnit === 'seconds' && batteryRuntime > 0) {
const minutes = Math.floor(batteryRuntime / 60);
if (this.debug) {
logger.dim(
`Converting runtime from ${batteryRuntime} seconds to ${minutes} minutes (runtimeUnit: seconds)`,
);
}
return minutes;
} else if (config.runtimeUnit === 'ticks' && batteryRuntime > 0) {
const minutes = Math.floor(batteryRuntime / 6000);
if (this.debug) {
logger.dim(
`Converting runtime from ${batteryRuntime} ticks to ${minutes} minutes (runtimeUnit: ticks)`,
);
}
return minutes;
}
// runtimeUnit === 'minutes' — return as-is
return batteryRuntime;
const runtimeUnit = config.runtimeUnit ||
getDefaultRuntimeUnitForUpsModel(config.upsModel, batteryRuntime);
const minutes = convertRuntimeValueToMinutes(config, batteryRuntime);
if (this.debug && minutes !== batteryRuntime) {
const source = config.runtimeUnit
? `runtimeUnit: ${runtimeUnit}`
: `upsModel: ${config.upsModel || 'auto'}`;
logger.dim(
`Converting runtime from ${batteryRuntime} ${runtimeUnit} to ${minutes} minutes (${source})`,
);
}
// Fallback: model-based detection (for configs without runtimeUnit)
const upsModel = config.upsModel;
if (upsModel === 'cyberpower' && batteryRuntime > 0) {
const minutes = Math.floor(batteryRuntime / 6000);
if (this.debug) {
logger.dim(
`Converting CyberPower runtime from ${batteryRuntime} ticks to ${minutes} minutes`,
);
}
return minutes;
} else if (upsModel === 'eaton' && batteryRuntime > 0) {
const minutes = Math.floor(batteryRuntime / 60);
if (this.debug) {
logger.dim(
`Converting Eaton runtime from ${batteryRuntime} seconds to ${minutes} minutes`,
);
}
return minutes;
} else if (batteryRuntime > 10000) {
const minutes = Math.floor(batteryRuntime / 6000);
if (this.debug) {
logger.dim(`Converting ${batteryRuntime} ticks to ${minutes} minutes (heuristic)`);
}
return minutes;
}
return batteryRuntime;
return minutes;
}
/**
+1 -1
View File
@@ -28,7 +28,7 @@ export class UpsOidSets {
apc: {
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
BATTERY_RUNTIME: '1.3.6.1.4.1.318.1.1.1.2.2.3.0', // Remaining runtime (TimeTicks)
OUTPUT_LOAD: '1.3.6.1.4.1.318.1.1.1.4.2.3.0', // upsAdvOutputLoad (percentage)
OUTPUT_POWER: '1.3.6.1.4.1.318.1.1.1.4.2.8.0', // upsAdvOutputActivePower (watts)
OUTPUT_VOLTAGE: '1.3.6.1.4.1.318.1.1.1.4.2.1.0', // upsAdvOutputVoltage
+50
View File
@@ -0,0 +1,50 @@
import type { ISnmpConfig, TRuntimeUnit, TUpsModel } from './types.ts';
/**
* Return the runtime unit that matches the bundled OID set for a UPS model.
*/
export function getDefaultRuntimeUnitForUpsModel(
upsModel: TUpsModel | undefined,
batteryRuntime?: number,
): TRuntimeUnit {
switch (upsModel) {
case 'cyberpower':
case 'apc':
return 'ticks';
case 'eaton':
return 'seconds';
case 'custom':
case 'tripplite':
case 'liebert':
case undefined:
if (batteryRuntime !== undefined && batteryRuntime > 10000) {
return 'ticks';
}
return 'minutes';
}
}
/**
* Convert an SNMP runtime value to minutes using explicit config first, then model defaults.
*/
export function convertRuntimeValueToMinutes(
config: Pick<ISnmpConfig, 'runtimeUnit' | 'upsModel'>,
batteryRuntime: number,
): number {
if (batteryRuntime <= 0) {
return batteryRuntime;
}
const runtimeUnit = config.runtimeUnit ||
getDefaultRuntimeUnitForUpsModel(config.upsModel, batteryRuntime);
if (runtimeUnit === 'seconds') {
return Math.floor(batteryRuntime / 60);
}
if (runtimeUnit === 'ticks') {
return Math.floor(batteryRuntime / 6000);
}
return batteryRuntime;
}