manthony
6 min readDec 22, 2023

Setting Up a Raspberry Pi to Send NMEA Messages via Bluetooth to ATAK for External GPS Integration

Introduction:

This article gives a step-by-step guide to setting up a Raspberry Pi to function as a bridge between a GPS module and ATAK by transmitting NMEA (National Marine Electronics Association) messages over Bluetooth.

The Raspberry Pi is perfectly suited for this task due to its low cost, small size, and availability of Bluetooth module. By the end of this guide, you’ll have a fully functional setup that allows ATAK to receive GPS data from an external source via SPP (Serial Port Profile) https://www.bluetooth.com/specifications/specs/serial-port-profile-1-1/.

What you will need:

  1. A Raspberry Pi that has BT capabilities.
  2. An Android device that can install and run ATAK and pair with Bluetooth devices over SPP

Step 1. Setting up your RPi

First, make sure your package manager is up to date by running the following:

sudo apt update
sudo apt upgrade

Next, install the necessary Bluetooth packages:

sudo apt install bluetooth pi-bluetooth bluez blueman

bluetooth: This package is a meta-package for Bluetooth support, typically including all necessary daemons and utilities for Bluetooth functionality.

pi-bluetooth: This package provides Bluetooth support specifically for the Raspberry Pi, ensuring compatibility with the Pi’s hardware.

bluez: BlueZ is the official Linux Bluetooth protocol stack, providing the core functionality for Bluetooth communication and device management.

blueman: Blueman is a graphical Bluetooth management tool, offering a user-friendly interface for managing Bluetooth devices and connections.

Let’s test that everything is running correctly. Run bluetothctl

The bluetoothctl agent is registered and running

Let’s run a command to scan for other BT-enabled devices in our area

[bluetooth]#: scan on
I have quite a few BT devices set around my house, your list may be smaller (or larger)

Great, BT appears to be functional. Next, we will set up a custom Bluetooth agent that will handle pairing devices for us so that we don’t have to manually pair and trust devices via the bluetoothctl CLI.

Step 2: Creating a Custom Bluetooth Agent to Handle Connections

import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib

class Agent(dbus.service.Object):
AGENT_PATH = "/test/agent"
AGENT_INTERFACE = "org.bluez.Agent1"
CAPABILITY = "DisplayOnly"

def __init__(self, bus, pin_code):
self.pin_code = pin_code
dbus.service.Object.__init__(self, bus, self.AGENT_PATH)

@dbus.service.method(AGENT_INTERFACE, in_signature="", out_signature="")
def Release(self):
print("Release")

@dbus.service.method(AGENT_INTERFACE, in_signature="o", out_signature="s")
def RequestPinCode(self, device):
print(f"RequestPinCode: {device}")
return self.pin_code

@dbus.service.method(AGENT_INTERFACE, in_signature="o", out_signature="u")
def RequestPasskey(self, device):
passkey = int(self.pin_code)
print(f"RequestPasskey: {device}, Passkey: {passkey}")
return dbus.UInt32(passkey)

@dbus.service.method(AGENT_INTERFACE, in_signature="ouq", out_signature="")
def DisplayPasskey(self, device, passkey, entered):
print(f"DisplayPasskey ({device}, {passkey} entered {entered})")

@dbus.service.method(AGENT_INTERFACE, in_signature="ou", out_signature="")
def RequestConfirmation(self, device, passkey):
print(f"RequestConfirmation ({device}, {passkey})")
return

@dbus.service.method(AGENT_INTERFACE, in_signature="o", out_signature="")
def RequestAuthorization(self, device):
print(f"RequestAuthorization ({device})")
return

@dbus.service.method(AGENT_INTERFACE, in_signature="", out_signature="")
def Cancel(self):
print("Cancel")

if __name__ == '__main__':
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

bus = dbus.SystemBus()
pin_code = "1234" # Set your desired PIN code here
agent = Agent(bus, pin_code)

obj = bus.get_object("org.bluez", "/org/bluez")
manager = dbus.Interface(obj, "org.bluez.AgentManager1")
manager.RegisterAgent(agent.AGENT_PATH, agent.CAPABILITY)
manager.RequestDefaultAgent(agent.AGENT_PATH)

mainloop = GLib.MainLoop()
try:
mainloop.run()
except KeyboardInterrupt:
manager.UnregisterAgent(agent.AGENT_PATH)
print("Agent unregistered")

This Python script implements a Bluetooth agent using D-Bus and GLib to handle pairing requests on a Raspberry Pi or similar Linux system. It provides methods for handling various pairing scenarios, like requesting a PIN code or passkey, and automatically registers itself as the default Bluetooth agent with the specified PIN code. A PIN code of “1234” is provided by default here. PIN code authentication allows you to connect your screened device (eg your phone or tablet that is running ATAK) to your raspberry pi which does not have a screen.

