Getting real-time MEASX messages from GNSS without u-center

Last update: November 16th, 2021

  • Added Compact raw measurement and binary format

Introduction

The purpose of this guide is to learn how to get the real-time MEASX messages from u-blox GNSS M8/M9 without using u-center, directly communicating with the GNSS evaluation kit, and sending the message to the CloudLocate service.

Two options are described below:

  1. Get MEASX messages from GNSS receiver and store it in a file

    • At the end of this section you will be able to quickly configure the GNSS module, even without using u-center. Of course you can do the same operations with u-center, if you prefer using it.

  2. Get MEASX messages from GNSS receiver using Python script

    • At the end of this section you will be able to use CloudLocate exactly as it will be with your IoT device and you will have a tool for testing and optimizing the custom parameters to better adapt CloudLocate to your scenario.


Prerequisites

Before reading this guide:

  • read carefully the CloudLocate getting started guide

  • be sure to have Python 3.9.x installed in your PC and to be familiar with Python coding

  • get an M8 EVK or an M9 EVK. Alternatively, you can have an M8/M9 GNSS mounted on your device. This guide only covers direct communication with the GNSS module and does not cover combo modules.

Get MEASX messages from GNSS receiver on a file

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 .

  1. 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

  1. Capture MEASX messages: select the “Capture” tab, and select a file to save your data in. Make sure to save the file with extension “.ubx”. No need to configure anything else. Once you’re ready, click the “Start” button to start capturing the MEASX messages. Stop the capture after few seconds to complete the message acquisition.

In order to get the position, convert the contents of the file into Base64 and send it to Cloudlocate as described in the CloudLocate getting started guide.

  1. Disable the MEASX message: If, for some reason, you want to disable it, 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

Get MEASX message from GNSS receiver using a Python script

The above section is great for getting an understanding on how to configure the GNSS module and get snapshot MEASX messages using GNSS receiver. However, in a real use case, you need to get the message real-time from the GNSS receiver using some code in the MCU of your device and send it to the CloudLocate service to calculate your position. You may also need to balance the power consumption versus the location accuracy. In the sample code below we provide some parameters that you can adjust to obtain the desired result.

The script is able to collect real-time MEASX messages from an M8-EVK, and send the correct payload to CloudLocate when the configured conditions are met.

Before presenting the whole script (which you can run as-is, provided you have GNSS receiver kit plugged in), let's have look at some key sections of it:

  1. Custom parameters to optimize the quality of MEASX message

COMPORT = "COM4"

BAUD_RATE = 9600

GNSS_TYPE = "GPS"

TIMEOUT = 20

CNO_THRESHOLD = 25

MIN_NO_OF_SATELLITES = 4

MULTIPATH_INDEX = 3

PSEUDO_RANGE_ERR = 10

EPOCHS = 3

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 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 MEASX messages that fall under given criteria.

  • CNO_THRESHOLD: is carrier-to-noise ratio threshold. Any satellite with a CNO below this threshold will be ignored. The higher the value, the better the message. A meaningful threshold can be 35 dB/Hz, but the correct one can be achieved only by doing some testing in your real environment

  • MIN_NO_OF_SATELLITES: each MEASX message can contain information for more than 1 satellite. This parameter allows you to set the number of satellites that are part of a MEASX message. The minimum value is 4; a low number could affect the accuracy, a high number (i.e. 32) 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 4 and 10

It's worth remembering that a MEASX message comprises 44 bytes for the header plus 24 bytes for each satellite.

  • 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 (3 can be used as starting point for testing)

  • PSEUDO_RANGE_ERR: this parameter determines pseudo-range RMS error value. It shows the error index of the pseudo-distance between the satellite and the GNSS receiver.

  • 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 range is between 1 to 3 epochs. This setting affects the size of payload and of course in several use cases the only acceptable value is 1.

  • 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 delayed messages, 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 temporary the connectivity and you need 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.

  1. Once you have adjusted the parameters, you can capture real-time MEASX messages 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:

  1. It opens up a COM port to communicate with the receiver

  2. It sends a message to receiver to enable MEASX messages

  3. It then reads a chunk of data received from the receiver (based on the MEASX message header)

  4. 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.

  5. 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.

  1. Now that you are capturing a real-time MEASX message, you need to send it to CloudLocate to get the position. For this, as mentioned in the getting started guide (CloudLocate getting started ), we 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()


  1. The whole script to get MEASX message is available in the Download section. Do not forget to:

    • set the MQTT credentials for the Location Thing in the proper section

    • configure the communication port, and baud rate

    • adjust the settings

Note: the code is provided just as reference and you are allowed to modify it accordingly to your needs

Compact raw measurement and binary format

The MEASX message is approximately 170 per message (44 bytes for the header, 24 bytes for each satellite, minimum 5 satellites) but it rapidly increase of size if you want to include more satellites.

If your Wide Area Network or your use case does not allow to send over the air this payload size, you can use the compact raw measurement. You can further reduce the message size by using the binary format. If you are in this scenario, look at Compact raw measurement guide.