Modbus plugin: Started working on a "Modbus-TCP" plugin for collectd.
authorFlorian Forster <octo@noris.net>
Wed, 10 Mar 2010 17:38:58 +0000 (18:38 +0100)
committerFlorian Forster <octo@noris.net>
Wed, 10 Mar 2010 17:38:58 +0000 (18:38 +0100)
src/modbus.c [new file with mode: 0644]

diff --git a/src/modbus.c b/src/modbus.c
new file mode 100644 (file)
index 0000000..4b9c88e
--- /dev/null
@@ -0,0 +1,419 @@
+/**
+ * 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 <octo at noris.net>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <modbus/modbus.h>
+
+/*
+ * <Data "data_name">
+ *   RegisterBase 1234
+ *   RegisterType float
+ *   Type gauge
+ *   Instance "..."
+ * </Data>
+ *
+ * <Host "name">
+ *   Address "addr"
+ *   Port "1234"
+ *   Interval 60
+ *
+ *   <Slave 1>
+ *     Instance "foobar" # optional
+ *     Collect "data_name"
+ *   </Slave>
+ * </Host>
+ */
+
+/*
+ * 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 interval;
+
+  mb_slave_t *slaves;
+  size_t slaves_num;
+
+  modbus_param_t connection;
+}
+
+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 (const 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, const 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 = sstrdup (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, const 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 */
+
+static int mb_config_add_data (oconfig_item_t *ci) /* {{{ */
+{
+  mb_data_t data;
+  int status;
+  int i;
+
+  memset (&data, 0, sizeof (data));
+
+  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++) */
+
+  /* TODO: Validate the struct. */
+
+  data_copy (&data_definitions, &data);
+  sfree (data.name);
+
+  return (0);
+} /* }}} int mb_config_add_data */
+
+static void mb_free_internal (mb_internal_t *i) /* {{{ */
+{
+  if (i == NULL)
+    return;
+
+  free (i->connection);
+  free (i);
+} /* }}} void mb_free_internal */
+
+static mb_internal_t *mb_alloc_internal (void) /* {{{ */
+{
+  mb_internal_t *ret;
+
+  ret = malloc (sizeof (*ret));
+  if (ret == NULL)
+    return (NULL);
+  memset (ret, 0, sizeof (*ret));
+
+  ret->connection = calloc (1, sizeof (*ret->connection));
+  if (ret->connection == NULL)
+  {
+    mb_free_internal (ret);
+    return (NULL);
+  }
+
+  return (ret);
+} /* }}} mb_internal_t *mb_alloc_internal */
+
+static mb_internal_t *mb_init (void) /* {{{ */
+{
+  mb_internal_t *ret;
+  int status;
+
+  ret = mb_alloc_internal ();
+  if (ret == NULL)
+    return (NULL);
+
+  modbus_set_debug (ret->connection, 1);
+
+  /* We'll do the error handling ourselves. */
+  modbus_set_error_handling (ret->connection, NOP_ON_ERROR);
+
+  modbus_init_tcp (ret->connection,
+      /* host = */ "172.18.20.30", /* FIXME: Only IP adresses allowed. -> convert hostnames. */
+      /* post = */ MODBUS_TCP_DEFAULT_PORT); /* FIXME: Use configured port. */
+
+  status = modbus_connect (ret->connection);
+  printf ("mb_init: modbus_connect returned status %i\n", status);
+  if (status != 0)
+  {
+    mb_free_internal (ret);
+    return (NULL);
+  }
+
+  return (ret);
+} /* }}} mb_internal_t *mb_init */
+
+static float mb_register_to_float (uint16_t hi, uint16_t lo) /* {{{ */
+{
+  union
+  {
+    uint8_t b[4];
+    float f;
+  } conv;
+
+#if 1
+  /* 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_read (mb_internal_t *dev, int register_addr, _Bool is_float) /* {{{ */
+{
+  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 mb_read */
+
+static int mb_shutdown (mb_internal_t *dev) /* {{{ */
+{
+  if ((dev != NULL) && (dev->connection != NULL))
+    modbus_close (dev->connection);
+  mb_free_internal (dev);
+
+  return (0);
+} /* }}} int mb_shutdown */
+
+int main (int argc, char **argv) /* {{{ */
+{
+  mb_internal_t *dev;
+  /* int j; */
+
+  argc = 0; argv = NULL;
+
+  dev = mb_init ();
+
+#if 0
+  for (j = 1; j < argc; j++)
+  {
+    mb_read (dev, atoi (argv[j]));
+  }
+#endif
+  mb_read (dev, 4096+3, /* is_float = */ 0);
+  mb_read (dev, 2*3, /* is_float = */ 1);
+
+  mb_shutdown (dev);
+
+  exit (EXIT_SUCCESS);
+} /* }}} int main */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("modbus", mb_config);
+  plugin_register_init ("modbus", mv_init);
+  plugin_register_shutdown ("modbus", mv_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */