1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
|
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE article PUBLIC
"-//OASIS//DTD DocBook XML V4.1.2//EN"
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
]>
<article>
<title>Notes on Writing a GPSD Driver</title>
<articleinfo>
<author>
<firstname>Mick</firstname>
<surname>Durkin</surname>
</author>
<revhistory>
<revision>
<revnumber>1.13</revnumber>
<date>25 Aug 2014</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr; added init_query method.
</revremark>
</revision>
<revision>
<revnumber>1.12</revnumber>
<date>31 Oct 2013</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr; ntp_offset becomes time_offset
</revremark>
</revision>
<revision>
<revnumber>1.11</revnumber>
<date>19 Jan 2011</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr; driver type flag field added.
</revremark>
</revision>
<revision>
<revnumber>1.10</revnumber>
<date>9 Jan 2011</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr; event_wakeup no longer fires for USB devices,
in order to avoid spamming unidentified devices behind
USB-to-serial adapters that may not be GPSes at all.
</revremark>
</revision>
<revision>
<revnumber>1.9</revnumber>
<date>13 Apr 2010</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr; added event_triggermatch and the new
ntp_offset member.
</revremark>
</revision>
<revision>
<revnumber>1.8</revnumber>
<date>16 Sep 2009</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr; major rearrangement of driver event set.
</revremark>
</revision>
<revision>
<revnumber>1.8</revnumber>
<date>9 Aug 2009</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr; the device_class experiment failed.
</revremark>
</revision>
<revision>
<revnumber>1.7</revnumber>
<date>24 Jul 2009</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr; Added the device_class member.
</revremark>
</revision>
<revision>
<revnumber>1.6</revnumber>
<date>9 Mar 2009</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr; libgpsd_core.c no longr requires modification
when you add a driver.
</revremark>
</revision>
<revision>
<revnumber>1.6</revnumber>
<date>1 Mar 2009</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr to reflect removal of the cycle member.
</revremark>
</revision>
<revision>
<revnumber>1.5</revnumber>
<date>1 Mar 2009</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr to reflect the parity/stopbits
extension of the sopeed_switcher method.
</revremark>
</revision>
<revision>
<revnumber>1.4</revnumber>
<date>17 February 2009</date>
<authorinitials>er</authorinitials>
<revremark>
Updated by esr to reflect the renaming of sirfmon to gpsmon,
and document the control_send method.
</revremark>
</revision>
<revision>
<revnumber>1.3</revnumber>
<date>14 November 2006</date>
<authorinitials>md</authorinitials>
<revremark>
Updated to conform to the fepo source at this date.
</revremark>
</revision>
</revhistory>
<abstract>
<para>If you are thinking of writing a GPSD driver for some GPS-like
device, these notes by a person who did it may help you decide
whether or not it's a good idea, and if it is, help you get started.</para>
</abstract>
</articleinfo>
<sect1><title>Introduction</title>
<para>First, ask yourself <quote>Why would I write a driver?</quote>
and do that several times. At the date of writing,
<application>gpsd</application> ships with more than 10 drivers and
supports around 40 different GPS devices, so it may be that your
device is already supported by an existing driver.</para>
<para>It may be worth noting that these notes were written against
gpsd version 2.34 as it existed on of November 14th
2006. The situation will likely have changed by the time you read
this, but the broad principles should still apply. Check what version
you are using.</para>
<para><application>gpsd</application> supports autodetection, so
connect your device and try it; you may be lucky and find it is
already supported. If you are unlucky, you have to be prepared to do a
lot of work and research on your own.</para>
<para>I found that the device I wanted to use (a Navman Jupiter-T) was
not supported in its default operation mode. The device actually
provides two other supported modes, NMEA (exceptional support) and
Rockwell/Conexant/Navman binary (supported by the Earthmate driver
<filename>zodiac.c</filename>), I could have possibly avoided
writing a driver by switching to one of these modes, but there was an
overwhelming reason to use the unsupported default mode.</para>
<para>I use the Jupiter-T as a precision timing
device with some dedicated hardware to provide a disciplining
signal to a house standard 10MHz signal. In this application I
wanted to keep it as intended; as a drop in emulator of the
Motorola Oncore UT+ running in fixed-location mode giving a
1PPS signal to very high precision (claimed to be 25ns, 1
sigma).</para>
<para>So for me, the decision whether to write a
driver or not was already made; either write one or forget
connecting this high quality time source to my network, as
<application>gpsd</application> can also use the GPS device to
synchronise the ntpd daemon either to the GPS data or to the
1PPS, if it is available. A 25ns accurate input to <filename>
ntpd</filename> looked very appealing.</para>
</sect1>
<sect1><title>What you will need to go ahead</title>
<para>To take this decision to maturity, you need
to be able to code in C and compile/install the results. If you
can't do that, you should temporarily shelve the driver project
and start learning to code. While I would not advise this as
the way to go for a complete novice, you will find some good
code to follow in the
<application>gpsd</application> files and there are some good
clues in the existing drivers.</para>
<para>You will also need some understanding of what a GPS device is
and how it works, although I guess you are likely to have this
knowledge or you wouldn't be reading this document. You don't need to
be a satellite navigation guru, but it helps if you are familiar with
the terms used.</para>
<para>You will also need access to the documentation for your GPS
device, specifically the format (communications protocol, speed etc.)
and content of the data the device will generate and the commands it
needs to control it. The ideal source for that would be the
manufacturer's user guide or programming manual.</para>
<para>The information available may be a bit sparse and it may even be
that the device is not best suited for use with
<application>gpsd</application>. In my case, I was lucky because the
Jupiter-T was intended for the professional OEM market. It was
designed to be used in devices like custom vehicle trackers. It has
almost no internal intelligence, being designed to interface to an
external computing device and provide raw navigation and time data,
but the interfaces and control language were superbly specified in a
well written technical document of about 200 pages. In actual fact the
document I used most was the Motorola documentation for the default
Oncore mode, but the NMEA and Rockwell/Conexant/Navman modes were
equally well documented in the Navman manuals.</para>
<para>You will also need to set aside some time
and equipment to do the coding and testing. I actually spent
several weeks on this task and went to some trouble to get the
GPS working on my desktop in a temporary harness so that I
could have the device available to test the code. It took some
imagination to get a usable satellite signal as I live in a
ground floor apartment of a 6 floor block in Helsinki with a
limited view of the sky. I ended up pushing my puck antenna
away from the building by fixing it to a broom handle and
poking it out of a partly open window. As you can imagine, I
suspended operations in winter as an open window at -15
centigrade is no joke.</para>
</sect1>
<sect1><title>What is involved in the coding</title>
<para>I looked long and hard at the other drivers, at the main parts
of <application>gpsd</application>, at the test applications (
<application>like xgps</application>) and at the descriptions on the
<application>gpsd</application> web site to get a feel for what my
driver needed to do to behave like the existing drivers. After some
time, I concluded that for me the best way forward was to write my
driver by modifying an existing driver.</para>
<para>Using this approach, I knew that that I would be working on a
layout/functionality that was already conformant to
<application>gpsd</application>'s internal standards and that already
actually produced viable output. Apart from that, I intended, in the
best tradition, to build on everybody else's good ideas instead of
re-inventing the wheel, wherever possible. Modification is probably an
understatement as actually I ended up replacing almost all the
original code, but it acted as a template during the
development.</para>
<para>My first efforts were directed to understanding what the
different sections of the code did (I actually hacked the
<filename>zodiac.c</filename> driver). This took me into some of the
other programs such as <filename>gpsd.c</filename> to see why the
driver was doing certain things and to see what the inputs to/outputs
from the driver were. I also looked at
<filename>driver-proto.c</filename>, as this is a skeleton which
contains the minimal set of services and entry points. Real drivers
provide these and more, but at first, this is a less overwhelming
piece of code and can act as a guide when unravelling a working
driver.</para>
<para>The real trigger for me writing a new driver was that the native
output of the Jupiter-T is a binary protocol with variable length
strings. None of the existing drivers spoke this language, but I could
get some general ideas on how to handle this from drivers like
<filename>zodiac.c</filename> and
<filename>evermore.c</filename>.</para>
<para>I found from the manufacturer's documentation that the Jupiter-T
produces output only when instructed, rather than spewing out a
set sequence at some pre-defined rate. All the commands and
responses in <quote>Jupiter-T-speak</quote> start with a 4 character
ASCII string <quote> <filename>@@Nn</filename></quote> followed by a
payload of 0 to approx 300 binary bytes, a single byte binary checksum
and an ASCII CR/LF pair. Thus it would be possible to generate all
the wanted data for <application>gpsd</application> by activating
two main messages, <quote> <filename>@@Bb</filename></quote> and
<quote><filename>@@Ea</filename></quote>. This particular structure
was the cause of some headaches in the interpretation, but it means
that the important data is impressively dense. The first command
(<filename>@@Bb</filename>) gives the status of all visible satellites
(up to 12) in 92 bytes and the second command
(<filename>@@Ea</filename>) gives all the navigational data plus
receiver status in 76 bytes.</para>
<para>Once I had determined the two commands and responses that were
needed (a few others were needed for initialisation and
administration), I set about writing the decoder to fill in the
standard data structures that <application>gpsd</application> uses. For
this, the <filename>zodiac.c</filename> driver was very helpful as it
had a routine <quote> <function>static gps_mask_t handle1000(struct
gps_device_t *session)</function></quote> that did a very similar
job.</para>
<para>This brought me neatly to a chicken/egg problem; the device,
as I said earlier, is mute on power-up and unless you send it
some instructions to turn on one or more messages, you will have no
indication if it is even alive. Actually, this is not strictly true,
as it does output a 1PPS and a 10kHz square wave on power up, but
these are free running until satellites are acquired, and
<application>gpsd</application> needs a data stream if it is to do
anything. If it has not discovered your GPS device, it cannot lock to
the 1PPS that is coming in all the while.</para>
<para>The <application>gpsd</application> installation instructions
give a clue about how to check if your device is working, but the
method is best suited to NMEA devices or others which give real ASCII
output. If your device outputs binary, you could mistake the output
for garbage caused by a serial port speed or word size error. A mute
device also makes this a non-starter!</para>
<para>The manufacturer's manual gives the commands to set up the
device, but to be honest, I was not sure what I really needed to send
or even how to do it and see the results easily under Linux. I am sure
there are many of you that are confident in driving and reading a
serial port at low level and could have done this in a few minutes. I
chose a simpler way, though it did involve using some Windows
software.</para>
<para>There is a program available called TAC32 (Totally Accurate
Clock) written to run under Windows which can talk to several
varieties of GPS devices and <quote>wake</quote> them in the correct
way to generate navigation data and timing signals, displaying them in
a nice screen, something like <application>xgps</application> does.
This is available for a free evaluation download (about 30 days expiry,
I believe), but I actually shelled out for a license as I had a
continuing use for this on a laptop which only had Windows on it.
Information was available at the time of writing at <ulink
url="http://www.cnssys.com/">www.cnssys.com</ulink>.</para>
<para>With this software you can monitor the raw
data stream and send arbitrary commands to the GPS (the command
constructor includes a nice syntax verifier and CRC generator), so
I was able to watch the initialisation of the device, check the
output stream used to generate the navigation data and
experiment with the command set.</para>
<para>Armed with this information, I was then
able to start testing my driver as I was able to initialise the
device into a working state and be sure I had a good fix and
valid 1PPS under Windows and then transfer the serial
connection to my Linux box whilst leaving the device powered
up.</para>
<para>Later, when I had the basic decoder working, I looked at a
better way to handle communications to the device for test purposes
and general monitoring of how the driver was behaving. In the end, I
was able to get good results by monitoring the serial link to the
device with a specially made <quote>Y</quote> cable (diagram available
at
<ulink url="http://www.beyondlogic.org/protocolanalyser/protocolanalyser.htm">
http://www.beyondlogic.org/protocolanalyser/protocolanalyser.htm</ulink>)
and some Linux based software, SerLook (
<ulink url="http://serlook.sunsite.dk/">
http://serlook.sunsite.dk/</ulink>). I had access to a 4-port
RS-232 to USB adapter and so I could use two of the ports on
this device with special cable and the SerLook software to monitor
the send and receive streams of my <application>gpsd</application> port.</para>
<para>For sending experimental commands, I
settled on building the wanted commands as simple files using
<application>KhexEdit</application> and then sending them to
the serial port with <function>cat</function>. This allowed me
to experiment with the different commands and to swap between
the three modes (Oncore/Jupiter/NMEA). This is crude, but I found it hard
to get the right results with <function>minicom</function>.</para>
<para>To return to the development, I liberally
sprinkled the driver code with <quote>
<function>gpsd_report</function></quote> statements set
to trigger at the lowest level of debugging and invoked the
daemon in <quote>non-daemon</quote> mode with
debugging set to LOG_WARN. This made sure that I could watch
the code step through its various routines.</para>
<para>This leads nicely to two things that I had to master early on
and write down so that I wouldn't forget; how to compile/install the
daemon and how to fire it up. The first is fairly straightforward if
you have compiled anything before. You simply issue a <quote>
<command>./configure</command></quote> command to specify what you
want compiling and then issue a
<quote>
<command>make</command></quote> command to compile
the software to that configuration. If it compiles
successfully, you can then issue a <quote>
<command>make install</command></quote> command to
install the driver. This last command will need to be done as
<function>root</function> because the daemon is designed to
be invoked by root.</para>
<para>The second thing is a bit more tricky, at least the first time
for me, as I find the <quote> <command>man</command></quote> output of
how to invoke any command almost impossible to understand. I got more
out of the source code than I did from <quote>
<command>man</command></quote>, but maybe that is just me! What you
basically do, again as root, is to invoke the daemon, telling it which
port (in my case, a serial port) it should use, that it should stay
permanently active (don't wait for an application to ask for data),
should not go into the background (not <quote>daemonize</quote>) and
which debug level to run at. For me this came out as <quote>
<filename>gpsd -n -N -D1 /dev/ttyS0</filename></quote> from a terminal
session activated as root.</para>
<para>The options for compilation would bear a bit more scrutiny. In
the initial stages, I wanted to keep things simple, so I figured out
from the <command>./configure help</command> command what options were
supported and what were the default settings for them. I initially
compiled with everything except NMEA and my driver disabled. This
keeps the code smaller and ensures that you don't trigger the wrong
driver. My reasoning with leaving the NMEA active was twofold; I
wanted to be able to check at an early stage if I could get
<emphasis>any</emphasis> output to be understood (remember, my GPS
also speaks NMEA and I could change the mode in Windows if needed),
also I was not sure if turning this most basic mode off would break
the daemon. Later on, I modified the default settings in
<filename>configure.ac</filename> to default to just this basic
configuration automatically.</para>
<para>Of course, I have jumped a long way forward
in the story as to be able to compile your new driver, you have
to write it and modify several other parts of the existing code
to be aware of your work.</para>
</sect1>
<sect1><title>Where will your driver make an impact?</title>
<para>If we assume for the time being that you are able to write the
code for your GPS, where does it make its <quote>footprint</quote> on
the existing code? I turned again to the
<filename>zodiac.c</filename> driver for inspiration and did a search
over the source code for any mention of the word <quote>
<filename>zodiac</filename></quote>. Once I knew which files were
involved, I then had to figure out why they mentioned the driver and
see where/if I needed to integrate my driver. I had settled on the
name <filename>jupiter_t.c</filename> for my driver, since that did
not conflict with any existing name space.</para>
<para>Several of the files I turned up were obviously not interesting
at this stage such as <filename>gpsd.spec</filename> and
<filename>gpsd.xml</filename> and some others like
<filename>gpsfake.py</filename> were determined not to be part of the
main daemon, but <quote>support</quote> files used for things like
regression testing or dummy traffic generation. Finally, I concluded
that I needed to make mention of my driver in the following
files:</para>
<informaltable frame='none'>
<tgroup cols='2'>
<tbody>
<row>
<entry><filename>Makefile.am</filename></entry>
<entry><para>controls what gets
<quote><filename>make</filename></quote>d</para></entry>
</row>
<row>
<entry><filename>configure.ac</filename></entry>
<entry><para>configuration of compilation options</para></entry>
</row>
<row>
<entry><filename>drivers.c</filename></entry>
<entry><para>generic NMEA driver with device type scanner</para></entry>
</row>
<row>
<entry><filename>gpsd.h</filename></entry>
<entry><para>data type definitions</para></entry>
</row>
<row>
<entry><filename>packet.c</filename></entry>
<entry><para>packet sniffing state machine</para></entry>
</row>
<row>
<entry><filename>packet_states.h</filename></entry>
<entry><para>defines state machine entries each driver uses</para></entry>
</row>
</tbody>
</tgroup>
</informaltable>
<para>These files will cause various files to be created which also
inherit knowledge of your driver such as
<filename>packet_names.h</filename> and later on you will probably need
to modify other files like <filename>gpsfake.py</filename>, but the
above fairly short list was all I had to handle at first. You will
probably find something similar is necessary and if you miss one out,
you will likely fail to get compilation to complete, usually with a
message telling you where your new code is unknown.</para>
</sect1>
<sect1><title>What these important files do</title>
<para>The first two files only need to know simple things for
compilation; the <quote><filename>Makefile.am</filename></quote>
needs only to have your driver added to the list of
<quote><filename>libgps_c_sources</filename></quote>. I simply
duplicated one of the existing lines and substituted my driver's name
for the original copied name. The <quote><filename>config.ac
</filename></quote> needs a few lines to tell the user what compile
time options are available for your driver and to set its default
options. I again copied an existing entry and changed the name,
making sure I set the options so my driver was active by default.
I also, as mentioned, modified the other drivers to default to
inactive. You will also need to add your driver name to the list
at the end of the file which issues a warning if no device drivers
at all are selected at compilation time. Again, I copied and changed
an existing entry.</para>
<para>The <quote><filename>drivers.c</filename></quote> file handles
some basic stuff for the NMEA driver and tries to wake up many of the
other drivers. It needed four small modifications to integrate my
code. The first was a copy of an existing entry in the generic NMEA
handler <quote><function>nmea_parse_input</function></quote> to
generate a debug error if one of my packets was detected when the NMEA
driver had been selected and switch to my driver instead (this is no
longer needed in versions beyond 2,38). The second was a pointer to
simple command to send a Jupiter-T specific string to the GPS at
detection time to test if it is a Jupiter-T in <quote>
<function>nmea_initializer</function></quote>. If it returns the right
answer (in my case, the manufacturer's PROM header), then the packet
sniffer should see this and select my driver. The third was a
(copied/modified) declaration entry in the list of structs known to
<application>gpsd</application> which is located immediately before
and is used by the fourth location,
<quote>
<userinput>*gpsd_driver_array[]</userinput></quote>, to
give the address of the entry point table in my driver.</para>
<para>The <quote><filename>gpsd.h</filename></quote> file is a
conventional header file with declarations common to the whole
application. The changes are again quite simple. There is an entry
added to put my driver in the list of drivers that use binary
mode. This depends if your driver is binary or not. I then modified
the code which sets the maximum packet size as by default the largest
packet was set to 196 bytes for the SiRF driver and the Jupiter-T can
generate a maximum packet of 294 bytes. This is not as bad as it might
seem, as this giant only comes when you dump the device identity
strings from the PROM. The largest <quote>real</quote> packet is 96
bytes for the <quote>Report ASCII Position</quote> message. The
largest command sent is 52 bytes for a <quote>Input Pseudorange
Correction</quote>. The largest received/sent packets used in
<application>gpsd</application> so far are 92 and 20 bytes
respectively. There is a single <quote>#define</quote> in <quote>
<userinput>gps_device_t</userinput></quote> for the new packet type
that this driver needs. This is simply an entry at the end of the
existing list. The last two changes are two <quote>extern</quote>
declarations of prototypes in <quote>
<userinput>**gpsd_drivers</userinput></quote> that the new driver
needs to interface to the rest of the code.</para>
<para>The file <quote><filename>packet.c</filename></quote> is
the state engine which scans packets as they arrive and tries to
match them to an existing driver. Here is where our driver will
be called, so the changes are a little larger. The driver starts
at the beginning of each packet and tries to match, character by
character, until it has determined which (if any) driver owns
this packet in routine <quote>
<function>nextstate</function></quote>. As all
Jupiter-T packets start with <quote>
<constant>@@</constant></quote>, this collides with the
TNT driver, but fortunately, the TNT only uses a single
<quote>
<constant>@</constant></quote>, so matching the second
one allows us to start checking more strictly for Jupiter-T
data. This checking is done in a new block of code lower down
in <quote>
<function>nextstate</function></quote> that was
modelled on the other drivers, but must needs be unique. The
packet is scanned byte by byte until a fully formed packet
has been detected and then it can be parsed in the main driver.
If it fails any of the tests, the state engine is set back to
<quote><constant>GROUND_STATE</constant></quote> and detection
starts again. The code to trigger parsing and deletion of the
packet after it has been parsed is included lower down in the
code <quote>
<function>packet_parse</function></quote> and is based
on existing drivers.</para>
<para>The file
<quote><filename>packet_states.h</filename></quote> is simply
a list of every state needed by every type of GPS which will
produce a long list of unique entries (a big
<filename>enum</filename> list) for use in the
<quote><filename>packet.c</filename></quote> state engine.
The changes here are limited to a small change to the TNT
code, since both drivers share a common first character,
so thus they share a state. There then follow the four new
states that are required by the Jupiter-T state analysis.</para>
</sect1>
<sect1><title>Writing the actual driver code</title>
<para>All that remains now is to write the driver
and you are done. Actually, this part is not too hard,
given the existing code base to guide and I
actually found that the above changes were more troublesome as
I did not know what would need to be updated; you, on the other
hand, now have a nice list to guide you.</para>
<para>The basic entry points or data values
required of every driver are in visible in the
<userinput>struct gps_type_t proto_binary</userinput> in
<quote><filename>drivers_proto.c</filename></quote>. If
any functions are not needed or not provided for your
device, then the corresponding table entry should be a
NULL or -1 (as appropriate). If they exist, the entry
should contain the name of the function or the default
value of the data. What follows is a list of each of
the table entries with a short description of what it
is expected to do or contain.</para>
<para><structfield>.typename</structfield> is a simple string that
uniquely identifies your driver. The first few characters are also
output in some of the monitor output as generated by <filename>
cgps</filename> or <filename>xgps</filename>.</para>
<!-- added by ESR, 2009 -->
<para><structfield>.packet_type</structfield> What packet type this
driver expects to see. This value must be one of those produced by the
packet sniffer and <emphasis>must be unique to each
driver</emphasis>. It is used internally to dispatch to the correct
driver when it collects a complete packet.</para>
<!-- added by ESR, 2010 -->
<para><structfield>.flags</structfield> Driver property flags. This
field is reserved for future expansion.</para>
<para><structfield>.trigger</structfield> is the unique string that,
when seen, will confirm your device is present. This will be detected
in <quote><filename>drivers.c</filename></quote> and will probably
be the same value as that provoked by sending the command mentioned
in <structfield>.probe_detect</structfield>below.</para>
<para><structfield>.channels</structfield> is the number of channels your
GPS uses. Typically this will be 12 for a consumer grade
device.</para>
<para><structfield>.probe_detect</structfield> points to a block of
code that generates a command to send to the device that will provoke
a response if your device is present. The code should then detect and
recognise the response, signalling if detection was successful or
not. Successful detection results in this driver claiming the attached
device. It may also do some more exotic things like set the port to
different operation modes (e.g. raw mode) from the default. If it
makes changes to the port permanently, it should store the
original settings for later restoration, probably by
<structfield>.wrapup</structfield> mentioned below. Later in this
document I discuss my work to implement this function.</para>
<!-- added by ESR, 2014 -->
<para><structfield>.init_query</structfield> points to a block of code
that will be called to query the firmware version of the device. This
code <emphasis>must not</emphasis> alter device state or settings.</para>
<para><structfield>.event_hook</structfield> points to a block of code
that will be executed on and after various events, distinguished by a
second argument that specifies the event type. The event_hook hook is
called in the following circumstances:</para>
<itemizedlist>
<listitem><para>When the main auto-baud hunt loop in the daemon offers
a new speed to probe at, with event argument 'event_wakeup'. Note that
this event does <emphasis>not</emphasis> fire for USB devices, in
order to avoid spamming unidentified devices behind USB-to-serial adapters
that may not be GPSes at all.</para></listitem>
<listitem><para>When the driver has a trigger string and the NMEA driver
sees it, 'event_triggermatch' fires. An 'event_switch_driver' should
follow immediately.</para></listitem>
<listitem><para>
Whenever <application>gpsd</application> first achieves packet lock
with a device, with event type 'identified'.
</para></listitem>
<listitem><para> Whenever a full packet is received, with event type
'event_configure'. On the first such packet, the packet sequence
number is zeroed, then 'event_identify' fires, then 'event_configure'
fires. On later packets, 'event_configure fires with the packet
sequence number as its argument. </para></listitem>
<listitem><para> Whenever a call to gpsd_switch_driver() sets a
device's driver to a different type, with event type
'event_switch_driver'.</para></listitem>
<listitem><para>When the device is closed, with event type
'event_deactivate'. (Closes happen when all clients have disconnected
and the <quote><option>-n</option> </quote> switch is not active.) The
premise is that there may be a special mode you initialized the device
into for <application>gpsd</application> operation which should be
turned off otherwise. It allows for changing the device to a low power
mode, for instance. Any changes you made when 'event_configure' fired
should be undone here. This is also where you should undo any port
parameter changes you made in
<structfield>.probe_detect</structfield>above.</para></listitem>
<listitem><para>When a device is reactivated — that is, reopened
after being been closed because no clients were listening to it, with
event type 'event_reactivate' </para></listitem>
</itemizedlist>
<para>The 'event_identify' event is normally used to send probe
strings that are expected to elicit a later response that will reveal
the subtype of the driver. Such responses are expected to store
information about the software version in member
<quote><filename>subtype</filename></quote> of the driver data
structure <userinput>struct gps_device_t *session</userinput>.</para>
<para>The 'event_configure' event should set up the device to deliver
the correct set of sentences to supply the parser with the data needed
by <filename>gpsd</filename>.</para>
<para>When writing hook code, it is useful to bear in mind that the
<structfield>.packet.counter</structfield> member of the session
structure is available; it is often useful to take action only
when this counter is zero. It is zeroed when the device is activated
or someting triggers a device change.</para>
<para><structfield>.get_packet</structfield> points to a block of code
that actually gets the packets from the serial stream. You will
almost certainly use the generic routine
<function>packet_get</function>. If you know this won't do,
you already know enough not to need this explaining.</para>
<para><structfield>.parse_packet</structfield> points to a block of code
which parses a packet. This will be the main part of your
driver.</para>
<para><structfield>.rtcm_writer</structfield> points to a block of code
used if the GPS type is capable of accepting differential-GPS
corrections in RTCM-104 format. This is the routine needed to
ship the data to the device. Usually it is a straight binary
write of the data, which is provided by the default routine
<function>pass_rtcm</function>. If the device does not
accept differential data, the value is NULL.</para>
<para><structfield>.speed_switcher</structfield> points to a block of
code to change baud rate, parity, and stop bits (if supported). If
your device can support some speed/parity/stopbits combinations but
not others, it should return false on a mode-change request it can't
handle.</para>
<para><structfield>.mode_switcher</structfield> points to a block of code
to change the mode (if supported) between NMEA (mode 0) and our
binary mode (1).</para>
<para><structfield>.rate_switcher</structfield> points to a block of code
to change the maximum number of fixes your device can generate
in 1 second. If this method is present, you should also fill in
<structfield>.min_cycle</structfield> to indicate the device's
minimum cycle time in seconds; a 0 value indicates that it is limited
only by the data throughput of the reporting channel.</para>
<para><structfield>.control_send</structfield> points to a block of
code that can take a buffer full of message payload, wrap iit in
appropriate headers and trailers and checksumming, and ship it to the
device. This entry point is not used by gpsd itself; it's for
diagnotic tools like gpsctl and gpsmon. Once you've written it,
though, you may find it useful for implementing the other switcher
methods and whatever other probe strings you need to send. Note: if
possible, assemble your packet in
<structfield>session->msgbuf</structfield> and put the length in
<structfield>session->msgbuflen</structfield>; this will allow
gpsmon to display the control messages it sends for you.</para>
<!-- added by ESR, 2009 -->
<para><structfield>.timr_offset</structfield> points to code to
compute an offset to be added, additionally to that in
<filename>ntp.conf</filename> (if any) when shipping time
notifications to NTP, or the equivalent configuration file for
chrony. If multiple sentences set TIME_IS, this will differ by
sentence type; it should differ by baud rate, as well. </para>
</sect1>
<sect1><title>Details of the driver parser</title>
<para>This part of the driver is likely to be the most unique part of
your code and as such you will have to design and implement this your
own way, but it may be useful to cover the details I included in my
driver as the problems you will encounter are likely to be the same
that I did.</para>
<para>It is important not to lose sight of the aim of your driver. You
are trying to convert the manufacturer-specific output of your GPS
into a standard data block in <application>gpsd</application> so that a
consistent set of information is available to client software
regardless of what the original source was. In fact,
<application>gpsd</application> will produce a nice set of NMEA output
from your data stream for you to look at if you wish. This output can
be captured and played back into <application>gpsd</application> at a
later date and it will be handled as though it came from a standard
NMEA device.</para>
<para>The most important information is the actual navigation
position/track/speed/time/climb rate information, but we also take
note of some secondary things like DOP/satellite status if it is
available. In my case, all the fields could be filled directly from
the data shipped by the GPS in the two messages which I activated.
The satellite status data contained exactly what was needed. The
navigation data was all present but some fields did need some
massaging; for example, my GPS reports location data in
milliArcseconds whilst <application>gpsd</application> works in
degrees. All conversions were achieved by simple division by
constants. A few of the more exotic fields such as the quality of the
fix (2D/3D etc.) were packed bit values in bytes or words, but these
were extracted by simple masking and testing.</para>
<para>Initially, I was able to get testable
results from just the two command/report strings that were set
in Windows, but later on I added the capability to bring the
device into use from a cold start through the daemon by adding
the routines such as
<structfield>.probe_detect</structfield> and
<structfield>.trigger</structfield> along with some status
requests.</para>
<para>As I hinted earlier, I found that support code like <quote>
<userinput>gpsd_report(LOG_WARN, "satellites tracked = %d, seen =
%d\n", tracked, seen);</userinput></quote> was very helpful in the
early stages. Once the driver reached a production stage, much of the
support code was removed and that which was retained had the first
parameter (the debug level it responds to) increased to a more
appropriate level.</para>
<para>For me, support code and copious comments
were vital since I find that the code I wrote as a genius
yesterday is incomprehensible today when I am an idiot. I
realise this is not to everyone's taste, but my view is that
excessive comments can be ignored; missing comments don't help
someone trying to follow your code later <rant mode
off></para>
</sect1>
<sect1><title>Implementing the <structfield>.probe_detect</structfield> function</title>
<para>As I mentioned earlier, my GPS device
needed to be <quote>woken up</quote>,
otherwise it would never be detected by the normal packet
scanner. The
<structfield>.probe_detect</structfield> function is intended for
just such a case and allows you to seek your device and claim
the port ahead of the normal initialisation, since a check for
devices supporting the
<structfield>.probe_detect</structfield> is made at a very early
point in the startup. The unfortunate thing is that to
implement the function could mean getting down to low level
programming of the tty port since you may find the normal
operating mode capabilities may not match your device's
requirement, even if the baud rate is correct. This
proved to be the case for me and was the single most difficult
part of writing the driver. This, I am sure, is because it involves
working virtually directly with the system hardware. I have documented
this process in some detail in the hope that it may save some other
poor soul the trials I went through.</para>
<!-- Mick actually looked at sirfmon -->
<para>I looked at the code in
<quote><filename>serial.c</filename></quote>,
<quote><filename>garmin.c</filename></quote> and
<quote><filename>gpsmon.c</filename></quote> for inspiration
and noticed some important things:</para>
<itemizedlist>
<listitem><para>You must read and preserve the existing port
settings so that if you change anything, it can be restored
later. This probably means you will have to implement the
<structfield>.wrapup</structfield> function, but this is
likely to be a reverse of the settings you finally arrive
at in this function.</para></listitem>
<listitem><para>You need to be aware of and understand the low level
control settings that are needed to manipulate parameters like parity,
stop bit number, flow control and port mode (raw/cooked). Take some
time to read up on termios (<userinput>man 3 termios</userinput> will
give the grisly details).</para></listitem>
<listitem><para>You will probably find that you need to verify what
delay loops are needed to allow your hardware to catch up if you
change port settings (UART flush and other factors are described in
detail in <quote><filename>serial.c</filename></quote> and you should
read it carefully).</para></listitem>
</itemizedlist>
<para>I also found that even this was not the whole
story, since even when I had allowed the device to catch up on
a settings change, I could not get it to respond reliably to a
<quote>device identify</quote> command. I found that I was
missing some or all of the response message when operating
at 9600 bps.</para>
<para>The reason was that I originally checked the port with a
single character sniff routine a maximum of 300 times (just bigger
than the block of text being returned), which comes out at 300 *
(1 start bit + 8 data bits + 1 stop bit) = 3000 bits. I expected
this would occupy about 312 milliseconds which I considered as an
acceptable delay during the probing phase, but my understanding of
how the serial port is accessed turned out to be faulty. This method
was originally chosen because the probe is speculative and must handle
cases like wrong port speed or the type of device being probed for
is not present and should not hold up progress for too long. Don't
forget that all installed drivers get a chance to probe, one after
another, so the delays for each are cumulative and if no driver
finds and claims the device, you can have many seconds of delay.</para>
<para>When it failed to work as expected, I investigated the GPS device's
documentation (<quote>RTFM</quote> did I hear you say!) and I found in the
Oncore manual that the device's internal scheduler uses a 1
second loop time. Within this loop, the navigation tasks are handled
first, followed by processing of the input commands. Any
resultant output will be generated as soon as the input
buffer is processed, assuming the buffer holds one or more
complete commands.</para>
<para>If you are lucky and just finish your input as the
buffer is ready to be scanned, you can get a result back in 70
milliseconds. If you are unlucky, the most extreme delay is 2
seconds. On average, the turnaround is 1025 milliseconds.
Unfortunately, in the probing code, we have to allow for the
worst case, so once the code issues a command, it has then to
allow a full 2 seconds before scanning for output.</para>
<para>When I found that my initial scanning method was not viable,
I experimented and eventually settled on a loop using a
<function>while</function> statement that checked
<function>timestamp()</function> values and was set
to time out at 2 seconds maximum duration, with an early exit on
successfully finding the wanted data. Within the loop, I tested
the serial port for an available character. If one was available,
I checked it against my expected string; if one was not available,
I looped again if the timer had not expired. If I encountered an
error when reading the port I exited. All exits returned a
success/fail value.</para>
<para>This worked better, but still failed occasionally. I then used
the <quote><function>gpsd_report</function></quote> to check the
error returned and I saw that I was getting lots of <quote>EAGAIN</quote>
errors. This suggested that the port was not able to handle all my
read requests, so I suspected the rate of reading was too fast. Not knowing
for sure, I trapped this particular error and applied a <function>
usleep()</function> of a couple of milliseconds when it occurred. This was
enough to cure the problem and I could get the detection to track
the device's responses reliably. I saw a spread of detection timing
between 250 milliseconds and 1.7 seconds over a large number of tests,
so I concluded that the manufacturer's predictions were being
satisfied.</para>
<para>The only other serial port setting which was not immediately
obvious to me (although present in both <quote><filename>serial.c
</filename></quote> and <quote><filename>sirfmon.c</filename></quote>
) was <quote><userinput>session->ttyset.c_cflag |= CREAD |
CLOCAL;</userinput></quote>. This is needed to enable the port and
cause it to ignore any modem control lines. If you are using a
binary protocol , you will also need to issue a
<quote><userinput>cfmakeraw (struct termios *termios_p);</userinput></quote>
to quickly set the most important flags correctly. I was bitten by
this and found that transmitted <CR/LF> sequences were being modified
to <CR/CR/LF> by the kernel's tty port driver.</para>
</sect1>
<sect1><title>Sign off</title>
<para>Hopefully this short document has been some use to you and maybe
encouraged you to <quote>have a go</quote>. I had never attempted
anything so ambitious as this driver before where my code would be put
up to public scrutiny, but I found the experience very rewarding and
found the <application>gpsd</application> community, especially Eric
Raymond, highly supportive and encouraging.</para>
<para>Your feedback on this document, especially any suggestions for
improvements would be most welcome.</para>
<para>Mick Durkin
<mick.durkin@saunalahti.fi></para>
<para>Helsinki</para>
<para>November 2006</para>
</sect1>
</article>
|