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