Skip to content

Commit 247a970

Browse files
committed
Add support for Quectel LC29H-BS
Signed-off-by: Alastair D'Silva <alastair@d-silva.org>
1 parent 03f614a commit 247a970

File tree

12 files changed

+231
-6
lines changed

12 files changed

+231
-6
lines changed

configure_gps.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/bash
2+
#
3+
# configure_gps.sh: script to provide GPS modules with commands
4+
# that are not saved in flash on the module (ie. they must be provided
5+
# each time the module is started).
6+
7+
8+
BASEDIR="$(dirname "$0")"
9+
source <( grep -v '^#' "${BASEDIR}"/settings.conf | grep '=' ) #import settings
10+
11+
if [[ "${receiver}" = "Quectel LC29HBS" ]]; then
12+
speed="${com_port_settings%%:*}"
13+
python3 "${BASEDIR}"/tools/nmea.py --file "${BASEDIR}"/receiver_cfg/LC29HBS_Configure.txt /dev/"${com_port}" "${speed}" 3
14+
echo Configuring Quectel LC29HBS on /dev/"${com_port}" at speed "${speed}"
15+
fi

receiver_cfg/LC29HBS_Configure.txt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#Enable MSM7 messages
2+
$PAIR432,1
3+
4+
#Enable Station Reference Message 1005
5+
$PAIR434,1
6+
7+
#Enable Ephemeris messages
8+
$PAIR436,1
9+
10+
#Enable NMEA GGA Time, position, and fix related data
11+
$PAIR062,0,1
12+
13+
#Enable NMEA GLL Position data: position fix, time of position fix, and status
14+
$PAIR062,1,1
15+
16+
#Enable NMEA GSA GPS DOP and active satellites
17+
$PAIR062,2,1
18+
19+
#Enable NMEA GSV Satellite information
20+
$PAIR062,3,1
21+
22+
#Enable NMEA RMC Position, velocity, and time
23+
$PAIR062,4,1
24+
25+
#Enable NMEA VTG Track made good and speed over ground
26+
$PAIR062,5,1
27+
28+
#Enable NMEA ZDA UTC day, month, and year, and local time zone offset
29+
$PAIR062,6,1
30+
31+
#Enable NMEA GRS GRS range residuals
32+
$PAIR062,7,1
33+
34+
#Enable NMEA GST Position error statistics
35+
$PAIR062,8,1
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Restore factory defaults
2+
$PQTMRESTOREPAR

receiver_cfg/LC29HBS_Reboot.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#Power off the GNSS
2+
$PAIR003
3+
4+
#SLEEP# 1000
5+
6+
#Power on the GNSS
7+
$PAIR002
8+
9+
#SLEEP# 5000

receiver_cfg/LC29HBS_Save.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#Save parameters
2+
$PQTMSAVEPAR

receiver_cfg/LC29HBS_Set_Baud.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#Crank the baud rate up (Could also go 3000000)
2+
$PAIR864,0,0,921600

receiver_cfg/LC29HBS_Version.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Get the model and firmware version
2+
$PQTMVERNO

