~ubuntu-branches/ubuntu/saucy/nut/saucy

« back to all changes in this revision

Viewing changes to drivers/newapc.c

  • Committer: Bazaar Package Importer
  • Author(s): Arnaud Quette
  • Date: 2004-05-28 13:10:01 UTC
  • mto: (16.1.1 squeeze)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20040528131001-yj2m9qcez4ya2w14
Tags: upstream-1.4.2
ImportĀ upstreamĀ versionĀ 1.4.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
   newapc.c - model specific routines for APC smart protocol units
 
3
 
 
4
        $Id: newapc.c,v 1.2 2002/03/04 13:31:32 cvs Exp $
 
5
 
 
6
   Copyright (C) 1999  Russell Kroll <rkroll@exploits.org>
 
7
             (C) 2000  Nigel Metheringham <Nigel.Metheringham@Intechnology.co.uk>
 
8
 
 
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.
 
13
 
 
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.
 
18
 
 
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
 
22
*/
 
23
 
 
24
#define APC_DRIVER_VERSION      "1.99.1a"
 
25
 
 
26
#include "main.h"
 
27
#include "newapc.h"
 
28
 
 
29
#define ALT_CABLE_1 "940-0095B"
 
30
 
 
31
static struct apc_vartab_t *vartab_lookup_char(char cmdchar)
 
32
{
 
33
        int     i;
 
34
 
 
35
        for (i = 0; apc_vartab[i].name != NULL; i++)
 
36
                if (apc_vartab[i].cmd == cmdchar)
 
37
                        return &apc_vartab[i];
 
38
 
 
39
        return NULL;
 
40
}
 
41
 
 
42
static struct apc_vartab_t *vartab_lookup_name(const char *var)
 
43
{
 
44
        int     i;
 
45
 
 
46
        for (i = 0; apc_vartab[i].name != NULL; i++)
 
47
                if (!strcasecmp(apc_vartab[i].name, var))
 
48
                        return &apc_vartab[i];
 
49
 
 
50
        return NULL;
 
51
}
 
52
 
 
53
/* FUTURE: change to use function pointers */
 
54
 
 
55
/* convert APC formatting to NUT formatting */
 
56
static char *convert_data(struct apc_vartab_t *cmd_entry, char *upsval)
 
57
{
 
58
        static  char tmp[128];
 
59
        int     tval;
 
60
 
 
61
        switch(cmd_entry->flags & APC_FORMATMASK) {
 
62
                case APC_F_PERCENT:
 
63
                case APC_F_VOLT:
 
64
                case APC_F_AMP:
 
65
                case APC_F_CELSIUS:
 
66
                case APC_F_HEX:
 
67
                case APC_F_DEC:
 
68
                case APC_F_SECONDS:
 
69
                case APC_F_LEAVE:
 
70
 
 
71
                        /* no conversion for any of these */
 
72
                        return upsval;
 
73
 
 
74
                case APC_F_HOURS:
 
75
                        /* convert to seconds */
 
76
 
 
77
                        tval = 60 * 60 * strtol(upsval, NULL, 10);
 
78
 
 
79
                        snprintf(tmp, sizeof(tmp), "%d", tval);
 
80
                        return tmp;
 
81
 
 
82
                case APC_F_MINUTES:
 
83
                        /* Convert to seconds - NUT standard time measurement */
 
84
                        tval = 60 * strtol(upsval, NULL, 10);
 
85
                        /* Ignore errors - Theres not much we can do */
 
86
                        snprintf(tmp, sizeof(tmp), "%d", tval);
 
87
                        return tmp;
 
88
 
 
89
                default:
 
90
                        upslogx(LOG_NOTICE, "Unable to handle conversion of %s",
 
91
                                cmd_entry->name);
 
92
                        return upsval;
 
93
        }
 
94
 
 
95
        /* NOTREACHED */
 
96
        return upsval;
 
97
}
 
98
 
 
99
static void poll_data(struct apc_vartab_t *vt)
 
100
{
 
101
        int     ret;
 
102
        char    tmp[SMALLBUF];
 
103
 
 
104
        if ((vt->flags & APC_PRESENT) == 0)
 
105
                return;
 
106
 
 
107
        upsdebugx(4, "poll_data: %s", vt->name);
 
108
 
 
109
        upssendchar(vt->cmd);
 
110
 
 
111
        ret = upsrecv(tmp, sizeof(tmp), ENDCHAR, IGNCHARS);
 
112
 
 
113
        if (ret < 1) {                  /* comm failure */
 
114
                dstate_datastale();
 
115
                return;
 
116
        }
 
117
 
 
118
        /* no longer supported by the hardware somehow */
 
119
        if (!strcmp(tmp, "NA")) {
 
120
                dstate_delinfo(vt->name);
 
121
                return;
 
122
        }
 
123
 
 
124
        dstate_setinfo(vt->name, "%s", convert_data(vt, tmp));
 
125
        dstate_dataok();
 
126
}
 
127
 
 
128
/* check for support or just update a named variable */
 
