BaseStationReader.Logic
1.26.0
See the version list below for details.
dotnet add package BaseStationReader.Logic --version 1.26.0
NuGet\Install-Package BaseStationReader.Logic -Version 1.26.0
<PackageReference Include="BaseStationReader.Logic" Version="1.26.0" />
paket add BaseStationReader.Logic --version 1.26.0
#r "nuget: BaseStationReader.Logic, 1.26.0"
// Install BaseStationReader.Logic as a Cake Addin #addin nuget:?package=BaseStationReader.Logic&version=1.26.0 // Install BaseStationReader.Logic as a Cake Tool #tool nuget:?package=BaseStationReader.Logic&version=1.26.0
ADS-B-BaseStationReader
Overview
- An RTL2832/R820T2 USB Dongle is plugged into the Raspberry Pi
- The Raspberry Pi is running the dump1090-mutability service to decode the data from the dongle
- One of the outputs is a decoded stream of messages in "Basestation" format, that is exposed on a TCP port on the Pi
- This stream is read by the MessageReader, that exposes an event used to notify subscribers when a new message arrives
- The AircraftTracker subscribes to these events and passes each new message to the message parsers to have the information it contains extracted into an aircraft tracking object
- The AircraftTracker enqueues each new tracking object for asynchronous writing to the SQLite database
- It also exposes events to notify subscribers when aircraft are added, updated and removed
- On a timed interval, the QueuedWriter processes pending writes from the the queue
The Console Application
Overview
- The repository includes a console application that uses the Spectre.Console package to render a live view of the aircraft currently being tracked:
- The application subscribes to the events exposed by the AircraftTracker (see below) to implement continuous live updates
- As an aircraft's details are updated on receipt of a new messages, it's details are immediately updated in the live view
- As it moves through the tracking states (see below), it will be highlighted in yellow, when it reaches the "Recent" state, and red, when it reaches the "Stale" state
- When it is removed from the tracker's tracking list, it is also removed from the live table
Configuration File
General Settings and Database Connection String
- The appsettings.json file in the console application project contains the following keys for controlling the application:
Section | Key | Command Line | Short Name | Purpose |
---|---|---|---|---|
ApplicationSettings | Host | --host | -h | Host the reader connects to for reading messages |
ApplicationSettings | Port | --port | -p | Port the reader connects to for reading messages |
ApplicationSettings | SocketReadTimeout | --read-timout | -t | Timeout, in ms, for read operations on the message stream |
ApplicationSettings | ApplicationTimeout | --app-timeout | -a | Timeout (ms) after which the terminal application will quit if no messages are recieved |
ApplicationSettings | TimeToRecent | --recent | -r | Threshold, in ms, after the most recent message at which an aircraft is considered "recent" (see states, below) |
ApplicationSettings | TimeToStale | --stale | -s | Threshold, in ms, after the most recent message at which an aircraft is considered "stale" (see states, below) |
ApplicationSettings | TimeToRemoval | --remove | -x | Threshold, in ms, after the most recent message at which an aircraft is removed from tracking (see states, below) |
ApplicationSettings | TimeToLock | --lock | -k | Threshold, in ms, after which an active aircraft record is locked, having received no updates |
ApplicationSettings | LogFile | --log-file | -l | Path and name of the log file. If this is blank, no log file is created |
ApplicationSettings | MinimumLogLevel | --log-level | -ll | Minimum message severity to log (Debug, Info, Warning or Error) |
ApplicationSettings | EnableSqlWriter | --enable-sql-writer | -w | Set to true to enable the SQL writer or false to disable it |
ApplicationSettings | WriterInterval | --writer-interval | -i | Interval, in ms, at which the writer writes batches of changes from the queue to the database |
ApplicationSettings | WriterBatchSize | --writer-batch-size | -b | Maximum number of changes to consider on each WriterInterval |
ApplicationSettings | RefreshInterval | --ui-interval | -b | GUI live view refresh interval (ms) |
ApplicationSettings | MaximumRows | --max-rows | -m | Maximum rows in the live table view at any one time or 0 for unlimited rows |
ApplicationSettings | ReceiverLatitude | --latitude | -la | Receiver latitude, used in aircraft distance calculations |
ApplicationSettings | ReceiverLongitude | --longitude | -lo | Receiver longitude, used in aircraft distance calculations |
ApplicationSettings | Columns | - | - | Set of column definitions for columns to be included in the output |
ConnectionStrings | BaseStationReaderDB | - | - | SQLite connection string for the database |
- Values may also be passed using the indicated command line arguments, in which case the values are first read from the configuration file and then any values specified on the command line are then applied
Column Definitions
- The Columns property in the ApplicationSettings section of the file contains a list of column definitions:
[
{
"Property": "Address",
"Label": "ID",
"Format": ""
},
{
"Property": "Callsign",
"Label": "Callsign",
"Format": ""
},
{
"Property": "Latitude",
"Label": "Latitude",
"Format": "N5"
}
]
- Each column definition contains the following items:
Item | Comments |
---|---|
Property | Case-sensitive name of the property on the Aircraft entity to be rendered in this column |
Label | Column title |
Format | The C# format string used to render the property (for Decimal and DateTime types) or blank |
- The application will show only the columns listed in this section of the configuration file, showing them in the order in which they appear here and formatted according to the format specifier
Row Limits and Column Control
- The maximum row limit and custom column control are intended to support running the application on small screens
- The following shows the console application running on a Raspberry Pi with 3.5" LCD screen:
Aircraft Tracking
Adding and Updating Tracking Objects
- Aircraft are identified by their ICAO 24-bit address
- When a new aircraft is seen for the first time in a session, it is added to the collection of tracked aircraft
- In the first instance, the tracking object is populated with data from the initial message that caused it to be added to the tracking collection
- As new messages come in for that aircraft, the existing tracking object is updated with new/updated information from each new message
Event Model
- The AircraftTracker exposes the following events that subscribers can subscribe to to receive updates on tracked aircraft:
- Aircraft added
- Aircraft updated
- Aircraft removed
- The event notification for each event includes the current tracking object for the aircraft
Tracked Aircraft Statuses
- Tracked aircraft pass through the following set of statuses from the point where they are added:
Status | Value | Meaning |
---|---|---|
Active | 0 | The aircraft has just been added and ongoing messages are being received |
Inactive | 1 | Messages have been received recently but are not currently being received |
Stale | 2 | Messages have not been received for a while - scheduled for removal |
Locked | 3 | Aircraft's database record has been locked against further updates |
- Changes in status are communicated to AircraftTracker subscribers via the "aircraft updated" event (see above), with the status as a property of the tracking object
Message Parsing
- The AircraftTracker is supplied with a dictionary of message parsers, each associated with a Basestation message type
- As messages are received, the tracker selects the appropriate parser based on the message type
- Currently, the only parser that has been implemented is for the MSG message type
SQLite Database
Database Schema
- Each aircraft tracked in a given session has a record in the AIRCRAFT table that is created when the aircraft is first seen and updated as further messages are received from that aircraft:
- The altitude, latitude and longitude of an aircraft are recorded in the AIRCRAFT_POSITION table as changes are reported
- The AIRCRAFT_POSITION table has a foreign key back to the related record in the AIRCRAFT table:
Database Management
- The application uses Entity Framework Core and initial creation and management of the database is achieved using EF Core database migrations
- To create the database for the first time, first install the .NET Core SDK and then install the "dotnet ef" tool:
dotnet tool install --global dotnet-ef
- Update the database path in the "appsettings.json" file in the terminal application project to point to the required database location
- Build the solution
- Open a terminal window and change to the BaseStation.Data project
- Run the following command, making sure to use the path separator appropriate for your OS:
dotnet ef database update -s ../BaseStationReader.Terminal/BaseStationReader.Terminal.csproj
- If the database doesn't exist, it will create it
- It will then bring the database up to date by applying all pending migrations
Record Locking
- As stated above, the ICAO 24-bit address is used as the unique identifier for an aircraft when writing updates to the database
- Consequently, if an aircraft goes out of range then comes back into range, the original record would be picked up again on the second pass, though that pass may represent a different flight on a different date
- Further, from this article:
Mode S equipped aircraft are assigned a unique ICAO 24-bit address or (informally) Mode-S "hex code" upon national registration and this address becomes a part of the aircraft's Certificate of Registration. Normally, the address is never changed, however, the transponders are reprogrammable and, occasionally, are moved from one aircraft to another (presumably for operational or cost purposes), either by maintenance or by changing the appropriate entry in the aircraft's Flight management system
- The record for a given address should only be updated while the aircraft in question remains in range
- Once it passes out of range, or when a new tracking session is started, if the address is seen again it should result in a new tracking record
- This is achieved using the "Locked" status on tracking records (see the screenshot, above):
- When an aircraft moves out of range and is removed from the tracking collection, a notional "lock timer" starts
- If it's seen again within the timout, the record remains unlocked to avoid duplication of aircraft records for the same flight
- Once the timeout is reached, the record is locked and any further updates for that ICAO address result in a new record
- When the QueuedWriter starts, it immediately queues updates to mark all records that are not currently locked as locked, before accepting any other updates into the queue
- Records marked as "Locked" are not considered candidates for further updates
Queued Writing
- SQLite has been chosen as an appropriate DBMS for storing the data
- It allows multiple readers but, at any one time, there can only be a single writer
- As indicated above, the AircraftTracker exposes multiple events that require updates to be written to the database
- If the console application attempts to write to the database from the event handlers as soon as an event notification is received, at some point a conflict arises between multiple concurrent updates and a "database is locked" error is thrown
- Asynchronous, queued writing to the tracking database is required to avoid these conflicts and this is what the FIFO queue and the QueuedWriter implement
- This architecture has the further advantage that database updates are separated from the subscribing application
Querying the Database
- To avoid conflicts between readers and writers that may cause a "database is locked" error and halt the application, WAL journal mode should be used when querying the database if the application is running
- The following is an example query that uses a PRAGMA to enable WAL mode then lists all aircraft in the database matching the specified ICAO 24-bit address:
PRAGMA journal_mode=WAL;
SELECT *
FROM AIRCRAFT
WHERE Address = '3949F8';
Simulator
- The project includes a simulator that broadcasts messages in BaseStation format on the local machine
- A set of simulated aircraft, with random ICAO addresses and callsigns, are created
- At specified intervals, an MSG message with a random transmission type and a randomly selected aircraft is generated and sent to all connected clients
- Simulated aircraft have a configurable lifespan after which no further messages are sent from them, to simulate real behaviour and exercise the tracking lifecycle (see above)
- The simulator is intended for development use when no ADS-B receiver is available to provide a real message stream
- The simulator is controlled via an "appsettings.json" configuration file, supporting the following keys:
Section | Key | Command Line | Short Name | Purpose |
---|---|---|---|---|
ApplicationSettings | Port | --port | -p | Port the simlator broadcasts on |
ApplicationSettings | SendInterval | --send-interval | -s | Interval at which messages are sent (ms) |
ApplicationSettings | NumberOfAircraft | --number | -n | Number of active simulated aircraft |
ApplicationSettings | AircraftLifespan | --lifespan | -ls | Aircraft lifespan (ms) |
ApplicationSettings | LogFile | --log-file | -l | Path and name of the log file. If this is blank, no log file is created |
ApplicationSettings | MinimumLogLevel | --log-level | -ll | Minimum message severity to log (Debug, Info, Warning or Error) |
Authors
- Dave Walker - Initial work - LinkedIn
Feedback
To file issues or suggestions, please use the Issues page for this project on GitHub.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net7.0
- BaseStationReader.Data (>= 1.26.0)
- BaseStationReader.Entities (>= 1.26.0)
- ClosedXML (>= 0.102.1)
- Serilog (>= 3.0.1)
- Serilog.Sinks.File (>= 5.0.0)
- System.Linq.Async (>= 6.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.31.0 | 126 | 4/8/2024 |
1.30.0 | 156 | 9/28/2023 |
1.29.0 | 147 | 9/28/2023 |
1.28.0 | 162 | 9/27/2023 |
1.27.0 | 153 | 9/25/2023 |
1.26.0 | 146 | 9/24/2023 |
1.25.0 | 150 | 9/23/2023 |
1.24.0 | 145 | 9/22/2023 |
1.23.0 | 128 | 9/22/2023 |
1.22.0 | 162 | 9/19/2023 |
1.19.0 | 178 | 9/2/2023 |
1.18.0 | 169 | 9/1/2023 |
1.17.0 | 169 | 8/29/2023 |
1.16.0 | 177 | 8/28/2023 |
1.15.0 | 175 | 8/27/2023 |
1.14.0 | 177 | 8/27/2023 |
1.13.0 | 171 | 8/27/2023 |
1.12.0 | 178 | 8/27/2023 |
1.11.0 | 171 | 8/27/2023 |
1.9.0 | 175 | 8/24/2023 |
1.8.0 | 169 | 8/24/2023 |