Skip to content

Commit cab7006

Browse files
committed
Add support for Quectel LC29H-BS
Signed-off-by: Alastair D'Silva <[email protected]>
1 parent 6a5a7ad commit cab7006

12 files changed

+229
-4
lines changed

configure_gps.sh

+15
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

+35
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Restore factory defaults
2+
$PQTMRESTOREPAR

receiver_cfg/LC29HBS_Reboot.txt

+9
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

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#Save parameters
2+
$PQTMSAVEPAR

receiver_cfg/LC29HBS_Set_Baud.txt

+2
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

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Get the model and firmware version
2+
$PQTMVERNO

tools/install.sh

+33-3
Original file line numberDiff line numberDiff line change
@@ -421,13 +421,23 @@ detect_gnss() {
421421
echo '################################'
422422
systemctl is-active --quiet str2str_tcp.service && sudo systemctl stop str2str_tcp.service && echo 'Stopping str2str_tcp service'
423423
for port in ttyS1 serial0 ttyS2 ttyS3 ttyS0; do
424-
for port_speed in 115200 57600 38400 19200 9600; do
424+
for port_speed in 3000000 921600 115200 57600 38400 19200 9600; do
425425
echo 'DETECTION ON ' $port ' at ' $port_speed
426+
# Detect u-blox ZED-F9P receivers
426427
if [[ $(python3 "${rtkbase_path}"/tools/ubxtool -f /dev/$port -s $port_speed -p MON-VER -w 5 2>/dev/null) =~ 'ZED-F9P' ]]; then
427428
detected_gnss[0]=$port
428429
detected_gnss[1]='u-blox'
429430
detected_gnss[2]=$port_speed
430-
#echo 'U-blox ZED-F9P DETECTED ON '$port $port_speed
431+
#echo 'U-blox ZED-F9P DETECTED ON ' $port ' at ' $port_speed
432+
break
433+
fi
434+
435+
# Detect Quectel LC29H-BS receivers using nmea.py
436+
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
437+
detected_gnss[0]=$port
438+
detected_gnss[1]='LC29H-BS'
439+
detected_gnss[2]=$port_speed
440+
#echo 'Quectel LC29H-BS DETECTED ON ' $port ' at ' $port_speed
431441
break
432442
fi
433443
sleep 1
@@ -536,11 +546,30 @@ configure_gnss(){
536546
sudo -u "${RTKBASE_USER}" sed -i s/^receiver=.*/receiver=\'Septentrio_Mosaic-X5\'/ "${rtkbase_path}"/settings.conf && \
537547
sudo -u "${RTKBASE_USER}" sed -i s/^receiver_format=.*/receiver_format=\'sbf\'/ "${rtkbase_path}"/settings.conf
538548
return $?
549+
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
550+
# Factory reset and configure the module
551+
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 && \
552+
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 && \
553+
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 && \
554+
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 && \
555+
556+
# Speed has now been configured to 921600
557+
speed=921600
558+
version_str="$(python3 "${rtkbase_path}"/tools/nmea.py --file "${rtkbase_path}"/receiver_cfg/LC29HBS_Version.txt /dev/"${com_port}" ${speed} 3 2>/dev/null)"
559+
firmware="`echo "$version_str" | cut -d , -f 2`"
560+
if [[ -z "$version_str" ]]; then
561+
echo "Could not get LC29HBS version string after rebooting the module, try power cycling the module."
562+
return 1
563+
fi
564+
sudo -u "${RTKBASE_USER}" sed -i s/^receiver_firmware=.*/receiver_firmware=\'${firmware}\'/ "${rtkbase_path}"/settings.conf && \
565+
sudo -u "${RTKBASE_USER}" sed -i s/^com_port_settings=.*/com_port_settings=\'921600:8:n:1\'/ "${rtkbase_path}"/settings.conf && \
566+
sudo -u "${RTKBASE_USER}" sed -i s/^receiver=.*/receiver=\'Quectel LC29HBS\'/ "${rtkbase_path}"/settings.conf && \
567+
sudo -u "${RTKBASE_USER}" sed -i s/^receiver_format=.*/receiver_format=\'rtcm3\'/ "${rtkbase_path}"/settings.conf
568+
return $?
539569
else
540570
echo 'Failed to configure the Gnss receiver'
541571
return 1
542572
fi
543-
544573
else
545574
echo 'No Gnss receiver has been set. We can'\''t configure'
546575
return 1
@@ -611,6 +640,7 @@ start_services() {
611640
systemctl daemon-reload
612641
systemctl enable --now rtkbase_web.service
613642
systemctl enable --now str2str_tcp.service
643+
systemctl enable --now configure_gps.service
614644
systemctl restart gpsd.service
615645
systemctl restart chrony.service
616646
systemctl enable --now rtkbase_archive.timer

tools/nmea.py

+107
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

+2-1
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}"

unit/configure_gps.service

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[Unit]
2+
Description=Configure GPS
3+
Before=str2str_tcp.service
4+
#After=network-online.target
5+
#Wants=network-online.target
6+
#Requires=network-online.target
7+
8+
[Service]
9+
Type=oneshot
10+
RemainAfterExit=yes
11+
User={user}
12+
ExecStart={script_path}/configure_gps.sh
13+
Restart=no
14+
ProtectHome=read-only
15+
ProtectSystem=strict
16+
ReadWritePaths={script_path}
17+
18+
[Install]
19+
WantedBy=multi-user.target

web_app/server.py

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
{'service_unit' : 'rtkbase_archive.timer', "name" : "archive_timer"},
106106
{'service_unit' : 'rtkbase_archive.service', "name" : "archive_service"},
107107
{'service_unit' : 'rtkbase_raw2nmea.service', "name" : "raw2nmea"},
108+
{'service_unit' : 'configure_gps.service', "name" : "configure_gps"},
108109
]
109110

110111
#Delay before rtkrcv will stop if no user is on status.html page

0 commit comments

Comments
 (0)