1
/* energizerups.c - model specific routines for Energizer USB units
3
Copyright (C) 2003 Viktor T. Toth <vttoth@vttoth.com>
5
Based in part on the fentonups.c driver by Russell Kroll:
7
Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
9
This program is free software; you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation; either version 2 of the License, or
12
(at your option) any later version.
14
This program is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
GNU General Public License for more details.
19
You should have received a copy of the GNU General Public License
20
along with this program; if not, write to the Free Software
21
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
#include <asm/types.h>
25
#include <sys/ioctl.h>
26
#include <linux/hiddev.h>
30
#include <sys/select.h>
34
#define DRV_VERSION "0.01"
38
/* Response to I identification queries is in the following format:
40
* 012345678901234567890123456789012345678
41
* #Energizer ER-HMOF600 A0 ^M
42
* #Energizer ER-OF800 A0 ^M
44
#define MANUFR (buf+1)
45
#define MDLNUM (buf+17)
46
#define HWVERS (buf+28)
48
/* Response to Q1 queries is in the following format:
50
* 01234567890123456789012345678901234567890123456
51
* (118.6 118.6 118.6 020 60.0 13.8 32.0 00001000^M
53
#define INVOLT (buf+1)
54
#define OUTVOLT (buf+13)
55
#define LOADPCT (buf+19)
56
#define ACFREQ (buf+23)
57
#define BATVOLT (buf+28)
58
#define UPSTEMP (buf+33)
59
#define STATUS (buf+38)
61
#define OBFLAG (STATUS[0]=='1'||STATUS[5]=='1')
62
#define LBFLAG (STATUS[1]=='1')
63
#define BYPASS (STATUS[2]=='1')
64
#define BEEPER (STATUS[7]=='1')
66
/* A calibration test of the ER-HMOF600 shows its maximum voltage at
67
* 14.0V during charging, falling down to 13.6 if the UPS is turned off
68
* for three minutes (but connected to line power) and to 12.8V
69
* immediately after starting a test with a 60W load. The 'low battery'
70
* warning came at 11.0V, and a discharge time to LB with a 60W light
71
* bulb took 35 minutes. The UPS ran another 5 minutes on LB before
72
* shutting down, at which time the battery voltage was 10.1V. When
73
* charging resumed at the moment the LB warning came on, the battery
76
* With my ER-OF800, the maximum voltage is 13.8V. The difference is
77
* probably within circuit tolerance and not due to a substantial design
78
* difference between the units.
80
* For now, these are the only two units I have. Both are 115VAC units
81
* with a 12V internal battery and an identical software interface. The
82
* characteristic AC voltage values here are reasonable guesses based on
83
* the values of other similar UPSs.
86
#define LOWVOLT (OBFLAG?11.0:12.0)
93
#define hidsend(x) hidcmd(x,NULL,0)
97
* Encode and send a string to the UPS. A trailing <cr> (0x0D) is appended.
99
* Commands are encoded in 8-byte reports, each consisting of 64 settable
100
* bits. The last report is padded with null bits, just to be safe and not
101
* send the UPS garbage by accident.
104
void sendstring(int fd, char *psz)
106
struct hiddev_report_info rinfo;
107
struct hiddev_usage_ref uref;
111
uref.usage_index = 0;
116
c = (*psz) ? *psz :0x0D;
117
for (j = 0; j < 8; j++)
119
uref.report_type = HID_REPORT_TYPE_OUTPUT;
121
uref.field_index = 0;
122
uref.usage_code = 0x90001 + uref.usage_index;
123
uref.value = (c & 1);
124
if (ioctl(fd, HIDIOCSUSAGE, &uref) < 0)
125
fatalx("Error (SUSAGE) while talking to the UPS");
129
if (*psz == 0 || ++i == 8)
132
while (uref.usage_index++ < 63)
134
uref.report_type = HID_REPORT_TYPE_OUTPUT;
136
uref.field_index = 0;
137
uref.usage_code = 0x90001 + uref.usage_index;
139
if (ioctl(fd, HIDIOCSUSAGE, &uref) < 0)
140
fatalx("Error (SUSAGE) while talking to the UPS");
142
uref.usage_index = 0;
144
rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
146
rinfo.num_fields = 1;
147
if (ioctl(fd, HIDIOCSREPORT, &rinfo) < 0)
148
fatalx("Error (SREPORT) while talking to the UPS");
154
* Send a command to the UPS and (optionally) receive a reply. The parameter
155
* pRsp can be zero, in which case no reply is expected.
157
* A new file descriptor is opened as experience has shown that a previously
158
* opened descriptor cannot be reused reliably for commanding.
160
* Data is received in the form of an 8-byte buffer. After the initial report
161
* has been read, HID events are received indicating bit changes in the 8-byte
162
* buffer. When the bit counter rolls over, we know we have received a full
163
* buffer. This doesn't seem to be a 100% reliable method, since it'd not tell
164
* us, for instance, if the same 8 bytes are sent twice in a row, but in
165
* practice, it does appear to reliably transmit all UPS response messages.
168
int hidcmd(unsigned char *pCmd, unsigned char *pRsp, int l)
170
int fd, rd, i, j, k, hid;
173
struct hiddev_event ev[NUM_EVTS];
174
struct hiddev_usage_ref uref;
175
unsigned char data[8];
177
if ((fd = open(device_path, O_RDWR)) < 0)
178
fatalx("Cannot communicate with UPS at %s", device_path);
183
memset(data, 0, sizeof(data));
184
for (i = 0; i < 64; i++)
186
uref.report_type = HID_REPORT_TYPE_INPUT;
188
uref.field_index = 0;
189
uref.usage_index = i;
190
ioctl(fd, HIDIOCGUCODE, &uref);
191
ioctl(fd, HIDIOCGUSAGE, &uref);
193
if (uref.value) data[i >> 3] |= 1 << (i & 7);
196
sendstring(fd, pCmd);
207
if (select(fd+1, &rdfs, 0, 0, &tv) <= 0) break;
208
rd = read(fd, ev, sizeof(ev));
209
if (rd < (int) sizeof(ev[0]))
210
fatalx("Communication failure with UPS");
212
for (i = 0; i < rd / sizeof(ev[0]); i++)
214
if (hid >= 0 && ev[i].hid <= hid)
216
for (j = 0; j < 8; j++)
220
if (k > 0 || data[j] != 0xFF) pRsp[k++] = data[j];
225
j = (ev[i].hid - 1) & 0x3F;
226
if (ev[i].value) data[j >> 3] |= 1 << (j & 7);
227
else data[j >> 3] &= ~(1 << (j & 7));
231
if (hid >= 0) for (j = 0; j < ((hid - 1) & 0x3F) >> 3; j++)
235
if (k > 0 || data[j] != 0xFF) pRsp[k++] = data[j];
246
int instcmd(const char *cmdname, const char *extra)
248
if (!strcasecmp(cmdname, "test.battery.start"))
251
upslogx(LOG_NOTICE, "UPS test start");
252
return STAT_INSTCMD_HANDLED;
255
if (!strcasecmp(cmdname, "test.battery.stop"))
258
upslogx(LOG_NOTICE, "UPS test stop");
259
return STAT_INSTCMD_HANDLED;
262
if (!strcasecmp(cmdname, "beeper.on") ||
263
!strcasecmp(cmdname, "beeper.off"))
268
/* Find out beeper status and send appropriate command. */
270
r = hidcmd("Q1", buf, sizeof(buf));
271
if (r < 46 || r > 47 || buf[0] != '(')
272
r = hidcmd("Q1", buf, sizeof(buf));
273
if (r >= 46 && r <= 47 && buf[0] == '(')
275
if (OBFLAG && !LBFLAG) /* Otherwise there's not much we can do */
277
if ((BEEPER && !strcasecmp(cmdname, "beeper.off")) ||
278
(!BEEPER && !strcasecmp(cmdname, "beeper.on")))
280
return STAT_INSTCMD_HANDLED;
283
return STAT_INSTCMD_HANDLED; /* FUTURE: failure */
286
upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
287
return STAT_INSTCMD_UNKNOWN;
291
* Some trivial initialization. Let's detect the UPS.
294
void upsdrv_initinfo(void)
299
/* Yes, sometimes it took as many as 6-7 tries after the UPS has just
300
* been turned on or after a communications problem.
302
for (i = 0; i < 10; i++)
304
r = hidcmd("I", buf, sizeof(buf));
305
if (r == 39 && buf[0] == '#') break;
308
if (r != 39 || buf[0] != '#') fatalx("No Energizer UPS detected");
309
buf[16]=buf[27]=buf[38] = '\0';
314
dstate_setinfo("driver.version.internal", "%s", DRV_VERSION);
315
dstate_setinfo("ups.mfr", MANUFR);
316
dstate_setinfo("ups.model", MDLNUM);
318
dstate_setinfo("input.transfer.low", "%d", LOXFER);
319
dstate_setinfo("input.transfer.high", "%d", HIXFER);
323
/* now add instant command support info */
324
dstate_addcmd("test.battery.start");
325
dstate_addcmd("test.battery.stop");
326
dstate_addcmd("beeper.on");
327
dstate_addcmd("beeper.off");
328
upsh.new_instcmd = instcmd;
332
* Query the UPS and update values.
335
void upsdrv_updateinfo(void)
339
static int f; /* To prevent excessive logging */
343
r = hidcmd("Q1", buf, sizeof(buf));
344
if (r < 46 || r > 47)
347
upslogx(LOG_ERR, "Invalid response length from UPS [%s]", buf);
354
upslogx(LOG_ERR, "Invalid response data from UPS [%s]", buf);
360
buf[6]=buf[12]=buf[18]=buf[22]=buf[27]=buf[32]=buf[37]=buf[46] = '\0';
362
dstate_setinfo("input.voltage", "%s", INVOLT);
363
dstate_setinfo("output.voltage", "%s", OUTVOLT);
364
dstate_setinfo("battery.voltage", "%s", BATVOLT);
366
v = ((atof(BATVOLT) - LOWVOLT) / VOLTRNG) * 100.0;
367
if (v > 100.0) v = 100.0;
368
if (v < 0.0) v = 0.0;
369
dstate_setinfo("battery.charge", "%02.1f", v);
372
if (OBFLAG) status_set("OB"); /* on battery */
375
status_set("OL"); /* on line */
376
/* only allow these when OL since they're bogus when OB */
377
if (BYPASS) /* boost or trim in effect */
379
if (atoi(INVOLT) < LONORM) status_set("BOOST");
380
else if (atoi(INVOLT) > HINORM) status_set("TRIM");
383
if (LBFLAG) status_set("LB"); /* low battery */
387
dstate_setinfo("ups.temperature", "%s", UPSTEMP);
388
dstate_setinfo("input.frequency", "%s", ACFREQ);
389
dstate_setinfo("ups.load", "%s", LOADPCT);
393
void upsdrv_shutdown(void)
399
/* Basic idea: find out line status and send appropriate command.
400
* If the status query fails, we do not retry more than once (we
401
* may be short on time with a dying UPS) but assume that we're
405
r = hidcmd("Q1", buf, sizeof(buf));
406
if (r < 46 || r > 47 || buf[0] != '(') r = hidcmd("Q1", buf, sizeof(buf));
408
if (r >= 46 && r <= 47 && buf[0] == '(')
411
upslogx(LOG_WARNING, "On battery, sending shutdown command...");
415
upslogx(LOG_WARNING, "On line, sending shutdown+return command...");
418
else upslogx(LOG_WARNING, "Status undetermined, assuming battery power, "
419
"sending shutdown command...");
421
hidsend(b ? "S01" : "S01R0003");
425
void upsdrv_help(void)
429
/* list flags and values that you want to receive via -x */
430
void upsdrv_makevartable(void)
432
/* allow '-x xyzzy' */
433
/* addvar(VAR_FLAG, "xyzzy", "Enable xyzzy mode"); */
435
/* allow '-x foo=<some value>' */
436
/* addvar(VAR_VALUE, "foo", "Override foo setting"); */
439
void upsdrv_banner(void)
441
printf("Network UPS Tools - Energizer USB UPS driver %s (%s)\n\n",
442
DRV_VERSION, UPS_VERSION);
443
experimental_driver = 1; /* Causes a warning to be printed */
446
void upsdrv_initups(void)
448
/* No initialization needed */
451
void upsdrv_cleanup(void)
453
/* No cleanup needed */