1 /* powercom-hid.c - subdriver to monitor PowerCOM USB/HID devices with NUT
2  *
3  *  Copyright (C)
4  *  2003 - 2009	Arnaud Quette <ArnaudQuette@Eaton.com>
5  *  2005 - 2006	Peter Selinger <selinger@users.sourceforge.net>
6  *  2008 - 2009	Arjen de Korte <adkorte-guest@alioth.debian.org>
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21  */
22 
23 #include "main.h"	/* for getval() */
24 #include "usbhid-ups.h"
25 #include "powercom-hid.h"
26 #include "usb-common.h"
27 
28 #define POWERCOM_HID_VERSION	"PowerCOM HID 0.5"
29 /* FIXME: experimental flag to be put in upsdrv_info */
30 
31 /* PowerCOM */
32 #define POWERCOM_VENDORID	0x0d9f
33 
34 /* USB IDs device table */
35 static usb_device_id_t powercom_usb_device_table[] = {
36 	/* PowerCOM IMP - IMPERIAL Series */
37 	{ USB_DEVICE(POWERCOM_VENDORID, 0x00a2), NULL },
38 	/* PowerCOM SKP - Smart KING Pro (all Smart series) */
39 	{ USB_DEVICE(POWERCOM_VENDORID, 0x00a3), NULL },
40 	/* PowerCOM WOW */
41 	{ USB_DEVICE(POWERCOM_VENDORID, 0x00a4), NULL },
42 	/* PowerCOM VGD - Vanguard */
43 	{ USB_DEVICE(POWERCOM_VENDORID, 0x00a5), NULL },
44 	/* PowerCOM BNT - Black Knight Pro */
45 	{ USB_DEVICE(POWERCOM_VENDORID, 0x00a6), NULL },
46 	/* PowerCOM Vanguard and BNT-xxxAP */
47 	{ USB_DEVICE(POWERCOM_VENDORID, 0x0004), NULL },
48 	{ USB_DEVICE(POWERCOM_VENDORID, 0x0001), NULL },
49 
50 	/* Terminating entry */
51 	{ 0, 0, NULL }
52 };
53 
54 static char powercom_scratch_buf[32];
55 
powercom_startup_fun(double value)56 static const char *powercom_startup_fun(double value)
57 {
58 	uint16_t	i = value;
59 
60 	snprintf(powercom_scratch_buf, sizeof(powercom_scratch_buf), "%d", 60 * (((i & 0x00FF) << 8) + (i >> 8)));
61 	upsdebugx(3, "%s: value = %.0f, buf = %s", __func__, value, powercom_scratch_buf);
62 
63 	return powercom_scratch_buf;
64 }
65 
powercom_startup_nuf(const char * value)66 static double powercom_startup_nuf(const char *value)
67 {
68 	const char	*s = dstate_getinfo("ups.delay.start");
69 	uint16_t	val, command;
70 	int iv;
71 
72 	iv = atoi(value ? value : s) / 60;
73 	if (iv < 0 || (intmax_t)iv > (intmax_t)UINT16_MAX) {
74 		upsdebugx(0, "%s: value = %d is not in uint16_t range", __func__, iv);
75 		return 0;
76 	}
77 
78 	/* COMMENTME: What are we doing here, a byte-swap in the word? */
79 	val = (uint16_t)iv;
80 	command =  (uint16_t)(val << 8);
81 	command += (uint16_t)(val >> 8);
82 	upsdebugx(3, "%s: value = %s, command = %04X", __func__, value, command);
83 
84 	return command;
85 }
86 
87 static info_lkp_t powercom_startup_info[] = {
88 	{ 0, NULL, powercom_startup_fun, powercom_startup_nuf }
89 };
90 
powercom_shutdown_fun(double value)91 static const char *powercom_shutdown_fun(double value)
92 {
93 	uint16_t	i = value;
94 
95 	snprintf(powercom_scratch_buf, sizeof(powercom_scratch_buf), "%d", 60 * (i & 0x00FF) + (i >> 8));
96 	upsdebugx(3, "%s: value = %.0f, buf = %s", __func__, value, powercom_scratch_buf);
97 
98 	return powercom_scratch_buf;
99 }
100 
powercom_shutdown_nuf(const char * value)101 static double powercom_shutdown_nuf(const char *value)
102 {
103 	const char	*s = dstate_getinfo("ups.delay.shutdown");
104 	uint16_t	val, command;
105 	int iv;
106 
107 	iv = atoi(value ? value : s);
108 	if (iv < 0 || (intmax_t)iv > (intmax_t)UINT16_MAX) {
109 		upsdebugx(0, "%s: value = %d is not in uint16_t range", __func__, iv);
110 		return 0;
111 	}
112 
113 	val = (uint16_t)iv;
114 	val = val ? val : 1;    /* 0 sets the maximum delay */
115 	command = ((uint16_t)((val % 60) << 8)) + (uint16_t)(val / 60);
116 	command |= 0x4000;	/* AC RESTART NORMAL ENABLE */
117 	upsdebugx(3, "%s: value = %s, command = %04X", __func__, value, command);
118 
119 	return command;
120 }
121 
122 static info_lkp_t powercom_shutdown_info[] = {
123 	{ 0, NULL, powercom_shutdown_fun, powercom_shutdown_nuf }
124 };
125 
powercom_stayoff_nuf(const char * value)126 static double powercom_stayoff_nuf(const char *value)
127 {
128 	const char	*s = dstate_getinfo("ups.delay.shutdown");
129 	uint16_t	val, command;
130 	int iv;
131 
132 	iv = atoi(value ? value : s);
133 	if (iv < 0 || (intmax_t)iv > (intmax_t)UINT16_MAX) {
134 		upsdebugx(0, "%s: value = %d is not in uint16_t range", __func__, iv);
135 		return 0;
136 	}
137 
138 	val = (uint16_t)iv;
139 	val = val ? val : 1;    /* 0 sets the maximum delay */
140 	command = ((uint16_t)((val % 60) << 8)) + (uint16_t)(val / 60);
141 	command |= 0x8000;	/* AC RESTART NORMAL DISABLE */
142 	upsdebugx(3, "%s: value = %s, command = %04X", __func__, value, command);
143 
144 	return command;
145 }
146 
147 static info_lkp_t powercom_stayoff_info[] = {
148 	{ 0, NULL, NULL, powercom_stayoff_nuf }
149 };
150 
151 static info_lkp_t powercom_beeper_info[] = {
152 	{ 1, "enabled", NULL, NULL },
153 	{ 2, "disabled", NULL, NULL },	/* muted? */
154 	{ 0, NULL, NULL, NULL }
155 };
156 
powercom_voltage_conversion_fun(double value)157 static const char *powercom_voltage_conversion_fun(double value)
158 {
159 	static char buf[20];
160 	snprintf(buf, sizeof(buf), "%0.0f", value * 4);
161 	return buf;
162 }
163 
164 static info_lkp_t powercom_voltage_conversion[] = {
165 	{ 0, NULL, powercom_voltage_conversion_fun, NULL }
166 };
167 
powercom_upsfail_conversion_fun(double value)168 static const char *powercom_upsfail_conversion_fun(double value)
169 {
170 	if ((long)value & 0x0001) {
171 		return "fanfail";
172 	} else {
173 		return "!fanfail";
174 	}
175 }
176 
177 static info_lkp_t powercom_upsfail_conversion[] = {
178 	{ 0, NULL, powercom_upsfail_conversion_fun, NULL }
179 };
180 
powercom_replacebatt_conversion_fun(double value)181 static const char *powercom_replacebatt_conversion_fun(double value)
182 {
183 	if ((long)value & 0x0002) {
184 		return "replacebatt";
185 	} else {
186 		return "!replacebatt";
187 	}
188 }
189 
190 static info_lkp_t powercom_replacebatt_conversion[] = {
191 	{ 0, NULL, powercom_replacebatt_conversion_fun, NULL }
192 };
193 
powercom_test_conversion_fun(double value)194 static const char *powercom_test_conversion_fun(double value)
195 {
196 	if ((long)value & 0x0004) {
197 		return "cal";
198 	} else {
199 		return "!cal";
200 	}
201 }
202 
203 static info_lkp_t powercom_test_conversion[] = {
204 	{ 0, NULL, powercom_test_conversion_fun, NULL }
205 };
206 
powercom_shutdownimm_conversion_fun(double value)207 static const char *powercom_shutdownimm_conversion_fun(double value)
208 {
209 	if ((long)value & 0x0010) {
210 		return "shutdownimm";
211 	} else {
212 		return "!shutdownimm";
213 	}
214 }
215 
216 static info_lkp_t powercom_shutdownimm_conversion[] = {
217 	{ 0, NULL, powercom_shutdownimm_conversion_fun, NULL }
218 };
219 
powercom_online_conversion_fun(double value)220 static const char *powercom_online_conversion_fun(double value)
221 {
222 	if ((long)value & 0x0001) {
223 		return "!online";
224 	} else {
225 		return "online";
226 	}
227 }
228 
229 static info_lkp_t powercom_online_conversion[] = {
230 	{ 0, NULL, powercom_online_conversion_fun, NULL }
231 };
232 
powercom_lowbatt_conversion_fun(double value)233 static const char *powercom_lowbatt_conversion_fun(double value)
234 {
235 	if ((long)value & 0x0002) {
236 		return "lowbatt";
237 	} else {
238 		return "!lowbatt";
239 	}
240 }
241 
242 static info_lkp_t powercom_lowbatt_conversion[] = {
243 	{ 0, NULL, powercom_lowbatt_conversion_fun, NULL }
244 };
245 
powercom_trim_conversion_fun(double value)246 static const char *powercom_trim_conversion_fun(double value)
247 {
248 	if (((long)value & 0x0018) == 0x0008) {
249 		return "trim";
250 	} else {
251 		return "!trim";
252 	}
253 }
254 
255 static info_lkp_t powercom_trim_conversion[] = {
256 	{ 0, NULL, powercom_trim_conversion_fun, NULL }
257 };
258 
powercom_boost_conversion_fun(double value)259 static const char *powercom_boost_conversion_fun(double value)
260 {
261 	if (((long)value & 0x0018) == 0x0018) {
262 		return "boost";
263 	} else {
264 		return "!boost";
265 	}
266 }
267 
268 static info_lkp_t powercom_boost_conversion[] = {
269 	{ 0, NULL, powercom_boost_conversion_fun, NULL }
270 };
271 
powercom_overload_conversion_fun(double value)272 static const char *powercom_overload_conversion_fun(double value)
273 {
274 	if ((long)value & 0x0020) {
275 		return "overload";
276 	} else {
277 		return "!overload";
278 	}
279 }
280 
281 static info_lkp_t powercom_overload_conversion[] = {
282 	{ 0, NULL, powercom_overload_conversion_fun, NULL }
283 };
284 
285 /* --------------------------------------------------------------- */
286 /* Vendor-specific usage table */
287 /* --------------------------------------------------------------- */
288 
289 /* POWERCOM usage table */
290 static usage_lkp_t powercom_usage_lkp[] = {
291 	{ "PowercomUPS",                      0x00020004 },
292 	{ "PowercomBatterySystem",            0x00020010 },
293 	{ "PowercomPowerConverter",           0x00020016 },
294 	{ "PowercomInput",                    0x0002001a },
295 	{ "PowercomOutput",                   0x0002001c },
296 	{ "PowercomVoltage",                  0x00020030 },
297 	{ "PowercomFrequency",                0x00020032 },
298 	{ "PowercomPercentLoad",              0x00020035 },
299 	{ "PowercomTemperature",              0x00020036 },
300 	{ "PowercomDelayBeforeStartup",       0x00020056 },
301 	{ "PowercomDelayBeforeShutdown",      0x00020057 },
302 	{ "PowercomTest",                     0x00020058 },
303 	{ "PowercomShutdownRequested",        0x00020068 },
304 	{ "PowercomInternalChargeController", 0x00020081 },
305 	{ "PowercomPrimaryBatterySupport",    0x00020082 },
306 	{ "PowercomDesignCapacity",           0x00020083 },
307 	{ "PowercomSpecificationInfo",        0x00020084 },
308 	{ "PowercomManufacturerDate",         0x00020085 },
309 	{ "PowercomSerialNumber",             0x00020086 },
310 	{ "PowercomManufacturerName",         0x00020087 },
311 	{ "POWERCOM1",	0x0084002f },
312 	{ "POWERCOM2",	0xff860060 },
313 	{ "POWERCOM3",	0xff860080 },
314 	{ "PCMDelayBeforeStartup",	0x00ff0056 },
315 	{ "PCMDelayBeforeShutdown",	0x00ff0057 },
316 	{  NULL, 0 }
317 };
318 
319 static usage_tables_t powercom_utab[] = {
320 	powercom_usage_lkp,
321 	hid_usage_lkp,
322 	NULL,
323 };
324 
325 /* --------------------------------------------------------------- */
326 /* HID2NUT lookup table                                            */
327 /* --------------------------------------------------------------- */
328 
329 static hid_info_t powercom_hid2nut[] = {
330 	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ACPresent", NULL, NULL, 0, online_info },
331 	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.BatteryPresent", NULL, NULL, 0, nobattery_info },
332 	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.BelowRemainingCapacityLimit", NULL, NULL, 0, lowbatt_info },
333 	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Charging", NULL, NULL, 0, charging_info },
334 	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.CommunicationLost", NULL, NULL, 0, commfault_info },
335 	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Discharging", NULL, NULL, 0, discharging_info },
336 	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.NeedReplacement", NULL, NULL, 0, replacebatt_info },
337 	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.Overload", NULL, NULL, 0, overload_info },
338 	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.RemainingTimeLimitExpired", NULL, NULL, 0, timelimitexpired_info },
339 	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ShutdownImminent", NULL, NULL, 0, shutdownimm_info },
340 /*	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.POWERCOM3", NULL, "%.0f", 0, NULL }, */
341 /*	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ShutdownRequested", NULL, "%.0f", 0, NULL }, */
342 /*	{ "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.VoltageNotRegulated", NULL, "%.0f", 0, NULL }, */
343 	{ "BOOL", 0, 0, "UPS.PresentStatus.ACPresent", NULL, NULL, 0, online_info },
344 	{ "BOOL", 0, 0, "UPS.PresentStatus.BatteryPresent", NULL, NULL, 0, nobattery_info },
345 	{ "BOOL", 0, 0, "UPS.PresentStatus.BelowRemainingCapacityLimit", NULL, NULL, 0, lowbatt_info },
346 	{ "BOOL", 0, 0, "UPS.PresentStatus.Boost", NULL, NULL, 0, boost_info },
347 	{ "BOOL", 0, 0, "UPS.PresentStatus.Buck", NULL, NULL, 0, trim_info },
348 	{ "BOOL", 0, 0, "UPS.PresentStatus.Charging", NULL, NULL, 0, charging_info },
349 	{ "BOOL", 0, 0, "UPS.PresentStatus.CommunicationLost", NULL, NULL, 0, commfault_info },
350 	{ "BOOL", 0, 0, "UPS.PresentStatus.Discharging", NULL, NULL, 0, discharging_info },
351 	{ "BOOL", 0, 0, "UPS.PresentStatus.NeedReplacement", NULL, NULL, 0, replacebatt_info },
352 	{ "BOOL", 0, 0, "UPS.PresentStatus.Overload", NULL, NULL, 0, overload_info },
353 	{ "BOOL", 0, 0, "UPS.PresentStatus.RemainingTimeLimitExpired", NULL, NULL, 0, timelimitexpired_info },
354 	{ "BOOL", 0, 0, "UPS.PresentStatus.ShutdownImminent", NULL, NULL, 0, shutdownimm_info },
355 /*	{ "BOOL", 0, 0, "UPS.PresentStatus.POWERCOM3", NULL, "%.0f", 0, NULL }, */
356 /*	{ "BOOL", 0, 0, "UPS.PresentStatus.ShutdownRequested", NULL, "%.0f", 0, NULL }, */
357 /*	{ "BOOL", 0, 0, "UPS.PresentStatus.Tested", NULL, "%.0f", 0, NULL }, */
358 /*	{ "BOOL", 0, 0, "UPS.PresentStatus.VoltageNotRegulated", NULL, "%.0f", 0, NULL }, */
359 
360 /*
361  * According to the HID PDC specifications, the below values should report battery.voltage(.nominal)
362  * PowerCOM duplicates the output.voltage(.nominal) here, so we ignore them
363  *	{ "battery.voltage", 0, 0, "UPS.PowerSummary.Voltage", NULL, "%.2f", 0, NULL },
364  *	{ "battery.voltage", 0, 0, "UPS.Battery.Voltage", NULL, "%.2f", 0, NULL },
365  *	{ "battery.voltage.nominal", 0, 0, "UPS.PowerSummary.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL },
366  *	{ "battery.voltage.nominal", 0, 0, "UPS.Battery.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL },
367  */
368 	{ "battery.charge", 0, 0, "UPS.PowerSummary.RemainingCapacity", NULL, "%.0f", 0, NULL },
369 	{ "battery.charge", 0, 0, "UPS.Battery.RemainingCapacity", NULL, "%.0f", 0, NULL },
370 	{ "battery.charge.low", 0, 0, "UPS.PowerSummary.RemainingCapacityLimit", NULL, "%.0f", 0, NULL },
371 	{ "battery.charge.warning", 0, 0, "UPS.PowerSummary.WarningCapacityLimit", NULL, "%.0f", 0, NULL },
372 	{ "battery.runtime", 0, 0, "UPS.PowerSummary.RunTimeToEmpty", NULL, "%.0f", 0, NULL },
373 	{ "battery.date", 0, 0, "UPS.Battery.ManufacturerDate", NULL, "%s", HU_FLAG_STATIC, date_conversion },
374 	{ "battery.type", 0, 0, "UPS.PowerSummary.iDeviceChemistry", NULL, "%s", HU_FLAG_STATIC, stringid_conversion },
375 /*	{ "unmapped.ups.battery.delaybeforestartup", 0, 0, "UPS.Battery.DelayBeforeStartup", NULL, "%.0f", 0, NULL }, */
376 /*	{ "unmapped.ups.battery.initialized", 0, 0, "UPS.Battery.Initialized", NULL, "%.0f", 0, NULL }, */
377 
378 	{ "ups.beeper.status", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "%s", 0, powercom_beeper_info },
379 	{ "ups.beeper.status", 0, 0, "UPS.AudibleAlarmControl", NULL, "%s", 0, powercom_beeper_info },
380 	{ "ups.load", 0, 0, "UPS.Output.PercentLoad", NULL, "%.0f", 0, NULL },
381 	{ "ups.date", 0, 0, "UPS.PowerSummary.ManufacturerDate", NULL, "%s", HU_FLAG_STATIC, date_conversion },
382 	{ "ups.test.result", 0, 0, "UPS.Battery.Test", NULL, "%s", 0, test_read_info },
383 /*	{ "unmapped.ups.powersummary.imanufacturer", 0, 0, "UPS.PowerSummary.iManufacturer", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, */
384 /*	{ "unmapped.ups.powersummary.iproduct", 0, 0, "UPS.PowerSummary.iProduct", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, */
385 /*	{ "unmapped.ups.powersummary.iserialnumber", 0, 0, "UPS.PowerSummary.iSerialNumber", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, */
386 /*	{ "unmapped.ups.iname", 0, 0, "UPS.iName", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, */
387 /*	{ "unmapped.ups.powersummary.ioeminformation", 0, 0, "UPS.PowerSummary.iOEMInformation", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, */
388 
389 /* The implementation of the HID path UPS.PowerSummary.DelayBeforeStartup is unconventional:
390  * Read:
391  *	Byte 7, byte 8 (min)
392  * Write:
393  *	Command 4, high byte min, low byte min
394  */
395 	{ "ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 8, "UPS.PowerSummary.DelayBeforeStartup", NULL, "60", HU_FLAG_ABSENT, NULL },
396 	{ "ups.timer.start", 0, 0, "UPS.PowerSummary.DelayBeforeStartup", NULL, "%.0f", 0, powercom_startup_info },
397 
398 /* The implementation of the HID path UPS.PowerSummary.DelayBeforeShutdown is unconventional:
399  * Read:
400  *	Byte 13, Byte 14 (min, sec)
401  * Write:
402  *	If Byte(sec), bit7=0 and bit6=0 Then
403  *		If Byte 9, bit0=1 Then command 185, 188, min, sec (OL -> shutdown.return)
404  *		If Byte 9, bit0=0 Then command 186, 188, min, sec (OB -> shutdown.stayoff)
405  *	If Byte(sec), bit7=0 and bit6=1
406  *		Then command 185, 188, min, sec (shutdown.return)
407  *	If Byte(sec), bit7=1 and bit6=0 Then
408  *		Then command 186, 188, min, sec (shutdown.stayoff)
409  *	If Byte(sec), bit7=1 and bit6=1 Then
410  *		No actions
411  */
412 	{ "ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 8, "UPS.PowerSummary.DelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_FLAG_ABSENT, NULL },
413 	{ "ups.timer.shutdown", 0, 0, "UPS.PowerSummary.DelayBeforeShutdown", NULL, "%.0f", HU_FLAG_QUICK_POLL, powercom_shutdown_info },
414 	{ "ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 8, "UPS.PowerSummary.PCMDelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_FLAG_ABSENT, NULL },
415 	{ "ups.timer.shutdown", 0, 0, "UPS.PowerSummary.PCMDelayBeforeShutdown", NULL, "%.0f", HU_FLAG_QUICK_POLL, powercom_shutdown_info },
416 
417 	{ "input.voltage", 0, 0, "UPS.Input.Voltage", NULL, "%.1f", 0, NULL },
418 	{ "input.voltage.nominal", 0, 0, "UPS.Input.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL },
419 	{ "input.frequency", 0, 0, "UPS.Input.Frequency", NULL, "%.1f", 0, NULL },
420 
421 	{ "output.voltage", 0, 0, "UPS.Output.Voltage", NULL, "%.1f", 0, NULL },
422 	{ "output.voltage.nominal", 0, 0, "UPS.Output.ConfigVoltage", NULL, "%.0f", HU_FLAG_STATIC, NULL },
423 	{ "output.frequency", 0, 0, "UPS.Output.Frequency", NULL, "%.1f", 0, NULL },
424 
425 /*	{ "unmapped.ups.powercom1", 0, 0, "UPS.POWERCOM1", NULL, "%.0f", 0, NULL }, broken pipe */
426 /*	{ "unmapped.ups.powercom2", 0, 0, "UPS.POWERCOM2", NULL, "%.0f", 0, NULL }, broken pipe */
427 /* 	{ "unmapped.ups.powersummary.rechargeable", 0, 0, "UPS.PowerSummary.Rechargeable", NULL, "%.0f", 0, NULL }, */
428 /*	{ "unmapped.ups.shutdownimminent", 0, 0, "UPS.ShutdownImminent", NULL, "%.0f", 0, NULL }, */
429 
430 	/* instcmds */
431 	{ "beeper.toggle", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "1", HU_TYPE_CMD, NULL },
432 	{ "beeper.enable", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "1", HU_TYPE_CMD, NULL },
433 	{ "beeper.disable", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "0", HU_TYPE_CMD, NULL },
434 	{ "test.battery.start.quick", 0, 0, "UPS.Battery.Test", NULL, "1", HU_TYPE_CMD, NULL },
435 	{ "load.on.delay", 0, 0, "UPS.PowerSummary.DelayBeforeStartup", NULL, NULL, HU_TYPE_CMD, powercom_startup_info },
436 	{ "shutdown.return", 0, 0, "UPS.PowerSummary.DelayBeforeShutdown", NULL, NULL, HU_TYPE_CMD, powercom_shutdown_info },
437 	{ "shutdown.stayoff", 0, 0, "UPS.PowerSummary.DelayBeforeShutdown", NULL, NULL, HU_TYPE_CMD, powercom_stayoff_info },
438 	{ "load.on", 0, 0, "UPS.PowerSummary.PCMDelayBeforeStartup", NULL, "0", HU_TYPE_CMD, powercom_startup_info },
439 	{ "load.off", 0, 0, "UPS.PowerSummary.PCMDelayBeforeShutdown", NULL, "0", HU_TYPE_CMD, powercom_stayoff_info },
440 	{ "shutdown.return", 0, 0, "UPS.PowerSummary.PCMDelayBeforeShutdown", NULL, NULL, HU_TYPE_CMD, powercom_shutdown_info },
441 	{ "shutdown.stayoff", 0, 0, "UPS.PowerSummary.PCMDelayBeforeShutdown", NULL, NULL, HU_TYPE_CMD, powercom_stayoff_info },
442 
443 	{ "ups.serial", 0, 0, "PowercomUPS.PowercomSerialNumber", NULL, "%s", 0, stringid_conversion },
444 	{ "ups.mfr", 0, 0, "PowercomUPS.PowercomManufacturerName", NULL, "%s", 0, stringid_conversion },
445 /*	{ "UPS.DesignCapacity", 0, 0, "PowercomUPS.PowercomDesignCapacity", NULL, "%.0f", 0, NULL }, is always 255 */
446 	{ "ups.mfr.date", 0, 0, "PowercomUPS.PowercomManufacturerDate", NULL, "%s", 0, date_conversion },
447 	{ "battery.temperature", 0, 0, "PowercomUPS.PowercomBatterySystem.PowercomTemperature", NULL, "%.0f", 0, NULL },
448 	{ "battery.temperature", 0, 0, "UPS.Battery.Temperature", NULL, "%.1f", 0, NULL },
449 	{ "battery.charge", 0, 0, "PowercomUPS.PowercomBatterySystem.PowercomVoltage", NULL, "%.0f", 0, NULL },
450 /*	{ "UPS.BatterySystem.SpecificationInfo", 0, 0, "PowercomUPS.PowercomBatterySystem.PowercomSpecificationInfo", NULL, "%.0f", 0, NULL }, */
451 	{ "input.frequency", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomInput.PowercomFrequency", NULL, "%.0f", 0, NULL },
452 	{ "input.voltage", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomInput.PowercomVoltage", NULL, "%.0f", 0, powercom_voltage_conversion },
453 	{ "output.voltage", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomVoltage", NULL, "%.0f", 0, powercom_voltage_conversion },
454 	{ "ups.load", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomPercentLoad", NULL, "%.0f", 0, NULL },
455 	/* flags: 4 - Testing, 8 - Probably mute (it's set on battery with muted beeper and sometimes during ups test)
456 	 * bit 0  UPS fault  (1 = FAILT)
457 	 * bit 1  Battery status (1 = BAD, 0 = NORMAL)
458 	 * bit 2  Test  mode  (1 = TEST, 0 = NORMAL)
459 	 * bit 3  X
460 	 * bit 4  Pre-SD count mode (1 = ACTIVE)
461 	 * bit 5  Schedule count mode (1 = ACTIVE)
462 	 * bit 6  Disable NO LOAD SHUTDOWN (1 = ACTIVE)
463 	 * bit 7  0
464 	 */
465 /*	{ "UPS.PowerConverter.Output.InternalChargeController", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomInternalChargeController", NULL, "%.0f", 0, NULL }, */
466 	{ "BOOL", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomInternalChargeController", NULL, NULL, HU_FLAG_QUICK_POLL, powercom_upsfail_conversion },
467 	{ "BOOL", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomInternalChargeController", NULL, NULL, HU_FLAG_QUICK_POLL, powercom_replacebatt_conversion },
468 	{ "BOOL", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomInternalChargeController", NULL, NULL, HU_FLAG_QUICK_POLL, powercom_test_conversion },
469 	{ "BOOL", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomInternalChargeController", NULL, NULL, HU_FLAG_QUICK_POLL, powercom_shutdownimm_conversion },
470 	/* flags: 1 - On battery, 2 - Low Battery, 8 - Trim, 8+16 - Boost
471 	 * bit 0  is line fail (1 = INV, 0 = LINE)
472 	 * bit 1  is low battery (1 = BAT_ LOW, 0 = NORMAL)
473 	 * bit 2  X
474 	 * bit 3  AVR status (1 = AVR, 0 = NO_AVR)
475 	 * bit 4  AVR mode (1 = BOOST, 0 = BUCK)
476 	 * bit 5  Load status (1 = OVER LOAD, 0 = NORMAL)
477 	 * bit 6  X
478 	 * bit 7  SD mode display
479 	 */
480 /*	{ "UPS.PowerConverter.Output.PrimaryBatterySupport", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomPrimaryBatterySupport", NULL, "%.0f", 0, NULL }, */
481 	{ "BOOL", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomPrimaryBatterySupport", NULL, NULL, HU_FLAG_QUICK_POLL, powercom_online_conversion },
482 	/* Low battery status may not work */
483 	{ "BOOL", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomPrimaryBatterySupport", NULL, NULL, HU_FLAG_QUICK_POLL, powercom_lowbatt_conversion },
484 	{ "BOOL", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomPrimaryBatterySupport", NULL, NULL, HU_FLAG_QUICK_POLL, powercom_trim_conversion },
485 	{ "BOOL", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomPrimaryBatterySupport", NULL, NULL, HU_FLAG_QUICK_POLL, powercom_boost_conversion },
486 	{ "BOOL", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomPrimaryBatterySupport", NULL, NULL, HU_FLAG_QUICK_POLL, powercom_overload_conversion },
487 	{ "output.frequency", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomOutput.PowercomFrequency", NULL, "%.0f", 0, NULL },
488 	{ "ups.test.result", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomTest", NULL, "%s", 0, test_read_info },
489 /*	{ "UPS.PowerConverter.ShutdownRequested", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomShutdownRequested", NULL, "%.0f", 0, NULL }, */
490 	{ "ups.delay.shutdown", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomDelayBeforeShutdown", NULL, "%.0f", 0, NULL },
491 	{ "ups.delay.start", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomDelayBeforeStartup", NULL, "%.0f", 0, NULL },
492 	{ "load.off", 0, 0, "PowercomUPS.PowercomPowerConverter.PowercomDelayBeforeShutdown", NULL, "0", HU_TYPE_CMD, NULL },
493 
494 	/* end of structure. */
495 	{ NULL, 0, 0, NULL, NULL, NULL, 0, NULL }
496 };
497 
powercom_format_model(HIDDevice_t * hd)498 static const char *powercom_format_model(HIDDevice_t *hd) {
499 	return hd->Product;
500 }
501 
powercom_format_mfr(HIDDevice_t * hd)502 static const char *powercom_format_mfr(HIDDevice_t *hd) {
503 	return hd->Vendor ? hd->Vendor : "PowerCOM";
504 }
505 
powercom_format_serial(HIDDevice_t * hd)506 static const char *powercom_format_serial(HIDDevice_t *hd) {
507 	return hd->Serial;
508 }
509 
510 /* this function allows the subdriver to "claim" a device: return 1 if
511  * the device is supported by this subdriver, else 0. */
powercom_claim(HIDDevice_t * hd)512 static int powercom_claim(HIDDevice_t *hd)
513 {
514 	int status = is_usb_device_supported(powercom_usb_device_table, hd);
515 
516 	switch (status)
517 	{
518 	case POSSIBLY_SUPPORTED:
519 		if (hd->ProductID == 0x0002) {
520 			upsdebugx(0,
521 				"This Powercom device (%04x/%04x) is not supported by usbhid-ups.\n"
522 				"Please use the 'powercom' driver instead.\n", hd->VendorID, hd->ProductID);
523 			return 0;
524 		}
525 		/* by default, reject, unless the productid option is given */
526 		if (getval("productid")) {
527 			return 1;
528 		}
529 		possibly_supported("PowerCOM", hd);
530 		return 0;
531 
532 	case SUPPORTED:
533 		if (hd->ProductID == 0x0001) {
534 			interrupt_only = 1;
535 			interrupt_size = 8;
536 		}
537 		return 1;
538 
539 	case NOT_SUPPORTED:
540 	default:
541 		return 0;
542 	}
543 }
544 
545 subdriver_t powercom_subdriver = {
546 	POWERCOM_HID_VERSION,
547 	powercom_claim,
548 	powercom_utab,
549 	powercom_hid2nut,
550 	powercom_format_model,
551 	powercom_format_mfr,
552 	powercom_format_serial,
553 };
554