Skip to content

Added decoding of 'PGTOP' to get the status of the antenna extension … #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
164 changes: 163 additions & 1 deletion adafruit_gps.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +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_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
Expand Down Expand Up @@ -291,10 +310,12 @@ def update(self) -> bool:
# GP - GPS
# GQ - QZSS
# GN - GNSS / More than one of the above
# PG - Status of antenna extension
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.
return True if not data_type.endswith(b"PGTOP") else self._parse_pgtop(args)

result = True
args = args.split(",")
Expand Down Expand Up @@ -327,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."""
Expand Down Expand Up @@ -676,6 +809,35 @@ def _parse_gsv(self, talker: bytes, data: List[str]) -> bool:

return True

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

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):
"""GTop-compatible I2C GPS parsing module. Can parse simple NMEA data
Expand Down