729 lines
24 KiB
TypeScript
729 lines
24 KiB
TypeScript
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';
|
||
|
||
/**
|
||
* Class for SNMP communication with UPS devices
|
||
* Main entry point for SNMP functionality
|
||
*/
|
||
export class NupstSnmp {
|
||
// Active OID set
|
||
private activeOIDs: IOidSet;
|
||
// Reference to the parent Nupst instance
|
||
private nupst: any; // Type 'any' to avoid circular dependency
|
||
// Debug mode flag
|
||
private debug: boolean = false;
|
||
|
||
// Default SNMP configuration
|
||
private readonly DEFAULT_CONFIG: ISnmpConfig = {
|
||
host: '127.0.0.1', // Default to localhost
|
||
port: 161, // Default SNMP port
|
||
community: 'public', // Default community string for v1/v2c
|
||
version: 1, // SNMPv1
|
||
timeout: 5000, // 5 seconds timeout
|
||
upsModel: 'cyberpower', // Default UPS model
|
||
};
|
||
|
||
/**
|
||
* Create a new SNMP manager
|
||
* @param debug Whether to enable debug mode
|
||
*/
|
||
constructor(debug = false) {
|
||
this.debug = debug;
|
||
// Set default OID set
|
||
this.activeOIDs = UpsOidSets.getOidSet('cyberpower');
|
||
}
|
||
|
||
/**
|
||
* Set reference to the main Nupst instance
|
||
* @param nupst Reference to the main Nupst instance
|
||
*/
|
||
public setNupst(nupst: any): void {
|
||
this.nupst = nupst;
|
||
}
|
||
|
||
/**
|
||
* Get reference to the main Nupst instance
|
||
*/
|
||
public getNupst(): any {
|
||
return this.nupst;
|
||
}
|
||
|
||
/**
|
||
* Enable debug mode
|
||
*/
|
||
public enableDebug(): void {
|
||
this.debug = true;
|
||
console.log('SNMP debug mode enabled - detailed logs will be shown');
|
||
}
|
||
|
||
/**
|
||
* Set active OID set based on UPS model
|
||
* @param config SNMP configuration
|
||
*/
|
||
private setActiveOIDs(config: ISnmpConfig): void {
|
||
// If custom OIDs are provided, use them
|
||
if (config.upsModel === 'custom' && config.customOIDs) {
|
||
this.activeOIDs = config.customOIDs;
|
||
if (this.debug) {
|
||
console.log('Using custom OIDs:', this.activeOIDs);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Use OIDs for the specified UPS model or default to Cyberpower
|
||
const model = config.upsModel || 'cyberpower';
|
||
this.activeOIDs = UpsOidSets.getOidSet(model);
|
||
|
||
if (this.debug) {
|
||
console.log(`Using OIDs for UPS model: ${model}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Send an SNMP GET request using the net-snmp package
|
||
* @param oid OID to query
|
||
* @param config SNMP configuration
|
||
* @param retryCount Current retry count (unused in this implementation)
|
||
* @returns Promise resolving to the SNMP response value
|
||
*/
|
||
public snmpGet(
|
||
oid: string,
|
||
config = this.DEFAULT_CONFIG,
|
||
retryCount = 0,
|
||
): Promise<any> {
|
||
return new Promise((resolve, reject) => {
|
||
if (this.debug) {
|
||
console.log(
|
||
`Sending SNMP v${config.version} GET request for OID ${oid} to ${config.host}:${config.port}`,
|
||
);
|
||
console.log('Using community:', config.community);
|
||
}
|
||
|
||
// Create SNMP options based on configuration
|
||
const options: any = {
|
||
port: config.port,
|
||
retries: 2, // Number of retries
|
||
timeout: config.timeout,
|
||
transport: 'udp4',
|
||
idBitsSize: 32,
|
||
context: config.context || '',
|
||
};
|
||
|
||
// Set version based on config
|
||
if (config.version === 1) {
|
||
options.version = snmp.Version1;
|
||
} else if (config.version === 2) {
|
||
options.version = snmp.Version2c;
|
||
} else {
|
||
options.version = snmp.Version3;
|
||
}
|
||
|
||
// Create appropriate session based on SNMP version
|
||
let session;
|
||
|
||
if (config.version === 3) {
|
||
// For SNMPv3, we need to set up authentication and privacy
|
||
// For SNMPv3, we need a valid security level
|
||
const securityLevel = config.securityLevel || 'noAuthNoPriv';
|
||
|
||
// Create the user object with required structure for net-snmp
|
||
const user: any = {
|
||
name: config.username || '',
|
||
};
|
||
|
||
// Set security level
|
||
if (securityLevel === 'noAuthNoPriv') {
|
||
user.level = snmp.SecurityLevel.noAuthNoPriv;
|
||
} else if (securityLevel === 'authNoPriv') {
|
||
user.level = snmp.SecurityLevel.authNoPriv;
|
||
|
||
// Set auth protocol - must provide both protocol and key
|
||
if (config.authProtocol && config.authKey) {
|
||
if (config.authProtocol === 'MD5') {
|
||
user.authProtocol = snmp.AuthProtocols.md5;
|
||
} else if (config.authProtocol === 'SHA') {
|
||
user.authProtocol = snmp.AuthProtocols.sha;
|
||
}
|
||
user.authKey = config.authKey;
|
||
} else {
|
||
// Fallback to noAuthNoPriv if auth details missing
|
||
user.level = snmp.SecurityLevel.noAuthNoPriv;
|
||
if (this.debug) {
|
||
console.log('Warning: Missing authProtocol or authKey, falling back to noAuthNoPriv');
|
||
}
|
||
}
|
||
} else if (securityLevel === 'authPriv') {
|
||
user.level = snmp.SecurityLevel.authPriv;
|
||
|
||
// Set auth protocol - must provide both protocol and key
|
||
if (config.authProtocol && config.authKey) {
|
||
if (config.authProtocol === 'MD5') {
|
||
user.authProtocol = snmp.AuthProtocols.md5;
|
||
} else if (config.authProtocol === 'SHA') {
|
||
user.authProtocol = snmp.AuthProtocols.sha;
|
||
}
|
||
user.authKey = config.authKey;
|
||
|
||
// Set privacy protocol - must provide both protocol and key
|
||
if (config.privProtocol && config.privKey) {
|
||
if (config.privProtocol === 'DES') {
|
||
user.privProtocol = snmp.PrivProtocols.des;
|
||
} else if (config.privProtocol === 'AES') {
|
||
user.privProtocol = snmp.PrivProtocols.aes;
|
||
}
|
||
user.privKey = config.privKey;
|
||
} else {
|
||
// Fallback to authNoPriv if priv details missing
|
||
user.level = snmp.SecurityLevel.authNoPriv;
|
||
if (this.debug) {
|
||
console.log('Warning: Missing privProtocol or privKey, falling back to authNoPriv');
|
||
}
|
||
}
|
||
} else {
|
||
// Fallback to noAuthNoPriv if auth details missing
|
||
user.level = snmp.SecurityLevel.noAuthNoPriv;
|
||
if (this.debug) {
|
||
console.log('Warning: Missing authProtocol or authKey, falling back to noAuthNoPriv');
|
||
}
|
||
}
|
||
}
|
||
|
||
if (this.debug) {
|
||
console.log('SNMPv3 user configuration:', {
|
||
name: user.name,
|
||
level: Object.keys(snmp.SecurityLevel).find((key) =>
|
||
snmp.SecurityLevel[key] === user.level
|
||
),
|
||
authProtocol: user.authProtocol ? 'Set' : 'Not Set',
|
||
authKey: user.authKey ? 'Set' : 'Not Set',
|
||
privProtocol: user.privProtocol ? 'Set' : 'Not Set',
|
||
privKey: user.privKey ? 'Set' : 'Not Set',
|
||
});
|
||
}
|
||
|
||
session = snmp.createV3Session(config.host, user, options);
|
||
} else {
|
||
// For SNMPv1/v2c, we use the community string
|
||
session = snmp.createSession(config.host, config.community || 'public', options);
|
||
}
|
||
|
||
// Convert the OID string to an array of OIDs if multiple OIDs are needed
|
||
const oids = [oid];
|
||
|
||
// Send the GET request
|
||
session.get(oids, (error: any, varbinds: any[]) => {
|
||
// Close the session to release resources
|
||
session.close();
|
||
|
||
if (error) {
|
||
if (this.debug) {
|
||
console.error('SNMP GET error:', error);
|
||
}
|
||
reject(new Error(`SNMP GET error: ${error.message || error}`));
|
||
return;
|
||
}
|
||
|
||
if (!varbinds || varbinds.length === 0) {
|
||
if (this.debug) {
|
||
console.error('No varbinds returned in response');
|
||
}
|
||
reject(new Error('No varbinds returned in response'));
|
||
return;
|
||
}
|
||
|
||
// Check for SNMP errors in the response
|
||
if (
|
||
varbinds[0].type === snmp.ObjectType.NoSuchObject ||
|
||
varbinds[0].type === snmp.ObjectType.NoSuchInstance ||
|
||
varbinds[0].type === snmp.ObjectType.EndOfMibView
|
||
) {
|
||
if (this.debug) {
|
||
console.error('SNMP error:', snmp.ObjectType[varbinds[0].type]);
|
||
}
|
||
reject(new Error(`SNMP error: ${snmp.ObjectType[varbinds[0].type]}`));
|
||
return;
|
||
}
|
||
|
||
// Process the response value based on its type
|
||
let value = varbinds[0].value;
|
||
|
||
// Handle specific types that might need conversion
|
||
if (Buffer.isBuffer(value)) {
|
||
// If value is a Buffer, try to convert it to a string if it's printable ASCII
|
||
const isPrintableAscii = value.every((byte: number) => byte >= 32 && byte <= 126);
|
||
if (isPrintableAscii) {
|
||
value = value.toString();
|
||
}
|
||
} else if (typeof value === 'bigint') {
|
||
// Convert BigInt to a normal number or string if needed
|
||
value = Number(value);
|
||
}
|
||
|
||
if (this.debug) {
|
||
console.log('SNMP response:', {
|
||
oid: varbinds[0].oid,
|
||
type: varbinds[0].type,
|
||
value: value,
|
||
});
|
||
}
|
||
|
||
resolve(value);
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Get the current status of the UPS
|
||
* @param config SNMP configuration
|
||
* @returns Promise resolving to the UPS status
|
||
*/
|
||
public async getUpsStatus(config = this.DEFAULT_CONFIG): Promise<IUpsStatus> {
|
||
try {
|
||
// Set active OID set based on UPS model in config
|
||
this.setActiveOIDs(config);
|
||
|
||
if (this.debug) {
|
||
console.log('---------------------------------------');
|
||
console.log('Getting UPS status with config:');
|
||
console.log(' Host:', config.host);
|
||
console.log(' Port:', config.port);
|
||
console.log(' Version:', config.version);
|
||
console.log(' Timeout:', config.timeout, 'ms');
|
||
console.log(' UPS Model:', config.upsModel || 'cyberpower');
|
||
if (config.version === 1 || config.version === 2) {
|
||
console.log(' Community:', config.community);
|
||
} else if (config.version === 3) {
|
||
console.log(' Security Level:', config.securityLevel);
|
||
console.log(' Username:', config.username);
|
||
console.log(' Auth Protocol:', config.authProtocol || 'None');
|
||
console.log(' Privacy Protocol:', config.privProtocol || 'None');
|
||
}
|
||
console.log('Using OIDs:');
|
||
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('---------------------------------------');
|
||
}
|
||
|
||
// Get all values with independent retry logic
|
||
const powerStatusValue = await this.getSNMPValueWithRetry(
|
||
this.activeOIDs.POWER_STATUS,
|
||
'power status',
|
||
config,
|
||
);
|
||
const batteryCapacity = await this.getSNMPValueWithRetry(
|
||
this.activeOIDs.BATTERY_CAPACITY,
|
||
'battery capacity',
|
||
config,
|
||
) || 0;
|
||
const batteryRuntime = await this.getSNMPValueWithRetry(
|
||
this.activeOIDs.BATTERY_RUNTIME,
|
||
'battery runtime',
|
||
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,
|
||
},
|
||
};
|
||
|
||
if (this.debug) {
|
||
console.log('---------------------------------------');
|
||
console.log('UPS status result:');
|
||
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('---------------------------------------');
|
||
}
|
||
|
||
return result;
|
||
} catch (error) {
|
||
if (this.debug) {
|
||
console.error('---------------------------------------');
|
||
console.error(
|
||
'Error getting UPS status:',
|
||
error instanceof Error ? error.message : String(error),
|
||
);
|
||
console.error('---------------------------------------');
|
||
}
|
||
throw new Error(
|
||
`Failed to get UPS status: ${error instanceof Error ? error.message : String(error)}`,
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Helper method to get SNMP value with retry and fallback logic
|
||
* @param oid OID to query
|
||
* @param description Description of the value for logging
|
||
* @param config SNMP configuration
|
||
* @returns Promise resolving to the SNMP value
|
||
*/
|
||
private async getSNMPValueWithRetry(
|
||
oid: string,
|
||
description: string,
|
||
config: ISnmpConfig,
|
||
): Promise<any> {
|
||
if (oid === '') {
|
||
if (this.debug) {
|
||
console.log(`No OID provided for ${description}, skipping`);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
if (this.debug) {
|
||
console.log(`Getting ${description} OID: ${oid}`);
|
||
}
|
||
|
||
try {
|
||
const value = await this.snmpGet(oid, config);
|
||
if (this.debug) {
|
||
console.log(`${description} value:`, value);
|
||
}
|
||
return value;
|
||
} catch (error) {
|
||
if (this.debug) {
|
||
console.error(
|
||
`Error getting ${description}:`,
|
||
error instanceof Error ? error.message : String(error),
|
||
);
|
||
}
|
||
|
||
// If we're using SNMPv3, try with different security levels
|
||
if (config.version === 3) {
|
||
return await this.tryFallbackSecurityLevels(oid, description, config);
|
||
}
|
||
|
||
// Try with standard OIDs as fallback
|
||
if (config.upsModel !== 'custom') {
|
||
return await this.tryStandardOids(oid, description, config);
|
||
}
|
||
|
||
// Return a default value if all attempts fail
|
||
if (this.debug) {
|
||
console.log(`Using default value 0 for ${description}`);
|
||
}
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Try fallback security levels for SNMPv3
|
||
* @param oid OID to query
|
||
* @param description Description of the value for logging
|
||
* @param config SNMP configuration
|
||
* @returns Promise resolving to the SNMP value
|
||
*/
|
||
private async tryFallbackSecurityLevels(
|
||
oid: string,
|
||
description: string,
|
||
config: ISnmpConfig,
|
||
): Promise<any> {
|
||
if (this.debug) {
|
||
console.log(`Retrying ${description} with fallback security level...`);
|
||
}
|
||
|
||
// Try with authNoPriv if current level is authPriv
|
||
if (config.securityLevel === 'authPriv') {
|
||
const retryConfig = { ...config, securityLevel: 'authNoPriv' as 'authNoPriv' };
|
||
try {
|
||
if (this.debug) {
|
||
console.log(`Retrying with authNoPriv security level`);
|
||
}
|
||
const value = await this.snmpGet(oid, retryConfig);
|
||
if (this.debug) {
|
||
console.log(`${description} retry value:`, value);
|
||
}
|
||
return value;
|
||
} catch (retryError) {
|
||
if (this.debug) {
|
||
console.error(
|
||
`Retry failed for ${description}:`,
|
||
retryError instanceof Error ? retryError.message : String(retryError),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Try with noAuthNoPriv as a last resort
|
||
if (config.securityLevel === 'authPriv' || config.securityLevel === 'authNoPriv') {
|
||
const retryConfig = { ...config, securityLevel: 'noAuthNoPriv' as 'noAuthNoPriv' };
|
||
try {
|
||
if (this.debug) {
|
||
console.log(`Retrying with noAuthNoPriv security level`);
|
||
}
|
||
const value = await this.snmpGet(oid, retryConfig);
|
||
if (this.debug) {
|
||
console.log(`${description} retry value:`, value);
|
||
}
|
||
return value;
|
||
} catch (retryError) {
|
||
if (this.debug) {
|
||
console.error(
|
||
`Retry failed for ${description}:`,
|
||
retryError instanceof Error ? retryError.message : String(retryError),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Try standard OIDs as fallback
|
||
* @param oid OID to query
|
||
* @param description Description of the value for logging
|
||
* @param config SNMP configuration
|
||
* @returns Promise resolving to the SNMP value
|
||
*/
|
||
private async tryStandardOids(
|
||
oid: string,
|
||
description: string,
|
||
config: ISnmpConfig,
|
||
): Promise<any> {
|
||
try {
|
||
// Try RFC 1628 standard UPS MIB OIDs
|
||
const standardOIDs = UpsOidSets.getStandardOids();
|
||
|
||
if (this.debug) {
|
||
console.log(
|
||
`Trying standard RFC 1628 OID for ${description}: ${standardOIDs[description]}`,
|
||
);
|
||
}
|
||
|
||
const standardValue = await this.snmpGet(standardOIDs[description], config);
|
||
if (this.debug) {
|
||
console.log(`${description} standard OID value:`, standardValue);
|
||
}
|
||
return standardValue;
|
||
} catch (stdError) {
|
||
if (this.debug) {
|
||
console.error(
|
||
`Standard OID retry failed for ${description}:`,
|
||
stdError instanceof Error ? stdError.message : String(stdError),
|
||
);
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Determine power status based on UPS model and raw value
|
||
* Uses the value mappings defined in the OID sets
|
||
* @param upsModel UPS model
|
||
* @param powerStatusValue Raw power status value
|
||
* @returns Standardized power status
|
||
*/
|
||
private determinePowerStatus(
|
||
upsModel: TUpsModel | undefined,
|
||
powerStatusValue: number,
|
||
): 'online' | 'onBattery' | 'unknown' {
|
||
// Get the OID set for this UPS model
|
||
if (upsModel && upsModel !== 'custom') {
|
||
const oidSet = UpsOidSets.getOidSet(upsModel);
|
||
|
||
// Use the value mappings if available
|
||
if (oidSet.POWER_STATUS_VALUES) {
|
||
if (powerStatusValue === oidSet.POWER_STATUS_VALUES.online) {
|
||
return 'online';
|
||
} else if (powerStatusValue === oidSet.POWER_STATUS_VALUES.onBattery) {
|
||
return 'onBattery';
|
||
}
|
||
}
|
||
}
|
||
|
||
// Fallback for custom or undefined models (RFC 1628 standard)
|
||
// upsOutputSource: 3=normal (mains), 5=battery
|
||
if (powerStatusValue === 3) {
|
||
return 'online';
|
||
} else if (powerStatusValue === 5) {
|
||
return 'onBattery';
|
||
}
|
||
|
||
return 'unknown';
|
||
}
|
||
|
||
/**
|
||
* Process runtime value based on UPS model
|
||
* @param upsModel UPS model
|
||
* @param batteryRuntime Raw battery runtime value
|
||
* @returns Processed runtime in minutes
|
||
*/
|
||
private processRuntimeValue(
|
||
upsModel: TUpsModel | undefined,
|
||
batteryRuntime: number,
|
||
): number {
|
||
if (this.debug) {
|
||
console.log('Raw runtime value:', batteryRuntime);
|
||
}
|
||
|
||
if (upsModel === 'cyberpower' && batteryRuntime > 0) {
|
||
// CyberPower: TimeTicks is in 1/100 seconds, convert to minutes
|
||
const minutes = Math.floor(batteryRuntime / 6000); // 6000 ticks = 1 minute
|
||
if (this.debug) {
|
||
console.log(
|
||
`Converting CyberPower runtime from ${batteryRuntime} ticks to ${minutes} minutes`,
|
||
);
|
||
}
|
||
return minutes;
|
||
} else if (upsModel === 'eaton' && batteryRuntime > 0) {
|
||
// Eaton: Runtime is in seconds, convert to minutes
|
||
const minutes = Math.floor(batteryRuntime / 60);
|
||
if (this.debug) {
|
||
console.log(
|
||
`Converting Eaton runtime from ${batteryRuntime} seconds to ${minutes} minutes`,
|
||
);
|
||
}
|
||
return minutes;
|
||
} else if (batteryRuntime > 10000) {
|
||
// Generic conversion for large tick values (likely TimeTicks)
|
||
const minutes = Math.floor(batteryRuntime / 6000);
|
||
if (this.debug) {
|
||
console.log(`Converting ${batteryRuntime} ticks to ${minutes} minutes`);
|
||
}
|
||
return minutes;
|
||
}
|
||
|
||
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 === '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;
|
||
}
|
||
|
||
// Eaton XUPS-MIB and APC PowerNet report current directly in RMS Amps (no scaling needed)
|
||
if ((upsModel === 'eaton' || upsModel === 'apc') && this.debug && outputCurrent > 0) {
|
||
console.log(`${upsModel.toUpperCase()} current already in RMS Amps: ${outputCurrent}A`);
|
||
}
|
||
|
||
return outputCurrent;
|
||
}
|
||
}
|