Cellular & GPS Enabled Pi 3: Fona + Pi 3

By Initial State Technologies

Courtesy of Initial State

Cellular and GPS Pi 3 Fona

What if your Raspberry Pi 3 could access internet services from anywhere? What if your Fona Cellular GSM and GPS breakout had the brains of a full Linux computer behind it? What if you could access the Fona's cellular capabilities and GPS data from a Raspberry Pi at the same time?

Well, you'd have a pretty sweet start to any IoT project you can think of. And this tutorial's gonna help you do it.


Copy Code
Project level: Advanced Beginner
Approximate time to complete: 40 minutes
Approximate impressiveness: Immeasurable

In this step-by-step tutorial, you will:

  • Setup a serial connection between the Fona and the Pi 3
  • Learn how to use the Fona's cellular connection to give the Pi internet access
  • Learn how to access the Fona's built-in GPS
  • Setup the Pi 3 to collect GPS info and then stream it over a cellular connection

Part 1. Equipment

Parts list

Here is a list of what you'll need along with links to where you can purchase them.

For the final product, you need:

Something to consider - I scavenged the Lipoly battery, GSM antenna, and, most importantly, SIM card from a Particle Electron kit. I am a huge fan of Particle's data plan for small IoT devices and highly recommend getting a hold of one of their SIM cards.

Battery, Antenna, and SIM card

For transferring code and debugging on the Pi 3, you may also need:

  • Bluetooth Keyboard/Mouse
  • Screen or Display
  • WiFi Dongle
  • USB Hub

Part 2. Connecting the Fona


The Fona is a neat little board from Adafruit that allows internet access through a GPRS cellular data connection. It also comes with a built in GPS! Basically what this means is that you can give pretty much any device that can make a serial connection an internet connection as reliable as your smartphone - which sounds like a pretty good deal to me.

When I called it a "neat little board" above, I meant little. Here's a picture of it next to the Pi:

Fona and Pi

Getting it wired up and working is pretty much a cinch. I've adapted Adafruit's instructions on tethering a Raspberry Pi 2 to the Fona to work on the Pi 3.

Let's get connected.



Make sure that everything is powered down and unplugged before you start connecting anything! We'll be using Female-Female tie lines.

  • Connect the Fona's GND to the Pi's GND (Row 2, Pin 3) AND the Fona's Key (I ended up soldering 3 tie lines together to do this, but you can prototype with a breadboard)

Connecting the Fona GND and Pi GND

  • Connect the Fona's Vio to the Pi's 3.3V power (Row 1, Pin1)
  • Connect the Fona's TX to the Pi's RX (Row 2, Pin 5)
  • Connect the Fona's RX to the Pi's TX (Row 2, Pin 4)
  • Connect the cellular antenna now - it is labeled "GSM ANT" on the Fona
  • Connect the Fona and Pi with the micro-USB to USB cable
  • Connect the Lipoly battery to the Fona

Power on the Pi and the Fona should light up, too, with a solid blue LED and slow blinking red LED.


Fona connected to Pi

The Fona is just a bit tricky to setup with the Pi because it communicates with a serial connection (hence the UART pins, TX, RX). By default any Raspberry Pi uses its hardware serial pins for the kernel serial console, so this needs to be disabled.

Adafruit's method didn't work for me on the Pi 3, so I poked around and figured out that it's super easy. All you need to do is edit the /boot/config.txt file (use sudo if you receive a permissions error).

Copy Code
nano /boot/config.txt

Add enable_uart=1 to the end of the file. Save the file with Ctrl-X and reboot your Pi to apply the change.

To test the serial connection, we are going to install PPP and the serial console "screen" on the Pi. You need an internet connection for this part so make sure you've either got an ethernet connection or setup the Pi's onboard WiFi.

Copy Code
sudo apt-get update

sudo apt-get install ppp screen

Now we're going to try out our newly installed screen:

