gps.py.in 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. #!@PYSHEBANG@
  2. # -*- coding: utf-8 -*-
  3. # @GENERATED@
  4. """
  5. gps.py -- Python interface to GPSD.
  6. This interface has a lot of historical cruft in it related to old
  7. protocol, and was modeled on the C interface. It won't be thrown
  8. away, but it's likely to be deprecated in favor of something more
  9. Pythonic.
  10. The JSON parts of this (which will be reused by any new interface)
  11. now live in a different module.
  12. """
  13. #
  14. # This file is Copyright 2010 by the GPSD project
  15. # SPDX-License-Identifier: BSD-2-Clause
  16. #
  17. # This code runs compatibly under Python 2 and 3.x for x >= 2.
  18. # Preserve this property!
  19. from __future__ import absolute_import, print_function, division
  20. import argparse
  21. import binascii # for binascii.hexlify()
  22. import os # for file handling in gps_io
  23. import socket # for socket.error
  24. import stat # for stat.S_ISBLK()
  25. import sys # to get script name for nice error/warning messages
  26. from .misc import monotonic, polybytes
  27. from .client import *
  28. from .watch_options import *
  29. # Sometimes used in gps_io if a serial device is specified
  30. try:
  31. import serial
  32. except ImportError:
  33. serial = None # Defer complaining until we know we need it.
  34. NaN = float('nan')
  35. VERB_QUIET = 0 # quiet
  36. VERB_NONE = 1 # just output requested data and some info
  37. VERB_DECODE = 2 # decode all messages
  38. VERB_INFO = 3 # more info
  39. VERB_RAW = 4 # raw info
  40. VERB_PROG = 5 # program trace
  41. GNSSID_GPS = 0
  42. GNSSID_SBAS = 1
  43. GNSSID_GAL = 2
  44. GNSSID_BD = 3
  45. GNSSID_IMES = 4
  46. GNSSID_QZSS = 5
  47. GNSSID_GLO = 6
  48. GNSSID_IRNSS = 7 # ZED-F9T mapping
  49. def isfinite(f):
  50. """Check if f is finite."""
  51. # Python 2 does not think +Inf or -Inf are NaN
  52. # Python 2 has no easier way to test for Inf
  53. return float('-inf') < float(f) < float('inf')
  54. # Don't hand-hack this list, it's generated.
  55. ONLINE_SET = (1 << 1)
  56. TIME_SET = (1 << 2)
  57. TIMERR_SET = (1 << 3)
  58. LATLON_SET = (1 << 4)
  59. ALTITUDE_SET = (1 << 5)
  60. SPEED_SET = (1 << 6)
  61. TRACK_SET = (1 << 7)
  62. CLIMB_SET = (1 << 8)
  63. STATUS_SET = (1 << 9)
  64. MODE_SET = (1 << 10)
  65. DOP_SET = (1 << 11)
  66. HERR_SET = (1 << 12)
  67. VERR_SET = (1 << 13)
  68. ATTITUDE_SET = (1 << 14)
  69. SATELLITE_SET = (1 << 15)
  70. SPEEDERR_SET = (1 << 16)
  71. TRACKERR_SET = (1 << 17)
  72. CLIMBERR_SET = (1 << 18)
  73. DEVICE_SET = (1 << 19)
  74. DEVICELIST_SET = (1 << 20)
  75. DEVICEID_SET = (1 << 21)
  76. RTCM2_SET = (1 << 22)
  77. RTCM3_SET = (1 << 23)
  78. AIS_SET = (1 << 24)
  79. PACKET_SET = (1 << 25)
  80. SUBFRAME_SET = (1 << 26)
  81. GST_SET = (1 << 27)
  82. VERSION_SET = (1 << 28)
  83. POLICY_SET = (1 << 29)
  84. LOGMESSAGE_SET = (1 << 30)
  85. ERROR_SET = (1 << 31)
  86. TIMEDRIFT_SET = (1 << 32)
  87. EOF_SET = (1 << 33)
  88. SET_HIGH_BIT = 34
  89. UNION_SET = (RTCM2_SET | RTCM3_SET | SUBFRAME_SET | AIS_SET | VERSION_SET |
  90. DEVICELIST_SET | ERROR_SET | GST_SET)
  91. STATUS_UNK = 0
  92. STATUS_GPS = 1
  93. STATUS_DGPS = 2
  94. STATUS_RTK_FIX = 3
  95. STATUS_RTK_FLT = 4
  96. STATUS_DR = 5
  97. STATUS_GNSSDR = 6
  98. STATUS_TIME = 7
  99. STATUS_SIM = 8
  100. STATUS_PPS_FIX = 9
  101. MODE_NO_FIX = 1
  102. MODE_2D = 2
  103. MODE_3D = 3
  104. MAXCHANNELS = 72 # Copied from gps.h, but not required to match
  105. SIGNAL_STRENGTH_UNKNOWN = NaN
  106. class gpsfix(object):
  107. """Class to hold one GPS fix."""
  108. def __init__(self):
  109. """Init class gpsfix."""
  110. self.altitude = NaN # Meters DEPRECATED
  111. self.altHAE = NaN # Meters
  112. self.altMSL = NaN # Meters
  113. self.climb = NaN # Meters per second
  114. self.datum = ""
  115. self.dgpsAge = -1
  116. self.dgpsSta = ""
  117. self.depth = NaN
  118. self.device = ""
  119. self.ecefx = NaN
  120. self.ecefy = NaN
  121. self.ecefz = NaN
  122. self.ecefvx = NaN
  123. self.ecefvy = NaN
  124. self.ecefvz = NaN
  125. self.ecefpAcc = NaN
  126. self.ecefvAcc = NaN
  127. self.epc = NaN
  128. self.epd = NaN
  129. self.eph = NaN
  130. self.eps = NaN
  131. self.ept = NaN
  132. self.epv = NaN
  133. self.epx = NaN
  134. self.epy = NaN
  135. self.geoidSep = NaN # Meters
  136. self.latitude = self.longitude = 0.0
  137. self.magtrack = NaN
  138. self.magvar = NaN
  139. self.mode = MODE_NO_FIX
  140. self.relN = NaN
  141. self.relE = NaN
  142. self.relD = NaN
  143. self.sep = NaN # a.k.a. epe
  144. self.speed = NaN # Knots
  145. self.status = STATUS_UNK
  146. self.time = NaN
  147. self.track = NaN # Degrees from true north
  148. self.velN = NaN
  149. self.velE = NaN
  150. self.velD = NaN
  151. class gps_io(object):
  152. """All the GPS I/O in one place.
  153. Three types of GPS I/O
  154. 1. read only from a file
  155. 2. read/write through a device
  156. 3. read only from a gpsd instance
  157. """
  158. out = b''
  159. ser = None
  160. input_is_device = False
  161. def __init__(self, input_file_name=None, read_only=False,
  162. gpsd_host='localhost', gpsd_port=2947,
  163. gpsd_device=None,
  164. input_speed=9600,
  165. verbosity_level=0,
  166. write_requested=True):
  167. """Initialize class.
  168. Arguments:
  169. input_file_name: Name of a device/file to open - None if connection to
  170. gpsd via network
  171. read_only: request a read only access (will be set automagically when
  172. a file is used for input)
  173. gpsd_host: hostname of host running the gpsd
  174. gpsd_port: port of [hostname] running the gpsd
  175. gpsd_device: Specify a dedicated device for the gpsd - None for auto
  176. input_speed: If input_file_name is a (serial) device this specifies
  177. the speed in baud
  178. verbosity_level: Specify the verbosity level (0..5)
  179. write_requested: Set to true if a write operation shall be executed
  180. (used for internal sanity checking)
  181. """
  182. self.gpsd_device = gpsd_device
  183. # Used as an indicator in read if a device, file or network connection
  184. # is used
  185. self.gpsd_host = gpsd_host
  186. self.gpsd_port = gpsd_port
  187. # required by write for packet construction
  188. self.gpsd_device = gpsd_device
  189. # used in read to print meaningfull error
  190. self.input_file_name = input_file_name
  191. self.verbosity_level = verbosity_level
  192. self.prog_name = os.path.basename(sys.argv[0])
  193. Serial = serial
  194. Serial_v3 = Serial and Serial.VERSION.split('.')[0] >= '3'
  195. # buffer to hold read data
  196. self.out = b''
  197. if VERB_PROG <= verbosity_level:
  198. print('gps_io(gpsd_device=%s gpsd_host=%s gpsd_port=%s\n'
  199. ' input_file_name=%s input_speed=%s read_only=%s\n'
  200. ' verbosity_level=%s write_requested=%s)' %
  201. (gpsd_device, gpsd_host, gpsd_port,
  202. input_file_name, input_speed, read_only,
  203. verbosity_level, write_requested))
  204. # open the input: device, file, or gpsd
  205. if input_file_name is not None:
  206. # check if input file is a file or device
  207. try:
  208. mode = os.stat(input_file_name).st_mode
  209. except OSError:
  210. sys.stderr.write('%s: failed to open input file %s\n' %
  211. (self.prog_name, input_file_name))
  212. sys.exit(1)
  213. if stat.S_ISCHR(mode):
  214. # character device, need not be read only
  215. self.input_is_device = True
  216. # FIXME: test broken
  217. if write_requested:
  218. # check for inconsistend arguments
  219. if read_only:
  220. sys.stderr.write('%s: read-only mode, '
  221. 'can not send commands\n' %
  222. self.prog_name)
  223. sys.exit(1)
  224. # check if a file instead of device was specified
  225. if self.input_is_device is False:
  226. sys.stderr.write('%s: input is plain file, '
  227. 'can not send commands\n' %
  228. self.prog_name)
  229. sys.exit(1)
  230. else:
  231. # try to open local/remote gpsd daemon over tcp
  232. if not self.gpsd_host:
  233. self.gpsd_host = 'localhost'
  234. try:
  235. self.ser = gpscommon(host=self.gpsd_host,
  236. input_file_name=input_file_name,
  237. port=self.gpsd_port,
  238. verbose=self.verbosity_level)
  239. # alias self.ser.write() to self.write_gpsd()
  240. self.ser.write = self.write_gpsd
  241. # ask for raw, not rare, data
  242. data_out = b'?WATCH={'
  243. if gpsd_device is not None:
  244. # add in the requested device
  245. data_out += (b'"device":"' +
  246. polybytes(gpsd_device) +
  247. b'",')
  248. data_out += b'"enable":true,"raw":2}\r\n'
  249. if VERB_RAW <= verbosity_level:
  250. print("sent: ", data_out)
  251. self.ser.send(data_out)
  252. except socket.error as err:
  253. sys.stderr.write('%s: failed to connect to gpsd %s\n' %
  254. (self.prog_name, err))
  255. sys.exit(1)
  256. return
  257. if self.input_is_device:
  258. # configure the serial connections (the parameters refer to
  259. # the device you are connecting to)
  260. # pyserial Ver 3.0+ changes writeTimeout to write_timeout
  261. # Using the wrong one causes an error
  262. write_timeout_arg = ('write_timeout'
  263. if Serial_v3 else 'writeTimeout')
  264. try:
  265. self.ser = Serial.Serial(
  266. baudrate=input_speed,
  267. # 8N1 is UBX default
  268. bytesize=Serial.EIGHTBITS,
  269. parity=Serial.PARITY_NONE,
  270. port=input_file_name,
  271. stopbits=Serial.STOPBITS_ONE,
  272. # read timeout
  273. timeout=0.05,
  274. **{write_timeout_arg: 0.5}
  275. )
  276. except AttributeError:
  277. sys.stderr.write('%s: failed to import pyserial\n' %
  278. self.prog_name)
  279. sys.exit(2)
  280. except Serial.serialutil.SerialException:
  281. # this exception happens on bad serial port device name
  282. sys.stderr.write('%s: failed to open serial port "%s"\n'
  283. '%s: Your computer has the serial ports:\n' %
  284. (self.prog_name, input_file_name,
  285. self.prog_name))
  286. # print out list of supported ports
  287. # FIXME: bad location for an import
  288. import serial.tools.list_ports as List_Ports
  289. ports = List_Ports.comports()
  290. for port in ports:
  291. sys.stderr.write(" %s: %s\n" %
  292. (port.device, port.description))
  293. sys.exit(1)
  294. # flush input buffer, discarding all its contents
  295. # pyserial 3.0+ deprecates flushInput() in favor of
  296. # reset_input_buffer(), but flushInput() is still present.
  297. self.ser.flushInput()
  298. elif input_file_name is not None:
  299. # Read from a plain file of UBX messages
  300. try:
  301. self.ser = open(input_file_name, 'rb')
  302. except IOError:
  303. sys.stderr.write('%s: failed to open input %s\n' %
  304. (self.prog_name, input_file_name))
  305. sys.exit(1)
  306. def read(self, decode_func,
  307. input_wait=2.0, expect_statement_identifier=None,
  308. raw_fd=None):
  309. """Read from device, until timeout or expected message.
  310. Arguments:
  311. decode_func: callable function that accepts the raw data which
  312. converts it to a human readable format
  313. expect_statement_identifier: return only the specified package or
  314. 1 if timeout. None (default) if no
  315. filtering is requested
  316. input_wait: read timeout in seconds. Default: 2 seconds
  317. raw: file descriptor like object (has to support the .write method)
  318. to dump raw data. None if not used
  319. """
  320. # are we expecting a certain message?
  321. if expect_statement_identifier:
  322. # assume failure, until we see expected message
  323. ret_code = 1
  324. else:
  325. # not expecting anything, so OK if we did not see it.
  326. ret_code = 0
  327. try:
  328. if self.gpsd_host is not None:
  329. # gpsd input
  330. start = monotonic()
  331. while input_wait > (monotonic() - start):
  332. # First priority is to be sure the input buffer is read.
  333. # This is to prevent input buffer overuns
  334. if 0 < self.ser.waiting():
  335. # We have serial input waiting, get it
  336. # No timeout possible
  337. # RTCM3 JSON can be over 4.4k long, so go big
  338. new_out = self.ser.sock.recv(8192)
  339. if raw_fd is not None:
  340. # save to raw file
  341. raw_fd.write(polybytes(new_out))
  342. self.out += new_out
  343. consumed = decode_func(self.out)
  344. # TODO: the decoder shall return a some current
  345. # statement_identifier # to fill last_statement_identifier
  346. last_statement_identifier = None
  347. #
  348. self.out = self.out[consumed:]
  349. if ((expect_statement_identifier and
  350. (expect_statement_identifier ==
  351. last_statement_identifier))):
  352. # Got what we were waiting for. Done?
  353. ret_code = 0
  354. elif self.input_is_device:
  355. # input is a serial device
  356. start = monotonic()
  357. while input_wait > (monotonic() - start):
  358. # First priority is to be sure the input buffer is read.
  359. # This is to prevent input buffer overuns
  360. # pyserial 3.0+ deprecates inWaiting() in favor of
  361. # in_waiting, but inWaiting() is still present.
  362. if 0 < self.ser.inWaiting():
  363. # We have serial input waiting, get it
  364. # 1024 is comfortably large, almost always the
  365. # Read timeout is what causes ser.read() to return
  366. new_out = self.ser.read(1024)
  367. if raw_fd is not None:
  368. # save to raw file
  369. raw_fd.write(polybytes(new_out))
  370. self.out += new_out
  371. # TODO: Code duplicated from above - make it better
  372. consumed = decode_func(self.out)
  373. # TODO: the decoder shall return a some current
  374. # statement_identifier to fill last_statement_identifier
  375. last_statement_identifier = None
  376. #
  377. self.out = self.out[consumed:]
  378. if ((expect_statement_identifier and
  379. (expect_statement_identifier ==
  380. last_statement_identifier))):
  381. # Got what we were waiting for. Done?
  382. ret_code = 0
  383. else:
  384. # ordinary file, so all read at once
  385. self.out += self.ser.read()
  386. if raw_fd is not None:
  387. # save to raw file
  388. raw_fd.write(polybytes(self.out))
  389. while True:
  390. consumed = decode_func(self.out)
  391. self.out = self.out[consumed:]
  392. if 0 >= consumed:
  393. break
  394. except IOError:
  395. # This happens on a good device name, but gpsd already running.
  396. # or if USB device unplugged
  397. sys.stderr.write('%s: failed to read %s\n'
  398. '%s: Is gpsd already holding the port?\n'
  399. % (self.prog_name, self.input_file_name,
  400. self.prog_name))
  401. return 1
  402. if 0 < ret_code:
  403. # did not see the message we were expecting to see
  404. sys.stderr.write('%s: waited %0.2f seconds for, '
  405. 'but did not get: %%%s%%\n'
  406. % (self.prog_name, input_wait,
  407. expect_statement_identifier))
  408. return ret_code
  409. def write_gpsd(self, data):
  410. """write data to gpsd daemon."""
  411. # HEXDATA_MAX = 512, from gps.h, The max hex digits can write.
  412. # Input data is binary, converting to hex doubles its size.
  413. # Limit binary data to length 255, so hex data length less than 510.
  414. if 255 < len(data):
  415. sys.stderr.write('%s: trying to send %d bytes, max is 255\n'
  416. % (self.prog_name, len(data)))
  417. return 1
  418. if self.gpsd_device is not None:
  419. # add in the requested device
  420. data_out = (b'?DEVICE={"path":"' +
  421. polybytes(self.gpsd_device) + b'",')
  422. else:
  423. data_out = b'?DEVICE={'
  424. # Convert binary data to hex and build the message.
  425. data_out += b'"hexdata":"' + binascii.hexlify(data) + b'"}\r\n'
  426. if VERB_RAW <= self.verbosity_level:
  427. print("sent: ", data_out)
  428. self.ser.send(data_out)
  429. return 0
  430. class gpsdata(object):
  431. """Position, track, velocity and status information returned by a GPS."""
  432. class satellite(object):
  433. """Class to hold satellite data."""
  434. def __init__(self, PRN, elevation, azimuth, ss, used=None):
  435. self.PRN = PRN
  436. self.elevation = elevation
  437. self.azimuth = azimuth
  438. self.ss = ss
  439. self.used = used
  440. def __repr__(self):
  441. return "PRN: %3d E: %3d Az: %3d Ss: %3d Used: %s" % (
  442. self.PRN, self.elevation, self.azimuth, self.ss,
  443. "ny"[self.used])
  444. def __init__(self):
  445. """Initialize all data members."""
  446. self.online = 0 # NZ if GPS on, zero if not
  447. self.valid = 0
  448. self.fix = gpsfix()
  449. self.status = STATUS_UNK
  450. self.utc = ""
  451. self.satellites_used = 0 # Satellites used in last fix
  452. self.xdop = self.ydop = self.vdop = self.tdop = 0
  453. self.pdop = self.hdop = self.gdop = 0.0
  454. self.epe = 0.0
  455. self.satellites = [] # satellite objects in view
  456. self.gps_id = None
  457. self.driver_mode = 0
  458. self.baudrate = 0
  459. self.stopbits = 0
  460. self.cycle = 0
  461. self.mincycle = 0
  462. self.device = None
  463. self.devices = []
  464. self.version = None
  465. def __repr__(self):
  466. st = "Time: %s (%s)\n" % (self.utc, self.fix.time)
  467. st += "Lat/Lon: %f %f\n" % (self.fix.latitude, self.fix.longitude)
  468. if not isfinite(self.fix.altHAE):
  469. st += "Altitude HAE: ?\n"
  470. else:
  471. st += "Altitude HAE: %f\n" % (self.fix.altHAE)
  472. if not isfinite(self.fix.speed):
  473. st += "Speed: ?\n"
  474. else:
  475. st += "Speed: %f\n" % (self.fix.speed)
  476. if not isfinite(self.fix.track):
  477. st += "Track: ?\n"
  478. else:
  479. st += "Track: %f\n" % (self.fix.track)
  480. # FIXME: what about other status values?
  481. st += "Status: STATUS_%s\n" \
  482. % ("NO_FIX", "FIX", "DGPS_FIX")[self.status]
  483. st += "Mode: MODE_%s\n" \
  484. % ("ZERO", "NO_FIX", "2D", "3D")[self.fix.mode]
  485. st += "Quality: %d p=%2.2f h=%2.2f v=%2.2f t=%2.2f g=%2.2f\n" % \
  486. (self.satellites_used, self.pdop, self.hdop, self.vdop,
  487. self.tdop, self.gdop)
  488. st += "Y: %s satellites in view:\n" % len(self.satellites)
  489. for sat in self.satellites:
  490. st += " %r\n" % sat
  491. return st
  492. class gps(gpscommon, gpsdata, gpsjson):
  493. """Client interface to a running gpsd instance.
  494. Or maybe a gpsd JSON file.
  495. """
  496. # module version, would be nice to automate the version
  497. __version__ = "@VERSION@"
  498. def __init__(self,
  499. device=None,
  500. host="127.0.0.1",
  501. input_file_name=None,
  502. mode=0,
  503. port=GPSD_PORT,
  504. reconnect=False,
  505. verbose=0):
  506. self.activated = None
  507. self.clock_sec = NaN
  508. self.clock_nsec = NaN
  509. self.device = device
  510. self.input_file_name = input_file_name
  511. self.path = ''
  512. self.precision = 0
  513. self.real_sec = NaN
  514. self.real_nsec = NaN
  515. self.serialmode = "8N1"
  516. self.verbose = verbose
  517. if VERB_PROG <= verbose:
  518. print('gps(device=%s host=%s port=%s\n'
  519. ' input_file_name=%s verbose=%s)' %
  520. (device, host, port, input_file_name,
  521. verbose))
  522. gpscommon.__init__(self, host=host, port=port,
  523. input_file_name=input_file_name,
  524. should_reconnect=reconnect,
  525. verbose=verbose)
  526. gpsdata.__init__(self)
  527. gpsjson.__init__(self)
  528. if mode:
  529. self.stream(mode)
  530. def _oldstyle_shim(self):
  531. # The rest is backwards compatibility for the old interface
  532. def default(k, dflt, vbit=0):
  533. """Return default for key."""
  534. if k not in self.data.keys():
  535. return dflt
  536. self.valid |= vbit
  537. return self.data[k]
  538. if self.data.get("class") == "VERSION":
  539. self.version = self.data
  540. elif self.data.get("class") == "DEVICE":
  541. self.valid = ONLINE_SET | DEVICE_SET
  542. self.path = self.data["path"]
  543. self.activated = default("activated", None)
  544. driver = default("driver", None, DEVICEID_SET)
  545. subtype = default("subtype", None, DEVICEID_SET)
  546. self.gps_id = driver
  547. if subtype:
  548. self.gps_id += " " + subtype
  549. self.baudrate = default("bps", 0)
  550. self.cycle = default("cycle", NaN)
  551. self.driver_mode = default("native", 0)
  552. self.mincycle = default("mincycle", NaN)
  553. self.serialmode = default("serialmode", "8N1")
  554. elif self.data.get("class") == "TPV":
  555. self.device = default("device", "missing")
  556. self.utc = default("time", None, TIME_SET)
  557. self.valid = ONLINE_SET
  558. if self.utc is not None:
  559. # self.utc is always iso 8601 string
  560. # just copy to fix.time
  561. self.fix.time = self.utc
  562. self.fix.altitude = default("alt", NaN, ALTITUDE_SET) # DEPRECATED
  563. self.fix.altHAE = default("altHAE", NaN, ALTITUDE_SET)
  564. self.fix.altMSL = default("altMSL", NaN, ALTITUDE_SET)
  565. self.fix.climb = default("climb", NaN, CLIMB_SET)
  566. self.fix.epc = default("epc", NaN, CLIMBERR_SET)
  567. self.fix.epd = default("epd", NaN)
  568. self.fix.eps = default("eps", NaN, SPEEDERR_SET)
  569. self.fix.ept = default("ept", NaN, TIMERR_SET)
  570. self.fix.epv = default("epv", NaN, VERR_SET)
  571. self.fix.epx = default("epx", NaN, HERR_SET)
  572. self.fix.epy = default("epy", NaN, HERR_SET)
  573. self.fix.latitude = default("lat", NaN, LATLON_SET)
  574. self.fix.longitude = default("lon", NaN)
  575. self.fix.mode = default("mode", 0, MODE_SET)
  576. self.fix.speed = default("speed", NaN, SPEED_SET)
  577. self.fix.status = default("status", 1)
  578. self.fix.track = default("track", NaN, TRACK_SET)
  579. elif self.data.get("class") == "SKY":
  580. self.device = default("device", "missing")
  581. for attrp in ("g", "h", "p", "t", "v", "x", "y"):
  582. n = attrp + "dop"
  583. setattr(self, n, default(n, NaN, DOP_SET))
  584. if "satellites" in self.data.keys():
  585. self.satellites = []
  586. for sat in self.data['satellites']:
  587. if 'el' not in sat:
  588. sat['el'] = -999
  589. if 'az' not in sat:
  590. sat['az'] = -999
  591. if 'ss' not in sat:
  592. sat['ss'] = -999
  593. self.satellites.append(gps.satellite(PRN=sat['PRN'],
  594. elevation=sat['el'],
  595. azimuth=sat['az'], ss=sat['ss'],
  596. used=sat['used']))
  597. self.satellites_used = 0
  598. for sat in self.satellites:
  599. if sat.used:
  600. self.satellites_used += 1
  601. self.valid = ONLINE_SET | SATELLITE_SET
  602. elif self.data.get("class") == "PPS":
  603. self.device = default("device", "missing")
  604. self.real_sec = default("real_sec", NaN)
  605. self.real_nsec = default("real_nsec", NaN)
  606. self.clock_sec = default("clock_sec", NaN)
  607. self.clock_nsec = default("clock_nsec", NaN)
  608. self.precision = default("precision", 0)
  609. # elif self.data.get("class") == "DEVICES":
  610. # TODO: handle class DEVICES # pylint: disable=fixme
  611. def read(self):
  612. """Read and interpret data from the daemon."""
  613. status = gpscommon.read(self)
  614. if status <= 0:
  615. return status
  616. if self.response.startswith("{") and self.response.endswith("}\r\n"):
  617. self.unpack(self.response)
  618. self._oldstyle_shim()
  619. self.valid |= PACKET_SET
  620. return 0
  621. def __next__(self):
  622. """Python 3 version of next()."""
  623. if self.read() == -1:
  624. raise StopIteration
  625. if hasattr(self, "data"):
  626. return self.data
  627. return self.response
  628. def next(self):
  629. """Python 2 backward compatibility."""
  630. return self.__next__()
  631. def stream(self, flags=0, devpath=None):
  632. """Ask gpsd to stream reports at your client."""
  633. gpsjson.stream(self, flags, devpath)
  634. def is_sbas(prn):
  635. """Is this the NMEA ID of an SBAS satellite?."""
  636. return 120 <= prn <= 158
  637. if __name__ == '__main__':
  638. # FIXME: relative imports break this __main__
  639. description = 'gps/gps.py module.'
  640. usage = '%(prog)s [OPTIONS] [host [port]]'
  641. epilog = ('BSD terms apply: see the file COPYING in the distribution root'
  642. ' for details.')
  643. parser = argparse.ArgumentParser(
  644. description=description,
  645. epilog=epilog,
  646. formatter_class=argparse.RawDescriptionHelpFormatter,
  647. usage=usage)
  648. parser.add_argument(
  649. '-?',
  650. action="help",
  651. help='show this help message and exit'
  652. )
  653. parser.add_argument(
  654. '-v',
  655. '--verbose',
  656. dest='verbose',
  657. default=0,
  658. action='count',
  659. help='Verbose. Repeat for more verbosity. [Default %(default)s]',
  660. )
  661. parser.add_argument(
  662. '-V', '--version',
  663. action='version',
  664. version="%(prog)s: Version " + gps_version + "\n",
  665. help='Output version to stderr, then exit'
  666. )
  667. parser.add_argument(
  668. 'arguments',
  669. metavar='[host [port]]',
  670. nargs='*',
  671. help='[host [port]] Host and port to connec to gpsd on.'
  672. )
  673. options = parser.parse_args()
  674. streaming = False
  675. if arguments and 2 < len(arguments):
  676. sys.stderr.write("gps.py: too many positional arguments.")
  677. sys.exit(1)
  678. opts = {"verbose": options.verb}
  679. if options.arguments:
  680. opts["host"] = options.arguments[0]
  681. if 2 == len(options.arguments):
  682. opts["port"] = options.arguments[1]
  683. session = gps(**opts)
  684. session.stream(WATCH_ENABLE)
  685. try:
  686. for report in session:
  687. print(report)
  688. except KeyboardInterrupt:
  689. # Avoid garble on ^C
  690. print("")
  691. # gps.py ends here
  692. # vim: set expandtab shiftwidth=4