gpssubframe.py.in 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402
  1. #!@PYSHEBANG@
  2. # -*- coding: utf-8 -*-
  3. # @GENERATED@
  4. # This file is Copyright 2020 by the GPSD project
  5. # SPDX-License-Identifier: BSD-2-clause
  6. # This code runs compatibly under Python 2 and 3.x for x >= 2.
  7. # Preserve this property!
  8. #
  9. """gpssubframe -- read JSON subframe messages from gpsd and decode them."""
  10. from __future__ import print_function
  11. import argparse
  12. import json # for json.dumps()
  13. import math
  14. import os.path # for os.path.isfile()
  15. import socket
  16. import sys
  17. import time # for time.time()
  18. # pylint wants local modules last
  19. try:
  20. import gps
  21. except ImportError as e:
  22. sys.stderr.write(
  23. "%s: can't load Python gps libraries -- check PYTHONPATH.\n" %
  24. (sys.argv[0]))
  25. sys.stderr.write("%s\n" % e)
  26. sys.exit(1)
  27. gps_version = '@VERSION@'
  28. if gps.__version__ != gps_version:
  29. sys.stderr.write("%s: ERROR: need gps module version %s, got %s\n" %
  30. (sys.argv[0], gps_version, gps.__version__))
  31. sys.exit(1)
  32. ura2ure = {0: "0.00 to 2.40",
  33. 1: "2.40 to 3.40",
  34. 2: "3.40 to 4.85",
  35. 3: "4.85 to 6.85",
  36. 4: "6.85 to 9.65",
  37. 5: "9.65 to 13.65",
  38. 6: "13.65 to 24.00",
  39. 7: "24.00 to 48.00",
  40. 8: "48.00 to 96.00",
  41. 9: "96.00 to 192.00",
  42. 10: "192.00 to 384.00",
  43. 11: "384.00 to 768.00",
  44. 12: "768.00 to 1536.00",
  45. 13: "1536.00 to 3072.00",
  46. 14: "3072.00 to 6144.00",
  47. 15: "6144.00 or worse",
  48. }
  49. health_str = {0: "All Signals OK",
  50. 1: "All Signals Weak",
  51. 2: "All Signals Dead",
  52. 3: "All Signals Have No Data Modulation",
  53. 4: "L1 P Signal Weak",
  54. 5: "L1 P Signal Dead",
  55. 6: "L1 P Signal Has No Data Modulation",
  56. 7: "L2 P Signal Weak",
  57. 8: "L2 P Signal Dead",
  58. 9: "L2 P Signal Has No Data Modulation",
  59. 10: "L1C Signal Weak",
  60. 11: "L1C Signal Dead",
  61. 12: "L1C Signal Has No Data Modulation",
  62. 13: "L2C Signal Weak",
  63. 14: "L2C Signal Dead",
  64. 15: "L2C Signal Has No Data Modulation",
  65. 16: "L1 & L2 P Signal Weak",
  66. 18: "L1 & L2 P Signal Dead",
  67. 19: "L1 & L2 P Signal Has No Data Modulation",
  68. 20: "L1 & L2C Signal Weak",
  69. 21: "L1 & L2C Signal Dead",
  70. 22: "L1 & L2C Signal Has No Data Modulation",
  71. 23: "L1 Signal Weak",
  72. 24: "L1 Signal Dead",
  73. 25: "L1 Signal Has No Data Modulation",
  74. 26: "L2 Signal Weak",
  75. 27: "L2 Signal Dead",
  76. 28: "L2 Signal Has No Data Modulation",
  77. 29: "SV Is Temporarily Out",
  78. 30: "SV Will Be Temporarily Out",
  79. 31: "Multiple Anomalies",
  80. }
  81. # subframe 4 page 25 sv config
  82. sv_conf = {
  83. 0: "Reserved",
  84. 1: "Block II/Block IIA/IIR SV",
  85. 2: "M-code, L2C, Block IIR-M SV",
  86. 3: "M-code, L2C, L5, Block IIF SV",
  87. 4: "M-code, L2C, L5, No SA, Block III SV",
  88. 5: "Reserved",
  89. 6: "Reserved",
  90. 7: "Reserved",
  91. }
  92. def kepler(e, M):
  93. """Keplers iteration to solve his equation."""
  94. # https://en.wikipedia.org/wiki/Kepler%27s_equation
  95. E = M # initial guess
  96. for i in range(1, 20):
  97. temp = E
  98. E = M + e * math.sin(E)
  99. # not sure how accurate we need to be
  100. # but we want 18 digits of lat/lon
  101. if 1e-18 > math.fabs(E - temp):
  102. break
  103. return E
  104. # another kepler algorithm here:
  105. # http://www.alpheratz.net/dynamics/twobody/KeplerIterations_summary.pdf
  106. # Online data check:
  107. # real time sat positions: https://in-the-sky.org/satmap_worldmap.php
  108. #
  109. # See also:
  110. # RTKLIB src/ephemeris.c
  111. # https://www.telesens.co/2017/07/17/calculating-position-from-raw-gps-data/#1b_Code_for_Calculating_Satellite_Position
  112. # gnss-sdr: src/core/system_parameters/gps_ephemeris.cc
  113. def SatPos(ephm, t):
  114. """Calculate GPS Satellite Position from Ephemeris/Almanac.
  115. Calculations from IS-GPS-200 table 20-IV
  116. return (x, y, z, delta_tsv) of satellite at gps_tow t
  117. """
  118. # Semi-major axis
  119. A = ephm['sqrtA'] ** 2 # meters
  120. # Computed mean motion
  121. n0 = math.sqrt(gps.WGS84GM / (A ** 3)) # rad/sec
  122. # t: GPS system time of week at time of transmission
  123. # tk: time from ephemeris reference epoch
  124. tk = t - ephm['toe'] # seconds
  125. # handle week rollover
  126. if 302400 < tk:
  127. tk -= 604800
  128. elif -302400 > tk:
  129. tk += 604800
  130. # corrected mean motion
  131. n = n0 + ephm['deltan'] # rad/sec
  132. # mean anomaly, rads
  133. Mk = ephm['M0'] + (n * tk)
  134. # solve for eccentric anomaly
  135. Ek = kepler(ephm['e'], Mk) # radians
  136. cosEk = math.cos(Ek)
  137. sinEk = math.sin(Ek)
  138. # true anomaly
  139. nuk = math.atan2(
  140. math.sqrt(1 - (ephm['e'] ** 2)) * sinEk /
  141. (1 - ephm['e'] * math.cos(Ek)),
  142. (cosEk - ephm['e']) / (1 - ephm['e'] * cosEk))
  143. # alternate true anomaly
  144. # close enough?
  145. # nuk = math.atan2(math.sqrt(1 - (ephm['e'] ** 2)) * sinEk,
  146. # (cosEk - ephm['e']))
  147. # Argument of Latitude
  148. Phik = nuk + ephm['omega']
  149. if 'Cus' in ephm:
  150. # 2nd harmonic corrections
  151. # Argument of Latitude Correction
  152. sin2Phik = math.sin(2 * Phik)
  153. cos2Phik = math.cos(2 * Phik)
  154. deltauk = (ephm['Cus'] * sin2Phik + ephm['Cuc'] * cos2Phik)
  155. # Radius Correction
  156. deltark = (ephm['Crs'] * sin2Phik + ephm['Crc'] * cos2Phik)
  157. # Inclination Correction
  158. deltaik = (ephm['Cis'] * sin2Phik + ephm['Cic'] * cos2Phik)
  159. else:
  160. # almanac does not have these corrections
  161. deltauk = 0
  162. deltark = 0
  163. deltaik = 0
  164. # Corrected Argument of Latitude
  165. uk = Phik + deltauk
  166. # Corrected Radius
  167. rk = A * (1 - ephm['e'] * cosEk) + deltark
  168. # Corrected Inclination Angle
  169. ik = ephm['i0'] + ephm['IDOT'] * tk + deltaik
  170. # Positions in orbital plane.
  171. xkprime = rk * math.cos(uk)
  172. ykprime = rk * math.sin(uk)
  173. # diff of sat and earth Omega dot
  174. delta_Omegad = ephm['Omegad'] - gps.WGS84AV
  175. # Corrected longitude of ascending node.
  176. Omegak = (ephm['Omega0'] + delta_Omegad * tk - gps.WGS84AV * ephm['toe'])
  177. # Earth-fixed coordinates.
  178. cosik = math.cos(ik)
  179. sinik = math.sin(ik)
  180. ykprimecosik = ykprime * cosik
  181. sinOmegak = math.sin(Omegak)
  182. cosOmegak = math.cos(Omegak)
  183. x = xkprime * cosOmegak - ykprimecosik * sinOmegak
  184. y = xkprime * sinOmegak + ykprimecosik * cosOmegak
  185. z = ykprime * sinik
  186. # sat velocity in ECEF coordinates, meters per second
  187. # FIXME: broken calculation
  188. # vx = (-delta_Omegad * (xkprime + ykprime * cosik) +
  189. # x * cosOmegak -
  190. # y * cosik * sinOmegak)
  191. # vy = (delta_Omegad * (xkprime * cosOmegak - ykprime * cosik * sinOmegak)
  192. # + x * sinOmegak + y * cosik * cosOmegak)
  193. # vz = y * sinik
  194. # Almanac does not have af2, toc, or ure
  195. # but it does have af0 and af1, so compute what we can
  196. # relativistic correction term, seconds
  197. deltatr = gps.GPS_F * ephm['e'] * ephm['sqrtA'] * sinEk
  198. # toff == SV PRN code phase offset, seconds
  199. # we want tgps, but our t (tsv) is close enough.
  200. toff = t - ephm['toc']
  201. # handle week rollover
  202. if 302400 < toff:
  203. toff -= 604800
  204. elif -302400 > toff:
  205. toff += 604800
  206. # IS-GPS-200, Section 20.3.3.3.3.1 SV Clock Correction
  207. delta_tsv = (ephm['af0'] + ephm['af1'] * toff +
  208. ephm['af2'] * toff ** 2 + deltatr)
  209. print(" deltatr %.9e delta_tsv %.9e toff %.3f" %
  210. (deltatr, delta_tsv, toff))
  211. if 'Tgd' in ephm:
  212. # we don't use Tgd because it is small, do not know the freuency.
  213. print(" Tgd %.9e" % ephm['Tgd'])
  214. if 'ura' in ephm:
  215. # ephemeris, not almanac
  216. print(" URE %s Health %s" %
  217. (ura2ure[ephm['ura']], health_str[ephm['hlth'] & 0x1f]))
  218. if 1 < options.debug:
  219. if 2 < options.debug:
  220. print(ephm)
  221. print(" t %.3f tk %.3f" % (t, tk))
  222. print(" A %s n0 %s n %s\n Mk %s Ek %s" % (A, n0, n, Mk, Ek))
  223. print(" nuk %.10g Phik %.10g" % (nuk, Phik))
  224. print(" deltauk %.10g deltark %.10g deltaik %.10g" %
  225. (deltauk, deltark, deltaik))
  226. print(" uk %.10g rk %.10g ik %.10g" % (uk, rk, ik))
  227. print(" xkprime %.10g ykprime %.10g Omegak %.10g" %
  228. (xkprime, ykprime, Omegak))
  229. # compute distance from earth's center, and orbital speed, as
  230. # simple cross-check
  231. # GPS orbit radius about 26,600 km
  232. dist = math.sqrt((x * x) + (y * y) + (z * z))
  233. print(" dist %13.3f" % dist)
  234. # FIXME: broken
  235. # if 1 < options.debug:
  236. # GPS orbit speed about 3.9 km/sec
  237. # speed = math.sqrt((vx * vx) + (vy * vy) + (vz * vz))
  238. # print("speed %13.3f" % speed)
  239. # print("vx %13.3f yv %13.3f zv %13.3f" % (vx, vy, vz))
  240. return (x, y, z, delta_tsv)
  241. def SatPosSum(eph, gps_tow):
  242. """Call SatPos() many times to compute and display more data.
  243. Compute Velocities. Use delta_tsv to correct pseudorange.
  244. """
  245. print(" Computing using gps_tow of reception %0.9f" % gps_tow)
  246. (x, y, z, delta_tsv) = SatPos(eph, gps_tow)
  247. print(" Sat: x %13.3f y %13.3f z %13.3f delta_tsv %g" %
  248. (x, y, z, delta_tsv))
  249. # Try ecef first, no trig. This agrees to the meter level
  250. # with the next range calculation that uses lat/Log/AltHAE.
  251. # This one likely better.
  252. if (('ecefx' in all_data['tpv'] and
  253. 'ecefy' in all_data['tpv'] and
  254. 'ecefz' in all_data['tpv'])):
  255. dx = x - all_data['tpv']['ecefx']
  256. dy = y - all_data['tpv']['ecefy']
  257. dz = z - all_data['tpv']['ecefz']
  258. rng = math.sqrt(dx ** 2 + dy ** 2 + dz ** 2)
  259. print(" Antenna position: x %.3f y %.3f z %.3f\n"
  260. " rng %.3f" %
  261. (all_data['tpv']['ecefx'], all_data['tpv']['ecefy'],
  262. all_data['tpv']['ecefz'], rng))
  263. else:
  264. rng = 0
  265. # lat/lon/alt
  266. (lat, lon, altHAE) = gps.ecef2lla(x, y, z)
  267. print(" Sat lat %.6f lon %.6f altHAE %.3f" % (lat, lon, altHAE))
  268. if 0 < rng:
  269. # compute sat position at time signal sent. That speed of light thing.
  270. delta_tC = rng / gps.CLIGHT
  271. delta_t = delta_tC
  272. # delta_tiono ionosphere corrections
  273. delta_t -= delta_tiono
  274. # delta_tsv from previous SatPos()
  275. delta_t -= delta_tsv
  276. new_t = gps_tow - delta_t
  277. # someday deal with clock errors, etc.
  278. print(" Computing using gps_tow of sending %.9f" % new_t)
  279. if 1 < options.debug:
  280. print(" delta_tC %.9f delta_tiono %.9f delta_tsv %.9f" %
  281. (delta_tC, delta_tiono, delta_tsv))
  282. (x2, y2, z2, delta_tsv2) = SatPos(eph, new_t)
  283. print(" Sat: x %13.3f y %13.3f z %13.3f delta_tsv2 %g" %
  284. (x2, y2, z2, delta_tsv2))
  285. # compute sat velocity by subtracting two postions.
  286. # because I can't get the direct method to work...
  287. vx = (x2 - x) / delta_t
  288. vy = (y2 - y) / delta_t
  289. vz = (z2 - z) / delta_t
  290. # GPS orbit speed about 3.9 km/sec
  291. speed = math.sqrt((vx * vx) + (vy * vy) + (vz * vz))
  292. print(" Sat: vx %13.3f yv %13.3f zv %13.3f speed %13.3f" %
  293. (vx, vy, vz, speed))
  294. # ax and el to sat, using trig
  295. if (('lat' in all_data['tpv'] and
  296. 'lon' in all_data['tpv'] and
  297. 'altHAE' in all_data['tpv'])):
  298. (E, N, U) = gps.ecef2enu(
  299. x, y, z, all_data['tpv']['lat'], all_data['tpv']['lon'],
  300. all_data['tpv']['altHAE'])
  301. (az, el, rng) = gps.enu2aer(E, N, U)
  302. print(" E %.3f N %.3f U %.3f\n"
  303. " rng %.3f az %.3f el %.3f" %
  304. (E, N, U, rng, az, el))
  305. (E2, N2, U2) = gps.ecef2enu(
  306. x2, y2, z2, all_data['tpv']['lat'], all_data['tpv']['lon'],
  307. all_data['tpv']['altHAE'])
  308. (az2, el2, rng2) = gps.enu2aer(E2, N2, U2)
  309. range_speed = (rng2 - rng) / delta_t
  310. # doppler is postive when sat egetting nearer
  311. dopplerl1 = -2 * range_speed / gps.GPS_L1_WL
  312. print(" range speed %.3f L1 doppler %.3f" %
  313. (range_speed, dopplerl1))
  314. # pseudorange is range + C * clock_errors + C * path_delays
  315. # current GPS Ephemeris here, url split: https://cddis.nasa.gov/
  316. # Data_and_Derived_Products/GNSS/broadcast_ephemeris_data.html
  317. # all the data is one big JSON for easy save/load
  318. all_data = {
  319. # current almanac here: https://www.navcen.uscg.gov/?pageName=gpsAlmanacs
  320. 'almanac': {},
  321. 'ephemeris': {}, # new style combined ephemeris
  322. 'ephemeris1': {},
  323. 'ephemeris2': {},
  324. 'ephemeris3': {},
  325. # subframe 4 page 25
  326. 'health': {},
  327. # subframe 5 page 25
  328. 'health2': {},
  329. # ionosphere, subframe 4, page 18, data id 56
  330. 'ionosphere': {},
  331. # raw for comparison
  332. 'raw': None,
  333. # for current time and position
  334. 'tpv': None,
  335. }
  336. ephem1_fields = {
  337. 1: ('ura', 'URA Index'),
  338. 2: ('WN', 'Data Sequence Propagation Week Number'),
  339. 3: ('L2P', 'L2 P data flag'),
  340. 4: ('hlth', 'SV health'),
  341. 5: ('Tgd', '(s) Group Delay Differential'),
  342. 6: ('IODC', 'Issue of Data, Clock'),
  343. 7: ('toc', '(s) Time of Clock'),
  344. 8: ('L2', 'Code on L2'),
  345. 9: ('af0', '(sc) SV Clock Bias Correction Coefficient'),
  346. 10: ('af1', '(s) SV Clock Drift Correction Coefficient'),
  347. 11: ('af2', '(s/s) Drift Rate Correction Coefficient'),
  348. }
  349. ephem2_fields = {
  350. 1: ('IODE', 'Issue of Data, Ephemeris'),
  351. 2: ('M0', '(sc) Mean Anomaly at Reference Time'),
  352. 3: ('deltan', '(sc/s) Mean Motion Difference from Computed Value'),
  353. 4: ('e', 'Eccentricity '),
  354. 5: ('sqrtA', '(sqrt(m))Square Root of the Semi-Major Axis'),
  355. 6: ('FIT', 'Fit Interval Flag'),
  356. 7: ('AODO', '(s) Age of Data Offset'),
  357. 8: ('Crs', '(m) Sine Correction Amplitude Term to Orbit Radius'),
  358. 9: ('Cus', '(rad) Cosine Correction Amplitude Term to Orbit Radius'),
  359. 10: ('Cuc', '(rad) Sine Harmonic Correction Term to Arg of Lat'),
  360. 11: ('toe', '(s) Reference Time Ephemeris')
  361. }
  362. ephem3_fields = {
  363. 1: ('IODE', 'Issue of Data, Ephemeris'),
  364. 2: ('Crc', '(m) Cosine Harmonic Correction Amplitude Orbit Radius'),
  365. 3: ('Cic',
  366. '(rad) Cosine Harmonic Correction Amplitude Angle of Inclination'),
  367. 4: ('Cis',
  368. '(rad) Sine Harmonic Correction Amplitude Angle of Inclination'),
  369. 5: ('Omega0',
  370. '(sc) Longitude of Ascending Node of Orbit Plane Week beginning'),
  371. 6: ('i0', '(sc) Inclination Angle at Reference Time'),
  372. 7: ('omega', '(sc) Argument of Perigee'),
  373. 8: ('Omegad', '(sc/s) Rate of Right Ascension'),
  374. 9: ('IDOT', '(sc/s) Rate of Inclination Angle'),
  375. }
  376. ephem_fields = {
  377. 'af0': ('(s) SV Clock Bias Correction Coefficient'),
  378. 'af1': ('(s/s) SV Clock Drift Correction Coefficient'),
  379. 'af2': ('(s/s^2) Drift Rate Correction Coefficient'),
  380. 'alpha0': ('(s) Ionospheric delay model'),
  381. 'alpha1': ('(s/sc) Ionospheric delay model'),
  382. 'alpha2': ('(s/sc2) Ionospheric delay model'),
  383. 'alpha3': ('(s/sc3) Ionospheric delay model'),
  384. 'AODC': ('(s) Age of Data Clock'),
  385. 'AODE': ('(s) Age of Data Ephemeris'),
  386. 'AODO': ('(s) Age of Data Offset'),
  387. 'beta0': ('(s) Ionospheric delay model'),
  388. 'beta1': ('(s/sc) Ionospheric delay model'),
  389. 'beta2': ('(s/sc2) Ionospheric delay model'),
  390. 'beta3': ('(s/sc3) Ionospheric delay model'),
  391. 'Cic': ('(rad) Cosine Harmonic Correction Amplitude Angle of Inclination'),
  392. 'Cis': ('(rad) Sine Harmonic Correction Amplitude Angle of Inclination'),
  393. 'Crc': ('(m) Cosine Harmonic Correction Amplitude Orbit Radius'),
  394. 'Crs': ('(m) Sine Correction Amplitude Term to Orbit Radius'),
  395. 'Cuc': ('(rad) Sine Harmonic Correction Term to Arg of Lat'),
  396. 'Cus': ('(rad) Cosine Correction Amplitude Term to Orbit Radius'),
  397. 'deltai': ('(sc) Correction to Inclination Angle'),
  398. 'deltan': ('(sc/s) Mean Motion Difference from Computed Value'),
  399. 'IODA': ('Issue of Data, Almanac'),
  400. 'e': ('Eccentricity '),
  401. 'E1BHS': ('E1B Health'),
  402. 'E5bHS': ('E5B Health'),
  403. 'hlth': ('SV health'),
  404. 'IDOT': ('(sc/s) Rate of Inclination Angle'),
  405. 'IODA': ('Issue of Data, Almanac'),
  406. 'IODC': ('Issue of Data, Clock'),
  407. 'IODE': ('Issue of Data, Ephemeris'),
  408. 'i0': ('(sc) Inclination Angle at Reference Time'),
  409. 'L2': ('Code on L2'),
  410. 'L2P': ('L2 P data flag'),
  411. 'M0': ('(sc) Mean Anomaly at Reference Time'),
  412. 'Omega0': ('(sc) Longitude Ascending Node of Orbit Plane Week t0'),
  413. 'Omegad': ('(sc/s) Rate of Right Ascension'),
  414. 'omega': ('(sc) Argument of Perigee'),
  415. 'sqrtA': ('(sqrt(m))Square Root of the Semi-Major Axis'),
  416. 'SISAa': ('Signal-In-Space Accuracy (E1,E5a)'),
  417. 'SISAb': ('Signal-In-Space Accuracy (E1,E5b)'),
  418. 'svh': ('Health'),
  419. 'Tgd': ('(s) Group Delay Differential'),
  420. 'TGD1': ('(ns) Group Delay Differential'),
  421. 'TGD2': ('(ns) Group Delay Differential'),
  422. 'toa': ('(s) Time of Almanac'),
  423. 'toc': ('(s) Time of Clock'),
  424. 'toe': ('(s) Reference Time Ephemeris'),
  425. 'toeLSB': ('(s) Reference Time Ephemeris LSB'),
  426. 'toeMSB': ('(s) Reference Time Ephemeris MSB'),
  427. 'URAI': ('URA Index'),
  428. 'WN': ('Data Sequence Propagation Week Number'),
  429. }
  430. almanac_fields = {
  431. # 'ID': same as SV
  432. 'af0': ('(s) SV Clock Bias Correction Coefficient'),
  433. 'af1': ('(s/s) SV Clock Drift Correction Coefficient'),
  434. 'deltai': ('(sc) Inclination offset from 0.3 semicircles (= 54 deg)'),
  435. 'E1BHS': ('E1B health'),
  436. 'E5bHS': ('E5b health'),
  437. 'e': ('Eccentricity '),
  438. 'Health': ('SV health'),
  439. 'i0': ('(sc) Inclination, computed from deltai'),
  440. 'IOD': ('IOD health'),
  441. 'M0': ('(sc) Mean Anomaly at Reference Time'),
  442. 'Omega0': ('(sc) Longitude of Ascending Node of Orbit Plane Week t0'),
  443. 'Omegad': ('(sc/s) Rate of Right Ascension'),
  444. 'omega': ('(sc) Argument of Perigee'),
  445. 'sqrtA': ('(m-2)Square Root of the Semi-Major Axis'),
  446. 'toa': ('(s) Almanac Reference Time'),
  447. 'WN': ('(wk) Almanac Reference Week'),
  448. }
  449. # see IS-GPS-200 Section 20.3.3.5.2.4 Coordinated Universal Time (UTC).
  450. ionosphere_fields = {
  451. 1: ('A0', '(s) constant term of polynomial'),
  452. 2: ('A1', '(s) first order term of polynomial'),
  453. 3: ('ls', '(s) delta time due to leap seconds'),
  454. 4: ('tot', '(s) reference time for UTC data'),
  455. 5: ('WNt', '(wk) UTC reference week number'),
  456. 6: ('WNlsf', '(wk) Week Number of future Leap Second'),
  457. 7: ('DN', '(dy) Day Number of future Leap Second'),
  458. 8: ('lsf', '(s) future Leap Second'),
  459. 9: ('a0', '(s) constant term amplitude of vertical delay'),
  460. 10: ('a1', '(s/sc) first order term amplitude of vertical delay'),
  461. 11: ('a2', '(s/sc^2) second order term amplitude of vertical delay'),
  462. 12: ('a3', '(s/sc^3) third order term amplitude of vertical delay'),
  463. 13: ('b0', '(s) constant term period of the model'),
  464. 14: ('b1', '(s/sc) first order term period of the model'),
  465. 15: ('b2', '(s/sc^2) second order term period of the model'),
  466. 16: ('b3', '(s/sc^3) third order term period of the model'),
  467. }
  468. def _print_msg(ephem, fields):
  469. """Print Subframe Data."""
  470. for index in sorted(fields.keys()):
  471. fld = fields[index]
  472. if fld[0] in ephem:
  473. print("%10s %s" % (fld[0], ephem[fld[0]]))
  474. else:
  475. # missing field
  476. print("%10s MISSING" % (fld[0]))
  477. if options.desc:
  478. print(" %-48s " % (fld[1]))
  479. def _print_msg1(ephem, fields):
  480. """Print Subframe Data. New style"""
  481. for key in fields.keys():
  482. if key in ephem:
  483. print("%10s %s" % (key, ephem[key]))
  484. elif options.desc or gps.VERB_INFO <= options.debug:
  485. # missing field
  486. print("%10s MISSING" % (key))
  487. if options.desc:
  488. print(" %-48s " % (fields[key]))
  489. def test():
  490. # FIXME: Broken
  491. # SatPos() tests
  492. # lat 0.000000 lon 0.000000 altHAE 20175272.000
  493. ephm = {
  494. 'af0': 0,
  495. 'af1': 0,
  496. 'af2': 0,
  497. 'AODO': 0,
  498. 'Cic': 0,
  499. 'Cis': 0,
  500. 'Crc': 0,
  501. 'Crs': 0,
  502. 'Cuc': 0,
  503. 'Cus': 0,
  504. 'deltan': 0,
  505. 'e': 0, # nominal 0
  506. 'FIT': 0,
  507. 'hlth': 0,
  508. 'i0': 0, # nominal 55.0 degrees +/- 3
  509. 'IDOT': 0,
  510. 'IODC': 53,
  511. 'IODE': 53,
  512. 'L2': 1,
  513. 'L2P': 0,
  514. 'M0': 0,
  515. 'omega': 0, # nominal 0, +/1 180 degrees
  516. 'Omega0': 0,
  517. 'Omegad': 0,
  518. 'sqrtA': 5153, # nominal 5153.542538875565
  519. 'Tgd': 0,
  520. 'toc': 0,
  521. 'toe': 0,
  522. 'ura': 0,
  523. 'WN': 75}
  524. options.debug = 2
  525. print("\nTest Ephemeris:")
  526. SatPos(ephm, 0)
  527. # move 90 degrees
  528. print("\nTest omega 90:")
  529. ephm['omega'] = gps.GPS_PI / 2
  530. SatPos(ephm, 0)
  531. # move 180 degrees
  532. print("\nTest omega 180:")
  533. ephm['omega'] = gps.GPS_PI
  534. SatPos(ephm, 0)
  535. # move 270 degrees
  536. print("\nTest omega 270:")
  537. ephm['omega'] = gps.GPS_PI * 1.5
  538. SatPos(ephm, 0)
  539. # move omega 0, Omega0 90
  540. print("\nTest Omega 90:")
  541. ephm['omega'] = 0
  542. ephm['Omega0'] = gps.GPS_PI / 2
  543. SatPos(ephm, 0)
  544. # move omega 0, Omega0 180
  545. print("\nTest Omega 180:")
  546. ephm['omega'] = 0
  547. ephm['Omega0'] = gps.GPS_PI
  548. SatPos(ephm, 0)
  549. # move omega 90, Omega0 90
  550. print("\nTest omega 90 Omega0 90:")
  551. ephm['omega'] = gps.GPS_PI / 2
  552. ephm['Omega0'] = gps.GPS_PI / 2
  553. SatPos(ephm, 0)
  554. # move omega 0, Omega0 0, i0 90
  555. print("\nTest omega 0 Omega0 0 i0 90:")
  556. ephm['omega'] = 0
  557. ephm['Omega0'] = 0
  558. ephm['i0'] = gps.GPS_PI / 2
  559. SatPos(ephm, 0)
  560. # move omega 45, Omega0 0, i0 90
  561. print("\nTest omega 45 Omega0 0 i0 90:")
  562. ephm['omega'] = gps.GPS_PI / 4
  563. ephm['Omega0'] = 0
  564. ephm['i0'] = gps.GPS_PI / 2
  565. SatPos(ephm, 0)
  566. # move omega 45, Omega0 45, i0 90
  567. print("\nTest omega 45 Omega0 45 i0 90:")
  568. ephm['omega'] = gps.GPS_PI / 4
  569. ephm['Omega0'] = gps.GPS_PI / 4
  570. ephm['i0'] = gps.GPS_PI / 2
  571. SatPos(ephm, 0)
  572. # move omega 45, Omega0 0, i0 45
  573. print("\nTest omega 45 Omega0 0 i0 45:")
  574. ephm['omega'] = gps.GPS_PI / 4
  575. ephm['Omega0'] = 0
  576. ephm['i0'] = gps.GPS_PI / 4
  577. SatPos(ephm, 0)
  578. # move omega 90, Omega0 0, i0 90
  579. # altitude blows up.
  580. print("\nTest omega 0 Omega0 0 i0 0:")
  581. ephm['omega'] = gps.GPS_PI / 2
  582. ephm['Omega0'] = 0
  583. ephm['i0'] = gps.GPS_PI / 2
  584. SatPos(ephm, 0)
  585. # omega Omega0, i0 seem OK.
  586. # move omega 0, Omega0 0, i0 0
  587. # t = one quarter sideral day
  588. siderial_day = 86164.0905
  589. print("\nTest omega 0 Omega0 0 i0 0:")
  590. ephm['omega'] = 0
  591. ephm['Omega0'] = 0
  592. ephm['i0'] = 0
  593. SatPos(ephm, siderial_day / 4)
  594. # move omega 0, Omega0 0, i0 0
  595. # t = one half sideral day
  596. # sat has done one orbit, earth 1/2 orbit
  597. print("\nTest omega 0 Omega0 0 i0 0:")
  598. ephm['omega'] = 0
  599. ephm['Omega0'] = 0
  600. ephm['i0'] = 0
  601. SatPos(ephm, siderial_day / 2)
  602. return 0
  603. description = 'Convert one gpsd JSON message class to csv format.'
  604. usage = '%(prog)s [OPTIONS] [host[:port[:device]]]'
  605. epilog = ('BSD terms apply: see the file COPYING in the distribution root'
  606. ' for details.')
  607. parser = argparse.ArgumentParser(
  608. description=description,
  609. epilog=epilog,
  610. formatter_class=argparse.RawDescriptionHelpFormatter,
  611. usage=usage)
  612. parser.add_argument(
  613. '-?',
  614. action="help",
  615. help='show this help message and exit'
  616. )
  617. # WIP
  618. parser.add_argument(
  619. '-c',
  620. '--continuous',
  621. dest='continuous',
  622. default=False,
  623. action="store_true",
  624. help=('INOP: Continuously calculate a SatPos() after -x SECONDS '
  625. '[Default %(default)s]')
  626. )
  627. parser.add_argument(
  628. '-D',
  629. '--debug',
  630. dest='debug',
  631. default=0,
  632. type=int,
  633. help='Set level of debug. Must be integer. [Default %(default)s]'
  634. )
  635. parser.add_argument(
  636. '--desc',
  637. dest='desc',
  638. default=False,
  639. action="store_true",
  640. help='Print long descriptions [Default %(default)s]'
  641. )
  642. parser.add_argument(
  643. '--device',
  644. dest='device',
  645. default='',
  646. help='The device to connect. [Default %(default)s]'
  647. )
  648. parser.add_argument(
  649. '-f', '--file',
  650. dest='input_file_name',
  651. default=None,
  652. metavar='FILE',
  653. help='Read gpsd JSON from FILE instead of a gpsd instance.',
  654. )
  655. parser.add_argument(
  656. '--host',
  657. dest='host',
  658. default='localhost',
  659. help='The host to connect. [Default %(default)s]'
  660. )
  661. parser.add_argument(
  662. '-n',
  663. '--count',
  664. dest='count',
  665. default=0,
  666. type=int,
  667. help='Count of messages to parse. 0 to disable. [Default %(default)s]'
  668. )
  669. parser.add_argument(
  670. '--load',
  671. dest='loadfile',
  672. default=None,
  673. type=str,
  674. help=('Load saved JSON Subframe data trom loadfile. '
  675. ' [Default %(default)s]')
  676. )
  677. parser.add_argument(
  678. '--port',
  679. dest='port',
  680. default=gps.GPSD_PORT,
  681. help='The port to connect. [Default %(default)s]'
  682. )
  683. parser.add_argument(
  684. '--progress',
  685. dest='progress',
  686. default=False,
  687. action="store_true",
  688. help='Print progress reports [Default %(default)s]'
  689. )
  690. parser.add_argument(
  691. '--satpos',
  692. dest='satpos',
  693. default=False,
  694. action="store_true",
  695. help='Compute Satellite Positions [Default %(default)s]'
  696. )
  697. parser.add_argument(
  698. '--save',
  699. dest='savefile',
  700. default=None,
  701. type=str,
  702. help=('Save decoded Subframe data in savefile as JSON. '
  703. ' [Default %(default)s]')
  704. )
  705. parser.add_argument(
  706. '--test',
  707. action="store_true",
  708. dest='test',
  709. default=False,
  710. help='Run tests of the algorithms [Default %(default)s]'
  711. )
  712. parser.add_argument(
  713. '--time',
  714. dest='timeat',
  715. default=False,
  716. metavar='TIME',
  717. type=float,
  718. help='Compute sat positions at TIME seconds, [Default: current time ]'
  719. )
  720. parser.add_argument(
  721. '-V', '--version',
  722. action='version',
  723. version="%(prog)s: Version " + gps_version + "\n",
  724. help='Output version to stderr, then exit'
  725. )
  726. parser.add_argument(
  727. '-x',
  728. '--seconds',
  729. dest='seconds',
  730. default=16,
  731. type=int,
  732. help='Seconds of messages to parse. 0 to disable. [Default %(default)s]'
  733. )
  734. parser.add_argument(
  735. 'target',
  736. nargs='?',
  737. help='[host[:port[:device]]]'
  738. )
  739. options = parser.parse_args()
  740. if options.loadfile:
  741. with open(options.loadfile, 'r') as load_file:
  742. # the JSON format will change, do not depend on it.
  743. all_data = json.load(load_file)
  744. # the options host, port, device are set by the defaults
  745. if options.target:
  746. # override host, port and device with target
  747. arg = options.target.split(':')
  748. len_arg = len(arg)
  749. if len_arg == 1:
  750. (options.host,) = arg
  751. elif len_arg == 2:
  752. (options.host, options.port) = arg
  753. elif len_arg == 3:
  754. (options.host, options.port, options.device) = arg
  755. else:
  756. parser.print_help()
  757. sys.exit(0)
  758. if options.test:
  759. ret = test()
  760. sys.exit(ret)
  761. if options.input_file_name and not os.path.isfile(options.input_file_name):
  762. sys.stderr.write("gpssubframe: File does not exist: %s\n" %
  763. options.input_file_name)
  764. sys.exit(1)
  765. if not options.port:
  766. options.port = 2947
  767. try:
  768. session = gps.gps(device=options.device,
  769. input_file_name=options.input_file_name,
  770. host=options.host,
  771. port=options.port,
  772. verbose=options.debug)
  773. except socket.error:
  774. sys.stderr.write("gpssubframe: Could not connect to gpsd daemon\n")
  775. sys.exit(1)
  776. session.stream(gps.WATCH_ENABLE | gps.WATCH_SCALED, devpath=options.device)
  777. # save subframe 1 WN
  778. subframe1_WN = -1
  779. count = 0
  780. if 0 < options.seconds:
  781. end_seconds = time.time() + options.seconds
  782. else:
  783. end_seconds = 0
  784. if options.continuous:
  785. # force --satpos
  786. options.satpos = True
  787. try:
  788. while True:
  789. try:
  790. msg = session.next()
  791. except StopIteration:
  792. # most likely end of file, maybe lost connection to gpsd
  793. break
  794. if not msg or 'class' not in msg:
  795. # ??
  796. continue
  797. if 'SUBFRAME' == msg['class']:
  798. if gps.GNSSID_GPS == msg['gnssId']:
  799. gnss = 'GP'
  800. elif gps.GNSSID_SBAS == msg['gnssId']:
  801. gnss = 'SB'
  802. elif gps.GNSSID_GAL == msg['gnssId']:
  803. gnss = 'GA'
  804. if 'ALMANAC' in msg:
  805. # hack to get the target sv
  806. msg['dataid'] = msg['ALMANAC']['sv']
  807. elif gps.GNSSID_BD == msg['gnssId']:
  808. gnss = 'BD'
  809. if 'ALMANAC' in msg:
  810. # hack to get the target sv
  811. msg['dataid'] = msg['ALMANAC']['sv']
  812. elif gps.GNSSID_IMES == msg['gnssId']:
  813. gnss = 'IM'
  814. elif gps.GNSSID_QZSS == msg['gnssId']:
  815. gnss = 'QZ'
  816. elif gps.GNSSID_GLO == msg['gnssId']:
  817. gnss = 'GL'
  818. elif gps.GNSSID_IRNSS == msg['gnssId']:
  819. gnss = 'IR'
  820. else:
  821. gnss = 'UN'
  822. # use %02d so it sorts nicely.
  823. if 'dataid' in msg:
  824. # data id in subframes 4 and 5
  825. if 0 == msg['dataid']:
  826. # dummy SV
  827. continue
  828. idx = "%2s%02d" % (gnss, msg['dataid'])
  829. elif 'tSV' in msg:
  830. # tSV in all subframes
  831. idx = "%2s%02d" % (gnss, msg['tSV'])
  832. else:
  833. # huh?
  834. idx = "X"
  835. if 1 < options.debug:
  836. if 'TOW17' in msg:
  837. if 0 <= subframe1_WN:
  838. s = " WN %d" % subframe1_WN
  839. else:
  840. s = ''
  841. print("Subframe %d idx %s TOW17 %s%s" %
  842. (msg["frame"], idx, msg['TOW17'], s))
  843. elif 'SOW' in msg:
  844. if 'WN' in msg:
  845. s = " WN %d" % msg['WN']
  846. else:
  847. s = ''
  848. print("Subframe %d idx %s SOW %s%s" %
  849. (msg["frame"], idx, msg['SOW'], s))
  850. elif 'TOW' in msg:
  851. if 'WN' in msg:
  852. s = " WN %d" % msg['WN']
  853. else:
  854. s = ''
  855. print("Subframe %d idx %s TOW %s%s" %
  856. (msg["frame"], idx, msg['TOW'], s))
  857. else:
  858. # no time
  859. print("Subframe %d idx %s" %
  860. (msg["frame"], idx))
  861. if gnss in ['BD', 'GA']:
  862. if 'ALMANAC' in msg:
  863. # new style almanac
  864. # save, or merge, it if???
  865. s = ''
  866. if idx not in all_data['almanac']:
  867. if options.progress:
  868. if 'IODA' in msg['ALMANAC']:
  869. s = " IODA %d" % msg['ALMANAC']['IODA']
  870. if 'toa' in msg['ALMANAC']:
  871. s += " toa %d" % msg['ALMANAC']['toa']
  872. print("New Almanac %s frame %d%s" %
  873. (idx, msg['frame'], s))
  874. all_data['almanac'][idx] = msg['ALMANAC']
  875. elif ('IODA' in msg['ALMANAC'] and
  876. ('IODA' not in all_data['almanac'][idx] or
  877. (all_data['almanac'][idx]['IODA'] <
  878. msg['ALMANAC']['IODA']))):
  879. # FIXME: handle rollover
  880. if options.progress:
  881. if 'IODA' in msg['ALMANAC']:
  882. s = " IODA %d" % msg['ALMANAC']['IODA']
  883. if 'toa' in msg['ALMANAC']:
  884. s += " toa %d" % msg['ALMANAC']['toa']
  885. print("Merged Almanac %s frame %d%s" %
  886. (idx, msg['frame'], s))
  887. all_data['almanac'][idx].update(msg['ALMANAC'])
  888. if 'ALMANAC1' in msg:
  889. # weird Galileo almanac fragment
  890. idx = "%2s%02d" % (gnss, msg['ALMANAC1']['sv'])
  891. # save it if:
  892. # not dummy (data id != 0)
  893. # don't already have it
  894. # newer than what we have
  895. s = ''
  896. if ((idx not in all_data['almanac'] or
  897. (all_data['almanac'][idx]['IODA'] <
  898. msg['ALMANAC1']['IODA']))):
  899. # FIXME: handle rollover
  900. if options.progress:
  901. if 'toa' in msg['ALMANAC']:
  902. s = " toa %d" % msg['ALMANAC']['toa']
  903. print("New Almanac1 %s: IODA %d%s" %
  904. (idx, msg['ALMANAC1']['IODA'], s))
  905. all_data['almanac'][idx] = msg['ALMANAC1']
  906. elif (all_data['almanac'][idx]['IODA'] ==
  907. msg['ALMANAC1']['IODA']):
  908. # maybe other half, merge in
  909. all_data['almanac'][idx].update(msg['ALMANAC1'])
  910. if options.progress:
  911. if 'toa' in msg['ALMANAC']:
  912. s = " toa %d" % msg['ALMANAC']['toa']
  913. print("Merged Almanac1 2/2 %s: IODA %d%s" %
  914. (idx, msg['ALMANAC1']['IODA'], s))
  915. if 'EPHEMERIS' in msg:
  916. # new style ephemeris
  917. if idx not in all_data['ephemeris']:
  918. # new
  919. all_data['ephemeris'][idx] = msg['EPHEMERIS']
  920. else:
  921. # merge in
  922. all_data['ephemeris'][idx].update(msg['EPHEMERIS'])
  923. if (('toeMSB' in all_data['ephemeris'][idx] and
  924. 'toeLSB' in all_data['ephemeris'][idx])):
  925. # fix BDS toa mess
  926. all_data['ephemeris'][idx]['toe'] = (
  927. all_data['ephemeris'][idx]['toeMSB'] +
  928. all_data['ephemeris'][idx]['toeLSB'])
  929. if options.progress:
  930. print("Received Ephemeris %s frame %d" %
  931. (idx, msg['frame']))
  932. elif 'GP' == gnss and 1 == msg['frame']:
  933. if 'WN' in msg['EPHEM1']:
  934. # save WN for later use
  935. subframe1_WN = msg['EPHEM1']['WN']
  936. if ((idx not in all_data['ephemeris1'] or
  937. msg['EPHEM1']['IODC'] !=
  938. all_data['ephemeris1'][idx]['IODC'])):
  939. if options.progress:
  940. print("Received Ephemeris1 %5s, IODC %3d" %
  941. (idx, msg['EPHEM1']['IODC']))
  942. if 1 < math.fabs(msg['EPHEM1']['af0']):
  943. print("ERROR: af0 %s out of range. Unscaled data?" %
  944. msg['EPHEM1']['af0'])
  945. sys.exit(1)
  946. all_data['ephemeris1'][idx] = msg['EPHEM1']
  947. elif 'GP' == gnss and 2 == msg['frame']:
  948. if ((idx not in all_data['ephemeris2'] or
  949. msg['EPHEM2']['IODE'] !=
  950. all_data['ephemeris2'][idx]['IODE'])):
  951. if options.progress:
  952. print("Received Ephemeris2 %5s, IODE %3d" %
  953. (idx, msg['EPHEM2']['IODE']))
  954. all_data['ephemeris2'][idx] = msg['EPHEM2']
  955. elif 'GP' == gnss and 3 == msg['frame']:
  956. if ((idx not in all_data['ephemeris3'] or
  957. msg['EPHEM3']['IODE'] !=
  958. all_data['ephemeris3'][idx]['IODE'])):
  959. if options.progress:
  960. print("Received Ephemeris3 %5s, IODE %3d" %
  961. (idx, msg['EPHEM3']['IODE']))
  962. all_data['ephemeris3'][idx] = msg['EPHEM3']
  963. elif 'GP' == gnss and 4 <= msg['frame'] <= 5:
  964. if 'ALMANAC' in msg:
  965. # save it if:
  966. # not dummy (data id != 0)
  967. # don't already have it
  968. # newer than what we have
  969. if ((idx not in all_data['almanac'] or
  970. all_data['almanac'][idx]['toa'] <
  971. msg['ALMANAC']['toa'])):
  972. # FIXME: handle week rollover
  973. if options.progress:
  974. print("Received Almanac dataid %2d toa %d" %
  975. (msg['dataid'], msg['ALMANAC']['toa']))
  976. all_data['almanac'][idx] = msg['ALMANAC']
  977. elif 'IONO' in msg:
  978. if ((not all_data['ionosphere'] or
  979. msg['IONO']['tot'] < all_data['ionosphere']['tot'])):
  980. # FIXME handle WNt rollover
  981. # new data set
  982. if options.progress:
  983. print("Received Subframe 4 dataid 56 "
  984. "(Ionosphere and UTC) tot %d WNt %d" %
  985. (msg['IONO']['tot'], msg['IONO']['WNt']))
  986. all_data['ionosphere'] = msg['IONO']
  987. elif 'HEALTH' in msg:
  988. if options.progress:
  989. print("Received Subframe 4 dataid 63 "
  990. "(A-S, Configurations)")
  991. all_data['health'] = msg['HEALTH']
  992. elif 'HEALTH2' in msg:
  993. if ((not all_data['health2'] or
  994. msg['HEALTH2']['toa'] < all_data['health2']['toa'])):
  995. # FIXME handle WNa rollover
  996. # new data set
  997. if options.progress:
  998. print("Received Subframe 5 dataid 51 "
  999. "(SV Health) toa %s WNa %s" %
  1000. (msg['HEALTH2']['toa'],
  1001. msg['HEALTH2']['WNa']))
  1002. all_data['health2'] = msg['HEALTH2']
  1003. # else nothing to do
  1004. # else, bad packet...
  1005. elif 'RAW' == msg['class']:
  1006. # message is a "dictwrapper" not a dictionary
  1007. # Python does not document what a "dictwarapper" is.
  1008. # we just keep the last one, for comparison purposes
  1009. all_data['raw'] = dict(msg)
  1010. elif 'TPV' == msg['class']:
  1011. # message is a "dictwrapper" not a dictionary
  1012. # Python does not document what a "dictwarapper" is.
  1013. # we just keep the last one, for current PVT.
  1014. all_data['tpv'] = dict(msg)
  1015. if 0 < options.count:
  1016. count += 1
  1017. if count >= options.count:
  1018. break
  1019. if 0 < options.seconds:
  1020. if time.time() > end_seconds:
  1021. break
  1022. except KeyboardInterrupt:
  1023. # caught control-C
  1024. print()
  1025. sys.exit(1)
  1026. all_sv = (list(all_data['ephemeris'].keys()) +
  1027. list(all_data['ephemeris1'].keys()) +
  1028. list(all_data['ephemeris2'].keys()) +
  1029. list(all_data['ephemeris3'].keys()) +
  1030. list(all_data['almanac'].keys()))
  1031. all_sv = sorted(set(all_sv))
  1032. for sv in all_sv:
  1033. print("\n%s" % sv)
  1034. if 'GP' == sv[0:2]:
  1035. print(" Subframe 1, Clock Data")
  1036. if sv in all_data['ephemeris1']:
  1037. _print_msg(all_data['ephemeris1'][sv], ephem1_fields)
  1038. else:
  1039. print(" Missing")
  1040. print(" Subframe 2, Orbit Data")
  1041. if sv in all_data['ephemeris2']:
  1042. _print_msg(all_data['ephemeris2'][sv], ephem2_fields)
  1043. else:
  1044. print(" Missing")
  1045. print(" Subframe 3, Orbit Data")
  1046. if sv in all_data['ephemeris3']:
  1047. _print_msg(all_data['ephemeris3'][sv], ephem3_fields)
  1048. else:
  1049. print(" Missing")
  1050. print(" Subframe 4 page 25 %s" % sv)
  1051. svid = "SV%d" % int(sv[2:])
  1052. if all_data['health'] and svid in all_data['health']:
  1053. if 0x3f == all_data['health'][svid]:
  1054. print(" Flags %2d (n/a)" % all_data['health'][svid])
  1055. else:
  1056. print(" Flags %2d (A/S %3s Config %s)" %
  1057. (all_data['health'][svid],
  1058. 'Yes' if (all_data['health'][svid] & 8) else 'No',
  1059. sv_conf[all_data['health'][svid] & 0x7]))
  1060. else:
  1061. print(" Missing")
  1062. else:
  1063. # new style combined ephemeris
  1064. print(" Ephemeris")
  1065. if sv in all_data['ephemeris']:
  1066. _print_msg1(all_data['ephemeris'][sv], ephem_fields)
  1067. else:
  1068. print(" Missing")
  1069. print(" Almanac")
  1070. if sv in all_data['almanac']:
  1071. _print_msg1(all_data['almanac'][sv], almanac_fields)
  1072. else:
  1073. print(" Missing")
  1074. gps_time = None
  1075. gps_week = None
  1076. gps_tow = None
  1077. if all_data['tpv'] and 'time' in all_data['tpv']:
  1078. if 'leapseconds' in all_data['tpv']:
  1079. leapseconds = all_data['tpv']['leapseconds']
  1080. print("\nTPV leapseconds: %s" % all_data['tpv']['leapseconds'])
  1081. else:
  1082. leapseconds = 18
  1083. print("\nTPV leapseconds: Unknown, assuming 18")
  1084. # convert to UNIX time
  1085. unix_time = gps.isotime(all_data['tpv']['time'])
  1086. (gps_time, gps_week, gps_tow) = gps.posix2gps(unix_time, leapseconds)
  1087. print("TPV time: %s, gps time: %s, week: %s tow: %.3f" %
  1088. (unix_time, gps_time, int(gps_week), gps_tow))
  1089. if (('lat' in all_data['tpv'] and
  1090. 'lon' in all_data['tpv'] and
  1091. 'altHAE' in all_data['tpv'])):
  1092. print("TPV position: lat %.8f lon %.8f altHAE %.3f" %
  1093. (all_data['tpv']['lat'], all_data['tpv']['lon'],
  1094. all_data['tpv']['altHAE']))
  1095. elif 0 < options.debug:
  1096. print("Warning: No TPV")
  1097. if all_data['raw'] and 'time' in all_data['raw']:
  1098. raw_time = int(all_data['raw']['time'])
  1099. if 'nsec' in all_data['raw']:
  1100. raw_time += float(all_data['raw']['nsec']) / 1e9
  1101. (gps_time, gps_week, gps_tow) = gps.posix2gps(raw_time, leapseconds)
  1102. print("\nRAW time: %s, gps time: %s, week: %s tow: %.3f" %
  1103. (raw_time, gps_time, int(gps_week), gps_tow))
  1104. # don't warn on missing RAW, it will be rare.
  1105. if options.timeat:
  1106. (gps_week, gps_tow) = divmod(gps_time, 604800)
  1107. (gps_time, gps_week, gps_tow) = gps.posix2gps(options.timeat, leapseconds)
  1108. print("Use time: %s, gps time: %s, week: %s tow: %.3f" %
  1109. (options.timeat, gps_time, int(gps_week), gps_tow))
  1110. delta_tutc = 0.0
  1111. delta_tiono = 0.0
  1112. if gps_week and all_data['ionosphere']:
  1113. print("\nSubframe 4, page 18")
  1114. # IS-GPS-200, Section 20.3.3.5.2.5, says if we use
  1115. # this data correctly L1 single frequency error gets cut in half.
  1116. _print_msg(all_data['ionosphere'], ionosphere_fields)
  1117. if gps_tow:
  1118. # tUTC = tLS + A0 + A1 (tE - tot + 604800 (WN - WNt))
  1119. delta_tiono = (all_data['ionosphere']['A0'] +
  1120. all_data['ionosphere']['A1'] *
  1121. (gps_tow - all_data['ionosphere']['tot'] +
  1122. 604800 * (gps_week - all_data['ionosphere']['WNt'])))
  1123. delta_tutc = all_data['ionosphere']['ls'] + delta_tiono
  1124. print(" delta_tutc %.9f delta_tiono %.9f" % (delta_tutc, delta_tiono))
  1125. if all_data['health']:
  1126. print("\nSubframe 4, page 25")
  1127. for sv in range(1, 33):
  1128. svid = "SV%d" % sv
  1129. if svid in all_data['health']:
  1130. print(" %s %2d (A/S %3s Config %s)" %
  1131. (svid, all_data['health'][svid],
  1132. 'Yes' if (all_data['health'][svid] & 8) else 'No',
  1133. sv_conf[all_data['health'][svid] & 0x7]))
  1134. for sv in range(25, 33):
  1135. svid = "SVH%d" % sv
  1136. if svid in all_data['health']:
  1137. if 0x3f == all_data['health'][svid]:
  1138. print(" %s %2d (n/a)" % (svid, all_data['health'][svid]))
  1139. else:
  1140. print(" %s %2d (Data %s, %s)" %
  1141. (svid, all_data['health'][svid],
  1142. 'Bad' if (all_data['health'][svid] & 0x20) else 'OK',
  1143. health_str[all_data['health'][svid] & 0x1f]))
  1144. if all_data['health2']:
  1145. print("\nSubframe 5, page 25\n WNa %d toa %d" %
  1146. (all_data['health2']['WNa'], all_data['health2']['toa']))
  1147. for sv in range(1, 25):
  1148. id = "SVH%d" % sv
  1149. if id in all_data['health2']:
  1150. if 0x3f == all_data['health2'][id]:
  1151. print(" %s %2d (n/a)" % (id, all_data['health2'][id]))
  1152. else:
  1153. print(" %s %2d (Data %s, %s)" %
  1154. (id, all_data['health2'][id],
  1155. 'Bad' if (all_data['health2'][id] & 0x20) else 'OK',
  1156. health_str[all_data['health2'][id] & 0x1f]))
  1157. if gps_week and options.satpos:
  1158. for sv in all_sv:
  1159. if ((sv in all_data['ephemeris1'] and
  1160. sv in all_data['ephemeris2'] and
  1161. sv in all_data['ephemeris3'] and
  1162. all_data['ephemeris1'][sv]['IODC'] ==
  1163. all_data['ephemeris2'][sv]['IODE'] and
  1164. all_data['ephemeris2'][sv]['IODE'] ==
  1165. all_data['ephemeris3'][sv]['IODE'])):
  1166. # Ensure we have all 3 parts of GPS, and their IODx matches.
  1167. print("\n%s Ephemeris position:" % sv)
  1168. # mash into one dict
  1169. # careful, = makes a link, not a copy, on dictionaries
  1170. # careful, update makes a link, not a copy, on dictionaries
  1171. eph = all_data['ephemeris1'][sv].copy()
  1172. eph.update(all_data['ephemeris2'][sv])
  1173. eph.update(all_data['ephemeris3'][sv])
  1174. # convert semi-circles to radians
  1175. # use military PI, not real pi.
  1176. cvt = ('deltan', 'i0', 'IDOT', 'M0', 'omega', 'Omega0',
  1177. 'Omegad')
  1178. for i in cvt:
  1179. eph[i] *= gps.GPS_PI
  1180. SatPosSum(eph, gps_tow)
  1181. elif sv in all_data['ephemeris']:
  1182. print("\n%s Ephemeris position:" % sv)
  1183. # careful, = makes a link, not a copy, on dictionaries
  1184. # careful, update makes a link, not a copy, on dictionaries
  1185. ephm = all_data['ephemeris'][sv].copy()
  1186. needed = True
  1187. # convert semi-circles to radians
  1188. # use military PI, not real pi.
  1189. cvt = ('deltan', 'i0', 'IDOT', 'M0', 'omega', 'Omega0',
  1190. 'Omegad')
  1191. for i in cvt:
  1192. if i in ephm:
  1193. # GPS_PI != math.pi!
  1194. ephm[i] *= gps.GPS_PI
  1195. else:
  1196. needed = False
  1197. print(" Missing %s" % i)
  1198. break
  1199. # Galileo has no Tgd? Use TGD1 or TGD2?
  1200. ephm.update({'Tgd': 0})
  1201. need = ('Cic', 'Cis', 'Crc', 'Crs', 'Cuc', 'Cus', 'sqrtA', 'toc')
  1202. for i in need:
  1203. if i not in ephm:
  1204. needed = False
  1205. print(" Missing %s" % i)
  1206. break
  1207. if needed:
  1208. SatPosSum(ephm, gps_tow)
  1209. if sv in all_data['almanac']:
  1210. print("\n%s Almanac position:" % sv)
  1211. # careful, = makes a link, not a copy, on dictionaries
  1212. ephm = all_data['almanac'][sv].copy()
  1213. if 'toa' not in ephm:
  1214. # partial almanac
  1215. if gps.VERB_INFO <= options.debug:
  1216. print(" Missing toa")
  1217. continue
  1218. ephm.update({'af2': 0,
  1219. 'deltan': 0,
  1220. 'IDOT': 0,
  1221. 'Tgd': 0,
  1222. 'toc': ephm['toa'],
  1223. 'toe': ephm['toa']})
  1224. if 'i0' not in ephm and 'deltai' in ephm:
  1225. # old style almanac
  1226. ephm.update({'i0': ephm['deltai'] + 0.30})
  1227. # convert semi-circles to radians
  1228. # use military PI, not real pi.
  1229. # check that we have required data.
  1230. # Galileo splits almanacs over many frames...
  1231. needed = True
  1232. cvt = ('af0', 'af1', 'deltan', 'i0', 'IDOT', 'M0', 'omega',
  1233. 'Omega0', 'Omegad')
  1234. for i in cvt:
  1235. if i in ephm:
  1236. # Galileo splits almanacs over many frames...
  1237. # GPS_PI != math.pi!
  1238. ephm[i] *= gps.GPS_PI
  1239. else:
  1240. needed = False
  1241. if gps.VERB_INFO <= options.debug:
  1242. print(" Missing %s" % i)
  1243. break
  1244. if 'sqrtA' not in ephm:
  1245. needed = False
  1246. if needed:
  1247. SatPosSum(ephm, gps_tow)
  1248. if options.savefile:
  1249. with open(options.savefile, 'w') as save_file:
  1250. # the JSON format will change, do not depend on it.
  1251. # JSON turns all the int keys into strings!
  1252. json.dump(all_data, save_file, indent=4, sort_keys=True)