HP-04: ACARS Text Messages

hobby-programming, radio, python, airplanes, sqlite, acars

Preface #

This is the fourth entry in my “Hobby Programming” series. This post will build upon the project from the second post. After collecting ACARS messages for 11 days, I have thousands of messages to sort through.

Intro #

After collecting over 22,000 ACARS messages I was up against the daunting task of sorting through them all. Luckily, they were all neatly arranged in a sqlite3 database. The data I was after is stored in the tables Messages and Flights. The Messages table holds each of the 22,307 ACARS messages that the acarsdec decoded, wheras the Flights table holds metadata about each flight. Each record in Messages holds a FlightID which maps to an entry in the Flights table with the same FlightID.

By using the command .schema Flights, we can see the data that is stored in the Flights table.

CREATE TABLE Flights  (FlightID integer primary key,  Registration char(7) ,  FlightNumber char(6),  Start
Time datetime,  LastTime datetime, NbMessages integer);
CREATE INDEX FlightsFlightNumber on Flights(FlightNumber);
CREATE INDEX FlightsRegistration on Flights(Registration);
CREATE TRIGGER MessDel before delete on Flights for each row begin delete from Messages where FlightI = old.FlightID ; end;

There are two key pieces of identifying information that we can use from the Flights table, the FlightNumber and Registration. The FlightNumber is the callsign that the aircraft was flying as. For commercial planes, this can tell us the route that the plane was flying, but not which specific plane was flying. The Registration is the actual tail number that is unique to that airplane. By cross correlating the flight number and registration, we can find when a specific plane flew a specific route.

Labels #

Each ACARS message is sent under a specific label. These should describe the purpose or even intended recipient of the message. The vast majority of ACARS data is just that, data. Long strings of numbers and letters representing who knows what. Lots of the data I recorded is telemetry in a semi-human-readable format. Another huge chunk of the data is label H1. From what I can tell, this data is miscellaneous telemetry and maintenance data. While this data is moderately interesting, it really isn’t what I was looking for and when it is human-readable it is all very repetitive. The oil pressure of a plane from a flight two weeks ago is really not that useful for me.

So to make it easier to trudge through the data for actual text messages, I decided to try and filter out the data formats. To do this, I wanted to establish a list of known data-only labels so that I could filter them out. I wrote a short python script to present each label in an approachable way with a preview of the messages contained within.

"""
Show the first 5 entries for each ACARS label

The labels are ordered from most frequently seen to least frequently seen
"""


import sqlite3

with sqlite3.connect('acarsserv.sqlite') as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT Label, COUNT(*) AS frequency FROM Messages GROUP BY Label ORDER BY frequency DESC;")
    label_freqs = cursor.fetchall()
    for label, frequency in label_freqs:
        print(f"{'-'*10} {label} was seen {frequency} times {'-'*10}")
        cursor.execute(f"SELECT F.FlightNumber, F.Registration, M.Txt FROM Messages AS M JOIN Flights AS F ON M.FlightID = F.FlightID WHERE Label='{label}' LIMIT 10;")
        examples = cursor.fetchall()
        print("Flight | Reg    | Txt")
        for f_num, reg, txt in examples:
            print(f"{f_num} | {reg} | {txt}")

This simple program, piped through less, made it fairly easy to see which labels I really didn’t care about. I ended up with a list of labels that only contained uninteresting data.

I used another program to output all of the messages from the other labels.

"""
Print out all messages from labels that likely contian text

Manually filtered out all the labels that are mostly just data
"""

import sqlite3

with sqlite3.connect('acarsserv.sqlite') as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT F.FlightNumber, F.Registration, M.Time, M.Label, M.Txt FROM Messages AS M JOIN Flights AS F ON M.FlightID = F.FlightID WHERE Label NOT IN ('H1','5Z','32','21','22','B9','16','33','11',',37','24','SA','15','80','5V','39','5D','4T','25','Q7','QX','44','4N','48','1C','17','CA','BA','4s','20','H2','4H','45','4Z','2F','QQ','2Q','1B','83','51','2R','2N','B0','5P','4W','18','Q6','4L','1A','BB','49','46','40','35','34','2T','27','26') ORDER BY M.Time ASC;")
    data = cursor.fetchall()
    print("Flight | Reg    | Datetime   | Label | Txt")
    for f_num, reg, time, label, txt in data:
        print(f"{f_num} | {reg} | {time} | {label} | {txt}")
        print('-'*80)

The interesting messages #

From there, I could sort through the 358 “good” messages.

