ubxtool.py.in 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. #!@PYSHEBANG@
  2. # -*- coding: UTF-8
  3. # @GENERATED@
  4. # This file is Copyright 2018 by the GPSD project
  5. # SPDX-License-Identifier: BSD-2-clause
  6. #
  7. # This code runs compatibly under Python 2 and 3.x for x >= 2.
  8. # Preserve this property!
  9. #
  10. # ENVIRONMENT:
  11. # Options in the UBXOPTS environment variable will be parsed before
  12. # the CLI options. A handy place to put your '-f /dev/ttyXX -s SPEED'
  13. #
  14. # To see what constellations are enabled:
  15. # ubxtool -p CFG-GNSS -f /dev/ttyXX
  16. #
  17. # To disable GLONASS and enable GALILEO:
  18. # ubxtool -d GLONASS -f /dev/ttyXX
  19. # ubxtool -e GALILEO -f /dev/ttyXX
  20. #
  21. # To read GPS messages a log file:
  22. # ubxtool -v 2 -f test/daemon/ublox-neo-m8n.log
  23. #
  24. # References:
  25. # [1] IS-GPS-200K
  26. # Codacy D203 and D211 conflict, I choose D203
  27. # Codacy D212 and D213 conflict, I choose D212
  28. """ubxtool -- u-blox configurator and packet decoder.
  29. usage: ubxtool [OPTIONS] [host[:port[:device]]]
  30. """
  31. from __future__ import absolute_import, print_function, division
  32. import argparse # to parse CLI options
  33. from functools import reduce # pylint: disable=redefined-builtin
  34. import operator # for or_
  35. import os # for os.environ
  36. import re # for regular expressions
  37. import struct # for pack()
  38. import sys
  39. PROG_NAME = 'ubxtool'
  40. try:
  41. import gps
  42. import gps.ubx
  43. except ImportError:
  44. # PEP8 says local imports last
  45. sys.stderr.write("%s: failed to import gps, check PYTHONPATH\n" %
  46. PROG_NAME)
  47. sys.exit(2)
  48. gps_version = '@VERSION@'
  49. if gps.__version__ != gps_version:
  50. sys.stderr.write("%s: ERROR: need gps module version %s, got %s\n" %
  51. (PROG_NAME, gps_version, gps.__version__))
  52. sys.exit(1)
  53. # Some old versions of Python fail to accept a bytearray as an input to
  54. # struct.unpack_from, though it can be worked around by wrapping it with
  55. # buffer(). Since the fix is only needed in rare cases, this monkey-patches
  56. # struct.unpack_from() when needed, and otherwise changes nothing. If
  57. # struct.unpack() were used, it would need similar treatment, as would
  58. # methods from struct.Struct if that were used.
  59. try:
  60. struct.unpack_from('B', bytearray(1))
  61. except TypeError:
  62. unpack_from_orig = struct.unpack_from
  63. def unpack_from_fixed(fmt, buf, offset=0):
  64. """Unpack_from_fixed."""
  65. # buffer() is a Python 2 thing.
  66. return unpack_from_orig(fmt, buffer(buf), offset=offset)
  67. struct.unpack_from = unpack_from_fixed
  68. # opts['protver'], protocol version for sent commands
  69. # u-blox 5, firmware 4 to 6 is protver 10 to 12
  70. # u-blox 6, firmware 6 to 7 is protver 12 to 13
  71. # u-blox 6, firmware 1 is protver 14
  72. # u-blox 7, firmware 1 is protver 14
  73. # u-blox 8, is protver 15 to 23
  74. # u-blox 9, firmware 1 is protver 27
  75. # u-blox F9T, firmware 2 is protver 29
  76. # u-blox F9N, firmware 4 is protver 32
  77. # instantiate the GPS class
  78. gps_model = gps.ubx.ubx()
  79. if 'UBXOPTS' in os.environ:
  80. # grab the UBXOPTS environment variable to be handled as prepended options
  81. options = os.environ['UBXOPTS'].split(' ') + sys.argv[1:]
  82. else:
  83. options = sys.argv[1:]
  84. # "m:")
  85. usage = '%(prog)s [OPTIONS] [host[:port[:device]]]'
  86. epilog = ('BSD terms apply: see the file COPYING in the distribution '
  87. 'root for details.')
  88. parser = argparse.ArgumentParser(add_help=False, epilog=epilog, usage=usage)
  89. parser.add_argument(
  90. '-?', '-h', '--help',
  91. action="store_true",
  92. dest='help',
  93. default=False,
  94. help='Show this help message and exit. Use -v 2, or -v 3, for extra help'
  95. )
  96. parser.add_argument(
  97. '-c', '--command',
  98. dest='command',
  99. default=None,
  100. metavar='CMD',
  101. help='Send raw command CMD (cls,id...) to receiver'
  102. )
  103. parser.add_argument(
  104. '-d', '--disable',
  105. action='append',
  106. dest='disable',
  107. default=None,
  108. metavar='ABLE',
  109. help='Disable ABLE in the receiver. May be used multiple times. ',
  110. )
  111. parser.add_argument(
  112. '-e', '--enable',
  113. action='append',
  114. dest='enable',
  115. default=None,
  116. metavar='ABLE',
  117. help='Enable ABLE in the receiver. May be used multiple times. ',
  118. )
  119. parser.add_argument(
  120. '--device',
  121. dest='gpsd_device',
  122. default=None,
  123. metavar='DEVICE',
  124. help='The gpsd device to connect to. [Default %(default)s]',
  125. )
  126. parser.add_argument(
  127. '-f', '--file',
  128. dest='input_file_name',
  129. default=None,
  130. metavar='FILE',
  131. help='Read from FILE instead of a gpsd instance.',
  132. )
  133. parser.add_argument(
  134. '-g', '--getitem',
  135. action='append',
  136. dest='get_item',
  137. default=None,
  138. metavar='ITEM,LAYER,POSITION',
  139. help=('Get ITEM from LAYER and POSITION. LAYER and POSITION are '
  140. 'optional. May be used multiple times.'),
  141. )
  142. parser.add_argument(
  143. '--host',
  144. dest='gpsd_host',
  145. default=None,
  146. metavar='HOST',
  147. help='The gpsd host to connect to.',
  148. )
  149. parser.add_argument(
  150. '-i', '-portid',
  151. dest='port',
  152. default=None,
  153. metavar='PORTID',
  154. help=('Specifies receiver PORTID (interface) for port-related commands. '
  155. '[Default %(default)s]'),
  156. )
  157. parser.add_argument(
  158. '--port',
  159. dest='gpsd_port',
  160. default=gps.GPSD_PORT,
  161. metavar='PORT',
  162. type=int,
  163. help='The gpsd port to connect to. [Default %(default)s]',
  164. )
  165. parser.add_argument(
  166. '-p', '--preset',
  167. action='append',
  168. dest='poll',
  169. metavar='PRESET',
  170. help='Poll the receiver for PRESET. May be used multiple times.',
  171. )
  172. parser.add_argument(
  173. '-P', '--protver',
  174. dest='protver',
  175. default=10.0,
  176. type=float,
  177. help='Protocol version for sending commands. [Default %(default)s]',
  178. )
  179. parser.add_argument(
  180. '-r', '--readonly',
  181. action='store_true',
  182. dest='read_only',
  183. default=False,
  184. help='Read only. Do not send anything to the GPS.',
  185. )
  186. parser.add_argument(
  187. '-R', '--rawfile',
  188. dest='raw_file',
  189. default=None,
  190. metavar='FILE',
  191. help='Save raw data from receiver in FILE\n',
  192. )
  193. parser.add_argument(
  194. '-s', '--inspeed',
  195. choices=gps_model.speeds,
  196. dest='input_speed',
  197. default=9600,
  198. metavar='SPEED',
  199. type=int,
  200. help='Set local serial port speed to SPEED bps.',
  201. )
  202. parser.add_argument(
  203. '-S', '--setspeed',
  204. # FIXME: speeds s/b in io class
  205. choices=gps_model.speeds,
  206. dest='set_speed',
  207. metavar='SPEED',
  208. type=int,
  209. help='Configure receiver speed to SETSPEED.',
  210. )
  211. parser.add_argument(
  212. '-t', '--timestamp',
  213. action='count',
  214. dest='timestamp',
  215. default=0,
  216. help='Timestamp messages with seconds since UNIX epoch. Use -tt for UTC',
  217. )
  218. parser.add_argument(
  219. '-v', '--verbosity',
  220. dest='verbosity',
  221. default=0,
  222. metavar='VERB',
  223. type=int,
  224. help='Set verbosity level to V, 0 to 5. [Default %(default)s]',
  225. )
  226. parser.add_argument(
  227. '-V', '--version',
  228. action='version',
  229. version="%(prog)s: Version " + gps_version + "\n",
  230. help='Output version to stderr, then exit',
  231. )
  232. parser.add_argument(
  233. '-w', '--wait',
  234. dest='input_wait',
  235. default=2.0,
  236. metavar='WAIT',
  237. type=float,
  238. help='Wait for WAIT seconds before exiting.. [Default %(default)s]',
  239. )
  240. parser.add_argument(
  241. '-x', '--delitem',
  242. action='append',
  243. dest='del_item',
  244. default=None,
  245. metavar='ITEM',
  246. help=('Delete ITEM from receiver BBR and FLASH layers. '
  247. 'May be used multiple times. '),
  248. )
  249. parser.add_argument(
  250. '-z', '--setitem',
  251. action='append',
  252. dest='set_item',
  253. default=None,
  254. metavar='ITEM,VAL[,LAYER]',
  255. help=('Set ITEM in receiver to VAL in LAYER. LAYER is optional. '
  256. 'May be used multiple times. '),
  257. )
  258. parser.add_argument(
  259. 'target',
  260. nargs='?',
  261. help='[host[:port[:device]]]',
  262. )
  263. # turn the stupid Namespace into a nice dictionary
  264. opts = vars(parser.parse_args(options))
  265. gps_model.port = opts['port']
  266. gps_model.protver = opts['protver']
  267. gps_model.read_only = opts['read_only']
  268. gps_model.timestamp = opts['timestamp']
  269. gps_model.verbosity = opts['verbosity']
  270. if opts['port']:
  271. # FIXME: better error message
  272. valnum = gps_model.port_id_map.get(opts['port'].upper())
  273. opts['port'] = valnum if valnum is not None else int(opts['port'])
  274. if opts['help']:
  275. parser.print_help()
  276. if gps.VERB_DECODE <= opts['verbosity']:
  277. print('\nABLE for -d/--disable and -e/--enable can be one of:')
  278. for item in sorted(gps_model.able_commands.keys()):
  279. print(" %-13s %s" %
  280. (item, gps_model.able_commands[item]["help"]))
  281. print('\nPRESET for -p can be one of:')
  282. for item in sorted(gps_model.commands.keys()):
  283. print(" %-13s %s" % (item, gps_model.commands[item]["help"]))
  284. print('\n')
  285. if gps.VERB_DECODE < opts['verbosity']:
  286. print('\nITEM for -g/--getitem, -x/--delitem and -z/--setitem '
  287. 'can be one of:')
  288. for item in sorted(gps_model.cfgs):
  289. print(" %s\n"
  290. " %s" % (item[0], item[5]))
  291. print('\n')
  292. print('Options can be placed in the UBXOPTS environment variable.\n'
  293. 'UBXOPTS is processed before the CLI options.')
  294. sys.exit(0)
  295. if opts['input_file_name']:
  296. # input file given
  297. if opts['target']:
  298. sys.stderr.write('%s: ERROR: both input file and target given.\n' %
  299. (PROG_NAME,))
  300. sys.exit(0)
  301. elif opts['target']:
  302. # TODO: move to module gps as a function
  303. # host[:port[:device]]
  304. # or maybe ::device
  305. # or maybe \[ipv6\][:port[:device]]
  306. if '[' == opts['target'][0]:
  307. # hex, or hex+zoneindex IPv6 address
  308. # could be like [fe80::1ff:fe23:4567:890a%eth2]
  309. match = re.match(r'''\[([^]]+)\](.*)''', opts['target'])
  310. opts['gpsd_host'] = match.group(1)
  311. parts = match.group(2).split(':')
  312. else:
  313. # maybe IPv4 address, maybe hostname
  314. parts = opts['target'].split(':')
  315. if parts[0]:
  316. opts['gpsd_host'] = parts[0]
  317. if 1 < len(parts):
  318. if parts[1]:
  319. opts['gpsd_port'] = parts[1]
  320. if 2 < len(parts) and parts[2]:
  321. opts['gpsd_device'] = parts[2]
  322. else:
  323. # else, use defaults: localhost:2947:
  324. if not opts['gpsd_host']:
  325. opts['gpsd_host'] = 'localhost'
  326. if gps.VERB_PROG <= opts['verbosity']:
  327. # dump versions and all options
  328. print('%s: Version %s\n' % (PROG_NAME, gps_version))
  329. print('Options:')
  330. for option in sorted(opts):
  331. print(" %s: %s" % (option, opts[option]))
  332. # done parsing arguments from environment and CLI
  333. try:
  334. # raw log file requested?
  335. raw = None
  336. if opts['raw_file']:
  337. try:
  338. raw = open(opts['raw_file'], 'wb')
  339. except IOError:
  340. sys.stderr.write('%s: failed to open raw file %s\n' %
  341. (PROG_NAME, opts['raw_file']))
  342. sys.exit(1)
  343. # Check if there is a conflict
  344. # create the I/O instance
  345. io_handle = gps.gps_io(
  346. input_file_name=opts['input_file_name'],
  347. read_only=opts['read_only'],
  348. gpsd_host=opts['gpsd_host'],
  349. gpsd_port=opts['gpsd_port'],
  350. gpsd_device=opts['gpsd_device'],
  351. input_speed=opts['input_speed'],
  352. verbosity_level=opts['verbosity'],
  353. write_requested=(opts['disable'] or opts['enable'] or opts['poll']))
  354. gps_model.io_handle = io_handle
  355. sys.stdout.flush()
  356. if opts['disable']:
  357. for disable in opts['disable']:
  358. if gps.VERB_QUIET < opts['verbosity']:
  359. print('%s: disable %s\n' % (PROG_NAME, disable))
  360. args = disable.split(',')
  361. disable = args[0].upper()
  362. if disable in gps_model.able_commands:
  363. command = gps_model.able_commands[disable]
  364. command["command"](gps_model, 0, disable[1:])
  365. else:
  366. sys.stderr.write('%s: disable %s not found\n' %
  367. (PROG_NAME, disable))
  368. sys.exit(1)
  369. if opts['enable']:
  370. for enable in opts['enable']:
  371. if gps.VERB_QUIET < opts['verbosity']:
  372. print('%s: enable %s\n' % (PROG_NAME, enable))
  373. args = enable.split(',')
  374. enable = args[0].upper()
  375. if enable in gps_model.able_commands:
  376. command = gps_model.able_commands[enable]
  377. command["command"](gps_model, 1, args[1:])
  378. else:
  379. sys.stderr.write('%s: enable %s not found\n' %
  380. (PROG_NAME, enable))
  381. sys.exit(1)
  382. if opts['poll']:
  383. for poll in opts['poll']:
  384. if gps.VERB_QUIET < opts['verbosity']:
  385. print('%s: poll %s\n' % (PROG_NAME, poll))
  386. args = poll.split(',')
  387. poll = args[0].upper()
  388. if poll in gps_model.commands:
  389. command = gps_model.commands[poll]
  390. if (('minVer' in command and
  391. opts['protver'] < command['minVer'])):
  392. print('%s: WARNING poll %s requires protVer >= %s '
  393. 'you have %s\n' %
  394. (PROG_NAME, poll, command['minVer'],
  395. opts['protver']))
  396. if (('maxVer' in command and
  397. opts['protver'] > command['maxVer'])):
  398. print('%s: WARNING poll %s requires protVer <= %s '
  399. 'you have %s\n' %
  400. (PROG_NAME, poll, command['maxVer'],
  401. opts['protver']))
  402. if 'opt' in command:
  403. command["command"](gps_model, command["opt"])
  404. elif 'args' in command:
  405. # pass on args, except arg[0]
  406. command["command"](gps_model, args[1:])
  407. else:
  408. command["command"](gps_model)
  409. else:
  410. sys.stderr.write('%s: poll %s not found\n' %
  411. (PROG_NAME, poll))
  412. sys.exit(1)
  413. elif opts['set_speed'] is not None:
  414. gps_model.send_set_speed(opts['set_speed'])
  415. elif opts['command'] is not None:
  416. cmd_list = opts['command'].split(',')
  417. try:
  418. cmd_data = [int(v, 16) for v in cmd_list]
  419. except ValueError:
  420. badarg = True
  421. else:
  422. data_or = reduce(operator.or_, cmd_data)
  423. badarg = data_or != data_or & 0xFF
  424. if badarg or len(cmd_list) < 2:
  425. sys.stderr.write('%s: Argument format (hex bytes) is'
  426. ' class,id[,payload...]\n' % PROG_NAME)
  427. sys.exit(1)
  428. payload = bytearray(cmd_data[2:])
  429. if gps.VERB_QUIET < opts['verbosity']:
  430. print('%s: command %s\n' % (PROG_NAME, opts['command']))
  431. gps_model.gps_send(cmd_data[0], cmd_data[1], payload)
  432. elif opts['del_item']:
  433. keys = []
  434. for name in opts['del_item']:
  435. item = gps_model.cfg_by_name(name)
  436. if item:
  437. keys.append(item[1])
  438. else:
  439. sys.stderr.write('%s: ERROR: item %s unknown\n' %
  440. (PROG_NAME, opts['del_item']))
  441. sys.exit(1)
  442. gps_model.send_cfg_valdel(keys)
  443. elif opts['get_item']:
  444. keys = []
  445. layer = None
  446. position = 0
  447. end = None
  448. for item in opts['get_item']:
  449. parts = item.split(',')
  450. item = gps_model.cfg_by_name(parts[0].upper())
  451. if item:
  452. keys.append(item[1])
  453. else:
  454. sys.stderr.write('%s: ERROR: item %s unknown\n' %
  455. (PROG_NAME, parts[0]))
  456. sys.exit(1)
  457. try:
  458. if 1 < len(parts):
  459. layer = int(parts[1])
  460. if 2 < len(parts):
  461. position = int(parts[2])
  462. if 3 < len(parts):
  463. end = int(parts[3])
  464. except ValueError:
  465. sys.stderr.write('%s: ERROR: -g %s, non numeric parameter\n' %
  466. (PROG_NAME, parts[0]))
  467. sys.exit(1)
  468. print("layer %s position %d end %s " % (layer, position, end))
  469. if end:
  470. for p in range(position, end, 64):
  471. gps_model.send_cfg_valget(keys, layer, p)
  472. else:
  473. gps_model.send_cfg_valget(keys, layer, position)
  474. elif opts['set_item']:
  475. nvs = []
  476. for nv in opts['set_item']:
  477. parts = nv.split(',')
  478. if 1 >= len(parts):
  479. sys.stderr.write('%s: ERROR: item %s missing value to set\n' %
  480. (PROG_NAME, parts[0]))
  481. sys.exit(1)
  482. item = gps_model.cfg_by_name(parts[0])
  483. if item:
  484. nvs.append(nv)
  485. else:
  486. sys.stderr.write('%s: ERROR: item %s unknown\n' %
  487. (PROG_NAME, parts[0]))
  488. sys.exit(1)
  489. gps_model.send_cfg_valset(nvs)
  490. exit_code = io_handle.read(gps_model.decode_msg, # decode function
  491. opts['input_wait']) # raw input
  492. if ((gps.VERB_RAW <= opts['verbosity']) and io_handle.out):
  493. # dump raw left overs
  494. print("Left over data:")
  495. print(io_handle.out)
  496. sys.stdout.flush()
  497. io_handle.ser.close()
  498. except KeyboardInterrupt:
  499. print('')
  500. exit_code = 1
  501. sys.exit(exit_code)
  502. # vim: set expandtab shiftwidth=4