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.
 
 
 
 
 
 

342 lines
11 KiB

  1. "gpsd client functions"
  2. # This file is Copyright (c) 2010 by the GPSD project
  3. # BSD terms apply: see the file COPYING in the distribution root for details.
  4. #
  5. # This code run compatibly under Python 2 and 3.x for x >= 2.
  6. # Preserve this property!
  7. from __future__ import absolute_import, print_function, division
  8. import json
  9. import select
  10. import socket
  11. import sys
  12. import time
  13. from .misc import polystr, polybytes
  14. from .watch_options import *
  15. GPSD_PORT = "2947"
  16. class gpscommon(object):
  17. "Isolate socket handling and buffering from the protocol interpretation."
  18. host = "127.0.0.1"
  19. port = GPSD_PORT
  20. def __init__(self, host="127.0.0.1", port=GPSD_PORT, verbose=0,
  21. should_reconnect=False):
  22. self.stream_command = b''
  23. self.linebuffer = b''
  24. self.received = time.time()
  25. self.reconnect = should_reconnect
  26. self.verbose = verbose
  27. self.sock = None # in case we blow up in connect
  28. # Provide the response in both 'str' and 'bytes' form
  29. self.bresponse = b''
  30. self.response = polystr(self.bresponse)
  31. if host is not None:
  32. self.host = host
  33. if port is not None:
  34. self.port = port
  35. self.connect(self.host, self.port)
  36. def connect(self, host, port):
  37. """Connect to a host on a given port.
  38. If the hostname ends with a colon (`:') followed by a number, and
  39. there is no port specified, that suffix will be stripped off and the
  40. number interpreted as the port number to use.
  41. """
  42. if not port and (host.find(':') == host.rfind(':')):
  43. i = host.rfind(':')
  44. if i >= 0:
  45. host, port = host[:i], host[i + 1:]
  46. try:
  47. port = int(port)
  48. except ValueError:
  49. raise socket.error("nonnumeric port")
  50. # if self.verbose > 0:
  51. # print 'connect:', (host, port)
  52. msg = "getaddrinfo returns an empty list"
  53. self.sock = None
  54. for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
  55. af, socktype, proto, _canonname, sa = res
  56. try:
  57. self.sock = socket.socket(af, socktype, proto)
  58. # if self.debuglevel > 0: print 'connect:', (host, port)
  59. self.sock.connect(sa)
  60. if self.verbose > 0:
  61. print('connected to tcp://{}:{}'.format(host, port))
  62. break
  63. # do not use except ConnectionRefusedError
  64. # # Python 2.7 doc does have this exception
  65. except socket.error as e:
  66. if self.verbose > 1:
  67. msg = str(e) + ' (to {}:{})'.format(host, port)
  68. sys.stderr.write("error: {}\n".format(msg.strip()))
  69. self.close()
  70. raise # propogate error to caller
  71. def close(self):
  72. "Close the gpsd socket"
  73. if self.sock:
  74. self.sock.close()
  75. self.sock = None
  76. def __del__(self):
  77. "Close the gpsd socket"
  78. self.close()
  79. def waiting(self, timeout=0):
  80. "Return True if data is ready for the client."
  81. if self.linebuffer:
  82. return True
  83. if self.sock is None:
  84. return False
  85. (winput, _woutput, _wexceptions) = select.select(
  86. (self.sock,), (), (), timeout)
  87. return winput != []
  88. def read(self):
  89. "Wait for and read data being streamed from the daemon."
  90. if None is self.sock:
  91. self.connect(self.host, self.port)
  92. if None is self.sock:
  93. return -1
  94. self.stream()
  95. eol = self.linebuffer.find(b'\n')
  96. if eol == -1:
  97. # RTCM3 JSON can be over 4.4k long, so go big
  98. frag = self.sock.recv(8192)
  99. self.linebuffer += frag
  100. if not self.linebuffer:
  101. if self.verbose > 1:
  102. sys.stderr.write(
  103. "poll: no available data: returning -1.\n")
  104. # Read failed
  105. return -1
  106. eol = self.linebuffer.find(b'\n')
  107. if eol == -1:
  108. if self.verbose > 1:
  109. sys.stderr.write("poll: partial message: returning 0.\n")
  110. # Read succeeded, but only got a fragment
  111. self.response = '' # Don't duplicate last response
  112. return 0
  113. else:
  114. if self.verbose > 1:
  115. sys.stderr.write("poll: fetching from buffer.\n")
  116. # We got a line
  117. eol += 1
  118. # Provide the response in both 'str' and 'bytes' form
  119. self.bresponse = self.linebuffer[:eol]
  120. self.response = polystr(self.bresponse)
  121. self.linebuffer = self.linebuffer[eol:]
  122. # Can happen if daemon terminates while we're reading.
  123. if not self.response:
  124. return -1
  125. if 1 < self.verbose:
  126. sys.stderr.write("poll: data is %s\n" % repr(self.response))
  127. self.received = time.time()
  128. # We got a \n-terminated line
  129. return len(self.response)
  130. # Note that the 'data' method is sometimes shadowed by a name
  131. # collision, rendering it unusable. The documentation recommends
  132. # accessing 'response' directly. Consequently, no accessor method
  133. # for 'bresponse' is currently provided.
  134. def data(self):
  135. "Return the client data buffer."
  136. return self.response
  137. def send(self, commands):
  138. "Ship commands to the daemon."
  139. lineend = "\n"
  140. if isinstance(commands, bytes):
  141. lineend = polybytes("\n")
  142. if not commands.endswith(lineend):
  143. commands += lineend
  144. if self.sock is None:
  145. self.stream_command = commands
  146. else:
  147. self.sock.send(polybytes(commands))
  148. class json_error(BaseException):
  149. "Class for JSON errors"
  150. def __init__(self, data, explanation):
  151. BaseException.__init__(self)
  152. self.data = data
  153. self.explanation = explanation
  154. class gpsjson(object):
  155. "Basic JSON decoding."
  156. def __init__(self):
  157. self.data = None
  158. self.stream_command = None
  159. self.enqueued = None
  160. self.verbose = -1
  161. def __iter__(self):
  162. "Broken __iter__"
  163. return self
  164. def unpack(self, buf):
  165. "Unpack a JSON string"
  166. try:
  167. self.data = dictwrapper(json.loads(buf.strip(), encoding="ascii"))
  168. except ValueError as e:
  169. raise json_error(buf, e.args[0])
  170. # Should be done for any other array-valued subobjects, too.
  171. # This particular logic can fire on SKY or RTCM2 objects.
  172. if hasattr(self.data, "satellites"):
  173. self.data.satellites = [dictwrapper(x)
  174. for x in self.data.satellites]
  175. def stream(self, flags=0, devpath=None):
  176. "Control streaming reports from the daemon,"
  177. if 0 < flags:
  178. self.stream_command = self.generate_stream_command(flags, devpath)
  179. else:
  180. self.stream_command = self.enqueued
  181. if self.stream_command:
  182. if self.verbose > 1:
  183. sys.stderr.write("send: stream as:"
  184. " {}\n".format(self.stream_command))
  185. self.send(self.stream_command)
  186. else:
  187. raise TypeError("Invalid streaming command!! : " + str(flags))
  188. def generate_stream_command(self, flags=0, devpath=None):
  189. "Generate stream command"
  190. if flags & WATCH_OLDSTYLE:
  191. return self.generate_stream_command_old_style(flags)
  192. return self.generate_stream_command_new_style(flags, devpath)
  193. @staticmethod
  194. def generate_stream_command_old_style(flags=0):
  195. "Generate stream command, old style"
  196. if flags & WATCH_DISABLE:
  197. arg = "w-"
  198. if flags & WATCH_NMEA:
  199. arg += 'r-'
  200. elif flags & WATCH_ENABLE:
  201. arg = 'w+'
  202. if flags & WATCH_NMEA:
  203. arg += 'r+'
  204. return arg
  205. @staticmethod
  206. def generate_stream_command_new_style(flags=0, devpath=None):
  207. "Generate stream command, new style"
  208. if (flags & (WATCH_JSON | WATCH_OLDSTYLE | WATCH_NMEA |
  209. WATCH_RAW)) == 0:
  210. flags |= WATCH_JSON
  211. if flags & WATCH_DISABLE:
  212. arg = '?WATCH={"enable":false'
  213. if flags & WATCH_JSON:
  214. arg += ',"json":false'
  215. if flags & WATCH_NMEA:
  216. arg += ',"nmea":false'
  217. if flags & WATCH_RARE:
  218. arg += ',"raw":1'
  219. if flags & WATCH_RAW:
  220. arg += ',"raw":2'
  221. if flags & WATCH_SCALED:
  222. arg += ',"scaled":false'
  223. if flags & WATCH_TIMING:
  224. arg += ',"timing":false'
  225. if flags & WATCH_SPLIT24:
  226. arg += ',"split24":false'
  227. if flags & WATCH_PPS:
  228. arg += ',"pps":false'
  229. else: # flags & WATCH_ENABLE:
  230. arg = '?WATCH={"enable":true'
  231. if flags & WATCH_JSON:
  232. arg += ',"json":true'
  233. if flags & WATCH_NMEA:
  234. arg += ',"nmea":true'
  235. if flags & WATCH_RARE:
  236. arg += ',"raw":1'
  237. if flags & WATCH_RAW:
  238. arg += ',"raw":2'
  239. if flags & WATCH_SCALED:
  240. arg += ',"scaled":true'
  241. if flags & WATCH_TIMING:
  242. arg += ',"timing":true'
  243. if flags & WATCH_SPLIT24:
  244. arg += ',"split24":true'
  245. if flags & WATCH_PPS:
  246. arg += ',"pps":true'
  247. if flags & WATCH_DEVICE:
  248. arg += ',"device":"%s"' % devpath
  249. arg += "}"
  250. return arg
  251. class dictwrapper(object):
  252. "Wrapper that yields both class and dictionary behavior,"
  253. def __init__(self, ddict):
  254. "Init class dictwrapper"
  255. self.__dict__ = ddict
  256. def get(self, k, d=None):
  257. "Get dictwrapper"
  258. return self.__dict__.get(k, d)
  259. def keys(self):
  260. "Keys dictwrapper"
  261. return self.__dict__.keys()
  262. def __getitem__(self, key):
  263. "Emulate dictionary, for new-style interface."
  264. return self.__dict__[key]
  265. def __iter__(self):
  266. "Iterate dictwrapper"
  267. return self.__dict__.__iter__()
  268. def __setitem__(self, key, val):
  269. "Emulate dictionary, for new-style interface."
  270. self.__dict__[key] = val
  271. def __contains__(self, key):
  272. "Find key in dictwrapper"
  273. return key in self.__dict__
  274. def __str__(self):
  275. "dictwrapper to string"
  276. return "<dictwrapper: " + str(self.__dict__) + ">"
  277. __repr__ = __str__
  278. def __len__(self):
  279. "length of dictwrapper"
  280. return len(self.__dict__)
  281. #
  282. # Someday a cleaner Python interface using this machinery will live here
  283. #
  284. # End