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 **/
22 #include "collectd.h"
24 #include "common.h"
25 #include "plugin.h"
26 #include "utils_ignorelist.h"
28 #include <owcapi.h>
29 #include <regex.h>
30 #include <sys/time.h>
31 #include <sys/types.h>
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;
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 */
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:]]+)$";
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);
89 static char *device_g = NULL;
90 static cdtime_t ow_interval = 0;
91 static _Bool direct_access = 0;
93 static const char *config_keys[] = {"Device", "IgnoreSelected", "Sensor",
94 "Interval"};
95 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
97 static ignorelist_t *sensor_list;
99 static _Bool regex_direct_initialized = 0;
100 static regex_t regex_direct;
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;
112 static direct_access_element_t *direct_list = NULL;
114 /* ===================================================================================
115 */
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;
126 return (diff < 0);
127 }
128 #endif /* COLLECT_DEBUG */
130 /* ===================================================================================
131 */
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 }
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;
148 DEBUG("onewire plugin: direct_list_insert <%s>", config);
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;
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 }
166 DEBUG("onewire plugin: direct_list_insert - about to match %s", config);
168 if (!regex_direct_initialized) {
169 if (regcomp(®ex_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 }
178 if (regexec(®ex_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 }
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);
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);
213 element->next = direct_list;
214 direct_list = element;
216 return 0;
217 }
219 static void direct_list_free(void) {
220 direct_access_element_t *traverse = direct_list;
221 direct_access_element_t *tmp = NULL;
222 ;
224 while (traverse != NULL) {
225 tmp = traverse;
226 traverse = traverse->next;
227 direct_list_element_free(tmp);
228 tmp = NULL;
229 }
230 }
232 /* ===================================================================================
233 */
235 static int cow_load_config(const char *key, const char *value) {
236 if (sensor_list == NULL)
237 sensor_list = ignorelist_create(1);
239 if (strcasecmp(key, "Sensor") == 0) {
240 if (direct_list_insert(value)) {
241 DEBUG("onewire plugin: Cannot add %s to direct_list_insert.", value);
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 }
275 return (0);
276 }
278 static int cow_read_values(const char *path, const char *name,
279 const ow_family_features_t *family_info) {
280 value_t values[1];
281 value_list_t vl = VALUE_LIST_INIT;
282 int success = 0;
284 if (sensor_list != NULL) {
285 DEBUG("onewire plugin: Checking ignorelist for `%s'", name);
286 if (ignorelist_match(sensor_list, name) != 0)
287 return 0;
288 }
290 vl.values = values;
291 vl.values_len = 1;
293 sstrncpy(vl.host, hostname_g, sizeof(vl.host));
294 sstrncpy(vl.plugin, "onewire", sizeof(vl.plugin));
295 sstrncpy(vl.plugin_instance, name, sizeof(vl.plugin_instance));
297 for (size_t i = 0; i < family_info->features_num; i++) {
298 char *buffer;
299 size_t buffer_size;
300 int status;
302 char file[4096];
303 char *endptr;
305 snprintf(file, sizeof(file), "%s/%s", path,
306 family_info->features[i].filename);
307 file[sizeof(file) - 1] = 0;
309 buffer = NULL;
310 buffer_size = 0;
311 DEBUG("Start reading onewire device %s", file);
312 status = OW_get(file, &buffer, &buffer_size);
313 if (status < 0) {
314 ERROR("onewire plugin: OW_get (%s/%s) failed. status = %#x;", path,
315 family_info->features[i].filename, status);
316 return (-1);
317 }
318 DEBUG("Read onewire device %s as %s", file, buffer);
320 endptr = NULL;
321 values[0].gauge = strtod(buffer, &endptr);
322 if (endptr == NULL) {
323 ERROR("onewire plugin: Buffer is not a number: %s", buffer);
324 continue;
325 }
327 sstrncpy(vl.type, family_info->features[i].type, sizeof(vl.type));
328 sstrncpy(vl.type_instance, family_info->features[i].type_instance,
329 sizeof(vl.type_instance));
331 plugin_dispatch_values(&vl);
332 success++;
334 free(buffer);
335 } /* for (i = 0; i < features_num; i++) */
337 return ((success > 0) ? 0 : -1);
338 } /* int cow_read_values */
340 /* Forward declaration so the recursion below works */
341 static int cow_read_bus(const char *path);
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;
353 status = ssnprintf(subpath, sizeof(subpath), "%s/main", path);
354 if ((status > 0) && (status < (int)sizeof(subpath)))
355 cow_read_bus(subpath);
357 status = ssnprintf(subpath, sizeof(subpath), "%s/aux", path);
358 if ((status > 0) && (status < (int)sizeof(subpath)))
359 cow_read_bus(subpath);
361 return (0);
362 } /* int cow_read_ds2409 */
364 static int cow_read_bus(const char *path) {
365 char *buffer;
366 size_t buffer_size;
367 int status;
369 char *buffer_ptr;
370 char *dummy;
371 char *saveptr;
372 char subpath[4096];
374 status = OW_get(path, &buffer, &buffer_size);
375 if (status < 0) {
376 ERROR("onewire plugin: OW_get (%s) failed. status = %#x;", path, status);
377 return (-1);
378 }
379 DEBUG("onewire plugin: OW_get (%s) returned: %s", path, buffer);
381 dummy = buffer;
382 saveptr = NULL;
383 while ((buffer_ptr = strtok_r(dummy, ",/", &saveptr)) != NULL) {
384 int i;
386 dummy = NULL;
388 if (strcmp("/", path) == 0)
389 status = ssnprintf(subpath, sizeof(subpath), "/%s", buffer_ptr);
390 else
391 status = ssnprintf(subpath, sizeof(subpath), "%s/%s", path, buffer_ptr);
392 if ((status <= 0) || (status >= (int)sizeof(subpath)))
393 continue;
395 for (i = 0; i < ow_family_features_num; i++) {
396 if (strncmp(ow_family_features[i].family, buffer_ptr,
397 strlen(ow_family_features[i].family)) != 0)
398 continue;
400 cow_read_values(subpath,
401 buffer_ptr + strlen(ow_family_features[i].family),
402 ow_family_features + i);
403 break;
404 }
405 if (i < ow_family_features_num)
406 continue;
408 /* DS2409 */
409 if (strncmp("1F.", buffer_ptr, strlen("1F.")) == 0) {
410 cow_read_ds2409(subpath);
411 continue;
412 }
413 } /* while (strtok_r) */
415 free(buffer);
416 return (0);
417 } /* int cow_read_bus */
419 /* ===================================================================================
420 */
422 static int cow_simple_read(void) {
423 value_t values[1];
424 value_list_t vl = VALUE_LIST_INIT;
425 char *buffer;
426 size_t buffer_size;
427 int status;
428 char *endptr;
429 direct_access_element_t *traverse;
431 /* traverse list and check entries */
432 for (traverse = direct_list; traverse != NULL; traverse = traverse->next) {
433 vl.values = values;
434 vl.values_len = 1;
436 sstrncpy(vl.host, hostname_g, sizeof(vl.host));
437 sstrncpy(vl.plugin, "onewire", sizeof(vl.plugin));
438 sstrncpy(vl.plugin_instance, traverse->address, sizeof(vl.plugin_instance));
440 status = OW_get(traverse->path, &buffer, &buffer_size);
441 if (status < 0) {
442 ERROR("onewire plugin: OW_get (%s) failed. status = %#x;", traverse->path,
443 status);
444 return (-1);
445 }
446 DEBUG("onewire plugin: Read onewire device %s as %s", traverse->path,
447 buffer);
449 endptr = NULL;
450 values[0].gauge = strtod(buffer, &endptr);
451 if (endptr == NULL) {
452 ERROR("onewire plugin: Buffer is not a number: %s", buffer);
453 continue;
454 }
456 sstrncpy(vl.type, traverse->file, sizeof(vl.type));
457 sstrncpy(vl.type_instance, "", sizeof(""));
459 plugin_dispatch_values(&vl);
460 free(buffer);
461 } /* for (traverse) */
463 return 0;
464 } /* int cow_simple_read */
466 /* ===================================================================================
467 */
469 static int cow_read(user_data_t *ud __attribute__((unused))) {
470 int result = 0;
472 #if COLLECT_DEBUG
473 gettimeofday(&tv_begin, NULL);
474 #endif /* COLLECT_DEBUG */
476 if (direct_access) {
477 DEBUG("onewire plugin: Direct access read");
478 result = cow_simple_read();
479 } else {
480 DEBUG("onewire plugin: Standard access read");
481 result = cow_read_bus("/");
482 }
484 #if COLLECT_DEBUG
485 gettimeofday(&tv_end, NULL);
486 timeval_subtract(&tv_diff, &tv_end, &tv_begin);
487 DEBUG("onewire plugin: Onewire read took us %ld.%06ld s", tv_diff.tv_sec,
488 tv_diff.tv_usec);
489 #endif /* COLLECT_DEBUG */
491 return result;
492 } /* int cow_read */
494 static int cow_shutdown(void) {
495 OW_finish();
496 ignorelist_free(sensor_list);
498 direct_list_free();
500 if (regex_direct_initialized) {
501 regfree(®ex_direct);
502 }
504 return (0);
505 } /* int cow_shutdown */
507 static int cow_init(void) {
508 int status;
510 if (device_g == NULL) {
511 ERROR("onewire plugin: cow_init: No device configured.");
512 return (-1);
513 }
515 DEBUG("onewire plugin: about to init device <%s>.", device_g);
516 status = (int)OW_init(device_g);
517 if (status != 0) {
518 ERROR("onewire plugin: OW_init(%s) failed: %i.", device_g, status);
519 return (1);
520 }
522 plugin_register_complex_read(/* group = */ NULL, "onewire", cow_read,
523 ow_interval, /* user data = */ NULL);
524 plugin_register_shutdown("onewire", cow_shutdown);
526 return (0);
527 } /* int cow_init */
529 void module_register(void) {
530 plugin_register_init("onewire", cow_init);
531 plugin_register_config("onewire", cow_load_config, config_keys,
532 config_keys_num);
533 }
535 /* vim: set sw=2 sts=2 ts=8 et fdm=marker cindent : */