gps2udp.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. /*
  2. * gps2udp
  3. *
  4. * Dump NMEA to UDP socket for AIShub
  5. * gps2udp -u data.aishub.net:1234
  6. *
  7. * Author: Fulup Ar Foll (directly inspired from gpspipe.c)
  8. * Date: 2013-03-01
  9. *
  10. * This file is Copyright 2013 by the GPSD project
  11. * SPDX-License-Identifier: BSD-2-clause
  12. *
  13. */
  14. #include "../include/gpsd_config.h" /* must be before all includes */
  15. #include <arpa/inet.h>
  16. #include <assert.h>
  17. #include <errno.h>
  18. #include <fcntl.h>
  19. #ifdef HAVE_GETOPT_LONG
  20. #include <getopt.h> // for getopt_long()
  21. #endif
  22. #include <netdb.h> /* for gethostbyname() */
  23. #include <netinet/in.h>
  24. #include <stdbool.h>
  25. #include <stdio.h>
  26. #include <stdlib.h>
  27. // do not use strsep() it is not POSIX
  28. #include <string.h> /* for strlcpy(), strtok(), etc. */
  29. #include <strings.h>
  30. #include <sys/select.h>
  31. #include <sys/socket.h>
  32. #include <sys/stat.h>
  33. #include <sys/time.h>
  34. #include <sys/types.h>
  35. #include <termios.h>
  36. #include <time.h>
  37. #include <unistd.h>
  38. #include "../include/gpsd.h"
  39. #include "../include/gpsdclient.h"
  40. #include "../include/strfuncs.h"
  41. #include "../include/timespec.h"
  42. #define MAX_TIME_LEN 80
  43. #define MAX_GPSD_RETRY 10
  44. static struct gps_data_t gpsdata;
  45. /* UDP socket variables */
  46. #define MAX_UDP_DEST 5
  47. static struct sockaddr_in remote[MAX_UDP_DEST];
  48. static int sock[MAX_UDP_DEST];
  49. static int udpchannel;
  50. // gpsclient source
  51. static struct fixsource_t gpsd_source;
  52. static unsigned int flags;
  53. static unsigned int debug = 0;
  54. static bool aisonly = false;
  55. static bool tpvonly = false;
  56. // return local time hh:mm:ss
  57. static char* time2string(void)
  58. {
  59. static char buffer[MAX_TIME_LEN];
  60. time_t curtime;
  61. struct tm *loctime;
  62. // Get the current time.
  63. curtime = time(NULL);
  64. // Convert it to local time representation.
  65. loctime = localtime(&curtime);
  66. // Print it out in a nice format.
  67. (void)strftime(buffer, sizeof(buffer), "%H:%M:%S", loctime);
  68. return buffer;
  69. }
  70. static int send_udp(char *nmeastring, size_t ind)
  71. {
  72. char message[MAX_PACKET_LENGTH];
  73. char *buffer;
  74. int channel;
  75. static char mstr[] = "{\"class\":\"TPV\",";
  76. // if string length is unknown make a copy and compute it
  77. if (0 == ind) {
  78. // compute message size and add 0x0a 0x0d
  79. for (ind=0; nmeastring [ind] != '\0'; ind ++) {
  80. if ((sizeof(message) - 3) <= ind) {
  81. (void)fprintf(stderr, "gps2udp: too big [%s] \n", nmeastring);
  82. return -1;
  83. }
  84. message[ind] = nmeastring[ind];
  85. }
  86. buffer = message;
  87. } else {
  88. // use directly nmeastring but change terminition
  89. buffer = nmeastring;
  90. ind = ind-1;
  91. }
  92. // Add termination to NMEA feed for AISHUB
  93. buffer[ind] = '\r'; ind++;
  94. buffer[ind] = '\n'; ind++;
  95. buffer[ind] = '\0';
  96. if (0 == (flags & WATCH_JSON) &&
  97. '{' == buffer[0]) {
  98. // do not send JSON when not configured to do so
  99. // JSON, skip it
  100. if (1 < debug) {
  101. (void)fprintf(stdout, "...j [%s] '%s'\n", time2string(), buffer);
  102. }
  103. return 0;
  104. }
  105. if (tpvonly &&
  106. (0 != strncmp(mstr, buffer, sizeof(mstr) - 1))) {
  107. // only TPV requests, but not TPV, skip it
  108. if (1 < debug) {
  109. (void)fprintf(stdout, "...t [%s] '%s'\n", time2string(), buffer);
  110. }
  111. return 0;
  112. }
  113. // send message on udp channel
  114. for (channel=0; channel < udpchannel; channel ++) {
  115. ssize_t status = sendto(sock[channel],
  116. buffer,
  117. ind,
  118. 0,
  119. (struct sockaddr *)&remote[channel],
  120. (int)sizeof(remote));
  121. if (status < (ssize_t)ind) {
  122. (void)fprintf(stderr, "gps2udp: failed to send [%s] \n",
  123. buffer);
  124. return -1;
  125. }
  126. }
  127. return 0;
  128. }
  129. // Open and bind udp socket to host
  130. static int open_udp(char **hostport)
  131. {
  132. int channel;
  133. for (channel = 0; channel < udpchannel; channel++) {
  134. char *hostname = NULL;
  135. char *portname = NULL;
  136. char *endptr = NULL;
  137. int portnum;
  138. struct hostent *hp;
  139. if (NULL == hostport[channel]) {
  140. // pacify coverity
  141. (void)fprintf(stderr, "gps2udp: syntax is [-u hostname:port]\n");
  142. return -1;
  143. }
  144. // parse argument
  145. hostname = strtok(hostport[channel], ":");
  146. // NULL tells strtok() to resume search from last found token
  147. portname = strtok(NULL, ":");
  148. if ((NULL == hostname) ||
  149. (NULL == portname)) {
  150. (void)fprintf(stderr, "gps2udp: syntax is [-u hostname:port]\n");
  151. return -1;
  152. }
  153. errno = 0;
  154. portnum = (int)strtol(portname, &endptr, 10);
  155. if (1 > portnum || 65535 < portnum || '\0' != *endptr || 0 != errno) {
  156. (void)fprintf(stderr, "gps2udp: syntax is [-u hostname:port] "
  157. "[%s] is not a valid port number\n", portname);
  158. return -1;
  159. }
  160. sock[channel]= socket(AF_INET, SOCK_DGRAM, 0);
  161. if (0 > sock[channel]) {
  162. (void)fprintf(stderr, "gps2udp: error creating UDP socket\n");
  163. return -1;
  164. }
  165. remote[channel].sin_family = (sa_family_t)AF_INET;
  166. hp = gethostbyname(hostname);
  167. if (NULL == hp) {
  168. (void)fprintf(stderr,
  169. "gps2udp: syntax is [-u hostname:port] [%s]"
  170. " is not a valid hostname\n",
  171. hostname);
  172. return -1;
  173. }
  174. memcpy( &remote[channel].sin_addr, hp->h_addr_list[0], hp->h_length);
  175. remote[channel].sin_port = htons((in_port_t)portnum);
  176. }
  177. return 0;
  178. }
  179. static void usage(void)
  180. {
  181. (void)fprintf(stderr,
  182. "Usage: gps2udp [OPTIONS] [server[:port[:device]]]\n\n"
  183. #ifdef HAVE_GETOPT_LONG
  184. " -? Show this help, then exit\n"
  185. " --ais Select AIS messages only.\n"
  186. " --count COUNT exit after count packets.\n"
  187. " --daemon Daemonize\n"
  188. " --debug DEBUGLEVEL See -d for DEBUGLEVEL\n"
  189. " --help Show this help, then exit\n"
  190. " --json Feed JSON messages only.\n"
  191. " --nmea Feed NMEA messages only.\n"
  192. " --tpv Feed TPV JSON messages only.\n"
  193. " Implies --json.\n"
  194. " --udp HOST:PORT Send UDP feed to host:port.\n"
  195. " Up to five --udp accepted.\n"
  196. " --version Show version, then exit\n"
  197. #endif
  198. " -a Select AIS messages only.\n"
  199. " -b Run in background as a daemon.\n"
  200. " -c COUNT Exit after count packets.\n"
  201. " -d [0-2] 1 display sent packets, "
  202. "2 display ignored packets.\n"
  203. " -h Show this help.\n"
  204. " -j Feed JSON.\n"
  205. " -n Feed NMEA.\n"
  206. " -t Feed TPV JSON messages only.\n"
  207. " Implies --json.\n"
  208. " -u HOST:PORT Send UDP NMEA/JSON feed to "
  209. "host:port.\n"
  210. " Up to five -u accepted.\n"
  211. " -V Print version and exit.\n"
  212. "\n"
  213. "example: gps2udp -a -n -c 2 -d 1 -u data.aishub.net:2222 "
  214. "fridu.net\n"
  215. );
  216. }
  217. // loop until we connect with gpsd
  218. static void connect2gpsd(bool restart)
  219. {
  220. unsigned int delay;
  221. if (restart) {
  222. (void)gps_close(&gpsdata);
  223. if (0 < debug) {
  224. (void)fprintf(stdout,
  225. "gps2udp [%s] reset gpsd connection\n",
  226. time2string());
  227. }
  228. }
  229. // loop until we reach GPSd
  230. for (delay = 10; ; delay = delay * 2) {
  231. int status = gps_open(gpsd_source.server, gpsd_source.port, &gpsdata);
  232. if (0 != status) {
  233. (void)fprintf(stderr,
  234. "gps2udp [%s] connection failed at %s:%s\n",
  235. time2string(), gpsd_source.server, gpsd_source.port);
  236. (void)sleep(delay);
  237. } else {
  238. if (0 < debug) {
  239. (void)fprintf(stdout, "gps2udp [%s] connect to gpsd %s:%s\n",
  240. time2string(), gpsd_source.server,
  241. gpsd_source.port);
  242. }
  243. break;
  244. }
  245. }
  246. // select the right set of gps data
  247. (void)gps_stream(&gpsdata, flags, gpsd_source.device);
  248. }
  249. // get data from gpsd
  250. static ssize_t read_gpsd(char *message, size_t len)
  251. {
  252. int ind;
  253. char c;
  254. int retry = 0;
  255. struct timespec to;
  256. // allow room for trailing NUL
  257. len--;
  258. // loop until we get some data or an error
  259. for (ind = 0; ind < (int)len;) {
  260. // prepare for a blocking read with a 10s timeout
  261. to.tv_sec = 10;
  262. to.tv_nsec = 0;
  263. int result = nanowait(gpsdata.gps_fd, &to);
  264. switch (result) {
  265. case 1:
  266. // we have data waiting, let's process them
  267. // FIXME! Do not do one at a time!
  268. result = (int)read(gpsdata.gps_fd, &c, 1);
  269. // If we lost gpsd connection reset it
  270. if (1 != result ) {
  271. connect2gpsd (true);
  272. }
  273. if (('\n' == c) ||
  274. ('\r' == c)) {
  275. message[ind]='\0';
  276. if (0 < ind) {
  277. if (0 < retry) {
  278. if (1 == debug) {
  279. (void)fprintf (stdout,"\r");
  280. } else if (1 < debug) {
  281. (void)fprintf(stdout,
  282. " [%s] No Data for: %ds\n",
  283. time2string(), retry*10);
  284. }
  285. }
  286. if (tpvonly &&
  287. '{' != message[0]) {
  288. if (1 < debug) {
  289. (void)fprintf(stdout,
  290. "...{ [%s %d] '%s'\n", time2string(),
  291. ind, message);
  292. }
  293. return 0;
  294. }
  295. if (aisonly &&
  296. '!' != message[0]) {
  297. if (1 < debug) {
  298. (void)fprintf(stdout,
  299. "...! [%s %d] '%s'\n", time2string(),
  300. ind, message);
  301. }
  302. return 0;
  303. }
  304. }
  305. return (ssize_t)ind + 1;
  306. } else {
  307. message[ind]= c;
  308. ind++;
  309. }
  310. break;
  311. case 0: // no data fail in timeout
  312. retry++;
  313. // if too many empty packets are received reset gpsd connection
  314. if (MAX_GPSD_RETRY < retry) {
  315. connect2gpsd(true);
  316. retry = 0;
  317. }
  318. if (0 < debug) {
  319. ignore_return(write (1, ".", 1));
  320. }
  321. break;
  322. default: // we lost connection with gpsd
  323. connect2gpsd(true);
  324. break;
  325. }
  326. }
  327. message[ind] = '\0';
  328. (void)fprintf (stderr,"\n gps2udp: message too big [%s]\n", message);
  329. return -1;
  330. }
  331. // 6 bits decoding of AIS payload
  332. static unsigned char AISto6bit(unsigned char c)
  333. {
  334. unsigned char cp = c;
  335. if((unsigned char)0x30 > c) {
  336. return (unsigned char)-1;
  337. }
  338. if((unsigned char)0x77 < c) {
  339. return (unsigned char)-1;
  340. }
  341. if(((unsigned char)0x57 < c) &&
  342. ((unsigned char)0x60 > c)) {
  343. return (unsigned char)-1;
  344. }
  345. cp += (unsigned char)0x28;
  346. if((unsigned char)0x80 < cp) {
  347. cp += (unsigned char)0x20;
  348. } else {
  349. cp += (unsigned char)0x28;
  350. }
  351. return (unsigned char)(cp & (unsigned char)0x3f);
  352. }
  353. // get MMSI from AIS bit string
  354. static unsigned int AISGetInt(unsigned char *bitbytes, unsigned int sp,
  355. unsigned int len)
  356. {
  357. unsigned int acc = 0;
  358. unsigned int s0p = sp-1; // to zero base
  359. unsigned int i;
  360. for(i = 0; i < len; i++) {
  361. unsigned int cp, cx, c0;
  362. acc = acc << 1;
  363. cp = (s0p + i) / 6;
  364. cx = (unsigned int)bitbytes[cp]; // what if cp >= byte_length?
  365. c0 = (cx >> (5 - ((s0p + i) % 6))) & 1;
  366. acc |= c0;
  367. }
  368. return acc;
  369. }
  370. int main(int argc, char **argv)
  371. {
  372. bool daemonize = false;
  373. long count = -1;
  374. char *udphostport[MAX_UDP_DEST];
  375. const char *optstring = "?abc:d:hjntu:V";
  376. #ifdef HAVE_GETOPT_LONG
  377. int option_index = 0;
  378. static struct option long_options[] = {
  379. {"ais", no_argument, NULL, 'a'},
  380. {"count", required_argument, NULL, 'c'},
  381. {"daemon", no_argument, NULL, 'b'},
  382. {"debug", required_argument, NULL, 'd'},
  383. {"help", no_argument, NULL, 'h'},
  384. {"json", no_argument, NULL, 'j'},
  385. {"nmea", no_argument, NULL, 'n'},
  386. {"udp", required_argument, NULL, 'u'},
  387. {"version", no_argument, NULL, 'V' },
  388. {NULL, 0, NULL, 0},
  389. };
  390. #endif
  391. // pacify covarity
  392. memset(udphostport, 0, sizeof(udphostport));
  393. flags = WATCH_ENABLE;
  394. while (1) {
  395. int ch;
  396. #ifdef HAVE_GETOPT_LONG
  397. ch = getopt_long(argc, argv, optstring, long_options, &option_index);
  398. #else
  399. ch = getopt(argc, argv, optstring);
  400. #endif
  401. if (ch == -1) {
  402. break;
  403. }
  404. switch (ch) {
  405. case 'a':
  406. aisonly = true;
  407. if (0 < debug)
  408. (void)fprintf(stdout, "AIS only selected\n");
  409. break;
  410. case 'b':
  411. daemonize = true;
  412. if (0 < debug)
  413. (void)fprintf(stdout, "Daemonize selected\n");
  414. break;
  415. case 'c':
  416. count = atol(optarg);
  417. if (0 < debug)
  418. (void)fprintf(stdout, "Count %ld selected\n", count);
  419. break;
  420. case 'd':
  421. debug = atoi(optarg);
  422. if (2 < debug) {
  423. usage();
  424. exit(1);
  425. }
  426. if (0 < debug)
  427. (void)fprintf(stdout, "Debug %u selected\n", debug);
  428. break;
  429. case 'j':
  430. if (0 < debug)
  431. (void)fprintf(stdout, "JSON selected\n");
  432. flags |= WATCH_JSON;
  433. break;
  434. case 'n':
  435. if (0 < debug) {
  436. (void)fprintf(stdout, "NMEA selected\n");
  437. }
  438. flags |= WATCH_NMEA;
  439. break;
  440. case 't':
  441. if (0 < debug) {
  442. (void)fprintf(stdout, "TPV and JSON selected\n");
  443. }
  444. flags |= WATCH_JSON;
  445. tpvonly = true;
  446. break;
  447. case 'u':
  448. if (udpchannel >= MAX_UDP_DEST) {
  449. (void)fprintf(stderr,
  450. "gps2udp: too many UDP destinations (max=%d).\n",
  451. MAX_UDP_DEST);
  452. } else {
  453. udphostport[udpchannel++] = optarg;
  454. if (0 < debug) {
  455. (void)fprintf(stdout, "UDP %s added.\n", optarg);
  456. }
  457. }
  458. break;
  459. case '?':
  460. case 'h':
  461. default:
  462. usage();
  463. exit(1);
  464. case 'V':
  465. (void)fprintf(stderr, "%s: %s (revision %s)\n",
  466. argv[0], VERSION, REVISION);
  467. exit(0);
  468. }
  469. }
  470. // Grok the server, port, and device.
  471. if (optind < argc) {
  472. gpsd_source_spec(argv[optind], &gpsd_source);
  473. } else {
  474. gpsd_source_spec(NULL, &gpsd_source);
  475. }
  476. if (NULL == gpsd_source.device) {
  477. if (0 < debug) {
  478. (void)fprintf(stdout, "gpsd source %s:%s\n",
  479. gpsd_source.server, gpsd_source.port);
  480. }
  481. } else {
  482. flags |= WATCH_DEVICE;
  483. if (0 < debug) {
  484. (void)fprintf(stdout, "gpsd source %s:%s:%s\n",
  485. gpsd_source.server, gpsd_source.port, gpsd_source.device);
  486. }
  487. }
  488. // check before going background if we can connect to gpsd
  489. connect2gpsd(false);
  490. // Open UDP port
  491. if (0 < udpchannel) {
  492. int status = open_udp(udphostport);
  493. if (0 != status) {
  494. exit(1);
  495. }
  496. }
  497. // Daemonize if the user requested it.
  498. if (daemonize) {
  499. if (0 != os_daemon(0, 0)) {
  500. (void)fprintf(stderr,
  501. "gps2udp: daemonization failed: %s\n",
  502. strerror(errno));
  503. }
  504. }
  505. // infinite loop to get data from gpsd and push them to aggregators
  506. for (;;) {
  507. char buffer[MAX_PACKET_LENGTH];
  508. ssize_t len;
  509. len = read_gpsd(buffer, sizeof(buffer));
  510. // ignore empty message
  511. if (3 < len) {
  512. if (0 < debug) {
  513. (void)fprintf (stdout,"---> [%s] -- %s",time2string(),buffer);
  514. // Try to extract MMSI from AIS payload
  515. if (str_starts_with(buffer, "!AIVDM")) {
  516. #define MAX_INFO 6
  517. int i, j;
  518. char packet[MAX_PACKET_LENGTH];
  519. char *adrpkt = packet;
  520. unsigned char *info[MAX_INFO];
  521. unsigned int mmsi;
  522. unsigned char bitstrings[255];
  523. int info_5_len;
  524. // pacify coverity.
  525. memset(bitstrings, 0, sizeof(bitstrings));
  526. // strtok break original string
  527. (void)strlcpy(packet, buffer, sizeof(packet));
  528. for (j = 0; j < MAX_INFO; j++) {
  529. info[j] = (unsigned char *)strtok(adrpkt, ",");
  530. // have strtok() continue from last position
  531. adrpkt = NULL;
  532. }
  533. // codacy does not like strlen()
  534. info_5_len = (int)strnlen((char *)info[5],
  535. sizeof(bitstrings));
  536. if (((int)sizeof(bitstrings) - 1) <= info_5_len) {
  537. info_5_len = (int)sizeof(bitstrings) - 1;
  538. }
  539. for(i = 0 ; i < info_5_len; i++) {
  540. bitstrings[i] = AISto6bit(info[5][i]);
  541. }
  542. mmsi = AISGetInt(bitstrings, 9, 30);
  543. (void)fprintf(stdout," MMSI=%9u", mmsi);
  544. }
  545. (void)fprintf(stdout,"\n");
  546. }
  547. // send to all UDP destinations
  548. if (0 < udpchannel) {
  549. (void)send_udp(buffer, (size_t)len);
  550. }
  551. // if we count messages check it now
  552. if (0 <= count) {
  553. if (count-- == 0) {
  554. // completed count
  555. (void)fprintf(stderr,
  556. "gpsd2udp: normal exit after counted "
  557. "packets\n");
  558. exit (0);
  559. }
  560. } // end count
  561. } // end len > 3
  562. } // end for (;;)
  563. // This is an infinite loop, should never be here
  564. (void)fprintf (stderr, "gpsd2udp ERROR abnormal exit\n");
  565. exit (-1);
  566. }
  567. // vim: set expandtab shiftwidth=4