129
static int query_ups(const char *var, int first)
 
130
{
 
131
        int     ret;
 
132
        char    temp[256], *ptr;
 
133
        struct  apc_vartab_t *vt;
 
134
 
 
135
        vt = vartab_lookup_name(var);
 
136
 
 
137
        if (!vt) {
 
138
                upsdebugx(1, "query_ups: unknown variable %s", var);
 
139
                return 0;
 
140
        }
 
141
 
 
142
        /* already known to not be supported? */
 
143
        if (vt->flags & APC_IGNORE)
 
144
                return 0;
 
145
 
 
146
        upsflushin(0, nut_debug_level, IGNCHARS);
 
147
 
 
148
        upssendchar(vt->cmd);
 
149
 
 
150
        if (first)
 
151
                flag_timeoutfailure = -1;
 
152
 
 
153
        ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
154
 
 
155
        if ((ret < 1) || (!strcmp(temp, "NA"))) {       /* not supported */
 
156
                vt->flags |= APC_IGNORE;
 
157
                return 0;
 
158
        }
 
159
 
 
160
        ptr = convert_data(vt, temp);
 
161
        dstate_setinfo(vt->name, "%s", ptr);
 
162
 
 
163
        return 1;       /* success */
 
164
}
 
165
 
 
166
static void do_capabilities(void)
 
167
{
 
168
        const   char    *ptr, *entptr;
 
169
        char    upsloc, temp[512], cmd, loc, etmp[16], *endtemp;
 
170
        int     nument, entlen, i, matrix, ret;
 
171
        struct  apc_vartab_t *vt;
 
172
 
 
173
        upsdebugx(1, "APC - About to get capabilities string");
 
174
        /* If we can do caps, then we need the Firmware revision which has
 
175
           the locale descriptor as the last character (ugh)
 
176
        */
 
177
        ptr = dstate_getinfo("ups.firmware");
 
178
        if (ptr)
 
179
                upsloc = ptr[strlen(ptr) - 1];
 
180
        else
 
181
                upsloc = 0;
 
182
 
 
183
        /* get capability string */
 
184
        upssendchar(APC_CAPABILITY);            /* ^Z */
 
185
 
 
186
        ret = upsrecv(temp, sizeof(temp), ENDCHAR, MINIGNCHARS);
 
187
 
 
188
        if ((ret < 1) || (!strcmp(temp, "NA"))) {
 
189
 
 
190
                /* Early Smart-UPS, not as smart as later ones */
 
191
                /* This should never happen since we only call
 
192
                   this if the REQ_CAPABILITIES command is supported
 
193
                */
 
194
                upslogx(LOG_ERR, "ERROR: APC cannot do capabilites but said it could!");
 
195
                return;
 
196
        }
 
197
 
 
198
        /* recv always puts a \0 at the end, so this is safe */
 
199
        /* however it assumes a zero byte cannot be embedded */
 
200
        endtemp = &temp[0] + strlen(temp);
 
201
 
 
202
        if (temp[0] != '#') {
 
203
                printf("Unrecognized capability start char %c\n", temp[0]);
 
204
                printf("Please report this error [%s]\n", temp);
 
205
                upslogx(LOG_ERR, "ERROR: unknown capability start char %c!", 
 
206
                        temp[0]);
 
207
 
 
208
                return;
 
209
        }
 
210
 
 
211
        if (temp[1] == '#') {           /* Matrix-UPS */
 
212
                matrix = 1;
 
213
                ptr = &temp[0];
 
214
        }
 
215
        else {
 
216
                ptr = &temp[1];
 
217
                matrix = 0;
 
218
        }
 
219
 
 
220
        /* command char, location, # of entries, entry length */
 
221
 
 
222
        while (ptr[0] != '\0') {
 
223
                if (matrix)
 
224
                        ptr += 2;       /* jump over repeating ## */
 
225
 
 
226
                /* check for idiocy */
 
227
                if (ptr >= endtemp) {
 
228
                        printf("Capability string has overflowed\n");
 
229
                        printf("Please report this error\n");
 
230
                        fatalx("ERROR: capability overflow!");
 
231
                }
 
232
 
 
233
                cmd = ptr[0];
 
234
                loc = ptr[1];
 
235
                nument = ptr[2] - 48;
 
236
                entlen = ptr[3] - 48;
 
237
                entptr = &ptr[4];
 
238
 
 
239
                vt = vartab_lookup_char(cmd);
 
240
 
 
241
                /* mark this as writable */
 
242
                if (vt && ((loc == upsloc) || (loc == '4'))) {
 
243
                        upsdebugx(1, "Supported capability: %02x (%c) - %s", 
 
244
                                cmd, loc, vt->name);
 
245
 
 
246
                        dstate_setflags(vt->name, ST_FLAG_RW);
 
247
 
 
248
                        /* make sure setvar knows what this is */
 
249
                        vt->flags |= APC_RW | APC_ENUM;
 
250
                }
 
251
 
 
252
                for (i = 0; i < nument; i++) {
 
253
                        snprintf(etmp, entlen + 1, "%s", entptr);
 
254
 
 
255
                        if (vt && ((loc == upsloc) || (loc == '4')))
 
256
                                dstate_addenum(vt->name, convert_data(vt, etmp));
 
257
 
 
258
                        entptr += entlen;
 
259
                }
 
260
 
 
261
                ptr = entptr;
 
262
        }
 
263
}
 
