Merge pull request #2729 from cekstam/add-scale-and-shift-to-modbus
[collectd.git] / src / modbus.c
1 /**
2  * collectd - src/modbus.c
3  * Copyright (C) 2010,2011  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 Lesser General Public License as published by
7  * the Free Software Foundation; only version 2.1 of the License is
8  * applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors:
20  *   Florian Forster <octo at noris.net>
21  **/
22
23 #include "collectd.h"
24
25 #include "common.h"
26 #include "configfile.h"
27 #include "plugin.h"
28
29 #include <modbus.h>
30 #include <netdb.h>
31 #include <sys/socket.h>
32
33 #ifndef LIBMODBUS_VERSION_CHECK
34 /* Assume version 2.0.3 */
35 #define LEGACY_LIBMODBUS 1
36 #else
37 /* Assume version 2.9.2 */
38 #endif
39
40 #ifndef MODBUS_TCP_DEFAULT_PORT
41 #ifdef MODBUS_TCP_PORT
42 #define MODBUS_TCP_DEFAULT_PORT MODBUS_TCP_PORT
43 #else
44 #define MODBUS_TCP_DEFAULT_PORT 502
45 #endif
46 #endif
47
48 /*
49  * <Data "data_name">
50  *   RegisterBase 1234
51  *   RegisterCmd ReadHolding
52  *   RegisterType float
53  *   Type gauge
54  *   Instance "..."
55  * </Data>
56  *
57  * <Host "name">
58  *   Address "addr"
59  *   Port "1234"
60  *   # Or:
61  *   # Device "/dev/ttyUSB0"
62  *   # Baudrate 38400
63  *   # (Assumes 8N1)
64  *   Interval 60
65  *
66  *   <Slave 1>
67  *     Instance "foobar" # optional
68  *     Collect "data_name"
69  *   </Slave>
70  * </Host>
71  */
72
73 /*
74  * Data structures
75  */
76 enum mb_register_type_e /* {{{ */
77 { REG_TYPE_INT16,
78   REG_TYPE_INT32,
79   REG_TYPE_INT32_CDAB,
80   REG_TYPE_UINT16,
81   REG_TYPE_UINT32,
82   REG_TYPE_UINT32_CDAB,
83   REG_TYPE_INT64,
84   REG_TYPE_UINT64,
85   REG_TYPE_FLOAT,
86   REG_TYPE_FLOAT_CDAB }; /* }}} */
87
88 enum mb_mreg_type_e /* {{{ */
89 { MREG_HOLDING,
90   MREG_INPUT }; /* }}} */
91 typedef enum mb_register_type_e mb_register_type_t;
92 typedef enum mb_mreg_type_e mb_mreg_type_t;
93
94 /* TCP or RTU depending on what is specified in host config block */
95 enum mb_conntype_e /* {{{ */
96 { MBCONN_TCP,
97   MBCONN_RTU }; /* }}} */
98 typedef enum mb_conntype_e mb_conntype_t;
99
100 struct mb_data_s;
101 typedef struct mb_data_s mb_data_t;
102 struct mb_data_s /* {{{ */
103 {
104   char *name;
105   int register_base;
106   mb_register_type_t register_type;
107   mb_mreg_type_t modbus_register_type;
108   char type[DATA_MAX_NAME_LEN];
109   char instance[DATA_MAX_NAME_LEN];
110   double scale;
111   double shift;
112
113   mb_data_t *next;
114 }; /* }}} */
115
116 struct mb_slave_s /* {{{ */
117 {
118   int id;
119   char instance[DATA_MAX_NAME_LEN];
120   mb_data_t *collect;
121 }; /* }}} */
122 typedef struct mb_slave_s mb_slave_t;
123
124 struct mb_host_s /* {{{ */
125 {
126   char host[DATA_MAX_NAME_LEN];
127   char node[NI_MAXHOST]; /* TCP hostname or RTU serial device */
128   /* char service[NI_MAXSERV]; */
129   int port;     /* for Modbus/TCP */
130   int baudrate; /* for Modbus/RTU */
131   mb_conntype_t conntype;
132   cdtime_t interval;
133
134   mb_slave_t *slaves;
135   size_t slaves_num;
136
137 #if LEGACY_LIBMODBUS
138   modbus_param_t connection;
139 #else
140   modbus_t *connection;
141 #endif
142   bool is_connected;
143 }; /* }}} */
144 typedef struct mb_host_s mb_host_t;
145
146 struct mb_data_group_s;
147 typedef struct mb_data_group_s mb_data_group_t;
148 struct mb_data_group_s /* {{{ */
149 {
150   mb_data_t *registers;
151   size_t registers_num;
152
153   mb_data_group_t *next;
154 }; /* }}} */
155
156 /*
157  * Global variables
158  */
159 static mb_data_t *data_definitions;
160
161 /*
162  * Functions
163  */
164 static mb_data_t *data_get_by_name(mb_data_t *src, /* {{{ */
165                                    const char *name) {
166   if (name == NULL)
167     return NULL;
168
169   for (mb_data_t *ptr = src; ptr != NULL; ptr = ptr->next)
170     if (strcasecmp(ptr->name, name) == 0)
171       return ptr;
172
173   return NULL;
174 } /* }}} mb_data_t *data_get_by_name */
175
176 static int data_append(mb_data_t **dst, mb_data_t *src) /* {{{ */
177 {
178   mb_data_t *ptr;
179
180   if ((dst == NULL) || (src == NULL))
181     return EINVAL;
182
183   ptr = *dst;
184
185   if (ptr == NULL) {
186     *dst = src;
187     return 0;
188   }
189
190   while (ptr->next != NULL)
191     ptr = ptr->next;
192
193   ptr->next = src;
194
195   return 0;
196 } /* }}} int data_append */
197
198 /* Copy a single mb_data_t and append it to another list. */
199 static int data_copy(mb_data_t **dst, const mb_data_t *src) /* {{{ */
200 {
201   mb_data_t *tmp;
202   int status;
203
204   if ((dst == NULL) || (src == NULL))
205     return EINVAL;
206
207   tmp = malloc(sizeof(*tmp));
208   if (tmp == NULL)
209     return ENOMEM;
210   memcpy(tmp, src, sizeof(*tmp));
211   tmp->name = NULL;
212   tmp->next = NULL;
213
214   tmp->name = strdup(src->name);
215   if (tmp->name == NULL) {
216     sfree(tmp);
217     return ENOMEM;
218   }
219
220   status = data_append(dst, tmp);
221   if (status != 0) {
222     sfree(tmp->name);
223     sfree(tmp);
224     return status;
225   }
226
227   return 0;
228 } /* }}} int data_copy */
229
230 /* Lookup a single mb_data_t instance, copy it and append the copy to another
231  * list. */
232 static int data_copy_by_name(mb_data_t **dst, mb_data_t *src, /* {{{ */
233                              const char *name) {
234   mb_data_t *ptr;
235
236   if ((dst == NULL) || (src == NULL) || (name == NULL))
237     return EINVAL;
238
239   ptr = data_get_by_name(src, name);
240   if (ptr == NULL)
241     return ENOENT;
242
243   return data_copy(dst, ptr);
244 } /* }}} int data_copy_by_name */
245
246 /* Read functions */
247
248 static int mb_submit(mb_host_t *host, mb_slave_t *slave, /* {{{ */
249                      mb_data_t *data, value_t value) {
250   value_list_t vl = VALUE_LIST_INIT;
251
252   if ((host == NULL) || (slave == NULL) || (data == NULL))
253     return EINVAL;
254
255   if (host->interval == 0)
256     host->interval = plugin_get_interval();
257
258   if (slave->instance[0] == 0)
259     snprintf(slave->instance, sizeof(slave->instance), "slave_%i", slave->id);
260
261   vl.values = &value;
262   vl.values_len = 1;
263   vl.interval = host->interval;
264   sstrncpy(vl.host, host->host, sizeof(vl.host));
265   sstrncpy(vl.plugin, "modbus", sizeof(vl.plugin));
266   sstrncpy(vl.plugin_instance, slave->instance, sizeof(vl.plugin_instance));
267   sstrncpy(vl.type, data->type, sizeof(vl.type));
268   sstrncpy(vl.type_instance, data->instance, sizeof(vl.type_instance));
269
270   return plugin_dispatch_values(&vl);
271 } /* }}} int mb_submit */
272
273 static float mb_register_to_float(uint16_t hi, uint16_t lo) /* {{{ */
274 {
275   union {
276     uint8_t b[4];
277     uint16_t s[2];
278     float f;
279   } conv;
280
281 #if BYTE_ORDER == LITTLE_ENDIAN
282   /* little endian */
283   conv.b[0] = lo & 0x00ff;
284   conv.b[1] = (lo >> 8) & 0x00ff;
285   conv.b[2] = hi & 0x00ff;
286   conv.b[3] = (hi >> 8) & 0x00ff;
287 #else
288   conv.b[3] = lo & 0x00ff;
289   conv.b[2] = (lo >> 8) & 0x00ff;
290   conv.b[1] = hi & 0x00ff;
291   conv.b[0] = (hi >> 8) & 0x00ff;
292 #endif
293
294   return conv.f;
295 } /* }}} float mb_register_to_float */
296
297 #if LEGACY_LIBMODBUS
298 /* Version 2.0.3 */
299 static int mb_init_connection(mb_host_t *host) /* {{{ */
300 {
301   int status;
302
303   if (host == NULL)
304     return EINVAL;
305
306 #if COLLECT_DEBUG
307   modbus_set_debug(&host->connection, 1);
308 #endif
309
310   /* We'll do the error handling ourselves. */
311   modbus_set_error_handling(&host->connection, NOP_ON_ERROR);
312
313   if (host->conntype == MBCONN_TCP) {
314     if ((host->port < 1) || (host->port > 65535))
315       host->port = MODBUS_TCP_DEFAULT_PORT;
316
317     DEBUG("Modbus plugin: Trying to connect to \"%s\", port %i.", host->node,
318           host->port);
319
320     modbus_init_tcp(&host->connection,
321                     /* host = */ host->node,
322                     /* port = */ host->port);
323   } else /* MBCONN_RTU */
324   {
325     DEBUG("Modbus plugin: Trying to connect to \"%s\".", host->node);
326
327     modbus_init_rtu(&host->connection,
328                     /* device = */ host->node,
329                     /* baudrate = */ host->baudrate, 'N', 8, 1, 0);
330   }
331
332   status = modbus_connect(&host->connection);
333   if (status != 0) {
334     ERROR("Modbus plugin: modbus_connect (%s, %i) failed with status %i.",
335           host->node, host->port ? host->port : host->baudrate, status);
336     return status;
337   }
338
339   host->is_connected = true;
340   return 0;
341 } /* }}} int mb_init_connection */
342 /* #endif LEGACY_LIBMODBUS */
343
344 #else /* if !LEGACY_LIBMODBUS */
345 /* Version 2.9.2 */
346 static int mb_init_connection(mb_host_t *host) /* {{{ */
347 {
348   int status;
349
350   if (host == NULL)
351     return EINVAL;
352
353   if (host->connection != NULL)
354     return 0;
355
356   if (host->conntype == MBCONN_TCP) {
357     if ((host->port < 1) || (host->port > 65535))
358       host->port = MODBUS_TCP_DEFAULT_PORT;
359
360     DEBUG("Modbus plugin: Trying to connect to \"%s\", port %i.", host->node,
361           host->port);
362
363     host->connection = modbus_new_tcp(host->node, host->port);
364     if (host->connection == NULL) {
365       ERROR("Modbus plugin: Creating new Modbus/TCP object failed.");
366       return -1;
367     }
368   } else {
369     DEBUG("Modbus plugin: Trying to connect to \"%s\", baudrate %i.",
370           host->node, host->baudrate);
371
372     host->connection = modbus_new_rtu(host->node, host->baudrate, 'N', 8, 1);
373     if (host->connection == NULL) {
374       ERROR("Modbus plugin: Creating new Modbus/RTU object failed.");
375       return -1;
376     }
377   }
378
379 #if COLLECT_DEBUG
380   modbus_set_debug(host->connection, 1);
381 #endif
382
383   /* We'll do the error handling ourselves. */
384   modbus_set_error_recovery(host->connection, 0);
385
386   status = modbus_connect(host->connection);
387   if (status != 0) {
388     ERROR("Modbus plugin: modbus_connect (%s, %i) failed with status %i.",
389           host->node, host->port ? host->port : host->baudrate, status);
390     modbus_free(host->connection);
391     host->connection = NULL;
392     return status;
393   }
394
395   return 0;
396 } /* }}} int mb_init_connection */
397 #endif /* !LEGACY_LIBMODBUS */
398
399 #define CAST_TO_VALUE_T(ds, vt, raw, scale, shift)                             \
400   do {                                                                         \
401     if ((ds)->ds[0].type == DS_TYPE_COUNTER)                                   \
402       (vt).counter = (((counter_t)(raw)*scale) + shift);                       \
403     else if ((ds)->ds[0].type == DS_TYPE_GAUGE)                                \
404       (vt).gauge = (((gauge_t)(raw)*scale) + shift);                           \
405     else if ((ds)->ds[0].type == DS_TYPE_DERIVE)                               \
406       (vt).derive = (((derive_t)(raw)*scale) + shift);                         \
407     else /* if (ds->ds[0].type == DS_TYPE_ABSOLUTE) */                         \
408       (vt).absolute = (((absolute_t)(raw)*scale) + shift);                     \
409   } while (0)
410
411 static int mb_read_data(mb_host_t *host, mb_slave_t *slave, /* {{{ */
412                         mb_data_t *data) {
413   uint16_t values[4] = {0};
414   int values_num;
415   const data_set_t *ds;
416   int status = 0;
417
418   if ((host == NULL) || (slave == NULL) || (data == NULL))
419     return EINVAL;
420
421   ds = plugin_get_ds(data->type);
422   if (ds == NULL) {
423     ERROR("Modbus plugin: Type \"%s\" is not defined.", data->type);
424     return -1;
425   }
426
427   if (ds->ds_num != 1) {
428     ERROR("Modbus plugin: The type \"%s\" has %" PRIsz " data sources. "
429           "I can only handle data sets with only one data source.",
430           data->type, ds->ds_num);
431     return -1;
432   }
433
434   if ((ds->ds[0].type != DS_TYPE_GAUGE) &&
435       (data->register_type != REG_TYPE_INT32) &&
436       (data->register_type != REG_TYPE_INT32_CDAB) &&
437       (data->register_type != REG_TYPE_UINT32) &&
438       (data->register_type != REG_TYPE_UINT32_CDAB) &&
439       (data->register_type != REG_TYPE_INT64) &&
440       (data->register_type != REG_TYPE_UINT64)) {
441     NOTICE(
442         "Modbus plugin: The data source of type \"%s\" is %s, not gauge. "
443         "This will most likely result in problems, because the register type "
444         "is not UINT32 or UINT64.",
445         data->type, DS_TYPE_TO_STRING(ds->ds[0].type));
446   }
447
448   if ((data->register_type == REG_TYPE_INT32) ||
449       (data->register_type == REG_TYPE_INT32_CDAB) ||
450       (data->register_type == REG_TYPE_UINT32) ||
451       (data->register_type == REG_TYPE_UINT32_CDAB) ||
452       (data->register_type == REG_TYPE_FLOAT) ||
453       (data->register_type == REG_TYPE_FLOAT_CDAB))
454     values_num = 2;
455   else if ((data->register_type == REG_TYPE_INT64) ||
456            (data->register_type == REG_TYPE_UINT64))
457     values_num = 4;
458   else
459     values_num = 1;
460
461   if (host->connection == NULL) {
462     status = EBADF;
463   } else if (host->conntype == MBCONN_TCP) {
464     /* getpeername() is used only to determine if the socket is connected, not
465      * because we're really interested in the peer's IP address. */
466     if (getpeername(modbus_get_socket(host->connection),
467                     (void *)&(struct sockaddr_storage){0},
468                     &(socklen_t){sizeof(struct sockaddr_storage)}) != 0)
469       status = errno;
470   }
471
472   if ((status == EBADF) || (status == ENOTSOCK) || (status == ENOTCONN)) {
473     status = mb_init_connection(host);
474     if (status != 0) {
475       ERROR("Modbus plugin: mb_init_connection (%s/%s) failed. ", host->host,
476             host->node);
477       host->is_connected = false;
478       host->connection = NULL;
479       return -1;
480     }
481   } else if (status != 0) {
482 #if LEGACY_LIBMODBUS
483     modbus_close(&host->connection);
484 #else
485     modbus_close(host->connection);
486     modbus_free(host->connection);
487 #endif
488   }
489
490 #if LEGACY_LIBMODBUS
491 /* Version 2.0.3: Pass the connection struct as a pointer and pass the slave
492  * id to each call of "read_holding_registers". */
493 #define modbus_read_registers(ctx, addr, nb, dest)                             \
494   read_holding_registers(&(ctx), slave->id, (addr), (nb), (dest))
495 #else /* if !LEGACY_LIBMODBUS */
496   /* Version 2.9.2: Set the slave id once before querying the registers. */
497   status = modbus_set_slave(host->connection, slave->id);
498   if (status != 0) {
499     ERROR("Modbus plugin: modbus_set_slave (%i) failed with status %i.",
500           slave->id, status);
501     return -1;
502   }
503 #endif
504   if (data->modbus_register_type == MREG_INPUT) {
505     status = modbus_read_input_registers(host->connection,
506                                          /* start_addr = */ data->register_base,
507                                          /* num_registers = */ values_num,
508                                          /* buffer = */ values);
509   } else {
510     status = modbus_read_registers(host->connection,
511                                    /* start_addr = */ data->register_base,
512                                    /* num_registers = */ values_num,
513                                    /* buffer = */ values);
514   }
515   if (status != values_num) {
516     ERROR("Modbus plugin: modbus read function (%s/%s) failed. "
517           " status = %i, start_addr = %i, values_num = %i. Giving up.",
518           host->host, host->node, status, data->register_base, values_num);
519 #if LEGACY_LIBMODBUS
520     modbus_close(&host->connection);
521 #else
522     modbus_close(host->connection);
523     modbus_free(host->connection);
524 #endif
525     host->connection = NULL;
526     return -1;
527   }
528
529   DEBUG("Modbus plugin: mb_read_data: Success! "
530         "modbus_read_registers returned with status %i.",
531         status);
532
533   if (data->register_type == REG_TYPE_FLOAT) {
534     float float_value;
535     value_t vt;
536
537     float_value = mb_register_to_float(values[0], values[1]);
538     DEBUG("Modbus plugin: mb_read_data: "
539           "Returned float value is %g",
540           (double)float_value);
541
542     CAST_TO_VALUE_T(ds, vt, float_value, data->scale, data->shift);
543     mb_submit(host, slave, data, vt);
544   } else if (data->register_type == REG_TYPE_FLOAT_CDAB) {
545     float float_value;
546     value_t vt;
547
548     float_value = mb_register_to_float(values[1], values[0]);
549     DEBUG("Modbus plugin: mb_read_data: "
550           "Returned float value is %g",
551           (double)float_value);
552
553     CAST_TO_VALUE_T(ds, vt, float_value, data->scale, data->shift);
554     mb_submit(host, slave, data, vt);
555   } else if (data->register_type == REG_TYPE_INT32) {
556     union {
557       uint32_t u32;
558       int32_t i32;
559     } v;
560     value_t vt;
561
562     v.u32 = (((uint32_t)values[0]) << 16) | ((uint32_t)values[1]);
563     DEBUG("Modbus plugin: mb_read_data: "
564           "Returned int32 value is %" PRIi32,
565           v.i32);
566
567     CAST_TO_VALUE_T(ds, vt, v.i32, data->scale, data->shift);
568     mb_submit(host, slave, data, vt);
569   } else if (data->register_type == REG_TYPE_INT32_CDAB) {
570     union {
571       uint32_t u32;
572       int32_t i32;
573     } v;
574     value_t vt;
575
576     v.u32 = (((uint32_t)values[1]) << 16) | ((uint32_t)values[0]);
577     DEBUG("Modbus plugin: mb_read_data: "
578           "Returned int32 value is %" PRIi32,
579           v.i32);
580
581     CAST_TO_VALUE_T(ds, vt, v.i32, data->scale, data->shift);
582     mb_submit(host, slave, data, vt);
583   } else if (data->register_type == REG_TYPE_INT16) {
584     union {
585       uint16_t u16;
586       int16_t i16;
587     } v;
588     value_t vt;
589
590     v.u16 = values[0];
591
592     DEBUG("Modbus plugin: mb_read_data: "
593           "Returned int16 value is %" PRIi16,
594           v.i16);
595
596     CAST_TO_VALUE_T(ds, vt, v.i16, data->scale, data->shift);
597     mb_submit(host, slave, data, vt);
598   } else if (data->register_type == REG_TYPE_UINT32) {
599     uint32_t v32;
600     value_t vt;
601
602     v32 = (((uint32_t)values[0]) << 16) | ((uint32_t)values[1]);
603     DEBUG("Modbus plugin: mb_read_data: "
604           "Returned uint32 value is %" PRIu32,
605           v32);
606
607     CAST_TO_VALUE_T(ds, vt, v32, data->scale, data->shift);
608     mb_submit(host, slave, data, vt);
609   } else if (data->register_type == REG_TYPE_UINT32_CDAB) {
610     uint32_t v32;
611     value_t vt;
612
613     v32 = (((uint32_t)values[1]) << 16) | ((uint32_t)values[0]);
614     DEBUG("Modbus plugin: mb_read_data: "
615           "Returned uint32 value is %" PRIu32,
616           v32);
617
618     CAST_TO_VALUE_T(ds, vt, v32, data->scale, data->shift);
619     mb_submit(host, slave, data, vt);
620   } else if (data->register_type == REG_TYPE_UINT64) {
621     uint64_t v64;
622     value_t vt;
623
624     v64 = (((uint64_t)values[0]) << 48) | (((uint64_t)values[1]) << 32) |
625           (((uint64_t)values[2]) << 16) | (((uint64_t)values[3]));
626     DEBUG("Modbus plugin: mb_read_data: "
627           "Returned uint64 value is %" PRIu64,
628           v64);
629
630     CAST_TO_VALUE_T(ds, vt, v64, data->scale, data->shift);
631     mb_submit(host, slave, data, vt);
632   } else if (data->register_type == REG_TYPE_INT64) {
633     union {
634       uint64_t u64;
635       int64_t i64;
636     } v;
637     value_t vt;
638
639     v.u64 = (((uint64_t)values[0]) << 48) | (((uint64_t)values[1]) << 32) |
640             (((uint64_t)values[2]) << 16) | ((uint64_t)values[3]);
641     DEBUG("Modbus plugin: mb_read_data: "
642           "Returned uint64 value is %" PRIi64,
643           v.i64);
644
645     CAST_TO_VALUE_T(ds, vt, v.i64, data->scale, data->shift);
646     mb_submit(host, slave, data, vt);
647   } else /* if (data->register_type == REG_TYPE_UINT16) */
648   {
649     value_t vt;
650
651     DEBUG("Modbus plugin: mb_read_data: "
652           "Returned uint16 value is %" PRIu16,
653           values[0]);
654
655     CAST_TO_VALUE_T(ds, vt, values[0], data->scale, data->shift);
656     mb_submit(host, slave, data, vt);
657   }
658
659   return 0;
660 } /* }}} int mb_read_data */
661
662 static int mb_read_slave(mb_host_t *host, mb_slave_t *slave) /* {{{ */
663 {
664   int success;
665   int status;
666
667   if ((host == NULL) || (slave == NULL))
668     return EINVAL;
669
670   success = 0;
671   for (mb_data_t *data = slave->collect; data != NULL; data = data->next) {
672     status = mb_read_data(host, slave, data);
673     if (status == 0)
674       success++;
675   }
676
677   if (success == 0)
678     return -1;
679   else
680     return 0;
681 } /* }}} int mb_read_slave */
682
683 static int mb_read(user_data_t *user_data) /* {{{ */
684 {
685   mb_host_t *host;
686   int success;
687   int status;
688
689   if ((user_data == NULL) || (user_data->data == NULL))
690     return EINVAL;
691
692   host = user_data->data;
693
694   success = 0;
695   for (size_t i = 0; i < host->slaves_num; i++) {
696     status = mb_read_slave(host, host->slaves + i);
697     if (status == 0)
698       success++;
699   }
700
701   if (success == 0)
702     return -1;
703   else
704     return 0;
705 } /* }}} int mb_read */
706
707 /* Free functions */
708
709 static void data_free_one(mb_data_t *data) /* {{{ */
710 {
711   if (data == NULL)
712     return;
713
714   sfree(data->name);
715   sfree(data);
716 } /* }}} void data_free_one */
717
718 static void data_free_all(mb_data_t *data) /* {{{ */
719 {
720   mb_data_t *next;
721
722   if (data == NULL)
723     return;
724
725   next = data->next;
726   data_free_one(data);
727
728   data_free_all(next);
729 } /* }}} void data_free_all */
730
731 static void slaves_free_all(mb_slave_t *slaves, size_t slaves_num) /* {{{ */
732 {
733   if (slaves == NULL)
734     return;
735
736   for (size_t i = 0; i < slaves_num; i++)
737     data_free_all(slaves[i].collect);
738   sfree(slaves);
739 } /* }}} void slaves_free_all */
740
741 static void host_free(void *void_host) /* {{{ */
742 {
743   mb_host_t *host = void_host;
744
745   if (host == NULL)
746     return;
747
748   slaves_free_all(host->slaves, host->slaves_num);
749   sfree(host);
750 } /* }}} void host_free */
751
752 /* Config functions */
753
754 static int mb_config_add_data(oconfig_item_t *ci) /* {{{ */
755 {
756   mb_data_t data = {0};
757   int status;
758
759   data.name = NULL;
760   data.register_type = REG_TYPE_UINT16;
761   data.next = NULL;
762   data.scale = 1;
763   data.shift = 0;
764
765   status = cf_util_get_string(ci, &data.name);
766   if (status != 0)
767     return status;
768
769   for (int i = 0; i < ci->children_num; i++) {
770     oconfig_item_t *child = ci->children + i;
771
772     if (strcasecmp("Type", child->key) == 0)
773       status = cf_util_get_string_buffer(child, data.type, sizeof(data.type));
774     else if (strcasecmp("Instance", child->key) == 0)
775       status = cf_util_get_string_buffer(child, data.instance,
776                                          sizeof(data.instance));
777     else if (strcasecmp("Scale", child->key) == 0)
778       status = cf_util_get_double(child, &data.scale);
779     else if (strcasecmp("Shift", child->key) == 0)
780       status = cf_util_get_double(child, &data.shift);
781     else if (strcasecmp("RegisterBase", child->key) == 0)
782       status = cf_util_get_int(child, &data.register_base);
783     else if (strcasecmp("RegisterType", child->key) == 0) {
784       char tmp[16];
785       status = cf_util_get_string_buffer(child, tmp, sizeof(tmp));
786       if (status != 0)
787         /* do nothing */;
788       else if (strcasecmp("Int16", tmp) == 0)
789         data.register_type = REG_TYPE_INT16;
790       else if (strcasecmp("Int32", tmp) == 0)
791         data.register_type = REG_TYPE_INT32;
792       else if (strcasecmp("Int32LE", tmp) == 0)
793         data.register_type = REG_TYPE_INT32_CDAB;
794       else if (strcasecmp("Uint16", tmp) == 0)
795         data.register_type = REG_TYPE_UINT16;
796       else if (strcasecmp("Uint32", tmp) == 0)
797         data.register_type = REG_TYPE_UINT32;
798       else if (strcasecmp("Uint32LE", tmp) == 0)
799         data.register_type = REG_TYPE_UINT32_CDAB;
800       else if (strcasecmp("Float", tmp) == 0)
801         data.register_type = REG_TYPE_FLOAT;
802       else if (strcasecmp("FloatLE", tmp) == 0)
803         data.register_type = REG_TYPE_FLOAT_CDAB;
804       else if (strcasecmp("Uint64", tmp) == 0)
805         data.register_type = REG_TYPE_UINT64;
806       else if (strcasecmp("Int64", tmp) == 0)
807         data.register_type = REG_TYPE_INT64;
808       else {
809         ERROR("Modbus plugin: The register type \"%s\" is unknown.", tmp);
810         status = -1;
811       }
812     } else if (strcasecmp("RegisterCmd", child->key) == 0) {
813 #if LEGACY_LIBMODBUS
814       ERROR("Modbus plugin: RegisterCmd parameter can not be used "
815             "with your libmodbus version");
816 #else
817       char tmp[16];
818       status = cf_util_get_string_buffer(child, tmp, sizeof(tmp));
819       if (status != 0)
820         /* do nothing */;
821       else if (strcasecmp("ReadHolding", tmp) == 0)
822         data.modbus_register_type = MREG_HOLDING;
823       else if (strcasecmp("ReadInput", tmp) == 0)
824         data.modbus_register_type = MREG_INPUT;
825       else {
826         ERROR("Modbus plugin: The modbus_register_type \"%s\" is unknown.",
827               tmp);
828         status = -1;
829       }
830 #endif
831     } else {
832       ERROR("Modbus plugin: Unknown configuration option: %s", child->key);
833       status = -1;
834     }
835
836     if (status != 0)
837       break;
838   } /* for (i = 0; i < ci->children_num; i++) */
839
840   assert(data.name != NULL);
841   if (data.type[0] == 0) {
842     ERROR("Modbus plugin: Data block \"%s\": No type has been specified.",
843           data.name);
844     status = -1;
845   }
846
847   if (status == 0)
848     data_copy(&data_definitions, &data);
849
850   sfree(data.name);
851
852   return status;
853 } /* }}} int mb_config_add_data */
854
855 static int mb_config_set_host_address(mb_host_t *host, /* {{{ */
856                                       const char *address) {
857   struct addrinfo *ai_list;
858   int status;
859
860   if ((host == NULL) || (address == NULL))
861     return EINVAL;
862
863   struct addrinfo ai_hints = {
864       /* XXX: libmodbus can only handle IPv4 addresses. */
865       .ai_family = AF_INET,
866       .ai_flags = AI_ADDRCONFIG};
867
868   status = getaddrinfo(address, /* service = */ NULL, &ai_hints, &ai_list);
869   if (status != 0) {
870     ERROR("Modbus plugin: getaddrinfo failed: %s",
871           (status == EAI_SYSTEM) ? STRERRNO : gai_strerror(status));
872     return status;
873   }
874
875   for (struct addrinfo *ai_ptr = ai_list; ai_ptr != NULL;
876        ai_ptr = ai_ptr->ai_next) {
877     status = getnameinfo(ai_ptr->ai_addr, ai_ptr->ai_addrlen, host->node,
878                          sizeof(host->node),
879                          /* service = */ NULL, /* length = */ 0,
880                          /* flags = */ NI_NUMERICHOST);
881     if (status == 0)
882       break;
883   } /* for (ai_ptr) */
884
885   freeaddrinfo(ai_list);
886
887   if (status != 0)
888     ERROR("Modbus plugin: Unable to translate node name: \"%s\"", address);
889   else /* if (status == 0) */
890   {
891     DEBUG("Modbus plugin: mb_config_set_host_address: %s -> %s", address,
892           host->node);
893   }
894
895   return status;
896 } /* }}} int mb_config_set_host_address */
897
898 static int mb_config_add_slave(mb_host_t *host, oconfig_item_t *ci) /* {{{ */
899 {
900   mb_slave_t *slave;
901   int status;
902
903   if ((host == NULL) || (ci == NULL))
904     return EINVAL;
905
906   slave = realloc(host->slaves, sizeof(*slave) * (host->slaves_num + 1));
907   if (slave == NULL)
908     return ENOMEM;
909   host->slaves = slave;
910   slave = host->slaves + host->slaves_num;
911   memset(slave, 0, sizeof(*slave));
912   slave->collect = NULL;
913
914   status = cf_util_get_int(ci, &slave->id);
915   if (status != 0)
916     return status;
917
918   for (int i = 0; i < ci->children_num; i++) {
919     oconfig_item_t *child = ci->children + i;
920
921     if (strcasecmp("Instance", child->key) == 0)
922       status = cf_util_get_string_buffer(child, slave->instance,
923                                          sizeof(slave->instance));
924     else if (strcasecmp("Collect", child->key) == 0) {
925       char buffer[1024];
926       status = cf_util_get_string_buffer(child, buffer, sizeof(buffer));
927       if (status == 0)
928         data_copy_by_name(&slave->collect, data_definitions, buffer);
929       status = 0; /* continue after failure. */
930     } else {
931       ERROR("Modbus plugin: Unknown configuration option: %s", child->key);
932       status = -1;
933     }
934
935     if (status != 0)
936       break;
937   }
938
939   if ((status == 0) && (slave->collect == NULL))
940     status = EINVAL;
941
942   if (slave->id < 0)
943     status = EINVAL;
944
945   if (status == 0)
946     host->slaves_num++;
947   else /* if (status != 0) */
948     data_free_all(slave->collect);
949
950   return status;
951 } /* }}} int mb_config_add_slave */
952
953 static int mb_config_add_host(oconfig_item_t *ci) /* {{{ */
954 {
955   mb_host_t *host;
956   int status;
957
958   host = calloc(1, sizeof(*host));
959   if (host == NULL)
960     return ENOMEM;
961   host->slaves = NULL;
962
963   status = cf_util_get_string_buffer(ci, host->host, sizeof(host->host));
964   if (status != 0) {
965     sfree(host);
966     return status;
967   }
968   if (host->host[0] == 0) {
969     sfree(host);
970     return EINVAL;
971   }
972
973   for (int i = 0; i < ci->children_num; i++) {
974     oconfig_item_t *child = ci->children + i;
975     status = 0;
976
977     if (strcasecmp("Address", child->key) == 0) {
978       char buffer[NI_MAXHOST];
979       status = cf_util_get_string_buffer(child, buffer, sizeof(buffer));
980       if (status == 0)
981         status = mb_config_set_host_address(host, buffer);
982       if (status == 0)
983         host->conntype = MBCONN_TCP;
984     } else if (strcasecmp("Port", child->key) == 0) {
985       host->port = cf_util_get_port_number(child);
986       if (host->port <= 0)
987         status = -1;
988     } else if (strcasecmp("Device", child->key) == 0) {
989       status = cf_util_get_string_buffer(child, host->node, sizeof(host->node));
990       if (status == 0)
991         host->conntype = MBCONN_RTU;
992     } else if (strcasecmp("Baudrate", child->key) == 0)
993       status = cf_util_get_int(child, &host->baudrate);
994     else if (strcasecmp("Interval", child->key) == 0)
995       status = cf_util_get_cdtime(child, &host->interval);
996     else if (strcasecmp("Slave", child->key) == 0)
997       /* Don't set status: Gracefully continue if a slave fails. */
998       mb_config_add_slave(host, child);
999     else {
1000       ERROR("Modbus plugin: Unknown configuration option: %s", child->key);
1001       status = -1;
1002     }
1003
1004     if (status != 0)
1005       break;
1006   } /* for (i = 0; i < ci->children_num; i++) */
1007
1008   assert(host->host[0] != 0);
1009   if (host->node[0] == 0) {
1010     ERROR("Modbus plugin: Data block \"%s\": No address or device has been "
1011           "specified.",
1012           host->host);
1013     status = -1;
1014   }
1015   if (host->conntype == MBCONN_RTU && !host->baudrate) {
1016     ERROR("Modbus plugin: Data block \"%s\": No serial baudrate has been "
1017           "specified.",
1018           host->host);
1019     status = -1;
1020   }
1021   if ((host->conntype == MBCONN_TCP && host->baudrate) ||
1022       (host->conntype == MBCONN_RTU && host->port)) {
1023     ERROR("Modbus plugin: Data block \"%s\": You've mixed up RTU and TCP "
1024           "options.",
1025           host->host);
1026     status = -1;
1027   }
1028
1029   if (status == 0) {
1030     char name[1024];
1031
1032     snprintf(name, sizeof(name), "modbus-%s", host->host);
1033
1034     plugin_register_complex_read(/* group = */ NULL, name,
1035                                  /* callback = */ mb_read,
1036                                  /* interval = */ host->interval,
1037                                  &(user_data_t){
1038                                      .data = host, .free_func = host_free,
1039                                  });
1040   } else {
1041     host_free(host);
1042   }
1043
1044   return status;
1045 } /* }}} int mb_config_add_host */
1046
1047 static int mb_config(oconfig_item_t *ci) /* {{{ */
1048 {
1049   if (ci == NULL)
1050     return EINVAL;
1051
1052   for (int i = 0; i < ci->children_num; i++) {
1053     oconfig_item_t *child = ci->children + i;
1054
1055     if (strcasecmp("Data", child->key) == 0)
1056       mb_config_add_data(child);
1057     else if (strcasecmp("Host", child->key) == 0)
1058       mb_config_add_host(child);
1059     else
1060       ERROR("Modbus plugin: Unknown configuration option: %s", child->key);
1061   }
1062
1063   return 0;
1064 } /* }}} int mb_config */
1065
1066 /* ========= */
1067
1068 static int mb_shutdown(void) /* {{{ */
1069 {
1070   data_free_all(data_definitions);
1071   data_definitions = NULL;
1072
1073   return 0;
1074 } /* }}} int mb_shutdown */
1075
1076 void module_register(void) {
1077   plugin_register_complex_config("modbus", mb_config);
1078   plugin_register_shutdown("modbus", mb_shutdown);
1079 } /* void module_register */