Mottak og bruk av ADS-B signaler fra fly

 357 total views,  3 views today

I denne artikkelen beskriver jeg hvordan det er mulig å ta imot radiosignaler fra fly med ADS-B meldinger, og hvilken informasjon disse meldingene inneholder.

(c) Anders Fongen, februar 2024

Fly i normal trafikk sender ut radiosignaler med meldinger om flight-id., posisjon, høyde, fart og retning. Dette skjer i et meldingsformat som kalles ADS-B og er en digital transmisjon på frekvensen 1080 MHz. Dette signalet brukes av luftkontrollen som supplement til radar, og tjenester som Flightradar24 benytter også i stor grad disse radiosignalene.

Kan du ha interesse av å bruke disse signalene? Selv har jeg planer om å bruke fly som reflektorer av radiosignaler, og på den måten utvide rekkevidden av VHF/UHF-signaler over horisonten. Da trenger jeg å vite hvordan antennen skal stilles for å peke mot flyet, og jeg må derfor vite hvor det befinner seg til enhver tid.

Hva du trenger for å motta ADS-B

  • En SDR mottaker av typen RTL-SDR. De finnes på Ebay fra en hundrelapp og oppover. De selges ofte som utstyr for å kunne motta digital TV på PC, men egner seg til mange andre formål også.
  • En Linux-maskin, gjerne en Raspberry Pi. Har du en gammel PC stående som ikke brukes og som har en USB port og nettverksadapter (WLAN eller Ethernet), så bruk gjerne den. Du vil trenge litt Linux-erfaring for å gjennomføre det som blir beskrevet her.
  • Noe gratis programvare som lastes ned fra Internet, bl.a. dump1090.

Oversikt over konfigurasjonen som skal vises her

  1. Radiosignalet fra fly mottas av SDR-dongelen som er koplet til en Linux-maskinens USB-port.
  2. Fra SDR går et “råsignal” (I/Q-signalet) til Linux-maskinen (grønn pil), hvor programmet dump1090 dekoder signalet og henter ut meldingene. Disse meldingene, gjengitt på såkalt SBS-1 format, sendes ut fra Linux-maskinen med TCP-protokoll på port 30003 (rød pil).
  3. Meldingene på SBS-1 format kan leses med et program som man kan skrive selv etter hvilke ønsker man har. Jeg har valgt å lage et Python-program for en Raspberry Pico W mikrokontroller som henter ut flyenes løpende posisjon (lengdegrad, breddegrad, høyde) og regne ut retningen til en direktiv antenne som skal følge flyet.
  4. Raspberry Pico lager elektriske signaler for å styre to motorer på et enkel platform (blå pil) for å stille inn ønsket retning og vinkel (azimuth og elevation).

Konfigurasjon av Raspberry pi (2)

Komponent (2) skal ta imot I/Q-signaler fra SDR-dongelen, dekode ADS-B meldingene som overføres, og presentere data på SBS-1 format gjennom en TCP-forbindelse. For dette trenges en enkel Linux-maskin som må konfigureres for dette formålet. Den følgende beskrivelsen tar utgangspunkt i en Raspberry Pi v.3, men fremgangsmåten blir nokså lik også for andre type Linux. Sørg for at Linux-versjonen er nylig oppdatert.

Følgende kommandoer kan gis for å installere drivere og dump1090-programmet:

$ sudo apt update
$ sudo apt upgrade
$ sudo apt install librtlsdr-dev
$ sudo apt install rtl-sdr
$ git clone https://github.com/antirez/dump1090.git dump1090
$ cd dump1090
$ make

Evt. feilmeldinger følges opp og rettes. På katalogen dump1090 ligger nå programmet dump1090 som startes med kommandoen ./dump1090 --net.

SDR-dongelen kan nå koples til maskinens USB-port og en egnet antenne, slik som vist på figuren. Deretter kan dump1090-programmet startes. Det gir ingen løpende utdata til skjermen, man overvåker utdata på metoden som er vist nedenfor.

Utdata fra dump1090