264
 
 
265
static void update_status(void)
 
266
{
 
267
        int     ret, tval;
 
268
        char    buf[SMALLBUF];
 
269
 
 
270
        upsflushin(0, nut_debug_level, IGNCHARS);
 
271
 
 
272
        upssendchar(APC_STATUS);
 
273
        ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
 
274
 
 
275
        if ((ret < 1) || (!strcmp(buf, "NA"))) {
 
276
                dstate_datastale();
 
277
                return;
 
278
        }
 
279
 
 
280
        tval = strtol(buf, 0, 16);
 
281
        tval &= 0xff;
 
282
 
 
283
        status_init();
 
284
        if (tval & 1)
 
285
                status_set("CAL");              /* calibration */
 
286
        if (tval & 2)
 
287
                status_set("TRIM");             /* SmartTrim */
 
288
        if (tval & 4)
 
289
                status_set("BOOST");            /* SmartBoost */
 
290
        if (tval & 8)
 
291
                status_set("OL");               /* on line */
 
292
        if (tval & 16)
 
293
                status_set("OB");               /* on battery */
 
294
        if (tval & 32)
 
295
                status_set("OVER");             /* overload */
 
296
        if (tval & 64)
 
297
                status_set("LB");               /* low battery */
 
298
        if (tval & 128)
 
299
                status_set("RB");               /* replace batt */
 
300
 
 
301
        if (!strcmp(buf, "00"))
 
302
                status_set("OFF");
 
303
 
 
304
        status_commit();
 
305
        dstate_dataok();
 
306
}
 
307
 
 
308
static void oldapcsetup(void)
 
309
{
 
310
        int     ret = 0;
 
311
 
 
312
        /* really old models ignore REQ_MODEL, so find them first */
 
313
        ret = query_ups("ups.model", 1);
 
314
 
 
315
        if (ret != 1) {
 
316
                /* force the model name */
 
317
                dstate_setinfo("ups.model", "Smart-UPS");
 
318
        }
 
319
 
 
320
        /* see if this might be an old Matrix-UPS instead */
 
321
        if (query_ups("output.current", 1))
 
322
                dstate_setinfo("ups.model", "Matrix-UPS");
 
323
 
 
324
        query_ups("ups.serial", 1);
 
325
        query_ups("input.voltage", 1); /* This one may fail... no problem */
 
326
 
 
327
        update_status();
 
328
 
 
329
        /* If we have come down this path then we dont do capabilities and
 
330
           other shiny features
 
331
        */
 
332
}
 
333
 
 
334
static void protocol_verify(unsigned char cmd)
 
335
{
 
336
        int     i, found;
 
337
 
 
338
        /* we might not care about this one */
 
339
        if (strchr(CMD_IGN_CHARS, cmd))
 
340
                return;
 
341
 
 
342
        /* see if it's a variable */
 
343
        for (i = 0; apc_vartab[i].name != NULL; i++) {
 
344
 
 
345
                /* 1:1 here, so the first match is the only match */
 
346
 
 
347
                if (apc_vartab[i].cmd == cmd) {
 
348
                        upsdebugx(3, "UPS supports variable [%s]",
 
349
                                apc_vartab[i].name);
 
350
 
 
351
                        /* load initial data */
 
352
                        apc_vartab[i].flags |= APC_PRESENT;
 
353
                        poll_data(&apc_vartab[i]);
 
354
 
 
355
                        /* handle special data for our two strings */
 
356
                        if (apc_vartab[i].flags & APC_STRING) {
 
357
                                dstate_setflags(apc_vartab[i].name, 
 
358
                                        ST_FLAG_RW | ST_FLAG_STRING);
 
359
                                dstate_setaux(apc_vartab[i].name, APC_STRLEN);
 
360
 
 
361
                                apc_vartab[i].flags |= APC_RW;
 
362
                        }
 
363
 
 
364
                        return;
 
365
                }
 
366
        }
 
367
 
 
368
        /* check the command list */
 
369
 
 
370
        /* some cmdchars map onto multiple commands (start and stop) */
 
371
 
 
372
        found = 0;
 
373
 
 
374
        for (i = 0; apc_cmdtab[i].name != NULL; i++) {
 
375
                if (apc_cmdtab[i].cmd == cmd) {
 
376
                        upsdebugx(2, "UPS supports command [%s]",
 
377
                                apc_cmdtab[i].name);
 
378
 
 
379
                        dstate_addcmd(apc_cmdtab[i].name);
 
380
 
 
381
                        apc_cmdtab[i].flags |= APC_PRESENT;
 
382
                        found = 1;
 
383
                }
 
384
        }
 
385
 
 
386
        if (found)
 
387
                return;
 
388
 
 
389
        if (isprint(cmd))
 
390
                upsdebugx(1, "protocol_verify: 0x%02x [%c] unrecognized", 
 
391
                        cmd, cmd);
 
392
        else
 
393
                upsdebugx(1, "protocol_verify: 0x%02x unrecognized", cmd);
 
394
}
 
