#!/usr/bin/python
# -*- coding: utf-8 -*-
# The MIT License (MIT)
# Copyright (c) 2016 Bruno Tibério
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
""" Module NovatelOEM4
:Date: 22 Nov 2018
:Version: 0.4.1
:Author: Bruno Tibério
:Contact: bruno.tiberio@tecnico.ulisboa.pt
This module contains a few functions to interact with Novatel OEM4 GPS devices.
Currently only the most important functions and definitions are configured, but
the intention is to make it as much complete as possible.
A simple example can be run by executing the main function which creates a Gps
class object and execute the following commands on gps receiver:
- **begin:** on default port or given port by argv[1].
- **sendUnlogall**
- **setCom(baud=115200):** changes baudrate to 115200bps
- **askLog(trigger=2,period=0.1):** ask for log *bestxyz* with trigger `ONTIME`
and period `0.1`
- wait for 10 seconds
- **shutdown:** safely disconnects from gps receiver
Example:
.. code-block:: console
$python NovatelOEM4.py
"""
import binascii
import crcmod
from datetime import datetime
import logging
import os
import serial
import struct
import threading
from time import sleep
try:
import queue
except ImportError:
import Queue as queue
[docs]class Gps:
"""Novatel OEM4 GPS library class
This class contents is an approach to create a library for Novatel OEM 4 GPS
Args:
sensorName (optional): A sensor name if used with multiple devices.
Attributes:
header_keys: all field keys for the headers of messages.
MessageID: A dictionary for the types of messages sent. Not all are
implemented yet!
"""
#: all field keys for the headers of messages.
header_keys = ('sync', 'headerLength', 'messageID', 'messageType',
'portAddress', 'messageLength', 'sequence', 'idleTime',
'timeStatus', 'week', 'ms', 'receiverStatus', 'reserved',
'swVersion')
#: A dictionary for the types of messages sent. Not all are implemented yet!
MessageID = {'LOG': 1,
'COM': 4,
'RESET': 18,
'SAVECONFIG': 19,
'UNLOG': 36,
'UNLOGALL': 38,
'DATUM': 160,
'DYNAMICS': 258,
'BESTXYZ': 241,
'SBASCONTROL': 652}
def __init__(self, sensorName="GPS"):
self.myPort = ""
self.isOpen = 0 # is port open?*/
self.baudRate = 9600 # current communication baudRate*/
self.openError = 0 # if any error during any process occurs this will be set */
self.dataQueue = None
self.name = sensorName
self.isSet = False
self.exitFlag = threading.Event()
self.orders = queue.Queue()
self.current_header = []
self.Index = 1
[docs] @staticmethod
def CRC32Value(i):
"""Calculate the 32bits CRC of message.
See OEMStar Firmware Reference Manual Rev 6 page 24 for
more information.
Args:
i: message to calculate the crc-32.
Returns:
The CRC value calculated over the input message.
"""
crc = crcmod.mkCrcFun(0x104C11DB7, 0, True, 0)
return crc(i)
[docs] @staticmethod
def getDebugMessage(message):
"""Create a string which contains all bytes represented as hex values
Auxiliary function for helping with debug. Receives a binary message as
input and convert it as a string with the hexdecimal representation.
Args:
message: message to be represented.
Returns:
A string of corresponding hex representation of message.
"""
debugMessage = (binascii.hexlify(message)).upper()
debugMessage = [debugMessage[i:i + 2] for i in range(0, len(debugMessage), 2)]
debugMessage = ' '.join('0x{}'.format(item) for item in debugMessage)
return debugMessage
[docs] def parseResponces(self):
"""
A thread to parse responses from device
"""
# used definitions for keys
bestxyz_keys = ('pSolStatus', 'posType', 'position', 'positionStd',
'velSolStatus', 'velType', 'velocity', 'velocityStd',
'stnID', 'vLatency', 'diffAge', 'solAge', 'numStasVs',
'numSolSatVs', 'numGGL1', 'reserved', 'extSolStat',
'reserved2', 'sigMask', 'crc32')
self.log.info("Entering Thread logger")
if(not self.isOpen):
self.log.warning('Port is not open: {0}'.format(self.myPort))
self.log.info("Exiting Thread logger")
return
MYPORT = self.myPort
# dataFile = self.dataFile
while(self.exitFlag.isSet() == False):
header = [0] * 14
newByte = ord(MYPORT.read(1))
if newByte == 0xAA:
header[0] = [0, 0, 0]
header[0][0] = newByte
header[0][1] = ord(MYPORT.read(1))
if header[0][1] == 0x44:
header[0][2] = ord(MYPORT.read(1))
if header[0][2] == 0x12:
# got a valid header sync vector
serialBuffer = MYPORT.read(25)
header[1:] = struct.unpack('<BHBBHHBBHlLHH',
serialBuffer)
self.current_header = dict(zip(self.header_keys, header))
header = self.current_header
if header['messageID'] == self.MessageID['LOG']:
message = [0] * 3
message_keys = ('responseID', 'ascii', 'crc32')
serialBuffer = MYPORT.read(4)
message[0] = struct.unpack('<I', serialBuffer)
message[1] = MYPORT.read(self.current_header['messageLength'] - 4)
message[2] = MYPORT.read(4)
message[2] = struct.unpack('<L', message[2])
message = dict(zip(message_keys, message))
self.log.info("LOG response received : {0}".format(message['ascii']))
self.orders.put({'order': 'LOG', 'data': message})
elif header['messageID'] == self.MessageID['UNLOGALL']:
message = [0] * 3
message_keys = ('responseID', 'ascii', 'crc32')
serialBuffer = MYPORT.read(4)
message[0] = struct.unpack('<I', serialBuffer)
message[1] = MYPORT.read(self.current_header['messageLength'] - 4)
message[2] = MYPORT.read(4)
message[2] = struct.unpack('<L', message[2])
message = dict(zip(message_keys, message))
self.log.info("UNLOGALL response received : {0}".format(message['ascii']))
self.orders.put({'order': 'UNLOGALL', 'data': message})
elif header['messageID'] == self.MessageID['COM']:
message = [0] * 3
message_keys = ('responseID', 'ascii', 'crc32')
serialBuffer = MYPORT.read(4)
message[0] = struct.unpack('<I', serialBuffer)
message[1] = MYPORT.read(self.current_header['messageLength'] - 4)
message[2] = MYPORT.read(4)
message[2] = struct.unpack('<L', message[2])
message = dict(zip(message_keys, message))
self.log.info("COM response received : {0}".format(message['ascii']))
self.orders.put({'order': 'COM', 'data': message})
elif header['messageID'] == self.MessageID['DYNAMICS']:
# is message responce to command setDynamics?
if header['messageType'] == 130:
message = [0] * 3
message_keys = ('responseID', 'ascii', 'crc32')
serialBuffer = MYPORT.read(4)
message[0] = struct.unpack('<I', serialBuffer)
message[1] = MYPORT.read(self.current_header['messageLength'] - 4)
message[2] = MYPORT.read(4)
message[2] = struct.unpack('<L', message[2])
message = dict(zip(message_keys, message))
self.log.info("DYNAMICS response received : {0}".format(message['ascii']))
self.orders.put({'order': 'DYNAMICS', 'data': message})
else:
# is a log dynamic response
message = [0] * 2
message_keys = ('dynamicID', 'crc32')
serialBuffer = MYPORT.read(4)
message[0] = struct.unpack('<L', serialBuffer)
message[1] = MYPORT.read(4)
message[1] = struct.unpack('<L', message[1])
message = dict(zip(message_keys, message))
self.log.info("DYNAMICS response received : dynamicID={0}".format(message['dynamicID'][0]))
elif header['messageID'] == self.MessageID['RESET']:
message = [0] * 3
message_keys = ('responseID', 'ascii', 'crc32')
serialBuffer = MYPORT.read(4)
message[0] = struct.unpack('<I', serialBuffer)
message[1] = MYPORT.read(self.current_header['messageLength'] - 4)
message[2] = MYPORT.read(4)
message[2] = struct.unpack('<L', message[2])
message = dict(zip(message_keys, message))
self.log.info("RESET response received : {0}".format(message['ascii']))
self.orders.put({'order': 'RESET', 'data': message})
elif header['messageID'] == self.MessageID['SAVECONFIG']:
message = [0] * 3
message_keys = ('responseID', 'ascii', 'crc32')
serialBuffer = MYPORT.read(4)
message[0] = struct.unpack('<I', serialBuffer)
message[1] = MYPORT.read(self.current_header['messageLength'] - 4)
message[2] = MYPORT.read(4)
message[2] = struct.unpack('<L', message[2])
message = dict(zip(message_keys, message))
self.log.info("SAVECONFIG response received : {0}".format(message['ascii']))
self.orders.put({'order': 'SAVECONFIG', 'data': message})
elif header['messageID'] == self.MessageID['SBASCONTROL']:
message = [0] * 3
message_keys = ('responseID', 'ascii', 'crc32')
serialBuffer = MYPORT.read(4)
message[0] = struct.unpack('<I', serialBuffer)
message[1] = MYPORT.read(self.current_header['messageLength'] - 4)
message[2] = MYPORT.read(4)
message[2] = struct.unpack('<L', message[2])
message = dict(zip(message_keys, message))
self.log.info("SBASCONTROL response received : {0}".format(message['ascii']))
self.orders.put({'order': 'SBASCONTROL', 'data': message})
elif header['messageID'] == self.MessageID['BESTXYZ']:
message = [0] * 20
serialBuffer = MYPORT.read(self.current_header['messageLength'])
message[0:2] = struct.unpack('<II', serialBuffer[0:8])
message[2] = [0, 0, 0]
message[2][0:] = struct.unpack('<ddd', serialBuffer[8:32])
message[3] = [0, 0, 0]
message[3][0:] = struct.unpack('<fff', serialBuffer[32:44])
message[4:6] = struct.unpack('<II', serialBuffer[44:52])
message[6] = [0, 0, 0]
message[6][0:] = struct.unpack('<ddd', serialBuffer[52:76])
message[7] = [0, 0, 0]
message[7][0:] = struct.unpack('<fff', serialBuffer[76:88])
message[8] = serialBuffer[88:92]
message[9:12] = struct.unpack('<fff', serialBuffer[92:104])
message[12:15] = struct.unpack('<3B', serialBuffer[104:107])
message[15] = [0, 0]
message[15][0:] = struct.unpack('<2B', serialBuffer[107:109])
message[16:19] = struct.unpack('<3B', serialBuffer[109:112])
serialBuffer = MYPORT.read(4) # crc32
message[19] = struct.unpack('<I', serialBuffer)
message = dict(zip(bestxyz_keys, message))
currentTime = datetime.now()
myTime = '{0:%Y-%m-%d %H:%M:%S}'.format(currentTime) + '.{0:02.0f}'.format(round(currentTime.microsecond / 10000.0))
outMessage = dict(Index=self.Index, Time=myTime)
outMessage.update(message)
self.dataQueue.put_nowait(outMessage)
self.Index = self.Index + 1
else:
# .. todo:: error processing.
pass
else:
self.log.debug("New Byte unexpected: 0x{0:X}".format(newByte))
self.log.info("Exiting Thread logger")
return
[docs] def begin(self, dataQueue,
comPort="/dev/ttyUSB0",
baudRate=9600):
""" Initializes the gps receiver.
This function resets the current port to factory default and setup the
gps receiver to be able to acept new commands. If connection to gps
is made, it launchs a thread used to parse messages comming from gps.
Args:
comPort: system port where receiver is connected.
dataQueue: a Queue object to store incoming bestxyz messages.
baudRate: baudrate to configure port. (should always be equal to
factory default of receiver).
Returns:
True or False if the setup has gone as expected or not.
:Example:
.. code-block:: python
Gps.begin(comPort="<port>",
dataQueue=<your Queue obj>,
baudRate=9600)
**Default values**
:comPort: "/dev/ttyUSB0"
:baudRate: 9600
.. warning::
This class uses module ``logging`` wich must be configured in your
main program using the ``basicConfig`` method. Check documentation
of `module logging`_ for more info.
**HW info:**
:Receptor: Novatel Flexpak G2L-3151W.
:Antenna: Novatel Pinwheel.
.. _module logging: https://docs.python.org/2/library/logging.html
"""
self.log = logging.getLogger(self.name)
# checking if port exists on system
if not os.path.exists(comPort):
self.log.warning('Port is not available: {0}'.format(comPort))
return False
else:
# port exists, open it
self.myPort = serial.Serial(comPort, baudrate=baudRate)
if not self.myPort.is_open:
self.log.warning("Error opening port: {0}".format(comPort))
self.isOpen = False
return False
# reset port settings to default
self.myPort.break_condition = True
self.myPort.send_break()
sleep(1.5)
self.myPort.send_break()
sleep(0.25)
self.baudRate = baudRate
self.isOpen = True
# open dataFile to save GPS data
self.dataQueue = dataQueue
# start thread to handle GPS responces
self.threadID = threading.Thread(name="Logger", target=self.parseResponces)
self.threadID.start()
self.log.info("Started Logger Thread")
sleep(0.1)
return True
[docs] def sendUnlogall(self, port=8, held=1):
"""Send command unlogall to gps device.
On sucess clears all logs on all ports even held logs.
Returns:
True or False if the request has gone as expected or not.
unlogall message is defined as:
+------+------------+----------+-------------------------------+
| Field| value | N Bytes | Description |
+======+============+==========+===============================+
|1 | header | H = 28 | Header of message |
+------+------------+----------+-------------------------------+
|2 | port | ENUM = 4 | identification of port |
+------+------------+----------+-------------------------------+
|3 | Held | ENUM = 4 | can only be 0 or 1. Clear logs|
| | | | with hold flag or not? |
+------+------------+----------+-------------------------------+
| CRC32| | UL = 4 | |
+------+------------+----------+-------------------------------+
.. note:: See: OEMStar Firmware Reference Manual Rev 6 page 161
"""
if self.isOpen:
MYPORT = self.myPort
messageSize = 8 # 2 * ENUM
header = self.create_header(messageID=38,
messageLength=messageSize)
myMessage = struct.pack('<BBBBHBBHHBBHlLHH', *header)
myMessage = myMessage + struct.pack('<LL', port, held)
crc_value = self.CRC32Value(myMessage)
finalMessage = myMessage + struct.pack('<L', crc_value)
# print messages to logFile
self.log.info("Requested unlogall")
self.log.debug('Message sent to GPS: {0}'.format(self.getDebugMessage(finalMessage)))
MYPORT.write(finalMessage)
MYPORT.flush()
# wait for data on queue with response
message = self.orders.get()
if message['order'] == 'UNLOGALL':
message = message['data']
self.log.info("Unlogall response received : {0}".format(message['ascii']))
if message['responseID'][0] == 1:
return True
else:
return False
else:
self.log.warning("Unexpected responce type: {0}".format(message['order']))
else:
self.log.info("Port not open. Couldn't request unlogall command")
return False
[docs] def setCom(self, baud, port=6, parity=0, databits=8, stopbits=1,
handshake=0, echo=0, breakCond=1):
"""Set com configuration.
Args:
baud: communication baudrate.
port: Novatel serial ports identifier (default 6 = "thisport").
parity: byte parity check (default 0).
databits: Number of data bits (default 8).
stopbits: Number of stop bits (default 1).
handshake: Handshaking (default No handshaking).
echo: echo input back to user (default false)
breakCond: Enable break detection (default true)
Return:
True or false if command was sucessfull or not.
The com request command is defined as:
+-----+------------+----------+----------------------------------+
|Field| ID | N Bytes | Description |
+=====+============+==========+==================================+
|1 | Com header | H = 28 | Header of message |
+-----+------------+----------+----------------------------------+
|2 | port | ENUM = 4 | identification of port |
+-----+------------+----------+----------------------------------+
|3 | baud | Ulong = 4| Communication baud rate (bps) |
+-----+------------+----------+----------------------------------+
|4 | parity | ENUM = 4 | Parity |
+-----+------------+----------+----------------------------------+
|5 | databits | Ulong = 4| Number of data bits (default = 8)|
+-----+------------+----------+----------------------------------+
|6 | stopbits | Ulong = 4| Number of stop bits (default = 1)|
+-----+------------+----------+----------------------------------+
|7 | handshake | ENUM = 4 | Handshaking |
+-----+------------+----------+----------------------------------+
|8 | echo | ENUM = 4 | No echo (default)(must be 0 or 1)|
+-----+------------+----------+----------------------------------+
|9 | break | ENUM = 4 |Enable break detection (default 0)|
| | | |,(must be 0 or 1) |
+-----+------------+----------+----------------------------------+
.. note:: Total byte size = header + 32 = 60 bytes
COM Serial Port Identifiers (field 2):
+-------+--------------+--------------------+
|Binary |ASCII |Description |
+=======+==============+====================+
|1 | COM1 |COM port 1 |
+-------+--------------+--------------------+
|2 | COM2 |COM port 2 |
+-------+--------------+--------------------+
|6 | THISPORT |The current COM port|
+-------+--------------+--------------------+
|8 | ALL |All COM ports |
+-------+--------------+--------------------+
|9 | XCOM1 |Virtual COM1 port |
+-------+--------------+--------------------+
|10 | XCOM2 |Virtual COM2 port |
+-------+--------------+--------------------+
|13 | USB1 |USB port 1 |
+-------+--------------+--------------------+
|14 | USB2 |USB port 2 |
+-------+--------------+--------------------+
|15 | USB3 |USB port 3 |
+-------+--------------+--------------------+
|17 | XCOM3 |Virtual COM3 port |
+-------+--------------+--------------------+
Parity(field 4):
+-------+-----------+-----------------------+
|Binary |ASCII |Description |
+=======+===========+=======================+
|0 | N | No parity (default) |
+-------+-----------+-----------------------+
|1 | E | Even parity |
+-------+-----------+-----------------------+
|2 | O | Odd parity |
+-------+-----------+-----------------------+
Handshaking (field 7):
+-------+---------+-------------------------------+
|Binary |ASCII |Description |
+=======+=========+===============================+
|0 |N |No handshaking (default) |
+-------+---------+-------------------------------+
|1 |XON |XON/XOFF software handshaking |
+-------+---------+-------------------------------+
|2 |CTS |CTS/RTS hardware handshaking |
+-------+---------+-------------------------------+
.. note:: See: OEMStar Firmware Reference Manual Rev 6 page 56
"""
if self.isOpen:
MYPORT = self.myPort
messageSize = 32 # 32bytes length
header = self.create_header(messageID=4, messageLength=messageSize)
myMessage = struct.pack('<BBBBHBBHHBBHlLHH', *header)
myMessage = myMessage + struct.pack('<8L', port, baud, parity, databits,
stopbits, handshake, echo, breakCond)
crc_value = self.CRC32Value(myMessage)
finalMessage = myMessage + struct.pack('<L', crc_value)
self.log.info("Requested Com command")
self.log.debug('Message sent to GPS: {0}'.format(self.getDebugMessage(finalMessage)))
MYPORT.write(finalMessage)
MYPORT.flush()
self.log.info("waiting for port settings to change")
sleep(1)
portOptions = MYPORT.get_settings()
# change port settings
portOptions['baudrate'] = baud
# auxiliar vector
parity_vect = [serial.PARITY_NONE, serial.PARITY_EVEN, serial.PARITY_ODD]
portOptions['parity'] = parity_vect[parity]
portOptions['bytesize'] = databits
portOptions['stopbits0'] = stopbits
if handshake == 0:
portOptions['xonxoff'] = False
portOptions['rtscts'] = False
elif handshake == 1:
portOptions['xonxoff'] = True
portOptions['rtscts'] = False
elif handshake == 2:
portOptions['xonxoff'] = False
portOptions['rtscts'] = True
MYPORT.apply_settings(portOptions)
MYPORT.reset_input_buffer()
MYPORT.reset_output_buffer()
if MYPORT.is_open:
self.log.info("changed port settings: {0}".format(MYPORT.portstr))
return True
else:
self.log.warning("Not able to change port settings: {0}".format(MYPORT.portstr))
return False
else:
self.log.info("Port not open. Couldn't request COM command")
return False
[docs] def askLog(self, logID='BESTXYZ', port=192, trigger=4, period=0, offset=0, hold=0):
"""Request a log from receiver.
Args:
logID: log type to request.
port: port to report log.
trigger: trigger identifier.
period: the period of log.
offset: offset in seconds after period.
hold: mark log with hold flag or not.
Returns:
True or false if command was sucessfull or not.
The log request command is defined as:
+-----+------------+--------------+-----------------------------------+
|Field| ID | N Bytes | Description |
+=====+============+==============+===================================+
|1 | Com header | H = 28 | Header of message |
+-----+------------+--------------+-----------------------------------+
|2 | port | ENUM = 4 | identification of port |
+-----+------------+--------------+-----------------------------------+
|3 | message | Ushort = 2 | Message ID of log to output |
+-----+------------+--------------+-----------------------------------+
|4 | messageType| char = 1 | Message type (Binary) |
+-----+------------+--------------+-----------------------------------+
|5 | RESERVED | char = 1 | |
+-----+------------+--------------+-----------------------------------+
|6 | trigger | ENUM = 4 | message trigger |
+-----+------------+--------------+-----------------------------------+
|7 | period | double = 8 | Log period (for ONTIME in secs) |
+-----+------------+--------------+-----------------------------------+
|8 | offset | double = 8 | Offset for period (ONTIME in secs |
+-----+------------+--------------+-----------------------------------+
|9 | hold | ENUM = 4 | Hold log |
+-----+------------+--------------+-----------------------------------+
|10 | crc32 | Ulong = 4 | crc32 value |
+-----+------------+--------------+-----------------------------------+
.. note:: Total byte size = header + 32 = 60 bytes
Log trigger Identifiers (field 6):
+-------+-----------+-------------------------------------------------+
|Binary |ASCII |Description |
+=======+===========+=================================================+
|0 | ONNEW | | when the message is updated (not necessarily |
| | | | changed) |
+-------+-----------+-------------------------------------------------+
|1 | ONCHANGED | | Current message and then continue to output |
| | | | when the message is changed |
+-------+-----------+-------------------------------------------------+
|2 | ONTIME |Output on a time interval |
+-------+-----------+-------------------------------------------------+
|3 | ONNEXT |Output only the next message |
+-------+-----------+-------------------------------------------------+
|4 | ONCE |Output only the current message |
+-------+-----------+-------------------------------------------------+
|5 | ONMARK | | Output when a pulse is detected on the mark 1 |
| | | | input |
+-------+-----------+-------------------------------------------------+
"""
if self.isOpen:
MYPORT = self.myPort
messageSize = 32
header = self.create_header(messageID=1, messageLength=messageSize)
myMessage = struct.pack('<BBBBHBBHHBBHlLHH', *header)
myMessage = myMessage + struct.pack('<LHBBLddL', port, self.MessageID[logID],
0, 0, trigger, period, offset, hold)
crc_value = self.CRC32Value(myMessage)
finalMessage = myMessage + struct.pack('<L', crc_value)
self.log.info("Requested Log command")
self.log.debug('Message sent to GPS: {0}'.format(self.getDebugMessage(finalMessage)))
MYPORT.write(finalMessage)
MYPORT.flush()
# wait for responce
message = self.orders.get()
if message['order'] == 'LOG':
message = message['data']
self.log.info("LOG response received : {0}".format(message['ascii']))
if message['responseID'][0] == 1:
return True
else:
return False
else:
self.log.warning("Unexpected responce type: {0}".format(message['order']))
else:
self.log.info("Port not open. Couldn't request LOG command")
return False
[docs] def setDynamics(self, dynamicID):
"""Set Dynamics of receiver.
Args:
dynamicID: identifier of the type of dynamic.
Returns:
True or False if the request has gone as expected or not.
dynamics message is defined as:
+------+------------+----------+-------------------------------+
| Field| value | N Bytes | Description |
+======+============+==========+===============================+
|1 | header | H = 28 | Header of message |
+------+------------+----------+-------------------------------+
|2 | dynamics | ENUM = 4 | identification of dynamics |
+------+------------+----------+-------------------------------+
| CRC32| | UL = 4 | |
+------+------------+----------+-------------------------------+
The dynamics identifiers (field 2) are defined as:
+-------+--------+------------------------------------------------+
|Binary |ASCII |Description |
+=======+========+================================================+
|0 | AIR | | Receiver is in an aircraft or a land vehicle,|
| | | | for example a high speed train, with velocity|
| | | | greater than 110 km/h (30 m/s). This is also |
| | | | the most suitable dynamic for a jittery |
| | | | vehicle at any speed. |
+-------+--------+------------------------------------------------+
|1 | LAND | | Receiver is in a stable land vehicle with |
| | | | velocity less than 110 km/h (30 m/s). |
+-------+--------+------------------------------------------------+
|2 | FOOT | | Receiver is being carried by a person with |
| | | | velocity less than 11 km/h (3 m/s). |
+-------+--------+------------------------------------------------+
This command adjusts the receiver dynamics to that of your environment.
It is used to optimally tune receiver parameters.
The DYNAMICS command adjusts the Tracking State transition time-out
value of the receiver.
When the receiver loses the position solution, it attempts to steer the
tracking loops for fast reacquisition (5 s time-out by default).
The DYNAMICS command allows you to adjust this time-out value,
effectively increasing the steering time. The three states 0, 1, and 2
set the time-out to 5, 10, or 20 seconds respectively.
.. note::
* The DYNAMICS command should only be used by advanced users of GPS.
The default of AIR should not be changed except under very
specific conditions.
* The DYNAMICS command affects satellite reacquisition. The
constraint of the DYNAMICS filter with FOOT is very tight and is
appropriate for a user on foot. A sudden tilted or up and down
movement, for example while a tractor is moving slowly along a
track, may trip the RTK filter to reset and cause the position to
jump. AIR should be used in this case.
"""
if self.isOpen:
if dynamicID in [0, 1, 2]:
MYPORT = self.myPort
messageSize = 4 # ENUM
header = self.create_header(messageID=self.MessageID['DYNAMICS'],
messageLength=messageSize)
myMessage = struct.pack('<BBBBHBBHHBBHlLHH', *header)
myMessage = myMessage + struct.pack('<L', dynamicID)
crc_value = self.CRC32Value(myMessage)
finalMessage = myMessage + struct.pack('<L', crc_value)
# print messages to logFile
self.log.info("Requested dynamics")
self.log.debug('Message sent to GPS: {0}'.format(self.getDebugMessage(finalMessage)))
MYPORT.write(finalMessage)
MYPORT.flush()
# wait for data on queue with response
message = self.orders.get()
if message['order'] == 'DYNAMICS':
message = message['data']
self.log.info("DYNAMICS response received : {0}".format(message['ascii']))
if message['responseID'][0] == 1:
return True
else:
return False
else:
self.log.warning("Unexpected responce type: {0}".format(message['order']))
else:
self.log.info("dynamicID not valid")
return False
else:
self.log.info("Port not open. Couldn't request DYNAMICS command")
return False
# def setPDPFilter(self, switchID):
# """ set PDPFilter type
#
# Args:
# switchID: Enable, disable or reset.
#
# Returns:
# A boolean if request was sucessful or not
#
# PDPFilter message is defined as:
#
# +------+------------+----------+-------------------------------+
# | Field| value | N Bytes | Description |
# +======+============+==========+===============================+
# |1 | header | H = 28 | Header of message |
# +------+------------+----------+-------------------------------+
# |2 | switchID | ENUM = 4 | Enable, disable or reset. |
# +------+------------+----------+-------------------------------+
# | CRC32| | UL = 4 | |
# +------+------------+----------+-------------------------------+
#
# the switchID is defined as:
#
# +-------+--------+------------------------------------------------+
# |Binary |ASCII |Description |
# +=======+========+================================================+
# |0 | DISABLE| | Enable/disable/reset the PDP filter. A reset |
# +-------+--------+ | clears the filter memory so that the pdp |
# |1 | ENABLE | | filter can start over. |
# +-------+--------+ |
# |2 | Reset | |
# +-------+--------+------------------------------------------------+
#
# This command enables, disables or resets the Pseudorange/Delta-Phase
# (PDP) filter. The main advantages of the Pseudorange/Delta-Phase
# (PDP) implementation are:
#
# * Smooths a jumpy position
# * Bridges outages in satellite coverage (the solution is degraded from
# normal but there is at least a reasonable solution without gaps)
#
# .. note::
# For channel configurations that include GPS, PDP is enabled by
# default on the OEMStar.
# With PDP enabled (default), the BESTPOS log is not updated until the
# receiver has achieved FINESTEERING.
# PDP and GLIDE are disabled for GLONASS-only applications.
# Enable the PDP filter to output the PDP solution in BESTPOS, BESTVEL
# and NMEA logs.
#
# """
# if self.isOpen:
# if switchID in [0, 1, 2]:
# MYPORT = self.myPort
# messageSize = 4 # ENUM
# header = self.create_header(messageID=self.MessageID('PDPFILTER'),
# messageLength=messageSize)
# myMessage = struct.pack('<BBBBHBBHHBBHlLHH', *header)
# myMessage = myMessage + struct.pack('<L', switchID)
# crc_value = self.CRC32Value(myMessage)
# finalMessage = myMessage + struct.pack('<L', crc_value)
#
# # print messages to logFile
# self.log.info("Requested PDPFilter")
# self.log.debug('Message sent to GPS: {0}'.format(self.getDebugMessage(finalMessage)))
# MYPORT.write(finalMessage)
# MYPORT.flush()
#
# # wait for data on queue with response
# message = self.orders.get()
# if message['order'] == 'PDPFILTER':
# message = message['data']
# self.log.info("PDPFilter response received : {0}".format(message['ascii']))
# if message['responseID'][0] == 1:
# return True
# else:
# return False
# else:
# self.log.warning("Unexpected responce type: {0}".format(message['order']))
# else:
# self.log.info("switchID not valid")
# return False
# else:
# self.log.info("Port not open. Couldn't request PDPFILTER command")
# return False
#
# def setPDPMode(self, mode, dynamics):
# pass
[docs] def reset(self, delay=0):
""" Performs a hardware reset
Args:
delay: seconds to wait before resetting. Default to zero.
Returns:
A boolean if request was sucessful or not
The reset message is defined as:
+------+------------+----------+-------------------------------+
| Field| value | N Bytes | Description |
+======+============+==========+===============================+
|1 | header | H = 28 | Header of message |
+------+------------+----------+-------------------------------+
|2 | delay | UL = 4 | Seconds to wait before reset |
+------+------------+----------+-------------------------------+
| CRC32| | UL = 4 | |
+------+------------+----------+-------------------------------+
Following a RESET command, the receiver initiates a coldstart boot up.
Therefore, the receiver configuration reverts either to the factory
default, if no user configuration was saved, or the last SAVECONFIG
settings.
The optional delay field is used to set the number of seconds the
receiver is to wait before resetting.
"""
if self.isOpen:
MYPORT = self.myPort
messageSize = 4 # Ulong
header = self.create_header(messageID=self.MessageID['RESET'],
messageLength=messageSize)
myMessage = struct.pack('<BBBBHBBHHBBHlLHH', *header)
myMessage = myMessage + struct.pack('<L', delay)
crc_value = self.CRC32Value(myMessage)
finalMessage = myMessage + struct.pack('<L', crc_value)
# print messages to logFile
self.log.info("Requested Reset")
self.log.debug('Message sent to GPS: {0}'.format(self.getDebugMessage(finalMessage)))
MYPORT.write(finalMessage)
MYPORT.flush()
# wait for data on queue with response
message = self.orders.get()
if message['order'] == 'RESET':
message = message['data']
self.log.info("RESET response received : {0}".format(message['ascii']))
if message['responseID'][0] == 1:
return True
else:
return False
else:
self.log.warning("Unexpected responce type: {0}".format(message['order']))
else:
self.log.info("Port not open. Couldn't request RESET command")
return False
[docs] def saveconfig(self):
""" Save user current configuration
Returns:
A boolean if request was sucessful or not
Saveconfig message is defined as:
+------+------------+----------+-------------------------------+
| Field| value | N Bytes | Description |
+======+============+==========+===============================+
|1 | header | H = 28 | Header of message |
+------+------------+----------+-------------------------------+
| CRC32| | UL = 4 | |
+------+------------+----------+-------------------------------+
This command saves the user’s present configuration in non-volatile
memory. The configuration includes the current log settings, FIX
settings, port configurations, and so on. Its output is in the
RXCONFIG log.
"""
if self.isOpen:
MYPORT = self.myPort
messageSize = 0 # Ulong
header = self.create_header(messageID=self.MessageID['SAVECONFIG'],
messageLength=messageSize)
myMessage = struct.pack('<BBBBHBBHHBBHlLHH', *header)
crc_value = self.CRC32Value(myMessage)
finalMessage = myMessage + struct.pack('<L', crc_value)
# print messages to logFile
self.log.info("Requested SAVECONFIG")
self.log.debug('Message sent to GPS: {0}'.format(self.getDebugMessage(finalMessage)))
MYPORT.write(finalMessage)
MYPORT.flush()
# wait for data on queue with response
message = self.orders.get()
if message['order'] == 'SAVECONFIG':
message = message['data']
self.log.info("SAVECONFIG response received : {0}".format(message['ascii']))
if message['responseID'][0] == 1:
return True
else:
return False
else:
self.log.warning("Unexpected responce type: {0}".format(message['order']))
else:
self.log.info("Port not open. Couldn't request SAVCONFIG command")
return False
[docs] def sbascontrol(self, keywordID=1, systemID=1, prn=0, testmode=0):
""" Set SBAS test mode and PRN SBAS
Args:
keywordID: True or false. Control the reception of SBAS
corrections Enable = 1, Disable = 0.
systemID: SBAS system to be used.
prn: PRN corrections to be used.
testmode: Interpretation of type 0 messages.
Returns:
A boolean if request was sucessful or not
sbascontrol message is defined as:
+------+------------+----------+-------------------------------+
| Field| value | N Bytes | Description |
+======+============+==========+===============================+
|1 | header | H = 28 | Header of message |
+------+------------+----------+-------------------------------+
|2 | keyword | Enum = 4 |Enable = 1 or Disable = 0 |
+------+------------+----------+-------------------------------+
|3 | system | Enum = 4 |Choose the SBAS the receiver |
| | | |will use |
+------+------------+----------+-------------------------------+
|4 | prn | UL = 4 | 0 - Receiver will use any PRN |
| | | +-------------------------------+
| | | | 120~138 - Receiver will use |
| | | | SBAS only from this PRN |
+------+------------+----------+-------------------------------+
|5 | testmode | Enum = 4 | Interpretation of type 0 |
| | | | messages |
+------+------------+----------+-------------------------------+
| CRC32| | UL = 4 | |
+------+------------+----------+-------------------------------+
System (Field 2) is defined as:
+-------+--------+------------------------------------------------+
|Binary |ASCII |Description |
+=======+========+================================================+
|0 | NONE | Don't use any SBAS satellites. |
+-------+--------+------------------------------------------------+
|1 | AUTO | Automatically determinate satellite system to |
| | | use (default). |
+-------+--------+------------------------------------------------+
|2 | ANY | Use any and all SBAS satellites found |
+-------+--------+------------------------------------------------+
|3 | WAAS | Use only WAAS satellites |
+-------+--------+------------------------------------------------+
|4 | EGNOS | Use only EGNOS satellites |
+-------+--------+------------------------------------------------+
|5 | MSAS | Use only MSAS satellites |
+-------+--------+------------------------------------------------+
Testmode (field 5) is defined as:
+-------+-----------+----------------------------------------------+
|Binary |ASCII |Description |
+=======+===========+==============================================+
|0 | NONE | Interpret Type 0 messages as they are |
| | | intended (as do not use).(default) |
+-------+-----------+----------------------------------------------+
|1 | ZEROTOTWO | Interpret Type 0 messages as type 2 messages |
+-------+-----------+----------------------------------------------+
|2 | IGNOREZERO| Ignore the usual interpretation of Type 0 |
| | | messages (as do not use) and continue |
+-------+-----------+----------------------------------------------+
This command allows you to dictate how the receiver handles
Satellite Based Augmentation System (SBAS) corrections and replaces
the now obsolete WAASCORRECTION command. The receiver automatically
switches to Pseudorange Differential (RTCM or RTCA) or RTK if the
appropriate corrections are received, regardless of the current
setting.
"""
if self.isOpen:
MYPORT = self.myPort
messageSize = 0 # Ulong
header = self.create_header(messageID=self.MessageID['SBASCONTROL'],
messageLength=messageSize)
myMessage = struct.pack('<BBBBHBBHHBBHlLHH', *header)
myMessage = myMessage + struct.pack('<LLLL', keywordID, systemID,
prn, testmode)
crc_value = self.CRC32Value(myMessage)
finalMessage = myMessage + struct.pack('<L', crc_value)
# print messages to logFile
self.log.info("Requested SBASCONTROL")
self.log.debug('Message sent to GPS: {0}'.format(self.getDebugMessage(finalMessage)))
MYPORT.write(finalMessage)
MYPORT.flush()
# wait for data on queue with response
message = self.orders.get()
if message['order'] == 'SBASCONTROL':
message = message['data']
self.log.info("SBASCONTROL response received : {0}".format(message['ascii']))
if message['responseID'][0] == 1:
return True
else:
return False
else:
self.log.warning("Unexpected responce type: {0}".format(message['order']))
else:
self.log.info("Port not open. Couldn't request SBASCONTROL command")
return False
[docs] def shutdown(self):
"""Prepare for exiting program
Returns:
always returns true after all tasks are done.
Prepare for turn off the program by executing the following tasks:
* unlogall
* reset port settings
* close port
"""
self.sendUnlogall()
self.exitFlag.set()
self.sendUnlogall()
self.threadID.join()
# reset port settings to default
self.myPort.break_condition = True
self.myPort.send_break()
sleep(1.5)
self.myPort.send_break()
sleep(0.25)
self.myPort.close()
self.isOpen = False
self.log.info("Shuting down")
return True
def main():
"""Set of test to run to see if class behaves as expected.
Creates a Gps class object and execute the following commands on gps receiver:
- begin: on default port or given port by argv[1].
- sendUnlogall
- setCom(baud=115200): changes baudrate to 115200bps
- askLog(trigger=2, period=0.1): ask for log *bestxyz* with trigger `ONTIME` and period `0.1`
- wait for 10 seconds
- shutdown: safely disconnects from gps receiver
"""
import argparse
def printData(dataQueue, exitFlag):
""" prints data to console
Thread used to print data from request log (bestxyz) to the console.
Args:
dataQueue: queue class object where data is stored
exitFlag: a flag to control the exit of program gracefully
"""
print("Index,Time,PSolStatus,X,Y,Z,stdX,stdY,stdZ,VSolStatus,VX,VY,VZ,stdVX,stdVY,stdVZ,VLatency,SolAge,SolSatNumber\n")
while(exitFlag.isSet() == False):
if(dataQueue.empty() == False):
newData = dataQueue.get()
print('{0:5d},{1},{2},{3},{4},{5},'
'{6},{7},{8},{9},{10},{11},'
'{12},{13},{14},{15},{16},'
'{17},{18}\n'.format(newData['Index'],
newData['Time'],
newData['pSolStatus'],
newData['position'][0],
newData['position'][1],
newData['position'][2],
newData['positionStd'][0],
newData['positionStd'][1],
newData['positionStd'][2],
newData['velSolStatus'],
newData['velocity'][0],
newData['velocity'][1],
newData['velocity'][2],
newData['velocityStd'][0],
newData['velocityStd'][1],
newData['velocityStd'][2],
newData['vLatency'],
newData['solAge'],
newData['numSolSatVs']
))
else:
sleep(0.1)
return
# -------------------------------------------------------------------------
# Start of the main program
# -------------------------------------------------------------------------
# add arguments options
parser = argparse.ArgumentParser(add_help=True,
description='Novatel GPS receiver interface library')
parser.add_argument("-p", "--port", action="store", type=str,
dest="port", default="/dev/ttyUSB0", help='serial port used')
parser.add_argument("-n", "--name", action="store", type=str,
dest="name", default="GPS1", help='ID of sensor, in case of multiple units')
parser.add_argument('--log', action='store', type=str, dest='log', default='output.log',
help='log file to be used')
parser.add_argument("--log-level", action="store", type=str,
dest="logLevel", default='info',
help='Log level to be used. See logging module for more info',
choices=['critical', 'error', 'warning', 'info', 'debug'])
args = parser.parse_args()
log_Level = {'error': logging.ERROR,
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'critical': logging.CRITICAL
}
logging.basicConfig(filename=args.log,
level=log_Level[args.logLevel],
format='[%(asctime)s] [%(threadName)-10s] %(levelname)-8s %(message)s',
filemode="w")
# ---------------------------------------------------------------------------
# define a Handler which writes INFO messages or higher in console
# ---------------------------------------------------------------------------
console = logging.StreamHandler()
console.setLevel(log_Level[args.logLevel])
# set a format which is simpler for console use
formatter = logging.Formatter('%(name)-20s: %(levelname)-8s %(message)s')
# tell the handler to use this format
console.setFormatter(formatter)
# add the handler to the root logger
logging.getLogger('').addHandler(console)
# ---------------------------------------------------------------------------
# event flag to exit
exitFlag = threading.Event()
# create a queue to receive comands
dataFIFO = queue.Queue()
# create a thread to parse responses
thread1 = threading.Thread(name="printData", target=printData,
args=(dataFIFO, exitFlag))
thread1.start()
# instanciate a class object
gps = Gps(args.name)
# begin
if gps.begin(dataFIFO, comPort=args.port) != 1:
logging.info("Not able to begin device properly... check logfile")
exitFlag.set()
thread1.join()
return
# send unlogall
if gps.sendUnlogall() != 1:
logging.info("Unlogall command failed... check logfile")
gps.myPort.close()
return
# reconfigure port
gps.setCom(baud=115200)
# ask for bestxyz log
gps.askLog(trigger=2, period=0.1)
# wait 10 seconds
sleep(10)
# stop logs and shutdown gps
gps.shutdown()
# stop print thread
exitFlag.set()
thread1.join()
logging.shutdown()
# exit
logging.info('Exiting now')
return
if __name__ == '__main__':
main()