Merge remote-tracking branch 'github/pr/1749'
[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.plugin, "onewire", sizeof (vl.plugin));
350   sstrncpy (vl.plugin_instance, name, sizeof (vl.plugin_instance));
351
352   for (size_t i = 0; i < family_info->features_num; i++)
353   {
354     char *buffer;
355     size_t buffer_size;
356     int status;
357     char errbuf[1024];
358
359     char file[4096];
360     char *endptr;
361
362     snprintf (file, sizeof (file), "%s/%s",
363         path, family_info->features[i].filename);
364     file[sizeof (file) - 1] = 0;
365
366     buffer = NULL;
367     buffer_size = 0;
368     DEBUG ("Start reading onewire device %s", file);
369     status = OW_get (file, &buffer, &buffer_size);
370     if (status < 0)
371     {
372       ERROR ("onewire plugin: OW_get (%s/%s) failed. error = %s;",
373           path, family_info->features[i].filename, sstrerror(errno, errbuf, sizeof (errbuf)));
374       return (-1);
375     }
376     DEBUG ("Read onewire device %s as %s", file, buffer);
377
378     endptr = NULL;
379     gauge_t g = strtod (buffer, &endptr);
380     if (endptr == NULL)
381     {
382       ERROR ("onewire plugin: Buffer is not a number: %s", buffer);
383       continue;
384     }
385
386     sstrncpy (vl.type, family_info->features[i].type, sizeof (vl.type));
387     sstrncpy (vl.type_instance, family_info->features[i].type_instance,
388         sizeof (vl.type_instance));
389
390     vl.values = &(value_t) { .gauge = g };
391     vl.values_len = 1;
392
393     plugin_dispatch_values (&vl);
394     success++;
395
396     free (buffer);
397   } /* for (i = 0; i < features_num; i++) */
398
399   return ((success > 0) ? 0 : -1);
400 } /* int cow_read_values */
401
402 /* Forward declaration so the recursion below works */
403 static int cow_read_bus (const char *path);
404
405 /*
406  * cow_read_ds2409
407  *
408  * Handles:
409  * - DS2409 - MicroLAN Coupler
410  */
411 static int cow_read_ds2409 (const char *path)
412 {
413   char subpath[4096];
414   int status;
415
416   status = ssnprintf (subpath, sizeof (subpath), "%s/main", path);
417   if ((status > 0) && (status < (int) sizeof (subpath)))
418     cow_read_bus (subpath);
419
420   status = ssnprintf (subpath, sizeof (subpath), "%s/aux", path);
421   if ((status > 0) && (status < (int) sizeof (subpath)))
422     cow_read_bus (subpath);
423
424   return (0);
425 } /* int cow_read_ds2409 */
426
427 static int cow_read_bus (const char *path)
428 {
429   char *buffer;
430   size_t buffer_size;
431   int status;
432   char errbuf[1024];
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. error = %s;",
443         path, sstrerror(errno, errbuf, sizeof (errbuf)));
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_list_t vl = VALUE_LIST_INIT;
497   char        *buffer;
498   size_t       buffer_size;
499   int          status;
500   char         errbuf[1024];
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       sstrncpy (vl.plugin, "onewire", sizeof (vl.plugin));
508       sstrncpy (vl.plugin_instance, traverse->address, sizeof (vl.plugin_instance));
509
510       status = OW_get (traverse->path, &buffer, &buffer_size);
511       if (status < 0)
512       {
513           ERROR ("onewire plugin: OW_get (%s) failed. status = %s;",
514                  traverse->path,
515                  sstrerror(errno, errbuf, sizeof (errbuf)));
516           return (-1);
517       }
518       DEBUG ("onewire plugin: Read onewire device %s as %s", traverse->path, buffer);
519
520       endptr = NULL;
521       gauge_t g = strtod (buffer, &endptr);
522       if (endptr == NULL)
523       {
524           ERROR ("onewire plugin: Buffer is not a number: %s", buffer);
525           continue;
526       }
527
528       sstrncpy (vl.type, traverse->file, sizeof (vl.type));
529       sstrncpy (vl.type_instance, "",   sizeof (""));
530
531       vl.values = &(value_t) { .gauge = g };
532       vl.values_len = 1;
533
534       plugin_dispatch_values (&vl);
535       free (buffer);
536   } /* for (traverse) */
537
538   return 0;
539 } /* int cow_simple_read */
540
541 /* =================================================================================== */
542
543 static int cow_read (user_data_t *ud __attribute__((unused)))
544 {
545     int result=0;
546
547 #if COLLECT_DEBUG
548     gettimeofday (&tv_begin, NULL);
549 #endif /* COLLECT_DEBUG */
550
551     if (direct_access)
552     {
553         DEBUG ("onewire plugin: Direct access read");
554         result = cow_simple_read ();
555     }
556     else
557     {
558         DEBUG ("onewire plugin: Standard access read");
559         result = cow_read_bus ("/");
560     }
561
562 #if COLLECT_DEBUG
563     gettimeofday (&tv_end, NULL);
564     timeval_subtract (&tv_diff, &tv_end, &tv_begin);
565     DEBUG ("onewire plugin: Onewire read took us %ld.%06ld s",
566            tv_diff.tv_sec,
567            tv_diff.tv_usec);
568 #endif /* COLLECT_DEBUG */
569
570     return result;
571 } /* int cow_read */
572
573 static int cow_shutdown (void)
574 {
575     OW_finish ();
576     ignorelist_free (sensor_list);
577
578     direct_list_free ();
579
580     if (regex_direct_initialized)
581     {
582         regfree(&regex_direct);
583     }
584
585     return (0);
586 } /* int cow_shutdown */
587
588 static int cow_init (void)
589 {
590   int status;
591   char errbuf[1024];
592
593   if (device_g == NULL)
594   {
595     ERROR ("onewire plugin: cow_init: No device configured.");
596     return (-1);
597   }
598
599   DEBUG ("onewire plugin: about to init device <%s>.", device_g);
600   status = (int) OW_init (device_g);
601   if (status != 0)
602   {
603     ERROR ("onewire plugin: OW_init(%s) failed: %s.", device_g, sstrerror(errno, errbuf, sizeof (errbuf)));
604     return (1);
605   }
606
607   plugin_register_complex_read (/* group = */ NULL, "onewire", cow_read,
608       ow_interval, /* user data = */ NULL);
609   plugin_register_shutdown ("onewire", cow_shutdown);
610
611   return (0);
612 } /* int cow_init */
613
614 void module_register (void)
615 {
616   plugin_register_init ("onewire", cow_init);
617   plugin_register_config ("onewire", cow_load_config,
618                           config_keys, config_keys_num);
619 }
620
621 /* vim: set sw=2 sts=2 ts=8 et fdm=marker cindent : */