395
 
 
396
static void getbaseinfo(void)
 
397
{
 
398
        int     i, ret = 0;
 
399
        char    *alrts, *cmds, temp[512];
 
400
 
 
401
        upsdebugx(1, "APC - Attempting to find command set");
 
402
        /* Initially we ask the UPS what commands it takes
 
403
           If this fails we are going to need an alternate
 
404
           strategy - we can deal with that if it happens
 
405
        */
 
406
 
 
407
        upssendchar(APC_CMDSET);
 
408
 
 
409
        /* disable timeout complaints temporarily */
 
410
        flag_timeoutfailure = -1;
 
411
        ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
412
        flag_timeoutfailure = 0;
 
413
 
 
414
        if ((ret < 1) || (!strcmp(temp, "NA"))) {
 
415
 
 
416
                /* We have an old dumb UPS - go to specific code for old stuff */
 
417
                oldapcsetup();
 
418
                return;
 
419
        }
 
420
 
 
421
        upsdebugx(1, "APC - Parsing out command set");
 
422
        /* We have the version.alert.cmdchars string
 
423
           NB the alert chars are normally in IGNCHARS
 
424
           so will have been pretty much edited out.
 
425
           You will need to change the upsrecv above if
 
426
           you want to check those out too....
 
427
        */
 
428
        alrts = strchr(temp, '.');
 
429
        if (alrts == NULL) {
 
430
                printf("Unable to split APC version string\n");
 
431
                printf("Bailing out\n");
 
432
                exit(1);
 
433
        }
 
434
        *alrts++ = 0;
 
435
 
 
436
        cmds = strchr(alrts, '.');
 
437
        if (cmds == NULL) {
 
438
                printf("Unable to find APC command string\n");
 
439
                printf("Bailing out\n");
 
440
                exit(1);
 
441
        }
 
442
        *cmds++ = 0;
 
443
 
 
444
        for (i = 0; i < strlen(cmds); i++)
 
445
                protocol_verify(cmds[i]);
 
446
 
 
447
        /* if capabilities are supported, add them here */
 
448
        if (strchr(cmds, APC_CAPABILITY))
 
449
                do_capabilities();
 
450
 
 
451
        upsdebugx(1, "APC - UPS capabilities determined");
 
452
}
 
453
 
 
454
/* TODO: roll this into upscommon ala bestups */
 
455
static void sendstring(char * string, int len, int delay)
 
456
{
 
457
        int     i;
 
458
 
 
459
        upsflushin(0, nut_debug_level, IGNCHARS);
 
460
        for (i=0; (i<len); i++) {
 
461
                upssendchar(string[i]);
 
462
                usleep(delay);
 
463
        }
 
464
}
 
465
 
 
466
/* check for calibration status and either start or stop */
 
467
static int do_cal(int start)
 
468
{
 
469
        char    temp[256];
 
470
        int     tval, ret;
 
471
 
 
472
        upssendchar(APC_STATUS);
 
473
        ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
474
 
 
475
        /* if we can't check the current calibration status, bail out */
 
476
        if ((ret < 1) || (!strcmp(temp, "NA")))
 
477
                return STAT_INSTCMD_HANDLED;            /* FUTURE: failure */
 
478
 
 
479
        tval = strtol(temp, 0, 16);
 
480
 
 
481
        if (tval & 1) {         /* calibration currently happening */
 
482
                if (start == 1) {
 
483
                        /* requested start while calibration still running */
 
484
                        upslogx(LOG_INFO, "Runtime calibration already in progress");
 
485
                        return STAT_INSTCMD_HANDLED;    /* FUTURE: failure */
 
486
                }
 
487
 
 
488
                /* stop requested */
 
489
 
 
490
                upslogx(LOG_INFO, "Stopping runtime calibration");
 
491
                upssendchar(APC_CMD_CALTOGGLE);
 
492
 
 
493
                ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
494
 
 
495
                if ((ret < 1) || (!strcmp(temp, "NA")) || (!strcmp(temp, "NO"))) {
 
496
                        upslogx(LOG_WARNING, "Stop calibration failed", temp);
 
497
                        return STAT_INSTCMD_HANDLED;    /* FUTURE: failure */
 
498
                }
 
499
 
 
500
                return STAT_INSTCMD_HANDLED;    /* FUTURE: success */
 
501
        }
 
502
 
 
503
        /* calibration not happening */
 
504
 
 
505
        if (start == 0) {               /* stop requested */
 
506
                upslogx(LOG_INFO, "Runtime calibration not occurring");
 
507
                return STAT_INSTCMD_HANDLED;            /* FUTURE: failure */
 
508
        }
 
509
 
 
510
        upslogx(LOG_INFO, "Starting runtime calibration");
 
511
        upssendchar(APC_CMD_CALTOGGLE);
 
512
 
 
513
        ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
514
 
 
515
        if ((ret < 1) || (!strcmp(temp, "NA")) || (!strcmp(temp, "NO"))) {
 
516
                upslogx(LOG_WARNING, "Start calibration failed", temp);
 
517
                return STAT_INSTCMD_HANDLED;    /* FUTURE: failure */
 
518
        }
 
519
 
 
520
        return STAT_INSTCMD_HANDLED;                    /* FUTURE: success */
 
521
}
 
