Modbus plugin: Started working on a "Modbus-TCP" plugin for collectd.
[collectd.git] / src / modbus.c
1 /**
2  * collectd - src/modbus.c
3  * Copyright (C) 2010  noris network AG
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian Forster <octo at noris.net>
20  **/
21
22 #include "collectd.h"
23 #include "plugin.h"
24 #include "configfile.h"
25
26 #include <modbus/modbus.h>
27
28 /*
29  * <Data "data_name">
30  *   RegisterBase 1234
31  *   RegisterType float
32  *   Type gauge
33  *   Instance "..."
34  * </Data>
35  *
36  * <Host "name">
37  *   Address "addr"
38  *   Port "1234"
39  *   Interval 60
40  *
41  *   <Slave 1>
42  *     Instance "foobar" # optional
43  *     Collect "data_name"
44  *   </Slave>
45  * </Host>
46  */
47
48 /*
49  * Data structures
50  */
51 enum mb_register_type_e
52 {
53   REG_TYPE_UINT16,
54   REG_TYPE_UINT32,
55   REG_TYPE_FLOAT
56 };
57 typedef enum mb_register_type_e mb_register_type_t;
58
59 struct mb_data_s;
60 typedef struct mb_data_s mb_data_t;
61 struct mb_data_s
62 {
63   char *name;
64   int register_base;
65   mb_register_type_t register_type;
66   char type[DATA_MAX_NAME_LEN];
67   char instance[DATA_MAX_NAME_LEN];
68
69   mb_data_t *next;
70 };
71
72 struct mb_slave_s
73 {
74   int id;
75   char instance[DATA_MAX_NAME_LEN];
76   mb_data_t *collect;
77 };
78 typedef struct mb_slave_s mb_slave_t;
79
80 struct mb_host_s
81 {
82   char host[DATA_MAX_NAME_LEN];
83   char node[NI_MAXHOST];
84   char service[NI_MAXSERV];
85   int interval;
86
87   mb_slave_t *slaves;
88   size_t slaves_num;
89
90   modbus_param_t connection;
91 }
92
93 struct mb_data_group_s;
94 typedef struct mb_data_group_s mb_data_group_t;
95 struct mb_data_group_s
96 {
97   mb_data_t *registers;
98   size_t registers_num;
99
100   mb_data_group_t *next;
101 };
102
103 /*
104  * Global variables
105  */
106 static mb_data_t *data_definitions = NULL;
107
108 /*
109  * Functions
110  */
111 static mb_data_t *data_get_by_name (const mb_data_t *src, const char *name) /* {{{ */
112 {
113   mb_data_t *ptr;
114
115   if (name == NULL)
116     return (NULL);
117
118   for (ptr = src; ptr != NULL; ptr = ptr->next)
119     if (strcasecmp (ptr->name, name) == 0)
120       return (ptr);
121
122   return (NULL);
123 } /* }}} mb_data_t *data_get_by_name */
124
125 static int data_append (mb_data_t **dst, const mb_data_t *src) /* {{{ */
126 {
127   mb_data_t *ptr;
128
129   if ((dst == NULL) || (src == NULL))
130     return (EINVAL);
131
132   ptr = *dst;
133
134   if (ptr == NULL)
135   {
136     *dst = src;
137     return (0);
138   }
139
140   while (ptr->next != NULL)
141     ptr = ptr->next;
142
143   ptr->next = src;
144
145   return (0);
146 } /* }}} int data_append */
147
148 /* Copy a single mb_data_t and append it to another list. */
149 static int data_copy (mb_data_t **dst, const mb_data_t *src) /* {{{ */
150 {
151   mb_data_t *tmp;
152   int status;
153
154   if ((dst == NULL) || (src == NULL))
155     return (EINVAL);
156
157   tmp = malloc (sizeof (*tmp));
158   if (tmp == NULL)
159     return (ENOMEM);
160   memcpy (tmp, src, sizeof (*tmp));
161   tmp->name = NULL;
162   tmp->next = NULL;
163
164   tmp->name = sstrdup (src->name);
165   if (tmp->name == NULL)
166   {
167     sfree (tmp);
168     return (ENOMEM);
169   }
170
171   status = data_append (dst, tmp);
172   if (status != 0)
173   {
174     sfree (tmp->name);
175     sfree (tmp);
176     return (status);
177   }
178
179   return (0);
180 } /* }}} int data_copy */
181
182 /* Lookup a single mb_data_t instance, copy it and append the copy to another
183  * list. */
184 static int data_copy_by_name (mb_data_t **dst, const mb_data_t *src, /* {{{ */
185     const char *name)
186 {
187   mb_data_t *ptr;
188
189   if ((dst == NULL) || (src == NULL) || (name == NULL))
190     return (EINVAL);
191
192   ptr = data_get_by_name (src, name);
193   if (ptr == NULL)
194     return (ENOENT);
195
196   return (data_copy (dst, ptr));
197 } /* }}} int data_copy_by_name */
198
199 static int mb_config_add_data (oconfig_item_t *ci) /* {{{ */
200 {
201   mb_data_t data;
202   int status;
203   int i;
204
205   memset (&data, 0, sizeof (data));
206
207   status = cf_util_get_string (ci, &data.name);
208   if (status != 0)
209     return (status);
210
211   for (i = 0; i < ci->children_num; i++)
212   {
213     oconfig_item_t *child = ci->children + i;
214     status = 0;
215
216     if (strcasecmp ("Type", child->key) == 0)
217       status = cf_util_get_string_buffer (child,
218           data.type, sizeof (data.type));
219     else if (strcasecmp ("Instance", child->key) == 0)
220       status = cf_util_get_string_buffer (child,
221           data.instance, sizeof (data.instance));
222     else if (strcasecmp ("RegisterBase", child->key) == 0)
223       status = cf_util_get_int (child, &data.register_base);
224     else if (strcasecmp ("RegisterType", child->key) == 0)
225     {
226       char tmp[16];
227       status = cf_util_get_string_buffer (child, tmp, sizeof (tmp));
228       if (status != 0)
229         /* do nothing */;
230       else if (strcasecmp ("Uint16", tmp) == 0)
231         data.register_type = REG_TYPE_UINT16;
232       else if (strcasecmp ("Uint32", tmp) == 0)
233         data.register_type = REG_TYPE_UINT32;
234       else if (strcasecmp ("Float", tmp) == 0)
235         data.register_type = REG_TYPE_FLOAT;
236       else
237       {
238         ERROR ("Modbus plugin: The register type \"%s\" is unknown.", tmp);
239         status = -1;
240       }
241     }
242     else
243     {
244       ERROR ("Modbus plugin: Unknown configuration option: %s", child->key);
245       status = -1;
246     }
247
248     if (status != 0)
249       break;
250   } /* for (i = 0; i < ci->children_num; i++) */
251
252   /* TODO: Validate the struct. */
253
254   data_copy (&data_definitions, &data);
255   sfree (data.name);
256
257   return (0);
258 } /* }}} int mb_config_add_data */
259
260 static void mb_free_internal (mb_internal_t *i) /* {{{ */
261 {
262   if (i == NULL)
263     return;
264
265   free (i->connection);
266   free (i);
267 } /* }}} void mb_free_internal */
268
269 static mb_internal_t *mb_alloc_internal (void) /* {{{ */
270 {
271   mb_internal_t *ret;
272
273   ret = malloc (sizeof (*ret));
274   if (ret == NULL)
275     return (NULL);
276   memset (ret, 0, sizeof (*ret));
277
278   ret->connection = calloc (1, sizeof (*ret->connection));
279   if (ret->connection == NULL)
280   {
281     mb_free_internal (ret);
282     return (NULL);
283   }
284
285   return (ret);
286 } /* }}} mb_internal_t *mb_alloc_internal */
287
288 static mb_internal_t *mb_init (void) /* {{{ */
289 {
290   mb_internal_t *ret;
291   int status;
292
293   ret = mb_alloc_internal ();
294   if (ret == NULL)
295     return (NULL);
296
297   modbus_set_debug (ret->connection, 1);
298
299   /* We'll do the error handling ourselves. */
300   modbus_set_error_handling (ret->connection, NOP_ON_ERROR);
301
302   modbus_init_tcp (ret->connection,
303       /* host = */ "172.18.20.30", /* FIXME: Only IP adresses allowed. -> convert hostnames. */
304       /* post = */ MODBUS_TCP_DEFAULT_PORT); /* FIXME: Use configured port. */
305
306   status = modbus_connect (ret->connection);
307   printf ("mb_init: modbus_connect returned status %i\n", status);
308   if (status != 0)
309   {
310     mb_free_internal (ret);
311     return (NULL);
312   }
313
314   return (ret);
315 } /* }}} mb_internal_t *mb_init */
316
317 static float mb_register_to_float (uint16_t hi, uint16_t lo) /* {{{ */
318 {
319   union
320   {
321     uint8_t b[4];
322     float f;
323   } conv;
324
325 #if 1
326   /* little endian */
327   conv.b[0] = lo & 0x00ff;
328   conv.b[1] = (lo >> 8) & 0x00ff;
329   conv.b[2] = hi & 0x00ff;
330   conv.b[3] = (hi >> 8) & 0x00ff;
331 #else
332   conv.b[3] = lo & 0x00ff;
333   conv.b[2] = (lo >> 8) & 0x00ff;
334   conv.b[1] = hi & 0x00ff;
335   conv.b[0] = (hi >> 8) & 0x00ff;
336 #endif
337
338   return (conv.f);
339 } /* }}} float mb_register_to_float */
340
341 static int mb_read (mb_internal_t *dev, int register_addr, _Bool is_float) /* {{{ */
342 {
343   int status;
344   uint16_t values[2];
345   int values_num;
346
347   if (dev == NULL)
348     return (EINVAL);
349
350   printf ("mb_read (addr = %i, float = %s);\n", register_addr,
351       is_float ? "true" : "false");
352
353   memset (values, 0, sizeof (values));
354   if (is_float)
355     values_num = 2;
356   else
357     values_num = 1;
358
359   status = read_holding_registers (dev->connection,
360       /* slave = */ 1, /* start_addr = */ register_addr,
361       /* num_registers = */ values_num, /* buffer = */ values);
362   printf ("read_coil_status returned with status %i\n", status);
363   if (status <= 0)
364     return (EAGAIN);
365
366   if (is_float)
367   {
368     float value = mb_register_to_float (values[0], values[1]);
369     printf ("read_coil_status returned value %g (hi %#"PRIx16", lo %#"PRIx16")\n",
370         value, values[0], values[1]);
371   }
372   else
373   {
374     printf ("read_coil_status returned value %"PRIu16"\n", values[0]);
375   }
376
377   return (0);
378 } /* }}} int mb_read */
379
380 static int mb_shutdown (mb_internal_t *dev) /* {{{ */
381 {
382   if ((dev != NULL) && (dev->connection != NULL))
383     modbus_close (dev->connection);
384   mb_free_internal (dev);
385
386   return (0);
387 } /* }}} int mb_shutdown */
388
389 int main (int argc, char **argv) /* {{{ */
390 {
391   mb_internal_t *dev;
392   /* int j; */
393
394   argc = 0; argv = NULL;
395
396   dev = mb_init ();
397
398 #if 0
399   for (j = 1; j < argc; j++)
400   {
401     mb_read (dev, atoi (argv[j]));
402   }
403 #endif
404   mb_read (dev, 4096+3, /* is_float = */ 0);
405   mb_read (dev, 2*3, /* is_float = */ 1);
406
407   mb_shutdown (dev);
408
409   exit (EXIT_SUCCESS);
410 } /* }}} int main */
411
412 void module_register (void)
413 {
414   plugin_register_complex_config ("modbus", mb_config);
415   plugin_register_init ("modbus", mv_init);
416   plugin_register_shutdown ("modbus", mv_shutdown);
417 } /* void module_register */
418
419 /* vim: set sw=2 sts=2 et fdm=marker : */