Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0daf640
feat(config): add AtsuCodes dictionary to plugin configuration
YuKitsune May 1, 2026
49b1d23
feat(nda): calculate NDA from AtsuCodes config instead of ATIS
YuKitsune May 1, 2026
beffd4d
refactor(nda): remove ATIS and controller connection infrastructure
YuKitsune May 1, 2026
e115067
feat(server): gate UM160 on NDA ATSU online status, raise lead time t…
YuKitsune May 1, 2026
6c4f60d
fix(nda): reduce log noise from periodic NDA recalculation
YuKitsune May 1, 2026
442c455
feat(config): make NDA recalculation interval configurable
YuKitsune May 1, 2026
30a5a97
docs(config): document AtsuCodes and NdaRecalculationIntervalMinutes
YuKitsune May 1, 2026
b1c865a
refactor(server): rename handlers and services to reflect NDA purpose
YuKitsune May 1, 2026
9fbab15
fix(server): add debug logging to NDA uplink early exit paths
YuKitsune May 1, 2026
541f52b
test(server): fix handler tests and cover NDA ATSU offline case
YuKitsune May 1, 2026
3556aaf
chore: update TODO
YuKitsune May 1, 2026
4f2a6b6
chore(server): remove resolved TODO comments and tidy handler
YuKitsune May 1, 2026
7e81d48
config: Complete ATSU code list
YuKitsune May 1, 2026
f485f18
chore: Clean up logging
YuKitsune May 1, 2026
ff7b4da
config: Add upper sectors
YuKitsune May 1, 2026
78fdc12
fix: Build error
YuKitsune May 1, 2026
c0a61a2
config: Add upper
YuKitsune May 1, 2026
4a70946
docs(config): fix sector names and source file reference in AtsuCodes…
YuKitsune May 1, 2026
9e335fe
refactor(config): replace AtsuCodes dictionary with typed AtsuCodeMap…
YuKitsune May 1, 2026
92d55c7
docs(config): update AtsuCodes examples for new object format
YuKitsune May 1, 2026
7353185
chore(config): fix AtsuCodes sector mappings
YuKitsune May 1, 2026
003b87f
refactor: rename AcarsConnectedCallsignsStore to AcarsStationStore
YuKitsune May 2, 2026
c10ad03
feat: collect ATSU candidates from full sector hierarchy for NDA
YuKitsune May 2, 2026
40f24d0
config: add subsectors to ATSU code mappings
YuKitsune May 2, 2026
c3564e1
docs(config): update AtsuCodes to document per-subsector NDA selection
YuKitsune May 2, 2026
b96224f
update todos
YuKitsune May 2, 2026
429a630
refactor: rename AcarsConnectedCallsigns types to AcarsStation equiva…
YuKitsune May 2, 2026
82d297d
fix: Clear connections on disconnect
YuKitsune May 2, 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
17 changes: 17 additions & 0 deletions CPDLC.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@
"MaxArchivedMessages": 50,
"MaxDisplayMessageLength": 40,
"MaxExtendedMessageLength": 80,
"AtsuCodes": [
{ "AtsuCode": "YBBB", "Sectors": ["INL", "DOS", "SDY", "BUR", "GOL", "NSA", "ARL", "MDE", "MLD", "OCN", "MNN", "CNK", "KPL", "CVN", "SWY", "KEN", "BAR", "TBP", "WIL", "ISA", "WEG", "ARA", "STR", "TRT", "KIY", "ASH", "TRS", "TSN", "HWE", "FLD", "COL"] },
{ "AtsuCode": "YMMM", "Sectors": ["GUN", "KAT", "BIK", "WOL", "SNO", "BLA", "ELW", "HUO", "WON", "MUN", "YWE", "OXL", "GTH", "TBD", "AUG", "ASP", "FOR", "WAR", "ASW", "WRA", "BKE", "ESP", "HYD", "JAR", "PIY", "SCR", "GVE", "GEL", "LEA", "OLW", "POT", "PAR", "MEK", "MTK", "NEW", "MZI", "IND", "INE", "INS"] },
{ "AtsuCode": "NZZO", "Sectors": ["NZZO", "NZZOT"] },
{ "AtsuCode": "NZCM", "Sectors": ["NZCM"] },
{ "AtsuCode": "FAJO", "Sectors": ["FAJO"] },
{ "AtsuCode": "FIMM", "Sectors": ["FIMM", "FIMMU"] },
{ "AtsuCode": "VRMF", "Sectors": ["VRMF"] },
{ "AtsuCode": "VCCF", "Sectors": ["VCCF"] },
{ "AtsuCode": "WIIF", "Sectors": ["WIIF", "WIIFSG", "WIIFBD", "WIIFMN", "WIIFIS"] },
{ "AtsuCode": "WAAF", "Sectors": ["WAAF", "UBLI", "WAAFNA", "WAAFAN", "WAAFPA"] },
{ "AtsuCode": "AYPM", "Sectors": ["AYPM"] },
{ "AtsuCode": "KZAK", "Sectors": ["KZAK", "KZAKE", "KZAKW", "KZAK3", "KZAK5", "KZAK6", "KZAK9"] },
{ "AtsuCode": "ASIW", "Sectors": ["ASIAW"] },
{ "AtsuCode": "ASEA", "Sectors": ["ASEA"] },
{ "AtsuCode": "AFRS", "Sectors": ["AFRS"] }
],
"UplinkMessages": {
"MasterMessages": [
{
Expand Down
8 changes: 4 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
TODO:

- [ ] Clean up label item creation.
- [ ] Add strip item cration.
- [ ] Write documentation.
- [X] Clean up label item creation.
- [X] Add strip item cration.
- [X] Write documentation.
- [X] Write SOPs.
- [ ] Dialogue timeouts
- [ ] Write SOPs.
- [ ] Write unit tests for plugin code.
- [ ] Write test cases for same flight across multiple units within the same server.
47 changes: 47 additions & 0 deletions docs/docs/vatsys-plugin/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,48 @@ Maximum character length for messages shown in the extended message view. Messag
"MaxExtendedMessageLength": 80
```

## Next Data Authority

### AtsuCodes

Maps ATSU/CPDLC logon codes to the vatSys sector names they cover. The plugin uses this to determine the Next Data Authority (NDA) for each tracked aircraft by walking the FDR route and finding the first sector that belongs to a different ATSU.

Each entry has an `AtsuCode` (the code aircraft logon to) and a `Sectors` array of sector names from the vatSys `Sectors.xml` file.

When calculating the NDA, the plugin walks the sector hierarchy from most-specific to least-specific (subsector to parent to grandparent). For each level it collects ATSU code candidates, then selects the first candidate that is currently connected to the ACARS network. This allows FIRs that use individual Hoppie logon codes per controller position to be mapped at the subsector level.

**Example (FIR using a single logon code):**
```json
"AtsuCodes": [
{ "AtsuCode": "YBBB", "Sectors": ["ARL", "INL", "KPL", "TSN"] },
{ "AtsuCode": "YMMM", "Sectors": ["GUN", "BLA", "IND"] }
]
```

**Example (FIR using per-position logon codes):**
```json
"AtsuCodes": [
{ "AtsuCode": "WIIF", "Sectors": ["WIIF"] },
{ "AtsuCode": "WIIFSG", "Sectors": ["WIIFSG", "WIIFBD"] },
{ "AtsuCode": "WIIFMN", "Sectors": ["WIIFMN", "WIIFIS"] }
]
```

In the per-position example, an aircraft entering `WIIFBD` airspace will have `WIIFSG` set as its NDA if that controller is online, falling back to `WIIF` if not.

If a sector appears in more than one entry, the NDA calculation will produce an error for that aircraft. If a sector does not appear in any entry, it is treated as outside CPDLC coverage and skipped.

### NdaRecalculationIntervalMinutes

How often (in minutes) the plugin recalculates the NDA for all tracked aircraft in the background. The NDA is also recalculated on every FDR update, so this is a catch-all interval.

Default: `1`

**Example:**
```json
"NdaRecalculationIntervalMinutes": 1
```

## Uplink Messages

The `UplinkMessages` object defines the available CPDLC messages for the controller to send.
Expand Down Expand Up @@ -166,6 +208,11 @@ Array of message groups for organizing messages in the editor. Each group has:
"MaxArchivedMessages": 50,
"MaxDisplayMessageLength": 40,
"MaxExtendedMessageLength": 80,
"AtsuCodes": [
{ "AtsuCode": "YBBB", "Sectors": ["ARL", "INL", "KPL", "TSN"] },
{ "AtsuCode": "YMMM", "Sectors": ["GUN", "BLA", "IND"] }
],
"NdaRecalculationIntervalMinutes": 1,
"UplinkMessages": {
"MasterMessages": [
{
Expand Down
4 changes: 2 additions & 2 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Acars__0__AuthenticationCode=your-authentication-code-here
# Acars__1__AuthenticationCode=your-other-authentication-code

# Handoff Configuration
# How many minutes before the expected transfer time to send the NEXT DATA AUTHORITY message (default: 10)
Handoff__NotificationLeadTime=10
# How many minutes before the expected transfer time to send the NEXT DATA AUTHORITY message (default: 20)
Handoff__NotificationLeadTime=20

# ngrok auth token to allow remote connections to your local debug server
# https://ngrok.com/
Expand Down
90 changes: 0 additions & 90 deletions source/CPDLCPlugin/ATISCache.cs

This file was deleted.

50 changes: 0 additions & 50 deletions source/CPDLCPlugin/AcarsConnectedCallsignStore.cs

This file was deleted.

3 changes: 3 additions & 0 deletions source/CPDLCPlugin/AcarsStation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace CPDLCPlugin;

public record AcarsStation(string StationId);
63 changes: 63 additions & 0 deletions source/CPDLCPlugin/AcarsStationStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
namespace CPDLCPlugin;

public class AcarsStationStore
{
readonly HashSet<string> _stationIds = new(StringComparer.OrdinalIgnoreCase);
readonly SemaphoreSlim _semaphore = new(1, 1);

public async Task Populate(string[] stationIds, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
_stationIds.Clear();
foreach (var stationId in stationIds)
{
_stationIds.Add(stationId);
}
}
finally
{
_semaphore.Release();
}
}

public async Task<bool> IsOnline(string stationId, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
return _stationIds.Contains(stationId);
}
finally
{
_semaphore.Release();
}
}

public async Task<AcarsStation[]> All(CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
return _stationIds.Select(id => new AcarsStation(id)).ToArray();
}
finally
{
_semaphore.Release();
}
}

public async Task Clear(CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
_stationIds.Clear();
}
finally
{
_semaphore.Release();
}
}
}
3 changes: 3 additions & 0 deletions source/CPDLCPlugin/Configuration/AtsuCodeMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace CPDLCPlugin.Configuration;

public record AtsuCodeMapping(string AtsuCode, string[] Sectors);
8 changes: 8 additions & 0 deletions source/CPDLCPlugin/Configuration/PluginConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,12 @@ public class PluginConfiguration
public required UplinkMessagesConfiguration UplinkMessages { get; init; }
public int MaxLogFileAgeDays { get; init; } = 5;
public LogEventLevel LogLevel { get; init; } = LogEventLevel.Information;

// Maps ATSU/CPDLC codes to the sector names they cover (as reported by vatSys SectorsVolumes).
// Used to determine the Next Data Authority when walking the FDR route.
public AtsuCodeMapping[] AtsuCodes { get; init; } = [];

// How often (in minutes) to recalculate the NDA for all tracked aircraft.
// NDA is also recalculated on every FDR update, so this is a catch-all interval.
public int NdaRecalculationIntervalMinutes { get; init; } = 1;
}
Loading
Loading