522
 
 
523
/* get the UPS talking to us in smart mode */
 
524
static int smartmode(void)
 
525
{
 
526
        int     ret, tries;
 
527
        char    temp[256];
 
528
 
 
529
        for (tries = 0; tries < 5; tries++) {
 
530
 
 
531
                upssendchar(APC_GOSMART);
 
532
                ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
533
 
 
534
                if (ret > 0)
 
535
                        if (!strcmp(temp, "SM"))
 
536
                                return 1;       /* success */
 
537
 
 
538
                sleep(1);       /* wait before trying again */
 
539
 
 
540
                /* it failed, so try to bail out of menus on newer units */
 
541
 
 
542
                upssendchar(27);        /* ESC */
 
543
 
 
544
                /* eat the response (might be NA, might be something else) */
 
545
                upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
546
        }
 
547
 
 
548
        return 0;       /* failure */
 
549
}
 
550
 
 
551
/* power down the attached load immediately */
 
552
void upsdrv_shutdown(void)
 
553
{
 
554
        char    temp[32];
 
555
        int     ret, tval, sdtype = 0;
 
556
 
 
557
        if (!smartmode())
 
558
                printf("Detection failed.  Trying a shutdown command anyway.\n");
 
559
 
 
560
        /* check the line status */
 
561
 
 
562
        upssendchar(APC_STATUS);
 
563
        ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
564
 
 
565
        if (ret < 1) {
 
566
                printf("Status read failed!  Assuming you're on battery...\n");
 
567
                tval = 64 | 16;         /* LB + OB */
 
568
 
 
569
        } else {
 
570
                tval = strtol(temp, 0, 16);
 
571
        }
 
572
 
 
573
        if (testvar("sdtype"))
 
574
                sdtype = atoi(getval("sdtype"));
 
575
 
 
576
        switch (sdtype) {
 
577
        case 3:         /* shutdown with grace period */
 
578
                printf("Sending delayed power off command to UPS\n");
 
579
 
 
580
                upssendchar(APC_CMD_SHUTDOWN);
 
581
                usleep(1500000);
 
582
                upssendchar(APC_CMD_SHUTDOWN);
 
583
 
 
584
                break;
 
585
 
 
586
        case 2:         /* instant shutdown */
 
587
                printf("Sending power off command to UPS\n");
 
588
 
 
589
                upssendchar(APC_CMD_OFF);
 
590
                usleep(1500000);
 
591
                upssendchar(APC_CMD_OFF);
 
592
 
 
593
                break;
 
594
 
 
595
        case 1:
 
596
 
 
597
                /* Send a combined set of shutdown commands which can work better */
 
598
                /* if the UPS gets power during shutdown process */
 
599
                /* Specifically it sends both the soft shutdown 'S' */
 
600
                /* and the powerdown after grace period - '@000' commands */
 
601
                printf("UPS - currently %s - sending shutdown/powerdown\n",
 
602
                        (tval & 8) ? "on-line" : "on battery");
 
603
                sendstring("S@000", 5, 50000);
 
604
                break;
 
605
 
 
606
        default:
 
607
 
 
608
                /* @000 - shutdown after 'p' grace period             */
 
609
                /*      - returns after 000 minutes (i.e. right away) */
 
610
 
 
611
                /* S    - shutdown after 'p' grace period, only on battery */
 
612
                /*        returns after 'e' charge % plus 'r' seconds      */
 
613
 
 
614
                if (tval & 8) {                 /* on line */
 
615
                        printf("On line, sending shutdown+return command...\n");
 
616
                        sendstring("@000", 4, 50000);
 
617
                }
 
618
                else {
 
619
                        printf("On battery, sending normal shutdown command...\n");
 
620
                        upssendchar(APC_CMD_SOFTDOWN);
 
621
                }
 
622
        }
 
623
}
 
624
 
 
625
/* 940-0095B support: set DTR, lower RTS */
 
626
static void init_serial_0095B()
 
627
{
 
628
        int     dtr_bit = TIOCM_DTR;
 
629
        int     rts_bit = TIOCM_RTS;
 
630
 
 
631
        ioctl(upsfd, TIOCMBIS, &dtr_bit);
 
632
        ioctl(upsfd, TIOCMBIC, &rts_bit);
 
633
}
 
634
 
 
635
/* normal idle loop - keep up with the current state of the UPS */
 
