Code

Merge branch 'collectd-5.5' into collectd-5.6
[collectd.git] / src / modbus.c
index 7349dc564976b2a91aa44b7bb760e8d33a9bf4e0..473a4ee435230a96268eee789ce531ba2c56dbdc 100644 (file)
  **/
 
 #include "collectd.h"
+
 #include "common.h"
-#include "plugin.h"
 #include "configfile.h"
-
-#include <netdb.h>
+#include "plugin.h"
 
 #include <modbus.h>
+#include <netdb.h>
+#include <sys/socket.h>
 
 #ifndef LIBMODBUS_VERSION_CHECK
 /* Assume version 2.0.3 */
@@ -47,6 +48,7 @@
 /*
  * <Data "data_name">
  *   RegisterBase 1234
+ *   RegisterCmd ReadHolding
  *   RegisterType float
  *   Type gauge
  *   Instance "..."
  * <Host "name">
  *   Address "addr"
  *   Port "1234"
+ *   # Or:
+ *   # Device "/dev/ttyUSB0"
+ *   # Baudrate 38400
+ *   # (Assumes 8N1)
  *   Interval 60
  *
  *   <Slave 1>
@@ -75,7 +81,21 @@ enum mb_register_type_e /* {{{ */
   REG_TYPE_UINT32,
   REG_TYPE_FLOAT
 }; /* }}} */
+enum mb_mreg_type_e /* {{{ */
+{
+  MREG_HOLDING,
+  MREG_INPUT
+}; /* }}} */
 typedef enum mb_register_type_e mb_register_type_t;
+typedef enum mb_mreg_type_e mb_mreg_type_t;
+
+/* TCP or RTU depending on what is specified in host config block */
+enum mb_conntype_e /* {{{ */
+{
+  MBCONN_TCP,
+  MBCONN_RTU
+}; /* }}} */
+typedef enum mb_conntype_e mb_conntype_t;
 
 struct mb_data_s;
 typedef struct mb_data_s mb_data_t;
@@ -84,6 +104,7 @@ struct mb_data_s /* {{{ */
   char *name;
   int register_base;
   mb_register_type_t register_type;
+  mb_mreg_type_t modbus_register_type;
   char type[DATA_MAX_NAME_LEN];
   char instance[DATA_MAX_NAME_LEN];
 
@@ -101,9 +122,11 @@ typedef struct mb_slave_s mb_slave_t;
 struct mb_host_s /* {{{ */
 {
   char host[DATA_MAX_NAME_LEN];
-  char node[NI_MAXHOST];
+  char node[NI_MAXHOST];       /* TCP hostname or RTU serial device */
   /* char service[NI_MAXSERV]; */
-  int port;
+  int port;                    /* for Modbus/TCP */
+  int baudrate;                        /* for Modbus/RTU */
+  mb_conntype_t conntype;
   cdtime_t interval;
 
   mb_slave_t *slaves;
@@ -139,12 +162,10 @@ static mb_data_t *data_definitions = NULL;
 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)
+  for (mb_data_t *ptr = src; ptr != NULL; ptr = ptr->next)
     if (strcasecmp (ptr->name, name) == 0)
       return (ptr);
 
@@ -235,7 +256,7 @@ 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)
+  if (host->interval == 0)
     host->interval = plugin_get_interval ();
 
   if (slave->instance[0] == 0)
@@ -295,21 +316,33 @@ static int mb_init_connection (mb_host_t *host) /* {{{ */
   /* 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;
+  if (host->conntype == MBCONN_TCP)
+  {
+    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);
+    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);
+    modbus_init_tcp (&host->connection,
+        /* host = */ host->node,
+        /* port = */ host->port);
+  }
+  else /* MBCONN_RTU */
+  {
+    DEBUG ("Modbus plugin: Trying to connect to \"%s\".", host->node);
+
+    modbus_init_rtu (&host->connection,
+       /* device = */ host->node,
+     /* baudrate = */ host->baudrate,
+                      'N', 8, 1, 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);
+        host->node, host->port ? host->port : host->baudrate, status);
     return (status);
   }
 
