Merge branch 'collectd-5.5' into collectd-5.6
[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_t values[1];
340   value_list_t vl = VALUE_LIST_INIT;
341   int success = 0;
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 (size_t 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 : */