From 8629b4fe3e019d60a873e30fcdeeaebec72772b0 Mon Sep 17 00:00:00 2001 From: James Carr Date: Thu, 11 Feb 2021 23:19:49 +0000 Subject: [PATCH 1/6] Add support for a wider range of talkers. Reference: https://www.nmea.org/Assets/20190303%20nmea%200183%20talker%20identifier%20mnemonics.pdf --- adafruit_gps.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index 14d0b42..10a045f 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -121,15 +121,27 @@ def update(self): print(sentence) data_type, args = sentence data_type = bytes(data_type.upper(), "ascii") - # return sentence - if data_type in ( - b"GPGLL", - b"GNGLL", - ): # GLL, Geographic Position – Latitude/Longitude + (talker, sentence_type) = (data_type[:2], data_type[2:]) + + # Check for all currently known talkers + # GA - Galileo + # GB - BeiDou Systems + # GI - NavIC + # GL - GLONASS + # GP - GPS + # GQ - QZSS + # GN - GNSS / More than one of the above + if talker not in (b'GA', b'GB', b'GI', b'GL', b'GP', b'GQ', b'GN'): + if self.debug: + print(f" Unknown talker: {talker}") + # We don't know the talker so it's not new data + return False + + if sentence_type == b'GLL': # Geographic position - Latitude/Longitude self._parse_gpgll(args) - elif data_type in (b"GPRMC", b"GNRMC"): # RMC, minimum location info + elif sentence_type == b'RMC': # Minimum location info self._parse_gprmc(args) - elif data_type in (b"GPGGA", b"GNGGA"): # GGA, 3d location fix + elif sentence_type == b'GGA': # 3D location fix self._parse_gpgga(args) return True From 8786f1fc972324e5ad97d15b80e4554e56aeebe8 Mon Sep 17 00:00:00 2001 From: James Carr Date: Fri, 12 Feb 2021 20:10:47 +0000 Subject: [PATCH 2/6] Enable GSA (DOP and active satellites) parsing Enable GSV (satellites in view) parsing. Rewrote the GSA and GSV parsing to handle each satellite system (talker) separately. - self.sats now uses keys based upon the talker and satellite number, eg. GL67 for GLONASS #67, GP7 for GPS #7 - When the end message of a GSV sequence is received, eg. 3 of 3, all previous records in self.sats matching that talker are removed before adding the updated ones. - self.sat_prns stores the last satellite IDs that were used for a fix and returned in the most recent GSA sentence. They will be from only one Satellite system and should have a record in self.sats . --- adafruit_gps.py | 83 +++++++++++++++++----------- examples/gps_satellitefix.py | 103 +++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 32 deletions(-) create mode 100644 examples/gps_satellitefix.py diff --git a/adafruit_gps.py b/adafruit_gps.py index 10a045f..48bc5e9 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -90,6 +90,7 @@ def __init__(self, uart, debug=False): self.height_geoid = None self.speed_knots = None self.track_angle_deg = None + self._sats = None self.sats = None self.isactivedata = None self.true_track = None @@ -121,9 +122,9 @@ def update(self): print(sentence) data_type, args = sentence data_type = bytes(data_type.upper(), "ascii") - (talker, sentence_type) = (data_type[:2], data_type[2:]) + (talker, sentence_type) = self._parse_talker(data_type) - # Check for all currently known talkers + # Check for all currently known GNSS talkers # GA - Galileo # GB - BeiDou Systems # GI - NavIC @@ -132,10 +133,8 @@ def update(self): # GQ - QZSS # GN - GNSS / More than one of the above if talker not in (b'GA', b'GB', b'GI', b'GL', b'GP', b'GQ', b'GN'): - if self.debug: - print(f" Unknown talker: {talker}") - # We don't know the talker so it's not new data - return False + # It's not a known GNSS source of data + return True if sentence_type == b'GLL': # Geographic position - Latitude/Longitude self._parse_gpgll(args) @@ -143,6 +142,10 @@ def update(self): self._parse_gprmc(args) elif sentence_type == b'GGA': # 3D location fix self._parse_gpgga(args) + elif sentence_type == b'GSV': # Satellites in view + self._parse_gpgsv(talker, args) + elif sentence_type == b'GSA': # GPS DOP and active satellites + self._parse_gpgsa(talker, args) return True def send_command(self, command, add_checksum=True): @@ -253,6 +256,13 @@ def _parse_sentence(self): data_type = sentence[1:delimiter] return (data_type, sentence[delimiter + 1 :]) + def _parse_talker(self, data_type): + # Split the data_type into talker and sentence_type + if data_type[0] == b'P': # Proprietary codes + return (data_type[:1], data_type[1:]) + else: + return (data_type[:2], data_type[2:]) + def _parse_gpgll(self, args): data = args.split(",") if data is None or data[0] is None or (data[0] == ""): @@ -414,7 +424,8 @@ def _parse_gpgga(self, args): self.altitude_m = _parse_float(data[8]) self.height_geoid = _parse_float(data[10]) - def _parse_gpgsa(self, args): + def _parse_gpgsa(self, talker, args): + talker = talker.decode('ascii') data = args.split(",") if data is None or (data[0] == ""): return # Unexpected number of params @@ -424,9 +435,9 @@ def _parse_gpgsa(self, args): # Parse 3d fix self.fix_quality_3d = _parse_int(data[1]) satlist = list(filter(None, data[2:-4])) - self.sat_prns = {} - for i, sat in enumerate(satlist, 1): - self.sat_prns["gps{}".format(i)] = _parse_int(sat) + self.sat_prns = [] + for sat in satlist: + self.sat_prns.append("{}{}".format(talker, _parse_int(sat))) # Parse PDOP, dilution of precision self.pdop = _parse_float(data[-3]) @@ -435,9 +446,10 @@ def _parse_gpgsa(self, args): # Parse VDOP, vertical dilution of precision self.vdop = _parse_float(data[-1]) - def _parse_gpgsv(self, args): + def _parse_gpgsv(self, talker, args): # Parse the arguments (everything after data type) for NMEA GPGGA # 3D location fix sentence. + talker = talker.decode('ascii') data = args.split(",") if data is None or (data[0] == ""): return # Unexpected number of params. @@ -454,33 +466,40 @@ def _parse_gpgsv(self, args): sat_tup = data[3:] - satdict = {} - for i in range(len(sat_tup) / 4): + satlist = [] + for i in range(len(sat_tup) // 4): j = i * 4 - key = "gps{}".format(i + (4 * (self.mess_num - 1))) - satnum = _parse_int(sat_tup[0 + j]) # Satellite number + satnum = "{}{}".format(talker, _parse_int(sat_tup[0 + j])) # Satellite number satdeg = _parse_int(sat_tup[1 + j]) # Elevation in degrees satazim = _parse_int(sat_tup[2 + j]) # Azimuth in degrees satsnr = _parse_int(sat_tup[3 + j]) # signal-to-noise ratio in dB value = (satnum, satdeg, satazim, satsnr) - satdict[key] = value + satlist.append(value) + + if self._sats is None: + self._sats = [] + for value in satlist: + self._sats.append(value) + + if self.mess_num == self.total_mess_num: + # Last part of GSV message + if len(self._sats) == self.satellites: + # Transfer received satellites to self.sats + if self.sats is None: + self.sats = {} + else: + # Remove all old data from self.sats which + # match the current talker + old = [] + for i in self.sats: + if i[0:2] == talker: + old.append(i) + for i in old: + self.sats.pop(i) + for s in self._sats: + self.sats[s[0]] = s + self._sats.clear() - if self.sats is None: - self.sats = {} - for satnum in satdict: - self.sats[satnum] = satdict[satnum] - - try: - if self.satellites < self.satellites_prev: - for i in self.sats: - try: - if int(i[-2]) >= self.satellites: - del self.sats[i] - except ValueError: - if int(i[-1]) >= self.satellites: - del self.sats[i] - except TypeError: - pass self.satellites_prev = self.satellites diff --git a/examples/gps_satellitefix.py b/examples/gps_satellitefix.py new file mode 100644 index 0000000..f3d456b --- /dev/null +++ b/examples/gps_satellitefix.py @@ -0,0 +1,103 @@ +# SPDX-FileCopyrightText: 2021 lesamouraipourpre +# SPDX-License-Identifier: MIT + +import time +import board +import busio + +import adafruit_gps + +# Create a serial connection for the GPS connection using default speed and +# a slightly higher timeout (GPS modules typically update once a second). +# These are the defaults you should use for the GPS FeatherWing. +# For other boards set RX = GPS module TX, and TX = GPS module RX pins. +# uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10) + +# for a computer, use the pyserial library for uart access +# import serial +# uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=10) + +# If using I2C, we'll create an I2C interface to talk to using default pins +i2c = board.I2C() + +# Create a GPS module instance. +# gps = adafruit_gps.GPS(uart, debug=False) # Use UART/pyserial +gps = adafruit_gps.GPS_GtopI2C(i2c, debug=False) # Use I2C interface + +# Initialize the GPS module by changing what data it sends and at what rate. +# These are NMEA extensions for PMTK_314_SET_NMEA_OUTPUT and +# PMTK_220_SET_NMEA_UPDATERATE but you can send anything from here to adjust +# the GPS module behavior: +# https://cdn-shop.adafruit.com/datasheets/PMTK_A11.pdf + +# Turn on everything (not all of it is parsed!) +gps.send_command(b'PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0') + +# Set update rate to once a second (1hz) which is what you typically want. +gps.send_command(b"PMTK220,1000") +# Or decrease to once every two seconds by doubling the millisecond value. +# Be sure to also increase your UART timeout above! +# gps.send_command(b'PMTK220,2000') +# You can also speed up the rate, but don't go too fast or else you can lose +# data during parsing. This would be twice a second (2hz, 500ms delay): +# gps.send_command(b'PMTK220,500') + +def format_dop(dop): + # https://en.wikipedia.org/wiki/Dilution_of_precision_(navigation) + if dop > 20: + msg = "Poor" + elif dop > 10: + msg = "Fair" + elif dop > 5: + msg = "Moderate" + elif dop > 2: + msg = "Good" + elif dop > 1: + msg = "Excellent" + else: + msg = "Ideal" + return f"{dop} - {msg}" + +talkers = { + 'GA': 'Galileo', + 'GB': 'BeiDou', + 'GI': 'NavIC', + 'GL': 'GLONASS', + 'GP': 'GPS', + 'GQ': 'QZSS', + 'GN': 'GNSS' + } + +# Main loop runs forever printing the location, etc. every second. +last_print = time.monotonic() +while True: + # Make sure to call gps.update() every loop iteration and at least twice + # as fast as data comes from the GPS unit (usually every second). + # This returns a bool that's true if it parsed new data (you can ignore it + # though if you don't care and instead look at the has_fix property). + if not gps.update() or not gps.has_fix: + time.sleep(0.1) + continue + + if gps.nmea_sentence[3:6] == "GSA": + print(f"{gps.latitude:.6f}, {gps.longitude:.6f} {gps.altitude_m}m") + print(f"2D Fix: {gps.has_fix} 3D Fix: {gps.has_3d_fix}") + print(f" PDOP (Position Dilution of Precision): {format_dop(gps.pdop)}") + print(f" HDOP (Horizontal Dilution of Precision): {format_dop(gps.hdop)}") + print(f" VDOP (Vertical Dilution of Precision): {format_dop(gps.vdop)}") + print(f"Satellites used for fix:") + for s in gps.sat_prns: + talker = talkers[s[0:2]] + number = s[2:] + if gps.sats is None: + print(f" {talker}-{number} - no info") + else: + try: + sat = gps.sats[s] + if sat is None: + print(f" {talker}-{number} - no info") + else: + print(f" {talker}-{number} Elevation:{sat[1]}* Azimuth:{sat[2]}* SNR:{sat[3]}dB") + except KeyError: + print(f" {talker}-{number} - no info") + print() From 84480ede5bd10611d86297f33ebc46ab0e9c8000 Mon Sep 17 00:00:00 2001 From: James Carr Date: Thu, 18 Feb 2021 12:43:31 +0000 Subject: [PATCH 3/6] Update the processing of GSV messages, to include a time reference to when it was received. The data stored in self.sats dictionary is: key is TTNN where TT = the talker name, eg. GL for GLONASS NN = the number of the satellite, currently a 1 or 2 digit number value is a 5 entry list (V0, V1, V2, V3, V4) V0 = satellite number TTNN as used for the key V1 = satellite elevation in degrees V2 = satellite azimuth in degrees V3 = satellite signal to noise ratio in dB, or None V4 = timestamp, time.monotonic(), of last GSV message --- adafruit_gps.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index 48bc5e9..ca488ab 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -90,8 +90,8 @@ def __init__(self, uart, debug=False): self.height_geoid = None self.speed_knots = None self.track_angle_deg = None - self._sats = None - self.sats = None + self._sats = None # Temporary holder for information from GSV messages + self.sats = None # Completed information from GSV messages self.isactivedata = None self.true_track = None self.mag_track = None @@ -467,14 +467,23 @@ def _parse_gpgsv(self, talker, args): sat_tup = data[3:] satlist = [] + timestamp = time.monotonic() for i in range(len(sat_tup) // 4): - j = i * 4 - satnum = "{}{}".format(talker, _parse_int(sat_tup[0 + j])) # Satellite number - satdeg = _parse_int(sat_tup[1 + j]) # Elevation in degrees - satazim = _parse_int(sat_tup[2 + j]) # Azimuth in degrees - satsnr = _parse_int(sat_tup[3 + j]) # signal-to-noise ratio in dB - value = (satnum, satdeg, satazim, satsnr) - satlist.append(value) + try: + j = i * 4 + # Satellite number + satnum = "{}{}".format(talker, _parse_int(sat_tup[0 + j])) + # Elevation in degrees + satdeg = _parse_int(sat_tup[1 + j]) + # Azimuth in degrees + satazim = _parse_int(sat_tup[2 + j]) + # signal-to-noise ratio in dB + satsnr = _parse_int(sat_tup[3 + j]) + value = (satnum, satdeg, satazim, satsnr, timestamp) + satlist.append(value) + except ValueError: + # Something wasn't an int + pass if self._sats is None: self._sats = [] @@ -488,11 +497,13 @@ def _parse_gpgsv(self, talker, args): if self.sats is None: self.sats = {} else: - # Remove all old data from self.sats which - # match the current talker + # Remove all satellites which haven't + # been seen for 30 seconds + timestamp = time.monotonic() old = [] for i in self.sats: - if i[0:2] == talker: + sat = self.sats[i] + if (timestamp - sat[4]) > 30: old.append(i) for i in old: self.sats.pop(i) From 93a8430a09d15d73e6d4cd4c6b473350f0259db3 Mon Sep 17 00:00:00 2001 From: James Carr Date: Thu, 18 Feb 2021 13:30:00 +0000 Subject: [PATCH 4/6] Reformat with black. --- adafruit_gps.py | 22 +++++++++++----------- examples/gps_satellitefix.py | 26 +++++++++++++++----------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index ca488ab..e2aa70f 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -90,8 +90,8 @@ def __init__(self, uart, debug=False): self.height_geoid = None self.speed_knots = None self.track_angle_deg = None - self._sats = None # Temporary holder for information from GSV messages - self.sats = None # Completed information from GSV messages + self._sats = None # Temporary holder for information from GSV messages + self.sats = None # Completed information from GSV messages self.isactivedata = None self.true_track = None self.mag_track = None @@ -132,19 +132,19 @@ def update(self): # GP - GPS # GQ - QZSS # GN - GNSS / More than one of the above - if talker not in (b'GA', b'GB', b'GI', b'GL', b'GP', b'GQ', b'GN'): + if talker not in (b"GA", b"GB", b"GI", b"GL", b"GP", b"GQ", b"GN"): # It's not a known GNSS source of data return True - if sentence_type == b'GLL': # Geographic position - Latitude/Longitude + if sentence_type == b"GLL": # Geographic position - Latitude/Longitude self._parse_gpgll(args) - elif sentence_type == b'RMC': # Minimum location info + elif sentence_type == b"RMC": # Minimum location info self._parse_gprmc(args) - elif sentence_type == b'GGA': # 3D location fix + elif sentence_type == b"GGA": # 3D location fix self._parse_gpgga(args) - elif sentence_type == b'GSV': # Satellites in view + elif sentence_type == b"GSV": # Satellites in view self._parse_gpgsv(talker, args) - elif sentence_type == b'GSA': # GPS DOP and active satellites + elif sentence_type == b"GSA": # GPS DOP and active satellites self._parse_gpgsa(talker, args) return True @@ -258,7 +258,7 @@ def _parse_sentence(self): def _parse_talker(self, data_type): # Split the data_type into talker and sentence_type - if data_type[0] == b'P': # Proprietary codes + if data_type[0] == b"P": # Proprietary codes return (data_type[:1], data_type[1:]) else: return (data_type[:2], data_type[2:]) @@ -425,7 +425,7 @@ def _parse_gpgga(self, args): self.height_geoid = _parse_float(data[10]) def _parse_gpgsa(self, talker, args): - talker = talker.decode('ascii') + talker = talker.decode("ascii") data = args.split(",") if data is None or (data[0] == ""): return # Unexpected number of params @@ -449,7 +449,7 @@ def _parse_gpgsa(self, talker, args): def _parse_gpgsv(self, talker, args): # Parse the arguments (everything after data type) for NMEA GPGGA # 3D location fix sentence. - talker = talker.decode('ascii') + talker = talker.decode("ascii") data = args.split(",") if data is None or (data[0] == ""): return # Unexpected number of params. diff --git a/examples/gps_satellitefix.py b/examples/gps_satellitefix.py index f3d456b..c61e2b6 100644 --- a/examples/gps_satellitefix.py +++ b/examples/gps_satellitefix.py @@ -31,7 +31,7 @@ # https://cdn-shop.adafruit.com/datasheets/PMTK_A11.pdf # Turn on everything (not all of it is parsed!) -gps.send_command(b'PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0') +gps.send_command(b"PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0") # Set update rate to once a second (1hz) which is what you typically want. gps.send_command(b"PMTK220,1000") @@ -42,6 +42,7 @@ # data during parsing. This would be twice a second (2hz, 500ms delay): # gps.send_command(b'PMTK220,500') + def format_dop(dop): # https://en.wikipedia.org/wiki/Dilution_of_precision_(navigation) if dop > 20: @@ -58,15 +59,16 @@ def format_dop(dop): msg = "Ideal" return f"{dop} - {msg}" + talkers = { - 'GA': 'Galileo', - 'GB': 'BeiDou', - 'GI': 'NavIC', - 'GL': 'GLONASS', - 'GP': 'GPS', - 'GQ': 'QZSS', - 'GN': 'GNSS' - } + "GA": "Galileo", + "GB": "BeiDou", + "GI": "NavIC", + "GL": "GLONASS", + "GP": "GPS", + "GQ": "QZSS", + "GN": "GNSS", +} # Main loop runs forever printing the location, etc. every second. last_print = time.monotonic() @@ -97,7 +99,9 @@ def format_dop(dop): if sat is None: print(f" {talker}-{number} - no info") else: - print(f" {talker}-{number} Elevation:{sat[1]}* Azimuth:{sat[2]}* SNR:{sat[3]}dB") + print( + f" {talker}-{number} Elevation:{sat[1]}* Azimuth:{sat[2]}* SNR:{sat[3]}dB" + ) except KeyError: - print(f" {talker}-{number} - no info") + print(f" {talker}-{number} - no info") print() From 6bf1132fd79da51dfa22f497662bf0701eb00126 Mon Sep 17 00:00:00 2001 From: James Carr Date: Thu, 18 Feb 2021 18:21:01 +0000 Subject: [PATCH 5/6] Reformat with pylint and black. --- adafruit_gps.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index e2aa70f..c16f847 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -122,7 +122,7 @@ def update(self): print(sentence) data_type, args = sentence data_type = bytes(data_type.upper(), "ascii") - (talker, sentence_type) = self._parse_talker(data_type) + (talker, sentence_type) = GPS._parse_talker(data_type) # Check for all currently known GNSS talkers # GA - Galileo @@ -256,12 +256,13 @@ def _parse_sentence(self): data_type = sentence[1:delimiter] return (data_type, sentence[delimiter + 1 :]) - def _parse_talker(self, data_type): + @staticmethod + def _parse_talker(data_type): # Split the data_type into talker and sentence_type if data_type[0] == b"P": # Proprietary codes return (data_type[:1], data_type[1:]) - else: - return (data_type[:2], data_type[2:]) + + return (data_type[:2], data_type[2:]) def _parse_gpgll(self, args): data = args.split(",") @@ -448,6 +449,7 @@ def _parse_gpgsa(self, talker, args): def _parse_gpgsv(self, talker, args): # Parse the arguments (everything after data type) for NMEA GPGGA + # pylint: disable=too-many-branches # 3D location fix sentence. talker = talker.decode("ascii") data = args.split(",") @@ -471,15 +473,18 @@ def _parse_gpgsv(self, talker, args): for i in range(len(sat_tup) // 4): try: j = i * 4 - # Satellite number - satnum = "{}{}".format(talker, _parse_int(sat_tup[0 + j])) - # Elevation in degrees - satdeg = _parse_int(sat_tup[1 + j]) - # Azimuth in degrees - satazim = _parse_int(sat_tup[2 + j]) - # signal-to-noise ratio in dB - satsnr = _parse_int(sat_tup[3 + j]) - value = (satnum, satdeg, satazim, satsnr, timestamp) + value = ( + # Satellite number + "{}{}".format(talker, _parse_int(sat_tup[0 + j])), + # Elevation in degrees + _parse_int(sat_tup[1 + j]), + # Azimuth in degrees + _parse_int(sat_tup[2 + j]), + # signal-to-noise ratio in dB + _parse_int(sat_tup[3 + j]), + # Timestamp + timestamp, + ) satlist.append(value) except ValueError: # Something wasn't an int @@ -507,8 +512,8 @@ def _parse_gpgsv(self, talker, args): old.append(i) for i in old: self.sats.pop(i) - for s in self._sats: - self.sats[s[0]] = s + for sat in self._sats: + self.sats[sat[0]] = sat self._sats.clear() self.satellites_prev = self.satellites From 13b6cc98d97b781c743b04111b96be98a713be7c Mon Sep 17 00:00:00 2001 From: James Carr Date: Thu, 18 Feb 2021 18:52:47 +0000 Subject: [PATCH 6/6] Run pylint on satellitefix example. --- examples/gps_satellitefix.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/gps_satellitefix.py b/examples/gps_satellitefix.py index c61e2b6..17ef3c0 100644 --- a/examples/gps_satellitefix.py +++ b/examples/gps_satellitefix.py @@ -3,7 +3,6 @@ import time import board -import busio import adafruit_gps @@ -87,21 +86,20 @@ def format_dop(dop): print(f" PDOP (Position Dilution of Precision): {format_dop(gps.pdop)}") print(f" HDOP (Horizontal Dilution of Precision): {format_dop(gps.hdop)}") print(f" VDOP (Vertical Dilution of Precision): {format_dop(gps.vdop)}") - print(f"Satellites used for fix:") + print("Satellites used for fix:") for s in gps.sat_prns: talker = talkers[s[0:2]] number = s[2:] + print(f" {talker}-{number} ", end="") if gps.sats is None: - print(f" {talker}-{number} - no info") + print("- no info") else: try: sat = gps.sats[s] if sat is None: - print(f" {talker}-{number} - no info") + print("- no info") else: - print( - f" {talker}-{number} Elevation:{sat[1]}* Azimuth:{sat[2]}* SNR:{sat[3]}dB" - ) + print(f"Elevation:{sat[1]}* Azimuth:{sat[2]}* SNR:{sat[3]}dB") except KeyError: - print(f" {talker}-{number} - no info") + print("- no info") print()