Using CloudLocate with M8/M9 GNSS
Introduction
The purpose of this guide is to learn how to use CloudLocate in Device to Service mode using real-time GNSS raw measurements from u-blox GNSS M8/M9.
Prerequisites
Before reading this guide:
read carefully the CloudLocate getting started guide
install Python 3.9.x installed in your PC (to use the sample code)
get an M8 EVK or an M9 EVK or an M10 EVK (EVK-M101-00-01) . Alternatively, you can have an M8/M9/M10 GNSS mounted on your device. This guide only covers direct communication with the GNSS module and does not cover combo modules.
Note: if you are not familiar with GNSS configuration, you can initially collect the raw measurement using u-center (See this guide) and then come back to this guide
Set-up the connection with GNSS
You can use any program for serial communication with the GNSS EVK. In the example below we used Realterm on Windows 10.
Step 1 : connect the GNSS evaluation kit to your computer using USB. Once you’ve connected, windows will automatically recognize the device and will install necessary drivers.
Step 2: open Realterm software, and by default the “Display” tab will be selected. Even without any configuration you will see some data flowing. Change the display to “Hex[space]”.
Next, on the “Port” tab (next to Display), set the following:
Baud to 9600
Set to the port of your PC on which GNSS evaluation kit is connected. It would most probably be displayed as “USBSER000”
Once you’ve configured Port settings (as above), click “Open” button, and it will open a channel with GNSS module.
Note: depending on the EVK that you are using, the baud rate can be different. In order to find which is your EVK's baud rate, see the corresponding products user guide.
Step 3: now that the communication with the EVK has been successfully established, you can configure the GNSS to generate the UBX-RXM-MEASX message and send it out to the configured port .
enable the UBX-RXM-MEASX message by sending the UBX-CFG-MSG command with following parameters (in hex):
0xB5 0x62 0x06 0x01 0x03 0x00 0x02 0x14 0x01 0x21 0x6E
To send this command, select “Send” tab, and paste the above hex in the text box, and then click “Send Numbers”. This will enable MEASX message on GNSS receiver.
Note: in the case that after enabling the message, you do not see any data being sent from the module, you may have to enable periodic messages. To do that, send UBX-NAV-STATUS message with following parameters (in hex)
0xB5 0x62 0x01 0x03 0x00 0x00 0x04 0x0D
Capture a UBX-RXM-MEASX messages after 3 to 5 seconds with good signal condition
In order to get the position, send the message to Cloudlocate using MQTT protocol as explained in the Getting started guide. You can use two different formats
for real time position, send it in binary mode as you received from the GNSS, without any change/encoding, including the header and the checksum
for not real-time position send it in the JSON format as described in the Getting started guide, but differently from the other type of raw measurement, in this case you shall include also the header and encode all the message in Base64
Note: to disable the UBX-RXM-MEASX message send the following command:
0xB5 0x62 0x06 0x01 0x03 0x00 0x02 0x14 0x00 0x20 0x6D
Note: The configuration is not persistent. In order to make it persistent, please see Receiver configuration Section 3 on Page 9 of Protocol specification document .
Note: In order to get an understanding of how the commands are structured, and created, and what are the commands available, please use the above mentioned protocol specification document and look for the 3 message format used for the configuration
In this section
Still need help?
CloudLocate getting started guide
If you need more help or have any questions, please send an email to services-support@u-blox.com.
Sample code
The above section allows you to understanding on how to configure the GNSS module and get the raw measurement (UBX-RXM-MEASX) using GNSS receiver. However, in a real use case, you need to filter the real-time messages coming from the GNSS to validate the quality of the satellite signals and avoid no-fix condition, and also to apply a fallback strategy if the quality of the signal is not good enough. The main goal is balance the power consumption versus the location accuracy and to avoid no-fix situations in the cloud
u-blox provides a sample code for host processor that allows you modify few parameters to achieve the best result according to your strategy.
The script:
collects real-time UBX-RXM-MEASX messages from an M8/M9 GNSS,
filters the messages to met the given criteria
send the correct payload to CloudLocate when the configured conditions are met, or apply a fallback option
The sample code for M8 (and M9) is available in the Download area of the Thingstream portal in the IoT Location-as-a- service > CloudLocate section.
The next sections provides more details about the code, the settings and the fallback options
Note: the code is provided just as reference and you are allowed to modify it accordingly to your needs
General settings
In this section you can find the explanation of the general settings to connect to GNSS and the custom parameters to optimize the validate the quality the satellite information contained in the UBX-RXM-MEASX message so that you can filter out low quality signals.
COMPORT = "COM4"
BAUD_RATE = 9600
GNSS_TYPE = "GPS"
TIMEOUT = 10
CNO_THRESHOLD = 25
MIN_NO_OF_SATELLITES = 5
MULTIPATH_INDEX = 2
PSEUDO_RANGE_ERR = 39
EPOCHS = 1
SEND_BINARY = False
FALLBACK_METHODOLOGY = FallbackConfig.FALLBACK_DO_NOT_SEND
where:
COMPORT is the port on which you've connected the EVK
BAUD_RATE is the rate on which your EVK is configured. In order to see what is your EVK's baud rate see the corresponding products user guide.
GNSS_TPYE is the preferred constellation that will be used for the target UBX-RXM-MEASX messages. The script will ignore any satellite that does not belong to the configured constellation. Our tests shows that the best performance can be achieved with GPS, but this can change from region to region. Only one constellation is allowed
TIMEOUT is the maximum number of seconds the script will try to get a UBX-RXM-MEASX message that falls under given criteria.
MIN_NO_OF_SATELLITES: each UBX-RXM-MEASX message contains information for more than 1 satellite. This parameter allows you to set the minimum number of satellites to consider valid the UBX-RXM-MEASX message. The minimum value is 5; a low number could affect the accuracy, a high number (i.e. 16) affects the size of the payload and the time to meet this condition (and the risk to not meet the condition) and so finally the power consumption. An acceptable range for constrained devices can be between 5 and 10
It's worth remembering that a MEASX message comprises 44 bytes for the header plus 24 bytes for each satellite.
CNO_THRESHOLD: it is the carrier-to-noise ratio threshold. Any satellite with a CNO below this threshold will be ignored and all the UBX-RXM-MEASX will be discarded. The higher the value, the better the message. A good threshold is 35 dB/Hz, but the correct one can be achieved only by doing some testing in your real environment
MULTIPATH_INDEX: multipath index determines the path that the GNSS signal took to reach the receiver (whether it had a straight line of sight, or it bounced off buildings or other obstacles). The lower value the better (2 can be used as starting point for testing, 1 = low, 2 = medium, 3 = high). More information about the the index can be found in the specifications of GNSS Signal Measurement field of the 3GPP Radio Resource Location Protocol.
PSEUDO_RANGE_ERR: this parameter determines pseudo-range RMS error value. The index represents the range of the error of the calculated distance between the satellite and the GNSS receiver. This is an index that can span from 0 to 63; setting a low value can lead to a better accuracy in good sky conditions, but it brings also the risk to not being able to converge to a solution in bad sky conditions. More information about the meaning of the index can be found in the specifications of GNSS Signal Measurement field of the 3GPP Radio Resource Location Protocol. The value has two effects:
if any of the satellite reported in the MEASX message does not met this condition, the entire frame is discarded and a new one will be considered, thus increasing the time that the GNSS is switched on and the corresponding power consumption
A satellite with a lower value will be considered as more reliable by the CloudLocate location engine
An acceptable index to start testing can be 39.
EPOCHS: each MEASX message is 1 epoch. This parameter lets us configure number of epochs to record (which fall under above parameters) before sending the payload to CloudLocate. The recommended setting is 1 epoch since an higher value increase significantly the payload size.
SEND_BINARY: set this flag when you want to send the raw measurement as a binary messages (exactly as provided by GNSS) to CloudLocate service endpoint. Otherwise, Base64 encoded JSON payload will be sent. JSON format is the only option that you can use whenever you need to send not real-time (delayed) measurements, because you need to add also date and time. A delayed message is a raw measurement that reaches CloudLocate endpoint more than 59 seconds after the generation, useful for example if you do not have the connectivity and you want to store the measurement in the device for post-processing.
FALLBACK_METHODOLOGY: controls the way the script behaves if it does not find required MEASX messages as per main configuration. There are a few fallback methodologies defined in the code, and which one to use depends on this parameter. Available fallback methodologies (configurations) are:
FALLBACK_DO_NOT_SEND will not use any fallback, and is the default when running this script.
FALLBACK_NO_OF_SATELLITIES_ONLY will fallback to only match # of satellites (irrespective of CNO value).
FALLBACK_EXTEND_TIMEOUT will extend the timeout value, so if a match is not found without original time, it will add this more seconds.
FALLBACK_EPOCHS will use main configuration but override number of epochs set to 1.
FALLBACK_RECEIVER_POSITION: this option allows to use the position estimated by the GNSS and forward it to CloudLocate endpoint. See Mixed mode section for more information
Capturing raw measurements
Once you have adjusted the parameters, you can capture real-time UBX-RXM-MEASX measurements using the following code snippet:
with serial.Serial(COMPORT, BAUD_RATE, timeout= TIMEOUT) as ser:
#enable MEASX message on the receiver
ser.write(MEASX_ENABLE_MESSAGE)
# even though it is possible to get first message as a valid MEASX message
# but we are not sure, and cannot rely on the first message to be so
# so, we always ignore first message
rawMessage = ser.read_until(MEASX_HEADER)
startTime = time.time()
# continue reading MEASX messages until:
# [a] timeout happens
# [b] you get number of messages as per configuration
# [c] save this measx message, along with satellite info so it can be used for fallback logic
while (time.time()-startTime) <= TIMEOUT and validMessageCounter < EPOCHS :
# read message from receiver
rawMessage = ser.read_until(MEASX_HEADER)
# calculate size of payload contained inside read MEASX message
size = (rawMessage[1]<<8)|rawMessage[0]
#checking size for a valid measx message
if(size <= 33) :
print(f"Message skipped: {rawMessage.hex()}")
#skipping this message
continue
# extract actual MEASX payload (without header, and checksum)
measxMessagePayload = rawMessage[2:size+2]
# get the number of satellites contained in this message
numSv = measxMessagePayload[34]
print("Number of satellites: ", numSv)
# need to save this message for fallback logic
processedMeasxMessage = {
'measxMessage': rawMessage[0:size+4],
'maxCNO': 0,
'satellitesInfo': { }
}
satelliteCount = 0
# for the number of satellites contained in the message
# we need to see if every satellite's data falls
# as per our configuration
# because a single MEASX message can contain
# more than one satellite's information
for i in range(0, numSv):
# only accept the message if it fulfills our criteria,
# based on configuration parameters above
gnss = measxMessagePayload[44+24*i]
if gnss == CONSTELLATION_TYPES[GNSS_TYPE]:
# psuedoRangeRMS error index
psuedoRange = measxMessagePayload[65 + 24 * i]
# carrier-to-noise ratio
cNO = measxMessagePayload[46+24*i]
if cNO > processedMeasxMessage['maxCNO']:
processedMeasxMessage['maxCNO'] = cNO
multipathIndex = measxMessagePayload[47+24*i]
svID = measxMessagePayload[45+24*i]
processedMeasxMessage['satellitesInfo'][svID] = {'cno': cNO, 'mpi': multipathIndex }
if cNO >= CNO_THRESHOLD and multipathIndex <= MULTIPATH_INDEX and psuedoRange <= PSEUDO_RANGE_ERR:
satelliteCount = satelliteCount + 1
print(f"gnss: {gnss} ... svID:{svID} ... cNO: {cNO} ... multipathIndex: {multipathIndex} ... psuedoRange:{psuedoRange}")
# saving processed message for fallback logic
READ_RAW_MEASX_MESSAGES.append(processedMeasxMessage)
if satelliteCount >= MIN_NO_OF_SATELLITES:
MEASX_MESSAGE.extend(MEASX_HEADER)
MEASX_MESSAGE.extend(bytearray(rawMessage[0:size+4]))
validMessageCounter = validMessageCounter + 1
The above code snippet does the following:
It opens up a COM port to communicate with the receiver
It sends a message to receiver to enable MEASX messages
It then reads a chunk of data received from the receiver (based on the MEASX message header)
It then determines, based on set parameters, if the read MEASX message falls under our required criteria. If it does, we keep it, otherwise, we discard it, and read another.
It also saves this MEASX message in memory, to be used by fallback logic.
At the end, we determine if we have our desired MEASX message or not. If yes, we continue on. If no, we see if any fallback methodology is selected (not shown in code snippet above). If yes, we run selected fallback configuration against saved MEASX messages. If we find one, we continue on. If no, we stop.
Send the raw measurement to the service
Now that you are capturing a real-time UBX-RXM-MEASX message, you need to send it to CloudLocate endpoint to get the position. For this, as mentioned in the CloudLocate getting started guide, you should already have a CloudLocate Thing. The following code snippet converts MEASX message into a base64 encoded string, establishes an MQTT connection with the server, sends the data, and gets the position back on the device. Optionally, as described in the Getting Started guide, you can get the position from your application platform.
# we need to publish our base64 encoded message on this topic
MQTT_PUB_TOPIC = "CloudLocate/GNSS/request"
# we will get a position back on this topic, so we need to subscribe to it
MQTT_SUB_TOPIC = f"CloudLocate/{DeviceID}/GNSS/response"
# create JSON payload, which will be sent to CloudLocate, exit if it exceeds 8KB
if SEND_BINARY:
# Sending RAW binary message
MQTT_MSG = MEASX_MESSAGE
else:
# convert our MEASX byte-array into base64 encoded string
BASE64_ENC_PAYLOAD = base64.b64encode(MEASX_MESSAGE).decode()
MQTT_MSG = f"{{\"body\": \"{BASE64_ENC_PAYLOAD}\"}}"
print(f"JSON formatted payload: {MQTT_MSG}")
if len(MQTT_MSG) > 8192:
print("Cannot send MQTT message greater than 8KB. Please reduce the number of EPOCHS in configuration parameters to reduce the size.")
exit()
# this is our callback function when we receive a location on our subscribed topic
def message_handler(client, userdata, message):
json_resp = message.payload.decode('utf-8')
print(f"received message: {json_resp}")
print(f"message topic: {message.topic}")
print(f"message QoS: {message.qos}")
json_resp = json.loads(json_resp)
lat = json_resp["Lat"]
lon = json_resp["Lon"]
print(f"I am here: https://www.google.com/maps/search/?api=1&query={lat},{lon}")
# calling disconnect will cause blocking loop to exit, effectively ending the program
client.disconnect()
# configure MQTT client
client = mqtt.Client(DeviceID)
client.username_pw_set(username = Username, password = Password)
client.connect(Hostname)
client.on_message = message_handler
# subscribe to topic
if MQTT_SUB_TOPIC:
client.subscribe(MQTT_SUB_TOPIC)
# publish message
client.publish(MQTT_PUB_TOPIC, MQTT_MSG)
print(f"message published: {MQTT_MSG} ")
# wait for a position back from CloudLocate
# you will receive it in the callback function
# once payload is received in callback, it will disconnect the client, which will cause the loop to break
# hence ending the script
client.loop_forever()