Matthew HurleySam Freeze
Published © GPL3+

Batcopter - A Bat Tracking Drone

The Batcopter uses NXPs Hovergames drone kit, a raspberrypi Zero W, and a lightweight yagi antenna to triangulate a VHF radio signal source.

AdvancedFull instructions providedOver 2 days1,377

Things used in this project

Hardware components

Raspberry Pi Zero Wireless
Raspberry Pi Zero Wireless
For offboard control.
×1
KIT-HGDRONEK66
NXP KIT-HGDRONEK66
×1
RTLSDR - software defined radio
Software defined radio for detecting VHF source.
×1
Drok 90010 Buck Converter
Power supply for the raspberry pi.
×1
CNHL 5000 mAh Battery
Primary power for the drone.
×1
Zip Ties
Used all over the drone to secure things.
×1
KD5IVP Backpacker Yagi
This is a lightweight antenna design that must be built DIY style.
×1
USB-A Female to Micro-USB Male Cable
For connecting the RTL-SDR to the Raspberry Pi
×1
Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
Used in combination with mpradio to create a 150 MHz signal, which is used as the beacon which we attempt to track.
×1
Jumper wire
Used for the antenna on the transmitter (beacon).
×1

Software apps and online services

Raspbian
Raspberry Pi Raspbian
MAVSDK
PX4 MAVSDK
PX4
PX4

Hand tools and fabrication machines

Soldering Station, 110 V
Soldering Station, 110 V
For soldering various wires: Buck converter to power distribution board, serial cable and power cables to rpi, fabricating the antenna connector.
Solder Wire, Lead Free
Solder Wire, Lead Free
Drill Press
Used to drill holes for the antenna.
Wire Stripper & Cutter, 26-14 AWG Solid & Stranded Wires
Wire Stripper & Cutter, 26-14 AWG Solid & Stranded Wires
3D Printer (generic)
3D Printer (generic)
For 3d printing the raspberry pi case, which is largely optional.
Hot glue gun (generic)
Hot glue gun (generic)
Used for securing antenna parts.

Story

Read more

Custom parts and enclosures

Raspberry Pi Zero W Mount

This is a design for mounting a Raspberry Pi Zero W to the Hovergames drone. The design may seem a little odd, maybe because it has the correct hole spacing for screwing a DROK - 90010 DC buck converter to the bottom. You can tap battery power off the power distribution board, through the buck converter, to generate 5V for the raspberry pi. There are two other STL files that go with this - the top and the drone attachment. Please see my Thingiverse page for stl files (in case they are not loading properly here):
https://www.thingiverse.com/pruneface/designs

Pi Zero Drone Mount Attachment

This is the drone attachment for the pi zero case.

Pi Zero Mount Top

The cover for the case.

Schematics

Hardware Block Diagram

This is a high level diagram for the hardware used in this project.

Software Functional Diagram

This is a high level (rough) functional diagram for the software/firmware used in this project.

Code

Python script to run on the Raspberry Pi

Python
This is the python script for running on the Raspberry Pi zero, which will perform offboard control, read telemetry data, and query the rtl-sdr. The drone will fly up to 10 meters, and then slowly spin in a circle, logging data at every 3rd degree increment. It logs longitude, latitude, compass direction (degrees from North, clockwise when looking from above), signal frequency (the radio scans a band of frequencies), and real-valued power spectrum values in dB. Note that the frequencies are shifted off-center to avoid the center frequency spike that results from rtl sdr inherent design. You can then average a few frequencies around the desired frequency (150 MHz in this case). The mavsdk connection code is written to connect through the telem2 port, via the raspberry pi's UART.

This code is different than the code that successfully ran in the simulator (JMavSim). Namely, the error check for connection state, the check for altitude during offboard control, and the return to launch command have all been removed. The reason is because all three blocks mentioned above caused minor to catastrophic issues when actually run on the drone
#!/usr/bin/env python3

import asyncio
import sys
import numpy as np
from matplotlib import mlab
import time

from mavsdk import System
from mavsdk.offboard import (OffboardError, PositionNedYaw)
from rtlsdr import *

sdr = RtlSdr()

