From b2409d3e172510c90168cb662883158186095b53 Mon Sep 17 00:00:00 2001 From: Pierre Lepage Date: Fri, 30 Dec 2022 13:08:17 -0500 Subject: [PATCH 1/4] Added decoding of 'PGTOP' to get the status of the antenna extension (if any). The antenna status is made accessible via the new 'self.antenna' property. A tuple (current status, last status). Possible values are: 1: Antenna shorted. 2: Internal antenna. 3: Active (extending) antenna. So self.antenna[0] gives the current status and self.antenna[1] gives the last status. User is responsible to acknowledge change in the antenna status by making self.antenna[1] same as item 0. --- adafruit_gps.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index 40f1b10..150be95 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -229,6 +229,7 @@ def __init__(self, uart: UART, debug: bool = False) -> None: self._uart = uart # Initialize null starting values for GPS attributes. self.timestamp_utc = None + self.antenna = (None, None) # Antenna status (current, last). self.latitude = None self.latitude_degrees = None self.latitude_minutes = None # Use for full precision minutes @@ -291,10 +292,12 @@ def update(self) -> bool: # GP - GPS # GQ - QZSS # GN - GNSS / More than one of the above + # PG - Status of antenna extension (added by Pierre Lepage) 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 # Assume it's a valid packet anyway - return True + # False: PGTOP. Status of antenna extension. Added by Pierre Lepage. + return True if data_type[:5] != b"PGTOP" else self._parse_pgtop(args) result = True args = args.split(",") @@ -747,3 +750,14 @@ def readline(self) -> Optional[bytearray]: self._internalbuffer = [] # reset the buffer to empty return ret return None # no completed data yet + + def _parse_pgtop(self, data) -> int: + # Added by Pierre Lepage. + if data is None or len(data) != 4: + return False + + # Antenna status. 1: Antenna shorted. 2: Internal antenna. 3: Active (extension) antenna. + # User can detect change in antenna status by testing equality between item 0 and item 1 of tuple. + # User is responsible to acknowledge change in the antenna status by making self.antenna[1] same as item 0. + self.antenna = (data[3], self.antenna[1]) + return True From c985cfeb0c818648472ea93ccbc5d91581e48d4e Mon Sep 17 00:00:00 2001 From: Pierre Lepage Date: Fri, 30 Dec 2022 13:08:17 -0500 Subject: [PATCH 2/4] Added decoding of 'PGTOP' to get the status of the antenna extension (if any). The antenna status is made accessible via the new 'self.antenna' property. A tuple (current status, last status). Possible values are: 1: Antenna shorted. 2: Internal antenna. 3: Active (extending) antenna. So self.antenna[0] gives the current status and self.antenna[1] gives the last status. User is responsible to acknowledge change in the antenna status by making self.antenna[1] same as item 0. --- adafruit_gps.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index 40f1b10..b2a6525 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -229,6 +229,7 @@ def __init__(self, uart: UART, debug: bool = False) -> None: self._uart = uart # Initialize null starting values for GPS attributes. self.timestamp_utc = None + self.antenna = (None, None) # Antenna status (current, last). self.latitude = None self.latitude_degrees = None self.latitude_minutes = None # Use for full precision minutes @@ -291,10 +292,12 @@ def update(self) -> bool: # GP - GPS # GQ - QZSS # GN - GNSS / More than one of the above + # PG - Status of antenna extension (added by Pierre Lepage) 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 # Assume it's a valid packet anyway - return True + # False: PGTOP. Status of antenna extension. Added by Pierre Lepage. + return True if data_type[:5] != b"PGTOP" else self._parse_pgtop(args) result = True args = args.split(",") @@ -676,6 +679,17 @@ def _parse_gsv(self, talker: bytes, data: List[str]) -> bool: return True + def _parse_pgtop(self, data) -> int: + # Added by Pierre Lepage. + if data is None or len(data) != 4: + return False + + # Antenna status. 1: Antenna shorted. 2: Internal antenna. 3: Active (extension) antenna. + # User can detect change in antenna status by testing equality between item 0 and item 1 of tuple. + # User is responsible to acknowledge change in the antenna status by making self.antenna[1] same as item 0. + self.antenna = (data[3], self.antenna[1]) + return True + class GPS_GtopI2C(GPS): """GTop-compatible I2C GPS parsing module. Can parse simple NMEA data @@ -747,3 +761,4 @@ def readline(self) -> Optional[bytearray]: self._internalbuffer = [] # reset the buffer to empty return ret return None # no completed data yet + From 0b82035b5a6bbbed6fda5c2c477c2672d4a7c3c2 Mon Sep 17 00:00:00 2001 From: Pierre Lepage Date: Fri, 30 Dec 2022 16:32:14 -0500 Subject: [PATCH 3/4] Pre-commit check done. --- adafruit_gps.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index b485886..a949fe7 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -297,7 +297,7 @@ def update(self) -> bool: # It's not a known GNSS source of data # Assume it's a valid packet anyway # False: PGTOP. Status of antenna extension. - return True if data_type[:5] != b"PGTOP" else self._parse_pgtop(args) + return True if not data_type.endswith(b"PGTOP") else self._parse_pgtop(args) result = True args = args.split(",") @@ -685,11 +685,13 @@ def _parse_pgtop(self, data) -> int: return False # Antenna status. 1: Antenna shorted. 2: Internal antenna. 3: Active (extension) antenna. - # User can detect change in antenna status by testing equality between item 0 and item 1 of tuple. - # User is responsible to acknowledge change in the antenna status by making self.antenna[1] same as item 0. + # User can detect change in antenna status by testing equality between item 0 and item 1 of + # tuple. User is responsible to acknowledge change in the antenna status by making + # self.antenna[1] same as item 0. self.antenna = (data[3], self.antenna[1]) return True + class GPS_GtopI2C(GPS): """GTop-compatible I2C GPS parsing module. Can parse simple NMEA data sentences from an I2C-capable GPS module to read latitude, longitude, and more. From c3aee353a32cb5fbd9b4eb1315b186288eb9cfc9 Mon Sep 17 00:00:00 2001 From: Pierre Lepage Date: Thu, 12 Jan 2023 07:33:09 -0500 Subject: [PATCH 4/4] Add an acknowledgment mechanism. Added a property to make it easier to enable the antenna advisor on the PA6H chip. Write useful docstrings. --- adafruit_gps.py | 169 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 158 insertions(+), 11 deletions(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index a949fe7..5dfbd02 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -229,7 +229,25 @@ def __init__(self, uart: UART, debug: bool = False) -> None: self._uart = uart # Initialize null starting values for GPS attributes. self.timestamp_utc = None - self.antenna = (None, None) # Antenna status (current, last). + self.__antenna_advisor = ( + False # Control the antenna advisor mechanism of the PA6H chip. + ) + self.__antenna_actual = ( + None # Actual status acknowledge of the external antenna. + ) + self.__antenna_last = None # Last status acknowledge of the external antenna. + self.__antenna_change = False + self.__antenna_instant_status = None # Status of antenna as currently read. + self.__antenna_ack_delay = ( + 5 # Time (sec.) to wait between automatic acknowledge. + ) + self.__antenna_ack_time = time.monotonic() + self.__antenna_ack = ( + False # False: initialize a new delay. True: delay is in way. + ) + self.__antenna_ack = ( + False # False: initialize a new delay. True: delay is in way. + ) self.latitude = None self.latitude_degrees = None self.latitude_minutes = None # Use for full precision minutes @@ -330,6 +348,118 @@ def send_command(self, command: bytes, add_checksum: bool = True) -> None: self.write(bytes("{:02x}".format(checksum).upper(), "ascii")) self.write(b"\r\n") + @property + def antenna_advisor(self): + """ + new_state True: Force the PA6H to emit continuously the active antenna status. + new_state False: Stop the antenna advisor. + """ + return self.__antenna_advisor + + @antenna_advisor.setter + def antenna_advisor(self, new_state: bool = False): + if not isinstance(new_state, bool): + return + + if new_state: + self.send_command(b"PGCMD,33,1") + else: + self.send_command(b"PGCMD,33,0") + self.__antenna_change = False + self.__antenna_actual = None + self.__antenna_last = None + + self.__antenna_advisor = new_state + + @property + def antenna(self): + """Status of the active antenna. + + The user must enable the antenna advisor of the PA6H chip by issuing a + GPS.antenna_avisor(True) command. When the advisor is activated, the PA6H chip continuously + transmits a PGTOP message (status of the active antenna) which will be analyzed by the + module. The state of the active antenna will then be available in a tuple given by this + property and subject to the acknowledgment mechanism. + + The tuple returned is to be interpreted as follows. + [0]: A boolean value indicator. True: A change has occurred and has not yet been + acknowledged. + [1]: An integer value. The actual status of the active antenna. + [2]: An integer value. The last status of the active antenna. + + For items [1] and [2]. + - 1: Antenna shorted. + - 2: Internal antenna in use. + - 3: External (active) antenna in use. + + The user can query the PA6H chip for the status of the active antenna by issuing a + PGTOP,33,3 command. In this particular case, the PA6H chip will only respond once and the + result will be accessible via the antenna[1] property. The antenna[0] and antenna[1] values + are meaningless in this particular case if the antenna advisor is not enabled. + + If you want continuous monitoring of active antenna status, please enable the antenna + advisor with the GPS.antenna_advisor setter. + + Returns: + A tuple as follow (bool, int, int) when the antenna advisor is enabled else + (False, None, None). + """ + return self.__antenna_change, self.__antenna_actual, self.__antenna_last + + @property + def antenna_acknowledge_delay(self): + """ + Change the acknowledgement delay when the argument delay is present. No acknowledgement + of the current antenna status change take place at this time. + + Args: + delay (int | None): Use it to change the delay before an automatic acknowledgment + occurs. Use 0 to "No delay" (i.e. acknowledgement done silently with no delay). + A default value of 5 seconds is set at instanciation of the class. + """ + return self.__antenna_ack_delay + + @antenna_acknowledge_delay.setter + def antenna_acknowledge_delay(self, delay=None): + _new_delay = None + + if delay is not None: + try: + _new_delay = int(delay) + except ValueError: + return + + if _new_delay not in {0, 1, 2, 3, 4, 5}: + return + + if _new_delay != self.__antenna_ack_delay: + # Change delay of acknowledgement. + # Delay must be an integer value: 0, 1, 2, 3, 4, 5 seconds. 0 : free run! + + self.__antenna_ack_delay = _new_delay + + def antenna_acknowledge_receipt(self): + """ + The change of state of the antenna is governed by an acknowledgment with a locking mechanism + giving the user time to become aware of any change (for example: forgetting to connect the + active antenna, untimely disconnection of the micro connector on-board ufl or intermittent + problem). The user must acknowledge an antenna status change if they want to be notified of + any future problems with the active antenna. Anyway, an automatic acknowledgment is + performed after a delay which can be modified by the user. + + This method do an acknowledgement of the change in the antenna status only if the antenna + advisor is enabled. + """ + if not self.antenna_advisor: + return + + # Acknowledgment + # Antenna status. 1: Shorted. 2: Internal in use. 3: Active connected and in use. + self.__antenna_last = self.__antenna_actual + self.__antenna_actual = self.__antenna_instant_status + self.__antenna_change = False + self.__antenna_ack = False # Unlatch. + @property def has_fix(self) -> bool: """True if a current fix for location information is available.""" @@ -679,17 +809,34 @@ def _parse_gsv(self, talker: bytes, data: List[str]) -> bool: return True - def _parse_pgtop(self, data) -> int: - # Added by Pierre Lepage. - if data is None or len(data) != 4: - return False + def _parse_pgtop(self, data) -> None: + # data look like '33,x' where x should be 0, 1 or 2. + if data is None or len(data) != 4 or data[3] not in {"1", "2", "3"}: + return - # Antenna status. 1: Antenna shorted. 2: Internal antenna. 3: Active (extension) antenna. - # User can detect change in antenna status by testing equality between item 0 and item 1 of - # tuple. User is responsible to acknowledge change in the antenna status by making - # self.antenna[1] same as item 0. - self.antenna = (data[3], self.antenna[1]) - return True + self.__antenna_change = self.__antenna_actual != data[3] + + if not self.__antenna_change: + return + + self.__antenna_instant_status = data[3] + + # Case of free run (always acknowledge silently). + if self.__antenna_ack_delay == 0 or not self.antenna_advisor: + # No delay! Ignore the acknowledgment mechanism. + self.antenna_acknowledge_receipt() + return + + # Case of change in the antenna status with an acknowledgment governed by a timer. + # The latching mechanism is implemented through the test on self.__antenna_ack flag. + if not self.__antenna_ack: + self.__antenna_ack = True # Latch. + self.__antenna_ack_time = time.monotonic() # Start a timer. + + # Case of an automatic acknowledge. Its occurs after the programmed delay. + if time.monotonic() - self.__antenna_ack_time >= self.__antenna_ack_delay: + self.antenna_acknowledge_receipt() + return class GPS_GtopI2C(GPS):