Merge branch 'collectd-5.5'
[collectd.git] / src / onewire.c
1 /**
2  * collectd - src/onewire.c
3  * Copyright (C) 2008  noris network AG
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; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at noris.net>
20  **/
21
22 #include "collectd.h"
23 #include "common.h"
24 #include "plugin.h"
25 #include "utils_ignorelist.h"
26
27 #include <sys/time.h>
28 #include <sys/types.h>
29 #include <regex.h>
30 #include <owcapi.h>
31
32 #define OW_FAMILY_LENGTH 8
33 #define OW_FAMILY_MAX_FEATURES 2
34 struct ow_family_features_s
35 {
36   char family[OW_FAMILY_LENGTH];
37   struct
38   {
39     char filename[DATA_MAX_NAME_LEN];
40     char type[DATA_MAX_NAME_LEN];
41     char type_instance[DATA_MAX_NAME_LEN];
42   } features[OW_FAMILY_MAX_FEATURES];
43   size_t features_num;
44 };
45 typedef struct ow_family_features_s ow_family_features_t;
46
47 /* internal timing info collected in debug version only */
48 #if COLLECT_DEBUG
49 static struct timeval tv_begin, tv_end, tv_diff;
50 #endif /* COLLECT_DEBUG */
51
52 /* regexp to extract address (without family) and file from the owfs path */
53 static const char *regexp_to_match = "[A-Fa-f0-9]{2}\\.([A-Fa-f0-9]{12})/([[:alnum:]]+)$";
54
55 /* see http://owfs.sourceforge.net/ow_table.html for a list of families */
56 static ow_family_features_t ow_family_features[] =
57 {
58   { /* DS18S20 Precision Thermometer and DS1920 ibutton */
59     /* family = */ "10.",
60     {
61       {
62         /* filename = */ "temperature",
63         /* type = */ "temperature",
64         /* type_instance = */ ""
65       }
66     },
67     /* features_num = */ 1
68   },
69   { /* DS1822 Econo Thermometer */
70     /* family = */ "22.",
71     {
72       {
73         /* filename = */ "temperature",
74         /* type = */ "temperature",
75         /* type_instance = */ ""
76       }
77     },
78     /* features_num = */ 1
79   },
80   { /* DS18B20 Programmable Resolution Thermometer */
81     /* family = */ "28.",
82     {
83       {
84         /* filename = */ "temperature",
85         /* type = */ "temperature",
86         /* type_instance = */ ""
87       }
88     },
89     /* features_num = */ 1
90   },
91   { /* DS2436 Volts/Temp */
92     /* family = */ "1B.",
93     {
94       {
95         /* filename = */ "temperature",
96         /* type = */ "temperature",
97         /* type_instance = */ ""
98       }
99     },
100     /* features_num = */ 1
101   },
102   { /* DS2438 Volts/Temp */
103     /* family = */ "26.",
104     {
105       {
106         /* filename = */ "temperature",
107         /* type = */ "temperature",
108         /* type_instance = */ ""
109       }
110     },
111     /* features_num = */ 1
112   }
113 };
114 static int ow_family_features_num = STATIC_ARRAY_SIZE (ow_family_features);
115
116 static char *device_g = NULL;
117 static cdtime_t ow_interval = 0;
118 static _Bool direct_access = 0;
119
120 static const char *config_keys[] =
121 {
122   "Device",
123   "IgnoreSelected",
124   "Sensor",
125   "Interval"
126 };
127 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
128
129 static ignorelist_t *sensor_list;
130
131 static _Bool   regex_direct_initialized = 0;
132 static regex_t regex_direct;
133
134 /**
135  * List of onewire owfs "files" to be directly read
136  */
137 typedef struct direct_access_element_s
138 {
139         char *path;                 /**< The whole owfs path */
140     char *address;              /**< 1-wire address without family */
141     char *file;                 /**< owfs file - e.g. temperature */
142         struct direct_access_element_s *next; /**< Next in the list */
143 } direct_access_element_t;
144
145 static direct_access_element_t * direct_list = NULL;
146
147 /* =================================================================================== */
148
149 #if COLLECT_DEBUG
150 /* Return 1 if the difference is negative, otherwise 0.  */
151 static int timeval_subtract(struct timeval *result, struct timeval *t2, struct timeval *t1)
152 {
153     long int diff = (t2->tv_usec + 1000000 * t2->tv_sec) - (t1->tv_usec + 1000000 * t1->tv_sec);
154     result->tv_sec = diff / 1000000;
155     result->tv_usec = diff % 1000000;
156
157     return (diff<0);
158 }
159 #endif /* COLLECT_DEBUG */
160
161 /* =================================================================================== */
162
163 static void direct_list_element_free(direct_access_element_t *el)
164 {
165     if (el != NULL)
166     {
167         DEBUG ("onewire plugin: direct_list_element_free - deleting <%s>", el->path);
168         sfree (el->path);
169         sfree (el->address);
170         sfree (el->file);
171         free (el);
172     }
173 }
174
175 static int direct_list_insert(const char * config)
176 {
177     regmatch_t               pmatch[3];
178     size_t                   nmatch = 3;
179     direct_access_element_t *element;
180
181     DEBUG ("onewire plugin: direct_list_insert <%s>", config);
182
183     element = malloc (sizeof (*element));
184     if (element == NULL)
185     {
186         ERROR ("onewire plugin: direct_list_insert - cannot allocate element");
187         return 1;
188     }
189     element->path    = NULL;
190     element->address = NULL;
191     element->file    = NULL;
192
193     element->path = strdup (config);
194     if (element->path == NULL)
195     {
196         ERROR ("onewire plugin: direct_list_insert - cannot allocate path");
197         direct_list_element_free (element);
198         return 1;
199     }
200
201     DEBUG ("onewire plugin: direct_list_insert - about to match %s", config);
202
203     if (!regex_direct_initialized)
204     {
205         if (regcomp (&regex_direct, regexp_to_match, REG_EXTENDED))
206         {
207             ERROR ("onewire plugin: Cannot compile regex");
208             direct_list_element_free (element);
209             return (1);
210         }
211         regex_direct_initialized = 1;
212         DEBUG ("onewire plugin: Compiled regex!!");
213     }
214
215     if (regexec (&regex_direct, config, nmatch, pmatch, 0))
216     {
217         ERROR ("onewire plugin: direct_list_insert - no regex  match");
218         direct_list_element_free (element);
219         return 1;
220     }
221
222     if (pmatch[1].rm_so<0)
223     {
224         ERROR ("onewire plugin: direct_list_insert - no address regex match");
225         direct_list_element_free (element);
226         return 1;
227     }
228     element->address = strndup (config+pmatch[1].rm_so,
229                                 pmatch[1].rm_eo - pmatch[1].rm_so);
230     if (element->address == NULL)
231     {
232         ERROR ("onewire plugin: direct_list_insert - cannot allocate address");
233         direct_list_element_free (element);
234         return 1;
235     }
236     DEBUG ("onewire plugin: direct_list_insert - found address <%s>",
237            element->address);
238
239     if (pmatch[2].rm_so<0)
240     {
241         ERROR ("onewire plugin: direct_list_insert - no file regex match");
242         direct_list_element_free (element);
243         return 1;
244     }
245     element->file = strndup (config+pmatch[2].rm_so,
246                              pmatch[2].rm_eo - pmatch[2].rm_so);
247     if (element->file == NULL)
248     {
249         ERROR ("onewire plugin: direct_list_insert - cannot allocate file");
250         direct_list_element_free (element);
251         return 1;
252     }
253     DEBUG ("onewire plugin: direct_list_insert - found file <%s>", element->file);
254
255     element->next = direct_list;
256     direct_list = element;
257
258     return 0;
259 }
260
261 static void direct_list_free(void)
262 {
263     direct_access_element_t *traverse = direct_list;
264     direct_access_element_t *tmp = NULL;;
265
266     while(traverse != NULL)
267     {
268         tmp = traverse;
269         traverse = traverse->next;
270         direct_list_element_free (tmp);
271         tmp = NULL;
272     }
273 }
274
275 /* =================================================================================== */
276
277 static int cow_load_config (const char *key, const char *value)
278 {
279   if (sensor_list == NULL)
280     sensor_list = ignorelist_create (1);
281
282   if (strcasecmp (key, "Sensor") == 0)
283   {
284     if (direct_list_insert (value))
285     {
286         DEBUG ("onewire plugin: Cannot add %s to direct_list_insert.", value);
287
288         if (ignorelist_add (sensor_list, value))
289         {
290             ERROR ("onewire plugin: Cannot add value to ignorelist.");
291             return (1);
292         }
293     }
294     else
295     {
296         DEBUG ("onewire plugin: %s is a direct access", value);
297         direct_access = 1;
298     }
299   }
300   else if (strcasecmp (key, "IgnoreSelected") == 0)
301   {
302     ignorelist_set_invert (sensor_list, 1);
303     if (IS_TRUE (value))
304       ignorelist_set_invert (sensor_list, 0);
305   }
306   else if (strcasecmp (key, "Device") == 0)
307   {
308     char *temp;
309     temp = strdup (value);
310     if (temp == NULL)
311     {
312       ERROR ("onewire plugin: strdup failed.");
313       return (1);
314     }
315     sfree (device_g);
316     device_g = temp;
317   }
318   else if (strcasecmp ("Interval", key) == 0)
319   {
320     double tmp;
321     tmp = atof (value);
322     if (tmp > 0.0)
323       ow_interval = DOUBLE_TO_CDTIME_T (tmp);
324     else
325       ERROR ("onewire plugin: Invalid `Interval' setting: %s", value);
326   }
327   else
328   {
329     return (-1);
330   }
331
332   return (0);
333 }
334
335 static int cow_read_values (const char *path, const char *name,
336     const ow_family_features_t *family_info)
337 {
338   value_t values[1];
339   value_list_t vl = VALUE_LIST_INIT;
340   int success = 0;
341   size_t i;
342
343   if (sensor_list != NULL)
344   {
345     DEBUG ("onewire plugin: Checking ignorelist for `%s'", name);
346     if (ignorelist_match (sensor_list, name) != 0)
347       return 0;
348   }
349
350   vl.values = values;
351   vl.values_len = 1;
352
353   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
354   sstrncpy (vl.plugin, "onewire", sizeof (vl.plugin));
355   sstrncpy (vl.plugin_instance, name, sizeof (vl.plugin_instance));
356
357   for (i = 0; i < family_info->features_num; i++)
358   {
359     char *buffer;
360     size_t buffer_size;
361     int status;
362
363     char file[4096];
364     char *endptr;
365
366     snprintf (file, sizeof (file), "%s/%s",
367         path, family_info->features[i].filename);
368     file[sizeof (file) - 1] = 0;
369
370     buffer = NULL;
371     buffer_size = 0;
372     DEBUG ("Start reading onewire device %s", file);
373     status = OW_get (file, &buffer, &buffer_size);
374     if (status < 0)
375     {
376       ERROR ("onewire plugin: OW_get (%s/%s) failed. status = %#x;",
377           path, family_info->features[i].filename, status);
378       return (-1);
379     }
380     DEBUG ("Read onewire device %s as %s", file, buffer);
381
382     endptr = NULL;
383     values[0].gauge = strtod (buffer, &endptr);
384     if (endptr == NULL)
385     {
386       ERROR ("onewire plugin: Buffer is not a number: %s", buffer);
387       continue;
388     }
389
390     sstrncpy (vl.type, family_info->features[i].type, sizeof (vl.type));
391     sstrncpy (vl.type_instance, family_info->features[i].type_instance,
392         sizeof (vl.type_instance));
393
394     plugin_dispatch_values (&vl);
395     success++;
396
397     free (buffer);
398   } /* for (i = 0; i < features_num; i++) */
399
400   return ((success > 0) ? 0 : -1);
401 } /* int cow_read_values */
402
403 /* Forward declaration so the recursion below works */
404 static int cow_read_bus (const char *path);
405
406 /*
407  * cow_read_ds2409
408  *
409  * Handles:
410  * - DS2409 - MicroLAN Coupler
411  */
412 static int cow_read_ds2409 (const char *path)
413 {
414   char subpath[4096];
415   int status;
416
417   status = ssnprintf (subpath, sizeof (subpath), "%s/main", path);
418   if ((status > 0) && (status < (int) sizeof (subpath)))
419     cow_read_bus (subpath);
420
421   status = ssnprintf (subpath, sizeof (subpath), "%s/aux", path);
422   if ((status > 0) && (status < (int) sizeof (subpath)))
423     cow_read_bus (subpath);
424
425   return (0);
426 } /* int cow_read_ds2409 */
427
428 static int cow_read_bus (const char *path)
429 {
430   char *buffer;
431   size_t buffer_size;
432   int status;
433
434   char *buffer_ptr;
435   char *dummy;
436   char *saveptr;
437   char subpath[4096];
438
439   status = OW_get (path, &buffer, &buffer_size);
440   if (status < 0)
441   {
442     ERROR ("onewire plugin: OW_get (%s) failed. status = %#x;",
443         path, status);
444     return (-1);
445   }
446   DEBUG ("onewire plugin: OW_get (%s) returned: %s",
447       path, buffer);
448
449   dummy = buffer;
450   saveptr = NULL;
451   while ((buffer_ptr = strtok_r (dummy, ",/", &saveptr)) != NULL)
452   {
453     int i;
454
455     dummy = NULL;
456
457     if (strcmp ("/", path) == 0)
458       status = ssnprintf (subpath, sizeof (subpath), "/%s", buffer_ptr);
459     else
460       status = ssnprintf (subpath, sizeof (subpath), "%s/%s",
461           path, buffer_ptr);
462     if ((status <= 0) || (status >= (int) sizeof (subpath)))
463       continue;
464
465     for (i = 0; i < ow_family_features_num; i++)
466     {
467       if (strncmp (ow_family_features[i].family, buffer_ptr,
468             strlen (ow_family_features[i].family)) != 0)
469         continue;
470
471       cow_read_values (subpath,
472           buffer_ptr + strlen (ow_family_features[i].family),
473           ow_family_features + i);
474       break;
475     }
476     if (i < ow_family_features_num)
477       continue;
478
479     /* DS2409 */
480     if (strncmp ("1F.", buffer_ptr, strlen ("1F.")) == 0)
481     {
482       cow_read_ds2409 (subpath);
483       continue;
484     }
485   } /* while (strtok_r) */
486
487   free (buffer);
488   return (0);
489 } /* int cow_read_bus */
490
491
492 /* =================================================================================== */
493
494 static int cow_simple_read (void)
495 {
496   value_t      values[1];
497   value_list_t vl = VALUE_LIST_INIT;
498   char        *buffer;
499   size_t       buffer_size;
500   int          status;
501   char        *endptr;
502   direct_access_element_t *traverse;
503
504   /* traverse list and check entries */
505   for (traverse = direct_list; traverse != NULL; traverse = traverse->next)
506   {
507       vl.values = values;
508       vl.values_len = 1;
509
510       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
511       sstrncpy (vl.plugin, "onewire", sizeof (vl.plugin));
512       sstrncpy (vl.plugin_instance, traverse->address, sizeof (vl.plugin_instance));
513
514       status = OW_get (traverse->path, &buffer, &buffer_size);
515       if (status < 0)
516       {
517           ERROR ("onewire plugin: OW_get (%s) failed. status = %#x;",
518                  traverse->path,
519                  status);
520           return (-1);
521       }
522       DEBUG ("onewire plugin: Read onewire device %s as %s", traverse->path, buffer);
523
524
525       endptr = NULL;
526       values[0].gauge = strtod (buffer, &endptr);
527       if (endptr == NULL)
528       {
529           ERROR ("onewire plugin: Buffer is not a number: %s", buffer);
530           continue;
531       }
532
533       sstrncpy (vl.type, traverse->file, sizeof (vl.type));
534       sstrncpy (vl.type_instance, "",   sizeof (""));
535
536       plugin_dispatch_values (&vl);
537       free (buffer);
538   } /* for (traverse) */
539
540   return 0;
541 } /* int cow_simple_read */
542
543 /* =================================================================================== */
544
545 static int cow_read (user_data_t *ud __attribute__((unused)))
546 {
547     int result=0;
548
549 #if COLLECT_DEBUG
550     gettimeofday (&tv_begin, NULL);
551 #endif /* COLLECT_DEBUG */
552
553     if (direct_access)
554     {
555         DEBUG ("onewire plugin: Direct access read");
556         result = cow_simple_read ();
557     }
558     else
559     {
560         DEBUG ("onewire plugin: Standard access read");
561         result = cow_read_bus ("/");
562     }
563
564 #if COLLECT_DEBUG
565     gettimeofday (&tv_end, NULL);
566     timeval_subtract (&tv_diff, &tv_end, &tv_begin);
567     DEBUG ("onewire plugin: Onewire read took us %ld.%06ld s",
568            tv_diff.tv_sec,
569            tv_diff.tv_usec);
570 #endif /* COLLECT_DEBUG */
571
572     return result;
573 } /* int cow_read */
574
575 static int cow_shutdown (void)
576 {
577     OW_finish ();
578     ignorelist_free (sensor_list);
579
580     direct_list_free ();
581
582     if (regex_direct_initialized)
583     {
584         regfree(&regex_direct);
585     }
586
587     return (0);
588 } /* int cow_shutdown */
589
590 static int cow_init (void)
591 {
592   int status;
593
594   if (device_g == NULL)
595   {
596     ERROR ("onewire plugin: cow_init: No device configured.");
597     return (-1);
598   }
599
600   DEBUG ("onewire plugin: about to init device <%s>.", device_g);
601   status = (int) OW_init (device_g);
602   if (status != 0)
603   {
604     ERROR ("onewire plugin: OW_init(%s) failed: %i.", device_g, status);
605     return (1);
606   }
607
608   plugin_register_complex_read (/* group = */ NULL, "onewire", cow_read,
609       ow_interval, /* user data = */ NULL);
610   plugin_register_shutdown ("onewire", cow_shutdown);
611
612   return (0);
613 } /* int cow_init */
614
615 void module_register (void)
616 {
617   plugin_register_init ("onewire", cow_init);
618   plugin_register_config ("onewire", cow_load_config,
619                           config_keys, config_keys_num);
620 }
621
622 /* vim: set sw=2 sts=2 ts=8 et fdm=marker cindent : */