tools/install.sh

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ detect_gnss() {
407407
done
408408
if [[ ${#detected_gnss[*]} -ne 2 ]]; then
409409
vendor_and_product_ids=$(lsusb | grep -i "u-blox\|Septentrio" | grep -Eo "[0-9A-Za-z]+:[0-9A-Za-z]+")
410-
if [[ -z "$vendor_and_product_ids" ]]; then
410+
if [[ -z "$vendor_and_product_ids" ]]; then
411411
echo 'NO USB GNSS RECEIVER DETECTED'
412412
echo 'YOU CAN REDETECT IT FROM THE WEB UI'
413413
#return 1
@@ -426,13 +426,23 @@ detect_gnss() {
426426
systemctl is-active --quiet str2str_tcp.service && sudo systemctl stop str2str_tcp.service && echo 'Stopping str2str_tcp service'
427427
# TODO remove port if not available in /dev/
428428
for port in ttyS0 ttyUSB0 ttyUSB1 ttyUSB2 serial0 ttyS1 ttyS2 ttyS3 ttyS4 ttyS5; do
429-
for port_speed in 115200 57600 38400 19200 9600; do
429+
for port_speed in 3000000 921600 115200 57600 38400 19200 9600; do
430430
echo 'DETECTION ON ' $port ' at ' $port_speed
431+
# Detect u-blox ZED-F9P receivers
431432
if [[ $(python3 "${rtkbase_path}"/tools/ubxtool -f /dev/$port -s $port_speed -p MON-VER -w 5 2>/dev/null) =~ 'ZED-F9P' ]]; then
432433
detected_gnss[0]=$port
433434
detected_gnss[1]='u-blox'
434435
detected_gnss[2]=$port_speed
435-
#echo 'U-blox ZED-F9P DETECTED ON '$port $port_speed
436+
#echo 'U-blox ZED-F9P DETECTED ON ' $port ' at ' $port_speed
437+
break
438+
fi
439+
440+
# Detect Quectel LC29H-BS receivers using nmea.py
441+
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
442+
detected_gnss[0]=$port
443+
detected_gnss[1]='LC29H-BS'
444+
detected_gnss[2]=$port_speed
445+
#echo 'Quectel LC29H-BS DETECTED ON ' $port ' at ' $port_speed
436446
break
437447
elif { model=$(python3 "${rtkbase_path}"/tools/unicore_tool.py --port /dev/$port --baudrate $port_speed --command get_model 2>/dev/null) ; [[ "${model}" == 'UM98'[0-2] ]] ;}; then
438448
detected_gnss[0]=$port
@@ -594,7 +604,26 @@ configure_gnss(){
594604
echo 'Failed to configure the Gnss receiver'
595605
return 1
596606
fi
597-
607+
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
608+
# Factory reset and configure the module
609+
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 && \
610+
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 && \
611+
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 && \
612+
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 && \
613+
614+
# Speed has now been configured to 921600
615+
speed=921600
616+
version_str="$(python3 "${rtkbase_path}"/tools/nmea.py --file "${rtkbase_path}"/receiver_cfg/LC29HBS_Version.txt /dev/"${com_port}" ${speed} 3 2>/dev/null)"
617+
firmware="`echo "$version_str" | cut -d , -f 2`"
618+
if [[ -z "$version_str" ]]; then
619+
echo "Could not get LC29HBS version string after rebooting the module, try power cycling the module."
620+
return 1
621+
fi
622+
sudo -u "${RTKBASE_USER}" sed -i s/^receiver_firmware=.*/receiver_firmware=\'${firmware}\'/ "${rtkbase_path}"/settings.conf && \
623+
sudo -u "${RTKBASE_USER}" sed -i s/^com_port_settings=.*/com_port_settings=\'921600:8:n:1\'/ "${rtkbase_path}"/settings.conf && \
624+
sudo -u "${RTKBASE_USER}" sed -i s/^receiver=.*/receiver=\'Quectel LC29HBS\'/ "${rtkbase_path}"/settings.conf && \
625+
sudo -u "${RTKBASE_USER}" sed -i s/^receiver_format=.*/receiver_format=\'rtcm3\'/ "${rtkbase_path}"/settings.conf
626+
return $?
598627
else
599628
echo 'No Gnss receiver has been set. We can'\''t configure'
600629
return 1
@@ -665,6 +694,7 @@ start_services() {
665694
systemctl daemon-reload
666695
systemctl enable --now rtkbase_web.service
667696
systemctl enable --now str2str_tcp.service
697+
systemctl enable --now configure_gps.service
668698
systemctl restart gpsd.service
669699
systemctl restart chrony.service
670700
systemctl enable --now rtkbase_archive.timer

tools/nmea.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import serial
5+
import time
6+
7+
# Function to calculate NMEA checksum
8+
def calculate_nmea_checksum(nmea_sentence):
9+
checksum = 0
10+
# Iterate through each character after the starting '$' and before '*'
11+
for char in nmea_sentence[1:]:
12+
checksum ^= ord(char)
13+
return f"{nmea_sentence}*{checksum:02X}"
14+
15+
# Function to append checksum if not provided
16+
def append_checksum_if_missing(nmea_sentence):
17+
if '*' not in nmea_sentence:
18+
# Calculate and append checksum if '*' is missing
19+
return calculate_nmea_checksum(nmea_sentence)
20+
return nmea_sentence
21+
22+
# Function to handle serial communication
23+
def send_nmea_command(port, speed, timeout, nmea_command, verbose):
24+
# Open serial port
25+
try:
26+
with serial.Serial(port, baudrate=speed, timeout=timeout) as ser:
27+
# Append checksum if missing
28+
nmea_command_with_checksum = append_checksum_if_missing(nmea_command)
29+
30+
# Write NMEA command to the serial port
31+
ser.write((nmea_command_with_checksum + '\r\n').encode('ascii'))
32+
if verbose:
33+
print(f"Sent command: {nmea_command_with_checksum}")
34+
35+
# Wait for the response
36+
start_time = time.time()
37+
while time.time() - start_time < timeout:
38+
try:
39+
response = ser.readline().decode('ascii', errors='ignore').strip()
40+
# Process only valid ASCII responses
41+
if response and response.startswith('$'):
42+
if verbose:
43+
print(f"Received response: {response}")
44+
return response
45+
except UnicodeDecodeError as e:
46+
# Skip non-ASCII responses (likely RTCM3 messages)
47+
if verbose:
48+
print(f"Non-ASCII data skipped: {e}")
49+
if verbose:
50+
print("Timeout: No matching response received.")
51+
except serial.SerialException as e:
52+
print(f"Error opening serial port: {e}")
53+
54+
# Function to read NMEA commands from a file and ignore lines starting with '#' and blank lines
55+
def read_commands_from_file(file_path):
56+
try:
57+
with open(file_path, 'r') as file:
58+
commands = []
59+
for line in file:
60+
line = line.strip()
61+
# Ignore blank lines and lines starting with '#'
62+
if line and not line.startswith('#') or line.startswith('#SLEEP#'):
63+
commands.append(line)
64+
return commands
65+
except FileNotFoundError:
66+
print(f"Error: File '{file_path}' not found.")
67+
return []
68+
69+
# Function to handle the sleep command in the file
70+
def handle_sleep_command(command, verbose):
71+
try:
72+
sleep_time_ms = int(command.split('#SLEEP# ')[1])
73+
if verbose:
74+
print(f"Sleeping for {sleep_time_ms} ms")
75+
time.sleep(sleep_time_ms / 1000) # Convert to seconds
76+
except (IndexError, ValueError):
77+
print(f"Invalid sleep command format: {command}")
78+
79+
if __name__ == "__main__":
80+
# Parse command line arguments
81+
parser = argparse.ArgumentParser(description="Send NMEA commands to Quectel LC29H module")
82+
parser.add_argument('port', type=str, help='Serial port to use (e.g., /dev/ttyUSB0 or COM3)')
83+
parser.add_argument('speed', type=int, help='Baud rate (e.g., 9600)')
84+
parser.add_argument('timeout', type=int, help='Timeout in seconds')
85+
parser.add_argument('command', nargs='?', type=str, help='NMEA command to send (optional, overrides file)')
86+
parser.add_argument('--file', type=str, help='File with NMEA commands to send')
87+
parser.add_argument('--verbose', action='store_true', help='Enable verbose output for tracing')
88+
89+
args = parser.parse_args()
90+
91+
# Determine which commands to send (from file or argument)
92+
if args.command:
93+
# Send the provided command as an argument
94+
nmea_commands = [args.command]
95+
elif args.file:
96+
# Read commands from the file, ignoring comments and blank lines
97+
nmea_commands = read_commands_from_file(args.file)
98+
else:
99+
print("Error: You must provide either a command or a file containing commands.")
100+
exit(1)
101+
102+
# Send each NMEA command from the list
103+
for command in nmea_commands:
104+
if command.startswith('#SLEEP#'):
105+
handle_sleep_command(command, args.verbose)
106+
else:
107+
send_nmea_command(args.port, args.speed, args.timeout, command, args.verbose)

tools/uninstall.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ for service_name in str2str_tcp.service \
1818
rtkbase_archive.timer \
1919
modem_check.service \
2020
modem_check.timer \
21-
rtkbase_gnss_web_proxy.service
21+
rtkbase_gnss_web_proxy.service \
22+
configure_gps.service
2223
do
2324
echo 'Deleting ' "${service_name}"
2425
systemctl stop "${service_name}"

0 commit comments

Comments
 (0)