# ACARS and VDL2

At the end you have a message screen on your home network. It shows the text messages that aircraft near you send to the ground. Sharing them with a public network is one optional step.

Spurious Emissions · Rev B · checked 2026-10-21 · https://spurious-emissions.net/guides/receive-acars-vdl2/

Some links are affiliate links: if you buy through them, I may earn a commission at no extra cost to you. As an Amazon Associate I earn from qualifying purchases.

## 1. What this guide builds

A receiver for the short text messages that airliners send to the ground. One antenna feeds one or two USB radios. A small computer decodes the messages and shows them on a page in your browser.

Aircraft use two systems for these messages. The old system is ACARS (Aircraft Communications Addressing and Reporting System). The new system is VDL Mode 2, which I call VDL2 in this guide. Both carry the same kinds of messages: times, positions, weather requests, fault codes and free text.

| Signal | Frequency | Antenna | Radio | Decoder | Computer |
|---|---|---|---|---|---|
| ACARS, 2,400 bits each second | Channels from 129 to 132 MHz | Discone, splitter leg A | Airspy R2 | acarsdec | `pi` |
| VDL2, 31,500 bits each second | Channels from 136.650 to 136.975 MHz | Discone, splitter leg B | RTL-SDR Blog V4L | dumpvdl2 | `pi` |

The computer has the name `pi` in this guide. Two more programs run on it. acars_router removes duplicate messages and sends each message on. ACARS Hub stores the messages and shows them.

```
 roof                  cable entry                     computer "pi" (Docker)
 ----                  -----------                     ----------------------
 discone -- coax -- FM filter -- splitter
                                   |-- leg A -- Airspy R2 --- acarsdec --+
                                   |                                     +-- acars_router --+-- ACARS Hub
                                   `-- leg B -- RTL radio --- dumpvdl2 --+                  |   (message screen)
                                                                                            |
                                                                                            `-- airframes.io
                                                                                                (optional)
```

You can start with less. One RTL radio and a home-made antenna receive one of the two systems. Section 2 lists that path and says what it gives up.

## 2. What you need

Prices are US dollars, as listed by the named seller on 2026-10-07. They change. A part marked ◆ is what I use. A blank box is for your pencil.

Two terms first. An SDR (software-defined radio) is a radio that sends raw samples to a computer, and a program does the rest. An RTL radio is a low-cost SDR in the shape of a USB stick, built on the RTL2832U chip.

### Antenna and radio-frequency parts