@@ -330,17 +363,32 @@ static int mb_init_connection (mb_host_t *host) /* {{{ */
   if (host->connection != NULL)
     return (0);
 
-  if ((host->port < 1) || (host->port > 65535))
-    host->port = MODBUS_TCP_DEFAULT_PORT;
+  if (host->conntype == MBCONN_TCP)
+  {
+    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);
+    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->connection = modbus_new_tcp (host->node, host->port);
+    if (host->connection == NULL)
+    {
+      ERROR ("Modbus plugin: Creating new Modbus/TCP object failed.");
+      return (-1);
+    }
+  }
+  else
   {
-    ERROR ("Modbus plugin: Creating new Modbus/TCP object failed.");
-    return (-1);
+    DEBUG ("Modbus plugin: Trying to connect to \"%s\", baudrate %i.",
+        host->node, host->baudrate);
+
+    host->connection = modbus_new_rtu (host->node, host->baudrate, 'N', 8, 1);
+    if (host->connection == NULL)
+    {
+      ERROR ("Modbus plugin: Creating new Modbus/RTU object failed.");
+      return (-1);
+    }
   }
 
 #if COLLECT_DEBUG
@@ -354,7 +402,7 @@ static int mb_init_connection (mb_host_t *host) /* {{{ */
   if (status != 0)
   {
     ERROR ("Modbus plugin: modbus_connect (%s, %i) failed with status %i.",
-        host->node, host->port, status);
+        host->node, host->port ? host->port : host->baudrate, status);
     modbus_free (host->connection);
     host->connection = NULL;
     return (status);
@@ -378,7 +426,7 @@ static int mb_init_connection (mb_host_t *host) /* {{{ */
 static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
     mb_data_t *data)
 {
-  uint16_t values[2];
+  uint16_t values[2] = { 0 };
   int values_num;
   const data_set_t *ds;
   int status = 0;
@@ -395,7 +443,7 @@ static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
 
   if (ds->ds_num != 1)
   {
-    ERROR ("Modbus plugin: The type \"%s\" has %i data sources. "
+    ERROR ("Modbus plugin: The type \"%s\" has %zu data sources. "
         "I can only handle data sets with only one data source.",
         data->type, ds->ds_num);
     return (-1);
@@ -410,7 +458,6 @@ static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
         "is not UINT32.", data->type, DS_TYPE_TO_STRING (ds->ds[0].type));
   }
 
-  memset (values, 0, sizeof (values));
   if ((data->register_type == REG_TYPE_INT32)
       || (data->register_type == REG_TYPE_UINT32)
       || (data->register_type == REG_TYPE_FLOAT))
@@ -422,14 +469,11 @@ static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
   {
     status = EBADF;
   }
-  else
+  else if (host->conntype == MBCONN_TCP)
   {
-    struct sockaddr sockaddr;
-    socklen_t saddrlen = sizeof (sockaddr);
-
-    status = getpeername (modbus_get_socket (host->connection),
-        &sockaddr, &saddrlen);
-    if (status != 0)
+    if (getpeername (modbus_get_socket (host->connection),
+          (void *) &(struct sockaddr_storage) {0},
+          &(socklen_t) {sizeof(struct sockaddr_storage)}) != 0)
       status = errno;
   }
 
@@ -454,7 +498,7 @@ static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
     modbus_free (host->connection);
 #endif
   }
+
 #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". */
@@ -470,14 +514,21 @@ static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
     return (-1);
   }
 #endif
