Devuan fork of gpsd
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

384 lines
12 KiB

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. '''gps.py -- Python interface to GPSD.
  4. This interface has a lot of historical cruft in it related to old
  5. protocol, and was modeled on the C interface. It won't be thrown
  6. away, but it's likely to be deprecated in favor of something more
  7. Pythonic.
  8. The JSON parts of this (which will be reused by any new interface)
  9. now live in a different module.
  10. '''
  11. #
  12. # This file is Copyright (c) 2010-2019 by the GPSD project
  13. # BSD terms apply: see the file COPYING in the distribution root for details.
  14. #
  15. # This code runs compatibly under Python 2 and 3.x for x >= 2.
  16. # Preserve this property!
  17. from __future__ import absolute_import, print_function, division
  18. from .client import *
  19. from .watch_options import *
  20. NaN = float('nan')
  21. def isfinite(f):
  22. "Check if f is finite"
  23. # Python 2 does not think +Inf or -Inf are NaN
  24. # Python 2 has no easier way to test for Inf
  25. return float('-inf') < float(f) < float('inf')
  26. # Don't hand-hack this list, it's generated.
  27. ONLINE_SET = (1 << 1)
  28. TIME_SET = (1 << 2)
  29. TIMERR_SET = (1 << 3)
  30. LATLON_SET = (1 << 4)
  31. ALTITUDE_SET = (1 << 5)
  32. SPEED_SET = (1 << 6)
  33. TRACK_SET = (1 << 7)
  34. CLIMB_SET = (1 << 8)
  35. STATUS_SET = (1 << 9)
  36. MODE_SET = (1 << 10)
  37. DOP_SET = (1 << 11)
  38. HERR_SET = (1 << 12)
  39. VERR_SET = (1 << 13)
  40. ATTITUDE_SET = (1 << 14)
  41. SATELLITE_SET = (1 << 15)
  42. SPEEDERR_SET = (1 << 16)
  43. TRACKERR_SET = (1 << 17)
  44. CLIMBERR_SET = (1 << 18)
  45. DEVICE_SET = (1 << 19)
  46. DEVICELIST_SET = (1 << 20)
  47. DEVICEID_SET = (1 << 21)
  48. RTCM2_SET = (1 << 22)
  49. RTCM3_SET = (1 << 23)
  50. AIS_SET = (1 << 24)
  51. PACKET_SET = (1 << 25)
  52. SUBFRAME_SET = (1 << 26)
  53. GST_SET = (1 << 27)
  54. VERSION_SET = (1 << 28)
  55. POLICY_SET = (1 << 29)
  56. LOGMESSAGE_SET = (1 << 30)
  57. ERROR_SET = (1 << 31)
  58. TIMEDRIFT_SET = (1 << 32)
  59. EOF_SET = (1 << 33)
  60. SET_HIGH_BIT = 34
  61. UNION_SET = (RTCM2_SET | RTCM3_SET | SUBFRAME_SET | AIS_SET | VERSION_SET |
  62. DEVICELIST_SET | ERROR_SET | GST_SET)
  63. STATUS_NO_FIX = 0
  64. STATUS_FIX = 1
  65. STATUS_DGPS_FIX = 2
  66. STATUS_RTK_FIX = 3
  67. STATUS_RTK_FLT = 4
  68. STATUS_DR = 5
  69. STATUS_GNSSDR = 6
  70. STATUS_TIME = 7
  71. STATUS_SIM = 8
  72. STATUS_PPS_FIX = 9
  73. MODE_NO_FIX = 1
  74. MODE_2D = 2
  75. MODE_3D = 3
  76. MAXCHANNELS = 72 # Copied from gps.h, but not required to match
  77. SIGNAL_STRENGTH_UNKNOWN = NaN
  78. class gpsfix(object):
  79. "Class to hold one GPS fix"
  80. def __init__(self):
  81. "Init class gpsfix"
  82. self.altitude = NaN # Meters DEPRECATED
  83. self.altHAE = NaN # Meters
  84. self.altMSL = NaN # Meters
  85. self.climb = NaN # Meters per second
  86. self.datum = ""
  87. self.dgpsAge = -1
  88. self.dgpsSta = ""
  89. self.depth = NaN
  90. self.device = ""
  91. self.ecefx = NaN
  92. self.ecefy = NaN
  93. self.ecefz = NaN
  94. self.ecefvx = NaN
  95. self.ecefvy = NaN
  96. self.ecefvz = NaN
  97. self.ecefpAcc = NaN
  98. self.ecefvAcc = NaN
  99. self.epc = NaN
  100. self.epd = NaN
  101. self.eph = NaN
  102. self.eps = NaN
  103. self.ept = NaN
  104. self.epv = NaN
  105. self.epx = NaN
  106. self.epy = NaN
  107. self.geoidSep = NaN # Meters
  108. self.latitude = self.longitude = 0.0
  109. self.magtrack = NaN
  110. self.magvar = NaN
  111. self.mode = MODE_NO_FIX
  112. self.relN = NaN
  113. self.relE = NaN
  114. self.relD = NaN
  115. self.sep = NaN # a.k.a. epe
  116. self.speed = NaN # Knots
  117. self.status = STATUS_NO_FIX
  118. self.time = NaN
  119. self.track = NaN # Degrees from true north
  120. self.velN = NaN
  121. self.velE = NaN
  122. self.velD = NaN
  123. class gpsdata(object):
  124. "Position, track, velocity and status information returned by a GPS."
  125. class satellite(object):
  126. "Class to hold satellite data"
  127. def __init__(self, PRN, elevation, azimuth, ss, used=None):
  128. self.PRN = PRN
  129. self.elevation = elevation
  130. self.azimuth = azimuth
  131. self.ss = ss
  132. self.used = used
  133. def __repr__(self):
  134. return "PRN: %3d E: %3d Az: %3d Ss: %3d Used: %s" % (
  135. self.PRN, self.elevation, self.azimuth, self.ss,
  136. "ny"[self.used])
  137. def __init__(self):
  138. # Initialize all data members
  139. self.online = 0 # NZ if GPS on, zero if not
  140. self.valid = 0
  141. self.fix = gpsfix()
  142. self.status = STATUS_NO_FIX
  143. self.utc = ""
  144. self.satellites_used = 0 # Satellites used in last fix
  145. self.xdop = self.ydop = self.vdop = self.tdop = 0
  146. self.pdop = self.hdop = self.gdop = 0.0
  147. self.epe = 0.0
  148. self.satellites = [] # satellite objects in view
  149. self.gps_id = None
  150. self.driver_mode = 0
  151. self.baudrate = 0
  152. self.stopbits = 0
  153. self.cycle = 0
  154. self.mincycle = 0
  155. self.device = None
  156. self.devices = []
  157. self.version = None
  158. def __repr__(self):
  159. st = "Time: %s (%s)\n" % (self.utc, self.fix.time)
  160. st += "Lat/Lon: %f %f\n" % (self.fix.latitude, self.fix.longitude)
  161. if not isfinite(self.fix.altHAE):
  162. st += "Altitude HAE: ?\n"
  163. else:
  164. st += "Altitude HAE: %f\n" % (self.fix.altHAE)
  165. if not isfinite(self.fix.speed):
  166. st += "Speed: ?\n"
  167. else:
  168. st += "Speed: %f\n" % (self.fix.speed)
  169. if not isfinite(self.fix.track):
  170. st += "Track: ?\n"
  171. else:
  172. st += "Track: %f\n" % (self.fix.track)
  173. st += "Status: STATUS_%s\n" \
  174. % ("NO_FIX", "FIX", "DGPS_FIX")[self.status]
  175. st += "Mode: MODE_%s\n" \
  176. % ("ZERO", "NO_FIX", "2D", "3D")[self.fix.mode]
  177. st += "Quality: %d p=%2.2f h=%2.2f v=%2.2f t=%2.2f g=%2.2f\n" % \
  178. (self.satellites_used, self.pdop, self.hdop, self.vdop,
  179. self.tdop, self.gdop)
  180. st += "Y: %s satellites in view:\n" % len(self.satellites)
  181. for sat in self.satellites:
  182. st += " %r\n" % sat
  183. return st
  184. class gps(gpscommon, gpsdata, gpsjson):
  185. "Client interface to a running gpsd instance."
  186. # module version, would be nice to automate the version
  187. __version__ = "3.20"
  188. def __init__(self, host="127.0.0.1", port=GPSD_PORT, verbose=0, mode=0,
  189. reconnect=False):
  190. self.activated = None
  191. self.clock_sec = NaN
  192. self.clock_nsec = NaN
  193. self.path = ''
  194. self.precision = 0
  195. self.real_sec = NaN
  196. self.real_nsec = NaN
  197. self.serialmode = "8N1"
  198. gpscommon.__init__(self, host, port, verbose, reconnect)
  199. gpsdata.__init__(self)
  200. gpsjson.__init__(self)
  201. if mode:
  202. self.stream(mode)
  203. def _oldstyle_shim(self):
  204. # The rest is backwards compatibility for the old interface
  205. def default(k, dflt, vbit=0):
  206. "Return default for key"
  207. if k not in self.data.keys():
  208. return dflt
  209. self.valid |= vbit
  210. return self.data[k]
  211. if self.data.get("class") == "VERSION":
  212. self.version = self.data
  213. elif self.data.get("class") == "DEVICE":
  214. self.valid = ONLINE_SET | DEVICE_SET
  215. self.path = self.data["path"]
  216. self.activated = default("activated", None)
  217. driver = default("driver", None, DEVICEID_SET)
  218. subtype = default("subtype", None, DEVICEID_SET)
  219. self.gps_id = driver
  220. if subtype:
  221. self.gps_id += " " + subtype
  222. self.baudrate = default("bps", 0)
  223. self.cycle = default("cycle", NaN)
  224. self.driver_mode = default("native", 0)
  225. self.mincycle = default("mincycle", NaN)
  226. self.serialmode = default("serialmode", "8N1")
  227. elif self.data.get("class") == "TPV":
  228. self.device = default("device", "missing")
  229. self.utc = default("time", None, TIME_SET)
  230. self.valid = ONLINE_SET
  231. if self.utc is not None:
  232. # self.utc is always iso 8601 string
  233. # just copy to fix.time
  234. self.fix.time = self.utc
  235. self.fix.altitude = default("alt", NaN, ALTITUDE_SET) # DEPRECATED
  236. self.fix.altHAE = default("altHAE", NaN, ALTITUDE_SET)
  237. self.fix.altMSL = default("altMSL", NaN, ALTITUDE_SET)
  238. self.fix.climb = default("climb", NaN, CLIMB_SET)
  239. self.fix.epc = default("epc", NaN, CLIMBERR_SET)
  240. self.fix.epd = default("epd", NaN)
  241. self.fix.eps = default("eps", NaN, SPEEDERR_SET)
  242. self.fix.ept = default("ept", NaN, TIMERR_SET)
  243. self.fix.epv = default("epv", NaN, VERR_SET)
  244. self.fix.epx = default("epx", NaN, HERR_SET)
  245. self.fix.epy = default("epy", NaN, HERR_SET)
  246. self.fix.latitude = default("lat", NaN, LATLON_SET)
  247. self.fix.longitude = default("lon", NaN)
  248. self.fix.mode = default("mode", 0, MODE_SET)
  249. self.fix.speed = default("speed", NaN, SPEED_SET)
  250. self.fix.status = default("status", 1)
  251. self.fix.track = default("track", NaN, TRACK_SET)
  252. elif self.data.get("class") == "SKY":
  253. self.device = default("device", "missing")
  254. for attrp in ("g", "h", "p", "t", "v", "x", "y"):
  255. n = attrp + "dop"
  256. setattr(self, n, default(n, NaN, DOP_SET))
  257. if "satellites" in self.data.keys():
  258. self.satellites = []
  259. for sat in self.data['satellites']:
  260. if 'el' not in sat:
  261. sat['el'] = -999
  262. if 'az' not in sat:
  263. sat['az'] = -999
  264. if 'ss' not in sat:
  265. sat['ss'] = -999
  266. self.satellites.append(gps.satellite(PRN=sat['PRN'],
  267. elevation=sat['el'],
  268. azimuth=sat['az'], ss=sat['ss'],
  269. used=sat['used']))
  270. self.satellites_used = 0
  271. for sat in self.satellites:
  272. if sat.used:
  273. self.satellites_used += 1
  274. self.valid = ONLINE_SET | SATELLITE_SET
  275. elif self.data.get("class") == "PPS":
  276. self.device = default("device", "missing")
  277. self.real_sec = default("real_sec", NaN)
  278. self.real_nsec = default("real_nsec", NaN)
  279. self.clock_sec = default("clock_sec", NaN)
  280. self.clock_nsec = default("clock_nsec", NaN)
  281. self.precision = default("precision", 0)
  282. # elif self.data.get("class") == "DEVICES":
  283. # TODO: handle class DEVICES # pylint: disable=fixme
  284. def read(self):
  285. "Read and interpret data from the daemon."
  286. status = gpscommon.read(self)
  287. if status <= 0:
  288. return status
  289. if self.response.startswith("{") and self.response.endswith("}\r\n"):
  290. self.unpack(self.response)
  291. self._oldstyle_shim()
  292. self.valid |= PACKET_SET
  293. return 0
  294. def __next__(self):
  295. "Python 3 version of next()."
  296. if self.read() == -1:
  297. raise StopIteration
  298. if hasattr(self, "data"):
  299. return self.data
  300. return self.response
  301. def next(self):
  302. "Python 2 backward compatibility."
  303. return self.__next__()
  304. def stream(self, flags=0, devpath=None):
  305. "Ask gpsd to stream reports at your client."
  306. gpsjson.stream(self, flags, devpath)
  307. def is_sbas(prn):
  308. "Is this the NMEA ID of an SBAS satellite?"
  309. return 120 <= prn <= 158
  310. if __name__ == '__main__':
  311. import getopt
  312. import sys
  313. (options, arguments) = getopt.getopt(sys.argv[1:], "v")
  314. streaming = False
  315. verbose = False
  316. for (switch, val) in options:
  317. if switch == '-v':
  318. verbose = True
  319. if len(arguments) > 2:
  320. print('Usage: gps.py [-v] [host [port]]')
  321. sys.exit(1)
  322. opts = {"verbose": verbose}
  323. if arguments:
  324. opts["host"] = arguments[0]
  325. if arguments:
  326. opts["port"] = arguments[1]
  327. session = gps(**opts)
  328. session.stream(WATCH_ENABLE)
  329. try:
  330. for report in session:
  331. print(report)
  332. except KeyboardInterrupt:
  333. # Avoid garble on ^C
  334. print("")
  335. # gps.py ends here