Copy Code
sudo screen /dev/serial0 115200

Don't freak out - you should see a completely blank window with a cursor at the top. Type in the letters "AT", capitalized and everything, and hit enter. You should see an "OK" appear - that's the Fona talking back to you! You've verified that the serial connection is working and can exit with Ctrl-A and typing :quit.

If this doesn't work for you, make sure that you rebooted the Pi after changing the /boot/config.txt file and check to see if the red LED on the Fona is blinking rapidly. If the red LED is blinking quickly, there might be a wiring issue, so go back and double check.

Part 3. Using the Cellular Connection

Now that we've got the serial connection up and running, we can start taking advantage of the Fona's cellular capabilities!

Make sure that your SIM card is inserted in the slot on the back of the Fona

You'll also need your card's Access Point Name. Your cellular provider should be able to tell you what it is.

Note: This cellular connection is 2G, so it's only 5-10 kilobytes/second of download speed. You won't be surfing the web with it, but you can talk to internet services, tweet, and text just fine!

Cellular Setup

Now we're going to configure the Point-to-Point Protocol (PPP) so that we can use the Fona's cellular connection.

First we need to act as root and place a configuration file inside of a specific directory:

Copy Code
sudo -i

cd /etc/ppp/peers/

wget https://raw.githubusercontent.com/adafruit/FONA_PPP/master/fona

Open that file to view PPPD settings when "fona" is called.

Copy Code
nano fona

You should see this: 

Copy Code
# Example PPPD configuration for FONA GPRS connection on Debian/Ubuntu.

# MUST CHANGE: Change the -T parameter value **** to your network's APN value.
# For example if your APN is 'internet' (without quotes), the line would look like:
# connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T internet"
connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T ****"

# MUST CHANGE: Uncomment the appropriate serial device for your platform below.
# For Raspberry Pi use /dev/ttyAMA0 by uncommenting the line below:
# For BeagleBone Black use /dev/ttyO4 by uncommenting the line below:

# Speed of the serial line.

# Assumes that your IP address is allocated dynamically by the ISP.

# Try to get the name server addresses from the ISP.

# Use this connection as the default route to the internet.

# Makes PPPD "dial again" when the connection is lost.

# Do not ask the remote to authenticate.

# No hardware flow control on the serial link with FONA

# No modem control lines with FONA.

You'll notice two sections at the top that say "MUST CHANGE" - you guessed it, we're going to edit these. The first part is asking for the APN associated with your SIM card. Since I was using a Particle SIM card, I replaced "****" with "spark.telefonica.com". For the second section, we actually need to add our own serial interface since the Pi 3 is setup differently. Add a line that says "/dev/serial0" under the # MUST CHANGE: Uncomment the appropriate serial device for your platform below. section. Save and exit with Ctrl-X.

If your SIM card has a pin or you want to read more about customizing the GPRS connection, go here.

Be sure to exit the root shell with exit.

Cellular Testing

Time to test out our new way to access the internet!

Make sure that you've unplugged any ethernet cords and turned off the Pi 3's onboard WiFi so that you aren't accidentally seeing internet traffic from something other than the Fona. For this, you might need to hook your Pi up to a screen or monitor and use a bluetooth keyboard/mouse.

To turn on the Fona PPPD connection, run:

Copy Code
sudo pon fona

You won't see any response, so if you want to check on PPPD (after you've let it run for a couple seconds), use:

Copy Code
cat /var/log/syslog | grep pppd

Important pieces of information to look for after using the above command are "Connect: ppp0 <--> /dev/serial0", "PAP authentication succeeded", and a list of IP addresses. The Fona's red LED will also go from a periodic blink to a rapid blink.

If PPPD doesn't seem to be working, you can further debug by looking at the chat command log:

Copy Code
cat /var/log/syslog | grep chat

It should give you an indication of what might be wrong, which is the first step to debugging! If problems persist, it might be provider specific - be sure to check their GPRS and PPP configuration info.

If there were no errors and the red LED is going strong, you've got a connection! Check for the ppp network interface with:

Copy Code

You should see a ppp0 interface. If you don't, check on PPPD again.

You can test if your connection is working by pinging Google:

Copy Code
ping google.com

Sometimes I get a permissions error - if you do too, just add sudo to the front. You should start seeing bytes come in - congratulations, you're internet ready!

Note: If you have issues with pinging, Adafruit has good troubleshooting suggestions.

Second Note: If you want to start PPPD on boot, you can also find how to do that in Adafruit's tutorial. It's not necessary for this project, though.

Part 4. Reading from the GPS

Cool fact: The Fona also has a built-in GPS.

Not-so-cool fact: The Pi can only make one serial connection.

Which leads us to an interesting conundrum - how can you collect GPS data from the Fona and still use the cellular connection (handled by PPPD) to stream that data?

You could handle all the cellular AT commands yourself, but they are way complicated and way better handled by PPPD.

I scoured the internet (so you don't have to), and all I could find was a very complicated way to try (and most likely fail) to give the Pi virtual serial ports.

So instead of doing that, I decided to figure out a way that I could start and stop the PPPD connection and read GPS info in between. The results weren't just successful - I was pleasantly surprised to find out that the Fona was very capable of handling rapid changes in the cellular connection.

So here's how to do it!

GPS Usage

The Fona is already hooked up to your Pi, and we already know that the serial connection is good, so testing the GPS is very easy. The only difficulty I ran into was figuring out that my version of the Fona didn't use "GPS" commands, it used "GNS"! The Fona 808 v1 uses GPS; the Fona 808 v2 uses GNS.

Make sure that the PPPD connection is turned off:

Copy Code
sudo poff fona

And start screen:

Copy Code
sudo screen /dev/serial0 115200

Make sure it's working by typing AT and waiting for the "OK" response. Like I mentioned above, my Fona was v2, so commands used GNS. Substitute GPS if using a v1. You can find a list of GNS/GPS commands here.

Now enter AT+CGNSPWR?. This asks if the GPS is on. It should return a 0 because it's off. To turn it on, enter AT+CGNSPWR=1. You should see an "OK" response.

With the GPS on, we can try to fetch some info. GPS takes forever to find satellites (and actually forever if you're indoors), so we probably don't have a fix yet, but we can still see some info. Enter AT+CGNSINF. You should see +CGNSINF followed by a long line of comma-separated numbers. These correspond to, in order, <GNSS run status>,<Fix status>,<UTC date & Time>,<Latitude>,<Longitude>, <MSL Altitude>,<Speed Over Ground>,<Course Over Ground>,<Fix Mode>,<Reserved1>,<HDOP>,<PDOP>,<VDOP>,<Reserved2>,<GNSS Satellites in View>,<GNSS Satellites Used>,<GLONASS Satellites Used>,<Reserved3>,<C/N0 max>,<HPA>,<VPA>.

The important number to pay attention to is the second one. This indicates if you have a GPS fix (1) or not (0).

We just proved that your GPS is functional, but if you want to get a fix before moving on, take your setup outside!

Part 5. Streaming GPS Data

Let's put that GPS data to use!

Like I mentioned earlier, we are going to alternate between turning the PPPD cell service on and streaming, and reading from the GPS.

The way we'll do this is using a web service, Initial State, to stream our queued up GPS data. But first we're going to make sure that both Initial State and our cellular streaming are working.

Initial State

We want to stream all of our GPS data to a cloud service and have that service turn our data into a nice dashboard that we can access from our laptop or mobile device. We can also test our new cellular connection's ability to stream data.

Step 1: Register for Initial State Account Go to https://auth.initialstate.com/auth/#/login/ and create a new account.

Step 2: Install the ISStreamer Install the Initial State Python module onto your Raspberry Pi (you'll want a WiFi dongle plugged in for this step):

At a command prompt (don’t forget to SSH into your Pi first), run the following command:

Copy Code
$ cd /home/pi/
$ \curl -sSL https://get.initialstate.com/python -o - | sudo bash

Security Note: The above command has some important anatomy that the user should be aware of. 1) There is a preceding \ before curl. This is important to ensure no alias of curl gets run if one was created. This helps mitigate risk of the command doing more than intended. 2) The command is a piped command, so when running, you are piping the output of a script that is being retrieved from https://get.initialstate.com/python into the command sudo bash. This is done to simplify installation, however, it should be noted that https is important here for helping ensure no man-in-the-middle manipulation of the install script, especially since the script is being run with elevated privileges. This is a common way to simplify install and setup, but if you are a little more wary there are some slightly less convenient alternatives: you can break the command out into two steps and investigate the bash script being downloaded from the curl command yourself to insure it's fidelity OR you can follow the pip instructions, you just wont get an automatically generated example script.

Step 3: Make some Automagic

After Step 2 you will see something similar to the following output to the screen: 

Copy Code
pi@raspberrypi ~ $ \curl -sSL https://get.initialstate.com/python -o - | sudo bash
Beginning ISStreamer Python Easy Installation!
This may take a couple minutes to install, grab some coffee :)
But don't forget to come back, I'll have questions later!

