modbus plugin: Fix a small memory leak in mb_config_add_datagroup().
[collectd.git] / src / modbus.c
index 656d9c8..2276572 100644 (file)
@@ -1,22 +1,25 @@
 /**
  * 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 "plugin.h"
 #include "configfile.h"
 
+#include <netdb.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
+# else
+#  define MODBUS_TCP_DEFAULT_PORT 502
+# endif
+#endif
+
 /*
  * <Data "data_name">
  *   RegisterBase 1234
@@ -70,6 +90,16 @@ struct mb_data_s /* {{{ */
   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;
@@ -84,12 +114,16 @@ struct mb_host_s /* {{{ */
   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;
 }; /* }}} */
@@ -109,6 +143,7 @@ struct mb_data_group_s /* {{{ */
  * Global variables
  */
 static mb_data_t *data_definitions = NULL;
+static mb_datagroup_t *data_groups = NULL;
 
 /*
  * Functions
@@ -202,6 +237,44 @@ static int data_copy_by_name (mb_data_t **dst, mb_data_t *src, /* {{{ */
   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, /* {{{ */
@@ -212,12 +285,16 @@ static int mb_submit (mb_host_t *host, mb_slave_t *slave, /* {{{ */
   if ((host == NULL) || (slave == NULL) || (data == NULL))
     return (EINVAL);
 
+  if (host->interval <= 0)
+    host->interval = interval_g;
+
   if (slave->instance[0] == 0)
     ssnprintf (slave->instance, sizeof (slave->instance), "slave_%i",
         slave->id);
 
   vl.values = &value;
   vl.values_len = 1;
+  vl.interval = host->interval;
   sstrncpy (vl.host, host->host, sizeof (vl.host));
   sstrncpy (vl.plugin, "modbus", sizeof (vl.plugin));
   sstrncpy (vl.plugin_instance, slave->instance, sizeof (vl.plugin_instance));
@@ -251,6 +328,8 @@ static float mb_register_to_float (uint16_t hi, uint16_t lo) /* {{{ */
   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;
@@ -292,6 +371,57 @@ static int mb_init_connection (mb_host_t *host) /* {{{ */
   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) \
@@ -346,22 +476,46 @@ static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
   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);
     }
@@ -371,7 +525,7 @@ static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
     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);
@@ -385,7 +539,7 @@ static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
   } /* 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)
   {
@@ -692,6 +846,24 @@ static int mb_config_add_slave (mb_host_t *host, oconfig_item_t *ci) /* {{{ */
         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);
@@ -753,7 +925,7 @@ static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */
         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);
@@ -779,21 +951,18 @@ static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */
   {
     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 (name, mb_read,
-        (interval.tv_sec > 0) ? &interval : NULL,
+    plugin_register_complex_read (/* group = */ NULL, name,
+        /* callback = */ mb_read,
+        /* interval = */ (host->interval > 0) ? &interval : NULL,
         &ud);
   }
   else
@@ -804,6 +973,65 @@ static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */
   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;
@@ -819,6 +1047,8 @@ static int mb_config (oconfig_item_t *ci) /* {{{ */
       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);
   }
@@ -828,47 +1058,6 @@ static int mb_config (oconfig_item_t *ci) /* {{{ */
 
 /* ========= */
 
-#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);