Skip to content

Instantly share code, notes, and snippets.

@veproza
Last active July 23, 2023 04:36
Show Gist options
  • Save veproza/55ec6eaa612781ac29e7 to your computer and use it in GitHub Desktop.
Save veproza/55ec6eaa612781ac29e7 to your computer and use it in GitHub Desktop.
Getting u-blox MAX-7C GPS to work with Assisted A-GPS

Getting u-blox MAX-7C GPS to work with Assisted A-GPS

So you got your u-blox GPS and wired it up only to look at it struggling to get a valid fix? Under less than ideal conditions, it can take a better part of half an hour. That's because unlike your smartphone GPS, it doesn't have the luxury of having downloaded all the auxiliary navigation data (almanacs and the lot) out-of-band, via fast mobile connection. Instead it relies on the satellite's signal itself, which is being transmitted to you at meager 50 bits per second (I'm not missing "kilo" there, it's three orders of magnitude slower than your 2G GPRS connection).

Luckily, the u-blox receivers are fitted with what the company calls "AssistNow" capability and it does exactly the same thing your iPhone does - feeds the GPS with pre-downloaded almanacs, speeding up the acquisition process to mere seconds.

In principle, the process looks easy enough - we just need to download the data, and then push them to the receiver. Sadly, the AssistNow documentation is hard to find and even then a tad lacking. But that's why you're reading this article, right? Let's get to business!

U-blox A-GPS offerings

AssistNow comes in two flavors: Online and Offline. The difference isn't the method of delivery of data - u-blox won't be snail-mailing you the almanacs. Instead, it's all about the validity of the data: Online is good for only two hours, Offline can last for a month. That means you can get the data preloaded if you expect to be offline for the measurements, but can be online at some time before getting to the field.

Of course the Offline service comes at a cost. While the Online packages are weighing in at single kilobytes (and are therefore downloaded fast even on 2G infrastructure), the 35-day-valid Offline pack will set you back a hefty 125kB. Also the Online is stated to be faster - u-blox claims 1 second time-to-fix best-case-scenario, compared to 5 seconds for Offline.

From now on, I'll be focusing on the Online branch, but Offline should be pretty much analogous.

Getting the data

Setting up the free account

Here's the first hurdle: u-blox requires an account to download the data. The process is a little unconventional, but fast: you need to send an email to agps-account@u-blox.com. If you're shy, don't worry - it's an automated service, you don't need to write anything. Leave both the subject and the body unfilled and just send a completely blank email. Within a minute or two, you'll receive your login from agps-server@u-blox.com:

Dear xxx@xxx.xx

Thank you for using this AGPS Service from u-blox.

You account has been created as follows:

   Username: "xxx@xxx.xx"
   Password: "xxx"

   Server:   agps.u-blox.com
             DO NOT USE IP NUMBERS.
   Port:     46434
   Protocol: TCP or UDP

Newly created accounts can take up to a few hours until active.

Note the username, password, server and port and let's finally get the data package.

Downloading

Here comes the second hurdle, as the u-blox protocol is quite similar to, yet not compatible with HTTP. We'll therefore have to forgo the high-level Request libraries and go to bare sockets! Don't worry though, the most complicated thing is splitting the HTTP-like header and getting the body of the message.

As I'm using the Raspberry Pi, I'll be coding in Python. Other languages should be analogous, however.

The Connection

First, we need to connect to the u-blox server. We use the Socket package, which is included in Python by default.

import socket
sock = socket.socket()
address = "agps.u-blox.com"
port = 46434
print "Connecting to u-blox"
sock.connect((address, port))
print "Connection established"

The Request

