net_ntrip.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. /* net_ntrip.c -- gather and dispatch DGNSS data from Ntrip broadcasters
  2. *
  3. * This file is Copyright 2010 by the GPSD project
  4. * SPDX-License-Identifier: BSD-2-clause
  5. */
  6. #include "gpsd_config.h" /* must be before all includes */
  7. #include <errno.h>
  8. #include <fcntl.h>
  9. #include <math.h>
  10. #include <netdb.h>
  11. #include <stdbool.h>
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14. #include <string.h>
  15. #include <strings.h>
  16. #include <sys/socket.h>
  17. #include <sys/stat.h>
  18. #include <sys/types.h>
  19. #include <unistd.h>
  20. #include "gpsd.h"
  21. #include "strfuncs.h"
  22. #define NTRIP_SOURCETABLE "SOURCETABLE 200 OK\r\n"
  23. #define NTRIP_ENDSOURCETABLE "ENDSOURCETABLE"
  24. #define NTRIP_CAS "CAS;"
  25. #define NTRIP_NET "NET;"
  26. #define NTRIP_STR "STR;"
  27. #define NTRIP_BR "\r\n"
  28. #define NTRIP_QSC "\";\""
  29. #define NTRIP_ICY "ICY 200 OK"
  30. #define NTRIP_UNAUTH "401 Unauthorized"
  31. static char *ntrip_field_iterate(char *start,
  32. char *prev,
  33. const char *eol,
  34. const struct gpsd_errout_t *errout)
  35. {
  36. char *s, *t, *u;
  37. if (start)
  38. s = start;
  39. else {
  40. if (!prev)
  41. return NULL;
  42. s = prev + strlen(prev) + 1;
  43. if (s >= eol)
  44. return NULL;
  45. }
  46. /* ignore any quoted ; chars as they are part of the field content */
  47. t = s;
  48. while ((u = strstr(t, NTRIP_QSC)))
  49. t = u + strlen(NTRIP_QSC);
  50. if ((t = strstr(t, ";")))
  51. *t = '\0';
  52. GPSD_LOG(LOG_RAW, errout, "Next Ntrip source table field %s\n", s);
  53. return s;
  54. }
  55. static void ntrip_str_parse(char *str, size_t len,
  56. struct ntrip_stream_t *hold,
  57. const struct gpsd_errout_t *errout)
  58. {
  59. char *s, *eol = str + len;
  60. memset(hold, 0, sizeof(*hold));
  61. /* <mountpoint> */
  62. if ((s = ntrip_field_iterate(str, NULL, eol, errout)))
  63. (void)strlcpy(hold->mountpoint, s, sizeof(hold->mountpoint));
  64. /* <identifier> */
  65. s = ntrip_field_iterate(NULL, s, eol, errout);
  66. /* <format> */
  67. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  68. if ((strcasecmp("RTCM 2", s) == 0) ||
  69. (strcasecmp("RTCM2", s) == 0))
  70. hold->format = fmt_rtcm2;
  71. else if (strcasecmp("RTCM 2.0", s) == 0)
  72. hold->format = fmt_rtcm2_0;
  73. else if (strcasecmp("RTCM 2.1", s) == 0)
  74. hold->format = fmt_rtcm2_1;
  75. else if ((strcasecmp("RTCM 2.2", s) == 0) ||
  76. (strcasecmp("RTCM22", s) == 0))
  77. hold->format = fmt_rtcm2_2;
  78. else if ((strcasecmp("RTCM2.3", s) == 0) ||
  79. (strcasecmp("RTCM 2.3", s) == 0))
  80. hold->format = fmt_rtcm2_3;
  81. /* required for the SAPOS derver in Gemany, confirmed as RTCM2.3 */
  82. else if (strcasecmp("RTCM1_", s) == 0)
  83. hold->format = fmt_rtcm2_3;
  84. else if ((strcasecmp("RTCM 3", s) == 0) ||
  85. (strcasecmp("RTCM 3.0", s) == 0) ||
  86. (strcasecmp("RTCM3.0", s) == 0) ||
  87. (strcasecmp("RTCM3", s) == 0))
  88. hold->format = fmt_rtcm3_0;
  89. else if ((strcasecmp("RTCM3.1", s) == 0) ||
  90. (strcasecmp("RTCM 3.1", s) == 0))
  91. hold->format = fmt_rtcm3_1;
  92. else if ((strcasecmp("RTCM 3.2", s) == 0) ||
  93. (strcasecmp("RTCM32", s) == 0))
  94. hold->format = fmt_rtcm3_2;
  95. else if (strcasecmp("RTCM 3.3", s) == 0)
  96. hold->format = fmt_rtcm3_3;
  97. else
  98. hold->format = fmt_unknown;
  99. }
  100. /* <format-details> */
  101. s = ntrip_field_iterate(NULL, s, eol, errout);
  102. /* <carrier> */
  103. if ((s = ntrip_field_iterate(NULL, s, eol, errout)))
  104. hold->carrier = atoi(s);
  105. /* <nav-system> */
  106. s = ntrip_field_iterate(NULL, s, eol, errout);
  107. /* <network> */
  108. s = ntrip_field_iterate(NULL, s, eol, errout);
  109. /* <country> */
  110. s = ntrip_field_iterate(NULL, s, eol, errout);
  111. /* <latitude> */
  112. hold->latitude = NAN;
  113. if ((s = ntrip_field_iterate(NULL, s, eol, errout)))
  114. hold->latitude = safe_atof(s);
  115. /* <longitude> */
  116. hold->longitude = NAN;
  117. if ((s = ntrip_field_iterate(NULL, s, eol, errout)))
  118. hold->longitude = safe_atof(s);
  119. /* <nmea> */
  120. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  121. hold->nmea = atoi(s);
  122. }
  123. /* <solution> */
  124. s = ntrip_field_iterate(NULL, s, eol, errout);
  125. /* <generator> */
  126. s = ntrip_field_iterate(NULL, s, eol, errout);
  127. /* <compr-encryp> */
  128. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  129. if (strcasecmp("none", s) == 0)
  130. hold->compr_encryp = cmp_enc_none;
  131. else
  132. hold->compr_encryp = cmp_enc_unknown;
  133. }
  134. /* <authentication> */
  135. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  136. if (strcasecmp("N", s) == 0)
  137. hold->authentication = auth_none;
  138. else if (strcasecmp("B", s) == 0)
  139. hold->authentication = auth_basic;
  140. else if (strcasecmp("D", s) == 0)
  141. hold->authentication = auth_digest;
  142. else
  143. hold->authentication = auth_unknown;
  144. }
  145. /* <fee> */
  146. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  147. hold->fee = atoi(s);
  148. }
  149. /* <bitrate> */
  150. if ((s = ntrip_field_iterate(NULL, s, eol, errout))) {
  151. hold->bitrate = atoi(s);
  152. }
  153. /* ...<misc> */
  154. while ((s = ntrip_field_iterate(NULL, s, eol, errout)));
  155. }
  156. static int ntrip_sourcetable_parse(struct gps_device_t *device)
  157. {
  158. struct ntrip_stream_t hold;
  159. ssize_t llen, len = 0;
  160. char *line;
  161. bool sourcetable = false;
  162. bool match = false;
  163. char buf[BUFSIZ];
  164. size_t blen = sizeof(buf);
  165. int fd = device->gpsdata.gps_fd;
  166. for (;;) {
  167. char *eol;
  168. ssize_t rlen;
  169. memset(&buf[len], 0, (size_t) (blen - len));
  170. rlen = read(fd, &buf[len], (size_t)(blen - 1 - len));
  171. if (rlen == -1) {
  172. if (errno == EINTR) {
  173. continue;
  174. }
  175. if (sourcetable && !match && errno == EAGAIN) {
  176. /* we have not yet found a match, but there currently
  177. * is no more data */
  178. return 0;
  179. }
  180. if (match) {
  181. return 1;
  182. }
  183. GPSD_LOG(LOG_ERROR, &device->context->errout,
  184. "ntrip stream read error %d on fd %d\n",
  185. errno, fd);
  186. return -1;
  187. } else if (rlen == 0) { // server closed the connection
  188. GPSD_LOG(LOG_ERROR, &device->context->errout,
  189. "ntrip stream unexpected close %d on fd %d "
  190. "during sourcetable read\n",
  191. errno, fd);
  192. return -1;
  193. }
  194. line = buf;
  195. rlen = len += rlen;
  196. line[rlen] = '\0'; // pacify coverity that this is NUL terminated
  197. GPSD_LOG(LOG_RAW, &device->context->errout,
  198. "Ntrip source table buffer %s\n", buf);
  199. sourcetable = device->ntrip.sourcetable_parse;
  200. if (!sourcetable) {
  201. /* parse SOURCETABLE */
  202. if (str_starts_with(line, NTRIP_SOURCETABLE)) {
  203. sourcetable = true;
  204. device->ntrip.sourcetable_parse = true;
  205. llen = (ssize_t) strlen(NTRIP_SOURCETABLE);
  206. line += llen;
  207. len -= llen;
  208. } else {
  209. GPSD_LOG(LOG_WARN, &device->context->errout,
  210. "Received unexpected Ntrip reply %s.\n",
  211. buf);
  212. return -1;
  213. }
  214. }
  215. while (len > 0) {
  216. /* parse ENDSOURCETABLE */
  217. if (str_starts_with(line, NTRIP_ENDSOURCETABLE))
  218. goto done;
  219. eol = strstr(line, NTRIP_BR);
  220. if (NULL == eol){
  221. break;
  222. }
  223. GPSD_LOG(LOG_DATA, &device->context->errout,
  224. "next Ntrip source table line %s\n", line);
  225. *eol = '\0';
  226. llen = (ssize_t) (eol - line);
  227. /* TODO: parse headers */
  228. /* parse STR */
  229. if (str_starts_with(line, NTRIP_STR)) {
  230. ntrip_str_parse(line + strlen(NTRIP_STR),
  231. (size_t) (llen - strlen(NTRIP_STR)),
  232. &hold, &device->context->errout);
  233. if (strcmp(device->ntrip.stream.mountpoint,
  234. hold.mountpoint) == 0) {
  235. /* TODO: support for RTCM 3.0, SBAS (WAAS, EGNOS), ... */
  236. if (hold.format == fmt_unknown) {
  237. GPSD_LOG(LOG_ERROR, &device->context->errout,
  238. "Ntrip stream %s format not supported\n",
  239. line);
  240. return -1;
  241. }
  242. /* TODO: support encryption and compression algorithms */
  243. if (hold.compr_encryp != cmp_enc_none) {
  244. GPSD_LOG(LOG_ERROR, &device->context->errout,
  245. "Ntrip stream %s compression/encryption "
  246. "algorithm not supported\n",
  247. line);
  248. return -1;
  249. }
  250. /* TODO: support digest authentication */
  251. if (hold.authentication != auth_none
  252. && hold.authentication != auth_basic) {
  253. GPSD_LOG(LOG_ERROR, &device->context->errout,
  254. "Ntrip stream %s authentication method "
  255. "not supported\n",
  256. line);
  257. return -1;
  258. }
  259. /* no memcpy, so we can keep the other infos */
  260. device->ntrip.stream.format = hold.format;
  261. device->ntrip.stream.carrier = hold.carrier;
  262. device->ntrip.stream.latitude = hold.latitude;
  263. device->ntrip.stream.longitude = hold.longitude;
  264. device->ntrip.stream.nmea = hold.nmea;
  265. device->ntrip.stream.compr_encryp = hold.compr_encryp;
  266. device->ntrip.stream.authentication = hold.authentication;
  267. device->ntrip.stream.fee = hold.fee;
  268. device->ntrip.stream.bitrate = hold.bitrate;
  269. device->ntrip.stream.set = true;
  270. match = true;
  271. }
  272. /* TODO: compare stream location to own location to
  273. * find nearest stream if user hasn't provided one */
  274. }
  275. /* TODO: parse CAS */
  276. /* else if (str_starts_with(line, NTRIP_CAS)); */
  277. /* TODO: parse NET */
  278. /* else if (str_starts_with(line, NTRIP_NET)); */
  279. llen += strlen(NTRIP_BR);
  280. line += llen;
  281. len -= llen;
  282. GPSD_LOG(LOG_RAW, &device->context->errout,
  283. "Remaining Ntrip source table buffer %zd %s\n", len,
  284. line);
  285. }
  286. /* message too big to fit into buffer */
  287. if ((size_t)len == blen - 1)
  288. return -1;
  289. if (len > 0)
  290. memmove(buf, &buf[rlen - len], (size_t) len);
  291. }
  292. done:
  293. return match ? 1 : -1;
  294. }
  295. static int ntrip_stream_req_probe(const struct ntrip_stream_t *stream,
  296. struct gpsd_errout_t *errout)
  297. {
  298. int dsock;
  299. ssize_t r;
  300. char buf[BUFSIZ];
  301. dsock = netlib_connectsock(AF_UNSPEC, stream->url, stream->port, "tcp");
  302. if (dsock < 0) {
  303. GPSD_LOG(LOG_ERROR, errout,
  304. "ntrip stream connect error %d in req probe\n", dsock);
  305. return -1;
  306. }
  307. GPSD_LOG(LOG_SPIN, errout,
  308. "ntrip stream for req probe connected on fd %d\n", dsock);
  309. (void)snprintf(buf, sizeof(buf),
  310. "GET / HTTP/1.1\r\n"
  311. "User-Agent: NTRIP gpsd/%s\r\n"
  312. "Host: %s\r\n"
  313. "Connection: close\r\n"
  314. "\r\n", VERSION, stream->url);
  315. r = write(dsock, buf, strlen(buf));
  316. if (r != (ssize_t)strlen(buf)) {
  317. GPSD_LOG(LOG_ERROR, errout,
  318. "ntrip stream write error %d on fd %d "
  319. "during probe request %zd\n",
  320. errno, dsock, r);
  321. (void)close(dsock);
  322. return -1;
  323. }
  324. /* coverity[leaked_handle] This is an intentional allocation */
  325. return dsock;
  326. }
  327. static int ntrip_auth_encode(const struct ntrip_stream_t *stream,
  328. const char *auth,
  329. char buf[],
  330. size_t size)
  331. {
  332. memset(buf, 0, size);
  333. if (stream->authentication == auth_none)
  334. return 0;
  335. else if (stream->authentication == auth_basic) {
  336. char authenc[64];
  337. if (!auth)
  338. return -1;
  339. memset(authenc, 0, sizeof(authenc));
  340. if (b64_ntop
  341. ((unsigned char *)auth, strlen(auth), authenc,
  342. sizeof(authenc) - 1) < 0)
  343. return -1;
  344. (void)snprintf(buf, size - 1, "Authorization: Basic %s\r\n", authenc);
  345. } else {
  346. /* TODO: support digest authentication */
  347. }
  348. return 0;
  349. }
  350. /* *INDENT-ON* */
  351. static int ntrip_stream_get_req(const struct ntrip_stream_t *stream,
  352. const struct gpsd_errout_t *errout)
  353. {
  354. int dsock;
  355. char buf[BUFSIZ];
  356. dsock = netlib_connectsock(AF_UNSPEC, stream->url, stream->port, "tcp");
  357. if (BAD_SOCKET(dsock)) {
  358. GPSD_LOG(LOG_ERROR, errout,
  359. "ntrip stream connect error %d\n", dsock);
  360. return -1;
  361. }
  362. GPSD_LOG(LOG_SPIN, errout,
  363. "netlib_connectsock() returns socket on fd %d\n",
  364. dsock);
  365. (void)snprintf(buf, sizeof(buf),
  366. "GET /%s HTTP/1.1\r\n"
  367. "User-Agent: NTRIP gpsd/%s\r\n"
  368. "Host: %s\r\n"
  369. "Accept: rtk/rtcm, dgps/rtcm\r\n"
  370. "%s"
  371. "Connection: close\r\n"
  372. "\r\n", stream->mountpoint, VERSION, stream->url, stream->authStr);
  373. if (write(dsock, buf, strlen(buf)) != (ssize_t) strlen(buf)) {
  374. GPSD_LOG(LOG_ERROR, errout,
  375. "ntrip stream write error %d on fd %d during get request\n",
  376. errno, dsock);
  377. (void)close(dsock);
  378. return -1;
  379. }
  380. return dsock;
  381. }
  382. static int ntrip_stream_get_parse(const struct ntrip_stream_t *stream,
  383. const int dsock,
  384. const struct gpsd_errout_t *errout)
  385. {
  386. char buf[BUFSIZ];
  387. int opts;
  388. memset(buf, 0, sizeof(buf));
  389. while (read(dsock, buf, sizeof(buf) - 1) == -1) {
  390. if (errno == EINTR)
  391. continue;
  392. GPSD_LOG(LOG_ERROR, errout,
  393. "ntrip stream read error %d on fd %d during get rsp\n", errno,
  394. dsock);
  395. goto close;
  396. }
  397. buf[sizeof(buf) - 1] = '\0'; // pacify coverity about NUL-terminated.
  398. /* parse 401 Unauthorized */
  399. if (NULL != strstr(buf, NTRIP_UNAUTH)) {
  400. GPSD_LOG(LOG_ERROR, errout,
  401. "not authorized for Ntrip stream %s/%s\n", stream->url,
  402. stream->mountpoint);
  403. goto close;
  404. }
  405. /* parse SOURCETABLE */
  406. if (NULL != strstr(buf, NTRIP_SOURCETABLE)) {
  407. GPSD_LOG(LOG_ERROR, errout,
  408. "Broadcaster doesn't recognize Ntrip stream %s:%s/%s\n",
  409. stream->url, stream->port, stream->mountpoint);
  410. goto close;
  411. }
  412. /* parse ICY 200 OK */
  413. if (NULL == strstr(buf, NTRIP_ICY)) {
  414. GPSD_LOG(LOG_ERROR, errout,
  415. "Unknown reply %s from Ntrip service %s:%s/%s\n", buf,
  416. stream->url, stream->port, stream->mountpoint);
  417. goto close;
  418. }
  419. opts = fcntl(dsock, F_GETFL);
  420. if (opts >= 0)
  421. (void)fcntl(dsock, F_SETFL, opts | O_NONBLOCK);
  422. return dsock;
  423. close:
  424. (void)close(dsock);
  425. return -1;
  426. }
  427. /* open a connection to a Ntrip broadcaster */
  428. int ntrip_open(struct gps_device_t *device, char *caster)
  429. {
  430. char *amp, *colon, *slash;
  431. char *auth = NULL;
  432. char *port = NULL;
  433. char *stream = NULL;
  434. char *url = NULL;
  435. int ret = -1;
  436. switch (device->ntrip.conn_state) {
  437. case ntrip_conn_init:
  438. /* this has to be done here,
  439. * because it is needed for multi-stage connection */
  440. device->servicetype = service_ntrip;
  441. device->ntrip.works = false;
  442. device->ntrip.sourcetable_parse = false;
  443. device->ntrip.stream.set = false;
  444. if ((amp = strchr(caster, '@')) != NULL) {
  445. if (((colon = strchr(caster, ':')) != NULL) && colon < amp) {
  446. auth = caster;
  447. *amp = '\0';
  448. caster = amp + 1;
  449. url = caster;
  450. } else {
  451. GPSD_LOG(LOG_ERROR, &device->context->errout,
  452. "can't extract user-ID and password from %s\n",
  453. caster);
  454. device->ntrip.conn_state = ntrip_conn_err;
  455. return -1;
  456. }
  457. }
  458. if ((slash = strchr(caster, '/')) != NULL) {
  459. *slash = '\0';
  460. stream = slash + 1;
  461. } else {
  462. /* TODO: add autoconnect like in dgpsip.c */
  463. GPSD_LOG(LOG_ERROR, &device->context->errout,
  464. "can't extract Ntrip stream from %s\n",
  465. caster);
  466. device->ntrip.conn_state = ntrip_conn_err;
  467. return -1;
  468. }
  469. if ((colon = strchr(caster, ':')) != NULL) {
  470. port = colon + 1;
  471. *colon = '\0';
  472. }
  473. if (NULL == url) {
  474. // there was no @ in caster
  475. url = caster;
  476. }
  477. if (!port) {
  478. port = "rtcm-sc104";
  479. if (!getservbyname(port, "tcp"))
  480. port = DEFAULT_RTCM_PORT;
  481. }
  482. (void)strlcpy(device->ntrip.stream.mountpoint,
  483. stream,
  484. sizeof(device->ntrip.stream.mountpoint));
  485. if (auth != NULL)
  486. (void)strlcpy(device->ntrip.stream.credentials,
  487. auth,
  488. sizeof(device->ntrip.stream.credentials));
  489. /*
  490. * Semantically url and port ought to be non-NULL by now,
  491. * but just in case...this code appeases Coverity.
  492. */
  493. if (url != NULL)
  494. (void)strlcpy(device->ntrip.stream.url,
  495. url,
  496. sizeof(device->ntrip.stream.url));
  497. if (port != NULL)
  498. (void)strlcpy(device->ntrip.stream.port,
  499. port,
  500. sizeof(device->ntrip.stream.port));
  501. ret = ntrip_stream_req_probe(&device->ntrip.stream,
  502. &device->context->errout);
  503. if (ret == -1) {
  504. device->ntrip.conn_state = ntrip_conn_err;
  505. return -1;
  506. }
  507. device->gpsdata.gps_fd = ret;
  508. device->ntrip.conn_state = ntrip_conn_sent_probe;
  509. return ret;
  510. case ntrip_conn_sent_probe:
  511. ret = ntrip_sourcetable_parse(device);
  512. if (ret == -1) {
  513. device->ntrip.conn_state = ntrip_conn_err;
  514. return -1;
  515. }
  516. if (ret == 0 && device->ntrip.stream.set == false) {
  517. return ret;
  518. }
  519. (void)close(device->gpsdata.gps_fd);
  520. if (ntrip_auth_encode(&device->ntrip.stream,
  521. device->ntrip.stream.credentials,
  522. device->ntrip.stream.authStr,
  523. sizeof(device->ntrip.stream.authStr)) != 0) {
  524. device->ntrip.conn_state = ntrip_conn_err;
  525. return -1;
  526. }
  527. ret = ntrip_stream_get_req(&device->ntrip.stream,
  528. &device->context->errout);
  529. if (ret == -1) {
  530. device->ntrip.conn_state = ntrip_conn_err;
  531. return -1;
  532. }
  533. device->gpsdata.gps_fd = ret;
  534. device->ntrip.conn_state = ntrip_conn_sent_get;
  535. break;
  536. case ntrip_conn_sent_get:
  537. ret = ntrip_stream_get_parse(&device->ntrip.stream,
  538. device->gpsdata.gps_fd,
  539. &device->context->errout);
  540. if (ret == -1) {
  541. device->ntrip.conn_state = ntrip_conn_err;
  542. return -1;
  543. }
  544. device->ntrip.conn_state = ntrip_conn_established;
  545. device->ntrip.works = true; // we know, this worked.
  546. break;
  547. case ntrip_conn_established:
  548. case ntrip_conn_err:
  549. return -1;
  550. }
  551. return ret;
  552. }
  553. /* may be time to ship a usage report to the Ntrip caster */
  554. void ntrip_report(struct gps_context_t *context,
  555. struct gps_device_t *gps,
  556. struct gps_device_t *caster)
  557. {
  558. static int count;
  559. /*
  560. * 10 is an arbitrary number, the point is to have gotten several good
  561. * fixes before reporting usage to our Ntrip caster.
  562. *
  563. * count % 5 is as arbitrary a number as the fixcnt. But some delay
  564. * was needed here
  565. */
  566. count ++;
  567. if (caster->ntrip.stream.nmea != 0 &&
  568. context->fixcnt > 10 && (count % 5) == 0) {
  569. if (caster->gpsdata.gps_fd > -1) {
  570. char buf[BUFSIZ];
  571. gpsd_position_fix_dump(gps, buf, sizeof(buf));
  572. if (write(caster->gpsdata.gps_fd, buf, strlen(buf)) ==
  573. (ssize_t) strlen(buf)) {
  574. GPSD_LOG(LOG_IO, &context->errout, "=> dgps %s\n", buf);
  575. } else {
  576. GPSD_LOG(LOG_IO, &context->errout,
  577. "ntrip report write failed\n");
  578. }
  579. }
  580. }
  581. }
  582. // vim: set expandtab shiftwidth=4