Skip to content

Commit

Permalink
Merge pull request #398 from Stefal/mosaic-x5
Browse files Browse the repository at this point in the history
Septentrio Mosaic-x5 support
  • Loading branch information
Stefal authored May 28, 2024
2 parents 5686338 + e7b4dde commit 3b1b355
Show file tree
Hide file tree
Showing 18 changed files with 545 additions and 23 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## [] - not released
### Added
- Septentrio Mosaic-X5 detection and configuration
- Reverse proxy server with Rtkbase authentication, for Mosaic-X5 web interface
- Added description below form input. #381
### Changed
### Deprecated
Expand Down
17 changes: 17 additions & 0 deletions receiver_cfg/Septentrio_Mosaic-X5.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Config file for using a Septentrio Mosaic X5 with RTKBase
setSBFOutput, Stream1, USB1
setSBFOutput, Stream1, , , sec1
setSBFOutput, Stream1, , MeasEpoch+MeasExtra+EndOfMeas
setSBFOutput, Stream1, , +GPSRawCA+GPSRawL2C+GPSRawL5
setSBFOutput, Stream1, , +GLORawCA
setSBFOutput, Stream1, , +GALRawFNAV+GALRawINAV+GALRawCNAV
setSBFOutput, Stream1, , +BDSRaw+BDSRawB1C+BDSRawB2a+BDSRawB2b
setSBFOutput, Stream1, , +QZSRawL1CA+QZSRawL2C+QZSRawL5
setSBFOutput, Stream1, , +NAVICRaw
setSBFOutput, Stream1, , +GEORawL1+GEORawL5
setSignalTracking, GPSL1CA+GPSL1PY+GPSL2PY+GPSL2C+GPSL5+GLOL1CA+GLOL2P+GLOL2CA+GLOL3+GALL1BC+GALE6BC+GALE5a+GALE5b+GALE5+GEOL1+GEOL5+BDSB1I+BDSB2I+BDSB3I+BDSB1C+BDSB2a+BDSB2b+QZSL1CA+QZSL2C+QZSL5+QZSL1CB+NAVICL5
setSBFOutput, Stream1, , +PVTGeodetic+ChannelStatus+ReceiverStatus+SatVisibility+ReceiverTime
setPVTMode, Static
setPPSParameters, sec1, Low2High, , UTC
setUSBInternetAccess, on
#END
4 changes: 2 additions & 2 deletions run_cast.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# You can read the RTKLIB manual for more str2str informations:
# https://github.com/tomojitakasu/RTKLIB

BASEDIR=$(dirname "$0")
source <( grep '=' ${BASEDIR}/settings.conf ) #import settings
BASEDIR="$(dirname "$0")"
source <( grep -v '^#' "${BASEDIR}"/settings.conf | grep '=' ) #import settings

receiver_info="RTKBase ${receiver},${version} ${receiver_firmware}"
in_serial="serial://${com_port}:${com_port_settings}#${receiver_format}"
Expand Down
4 changes: 4 additions & 0 deletions settings.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ tcp_port='5015'
ext_tcp_source=''
#ext_tcp_port is the port used for ext_tcp_source
ext_tcp_port=''
#ip address of the integrated web service (ie on Mosaic X5)
gnss_rcv_web_ip=192.168.3.1
#port number for the Flask proxy app used to display the Gnss receiver web service
gnss_rcv_web_proxy_port=9090

[local_storage]

Expand Down
49 changes: 37 additions & 12 deletions tools/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ install_dependencies() {
echo 'INSTALLING DEPENDENCIES'
echo '################################'
apt-get "${APT_TIMEOUT}" update -y || exit 1
apt-get "${APT_TIMEOUT}" install -y git build-essential pps-tools python3-pip python3-venv python3-dev python3-setuptools python3-wheel python3-serial libsystemd-dev bc dos2unix socat zip unzip pkg-config psmisc proj-bin || exit 1
apt-get "${APT_TIMEOUT}" install -y git build-essential pps-tools python3-pip python3-venv python3-dev python3-setuptools python3-wheel python3-serial libsystemd-dev bc dos2unix socat zip unzip pkg-config psmisc proj-bin nftables || exit 1
apt-get install -y libxml2-dev libxslt-dev || exit 1 # needed for lxml (for pystemd)
#apt-get "${APT_TIMEOUT}" upgrade -y
}
Expand Down Expand Up @@ -190,7 +190,6 @@ install_rtklib() {
cp "${rtkbase_path}"'/tools/bin/rtklib_b34i/'"${arch_package}"/convbin /usr/local/bin/
else
echo 'No binary available for ' "${computer_model}" ' - ' "${arch_package}" '. We will build it from source'
echo 'exit test' ; exit
_compil_rtklib
fi
}
Expand Down Expand Up @@ -367,6 +366,11 @@ rtkbase_requirements(){
sudo -u "${RTKBASE_USER}" "${python_venv}" -m pip install -r "${rtkbase_path}"/web_app/requirements.txt --extra-index-url https://www.piwheels.org/simple
#when we will be able to launch the web server without root, we will use
#sudo -u $(logname) python3 -m pip install -r requirements.txt --user.

#Installing requirements for Cellular modem. Installing them during the Armbian firstrun doesn't work because the network isn't fully up.
sudo -u "${RTKBASE_USER}" "${rtkbase_path}/venv/bin/python" -m pip install nmcli --extra-index-url https://www.piwheels.org/simple
sudo -u "${RTKBASE_USER}" "${rtkbase_path}/venv/bin/python" -m pip install git+https://github.com/Stefal/sim-modem.git

}

