1 /**
2 * collectd - src/battery.c
3 * Copyright (C) 2006 Florian octo Forster
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 * Authors:
20 * Florian octo Forster <octo at verplant.org>
21 **/
23 #include "collectd.h"
24 #include "common.h"
25 #include "plugin.h"
26 #include "utils_debug.h"
28 #define MODULE_NAME "battery"
29 #define BUFSIZE 512
31 #if HAVE_MACH_MACH_TYPES_H
32 # include <mach/mach_types.h>
33 #endif
34 #if HAVE_MACH_MACH_INIT_H
35 # include <mach/mach_init.h>
36 #endif
37 #if HAVE_MACH_MACH_ERROR_H
38 # include <mach/mach_error.h>
39 #endif
40 #if HAVE_COREFOUNDATION_COREFOUNDATION_H
41 # include <CoreFoundation/CoreFoundation.h>
42 #endif
43 #if HAVE_IOKIT_IOKITLIB_H
44 # include <IOKit/IOKitLib.h>
45 #endif
46 #if HAVE_IOKIT_IOTYPES_H
47 # include <IOKit/IOTypes.h>
48 #endif
49 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
50 # include <IOKit/ps/IOPowerSources.h>
51 #endif
52 #if HAVE_IOKIT_PS_IOPSKEYS_H
53 # include <IOKit/ps/IOPSKeys.h>
54 #endif
56 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H || KERNEL_LINUX
57 # define BATTERY_HAVE_READ 1
58 #else
59 # define BATTERY_HAVE_READ 0
60 #endif
62 #define INVALID_VALUE 47841.29
64 static char *battery_current_file = "battery-%s/current.rrd";
65 static char *battery_voltage_file = "battery-%s/voltage.rrd";
66 static char *battery_charge_file = "battery-%s/charge.rrd";
68 static char *ds_def_current[] =
69 {
70 "DS:current:GAUGE:"COLLECTD_HEARTBEAT":U:U",
71 NULL
72 };
73 static int ds_num_current = 1;
75 static char *ds_def_voltage[] =
76 {
77 "DS:voltage:GAUGE:"COLLECTD_HEARTBEAT":U:U",
78 NULL
79 };
80 static int ds_num_voltage = 1;
82 static char *ds_def_charge[] =
83 {
84 "DS:charge:GAUGE:"COLLECTD_HEARTBEAT":0:U",
85 NULL
86 };
87 static int ds_num_charge = 1;
89 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
90 /* No global variables */
91 /* #endif HAVE_IOKIT_PS_IOPOWERSOURCES_H */
93 #elif KERNEL_LINUX
94 static int battery_pmu_num = 0;
95 static char *battery_pmu_file = "/proc/pmu/battery_%i";
96 #endif /* KERNEL_LINUX */
98 static void battery_init (void)
99 {
100 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
101 /* No init neccessary */
102 /* #endif HAVE_IOKIT_PS_IOPOWERSOURCES_H */
104 #elif KERNEL_LINUX
105 int len;
106 char filename[BUFSIZE];
108 for (battery_pmu_num = 0; ; battery_pmu_num++)
109 {
110 len = snprintf (filename, BUFSIZE, battery_pmu_file, battery_pmu_num);
112 if ((len >= BUFSIZE) || (len < 0))
113 break;
115 if (access (filename, R_OK))
116 break;
117 }
118 #endif /* KERNEL_LINUX */
120 return;
121 }
123 static void battery_current_write (char *host, char *inst, char *val)
124 {
125 char filename[BUFSIZE];
126 int len;
128 len = snprintf (filename, BUFSIZE, battery_current_file, inst);
129 if ((len >= BUFSIZE) || (len < 0))
130 return;
132 rrd_update_file (host, filename, val,
133 ds_def_current, ds_num_current);
134 }
136 static void battery_voltage_write (char *host, char *inst, char *val)
137 {
138 char filename[BUFSIZE];
139 int len;
141 len = snprintf (filename, BUFSIZE, battery_voltage_file, inst);
142 if ((len >= BUFSIZE) || (len < 0))
143 return;
145 rrd_update_file (host, filename, val,
146 ds_def_voltage, ds_num_voltage);
147 }
149 static void battery_charge_write (char *host, char *inst, char *val)
150 {
151 char filename[BUFSIZE];
152 int len;
154 len = snprintf (filename, BUFSIZE, battery_charge_file, inst);
155 if ((len >= BUFSIZE) || (len < 0))
156 return;
158 rrd_update_file (host, filename, val,
159 ds_def_charge, ds_num_charge);
160 }
162 #if BATTERY_HAVE_READ
163 static void battery_submit (char *inst, double current, double voltage, double charge)
164 {
165 int len;
166 char buffer[BUFSIZE];
168 if (current != INVALID_VALUE)
169 {
170 len = snprintf (buffer, BUFSIZE, "N:%.3f", current);
172 if ((len > 0) && (len < BUFSIZE))
173 plugin_submit ("battery_current", inst, buffer);
174 }
175 else
176 {
177 plugin_submit ("battery_current", inst, "N:U");
178 }
180 if (voltage != INVALID_VALUE)
181 {
182 len = snprintf (buffer, BUFSIZE, "N:%.3f", voltage);
184 if ((len > 0) && (len < BUFSIZE))
185 plugin_submit ("battery_voltage", inst, buffer);
186 }
187 else
188 {
189 plugin_submit ("battery_voltage", inst, "N:U");
190 }
192 if (charge != INVALID_VALUE)
193 {
194 len = snprintf (buffer, BUFSIZE, "N:%.3f", charge);
196 if ((len > 0) && (len < BUFSIZE))
197 plugin_submit ("battery_charge", inst, buffer);
198 }
199 else
200 {
201 plugin_submit ("battery_charge", inst, "N:U");
202 }
203 }
205 static void battery_read (void)
206 {
207 #if HAVE_IOKIT_PS_IOPOWERSOURCES_H
208 CFTypeRef ps_raw;
209 CFArrayRef ps_array;
210 int ps_array_len;
211 CFDictionaryRef ps_dict;
212 CFTypeRef ps_obj;
213 CFTypeRef ps_value;
215 int i;
217 char name[128];
218 double charge = INVALID_VALUE;
219 double current = INVALID_VALUE;
220 double voltage = INVALID_VALUE;
222 ps_raw = IOPSCopyPowerSourcesInfo ();
223 ps_array = IOPSCopyPowerSourcesList (ps_raw);
224 ps_array_len = CFArrayGetCount (ps_array);
226 DBG ("ps_array_len == %i", ps_array_len);
228 for (i = 0; i < ps_array_len; i++)
229 {
230 ps_obj = CFArrayGetValueAtIndex (ps_array, i);
231 ps_dict = IOPSGetPowerSourceDescription (ps_raw, ps_obj);
233 if (CFGetTypeID (ps_dict) != CFDictionaryGetTypeID ())
234 {
235 DBG ("IOPSGetPowerSourceDescription did not return a CFDictionaryRef");
236 continue;
237 }
239 if (ps_dict != NULL)
240 {
241 /* Get the current capacity/charge */
242 ps_value = NULL;
243 charge = INVALID_VALUE;
244 if (CFDictionaryGetValueIfPresent (ps_dict,
245 CFSTR (kIOPSCurrentCapacityKey),
246 &ps_value))
247 {
248 if (CFGetTypeID (ps_value) != CFNumberGetTypeID ())
249 CFNumberGetValue (ps_value,
250 kCFNumberDoubleType,
251 &charge);
252 else
253 DBG ("kIOPSCurrentCapacityKey: Not a CFNumber");
255 DBG ("charge = %f", charge);
256 }
257 else
258 DBG ("`%s' does not exist", kIOPSCurrentCapacityKey);
260 /* Get the current */
261 ps_value = NULL;
262 current = INVALID_VALUE;
263 if (CFDictionaryGetValueIfPresent (ps_dict,
264 CFSTR (kIOPSCurrentKey),
265 &ps_value))
266 {
267 if (CFGetTypeID (ps_value) != CFNumberGetTypeID ())
268 CFNumberGetValue (ps_value,
269 kCFNumberDoubleType,
270 ¤t);
271 else
272 DBG ("kIOPSCurrentKey: Not a CFNumber");
273 DBG ("current = %f", current);
274 }
275 else
276 DBG ("`%s' does not exist", kIOPSCurrentKey);
278 /* Get the voltage */
279 ps_value = NULL;
280 voltage = INVALID_VALUE;
281 if (CFDictionaryGetValueIfPresent (ps_dict,
282 CFSTR (kIOPSVoltageKey),
283 &ps_value))
284 {
285 if (CFGetTypeID (ps_value) != CFNumberGetTypeID ())
286 CFNumberGetValue (ps_value,
287 kCFNumberDoubleType,
288 &voltage);
289 else
290 DBG ("kIOPSVoltageKey: Not a CFNumber");
291 DBG ("voltage = %f", voltage);
292 }
293 else
294 DBG ("`%s' does not exist", kIOPSVoltageKey);
296 /* Get the name of the device.. */
297 ps_value = NULL;
298 if (CFDictionaryGetValueIfPresent (ps_dict,
299 CFSTR (kIOPSNameKey),
300 &ps_value))
301 {
302 if (CFGetTypeID (ps_value) != CFStringGetTypeID ())
303 {
304 if (!CFStringGetCString (ps_value,
305 name, 128,
306 kCFStringEncodingASCII))
307 continue;
308 }
309 else
310 {
311 DBG ("kIOPSNameKey: Not a CFStringGetTypeID");
312 }
313 DBG ("Original string: `%s'", name);
314 }
315 else
316 {
317 strncpy (name, "unknown", 128);
318 }
319 name[127] = '\0';
320 for (i = 0; i < 128; i++)
321 {
322 if (name[i] == '\0')
323 break;
324 else if (isalnum (name[i]))
325 name[i] = (char) tolower (name[i]);
326 else
327 name[i] = '_';
328 }
330 battery_submit (name, current, voltage, charge);
331 }
332 }
334 CFRelease(ps_array);
335 CFRelease(ps_raw);
336 /* #endif HAVE_IOKIT_PS_IOPOWERSOURCES_H */
338 #elif KERNEL_LINUX
339 FILE *fh;
340 char buffer[BUFSIZE];
341 char filename[BUFSIZE];
343 char *fields[8];
344 int numfields;
346 int i;
347 int len;
349 for (i = 0; i < battery_pmu_num; i++)
350 {
351 char batnum_str[BUFSIZE];
352 double current = INVALID_VALUE;
353 double voltage = INVALID_VALUE;
354 double charge = INVALID_VALUE;
355 double *valptr = NULL;
357 len = snprintf (filename, BUFSIZE, battery_pmu_file, i);
358 if ((len >= BUFSIZE) || (len < 0))
359 continue;
361 len = snprintf (batnum_str, BUFSIZE, "%i", i);
362 if ((len >= BUFSIZE) || (len < 0))
363 continue;
365 if ((fh = fopen (filename, "r")) == NULL)
366 continue;
368 while (fgets (buffer, BUFSIZE, fh) != NULL)
369 {
370 numfields = strsplit (buffer, fields, 8);
372 if (numfields < 3)
373 continue;
375 if (strcmp ("current", fields[0]) == 0)
376 valptr = ¤t;
377 else if (strcmp ("voltage", fields[0]) == 0)
378 valptr = &voltage;
379 else if (strcmp ("charge", fields[0]) == 0)
380 valptr = &charge;
381 else
382 valptr = NULL;
384 if (valptr != NULL)
385 {
386 char *endptr;
388 endptr = NULL;
389 errno = 0;
391 *valptr = strtod (fields[2], &endptr) / 1000.0;
393 if ((fields[2] == endptr) || (errno != 0))
394 *valptr = INVALID_VALUE;
395 }
396 }
398 if ((current != INVALID_VALUE)
399 || (voltage != INVALID_VALUE)
400 || (charge != INVALID_VALUE))
401 battery_submit (batnum_str, current, voltage, charge);
403 fclose (fh);
404 fh = NULL;
405 }
407 if (access ("/proc/acpi/battery", R_OK | X_OK) == 0)
408 {
409 double current = INVALID_VALUE;
410 double voltage = INVALID_VALUE;
411 double charge = INVALID_VALUE;
412 double *valptr = NULL;
413 int charging = 0;
415 struct dirent *ent;
416 DIR *dh;
418 if ((dh = opendir ("/proc/acpi/battery")) == NULL)
419 {
420 syslog (LOG_ERR, "Cannot open `/proc/acpi/battery': %s", strerror (errno));
421 return;
422 }
424 while ((ent = readdir (dh)) != NULL)
425 {
426 if (ent->d_name[0] == '.')
427 continue;
429 len = snprintf (filename, BUFSIZE, "/proc/acpi/battery/%s/state", ent->d_name);
430 if ((len >= BUFSIZE) || (len < 0))
431 continue;
433 if ((fh = fopen (filename, "r")) == NULL)
434 {
435 syslog (LOG_ERR, "Cannot open `%s': %s", filename, strerror (errno));
436 continue;
437 }
439 /*
440 * [11:00] <@tokkee> $ cat /proc/acpi/battery/BAT1/state
441 * [11:00] <@tokkee> present: yes
442 * [11:00] <@tokkee> capacity state: ok
443 * [11:00] <@tokkee> charging state: charging
444 * [11:00] <@tokkee> present rate: 1724 mA
445 * [11:00] <@tokkee> remaining capacity: 4136 mAh
446 * [11:00] <@tokkee> present voltage: 12428 mV
447 */
448 while (fgets (buffer, BUFSIZE, fh) != NULL)
449 {
450 numfields = strsplit (buffer, fields, 8);
452 if (numfields < 3)
453 continue;
455 if ((strcmp (fields[0], "present") == 0)
456 && (strcmp (fields[1], "rate:") == 0))
457 valptr = ¤t;
458 else if ((strcmp (fields[0], "remaining") == 0)
459 && (strcmp (fields[1], "capacity:") == 0))
460 valptr = &charge;
461 else if ((strcmp (fields[0], "present") == 0)
462 && (strcmp (fields[1], "voltage:") == 0))
463 valptr = &voltage;
464 else
465 valptr = NULL;
467 if ((strcmp (fields[0], "charging") == 0)
468 && (strcmp (fields[1], "state:") == 0))
469 {
470 if (strcmp (fields[2], "charging") == 0)
471 charging = 1;
472 else
473 charging = 0;
474 }
476 if (valptr != NULL)
477 {
478 char *endptr;
480 endptr = NULL;
481 errno = 0;
483 *valptr = strtod (fields[2], &endptr) / 1000.0;
485 if ((fields[2] == endptr) || (errno != 0))
486 *valptr = INVALID_VALUE;
487 }
488 }
490 if ((current != INVALID_VALUE) && (charging == 0))
491 current *= -1;
493 if ((current != INVALID_VALUE)
494 || (voltage != INVALID_VALUE)
495 || (charge != INVALID_VALUE))
496 battery_submit (ent->d_name, current, voltage, charge);
498 fclose (fh);
499 }
501 closedir (dh);
502 }
503 #endif /* KERNEL_LINUX */
504 }
505 #else
506 # define battery_read NULL
507 #endif /* BATTERY_HAVE_READ */
509 void module_register (void)
510 {
511 plugin_register (MODULE_NAME, battery_init, battery_read, NULL);
512 plugin_register ("battery_current", NULL, NULL, battery_current_write);
513 plugin_register ("battery_voltage", NULL, NULL, battery_voltage_write);
514 plugin_register ("battery_charge", NULL, NULL, battery_charge_write);
515 }
517 #undef BUFSIZE
518 #undef MODULE_NAME