| ☐ | Part | Job | Connector | Price |
|---|---|---|---|---|
| ☐ | ◆ [Diamond D-130J discone](https://www.amazon.com/dp/B00AR09A6I?tag=spuriousemiss-20), 25 to 1300 MHz receive, 1.7 m | One wide antenna for both systems | SO-239 (the D-130NJ has N type) | See listing, Amazon |
| ☐ | ◆ [RTL-SDR Blog FM band-stop filter](https://www.amazon.com/dp/B01LE9LRPM?tag=spuriousemiss-20), 88 to 108 MHz, over 50 dB | First part after the antenna cable | SMA | See listing, Amazon |
| ☐ | ◆ [Mini-Circuits ZX10-2-12-S+](https://www.minicircuits.com/WebStore/dashboard.html?model=ZX10-2-12-S%2B) 2-way splitter, 2 to 1200 MHz | Feeds two radios from one antenna | SMA | $42.20, Mini-Circuits |
| ☐ | 50 ohm coax with a PL-259 plug at the antenna end | Antenna to cable entry | SMA plug or adapter at the radio end | See seller |
| ☐ | Short SMA plug-to-plug jumpers, 3 | Filter to splitter to radios | SMA | See seller |
| ☐ | Grounding block and bonding wire | Section 4, "Ground the cable" | To suit the coax | See seller |

Do not buy an amplifier at the start. Section 4 says why.

### Radios

| ☐ | Part | Job | Price | Note |
|---|---|---|---|---|
| ☐ | ◆ [Airspy R2](https://airspy.com/airspy-r2/), 24 to 1700 MHz, 12-bit | ACARS | $169, airspy.us | An RTL radio also does this job[^why-airspy] |
| ☐ | ◆ [RTL-SDR Blog V4L](https://www.rtl-sdr.com/buy-rtl-sdr-dvb-t-dongles/) | VDL2 | $37.95, rtl-sdr.com | Needs a driver from August 2026 or newer (section 14) |
| ☐ | [Nooelec NESDR SMArt v5](https://www.nooelec.com/store/nesdr-smart-sdr.html) | VDL2, in place of the V4L | See listing | I ran VDL2 on one for six weeks |
| ☐ | ◆ Stick-on aluminum heat sinks, 50 x 25 x 10 mm ([mine](https://www.amazon.com/dp/B08QM84KPZ?tag=spuriousemiss-20), a pack of two) | Optional, on the radios | See seller | My own addition, not part of the radios as sold. They run warm |

The VDL2 decoder cannot use an Airspy radio. Its README says it needs a rate of 2,100,000 samples each second, and an Airspy has fixed rates. So the RTL radio always gets VDL2.

### Computer

| ☐ | Part | Job | Price | Note |
|---|---|---|---|---|
| ☐ | ◆ [Raspberry Pi 5](https://www.amazon.com/dp/B0CK2FCG1K?tag=spuriousemiss-20), 8 GB | `pi` | $175, list price | The 4 GB model is $110. I did not test it[^pi-load] |
| ☐ | Raspberry Pi 27 W USB-C power supply | Power for `pi` | See seller | A weaker supply limits the USB sockets to 600 mA in total |
| ☐ | microSD card, 32 GB or more | Storage for `pi` | See seller | |
| ☐ | ◆ Powered USB hub with its own power supply | The radios | See seller | Optional for two radios. A Pi alone can power about three |
| ☐ | Short USB extension cables | So a wide radio does not block the next socket | See seller | |

Any 64-bit ARM or x86 computer that runs Docker also works. The containers in this guide exist for `arm64` and `amd64` only. A 32-bit system cannot run them.

### The lowest-cost path

| ☐ | Part | Job | Price |
|---|---|---|---|
| ☐ | One RTL radio (the V4L above) | VDL2 or ACARS, not both | $37.95, rtl-sdr.com |
| ☐ | A home-made quarter-wave antenna (section 4) | Antenna for the aircraft band | Wire and one coax socket |
| ☐ | The FM band-stop filter above | Protection from FM broadcast stations | $16.95, rtl-sdr.com |
| ☐ | A 64-bit computer that you own, or the Raspberry Pi above | `pi` | See above |

What this path gives up:

- **One system at a time.** Each decoder is one program that owns one radio. One radio cannot serve both decoders.
- **Messages.** If you choose VDL2, you lose all ACARS messages. On my station that is about 1 message in 8.
- **Other bands.** The home-made antenna works near one frequency. A discone works from 25 to 1300 MHz.

Choose VDL2 for the one radio. My station hears about 7 VDL2 messages for each ACARS message. Add a second RTL radio and the splitter later.

## 3. Before you start

**Skills.** You connect coax and adapters. You type commands on a Linux computer over SSH. You edit a text file. This guide gives every command.

**Time.** I did not time the build. The antenna work is the long part.

**Accounts.** None for reception. Sharing in section 10 uses a station ID that you choose.

**Values to choose first.** Write these down. The steps use them as placeholders.

| Placeholder | Meaning | Example form |
|---|---|---|
| `<TZ>` | Time zone, TZ database name | `America/Los_Angeles` |
| `<STATION_ID>` | A station name. It becomes public only if you share (section 10) | `XX-YYYY` |
| `<SER_R2>` | Factory serial number of the Airspy R2 | 16 hexadecimal characters |
| `136` | The serial number that you write to the RTL radio for VDL2 | `136` |

This guide needs no latitude and no longitude. The decoders do not ask for your position.

**The law.** I am not a lawyer. This is what I read, for the United States. Other countries differ. US law (47 U.S.C. section 605) is about a radio message that is not meant for the public. A person who receives one must not tell others its contents, or use it for gain. It makes exceptions for broadcasts to the public, for amateur and CB radio, and for messages about ships, aircraft or people in distress. This station only receives. It never transmits.

## 4. The antenna and the radio-frequency chain

Build the chain in this order, antenna first.

```
 discone -- coax -- FM band-stop filter -- 2-way splitter
                                             |-- leg A -- Airspy R2      (ACARS)
                                             `-- leg B -- RTL radio      (VDL2)

 One radio:   antenna -- coax -- FM band-stop filter -- RTL radio
```

### Put the antenna outside

> **Warning.** An antenna or a mast that touches a power line can kill you. Keep the antenna, the mast and the cable away from every power line.

1. Mount the antenna outside, as high as you safely can.
2. Give it a clear view of the sky in every direction.
3. Keep the coax short and of good quality.

These signals travel in a straight line. The dumpvdl2 README says VDL2 signals are not strong, and an indoor antenna hears few of them. Aircraft antennas on this band are vertical. Mount your antenna vertical too.

### A wide antenna: the discone

A discone is a wide-band antenna made of a disc of rods above a cone of rods. It has little or no gain, but it receives a very wide range of frequencies. Mine is sold as covering 25 to 1300 MHz. I chose it because one antenna can then serve other radios later.

The cost of a wide antenna is that it receives everything. FM broadcast stations at 88 to 108 MHz arrive at full strength. So the filter below is not optional on a discone.

### A cheaper antenna: a quarter-wave ground plane

A quarter-wave ground plane is one vertical rod above four rods that slope down. The airframes.io documents say a 53 cm wire antenna works for tests on this band. That agrees with the standard formula.

1. Calculate the rod length: 71.25 divided by the frequency in MHz gives meters. For 134 MHz, the result is 0.53 m.
2. Cut five rods or stiff wires of that length.
3. Solder one rod to the center pin of a coax socket. This rod points up.
4. Attach four rods to the body of the socket. Bend them down about 45 degrees.
5. Mount the antenna outside with the long rod vertical.

I did not build this antenna. It is the standard design, and 134 MHz is between the two groups of channels.

### Ground the cable

An outdoor antenna cable needs a path to ground where it enters the building. In the US the rule is National Electrical Code Article 810. Bond the grounding block to the building's own grounding electrode system. If you add a ground rod, bond it to that system with 6 AWG copper or heavier. Never use a rod that stands alone: a surge then goes through your equipment. I read a published summary of the rule, not the code text.[^nec] Use a licensed electrician if you are not sure.

### The FM broadcast filter

1. Connect the coax from the antenna to the input of the FM band-stop filter.
2. Connect the output of the filter to the splitter, or to the one radio.

An FM broadcast station is very strong, and it is close to the aircraft band. A low-cost radio has a small range between its own noise and its overload point. A strong FM signal can fill that range. The radio then loses the weak aircraft signals. The filter cuts 88 to 108 MHz by more than 50 dB. Below 1 GHz it costs under 0.5 dB elsewhere.

### One antenna, two radios

> **Caution.** A radio that puts DC on the cable can feed it into the other leg of the splitter. Keep the bias tee off on every radio. The two containers keep it off unless you set `BIASTEE`. Do not set it.

1. Connect the output of the filter to the input of the splitter.
2. Connect leg A to the Airspy R2.
3. Connect leg B to the RTL radio.

Put the filter before the splitter. One filter then cleans both legs. A 2-way splitter gives each leg a little less than half of the signal. I have not measured what the split costs me in messages.[^splitter]

With one radio, leave the splitter out. A second antenna for the second radio also works, and loses nothing.

### No amplifier

Do not add an amplifier to a short, direct cable run. I tried a generic 20 dB amplifier between the filter and the radio. My station decoded 95 ACARS messages an hour before, and 42 with it. The strong messages became about 12 dB stronger, and the weak messages were gone. I removed it after 90 minutes.

The reason is the same range as above. An amplifier raises every signal, and the noise, by the same amount. The strong signals then reach the overload point. The amplifier cannot make a weak signal stronger than the noise that arrives with it.

That was one test on one day, before I installed the splitter. Add an amplifier only to pay for a loss that you measured, such as a long cable. Count messages for the same hours before and after.

## 5. The computer

These steps are for a Raspberry Pi. On another Debian or Ubuntu computer, start at step 3.

1. Write Raspberry Pi OS Lite (64-bit) to the card with Raspberry Pi Imager. In the Imager settings, set the host name to `pi`, turn SSH on, and add your public key.
2. Start the Pi on wired Ethernet. Log in with SSH.
3. Update the system.

   ```bash
   sudo apt update && sudo apt full-upgrade -y && sudo reboot
   ```

4. Install Docker with the sdr-enthusiasts script. Docker runs each program in a container: a closed box that holds the program and all it needs. The script also stops the kernel's TV drivers from taking the RTL radios.

   ```bash
   bash <(curl -s https://raw.githubusercontent.com/sdr-enthusiasts/docker-install/main/docker-install.sh)
   sudo reboot
   ```

5. Check Docker and the driver block.

   ```bash
   docker compose version
   cat /etc/modprobe.d/exclusions-rtl2832.conf      # expect 9 "blacklist" lines
   lsmod | grep -c dvb_usb_rtl28xxu                 # expect 0
   ```

   Result: a version number, nine lines that start with `blacklist`, and the number 0.

6. Install the Airspy tools and the clock service. Skip `airspy` if you have no Airspy radio.

   ```bash
   sudo apt install -y airspy chrony
   ```

   The clock matters. acars_router rejects a message that looks more than 5 seconds old.

## 6. The radios

Each decoder finds its radio by serial number. Never leave a serial number unset. A decoder that opens "the first radio" can take the wrong one.

### The RTL radio

> **Caution.** Change only the serial number. Do not change the maker or product text on an RTL-SDR Blog radio. Its driver looks for that text.

1. Connect **only** the RTL radio to the computer.
2. Write the serial number `136` to it. This command downloads one container only to use its `rtl_eeprom` tool.

   ```bash
   docker run --rm -it --device /dev/bus/usb --entrypoint rtl_eeprom \
     ghcr.io/sdr-enthusiasts/docker-adsb-ultrafeeder -s 136
   ```

3. Approve the write when the tool asks.
4. Disconnect the radio and connect it again. A restart of the computer does not load the new serial number.
5. Put a label with `136` on the radio.

For a second RTL radio on ACARS, do the same steps with the serial number `131`.

### The Airspy R2

1. Connect the Airspy R2.
2. Read its factory serial number.

   ```bash
   airspy_info | grep -i serial
   ```

   Result: one line with the serial number. This is `<SER_R2>`.

3. Put a label on the radio.

If you own two Airspy radios, read them one at a time. Both models report the same USB identity (`1d50:60a1`). Only the serial number tells them apart.

## 7. Choose the channels

### How one radio hears several channels

A voice radio tunes one channel. An SDR does not. It sends the computer a slice of the band about 2 MHz wide, as raw samples. The decoder then cuts each channel out of that slice with arithmetic, and decodes all of them at the same time.

So one rule decides your channel list: **every channel of one decoder must fit in 2 MHz.** The ACARS Hub README states this rule. The decoder chooses the center of the slice by itself.

Each decoder is one program that owns one radio. In North America the two groups of channels are also 5 MHz apart. That is why each system needs its own radio.

An Airspy R2 takes a wider slice. My six ACARS channels run from 129.125 to 131.550 MHz, which is 2.425 MHz, and it works on the R2. Do not try that list on an RTL radio.

### The lists for your region

These lists are from the airframes.io documents, read on 2026-10-10. Start with the list for your region.

| Region | ACARS channels, MHz | VDL2 channels, MHz |
|---|---|---|
| North America | 130.025, 130.425, 130.450, 131.125, 131.550 (also in use: 129.125) | 136.650, 136.700, 136.800, 136.975 |
| Europe | 131.525, 131.725, 131.825, 131.850 (also in use: 136.900, 136.925) | 136.725, 136.775, 136.825, 136.875, 136.975 |
| Asia-Pacific | 131.550, 131.450, 129.125, 130.025 | 136.700, 136.750, 136.975 |
| Middle East, Africa | 131.550, 129.125, 130.025 | 136.750, 136.975 |

Rules for the list:

1. Always include 136.975 MHz for VDL2. It is the Common Signalling Channel, in use worldwide.
2. Check the span of each list. Subtract the lowest channel from the highest. On an RTL radio the result must be under 2 MHz.
3. For Asia-Pacific and the Middle East on an RTL radio, leave out 129.125 MHz. The list is then 1.525 MHz wide.
4. Look at the stations page of airframes.io (<https://app.airframes.io/stations>). The documents say it shows the frequencies that stations near you use.

### Tune the list later

Most of my channels are quiet. I listen to six ACARS channels and eight VDL2 channels. Nearly all of my messages arrive on three: 130.025, 136.650 and 136.975 MHz. Other places have other busy channels.

1. Run the regional list for one week.
2. Read the count for each frequency on the statistics page of ACARS Hub.
3. Remove a channel that stays at zero, if the computer is busy. Each channel costs processor time.
4. Add one new channel at a time, and compare the counts after some days.

## 8. The software

Four containers, all from the sdr-enthusiasts project: [docker-acarsdec](https://github.com/sdr-enthusiasts/docker-acarsdec), [docker-dumpvdl2](https://github.com/sdr-enthusiasts/docker-dumpvdl2), [acars_router](https://github.com/sdr-enthusiasts/acars_router) and [ACARS Hub](https://github.com/sdr-enthusiasts/docker-acarshub). All four go into one compose file, so the containers find each other by name. A compose file is a text file that tells Docker which containers to run, and with which settings.

### Write the two files

1. Make the project folder.

   ```bash
   sudo mkdir -p -m 777 /opt/station && cd /opt/station
   ```

2. Create `/opt/station/.env` with your values.

   ```ini
   FEEDER_TZ=<TZ>
   VDL2_SDR_SERIAL=136
   ACARS_SDR_SERIAL=<SER_R2>
   ACARS_STATION_ID=<STATION_ID>
   ```

3. Create `/opt/station/docker-compose.yml`. Put your own channels in the two `FREQUENCIES` lines.

   ```yaml
   services:
     acarsdec:
       image: ghcr.io/sdr-enthusiasts/docker-acarsdec:latest
       container_name: acarsdec
       restart: unless-stopped
       environment:
         - TZ=${FEEDER_TZ}
         - AIRSPY_SERIAL=${ACARS_SDR_SERIAL}
         - FEED_ID=${ACARS_STATION_ID}-ACARS
         - FREQUENCIES=130.025;130.425;130.450;131.125;131.550
         - OUTPUT_SERVER=acars_router
         - OUTPUT_SERVER_PORT=5550
         - OUTPUT_SERVER_MODE=udp
       tmpfs:
         - /run:exec,size=64M
         - /var/log
       device_cgroup_rules:
         - "c 189:* rwm"
       volumes:
         - /dev/bus/usb:/dev/bus/usb:ro

     dumpvdl2:
       image: ghcr.io/sdr-enthusiasts/docker-dumpvdl2:latest
       container_name: dumpvdl2
       restart: unless-stopped
       environment:
         - TZ=${FEEDER_TZ}
         - RTL_SERIAL=${VDL2_SDR_SERIAL}
         - FEED_ID=${ACARS_STATION_ID}-VDL2
         - GAIN=40
         - FREQUENCIES=136.650;136.700;136.800;136.975
         - ZMQ_MODE=server
         - ZMQ_ENDPOINT=tcp://0.0.0.0:45555
       tmpfs:
         - /run:exec,size=64M
         - /var/log
       device_cgroup_rules:
         - "c 189:* rwm"
       volumes:
         - /dev/bus/usb:/dev/bus/usb:ro

     acars_router:
       image: ghcr.io/sdr-enthusiasts/acars_router:latest
       container_name: acars_router
       restart: unless-stopped
       environment:
         - TZ=${FEEDER_TZ}
         - AR_OVERRIDE_STATION_NAME=${ACARS_STATION_ID}
         - AR_ENABLE_DEDUPE=true
         - AR_RECV_ZMQ_VDLM2=dumpvdl2:45555
       tmpfs:
         - /run:exec,size=64M
         - /var/log:size=64M

     acarshub:
       image: ghcr.io/sdr-enthusiasts/docker-acarshub:latest
       container_name: acarshub
       restart: unless-stopped
       ports:
         - 8080:80
       volumes:
         - /opt/station/acars_data:/run/acars
       tmpfs:
         - /database:exec,size=64M
         - /run:exec,size=64M
         - /var/log:size=64M
       environment:
         - TZ=${FEEDER_TZ}
         - ENABLE_ACARS=true
         - ACARS_CONNECTIONS=zmq://acars_router:45550
         - ENABLE_VDLM=true
         - VDLM_CONNECTIONS=zmq://acars_router:45555
   ```

This file sends nothing to the internet. Section 10 adds the two lines that share.

How the messages move: acarsdec pushes each ACARS message to acars_router over UDP. acars_router collects the VDL2 messages from dumpvdl2 over ZMQ, a message link between programs. ACARS Hub collects both kinds from acars_router, also over ZMQ.

### If your radios differ from mine

| Your radios | Change in the compose file |
|---|---|
| One RTL radio, on VDL2 | Remove the whole `acarsdec` service. Remove the lines `ENABLE_ACARS` and `ACARS_CONNECTIONS`. |
| One RTL radio, on ACARS | Remove the whole `dumpvdl2` service. Remove `AR_RECV_ZMQ_VDLM2`, `ENABLE_VDLM` and `VDLM_CONNECTIONS`. Then make the change in the next row. |
| An RTL radio on ACARS, in place of the Airspy R2 | Replace the `AIRSPY_SERIAL` line with `- RTL_SERIAL=${ACARS_SDR_SERIAL}`. Set `ACARS_SDR_SERIAL=131` in `.env`. |

### Start it

1. Connect the radios to the computer.
2. Start the containers.

   ```bash
   cd /opt/station && docker compose up -d
   ```

   Result: Docker downloads four images and starts four containers.

3. Check that each decoder holds the correct radio.

   ```bash
   docker logs acarsdec 2>&1 | grep "Starting:"        # must show --airspy <SER_R2>
   docker logs dumpvdl2 2>&1 | grep "Starting:"        # must show --rtlsdr 136
   docker logs dumpvdl2 2>&1 | grep -i "detected"      # if you use a V4L, it must be named here
   ```

   Result: each `Starting:` line shows the full command of the decoder, with your serial number and your channels.

4. Open the message screen in a browser on your home network: `http://pi:8080/`. If the name does not work, use the address of the computer.

### Gain

Gain is how much the radio amplifies before it measures. Too little loses weak messages. Too much overloads on strong messages.

- **dumpvdl2:** `GAIN=40` is the container's default, and the value its author uses for an RTL radio. His README says not to go to the maximum.
- **acarsdec on an RTL radio:** the container's default is automatic gain. The decoder's README says automatic usually means maximum, which is bad for reception. Add a line such as `- GAIN=40` and compare.
- **acarsdec on an Airspy:** I run the container's default and it works. The decoder's README gives the Airspy its own scale, 0 to 21. I did not compare the two.

Change one value, run `docker compose up -d`, and count messages over the same hours of the day.

## 9. The first messages

1. Keep the message screen open.
2. Wait. Near a large airport, expect the first VDL2 messages in minutes. ACARS is slower and comes in bursts.
3. Open the statistics page. It counts the messages for each frequency.

The first day of my ACARS decoder brought 927 messages. My first VDL2 rate was about 150 messages an hour. Your numbers depend on your antenna and on the air traffic near you.

### Read one message

I wrote this message myself, in the real format. The aircraft and the flight do not exist. I do not publish the text of real messages (section 11).

```
EXAMPLE MESSAGE

.N123XY  QF  4  M12A  XX0123  KSEA1432KSFO
```

| Field | In the example | Meaning |
|---|---|---|
| Registration | `.N123XY` | The tail number of the aircraft. Seven characters, filled with dots at the front |
| Label | `QF` | Two characters that say what kind of message this is. `QF` is a takeoff report |
| Block | `4` | The block character: one character that identifies this block |
| Message number | `M12A` | The aircraft's own number for this message |
| Flight | `XX0123` | Two characters for the airline, then the flight number |
| Text | `KSEA1432KSFO` | For `QF`: departure airport, the time the wheels left the ground, destination |

The mode, acknowledgement and checksum characters are left out of this example. Your message screen shows the same fields for each message, plus the frequency and the signal level.

The layout of the text depends on the label and on the airline. The first job of ACARS, in 1978, was an automatic time clock with four moments. The decoder knows these labels for them:

| Label | Moment | Text starts with |
|---|---|---|
| `QA` | Out of the gate | An airport code, then the time as 4 digits |
| `QB` | Off the ground | An airport code, then the time as 4 digits |
| `QC` | On the ground | An airport code, then the time as 4 digits |
| `QD` | In the gate | An airport code, then the time as 4 digits |
| `QF` | Off the ground, with destination | Departure airport, time, destination |

Many other messages are easy to read: a weather request, a gate, a free text from the crew. Many are only codes. Some airlines encode or encrypt their text. This guide does not decode those.

VDL2 carries the same ACARS messages inside a newer link. It also carries link traffic that says nothing to a person. The dumpvdl2 container removes most of that traffic by default (`VDLM_FILTER_ENABLE`).

## 10. Share with airframes.io (optional)

airframes.io is a community network. It collects these messages from volunteer stations and shows them to everyone.

> **This step makes three things public.** First, the full text of every message that you receive goes to airframes.io, and the network shows it. Second, your station ID is public, and its recommended form names your nearest large airport. Third, the stations page shows a location for a station "if provided". Decide first if this fits the law where you live (section 3). I am not a lawyer.

1. Choose `<STATION_ID>`. The airframes.io form is `XX-YYYY`: your initials or another short code, then the code of the nearest large airport. Use a code that does not name you, if you prefer.
2. Put that value in `/opt/station/.env`.
3. Add two lines to the `environment` list of `acars_router` in the compose file.

   ```yaml
         - AR_SEND_UDP_ACARS=feed.airframes.io:5550
         - AR_SEND_TCP_VDLM2=feed.airframes.io:5553
   ```

4. Apply the change.

   ```bash
   cd /opt/station && docker compose up -d
   ```

5. Find your station by its ID on <https://app.airframes.io/stations>. The documents say a new station can take a few minutes to show.

The decoders add `-ACARS` and `-VDL2` to the ID in each message (`FEED_ID`). acars_router then puts the plain ID on every message it sends. With one radio, add only the line for your system.

To stop sharing, remove the two lines and run `docker compose up -d` again.

## 11. Manners with message content

These messages are not written for you. Most are routine. Some hold private details, or information that is sensitive for the airline.

- I show that a message arrived, and what kind it was. I do not publish the text of real aircraft messages.
- For an example, write your own message in the real format and mark it as an example. That is what I did in section 9.
- Do not post a real message to make a point about an airline, a flight or a person.
- Do not use a message for gain. In the US, the law in section 3 forbids it.
- Keep the message screen inside your home network. Do not open it to the internet.
- The alert function of ACARS Hub looks for words that you choose. Choose words about the system, not about people.

## 12. Run it unattended

The station needs no daily work. Each container has `restart: unless-stopped`, so Docker starts it again after a crash or a restart of the computer.

1. Update the containers from time to time.

   ```bash
   cd /opt/station && docker compose pull && docker compose up -d
   ```

2. Restart a decoder after you disconnect a radio or the USB hub.

   ```bash
   docker restart acarsdec dumpvdl2
   ```

3. Look at the health of the four containers.

   ```bash
   docker ps
   ```

Facts to know:

- **Storage.** ACARS Hub keeps messages for 7 days and alert matches for 120 days. `DB_SAVE_DAYS` and `DB_ALERT_SAVE_DAYS` change that. The database is in `/opt/station/acars_data`.
- **The memory card.** The `tmpfs` lines keep logs and working files in memory. That saves writes to the card.
- **Load.** My Pi 5 runs these four containers and a ship receiver. Together they used 1.6 GB of memory. The load average was near 1 on 4 cores.
- **A baseline.** Write down the message count of each system for one normal day. Change one thing at a time, and compare with that number.

## 13. Check that it works

| # | Check | Command or page | Pass | Wait |
|---|---|---|---|---|
| 1 | Four containers run | `docker ps` | `acarsdec`, `dumpvdl2`, `acars_router`, `acarshub` are all "Up" | 1 minute |
| 2 | Each decoder holds its radio | The three `docker logs` commands in section 8 | Your serial numbers, your channels, and the V4L by name | At once |
| 3 | The router receives | `docker logs acars_router 2>&1 \| tail -n 40` | A block of statistics every 5 minutes. Its counts rise | 5 to 10 minutes |
| 4 | VDL2 arrives | `http://pi:8080/` | New VDL2 messages | Minutes near a large airport |
| 5 | ACARS arrives | `http://pi:8080/` | New ACARS messages, in bursts | Longer than VDL2 |
| 6 | Sharing works (optional) | <https://app.airframes.io/stations> | Your station ID, with counts | A few minutes |

The all-time counts of my station show these proportions. Use them as a rough picture, not as a target.

- About 7 VDL2 messages for each ACARS message.
- 98 percent of ACARS on one channel (130.025 MHz).
- VDL2 on two channels: 64 percent on 136.650 MHz and 36 percent on 136.975 MHz.
- Thousands of messages every day in total.

## 14. If it does not work

| Symptom | Likely cause | What to do |
|---|---|---|
| `usb_claim_interface error -6`, "Device or resource busy" | The kernel's TV driver took the RTL radio | Check step 5 of section 5. Restart the computer |
| The RTL-SDR Blog V4L opens, the container looks healthy, and it decodes nothing | A driver older than August 2026 does not know its tuner | `docker compose pull && docker compose up -d`. The log must name the V4L |
| A container stops at once with "FEED_ID is not set" or "FREQUENCIES is not set" | A value is missing in `.env` or in the compose file | Fill the value. Run `docker compose up -d` |
| The log says "Either RTL_SERIAL or SOAPYSDR or AIRSPY_SERIAL must be set" | No serial number reached the decoder | Check the serial lines in `.env`. Check the name after `${` in the compose file |
| A decoder runs, and the screen stays empty for hours | Wrong channels for your region, or no traffic in range | Use the list for your region (section 7). Start VDL2 with 136.975 MHz only. Test at a busy hour |
| VDL2 decodes nothing on an old RTL radio | The radio's oscillator is off frequency | Measure the error with `rtl_test -p`, as the dumpvdl2 README shows. Add `- PPM=<value>` to the decoder |
| Few messages, and only from aircraft that are close | The antenna is indoors or low, or the coax is long and thin | Move the antenna outside and up. Use shorter, better coax |
| Few messages, and strong FM stations are near | The radio is in overload | Fit the FM band-stop filter as the first part. Then lower `GAIN` |
| Fewer messages after you add an amplifier | The strong signals now overload the radio, and the weak ones are lost | Remove it. On my station a 20 dB amplifier cut ACARS from 95 to 42 messages an hour |
| No messages after you disconnected a radio or the hub | The decoder lost its radio, or holds the wrong one | `docker restart acarsdec dumpvdl2` |
| With two Airspy radios: one feed stops when the other starts | Both report one USB identity, and a decoder opened "the first one" | Give every container its radio's serial number |
| A radio behind the splitter runs hot | A bias tee is on | Remove any `BIASTEE` line |
| The decoders log messages, and the screen shows none | The clock of the computer is wrong, so the router rejects the messages as old | Check the time with `date`. Check the `ACARS_CONNECTIONS` and `VDLM_CONNECTIONS` lines |
| dumpvdl2 reports lost samples on a Raspberry Pi | The computer is too busy | Remove quiet channels (section 7) |
| Airline names on the screen are wrong | The two-letter airline codes are not unique | Set `IATA_OVERRIDE` in ACARS Hub. In the US, its README suggests `UP\|UPS\|United Parcel Service` |
| The station does not show on airframes.io | The computer cannot reach `feed.airframes.io`, or the ID changed | Check the two `AR_SEND` lines. Keep one ID. Wait a few minutes |

## 15. What I did wrong

- **I added an amplifier to a chain that did not need one.** It cut my decoded messages by half. The strong signals got stronger and the weak ones were lost. Measure before and after every change.
- **I listen to more channels than I need.** Fourteen channels are in my two lists. Three carry nearly all of my messages. Read your own statistics.
- **Two of my radios report the same USB identity.** A decoder that opened "the first one" took the wrong radio, and another feed stopped. Give every decoder its serial number. This one is from my full-station guide.
- **I trusted a "healthy" container.** A new RTL radio opened with an old driver and decoded nothing. Check for messages, not for a status word. This one is also from my full-station guide.

## 16. Not run and not confirmed

- **The compose file in section 8 as written.** The file in my full-station guide has more lines, for shortwave messages and for an aircraft map. I removed them here. The other settings are the same.
- **The message screen at `8080:80`.** That is the upstream default. My full-station guide uses `8081:80`, because its aircraft map has `8080:80`.
- **The one-radio path and the two-RTL path.** My ACARS radio is an Airspy R2. The changes in section 8 follow each container's README.
- **The home-made antenna.** I did not build it. I did not test a narrow antenna without the FM filter.
- **Every regional channel list except North America.** I copied them from the airframes.io documents. Channels change.
- **The gain advice for acarsdec.** I run the container's default on an Airspy. I did not test `GAIN=40` on an RTL radio for ACARS.
- **`OUTPUT_SERVER_MODE=udp`.** It works on my station. The container's README recommends `tcp` as more reliable. I did not test `tcp`.
- **The PPM step for an old RTL radio.** It is from the dumpvdl2 README. My radios did not need it.
- **The limit on channels.** The decoder's README names no limit for acarsdec. The ACARS Hub README says 16. The airframes.io documents say 8. I run 6.
- **An amplifier with an Airspy.** The docker-acarsdec README recommends one. On my station an amplifier made reception worse. That was one test.
- **The form of the Airspy serial number.** I give the container 16 hexadecimal characters. The decoder's README shows an example with `0x` in front. I did not test that form.
- **The 2,400 bits each second of ACARS, and the field layout in section 9.** The speed is from the airframes.io documents. The layout agrees with the decoder's printed output, but I did not read the standard (ARINC 618).
- **The exact look of the ACARS Hub pages.** I did not open the current version to write this guide.
- **What airframes.io shows about a station, and what an account there adds.** I read its documents, not its terms.
- **That `restart: unless-stopped` behaves as I describe.** I did not read the Docker documents for this guide.
- **The `rtl_eeprom` command.** It is from my full-station guide. I did not run it again for this guide.
- **That the splitter passes or blocks DC.** Its data sheet does not say. So keep every bias tee off.
- **The price of the Nooelec radio, the coax and the small parts.** Read the listings.
- **The law outside the United States.** I did not read it.
- No step in this guide was run on a new, empty computer as written.

## 17. Sources

All were read on 2026-10-10, except where a date is given.

**The episode.** Episode 4 of Spurious Emissions, "ACARS and VDL2": its fact sheet, its script and its list of links.

**The full-station guide.** [Build the station](/guides/build-the-station/), sections 2, 4, 6, 10 and 17. Its commands were checked against my station on 2026-10-07. The prices are from that guide and that date.

**Containers.** <https://github.com/sdr-enthusiasts/docker-acarsdec> (README and start scripts) · <https://github.com/sdr-enthusiasts/docker-dumpvdl2> (README and start scripts) · <https://github.com/sdr-enthusiasts/acars_router> · <https://github.com/sdr-enthusiasts/docker-acarshub> (README and `Setting-Up-ACARSHub.MD`) · <https://github.com/sdr-enthusiasts/docker-install>

**Decoders.** <https://github.com/f00b4r0/acarsdec> (README and `label.c`) · <https://github.com/szpajder/dumpvdl2> (README)

**airframes.io.** <https://docs.airframes.io>, from its source at <https://github.com/airframesio/docs>. I read the pages on feeding, feeding with Docker, hardware, ACARS, VDL, the two decoder guides, troubleshooting, and using the site.

**History and speeds.** Wikipedia, "ACARS" and "VHF Data Link", read 2026-10-06 for the episode.

**Rules.** 47 U.S.C. 605: <https://www.law.cornell.edu/uscode/text/47/605>, read 2026-10-07.

[^why-airspy]: I use the Airspy R2 because I owned it. It takes a wider slice of the band than an RTL radio, so my ACARS list can be wider than 2 MHz. The docker-acarsdec README itself says an RTL radio may be the better choice for this job.
[^pi-load]: I measured an 8 GB Pi 5 that runs these four containers and a ship receiver. They used 1.6 GB of memory. So 4 GB may be enough for this guide alone. I did not test it.
[^splitter]: I bought my splitter from a surplus dealer. A 2-way splitter costs each leg 3 dB, plus a small loss of its own.
[^nec]: Mike Holt's summary of NEC 810.21, 2020 edition: <https://www.mikeholt.com/files/PDF/20BG_810.21.pdf>.