Now we need to send the actual request. It's a sequence of name=value pairs, separated with semicolons ;. The required variables are as follows:

  • cmd: the requested information. full in our case, as it's no use for us to download only ephemeris or only almanac.
  • user: your username (email)
  • pwd: your password, in plain text
  • lat: approximate latitude of your device (i.e. center of the state or country you're in)
  • lon: approximate longitude of your device
  • pacc: accuracy of the lat/lon position, in meters. Optional, defaults to 300000 (300 kilometers)

With that sorted out, let's send the request and load it into data variable

print "Sending the request"
sock.send("cmd=full;user=xxx@xxx.xx;pwd=xxx;lat=50.0;lon=14.3;pacc=10000")

data = ""
buffer = True;

while buffer:
    buffer = sock.recv(1024)
    if buffer:
        data += buffer

Parsing the received data

If you now print the data, you'll see something like this:

u-blox a-gps server (c) 1997-2009 u-blox AG
Content-Length: 2696
Content-Type: application/ubx

(binary data)

The binary is what we're after, so let's parse it out. It's separated from the header with two empty lines, so we'll find the first occurence of \r\n\r\n with index and then slice (substring) the data. Of course it would be more appropriate to parse the header and check for error codes, but hey, who ever does that?

headerEndsAt = data.index("\r\n\r\n")
binaryStartsAt = headerEndsAt + 4 # length of the newline sequence
binary = data[binaryStartsAt:]

That's it - we finally have all the data we need. Now to send them to the receiver.

Uploading the data to GPS

This is comparatively simple, with one gotcha - apparently, the GPS doesn't handle well when there is duplex (bi-directional) communication on the serial line. I'm not completely sure about it, but I think it works better when you first drain the buffer and only then send the AGPS data.

import serial
ser = serial.Serial("/dev/ttyAMA0", 9600)
print "Waiting for free line"
drainer = True
while drainer:
    drainer = ser.inWaiting()
    ser.read(drainer)

With the pipes clean, send the that just like we received it.

print "Writing AGPS data"
ser.write(binary)
print "Done"

Now let's check our success and read the GPS - we should see an almost instantaneous fix. Read the serial and print out every GPGGA NMEA message, until keyboard interrupt (ctrl+c) is sent.

buffer = True
message = ""
try:
    while buffer:
        buffer = ser.read()
        if buffer == "$":
            if message.startswith("$GPGGA"):
                print message.strip()
            message = ""
        message = message + buffer
except KeyboardInterrupt:
    ser.close()

Ideally, our output should look like this:

Connecting to u-blox
Connection established
Sending the request
Waiting for free line
Writing AGPS data
Done
$GPGGA,223734.00,,,,,0,03,3.64,,,,,,*57
$GPGGA,223735.00,,,,,0,03,3.64,,,,,,*56
$GPGGA,223736.00,,,,,0,04,3.16,,,,,,*57
$GPGGA,223737.00,,,,,0,04,3.16,,,,,,*56
$GPGGA,223738.00,,,,,0,04,3.16,,,,,,*59
$GPGGA,223739.00,,,,,0,04,3.16,,,,,,*58
$GPGGA,223740.00,50xx.xxxxx,N,014xx.xxxxx,E,1,04,3.16,345.1,M,44.5,M,,*53
$GPGGA,223741.00,50xx.xxxxx,N,014xx.xxxxx,E,1,04,3.16,345.4,M,44.5,M,,*51
$GPGGA,223742.00,50xx.xxxxx,N,014xx.xxxxx,E,1,04,3.16,346.2,M,44.5,M,,*50
$GPGGA,223743.00,50xx.xxxxx,N,014xx.xxxxx,E,1,04,3.16,345.0,M,44.5,M,,*50
$GPGGA,223744.00,50xx.xxxxx,N,014xx.xxxxx,E,1,04,3.16,344.4,M,44.5,M,,*59
$GPGGA,223745.00,50xx.xxxxx,N,014xx.xxxxx,E,1,04,3.16,344.0,M,44.5,M,,*52
$GPGGA,223746.00,50xx.xxxxx,N,014xx.xxxxx,E,1,04,3.16,344.3,M,44.5,M,,*5A
$GPGGA,223747.00,50xx.xxxxx,N,014xx.xxxxx,E,1,04,3.16,344.2,M,44.5,M,,*59

As you can see, we were able to get from cold start to fix in only six seconds. If you aren't so lucky on your first run, try executing the file again. For reasons yet unknown, the data package doesn't always get received. If you need a more bulletproof solution, you might want to run the sending in a cycle and check for the ACK message - you can see an implementation here.

The complete source code is available in the next file in this Gist. If you come upon any mistakes, file an Issue or even send a Pull Request, they're all welcome!

Thanks for reading and enjoy your newly fast GPS :-)

License (MIT)

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.

References:

  1. U-Blox AssistNow Application Note
