Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
daafd80
feat: Add ADS-C contracts and reports
YuKitsune Oct 13, 2026
a598b4c
wip: Show ADS-C track
YuKitsune Oct 14, 2026
9e0b149
add test button
YuKitsune Oct 14, 2026
1e0c2b9
Fix build
YuKitsune Oct 14, 2026
79d4309
Fixing bits up
YuKitsune Oct 14, 2026
6d70c76
temp
YuKitsune Oct 14, 2026
87ef506
Fix extrapolation symbol
YuKitsune Oct 14, 2026
e6ae20b
Stub ADS-C window
YuKitsune Oct 14, 2026
67bd5dd
Contract period management
YuKitsune Oct 14, 2026
654c822
Custom period
YuKitsune Oct 14, 2026
cba33a2
Tweak popup
YuKitsune Oct 14, 2026
1e17166
Small bits
YuKitsune Oct 14, 2026
97c03fd
Merge branch 'feat/ads-c' of github.com:YuKitsune/CPDLCPlugin into fe…
YuKitsune Oct 14, 2026
bb418a2
Wire up demand button
YuKitsune Oct 14, 2026
bf72bf8
Add ADS-C page
YuKitsune Oct 16, 2026
5051ad9
Add example viewmodel
YuKitsune Oct 17, 2026
086f870
Tweak window
YuKitsune Oct 17, 2026
117fbc6
Clean up
YuKitsune Oct 17, 2026
636c8e7
Fix window sizing
YuKitsune Oct 17, 2026
720bfc4
wip: Use vatSys forms
YuKitsune Oct 17, 2026
fd067c0
Merge branch 'feat/ads-c' of github.com:YuKitsune/CPDLCPlugin into fe…
YuKitsune Oct 17, 2026
61784b8
Fix window sizing
YuKitsune Oct 17, 2026
86d25b3
wip: use vatsys forms
YuKitsune Oct 17, 2026
3f58e50
Debug stuff
YuKitsune Oct 17, 2026
f7cb04f
Fix history window sizing
YuKitsune Oct 17, 2026
633d794
Fix disposal error
YuKitsune Oct 17, 2026
8bbc09e
Clean up build
YuKitsune Oct 17, 2026
3beb121
Use smaller cross
YuKitsune Oct 17, 2026
cebac96
Merge branch 'feat/ads-c' of github.com:YuKitsune/CPDLCPlugin into fe…
YuKitsune Oct 17, 2026
540f43f
Fix form title
YuKitsune Oct 17, 2026
af1b7ec
Store and load ADS-C reports on connection
YuKitsune Oct 17, 2026
126814d
Fix logs
YuKitsune Oct 17, 2026
060e279
Fix comments
YuKitsune Oct 17, 2026
26dab3d
Merge branch 'feat/ads-c' of https://github.com/YuKitsune/CPDLCPlugin…
YuKitsune Oct 17, 2026
1fa0ac1
ci: Fix fallout
YuKitsune Oct 17, 2026
ea1f4a1
Revert "ci: Fix fallout"
YuKitsune Oct 17, 2026
5c8693c
Docs
YuKitsune Oct 17, 2026
1531116
Merge branch 'feat/ads-c' of github.com:YuKitsune/CPDLCPlugin into fe…
YuKitsune Oct 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,15 @@ class Build : FalloutBuild
.Executes(() =>
{
var mainAssembly = PluginBuildOutputDirectory / PluginAssemblyFileName;
AbsolutePath[] nonMergedAssemblies =
[
// ILRepack cannot merge Harmony, so it stays as a separate file next to the plugin
PluginBuildOutputDirectory / "0Harmony.dll"
];

var assembliesToMerge = PluginBuildOutputDirectory
.GlobFiles("*.dll")
.Except([mainAssembly])
.Except([mainAssembly, ..nonMergedAssemblies])
.ToArray();

if (!mainAssembly.FileExists())
Expand Down
72 changes: 72 additions & 0 deletions docs/docs/concepts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,75 @@ sequenceDiagram
Note over YBBB,Pilot: Dialogue closes, connection terminates
```

## ADS-C

Automatic Dependent Surveillance - Contract (ADS-C) lets a ground station get position reports from an aircraft by data link.
Controllers use ADS-C to see aircraft that are outside radar coverage, such as aircraft over the ocean.

The aircraft sends reports automatically. The pilot does not have to do anything.
Each report has the position, the altitude, and the time of the report.

:::note
ADS-C and CPDLC are separate systems. An aircraft can have an ADS-C contract with a ground station without a CPDLC connection.
For convenience, this plugin connects ADS-C to CPDLC. The server requests an ADS-C contract when an aircraft logs on with CPDLC, and cancels the contract when the CPDLC connection ends.
:::

### Contracts

The ground station requests a **contract** with the aircraft. The contract tells the aircraft when to send reports.
The plugin uses these types of contract:

- **Periodic contract:** The aircraft sends a report at a fixed interval, for example, each 10 minutes
- **Demand contract:** The aircraft sends one report immediately

An aircraft has only one periodic contract with each ground station. A new periodic request replaces the previous one.

:::note
Event contracts are not supported.
:::

### Default and Selected Contracts

Each ATSU has a **default reporting interval**.
When an aircraft logs on with CPDLC, the server automatically requests a periodic contract with the default reporting interval. This is the **default contract**.

A controller can select a different reporting interval for an aircraft, for example, a shorter interval near a conflict. This is the **selected contract**.
A selected contract ends after a fixed time. Then the server requests the default contract again.

```mermaid
sequenceDiagram
participant Pilot
participant ATSU

Pilot->>ATSU: REQUEST LOGON
ATSU->>Pilot: LOGON ACCEPTED
ATSU->>Pilot: Periodic contract (default interval)
Pilot->>ATSU: Report
Note over ATSU,Pilot: Default contract accepted
ATSU->>Pilot: Periodic contract (selected interval)
Pilot->>ATSU: Report
Note over ATSU,Pilot: Selected contract accepted
Note over ATSU,Pilot: Selected contract ends
ATSU->>Pilot: Periodic contract (default interval)
```

When the CPDLC connection ends, the server cancels the contract.

### Contract State

Each request has one of these states:

| State | Description |
| :---: | ----------- |
| Sent | The request was sent to the aircraft. The aircraft has not answered. |
| Accepted | The aircraft sent a report after the request. |
| Rejected | The aircraft rejected the request. |

The aircraft does not acknowledge a request. The first report after a request accepts that request.

If the aircraft rejects a selected contract, the server requests the default contract again.
If the aircraft rejects the default contract, the server does not request it again.

## CPDLC on VATSIM

VATSIM does not have an official CPDLC solution, though one is in development. In the meantime, CPDLC for flight simulation networks typically uses Hoppie's ACARS network.
Expand All @@ -247,6 +316,9 @@ Stations connected to Hoppie's network poll for messages at regular intervals.
Messages are not received instantly by the recipient.
It may take up to one minute for a message to reach the pilot, and another minute for a response to return.

This delay also applies to ADS-C. A report can arrive up to one minute after its report time, and a demand report can take up to two minutes to arrive.
For this reason, the shortest reporting interval is one minute.

### Aggregation Server

Hoppie's network does not allow multiple controllers to use a single ATSU callsign. The aggregation server solves this problem by acting as a relay between controllers and the ACARS network, allowing multiple controllers to share a single ATSU connection.
1 change: 1 addition & 0 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The system consists of two components:
Multiple controllers can share a single ATSU connection, with messages automatically routed to the appropriate controller.

**[vatSys Plugin](/vatsys-plugin/installation)** provides the controller interface within vatSys for sending and receiving CPDLC messages with aircraft.
It also shows ADS-C position reports on the ASD, and lets controllers change the ADS-C contract with an aircraft.

![Architecture Diagram](../static/diagram.png)

Expand Down
17 changes: 17 additions & 0 deletions docs/docs/server/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ Acars__0__FlightSimulationNetwork=VATSIM
Acars__0__StationIdentifier=YBBB
Acars__0__Url=http://www.hoppie.nl/acars/system/connect.html
Acars__0__AuthenticationCode=your-hoppie-logon-code
# Reporting interval in minutes of the ADS-C contract requested on CPDLC logon (default: 10)
Acars__0__DefaultAdscReportingInterval=10
# Minutes from the request of a selected ADS-C contract to its end. Then the default contract is requested again (default: 60)
Acars__0__SelectedAdscContractDuration=60

# Handoff Configuration
# How many minutes before the expected transfer time to send the NEXT DATA AUTHORITY message (default: 10)
Expand Down Expand Up @@ -78,6 +82,19 @@ Acars__1__AuthenticationCode=your-ymmm-hoppie-code
`Hoppie` is currently the only supported ACARS network type.
:::

### ADS-C

When an aircraft logs on with CPDLC, the server requests the default ADS-C contract with the aircraft. When the CPDLC connection ends, the server cancels the contract.

Each ATSU has its own default reporting interval, set with `Acars__<index>__DefaultAdscReportingInterval`. Controllers can read this value.

A controller can select a different reporting interval for an aircraft. The selected contract ends `Acars__<index>__SelectedAdscContractDuration` minutes after the server sends the request. Then the server requests the default contract again. A controller can also go back to the default contract at any time.

The aircraft does not acknowledge a request. The first report after a request accepts it. If the aircraft rejects a selected contract, the server requests the default contract again. If the aircraft rejects the default contract, the server does not send it again.

The server keeps the latest report for each aircraft. When a controller connects, the plugin loads these reports, so that the controller sees the ADS-C tracks immediately.

The ADS-C page of the dashboard (`/adsc`) shows all the contracts and their states, and a log of all the ADS-C messages that the server sent and received.
## Running

### Docker
Expand Down
12 changes: 12 additions & 0 deletions docs/docs/vatsys-plugin/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ Default: `true`
"ShowInstallationMenuItems": false
```

### ADSCTrackExtrapolationThreshold

The minimum time (in seconds) that an ADS-C track shows the reported position with the `ADSC` symbol. After this time, the next flight plan extrapolation moves the track along its route, and the track uses the `ADSCExtrapolated` symbol.

Default: `10`

**Example:**
```json
"ADSCTrackExtrapolationThreshold": 10
```

## Uplink Messages

The `UplinkMessages` object defines the available CPDLC messages for the controller to send.
Expand Down Expand Up @@ -224,6 +235,7 @@ Array of message groups for organizing messages in the editor. Each group has:
{ "AtsuCode": "YMMM", "Sectors": ["GUN", "BLA", "IND"] }
],
"NdaRecalculationIntervalMinutes": 1,
"ADSCTrackExtrapolationThreshold": 10,
"UplinkMessages": {
"MasterMessages": [
{
Expand Down
70 changes: 68 additions & 2 deletions docs/docs/vatsys-plugin/plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sidebar_position: 2
# Plugin Guide

This guide covers the CPDLC Plugin for vatSys.
It explains how to connect to the server, interpret track labels, and manage CPDLC messages with pilots.
It explains how to connect to the server, interpret track labels, manage CPDLC messages with pilots, and use ADS-C.

## Connecting to the CPDLC Server

Expand Down Expand Up @@ -176,6 +176,72 @@ Enter a callsign in the ACID input to display that aircraft's message history. O

Messages are displayed the same way as in the Current Messages Window. Messages prefixed with `M` were manually acknowledged by the controller.

## ADS-C

The plugin shows ADS-C position reports on the ASD, and lets you change the ADS-C contract with an aircraft.
Refer to [ADS-C](/concepts#ads-c) for an explanation of contracts.

You do not have to do anything to start ADS-C. When an aircraft logs on with CPDLC, the server automatically requests the default contract.

### ADS-C Tracks

![Screenshot of an ADS-C track on the ASD, next to the same track after extrapolation. Show the ADSC symbol and the ADSCExtrapolated symbol.](../../static/adsc_tracks.png)

When a report arrives, the flight plan track of the aircraft moves to the reported position, and shows the `ADSC` symbol.
The reported altitude becomes the pilot reported level (PRL) of the flight plan.

After a short time, the track moves along the flight plan route from the reported position, and shows the `ADSCExtrapolated` symbol.
The plugin uses the route estimates of the flight plan to calculate the speed of the track.
You can change this time with the [`ADSCTrackExtrapolationThreshold`](configuration#adsctrackextrapolationthreshold) setting.

The vatSys profile sets how the symbols look, so the exact symbols may vary.
By default, the ADSC symbol is a filled triangle, and the ADSCExtrapolated is a hollow triangle.

A radar track always has priority. When the flight plan is coupled to a radar track, the plugin does not move the track.

### ADS Contract Window

![Screenshot of the ADS Contract window with an aircraft loaded.](../../static/adsc_window.png)

The ADS Contract Window shows the ADS-C contract that your ATSU has with an aircraft. You can use it to select a different reporting interval, or to request a demand report.

To open the window, select a track, then open the CPDLC menu and select ADS Contract. The window shows the contract for the selected track.

To show a different aircraft, click the ACID button, type the callsign, then press Enter.

The window shows:

- **Current Period:** The reporting interval of the latest periodic contract, in minutes
- **Last Report:** The time of the latest report from the aircraft
- **Default Period:** The default reporting interval of your ATSU, in minutes
- **Selected Period:** The reporting interval that a controller selected, in minutes. `DEF` shows that the default period is selected.
- **Selected Period End:** The time when the selected contract ends. Then the server requests the default contract again.

A symbol next to each contract shows the state of the request:

- `→`: The request was sent. The aircraft has not answered.
- `✓`: The aircraft accepted the request.
- `×`: The aircraft rejected the request.

#### Selecting a Reporting Interval

1. Click the Selected Period button. A list of periods opens.
2. Click a period in the list, or type a period (in minutes) in the box at the bottom of the list, then press Enter.

The server sends the request to the aircraft immediately.
The selected contract ends after a time that the server sets (60 minutes by default).

To go back to the default contract before the selected contract ends, click `DEF` in the list.

#### Requesting a Demand Report

Click Demand Contract to request one report from the aircraft immediately.
A demand report does not change the periodic contract.

:::tip
Because of the delays on Hoppie's network, a demand report can take up to two minutes to arrive.
:::

## Voice Capability Indicators

![Screenshot of voice capability label items](../../static/text_label_items.png)
Expand Down Expand Up @@ -220,7 +286,7 @@ This ensures pilots can acknowledge frequency change instructions before losing

The plugin does not fully simulate all aspects of real-world CPDLC.

**ADS-C:** ADS-C position reporting is not simulated due to limitations within the vatSys plugin API.
**ADS-C:** Only periodic and demand contracts are supported. Event contracts are not supported.

**Equipment codes:** Aircraft equipment codes are ignored. The `.` symbol indicates the aircraft is connected to an ACARS network, regardless of their filed equipment.

Expand Down
Binary file added docs/static/adsc_tracks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/static/adsc_window.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Acars__0__FlightSimulationNetwork=VATSIM
Acars__0__StationIdentifier=YBBB
Acars__0__Url=http://www.hoppie.nl/acars/system/connect.html
Acars__0__AuthenticationCode=your-authentication-code-here
# Reporting interval in minutes of the ADS-C contract requested on CPDLC logon (default: 10)
Acars__0__DefaultAdscReportingInterval=10
# Minutes from the request of a selected ADS-C contract to its end. Then the default contract is requested again (default: 60)
Acars__0__SelectedAdscContractDuration=60

# Example: Second ACARS Configuration (uncomment to use)
# Acars__1__Type=Hoppie
Expand Down
76 changes: 76 additions & 0 deletions source/CPDLCPlugin/AdscContractStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using CPDLCServer.Contracts;

namespace CPDLCPlugin;

public class AdscContractStore
{
readonly List<AdscContractDto> _contracts = new();
readonly SemaphoreSlim _semaphore = new(1, 1);

public async Task Populate(AdscContractDto[] contracts, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
_contracts.Clear();
_contracts.AddRange(contracts);
}
finally
{
_semaphore.Release();
}
}

public async Task Upsert(AdscContractDto contractDto, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
_contracts.RemoveAll(c => c.Callsign == contractDto.Callsign && c.StationId == contractDto.StationId);
_contracts.Add(contractDto);
}
finally
{
_semaphore.Release();
}
}

public async Task<IReadOnlyCollection<AdscContractDto>> All(CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
return _contracts.ToArray();
}
finally
{
_semaphore.Release();
}
}

public async Task<bool> Remove(string callsign, string stationId, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
return _contracts.RemoveAll(c => c.Callsign == callsign && c.StationId == stationId) > 0;
}
finally
{
_semaphore.Release();
}
}

public async Task Clear(CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
_contracts.Clear();
}
finally
{
_semaphore.Release();
}
}
}
Loading
Loading