add Modbus/RTU support to modbus plugin
authorEric Sandeen <sandeen@sandeen.net>
Wed, 10 Dec 2014 21:38:09 +0000 (15:38 -0600)
committerMarc Fournier <marc.fournier@camptocamp.com>
Fri, 16 Jan 2015 21:46:08 +0000 (22:46 +0100)
This allows access to a local RS-485 serial port
via the modbus plugin by specifying i.e.

  Device "/dev/ttyUSB0"
  Baudrate 38400

in a <Host> block.

For now it assumes 8N1; adding another config option
to support other configurations could be done later.

Lightly tested on my local setup.

Signed-off-by: Eric Sandeen <sandeen@sandeen.net>
src/collectd.conf.pod
src/modbus.c

index 1802d5a..12269ae 100644 (file)
@@ -2880,8 +2880,8 @@ which the sizes of physical memory vary.
 
 =head2 Plugin C<modbus>
 
-The B<modbus plugin> connects to a Modbus "slave" via Modbus/TCP and reads
-register values. It supports reading single registers (unsigned 16E<nbsp>bit
+The B<modbus plugin> connects to a Modbus "slave" via Modbus/TCP or Modbus/RTU and
+reads register values. It supports reading single registers (unsigned 16E<nbsp>bit
 values), large integer values (unsigned 32E<nbsp>bit values) and floating point
 values (two registers interpreted as IEEE floats in big endian notation).
 
@@ -2903,6 +2903,14 @@ B<Synopsis:>
    Instance "input-2"
  </Data>
 
+ <Data "supply-temperature-1">
+   RegisterBase 0
+   RegisterType Int16
+   ModbusRegisterType holding
+   Type temperature
+   Instance "temp-1"
+ </Data>
+
  <Host "modbus.example.com">
    Address "192.168.0.42"
    Port    "502"
@@ -2915,6 +2923,17 @@ B<Synopsis:>
    </Slave>
  </Host>
 
+ <Host "localhost">
+   Device "/dev/ttyUSB0"
+   Baudrate 38400
+   Interval 20
+
+   <Slave 1>
+     Instance "temperature"
+     Collect  "supply-temperature-1"
+   </Slave>
+ </Host>
+
 =over 4
 
 =item E<lt>B<Data> I<Name>E<gt> blocks
@@ -2968,15 +2987,25 @@ Within E<lt>HostE<nbsp>/E<gt> blocks, the following options are allowed:
 
 =item B<Address> I<Hostname>
 
-Specifies the node name (the actual network address) used to connect to the
-host. This may be an IP address or a hostname. Please note that the used
-I<libmodbus> library only supports IPv4 at the moment.
+For Modbus/TCP, specifies the node name (the actual network address) used to
+connect to the host. This may be an IP address or a hostname. Please note that
+the used I<libmodbus> library only supports IPv4 at the moment.
 
 =item B<Port> I<Service>
 
-Specifies the port used to connect to the host. The port can either be given as
-a number or as a service name. Please note that the I<Service> argument must be
-a string, even if ports are given in their numerical form. Defaults to "502".
+for Modbus/TCP, specifies the port used to connect to the host. The port can
+either be given as a number or as a service name. Please note that the
+I<Service> argument must be a string, even if ports are given in their numerical
+form. Defaults to "502".
+
+=item B<Device> I<Devicenode>
+
+For Modbus/RTU, specifies the path to the serial device being used.
+
+=item B<Baudrate> I<Baudrate>
+
+For Modbus/RTU, specifies the baud rate of the serial device.
+Note, connections currently support only 8/N/1.
 
 =item B<Interval> I<Interval>
 
@@ -2985,7 +3014,7 @@ host. By default the global B<Interval> setting will be used.
 
 =item E<lt>B<Slave> I<ID>E<gt>
 
-Over each TCP connection, multiple Modbus devices may be reached. The slave ID
+Over each connection, multiple Modbus devices may be reached. The slave ID
 is used to specify which device should be addressed. For each device you want
 to query, one B<Slave> block must be given.
 
index 795465f..5891938 100644 (file)
  * <Host "name">
  *   Address "addr"
  *   Port "1234"
+ *   # Or:
+ *   # Device "/dev/ttyUSB0"
+ *   # Baudrate 38400
+ *   # (Assumes 8N1)
  *   Interval 60
  *
  *   <Slave 1>
@@ -84,6 +88,14 @@ enum mb_mreg_type_e /* {{{ */
 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;
 struct mb_data_s /* {{{ */
@@ -109,9 +121,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;
@@ -301,21 +315,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);
   }
 
@@ -336,17 +362,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);
+    }
   }
 
   modbus_set_debug (host->connection, 1);
@@ -358,7 +399,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);
@@ -427,7 +468,7 @@ 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);
@@ -919,6 +960,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)
     {
@@ -926,6 +969,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)
@@ -942,9 +993,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;
   }