import socket
import serial
sock = socket.socket()
address = "agps.u-blox.com"
port = 46434
print "Connecting to u-blox"
sock.connect((address, port))
print "Connection established"
print "Sending the request"
sock.send("cmd=full;user=xxx@xxx.xx;pwd=xxx;lat=50.0;lon=14.3;pacc=10000")
data = ""
buffer = True;
while buffer:
buffer = sock.recv(1024)
if buffer:
data += buffer
headerEndsAt = data.index("\r\n\r\n")
binaryStartsAt = headerEndsAt + 4 # length of the newline sequence
binary = data[binaryStartsAt:]
ser = serial.Serial("/dev/ttyAMA0", 9600)
print "Waiting for free line"
drainer = True
while drainer:
drainer = ser.inWaiting()
ser.read(drainer)
print "Writing AGPS data"
ser.write(binary)
print "Done"
buffer = True
message = ""
try:
while buffer:
buffer = ser.read()
if buffer == "$":
if message.startswith("$GPGGA"):
print message.strip()
message = ""
message = message + buffer
except KeyboardInterrupt:
ser.close()
@shindler1982
Copy link

Hi,
you create a new array binary = data [binary StarsAt:]
and sent to the port array data ser.write(data).
Should I change line 34 to ser.write(binary)?

@veproza
Copy link
Author

veproza commented Dec 20, 2015

@shindler1982: Absolutely, thats for catching that! I'll change it right away. I was rewriting the code for this Gist and made this mistake. Then I must've gotten extremely lucky with a fast and exactly-on-time aquisition.

@veproza
Copy link
Author

veproza commented Dec 20, 2015

To be sure that the AGPS is working, and you're not getting the data the "traditional" way from the satellites, it's best to place the receiver deep in a building where there is no GPS signal. Then monitor the $GPGSV sentences. After successful AssistNow upload, they will be populated with 10 - 12 satellites, all of them with null or very low SNR, like this:

$GPGSV,3,1,12,02,21,242,,05,51,294,,06,05,206,,07,73,078,*78
$GPGSV,3,2,12,09,41,092,,13,18,276,,16,06,024,,20,05,317,*71
$GPGSV,3,3,12,23,06,096,,27,01,056,,28,08,166,,30,67,201,*77

Note the null SNRs on each of them (see http://aprs.gids.nl/nmea/#gsa to decode the sentence structure)

@sakamoto-poteko
Copy link

sakamoto-poteko commented Jun 8, 2016

AssitNow automatic registration service is no longer available.

Dear xx

This registration mechanism is no longer active. Please use the following link instead:

www.u-blox.com/services-form

For more information please refer to the MultiGNSS AssistNow user guide, available on the AssistNow section of our website:

www.u-blox.com/services

Thank you very much for choosing u-blox AssistNow Services and best regards,

You'll have to fill this form and wait for their approval.

@sachinrana2k11
Copy link

hi ,
i got this error ----
please help
Traceback (most recent call last):
File "C:/Users/SACHIN RANA/PycharmProjects/mygoogle/my_main.py", line 7, in
sock.connect((address, port))
TimeoutError: [WinError 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond

@mo8Zomo0
Copy link

mo8Zomo0 commented Mar 2, 2018

@sachinrana2k11
I am not sure, but I did not get the password and had tried the script 3-4 times just to see how it works and after
that I was not able to connect anymore, so I would assume they have some kind of content based firewall filtering your ip
after a given amount of false attempts.

@mo8Zomo0
Copy link

mo8Zomo0 commented Mar 2, 2018

Update: just saw that @sakamoto-poteko already mentioned the service being down, maybe worth changing the documentation.

@veproza

The "instant" email service does not exist anymore, you will get some email back saying that you have to
register now via a web form. That seems to require some human interaction and at least on a Friday evening
it did not happen anything anymore, so I guess its operated during swiss business hours and I have to wait till Monday
or just takes time, ...

URL of the form: https://www.u-blox.com/en/assistnow-service-registration-form

@nemanjan00
Copy link

I did get email back, but, I got token instead of username/password...

Anyone figured out how to use that?

@harishg92
Copy link

Hi is this script is working?

Even after running this script, not able to get lat and long values without connecting antenna.
I am using "ubox M8 GNSS Evaluation Kit".

Thanks in advance..!

@Hadatko
Copy link

Hadatko commented Nov 22, 2021

I did get email back, but, I got token instead of username/password...

Anyone figured out how to use that?

For future generation ;) :

https://developer.thingstream.io/guides/location-services/assistnow-getting-started-guide

#/bin/python3
# Documentation https://developer.thingstream.io/guides/location-services/assistnow-getting-started-guide

import requests
Token = # fill token
url = f"https://online-live1.services.u-blox.com/GetOnlineData.ashx?token={Token};gnss=gps,glo,gal,qzss;datatype=eph,alm,aux;"
print(url)
r = requests.get(url, allow_redirects=True )
if not r.ok:
    print(r.json())
print(len(r.content))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment