From: Florian Forster Date: Wed, 24 Mar 2010 09:08:51 +0000 (+0100) Subject: Merge branch 'master' into ff/modbus X-Git-Tag: collectd-4.10.0~28^2~7 X-Git-Url: https://git.octo.it/?a=commitdiff_plain;h=d86f0d115cc8e8d80167b65a24aa6ee7404bc766;hp=f4c25b4b234ba63fb9b15c2219d55d8af3e3f39d;p=collectd.git Merge branch 'master' into ff/modbus --- diff --git a/configure.in b/configure.in index 11935835..1a63dedd 100644 --- a/configure.in +++ b/configure.in @@ -1846,6 +1846,98 @@ fi AM_CONDITIONAL(BUILD_WITH_LIBMEMCACHED, test "x$with_libmemcached" = "xyes") # }}} +# --with-libmodbus {{{ +with_libmodbus_config="" +with_libmodbus_cflags="" +with_libmodbus_libs="" +AC_ARG_WITH(libmodbus, [AS_HELP_STRING([--with-libmodbus@<:@=PREFIX@:>@], [Path to the modbus library.])], +[ + if test "x$withval" = "xno" + then + with_libmodbus="no" + else if test "x$withval" = "xyes" + then + with_libmodbus="use_pkgconfig" + else if test -d "$with_libmodbus/lib" + then + AC_MSG_NOTICE([Not checking for libmodbus: Manually configured]) + with_libmodbus_cflags="-I$withval/include" + with_libmodbus_libs="-L$withval/lib -lmodbus" + with_libmodbus="yes" + fi; fi; fi +], +[with_libmodbus="use_pkgconfig"]) + +# configure using pkg-config +if test "x$with_libmodbus" = "xuse_pkgconfig" +then + if test "x$PKG_CONFIG" = "x" + then + with_libmodbus="no (Don't have pkg-config)" + fi +fi +if test "x$with_libmodbus" = "xuse_pkgconfig" +then + AC_MSG_NOTICE([Checking for modbus using $PKG_CONFIG]) + $PKG_CONFIG --exists 'modbus' 2>/dev/null + if test $? -ne 0 + then + with_libmodbus="no (pkg-config doesn't know library)" + fi +fi +if test "x$with_libmodbus" = "xuse_pkgconfig" +then + with_libmodbus_cflags="`$PKG_CONFIG --cflags 'modbus'`" + if test $? -ne 0 + then + with_libmodbus="no ($PKG_CONFIG failed)" + fi + with_libmodbus_libs="`$PKG_CONFIG --libs 'modbus'`" + if test $? -ne 0 + then + with_libmodbus="no ($PKG_CONFIG failed)" + fi +fi +if test "x$with_libmodbus" = "xuse_pkgconfig" +then + with_libmodbus="yes" +fi + +# with_libmodbus_cflags and with_libmodbus_libs are set up now, let's do +# the actual checks. +if test "x$with_libmodbus" = "xyes" +then + SAVE_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $with_libmodbus_cflags" + + AC_CHECK_HEADERS(modbus/modbus.h, [], [with_libmodbus="no (modbus/modbus.h not found)"]) + + CPPFLAGS="$SAVE_CPPFLAGS" +fi +if test "x$with_libmodbus" = "xyes" +then + SAVE_CPPFLAGS="$CPPFLAGS" + SAVE_LDFLAGS="$LDFLAGS" + + CPPFLAGS="$CPPFLAGS $with_libmodbus_cflags" + LDFLAGS="$LDFLAGS $with_libmodbus_libs" + + AC_CHECK_LIB(modbus, modbus_init_tcp, + [with_libmodbus="yes"], + [with_libmodbus="no (symbol modbus_init_tcp not found)"]) + + CPPFLAGS="$SAVE_CPPFLAGS" + LDFLAGS="$SAVE_LDFLAGS" +fi +if test "x$with_libmodbus" = "xyes" +then + BUILD_WITH_LIBMODBUS_CFLAGS="$with_libmodbus_cflags" + BUILD_WITH_LIBMODBUS_LIBS="$with_libmodbus_libs" + AC_SUBST(BUILD_WITH_LIBMODBUS_CFLAGS) + AC_SUBST(BUILD_WITH_LIBMODBUS_LIBS) +fi +# }}} + # --with-libmysql {{{ with_mysql_config="mysql_config" with_mysql_cflags="" @@ -4171,6 +4263,7 @@ AC_PLUGIN([mbmon], [yes], [Query mbmond]) AC_PLUGIN([memcachec], [$with_libmemcached], [memcachec statistics]) AC_PLUGIN([memcached], [yes], [memcached statistics]) AC_PLUGIN([memory], [$plugin_memory], [Memory usage]) +AC_PLUGIN([modbus], [$with_libmodbus], [Modbus plugin]) AC_PLUGIN([multimeter], [$plugin_multimeter], [Read multimeter values]) AC_PLUGIN([mysql], [$with_libmysql], [MySQL statistics]) AC_PLUGIN([netapp], [$with_libnetapp], [NetApp plugin]) @@ -4404,6 +4497,7 @@ Configuration: libkstat . . . . . . $with_kstat libkvm . . . . . . . $with_libkvm libmemcached . . . . $with_libmemcached + libmodbus . . . . . . $with_libmodbus libmysql . . . . . . $with_libmysql libnetapp . . . . . . $with_libnetapp libnetlink . . . . . $with_libnetlink @@ -4482,6 +4576,7 @@ Configuration: memcachec . . . . . . $enable_memcachec memcached . . . . . . $enable_memcached memory . . . . . . . $enable_memory + modbus . . . . . . . $enable_modbus multimeter . . . . . $enable_multimeter mysql . . . . . . . . $enable_mysql netapp . . . . . . . $enable_netapp diff --git a/src/Makefile.am b/src/Makefile.am index 02563509..68db92eb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -598,6 +598,16 @@ memory_la_LIBADD += -lperfstat endif endif +if BUILD_PLUGIN_MODBUS +pkglib_LTLIBRARIES += modbus.la +modbus_la_SOURCES = modbus.c +modbus_la_LDFLAGS = -module -avoid-version +modbus_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBMODBUS_CFLAGS) +modbus_la_LIBADD = $(BUILD_WITH_LIBMODBUS_LIBS) +collectd_LDADD += "-dlopen" modbus.la +collectd_DEPENDENCIES += modbus.la +endif + if BUILD_PLUGIN_MULTIMETER pkglib_LTLIBRARIES += multimeter.la multimeter_la_SOURCES = multimeter.c diff --git a/src/modbus.c b/src/modbus.c new file mode 100644 index 00000000..52e6bd89 --- /dev/null +++ b/src/modbus.c @@ -0,0 +1,818 @@ +/** + * collectd - src/modbus.c + * Copyright (C) 2010 noris network AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Florian Forster + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" +#include "configfile.h" + +#include + +/* + * + * RegisterBase 1234 + * RegisterType float + * Type gauge + * Instance "..." + * + * + * + * Address "addr" + * Port "1234" + * Interval 60 + * + * + * Instance "foobar" # optional + * Collect "data_name" + * + * + */ + +/* + * Data structures + */ +enum mb_register_type_e +{ + REG_TYPE_UINT16, + REG_TYPE_UINT32, + REG_TYPE_FLOAT +}; +typedef enum mb_register_type_e mb_register_type_t; + +struct mb_data_s; +typedef struct mb_data_s mb_data_t; +struct mb_data_s +{ + char *name; + int register_base; + mb_register_type_t register_type; + char type[DATA_MAX_NAME_LEN]; + char instance[DATA_MAX_NAME_LEN]; + + mb_data_t *next; +}; + +struct mb_slave_s +{ + int id; + char instance[DATA_MAX_NAME_LEN]; + mb_data_t *collect; +}; +typedef struct mb_slave_s mb_slave_t; + +struct mb_host_s +{ + char host[DATA_MAX_NAME_LEN]; + char node[NI_MAXHOST]; + /* char service[NI_MAXSERV]; */ + int port; + int interval; + + mb_slave_t *slaves; + size_t slaves_num; + + modbus_param_t connection; + _Bool is_connected; + _Bool have_reconnected; +}; +typedef struct mb_host_s mb_host_t; + +struct mb_data_group_s; +typedef struct mb_data_group_s mb_data_group_t; +struct mb_data_group_s +{ + mb_data_t *registers; + size_t registers_num; + + mb_data_group_t *next; +}; + +/* + * Global variables + */ +static mb_data_t *data_definitions = NULL; + +/* + * Functions + */ +static mb_data_t *data_get_by_name (mb_data_t *src, /* {{{ */ + const char *name) +{ + mb_data_t *ptr; + + if (name == NULL) + return (NULL); + + for (ptr = src; ptr != NULL; ptr = ptr->next) + if (strcasecmp (ptr->name, name) == 0) + return (ptr); + + return (NULL); +} /* }}} mb_data_t *data_get_by_name */ + +static int data_append (mb_data_t **dst, mb_data_t *src) /* {{{ */ +{ + mb_data_t *ptr; + + if ((dst == NULL) || (src == NULL)) + return (EINVAL); + + ptr = *dst; + + if (ptr == NULL) + { + *dst = src; + return (0); + } + + while (ptr->next != NULL) + ptr = ptr->next; + + ptr->next = src; + + return (0); +} /* }}} int data_append */ + +/* Copy a single mb_data_t and append it to another list. */ +static int data_copy (mb_data_t **dst, const mb_data_t *src) /* {{{ */ +{ + mb_data_t *tmp; + int status; + + if ((dst == NULL) || (src == NULL)) + return (EINVAL); + + tmp = malloc (sizeof (*tmp)); + if (tmp == NULL) + return (ENOMEM); + memcpy (tmp, src, sizeof (*tmp)); + tmp->name = NULL; + tmp->next = NULL; + + tmp->name = strdup (src->name); + if (tmp->name == NULL) + { + sfree (tmp); + return (ENOMEM); + } + + status = data_append (dst, tmp); + if (status != 0) + { + sfree (tmp->name); + sfree (tmp); + return (status); + } + + return (0); +} /* }}} int data_copy */ + +/* Lookup a single mb_data_t instance, copy it and append the copy to another + * list. */ +static int data_copy_by_name (mb_data_t **dst, mb_data_t *src, /* {{{ */ + const char *name) +{ + mb_data_t *ptr; + + if ((dst == NULL) || (src == NULL) || (name == NULL)) + return (EINVAL); + + ptr = data_get_by_name (src, name); + if (ptr == NULL) + return (ENOENT); + + return (data_copy (dst, ptr)); +} /* }}} int data_copy_by_name */ + +/* Read functions */ + +static float mb_register_to_float (uint16_t hi, uint16_t lo) /* {{{ */ +{ + union + { + uint8_t b[4]; + float f; + } conv; + +#if BYTE_ORDER == LITTLE_ENDIAN + /* little endian */ + conv.b[0] = lo & 0x00ff; + conv.b[1] = (lo >> 8) & 0x00ff; + conv.b[2] = hi & 0x00ff; + conv.b[3] = (hi >> 8) & 0x00ff; +#else + conv.b[3] = lo & 0x00ff; + conv.b[2] = (lo >> 8) & 0x00ff; + conv.b[1] = hi & 0x00ff; + conv.b[0] = (hi >> 8) & 0x00ff; +#endif + + return (conv.f); +} /* }}} float mb_register_to_float */ + +static int mb_init_connection (mb_host_t *host) /* {{{ */ +{ + int status; + + if (host == NULL) + return (EINVAL); + + if (host->is_connected) + return (0); + + /* Only reconnect once per interval. */ + if (host->have_reconnected) + return (-1); + + modbus_set_debug (&host->connection, 1); + + /* We'll do the error handling ourselves. */ + modbus_set_error_handling (&host->connection, NOP_ON_ERROR); + + if ((host->port < 1) || (host->port > 65535)) + host->port = MODBUS_TCP_DEFAULT_PORT; + + DEBUG ("Modbus plugin: Trying to connect to \"%s\", port %i.", + host->node, host->port); + + modbus_init_tcp (&host->connection, + /* host = */ host->node, + /* port = */ host->port); + + status = modbus_connect (&host->connection); + if (status != 0) + { + ERROR ("Modbus plugin: modbus_connect (%s, %i) failed with status %i.", + host->node, host->port, status); + return (status); + } + + host->is_connected = 1; + host->have_reconnected = 1; + return (0); +} /* }}} int mb_init_connection */ + +static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */ + mb_data_t *data) +{ + uint16_t values[2]; + int values_num; + const data_set_t *ds; + int status; + int i; + + if ((host == NULL) || (slave == NULL) || (data == NULL)) + return (EINVAL); + + ds = plugin_get_ds (data->type); + if (ds == NULL) + { + ERROR ("Modbus plugin: Type \"%s\" is not defined.", data->type); + return (-1); + } + + if (ds->ds_num != 1) + { + ERROR ("Modbus plugin: The type \"%s\" has %i data sources. " + "I can only handle data sets with only one data source.", + data->type, ds->ds_num); + return (-1); + } + + memset (values, 0, sizeof (values)); + if ((data->register_type == REG_TYPE_UINT32) + || (data->register_type == REG_TYPE_FLOAT)) + values_num = 2; + else + values_num = 1; + + for (i = 0; i < 2; i++) + { + status = read_holding_registers (&host->connection, + /* slave = */ slave->id, /* start_addr = */ data->register_base, + /* num_registers = */ values_num, /* buffer = */ values); + if (status > 0) + break; + + if (host->is_connected) + modbus_close (&host->connection); + host->is_connected = 0; + + /* If we already tried reconnecting this round, give up. */ + if (host->have_reconnected) + { + ERROR ("Modbus plugin: read_holding_registers (%s) failed. " + "Reconnecting has already been tried. Giving up.", host->host); + return (-1); + } + + /* Maybe the device closed the connection during the waiting interval. + * Try re-establishing the connection. */ + status = mb_init_connection (host); + if (status != 0) + { + ERROR ("Modbus plugin: read_holding_registers (%s) failed. " + "While trying to reconnect, connecting to \"%s\" failed. " + "Giving up.", + host->host, host->node); + return (-1); + } + + DEBUG ("Modbus plugin: Re-established connection to %s", host->host); + + /* try again */ + continue; + } /* for (i = 0, 1) */ + + DEBUG ("Modbus plugin: mb_read_data: Success! " + "read_holding_registers returned with status %i.", status); + + if (data->register_type == REG_TYPE_FLOAT) + { + float value; + + value = mb_register_to_float (values[0], values[1]); + DEBUG ("Modbus plugin: mb_read_data: " + "Returned float value is %g", (double) value); + } + + return (0); +} /* }}} int mb_read_data */ + +static int mb_read_slave (mb_host_t *host, mb_slave_t *slave) /* {{{ */ +{ + mb_data_t *data; + int success; + int status; + + if ((host == NULL) || (slave == NULL)) + return (EINVAL); + + success = 0; + for (data = slave->collect; data != NULL; data = data->next) + { + status = mb_read_data (host, slave, data); + if (status == 0) + success++; + } + + if (success == 0) + return (-1); + else + return (0); +} /* }}} int mb_read_slave */ + +static int mb_read (user_data_t *user_data) /* {{{ */ +{ + mb_host_t *host; + size_t i; + int success; + int status; + + if ((user_data == NULL) || (user_data->data == NULL)) + return (EINVAL); + + host = user_data->data; + + /* Clear the reconnect flag. */ + host->have_reconnected = 0; + + success = 0; + for (i = 0; i < host->slaves_num; i++) + { + status = mb_read_slave (host, host->slaves + i); + if (status == 0) + success++; + } + + if (success == 0) + return (-1); + else + return (0); +} /* }}} int mb_read */ + +/* Free functions */ + +static void data_free_one (mb_data_t *data) /* {{{ */ +{ + if (data == NULL) + return; + + sfree (data->name); + sfree (data); +} /* }}} void data_free_one */ + +static void data_free_all (mb_data_t *data) /* {{{ */ +{ + mb_data_t *next; + + if (data == NULL) + return; + + next = data->next; + data_free_one (data); + + data_free_all (next); +} /* }}} void data_free_all */ + +static void slaves_free_all (mb_slave_t *slaves, size_t slaves_num) /* {{{ */ +{ + size_t i; + + if (slaves == NULL) + return; + + for (i = 0; i < slaves_num; i++) + data_free_all (slaves[i].collect); + sfree (slaves); +} /* }}} void slaves_free_all */ + +static void host_free (void *void_host) /* {{{ */ +{ + mb_host_t *host = void_host; + + if (host == NULL) + return; + + slaves_free_all (host->slaves, host->slaves_num); + sfree (host); +} /* }}} void host_free */ + +/* Config functions */ + +static int mb_config_add_data (oconfig_item_t *ci) /* {{{ */ +{ + mb_data_t data; + int status; + int i; + + memset (&data, 0, sizeof (data)); + data.name = NULL; + data.register_type = REG_TYPE_UINT16; + data.next = NULL; + + status = cf_util_get_string (ci, &data.name); + if (status != 0) + return (status); + + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *child = ci->children + i; + status = 0; + + if (strcasecmp ("Type", child->key) == 0) + status = cf_util_get_string_buffer (child, + data.type, sizeof (data.type)); + else if (strcasecmp ("Instance", child->key) == 0) + status = cf_util_get_string_buffer (child, + data.instance, sizeof (data.instance)); + else if (strcasecmp ("RegisterBase", child->key) == 0) + status = cf_util_get_int (child, &data.register_base); + else if (strcasecmp ("RegisterType", child->key) == 0) + { + char tmp[16]; + status = cf_util_get_string_buffer (child, tmp, sizeof (tmp)); + if (status != 0) + /* do nothing */; + else if (strcasecmp ("Uint16", tmp) == 0) + data.register_type = REG_TYPE_UINT16; + else if (strcasecmp ("Uint32", tmp) == 0) + data.register_type = REG_TYPE_UINT32; + else if (strcasecmp ("Float", tmp) == 0) + data.register_type = REG_TYPE_FLOAT; + else + { + ERROR ("Modbus plugin: The register type \"%s\" is unknown.", tmp); + status = -1; + } + } + else + { + ERROR ("Modbus plugin: Unknown configuration option: %s", child->key); + status = -1; + } + + if (status != 0) + break; + } /* for (i = 0; i < ci->children_num; i++) */ + + assert (data.name != NULL); + if (data.type[0] == 0) + { + ERROR ("Modbus plugin: Data block \"%s\": No type has been specified.", + data.name); + status = -1; + } + + if (status == 0) + data_copy (&data_definitions, &data); + + sfree (data.name); + + return (status); +} /* }}} int mb_config_add_data */ + +static int mb_config_set_host_address (mb_host_t *host, /* {{{ */ + const char *address) +{ + struct addrinfo *ai_list; + struct addrinfo *ai_ptr; + struct addrinfo ai_hints; + int status; + + if ((host == NULL) || (address == NULL)) + return (EINVAL); + + memset (&ai_hints, 0, sizeof (ai_hints)); +#if AI_ADDRCONFIG + ai_hints.ai_flags |= AI_ADDRCONFIG; +#endif + /* XXX: libmodbus can only handle IPv4 addresses. */ + ai_hints.ai_family = AF_INET; + ai_hints.ai_addr = NULL; + ai_hints.ai_canonname = NULL; + ai_hints.ai_next = NULL; + + ai_list = NULL; + status = getaddrinfo (address, /* service = */ NULL, + &ai_hints, &ai_list); + if (status != 0) + { + char errbuf[1024]; + ERROR ("Modbus plugin: getaddrinfo failed: %s", + (status == EAI_SYSTEM) + ? sstrerror (errno, errbuf, sizeof (errbuf)) + : gai_strerror (status)); + return (status); + } + + for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) + { + status = getnameinfo (ai_ptr->ai_addr, ai_ptr->ai_addrlen, + host->node, sizeof (host->node), + /* service = */ NULL, /* length = */ 0, + /* flags = */ NI_NUMERICHOST); + if (status == 0) + break; + } /* for (ai_ptr) */ + + freeaddrinfo (ai_list); + + if (status != 0) + ERROR ("Modbus plugin: Unable to translate node name: \"%s\"", address); + else /* if (status == 0) */ + { + DEBUG ("Modbus plugin: mb_config_set_host_address: %s -> %s", + address, host->node); + } + + return (status); +} /* }}} int mb_config_set_host_address */ + +static int mb_config_add_slave (mb_host_t *host, oconfig_item_t *ci) /* {{{ */ +{ + mb_slave_t *slave; + int status; + int i; + + if ((host == NULL) || (ci == NULL)) + return (EINVAL); + + slave = realloc (host->slaves, sizeof (*slave) * (host->slaves_num + 1)); + if (slave == NULL) + return (ENOMEM); + host->slaves = slave; + slave = host->slaves + host->slaves_num; + memset (slave, 0, sizeof (*slave)); + slave->collect = NULL; + + status = cf_util_get_int (ci, &slave->id); + if (status != 0) + return (status); + + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *child = ci->children + i; + status = 0; + + if (strcasecmp ("Instance", child->key) == 0) + status = cf_util_get_string_buffer (child, + slave->instance, sizeof (slave->instance)); + else if (strcasecmp ("Collect", child->key) == 0) + { + char buffer[1024]; + status = cf_util_get_string_buffer (child, buffer, sizeof (buffer)); + if (status == 0) + data_copy_by_name (&slave->collect, data_definitions, buffer); + status = 0; /* continue after failure. */ + } + else + { + ERROR ("Modbus plugin: Unknown configuration option: %s", child->key); + status = -1; + } + + if (status != 0) + break; + } + + if ((status == 0) && (slave->collect == NULL)) + status = EINVAL; + + if (slave->id < 0) + status = EINVAL; + + if (status == 0) + host->slaves_num++; + else /* if (status != 0) */ + data_free_all (slave->collect); + + return (status); +} /* }}} int mb_config_add_slave */ + +static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */ +{ + mb_host_t *host; + int status; + int i; + + host = malloc (sizeof (*host)); + if (host == NULL) + return (ENOMEM); + memset (host, 0, sizeof (*host)); + host->slaves = NULL; + + status = cf_util_get_string_buffer (ci, host->host, sizeof (host->host)); + if (status != 0) + return (status); + if (host->host[0] == 0) + return (EINVAL); + + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *child = ci->children + i; + status = 0; + + if (strcasecmp ("Address", child->key) == 0) + { + char buffer[NI_MAXHOST]; + status = cf_util_get_string_buffer (child, buffer, sizeof (buffer)); + if (status == 0) + status = mb_config_set_host_address (host, buffer); + } + else if (strcasecmp ("Port", child->key) == 0) + { + host->port = cf_util_get_port_number (child); + if (host->port <= 0) + status = -1; + } + else if (strcasecmp ("Interval", child->key) == 0) + status = cf_util_get_int (child, &host->interval); + else if (strcasecmp ("Slave", child->key) == 0) + /* Don't set status: Gracefully continue if a slave fails. */ + mb_config_add_slave (host, child); + else + { + ERROR ("Modbus plugin: Unknown configuration option: %s", child->key); + status = -1; + } + + if (status != 0) + break; + } /* for (i = 0; i < ci->children_num; i++) */ + + assert (host->host[0] != 0); + if (host->host[0] == 0) + { + ERROR ("Modbus plugin: Data block \"%s\": No type has been specified.", + host->host); + status = -1; + } + + if (status == 0) + { + user_data_t ud; + char name[1024]; + struct timespec interval; + + ud.data = host; + ud.free_func = host_free; + + ssnprintf (name, sizeof (name), "modbus-%s", host->host); + + interval.tv_nsec = 0; + if (host->interval > 0) + interval.tv_sec = host->interval; + else + interval.tv_sec = 0; + + plugin_register_complex_read (name, mb_read, + (interval.tv_sec > 0) ? &interval : NULL, + &ud); + } + else + { + host_free (host); + } + + return (status); +} /* }}} int mb_config_add_host */ + +static int mb_config (oconfig_item_t *ci) /* {{{ */ +{ + int i; + + if (ci == NULL) + return (EINVAL); + + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *child = ci->children + i; + + if (strcasecmp ("Data", child->key) == 0) + mb_config_add_data (child); + else if (strcasecmp ("Host", child->key) == 0) + mb_config_add_host (child); + else + ERROR ("Modbus plugin: Unknown configuration option: %s", child->key); + } + + return (0); +} /* }}} int mb_config */ + +/* ========= */ + +#if 0 +static int foo (void) /* {{{ */ +{ + int status; + uint16_t values[2]; + int values_num; + + if (dev == NULL) + return (EINVAL); + + printf ("mb_read (addr = %i, float = %s);\n", register_addr, + is_float ? "true" : "false"); + + memset (values, 0, sizeof (values)); + if (is_float) + values_num = 2; + else + values_num = 1; + + status = read_holding_registers (dev->connection, + /* slave = */ 1, /* start_addr = */ register_addr, + /* num_registers = */ values_num, /* buffer = */ values); + printf ("read_coil_status returned with status %i\n", status); + if (status <= 0) + return (EAGAIN); + + if (is_float) + { + float value = mb_register_to_float (values[0], values[1]); + printf ("read_coil_status returned value %g (hi %#"PRIx16", lo %#"PRIx16")\n", + value, values[0], values[1]); + } + else + { + printf ("read_coil_status returned value %"PRIu16"\n", values[0]); + } + + return (0); +} /* }}} int foo */ +#endif + +static int mb_shutdown (void) /* {{{ */ +{ + data_free_all (data_definitions); + data_definitions = NULL; + + return (0); +} /* }}} int mb_shutdown */ + +void module_register (void) +{ + plugin_register_complex_config ("modbus", mb_config); + plugin_register_shutdown ("modbus", mb_shutdown); +} /* void module_register */ + +/* vim: set sw=2 sts=2 et fdm=marker : */