Found easy_install: setuptools 1.1.6
Found pip: pip 1.5.6 from /Library/Python/2.7/site-packages/pip-1.5.6- py2.7.egg (python 2.7)
pip major version: 1
pip minor version: 5
ISStreamer found, updating...
Requirement already up-to-date: ISStreamer in /Library/Python/2.7/site-packages
Cleaning up...
Do you want automagically get an example script? [y/N]

(the output may be different and take longer if you have never installed the Initial State Python streaming module before)

When prompted to automatically get an example script, type y. This will create a test script that we can run to ensure that we can stream data to Initial State from our Pi. You will be prompted:

Copy Code
Where do you want to save the example? [default: ./is_example.py]: 

You can either type a custom local path or hit enter to accept the default.

You will be prompted for your username and password that you just created when you registered your Initial State account. Enter both and the installation will complete.

Step 4: Access Keys Let’s take a look at the example script that was created.

Copy Code
$ nano is_example.py

On line 15, you will see a line that starts with streamer = Streamer(bucket_ ... . This lines creates a new data bucket named “Python Stream Example” and is associated with your account. This association happens because of the access_key=”...” parameter on that same line. That long series of letters and numbers is your Initial State account access key. If you go to your Initial State account in your web browser, click on your username in the top right, then go to “my account”, you will find that same access key at the bottom of the page under “Streaming Access Keys”.

Stream Access Key

Every time you create a data stream, that access key will direct that data stream to your account (so don’t share your key with anyone).

Step 5: Run the Example Run the test script to make sure we can create a data stream to your Initial State account. First, make sure that PPPD is turned on (and the WiFi dongle is no longer plugged in):

Copy Code
sudo pon fona

Wait for the red LED to start blinking quickly, then run the following:

Copy Code
$ python is_example.py

Step 6: Profit Go back to your Initial State account in your web browser. A new data bucket called “Python Stream Example” should have shown up on the left in your log shelf (you may have to refresh the page). Click on this bucket and then click on the Waves icon to view the test data.

Python Stream Example

You will want to step through the Waves tutorial to familiarize yourself with how to use this data visualization tool. Next, view the data in Tiles to see this same data in dashboard form.

Wave Stream

You are now ready to start streaming real data from your Fona and more!

The Code

Now we're to the fun part. You can either copy the fonagps.py script over to your Pi manually, or you can clone it directly, but you'll want to have an internet connection for either option.

Let's give the script a brief look over.

Lines 7-10 set your bucket name, bucket key, access key, and how long you wait between GPS readings. Remember, your Initial State access key can be found on your account page. The bucket key is the truly unique identifier for your bucket, so remember what it is in case your bucket fails to be created later.

Copy Code
BUCKET_KEY = "fona"
ACCESS_KEY = "Your_Access_Key"

The openPPPD() function works by checking if a cellular connection has already been established and then turning PPPD on if it hasn't.

Copy Code
# Start PPPD
def openPPPD():
# Check if PPPD is already running by looking at syslog output
output1 = subprocess.check_output("cat /var/log/syslog | grep pppd | tail -1", shell=True)
if "secondary DNS address" not in output1 and "locked" not in output1:
while True:
# Start the "fona" process
subprocess.call("sudo pon fona", shell=True)
output2 = subprocess.check_output("cat /var/log/syslog | grep pppd | tail -1", shell=True)
if "script failed" not in output2:
# Make sure the connection is working
while True:
output2 = subprocess.check_output("cat /var/log/syslog | grep pppd | tail -1", shell=True)
output3 = subprocess.check_output("cat /var/log/syslog | grep pppd | tail -3", shell=True)
if "secondary DNS address" in output2 or "secondary DNS address" in output3:
return True

This function sends the "sudo poff fona" command.

Copy Code
# Stop PPPD
def closePPPD():
print "turning off cell connection"
# Stop the "fona" process
subprocess.call("sudo poff fona", shell=True)
# Make sure connection was actually terminated
while True:
output = subprocess.check_output("cat /var/log/syslog | grep pppd | tail -1", shell=True)
if "Exit" in output:
return True

The checkForFix() function starts a serial connection so that we can send AT commands to the Fona (the same ones we used earlier in the screen environment) and turns on the GPS.

Copy Code
# Check for a GPS fix
def checkForFix():
print "checking for fix"
# Start the serial connection
ser=serial.Serial('/dev/serial0', 115200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=1)
# Turn on the GPS
while True:
response = ser.readline()
if " 1" in response:
# Ask for the navigation info parsed from NMEA sentences
while True:
response = ser.readline()
# Check if a fix was found
if "+CGNSINF: 1,1," in response:
print "fix found"
print response
return True
# If a fix wasn't found, wait and try again
if "+CGNSINF: 1,0," in response:
print "still looking for fix"

The getCoord() function could be used to fetch any of the GPS data, but, in this case, is used to get latitude and longitude by indexing them from the CGNSINF output.

Copy Code
# Read the GPS data for Latitude and Longitude
def getCoord():
# Start the serial connection
ser=serial.Serial('/dev/serial0', 115200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=1)
while True:
response = ser.readline()
if "+CGNSINF: 1," in response:
# Split the reading by commas and return the parts referencing lat and long
array = response.split(",")
lat = array[3]
print lat
lon = array[4]
print lon
return (lat,lon)

Here we open the cellular connection and create a bucket in Initial State to hold our data. The 10 second sleep is so that there is enough time to send the request before we cut the connection. The buffer_size setting when we initialize the streamer is how many data points the streamer will collect before actually sending them to Initial State. We manually control this later in the code, but this is a bit of a safety net to keep the streamer from trying to send data before we want it to (so the buffer_size is set to 20 even though we're sending every 10 data points).

Copy Code
# Start the program by opening the cellular connection and creating a bucket for our data
if openPPPD():
# Initialize the Initial State streamer
streamer = Streamer(bucket_name=BUCKET_NAME, bucket_key=BUCKET_KEY, access_key=ACCESS_KEY, buffer_size=20)
# Wait long enough for the request to complete

Finally, we close the connection, take GPS readings, open the connection, and then stream them.

Copy Code
	while True:
# Close the cellular connection
if closePPPD():
print "closing connection"
# The range is how many data points we'll collect before streaming
for i in range(10):
# Make sure there's a GPS fix
if checkForFix():
# Get lat and long
if getCoord():
latitude, longitude = getCoord()
coord = str(latitude) + "," + str(longitude)
print coord
# Buffer the coordinates to be streamed
# Turn the cellular connection on every 10 reads
if i == 9:
print "opening connection"

if openPPPD():
print "streaming"
# Flush the streaming queue and send the data
print "streaming complete"

if openPPPD(): print "streaming" # Flush the streaming queue and send the data streamer.flush() print "streaming complete"

Even though we have a streamer.log statement before the internet connection is open, the Initial State python streaming module is storing data locally before uploading it in a batch of 10. The data is being stored on line 110 where the "streamer.log()" statement is and officially uploaded on line 119 when the streamer is flushed with streamer.flush().

The batch number can changed by changing the range on line 101 and editing line 113 to be 1 less than the range (be sure to update the buffer_size on line 91 if batching more than 20 data points).

You could easily accomplish this a different way by using an array to store the data instead and then uploading that - just put the new code where the streamer statements are.

Now save and run the code!

Note: If you want to test this and know that you won't get a GPS fix, you can change line 59 from if "+CGNSINF: 1,1," in response: to if "+CGNSINF: 1,0," in response: and swap line 64 from if "+CGNSINF: 1,0," in response: to if "+CGNSINF: 1,1," in response:. This will make it run without a fix. But you also need to actually stream data for it to show up in Initial State (and lat/long will be blank with no fix), so change the indices on lines 81 and 83 to [1] and [2] to fetch (irrelevant) numbers and stream them.

Extra Note: If for some reason your bucket doesn't show up and you're sure everything's running correctly, it might just be that bucket creation failed. You can expose your bucket using the Initial State interface - read how here - just be sure to use the same bucket key that's in your script!

Your Personal Dashboard

Personal Dashboard

After you've been out and about with your new mobile GPS, go check out your dashboard! Coordinates should be auto-detected by the dashboard and plotted on a map in the Tiles view. You can resize the map by clicking on the "Edit Tiles" tab at the top of the dashboard.

Above you'll see my lovely route as I tried to hit as many Pokestops as possible while I collected data. Not gonna lie, it worked out pretty well. I picked up a nice Growlithe on the way.

If you want to see how I tried to make my setup as compact as possible for portability, read on!

Portable Setup

Run the Script from Boot

We're going to want to start our script as soon as we power on the Pi since our only control over it will be the power button on our portable power bank. Lucky for us, this is super easy!

Using the service crontab we can not only start our script at boot but we can log any error messages we might get, too:

Copy Code
sudo crontab -e

Pick your favorite text editor (I like nano) and at the bottom of the file (under all of the comments), add @reboot sudo python /home/pi/fonagps.py > /home/pi/fonalog.txt. If you named your script something else or put it in a different directory, replace /home/pi/fonagps.py with the correct path. The path in my example is just the main Pi directory.

Save the file and reboot your Pi for it to take effect!

Part 6. Putting it all Together

The Setup

I really wanted to make my project portable (since that's kind of the appeal of cellular), but I also wanted to take advantage of how small the Pi Zero and Fona are. As pictured above, I arranged the two boards, battery, and all their wires as nicely as I could above my portable battery (it's flat).

I also soldered and taped the GND tie lines to remove the need for a breadboard.

Soldered tie lines

To stick the boards on the battery, I used sticky dots and electrical tape.

Sticky Dots

Now I didn't stick my setup in anything amazing right off the bat (though I plan to). For my testing, I just threw it all in a cardboard box and started Pokemon hunting!

Key Parts and Components

Add all Digi-Key Parts to Cart
  • 1690-1000-ND
  • 1690-1002-ND
  • 1528-1428-ND
  • 1528-1981-ND
  • 1528-1586-ND
  • 1528-1841-ND
  • 1528-1379-ND
  • AE11229-ND
  • 1568-1179-ND