Merge branch 'collectd-5.8'
[collectd.git] / src / multimeter.c
1 /**
2  * collectd - src/multimeter.c
3  * Copyright (C) 2005,2006  Peter Holik
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; either version 2 of the License, or (at your
8  * option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Peter Holik <peter at holik.at>
21  *
22  * Used multimeter: Metex M-4650CR
23  **/
24
25 #include "collectd.h"
26
27 #include "common.h"
28 #include "plugin.h"
29
30 #if HAVE_TERMIOS_H && HAVE_SYS_IOCTL_H
31 #include <sys/ioctl.h>
32 #include <termios.h>
33 #else
34 #error "No applicable input method."
35 #endif
36
37 static int fd = -1;
38
39 #define LINE_LENGTH 14
40 static int multimeter_read_value(double *value) {
41   int retry = 3; /* sometimes we receive garbadge */
42
43   do {
44     struct timeval time_end;
45
46     tcflush(fd, TCIFLUSH);
47
48     if (gettimeofday(&time_end, NULL) < 0) {
49       ERROR("multimeter plugin: gettimeofday failed: %s", STRERRNO);
50       return -1;
51     }
52     time_end.tv_sec++;
53
54     while (1) {
55       char buf[LINE_LENGTH];
56       char *range;
57       int status;
58       fd_set rfds;
59       struct timeval timeout;
60       struct timeval time_now;
61
62       status = swrite(fd, "D", 1);
63       if (status != 0) {
64         ERROR("multimeter plugin: swrite failed.");
65         return -1;
66       }
67
68       FD_ZERO(&rfds);
69       FD_SET(fd, &rfds);
70
71       if (gettimeofday(&time_now, NULL) < 0) {
72         ERROR("multimeter plugin: "
73               "gettimeofday failed: %s",
74               STRERRNO);
75         return -1;
76       }
77       if (timeval_cmp(time_end, time_now, &timeout) < 0)
78         break;
79
80       status = select(fd + 1, &rfds, NULL, NULL, &timeout);
81
82       if (status > 0) /* usually we succeed */
83       {
84         status = read(fd, buf, LINE_LENGTH);
85
86         if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR)))
87           continue;
88
89         /* Format: "DC 00.000mV  \r" */
90         if (status > 0 && status == LINE_LENGTH) {
91           *value = strtod(buf + 2, &range);
92
93           if (range > (buf + 6)) {
94             range = buf + 9;
95
96             switch (*range) {
97             case 'p':
98               *value *= 1.0E-12;
99               break;
100             case 'n':
101               *value *= 1.0E-9;
102               break;
103             case 'u':
104               *value *= 1.0E-6;
105               break;
106             case 'm':
107               *value *= 1.0E-3;
108               break;
109             case 'k':
110               *value *= 1.0E3;
111               break;
112             case 'M':
113               *value *= 1.0E6;
114               break;
115             case 'G':
116               *value *= 1.0E9;
117               break;
118             }
119           } else
120             return -1; /* Overflow */
121
122           return 0; /* value received */
123         } else
124           break;
125       } else if (!status) /* Timeout */
126       {
127         break;
128       } else if ((status == -1) && ((errno == EAGAIN) || (errno == EINTR))) {
129         continue;
130       } else /* status == -1 */
131       {
132         ERROR("multimeter plugin: "
133               "select failed: %s",
134               STRERRNO);
135         break;
136       }
137     }
138   } while (--retry);
139
140   return -2; /* no value received */
141 } /* int multimeter_read_value */
142
143 static int multimeter_init(void) {
144   char device[] = "/dev/ttyS ";
145
146   for (int i = 0; i < 10; i++) {
147     device[strlen(device) - 1] = i + '0';
148
149     if ((fd = open(device, O_RDWR | O_NOCTTY)) != -1) {
150       struct termios tios = {0};
151       int rts = TIOCM_RTS;
152       double value;
153
154       tios.c_cflag = B1200 | CS7 | CSTOPB | CREAD | CLOCAL;
155       tios.c_iflag = IGNBRK | IGNPAR;
156       tios.c_oflag = 0;
157       tios.c_lflag = 0;
158       tios.c_cc[VTIME] = 3;
159       tios.c_cc[VMIN] = LINE_LENGTH;
160
161       tcflush(fd, TCIFLUSH);
162       tcsetattr(fd, TCSANOW, &tios);
163       ioctl(fd, TIOCMBIC, &rts);
164
165       if (multimeter_read_value(&value) < -1) {
166         close(fd);
167         fd = -1;
168       } else {
169         INFO("multimeter plugin: Device "
170              "found at %s",
171              device);
172         return 0;
173       }
174     }
175   }
176
177   ERROR("multimeter plugin: No device found");
178   return -1;
179 }
180 #undef LINE_LENGTH
181
182 static void multimeter_submit(double value) {
183   value_list_t vl = VALUE_LIST_INIT;
184
185   vl.values = &(value_t){.gauge = value};
186   vl.values_len = 1;
187   sstrncpy(vl.plugin, "multimeter", sizeof(vl.plugin));
188   sstrncpy(vl.type, "multimeter", sizeof(vl.type));
189
190   plugin_dispatch_values(&vl);
191 }
192
193 static int multimeter_read(void) {
194   double value;
195
196   if (fd < 0)
197     return -1;
198
199   if (multimeter_read_value(&value) != 0)
200     return -1;
201
202   multimeter_submit(value);
203   return 0;
204 } /* int multimeter_read */
205
206 static int multimeter_shutdown(void) {
207   if (fd >= 0) {
208     close(fd);
209     fd = -1;
210   }
211
212   return 0;
213 }
214
215 void module_register(void) {
216   plugin_register_init("multimeter", multimeter_init);
217   plugin_register_read("multimeter", multimeter_read);
218   plugin_register_shutdown("multimeter", multimeter_shutdown);
219 } /* void module_register */