8bb2d8161de5b3c145556da1f2be0575d95f97b8
[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     char errbuf[1024];
363
364     char file[4096];
365     char *endptr;
366
367     snprintf (file, sizeof (file), "%s/%s",
368         path, family_info->features[i].filename);
369     file[sizeof (file) - 1] = 0;
370
371     buffer = NULL;
372     buffer_size = 0;
373     DEBUG ("Start reading onewire device %s", file);
374     status = OW_get (file, &buffer, &buffer_size);
375     if (status < 0)
376     {
377       ERROR ("onewire plugin: OW_get (%s/%s) failed. error = %s;",
378           path, family_info->features[i].filename, sstrerror(errno, errbuf, sizeof (errbuf)));
379       return (-1);
380     }
381     DEBUG ("Read onewire device %s as %s", file, buffer);
382
383     endptr = NULL;
384     values[0].gauge = strtod (buffer, &endptr);
385     if (endptr == NULL)
386     {
387       ERROR ("onewire plugin: Buffer is not a number: %s", buffer);
388       continue;
389     }
390
391     sstrncpy (vl.type, family_info->features[i].type, sizeof (vl.type));
392     sstrncpy (vl.type_instance, family_info->features[i].type_instance,
393         sizeof (vl.type_instance));
394
395     plugin_dispatch_values (&vl);
396     success++;
397
398     free (buffer);
399   } /* for (i = 0; i < features_num; i++) */
400
401   return ((success > 0) ? 0 : -1);
402 } /* int cow_read_values */
403
404 /* Forward declaration so the recursion below works */
405 static int cow_read_bus (const char *path);
406
407 /*
408  * cow_read_ds2409
409  *
410  * Handles:
411  * - DS2409 - MicroLAN Coupler
412  */
413 static int cow_read_ds2409 (const char *path)
414 {
415   char subpath[4096];
416   int status;
417
418   status = ssnprintf (subpath, sizeof (subpath), "%s/main", path);
419   if ((status > 0) && (status < (int) sizeof (subpath)))
420     cow_read_bus (subpath);
421
422   status = ssnprintf (subpath, sizeof (subpath), "%s/aux", path);
423   if ((status > 0) && (status < (int) sizeof (subpath)))
424     cow_read_bus (subpath);
425
426   return (0);
427 } /* int cow_read_ds2409 */
428
429 static int cow_read_bus (const char *path)
430 {
431   char *buffer;
432   size_t buffer_size;
433   int status;
434   char errbuf[1024];
435
436   char *buffer_ptr;
437   char *dummy;
438   char *saveptr;
439   char subpath[4096];
440
441   status = OW_get (path, &buffer, &buffer_size);
442   if (status < 0)
443   {
444     ERROR ("onewire plugin: OW_get (%s) failed. error = %s;",
445         path, sstrerror(errno, errbuf, sizeof (errbuf)));
446     return (-1);
447   }
448   DEBUG ("onewire plugin: OW_get (%s) returned: %s",
449       path, buffer);
450
451   dummy = buffer;
452   saveptr = NULL;
453   while ((buffer_ptr = strtok_r (dummy, ",/", &saveptr)) != NULL)
454   {
455     int i;
456
457     dummy = NULL;
458
459     if (strcmp ("/", path) == 0)
460       status = ssnprintf (subpath, sizeof (subpath), "/%s", buffer_ptr);
461     else
462       status = ssnprintf (subpath, sizeof (subpath), "%s/%s",
463           path, buffer_ptr);
464     if ((status <= 0) || (status >= (int) sizeof (subpath)))
465       continue;
466
467     for (i = 0; i < ow_family_features_num; i++)
468     {
469       if (strncmp (ow_family_features[i].family, buffer_ptr,
470             strlen (ow_family_features[i].family)) != 0)
471         continue;
472
473       cow_read_values (subpath,
474           buffer_ptr + strlen (ow_family_features[i].family),
475           ow_family_features + i);
476       break;
477     }
478     if (i < ow_family_features_num)
479       continue;
480
481     /* DS2409 */
482     if (strncmp ("1F.", buffer_ptr, strlen ("1F.")) == 0)
483     {
484       cow_read_ds2409 (subpath);
485       continue;
486     }
487   } /* while (strtok_r) */
488
489   free (buffer);
490   return (0);
491 } /* int cow_read_bus */
492
493
494 /* =================================================================================== */
495
496 static int cow_simple_read (void)
497 {
498   value_t      values[1];
499   value_list_t vl = VALUE_LIST_INIT;
500   char        *buffer;
501   size_t       buffer_size;
502   int          status;
503   char         errbuf[1024];
504   char        *endptr;
505   direct_access_element_t *traverse;
506
507   /* traverse list and check entries */
508   for (traverse = direct_list; traverse != NULL; traverse = traverse->next)
509   {
510       vl.values = values;
511       vl.values_len = 1;
512
513       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
514       sstrncpy (vl.plugin, "onewire", sizeof (vl.plugin));
515       sstrncpy (vl.plugin_instance, traverse->address, sizeof (vl.plugin_instance));
516
517       status = OW_get (traverse->path, &buffer, &buffer_size);
518       if (status < 0)
519       {
520           ERROR ("onewire plugin: OW_get (%s) failed. status = %s;",
521                  traverse->path,
522                  sstrerror(errno, errbuf, sizeof (errbuf)));
523           return (-1);
524       }
525       DEBUG ("onewire plugin: Read onewire device %s as %s", traverse->path, buffer);
526
527
528       endptr = NULL;
529       values[0].gauge = strtod (buffer, &endptr);
530       if (endptr == NULL)
531       {
532           ERROR ("onewire plugin: Buffer is not a number: %s", buffer);
533           continue;
534       }
535
536       sstrncpy (vl.type, traverse->file, sizeof (vl.type));
537       sstrncpy (vl.type_instance, "",   sizeof (""));
538
539       plugin_dispatch_values (&vl);
540       free (buffer);
541   } /* for (traverse) */
542
543   return 0;
544 } /* int cow_simple_read */
545
546 /* =================================================================================== */
547
548 static int cow_read (user_data_t *ud __attribute__((unused)))
549 {
550     int result=0;
551
552 #if COLLECT_DEBUG
553     gettimeofday (&tv_begin, NULL);
554 #endif /* COLLECT_DEBUG */
555
556     if (direct_access)
557     {
558         DEBUG ("onewire plugin: Direct access read");
559         result = cow_simple_read ();
560     }
561     else
562     {
563         DEBUG ("onewire plugin: Standard access read");
564         result = cow_read_bus ("/");
565     }
566
567 #if COLLECT_DEBUG
568     gettimeofday (&tv_end, NULL);
569     timeval_subtract (&tv_diff, &tv_end, &tv_begin);
570     DEBUG ("onewire plugin: Onewire read took us %ld.%06ld s",
571            tv_diff.tv_sec,
572            tv_diff.tv_usec);
573 #endif /* COLLECT_DEBUG */
574
575     return result;
576 } /* int cow_read */
577
578 static int cow_shutdown (void)
579 {
580     OW_finish ();
581     ignorelist_free (sensor_list);
582
583     direct_list_free ();
584
585     if (regex_direct_initialized)
586     {
587         regfree(&regex_direct);
588     }
589
590     return (0);
591 } /* int cow_shutdown */
592
593 static int cow_init (void)
594 {
595   int status;
596   char errbuf[1024];
597
598   if (device_g == NULL)
599   {
600     ERROR ("onewire plugin: cow_init: No device configured.");
601     return (-1);
602   }
603
604   DEBUG ("onewire plugin: about to init device <%s>.", device_g);
605   status = (int) OW_init (device_g);
606   if (status != 0)
607   {
608     ERROR ("onewire plugin: OW_init(%s) failed: %s.", device_g, sstrerror(errno, errbuf, sizeof (errbuf)));
609     return (1);
610   }
611
612   plugin_register_complex_read (/* group = */ NULL, "onewire", cow_read,
613       ow_interval, /* user data = */ NULL);
614   plugin_register_shutdown ("onewire", cow_shutdown);
615
616   return (0);
617 } /* int cow_init */
618
619 void module_register (void)
620 {
621   plugin_register_init ("onewire", cow_init);
622   plugin_register_config ("onewire", cow_load_config,
623                           config_keys, config_keys_num);
624 }
625
626 /* vim: set sw=2 sts=2 ts=8 et fdm=marker cindent : */