perl plugin: Renamed and documented "RegisterLegacyFlush" option.
[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
24 #include "common.h"
25 #include "plugin.h"
26 #include "utils_ignorelist.h"
27
28 #include <sys/time.h>
29 #include <sys/types.h>
30 #include <regex.h>
31 #include <owcapi.h>
32
33 #define OW_FAMILY_LENGTH 8
34 #define OW_FAMILY_MAX_FEATURES 2
35 struct ow_family_features_s
36 {
37   char family[OW_FAMILY_LENGTH];
38   struct
39   {
40     char filename[DATA_MAX_NAME_LEN];
41     char type[DATA_MAX_NAME_LEN];
42     char type_instance[DATA_MAX_NAME_LEN];
43   } features[OW_FAMILY_MAX_FEATURES];
44   size_t features_num;
45 };
46 typedef struct ow_family_features_s ow_family_features_t;
47
48 /* internal timing info collected in debug version only */
49 #if COLLECT_DEBUG
50 static struct timeval tv_begin, tv_end, tv_diff;
51 #endif /* COLLECT_DEBUG */
52
53 /* regexp to extract address (without family) and file from the owfs path */
54 static const char *regexp_to_match = "[A-Fa-f0-9]{2}\\.([A-Fa-f0-9]{12})/([[:alnum:]]+)$";
55
56 /* see http://owfs.sourceforge.net/ow_table.html for a list of families */
57 static ow_family_features_t ow_family_features[] =
58 {
59   { /* DS18S20 Precision Thermometer and DS1920 ibutton */
60     /* family = */ "10.",
61     {
62       {
63         /* filename = */ "temperature",
64         /* type = */ "temperature",
65         /* type_instance = */ ""
66       }
67     },
68     /* features_num = */ 1
69   },
70   { /* DS1822 Econo Thermometer */
71     /* family = */ "22.",
72     {
73       {
74         /* filename = */ "temperature",
75         /* type = */ "temperature",
76         /* type_instance = */ ""
77       }
78     },
79     /* features_num = */ 1
80   },
81   { /* DS18B20 Programmable Resolution Thermometer */
82     /* family = */ "28.",
83     {
84       {
85         /* filename = */ "temperature",
86         /* type = */ "temperature",
87         /* type_instance = */ ""
88       }
89     },
90     /* features_num = */ 1
91   },
92   { /* DS2436 Volts/Temp */
93     /* family = */ "1B.",
94     {
95       {
96         /* filename = */ "temperature",
97         /* type = */ "temperature",
98         /* type_instance = */ ""
99       }
100     },
101     /* features_num = */ 1
102   },
103   { /* DS2438 Volts/Temp */
104     /* family = */ "26.",
105     {
106       {
107         /* filename = */ "temperature",
108         /* type = */ "temperature",
109         /* type_instance = */ ""
110       }
111     },
112     /* features_num = */ 1
113   }
114 };
115 static int ow_family_features_num = STATIC_ARRAY_SIZE (ow_family_features);
116
117 static char *device_g = NULL;
118 static cdtime_t ow_interval = 0;
119 static _Bool direct_access = 0;
120
121 static const char *config_keys[] =
122 {
123   "Device",
124   "IgnoreSelected",
125   "Sensor",
126   "Interval"
127 };
128 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
129
130 static ignorelist_t *sensor_list;
131
132 static _Bool   regex_direct_initialized = 0;
133 static regex_t regex_direct;
134
135 /**
136  * List of onewire owfs "files" to be directly read
137  */
138 typedef struct direct_access_element_s
139 {
140         char *path;                 /**< The whole owfs path */
141     char *address;              /**< 1-wire address without family */
142     char *file;                 /**< owfs file - e.g. temperature */
143         struct direct_access_element_s *next; /**< Next in the list */
144 } direct_access_element_t;
145
146 static direct_access_element_t * direct_list = NULL;
147
148 /* =================================================================================== */
149
150 #if COLLECT_DEBUG
151 /* Return 1 if the difference is negative, otherwise 0.  */
152 static int timeval_subtract(struct timeval *result, struct timeval *t2, struct timeval *t1)
153 {
154     long int diff = (t2->tv_usec + 1000000 * t2->tv_sec) - (t1->tv_usec + 1000000 * t1->tv_sec);
155     result->tv_sec = diff / 1000000;
156     result->tv_usec = diff % 1000000;
157
158     return (diff<0);
159 }
160 #endif /* COLLECT_DEBUG */
161
162 /* =================================================================================== */
163
164 static void direct_list_element_free(direct_access_element_t *el)
165 {
166     if (el != NULL)
167     {
168         DEBUG ("onewire plugin: direct_list_element_free - deleting <%s>", el->path);
169         sfree (el->path);
170         sfree (el->address);
171         sfree (el->file);
172         free (el);
173     }
174 }
175
176 static int direct_list_insert(const char * config)
177 {
178     regmatch_t               pmatch[3];
179     size_t                   nmatch = 3;
180     direct_access_element_t *element;
181
182     DEBUG ("onewire plugin: direct_list_insert <%s>", config);
183
184     element = malloc (sizeof (*element));
185     if (element == NULL)
186     {
187         ERROR ("onewire plugin: direct_list_insert - cannot allocate element");
188         return 1;
189     }
190     element->path    = NULL;
191     element->address = NULL;
192     element->file    = NULL;
193
194     element->path = strdup (config);
195     if (element->path == NULL)
196     {
197         ERROR ("onewire plugin: direct_list_insert - cannot allocate path");
198         direct_list_element_free (element);
199         return 1;
200     }
201
202     DEBUG ("onewire plugin: direct_list_insert - about to match %s", config);
203
204     if (!regex_direct_initialized)
205     {
206         if (regcomp (&regex_direct, regexp_to_match, REG_EXTENDED))
207         {
208             ERROR ("onewire plugin: Cannot compile regex");
209             direct_list_element_free (element);
210             return (1);
211         }
212         regex_direct_initialized = 1;
213         DEBUG ("onewire plugin: Compiled regex!!");
214     }
215
216     if (regexec (&regex_direct, config, nmatch, pmatch, 0))
217     {
218         ERROR ("onewire plugin: direct_list_insert - no regex  match");
219         direct_list_element_free (element);
220         return 1;
221     }
222
223     if (pmatch[1].rm_so<0)
224     {
225         ERROR ("onewire plugin: direct_list_insert - no address regex match");
226         direct_list_element_free (element);
227         return 1;
228     }
229     element->address = strndup (config+pmatch[1].rm_so,
230                                 pmatch[1].rm_eo - pmatch[1].rm_so);
231     if (element->address == NULL)
232     {
233         ERROR ("onewire plugin: direct_list_insert - cannot allocate address");
234         direct_list_element_free (element);
235         return 1;
236     }
237     DEBUG ("onewire plugin: direct_list_insert - found address <%s>",
238            element->address);
239
240     if (pmatch[2].rm_so<0)
241     {
242         ERROR ("onewire plugin: direct_list_insert - no file regex match");
243         direct_list_element_free (element);
244         return 1;
245     }
246     element->file = strndup (config+pmatch[2].rm_so,
247                              pmatch[2].rm_eo - pmatch[2].rm_so);
248     if (element->file == NULL)
249     {
250         ERROR ("onewire plugin: direct_list_insert - cannot allocate file");
251         direct_list_element_free (element);
252         return 1;
253     }
254     DEBUG ("onewire plugin: direct_list_insert - found file <%s>", element->file);
255
256     element->next = direct_list;
257     direct_list = element;
258
259     return 0;
260 }
261
262 static void direct_list_free(void)
263 {
264     direct_access_element_t *traverse = direct_list;
265     direct_access_element_t *tmp = NULL;;
266
267     while(traverse != NULL)
268     {
269         tmp = traverse;
270         traverse = traverse->next;
271         direct_list_element_free (tmp);
272         tmp = NULL;
273     }
274 }
275
276 /* =================================================================================== */
277
278 static int cow_load_config (const char *key, const char *value)
279 {
280   if (sensor_list == NULL)
281     sensor_list = ignorelist_create (1);
282
283   if (strcasecmp (key, "Sensor") == 0)
284   {
285     if (direct_list_insert (value))
286     {
287         DEBUG ("onewire plugin: Cannot add %s to direct_list_insert.", value);
288
289         if (ignorelist_add (sensor_list, value))
290         {
291             ERROR ("onewire plugin: Cannot add value to ignorelist.");
292             return (1);
293         }
294     }
295     else
296     {
297         DEBUG ("onewire plugin: %s is a direct access", value);
298         direct_access = 1;
299     }
300   }
301   else if (strcasecmp (key, "IgnoreSelected") == 0)
302   {
303     ignorelist_set_invert (sensor_list, 1);
304     if (IS_TRUE (value))
305       ignorelist_set_invert (sensor_list, 0);
306   }
307   else if (strcasecmp (key, "Device") == 0)
308   {
309     char *temp;
310     temp = strdup (value);
311     if (temp == NULL)
312     {
313       ERROR ("onewire plugin: strdup failed.");
314       return (1);
315     }
316     sfree (device_g);
317     device_g = temp;
318   }
319   else if (strcasecmp ("Interval", key) == 0)
320   {
321     double tmp;
322     tmp = atof (value);
323     if (tmp > 0.0)
324       ow_interval = DOUBLE_TO_CDTIME_T (tmp);
325     else
326       ERROR ("onewire plugin: Invalid `Interval' setting: %s", value);
327   }
328   else
329   {
330     return (-1);
331   }
332
333   return (0);
334 }
335
336 static int cow_read_values (const char *path, const char *name,
337     const ow_family_features_t *family_info)
338 {
339   value_list_t vl = VALUE_LIST_INIT;
340   int success = 0;
341
342   if (sensor_list != NULL)
343   {
344     DEBUG ("onewire plugin: Checking ignorelist for `%s'", name);
345     if (ignorelist_match (sensor_list, name) != 0)
346       return 0;
347   }
348
349   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
350   sstrncpy (vl.plugin, "onewire", sizeof (vl.plugin));
351   sstrncpy (vl.plugin_instance, name, sizeof (vl.plugin_instance));
352
353   for (size_t i = 0; i < family_info->features_num; i++)
354   {
355     char *buffer;
356     size_t buffer_size;
357     int status;
358     char errbuf[1024];
359
360     char file[4096];
361     char *endptr;
362
363     snprintf (file, sizeof (file), "%s/%s",
364         path, family_info->features[i].filename);
365     file[sizeof (file) - 1] = 0;
366
367     buffer = NULL;
368     buffer_size = 0;
369     DEBUG ("Start reading onewire device %s", file);
370     status = OW_get (file, &buffer, &buffer_size);
371     if (status < 0)
372     {
373       ERROR ("onewire plugin: OW_get (%s/%s) failed. error = %s;",
374           path, family_info->features[i].filename, sstrerror(errno, errbuf, sizeof (errbuf)));
375       return (-1);
376     }
377     DEBUG ("Read onewire device %s as %s", file, buffer);
378
379     endptr = NULL;
380     gauge_t g = strtod (buffer, &endptr);
381     if (endptr == NULL)
382     {
383       ERROR ("onewire plugin: Buffer is not a number: %s", buffer);
384       continue;
385     }
386
387     sstrncpy (vl.type, family_info->features[i].type, sizeof (vl.type));
388     sstrncpy (vl.type_instance, family_info->features[i].type_instance,
389         sizeof (vl.type_instance));
390
391     vl.values = &(value_t) { .gauge = g };
392     vl.values_len = 1;
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   char errbuf[1024];
434
435   char *buffer_ptr;
436   char *dummy;
437   char *saveptr;
438   char subpath[4096];
439
440   status = OW_get (path, &buffer, &buffer_size);
441   if (status < 0)
442   {
443     ERROR ("onewire plugin: OW_get (%s) failed. error = %s;",
444         path, sstrerror(errno, errbuf, sizeof (errbuf)));
445     return (-1);
446   }
447   DEBUG ("onewire plugin: OW_get (%s) returned: %s",
448       path, buffer);
449
450   dummy = buffer;
451   saveptr = NULL;
452   while ((buffer_ptr = strtok_r (dummy, ",/", &saveptr)) != NULL)
453   {
454     int i;
455
456     dummy = NULL;
457
458     if (strcmp ("/", path) == 0)
459       status = ssnprintf (subpath, sizeof (subpath), "/%s", buffer_ptr);
460     else
461       status = ssnprintf (subpath, sizeof (subpath), "%s/%s",
462           path, buffer_ptr);
463     if ((status <= 0) || (status >= (int) sizeof (subpath)))
464       continue;
465
466     for (i = 0; i < ow_family_features_num; i++)
467     {
468       if (strncmp (ow_family_features[i].family, buffer_ptr,
469             strlen (ow_family_features[i].family)) != 0)
470         continue;
471
472       cow_read_values (subpath,
473           buffer_ptr + strlen (ow_family_features[i].family),
474           ow_family_features + i);
475       break;
476     }
477     if (i < ow_family_features_num)
478       continue;
479
480     /* DS2409 */
481     if (strncmp ("1F.", buffer_ptr, strlen ("1F.")) == 0)
482     {
483       cow_read_ds2409 (subpath);
484       continue;
485     }
486   } /* while (strtok_r) */
487
488   free (buffer);
489   return (0);
490 } /* int cow_read_bus */
491
492
493 /* =================================================================================== */
494
495 static int cow_simple_read (void)
496 {
497   value_list_t vl = VALUE_LIST_INIT;
498   char        *buffer;
499   size_t       buffer_size;
500   int          status;
501   char         errbuf[1024];
502   char        *endptr;
503   direct_access_element_t *traverse;
504
505   /* traverse list and check entries */
506   for (traverse = direct_list; traverse != NULL; traverse = traverse->next)
507   {
508       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
509       sstrncpy (vl.plugin, "onewire", sizeof (vl.plugin));
510       sstrncpy (vl.plugin_instance, traverse->address, sizeof (vl.plugin_instance));
511
512       status = OW_get (traverse->path, &buffer, &buffer_size);
513       if (status < 0)
514       {
515           ERROR ("onewire plugin: OW_get (%s) failed. status = %s;",
516                  traverse->path,
517                  sstrerror(errno, errbuf, sizeof (errbuf)));
518           return (-1);
519       }
520       DEBUG ("onewire plugin: Read onewire device %s as %s", traverse->path, buffer);
521
522       endptr = NULL;
523       gauge_t g = strtod (buffer, &endptr);
524       if (endptr == NULL)
525       {
526           ERROR ("onewire plugin: Buffer is not a number: %s", buffer);
527           continue;
528       }
529
530       sstrncpy (vl.type, traverse->file, sizeof (vl.type));
531       sstrncpy (vl.type_instance, "",   sizeof (""));
532
533       vl.values = &(value_t) { .gauge = g };
534       vl.values_len = 1;
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   char errbuf[1024];
594
595   if (device_g == NULL)
596   {
597     ERROR ("onewire plugin: cow_init: No device configured.");
598     return (-1);
599   }
600
601   DEBUG ("onewire plugin: about to init device <%s>.", device_g);
602   status = (int) OW_init (device_g);
603   if (status != 0)
604   {
605     ERROR ("onewire plugin: OW_init(%s) failed: %s.", device_g, sstrerror(errno, errbuf, sizeof (errbuf)));
606     return (1);
607   }
608
609   plugin_register_complex_read (/* group = */ NULL, "onewire", cow_read,
610       ow_interval, /* user data = */ NULL);
611   plugin_register_shutdown ("onewire", cow_shutdown);
612
613   return (0);
614 } /* int cow_init */
615
616 void module_register (void)
617 {
618   plugin_register_init ("onewire", cow_init);
619   plugin_register_config ("onewire", cow_load_config,
620                           config_keys, config_keys_num);
621 }
622
623 /* vim: set sw=2 sts=2 ts=8 et fdm=marker cindent : */