1 /*
2 * This file is Copyright (c) 2010-2018 by the GPSD project
3 * SPDX-License-Identifier: BSD-2-clause
4 */
5
6 #include "gpsd_config.h" /* must be before all includes */
7
8 #include <stdbool.h>
9 #include <stdio.h>
10 #include "gpsd.h"
11
12 #if defined(ONCORE_ENABLE) && defined(BINARY_ENABLE)
13 #include "bits.h"
14 #include "timespec.h"
15
16 static char enableEa[] = { 'E', 'a', 1 };
17 static char enableBb[] = { 'B', 'b', 1 };
18 static char getfirmware[] = { 'C', 'j' };
19 /*static char enableEn[] =
20 { 'E', 'n', 1, 0, 100, 100, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };*/
21 /*static char enableAt2[] = { 'A', 't', 2, };*/
22 static unsigned char pollAs[] =
23 { 'A', 's', 0x7f, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0x7f, 0xff,
24 0xff, 0xff, 0xff
25 };
26 static unsigned char pollAt[] = { 'A', 't', 0xff };
27 static unsigned char pollAy[] = { 'A', 'y', 0xff, 0xff, 0xff, 0xff };
28 static unsigned char pollBo[] = { 'B', 'o', 0x01 };
29 static unsigned char pollEn[] = {
30 'E', 'n', 0xff, 0xff, 0xff, 0xff, 0xff,
31 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
32 };
33
34
35 /*
36 * These routines are specific to this driver
37 */
38
39 static gps_mask_t oncore_parse_input(struct gps_device_t *);
40 static gps_mask_t oncore_dispatch(struct gps_device_t *, unsigned char *,
41 size_t);
42 static gps_mask_t oncore_msg_navsol(struct gps_device_t *, unsigned char *,
43 size_t);
44 static gps_mask_t oncore_msg_utc_offset(struct gps_device_t *,
45 unsigned char *, size_t);
46 static gps_mask_t oncore_msg_pps_offset(struct gps_device_t *, unsigned char *,
47 size_t);
48 static gps_mask_t oncore_msg_svinfo(struct gps_device_t *, unsigned char *,
49 size_t);
50 static gps_mask_t oncore_msg_time_raim(struct gps_device_t *, unsigned char *,
51 size_t);
52 static gps_mask_t oncore_msg_firmware(struct gps_device_t *, unsigned char *,
53 size_t);
54
55 /*
56 * These methods may be called elsewhere in gpsd
57 */
58 static ssize_t oncore_control_send(struct gps_device_t *, char *, size_t);
59 static void oncore_event_hook(struct gps_device_t *, event_t);
60
61 /*
62 * Decode the navigation solution message
63 *
64 * @@Ea - Position/Status/Data Message
65
66 @@EamdyyhmsffffaaaaoooohhhhmmmmvvhhddtntimsdimsdimsdimsdimsdimsdimsdimsdsC
67 <CR><LF>
68
69 *
70 * Date
71 * m - month 1 .. 12
72 * d - day 1 .. 31
73 * yy - year 1980 .. 2079
74 *
75 * Time
76 * h - hours 0 .. 23
77 * m - minutes 0 .. 59
78 * s - seconds 0 .. 60
79 * ffff - fractional second 0 .. 999,999,999
80 * (0.0 .. 0.999999999)
81 *
82 * Position
83 * aaaa - latitude in mas -324,000,000 .. 324,000,000
84 * (-90 degrees .. 90 degrees)
85 *
86 * oooo - longitude in mas -648,000,000 .. 648,000,000
87 * (-180 degrees .. 180 degrees)
88 *
89 * hhhh - ellipsoid height in cm -100,000 .. 1,800,000
90 * (-1000.00 .. 18,000.00m)*
91 *
92 * mmmm - not used 0
93 *
94 * Velocity
95 * vv - velocity in cm/s 0 .. 51,400 (0..514.00 m/s)*
96 * hh - heading 0 .. 3599 (0.0..359.9 degrees)
97 *
98 * (true north res 0.1 degrees)
99 *
100 * Geometry
101 * dd - current DOP (0.1 res) 0 .. 999 (0.0 to 99.9 DOP)
102 * (0 = not computable, position-hold, or position propagate)
103 *
104 * t - DOP TYPE
105 * 0 PDOP (3D)/antenna ok
106 * 1 PDOP (3D)/antenna OK
107 * 64 PDOP (3D)/antenna shorted
108 * 65 PDOP (3D)/antenna shorted
109 * 128 PDOP (3D)/antenna open
110 * 129 PDOP (3D)/antenna open
111 * 192 PDOP (3D)/antenna shorted
112 * 193 PDOP (3D)/antenna shorted
113 *
114 * Satellite visibility and tracking status
115 * n - num of visible sats 0 .. 12
116 * t - num of satellites tracked 0 .. 8
117 *
118 * For each of eight receiver channels
119 * i - sat ID 0 .. 37
120 * m - channel tracking mode 0 .. 8
121 * 0 = code search 5 = message sync detect
122 * 1 = code acquire 6 = satellite time avail.
123 * 2 = AGC set 7 = ephemeris acquire
124 * 3 = prep acquire 8 = avail for position
125 * 4 = bit sync detect
126 *
127 * s - carrier to noise density ratio
128 * (C/No) 0 .. 255 db-Hz
129 *
130 * d - channel status flag
131 * Each bit represents one of the following:
132 * (msb) Bit 7: using for position fix
133 * Bit 6: satellite momentum alert flag
134 * Bit 5: satellite anti-spoof flag set
135 * Bit 4: satellite reported unhealthy
136 * Bit 3: satellite reported inaccurate
137 * (> 16m)
138 * Bit 2: spare
139 * Bit 1: spare
140 * (lsb) Bit 0: parity error
141 *
142 * End of channel dependent data
143 * s - receiver status flag
144 *
145 * Each bit represents one of the following:
146 * (msb) Bit 7: position propagate mode
147 * Bit 6: poor geometry (DOP > 12)
148 * Bit 5: 3D fix
149 * Bit 4: 2D fix
150 * Bit 3: acquiring satellites/position hold
151 * Bit 2: spare
152 * Bit 1: insufficient visible satellites
153 * (< 3)
154 * (lsb) Bit 0: bad almanac
155 *
156 * C - checksum
157 * Message length: 76 bytes
158 *
159 */
160 static gps_mask_t
oncore_msg_navsol(struct gps_device_t * session,unsigned char * buf,size_t data_len)161 oncore_msg_navsol(struct gps_device_t *session, unsigned char *buf,
162 size_t data_len)
163 {
164 gps_mask_t mask;
165 unsigned char flags;
166 double lat, lon, alt;
167 float speed, track, dop;
168 unsigned int i, j, st, nsv;
169 int Bbused;
170 struct tm unpacked_date;
171 char ts_buf[TIMESPEC_LEN];
172
173 if (data_len != 76)
174 return 0;
175
176 mask = ONLINE_SET;
177 GPSD_LOG(LOG_DATA, &session->context->errout,
178 "oncore NAVSOL - navigation data\n");
179
180 flags = (unsigned char)getub(buf, 72);
181
182 if (flags & 0x20) {
183 session->gpsdata.status = STATUS_FIX;
184 session->newdata.mode = MODE_3D;
185 } else if (flags & 0x10) {
186 session->gpsdata.status = STATUS_FIX;
187 session->newdata.mode = MODE_2D;
188 } else {
189 GPSD_LOG(LOG_WARN, &session->context->errout,
190 "oncore NAVSOL no fix - flags 0x%02x\n", flags);
191 session->newdata.mode = MODE_NO_FIX;
192 session->gpsdata.status = STATUS_NO_FIX;
193 }
194 mask |= MODE_SET;
195
196 /* Unless we have seen non-zero utc offset data, the time is GPS time
197 * and not UTC time. Do not use it.
198 */
199 if (session->context->leap_seconds) {
200 unsigned int nsec;
201 unpacked_date.tm_mon = (int)getub(buf, 4) - 1;
202 unpacked_date.tm_mday = (int)getub(buf, 5);
203 unpacked_date.tm_year = (int)getbeu16(buf, 6) - 1900;
204 unpacked_date.tm_hour = (int)getub(buf, 8);
205 unpacked_date.tm_min = (int)getub(buf, 9);
206 unpacked_date.tm_sec = (int)getub(buf, 10);
207 unpacked_date.tm_isdst = 0;
208 unpacked_date.tm_wday = unpacked_date.tm_yday = 0;
209 nsec = (unsigned int) getbeu32(buf, 11);
210
211 session->newdata.time.tv_sec = mkgmtime(&unpacked_date);
212 session->newdata.time.tv_nsec = nsec;
213 mask |= TIME_SET;
214 GPSD_LOG(LOG_DATA, &session->context->errout,
215 "oncore NAVSOL - time: %04d-%02d-%02d %02d:%02d:%02d.%09d\n",
216 unpacked_date.tm_year + 1900, unpacked_date.tm_mon + 1,
217 unpacked_date.tm_mday, unpacked_date.tm_hour,
218 unpacked_date.tm_min, unpacked_date.tm_sec, nsec);
219 }
220
221 lat = getbes32(buf, 15) / 3600000.0f;
222 lon = getbes32(buf, 19) / 3600000.0f;
223 alt = getbes32(buf, 23) / 100.0f;
224 speed = getbeu16(buf, 31) / 100.0f;
225 track = getbeu16(buf, 33) / 10.0f;
226 dop = getbeu16(buf, 35) / 10.0f;
227
228 GPSD_LOG(LOG_DATA, &session->context->errout,
229 "oncore NAVSOL - %lf %lf %.2lfm | %.2fm/s %.1fdeg dop=%.1f\n",
230 lat, lon, alt, speed, track,
231 (float)dop);
232
233 session->newdata.latitude = lat;
234 session->newdata.longitude = lon;
235 session->newdata.altHAE = alt; /* is WGS84 */
236 session->newdata.speed = speed;
237 session->newdata.track = track;
238
239 mask |= LATLON_SET | ALTITUDE_SET | SPEED_SET | TRACK_SET;
240
241 gpsd_zero_satellites(&session->gpsdata);
242 /* Merge the satellite information from the Bb message. */
243 Bbused = 0;
244 nsv = 0;
245 for (i = st = 0; i < 8; i++) {
246 int sv, mode, sn, status;
247 unsigned int off;
248
249 off = 40 + 4 * i;
250 sv = (int)getub(buf, off);
251 mode = (int)getub(buf, off + 1);
252 sn = (int)getub(buf, off + 2);
253 status = (int)getub(buf, off + 3);
254
255 GPSD_LOG(LOG_DATA, &session->context->errout,
256 "%2d %2d %2d %3d %02x\n", i, sv, mode, sn, status);
257
258 if (sn) {
259 session->gpsdata.skyview[st].PRN = (short)sv;
260 session->gpsdata.skyview[st].ss = (double)sn;
261 for (j = 0; (int)j < session->driver.oncore.visible; j++)
262 if (session->driver.oncore.PRN[j] == sv) {
263 session->gpsdata.skyview[st].elevation =
264 (double)session->driver.oncore.elevation[j];
265 session->gpsdata.skyview[st].azimuth =
266 (double)session->driver.oncore.azimuth[j];
267 Bbused |= 1 << j;
268 break;
269 }
270 /* bit 7 of the status word: sat used for position */
271 session->gpsdata.skyview[st].used = false;
272 if (status & 0x80) {
273 session->gpsdata.skyview[st].used = true;
274 nsv++;
275 }
276 /* bit 2 of the status word: using for time solution */
277 if (status & 0x02)
278 mask |= NTPTIME_IS | GOODTIME_IS;
279 /*
280 * The GOODTIME_IS mask bit exists distinctly from TIME_SET exactly
281 * so an OnCore running in time-service mode (and other GPS clocks)
282 * can signal that it's returning time even though no position fixes
283 * have been available.
284 */
285 st++;
286 }
287 }
288 for (j = 0; (int)j < session->driver.oncore.visible; j++)
289 if (!(Bbused & (1 << j))) {
290 session->gpsdata.skyview[st].PRN =
291 (short)session->driver.oncore.PRN[j];
292 session->gpsdata.skyview[st].elevation =
293 (double)session->driver.oncore.elevation[j];
294 session->gpsdata.skyview[st].azimuth =
295 (double)session->driver.oncore.azimuth[j];
296 st++;
297 }
298 session->gpsdata.skyview_time = session->newdata.time;
299 session->gpsdata.satellites_used = (int)nsv;
300 session->gpsdata.satellites_visible = (int)st;
301
302 mask |= SATELLITE_SET | USED_IS;
303
304 /* Some messages can only be polled. As they are not so
305 * important, would be enough to poll e.g. one message per cycle.
306 */
307 (void)oncore_control_send(session, (char *)pollAs, sizeof(pollAs));
308 (void)oncore_control_send(session, (char *)pollAt, sizeof(pollAt));
309 (void)oncore_control_send(session, (char *)pollAy, sizeof(pollAy));
310 (void)oncore_control_send(session, (char *)pollBo, sizeof(pollBo));
311 (void)oncore_control_send(session, (char *)pollEn, sizeof(pollEn));
312
313 GPSD_LOG(LOG_DATA, &session->context->errout,
314 "NAVSOL: time=%s lat=%.2f lon=%.2f altMSL=%.2f speed=%.2f "
315 "track=%.2f mode=%d status=%d visible=%d used=%d\n",
316 timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)),
317 session->newdata.latitude,
318 session->newdata.longitude, session->newdata.altHAE,
319 session->newdata.speed, session->newdata.track,
320 session->newdata.mode, session->gpsdata.status,
321 session->gpsdata.satellites_used,
322 session->gpsdata.satellites_visible);
323 return mask;
324 }
325
326 /**
327 * GPS Leap Seconds = UTC offset
328 */
329 static gps_mask_t
oncore_msg_utc_offset(struct gps_device_t * session,unsigned char * buf,size_t data_len)330 oncore_msg_utc_offset(struct gps_device_t *session, unsigned char *buf,
331 size_t data_len)
332 {
333 int utc_offset;
334
335 if (data_len != 8)
336 return 0;
337
338 GPSD_LOG(LOG_DATA, &session->context->errout,
339 "oncore UTCTIME - leap seconds\n");
340 utc_offset = (int)getub(buf, 4);
341 if (utc_offset == 0)
342 return 0; /* that part of almanac not received yet */
343
344 session->context->leap_seconds = utc_offset;
345 session->context->valid |= LEAP_SECOND_VALID;
346 return 0; /* no flag for leap seconds update */
347 }
348
349 /**
350 * PPS offset
351 */
352 static gps_mask_t
oncore_msg_pps_offset(struct gps_device_t * session,unsigned char * buf,size_t data_len)353 oncore_msg_pps_offset(struct gps_device_t *session, unsigned char *buf,
354 size_t data_len)
355 {
356 int pps_offset_ns;
357
358 if (data_len != 11)
359 return 0;
360
361 GPSD_LOG(LOG_DATA, &session->context->errout, "oncore PPS offset\n");
362 pps_offset_ns = (int)getbes32(buf, 4);
363
364 session->driver.oncore.pps_offset_ns = pps_offset_ns;
365 return 0;
366 }
367
368 /**
369 * GPS Satellite Info
370 */
371 static gps_mask_t
oncore_msg_svinfo(struct gps_device_t * session,unsigned char * buf,size_t data_len)372 oncore_msg_svinfo(struct gps_device_t *session, unsigned char *buf,
373 size_t data_len)
374 {
375 unsigned int i, nchan;
376 int j;
377
378 if (data_len != 92)
379 return 0;
380
381 GPSD_LOG(LOG_DATA, &session->context->errout,
382 "oncore SVINFO - satellite data\n");
383 nchan = (unsigned int)getub(buf, 4);
384 GPSD_LOG(LOG_DATA, &session->context->errout,
385 "oncore SVINFO - %d satellites:\n", nchan);
386 /* Then we clamp the value to not read outside the table. */
387 if (nchan > 12)
388 nchan = 12;
389 session->driver.oncore.visible = (int)nchan;
390 for (i = 0; i < nchan; i++) {
391 /* get info for one channel/satellite */
392 unsigned int off = 5 + 7 * i;
393
394 int sv = (int)getub(buf, off);
395 int el = (int)getub(buf, off + 3);
396 int az = (int)getbeu16(buf, off + 4);
397
398 GPSD_LOG(LOG_DATA, &session->context->errout,
399 "%2d %2d %2d %3d\n", i, sv, el, az);
400
401 /* Store for use when Ea messages come. */
402 session->driver.oncore.PRN[i] = sv;
403 session->driver.oncore.elevation[i] = (short)el;
404 session->driver.oncore.azimuth[i] = (short)az;
405 /* If it has an entry in the satellite list, update it! */
406 for (j = 0; j < session->gpsdata.satellites_visible; j++)
407 if (session->gpsdata.skyview[j].PRN == (short)sv) {
408 session->gpsdata.skyview[j].elevation = (double)el;
409 session->gpsdata.skyview[j].azimuth = (double)az;
410 }
411 }
412
413 GPSD_LOG(LOG_DATA, &session->context->errout,
414 "SVINFO: mask={SATELLITE}\n");
415 return SATELLITE_SET;
416 }
417
418 /**
419 * GPS Time RAIM
420 */
421 static gps_mask_t
oncore_msg_time_raim(struct gps_device_t * session UNUSED,unsigned char * buf UNUSED,size_t data_len UNUSED)422 oncore_msg_time_raim(struct gps_device_t *session UNUSED,
423 unsigned char *buf UNUSED, size_t data_len UNUSED)
424 {
425 int sawtooth_ns;
426
427 if (data_len != 69)
428 return 0;
429
430 sawtooth_ns = (int)getub(buf, 25);
431 GPSD_LOG(LOG_DATA, &session->context->errout,
432 "oncore PPS sawtooth: %d\n",sawtooth_ns);
433
434 /* session->driver.oncore.traim_sawtooth_ns = sawtooth_ns; */
435
436 return 0;
437 }
438
439 /**
440 * GPS Firmware
441 */
442 static gps_mask_t
oncore_msg_firmware(struct gps_device_t * session UNUSED,unsigned char * buf UNUSED,size_t data_len UNUSED)443 oncore_msg_firmware(struct gps_device_t *session UNUSED,
444 unsigned char *buf UNUSED, size_t data_len UNUSED)
445 {
446 return 0;
447 }
448
449 #define ONCTYPE(id2,id3) ((((unsigned int)id2)<<8)|(id3))
450
451 /**
452 * Parse the data from the device
453 */
oncore_dispatch(struct gps_device_t * session,unsigned char * buf,size_t len)454 gps_mask_t oncore_dispatch(struct gps_device_t * session, unsigned char *buf,
455 size_t len)
456 {
457 unsigned int type;
458
459 if (len == 0)
460 return 0;
461
462 type = ONCTYPE(buf[2], buf[3]);
463
464 /* we may need to dump the raw packet */
465 GPSD_LOG(LOG_RAW, &session->context->errout,
466 "raw Oncore packet type 0x%04x\n", type);
467
468 session->cycle_end_reliable = true;
469
470 switch (type) {
471 case ONCTYPE('B', 'b'):
472 return oncore_msg_svinfo(session, buf, len);
473 case ONCTYPE('E', 'a'):
474 return oncore_msg_navsol(session, buf, len) | (CLEAR_IS | REPORT_IS);
475 case ONCTYPE('E', 'n'):
476 return oncore_msg_time_raim(session, buf, len);
477 case ONCTYPE('C', 'j'):
478 return oncore_msg_firmware(session, buf, len);
479 case ONCTYPE('B', 'o'):
480 return oncore_msg_utc_offset(session, buf, len);
481 case ONCTYPE('A', 's'):
482 return 0; /* position hold mode */
483 case ONCTYPE('A', 't'):
484 return 0; /* position hold position */
485 case ONCTYPE('A', 'y'):
486 return oncore_msg_pps_offset(session, buf, len);
487
488 default:
489 /* FIX-ME: This gets noisy in a hurry. Change once your driver works */
490 GPSD_LOG(LOG_WARN, &session->context->errout,
491 "unknown packet id @@%c%c length %zd\n",
492 type >> 8, type & 0xff, len);
493 return 0;
494 }
495 }
496
497
498 /**********************************************************
499 *
500 * Externally called routines below here
501 *
502 **********************************************************/
503
504 /**
505 * Write data to the device, doing any required padding or checksumming
506 */
oncore_control_send(struct gps_device_t * session,char * msg,size_t msglen)507 static ssize_t oncore_control_send(struct gps_device_t *session,
508 char *msg, size_t msglen)
509 {
510 size_t i;
511 char checksum = 0;
512
513 session->msgbuf[0] = '@';
514 session->msgbuf[1] = '@';
515 for (i = 0; i < msglen; i++) {
516 checksum ^= session->msgbuf[i + 2] = msg[i];
517 }
518 session->msgbuf[msglen + 2] = checksum;
519 session->msgbuf[msglen + 3] = '\r';
520 session->msgbuf[msglen + 4] = '\n';
521 session->msgbuflen = msglen + 5;
522
523 GPSD_LOG(LOG_PROG, &session->context->errout,
524 "writing oncore control type %c%c\n", msg[0], msg[1]);
525 return gpsd_write(session, session->msgbuf, session->msgbuflen);
526 }
527
528
oncore_event_hook(struct gps_device_t * session,event_t event)529 static void oncore_event_hook(struct gps_device_t *session, event_t event)
530 {
531 if (session->context->readonly)
532 return;
533
534 /*
535 * Some oncore VP variants that have not been used after long
536 * power-down will be silent on startup. Provoke
537 * identification by requesting the firmware version.
538 */
539 if (event == event_wakeup)
540 (void)oncore_control_send(session, getfirmware, sizeof(getfirmware));
541
542 /*
543 * FIX-ME: It might not be necessary to call this on reactivate.
544 * Experiment to see if the holds its settings through a close.
545 */
546 if (event == event_identified || event == event_reactivate) {
547 (void)oncore_control_send(session, enableEa, sizeof(enableEa));
548 (void)oncore_control_send(session, enableBb, sizeof(enableBb));
549 /*(void)oncore_control_send(session, enableEn, sizeof(enableEn)); */
550 /*(void)oncore_control_send(session,enableAt2,sizeof(enableAt2)); */
551 /*(void)oncore_control_send(session,pollAs,sizeof(pollAs)); */
552 (void)oncore_control_send(session, (char*)pollBo, sizeof(pollBo));
553 }
554 }
555
oncore_time_offset(struct gps_device_t * session UNUSED)556 static double oncore_time_offset(struct gps_device_t *session UNUSED)
557 {
558 /*
559 * Only one sentence (NAVSOL) ships time. 0.175 seems best at
560 * 9600 for UT+, not sure what the fudge should be at other baud
561 * rates or for other models.
562 */
563 return 0.175;
564 }
565
oncore_parse_input(struct gps_device_t * session)566 static gps_mask_t oncore_parse_input(struct gps_device_t *session)
567 {
568 if (session->lexer.type == ONCORE_PACKET) {
569 return oncore_dispatch(session, session->lexer.outbuffer,
570 session->lexer.outbuflen);
571 #ifdef NMEA0183_ENABLE
572 } else if (session->lexer.type == NMEA_PACKET) {
573 return nmea_parse((char *)session->lexer.outbuffer, session);
574 #endif /* NMEA0183_ENABLE */
575 } else
576 return 0;
577 }
578
579 /* This is everything we export */
580 /* *INDENT-OFF* */
581 const struct gps_type_t driver_oncore = {
582
583 .type_name = "Motorola Oncore", /* Full name of type */
584 .packet_type = ONCORE_PACKET, /* numeric packet type */
585 .flags = DRIVER_STICKY, /* remember this */
586 .trigger = NULL, /* identifying response */
587 .channels = 12, /* device channel count */
588 .probe_detect = NULL, /* no probe */
589 .get_packet = generic_get, /* packet getter */
590 .parse_packet = oncore_parse_input, /* packet parser */
591 .rtcm_writer = gpsd_write, /* device accepts RTCM */
592 .init_query = NULL, /* non-perturbing query */
593 .event_hook = oncore_event_hook, /* lifetime event hook */
594 #ifdef RECONFIGURE_ENABLE
595 .speed_switcher = NULL, /* no speed setter */
596 .mode_switcher = NULL, /* no mode setter */
597 .rate_switcher = NULL, /* no speed setter */
598 .min_cycle.tv_sec = 1, /* 1Hz */
599 .min_cycle.tv_nsec = 0,
600 #endif /* RECONFIGURE_ENABLE */
601 #ifdef CONTROLSEND_ENABLE
602 /* Control string sender - should provide checksum and headers/trailer */
603 .control_send = oncore_control_send, /* to send control strings */
604 #endif /* CONTROLSEND_ENABLE */
605 .time_offset = oncore_time_offset, /* NTP offset array */
606 };
607 /* *INDENT-ON* */
608 #endif /* defined(ONCORE_ENABLE) && defined(BINARY_ENABLE) */
609