@ecobridge.xyz/devicemanager 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitea/workflows/default_nottags.yaml +66 -0
- package/.gitea/workflows/default_tags.yaml +124 -0
- package/.vscode/launch.json +11 -0
- package/.vscode/settings.json +26 -0
- package/changelog.md +91 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/device/device.classes.device.d.ts +205 -0
- package/dist_ts/device/device.classes.device.js +344 -0
- package/dist_ts/devicemanager.classes.devicemanager.d.ts +210 -0
- package/dist_ts/devicemanager.classes.devicemanager.js +835 -0
- package/dist_ts/discovery/discovery.classes.homeassistant.d.ts +101 -0
- package/dist_ts/discovery/discovery.classes.homeassistant.js +313 -0
- package/dist_ts/discovery/discovery.classes.mdns.d.ts +90 -0
- package/dist_ts/discovery/discovery.classes.mdns.js +291 -0
- package/dist_ts/discovery/discovery.classes.networkscanner.d.ts +64 -0
- package/dist_ts/discovery/discovery.classes.networkscanner.js +535 -0
- package/dist_ts/discovery/discovery.classes.ssdp.d.ts +112 -0
- package/dist_ts/discovery/discovery.classes.ssdp.js +255 -0
- package/dist_ts/factories/index.d.ts +190 -0
- package/dist_ts/factories/index.js +350 -0
- package/dist_ts/features/feature.abstract.d.ts +118 -0
- package/dist_ts/features/feature.abstract.js +172 -0
- package/dist_ts/features/feature.camera.d.ts +95 -0
- package/dist_ts/features/feature.camera.js +162 -0
- package/dist_ts/features/feature.climate.d.ts +174 -0
- package/dist_ts/features/feature.climate.js +328 -0
- package/dist_ts/features/feature.cover.d.ts +121 -0
- package/dist_ts/features/feature.cover.js +217 -0
- package/dist_ts/features/feature.fan.d.ts +127 -0
- package/dist_ts/features/feature.fan.js +235 -0
- package/dist_ts/features/feature.light.d.ts +156 -0
- package/dist_ts/features/feature.light.js +296 -0
- package/dist_ts/features/feature.lock.d.ts +103 -0
- package/dist_ts/features/feature.lock.js +170 -0
- package/dist_ts/features/feature.playback.d.ts +98 -0
- package/dist_ts/features/feature.playback.js +191 -0
- package/dist_ts/features/feature.power.d.ts +79 -0
- package/dist_ts/features/feature.power.js +173 -0
- package/dist_ts/features/feature.print.d.ts +78 -0
- package/dist_ts/features/feature.print.js +226 -0
- package/dist_ts/features/feature.scan.d.ts +79 -0
- package/dist_ts/features/feature.scan.js +315 -0
- package/dist_ts/features/feature.sensor.d.ts +95 -0
- package/dist_ts/features/feature.sensor.js +147 -0
- package/dist_ts/features/feature.snmp.d.ts +88 -0
- package/dist_ts/features/feature.snmp.js +175 -0
- package/dist_ts/features/feature.switch.d.ts +75 -0
- package/dist_ts/features/feature.switch.js +126 -0
- package/dist_ts/features/feature.volume.d.ts +104 -0
- package/dist_ts/features/feature.volume.js +186 -0
- package/dist_ts/features/index.d.ts +19 -0
- package/dist_ts/features/index.js +23 -0
- package/dist_ts/helpers/helpers.iprange.d.ts +35 -0
- package/dist_ts/helpers/helpers.iprange.js +151 -0
- package/dist_ts/helpers/helpers.retry.d.ts +25 -0
- package/dist_ts/helpers/helpers.retry.js +69 -0
- package/dist_ts/index.d.ts +15 -0
- package/dist_ts/index.js +40 -0
- package/dist_ts/interfaces/feature.interfaces.d.ts +255 -0
- package/dist_ts/interfaces/feature.interfaces.js +6 -0
- package/dist_ts/interfaces/homeassistant.interfaces.d.ts +550 -0
- package/dist_ts/interfaces/homeassistant.interfaces.js +111 -0
- package/dist_ts/interfaces/index.d.ts +330 -0
- package/dist_ts/interfaces/index.js +16 -0
- package/dist_ts/interfaces/smarthome.interfaces.d.ts +282 -0
- package/dist_ts/interfaces/smarthome.interfaces.js +7 -0
- package/dist_ts/paths.d.ts +1 -0
- package/dist_ts/paths.js +3 -0
- package/dist_ts/plugins.d.ts +23 -0
- package/dist_ts/plugins.js +30 -0
- package/dist_ts/protocols/index.d.ts +12 -0
- package/dist_ts/protocols/index.js +21 -0
- package/dist_ts/protocols/protocol.escl.d.ts +58 -0
- package/dist_ts/protocols/protocol.escl.js +364 -0
- package/dist_ts/protocols/protocol.homeassistant.d.ts +248 -0
- package/dist_ts/protocols/protocol.homeassistant.js +611 -0
- package/dist_ts/protocols/protocol.ipp.d.ts +45 -0
- package/dist_ts/protocols/protocol.ipp.js +284 -0
- package/dist_ts/protocols/protocol.nut.d.ts +160 -0
- package/dist_ts/protocols/protocol.nut.js +364 -0
- package/dist_ts/protocols/protocol.sane.d.ts +104 -0
- package/dist_ts/protocols/protocol.sane.js +597 -0
- package/dist_ts/protocols/protocol.snmp.d.ts +133 -0
- package/dist_ts/protocols/protocol.snmp.js +333 -0
- package/dist_ts/protocols/protocol.upnp.d.ts +207 -0
- package/dist_ts/protocols/protocol.upnp.js +442 -0
- package/dist_ts/protocols/protocol.upssnmp.d.ts +160 -0
- package/dist_ts/protocols/protocol.upssnmp.js +263 -0
- package/npmextra.json +24 -0
- package/package.json +39 -0
- package/readme.hints.md +115 -0
- package/readme.md +624 -0
- package/test/test.ts +261 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/device/device.classes.device.ts +454 -0
- package/ts/devicemanager.classes.devicemanager.ts +1219 -0
- package/ts/discovery/discovery.classes.homeassistant.ts +376 -0
- package/ts/discovery/discovery.classes.mdns.ts +347 -0
- package/ts/discovery/discovery.classes.networkscanner.ts +648 -0
- package/ts/discovery/discovery.classes.ssdp.ts +357 -0
- package/ts/factories/index.ts +696 -0
- package/ts/features/feature.abstract.ts +251 -0
- package/ts/features/feature.camera.ts +214 -0
- package/ts/features/feature.climate.ts +407 -0
- package/ts/features/feature.cover.ts +278 -0
- package/ts/features/feature.fan.ts +296 -0
- package/ts/features/feature.light.ts +369 -0
- package/ts/features/feature.lock.ts +223 -0
- package/ts/features/feature.playback.ts +246 -0
- package/ts/features/feature.power.ts +231 -0
- package/ts/features/feature.print.ts +297 -0
- package/ts/features/feature.scan.ts +370 -0
- package/ts/features/feature.sensor.ts +202 -0
- package/ts/features/feature.snmp.ts +235 -0
- package/ts/features/feature.switch.ts +170 -0
- package/ts/features/feature.volume.ts +256 -0
- package/ts/features/index.ts +25 -0
- package/ts/helpers/helpers.iprange.ts +172 -0
- package/ts/helpers/helpers.retry.ts +100 -0
- package/ts/index.ts +165 -0
- package/ts/interfaces/feature.interfaces.ts +359 -0
- package/ts/interfaces/homeassistant.interfaces.ts +666 -0
- package/ts/interfaces/index.ts +421 -0
- package/ts/interfaces/smarthome.interfaces.ts +421 -0
- package/ts/paths.ts +5 -0
- package/ts/plugins.ts +41 -0
- package/ts/protocols/index.ts +59 -0
- package/ts/protocols/protocol.escl.ts +439 -0
- package/ts/protocols/protocol.homeassistant.ts +737 -0
- package/ts/protocols/protocol.ipp.ts +329 -0
- package/ts/protocols/protocol.nut.ts +471 -0
- package/ts/protocols/protocol.sane.ts +694 -0
- package/ts/protocols/protocol.snmp.ts +439 -0
- package/ts/protocols/protocol.upnp.ts +627 -0
- package/ts/protocols/protocol.upssnmp.ts +377 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* NUT Protocol variable definitions
|
|
5
|
+
*/
|
|
6
|
+
export const NUT_VARIABLES = {
|
|
7
|
+
// Device info
|
|
8
|
+
deviceMfr: 'device.mfr',
|
|
9
|
+
deviceModel: 'device.model',
|
|
10
|
+
deviceSerial: 'device.serial',
|
|
11
|
+
deviceType: 'device.type',
|
|
12
|
+
|
|
13
|
+
// UPS status
|
|
14
|
+
upsStatus: 'ups.status',
|
|
15
|
+
upsAlarm: 'ups.alarm',
|
|
16
|
+
upsTime: 'ups.time',
|
|
17
|
+
upsLoad: 'ups.load',
|
|
18
|
+
upsTemperature: 'ups.temperature',
|
|
19
|
+
|
|
20
|
+
// Battery
|
|
21
|
+
batteryCharge: 'battery.charge',
|
|
22
|
+
batteryRuntime: 'battery.runtime',
|
|
23
|
+
batteryVoltage: 'battery.voltage',
|
|
24
|
+
batteryVoltageNominal: 'battery.voltage.nominal',
|
|
25
|
+
batteryType: 'battery.type',
|
|
26
|
+
batteryDate: 'battery.date',
|
|
27
|
+
batteryTemperature: 'battery.temperature',
|
|
28
|
+
|
|
29
|
+
// Input
|
|
30
|
+
inputVoltage: 'input.voltage',
|
|
31
|
+
inputVoltageNominal: 'input.voltage.nominal',
|
|
32
|
+
inputFrequency: 'input.frequency',
|
|
33
|
+
inputFrequencyNominal: 'input.frequency.nominal',
|
|
34
|
+
inputTransferHigh: 'input.transfer.high',
|
|
35
|
+
inputTransferLow: 'input.transfer.low',
|
|
36
|
+
|
|
37
|
+
// Output
|
|
38
|
+
outputVoltage: 'output.voltage',
|
|
39
|
+
outputVoltageNominal: 'output.voltage.nominal',
|
|
40
|
+
outputFrequency: 'output.frequency',
|
|
41
|
+
outputCurrent: 'output.current',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* NUT instant commands
|
|
46
|
+
*/
|
|
47
|
+
export const NUT_COMMANDS = {
|
|
48
|
+
testBatteryStart: 'test.battery.start',
|
|
49
|
+
testBatteryStartQuick: 'test.battery.start.quick',
|
|
50
|
+
testBatteryStartDeep: 'test.battery.start.deep',
|
|
51
|
+
testBatteryStop: 'test.battery.stop',
|
|
52
|
+
calibrateStart: 'calibrate.start',
|
|
53
|
+
calibrateStop: 'calibrate.stop',
|
|
54
|
+
shutdown: 'shutdown.return',
|
|
55
|
+
shutdownStayOff: 'shutdown.stayoff',
|
|
56
|
+
shutdownStop: 'shutdown.stop',
|
|
57
|
+
shutdownReboot: 'shutdown.reboot',
|
|
58
|
+
beeperEnable: 'beeper.enable',
|
|
59
|
+
beeperDisable: 'beeper.disable',
|
|
60
|
+
beeperMute: 'beeper.mute',
|
|
61
|
+
beeperToggle: 'beeper.toggle',
|
|
62
|
+
loadOff: 'load.off',
|
|
63
|
+
loadOn: 'load.on',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* UPS status flags from NUT
|
|
68
|
+
*/
|
|
69
|
+
export type TNutStatusFlag =
|
|
70
|
+
| 'OL' // Online (on utility power)
|
|
71
|
+
| 'OB' // On battery
|
|
72
|
+
| 'LB' // Low battery
|
|
73
|
+
| 'HB' // High battery
|
|
74
|
+
| 'RB' // Replace battery
|
|
75
|
+
| 'CHRG' // Charging
|
|
76
|
+
| 'DISCHRG' // Discharging
|
|
77
|
+
| 'BYPASS' // On bypass
|
|
78
|
+
| 'CAL' // Calibrating
|
|
79
|
+
| 'OFF' // Offline
|
|
80
|
+
| 'OVER' // Overloaded
|
|
81
|
+
| 'TRIM' // Trimming voltage
|
|
82
|
+
| 'BOOST' // Boosting voltage
|
|
83
|
+
| 'FSD'; // Forced shutdown
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* NUT UPS information
|
|
87
|
+
*/
|
|
88
|
+
export interface INutUpsInfo {
|
|
89
|
+
name: string;
|
|
90
|
+
description: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* NUT variable
|
|
95
|
+
*/
|
|
96
|
+
export interface INutVariable {
|
|
97
|
+
name: string;
|
|
98
|
+
value: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* NUT Protocol handler for Network UPS Tools
|
|
103
|
+
* TCP-based text protocol on port 3493
|
|
104
|
+
*/
|
|
105
|
+
export class NutProtocol {
|
|
106
|
+
private socket: plugins.net.Socket | null = null;
|
|
107
|
+
private address: string;
|
|
108
|
+
private port: number;
|
|
109
|
+
private connected: boolean = false;
|
|
110
|
+
private responseBuffer: string = '';
|
|
111
|
+
private responseResolver: ((value: string[]) => void) | null = null;
|
|
112
|
+
private responseRejecter: ((error: Error) => void) | null = null;
|
|
113
|
+
|
|
114
|
+
constructor(address: string, port: number = 3493) {
|
|
115
|
+
this.address = address;
|
|
116
|
+
this.port = port;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Connect to NUT server
|
|
121
|
+
*/
|
|
122
|
+
public async connect(): Promise<void> {
|
|
123
|
+
if (this.connected) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
this.socket = new plugins.net.Socket();
|
|
129
|
+
|
|
130
|
+
const timeout = setTimeout(() => {
|
|
131
|
+
if (this.socket) {
|
|
132
|
+
this.socket.destroy();
|
|
133
|
+
this.socket = null;
|
|
134
|
+
}
|
|
135
|
+
reject(new Error(`Connection timeout to ${this.address}:${this.port}`));
|
|
136
|
+
}, 5000);
|
|
137
|
+
|
|
138
|
+
this.socket.on('connect', () => {
|
|
139
|
+
clearTimeout(timeout);
|
|
140
|
+
this.connected = true;
|
|
141
|
+
resolve();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
this.socket.on('error', (err) => {
|
|
145
|
+
clearTimeout(timeout);
|
|
146
|
+
this.connected = false;
|
|
147
|
+
if (this.responseRejecter) {
|
|
148
|
+
this.responseRejecter(err);
|
|
149
|
+
this.responseRejecter = null;
|
|
150
|
+
this.responseResolver = null;
|
|
151
|
+
}
|
|
152
|
+
reject(err);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
this.socket.on('data', (data: Buffer) => {
|
|
156
|
+
this.handleData(data);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
this.socket.on('close', () => {
|
|
160
|
+
this.connected = false;
|
|
161
|
+
if (this.responseRejecter) {
|
|
162
|
+
this.responseRejecter(new Error('Connection closed'));
|
|
163
|
+
this.responseRejecter = null;
|
|
164
|
+
this.responseResolver = null;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
this.socket.connect(this.port, this.address);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Disconnect from NUT server
|
|
174
|
+
*/
|
|
175
|
+
public async disconnect(): Promise<void> {
|
|
176
|
+
if (!this.connected || !this.socket) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
await this.sendCommand('LOGOUT');
|
|
182
|
+
} catch {
|
|
183
|
+
// Ignore logout errors
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.socket.destroy();
|
|
187
|
+
this.socket = null;
|
|
188
|
+
this.connected = false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if connected
|
|
193
|
+
*/
|
|
194
|
+
public get isConnected(): boolean {
|
|
195
|
+
return this.connected;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Handle incoming data
|
|
200
|
+
*/
|
|
201
|
+
private handleData(data: Buffer): void {
|
|
202
|
+
this.responseBuffer += data.toString();
|
|
203
|
+
|
|
204
|
+
// Check for complete response (ends with newline)
|
|
205
|
+
const lines = this.responseBuffer.split('\n');
|
|
206
|
+
|
|
207
|
+
// Check if we have a complete response
|
|
208
|
+
if (this.responseBuffer.endsWith('\n')) {
|
|
209
|
+
const responseLines = lines.filter((l) => l.trim().length > 0);
|
|
210
|
+
this.responseBuffer = '';
|
|
211
|
+
|
|
212
|
+
if (this.responseResolver) {
|
|
213
|
+
this.responseResolver(responseLines);
|
|
214
|
+
this.responseResolver = null;
|
|
215
|
+
this.responseRejecter = null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Send command and get response
|
|
222
|
+
*/
|
|
223
|
+
private async sendCommand(command: string): Promise<string[]> {
|
|
224
|
+
if (!this.socket || !this.connected) {
|
|
225
|
+
throw new Error('Not connected to NUT server');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
this.responseResolver = resolve;
|
|
230
|
+
this.responseRejecter = reject;
|
|
231
|
+
|
|
232
|
+
const timeout = setTimeout(() => {
|
|
233
|
+
this.responseResolver = null;
|
|
234
|
+
this.responseRejecter = null;
|
|
235
|
+
reject(new Error(`Command timeout: ${command}`));
|
|
236
|
+
}, 10000);
|
|
237
|
+
|
|
238
|
+
this.responseResolver = (lines) => {
|
|
239
|
+
clearTimeout(timeout);
|
|
240
|
+
resolve(lines);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
this.responseRejecter = (err) => {
|
|
244
|
+
clearTimeout(timeout);
|
|
245
|
+
reject(err);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
this.socket!.write(`${command}\n`);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* List available UPS devices
|
|
254
|
+
*/
|
|
255
|
+
public async listUps(): Promise<INutUpsInfo[]> {
|
|
256
|
+
await this.ensureConnected();
|
|
257
|
+
const response = await this.sendCommand('LIST UPS');
|
|
258
|
+
|
|
259
|
+
const upsList: INutUpsInfo[] = [];
|
|
260
|
+
|
|
261
|
+
for (const line of response) {
|
|
262
|
+
// Format: UPS <name> "<description>"
|
|
263
|
+
const match = line.match(/^UPS\s+(\S+)\s+"([^"]*)"/);
|
|
264
|
+
if (match) {
|
|
265
|
+
upsList.push({
|
|
266
|
+
name: match[1],
|
|
267
|
+
description: match[2],
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return upsList;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get all variables for a UPS
|
|
277
|
+
*/
|
|
278
|
+
public async listVariables(upsName: string): Promise<INutVariable[]> {
|
|
279
|
+
await this.ensureConnected();
|
|
280
|
+
const response = await this.sendCommand(`LIST VAR ${upsName}`);
|
|
281
|
+
|
|
282
|
+
const variables: INutVariable[] = [];
|
|
283
|
+
|
|
284
|
+
for (const line of response) {
|
|
285
|
+
// Format: VAR <ups> <name> "<value>"
|
|
286
|
+
const match = line.match(/^VAR\s+\S+\s+(\S+)\s+"([^"]*)"/);
|
|
287
|
+
if (match) {
|
|
288
|
+
variables.push({
|
|
289
|
+
name: match[1],
|
|
290
|
+
value: match[2],
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return variables;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get a specific variable value
|
|
300
|
+
*/
|
|
301
|
+
public async getVariable(upsName: string, varName: string): Promise<string | null> {
|
|
302
|
+
await this.ensureConnected();
|
|
303
|
+
const response = await this.sendCommand(`GET VAR ${upsName} ${varName}`);
|
|
304
|
+
|
|
305
|
+
for (const line of response) {
|
|
306
|
+
// Format: VAR <ups> <name> "<value>"
|
|
307
|
+
const match = line.match(/^VAR\s+\S+\s+\S+\s+"([^"]*)"/);
|
|
308
|
+
if (match) {
|
|
309
|
+
return match[1];
|
|
310
|
+
}
|
|
311
|
+
// Handle error responses
|
|
312
|
+
if (line.startsWith('ERR')) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get multiple variables at once
|
|
322
|
+
*/
|
|
323
|
+
public async getVariables(upsName: string, varNames: string[]): Promise<Map<string, string>> {
|
|
324
|
+
const results = new Map<string, string>();
|
|
325
|
+
|
|
326
|
+
for (const varName of varNames) {
|
|
327
|
+
const value = await this.getVariable(upsName, varName);
|
|
328
|
+
if (value !== null) {
|
|
329
|
+
results.set(varName, value);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return results;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Execute an instant command
|
|
338
|
+
*/
|
|
339
|
+
public async runCommand(upsName: string, command: string): Promise<boolean> {
|
|
340
|
+
await this.ensureConnected();
|
|
341
|
+
const response = await this.sendCommand(`INSTCMD ${upsName} ${command}`);
|
|
342
|
+
|
|
343
|
+
for (const line of response) {
|
|
344
|
+
if (line === 'OK') {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
if (line.startsWith('ERR')) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* List available commands for a UPS
|
|
357
|
+
*/
|
|
358
|
+
public async listCommands(upsName: string): Promise<string[]> {
|
|
359
|
+
await this.ensureConnected();
|
|
360
|
+
const response = await this.sendCommand(`LIST CMD ${upsName}`);
|
|
361
|
+
|
|
362
|
+
const commands: string[] = [];
|
|
363
|
+
|
|
364
|
+
for (const line of response) {
|
|
365
|
+
// Format: CMD <ups> <command>
|
|
366
|
+
const match = line.match(/^CMD\s+\S+\s+(\S+)/);
|
|
367
|
+
if (match) {
|
|
368
|
+
commands.push(match[1]);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return commands;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Parse UPS status string into flags
|
|
377
|
+
*/
|
|
378
|
+
public parseStatus(statusString: string): TNutStatusFlag[] {
|
|
379
|
+
return statusString.split(/\s+/).filter((s) => s.length > 0) as TNutStatusFlag[];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Get comprehensive UPS status
|
|
384
|
+
*/
|
|
385
|
+
public async getUpsStatus(upsName: string): Promise<{
|
|
386
|
+
status: TNutStatusFlag[];
|
|
387
|
+
batteryCharge: number;
|
|
388
|
+
batteryRuntime: number;
|
|
389
|
+
inputVoltage: number;
|
|
390
|
+
outputVoltage: number;
|
|
391
|
+
load: number;
|
|
392
|
+
}> {
|
|
393
|
+
const vars = await this.getVariables(upsName, [
|
|
394
|
+
NUT_VARIABLES.upsStatus,
|
|
395
|
+
NUT_VARIABLES.batteryCharge,
|
|
396
|
+
NUT_VARIABLES.batteryRuntime,
|
|
397
|
+
NUT_VARIABLES.inputVoltage,
|
|
398
|
+
NUT_VARIABLES.outputVoltage,
|
|
399
|
+
NUT_VARIABLES.upsLoad,
|
|
400
|
+
]);
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
status: this.parseStatus(vars.get(NUT_VARIABLES.upsStatus) || ''),
|
|
404
|
+
batteryCharge: parseFloat(vars.get(NUT_VARIABLES.batteryCharge) || '0'),
|
|
405
|
+
batteryRuntime: parseFloat(vars.get(NUT_VARIABLES.batteryRuntime) || '0'),
|
|
406
|
+
inputVoltage: parseFloat(vars.get(NUT_VARIABLES.inputVoltage) || '0'),
|
|
407
|
+
outputVoltage: parseFloat(vars.get(NUT_VARIABLES.outputVoltage) || '0'),
|
|
408
|
+
load: parseFloat(vars.get(NUT_VARIABLES.upsLoad) || '0'),
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get device information
|
|
414
|
+
*/
|
|
415
|
+
public async getDeviceInfo(upsName: string): Promise<{
|
|
416
|
+
manufacturer: string;
|
|
417
|
+
model: string;
|
|
418
|
+
serial: string;
|
|
419
|
+
type: string;
|
|
420
|
+
}> {
|
|
421
|
+
const vars = await this.getVariables(upsName, [
|
|
422
|
+
NUT_VARIABLES.deviceMfr,
|
|
423
|
+
NUT_VARIABLES.deviceModel,
|
|
424
|
+
NUT_VARIABLES.deviceSerial,
|
|
425
|
+
NUT_VARIABLES.deviceType,
|
|
426
|
+
]);
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
manufacturer: vars.get(NUT_VARIABLES.deviceMfr) || '',
|
|
430
|
+
model: vars.get(NUT_VARIABLES.deviceModel) || '',
|
|
431
|
+
serial: vars.get(NUT_VARIABLES.deviceSerial) || '',
|
|
432
|
+
type: vars.get(NUT_VARIABLES.deviceType) || '',
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Ensure connected before command
|
|
438
|
+
*/
|
|
439
|
+
private async ensureConnected(): Promise<void> {
|
|
440
|
+
if (!this.connected) {
|
|
441
|
+
await this.connect();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Check if a NUT server is reachable
|
|
447
|
+
*/
|
|
448
|
+
public static async probe(address: string, port: number = 3493, timeout: number = 3000): Promise<boolean> {
|
|
449
|
+
return new Promise((resolve) => {
|
|
450
|
+
const socket = new plugins.net.Socket();
|
|
451
|
+
|
|
452
|
+
const timer = setTimeout(() => {
|
|
453
|
+
socket.destroy();
|
|
454
|
+
resolve(false);
|
|
455
|
+
}, timeout);
|
|
456
|
+
|
|
457
|
+
socket.on('connect', () => {
|
|
458
|
+
clearTimeout(timer);
|
|
459
|
+
socket.destroy();
|
|
460
|
+
resolve(true);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
socket.on('error', () => {
|
|
464
|
+
clearTimeout(timer);
|
|
465
|
+
resolve(false);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
socket.connect(port, address);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|