Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions include/data/airlines.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

// Airline lookup: maps a flight callsign's 3-letter ICAO airline designator
// (e.g. "BAW" in "BAW123") to the airline's IATA acronym and full name.
// Curated list of common/major airlines; unknown or non-airline (GA, military,
// registration) callsigns resolve to nullptr.
namespace data::airlines {

struct Airline {
const char* icao; // 3-letter ICAO designator (callsign prefix)
const char* iata; // 2-letter IATA code / acronym (may be "")
const char* name; // full airline name, e.g. "British Airways"
const char* short_name; // friendly short name, e.g. "Virgin", "American"
};

/**
* Resolve a flight callsign (e.g. "BAW123") to an airline, or nullptr if the
* callsign does not look like an airline flight (needs 3 letters + a digit) or
* the ICAO code is not in the table.
*/
const Airline* forCallsign(const char* callsign);

} // namespace data::airlines
9 changes: 7 additions & 2 deletions include/services/adsb_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <cstddef>

#include "data/airlines.h"

namespace services::adsb {

struct Aircraft {
Expand All @@ -10,9 +12,12 @@ struct Aircraft {
float nose_deg;
float track_deg;
float gs_knots;
char callsign[9];
char type[5];
char callsign[9]; // flight/callsign, e.g. "BAW123"
char type[5]; // aircraft type code, e.g. "A320"
char alt[12];
// Resolved airline (nullptr if the callsign is not a known airline flight).
// Provides the IATA acronym, full name, and friendly short name.
const data::airlines::Airline* airline;
};

constexpr size_t kMaxAircraft = 64;
Expand Down
10 changes: 10 additions & 0 deletions include/ui/radar_range.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,21 @@ uint8_t rangeIndex();
/** ADSB fetch radius (km): scaled to screen edge so beyond-ring dots have data. */
float fetchRadiusKm();

/** How to label aircraft with their airline (portal "Show:" selector). */
enum class AirlineDisplay : uint8_t {
kNone = 0, // don't show airline
kFullName = 1, // e.g. "British Airways"
kAbbrev = 2, // friendly short name, e.g. "Virgin"
};

bool useMiles();
bool showRunways();
AirlineDisplay airlineDisplay();
/** WiFi portal checkbox: "T" = miles, otherwise km. */
void saveMilesFromPortal(const char* checkbox_value);
void saveRunwaysFromPortal(const char* checkbox_value);
/** WiFi portal select: "0" none, "1" full name, "2" abbreviation. */
void saveAirlineDisplayFromPortal(const char* select_value);
void formatRing3Label(char* buf, size_t len, float ring3_km, bool use_miles);
void formatCurrentRing3Label(char* buf, size_t len);
/** Reset distance units to km (e.g. with WiFi credential wipe). */
Expand Down
158 changes: 158 additions & 0 deletions src/data/airlines_data.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#include "data/airlines.h"

#include <cctype>
#include <cstring>

namespace data::airlines {

namespace {

// Curated list of common/major airlines, keyed by ICAO designator (callsign
// prefix). Extend as needed. Order is not significant (linear lookup).
constexpr Airline kAirlines[] = {
// {ICAO, IATA, full name, friendly short name}
// North America
{"AAL", "AA", "American Airlines", "American"},
{"UAL", "UA", "United Airlines", "United"},
{"DAL", "DL", "Delta Air Lines", "Delta"},
{"SWA", "WN", "Southwest Airlines", "Southwest"},
{"JBU", "B6", "JetBlue Airways", "JetBlue"},
{"ASA", "AS", "Alaska Airlines", "Alaska"},
{"FFT", "F9", "Frontier Airlines", "Frontier"},
{"NKS", "NK", "Spirit Airlines", "Spirit"},
{"HAL", "HA", "Hawaiian Airlines", "Hawaiian"},
{"ACA", "AC", "Air Canada", "Air Canada"},
{"WJA", "WS", "WestJet", "WestJet"},
{"AMX", "AM", "Aeromexico", "Aeromexico"},
{"VOI", "Y4", "Volaris", "Volaris"},
{"FDX", "FX", "FedEx Express", "FedEx"},
{"UPS", "5X", "UPS Airlines", "UPS"},
{"GTI", "5Y", "Atlas Air", "Atlas"},
// United Kingdom & Ireland
{"BAW", "BA", "British Airways", "British Airways"},
{"VIR", "VS", "Virgin Atlantic", "Virgin"},
{"EZY", "U2", "easyJet", "easyJet"},
{"EXS", "LS", "Jet2", "Jet2"},
{"RYR", "FR", "Ryanair", "Ryanair"},
{"TOM", "BY", "TUI Airways", "TUI"},
// Western Europe
{"DLH", "LH", "Lufthansa", "Lufthansa"},
{"AFR", "AF", "Air France", "Air France"},
{"KLM", "KL", "KLM", "KLM"},
{"IBE", "IB", "Iberia", "Iberia"},
{"SWR", "LX", "Swiss", "Swiss"},
{"AUA", "OS", "Austrian Airlines", "Austrian"},
{"BEL", "SN", "Brussels Airlines", "Brussels"},
{"TAP", "TP", "TAP Air Portugal", "TAP"},
{"ITY", "AZ", "ITA Airways", "ITA"},
{"VLG", "VY", "Vueling", "Vueling"},
{"EWG", "EW", "Eurowings", "Eurowings"},
{"CFG", "DE", "Condor", "Condor"},
{"TVF", "TO", "Transavia France", "Transavia"},
{"TRA", "HV", "Transavia", "Transavia"},
// Nordics & Baltics
{"SAS", "SK", "Scandinavian Airlines", "SAS"},
{"FIN", "AY", "Finnair", "Finnair"},
{"NAX", "DY", "Norwegian", "Norwegian"},
{"NSZ", "DY", "Norwegian", "Norwegian"},
{"ICE", "FI", "Icelandair", "Icelandair"},
{"BTI", "BT", "airBaltic", "airBaltic"},
// Central & Eastern Europe
{"WZZ", "W6", "Wizz Air", "Wizz"},
{"LOT", "LO", "LOT Polish Airlines", "LOT"},
{"CSA", "OK", "Czech Airlines", "Czech"},
{"AEE", "A3", "Aegean Airlines", "Aegean"},
{"THY", "TK", "Turkish Airlines", "Turkish"},
{"PGT", "PC", "Pegasus Airlines", "Pegasus"},
{"AFL", "SU", "Aeroflot", "Aeroflot"},
{"SBI", "S7", "S7 Airlines", "S7"},
{"ROT", "RO", "Tarom", "Tarom"},
// Middle East
{"UAE", "EK", "Emirates", "Emirates"},
{"QTR", "QR", "Qatar Airways", "Qatar"},
{"ETD", "EY", "Etihad Airways", "Etihad"},
{"FDB", "FZ", "flydubai", "flydubai"},
{"ABY", "G9", "Air Arabia", "Air Arabia"},
{"SVA", "SV", "Saudia", "Saudia"},
{"ELY", "LY", "El Al", "El Al"},
{"MEA", "ME", "Middle East Airlines", "MEA"},
{"RJA", "RJ", "Royal Jordanian", "Royal Jordanian"},
{"GFA", "GF", "Gulf Air", "Gulf Air"},
{"OMA", "WY", "Oman Air", "Oman Air"},
{"KAC", "KU", "Kuwait Airways", "Kuwait"},
// Africa
{"SAA", "SA", "South African Airways", "South African"},
{"ETH", "ET", "Ethiopian Airlines", "Ethiopian"},
{"MSR", "MS", "EgyptAir", "EgyptAir"},
{"RAM", "AT", "Royal Air Maroc", "Royal Air Maroc"},
{"KQA", "KQ", "Kenya Airways", "Kenya"},
{"DAH", "AH", "Air Algerie", "Air Algerie"},
{"TAR", "TU", "Tunisair", "Tunisair"},
// Asia-Pacific
{"SIA", "SQ", "Singapore Airlines", "Singapore"},
{"CPA", "CX", "Cathay Pacific", "Cathay"},
{"ANA", "NH", "All Nippon Airways", "ANA"},
{"JAL", "JL", "Japan Airlines", "Japan"},
{"KAL", "KE", "Korean Air", "Korean"},
{"AAR", "OZ", "Asiana Airlines", "Asiana"},
{"CCA", "CA", "Air China", "Air China"},
{"CES", "MU", "China Eastern Airlines", "China Eastern"},
{"CSN", "CZ", "China Southern Airlines", "China Southern"},
{"CHH", "HU", "Hainan Airlines", "Hainan"},
{"CAL", "CI", "China Airlines", "China Airlines"},
{"EVA", "BR", "EVA Air", "EVA"},
{"THA", "TG", "Thai Airways", "Thai"},
{"MAS", "MH", "Malaysia Airlines", "Malaysia"},
{"AXM", "AK", "AirAsia", "AirAsia"},
{"GIA", "GA", "Garuda Indonesia", "Garuda"},
{"AIC", "AI", "Air India", "Air India"},
{"IGO", "6E", "IndiGo", "IndiGo"},
{"PAL", "PR", "Philippine Airlines", "Philippine"},
{"CEB", "5J", "Cebu Pacific", "Cebu Pacific"},
{"VJC", "VJ", "VietJet Air", "VietJet"},
{"HVN", "VN", "Vietnam Airlines", "Vietnam"},
{"QFA", "QF", "Qantas", "Qantas"},
{"VOZ", "VA", "Virgin Australia", "Virgin Australia"},
{"JST", "JQ", "Jetstar", "Jetstar"},
{"ANZ", "NZ", "Air New Zealand", "Air New Zealand"},
// Latin America
{"LAN", "LA", "LATAM Airlines", "LATAM"},
{"TAM", "JJ", "LATAM Brasil", "LATAM"},
{"GLO", "G3", "Gol", "Gol"},
{"AZU", "AD", "Azul", "Azul"},
{"ARG", "AR", "Aerolineas Argentinas", "Aerolineas"},
{"AVA", "AV", "Avianca", "Avianca"},
{"CMP", "CM", "Copa Airlines", "Copa"},
};

constexpr size_t kAirlineCount = sizeof(kAirlines) / sizeof(kAirlines[0]);

} // namespace

const Airline* forCallsign(const char* callsign) {
if (callsign == nullptr) {
return nullptr;
}
// Airline callsign = 3-letter ICAO designator + flight number (starts digit).
if (!std::isalpha(static_cast<unsigned char>(callsign[0])) ||
!std::isalpha(static_cast<unsigned char>(callsign[1])) ||
!std::isalpha(static_cast<unsigned char>(callsign[2])) ||
!std::isdigit(static_cast<unsigned char>(callsign[3]))) {
return nullptr;
}

char code[4] = {
static_cast<char>(std::toupper(static_cast<unsigned char>(callsign[0]))),
static_cast<char>(std::toupper(static_cast<unsigned char>(callsign[1]))),
static_cast<char>(std::toupper(static_cast<unsigned char>(callsign[2]))),
'\0'};

for (size_t i = 0; i < kAirlineCount; ++i) {
if (std::strcmp(kAirlines[i].icao, code) == 0) {
return &kAirlines[i];
}
}
return nullptr;
}

} // namespace data::airlines
3 changes: 3 additions & 0 deletions src/services/adsb_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <cstring>

#include "config.h"
#include "data/airlines.h"

namespace services::adsb {

Expand Down Expand Up @@ -189,6 +190,8 @@ void formatAltitudeTag(const JsonObject& plane, char* out, size_t out_len) {

void fillTagFields(Aircraft* ac, const JsonObject& plane) {
copyJsonStringTrimmed(plane, "flight", ac->callsign, sizeof(ac->callsign));
// Resolve the airline from the flight callsign before any hex fallback.
ac->airline = data::airlines::forCallsign(ac->callsign);
if (ac->callsign[0] == '\0') {
copyJsonStringTrimmed(plane, "hex", ac->callsign, sizeof(ac->callsign));
}
Expand Down
30 changes: 30 additions & 0 deletions src/services/wifi_setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,30 @@ char s_runways_checkbox_attrs[32] = "type=\"checkbox\"";
WiFiManagerParameter s_param_runways("show_runways", "Show airport runways", "T", 2,
s_runways_checkbox_attrs, WFM_LABEL_AFTER);

// Airline label selector. Custom-HTML-only param renders a <select
// name="airline_mode">; the value is read back from the web server on save.
char s_airline_select_html[320] = "";
WiFiManagerParameter s_param_airline(s_airline_select_html);

void buildAirlineSelectHtml() {
const uint8_t cur = static_cast<uint8_t>(ui::radar::airlineDisplay());
const char* options[] = {"None", "Full Airline Name", "Airline Abbreviation"};
int n = snprintf(s_airline_select_html, sizeof(s_airline_select_html),
"<br/><label for='airline_mode'>Show:</label>"
"<select name='airline_mode' id='airline_mode'>");
for (uint8_t i = 0; i < 3 && n > 0 &&
n < static_cast<int>(sizeof(s_airline_select_html));
++i) {
n += snprintf(s_airline_select_html + n, sizeof(s_airline_select_html) - n,
"<option value='%u'%s>%s</option>", i,
i == cur ? " selected" : "", options[i]);
}
if (n > 0 && n < static_cast<int>(sizeof(s_airline_select_html))) {
snprintf(s_airline_select_html + n, sizeof(s_airline_select_html) - n,
"</select>");
}
}

void refreshPortalParamDefaults() {
char lat_buf[kCoordParamLen + 1];
char lon_buf[kCoordParamLen + 1];
Expand All @@ -97,6 +121,7 @@ void refreshPortalParamDefaults() {
snprintf(s_runways_checkbox_attrs, sizeof(s_runways_checkbox_attrs),
"type=\"checkbox\"%s", ui::radar::showRunways() ? " checked" : "");
s_param_runways.setValue("T", 2);
buildAirlineSelectHtml();
}

void onPortalParamsSaved() {
Expand All @@ -106,6 +131,10 @@ void onPortalParamsSaved() {
}
ui::radar::saveMilesFromPortal(s_param_miles.getValue());
ui::radar::saveRunwaysFromPortal(s_param_runways.getValue());
if (s_wm.server) {
ui::radar::saveAirlineDisplayFromPortal(
s_wm.server->arg("airline_mode").c_str());
}
}

void attachPortalParams(WiFiManager& wm) {
Expand All @@ -114,6 +143,7 @@ void attachPortalParams(WiFiManager& wm) {
wm.addParameter(&s_param_lon);
wm.addParameter(&s_param_miles);
wm.addParameter(&s_param_runways);
wm.addParameter(&s_param_airline);
wm.setSaveParamsCallback(onPortalParamsSaved);
}

Expand Down
Loading