I never got both sides of a conversation from the messages. I only got the transmissions from pilots, never the responses from control towers or the likes. I think that was to be expected somewhat, as I cannot really get great signal from ATC on voice transmissions from where I am.

I have picked out some of the most interesting ones, and I will share them here.

Delays #

F92111 | N379FR | 2025-07-01 02:33:53 | 1L | FFT2111,KPHL,0127,    ,MX. TOOK 3.15HRS TOTAPE AND WRITEUP FUELDOOR LATCHWX TO ORD. MISSED 1STWINDOW DUE 2 MX.
NW2370 | N337NB | 2025-07-02 13:20:30 | 30 | 021330 KDCA KMCO7
/FN 2370
S      
PCAB
FA  TRANSPORTATION TO
AIRPORT WAS VERY LATE
F92509 | N371FR | 2025-07-02 23:32:28 | 1L | FFT2509,KBWI,2254,    ,LATE INBOUND.....ANDUNKNOWN DELAY WAITINGFOR PAX COUNT
F92551 | N720FR | 2025-07-03 14:51:46 | 1L | FFT2551,KPHL,1411,    ,BTB TWICE FOR THE SAMEMX ISSUE. DEPLANED ANDBOARDEED AGAIN WITH7 WHEELCHAIRS.
9E5453 | N320PQ | 2025-07-04 10:53:36 | 30 | 041030  ABE  ATL3
        C S
PPAX CREW AT GATE D55.GATE AGENT SHOWD D30. BOARDIN STARTD D18

… or early #

F94817 | N310FR | 2025-07-08 11:10:57 | 14 | 7,,,Y,N,Y,KATL,122442,10362,GOOD MORNING,WE ARE EARLY.. BY A LOT,,,

Maintenance #

NW2799 | N549US | 2025-07-03 21:10:49 | 30 | 032130 KBWI KATL6
D      
2R LAV WILL NOT FLUSH.
FLT ATTENDANTS HAVE
BLOCKED IT OFF.




NW2496 | N557NW | 2025-07-04 21:45:45 | 30 | 042130 KPHL KATL7
/FN 2496
D      
RUNWAY TURNOFF RIGHT CB
IS POPPED AGAIN ALREADY
WROTE IT UP ONCE TODAY
NW1493 | N344NW | 2025-07-05 12:07:12 | 30 | 051230 KDCA KMSP7
/FN 1493
D      
GOT AIR ENG 2 FAULT
ON TAKEOFF
RAN ECAM
F91809 | N363FR | 2025-07-05 17:39:36 | HX | RA IV2 QUANPOCF9~1
ALR
MOVEMENT DELAY ON NOT IN
120 MIA JFK F91810 N363FR
ADVISE TIME FOOD/WATER SERVICE WAS COMPLETED VIA EXTENDED TARMAC DELAY KIT. FOO
F92387 | N343FR | 2025-07-08 12:04:16 | 1L | FFT2387,KPHL,1126,    ,FUELER LEFT CAP OFF. WAITED FOR HIM TO COME BACKGOOD CATCH FROM RAMPERWALK AROUND. STILL FUELING ON FO WALK

Friendly messages #

DL1248 | N995AT | 2025-07-07 17:22:47 | 30 | 071730 KMDT KATL6
G      
HEY RIZZA ITS VIK
HOPE ITS GOING WELL
CN525966




C54833 | N12201 | 2025-07-07 19:43:30 | 81 | ACK AMD 1 
BM
MB THANK YOU/HAGS

Runway problems #

UA0731 | N896UA | 2025-07-03 15:33:47 | HX | A9 FMT  OR
RNAV Y RY 27. SIMUL
APPROACHES IN USE. DEPG
RY 15L, RY 15R. NOTICE
TO AIRMEN. UNITED RAMP
CONTROL IN EFFECT. BIRD
ACTIVITY VICINITY ARPT.
F
UA1357 | N855UA | 2025-07-07 17:20:57 | HX | RA FMT WY E
CLOSED TO ACFT WINGSPAN
MORE THAN 171 FT. ILS
RWY 5L OTS. BIRD ACT IN
VCNTY OF RDU. HAZD WX
INFO WITHIN RDU AREA
AVBL ON FSS FREQ. ADG IV

Dispatch Problems #

NW2554 | N364NW | 2025-07-06 12:09:19 | 30 | 061230 KBDL KATL7
/FN 2554
A      
LET NY CENTR KNOW
134.32 IS BLOCKED
F92339 | N368FR | 2025-07-10 04:19:25 | 81 | DISPJUST LOST YELLOWHYD SYSTEM