diff --git a/configure_gps.sh b/configure_gps.sh new file mode 100755 index 00000000..b06543d8 --- /dev/null +++ b/configure_gps.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# +# configure_gps.sh: script to provide GPS modules with commands +# that are not saved in flash on the module (ie. they must be provided +# each time the module is started). + + +BASEDIR="$(dirname "$0")" +source <( grep -v '^#' "${BASEDIR}"/settings.conf | grep '=' ) #import settings + +if [[ "${receiver}" = "Quectel LC29HBS" ]]; then + speed="${com_port_settings%%:*}" + python3 "${BASEDIR}"/tools/nmea.py --file "${BASEDIR}"/receiver_cfg/LC29HBS_Configure.txt /dev/"${com_port}" "${speed}" 3 + echo Configuring Quectel LC29HBS on /dev/"${com_port}" at speed "${speed}" +fi diff --git a/receiver_cfg/LC29HBS_Configure.txt b/receiver_cfg/LC29HBS_Configure.txt new file mode 100644 index 00000000..897637c7 --- /dev/null +++ b/receiver_cfg/LC29HBS_Configure.txt @@ -0,0 +1,35 @@ +#Enable MSM7 messages +$PAIR432,1 + +#Enable Station Reference Message 1005 +$PAIR434,1 + +#Enable Ephemeris messages +$PAIR436,1 + +#Enable NMEA GGA Time, position, and fix related data +$PAIR062,0,1 + +#Enable NMEA GLL Position data: position fix, time of position fix, and status +$PAIR062,1,1 + +#Enable NMEA GSA GPS DOP and active satellites +$PAIR062,2,1 + +#Enable NMEA GSV Satellite information +$PAIR062,3,1 + +#Enable NMEA RMC Position, velocity, and time +$PAIR062,4,1 + +#Enable NMEA VTG Track made good and speed over ground +$PAIR062,5,1 + +#Enable NMEA ZDA UTC day, month, and year, and local time zone offset +$PAIR062,6,1 + +#Enable NMEA GRS GRS range residuals +$PAIR062,7,1 + +#Enable NMEA GST Position error statistics +$PAIR062,8,1 diff --git a/receiver_cfg/LC29HBS_Factory_Defaults.txt b/receiver_cfg/LC29HBS_Factory_Defaults.txt new file mode 100644 index 00000000..fa51a689 --- /dev/null +++ b/receiver_cfg/LC29HBS_Factory_Defaults.txt @@ -0,0 +1,2 @@ +# Restore factory defaults +$PQTMRESTOREPAR diff --git a/receiver_cfg/LC29HBS_Reboot.txt b/receiver_cfg/LC29HBS_Reboot.txt new file mode 100644 index 00000000..b097d5fe --- /dev/null +++ b/receiver_cfg/LC29HBS_Reboot.txt @@ -0,0 +1,9 @@ +#Power off the GNSS +$PAIR003 + +#SLEEP# 1000 + +#Power on the GNSS +$PAIR002 + +#SLEEP# 5000 diff --git a/receiver_cfg/LC29HBS_Save.txt b/receiver_cfg/LC29HBS_Save.txt new file mode 100644 index 00000000..2e1fe977 --- /dev/null +++ b/receiver_cfg/LC29HBS_Save.txt @@ -0,0 +1,2 @@ +#Save parameters +$PQTMSAVEPAR diff --git a/receiver_cfg/LC29HBS_Set_Baud.txt b/receiver_cfg/LC29HBS_Set_Baud.txt new file mode 100644 index 00000000..000b7483 --- /dev/null +++ b/receiver_cfg/LC29HBS_Set_Baud.txt @@ -0,0 +1,2 @@ +#Crank the baud rate up (Could also go 3000000) +$PAIR864,0,0,921600 diff --git a/receiver_cfg/LC29HBS_Version.txt b/receiver_cfg/LC29HBS_Version.txt new file mode 100644 index 00000000..216e921c --- /dev/null +++ b/receiver_cfg/LC29HBS_Version.txt @@ -0,0 +1,2 @@ +# Get the model and firmware version +$PQTMVERNO diff --git a/tools/install.sh b/tools/install.sh index a3972d52..7da95a2b 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -421,13 +421,23 @@ detect_gnss() { echo '################################' systemctl is-active --quiet str2str_tcp.service && sudo systemctl stop str2str_tcp.service && echo 'Stopping str2str_tcp service' for port in ttyS1 serial0 ttyS2 ttyS3 ttyS0; do - for port_speed in 115200 57600 38400 19200 9600; do + for port_speed in 3000000 921600 115200 57600 38400 19200 9600; do echo 'DETECTION ON ' $port ' at ' $port_speed + # Detect u-blox ZED-F9P receivers if [[ $(python3 "${rtkbase_path}"/tools/ubxtool -f /dev/$port -s $port_speed -p MON-VER -w 5 2>/dev/null) =~ 'ZED-F9P' ]]; then detected_gnss[0]=$port detected_gnss[1]='u-blox' detected_gnss[2]=$port_speed - #echo 'U-blox ZED-F9P DETECTED ON '$port $port_speed + #echo 'U-blox ZED-F9P DETECTED ON ' $port ' at ' $port_speed + break + fi + + # Detect Quectel LC29H-BS receivers using nmea.py + if [[ $(python3 "${rtkbase_path}"/tools/nmea.py --file "${rtkbase_path}"/receiver_cfg/LC29HBS_Version.txt /dev/$port $port_speed 3 2>/dev/null) =~ 'LC29HBS' ]]; then + detected_gnss[0]=$port + detected_gnss[1]='LC29H-BS' + detected_gnss[2]=$port_speed + #echo 'Quectel LC29H-BS DETECTED ON ' $port ' at ' $port_speed break fi sleep 1 @@ -536,11 +546,30 @@ configure_gnss(){ sudo -u "${RTKBASE_USER}" sed -i s/^receiver=.*/receiver=\'Septentrio_Mosaic-X5\'/ "${rtkbase_path}"/settings.conf && \ sudo -u "${RTKBASE_USER}" sed -i s/^receiver_format=.*/receiver_format=\'sbf\'/ "${rtkbase_path}"/settings.conf return $? + elif [[ $(python3 "${rtkbase_path}"/tools/nmea.py --file "${rtkbase_path}"/receiver_cfg/LC29HBS_Version.txt /dev/"${com_port}" ${com_port_settings%%:*} 3 2>/dev/null) =~ 'LC29HBS' ]]; then + # Factory reset and configure the module + python3 "${rtkbase_path}"/tools/nmea.py --verbose --file "${rtkbase_path}"/receiver_cfg/LC29HBS_Factory_Defaults.txt /dev/"${com_port}" ${com_port_settings%%:*} 3 >>"${rtkbase_path}"/logs/LC29HBS_Configure.log && \ + python3 "${rtkbase_path}"/tools/nmea.py --verbose --file "${rtkbase_path}"/receiver_cfg/LC29HBS_Set_Baud.txt /dev/"${com_port}" ${com_port_settings%%:*} 3 >>"${rtkbase_path}"/logs/LC29HBS_Configure.log && \ + python3 "${rtkbase_path}"/tools/nmea.py --verbose --file "${rtkbase_path}"/receiver_cfg/LC29HBS_Save.txt /dev/"${com_port}" ${com_port_settings%%:*} 3 >>"${rtkbase_path}"/logs/LC29HBS_Configure.log && \ + python3 "${rtkbase_path}"/tools/nmea.py --verbose --file "${rtkbase_path}"/receiver_cfg/LC29HBS_Reboot.txt /dev/"${com_port}" ${com_port_settings%%:*} 3 >>"${rtkbase_path}"/logs/LC29HBS_Configure.log && \ + + # Speed has now been configured to 921600 + speed=921600 + version_str="$(python3 "${rtkbase_path}"/tools/nmea.py --file "${rtkbase_path}"/receiver_cfg/LC29HBS_Version.txt /dev/"${com_port}" ${speed} 3 2>/dev/null)" + firmware="`echo "$version_str" | cut -d , -f 2`" + if [[ -z "$version_str" ]]; then + echo "Could not get LC29HBS version string after rebooting the module, try power cycling the module." + return 1 + fi + sudo -u "${RTKBASE_USER}" sed -i s/^receiver_firmware=.*/receiver_firmware=\'${firmware}\'/ "${rtkbase_path}"/settings.conf && \ + sudo -u "${RTKBASE_USER}" sed -i s/^com_port_settings=.*/com_port_settings=\'921600:8:n:1\'/ "${rtkbase_path}"/settings.conf && \ + sudo -u "${RTKBASE_USER}" sed -i s/^receiver=.*/receiver=\'Quectel LC29HBS\'/ "${rtkbase_path}"/settings.conf && \ + sudo -u "${RTKBASE_USER}" sed -i s/^receiver_format=.*/receiver_format=\'rtcm3\'/ "${rtkbase_path}"/settings.conf + return $? else echo 'Failed to configure the Gnss receiver' return 1 fi - else echo 'No Gnss receiver has been set. We can'\''t configure' return 1 @@ -611,6 +640,7 @@ start_services() { systemctl daemon-reload systemctl enable --now rtkbase_web.service systemctl enable --now str2str_tcp.service + systemctl enable --now configure_gps.service systemctl restart gpsd.service systemctl restart chrony.service systemctl enable --now rtkbase_archive.timer diff --git a/tools/nmea.py b/tools/nmea.py new file mode 100644 index 00000000..93baf747 --- /dev/null +++ b/tools/nmea.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 + +import argparse +import serial +import time + +# Function to calculate NMEA checksum +def calculate_nmea_checksum(nmea_sentence): + checksum = 0 + # Iterate through each character after the starting '$' and before '*' + for char in nmea_sentence[1:]: + checksum ^= ord(char) + return f"{nmea_sentence}*{checksum:02X}" + +# Function to append checksum if not provided +def append_checksum_if_missing(nmea_sentence): + if '*' not in nmea_sentence: + # Calculate and append checksum if '*' is missing + return calculate_nmea_checksum(nmea_sentence) + return nmea_sentence + +# Function to handle serial communication +def send_nmea_command(port, speed, timeout, nmea_command, verbose): + # Open serial port + try: + with serial.Serial(port, baudrate=speed, timeout=timeout) as ser: + # Append checksum if missing + nmea_command_with_checksum = append_checksum_if_missing(nmea_command) + + # Write NMEA command to the serial port + ser.write((nmea_command_with_checksum + '\r\n').encode('ascii')) + if verbose: + print(f"Sent command: {nmea_command_with_checksum}") + + # Wait for the response + start_time = time.time() + while time.time() - start_time < timeout: + try: + response = ser.readline().decode('ascii', errors='ignore').strip() + # Process only valid ASCII responses + if response and response.startswith('$'): + if verbose: + print(f"Received response: {response}") + return response + except UnicodeDecodeError as e: + # Skip non-ASCII responses (likely RTCM3 messages) + if verbose: + print(f"Non-ASCII data skipped: {e}") + if verbose: + print("Timeout: No matching response received.") + except serial.SerialException as e: + print(f"Error opening serial port: {e}") + +# Function to read NMEA commands from a file and ignore lines starting with '#' and blank lines +def read_commands_from_file(file_path): + try: + with open(file_path, 'r') as file: + commands = [] + for line in file: + line = line.strip() + # Ignore blank lines and lines starting with '#' + if line and not line.startswith('#') or line.startswith('#SLEEP#'): + commands.append(line) + return commands + except FileNotFoundError: + print(f"Error: File '{file_path}' not found.") + return [] + +# Function to handle the sleep command in the file +def handle_sleep_command(command, verbose): + try: + sleep_time_ms = int(command.split('#SLEEP# ')[1]) + if verbose: + print(f"Sleeping for {sleep_time_ms} ms") + time.sleep(sleep_time_ms / 1000) # Convert to seconds + except (IndexError, ValueError): + print(f"Invalid sleep command format: {command}") + +if __name__ == "__main__": + # Parse command line arguments + parser = argparse.ArgumentParser(description="Send NMEA commands to Quectel LC29H module") + parser.add_argument('port', type=str, help='Serial port to use (e.g., /dev/ttyUSB0 or COM3)') + parser.add_argument('speed', type=int, help='Baud rate (e.g., 9600)') + parser.add_argument('timeout', type=int, help='Timeout in seconds') + parser.add_argument('command', nargs='?', type=str, help='NMEA command to send (optional, overrides file)') + parser.add_argument('--file', type=str, help='File with NMEA commands to send') + parser.add_argument('--verbose', action='store_true', help='Enable verbose output for tracing') + + args = parser.parse_args() + + # Determine which commands to send (from file or argument) + if args.command: + # Send the provided command as an argument + nmea_commands = [args.command] + elif args.file: + # Read commands from the file, ignoring comments and blank lines + nmea_commands = read_commands_from_file(args.file) + else: + print("Error: You must provide either a command or a file containing commands.") + exit(1) + + # Send each NMEA command from the list + for command in nmea_commands: + if command.startswith('#SLEEP#'): + handle_sleep_command(command, args.verbose) + else: + send_nmea_command(args.port, args.speed, args.timeout, command, args.verbose) diff --git a/tools/uninstall.sh b/tools/uninstall.sh index 37d86d40..0d587a46 100755 --- a/tools/uninstall.sh +++ b/tools/uninstall.sh @@ -18,7 +18,8 @@ for service_name in str2str_tcp.service \ rtkbase_archive.timer \ modem_check.service \ modem_check.timer \ - rtkbase_gnss_web_proxy.service + rtkbase_gnss_web_proxy.service \ + configure_gps.service do echo 'Deleting ' "${service_name}" systemctl stop "${service_name}" diff --git a/unit/configure_gps.service b/unit/configure_gps.service new file mode 100644 index 00000000..1f7861b0 --- /dev/null +++ b/unit/configure_gps.service @@ -0,0 +1,19 @@ +[Unit] +Description=Configure GPS +Before=str2str_tcp.service +#After=network-online.target +#Wants=network-online.target +#Requires=network-online.target + +[Service] +Type=oneshot +RemainAfterExit=yes +User={user} +ExecStart={script_path}/configure_gps.sh +Restart=no +ProtectHome=read-only +ProtectSystem=strict +ReadWritePaths={script_path} + +[Install] +WantedBy=multi-user.target diff --git a/web_app/server.py b/web_app/server.py index 4c822aa2..224768ba 100755 --- a/web_app/server.py +++ b/web_app/server.py @@ -105,6 +105,7 @@ {'service_unit' : 'rtkbase_archive.timer', "name" : "archive_timer"}, {'service_unit' : 'rtkbase_archive.service', "name" : "archive_service"}, {'service_unit' : 'rtkbase_raw2nmea.service', "name" : "raw2nmea"}, + {'service_unit' : 'configure_gps.service', "name" : "configure_gps"}, ] #Delay before rtkrcv will stop if no user is on status.html page