1 /* liebert.c - support for Liebert UPS models via MultiLink cable.
2 
3    Copyright (C) 2002  Russell Kroll <rkroll@exploits.org>
4 
5    Based on old-style multilink.c driver:
6 
7    Copyright (C) 2001  Rick Lyons <rick@powerup.com.au>
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 #include "config.h"
25 #include "main.h"
26 #include "serial.h"
27 #include "attribute.h"
28 
29 #define DRIVER_NAME	"Liebert MultiLink UPS driver"
30 #define DRIVER_VERSION	"1.02"
31 
32 /* driver description structure */
33 upsdrv_info_t upsdrv_info = {
34 	DRIVER_NAME,
35 	DRIVER_VERSION,
36 	"Russell Kroll <rkroll@exploits.org>\n" \
37 	"Rick Lyons <rick@powerup.com.au>",
38 	DRV_EXPERIMENTAL,
39 	{ NULL }
40 };
41 
42 #define	ML_ONBATTERY	0x55
43 
44 void upsdrv_shutdown(void)
45 	__attribute__((noreturn));
46 
upsdrv_shutdown(void)47 void upsdrv_shutdown(void)
48 {
49 	/* XXX: replace with a proper shutdown function (raise DTR) */
50 
51 	/* worse yet: stock cables don't support shutdown at all */
52 
53 	fatalx(EXIT_FAILURE, "shutdown not supported");
54 }
55 
upsdrv_initinfo(void)56 void upsdrv_initinfo(void)
57 {
58 	char	*tmp;
59 
60 	tmp = getval("mfr");
61 
62 	if (!tmp)
63 		dstate_setinfo("ups.mfr", "Liebert");
64 	else
65 		dstate_setinfo("ups.mfr", "%s", tmp);
66 
67 	tmp = getval("model");
68 
69 	if (!tmp)
70 		dstate_setinfo("ups.model", "MultiLink");
71 	else
72 		dstate_setinfo("ups.model", "%s", tmp);
73 }
74 
75 /* require this many OBs or LBs before actually setting it */
76 #define DEBOUNCE 3
77 
78 /* normal idle loop - keep up with the current state of the UPS */
upsdrv_updateinfo(void)79 void upsdrv_updateinfo(void)
80 {
81 	unsigned char	c;
82 	unsigned int	ob, lb;
83 	static	unsigned int	ob_state = 0, ob_last = 0, ob_ctr = 0;
84 	static	unsigned int	lb_state = 0, lb_last = 0, lb_ctr = 0;
85 
86 	ob = lb = 0;
87 
88 	/* the UPS connects RX to TX when on battery, so test for loopback */
89 
90 	ser_flush_in(upsfd, "", 0);
91 
92 	c = ML_ONBATTERY;
93 	ser_send_char(upsfd, c);
94 	if (ser_get_char(upsfd, &c, 1, 0) == 1) {
95 		while (ser_get_char(upsfd, &c, 1, 0) == 1)
96 			continue;
97 		if (c == ML_ONBATTERY)
98 			ob = 1;
99 	}
100 
101 	if (ser_get_dcd(upsfd))
102 		lb = 1;
103 
104 	/* state machine below to ensure status changes are debounced */
105 
106 	/* OB/OL state change: reset counter */
107 	if (ob_last != ob)
108 		ob_ctr = 0;
109 	else
110 		ob_ctr++;
111 
112 	upsdebugx(2, "OB: state %d last %d now %d ctr %d",
113 		ob_state, ob_last, ob, ob_ctr);
114 
115 	if (ob_ctr >= DEBOUNCE) {
116 
117 		if (ob != ob_state) {
118 
119 			upsdebugx(2, "OB: toggling state");
120 
121 			if (ob_state == 0)
122 				ob_state = 1;
123 			else
124 				ob_state = 0;
125 		}
126 	}
127 
128 	ob_last = ob;
129 
130 	/* now do it again for LB */
131 
132 	/* state change: reset counter */
133 	if (lb_last != lb)
134 		lb_ctr = 0;
135 	else
136 		lb_ctr++;
137 
138 	upsdebugx(2, "LB: state %d last %d now %d ctr %d",
139 		lb_state, lb_last, lb, lb_ctr);
140 
141 	if (lb_ctr >= DEBOUNCE) {
142 
143 		if (lb != lb_state) {
144 
145 			upsdebugx(2, "LB: toggling state");
146 
147 			if (lb_state == 0)
148 				lb_state = 1;
149 			else
150 				lb_state = 0;
151 		}
152 	}
153 
154 	lb_last = lb;
155 
156 	status_init();
157 
158 	if (ob_state == 1)
159 		status_set("OB");	/* on battery */
160 	else
161 		status_set("OL");	/* on line */
162 
163 	if (lb_state == 1)
164 		status_set("LB");	/* low battery */
165 
166 	status_commit();
167 	dstate_dataok();
168 }
169 
upsdrv_makevartable(void)170 void upsdrv_makevartable(void)
171 {
172 	addvar(VAR_VALUE, "mfr", "Override manufacturer name");
173 	addvar(VAR_VALUE, "model", "Override model name");
174 }
175 
upsdrv_help(void)176 void upsdrv_help(void)
177 {
178 }
179 
upsdrv_initups(void)180 void upsdrv_initups(void)
181 {
182 	upsfd = ser_open(device_path);
183 
184 	/* Speed should not matter (see comments in upsdrv_updateinfo),
185 	 * but set it relatively low in case there are problems with higher
186 	 * speeds. */
187 	ser_set_speed(upsfd, device_path, B9600);
188 
189 	/* raise RTS */
190 	ser_set_rts(upsfd, 1);
191 }
192 
upsdrv_cleanup(void)193 void upsdrv_cleanup(void)
194 {
195 	ser_close(upsfd, device_path);
196 }
197