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_list_t vl = VALUE_LIST_INIT;
281 int success = 0;
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 }
289 sstrncpy(vl.plugin, "onewire", sizeof(vl.plugin));
290 sstrncpy(vl.plugin_instance, name, sizeof(vl.plugin_instance));
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];
298 char file[4096];
299 char *endptr;
301 snprintf(file, sizeof(file), "%s/%s", path,
302 family_info->features[i].filename);
303 file[sizeof(file) - 1] = 0;
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);
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 }
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));
328 vl.values = &(value_t){.gauge = g};
329 vl.values_len = 1;
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;
368 char errbuf[1024];
370 char *buffer_ptr;
371 char *dummy;
372 char *saveptr;
373 char subpath[4096];
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);
383 dummy = buffer;
384 saveptr = NULL;
385 while ((buffer_ptr = strtok_r(dummy, ",/", &saveptr)) != NULL) {
386 int i;
388 dummy = NULL;
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;
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;
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;
410 /* DS2409 */
411 if (strncmp("1F.", buffer_ptr, strlen("1F.")) == 0) {
412 cow_read_ds2409(subpath);
413 continue;
414 }
415 } /* while (strtok_r) */
417 free(buffer);
418 return (0);
419 } /* int cow_read_bus */
421 /* ===================================================================================
422 */
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;
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));
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);
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 }
454 sstrncpy(vl.type, traverse->file, sizeof(vl.type));
455 sstrncpy(vl.type_instance, "", sizeof(""));
457 vl.values = &(value_t){.gauge = g};
458 vl.values_len = 1;
460 plugin_dispatch_values(&vl);
461 free(buffer);
462 } /* for (traverse) */
464 return 0;
465 } /* int cow_simple_read */
467 /* ===================================================================================
468 */
470 static int cow_read(user_data_t *ud __attribute__((unused))) {
471 int result = 0;
473 #if COLLECT_DEBUG
474 gettimeofday(&tv_begin, NULL);
475 #endif /* COLLECT_DEBUG */
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 }
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 */
492 return result;
493 } /* int cow_read */
495 static int cow_shutdown(void) {
496 OW_finish();
497 ignorelist_free(sensor_list);
499 direct_list_free();
501 if (regex_direct_initialized) {
502 regfree(®ex_direct);
503 }
505 return (0);
506 } /* int cow_shutdown */
508 static int cow_init(void) {
509 int status;
510 char errbuf[1024];
512 if (device_g == NULL) {
513 ERROR("onewire plugin: cow_init: No device configured.");
514 return (-1);
515 }
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 }
525 plugin_register_complex_read(/* group = */ NULL, "onewire", cow_read,
526 ow_interval, /* user data = */ NULL);
527 plugin_register_shutdown("onewire", cow_shutdown);
529 return (0);
530 } /* int cow_init */
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 }
538 /* vim: set sw=2 sts=2 ts=8 et fdm=marker cindent : */