|
|
|
@@ -1,6 +1,6 @@
|
|
|
|
|
import * as snmp from "npm:net-snmp@3.20.0";
|
|
|
|
|
import { Buffer } from "node:buffer";
|
|
|
|
|
import type { IOidSet, ISnmpConfig, TUpsModel, IUpsStatus } from './types.ts';
|
|
|
|
|
import * as snmp from 'npm:net-snmp@3.20.0';
|
|
|
|
|
import { Buffer } from 'node:buffer';
|
|
|
|
|
import type { IOidSet, ISnmpConfig, IUpsStatus, TUpsModel } from './types.ts';
|
|
|
|
|
import { UpsOidSets } from './oid-sets.ts';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -34,7 +34,7 @@ export class NupstSnmp {
|
|
|
|
|
// Set default OID set
|
|
|
|
|
this.activeOIDs = UpsOidSets.getOidSet('cyberpower');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set reference to the main Nupst instance
|
|
|
|
|
* @param nupst Reference to the main Nupst instance
|
|
|
|
@@ -42,14 +42,14 @@ export class NupstSnmp {
|
|
|
|
|
public setNupst(nupst: any): void {
|
|
|
|
|
this.nupst = nupst;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get reference to the main Nupst instance
|
|
|
|
|
*/
|
|
|
|
|
public getNupst(): any {
|
|
|
|
|
return this.nupst;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Enable debug mode
|
|
|
|
|
*/
|
|
|
|
@@ -71,11 +71,11 @@ export class NupstSnmp {
|
|
|
|
|
}
|
|
|
|
|
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}`);
|
|
|
|
|
}
|
|
|
|
@@ -89,13 +89,15 @@ export class NupstSnmp {
|
|
|
|
|
* @returns Promise resolving to the SNMP response value
|
|
|
|
|
*/
|
|
|
|
|
public async snmpGet(
|
|
|
|
|
oid: string,
|
|
|
|
|
config = this.DEFAULT_CONFIG,
|
|
|
|
|
retryCount = 0
|
|
|
|
|
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(
|
|
|
|
|
`Sending SNMP v${config.version} GET request for OID ${oid} to ${config.host}:${config.port}`,
|
|
|
|
|
);
|
|
|
|
|
console.log('Using community:', config.community);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -106,7 +108,7 @@ export class NupstSnmp {
|
|
|
|
|
timeout: config.timeout,
|
|
|
|
|
transport: 'udp4',
|
|
|
|
|
idBitsSize: 32,
|
|
|
|
|
context: config.context || ''
|
|
|
|
|
context: config.context || '',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Set version based on config
|
|
|
|
@@ -120,23 +122,23 @@ export class NupstSnmp {
|
|
|
|
|
|
|
|
|
|
// 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 || ''
|
|
|
|
|
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') {
|
|
|
|
@@ -154,7 +156,7 @@ export class NupstSnmp {
|
|
|
|
|
}
|
|
|
|
|
} 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') {
|
|
|
|
@@ -163,7 +165,7 @@ export class NupstSnmp {
|
|
|
|
|
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') {
|
|
|
|
@@ -187,18 +189,20 @@ export class NupstSnmp {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.debug) {
|
|
|
|
|
console.log('SNMPv3 user configuration:', {
|
|
|
|
|
name: user.name,
|
|
|
|
|
level: Object.keys(snmp.SecurityLevel).find(key => snmp.SecurityLevel[key] === user.level),
|
|
|
|
|
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'
|
|
|
|
|
privKey: user.privKey ? 'Set' : 'Not Set',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
session = snmp.createV3Session(config.host, user, options);
|
|
|
|
|
} else {
|
|
|
|
|
// For SNMPv1/v2c, we use the community string
|
|
|
|
@@ -230,9 +234,11 @@ export class NupstSnmp {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 (
|
|
|
|
|
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]);
|
|
|
|
|
}
|
|
|
|
@@ -259,7 +265,7 @@ export class NupstSnmp {
|
|
|
|
|
console.log('SNMP response:', {
|
|
|
|
|
oid: varbinds[0].oid,
|
|
|
|
|
type: varbinds[0].type,
|
|
|
|
|
value: value
|
|
|
|
|
value: value,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -277,7 +283,7 @@ export class NupstSnmp {
|
|
|
|
|
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:');
|
|
|
|
@@ -300,18 +306,30 @@ export class NupstSnmp {
|
|
|
|
|
console.log(' Battery Runtime:', this.activeOIDs.BATTERY_RUNTIME);
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const result = {
|
|
|
|
|
powerStatus,
|
|
|
|
|
batteryCapacity,
|
|
|
|
@@ -322,7 +340,7 @@ export class NupstSnmp {
|
|
|
|
|
batteryRuntime,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.debug) {
|
|
|
|
|
console.log('---------------------------------------');
|
|
|
|
|
console.log('UPS status result:');
|
|
|
|
@@ -331,15 +349,20 @@ export class NupstSnmp {
|
|
|
|
|
console.log(' Battery Runtime:', result.batteryRuntime, 'minutes');
|
|
|
|
|
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(
|
|
|
|
|
'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)}`);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Failed to get UPS status: ${error instanceof Error ? error.message : String(error)}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -351,9 +374,9 @@ export class NupstSnmp {
|
|
|
|
|
* @returns Promise resolving to the SNMP value
|
|
|
|
|
*/
|
|
|
|
|
private async getSNMPValueWithRetry(
|
|
|
|
|
oid: string,
|
|
|
|
|
description: string,
|
|
|
|
|
config: ISnmpConfig
|
|
|
|
|
oid: string,
|
|
|
|
|
description: string,
|
|
|
|
|
config: ISnmpConfig,
|
|
|
|
|
): Promise<any> {
|
|
|
|
|
if (oid === '') {
|
|
|
|
|
if (this.debug) {
|
|
|
|
@@ -361,11 +384,11 @@ export class NupstSnmp {
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.debug) {
|
|
|
|
|
console.log(`Getting ${description} OID: ${oid}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const value = await this.snmpGet(oid, config);
|
|
|
|
|
if (this.debug) {
|
|
|
|
@@ -374,19 +397,22 @@ export class NupstSnmp {
|
|
|
|
|
return value;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (this.debug) {
|
|
|
|
|
console.error(`Error getting ${description}:`, error instanceof Error ? error.message : String(error));
|
|
|
|
|
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}`);
|
|
|
|
@@ -403,14 +429,14 @@ export class NupstSnmp {
|
|
|
|
|
* @returns Promise resolving to the SNMP value
|
|
|
|
|
*/
|
|
|
|
|
private async tryFallbackSecurityLevels(
|
|
|
|
|
oid: string,
|
|
|
|
|
description: string,
|
|
|
|
|
config: ISnmpConfig
|
|
|
|
|
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' };
|
|
|
|
@@ -425,7 +451,10 @@ export class NupstSnmp {
|
|
|
|
|
return value;
|
|
|
|
|
} catch (retryError) {
|
|
|
|
|
if (this.debug) {
|
|
|
|
|
console.error(`Retry failed for ${description}:`, retryError instanceof Error ? retryError.message : String(retryError));
|
|
|
|
|
console.error(
|
|
|
|
|
`Retry failed for ${description}:`,
|
|
|
|
|
retryError instanceof Error ? retryError.message : String(retryError),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -444,7 +473,10 @@ export class NupstSnmp {
|
|
|
|
|
return value;
|
|
|
|
|
} catch (retryError) {
|
|
|
|
|
if (this.debug) {
|
|
|
|
|
console.error(`Retry failed for ${description}:`, retryError instanceof Error ? retryError.message : String(retryError));
|
|
|
|
|
console.error(
|
|
|
|
|
`Retry failed for ${description}:`,
|
|
|
|
|
retryError instanceof Error ? retryError.message : String(retryError),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -460,18 +492,20 @@ export class NupstSnmp {
|
|
|
|
|
* @returns Promise resolving to the SNMP value
|
|
|
|
|
*/
|
|
|
|
|
private async tryStandardOids(
|
|
|
|
|
oid: string,
|
|
|
|
|
description: string,
|
|
|
|
|
config: ISnmpConfig
|
|
|
|
|
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]}`);
|
|
|
|
|
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);
|
|
|
|
@@ -479,10 +513,13 @@ export class NupstSnmp {
|
|
|
|
|
return standardValue;
|
|
|
|
|
} catch (stdError) {
|
|
|
|
|
if (this.debug) {
|
|
|
|
|
console.error(`Standard OID retry failed for ${description}:`, stdError instanceof Error ? stdError.message : String(stdError));
|
|
|
|
|
console.error(
|
|
|
|
|
`Standard OID retry failed for ${description}:`,
|
|
|
|
|
stdError instanceof Error ? stdError.message : String(stdError),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -493,8 +530,8 @@ export class NupstSnmp {
|
|
|
|
|
* @returns Standardized power status
|
|
|
|
|
*/
|
|
|
|
|
private determinePowerStatus(
|
|
|
|
|
upsModel: TUpsModel | undefined,
|
|
|
|
|
powerStatusValue: number
|
|
|
|
|
upsModel: TUpsModel | undefined,
|
|
|
|
|
powerStatusValue: number,
|
|
|
|
|
): 'online' | 'onBattery' | 'unknown' {
|
|
|
|
|
if (upsModel === 'cyberpower') {
|
|
|
|
|
// CyberPower RMCARD205: upsBaseOutputStatus values
|
|
|
|
@@ -528,7 +565,7 @@ export class NupstSnmp {
|
|
|
|
|
return 'onBattery';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return 'unknown';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -539,25 +576,29 @@ export class NupstSnmp {
|
|
|
|
|
* @returns Processed runtime in minutes
|
|
|
|
|
*/
|
|
|
|
|
private processRuntimeValue(
|
|
|
|
|
upsModel: TUpsModel | undefined,
|
|
|
|
|
batteryRuntime: number
|
|
|
|
|
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`);
|
|
|
|
|
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`);
|
|
|
|
|
console.log(
|
|
|
|
|
`Converting Eaton runtime from ${batteryRuntime} seconds to ${minutes} minutes`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return minutes;
|
|
|
|
|
} else if (batteryRuntime > 10000) {
|
|
|
|
@@ -568,7 +609,7 @@ export class NupstSnmp {
|
|
|
|
|
}
|
|
|
|
|
return minutes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return batteryRuntime;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|