-
-  status = modbus_read_registers (host->connection,
+  if (data->modbus_register_type == MREG_INPUT){
+    status = modbus_read_input_registers (host->connection,
         /* start_addr = */ data->register_base,
         /* num_registers = */ values_num, /* buffer = */ values);
+  }
+  else{
+    status = modbus_read_registers (host->connection,
+        /* start_addr = */ data->register_base,
+        /* num_registers = */ values_num, /* buffer = */ values);
+  }
   if (status != values_num)
   {
-    ERROR ("Modbus plugin: modbus_read_registers (%s/%s) failed. status = %i, values_num = %i "
-        "Giving up.", host->host, host->node, status, values_num);
+    ERROR ("Modbus plugin: modbus read function (%s/%s) failed. "
+           " status = %i, values_num = %i. Giving up.",
+           host->host, host->node, status, values_num);
 #if LEGACY_LIBMODBUS
     modbus_close (&host->connection);
 #else
@@ -566,7 +617,6 @@ static int mb_read_data (mb_host_t *host, mb_slave_t *slave, /* {{{ */
 
 static int mb_read_slave (mb_host_t *host, mb_slave_t *slave) /* {{{ */
 {
-  mb_data_t *data;
   int success;
   int status;
 
@@ -574,7 +624,7 @@ static int mb_read_slave (mb_host_t *host, mb_slave_t *slave) /* {{{ */
     return (EINVAL);
 
   success = 0;
-  for (data = slave->collect; data != NULL; data = data->next)
+  for (mb_data_t *data = slave->collect; data != NULL; data = data->next)
   {
     status = mb_read_data (host, slave, data);
     if (status == 0)
@@ -590,7 +640,6 @@ static int mb_read_slave (mb_host_t *host, mb_slave_t *slave) /* {{{ */
 static int mb_read (user_data_t *user_data) /* {{{ */
 {
   mb_host_t *host;
-  size_t i;
   int success;
   int status;
 
@@ -600,7 +649,7 @@ static int mb_read (user_data_t *user_data) /* {{{ */
   host = user_data->data;
 
   success = 0;
-  for (i = 0; i < host->slaves_num; i++)
+  for (size_t i = 0; i < host->slaves_num; i++)
   {
     status = mb_read_slave (host, host->slaves + i);
     if (status == 0)
@@ -639,12 +688,10 @@ static void data_free_all (mb_data_t *data) /* {{{ */
 
 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++)
+  for (size_t i = 0; i < slaves_num; i++)
     data_free_all (slaves[i].collect);
   sfree (slaves);
 } /* }}} void slaves_free_all */
@@ -664,11 +711,9 @@ static void host_free (void *void_host) /* {{{ */
 
 static int mb_config_add_data (oconfig_item_t *ci) /* {{{ */
 {
-  mb_data_t data;
+  mb_data_t data = { 0 };
   int status;
-  int i;
 
-  memset (&data, 0, sizeof (data));
   data.name = NULL;
   data.register_type = REG_TYPE_UINT16;
   data.next = NULL;
@@ -677,7 +722,7 @@ static int mb_config_add_data (oconfig_item_t *ci) /* {{{ */
   if (status != 0)
     return (status);
 
-  for (i = 0; i < ci->children_num; i++)
+  for (int i = 0; i < ci->children_num; i++)
   {
     oconfig_item_t *child = ci->children + i;
 
@@ -711,6 +756,28 @@ static int mb_config_add_data (oconfig_item_t *ci) /* {{{ */
         status = -1;
       }
     }
+    else if (strcasecmp ("RegisterCmd", child->key) == 0)
+    {
+#if LEGACY_LIBMODBUS
+      ERROR("Modbus plugin: RegisterCmd parameter can not be used "
+            "with your libmodbus version");
+#else
+      char tmp[16];
+      status = cf_util_get_string_buffer (child, tmp, sizeof (tmp));
+      if (status != 0)
+        /* do nothing */;
+      else if (strcasecmp ("ReadHolding", tmp) == 0)
+        data.modbus_register_type = MREG_HOLDING;
+      else if (strcasecmp ("ReadInput", tmp) == 0)
+        data.modbus_register_type = MREG_INPUT;
+      else
+      {
+        ERROR ("Modbus plugin: The modbus_register_type \"%s\" is unknown.",
+               tmp);
+        status = -1;
+      }
+#endif
+    }
     else
     {
       ERROR ("Modbus plugin: Unknown configuration option: %s", child->key);
@@ -741,24 +808,17 @@ 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;
+  struct addrinfo  ai_hints = {
+    /* XXX: libmodbus can only handle IPv4 addresses. */
+    .ai_family = AF_INET,
+    .ai_flags = AI_ADDRCONFIG
+  };
 
-  ai_list = NULL;
   status = getaddrinfo (address, /* service = */ NULL,
       &ai_hints, &ai_list);
   if (status != 0)
@@ -771,7 +831,7 @@ static int mb_config_set_host_address (mb_host_t *host, /* {{{ */
     return (status);
   }
 
-  for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+  for (struct addrinfo *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),
@@ -798,7 +858,6 @@ 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);
@@ -815,7 +874,7 @@ static int mb_config_add_slave (mb_host_t *host, oconfig_item_t *ci) /* {{{ */
   if (status != 0)
     return (status);
 
-  for (i = 0; i < ci->children_num; i++)
+  for (int i = 0; i < ci->children_num; i++)
   {
     oconfig_item_t *child = ci->children + i;
 
@@ -858,12 +917,10 @@ static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */
 {
   mb_host_t *host;
   int status;
-  int i;
 
-  host = malloc (sizeof (*host));
+  host = calloc (1, 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));
@@ -878,7 +935,7 @@ static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */
     return (EINVAL);
   }
 
-  for (i = 0; i < ci->children_num; i++)
+  for (int i = 0; i < ci->children_num; i++)
   {
     oconfig_item_t *child = ci->children + i;
     status = 0;
@@ -889,6 +946,8 @@ static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */
       status = cf_util_get_string_buffer (child, buffer, sizeof (buffer));
       if (status == 0)
         status = mb_config_set_host_address (host, buffer);
+      if (status == 0)
+        host->conntype = MBCONN_TCP;
     }
     else if (strcasecmp ("Port", child->key) == 0)
     {
@@ -896,6 +955,14 @@ static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */
       if (host->port <= 0)
         status = -1;
     }
+    else if (strcasecmp ("Device", child->key) == 0)
+    {
+      status = cf_util_get_string_buffer (child, host->node, sizeof (host->node));
+      if (status == 0)
+        host->conntype = MBCONN_RTU;
+    }
+    else if (strcasecmp ("Baudrate", child->key) == 0)
+      status = cf_util_get_int(child, &host->baudrate);
     else if (strcasecmp ("Interval", child->key) == 0)
       status = cf_util_get_cdtime (child, &host->interval);
     else if (strcasecmp ("Slave", child->key) == 0)
@@ -912,9 +979,22 @@ static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */
   } /* for (i = 0; i < ci->children_num; i++) */
 
   assert (host->host[0] != 0);
-  if (host->host[0] == 0)
+  if (host->node[0] == 0)
   {
-    ERROR ("Modbus plugin: Data block \"%s\": No type has been specified.",
+    ERROR ("Modbus plugin: Data block \"%s\": No address or device has been specified.",
+        host->host);
+    status = -1;
+  }
+  if (host->conntype == MBCONN_RTU && !host->baudrate)
+  {
+    ERROR ("Modbus plugin: Data block \"%s\": No serial baudrate has been specified.",
+        host->host);
+    status = -1;
+  }
+  if ((host->conntype == MBCONN_TCP && host->baudrate) ||
+      (host->conntype == MBCONN_RTU && host->port))
+  {
+    ERROR ("Modbus plugin: Data block \"%s\": You've mixed up RTU and TCP options.",
         host->host);
     status = -1;
   }
@@ -923,18 +1003,15 @@ static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */
   {
     user_data_t ud;
     char name[1024];
-    struct timespec interval = { 0, 0 };
 
     ud.data = host;
     ud.free_func = host_free;
 
     ssnprintf (name, sizeof (name), "modbus-%s", host->host);
 
-    CDTIME_T_TO_TIMESPEC (host->interval, &interval);
-
     plugin_register_complex_read (/* group = */ NULL, name,
         /* callback = */ mb_read,
-        /* interval = */ (host->interval > 0) ? &interval : NULL,
+        /* interval = */ host->interval,
         &ud);
   }
   else
@@ -947,12 +1024,10 @@ static int mb_config_add_host (oconfig_item_t *ci) /* {{{ */
 
 static int mb_config (oconfig_item_t *ci) /* {{{ */
 {
-  int i;
-
   if (ci == NULL)
     return (EINVAL);
 
-  for (i = 0; i < ci->children_num; i++)
+  for (int i = 0; i < ci->children_num; i++)
   {
     oconfig_item_t *child = ci->children + i;