636
/* If updateall is non-zero query all known UPS variables */
 
637
static void updateinfo(int updateall)
 
638
{
 
639
        int     i;
 
640
 
 
641
        /* try to wake up a dead ups once in awhile */
 
642
        if (flag_timeoutfailure == 1) {
 
643
                if (!smartmode()) {
 
644
                        upslogx(LOG_ERR, "Communication with UPS lost");
 
645
                        return;
 
646
                }
 
647
        }
 
648
 
 
649
        for (i = 0; apc_vartab[i].name != NULL; i++) {
 
650
 
 
651
                /* "all" mode - do everything regardless */
 
652
                if (updateall) {
 
653
                        poll_data(&apc_vartab[i]);
 
654
                        continue;
 
655
                }
 
656
 
 
657
                /* normal mode: check the reduced set */
 
658
                if (apc_vartab[i].flags & APC_POLL)
 
659
                        poll_data(&apc_vartab[i]);
 
660
        }
 
661
}
 
662
 
 
663
static int setvar_enum(struct apc_vartab_t *vt, const char *val)
 
664
{
 
665
        int     i, ret;
 
666
        char    orig[256], temp[256], *ptr;
 
667
 
 
668
        upssendchar(vt->cmd);
 
669
 
 
670
        ret = upsrecv(orig, sizeof(orig), ENDCHAR, IGNCHARS);
 
671
 
 
672
        if ((ret < 1) || (!strcmp(orig, "NA")))
 
673
                return STAT_SET_HANDLED;        /* FUTURE: failed */
 
674
 
 
675
        ptr = convert_data(vt, orig);
 
676
 
 
677
        /* suppress redundant changes - easier on the eeprom */
 
678
        if (!strcmp(ptr, val)) {
 
679
                upslogx(LOG_INFO, "Ignoring enum SET %s='%s' (unchanged value)",
 
680
                        vt->name, val);
 
681
 
 
682
                return STAT_SET_HANDLED;        /* FUTURE: no change */
 
683
        }
 
684
 
 
685
        for (i = 0; i < 6; i++) {
 
686
                upssendchar(APC_NEXTVAL);
 
687
 
 
688
                /* this should return either OK (if rotated) or NO (if not) */
 
689
                ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
690
 
 
691
                if ((ret < 1) || (!strcmp(temp, "NA")))
 
692
                        return STAT_SET_HANDLED;        /* FUTURE: failed */
 
693
 
 
694
                /* sanity checks */
 
695
                if (!strcmp(temp, "NO"))
 
696
                        return STAT_SET_HANDLED;        /* FUTURE: failed */
 
697
                if (strcmp(temp, "OK") != 0)
 
698
                        return STAT_SET_HANDLED;        /* FUTURE: failed */
 
699
 
 
700
                /* see what it rotated onto */
 
701
                upssendchar(vt->cmd);
 
702
                ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
703
 
 
704
                if ((ret < 1) || (!strcmp(temp, "NA")))
 
705
                        return STAT_SET_HANDLED;        /* FUTURE: failed */
 
706
 
 
707
                ptr = convert_data(vt, temp);
 
708
 
 
709
                upsdebugx(1, "Rotate value: got [%s], want [%s]",
 
710
                        ptr, val);
 
711
 
 
712
                if (!strcmp(ptr, val)) {        /* got it */
 
713
                        upslogx(LOG_INFO, "SET %s='%s'", vt->name, val);
 
714
 
 
715
                        /* refresh data from the hardware */
 
716
                        query_ups(vt->name, 0);
 
717
 
 
718
                        return STAT_SET_HANDLED;        /* FUTURE: success */
 
719
                }
 
720
 
 
721
                /* check for wraparound */
 
722
                if (!strcmp(ptr, orig)) {
 
723
                        upslogx(LOG_ERR, "setvar: variable %s wrapped",
 
724
                                vt->name);
 
725
 
 
726
                        return STAT_SET_HANDLED;        /* FUTURE: failed */
 
727
                }                       
 
728
        }
 
729
 
 
730
        upslogx(LOG_ERR, "setvar: gave up after 6 tries for %s",
 
731
                vt->name);
 
732
 
 
733
        /* refresh data from the hardware */
 
734
        query_ups(vt->name, 0);
 
735
 
 
736
        return STAT_SET_HANDLED;
 
737
}
 
738
 
 
739
static int setvar_string(struct apc_vartab_t *vt, const char *val)
 
