summaryrefslogtreecommitdiff
path: root/gps/client.py
diff options
context:
space:
mode:
authorEric S. Raymond <esr@thyrsus.com>2010-04-22 23:31:25 -0400
committerEric S. Raymond <esr@thyrsus.com>2010-04-22 23:31:25 -0400
commita5720352c3bd6c606aaeeb112ef5fc01a4123707 (patch)
tree637c84d64b69832a4f68b72e59be9276e899445d /gps/client.py
parent11675777d7502b234722d0fb3744730a688ea64b (diff)
downloadgpsd-a5720352c3bd6c606aaeeb112ef5fc01a4123707.tar.gz
More Python client partitioning. All regression tests pass.
Diffstat (limited to 'gps/client.py')
-rw-r--r--gps/client.py198
1 files changed, 198 insertions, 0 deletions
diff --git a/gps/client.py b/gps/client.py
new file mode 100644
index 00000000..2e03eafc
--- /dev/null
+++ b/gps/client.py
@@ -0,0 +1,198 @@
+# This file is Copyright (c) 2010 by the GPSD project
+# BSD terms apply: see the file COPYING in the distribution root for details.
+#
+import time, socket, sys, select
+
+if sys.hexversion >= 0x2060000:
+ import json # For Python 2.6
+else:
+ import simplejson as json # For Python 2.4 and 2.5
+
+GPSD_PORT="2947"
+
+class gpscommon:
+ "Isolate socket handling and buffering from the protcol interpretation."
+ def __init__(self, host="127.0.0.1", port=GPSD_PORT, verbose=0):
+ self.sock = None # in case we blow up in connect
+ self.linebuffer = ""
+ self.verbose = verbose
+ self.connect(host, port)
+
+ def connect(self, host, port):
+ """Connect to a host on a given port.
+
+ If the hostname ends with a colon (`:') followed by a number, and
+ there is no port specified, that suffix will be stripped off and the
+ number interpreted as the port number to use.
+ """
+ if not port and (host.find(':') == host.rfind(':')):
+ i = host.rfind(':')
+ if i >= 0:
+ host, port = host[:i], host[i+1:]
+ try: port = int(port)
+ except ValueError:
+ raise socket.error, "nonnumeric port"
+ #if self.verbose > 0:
+ # print 'connect:', (host, port)
+ msg = "getaddrinfo returns an empty list"
+ self.sock = None
+ for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
+ af, socktype, proto, canonname, sa = res
+ try:
+ self.sock = socket.socket(af, socktype, proto)
+ #if self.debuglevel > 0: print 'connect:', (host, port)
+ self.sock.connect(sa)
+ except socket.error, msg:
+ #if self.debuglevel > 0: print 'connect fail:', (host, port)
+ self.close()
+ continue
+ break
+ if not self.sock:
+ raise socket.error, msg
+
+ def close(self):
+ if self.sock:
+ self.sock.close()
+ self.sock = None
+
+ def __del__(self):
+ self.close()
+
+ def waiting(self):
+ "Return True if data is ready for the client."
+ if self.linebuffer:
+ return True
+ (winput, woutput, wexceptions) = select.select((self.sock,), (), (), 0)
+ return winput != []
+
+ def read(self):
+ "Wait for and read data being streamed from the daemon."
+ if self.verbose > 1:
+ sys.stderr.write("poll: reading from daemon...\n")
+ eol = self.linebuffer.find('\n')
+ if eol == -1:
+ frag = self.sock.recv(4096)
+ self.linebuffer += frag
+ if self.verbose > 1:
+ sys.stderr.write("poll: read complete.\n")
+ if not self.linebuffer:
+ if self.verbose > 1:
+ sys.stderr.write("poll: returning -1.\n")
+ # Read failed
+ return -1
+ eol = self.linebuffer.find('\n')
+ if eol == -1:
+ if self.verbose > 1:
+ sys.stderr.write("poll: returning 0.\n")
+ # Read succeeded, but only got a fragment
+ return 0
+ else:
+ if self.verbose > 1:
+ sys.stderr.write("poll: fetching from buffer.\n")
+
+ # We got a line
+ eol += 1
+ self.response = self.linebuffer[:eol]
+ self.linebuffer = self.linebuffer[eol:]
+
+ # Can happen if daemon terminates while we're reading.
+ if not self.response:
+ return -1
+ if self.verbose:
+ sys.stderr.write("poll: data is %s\n" % repr(self.response))
+ self.received = time.time()
+ # We got a \n-terminated line
+ return len(self.response)
+
+ def send(self, commands):
+ "Ship commands to the daemon."
+ if not commands.endswith("\n"):
+ commands += "\n"
+ self.sock.send(commands)
+
+WATCH_DISABLE = 0x0000
+WATCH_ENABLE = 0x0001
+WATCH_JSON = 0x0002
+WATCH_NMEA = 0x0004
+WATCH_RARE = 0x0008
+WATCH_RAW = 0x0010
+WATCH_SCALED = 0x0020
+WATCH_DEVICE = 0x0040
+
+class gpsjson(gpscommon):
+ "Basic JSON decoding."
+ def __iter__(self):
+ return self
+
+ def json_unpack(self, buf):
+ def asciify(d):
+ "De-Unicodify everything so we can copy dicts into Python objects."
+ t = {}
+ for (k, v) in d.items():
+ ka = k.encode("ascii")
+ if type(v) == type(u"x"):
+ va = v.encode("ascii")
+ elif type(v) == type({}):
+ va = asciify(v)
+ elif type(v) == type([]):
+ va = map(asciify, v)
+ else:
+ va = v
+ t[ka] = va
+ return t
+ self.data = dictwrapper(**asciify(json.loads(buf.strip(), encoding="ascii")))
+
+ def streamcooker(flags):
+ if flags & WATCH_DISABLE:
+ arg = '?WATCH={"enable":false'
+ if flags & WATCH_JSON:
+ arg += ',"json":false'
+ if flags & WATCH_NMEA:
+ arg += ',"nmea":false'
+ if flags & WATCH_RARE:
+ arg += ',"raw":1'
+ if flags & WATCH_RAW:
+ arg += ',"raw":2'
+ if flags & WATCH_SCALED:
+ arg += ',"scaled":false'
+ else: # flags & WATCH_ENABLE:
+ arg = '?WATCH={"enable":true'
+ if flags & WATCH_JSON:
+ arg += ',"json":true'
+ if flags & WATCH_NMEA:
+ arg += ',"nmea":true'
+ if flags & WATCH_RAW:
+ arg += ',"raw":1'
+ if flags & WATCH_RARE:
+ arg += ',"raw":0'
+ if flags & WATCH_SCALED:
+ arg += ',"scaled":true'
+ if flags & WATCH_DEVICE:
+ arg += ',"device":"%s"' % outfile
+ return arg
+
+class dictwrapper:
+ "Wrapper that yields both class and dictionary behavior,"
+ def __init__(self, **ddict):
+ self.__dict__ = ddict
+ def get(self, k, d=None):
+ return self.__dict__.get(k, d)
+ def keys(self):
+ return self.__dict__.keys()
+ def __getitem__(self, key):
+ "Emulate dictionary, for new-style interface."
+ return self.__dict__[key]
+ def __setitem__(self, key, val):
+ "Emulate dictionary, for new-style interface."
+ self.__dict__[key] = val
+ def __contains__(self, key):
+ return key in self.__dict__
+ def __str__(self):
+ return "<dictwrapper: " + str(self.__dict__) + ">"
+ __repr__ = __str__
+
+#
+# Someday a cleaner Python iterface using this machiner will live here
+#
+
+# End