summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xgps.py2
-rw-r--r--gpsd.spec.in3
-rw-r--r--gpsfake.xml3
-rwxr-xr-xgpsprobe66
-rw-r--r--gpsprobe.xml14
-rwxr-xr-xgpsprof146
-rw-r--r--gpsprof.xml9
7 files changed, 129 insertions, 114 deletions
diff --git a/gps.py b/gps.py
index 63ad4d1d..6670195a 100755
--- a/gps.py
+++ b/gps.py
@@ -293,6 +293,8 @@ class gps(gpsdata):
def poll(self):
"Wait for and read data being streamed from gpsd."
data = self.sockfile.readline()
+ if not data:
+ return None
if self.verbose:
sys.stderr.write("GPS DATA %s\n" % repr(data))
if self.profiling:
diff --git a/gpsd.spec.in b/gpsd.spec.in
index d5e73e4d..6198610b 100644
--- a/gpsd.spec.in
+++ b/gpsd.spec.in
@@ -128,7 +128,8 @@ if [ -d /etc/udev/rules.d/ ]; then rm /etc/udev/rules.d/80-gpsd.rules; fi
- Pass zero magnetic variation in generated NMEA from binary GPSes
correctly. Use O_SYNC rather than timeouts to guarantee that
baud-rate change strings get to the GPS before changing the line
- parameters. Introduced I command.
+ parameters. Introduced I command. Spatial scattergram plotting
+ moved from gpsprobe to gpsprof.
* Mon Feb 21 2005 Eric S. Raymond <esr@snark.thyrsus.com> - 2.13-1
- Correct a bug in binary-protocol dumping (applies to Zodiac and
diff --git a/gpsfake.xml b/gpsfake.xml
index 8e484f5e..aeb8f74e 100644
--- a/gpsfake.xml
+++ b/gpsfake.xml
@@ -33,6 +33,9 @@ thinks the slave side of the pty is its GPS device, and repeatedly
feeds the contents of a test logfile through the master side to the
GPS.</para>
+<para>Because of the baud-rate hunting logic, the first sentence in
+the file will not be the first presented to the sentence parser.</para>
+
</refsect1>
<refsect1 id='options'><title>OPTIONS</title>
diff --git a/gpsprobe b/gpsprobe
index e06ca3c8..e86b34f2 100755
--- a/gpsprobe
+++ b/gpsprobe
@@ -132,12 +132,12 @@ if __name__ == '__main__':
if fields[2] == 'T':
register(GE301)
else:
- register("# GPVTG format indicates NMEA version < 3.01.\n")
+ register("GPVTG format indicates NMEA version < 3.01.\n")
if leader == "GPRMC":
if len(fields) > 12 and fields[12] in "ADEMSN":
- register("# GPRMC format indicates NMEA version >= 2.3.\n")
+ register("GPRMC format indicates NMEA version >= 2.3.\n")
else:
- register("# GPRMC format indicates NMEA version < 2.3.\n")
+ register("GPRMC format indicates NMEA version < 2.3.\n")
try:
# Step one: Check that we have read permission on the device
@@ -196,72 +196,26 @@ if __name__ == '__main__':
if distribution[interval] > maxfreq:
cycles[key] = interval
maxfreq = distribution[interval]
- print "# This is a gnuplot script generated by gpsprobe at %s\n" % time.asctime()
- print "# First fix in %f seconds." % fixtime
+ print "First fix in %f seconds." % fixtime
for key in cycles:
if len(frequencies[key].values()) == 1:
if cycles[key] == 1:
- print "# %s: is emitted once a second." % key
+ print "%s: is emitted once a second." % key
else:
- print "# %s: is emitted once every %d seconds." % (key, cycles[key])
+ print "%s: is emitted once every %d seconds." % (key, cycles[key])
else:
if cycles[key] == 1:
- print "# %s: is probably emitted once a second." % key
+ print "%s: is probably emitted once a second." % key
else:
- print "# %s: is probably emitted once every %d seconds." % (key, cycles[key])
+ print "%s: is probably emitted once every %d seconds." % (key, cycles[key])
sendcycle = min(*cycles.values())
if sendcycle == 1:
- print "# Send cycle is once per second."
+ print "Send cycle is once per second."
else:
- print "# Send cycle is once per %d seconds." % sendcycle
+ print "Send cycle is once per %d seconds." % sendcycle
# Step four: print out registered traits
sys.stdout.write("".join(notifications) + "\n")
- # Step five: run an empirical check on uncertainty of position.
- if len(fixes) == 0:
- print "# No fixes collected, can't estimate accuracy."
- else:
- centroid = (sum(map(lambda x:x[0], fixes))/len(fixes), sum(map(lambda x:x[1], fixes))/len(fixes))
- # Sort fixes by distance from centroid
- def d(a, b): return sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2)
- fixes.sort(lambda x, y: cmp(d(centroid, x), d(centroid, y)))
- # Compute CEP(50%)
- cep_meters = gps.EarthDistance(centroid, fixes[len(fixes)/2])
- # Convert fixes to offsets from centroid in meters
- recentered = map(lambda fix: gps.MeterOffset(centroid, fix), fixes)
- if centroid[0] < 0:
- latstring = "%fS" % -centroid[0]
- elif centroid[0] == 0:
- latstring = "0"
- else:
- latstring = "%fN" % centroid[0]
- if centroid[1] < 0:
- lonstring = "%fW" % -centroid[1]
- elif centroid[1] == 0:
- lonstring = "0"
- else:
- lonstring = "%fE" % centroid[1]
- sys.stdout.write("set autoscale\n")
- sys.stdout.write('set key below\n')
- sys.stdout.write('set key title "%s"\n' % time.asctime())
- sys.stdout.write('set size ratio -1\n')
- sys.stdout.write('set style line 3 pt 2 # Looks good on X11\n')
- sys.stdout.write('set xlabel "Meters east from %s"\n' % lonstring)
- sys.stdout.write('set ylabel "Meters north from %s"\n' % latstring)
- sys.stdout.write('cep=%f\n' % d((0,0), recentered[len(fixes)/2]))
- sys.stdout.write('set parametric\n')
- sys.stdout.write('set trange [0:2*pi]\n')
- sys.stdout.write('cx(t, r) = sin(t)*r\n')
- sys.stdout.write('cy(t, r) = cos(t)*r\n')
- sys.stdout.write('chlen = cep/20\n')
- sys.stdout.write("set arrow from -chlen,0 to chlen,0 nohead\n")
- sys.stdout.write("set arrow from 0,-chlen to 0,chlen nohead\n")
- sys.stdout.write('plot cx(t, cep),cy(t, cep) title "CEP (50%%) = %f meters", "-" using 1:2 with points ls 3 title "%d GPS fixes"\n' % (cep_meters, len(fixes)))
- sys.stdout.write("#\n")
- sys.stdout.write("# Lat Lon\n")
- for (lat, lon) in recentered:
- sys.stdout.write(" %f %f\n" % (lat, lon))
- sys.stdout.write("end\n")
except KeyboardInterrupt:
print "Aborted."
diff --git a/gpsprobe.xml b/gpsprobe.xml
index a1971f6b..3f998ac1 100644
--- a/gpsprobe.xml
+++ b/gpsprobe.xml
@@ -36,20 +36,6 @@ information, including:</para>
<listitem><para>its send cycle (frequency of updates)</para></listitem>
</itemizedlist>
-<para>In addition, if the GPS is getting position fixes,
-<application>gpsprobe</application> generates a scattergram from them
-and plots a probable-error circle. This data is only meaningful if
-the GPS is held stationary while <application>gpsprobe</application>
-is running. Use it in combination with
-<citerefentry><refentrytitle>gpsprof</refentrytitle><manvolnum>1</manvolnum></citerefentry>
-to get a feel for the accuracy of your GPS.</para>
-
-<para>The report and scattergram is generated as a
-<citerefentry><refentrytitle>gnuplot</refentrytitle><manvolnum>1</manvolnum></citerefentry>
-script to standard output. View it with <command>gnuplot -persist
-&lt;foo&gt;</command> where &lt;foo&gt; should be replaced with
-the name of the plot script.</para>
-
<para>Note: <application>gpsprobe</application> needs access to the raw
GPS device. Therefore <application>gpsd</application> must not be running
when you call <application>gpsprobe</application>. You will probably
diff --git a/gpsprof b/gpsprof
index 47fd2141..e36f06c3 100755
--- a/gpsprof
+++ b/gpsprof
@@ -3,7 +3,7 @@
# Collect and plot latency-profiling data from a running gpsd.
# Requires gnuplot.
#
-import sys, os, time, getopt, gps, tempfile, time, socket
+import sys, os, time, getopt, gps, tempfile, time, socket, math
class Baton:
"Ship progress indication to stderr."
@@ -35,13 +35,70 @@ class Baton:
self.stream.write("...(%2.2f sec) %s.\n" % (time.time() - self.time, msg))
return
-#
-# Latency profiling.
-#
+class spaceplot:
+ "Total times without instrumentation."
+ name = "space"
+ def __init__(self, fp):
+ self.fixes = []
+ self.fp = fp
+ def header(self, session):
+ self.fp.write("# Position uncertainty, %s, %s, %ds cycle\n" % \
+ (title, session.gps_id, session.cycle))
+ def formatter(self, session):
+ self.fixes.append((session.latitude, session.longitude))
+ return True
+ def plot(self, file, title, session):
+ if len(self.fixes) == 0:
+ sys.stderr.write("No fixes collected, can't estimate accuracy.")
+ sys.exit(1)
+ else:
+ centroid = (sum(map(lambda x:x[0], self.fixes))/len(self.fixes), sum(map(lambda x:x[1], self.fixes))/len(self.fixes))
+ # Sort fixes by distance from centroid
+ def d(a, b): return math.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2)
+ self.fixes.sort(lambda x, y: cmp(d(centroid, x), d(centroid, y)))
+ # Compute CEP(50%)
+ cep_meters = gps.EarthDistance(centroid, self.fixes[len(self.fixes)/2])
+ # Convert fixes to offsets from centroid in meters
+ recentered = map(lambda fix: gps.MeterOffset(centroid, fix), self.fixes)
+ for (lat, lon) in recentered:
+ self.fp.write("%f %f\n" % (lat, lon))
+ self.fp.flush()
+ if centroid[0] < 0:
+ latstring = "%fS" % -centroid[0]
+ elif centroid[0] == 0:
+ latstring = "0"
+ else:
+ latstring = "%fN" % centroid[0]
+ if centroid[1] < 0:
+ lonstring = "%fW" % -centroid[1]
+ elif centroid[1] == 0:
+ lonstring = "0"
+ else:
+ lonstring = "%fE" % centroid[1]
+ fmt = ""
+ fmt += "set autoscale\n"
+ fmt += 'set key below\n'
+ fmt += 'set key title "%s"\n' % time.asctime()
+ fmt += 'set size -1\n'
+ fmt += 'set style line 3 pt 2 # Looks good on X11\n'
+ fmt += 'set xlabel "Meters east from %s"\n' % lonstring
+ fmt += 'set ylabel "Meters north from %s"\n' % latstring
+ fmt += 'cep=%f\n' % d((0,0), recentered[len(self.fixes)/2])
+ fmt += 'set parametric\n'
+ fmt += 'set trange [0:2*pi]\n'
+ fmt += 'cx(t, r) = sin(t)*r\n'
+ fmt += 'cy(t, r) = cos(t)*r\n'
+ fmt += 'chlen = cep/20\n'
+ fmt += "set arrow from -chlen,0 to chlen,0 nohead\n"
+ fmt += "set arrow from 0,-chlen to 0,chlen nohead\n"
+ fmt += 'plot cx(t, cep),cy(t, cep) title "CEP (50%%) = %f meters", "%s" using 1:2 with points ls 3 title "%d GPS fixes"\n' % (cep_meters, file, len(self.fixes))
+ return fmt
class uninstrumented:
"Total times without instrumentation."
name = "uninstrumented"
+ def __init__(self, fp):
+ self.fp = fp
def header(self, session, fp):
fp.write("# Uninstrumented total latency, %s, %s, %dN%d, cycle %ds\n" % \
(title,
@@ -65,21 +122,23 @@ plot "%s" using 0:1 title "Total time" with impulses
class rawplot:
"All measurement, no deductions."
name = "raw"
- def header(self, session, fp):
- fp.write("# Raw latency data, %s, %s, %dN%d, cycle %ds\n" % \
+ def __init__(self, fp):
+ self.fp = fp
+ def header(self, session):
+ self.fp.write("# Raw latency data, %s, %s, %dN%d, cycle %ds\n" % \
(title,
session.gps_id, session.baudrate,
session.stopbits, session.cycle))
- fp.write("#\t")
+ self.fp.write("#\t")
for hn in ("T1", "E1", "D1", "W", "E2", "T2", "D2"):
- fp.write("%8s\t" % hn)
- fp.write("tag\n#-\t")
+ self.fp.write("%8s\t" % hn)
+ self.fp.write("tag\n#-\t")
for i in range(0, 7):
- fp.write("--------\t")
- fp.write("--------\n")
+ self.fp.write("--------\t")
+ self.fp.write("--------\n")
- def formatter(self, session, fp):
- fp.write("%2d %2.6f %2.6f %2.6f %2.6f %2.6f %2.6f %2.6f # %s\n" \
+ def formatter(self, session):
+ self.fp.write("%2d %2.6f %2.6f %2.6f %2.6f %2.6f %2.6f %2.6f # %s\n" \
% (session.length,
session.d_xmit_time,
session.d_recv_time,
@@ -113,30 +172,31 @@ class splitplot:
"Discard base time, use color to indicate different tags."
name = "split"
sentences = ("GPGGA", "GPRMC", "GPGLL")
- def __init__(self):
+ def __init__(self, fp):
self.found = {}
- def header(self, session, fp):
- fp.write("# Split latency data, %s, %s, %dN%d, cycle %ds\n" % \
+ self.fp = fp
+ def header(self, session):
+ self.fp.write("# Split latency data, %s, %s, %dN%d, cycle %ds\n" % \
(title,
session.gps_id, session.baudrate,
session.stopbits, session.cycle))
- fp.write("#")
+ self.fp.write("#")
for s in splitplot.sentences:
- fp.write("%8s\t" % s)
+ self.fp.write("%8s\t" % s)
for hn in ("T1", "D1", "W", "E2", "T2", "D2", "length"):
- fp.write("%8s\t" % hn)
- fp.write("tag\n# ")
+ self.fp.write("%8s\t" % hn)
+ self.fp.write("tag\n# ")
for s in splitplot.sentences + ("T1", "D1", "W", "E2", "T2", "D2", "length"):
- fp.write("---------\t")
- fp.write("--------\n")
- def formatter(self, session, fp):
+ self.fp.write("---------\t")
+ self.fp.write("--------\n")
+ def formatter(self, session):
for s in splitplot.sentences:
if s == session.tag:
- fp.write("%2.6f\t"% session.d_xmit_time)
+ self.fp.write("%2.6f\t"% session.d_xmit_time)
self.found[s] = True
else:
- fp.write("- \t")
- fp.write("%2.6f %2.6f %2.6f %2.6f %2.6f %2.6f %8d # %s\n" \
+ self.fp.write("- \t")
+ self.fp.write("%2.6f %2.6f %2.6f %2.6f %2.6f %2.6f %8d # %s\n" \
% (session.d_recv_time,
session.d_decode_time,
session.poll_time,
@@ -175,25 +235,24 @@ plot \\
(file, i+1, splitplot.sentences[i])
return fmt[:-4] + "\n"
-formatters = (rawplot, splitplot, uninstrumented)
+formatters = (spaceplot, rawplot, splitplot, uninstrumented)
def timeplot(await, fname, file, speed, threshold, title):
"Return a string containing a GNUplot script "
+ if file:
+ out = open(file, "w")
+ elif fname == None:
+ out = sys.stdout
+ else:
+ out = tempfile.NamedTemporaryFile()
if fname:
for formatter in formatters:
if formatter.name == fname:
- plotter = formatter()
+ plotter = formatter(out)
break
else:
sys.stderr.write("gpsprof: no such formatter.\n")
sys.exit(1)
- if file:
- out = open(file, "w")
- elif formatter == None:
- out = sys.stdout
- else:
- out = tempfile.NamedTemporaryFile()
-
try:
session = gps.gps()
except socket.error:
@@ -205,26 +264,27 @@ def timeplot(await, fname, file, speed, threshold, title):
if session.baudrate != speed:
sys.stderr.write("gpsprof: baud rate change failed.\n")
session.query("w+bci")
- if formatter != uninstrumented:
+ if formatter not in (spaceplot, uninstrumented):
session.query("z+")
- #session.set_raw_hook(lambda x: sys.stdout.write(x))
- plotter.header(session, out)
+ #session.set_raw_hook(lambda x: sys.stderr.write(`x`+"\n"))
+ plotter.header(session)
baton = Baton("gpsprof: looking for fix", "done")
countdown = await
while countdown > 0:
- session.poll()
+ if session.poll() == None:
+ sys.stderr.write("gpsprof: gpsd has vanished.\n")
+ sys.exit(1)
baton.twirl()
- # If timestamp is no good, skip it.
- if session.utc == "?":
+ if session.status == gps.STATUS_NO_FIX:
continue
- if session.status and countdown == await:
+ if countdown == await:
sys.stderr.write("gathering samples...")
# We can get some funky artifacts at start of session
# apparently due to RS232 buffering effects. Ignore
# them.
if threshold and session.c_decode_time > session.cycle * threshold:
continue
- if plotter.formatter(session, out):
+ if plotter.formatter(session):
countdown -= 1
baton.end()
finally:
diff --git a/gpsprof.xml b/gpsprof.xml
index d41eba8c..a6f6536e 100644
--- a/gpsprof.xml
+++ b/gpsprof.xml
@@ -55,6 +55,15 @@ Currently the following plot types are defined:</para>
<variablelist>
<varlistentry>
+<term>space</term>
+<listitem>
+<para>Generate a scattergram of fixes and plot a probable-error
+circle. This data is only meaningful if the GPS is held stationary
+while <application>gpsprof</application> is running.</para>
+<para></para>
+</listitem>
+</varlistentry>
+<varlistentry>
<term>raw</term>
<listitem>
<para>Plot raw data.</para>