To install the necessary packages for this script you can use the following commands:

sudo apt-get install python-dbus
sudo apt-get install python-gi

These commands install the D-Bus Python bindings and the GObject introspection library (required for gi.repository) respectively.

Run the agent and it will continuously listen for connecting devices, requiring the passcode for pairing.

python ./agent.py

You can set up a systemd service that will automatically run this agent on startup. Create a service file with the command below.

sudo vi /etc/systemd/system/BTagent.service

And now copy in the service below and save.

[Unit]
Description=Bluetooth Agent Service
After=bluetooth.service
Requires=bluetooth.service

[Service]
Type=simple
ExecStart=/usr/bin/python /home/pi/agent.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Now, let’s create a producer that will mock some NMEA messages for us.

Step 3: Creating a NMEA Producer

The ATAK has a listener that will handle incoming Bluetooth messages. These messages must be NMEA standard, more specifically RMC and GGA messages. We will create a simple program that will create some mock NMEA data, advertise a service named ‘GNSS’ over Serial Port Profile, and then on a successful connection send the NMEA messages (GPRMC and GPGGA, respectively)

Note: We must use the name ‘GNSS’ since ATAK has a hardcoded list of Bluetooth device names that it will listen for. If it is not on this list, it will not attempt to connect.

import bluetooth
import time
import random
from math import floor

START_LATITUDE = 48.000000
START_LONGITUDE = 11.000000
current_position = [START_LATITUDE, START_LONGITUDE]

def update_position():
delta_lat = random.uniform(-0.0001, 0.0001) # approximately 10 meters
delta_lon = random.uniform(-0.0001, 0.0001) # approximately 10 meters

current_position[0] += delta_lat
current_position[1] += delta_lon

def format_nmea_latitude(lat):
degrees = int(lat)
minutes = (lat - degrees) * 60
return f"{degrees:02d}{minutes:07.4f}"

def format_nmea_longitude(lon):
degrees = int(lon)
minutes = (lon - degrees) * 60
return f"{degrees:03d}{minutes:07.4f}"

def mock_gga_data():
lat = format_nmea_latitude(current_position[0])
lon = format_nmea_longitude(current_position[1])
return f"$GPGGA,123519,{lat},N,{lon},E,1,08,0.9,545.4,M,46.9,M,,*47"

def mock_rmc_data():
lat = format_nmea_latitude(current_position[0])
lon = format_nmea_longitude(current_position[1])
return f"$GPRMC,123519,A,{lat},N,{lon},E,022.4,084.4,230394,003.1,W*6A"
def send_nmea_over_bluetooth():
uuid = "00001101-0000-1000-8000-00805F9B34FB"

server_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
server_sock.bind(("", bluetooth.PORT_ANY))
server_sock.listen(1)

bluetooth.advertise_service(server_sock, "GNSS",
service_id=uuid,
service_classes=[uuid, bluetooth.SERIAL_PORT_CLASS],
profiles=[bluetooth.SERIAL_PORT_PROFILE])

print("Waiting for connection on RFCOMM channel")

client_sock, client_info = server_sock.accept()
print(f"Accepted connection from {client_info}")

try:
while True:
update_position()

gga_data = mock_gga_data()
client_sock.send(gga_data + '\n')
print(f"Sent: {gga_data}")

rmc_data = mock_rmc_data()
client_sock.send(rmc_data + '\n')
print(f"Sent: {rmc_data}")

time.sleep(0.1)
except OSError:
pass
finally:
client_sock.close()
server_sock.close()

if __name__ == "__main__":
send_nmea_over_bluetooth()

This is just a mock creator, but if you had actual sensor data that you wanted to serialize into NMEA, I would recommend the pynmea2 library which has good support for the most common messages.

https://github.com/Knio/pynmea2

Step 4: Bluetooth Pairing of Raspberry Pi to ATAK

On your Android device, pull up the connection settings and rescan for devices. If your BT agent and mock producer are running, you will see GNSS them on the device list. Select it to connect, and enter the pin from the agent (defaults to “1234”). You should now have your Android device connected to your Raspberry Pi.

Now that your device is paired, you need ATAK to acknowledge and read the data from the connection. Open up ATAK and go to Settings > Callsign and Device Preferences > Bluetooth.

Check and uncheck ‘Bluetooth Support’. This will trigger ATAK to start scanning for devices. Since the ‘GNSS’ device name is in ATAK’s device list, it will connect to this device.

You should see your GPS status on the bottom right-hand corner of your screen switch to [BT] GPS fix (SPS)

And that’s it- You now can control the GPS that your ATAK is receiving from your Raspberry Pi.