net.py: typo fix + catch the right exception
[breadcrumb.git] / code / breadcrumb / client / gps.py
blob88e3662b7cefabd12ba003098efe8ba555f2f81c
1 # -*- coding: utf8 -*-
2 """
3 Provides several GPS interfaces and means to create them.
5 These GPS interfaces are all iterable and return data points.
6 """
8 # Copyright (C) 2008 Laurens Van Houtven <lvh at laurensvh.be>
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 import logging
23 import time
24 import os
26 # NMEA parsing and data point generation
27 import nmea
28 import deltas
30 # Serial port interfaces
31 import ceserial.ceserial as ceserial
32 import serial
34 try:
35 import gps
36 except ImportError:
37 logging.debug("'gps' module not found, gpsd support disabled...")
38 gps = None
40 # Default CE ports: try 8 and 4 first, those are usually the defaults for BT
41 # and raw serial GPSes, respectively. Also, yes, WinCE ports end in ':'.
42 DEFAULT_CE_PORTS = ['COM8:', 'COM4:', 'COM1:', 'COM2:', 'COM3:', 'COM5:']
43 # If we're not running on CE, let pyserial figure it out.
44 DEFAULT_GENERIC_PORTS = range(8)
46 DEFAULT_GPSD_HOSTNAME = 'localhost'
47 DEFAULT_GPSD_PORT = 2947
49 def connect(port = None,
50 platform = None,
51 host = None,
52 sentence_filename = None):
53 """Tries to connect to a GPS device.
55 Calling this without any arguments tries to automagically detect everything.
57 The platform is guessed based on the output of ``os.name``. On WinCE (aka
58 Windows Mobile), it tries ceserial. In almost all other cases, it tries
59 pyserial. Previously, ``sys.platform`` was used, but modern versions of
60 PythonCE return ``win32``, just like Python on Win2k/XP/Vista would.
62 When this autodetection failed (and no serial port was opened), the client
63 complains loudly, and we return a fake GPS with replayed sentences. Please
64 note that the fact that this did not happen does not imply success -- just
65 because you can open a port doesn't mean there's anything on it.
67 The port usually refers to a serial port. Depending on the platform, this
68 either looks a bit like "COM0" (Win32), "COM0:" (WinCE), or "/dev/ttyS0"
69 (Linux)...
71 gpsd support requires explicitly setting the platform to ``'gpsd'``. The
72 port then refers to a TCP port on which gpsd is listening (the default is
73 is 2947). The hostname argument is the hostname on which gpsd is listening.
75 The hostname argument is not used except for gpsd. This is because serial
76 ports can generally only be accessed locally. Remote serial port access,
77 although possible, is not supported, unless of course whatever it is you're
78 using manages to disguise the remote port as a local one sufficiently well.
79 """
80 logging.debug('Trying to connect to a GPS (platform: %s)...' % platform)
82 if platform == 'fake':
83 return _fake_nmea_gps(sentence_filename)
85 if platform == 'gpsd':
86 return connect_to_gpsd(host, port)
88 if platform is None:
89 # sys.platform on recent versions of PythonCE is 'win32', just like on
90 # normal Windows boxes. os.name returns 'ce' on WinCE.
91 platform = os.name.lower()
92 logging.debug("Detected platform %s..." % platform)
94 serial_port = connect_to_serial(port, platform)
96 if serial_port is not None:
97 return nmeaGPS(serial_port)
98 else:
99 raise RuntimeError, "Host doesn't seem to have any serial ports?!"
101 def connect_to_serial(port = None, platform = 'ce'):
102 """Attempts to connect to a serial GPS device. Returns a serial port.
104 When forced to autodetect, the first serial port that can be opened will be
105 picked. This might not be the wrong device -- the only way to fix that is
106 to make sure the default ports are *likely* to be GPSes.
108 Please note that a port might (usually: will) open even though there is no
109 device connected to it.
112 logging.debug("Attempting to connect to a GPS device...")
113 if port is None:
114 if platform == 'ce':
115 logging.debug('Using common defaults for WinCE...')
116 potential_ports = DEFAULT_CE_PORTS
117 else:
118 logging.debug("Using common defaults for pyserial...")
119 potential_ports = DEFAULT_GENERIC_PORTS
120 else:
121 logging.debug("Port specified: %s." % port)
123 if platform == 'ce' and port[-1] is not ':':
124 # WinCE does not agree with 99% of its users on how to name ports
125 port = ''.join([port, ':'])
127 potential_ports = [port]
129 for portname in potential_ports:
130 port = _try_serial_port(portname, platform)
131 if port is not None:
132 return port
134 logging.error("Exhausted potential serial ports...")
135 return None
137 def _try_serial_port(port, platform):
138 """ Tries to connect to a GPS device on a serial given port. """
140 logging.debug("Attempting to open serial port '%s'" % port)
141 if platform is 'ce':
142 gps = ceserial.CESerial(port = port)
143 else:
144 gps = serial.Serial(port)
146 try:
147 gps.open()
148 except:
149 logging.info("Couldn't open serial port '%s'." % port)
150 return None
152 logging.debug("Opening serial port %s successful..." )
153 gps.sentence_type = 'nmea' # Assume NMEA.
155 return gps
157 def _fake_nmea_gps(sentence_filename = None):
158 """Returns a fake GPS device (NMEA sentence replayer)."""
159 if sentence_filename is None:
160 ownpath = os.path.dirname(os.path.abspath(__file__))
161 filename = os.path.join(ownpath, 'sampletrip')
162 else:
163 filename = os.path.abspath(sentence_filename)
165 filehandle = open(filename, 'r')
167 logging.debug("Returning a fake GPS...")
168 return nmeaGPS(filehandle)
170 def connect_to_gpsd(host = None, port = None):
171 """ Connects to a gpsd daemon running on the specified hostname and port. """
173 return gpsdGPS(host, port)
175 class nmeaGPS(object):
177 An interface for any source of NMEA sentences.
179 The first argument must be an iterable object that returns NMEA sentences.
180 This can be a list (for a fake GPS), a serial device (a real GPS)...
182 # TODO:Implement sentence logging in nmeaGPS
183 def __init__(self, sentence_iterable):
184 self.sentences = sentence_iterable
186 def __iter__(self):
187 for sentence in self.sentences:
188 yield deltas.Point(nmea.parse(sentence))
190 def close(self):
191 """ Fake close() method."""
192 pass
194 class gpsdGPS(object):
196 An interface to the gpsd_ daemon.
198 .. _gpsd: http://gpsd.berlios.de/
200 def __init__(self, host = None, port = None):
201 if port is None:
202 self.port = DEFAULT_GPSD_PORT
203 if host is None:
204 self.host = DEFAULT_GPSD_HOSTNAME
206 self.session = gps.gps(host, port)
208 def __iter__(self):
209 """ Gets the next data point from the gpsd daemon. """
210 while True:
211 self.session.query('adyos')
213 data = {
214 'latitude': float(self.session.fix.latitude),
215 'longitude': float(self.session.fix.longitude),
216 'altitude': float(self.session.altitude),
217 'velocity': float(self.session.speed),
218 'timestamp': int(self.session.time),
221 yield deltas.Point(data)