Som vist på figuren over har vi satt opp dump1090 til å sende ut data i SBS-1 format til TCP-port 30003. Med programmet netcat kan vi ta en titt på hvordan dataene ser ut, og gjøre oss opp en mening om hvordan de kan behandles. Ta en titt på denne videoen:

Det er i hovedsak opplysninger om posisjon og flight-id som er at interesse for denne anvendelsen, men data om retning og fart er også tilgjengelig.

Analyse av SBS-1 data og beregning av antenneretning (3)

Figuren over viser hvordan utdata fra dump1090 overføres via TCP-protokoll til alle som ønsker det (rød pil). Figuren viser derfor at flere ulike anvendelser kan hente disse dataene fra samme dump1090-instans.

I det eksperimentet som beskrives her, skjer denne bearbeidingen i en Raspberry Pico W, som er en mikrokontroller med WLAN-grensesnitt og som kan programmeres i Micropython. Programkoden i denne mikrokontrolleren kan deles i tre deler:

  1. Et hovedprogram som etablerer en TCP-forbindelse til dump1090-noden (2), kaller så på Python-moduler for analyse, beregning og antennestyring
  2. En modul for å analysere SBS-1 melding for posisjon og identifikasjon av fly, og for å beregne antenneretning til flyet.
  3. En modul som mottar opplysninger om antenneretning og styrer servomotorene på antenneplattformen i henhold til disse opplysningene.

1 – Hovedprogram

Hovedprogrammet initialiserer TCP-forbindelsen, WLAN-adapteret, I2C-bussen og oppretter nødvendige objekter for de påfølgende operasjonene. Slik ser programkoden ut:

from wlan_config import wlan_config # WLAN configuration module
from AzElPlatform import AzElPlatform # Az-El antenna control
from AzElCalculator import AzElCalculator
import socket
from machine import Pin, I2C
import ssd1306

# using default address 0x3C
# TODO change GPIO numbers if necessary
i2c = I2C(0,sda=Pin(8), scl=Pin(9))
display = ssd1306.SSD1306_I2C(128, 64, i2c)

# Configure wlan
wlan_config()

# Make network tocket and connect to ads-b listener
sock = socket.socket()

# TODO change IP address to the actual dump1090 node
sock.connect(("192.168.2.11",30003))

# Configure bearing calculator (lat,lon,alt)
# TODO Replace numbers with your own location
azelcalc = AzElCalculator(61.1353,10.4419,253)

# Initialize platform motor control
# TODO change numbers to your own use of GPIO ports
azelplat = AzElPlatform(16,17)

# Start reading ads-b messages
while True:
    message = sock.readline().decode()
    result = azelcalc.analyzeMessage(message)
    if result != None:
        print(result) # For debugging only
        
        # If flightId is present, send to OLED display
        flightId = result['flightid']
        if flightId != None:
            distance = int(result['distance']/1000)
            display.fill(0)
            display.text('%s-%d km'%(flightId,distance),5,8)
        else:
            display.fill(0)
        display.show()
        # Find azimuth and elevation from result
        azimuth = float(result['azimuth'])
        elevation = float(result['elevation'])
        # Point antenna in that direction
        azelplat.setDirection(int(azimuth),int(elevation))

Deler av programkoden styrer et OLED-display for å vise flight-id og avstand til fly som blir fulgt av antennen. Disse kan kommenteres bort om dette er uten interesse.

Modulen wlan_config som importeres her er presentert i en annen blogg-artikkel: WLAN-konfigurasjon i Raspberry Pico W. Der vises hvordan wlan-adapteret kan konfigureres uten å skrive nettverkspassordet inn i koden.

2 – Analyse og behandling av SBS-1 utdata, beregningav retning

Metoden analyzeMessage() i modulen AzElCalculator mottar en SBS-1 melding som parameter og henter ut flight-id og posisjonsdata fra den. Programmet beregner så retningen for en antenne som skal følge dette flyet, i form av horisontal og vertikal vinkel (Azimuth og Elevation). For denne beregningen brukes Python-modulen AltAzRange som kan hentes fra denne adressen. Programkoden for AzElCalculator ser slik ut (merk at noen av linjene er lange og er brukket i flere deler):

# Receive SBS-1 message, find position of aircraft and
# calculate azimuth and elevation for the direction to it
from AltAzRange import AltAzimuthRange
class AzElCalculator:
    def __init__(self,myLatitude,myLongitude,myAltitude):
        AltAzimuthRange.default_observer(myLatitude,myLongitude,myAltitude)
        self.airplane=AltAzimuthRange()
        self.flightId = [(None,None),(None,None),(None,None),(None,None),(None,None)]
        
    def analyzeMessage(self,messageLine):
        elements = messageLine.split(",")
        
        # ES Airborne Position Message
        if (elements[1] == '3'):
            icao = elements[4]
            altitude = elements[11]
            if altitude == '': return None
            latitude = elements[14]
            if latitude == '': return None
            longitude = elements[15]
            if longitude == '': return None
            self.airplane.target(float(latitude),float(longitude),float(altitude)*0.3048)
            # Observe that the position is not returned, only the direction
            result = self.airplane.calculate()
            result['icao'] = icao
            result['flightid'] = self.getFlightId(icao)
            return result
        # ES Identification and Category
        elif (elements[1] == '1'):
            icao = elements[4]
            flightid = elements[10].strip()
            if flightid == '': return None
            self.storeFlightId(icao,flightid)
            return None
        # ES Airborne Velocity Message
        elif (elements[1] == '4'):
            result = dict()
            result['icao'] = elements[4]
            result['groundspeed'] = elements[12]
            result['track'] = elements[13]
            result['flightid'] = self.getFlightId(result['icao'])
            # We can return the "result" object, but choose not to
            # return result # is this data is needed
            return None

    def storeFlightId(self,icao,flightId):
        if self.getFlightId(icao) == None:
            # Add flight id to list
            self.flightId.insert(0,(icao,flightId))
            self.flightId.pop() # Kill the oldest entry
    
    def getFlightId(self,icao):
        for (i,f) in self.flightId:
            if i == icao: return f
        return None

3- Styre en direktiv antenne mot flyet

Oppgaven med programmering av to servomotorer for å styre en antenne i to akser (horisontalt og vertikalt) skjer med modulen AzElPlatform som er presentert i en tidligere blogg-artikkel: Styring av servomotor fra Raspberry Pico og Micropython. For ordens skyld gjengis programkoden er:

# Python class to control a simple Azimuth-Elevation
# platform. It uses two SG90 servo motors for the two
# axes. Since they only rotate 180 degrees, the 180
# degree range of the elevation rotor is used to cover
# the left half (180-359 degrees) of the azimuth

from machine import Pin, PWM
class AzElPlatform:

    def __init__(self,azrotor, elrotor):
        self.azport = PWM(Pin(azrotor))
        self.elport = PWM(Pin(elrotor))

        self.azport.freq(50)
        self.elport.freq(50)

    def setDirection(self,az,el): # Angle in degrees
        # Check parameters: 0-359 and 0-90 allowed
        if not az in range(0,360): return
        if not el in range(0,91): return
        if az>180:
            az = az-180
            el = 180-el # Bend elevation backwards for left half
        # Experimentally established values for
        # Calculation of duty cycles corresponding
        # to rotor angles
        dutyAz = 7800 - az * 6600/180
        dutyEl = el * 7000/180 + 1200

        self.azport.duty_u16(int(dutyAz))
        self.elport.duty_u16(int(dutyEl))
Raspberry Pico W med OLED display viser flight-id NOZ191 med avstand 78 km og følger dette flyet med en direktiv antenne.

Oppsummering

I denne blogg-artikkelen har jeg presentert noen aktiviteter som inkluderer bruk av Software Defined Radio, avansert digital dekoding av radiosignaler fra fly, noe programmering og beregninger på posisjonsdata, og styring av servomotorer for antenner.

Mange av disse emnene vil være av interesse i andre anvendelser enn akkurat den som her er presentert. Bruk gjerne delløsningene i denne artikkelen til dine egne prosjekter, og send meg en melding om resultatene blir interessante.

Leave a Reply

Your email address will not be published. Required fields are marked *

nine − two =