/**
* collectd - src/modbus.c
* Copyright (C) 2010 noris network AG
+ * Copyright (C) 2011 Universiteit Gent
*
* 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.
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; only version 2.1 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.
+ * Lesser 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
+ * You should have received a copy of the GNU Lesser 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 <octo at noris.net>
+ * Ivo De Decker <ivo.dedecker at ugent.be>
**/
#include "collectd.h"
#include <modbus/modbus.h>
+#ifndef LIBMODBUS_VERSION_CHECK
+/* Assume version 2.0.3 */
+# define LEGACY_LIBMODBUS 1
+#else
+/* Assume version 2.9.2 */
+#endif
+
#ifndef MODBUS_TCP_DEFAULT_PORT
# ifdef MODBUS_TCP_PORT
# define MODBUS_TCP_DEFAULT_PORT MODBUS_TCP_PORT
mb_data_t *next;
}; /* }}} */
+struct mb_datagroup_s;
+typedef struct mb_datagroup_s mb_datagroup_t;
+struct mb_datagroup_s /* {{{ */
+{
+ char *name;
+ mb_data_t *collect;
+
+ mb_datagroup_t *next;
+}; /* }}} */
+
struct mb_slave_s /* {{{ */
{
int id;
char node[NI_MAXHOST];
/* char service[NI_MAXSERV]; */
int port;
- int interval;
+ cdtime_t interval;
mb_slave_t *slaves;
size_t slaves_num;
+#if LEGACY_LIBMODBUS
modbus_param_t connection;
+#else
+ modbus_t *connection;
+#endif
_Bool is_connected;
_Bool have_reconnected;
}; /* }}} */
* Global variables
*/
static mb_data_t *data_definitions = NULL;
+static mb_datagroup_t *data_groups = NULL;
/*
* Functions
return (data_copy (dst, ptr));
} /* }}} int data_copy_by_name */
+static mb_datagroup_t *datagroup_get_by_name (mb_datagroup_t *src, /* {{{ */
+ const char *name)
+{
+ mb_datagroup_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_datagroup_t *datagroup_get_by_name */
+
+static int datagroup_append (mb_datagroup_t **dst, mb_datagroup_t *src) /* {{{ */
+{
+ mb_datagroup_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 datagroup_append */
+
/* Read functions */
static int mb_submit (mb_host_t *host, mb_slave_t *slave, /* {{{ */
return (conv.f);
} /* }}} float mb_register_to_float */
+#if LEGACY_LIBMODBUS
+/* Version 2.0.3 */
static int mb_init_connection (mb_host_t *host) /* {{{ */
{
int status;
modbus_set_debug (&host->connection, 1);
-#if 0
/* We'll do the error handling ourselves. */
modbus_set_error_handling (&host->connection, NOP_ON_ERROR);
-#endif
if ((host->port < 1) || (host->port > 65535))
host->port = MODBUS_TCP_DEFAULT_PORT;
host->node, host->port);
modbus_init_tcp (&host->connection,
- /* host = */ host->node);
-#if 0
+ /* host = */ host->node,
/* port = */ host->port);
-#endif
status = modbus_connect (&host->connection);
if (status != 0)
host->have_reconnected = 1;
return (0);
} /* }}} int mb_init_connection */
+/* #endif LEGACY_LIBMODBUS */
+
+#else /* if !LEGACY_LIBMODBUS */
+/* Version 2.9.2 */
+static int mb_init_connection (mb_host_t *host) /* {{{ */
+{
+ int status;
+
+ if (host == NULL)
+ return (EINVAL);
+
+ if (host->connection != NULL)
+ return (0);
+
+ /* Only reconnect once per interval. */
+ if (host->have_reconnected)
+ return (-1);
+
+ 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);
+
+ host->connection = modbus_new_tcp (host->node, host->port);
+ if (host->connection == NULL)
+ {
+ host->have_reconnected = 1;
+ ERROR ("Modbus plugin: Creating new Modbus/TCP object failed.");
+ return (-1);
+ }
+
+ modbus_set_debug (host->connection, 1);
+
+ /* We'll do the error handling ourselves. */
+ modbus_set_error_recovery (host->connection, 0);
+
+ status = modbus_connect (host->connection);
+ if (status != 0)
+ {
+ ERROR ("Modbus plugin: modbus_connect (%s, %i) failed with status %i.",
+ host->node, host->port, status);
+ modbus_free (host->connection);
+ host->connection = NULL;
+ return (status);
+ }
+
+ host->have_reconnected = 1;
+ return (0);
+} /* }}} int mb_init_connection */
+#endif /* !LEGACY_LIBMODBUS */
#define CAST_TO_VALUE_T(ds,vt,raw) do { \
if ((ds)->ds[0].type == DS_TYPE_COUNTER) \
static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
mb_data_t *data)
{
- int values[2];
+ uint16_t values[2];
int values_num;
const data_set_t *ds;
int status;
else
values_num = 1;
+#if LEGACY_LIBMODBUS
+ /* Version 2.0.3: Pass the connection struct as a pointer and pass the slave
+ * id to each call of "read_holding_registers". */
+# define modbus_read_registers(ctx, addr, nb, dest) \
+ read_holding_registers (&(ctx), slave->id, (addr), (nb), (dest))
+#else /* if !LEGACY_LIBMODBUS */
+ /* Version 2.9.2: Set the slave id once before querying the registers. */
+ status = modbus_set_slave (host->connection, slave->id);
+ if (status != 0)
+ {
+ ERROR ("Modbus plugin: modbus_set_slave (%i) failed with status %i.",
+ slave->id, status);
+ return (-1);
+ }
+#endif
+
for (i = 0; i < 2; i++)
{
- status = read_holding_registers (&host->connection,
- /* slave = */ slave->id, /* start_addr = */ data->register_base,
+ status = modbus_read_registers (host->connection,
+ /* start_addr = */ data->register_base,
/* num_registers = */ values_num, /* buffer = */ values);
if (status > 0)
break;
if (host->is_connected)
+ {
+#if LEGACY_LIBMODBUS
modbus_close (&host->connection);
- host->is_connected = 0;
+ host->is_connected = 0;
+#else
+ modbus_close (host->connection);
+ modbus_free (host->connection);
+ host->connection = NULL;
+#endif
+ }
/* If we already tried reconnecting this round, give up. */
if (host->have_reconnected)
{
- ERROR ("Modbus plugin: read_holding_registers (%s) failed. "
+ ERROR ("Modbus plugin: modbus_read_registers (%s) failed. "
"Reconnecting has already been tried. Giving up.", host->host);
return (-1);
}
status = mb_init_connection (host);
if (status != 0)
{
- ERROR ("Modbus plugin: read_holding_registers (%s) failed. "
+ ERROR ("Modbus plugin: modbus_read_registers (%s) failed. "
"While trying to reconnect, connecting to \"%s\" failed. "
"Giving up.",
host->host, host->node);
} /* for (i = 0, 1) */
DEBUG ("Modbus plugin: mb_read_data: Success! "
- "read_holding_registers returned with status %i.", status);
+ "modbus_read_registers returned with status %i.", status);
if (data->register_type == REG_TYPE_FLOAT)
{
data_copy_by_name (&slave->collect, data_definitions, buffer);
status = 0; /* continue after failure. */
}
+ else if (strcasecmp ("Datagroup", child->key) == 0)
+ {
+ char buffer[1024];
+ mb_datagroup_t *ds;
+
+ status = cf_util_get_string_buffer (child, buffer, sizeof (buffer));
+ if (status == 0) {
+ ds = datagroup_get_by_name (data_groups, buffer);
+ if (ds) {
+ mb_data_t *data;
+ for (data = ds->collect; data != NULL; data = data->next)
+ {
+ data_copy (&slave->collect, data);
+ }
+ }
+ }
+ status = 0; /* continue after failure. */
+ }
else
{
ERROR ("Modbus plugin: Unknown configuration option: %s", child->key);
status = -1;
}
else if (strcasecmp ("Interval", child->key) == 0)
- status = cf_util_get_int (child, &host->interval);
+ status = cf_util_get_cdtime (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);
{
user_data_t ud;
char name[1024];
- struct timespec interval;
+ struct timespec interval = { 0, 0 };
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;
+ CDTIME_T_TO_TIMESPEC (host->interval, &interval);
plugin_register_complex_read (/* group = */ NULL, name,
- mb_read, (interval.tv_sec > 0) ? &interval : NULL, &ud);
+ /* callback = */ mb_read,
+ /* interval = */ (host->interval > 0) ? &interval : NULL,
+ &ud);
}
else
{
return (status);
} /* }}} int mb_config_add_host */
+
+static int mb_config_add_datagroup (oconfig_item_t *ci) /* {{{ */
+{
+ mb_datagroup_t *datagroup;
+ int status;
+ int i;
+
+ datagroup = malloc (sizeof (*datagroup));
+ if (datagroup == NULL)
+ return (ENOMEM);
+ memset (datagroup, 0, sizeof (*datagroup));
+ datagroup->name = NULL;
+ datagroup->collect = NULL;
+ datagroup->next = NULL;
+
+ status = cf_util_get_string (ci, &datagroup->name);
+
+ for (i = 0; i < ci->children_num; i++)
+ {
+ oconfig_item_t *child = ci->children + i;
+ status = 0;
+
+ 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 (&datagroup->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;
+ } /* for (i = 0; i < ci->children_num; i++) */
+
+ if ((status == 0) && (datagroup->collect == NULL))
+ status = ENOENT;
+
+ if (status == 0)
+ {
+ datagroup_append (&data_groups, datagroup);
+ }
+ else /* if (status != 0) */
+ {
+ sfree (datagroup->name);
+ data_free_all (datagroup->collect);
+ assert (datagroup->next == NULL);
+ sfree (datagroup);
+ }
+
+ return (status);
+} /* }}} int mb_config_add_datagroup */
+
static int mb_config (oconfig_item_t *ci) /* {{{ */
{
int i;
mb_config_add_data (child);
else if (strcasecmp ("Host", child->key) == 0)
mb_config_add_host (child);
+ else if (strcasecmp ("Datagroup", child->key) == 0)
+ mb_config_add_datagroup (child);
else
ERROR ("Modbus plugin: Unknown configuration option: %s", child->key);
}