install_unit_files() {
Expand Down Expand Up @@ -401,15 +405,18 @@ detect_gnss() {
if [[ "$devname" == "bus/"* ]]; then continue; fi
eval "$(udevadm info -q property --export -p "${syspath}")"
if [[ -z "$ID_SERIAL" ]]; then continue; fi
if [[ "$ID_SERIAL" =~ (u-blox|skytraq) ]]
if [[ "$ID_SERIAL" =~ (u-blox|skytraq|Septentrio) ]]
then
detected_gnss[0]=$devname
detected_gnss[1]=$ID_SERIAL
#echo '/dev/'"${detected_gnss[0]}" ' - ' "${detected_gnss[1]}"
# If /dev/ttyGNSS is a symlink of the detected serial port, we've found the gnss receiver, break the loop.
# This test is useful with gnss receiver offering several serial ports (like mosaic X5). The Udev rule should symlink the right one with ttyGNSS
[[ '/dev/ttyGNSS' -ef '/dev/'"${detected_gnss[0]}" ]] && break
fi
done
if [[ ${#detected_gnss[*]} -ne 2 ]]; then
vendor_and_product_ids=$(lsusb | grep -i "u-blox" | grep -Eo "[0-9A-Za-z]+:[0-9A-Za-z]+")
vendor_and_product_ids=$(lsusb | grep -i "u-blox\|Septentrio" | grep -Eo "[0-9A-Za-z]+:[0-9A-Za-z]+")
if [[ -z "$vendor_and_product_ids" ]]; then
echo 'NO USB GNSS RECEIVER DETECTED'
echo 'YOU CAN REDETECT IT FROM THE WEB UI'
Expand All @@ -422,10 +429,10 @@ detect_gnss() {
fi
fi
# detection on uart port
echo '################################'
echo 'UART GNSS RECEIVER DETECTION'
echo '################################'
if [[ ${#detected_gnss[*]} -ne 2 ]]; then
echo '################################'
echo 'UART GNSS RECEIVER DETECTION'
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
Expand Down Expand Up @@ -493,14 +500,14 @@ configure_gnss(){
echo '################################'
if [ -d "${rtkbase_path}" ]
then
source <( grep '=' "${rtkbase_path}"/settings.conf )
source <( grep -v '^#' "${rtkbase_path}"/settings.conf | grep '=' )
systemctl is-active --quiet str2str_tcp.service && sudo systemctl stop str2str_tcp.service
#if the receiver is a U-Blox, launch the set_zed-f9p.sh. This script will reset the F9P and configure it with the corrects settings for rtkbase
#!!!!!!!!! CHECK THIS ON A REAL raspberry/orange Pi !!!!!!!!!!!
#if the receiver is a U-Blox F9P, launch the set_zed-f9p.sh. This script will reset the F9P and configure it with the corrects settings for rtkbase
if [[ $(python3 "${rtkbase_path}"/tools/ubxtool -f /dev/"${com_port}" -s ${com_port_settings%%:*} -p MON-VER) =~ 'ZED-F9P' ]]
then
#get F9P firmware release
firmware=$(python3 "${rtkbase_path}"/tools/ubxtool -f /dev/"${com_port}" -s ${com_port_settings%%:*} -p MON-VER | grep 'FWVER' | awk '{print $NF}')
echo 'F9P Firmware: ' "${firmware}"
sudo -u "${RTKBASE_USER}" sed -i s/^receiver_firmware=.*/receiver_firmware=\'${firmware}\'/ "${rtkbase_path}"/settings.conf
#configure the F9P for RTKBase
"${rtkbase_path}"/tools/set_zed-f9p.sh /dev/${com_port} ${com_port_settings%%:*} "${rtkbase_path}"/receiver_cfg/U-Blox_ZED-F9P_rtkbase.cfg && \
Expand All @@ -516,8 +523,26 @@ configure_gnss(){
sudo -u "${RTKBASE_USER}" sed -i s/^rtcm_serial_receiver_options=.*/rtcm_serial_receiver_options=\'-TADJ=1\'/ "${rtkbase_path}"/settings.conf && \
#remove SBAS Rtcm message (1107) as it is disabled in the F9P configuration.
sudo -u "${RTKBASE_USER}" sed -i -r '/^rtcm_/s/1107(\([0-9]+\))?,//' "${rtkbase_path}"/settings.conf && \
return $?

elif [[ $(python3 "${rtkbase_path}"/tools/sept_tool.py --port /dev/ttyGNSS_CTRL --baudrate ${com_port_settings%%:*} --command get_model) =~ 'mosaic-X5' ]]
then
#get mosaic-X5 firmware release
firmware="$(python3 "${rtkbase_path}"/tools/sept_tool.py --port /dev/ttyGNSS_CTRL --baudrate ${com_port_settings%%:*} --command get_firmware)" || firmware='?'
echo 'Mosaic-X5 Firmware: ' "${firmware}"
sudo -u "${RTKBASE_USER}" sed -i s/^receiver_firmware=.*/receiver_firmware=\'${firmware}\'/ "${rtkbase_path}"/settings.conf
#configure the mosaic-X5 for RTKBase
echo 'Resetting the mosaic-X5 settings....'
python3 "${rtkbase_path}"/tools/sept_tool.py --port /dev/ttyGNSS_CTRL --baudrate ${com_port_settings%%:*} --command reset
echo 'Waiting 30s for mosaic-X5 reboot'
sleep 30
echo 'Sending settings....'
python3 "${rtkbase_path}"/tools/sept_tool.py --port /dev/ttyGNSS_CTRL --baudrate ${com_port_settings%%:*} --command send_config_file "${rtkbase_path}"/receiver_cfg/Septentrio_Mosaic-X5.cfg --store
sudo -u "${RTKBASE_USER}" sed -i s/^com_port_settings=.*/com_port_settings=\'115200:8:n:1\'/ "${rtkbase_path}"/settings.conf && \
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 $?

else
echo 'No Gnss receiver has been set. We can'\''t configure'
return 1
Expand Down Expand Up @@ -576,8 +601,7 @@ _add_modem_port(){
}

_configure_modem(){
sudo -u "${RTKBASE_USER}" "${rtkbase_path}/venv/bin/python" -m pip install nmcli --extra-index-url https://www.piwheels.org/simple
python3 "${rtkbase_path}"/tools/modem_config.py --config
sudo -u "${RTKBASE_USER}" "${rtkbase_path}/venv/bin/python" "${rtkbase_path}"/tools/modem_config.py --config
"${rtkbase_path}"/tools/lte_network_mgmt.sh --connection_rename --lte_priority
}

Expand All @@ -593,6 +617,7 @@ start_services() {
systemctl restart gpsd.service
systemctl restart chrony.service
systemctl start rtkbase_archive.timer
grep -q "receiver='Septentrio_Mosaic-X5'" "${rtkbase_path}"/settings.conf && systemctl enable --now rtkbase_gnss_web_proxy.service
echo '################################'
echo 'END OF INSTALLATION'
echo 'You can open your browser to http://'"$(hostname -I)"
Expand Down
13 changes: 7 additions & 6 deletions tools/modem_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import sys
import os
import argparse
if os.getenv("SUDO_USER") is not None:
homedir = os.path.join("/home", os.getenv("SUDO_USER"))
else:
homedir = os.path.expanduser('~')
#if os.getenv("SUDO_USER") is not None:
# homedir = os.path.join("/home", os.getenv("SUDO_USER"))
#else:
# homedir = os.path.expanduser('~')

sys.path.insert(1, os.path.join(homedir, "sim-modem"))
from src.sim_modem import *
#sys.path.insert(1, os.path.join(homedir, "sim-modem"))
#from src.sim_modem import *
from sim_modem import *

def arg_parse():
parser=argparse.ArgumentParser(
Expand Down
42 changes: 42 additions & 0 deletions tools/sept_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#! /usr/bin/env python3

import argparse
from septentrio.septentrio_cmd import *
from enum import Enum
from operator import methodcaller

class CmdMapping(Enum):
"""Mapping human command to septentrio methods"""

#get_model = methodcaller('get_receiver_model')
get_model = 'get_receiver_model'
get_firmware = 'get_receiver_firmware'
get_ip = 'get_receiver_ip'
reset = 'set_factory_default'
test = 'test'
send_config_file = 'send_config_file'

def arg_parse():
""" Parse the command line you use to launch the script """

parser= argparse.ArgumentParser(prog='Septentrio tool', description="A tool to send comment to a Septentrio GNSS receiver")
parser.add_argument("-p", "--port", help="Port to connect to", type=str)
parser.add_argument("-b", "--baudrate", help="port baudrate", default=115200, type=int)
parser.add_argument("-c", "--command", nargs='+', help="Command to send to the gnss receiver", type=str)
parser.add_argument("-s", "--store", action='store_true', help="Store settings as permanent", default=False)
parser.add_argument("--version", action="version", version="%(prog)s 0.1")
args = parser.parse_args()
#print(args)
return args

if __name__ == '__main__':
args = arg_parse()
#print(args)
command = args.command[0]
with SeptGnss(args.port, baudrate=args.baudrate, timeout=30, debug=False) as gnss:
res = methodcaller(CmdMapping[command].value, *args.command[1:])(gnss)
if type(res) is str:
print(res)
if args.store:
gnss.set_config_permanent()
#methodcaller(args.command[0])(gnss)
Empty file added tools/septentrio/__init__.py
Empty file.
154 changes: 154 additions & 0 deletions tools/septentrio/septentrio_cmd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#! /usr/bin/env python3
from . serial_comm import SerialComm
from enum import Enum
from logging import getLogger
import xml.etree.ElementTree as ET
import time
#Code inspired by https://github.com/jonamat/sim-modem

class SeptGnss:
"""Class for sending command to Septentrio Gnss receivers"""

def __init__(
self,
address,
baudrate=115200,
timeout=2,
cmd_delay=0.1,
debug=False,
):
self.comm = SerialComm(
address=address,
baudrate=baudrate,
timeout=timeout,
cmd_delay=cmd_delay,
connection_descriptor='USB2>',
)
self.debug = debug
self.connect()

def connect(self) -> None:
self.comm.send('exeEchoMessage, COM1, "A:HELLO", none')
read = self.comm.read_raw(1000)
try:
check_hello = b"A:HELLO" in read
res_descriptor = read.decode().split()[-1]
check_descriptor = 'COM' in res_descriptor or 'USB' in res_descriptor or 'IP1' in res_descriptor
if check_hello and check_descriptor:
self.comm.connection_descriptor = read.decode().split()[-1]
else:
raise Exception
if self.debug:
print("GNSS receiver connected, debug mode enabled")
print("Connection descriptor: {}".format(self.comm.connection_descriptor))
except Exception:
print("GNSS receiver did not respond correctly")
if self.debug:
print(read)
self.close()

def close(self) -> None:
self.comm.close()

def __enter__(self):
return self

def __exit__(self, exception_type, exception_value, exception_traceback):
self.close()

# --------------------------------- Common methods --------------------------------- #

def get_receiver_model(self) -> str:
read = self.send_read_until('lstInternalFile', 'Identification')
model = self.__parse_rcv_info(read, 'hwplatform', 'product')
return model

def get_receiver_firmware(self) -> str:
read = self.send_read_until('lstInternalFile', 'Identification')
firmware = self.__parse_rcv_info(read, 'firmware', 'version')
return firmware

def get_receiver_ip(self) -> str:
read = self.send_read_until('lstInternalFile', 'IPParameters')
ip_addr = self.__parse_rcv_info(read, 'inet', 'addr')
return ip_addr

def __parse_rcv_info(self, pseudo_xml, element_tag, info) -> str:
'''
This methode will try to parse the xml file received
when we send the lstInternalFile command
'''
pseudo_xml = [line for line in pseudo_xml[2:-1] if not (line.startswith('$') or line == '---->')]
e = ET.ElementTree(ET.fromstring(''.join(pseudo_xml)))
res_info = None
for element in e.iter():
#print(element.tag, element.attrib, element.text)
res_info = element.get(info) if element_tag in element.tag and element.get(info) else res_info
return res_info

def get_port_applications(self, port) -> str:
read = self.send_read_until('getRegisteredApplications', port)
return read[-2].split(',')[-1].replace('"','').strip()

def set_port_applications(self, port, applications_name) -> None:
read = self.send_read_until('exeRegisteredApplications', port, applications_name)

def set_factory_default(self) -> None:
'''
Reset receiver settings to factory defaults and restart it
Connection will be closed
'''
if self.debug:
print("Sending: 'exeResetReceiver, Soft, Config'")
self.comm.send('exeResetReceiver, Soft, Config')
read = self.comm.read_until('STOP>')
if self.debug:
print("Receiving: {}".format(read))
if read[-1] != 'STOP>' or read[0].startswith('$R?'):
raise Exception("Command failed!\nSent: 'exeResetReceiver, Soft, Config'\nReceived: {}".format(read))
self.close()
print("Connection closed")

def send_config_file(self, file, perm=False) -> None:
'''
Send user commands from a txt file, line by line
Set perm to True if you want to set these settings permanent
'''
with open(file, 'r') as f:
for line in f:
if line.strip() != '' and not line.startswith('#'):
cmd,*args = line.split(',')
#print(cmd, args)
self.send_read_until(cmd + ', ' + ', '.join(args))
if perm:
self.set_config_permanent()

def set_config_permanent(self) -> None:
'''
Save current settings to boot config
'''
read = self.send_read_until('exeCopyConfigFile', 'Current', 'Boot')

# ----------------------------------- OTHERS --------------------------------- #

def send_read_lines(self, cmd, *args) -> list:
if self.debug:
print("Sending: {}{}{}".format(cmd, ', ' if args else '', ', '.join(args)))
self.comm.send("{}{}{}".format(cmd, ', ' if args else '', ', '.join(args)))
read = self.comm.read_lines()
if self.debug:
print("Receiving: {}".format(read))
if read[-1] != self.comm.connection_descriptor or read[0].startswith('$R?'):
raise Exception("Command failed!\nSent: {}\nReceived: {}".format((cmd + ', ' + ', '.join(args)), read))
return read

def send_read_until(self, cmd, *args) -> list:
if self.debug:
print("Sending: {}{}{}".format(cmd, ', ' if args else '', ', '.join(args)))
self.comm.send("{}{}{}".format(cmd, ', ' if args else '', ', '.join(args)))
read = self.comm.read_until()
if self.debug:
print("Receiving: {}".format(read))
if read[-1] != self.comm.connection_descriptor or read[0].startswith('$R?'):
raise Exception("Command failed!\nSent: {}\nReceived: {}".format((cmd + ', ' + ', '.join(args)), read))
return read
Loading

0 comments on commit 3b1b355

Please sign in to comment.