Merging with changes made by Florian Foster.
[collectd.git] / src / gps.c
1 /**
2  * collectd - src/gps.c
3  * Copyright (C) 2015  Nicolas JOURDEN
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Nicolas JOURDEN <nicolas.jourden at laposte.net>
25  **/
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "plugin.h"
30 #include "utils_time.h"
31 #include "configfile.h"
32
33 #define GPS_DEFAULT_HOST    "localhost"
34 #define GPS_DEFAULT_PORT    "2947"
35 #define GPS_DEFAULT_TIMEOUT TIME_T_TO_CDTIME_T (15)
36 #define GPS_DEFAULT_PAUSE   TIME_T_TO_CDTIME_T (1)
37
38 #include <gps.h>
39 #include <pthread.h>
40
41 typedef struct {
42   char *host;
43   char *port;
44   cdtime_t timeout;
45   cdtime_t pause;
46 } cgps_config_t;
47
48 typedef struct {
49   gauge_t sats_used;
50   gauge_t sats_visible;
51   gauge_t hdop;
52   gauge_t vdop;
53 } cgps_data_t;
54
55 // Thread items:
56 static pthread_t connector = (pthread_t) 0;
57
58 static cgps_config_t config;
59
60 static cgps_data_t      data = {NAN, NAN, NAN, NAN};
61 static pthread_mutex_t  data_lock = PTHREAD_MUTEX_INITIALIZER;
62
63 /**
64  * Thread reading from gpsd.
65  */
66 static void * gps_collectd_thread (void * pData)
67 {
68   struct gps_data_t conn;
69
70   while (1)
71   {
72     int status = gps_open (config.host, config.port, &conn);
73     if (status < 0)
74     {
75       WARNING ("gps plugin: Connecting to %s:%s failed: %s",
76                config.host, config.port, gps_errstr (status));
77       sleep (60);
78       continue;
79     }
80
81     gps_stream (&conn, WATCH_ENABLE | WATCH_JSON | WATCH_NEWSTYLE, NULL);
82     gps_send (&conn, "?WATCH={\"enable\":true,\"json\":true,\"nmea\":false}\r\n");
83
84     while (1)
85     {
86       long timeout_us = CDTIME_T_TO_US (config.timeout);
87       if (!gps_waiting (&conn, (int) timeout_us))
88       {
89         struct timespec pause_ns;
90         CDTIME_T_TO_TIMESPEC (config.pause, &pause_ns);
91         nanosleep (&pause_ns, NULL);
92         continue;
93       }
94
95       if (gps_read (&conn) == -1)
96       {
97         WARNING ("gps plugin: incorrect data!");
98         continue;
99       }
100
101       pthread_mutex_lock (&data_lock);
102
103       // Number of sats in view:
104       data.sats_used = (gauge_t) conn.satellites_used;
105       data.sats_visible = (gauge_t) conn.satellites_visible;
106
107       // dilution of precision:
108       data.vdop = NAN; data.hdop = NAN;
109       if (data.sats_used > 0)
110       {
111         data.hdop = conn.dop.hdop;
112         data.vdop = conn.dop.vdop;
113       }
114
115
116       DEBUG ("gps plugin: %.0f sats used (of %.0f visible), hdop = %.3f, vdop = %.3f",
117              data.sats_used, data.sats_visible, data.hdop, data.vdop);
118
119       pthread_mutex_unlock (&data_lock);
120     }
121   }
122
123   gps_stream (&conn, WATCH_DISABLE, /* data = */ NULL);
124   gps_close (&conn);
125
126   pthread_exit ((void *) 0);
127 }
128
129 /**
130  * Submit a piece of the data.
131  */
132 static void cgps_submit (const char *type, gauge_t value, const char *type_instance)
133 {
134   value_t values[1];
135   value_list_t vl = VALUE_LIST_INIT;
136
137   values[0].gauge = value;
138
139   vl.values = values;
140   vl.values_len = 1;
141   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
142   sstrncpy (vl.plugin, "gps", sizeof (vl.plugin));
143   sstrncpy (vl.type, type, sizeof (vl.type));
144   sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
145
146   plugin_dispatch_values (&vl);
147 }
148
149 /**
150  * Read the data and submit by piece.
151  */
152 static int cgps_read ()
153 {
154   cgps_data_t data_copy;
155
156   pthread_mutex_lock (&data_lock);
157   data_copy = data;
158   pthread_mutex_unlock (&data_lock);
159
160   cgps_submit ("dilution_of_precision", data_copy.hdop, "horizontal");
161   cgps_submit ("dilution_of_precision", data_copy.vdop, "vertical");
162   cgps_submit ("satellites", data_copy.sats_used, "used");
163   cgps_submit ("satellites", data_copy.sats_visible, "visible");
164
165   return (0);
166 }
167
168 /**
169  * Read configuration.
170  */
171 static int cgps_config (oconfig_item_t *ci)
172 {
173   int i;
174
175   for (i = 0; i < ci->children_num; i++)
176   {
177     oconfig_item_t *child = ci->children + i;
178
179     if (strcasecmp ("Host", child->key) == 0)
180       cf_util_get_string (child, &config.host);
181     else if (strcasecmp ("Port", child->key) == 0)
182       cf_util_get_service (child, &config.port);
183     else if (strcasecmp ("Timeout", child->key) == 0)
184       cf_util_get_cdtime (child, &config.timeout);
185     else if (strcasecmp ("Pause", child->key) == 0)
186       cf_util_get_cdtime (child, &config.pause);
187     else
188       WARNING ("gps plugin: Ignoring unknown config option \"%s\".", child->key);
189   }
190
191   return 0;
192 }
193
194 /**
195  * Init.
196  */
197 static int cgps_init (void)
198 {
199   int status;
200
201   DEBUG ("gps plugin: config{host: \"%s\", port: \"%s\", timeout: %.3f, pause: %.3f}",
202          config.host, config.port,
203          CDTIME_T_TO_DOUBLE (config.timeout), CDTIME_T_TO_DOUBLE (config.pause));
204
205   status = plugin_thread_create (&connector, NULL, gps_collectd_thread, NULL);
206   if (status != 0)
207   {
208     ERROR ("gps plugin: pthread_create() failed.");
209     return (-1);
210   }
211
212   return (0);
213 }
214
215 /**
216  * Shutdown.
217  */
218 static int cgps_shutdown (void)
219 {
220   if (connector != ((pthread_t) 0))
221   {
222     pthread_kill (connector, SIGTERM);
223     connector = (pthread_t) 0;
224   }
225
226   sfree (config.port);
227   sfree (config.host);
228
229   return (0);
230 }
231
232 /**
233  * Register the module.
234  */
235 void module_register (void)
236 {
237   config.host = sstrdup (GPS_DEFAULT_HOST);
238   config.port = sstrdup (GPS_DEFAULT_PORT);
239   config.timeout = GPS_DEFAULT_TIMEOUT;
240   config.pause = GPS_DEFAULT_PAUSE;
241
242   plugin_register_complex_config ("gps", cgps_config);
243   plugin_register_init ("gps", cgps_init);
244   plugin_register_read ("gps", cgps_read);
245   plugin_register_shutdown ("gps", cgps_shutdown);
246 }