GPS subagent
[public/netxms.git] / src / agent / subagents / gps / main.cpp
1 /*
2 ** NetXMS GPS receiver subagent
3 ** Copyright (C) 2006-2016 Raden Solutions
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: main.cpp
20 **
21 **/
22
23 #include <nms_agent.h>
24 #include <geolocation.h>
25 #include <math.h>
26 #include "nmea.h"
27
28 /**
29 * Port name
30 */
31 static TCHAR s_device[MAX_PATH];
32
33 /**
34 * NMEA info
35 */
36 static nmeaINFO s_nmeaInfo;
37
38 /**
39 * Parsed geolocation object
40 */
41 static GeoLocation s_geolocation;
42
43 /**
44 * Lock for NMEA info
45 */
46 static MUTEX s_nmeaInfoLock = MutexCreate();
47
48 /**
49 * Serial port
50 */
51 static Serial s_serial;
52
53 /**
54 * Initialize serial port
55 *
56 * s_device format: portname,speed,databits,parity,stopbits
57 */
58 static bool InitSerialPort()
59 {
60 bool success = false;
61 TCHAR *portName;
62
63 if (s_device[0] == 0)
64 {
65 #ifdef _WIN32
66 portName = _tcsdup(_T("COM1:"));
67 #else
68 portName = _tcsdup(_T("/dev/ttyS0"));
69 #endif
70 }
71 else
72 {
73 portName = _tcsdup(s_device);
74 }
75
76 AgentWriteDebugLog(1, _T("GPS: using serial port configuration \"%s\""), portName);
77
78 TCHAR *p;
79 const TCHAR *parityAsText;
80 int portSpeed = 4800;
81 int dataBits = 8;
82 int parity = NOPARITY;
83 int stopBits = ONESTOPBIT;
84
85 if ((p = _tcschr(portName, _T(','))) != NULL)
86 {
87 *p = 0; p++;
88 int tmp = _tcstol(p, NULL, 10);
89 if (tmp != 0)
90 {
91 portSpeed = tmp;
92
93 if ((p = _tcschr(p, _T(','))) != NULL)
94 {
95 *p = 0; p++;
96 tmp = _tcstol(p, NULL, 10);
97 if (tmp >= 5 && tmp <= 8)
98 {
99 dataBits = tmp;
100
101 // parity
102 if ((p = _tcschr(p, _T(','))) != NULL)
103 {
104 *p = 0; p++;
105 switch (tolower((char)*p))
106 {
107 case 'n': // none
108 parity = NOPARITY;
109 break;
110 case 'o': // odd
111 parity = ODDPARITY;
112 break;
113 case 'e': // even
114 parity = EVENPARITY;
115 break;
116 }
117
118 // stop bits
119 if ((p = _tcschr(p, _T(','))) != NULL)
120 {
121 *p = 0; p++;
122
123 if (*p == _T('2'))
124 {
125 stopBits = TWOSTOPBITS;
126 }
127 }
128 }
129 }
130 }
131 }
132 }
133
134 switch (parity)
135 {
136 case ODDPARITY:
137 parityAsText = _T("ODD");
138 break;
139 case EVENPARITY:
140 parityAsText = _T("EVEN");
141 break;
142 default:
143 parityAsText = _T("NONE");
144 break;
145 }
146 AgentWriteDebugLog(1, _T("GPS: initialize for port=\"%s\", speed=%d, data=%d, parity=%s, stop=%d"),
147 portName, portSpeed, dataBits, parityAsText, stopBits == TWOSTOPBITS ? 2 : 1);
148
149 if (s_serial.open(portName))
150 {
151 AgentWriteDebugLog(5, _T("GPS: port opened"));
152 s_serial.setTimeout(2000);
153
154 if (s_serial.set(portSpeed, dataBits, parity, stopBits))
155 {
156 success = true;
157 }
158 else
159 {
160 AgentWriteDebugLog(5, _T("GPS: cannot set port parameters"));
161 }
162
163 AgentWriteLog(NXLOG_INFO, _T("GPS: serial port initialized"));
164 }
165 else
166 {
167 AgentWriteLog(NXLOG_WARNING, _T("GPS: Unable to open serial port"));
168 }
169
170 free(portName);
171 return success;
172 }
173
174 /**
175 * Convert NMEA NDEG field ([dd][mm].[s/60]) to degrees
176 */
177 inline double NMEA_TO_DEG(double nm)
178 {
179 double n = fabs(nm);
180 int d = (int)(n / 100.0);
181 int m = (int)(n - d * 100);
182 double s = n - (d * 100 + m);
183 double r = d + m / 60.0 + s / 60.0;
184 return (nm < 0) ? -r : r;
185 }
186
187 /**
188 * Poller thread
189 */
190 static THREAD_RESULT THREAD_CALL PollerThread(void *arg)
191 {
192 AgentWriteDebugLog(3, _T("GPS: poller thread started"));
193
194 nmeaPARSER parser;
195 nmea_zero_INFO(&s_nmeaInfo);
196 nmea_parser_init(&parser);
197
198 while(!AgentSleepAndCheckForShutdown(30))
199 {
200 if (!s_serial.restart())
201 {
202 AgentWriteDebugLog(7, _T("GPS: cannot open serial port"));
203 continue;
204 }
205
206 while(!AgentSleepAndCheckForShutdown(0))
207 {
208 static const char *marks[] = { "\r\n", NULL };
209 char *occ, buffer[128];
210 if (s_serial.readToMark(buffer, 128, marks, &occ) <= 0)
211 {
212 AgentWriteDebugLog(8, _T("GPS: serial port read failure"));
213 break;
214 }
215
216 if (occ != NULL)
217 {
218 MutexLock(s_nmeaInfoLock);
219 int count = nmea_parse(&parser, buffer, (int)strlen(buffer), &s_nmeaInfo);
220 if (count > 0)
221 {
222 s_geolocation = GeoLocation(GL_GPS, NMEA_TO_DEG(s_nmeaInfo.lat), NMEA_TO_DEG(s_nmeaInfo.lon), 0, time(NULL));
223 }
224 MutexUnlock(s_nmeaInfoLock);
225 }
226 }
227 }
228
229 nmea_parser_destroy(&parser);
230 AgentWriteDebugLog(3, _T("GPS: poller thread stopped"));
231 return THREAD_OK;
232 }
233
234 /**
235 * Poller thread handle
236 */
237 static THREAD s_pollerThread = INVALID_THREAD_HANDLE;
238
239 /**
240 * Subagent initialization
241 */
242 static BOOL SubAgentInit(Config *config)
243 {
244 // Parse configuration
245 const TCHAR *value = config->getValue(_T("/GPS/Device"));
246 if (value != NULL)
247 {
248 nx_strncpy(s_device, value, MAX_PATH);
249 InitSerialPort();
250 s_pollerThread = ThreadCreateEx(PollerThread, 0, NULL);
251 }
252 else
253 {
254 AgentWriteLog(EVENTLOG_ERROR_TYPE, _T("GPS: device not specified"));
255 }
256
257 return value != NULL;
258 }
259
260 /**
261 * Called by master agent at unload
262 */
263 static void SubAgentShutdown()
264 {
265 ThreadJoin(s_pollerThread);
266 }
267
268 /**
269 * Handler for GPS.SerialConfig
270 */
271 static LONG H_Config(const TCHAR *param, const TCHAR *arg, TCHAR *value, AbstractCommSession *session)
272 {
273 ret_string(value, s_device);
274 return SYSINFO_RC_SUCCESS;
275 }
276
277 /**
278 * Handler for location information parameters
279 */
280 static LONG H_LocationInfo(const TCHAR *param, const TCHAR *arg, TCHAR *value, AbstractCommSession *session)
281 {
282 LONG rc = SYSINFO_RC_SUCCESS;
283
284 MutexLock(s_nmeaInfoLock);
285 switch(*arg)
286 {
287 case 'A': // latitude as text
288 ret_string(value, s_geolocation.getLatitudeAsString());
289 break;
290 case 'a': // latitude
291 ret_double(value, s_geolocation.getLatitude());
292 break;
293 case 'D': // direction
294 ret_int(value, s_nmeaInfo.direction);
295 break;
296 case 'L': // location as text
297 _sntprintf(value, MAX_RESULT_LENGTH, _T("%s %s"), s_geolocation.getLatitudeAsString(), s_geolocation.getLongitudeAsString());
298 break;
299 case 'O': // longitude as text
300 ret_string(value, s_geolocation.getLongitudeAsString());
301 break;
302 case 'o': // longitude
303 ret_double(value, s_geolocation.getLongitude());
304 break;
305 case 's': // number of satellites in view
306 ret_int(value, s_nmeaInfo.satinfo.inview);
307 break;
308 case 'S': // number of satellites in use
309 ret_int(value, s_nmeaInfo.satinfo.inuse);
310 break;
311 case 'X': // speed
312 ret_int(value, s_nmeaInfo.speed);
313 break;
314 default:
315 rc = SYSINFO_RC_UNSUPPORTED;
316 break;
317 }
318 MutexUnlock(s_nmeaInfoLock);
319 return rc;
320 }
321
322 /**
323 * Subagent parameters
324 */
325 static NETXMS_SUBAGENT_PARAM m_parameters[] =
326 {
327 { _T("GPS.Direction"), H_LocationInfo, _T("D"), DCI_DT_FLOAT, _T("GPS: direction") },
328 { _T("GPS.Latitude"), H_LocationInfo, _T("a"), DCI_DT_FLOAT, _T("GPS: latitude") },
329 { _T("GPS.LatitudeText"), H_LocationInfo, _T("A"), DCI_DT_STRING, _T("GPS: latitude (as text)") },
330 { _T("GPS.Location"), H_LocationInfo, _T("L"), DCI_DT_STRING, _T("GPS: location") },
331 { _T("GPS.Longitude"), H_LocationInfo, _T("o"), DCI_DT_FLOAT, _T("GPS: longitude") },
332 { _T("GPS.LongitudeText"), H_LocationInfo, _T("O"), DCI_DT_STRING, _T("GPS: longitude (as text)") },
333 { _T("GPS.Satellites.InUse"), H_LocationInfo, _T("S"), DCI_DT_INT, _T("GPS: satellites in use") },
334 { _T("GPS.Satellites.InView"), H_LocationInfo, _T("s"), DCI_DT_INT, _T("GPS: satellites in view") },
335 { _T("GPS.Speed"), H_LocationInfo, _T("X"), DCI_DT_FLOAT, _T("GPS: ground speed") },
336 { _T("GPS.SerialConfig"), H_Config, NULL, DCI_DT_STRING, _T("GPS: serial port configuration") }
337 };
338
339 /**
340 * Subagent information
341 */
342 static NETXMS_SUBAGENT_INFO m_info =
343 {
344 NETXMS_SUBAGENT_INFO_MAGIC,
345 _T("GPS"), NETXMS_VERSION_STRING,
346 SubAgentInit, SubAgentShutdown, NULL,
347 sizeof(m_parameters) / sizeof(NETXMS_SUBAGENT_PARAM),
348 m_parameters,
349 0, NULL, // lists
350 0, NULL, // tables
351 0, NULL, // actions
352 0, NULL // push parameters
353 };
354
355 /**
356 * Entry point for NetXMS agent
357 */
358 DECLARE_SUBAGENT_ENTRY_POINT(GPS)
359 {
360 *ppInfo = &m_info;
361 return TRUE;
362 }
363
364 #ifdef _WIN32
365
366 /**
367 * DLL entry point
368 */
369 BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
370 {
371 if (dwReason == DLL_PROCESS_ATTACH)
372 DisableThreadLibraryCalls(hInstance);
373 return TRUE;
374 }
375
376 #endif