sdr.sample_rate = 2.4e6
sdr.center_freq = 150.5e6
sdr.gain = 5

num_samples = 512*2048

async def run():

    # Init the drone
    drone = System()

    print("Waiting for drone to connect...")
    await drone.connect(system_address="serial:///dev/serial0:921600")

    print("Waiting for drone to have a global position estimate...")
    async for health in drone.telemetry.health():
        if health.is_global_position_ok:
            print("Global position estimate ok")
            break

    print("-- Arming")
    await drone.action.arm()

    print("-- Setting initial setpoint")
    await drone.offboard.set_position_ned(PositionNedYaw(0.0, 0.0, 0.0, 0.0))

    print("-- Starting offboard")
    try:
        await drone.offboard.start()
    except OffboardError as error:
        print(f"Starting offboard mode failed with error code: {error._result.result}")
        print("-- Disarming")
        await drone.action.disarm()
        return

    print("-- Go 0m North, 0m East, 10m Up within local coordinate system")
    await drone.offboard.set_position_ned(PositionNedYaw(0.0, 0.0, -10.0, 0.0))
    await asyncio.sleep(15)

    print("-- Go 0m North, 0m East, stay at 10m Up within local coordinate system, rotate 360 dgrees slowly")
    for i in range(0, 360, 3):
        await drone.offboard.set_position_ned(PositionNedYaw(0.0, 0.0, -10.0, i))
        samples = sdr.read_samples(num_samples)
        Pxx, freqs = mlab.psd(samples, Fs=sdr.sample_rate)

        async for position in drone.telemetry.position():
            latitude = position.latitude_deg
            longitude = position.longitude_deg
            break

        latitude_a = np.full(len(freqs), latitude)
        longitude_a = np.full(len(freqs), longitude)
        freqs+=sdr.center_freq
        amplitude_dB = 10 * np.log10(np.abs(Pxx))
        i_a = np.full(len(freqs), i)

        original_stdout = sys.stdout 

        with open('filename.txt', 'a') as f:
            sys.stdout = f
            res = "\n".join("{} {} {} {} {}".format(x, y, z, w, i) for x, y, z, w, i in zip(freqs, amplitude_dB, latitude_a, longitude_a, i_a))
            print(res)
            sys.stdout = original_stdout

        if i == 360:
            break
    await asyncio.sleep(5)

    print("-- Stopping offboard")
    try:
        await drone.offboard.stop()
    except OffboardError as error:
        print(f"Stopping offboard mode failed with error code: {error._result.result}")


if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(run())

Plotting RTL SDR Data

