driver_greis.c 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207
  1. /*
  2. * A Javad GNSS Receiver External Interface Specification (GREIS) driver.
  3. *
  4. * Author(s):
  5. * - Gregory Fong <gregory.fong@virginorbit.com>
  6. *
  7. * Documentation for GREIS can be found at:
  8. http://www.javad.com/downloads/javadgnss/manuals/GREIS/GREIS_Reference_Guide.pdf
  9. *
  10. * The version used for reference is that which
  11. * "Reflects Firmware Version 3.6.7, Last revised: August 25, 2016".
  12. *
  13. * This assumes little endian byte order in messages, which is the default, but
  14. * that is configurable. A future improvement could change to read the
  15. * information in [MF] Message Format.
  16. *
  17. * This file is Copyright 2017 Virgin Orbit
  18. * This file is Copyright 2017 the GPSD project
  19. * SPDX-License-Identifier: BSD-2-clause
  20. */
  21. #include "gpsd_config.h" /* must be before all includes */
  22. #include <assert.h>
  23. #include <math.h>
  24. #include <stdbool.h>
  25. #include <stdio.h>
  26. #include <stdlib.h> /* for abs() */
  27. #include <string.h>
  28. #include <sys/select.h>
  29. #include "bits.h"
  30. #include "driver_greis.h"
  31. #include "gpsd.h"
  32. #include "timespec.h"
  33. #if defined(GREIS_ENABLE) && defined(BINARY_ENABLE)
  34. #define HEADER_LENGTH 5
  35. static ssize_t greis_write(struct gps_device_t *session,
  36. const char *msg, size_t msglen);
  37. static const char disable_messages[] = "\%dm\%dm";
  38. static const char get_vendor[] = "\%vendor\%print,/par/rcv/vendor";
  39. static const char get_ver[] = "\%ver\%print,rcv/ver";
  40. static const char set_update_rate_4hz[] = "\%msint\%set,/par/raw/msint,250";
  41. /* Where applicable, the order here is how these will be received per cycle. */
  42. /* TODO: stop hardcoding the cycle time, make it selectable */
  43. static const char enable_messages_4hz[] =
  44. "\%em\%em,,jps/{RT,UO,GT,PV,SG,DP,SI,EL,AZ,EC,SS,ET}:0.25";
  45. /*
  46. * GREIS message handlers. The checksum has been already confirmed valid in the
  47. * packet acceptance logic, so we don't need to retest it here.
  48. */
  49. /**
  50. * Handle the message [RE] Reply
  51. */
  52. static gps_mask_t greis_msg_RE(struct gps_device_t *session,
  53. unsigned char *buf, size_t len)
  54. {
  55. if (0 == memcmp(buf, "%ver%", 5)) {
  56. strlcpy(session->subtype, (const char*)&buf[5],
  57. sizeof(session->subtype));
  58. GPSD_LOG(LOG_DATA, &session->context->errout,
  59. "GREIS: RE, ->subtype: %s\n", session->subtype);
  60. return DEVICEID_SET;
  61. }
  62. GPSD_LOG(LOG_INFO, &session->context->errout,
  63. "GREIS: RE %3zd, reply: %.*s\n", len, (int)len, buf);
  64. return 0;
  65. }
  66. /**
  67. * Handle the message [ER] Reply
  68. */
  69. static gps_mask_t greis_msg_ER(struct gps_device_t *session,
  70. unsigned char *buf, size_t len)
  71. {
  72. GPSD_LOG(LOG_WARN, &session->context->errout,
  73. "GREIS: ER %3zd, reply: %.*s\n", len, (int)len, buf);
  74. return 0;
  75. }
  76. /**
  77. * Handle the message [~~](RT) Receiver Time.
  78. */
  79. static gps_mask_t greis_msg_RT(struct gps_device_t *session,
  80. unsigned char *buf, size_t len)
  81. {
  82. if (len < 5) {
  83. GPSD_LOG(LOG_WARN, &session->context->errout,
  84. "GREIS: RT bad len %zu\n", len);
  85. return 0;
  86. }
  87. session->driver.greis.rt_tod = getleu32(buf, 0);
  88. memset(&session->gpsdata.raw, 0, sizeof(session->gpsdata.raw));
  89. session->driver.greis.seen_rt = true;
  90. session->driver.greis.seen_az = false;
  91. session->driver.greis.seen_ec = false;
  92. session->driver.greis.seen_el = false;
  93. session->driver.greis.seen_si = false;
  94. GPSD_LOG(LOG_DATA, &session->context->errout,
  95. "GREIS: RT, tod: %lu\n",
  96. (unsigned long)session->driver.greis.rt_tod);
  97. return CLEAR_IS;
  98. }
  99. /**
  100. * Handle the message [UO] GPS UTC Time Parameters.
  101. */
  102. static gps_mask_t greis_msg_UO(struct gps_device_t *session,
  103. unsigned char *buf, size_t len)
  104. {
  105. /*
  106. * For additional details on these parameters and the computation done using
  107. * them, refer to the Javad GREIS spec mentioned at the top of this file and
  108. * also to ICD-GPS-200C, Revision IRN-200C-004 April 12, 2000. At the time
  109. * of writing, that could be found at
  110. * https://www.navcen.uscg.gov/pubs/gps/icd200/ICD200Cw1234.pdf .
  111. */
  112. uint32_t tot; /* Reference time of week [s] */
  113. uint16_t wnt; /* Reference week number [dimensionless] */
  114. int8_t dtls; /* Delta time due to leap seconds [s] */
  115. uint8_t dn; /* 'Future' reference day number [1..7] */
  116. uint16_t wnlsf; /* 'Future' reference week number [dimensionless] */
  117. int8_t dtlsf; /* 'Future' delta time due to leap seconds [s] */
  118. if (len < 24) {
  119. GPSD_LOG(LOG_WARN, &session->context->errout,
  120. "GREIS: UO bad len %zu\n", len);
  121. return 0;
  122. }
  123. tot = getleu32(buf, 12);
  124. wnt = getleu16(buf, 16);
  125. dtls = getsb(buf, 18);
  126. dn = getub(buf, 19);
  127. wnlsf = getleu16(buf, 20);
  128. dtlsf = getsb(buf, 22);
  129. session->driver.greis.seen_uo = true;
  130. /*
  131. * See ICD-GPS-200C 20.3.3.5.2.4 "Universal Coordinated Time (UTC)".
  132. * I totally ripped this off of driver_navcom.c. Might want to dedupe at
  133. * some point.
  134. */
  135. if ((wnt % 256U) * 604800U + tot < wnlsf * 604800U + dn * 86400U) {
  136. /* Current time is before effectivity time of the leap second event */
  137. session->context->leap_seconds = dtls;
  138. } else {
  139. session->context->leap_seconds = dtlsf;
  140. }
  141. GPSD_LOG(LOG_DATA, &session->context->errout,
  142. "GREIS: UO, leap_seconds: %d\n", session->context->leap_seconds);
  143. return 0;
  144. }
  145. /**
  146. * Handle the message [GT] GPS Time.
  147. */
  148. static gps_mask_t greis_msg_GT(struct gps_device_t *session,
  149. unsigned char *buf, size_t len)
  150. {
  151. timespec_t ts_tow;
  152. uint32_t tow; /* Time of week [ms] */
  153. uint16_t wn; /* GPS week number (modulo 1024) [dimensionless] */
  154. char ts_buf[TIMESPEC_LEN];
  155. if (len < 7) {
  156. GPSD_LOG(LOG_WARN, &session->context->errout,
  157. "GREIS: GT bad len %zu\n", len);
  158. return 0;
  159. }
  160. if (!session->driver.greis.seen_uo) {
  161. GPSD_LOG(LOG_WARN, &session->context->errout,
  162. "GREIS: can't use GT until after UO has supplied "
  163. "leap second data\n");
  164. return 0;
  165. }
  166. tow = getleu32(buf, 0);
  167. wn = getleu16(buf, 4);
  168. MSTOTS(&ts_tow, tow);
  169. session->newdata.time = gpsd_gpstime_resolv(session, wn, ts_tow);
  170. GPSD_LOG(LOG_DATA, &session->context->errout,
  171. "GREIS: GT, tow: %" PRIu32 ", wn: %" PRIu16 ", time: %s Leap:%u\n",
  172. tow, wn,
  173. timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)),
  174. session->context->leap_seconds);
  175. /* save raw.mtime, just in case */
  176. session->gpsdata.raw.mtime = session->newdata.time;
  177. return TIME_SET | NTPTIME_IS | ONLINE_SET;
  178. }
  179. /**
  180. * Handle the message [PV] Cartesian Position and Velocity.
  181. */
  182. static gps_mask_t greis_msg_PV(struct gps_device_t *session,
  183. unsigned char *buf, size_t len)
  184. {
  185. double x, y, z; /* Cartesian coordinates [m] */
  186. float p_sigma; /* Position spherical error probability (SEP) [m] */
  187. float vx, vy, vz; /* Cartesian velocities [m/s] */
  188. float v_sigma; /* Velocity SEP [m/s] */
  189. uint8_t solution_type;
  190. gps_mask_t mask = 0;
  191. if (len < 46) {
  192. GPSD_LOG(LOG_WARN, &session->context->errout,
  193. "GREIS: PV bad len %zu\n", len);
  194. return 0;
  195. }
  196. x = getled64((char *)buf, 0);
  197. y = getled64((char *)buf, 8);
  198. z = getled64((char *)buf, 16);
  199. p_sigma = getlef32((char *)buf, 24);
  200. vx = getlef32((char *)buf, 28);
  201. vy = getlef32((char *)buf, 32);
  202. vz = getlef32((char *)buf, 36);
  203. v_sigma = getlef32((char *)buf, 40);
  204. solution_type = getub(buf, 44);
  205. session->newdata.ecef.x = x;
  206. session->newdata.ecef.y = y;
  207. session->newdata.ecef.z = z;
  208. session->newdata.ecef.pAcc = p_sigma;
  209. session->newdata.ecef.vx = vx;
  210. session->newdata.ecef.vy = vy;
  211. session->newdata.ecef.vz = vz;
  212. session->newdata.ecef.vAcc = v_sigma;
  213. /* GREIS Reference Guide 3.4.2 "General Notes" part "Solution Types" */
  214. if (solution_type > 0 && solution_type < 5) {
  215. session->newdata.mode = MODE_3D;
  216. if (solution_type > 1)
  217. session->newdata.status = STATUS_DGPS_FIX;
  218. else
  219. session->newdata.status = STATUS_FIX;
  220. }
  221. GPSD_LOG(LOG_DATA, &session->context->errout,
  222. "GREIS: PV, ECEF x=%.2f y=%.2f z=%.2f pAcc=%.2f\n",
  223. session->newdata.ecef.x,
  224. session->newdata.ecef.y,
  225. session->newdata.ecef.z,
  226. session->newdata.ecef.pAcc);
  227. GPSD_LOG(LOG_DATA, &session->context->errout,
  228. "GREIS: PV, ECEF vx=%.2f vy=%.2f vz=%.2f vAcc=%.2f "
  229. "solution_type: %d\n",
  230. session->newdata.ecef.vx,
  231. session->newdata.ecef.vy,
  232. session->newdata.ecef.vz,
  233. session->newdata.ecef.vAcc,
  234. solution_type);
  235. mask |= MODE_SET | STATUS_SET | ECEF_SET | VECEF_SET;
  236. return mask;
  237. }
  238. /**
  239. * Handle the message [SG] Position and Velocity RMS Errors.
  240. */
  241. static gps_mask_t greis_msg_SG(struct gps_device_t *session,
  242. unsigned char *buf, size_t len)
  243. {
  244. float hpos; /* Horizontal position RMS error [m] */
  245. float vpos; /* Vertical position RMS error [m] */
  246. float hvel; /* Horizontal velocity RMS error [m/s] */
  247. float vvel; /* Vertical velocity RMS error [m/s] */
  248. if (len < 18) {
  249. GPSD_LOG(LOG_WARN, &session->context->errout,
  250. "GREIS: SG bad len %zu\n", len);
  251. return 0;
  252. }
  253. hpos = getlef32((char *)buf, 0);
  254. vpos = getlef32((char *)buf, 4);
  255. hvel = getlef32((char *)buf, 8);
  256. vvel = getlef32((char *)buf, 12);
  257. /*
  258. * All errors are RMS which can be approximated as 1 sigma, so we can just
  259. * use them directly.
  260. *
  261. * Compute missing items in gpsd_error_model(), not here.
  262. */
  263. session->newdata.eph = hpos;
  264. session->newdata.epv = vpos;
  265. session->newdata.eps = hvel;
  266. session->newdata.epc = vvel;
  267. GPSD_LOG(LOG_DATA, &session->context->errout,
  268. "GREIS: SG, eph: %.2f, eps: %.2f, epc: %.2f\n",
  269. session->newdata.eph,
  270. session->newdata.eps, session->newdata.epc);
  271. return HERR_SET | SPEEDERR_SET | CLIMBERR_SET;
  272. }
  273. /**
  274. * Handle the message [DP] Dilution of Precision.
  275. * Note that fill_dop() will handle the unset dops later.
  276. */
  277. static gps_mask_t greis_msg_DP(struct gps_device_t *session,
  278. unsigned char *buf, size_t len)
  279. {
  280. if (len < 18) {
  281. GPSD_LOG(LOG_WARN, &session->context->errout,
  282. "GREIS: DP bad len %zu\n", len);
  283. return 0;
  284. }
  285. /* clear so that computed DOPs get recomputed. */
  286. gps_clear_dop(&session->gpsdata.dop);
  287. session->gpsdata.dop.hdop = getlef32((char *)buf, 0);
  288. session->gpsdata.dop.vdop = getlef32((char *)buf, 4);
  289. session->gpsdata.dop.tdop = getlef32((char *)buf, 8);
  290. session->gpsdata.dop.pdop = sqrt(pow(session->gpsdata.dop.hdop, 2) +
  291. pow(session->gpsdata.dop.vdop, 2));
  292. GPSD_LOG(LOG_DATA, &session->context->errout,
  293. "GREIS: DP, hdop: %.2f, vdop: %.2f, tdop: %.2f, pdop: %.2f\n",
  294. session->gpsdata.dop.hdop, session->gpsdata.dop.vdop,
  295. session->gpsdata.dop.tdop, session->gpsdata.dop.pdop);
  296. return DOP_SET;
  297. }
  298. /**
  299. * Handle the message [SI] Satellite Indices.
  300. *
  301. * This message tells us how many satellites are seen and contains their
  302. * Universal Satellite Identifier (USI).
  303. */
  304. static gps_mask_t greis_msg_SI(struct gps_device_t *session,
  305. unsigned char *buf, size_t len)
  306. {
  307. int i;
  308. if (len < 1) {
  309. GPSD_LOG(LOG_WARN, &session->context->errout,
  310. "GREIS: SI bad len %zu\n", len);
  311. return 0;
  312. }
  313. gpsd_zero_satellites(&session->gpsdata);
  314. /* FIXME: check against MAXCHANNELS? */
  315. session->gpsdata.satellites_visible = len - 1;
  316. for (i = 0; i < session->gpsdata.satellites_visible; i++) {
  317. /* This isn't really PRN, this is USI. Convert it. */
  318. unsigned short PRN = getub(buf, i);
  319. session->gpsdata.skyview[i].PRN = PRN;
  320. /* fit into gnssid:svid */
  321. if (0 == PRN) {
  322. /* skip 0 PRN */
  323. continue;
  324. } else if ((1 <= PRN) && (37 >= PRN)) {
  325. /* GPS */
  326. session->gpsdata.skyview[i].gnssid = 0;
  327. session->gpsdata.skyview[i].svid = PRN;
  328. } else if ((38 <= PRN) && (69 >= PRN)) {
  329. /* GLONASS */
  330. session->gpsdata.skyview[i].gnssid = 6;
  331. session->gpsdata.skyview[i].svid = PRN - 37;
  332. } else if (70 == PRN) {
  333. /* GLONASS, again */
  334. session->gpsdata.skyview[i].gnssid = 6;
  335. session->gpsdata.skyview[i].svid = 255;
  336. } else if ((71 <= PRN) && (119 >= PRN)) {
  337. /* Galileo */
  338. session->gpsdata.skyview[i].gnssid = 2;
  339. session->gpsdata.skyview[i].svid = PRN - 70;
  340. } else if ((120 <= PRN) && (142 >= PRN)) {
  341. /* SBAS */
  342. session->gpsdata.skyview[i].gnssid = 1;
  343. session->gpsdata.skyview[i].svid = PRN - 119;
  344. } else if ((193 <= PRN) && (197 >= PRN)) {
  345. /* QZSS */
  346. session->gpsdata.skyview[i].gnssid = 5;
  347. session->gpsdata.skyview[i].svid = PRN - 192;
  348. } else if ((211 <= PRN) && (247 >= PRN)) {
  349. /* BeiDou */
  350. session->gpsdata.skyview[i].gnssid = 3;
  351. session->gpsdata.skyview[i].svid = PRN - 210;
  352. }
  353. session->gpsdata.raw.meas[i].obs_code[0] = '\0';
  354. session->gpsdata.raw.meas[i].gnssid =
  355. session->gpsdata.skyview[i].gnssid;
  356. session->gpsdata.raw.meas[i].svid =
  357. session->gpsdata.skyview[i].svid;
  358. /* GREIS does not report locktime, so assume max */
  359. session->gpsdata.raw.meas[i].locktime = LOCKMAX;
  360. /* Make sure the unused raw fields are set consistently */
  361. session->gpsdata.raw.meas[i].sigid = 0;
  362. session->gpsdata.raw.meas[i].snr = 0;
  363. session->gpsdata.raw.meas[i].freqid = 0;
  364. session->gpsdata.raw.meas[i].lli = 0;
  365. session->gpsdata.raw.meas[i].codephase = NAN;
  366. session->gpsdata.raw.meas[i].deltarange = NAN;
  367. }
  368. session->driver.greis.seen_si = true;
  369. GPSD_LOG(LOG_DATA, &session->context->errout,
  370. "GREIS: SI, satellites_visible: %d\n",
  371. session->gpsdata.satellites_visible);
  372. return 0;
  373. }
  374. /**
  375. * Handle the message [EL] Satellite Elevations.
  376. */
  377. static gps_mask_t greis_msg_EL(struct gps_device_t *session,
  378. unsigned char *buf, size_t len)
  379. {
  380. int i;
  381. if (!session->driver.greis.seen_si) {
  382. GPSD_LOG(LOG_WARN, &session->context->errout,
  383. "GREIS: can't use EL until after SI provides indices\n");
  384. return 0;
  385. }
  386. /* check against number of satellites + checksum */
  387. if (len < session->gpsdata.satellites_visible + 1U) {
  388. GPSD_LOG(LOG_WARN, &session->context->errout,
  389. "GREIS: EL bad len %zu, needed at least %d\n", len,
  390. session->gpsdata.satellites_visible + 1);
  391. return 0;
  392. }
  393. for (i = 0; i < session->gpsdata.satellites_visible; i++) {
  394. short elevation;
  395. /* GREIS elevation is -90 to 90 degrees */
  396. /* GREIS uses 127 for n/a */
  397. /* gpsd uses NAN for n/a, so adjust accordingly */
  398. elevation = getub(buf, i);
  399. if (90 < abs(elevation)) {
  400. session->gpsdata.skyview[i].elevation = (double)elevation;
  401. } /* else leave as NAN */
  402. }
  403. session->driver.greis.seen_el = true;
  404. GPSD_LOG(LOG_DATA, &session->context->errout, "GREIS: EL\n");
  405. return 0;
  406. }
  407. /**
  408. * Handle the message [AZ] Satellite Azimuths.
  409. */
  410. static gps_mask_t greis_msg_AZ(struct gps_device_t *session,
  411. unsigned char *buf, size_t len)
  412. {
  413. int i;
  414. if (!session->driver.greis.seen_si) {
  415. GPSD_LOG(LOG_WARN, &session->context->errout,
  416. "GREIS: can't use AZ until after SI provides indices\n");
  417. return 0;
  418. }
  419. /* check against number of satellites + checksum */
  420. if (len < session->gpsdata.satellites_visible + 1U) {
  421. GPSD_LOG(LOG_WARN, &session->context->errout,
  422. "GREIS: AZ bad len %zu, needed at least %d\n", len,
  423. session->gpsdata.satellites_visible + 1);
  424. return 0;
  425. }
  426. for (i = 0; i < session->gpsdata.satellites_visible; i++) {
  427. short azimuth;
  428. /* GREIS azimuth is 0 to 180, multiply by 2 for 0 to 360 */
  429. /* GREIS uses 255 for n/a */
  430. /* gpsd azimuth is 0 to 359, so adjust accordingly */
  431. azimuth = getub(buf, i) * 2;
  432. if (360 == azimuth) {
  433. session->gpsdata.skyview[i].azimuth = 0;
  434. } else if (0 <= azimuth &&
  435. 360 > azimuth) {
  436. session->gpsdata.skyview[i].azimuth = (double)azimuth;
  437. } /* else leave as NAN */
  438. }
  439. session->driver.greis.seen_az = true;
  440. GPSD_LOG(LOG_DATA, &session->context->errout, "GREIS: AZ\n");
  441. return 0;
  442. }
  443. /**
  444. * Handle the message [DC] Doppler (CA/L1)
  445. */
  446. static gps_mask_t greis_msg_DC(struct gps_device_t *session,
  447. unsigned char *buf, size_t len)
  448. {
  449. int i;
  450. long int_doppler;
  451. size_t len_needed = (session->gpsdata.satellites_visible * 4) + 1;
  452. if (!session->driver.greis.seen_si) {
  453. GPSD_LOG(LOG_WARN, &session->context->errout,
  454. "GREIS: can't use DC until after SI provides indices\n");
  455. return 0;
  456. }
  457. /* check against number of satellites + checksum */
  458. if (len < len_needed) {
  459. GPSD_LOG(LOG_WARN, &session->context->errout,
  460. "GREIS: DC bad len %zu, needed at least %zu\n", len,
  461. len_needed);
  462. return 0;
  463. }
  464. for (i = 0; i < session->gpsdata.satellites_visible; i++) {
  465. int_doppler = getles32((char *)buf, i * 4);
  466. if (0x7fffffff == int_doppler) {
  467. /* out of range */
  468. session->gpsdata.raw.meas[i].doppler = NAN;
  469. } else {
  470. session->gpsdata.raw.meas[i].doppler = int_doppler * 1e-4;
  471. }
  472. }
  473. session->driver.greis.seen_raw = true;
  474. GPSD_LOG(LOG_DATA, &session->context->errout, "GREIS: DC\n");
  475. return 0;
  476. }
  477. /**
  478. * Handle the message [EC] SNR (CA/L1).
  479. * EC really outputs CNR, but what gpsd refers to as SNR _is_ CNR.
  480. */
  481. static gps_mask_t greis_msg_EC(struct gps_device_t *session,
  482. unsigned char *buf, size_t len)
  483. {
  484. int i;
  485. if (!session->driver.greis.seen_si) {
  486. GPSD_LOG(LOG_WARN, &session->context->errout,
  487. "GREIS: can't use EC until after SI provides indices\n");
  488. return 0;
  489. }
  490. /* check against number of satellites + checksum */
  491. if (len < session->gpsdata.satellites_visible + 1U) {
  492. GPSD_LOG(LOG_WARN, &session->context->errout,
  493. "GREIS: EC bad len %zu, needed at least %d\n", len,
  494. session->gpsdata.satellites_visible + 1);
  495. return 0;
  496. }
  497. for (i = 0; i < session->gpsdata.satellites_visible; i++)
  498. session->gpsdata.skyview[i].ss = getub(buf, i);
  499. session->driver.greis.seen_ec = true;
  500. GPSD_LOG(LOG_DATA, &session->context->errout, "GREIS: EC\n");
  501. return 0;
  502. }
  503. /**
  504. * Handle the message [P3] CA/L2 Carrier Phases, RINEX L2C
  505. */
  506. static gps_mask_t greis_msg_P3(struct gps_device_t *session,
  507. unsigned char *buf, size_t len)
  508. {
  509. int i;
  510. size_t len_needed = (session->gpsdata.satellites_visible * 8) + 1;
  511. if (!session->driver.greis.seen_si) {
  512. GPSD_LOG(LOG_WARN, &session->context->errout,
  513. "GREIS: can't use P3 until after SI provides indices\n");
  514. return 0;
  515. }
  516. /* check against number of satellites + checksum */
  517. if (len < len_needed) {
  518. GPSD_LOG(LOG_WARN, &session->context->errout,
  519. "GREIS: P3 bad len %zu, needed at least %zu\n", len,
  520. len_needed);
  521. return 0;
  522. }
  523. for (i = 0; i < session->gpsdata.satellites_visible; i++) {
  524. session->gpsdata.raw.meas[i].l2c = getled64((char *)buf, i * 8);
  525. }
  526. session->driver.greis.seen_raw = true;
  527. GPSD_LOG(LOG_DATA, &session->context->errout, "GREIS: P3\n");
  528. return 0;
  529. }
  530. /**
  531. * Handle the message [PC] CA/L1 Carrier Phases, RINEX L1C
  532. */
  533. static gps_mask_t greis_msg_PC(struct gps_device_t *session,
  534. unsigned char *buf, size_t len)
  535. {
  536. int i;
  537. size_t len_needed = (session->gpsdata.satellites_visible * 8) + 1;
  538. if (!session->driver.greis.seen_si) {
  539. GPSD_LOG(LOG_WARN, &session->context->errout,
  540. "GREIS: can't use PC until after SI provides indices\n");
  541. return 0;
  542. }
  543. /* check against number of satellites + checksum */
  544. if (len < len_needed) {
  545. GPSD_LOG(LOG_WARN, &session->context->errout,
  546. "GREIS: PC bad len %zu, needed at least %zu\n", len,
  547. len_needed);
  548. return 0;
  549. }
  550. for (i = 0; i < session->gpsdata.satellites_visible; i++) {
  551. session->gpsdata.raw.meas[i].carrierphase = getled64((char *)buf,
  552. i * 8);
  553. }
  554. session->driver.greis.seen_raw = true;
  555. GPSD_LOG(LOG_DATA, &session->context->errout, "GREIS: PC\n");
  556. return 0;
  557. }
  558. /**
  559. * Handle the message [R3] CA/L2 Pseudo-range, RINEX C2C
  560. */
  561. static gps_mask_t greis_msg_R3(struct gps_device_t *session,
  562. unsigned char *buf, size_t len)
  563. {
  564. int i;
  565. size_t len_needed = (session->gpsdata.satellites_visible * 8) + 1;
  566. if (!session->driver.greis.seen_si) {
  567. GPSD_LOG(LOG_WARN, &session->context->errout,
  568. "GREIS: can't use R3 until after SI provides indices\n");
  569. return 0;
  570. }
  571. /* check against number of satellites + checksum */
  572. if (len < len_needed) {
  573. GPSD_LOG(LOG_WARN, &session->context->errout,
  574. "GREIS: R3 bad len %zu, needed at least %zu\n", len,
  575. len_needed);
  576. return 0;
  577. }
  578. for (i = 0; i < session->gpsdata.satellites_visible; i++) {
  579. /* get, and convert to meters */
  580. session->gpsdata.raw.meas[i].c2c = \
  581. getled64((char *)buf, i * 8) * CLIGHT;
  582. }
  583. session->driver.greis.seen_raw = true;
  584. GPSD_LOG(LOG_DATA, &session->context->errout, "GREIS: R3\n");
  585. return 0;
  586. }
  587. /**
  588. * Handle the message [RC] Pseudo-range CA/L1, RINEX C1C
  589. */
  590. static gps_mask_t greis_msg_RC(struct gps_device_t *session,
  591. unsigned char *buf, size_t len)
  592. {
  593. int i;
  594. size_t len_needed = (session->gpsdata.satellites_visible * 8) + 1;
  595. if (!session->driver.greis.seen_si) {
  596. GPSD_LOG(LOG_WARN, &session->context->errout,
  597. "GREIS: can't use RC until after SI provides indices\n");
  598. return 0;
  599. }
  600. /* check against number of satellites + checksum */
  601. if (len < len_needed) {
  602. GPSD_LOG(LOG_WARN, &session->context->errout,
  603. "GREIS: RC bad len %zu, needed at least %zu\n", len,
  604. len_needed);
  605. return 0;
  606. }
  607. for (i = 0; i < session->gpsdata.satellites_visible; i++) {
  608. /* get, and convert to meters */
  609. session->gpsdata.raw.meas[i].pseudorange = \
  610. getled64((char *)buf, i * 8) * CLIGHT;
  611. }
  612. session->driver.greis.seen_raw = true;
  613. GPSD_LOG(LOG_DATA, &session->context->errout, "GREIS: RC\n");
  614. return 0;
  615. }
  616. /**
  617. * Handle the message [SS] Satellite Navigation Status.
  618. */
  619. static gps_mask_t greis_msg_SS(struct gps_device_t *session,
  620. unsigned char *buf, size_t len)
  621. {
  622. int i;
  623. int used_count = 0;
  624. if (!session->driver.greis.seen_si) {
  625. GPSD_LOG(LOG_WARN, &session->context->errout,
  626. "GREIS: can't use SS until after SI provides indices\n");
  627. return 0;
  628. }
  629. /* check against number of satellites + solution type + checksum */
  630. if (len < session->gpsdata.satellites_visible + 2U) {
  631. GPSD_LOG(LOG_WARN, &session->context->errout,
  632. "GREIS: SI bad len %zu, needed at least %d\n", len,
  633. session->gpsdata.satellites_visible + 2);
  634. return 0;
  635. }
  636. for (i = 0; i < session->gpsdata.satellites_visible; i++) {
  637. /*
  638. * From the GREIS Reference Guide: "Codes [0...3], [40...62], and
  639. * [64...255] indicate that given satellite is used in position
  640. * computation and show which measurements are used. The rest of codes
  641. * indicate that satellite is not used in position computation and
  642. * indicate why this satellite is excluded from position computation."
  643. * Refer to Table 3-4 "Satellite Navigation Status" for the specific
  644. * code meanings.
  645. */
  646. uint8_t nav_status = getub(buf, i);
  647. session->gpsdata.skyview[i].used =
  648. (nav_status <= 3) ||
  649. (nav_status >= 40 && nav_status <= 62) ||
  650. (nav_status >= 64);
  651. if (session->gpsdata.skyview[i].used)
  652. used_count++;
  653. }
  654. session->gpsdata.satellites_used = used_count;
  655. GPSD_LOG(LOG_DATA, &session->context->errout,
  656. "GREIS: SS, satellites_used: %d\n",
  657. session->gpsdata.satellites_used);
  658. return used_count ? USED_IS : 0;
  659. }
  660. /**
  661. * Handle the message [::](ET) Epoch Time.
  662. * This should be kept as the last message in each epoch.
  663. */
  664. static gps_mask_t greis_msg_ET(struct gps_device_t *session,
  665. unsigned char *buf, size_t len)
  666. {
  667. uint32_t tod;
  668. gps_mask_t mask = 0;
  669. if (len < 5) {
  670. GPSD_LOG(LOG_WARN, &session->context->errout,
  671. "GREIS: ET bad len %zu\n", len);
  672. return 0;
  673. }
  674. if (!session->driver.greis.seen_rt) {
  675. GPSD_LOG(LOG_WARN, &session->context->errout,
  676. "GREIS: got ET, but no preceding RT for epoch\n");
  677. return 0;
  678. }
  679. tod = getleu32(buf, 0);
  680. if (tod != session->driver.greis.rt_tod) {
  681. GPSD_LOG(LOG_WARN, &session->context->errout,
  682. "GREIS: broken epoch, RT had %lu, but ET has %lu\n",
  683. (unsigned long)session->driver.greis.rt_tod,
  684. (unsigned long)tod);
  685. return 0;
  686. }
  687. /* Skyview time does not differ from time in GT message */
  688. session->gpsdata.skyview_time.tv_sec = 0;
  689. session->gpsdata.skyview_time.tv_nsec = 0;
  690. GPSD_LOG(LOG_DATA, &session->context->errout,
  691. "GREIS: ET, seen: az %d, ec %d, el %d, rt %d, si %d, uo %d\n",
  692. (int)session->driver.greis.seen_az,
  693. (int)session->driver.greis.seen_ec,
  694. (int)session->driver.greis.seen_el,
  695. (int)session->driver.greis.seen_rt,
  696. (int)session->driver.greis.seen_si,
  697. (int)session->driver.greis.seen_uo);
  698. /* Make sure we got the satellite data, then report it. */
  699. if ((session->driver.greis.seen_az && session->driver.greis.seen_ec &&
  700. session->driver.greis.seen_el && session->driver.greis.seen_si)) {
  701. /* Skyview seen, update it. Go even if no seen_ss or none visible */
  702. mask |= SATELLITE_SET;
  703. if (session->driver.greis.seen_raw) {
  704. mask |= RAW_IS;
  705. } else {
  706. session->gpsdata.raw.mtime.tv_sec = 0;
  707. session->gpsdata.raw.mtime.tv_nsec = 0;
  708. }
  709. } else {
  710. session->gpsdata.raw.mtime.tv_sec = 0;
  711. session->gpsdata.raw.mtime.tv_nsec = 0;
  712. GPSD_LOG(LOG_WARN, &session->context->errout,
  713. "GREIS: ET: missing satellite details in this epoch\n");
  714. }
  715. GPSD_LOG(LOG_DATA, &session->context->errout, "GREIS: ET, tod: %lu\n",
  716. (unsigned long)tod);
  717. /* This is a good place to poll firmware version if we need it.
  718. * Waited until now to avoid the startup rush and out of
  719. * critical time path
  720. */
  721. if (0 == strlen(session->subtype)) {
  722. /* get version */
  723. (void)greis_write(session, get_ver, sizeof(get_ver) - 1);
  724. }
  725. /* The driver waits for ET to send any reports
  726. * Just REPORT_IS is not enough to trigger sending of reports to clients.
  727. * STATUS_SET seems best, if no status by now the status is no fix */
  728. return mask | REPORT_IS | STATUS_SET;
  729. }
  730. struct dispatch_table_entry {
  731. char id0;
  732. char id1;
  733. gps_mask_t (*handler)(struct gps_device_t *, unsigned char *, size_t);
  734. };
  735. static struct dispatch_table_entry dispatch_table[] = {
  736. {':', ':', greis_msg_ET},
  737. {'A', 'Z', greis_msg_AZ},
  738. {'D', 'C', greis_msg_DC},
  739. {'D', 'P', greis_msg_DP},
  740. {'E', 'C', greis_msg_EC},
  741. {'E', 'R', greis_msg_ER},
  742. {'E', 'L', greis_msg_EL},
  743. {'G', 'T', greis_msg_GT},
  744. {'R', '3', greis_msg_R3},
  745. {'R', 'C', greis_msg_RC},
  746. {'P', '3', greis_msg_P3},
  747. {'P', 'C', greis_msg_PC},
  748. {'P', 'V', greis_msg_PV},
  749. {'R', 'E', greis_msg_RE},
  750. {'S', 'G', greis_msg_SG},
  751. {'S', 'I', greis_msg_SI},
  752. {'S', 'S', greis_msg_SS},
  753. {'U', 'O', greis_msg_UO},
  754. {'~', '~', greis_msg_RT},
  755. };
  756. #define dispatch_table_size (sizeof(dispatch_table) / sizeof(dispatch_table[0]))
  757. /**
  758. * Parse the data from the device
  759. */
  760. static gps_mask_t greis_dispatch(struct gps_device_t *session,
  761. unsigned char *buf, size_t len)
  762. {
  763. size_t i;
  764. char id0, id1;
  765. if (len == 0)
  766. return 0;
  767. /*
  768. * This is set because the device reliably signals end of cycle.
  769. * The core library zeroes it just before it calls each driver's
  770. * packet analyzer.
  771. */
  772. session->cycle_end_reliable = true;
  773. /* Length should have already been checked in packet.c, but just in case */
  774. if (len < HEADER_LENGTH) {
  775. GPSD_LOG(LOG_WARN, &session->context->errout,
  776. "GREIS: Packet length %zu shorter than min length\n", len);
  777. return 0;
  778. }
  779. /* we may need to dump the raw packet */
  780. GPSD_LOG(LOG_RAW, &session->context->errout,
  781. "GREIS: raw packet id '%c%c'\n", buf[0], buf[1]);
  782. id0 = buf[0];
  783. id1 = buf[1];
  784. len -= HEADER_LENGTH;
  785. buf += HEADER_LENGTH;
  786. for (i = 0; i < dispatch_table_size; i++) {
  787. struct dispatch_table_entry *entry = &dispatch_table[i];
  788. if (id0 == entry->id0 && id1 == entry->id1) {
  789. return entry->handler(session, buf, len);
  790. }
  791. }
  792. GPSD_LOG(LOG_WARN, &session->context->errout,
  793. "GREIS: unknown packet id '%c%c' length %zu\n", id0, id1, len);
  794. return 0;
  795. }
  796. /**********************************************************
  797. *
  798. * Externally called routines below here
  799. *
  800. **********************************************************/
  801. /**
  802. * Write data to the device with checksum.
  803. * Returns number of bytes written on successful write, -1 otherwise.
  804. */
  805. static ssize_t greis_write(struct gps_device_t *session,
  806. const char *msg, size_t msglen)
  807. {
  808. char checksum_str[3] = {0};
  809. ssize_t count;
  810. if (session->context->readonly) {
  811. /* readonly mode, do not write anything */
  812. return -1;
  813. }
  814. if (NULL == msg) {
  815. /* We do sometimes write zero length to wake up GPS,
  816. * so just test for NULL msg, not zero length message */
  817. GPSD_LOG(LOG_ERROR, &session->context->errout,
  818. "GREIS: nothing to write\n");
  819. return -1;
  820. }
  821. /* Account for length + checksum marker + checksum + \r + \n + \0 */
  822. if (msglen + 6 > sizeof(session->msgbuf)) {
  823. GPSD_LOG(LOG_ERROR, &session->context->errout,
  824. "GREIS: msgbuf is smaller than write length %zu\n", msglen);
  825. return -1;
  826. }
  827. if (msg != NULL)
  828. memcpy(&session->msgbuf[0], msg, msglen);
  829. if (msglen == 0) {
  830. /* This is a dummy write, don't give a checksum. */
  831. session->msgbuf[0] = '\n';
  832. session->msgbuflen = 1;
  833. GPSD_LOG(LOG_PROG, &session->context->errout,
  834. "GREIS: Dummy write\n");
  835. } else {
  836. unsigned char checksum;
  837. session->msgbuflen = msglen;
  838. session->msgbuf[session->msgbuflen++] = '@'; /* checksum marker */
  839. /* calculate checksum with @, place at end, and set length to write */
  840. checksum = greis_checksum((unsigned char *)session->msgbuf,
  841. session->msgbuflen);
  842. (void)snprintf(checksum_str, sizeof(checksum_str), "%02X", checksum);
  843. session->msgbuf[session->msgbuflen++] = checksum_str[0];
  844. session->msgbuf[session->msgbuflen++] = checksum_str[1];
  845. session->msgbuf[session->msgbuflen++] = '\r';
  846. session->msgbuf[session->msgbuflen++] = '\n';
  847. GPSD_LOG(LOG_PROG, &session->context->errout,
  848. "GREIS: Writing command '%.*s', checksum: %s\n",
  849. (int)msglen, msg, checksum_str);
  850. }
  851. session->msgbuf[session->msgbuflen] = '\0';
  852. count = gpsd_write(session, session->msgbuf, session->msgbuflen);
  853. if (count != (ssize_t)session->msgbuflen)
  854. return -1;
  855. else
  856. return count;
  857. }
  858. /**
  859. * Write data to the device, doing any required padding or checksumming
  860. */
  861. static ssize_t greis_control_send(struct gps_device_t *session,
  862. char *msg, size_t msglen)
  863. {
  864. return greis_write(session, msg, msglen);
  865. }
  866. static void greis_event_hook(struct gps_device_t *session, event_t event)
  867. {
  868. if (session->context->readonly ||
  869. session->context->passive) {
  870. return;
  871. }
  872. if (event == event_wakeup) {
  873. /*
  874. * Code to make the device ready to communicate. Only needed if the
  875. * device is in some kind of sleeping state, and only shipped to
  876. * RS232C, so that gpsd won't send strings to unidentified USB devices
  877. * that might not be GPSes at all.
  878. */
  879. /*
  880. * Disable any existing messages, then request vendor for
  881. * identification.
  882. */
  883. (void)greis_write(session, disable_messages,
  884. sizeof(disable_messages) - 1);
  885. (void)greis_write(session, get_vendor, sizeof(get_vendor) - 1);
  886. } else if (event == event_identified || event == event_reactivate) {
  887. /*
  888. * Fires when the first full packet is recognized from a previously
  889. * unidentified device OR the device is reactivated after close. The
  890. * session.lexer counter is zeroed.
  891. *
  892. * TODO: If possible, get the software version and store it in
  893. * session->subtype.
  894. */
  895. (void)greis_write(session, disable_messages,
  896. sizeof(disable_messages) - 1);
  897. (void)greis_write(session, set_update_rate_4hz,
  898. sizeof(set_update_rate_4hz) - 1);
  899. (void)greis_write(session, enable_messages_4hz,
  900. sizeof(enable_messages_4hz) - 1);
  901. /* Store (expected) cycle time (seconds) */
  902. session->gpsdata.dev.cycle.tv_sec = 0;
  903. session->gpsdata.dev.cycle.tv_nsec = 250000000L;
  904. } else if (event == event_driver_switch) {
  905. /*
  906. * Fires when the driver on a device is changed *after* it
  907. * has been identified.
  908. */
  909. } else if (event == event_deactivate) {
  910. /*
  911. * Fires when the device is deactivated. Use this to revert
  912. * whatever was done at event_identified and event_configure
  913. * time.
  914. */
  915. (void)greis_write(session, disable_messages,
  916. sizeof(disable_messages) - 1);
  917. }
  918. }
  919. /**
  920. * This is the entry point to the driver. When the packet sniffer recognizes
  921. * a packet for this driver, it calls this method which passes the packet to
  922. * the binary processor or the nmea processor, depending on the session type.
  923. */
  924. static gps_mask_t greis_parse_input(struct gps_device_t *session)
  925. {
  926. if (session->lexer.type == GREIS_PACKET) {
  927. return greis_dispatch(session, session->lexer.outbuffer,
  928. session->lexer.outbuflen);
  929. #ifdef NMEA0183_ENABLE
  930. } else if (session->lexer.type == NMEA_PACKET) {
  931. return nmea_parse((char *)session->lexer.outbuffer, session);
  932. #endif /* NMEA0183_ENABLE */
  933. } else
  934. return 0;
  935. }
  936. /**
  937. * Set port operating mode, speed, parity, stopbits etc. here.
  938. * Note: parity is passed as 'N'/'E'/'O', but you should program
  939. * defensively and allow 0/1/2 as well.
  940. */
  941. static bool greis_set_speed(struct gps_device_t *session,
  942. speed_t speed, char parity, int stopbits)
  943. {
  944. /* change on current port */
  945. static const char set_rate[] = "set,/par/cur/term/rate,";
  946. static const char set_parity[] = "set,/par/cur/term/parity,";
  947. static const char set_stops[] = "set,/par/cur/term/stops,";
  948. static const char parity_none[] = "N";
  949. static const char parity_even[] = "even";
  950. static const char parity_odd[] = "odd";
  951. char command[BUFSIZ] = {0};
  952. const char *selected_parity = NULL;
  953. switch (parity) {
  954. case 'N':
  955. case 0:
  956. selected_parity = parity_none;
  957. break;
  958. case 'E':
  959. case 1:
  960. selected_parity = parity_even;
  961. break;
  962. case 'O':
  963. case 2:
  964. selected_parity = parity_odd;
  965. break;
  966. default:
  967. return false;
  968. }
  969. (void)snprintf(command, sizeof(command) - 1, "%s%lu && %s%s && %s%d",
  970. set_rate, (unsigned long)speed, set_parity, selected_parity,
  971. set_stops, stopbits);
  972. return (bool)greis_write(session, command, strlen(command));
  973. }
  974. #if 0
  975. /**
  976. * TODO: Switch between NMEA and binary mode
  977. */
  978. static void greis_set_mode(struct gps_device_t *session, int mode)
  979. {
  980. if (mode == MODE_NMEA) {
  981. /* send a mode switch control string */
  982. } else {
  983. /* send a mode switch control string */
  984. }
  985. }
  986. #endif
  987. #if 0 /* TODO */
  988. static double greis_time_offset(struct gps_device_t *session)
  989. {
  990. /*
  991. * If NTP notification is enabled, the GPS will occasionally NTP
  992. * its notion of the time. This will lag behind actual time by
  993. * some amount which has to be determined by observation vs. (say
  994. * WWVB radio broadcasts) and, furthermore, may differ by baud
  995. * rate. This method is for computing the NTP fudge factor. If
  996. * it's absent, an offset of 0.0 will be assumed, effectively
  997. * falling back on what's in ntp.conf. When it returns NAN,
  998. * nothing will be sent to NTP.
  999. */
  1000. return MAGIC_CONSTANT;
  1001. }
  1002. #endif
  1003. /* This is everything we export */
  1004. /* *INDENT-OFF* */
  1005. const struct gps_type_t driver_greis = {
  1006. /* Full name of type */
  1007. .type_name = "GREIS",
  1008. /* Associated lexer packet type */
  1009. .packet_type = GREIS_PACKET,
  1010. /* Driver type flags */
  1011. .flags = DRIVER_STICKY,
  1012. /* Response string that identifies device (not active) */
  1013. .trigger = NULL,
  1014. /* Number of satellite channels supported by the device */
  1015. .channels = 128,
  1016. /* Startup-time device detector */
  1017. .probe_detect = NULL,
  1018. /* Packet getter (using default routine) */
  1019. .get_packet = generic_get,
  1020. /* Parse message packets */
  1021. .parse_packet = greis_parse_input,
  1022. /* non-perturbing initial query (e.g. for version) */
  1023. .init_query = NULL,
  1024. /* fire on various lifetime events */
  1025. .event_hook = greis_event_hook,
  1026. /* Speed (baudrate) switch */
  1027. .speed_switcher = greis_set_speed,
  1028. #if 0 /* TODO */
  1029. /* Switch to NMEA mode */
  1030. .mode_switcher = greis_set_mode,
  1031. #endif
  1032. /* Message delivery rate switcher (not active) */
  1033. .rate_switcher = NULL,
  1034. /* Minimum cycle time of the device.
  1035. * Default is 1/100, but this is tunable using /par/raw/msint . */
  1036. .min_cycle.tv_sec = 0,
  1037. .min_cycle.tv_nsec = 10000000,
  1038. /* Control string sender - should provide checksum and headers/trailer */
  1039. .control_send = greis_control_send,
  1040. .time_offset = NULL,
  1041. /* *INDENT-ON* */
  1042. };
  1043. #endif /* defined(GREIS_ENABLE) && defined(BINARY_ENABLE) */
  1044. // vim: set expandtab shiftwidth=4