740
{
 
741
        int     i, ret;
 
742
        char    temp[256];
 
743
 
 
744
        upsflushin(0, nut_debug_level, IGNCHARS);
 
745
 
 
746
        upssendchar(vt->cmd);
 
747
 
 
748
        ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
749
 
 
750
        if ((ret < 1) || (!strcmp(temp, "NA")))
 
751
                return STAT_SET_HANDLED;        /* FUTURE: failed */
 
752
 
 
753
        /* suppress redundant changes - easier on the eeprom */
 
754
        if (!strcmp(temp, val)) {
 
755
                upslogx(LOG_INFO, "Ignoring string SET %s='%s' (unchanged value)",
 
756
                        vt->name, val);
 
757
 
 
758
                return STAT_SET_HANDLED;        /* FUTURE: no change */
 
759
        }
 
760
 
 
761
        upssendchar(APC_NEXTVAL);
 
762
        usleep(50000);
 
763
 
 
764
        for (i = 0; i < strlen(val); i++) {
 
765
                upssendchar(val[i]);
 
766
                usleep(50000);
 
767
        }
 
768
 
 
769
        /* pad to 8 chars with CRs */
 
770
        for (i = strlen(val); i < APC_STRLEN; i++) {
 
771
                upssendchar(13);
 
772
                usleep(50000);
 
773
        }
 
774
 
 
775
        ret = upsrecv(temp, sizeof(temp), ENDCHAR, IGNCHARS);
 
776
 
 
777
        if (ret < 1) {
 
778
                upslogx(LOG_ERR, "setvar_string: short final read");
 
779
                return STAT_SET_HANDLED;        /* FUTURE: failed */
 
780
        }
 
781
 
 
782
        if (!strcmp(temp, "NO")) {
 
783
                upslogx(LOG_ERR, "setvar_string: got NO at final read");
 
784
                return STAT_SET_HANDLED;        /* FUTURE: failed */
 
785
        }
 
786
 
 
787
        /* refresh data from the hardware */
 
788
        query_ups(vt->name, 0);
 
789
 
 
790
        upslogx(LOG_INFO, "SET %s='%s'", vt->name, val);
 
791
 
 
792
        return STAT_SET_HANDLED;        /* FUTURE: failed */
 
793
}
 
794
 
 
795
int setvar(const char *varname, const char *val)
 
796
{
 
797
        struct  apc_vartab_t    *vt;
 
798
 
 
799
        vt = vartab_lookup_name(varname);
 
800
 
 
801
        if (!vt)
 
802
                return STAT_SET_UNKNOWN;
 
803
 
 
804
        if ((vt->flags & APC_RW) == 0) {
 
805
                upslogx(LOG_WARNING, "setvar: [%s] is not writable", varname);
 
806
                return STAT_SET_UNKNOWN;
 
807
        }
 
808
 
 
809
        if (vt->flags & APC_ENUM)
 
810
                return setvar_enum(vt, val);
 
811
 
 
812
        if (vt->flags & APC_STRING)
 
813
                return setvar_string(vt, val);
 
814
 
 
815
        upslogx(LOG_WARNING, "setvar: Unknown type for [%s]", varname);
 
816
        return STAT_SET_UNKNOWN;
 
817
}
 
818
 
 
819
/* actually send the instcmd's char to the ups */
 
820
static int do_cmd(struct apc_cmdtab_t *ct)
 
821
{
 
822
        int     ret;
 
823
        char    buf[SMALLBUF];
 
824
 
 
825
        upssendchar(ct->cmd);
 
826
 
 
827
        /* some commands have to be sent twice with a 1.5s gap */
 
828
        if (ct->flags & APC_REPEAT) {
 
829
                usleep(1500000);
 
830
                upssendchar(ct->cmd);
 
831
        }
 
832
 
 
833
        ret = upsrecv(buf, sizeof(buf), ENDCHAR, IGNCHARS);
 
834
 
 
835
        if (ret < 1)
 
836
                return STAT_INSTCMD_HANDLED;            /* FUTURE: failed */
 
837
 
 
838
        if (strcmp(buf, "OK") != 0) {
 
839
                upslogx(LOG_WARNING, "Got [%s] after command [%s]",
 
840
                        buf, ct->name);
 
841
 
 
842
                return STAT_INSTCMD_HANDLED;            /* FUTURE: failed */
 
843
        }
 
844
 
 
845
        upslogx(LOG_INFO, "Command: %s", ct->name);
 
846
        return STAT_INSTCMD_HANDLED;                    /* FUTURE: success */
 
847
}
 
848
 
 
849
/* some commands must be repeated in a window to execute */
 
850
static int instcmd_chktime(struct apc_cmdtab_t *ct)
 
851
{
 
852
        double  elapsed;
 
853
        time_t  now;
 
854
        static  time_t  last = 0;
 
855
 
 
856
        time(&now);
 
857
 
 
858
        elapsed = difftime(now, last);
 
859
        last = now;
 
860
 
 
861
        /* you have to hit this in a small window or it fails */
 
862
        if ((elapsed < MINCMDTIME) || (elapsed > MAXCMDTIME)) {
 
863
                upsdebugx(1, "instcmd_chktime: outside window for %s (%2.0f)",
 
864
                                ct->name, elapsed);
 
865
                return STAT_INSTCMD_HANDLED;            /* FUTURE: again */
 
866
        }
 
867
 
 
868
        return do_cmd(ct);
 
869
}
 
870
 
 
871
int instcmd(const char *cmdname, const char *extra)
 
872
{
 
873
        int     i;
 
874
        struct  apc_cmdtab_t    *ct;
 
875
 
 
876
        ct = NULL;
 
877
 
 
878
        for (i = 0; apc_cmdtab[i].name != NULL; i++)
 
879
                if (!strcasecmp(apc_cmdtab[i].name, cmdname))
 
880
                        ct = &apc_cmdtab[i];
 
881
 
 
882
        if (!ct) {
 
883
                upslogx(LOG_WARNING, "instcmd: unknown command [%s]", cmdname);
 
884
                return STAT_INSTCMD_UNKNOWN;
 
885
        }
 
886
 
 
887
        if ((ct->flags & APC_PRESENT) == 0) {
 
888
                upslogx(LOG_WARNING, "instcmd: command [%s] is not supported",
 
889
                        cmdname);
 
890
                return STAT_INSTCMD_UNKNOWN;
 
891
        }
 
892
 
 
893
        if (!strcasecmp(cmdname, "calibrate.start"))
 
894
                return do_cal(1);
 
895
 
 
896
        if (!strcasecmp(cmdname, "calibrate.stop"))
 
897
                return do_cal(0);
 
898
 
 
899
        if (ct->flags & APC_NASTY)
 
900
                return instcmd_chktime(ct);
 
901
 
 
902
        /* nothing special here */
 
903
        return do_cmd(ct);
 
904
}       
 
905
 
 
906
/* install pointers to functions for msg handlers called from msgparse */
 
907
static void setuphandlers(void)
 
908
{
 
909
        upsh.new_setvar = setvar;
 
910
        upsh.new_instcmd = instcmd;
 
911
}
 
912
 
 
913
/* functions that interface with main.c */
 
914
 
 
915
void upsdrv_banner(void)
 
916
{
 
917
        printf("Network UPS Tools (version %s) - APC Smart protocol driver\n",
 
918
                UPS_VERSION);
 
919
        printf("\tDriver version %s, command table %s\n",
 
920
                APC_DRIVER_VERSION,
 
921
                APC_TABLE_VERSION);
 
922
}
 
923
 
 
924
void upsdrv_makevartable(void)
 
925
{
 
926
        addvar(VAR_VALUE, "cable", "Specify alternate cable (940-0095B)");
 
927
        addvar(VAR_VALUE, "sdtype", "Specify shutdown type (1-3)");
 
928
}
 
929
 
 
930
void upsdrv_initups(void)
 
931
{
 
932
        char    *cable;
 
933
 
 
934
        open_serial(device_path, B2400);
 
935
 
 
936
        cable = getval("cable");
 
937
 
 
938
        if (cable)
 
939
                if (!strcasecmp(cable, ALT_CABLE_1))
 
940
                        init_serial_0095B();
 
941
}
 
942
 
 
943
void upsdrv_help(void)
 
944
{
 
945
        printf("\nShutdown types:\n");
 
946
        printf("  0: soft shutdown or powerdown, depending on battery status\n");
 
947
        printf("  1: soft shutdown followed by powerdown\n");
 
948
        printf("  2: instant power off\n");
 
949
        printf("  3: power off with grace period\n");
 
950
        printf("Modes 0-1 will make the UPS come back when power returns\n");
 
951
        printf("Modes 2-3 will make the UPS stay turned off when power returns\n");
 
952
}
 
953
 
 
954
void upsdrv_initinfo(void)
 
955
{
 
956
        if (!smartmode()) {
 
957
                printf("Unable to detect an APC Smart protocol UPS on port %s\n", 
 
958
                        device_path);
 
959
                printf("Check the cabling, port name or model name and try again\n");
 
960
                exit(1);
 
961
        }
 
962
 
 
963
        /* manufacturer ID - hardcoded in this particular module */
 
964
        dstate_setinfo("ups.mfr", "APC");
 
965
 
 
966
        dstate_setinfo("driver.version.internal", "%s", APC_DRIVER_VERSION);
 
967
 
 
968
        getbaseinfo();
 
969
 
 
970
        printf("Detected %s [%s] on %s\n", dstate_getinfo("ups.model"),
 
971
                dstate_getinfo("ups.serial"), device_path);
 
972
 
 
973
        setuphandlers();
 
974
}
 
975
 
 
976
void upsdrv_updateinfo(void)
 
977
{
 
978
        static  time_t  last_full = 0;
 
979
        time_t  now;
 
980
 
 
981
        update_status();
 
982
 
 
983
        time(&now);
 
984
 
 
985
        /* refresh all variables hourly */
 
986
        /* does not catch measure-ups II insertion/removal */
 
987
        if (difftime(now, last_full) > 3600) {
 
988
                updateinfo(1);
 
989
                last_full = now;
 
990
                return;
 
991
        }
 
992
 
 
993
        updateinfo(0);
 
994
}
 
995
 
 
996
/*
 
997
 * Local variables:
 
998
 *  c-indent-level: 8
 
999
 *  c-basic-offset: 8
 
1000
 * End:
 
1001
 */
 
1002
 
 
1003
void upsdrv_cleanup(void)
 
1004
{
 
1005
}