Python
This code is good for getting an idea of what your spectrum looks like through the RTL SDR. This helped make the problem of a center frequency spike on the radio evident, which ended up demanding a shift from center of the desired frequency. This is in lieu of using a program like CubicSDR, which already averages and accounts for the center frequency pike (so it isn't apparent).
from pylab import *
from rtlsdr import *

sdr = RtlSdr()

# configure device
sdr.sample_rate = 2.4e6
sdr.center_freq = 250.5e6
sdr.gain = 4

samples = sdr.read_samples(512*1024)
sdr.close()

# use matplotlib to estimate and plot the PSD
psd(samples, NFFT=1024, Fs=sdr.sample_rate/1e6, Fc=sdr.center_freq/1e6)
xlabel('Frequency (MHz)')
ylabel('Relative power (dB)')

show()

Python script to run in the simulator

Python
This is the full-functionality code which ran in the simulator. Note that this code presented some major problems when actually running on the drone. However, the features that ended up causing problems are desirable, which is why this file has been included.
#!/usr/bin/env python3

import asyncio
import sys
import numpy as np
from matplotlib import mlab
import time

from mavsdk import System
from mavsdk.offboard import (OffboardError, PositionNedYaw)
from rtlsdr import *

sdr = RtlSdr()

sdr.sample_rate = 2.4e6
sdr.center_freq = 150.5e6
sdr.gain = 5

num_samples = 512*2048

async def run():

    # Init the drone
    drone = System()
    await drone.connect(system_address="serial:///dev/serial0:921600")

    print("Waiting for drone to connect...")
    async for state in drone.core.connection_state():
        if state.is_connected:
            print(f"Drone discovered with UUID: {state.uuid}")
            break

    print("Waiting for drone to have a global position estimate...")
    async for health in drone.telemetry.health():
        if health.is_global_position_ok:
            print("Global position estimate ok")
            break

    print("-- Arming")
    await drone.action.arm()

    print("-- Setting initial setpoint")
    await drone.offboard.set_position_ned(PositionNedYaw(0.0, 0.0, 0.0, 0.0))

    print("-- Starting offboard")
    try:
        await drone.offboard.start()
    except OffboardError as error:
        print(f"Starting offboard mode failed with error code: {error._result.result}")
        print("-- Disarming")
        await drone.action.disarm()
        return

    print("-- Go 0m North, 0m East, 10m Up within local coordinate system")
    await drone.offboard.set_position_ned(PositionNedYaw(0.0, 0.0, -10.0, 0.0))
    async for position in drone.telemetry.position():
        altitude = position.relative_altitude_m
        if altitude > 9.5:
            break
    await asyncio.sleep(5)

    print("-- Go 0m North, 0m East, stay at 10m Up within local coordinate system, rotate 360 dgrees slowly")
    for i in range(0, 360, 3):
        await drone.offboard.set_position_ned(PositionNedYaw(0.0, 0.0, -10.0, i))
        samples = sdr.read_samples(num_samples)
        Pxx, freqs = mlab.psd(samples, Fs=sdr.sample_rate)

        async for position in drone.telemetry.position():
            latitude = position.latitude_deg
            longitude = position.longitude_deg
            break

        latitude_a = np.full(len(freqs), latitude)
        longitude_a = np.full(len(freqs), longitude)
        freqs+=sdr.center_freq
        amplitude_dB = 10 * np.log10(np.abs(Pxx))
        i_a = np.full(len(freqs), i)

        original_stdout = sys.stdout 

        with open('filename.txt', 'a') as f:
            sys.stdout = f
            res = "\n".join("{} {} {} {} {}".format(x, y, z, w, i) for x, y, z, w, i in zip(freqs, amplitude_dB, latitude_a, longitude_a, i_a))
            print(res)
            sys.stdout = original_stdout

        if i == 360:
            break
    await asyncio.sleep(5)

    print("-- Stopping offboard")
    try:
        await drone.offboard.stop()
    except OffboardError as error:
        print(f"Stopping offboard mode failed with error code: {error._result.result}")

    print("Returning to launch")
    await drone.action.return_to_launch()





if __name__ == "__main__":
    asyncio.get_event_loop().run_until_complete(run())

Printing RTL SDR data to Terminal

Python
This is just some example code for printing RTL SDR samples to the Terminal.
from rtlsdr import *
from pylab import *
import time

sdr = RtlSdr()

# configure device
sdr.sample_rate = 2.048e6  # Hz
sdr.center_freq = 250e6     # Hz
sdr.gain = 4

for i in range(1000):
	samples = sdr.read_samples(512)
	print(10*log10(var(samples)))
	time.sleep(0.5)

Writing RTL SDR Data to a File

Python
This is code for writing RTL SDR data to a file, presented in two columns.
from rtlsdr import *

import sys
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import mlab
import time

sdr = RtlSdr()

sdr.sample_rate = 2.4e6
sdr.center_freq = 150.5e6
sdr.gain = 5

num_samples = 512*2048

samples = sdr.read_samples(num_samples)
Pxx, freqs = mlab.psd(samples, Fs=sdr.sample_rate)
freqs+=sdr.center_freq

amplitude_dB = 10 * np.log10(np.abs(Pxx))

original_stdout = sys.stdout 

with open('filename.txt', 'a') as f:
    sys.stdout = f
    res = "\n".join("{} {}".format(x, y) for x, y in zip(freqs, amplitude_dB))
    print(res)
    sys.stdout = original_stdout

Credits

Matthew Hurley

Matthew Hurley

5 projects • 4 followers
Sam Freeze

Sam Freeze

0 projects • 2 followers
PhD wildlife biology student and bat researcher working at the intersection of cutting-edge technology and bat conservation.

Comments