commit
dd53b36bdf
257
comm_b.c
257
comm_b.c
|
|
@ -29,8 +29,10 @@ static int decodeBDS17(struct modesMessage *mm, bool store);
|
||||||
static int decodeBDS20(struct modesMessage *mm, bool store);
|
static int decodeBDS20(struct modesMessage *mm, bool store);
|
||||||
static int decodeBDS30(struct modesMessage *mm, bool store);
|
static int decodeBDS30(struct modesMessage *mm, bool store);
|
||||||
static int decodeBDS40(struct modesMessage *mm, bool store);
|
static int decodeBDS40(struct modesMessage *mm, bool store);
|
||||||
|
static int decodeBDS44(struct modesMessage *mm, bool store);
|
||||||
static int decodeBDS50(struct modesMessage *mm, bool store);
|
static int decodeBDS50(struct modesMessage *mm, bool store);
|
||||||
static int decodeBDS60(struct modesMessage *mm, bool store);
|
static int decodeBDS60(struct modesMessage *mm, bool store);
|
||||||
|
static int decodeBDS05(struct modesMessage *mm, bool store);
|
||||||
|
|
||||||
static CommBDecoderFn comm_b_decoders[] = {
|
static CommBDecoderFn comm_b_decoders[] = {
|
||||||
&decodeEmptyResponse,
|
&decodeEmptyResponse,
|
||||||
|
|
@ -40,17 +42,18 @@ static CommBDecoderFn comm_b_decoders[] = {
|
||||||
&decodeBDS17,
|
&decodeBDS17,
|
||||||
&decodeBDS40,
|
&decodeBDS40,
|
||||||
&decodeBDS50,
|
&decodeBDS50,
|
||||||
&decodeBDS60
|
&decodeBDS60,
|
||||||
|
&decodeBDS44,
|
||||||
|
&decodeBDS05
|
||||||
};
|
};
|
||||||
|
|
||||||
void decodeCommB(struct modesMessage *mm)
|
void decodeCommB(struct modesMessage *mm)
|
||||||
{
|
{
|
||||||
mm->commb_format = COMMB_UNKNOWN;
|
|
||||||
|
|
||||||
// If DR or UM are set, this message is _probably_ noise
|
// If DR or UM are set, this message is _probably_ noise
|
||||||
// as nothing really seems to use the multisite broadcast stuff?
|
// as nothing really seems to use the multisite broadcast stuff?
|
||||||
// Also skip anything that had errors corrected
|
// Also skip anything that had errors corrected
|
||||||
if (mm->DR != 0 || mm->UM != 0 || mm->correctedbits > 0) {
|
if (mm->DR != 0 || mm->UM != 0 || mm->correctedbits > 0) {
|
||||||
|
mm->commb_format = COMMB_NOT_DECODED;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,12 +80,39 @@ void decodeCommB(struct modesMessage *mm)
|
||||||
// decode it
|
// decode it
|
||||||
bestDecoder(mm, true);
|
bestDecoder(mm, true);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
mm->commb_format = COMMB_UNKNOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int decodeEmptyResponse(struct modesMessage *mm, bool store)
|
static int decodeEmptyResponse(struct modesMessage *mm, bool store)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < 7; ++i) {
|
// 00000000000000 is a common response. Ignore it.
|
||||||
|
//
|
||||||
|
// Also, it's common to see responses that look like this:
|
||||||
|
// 40000000000000
|
||||||
|
// 50000000000000
|
||||||
|
// 60000000000000
|
||||||
|
// typically in grouped bursts (one of each message) from
|
||||||
|
// the same aircraft.
|
||||||
|
//
|
||||||
|
// I speculate that these are response to interrogations for
|
||||||
|
// BDS 4,0 5,0 and 6,0 respectively where the transponder
|
||||||
|
// doesn't support the register or has no data loaded for it.
|
||||||
|
// Treat them like empty responses.
|
||||||
|
|
||||||
|
switch (mm->MB[0]) {
|
||||||
|
case 0x00:
|
||||||
|
case 0x40:
|
||||||
|
case 0x50:
|
||||||
|
case 0x60:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned i = 1; i < 7; ++i) {
|
||||||
if (mm->MB[i] != 0) {
|
if (mm->MB[i] != 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -148,10 +178,10 @@ static int decodeBDS17(struct modesMessage *mm, bool store)
|
||||||
score -= 2;
|
score -= 2;
|
||||||
}
|
}
|
||||||
if (getbit(msg, 13)) { // 4,4 meterological routine report
|
if (getbit(msg, 13)) { // 4,4 meterological routine report
|
||||||
score -= 2;
|
score -= 1;
|
||||||
}
|
}
|
||||||
if (getbit(msg, 14)) { // 4,4 meterological hazard report
|
if (getbit(msg, 14)) { // 4,4 meterological hazard report
|
||||||
score -= 2;
|
score -= 1;
|
||||||
}
|
}
|
||||||
if (getbit(msg, 20)) { // 5,4 waypoint 1
|
if (getbit(msg, 20)) { // 5,4 waypoint 1
|
||||||
score -= 2;
|
score -= 2;
|
||||||
|
|
@ -173,8 +203,11 @@ static int decodeBDS17(struct modesMessage *mm, bool store)
|
||||||
} else if (!getbit(msg, 1) && !getbit(msg, 2) && !getbit(msg, 3) && !getbit(msg, 4) && !getbit(msg, 5) && !getbit(msg, 6)) {
|
} else if (!getbit(msg, 1) && !getbit(msg, 2) && !getbit(msg, 3) && !getbit(msg, 4) && !getbit(msg, 5) && !getbit(msg, 6)) {
|
||||||
// not ES capable
|
// not ES capable
|
||||||
score += 1;
|
score += 1;
|
||||||
|
} else if (!getbit(msg, 1) && !getbit(msg, 2) && getbit(msg, 3) && getbit(msg, 4) && getbit(msg, 5)) {
|
||||||
|
// ES with no position data
|
||||||
|
score += 3;
|
||||||
} else {
|
} else {
|
||||||
// partial ES support, unlikely
|
// other combinations, unlikely
|
||||||
score -= 12;
|
score -= 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -740,3 +773,213 @@ static int decodeBDS60(struct modesMessage *mm, bool store)
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BDS4,4 Meterological routine air report
|
||||||
|
static int decodeBDS44(struct modesMessage *mm, bool store)
|
||||||
|
{
|
||||||
|
unsigned char *msg = mm->MB;
|
||||||
|
|
||||||
|
unsigned source = getbits(msg, 1, 4);
|
||||||
|
|
||||||
|
unsigned wind_valid = getbit(msg, 5);
|
||||||
|
unsigned windspeed_raw = getbits(msg, 6, 14);
|
||||||
|
unsigned winddir_raw = getbits(msg, 15, 23);
|
||||||
|
|
||||||
|
// ICAO 9871 is inconsistent, it claims:
|
||||||
|
// bit 24 sign
|
||||||
|
// bits 25..34 static air temperature, MSB = 64C, LSB=0.25C, range -128C..+128C
|
||||||
|
//
|
||||||
|
// .. but this does not actually work, there is one too many bits in the bitfield
|
||||||
|
// for the claimed values.
|
||||||
|
//
|
||||||
|
// Based on observed data, the most plausible actual layout is:
|
||||||
|
//
|
||||||
|
// bit 24 status
|
||||||
|
// bit 25 sign
|
||||||
|
// bits 26..34 static air temperature, MSB=64C, LSB=0.25C, range -128C..+128C
|
||||||
|
|
||||||
|
unsigned sat_valid = getbit(msg, 24);
|
||||||
|
unsigned sat_sign = getbit(msg, 25);
|
||||||
|
unsigned sat_raw = getbits(msg, 26, 34);
|
||||||
|
|
||||||
|
unsigned asp_valid = getbit(msg, 35);
|
||||||
|
unsigned asp_raw = getbits(msg, 36, 46);
|
||||||
|
|
||||||
|
unsigned turbulence_valid = getbit(msg, 47);
|
||||||
|
unsigned turbulence_raw = getbits(msg, 48, 49);
|
||||||
|
|
||||||
|
unsigned humidity_valid = getbit(msg, 50);
|
||||||
|
unsigned humidity_raw = getbits(msg, 51, 56);
|
||||||
|
|
||||||
|
if (source == MRAR_SOURCE_INVALID || source >= MRAR_SOURCE_RESERVED)
|
||||||
|
return 0; // invalid or reserved source
|
||||||
|
|
||||||
|
if (!wind_valid || !sat_valid)
|
||||||
|
return 0; // all valid messages seen in the wild have at least temp + wind
|
||||||
|
|
||||||
|
if (!asp_valid && asp_raw != 0)
|
||||||
|
return 0; // ASP not valid, but non-zero values in the ASP field
|
||||||
|
|
||||||
|
if (!turbulence_valid && turbulence_raw != 0)
|
||||||
|
return 0; // turbulence not valid, but non-zero values in the turbulence field
|
||||||
|
|
||||||
|
if (!humidity_valid && humidity_raw != 0)
|
||||||
|
return 0; // humidity not valid, but non-zero values in the humidity field
|
||||||
|
|
||||||
|
int score = 0;
|
||||||
|
|
||||||
|
float wind_speed = 0;
|
||||||
|
float wind_dir = 0;
|
||||||
|
if (wind_valid) {
|
||||||
|
wind_dir = winddir_raw * (180.0 / 256.0);
|
||||||
|
wind_speed = windspeed_raw;
|
||||||
|
|
||||||
|
if (windspeed_raw == 0) // possible but uncommon
|
||||||
|
score += 2;
|
||||||
|
else if (wind_speed <= 250)
|
||||||
|
score += 19;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
score += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sat = 0;
|
||||||
|
if (sat_valid) {
|
||||||
|
sat = sat_raw * 0.25;
|
||||||
|
if (sat_sign)
|
||||||
|
sat -= 128;
|
||||||
|
if (sat == 0) // possible but uncommon
|
||||||
|
score += 2;
|
||||||
|
else if (sat >= -80 && sat <= 60)
|
||||||
|
score += 11;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
score += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float asp = 0;
|
||||||
|
if (asp_valid) {
|
||||||
|
asp = asp_raw;
|
||||||
|
if (asp >= 25 && asp <= 1100)
|
||||||
|
score += 12;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
score += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hazard_t turbulence = HAZARD_NIL;
|
||||||
|
if (turbulence_valid) {
|
||||||
|
turbulence = (hazard_t) turbulence_raw;
|
||||||
|
score += 3;
|
||||||
|
} else {
|
||||||
|
score += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float humidity = 0;
|
||||||
|
if (humidity_valid) {
|
||||||
|
humidity = humidity_raw * (100.0 / 64.0);
|
||||||
|
score += 7;
|
||||||
|
} else {
|
||||||
|
score += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source == MRAR_SOURCE_DMEDME && wind_valid && sat_valid && score > 0) {
|
||||||
|
// Some GICB messages can be easily mistaken for a MRAR:
|
||||||
|
//
|
||||||
|
// GICB bit 1: BDS 0,5 ES airborne position = 0 ]
|
||||||
|
// GICB bit 2: BDS 0,6 ES surface position = 0 ]
|
||||||
|
// GICB bit 3: BDS 0,7 ES status = 1 ]
|
||||||
|
// GICB bit 4: BDS 0,8 ES type & identification = 1 ] -> MRAR source = 3
|
||||||
|
// GICB bit 5: BDS 0,9 ES airborne velocity = 1 -> MRAR wind valid bit
|
||||||
|
// GICB bit 24: BDS 6,0 heading and speed report = 1 -> MRAR temp valid bit
|
||||||
|
// most trailing bits = 0
|
||||||
|
//
|
||||||
|
// so only treat this as MRAR as a last resort
|
||||||
|
score = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
mm->commb_format = COMMB_MRAR;
|
||||||
|
mm->mrar_source_valid = 1;
|
||||||
|
mm->mrar_source = (mrar_source_t) source;
|
||||||
|
|
||||||
|
if (wind_valid) {
|
||||||
|
mm->wind_valid = 1;
|
||||||
|
mm->wind_speed = wind_speed;
|
||||||
|
mm->wind_dir = wind_dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sat_valid) {
|
||||||
|
mm->temperature_valid = 1;
|
||||||
|
mm->temperature = sat;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asp_valid) {
|
||||||
|
mm->pressure_valid = 1;
|
||||||
|
mm->pressure = asp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turbulence_valid) {
|
||||||
|
mm->turbulence_valid = 1;
|
||||||
|
mm->turbulence = turbulence;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (humidity_valid) {
|
||||||
|
mm->humidity_valid = 1;
|
||||||
|
mm->humidity = humidity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BDS0,5 extended squitter airborne position
|
||||||
|
// (apparently this gets queried via comm-b sometimes??)
|
||||||
|
// We don't try to _use_ this as a position, but we can
|
||||||
|
// at least try to recognize it, to exclude other
|
||||||
|
// comm-b types (in particular they can be mistaken for MRAR)
|
||||||
|
static int decodeBDS05(struct modesMessage *mm, bool store)
|
||||||
|
{
|
||||||
|
// We recognize these by matching the position altitude against
|
||||||
|
// the altitude in the surrounding message, so we need a
|
||||||
|
// DF20 not a DF21
|
||||||
|
if (mm->msgtype != 20)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
unsigned char *msg = mm->MB;
|
||||||
|
|
||||||
|
unsigned typecode = getbits(msg, 1, 5);
|
||||||
|
if (typecode < 9 || typecode > 18)
|
||||||
|
return 0; // only consider typecodes that could be an airborne position with baro altitude
|
||||||
|
|
||||||
|
unsigned t_bit = getbit(msg, 21);
|
||||||
|
if (t_bit) // unlikely
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
unsigned ac12 = getbits(msg, 9, 20);
|
||||||
|
if (!ac12)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Insert M=0 to make an AC13 value, match against the
|
||||||
|
// AC13 value in the surrounding message
|
||||||
|
unsigned ac13 = ((ac12 & 0x0FC0) << 1) | (ac12 & 0x003F);
|
||||||
|
if (mm->AC != ac13)
|
||||||
|
return 0; // no altitude match
|
||||||
|
|
||||||
|
unsigned lat = getbits(msg, 23, 39);
|
||||||
|
unsigned lon = getbits(msg, 40, 56);
|
||||||
|
if (lat == 0 || lon == 0) // unlikely position
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
mm->commb_format = COMMB_AIRBORNE_POSITION;
|
||||||
|
// No further decoding done, we don't really trust this
|
||||||
|
// enough to use as real input to CPR
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score this high enough to override everything else
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
|
||||||
40
dump1090.h
40
dump1090.h
|
|
@ -202,6 +202,7 @@ typedef enum {
|
||||||
typedef enum {
|
typedef enum {
|
||||||
COMMB_UNKNOWN,
|
COMMB_UNKNOWN,
|
||||||
COMMB_AMBIGUOUS,
|
COMMB_AMBIGUOUS,
|
||||||
|
COMMB_NOT_DECODED,
|
||||||
COMMB_EMPTY_RESPONSE,
|
COMMB_EMPTY_RESPONSE,
|
||||||
COMMB_DATALINK_CAPS,
|
COMMB_DATALINK_CAPS,
|
||||||
COMMB_GICB_CAPS,
|
COMMB_GICB_CAPS,
|
||||||
|
|
@ -209,7 +210,9 @@ typedef enum {
|
||||||
COMMB_ACAS_RA,
|
COMMB_ACAS_RA,
|
||||||
COMMB_VERTICAL_INTENT,
|
COMMB_VERTICAL_INTENT,
|
||||||
COMMB_TRACK_TURN,
|
COMMB_TRACK_TURN,
|
||||||
COMMB_HEADING_SPEED
|
COMMB_HEADING_SPEED,
|
||||||
|
COMMB_MRAR,
|
||||||
|
COMMB_AIRBORNE_POSITION
|
||||||
} commb_format_t;
|
} commb_format_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|
@ -237,6 +240,24 @@ typedef enum {
|
||||||
NAV_ALT_INVALID, NAV_ALT_UNKNOWN, NAV_ALT_AIRCRAFT, NAV_ALT_MCP, NAV_ALT_FMS
|
NAV_ALT_INVALID, NAV_ALT_UNKNOWN, NAV_ALT_AIRCRAFT, NAV_ALT_MCP, NAV_ALT_FMS
|
||||||
} nav_altitude_source_t;
|
} nav_altitude_source_t;
|
||||||
|
|
||||||
|
// BDS4,4 MRAR - FOM/Source values
|
||||||
|
typedef enum {
|
||||||
|
MRAR_SOURCE_INVALID = 0,
|
||||||
|
MRAR_SOURCE_INS = 1,
|
||||||
|
MRAR_SOURCE_GNSS = 2,
|
||||||
|
MRAR_SOURCE_DMEDME = 3,
|
||||||
|
MRAR_SOURCE_VORDME = 4,
|
||||||
|
MRAR_SOURCE_RESERVED = 5
|
||||||
|
} mrar_source_t;
|
||||||
|
|
||||||
|
// BDS4,4 and BDS4,5 hazard reports
|
||||||
|
typedef enum {
|
||||||
|
HAZARD_NIL = 0,
|
||||||
|
HAZARD_LIGHT = 1,
|
||||||
|
HAZARD_MODERATE = 2,
|
||||||
|
HAZARD_SEVERE = 3
|
||||||
|
} hazard_t;
|
||||||
|
|
||||||
#define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses
|
#define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses
|
||||||
|
|
||||||
#define MODES_INTERACTIVE_REFRESH_TIME 250 // Milliseconds
|
#define MODES_INTERACTIVE_REFRESH_TIME 250 // Milliseconds
|
||||||
|
|
@ -364,6 +385,7 @@ struct _Modes { // Internal state
|
||||||
uint64_t json_stats_interval; // Interval between rewriting the json stats file, in milliseconds
|
uint64_t json_stats_interval; // Interval between rewriting the json stats file, in milliseconds
|
||||||
int json_location_accuracy; // Accuracy of location metadata: 0=none, 1=approx, 2=exact
|
int json_location_accuracy; // Accuracy of location metadata: 0=none, 1=approx, 2=exact
|
||||||
double faup_rate_multiplier; // Multiplier to adjust rate of faup1090 messages emitted
|
double faup_rate_multiplier; // Multiplier to adjust rate of faup1090 messages emitted
|
||||||
|
bool faup_upload_unknown_commb; // faup1090: should we upload Comm-B messages that weren't in a recognized format?
|
||||||
|
|
||||||
int json_aircraft_history_next;
|
int json_aircraft_history_next;
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -611,6 +633,22 @@ struct modesMessage {
|
||||||
|
|
||||||
nav_modes_t modes;
|
nav_modes_t modes;
|
||||||
} nav;
|
} nav;
|
||||||
|
|
||||||
|
// BDS 4,4 MRAR
|
||||||
|
unsigned mrar_source_valid : 1;
|
||||||
|
unsigned wind_valid : 1;
|
||||||
|
unsigned temperature_valid : 1;
|
||||||
|
unsigned pressure_valid : 1;
|
||||||
|
unsigned turbulence_valid : 1;
|
||||||
|
unsigned humidity_valid : 1;
|
||||||
|
|
||||||
|
mrar_source_t mrar_source;
|
||||||
|
float wind_speed; // kts
|
||||||
|
float wind_dir; // degrees
|
||||||
|
float temperature; // degrees C
|
||||||
|
float pressure; // hPa
|
||||||
|
hazard_t turbulence; // NIL/LIGHT/MODERATE/SEVERE
|
||||||
|
float humidity; // 0-100 %
|
||||||
};
|
};
|
||||||
|
|
||||||
// This one needs modesMessage:
|
// This one needs modesMessage:
|
||||||
|
|
|
||||||
44
mode_s.c
44
mode_s.c
|
|
@ -1681,6 +1681,8 @@ static const char *commb_format_to_string(commb_format_t format) {
|
||||||
return "empty response";
|
return "empty response";
|
||||||
case COMMB_AMBIGUOUS:
|
case COMMB_AMBIGUOUS:
|
||||||
return "ambiguous format";
|
return "ambiguous format";
|
||||||
|
case COMMB_NOT_DECODED:
|
||||||
|
return "not decoded";
|
||||||
case COMMB_DATALINK_CAPS:
|
case COMMB_DATALINK_CAPS:
|
||||||
return "BDS1,0 Datalink capabilities";
|
return "BDS1,0 Datalink capabilities";
|
||||||
case COMMB_GICB_CAPS:
|
case COMMB_GICB_CAPS:
|
||||||
|
|
@ -1695,6 +1697,10 @@ static const char *commb_format_to_string(commb_format_t format) {
|
||||||
return "BDS5,0 Track and turn report";
|
return "BDS5,0 Track and turn report";
|
||||||
case COMMB_HEADING_SPEED:
|
case COMMB_HEADING_SPEED:
|
||||||
return "BDS6,0 Heading and speed report";
|
return "BDS6,0 Heading and speed report";
|
||||||
|
case COMMB_MRAR:
|
||||||
|
return "BDS4,4 Meterological routine air report";
|
||||||
|
case COMMB_AIRBORNE_POSITION:
|
||||||
|
return "BDS0,5 Extended squitter airborne position";
|
||||||
default:
|
default:
|
||||||
return "unknown format";
|
return "unknown format";
|
||||||
}
|
}
|
||||||
|
|
@ -1748,6 +1754,29 @@ static const char *emergency_to_string(emergency_t emergency)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *mrar_source_to_string(mrar_source_t source)
|
||||||
|
{
|
||||||
|
switch (source) {
|
||||||
|
case MRAR_SOURCE_INVALID: return "invalid";
|
||||||
|
case MRAR_SOURCE_INS: return "INS";
|
||||||
|
case MRAR_SOURCE_GNSS: return "GNSS";
|
||||||
|
case MRAR_SOURCE_DMEDME: return "DME/DME";
|
||||||
|
case MRAR_SOURCE_VORDME: return "VOR/DME";
|
||||||
|
default: return "reserved";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *hazard_to_string(hazard_t hazard)
|
||||||
|
{
|
||||||
|
switch (hazard) {
|
||||||
|
case HAZARD_NIL: return "nil";
|
||||||
|
case HAZARD_LIGHT: return "light";
|
||||||
|
case HAZARD_MODERATE: return "moderate";
|
||||||
|
case HAZARD_SEVERE: return "severe";
|
||||||
|
default: return "invalid hazard severity";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void print_hex_bytes(unsigned char *data, size_t len) {
|
static void print_hex_bytes(unsigned char *data, size_t len) {
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i < len; ++i) {
|
for (i = 0; i < len; ++i) {
|
||||||
|
|
@ -2213,6 +2242,21 @@ void displayModesMessage(struct modesMessage *mm) {
|
||||||
printf(" Emergency/priority: %s\n", emergency_to_string(mm->emergency));
|
printf(" Emergency/priority: %s\n", emergency_to_string(mm->emergency));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mm->mrar_source_valid)
|
||||||
|
printf(" MRAR FOM/Source: %s\n", mrar_source_to_string(mm->mrar_source));
|
||||||
|
if (mm->wind_valid) {
|
||||||
|
printf(" Wind speed: %.0f kt\n", mm->wind_speed);
|
||||||
|
printf(" Wind direction: %.1f degrees\n", mm->wind_dir);
|
||||||
|
}
|
||||||
|
if (mm->temperature_valid)
|
||||||
|
printf(" Air temperature: %.1f degrees C\n", mm->temperature);
|
||||||
|
if (mm->pressure_valid)
|
||||||
|
printf(" Static pressure: %.0f hPa\n", mm->pressure);
|
||||||
|
if (mm->turbulence_valid)
|
||||||
|
printf(" Turbulence: %s\n", hazard_to_string(mm->turbulence));
|
||||||
|
if (mm->humidity_valid)
|
||||||
|
printf(" Humidity: %.0f%%\n", mm->humidity);
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
88
net_io.c
88
net_io.c
|
|
@ -1128,7 +1128,7 @@ static int handleFaupCommand(struct client *c, char *p) {
|
||||||
|
|
||||||
// Traverse through message for commands
|
// Traverse through message for commands
|
||||||
while (msg_field != NULL) {
|
while (msg_field != NULL) {
|
||||||
if (strcmp(msg_field, "upload_rate_multiplier") == 0) {
|
if (!strcmp(msg_field, "upload_rate_multiplier")) {
|
||||||
msg_field = strtok (NULL, "\t");
|
msg_field = strtok (NULL, "\t");
|
||||||
multiplier = atof(msg_field);
|
multiplier = atof(msg_field);
|
||||||
|
|
||||||
|
|
@ -1142,6 +1142,14 @@ static int handleFaupCommand(struct client *c, char *p) {
|
||||||
Modes.faup_rate_multiplier = multiplier;
|
Modes.faup_rate_multiplier = multiplier;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!strcmp(msg_field, "upload_unknown_commb")) {
|
||||||
|
msg_field = strtok (NULL, "\t");
|
||||||
|
unsigned enable = atoi(msg_field);
|
||||||
|
fprintf(stderr, "handleFaupCommand(): %s upload of unknown Comm-B messages\n", enable ? "Enabling" : "Disabling");
|
||||||
|
Modes.faup_upload_unknown_commb = enable;
|
||||||
|
break;
|
||||||
|
}
|
||||||
msg_field = strtok (NULL, "\t");
|
msg_field = strtok (NULL, "\t");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1606,6 +1614,29 @@ static const char *nav_altitude_source_enum_string(nav_altitude_source_t src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *mrar_source_enum_string(mrar_source_t src)
|
||||||
|
{
|
||||||
|
switch (src) {
|
||||||
|
case MRAR_SOURCE_INVALID: return "invalid";
|
||||||
|
case MRAR_SOURCE_INS: return "ins";
|
||||||
|
case MRAR_SOURCE_GNSS: return "gnss";
|
||||||
|
case MRAR_SOURCE_DMEDME: return "dmedme";
|
||||||
|
case MRAR_SOURCE_VORDME: return "vordme";
|
||||||
|
default: return "reserved";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *hazard_enum_string(hazard_t hazard)
|
||||||
|
{
|
||||||
|
switch (hazard) {
|
||||||
|
case HAZARD_NIL: return "nil";
|
||||||
|
case HAZARD_LIGHT: return "light";
|
||||||
|
case HAZARD_MODERATE: return "moderate";
|
||||||
|
case HAZARD_SEVERE: return "severe";
|
||||||
|
default: return "invalid";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
char *generateAircraftJson(const char *url_path, int *len) {
|
char *generateAircraftJson(const char *url_path, int *len) {
|
||||||
uint64_t now = mstime();
|
uint64_t now = mstime();
|
||||||
struct aircraft *a;
|
struct aircraft *a;
|
||||||
|
|
@ -1709,12 +1740,23 @@ char *generateAircraftJson(const char *url_path, int *len) {
|
||||||
p = safe_snprintf(p, end, ",\"gva\":%u", a->gva);
|
p = safe_snprintf(p, end, ",\"gva\":%u", a->gva);
|
||||||
if (trackDataValid(&a->sda_valid))
|
if (trackDataValid(&a->sda_valid))
|
||||||
p = safe_snprintf(p, end, ",\"sda\":%u", a->sda);
|
p = safe_snprintf(p, end, ",\"sda\":%u", a->sda);
|
||||||
|
if (trackDataValid(&a->mrar_source_valid))
|
||||||
|
p = safe_snprintf(p, end, ",\"mrar_source\":\"%s\"", mrar_source_enum_string(a->mrar_source));
|
||||||
|
if (trackDataValid(&a->wind_valid))
|
||||||
|
p = safe_snprintf(p, end, ",\"wind_speed\":%.0f,\"wind_dir\":%.1f", a->wind_speed, a->wind_dir);
|
||||||
|
if (trackDataValid(&a->temperature_valid))
|
||||||
|
p = safe_snprintf(p, end, ",\"temperature\":%.2f", a->temperature);
|
||||||
|
if (trackDataValid(&a->pressure_valid))
|
||||||
|
p = safe_snprintf(p, end, ",\"pressure\":%.0f", a->pressure);
|
||||||
|
if (trackDataValid(&a->turbulence_valid))
|
||||||
|
p = safe_snprintf(p, end, ",\"turbulence\":\"%s\"", hazard_enum_string(a->turbulence));
|
||||||
|
if (trackDataValid(&a->humidity_valid))
|
||||||
|
p = safe_snprintf(p, end, ",\"humidity\":%.1f", a->humidity);
|
||||||
if (a->modeA_hit)
|
if (a->modeA_hit)
|
||||||
p = safe_snprintf(p, end, ",\"modea\":true");
|
p = safe_snprintf(p, end, ",\"modea\":true");
|
||||||
if (a->modeC_hit)
|
if (a->modeC_hit)
|
||||||
p = safe_snprintf(p, end, ",\"modec\":true");
|
p = safe_snprintf(p, end, ",\"modec\":true");
|
||||||
|
|
||||||
|
|
||||||
p = safe_snprintf(p, end, ",\"mlat\":");
|
p = safe_snprintf(p, end, ",\"mlat\":");
|
||||||
p = append_flags(p, end, a, SOURCE_MLAT);
|
p = append_flags(p, end, a, SOURCE_MLAT);
|
||||||
p = safe_snprintf(p, end, ",\"tisb\":");
|
p = safe_snprintf(p, end, ",\"tisb\":");
|
||||||
|
|
@ -2259,7 +2301,7 @@ __attribute__ ((format (printf,4,5))) static char *appendFATSV(char *p, char *en
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TSV_MAX_PACKET_SIZE 800
|
#define TSV_MAX_PACKET_SIZE 800
|
||||||
#define TSV_VERSION "8E"
|
#define TSV_VERSION "9E"
|
||||||
|
|
||||||
static void writeFATSVPositionUpdate(float lat, float lon, float alt)
|
static void writeFATSVPositionUpdate(float lat, float lon, float alt)
|
||||||
{
|
{
|
||||||
|
|
@ -2354,6 +2396,23 @@ static void writeFATSVEvent(struct modesMessage *mm, struct aircraft *a)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case COMMB_GICB_CAPS:
|
||||||
|
// BDS 1,7: common usage GICB capability report
|
||||||
|
if (memcmp(mm->MB, a->fatsv_emitted_bds_17, 7) != 0) {
|
||||||
|
memcpy(a->fatsv_emitted_bds_17, mm->MB, 7);
|
||||||
|
writeFATSVEventMessage(mm, "gicb_caps", mm->MB, 7);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMMB_UNKNOWN:
|
||||||
|
// If enabled, upload raw unrecognized Comm-B messages
|
||||||
|
// for server-side analysis
|
||||||
|
if (Modes.faup_upload_unknown_commb && memcmp(mm->MB, a->fatsv_emitted_unknown_commb, 7) != 0) {
|
||||||
|
memcpy(a->fatsv_emitted_unknown_commb, mm->MB, 7);
|
||||||
|
writeFATSVEventMessage(mm, "unknown_commb", mm->MB, 7);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// nothing
|
// nothing
|
||||||
break;
|
break;
|
||||||
|
|
@ -2528,7 +2587,13 @@ static void writeFATSV()
|
||||||
(airgroundValid && a->airground == AG_AIRBORNE && a->fatsv_emitted_airground == AG_GROUND) ||
|
(airgroundValid && a->airground == AG_AIRBORNE && a->fatsv_emitted_airground == AG_GROUND) ||
|
||||||
(airgroundValid && a->airground == AG_GROUND && a->fatsv_emitted_airground == AG_AIRBORNE) ||
|
(airgroundValid && a->airground == AG_GROUND && a->fatsv_emitted_airground == AG_AIRBORNE) ||
|
||||||
(squawkValid && a->squawk != a->fatsv_emitted_squawk) ||
|
(squawkValid && a->squawk != a->fatsv_emitted_squawk) ||
|
||||||
(trackDataValid(&a->emergency_valid) && a->emergency != a->fatsv_emitted_emergency);
|
(trackDataValid(&a->emergency_valid) && a->emergency != a->fatsv_emitted_emergency) ||
|
||||||
|
(trackDataValid(&a->mrar_source_valid) && a->mrar_source_valid.updated > a->fatsv_last_emitted) ||
|
||||||
|
(trackDataValid(&a->wind_valid) && a->wind_valid.updated > a->fatsv_last_emitted) ||
|
||||||
|
(trackDataValid(&a->pressure_valid) && a->pressure_valid.updated > a->fatsv_last_emitted) ||
|
||||||
|
(trackDataValid(&a->temperature_valid) && a->temperature_valid.updated > a->fatsv_last_emitted) ||
|
||||||
|
(trackDataValid(&a->turbulence_valid) && a->turbulence_valid.updated > a->fatsv_last_emitted) ||
|
||||||
|
(trackDataValid(&a->humidity_valid) && a->humidity_valid.updated > a->fatsv_last_emitted);
|
||||||
|
|
||||||
uint64_t minAge;
|
uint64_t minAge;
|
||||||
double adjustedMinAge;
|
double adjustedMinAge;
|
||||||
|
|
@ -2630,10 +2695,17 @@ static void writeFATSV()
|
||||||
p = appendFATSVMeta(p, end, "nav_alt_mcp", a, &a->nav_altitude_mcp_valid, "%u", a->nav_altitude_mcp);
|
p = appendFATSVMeta(p, end, "nav_alt_mcp", a, &a->nav_altitude_mcp_valid, "%u", a->nav_altitude_mcp);
|
||||||
p = appendFATSVMeta(p, end, "nav_alt_fms", a, &a->nav_altitude_fms_valid, "%u", a->nav_altitude_fms);
|
p = appendFATSVMeta(p, end, "nav_alt_fms", a, &a->nav_altitude_fms_valid, "%u", a->nav_altitude_fms);
|
||||||
p = appendFATSVMeta(p, end, "nav_alt_src", a, &a->nav_altitude_src_valid, "%s", nav_altitude_source_enum_string(a->nav_altitude_src));
|
p = appendFATSVMeta(p, end, "nav_alt_src", a, &a->nav_altitude_src_valid, "%s", nav_altitude_source_enum_string(a->nav_altitude_src));
|
||||||
p = appendFATSVMeta(p, end, "nav_heading", a, &a->nav_heading_valid, "%.1f", a->nav_heading);
|
p = appendFATSVMeta(p, end, "nav_heading", a, &a->nav_heading_valid, "%.1f", a->nav_heading);
|
||||||
p = appendFATSVMeta(p, end, "nav_modes", a, &a->nav_modes_valid, "{%s}", nav_modes_flags_string(a->nav_modes));
|
p = appendFATSVMeta(p, end, "nav_modes", a, &a->nav_modes_valid, "{%s}", nav_modes_flags_string(a->nav_modes));
|
||||||
p = appendFATSVMeta(p, end, "nav_qnh", a, &a->nav_qnh_valid, "%.1f", a->nav_qnh);
|
p = appendFATSVMeta(p, end, "nav_qnh", a, &a->nav_qnh_valid, "%.1f", a->nav_qnh);
|
||||||
p = appendFATSVMeta(p, end, "emergency", a, &a->emergency_valid, "%s", emergency_enum_string(a->emergency));
|
p = appendFATSVMeta(p, end, "emergency", a, &a->emergency_valid, "%s", emergency_enum_string(a->emergency));
|
||||||
|
p = appendFATSVMeta(p, end, "mrar_source", a, &a->mrar_source_valid, "%s", mrar_source_enum_string(a->mrar_source));
|
||||||
|
p = appendFATSVMeta(p, end, "wind_speed", a, &a->wind_valid, "%.0f", a->wind_speed);
|
||||||
|
p = appendFATSVMeta(p, end, "wind_dir", a, &a->wind_valid, "%.1f", a->wind_dir);
|
||||||
|
p = appendFATSVMeta(p, end, "temperature", a, &a->temperature_valid, "%.2f", a->temperature);
|
||||||
|
p = appendFATSVMeta(p, end, "pressure", a, &a->pressure_valid, "%.0f", a->pressure);
|
||||||
|
p = appendFATSVMeta(p, end, "turbulence", a, &a->turbulence_valid, "%s", hazard_enum_string(a->turbulence));
|
||||||
|
p = appendFATSVMeta(p, end, "humidity", a, &a->humidity_valid, "%.0f", a->humidity);
|
||||||
|
|
||||||
// if we didn't get anything interesting, bail out.
|
// if we didn't get anything interesting, bail out.
|
||||||
// We don't need to do anything special to unwind prepareWrite().
|
// We don't need to do anything special to unwind prepareWrite().
|
||||||
|
|
|
||||||
37
track.c
37
track.c
|
|
@ -130,6 +130,12 @@ static struct aircraft *trackCreateAircraft(struct modesMessage *mm) {
|
||||||
F(sil, 60, 70); // ADS-B only
|
F(sil, 60, 70); // ADS-B only
|
||||||
F(gva, 60, 70); // ADS-B only
|
F(gva, 60, 70); // ADS-B only
|
||||||
F(sda, 60, 70); // ADS-B only
|
F(sda, 60, 70); // ADS-B only
|
||||||
|
F(mrar_source, 60, 70); // Comm-B only
|
||||||
|
F(wind, 60, 70); // Comm-B only
|
||||||
|
F(temperature, 60, 70); // Comm-B only
|
||||||
|
F(pressure, 60, 70); // Comm-B only
|
||||||
|
F(turbulence, 60, 70); // Comm-B only
|
||||||
|
F(humidity, 60, 70); // Comm-B only
|
||||||
#undef F
|
#undef F
|
||||||
|
|
||||||
Modes.stats_current.unique_aircraft++;
|
Modes.stats_current.unique_aircraft++;
|
||||||
|
|
@ -1237,6 +1243,31 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
|
||||||
a->sda = mm->accuracy.sda;
|
a->sda = mm->accuracy.sda;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mm->mrar_source_valid && accept_data(&a->mrar_source_valid, mm->source)) {
|
||||||
|
a->mrar_source = mm->mrar_source;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->wind_valid && accept_data(&a->wind_valid, mm->source)) {
|
||||||
|
a->wind_speed = mm->wind_speed;
|
||||||
|
a->wind_dir = mm->wind_dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->temperature_valid && accept_data(&a->temperature_valid, mm->source)) {
|
||||||
|
a->temperature = mm->temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->pressure_valid && accept_data(&a->pressure_valid, mm->source)) {
|
||||||
|
a->pressure = mm->pressure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->turbulence_valid && accept_data(&a->turbulence_valid, mm->source)) {
|
||||||
|
a->turbulence = mm->turbulence;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->humidity_valid && accept_data(&a->humidity_valid, mm->source)) {
|
||||||
|
a->humidity = mm->humidity;
|
||||||
|
}
|
||||||
|
|
||||||
// Now handle derived data
|
// Now handle derived data
|
||||||
|
|
||||||
// derive geometric altitude if we have baro + delta
|
// derive geometric altitude if we have baro + delta
|
||||||
|
|
@ -1400,6 +1431,12 @@ static void trackRemoveStaleAircraft(uint64_t now)
|
||||||
EXPIRE(sil);
|
EXPIRE(sil);
|
||||||
EXPIRE(gva);
|
EXPIRE(gva);
|
||||||
EXPIRE(sda);
|
EXPIRE(sda);
|
||||||
|
EXPIRE(mrar_source);
|
||||||
|
EXPIRE(wind);
|
||||||
|
EXPIRE(temperature);
|
||||||
|
EXPIRE(pressure);
|
||||||
|
EXPIRE(turbulence);
|
||||||
|
EXPIRE(humidity);
|
||||||
#undef EXPIRE
|
#undef EXPIRE
|
||||||
prev = a; a = a->next;
|
prev = a; a = a->next;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
track.h
18
track.h
|
|
@ -226,6 +226,22 @@ struct aircraft {
|
||||||
unsigned gva : 2; // GVA from opstatus
|
unsigned gva : 2; // GVA from opstatus
|
||||||
unsigned sda : 2; // SDA from opstatus
|
unsigned sda : 2; // SDA from opstatus
|
||||||
|
|
||||||
|
// data extracted from MRAR
|
||||||
|
data_validity mrar_source_valid;
|
||||||
|
data_validity wind_valid; // speed and direction
|
||||||
|
data_validity pressure_valid;
|
||||||
|
data_validity temperature_valid;
|
||||||
|
data_validity turbulence_valid;
|
||||||
|
data_validity humidity_valid;
|
||||||
|
|
||||||
|
mrar_source_t mrar_source;
|
||||||
|
float wind_speed;
|
||||||
|
float wind_dir;
|
||||||
|
float pressure;
|
||||||
|
float temperature;
|
||||||
|
hazard_t turbulence;
|
||||||
|
float humidity;
|
||||||
|
|
||||||
int modeA_hit; // did our squawk match a possible mode A reply in the last check period?
|
int modeA_hit; // did our squawk match a possible mode A reply in the last check period?
|
||||||
int modeC_hit; // did our altitude match a possible mode C reply in the last check period?
|
int modeC_hit; // did our altitude match a possible mode C reply in the last check period?
|
||||||
|
|
||||||
|
|
@ -250,7 +266,9 @@ struct aircraft {
|
||||||
nav_modes_t fatsv_emitted_nav_modes; // -"- enabled navigation modes
|
nav_modes_t fatsv_emitted_nav_modes; // -"- enabled navigation modes
|
||||||
float fatsv_emitted_nav_qnh; // -"- altimeter setting
|
float fatsv_emitted_nav_qnh; // -"- altimeter setting
|
||||||
unsigned char fatsv_emitted_bds_10[7]; // -"- BDS 1,0 message
|
unsigned char fatsv_emitted_bds_10[7]; // -"- BDS 1,0 message
|
||||||
|
unsigned char fatsv_emitted_bds_17[7]; // -"- BDS 1,7 message
|
||||||
unsigned char fatsv_emitted_bds_30[7]; // -"- BDS 3,0 message
|
unsigned char fatsv_emitted_bds_30[7]; // -"- BDS 3,0 message
|
||||||
|
unsigned char fatsv_emitted_unknown_commb[7]; // -"- unrecognized Comm-B message
|
||||||
unsigned char fatsv_emitted_es_status[7]; // -"- ES operational status message
|
unsigned char fatsv_emitted_es_status[7]; // -"- ES operational status message
|
||||||
unsigned char fatsv_emitted_es_acas_ra[7]; // -"- ES ACAS RA report message
|
unsigned char fatsv_emitted_es_acas_ra[7]; // -"- ES ACAS RA report message
|
||||||
char fatsv_emitted_callsign[9]; // -"- callsign
|
char fatsv_emitted_callsign[9]; // -"- callsign
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue