1 /*
2  * apceeprom.c
3  *
4  * Do APC EEPROM changes.
5  */
6 
7 /*
8  * Copyright (C) 2000-2004 Kern Sibbald
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of version 2 of the GNU General
12  * Public License as published by the Free Software Foundation.
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 GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public
20  * License along with this program; if not, write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22  * MA 02110-1335, USA.
23  */
24 
25 #include "apc.h"
26 #include "apcsmart.h"
27 
28 
29 /*********************************************************************/
program_eeprom(int command,const char * data)30 bool ApcSmartUpsDriver::program_eeprom(int command, const char *data)
31 {
32    char setting[20];
33 
34    setup();
35    get_capabilities();
36 
37    switch (command) {
38    case CI_BATTDAT:               /* change battery date */
39       if (_ups->UPS_Cap[CI_BATTDAT]) {
40          printf("Attempting to update UPS battery date ...\n");
41          change_ups_battery_date(data);
42       } else {
43          printf("UPS battery date configuration not supported by this UPS.\n");
44          return 0;
45       }
46       break;
47 
48    case CI_IDEN:
49       if (_ups->UPS_Cap[CI_IDEN]) {
50          printf("Attempting to rename UPS ...\n");
51          change_ups_name(data);
52       } else {
53          printf("UPS name configuration not supported by this UPS.\n");
54          return 0;
55       }
56       break;
57 
58       /* SENSITIVITY */
59    case CI_SENS:
60       if (_ups->UPS_Cap[CI_SENS]) {
61          asnprintf(setting, sizeof(setting), "%.1s", data);
62          change_ups_eeprom_item("sensitivity", _ups->UPS_Cmd[CI_SENS], setting);
63       } else {
64          printf("UPS sensitivity configuration not supported by this UPS.\n");
65          return 0;
66       }
67       break;
68 
69       /* ALARM_STATUS */
70    case CI_DALARM:
71       if (_ups->UPS_Cap[CI_DALARM]) {
72          asnprintf(setting, sizeof(setting), "%.1s", data);
73          change_ups_eeprom_item("alarm status", _ups->UPS_Cmd[CI_DALARM],
74             setting);
75       } else {
76          printf("UPS alarm status configuration not supported by this UPS.\n");
77          return 0;
78       }
79       break;
80 
81       /* LOWBATT_SHUTDOWN_LEVEL */
82    case CI_DLBATT:
83       if (_ups->UPS_Cap[CI_DLBATT]) {
84          asnprintf(setting, sizeof(setting), "%02d", (int)atoi(data));
85          change_ups_eeprom_item("low battery warning delay",
86             _ups->UPS_Cmd[CI_DLBATT], setting);
87       } else {
88          printf(
89             "UPS low battery warning configuration not supported by this UPS.\n");
90          return 0;
91       }
92       break;
93 
94       /* WAKEUP_DELAY */
95    case CI_DWAKE:
96       if (_ups->UPS_Cap[CI_DWAKE]) {
97          asnprintf(setting, sizeof(setting), "%03d", (int)atoi(data));
98          change_ups_eeprom_item("wakeup delay", _ups->UPS_Cmd[CI_DWAKE],
99             setting);
100       } else {
101          printf("UPS wakeup delay configuration not supported by this UPS.\n");
102          return 0;
103       }
104       break;
105 
106 
107       /* SLEEP_DELAY */
108    case CI_DSHUTD:
109       if (_ups->UPS_Cap[CI_DSHUTD]) {
110          asnprintf(setting, sizeof(setting), "%03d", (int)atoi(data));
111          change_ups_eeprom_item("shutdown delay", _ups->UPS_Cmd[CI_DSHUTD],
112             setting);
113       } else {
114          printf("UPS shutdown delay configuration not supported by this UPS.\n");
115          return 0;
116       }
117       break;
118 
119       /* LOW_TRANSFER_LEVEL */
120    case CI_LTRANS:
121       if (_ups->UPS_Cap[CI_LTRANS]) {
122          asnprintf(setting, sizeof(setting), "%03d", (int)atoi(data));
123          change_ups_eeprom_item("lower transfer voltage",
124             _ups->UPS_Cmd[CI_LTRANS], setting);
125       } else {
126          printf(
127             "UPS low transfer voltage configuration not supported by this UPS.\n");
128          return 0;
129       }
130       break;
131 
132       /* HIGH_TRANSFER_LEVEL */
133    case CI_HTRANS:
134       if (_ups->UPS_Cap[CI_HTRANS]) {
135          asnprintf(setting, sizeof(setting), "%03d", (int)atoi(data));
136          change_ups_eeprom_item("high transfer voltage",
137             _ups->UPS_Cmd[CI_HTRANS], setting);
138       } else {
139          printf(
140             "UPS high transfer voltage configuration not supported by this UPS.\n");
141          return 0;
142       }
143       break;
144 
145       /* UPS_BATT_CAP_RETURN */
146    case CI_RETPCT:
147       if (_ups->UPS_Cap[CI_RETPCT]) {
148          asnprintf(setting, sizeof(setting), "%02d", (int)atoi(data));
149          change_ups_eeprom_item("return threshold percent",
150             _ups->UPS_Cmd[CI_RETPCT], setting);
151       } else {
152          printf(
153             "UPS return threshold configuration not supported by this UPS.\n");
154          return 0;
155       }
156       break;
157 
158       /* UPS_SELFTEST */
159    case CI_STESTI:
160       if (_ups->UPS_Cap[CI_STESTI]) {
161          asnprintf(setting, sizeof(setting), "%.3s", data);
162          /* Make sure "ON" is 3 characters */
163          if (setting[2] == 0) {
164             setting[2] = ' ';
165             setting[3] = 0;
166          }
167          change_ups_eeprom_item("self test interval", _ups->UPS_Cmd[CI_STESTI],
168             setting);
169       } else {
170          printf(
171             "UPS self test interval configuration not supported by this UPS.\n");
172          return 0;
173       }
174       break;
175 
176       /* OUTPUT_VOLTAGE */
177    case CI_NOMOUTV:
178       if (_ups->UPS_Cap[CI_NOMOUTV]) {
179          asnprintf(setting, sizeof(setting), "%03d", (int)atoi(data));
180          change_ups_eeprom_item("output voltage on batteries",
181             _ups->UPS_Cmd[CI_NOMOUTV], setting);
182       } else {
183          printf(
184             "UPS output voltage on batteries configuration not supported by this UPS.\n");
185          return 0;
186       }
187       break;
188 
189 
190    case -1:                       /* old style from .conf file */
191 
192       printf("Attempting to configure UPS ...\n");
193       change_extended();        /* set new values in UPS */
194 
195       printf("\nReading updated UPS configuration ...\n\n");
196       read_volatile_data();
197       read_static_data();
198 
199       /* Print report of status */
200       output_status(_ups, 0, stat_open, stat_print, stat_close);
201       break;
202 
203    default:
204       printf("Ignoring unknown config request command=%d\n", command);
205       return 0;
206       break;
207    }
208 
209    return 1;
210 }
211 
212 /*********************************************************************/
change_ups_name(const char * newname)213 void ApcSmartUpsDriver::change_ups_name(const char *newname)
214 {
215    char *n;
216    char response[32];
217    char name[10];
218    char a = _ups->UPS_Cmd[CI_CYCLE_EPROM];
219    char c = _ups->UPS_Cmd[CI_IDEN];
220    int i;
221    int j = strlen(newname);
222 
223    name[0] = '\0';
224 
225    if (j == 0) {
226       fprintf(stderr, "Error, new name of zero length.\n");
227       return;
228    } else if (j > 8) {
229       j = 8;                       /* maximum size */
230    }
231 
232    strncpy(name, newname, 9);
233 
234    /* blank fill to 8 chars */
235    while (j < 8) {
236       name[j] = ' ';
237       j++;
238    }
239 
240    /* Ask for name */
241    write(_ups->fd, &c, 1);          /* c = 'c' */
242    getline(response, sizeof(response));
243    fprintf(stderr, "The old UPS name is: %s\n", response);
244 
245    /* Tell UPS we will change name */
246    write(_ups->fd, &a, 1);          /* a = '-' */
247    sleep(1);
248 
249    n = name;
250    for (i = 0; i < 8; i++) {
251       write(_ups->fd, n++, 1);
252       sleep(1);
253    }
254 
255    /* Expect OK after successful name change */
256    *response = 0;
257    getline(response, sizeof(response));
258    if (strcmp(response, "OK") != 0) {
259       fprintf(stderr, "\nError changing UPS name\n");
260    }
261 
262    _ups->upsname[0] = '\0';
263    smart_poll(_ups->UPS_Cmd[CI_IDEN]);
264    strlcpy(_ups->upsname, smart_poll(_ups->UPS_Cmd[CI_IDEN]),
265       sizeof(_ups->upsname));
266 
267    fprintf(stderr, "The new UPS name is: %s\n", _ups->upsname);
268 }
269 
270 /*
271  * Update date battery replaced
272  */
change_ups_battery_date(const char * newdate)273 void ApcSmartUpsDriver::change_ups_battery_date(const char *newdate)
274 {
275    char *n;
276    char response[32];
277    char battdat[9];
278    char a = _ups->UPS_Cmd[CI_CYCLE_EPROM];
279    char c = _ups->UPS_Cmd[CI_BATTDAT];
280    int i;
281    int j = strlen(newdate);
282 
283    battdat[0] = '\0';
284 
285    if (j != 8) {
286       fprintf(stderr, "Error, new battery date must be 8 characters long.\n");
287       return;
288    }
289 
290    strlcpy(battdat, newdate, sizeof(battdat));
291 
292    /* Ask for battdat */
293    write(_ups->fd, &c, 1);          /* c = 'x' */
294    getline(response, sizeof(response));
295    fprintf(stderr, "The old UPS battery date is: %s\n", response);
296 
297    /* Tell UPS we will change battdat */
298    write(_ups->fd, &a, 1);          /* a = '-' */
299    sleep(1);
300 
301    n = battdat;
302    for (i = 0; i < 8; i++) {
303       write(_ups->fd, n++, 1);
304       sleep(1);
305    }
306 
307    /* Expect OK after successful battdat change */
308    *response = 0;
309    getline(response, sizeof(response));
310    if (strcmp(response, "OK") != 0) {
311       fprintf(stderr, "\nError changing UPS battery date\n");
312    }
313 
314    _ups->battdat[0] = '\0';
315    smart_poll(_ups->UPS_Cmd[CI_BATTDAT]);
316    strlcpy(_ups->battdat, smart_poll(_ups->UPS_Cmd[CI_BATTDAT]),
317       sizeof(_ups->battdat));
318 
319    fprintf(stderr, "The new UPS battery date is: %s\n", _ups->battdat);
320 }
321 
322 /*********************************************************************/
change_ups_eeprom_item(const char * title,const char cmd,const char * setting)323 int ApcSmartUpsDriver::change_ups_eeprom_item(const char *title, const char cmd,
324    const char *setting)
325 {
326    char response[32];
327    char response1[32];
328    char oldvalue[32];
329    char lastvalue[32];
330    char allvalues[256];
331    char a = _ups->UPS_Cmd[CI_CYCLE_EPROM];
332    int i;
333 
334    /* Ask for old value */
335    write(_ups->fd, &cmd, 1);
336    if (getline(oldvalue, sizeof(oldvalue)) == FAILURE) {
337       fprintf(stderr, "Could not get old value of %s.\n", title);
338       return FAILURE;
339    }
340 
341    if (strcmp(oldvalue, setting) == 0) {
342       fprintf(stderr, "The UPS %s remains unchanged as: %s\n", title, oldvalue);
343       return SUCCESS;
344    }
345 
346    fprintf(stderr, "The old UPS %s is: %s\n", title, oldvalue);
347    strlcpy(allvalues, oldvalue, sizeof(allvalues));
348    strlcat(allvalues, " ", sizeof(allvalues));
349    strlcpy(lastvalue, oldvalue, sizeof(lastvalue));
350 
351    /* Try a second time to ensure that it is a stable value */
352    write(_ups->fd, &cmd, 1);
353    *response = 0;
354    getline(response, sizeof(response));
355    if (strcmp(oldvalue, response) != 0) {
356       fprintf(stderr, "\nEEPROM value of %s is not stable\n", title);
357       return FAILURE;
358    }
359 
360    /*
361     * Just before entering this loop, the last command sent
362     * to the UPS MUST be to query the old value.
363     */
364    for (i = 0; i < 10; i++) {
365       write(_ups->fd, &cmd, 1);
366       getline(response1, sizeof(response1));
367 
368       /* Tell UPS to cycle to next value */
369       write(_ups->fd, &a, 1);       /* a = '-' */
370 
371       /* Expect OK after successful change */
372       *response = 0;
373       getline(response, sizeof(response));
374       if (strcmp(response, "OK") != 0) {
375          fprintf(stderr, "\nError changing UPS %s\n", title);
376          fprintf(stderr, "Got %s instead of OK\n\n", response);
377          sleep(10);
378          return FAILURE;
379       }
380 
381       /* get cycled value */
382       write(_ups->fd, &cmd, 1);
383       getline(response1, sizeof(response1));
384 
385       /* get cycled value again */
386       write(_ups->fd, &cmd, 1);
387       if (getline(response, sizeof(response)) == FAILURE ||
388          strcmp(response1, response) != 0) {
389          fprintf(stderr, "Error cycling values.\n");
390          getline(response, sizeof(response));      /* eat any garbage */
391          return FAILURE;
392       }
393       if (strcmp(setting, response) == 0) {
394          fprintf(stderr, "The new UPS %s is: %s\n", title, response);
395          sleep(10);                /* allow things to settle down */
396          return SUCCESS;
397       }
398 
399       /*
400        * Check if we cycled back to the same value, but permit
401        * a duplicate because the L for sensitivy appears
402        * twice in a row, i.e. H M L L.
403        */
404       if (strcmp(oldvalue, response) == 0 && i > 0)
405          break;
406       if (strcmp(lastvalue, response) != 0) {
407          strlcat(allvalues, response, sizeof(allvalues));
408          strlcat(allvalues, " ", sizeof(allvalues));
409          strlcpy(lastvalue, response, sizeof(lastvalue));
410       }
411       sleep(5);                    /* don't cycle too fast */
412    }
413 
414    fprintf(stderr, "Unable to change %s to: %s\n", title, setting);
415    fprintf(stderr, "Permitted values are: %s\n", allvalues);
416    getline(response, sizeof(response));    /* eat any garbage */
417 
418    return FAILURE;
419 }
420 
421 
422 /*
423  * Set new values in EEPROM memmory.  Change the UPS EEPROM.
424  */
change_extended()425 void ApcSmartUpsDriver::change_extended()
426 {
427    char setting[20];
428 
429    get_capabilities();
430 
431    /*
432     * Note, a value of -1 in the variable at the beginning
433     * means that the user did not put a configuration directive
434     * in /etc/apcctrl/apcctrl.conf. Consequently, if no value
435     * was given, we won't attept to change it.
436     */
437 
438    /* SENSITIVITY */
439    if (_ups->UPS_Cap[CI_SENS] && strcmp(_ups->sensitivity, "-1") != 0) {
440       asnprintf(setting, sizeof(setting), "%.1s", _ups->sensitivity);
441       change_ups_eeprom_item("sensitivity", _ups->UPS_Cmd[CI_SENS], setting);
442    }
443 
444    /* WAKEUP_DELAY */
445    if (_ups->UPS_Cap[CI_DWAKE] && _ups->dwake != -1) {
446       asnprintf(setting, sizeof(setting), "%03d", (int)_ups->dwake);
447       change_ups_eeprom_item("wakeup delay", _ups->UPS_Cmd[CI_DWAKE], setting);
448    }
449 
450    /* SLEEP_DELAY */
451    if (_ups->UPS_Cap[CI_DSHUTD] && _ups->dshutd != -1) {
452       asnprintf(setting, sizeof(setting), "%03d", (int)_ups->dshutd);
453       change_ups_eeprom_item("shutdown delay", _ups->UPS_Cmd[CI_DSHUTD],
454          setting);
455    }
456 
457    /* LOW_TRANSFER_LEVEL */
458    if (_ups->UPS_Cap[CI_LTRANS] && _ups->lotrans != -1) {
459       asnprintf(setting, sizeof(setting), "%03d", (int)_ups->lotrans);
460       change_ups_eeprom_item("lower transfer voltage",
461          _ups->UPS_Cmd[CI_LTRANS], setting);
462    }
463 
464    /* HIGH_TRANSFER_LEVEL */
465    if (_ups->UPS_Cap[CI_HTRANS] && _ups->hitrans != -1) {
466       asnprintf(setting, sizeof(setting), "%03d", (int)_ups->hitrans);
467       change_ups_eeprom_item("upper transfer voltage",
468          _ups->UPS_Cmd[CI_HTRANS], setting);
469    }
470 
471    /* UPS_BATT_CAP_RETURN */
472    if (_ups->UPS_Cap[CI_RETPCT] && _ups->rtnpct != -1) {
473       asnprintf(setting, sizeof(setting), "%02d", (int)_ups->rtnpct);
474       change_ups_eeprom_item("return threshold percent",
475          _ups->UPS_Cmd[CI_RETPCT], setting);
476    }
477 
478    /* ALARM_STATUS */
479    if (_ups->UPS_Cap[CI_DALARM] && strcmp(_ups->beepstate, "-1") != 0) {
480       asnprintf(setting, sizeof(setting), "%.1s", _ups->beepstate);
481       change_ups_eeprom_item("alarm delay", _ups->UPS_Cmd[CI_DALARM], setting);
482    }
483 
484    /* LOWBATT_SHUTDOWN_LEVEL */
485    if (_ups->UPS_Cap[CI_DLBATT] && _ups->dlowbatt != -1) {
486       asnprintf(setting, sizeof(setting), "%02d", (int)_ups->dlowbatt);
487       change_ups_eeprom_item("low battery warning delay",
488          _ups->UPS_Cmd[CI_DLBATT], setting);
489    }
490 
491    /* UPS_SELFTEST */
492    if (_ups->UPS_Cap[CI_STESTI] && strcmp(_ups->selftest, "-1") != 0) {
493       asnprintf(setting, sizeof(setting), "%.3s", _ups->selftest);
494       /* Make sure "ON" is 3 characters */
495       if (setting[2] == 0) {
496          setting[2] = ' ';
497          setting[3] = 0;
498       }
499       change_ups_eeprom_item(
500          "self test interval", _ups->UPS_Cmd[CI_STESTI], setting);
501    }
502 
503    /* OUTPUT_VOLTAGE */
504    if (_ups->UPS_Cap[CI_NOMOUTV] && _ups->NomOutputVoltage != -1) {
505       asnprintf(setting, sizeof(setting), "%03d", (int)_ups->NomOutputVoltage);
506       change_ups_eeprom_item("output voltage on batteries",
507          _ups->UPS_Cmd[CI_NOMOUTV], setting);
508    }
509 }
510