diff --git a/README.md b/README.md index 37dc0dae..cf5a1a35 100644 --- a/README.md +++ b/README.md @@ -275,9 +275,9 @@ Don't write recent(1), full(2), either(3) traces Establish connection, can be specified multiple times (e.g. 127.0.0.1,23004,beast_out) Protocols: beast_out, beast_in, raw_out, raw_in, sbs_in, - sbs_in_jaero, sbs_out, sbs_out_jaero, vrs_out, - json_out, gpsd_in (one failover ip/address,port - can be specified: + sbs_in_jaero, sbs_out, sbs_out_jaero, sbs_out_prio + vrs_out, json_out, asterix_out, asterix_in, + gpsd_in (one failover ip/address,port can be specified: primary-address,primary-port,protocol,failover-address,failover-port) --net-connector-delay= Outbound re-connection delay (default: 30) @@ -319,6 +319,10 @@ TCP json position output: include aircraft without --net-sbs-port= TCP BaseStation output listen ports (default: 0) --net-sbs-reduce Apply beast reduce logic and interval to SBS outputs + --net-ao-port= TCP ASTERIX output listen ports (default: 0) + --net-ai-port= TCP ASTERIX input listen ports (default: 0) + --net-asterix-reduce Apply beast reduce logic and interval to ASTERIX + outputs --net-verbatim Forward messages unchanged --net-vrs-interval= TCP VRS json output interval (default: 5.0) diff --git a/comm_b.c b/comm_b.c index 27ac4962..24785549 100644 --- a/comm_b.c +++ b/comm_b.c @@ -35,6 +35,7 @@ static int decodeBDS30(struct modesMessage *mm, bool store); static int decodeBDS40(struct modesMessage *mm, bool store); static int decodeBDS50(struct modesMessage *mm, bool store); static int decodeBDS60(struct modesMessage *mm, bool store); +static int decodeBDS44(struct modesMessage *mm, bool store); static CommBDecoderFn comm_b_decoders[] = { &decodeEmptyResponse, @@ -44,7 +45,8 @@ static CommBDecoderFn comm_b_decoders[] = { &decodeBDS17, &decodeBDS40, &decodeBDS50, - &decodeBDS60 + &decodeBDS60, + &decodeBDS44 }; void decodeCommB(struct modesMessage *mm) { @@ -820,3 +822,140 @@ static int decodeBDS60(struct modesMessage *mm, bool store) { return score; } + +// BDS 4,4 Meteorological 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 wind_speed_raw = getbits(msg, 6, 14); + unsigned wind_direction_raw = getbits(msg, 15, 23); + + unsigned temperature_sign = getbit(msg, 24); + unsigned static_air_temperature_raw = getbits(msg, 25, 34); + + unsigned pressure_valid = getbit(msg, 35); + unsigned static_pressure_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 (!wind_valid || !temperature_valid || !pressure_valid || !turbulence_valid && !humidity_valid){ + return 0; + } +*/ + int met_source = source; + int score = 0; + int wind_speed = 0; + float wind_direction = 0; + float temperature = 0; + int static_pressure = 0; + int turbulence = 0; + float humidity = 0; + if (met_source >= 0 && met_source <= 6) { + score += 4; + } + else { + return 0; + } + if (wind_valid){ + wind_speed = (int)wind_speed_raw; + if (wind_speed <= 511 && wind_speed >= 0){ + score += 9; + } + else { + return 0; + } + wind_direction = wind_direction_raw * (180 / 256); + if (wind_direction >= 0 && wind_direction <= 360){ + score += 9; + } + else { + return 0; + } + } + else if (wind_speed == 0) { + score += 2; + } + if (temperature_sign){ + temperature = (static_air_temperature_raw - pow(2, 10)) * 0.25; + } + else { + temperature = static_air_temperature_raw * 0.25; + } + if (temperature >= -128 && temperature <= 128){ + score += 10; + } + else { + return 0; + } + if (pressure_valid){ + static_pressure = (int)static_pressure_raw; + if (static_pressure >= 0 && static_pressure <= 2048){ + score += 11; + return 0; + } + else { + } + } + else if (static_pressure == 0) { + score += 1; + } + if (turbulence_valid){ + turbulence = (int)turbulence_raw; + if (turbulence >= 0 && turbulence <= 3) { + score += 2; + } + else { + return 0; + } + } + else if (turbulence == 0) { + score += 1; + } + if (humidity_valid) { + humidity = humidity_raw * (100.0f / 64); + if (humidity >= 0 && humidity <= 100){ + score += 6; + } + else { + return 0; + } + } + else if (humidity == 0) { + score += 1; + } + if (store) { + mm->commb_format = COMMB_METEOROLOGICAL_ROUTINE; + mm->met_source_valid = 1; + mm->met_source = met_source; + if (wind_valid) { + mm->wind_valid = 1; + mm->wind_speed = wind_speed; + mm->wind_direction = wind_direction; + } + mm->oat_valid = 1; + mm->oat = temperature; + if (pressure_valid) { + mm->static_pressure_valid = 1; + mm->static_pressure = static_pressure; + } + if (turbulence_valid) { + mm->turbulence_valid = 1; + mm->turbulence = turbulence; + } + if (humidity_valid) { + mm->humidity_valid = 1; + mm->humidity = humidity; + } + } + return score; +} + diff --git a/help.h b/help.h index e38e8c74..936e0f01 100644 --- a/help.h +++ b/help.h @@ -120,6 +120,9 @@ static struct argp_option optionsReadsb[] = { {"net-only", OptNetOnly, 0, 0, "Enable just networking, no RTL device or file used", 2}, {"net-bind-address", OptNetBindAddr, "", 0, "IP address to bind to (default: Any; Use 127.0.0.1 for private)", 2}, {"net-bo-port", OptNetBoPorts, "", 0, "TCP Beast output listen ports (default: 0)", 2}, + {"net-ao-port", OptNetAsterixOutPorts, "", 0, "TCP Asterix output listen ports (default: 0)", 2}, + {"net-ai-port", OptNetAsterixInPorts, "", 0, "TCP Asterix input listen ports (default: 0)", 2}, + {"net-asterix-reduce", OptNetAsterixReduce, 0, 0, "Apply beast reduce logic and interval to ASTERIX outputs", 2}, {"net-ri-port", OptNetRiPorts, "", 0, "TCP raw input listen ports (default: 0)", 2}, {"net-ro-port", OptNetRoPorts, "", 0, "TCP raw output listen ports (default: 0)", 2}, {"net-sbs-port", OptNetSbsPorts, "", 0, "TCP BaseStation output listen ports (default: 0)", 2}, diff --git a/mode_s.c b/mode_s.c index bb93adc3..da0184ab 100644 --- a/mode_s.c +++ b/mode_s.c @@ -1645,6 +1645,8 @@ static const char *commb_format_to_string(commb_format_t format) { return "BDS5,0 Track and turn report"; case COMMB_HEADING_SPEED: return "BDS6,0 Heading and speed report"; + case COMMB_METEOROLOGICAL_ROUTINE: + return "BDS4,4 Meteorological routine air report"; default: return "unknown format"; } diff --git a/net_io.c b/net_io.c index 195c164e..17f7e642 100644 --- a/net_io.c +++ b/net_io.c @@ -77,6 +77,7 @@ static int decodeBinMessage(struct client *c, char *p, int remote, int64_t now, static int processHexMessage(struct client *c, char *hex, int remote, int64_t now, struct messageBuffer *mb); static int decodeUatMessage(struct client *c, char *msg, int remote, int64_t now, struct messageBuffer *mb); static int decodeSbsLine(struct client *c, char *line, int remote, int64_t now, struct messageBuffer *mb); +static int decodeAsterixMessage(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb); static int decodeSbsLineMlat(struct client *c, char *line, int remote, int64_t now, struct messageBuffer *mb) { MODES_NOTUSED(remote); return decodeSbsLine(c, line, 64 + SOURCE_MLAT, now, mb); @@ -195,6 +196,19 @@ static struct net_service *serviceInit(struct net_service_group *group, const ch return service; } +static char *ais_charset = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?"; +static uint8_t char_to_ais(int ch) +{ + char *match; + if (!ch) + return 32; + + match = strchr(ais_charset, ch); + if (match) + return (uint8_t)(match - ais_charset); + else + return 32; +} static int sendFiveHeartbeats(struct client *c, int64_t now) { // send 5 heartbeats to signal that we are a client that can accomodate feedback .... some counterparts crash if they get stuff they don't understand @@ -838,6 +852,8 @@ void modesInitNet(void) { struct net_service *sbs_out_mlat; struct net_service *sbs_out_jaero; struct net_service *sbs_out_prio; + struct net_service *asterix_out; + struct net_service *asterix_in; struct net_service *sbs_in; struct net_service *sbs_in_mlat; struct net_service *sbs_in_jaero; @@ -880,6 +896,7 @@ void modesInitNet(void) { serviceListen(sbs_out_jaero, Modes.net_bind_address, Modes.net_output_jaero_ports, Modes.net_epfd); + int sbs_port_len = strlen(Modes.net_output_sbs_ports); int pos = sbs_port_len - 1; if (sbs_port_len <= 5 && Modes.net_output_sbs_ports[pos] == '5') { @@ -938,6 +955,12 @@ void modesInitNet(void) { sfree(jaero); } + asterix_out = serviceInit(&Modes.services_out, "ASTERIX output", &Modes.asterix_out, no_heartbeat, no_heartbeat, READ_MODE_IGNORE, NULL, NULL); + serviceListen(asterix_out, Modes.net_bind_address, Modes.net_output_asterix_ports, Modes.net_epfd); + + asterix_in = serviceInit(&Modes.services_in, "ASTERIX TCP input", NULL, no_heartbeat, no_heartbeat, READ_MODE_ASTERIX, NULL, decodeAsterixMessage); + serviceListen(asterix_in, Modes.net_bind_address, Modes.net_input_asterix_ports, Modes.net_epfd); + gpsd_in = serviceInit(&Modes.services_in, "GPSD TCP input", &Modes.gpsd_in, no_heartbeat, no_heartbeat, READ_MODE_ASCII, "\n", handle_gpsd); if (Modes.json_dir && Modes.json_globe_index && Modes.globe_history_dir) { @@ -1002,6 +1025,10 @@ void modesInitNet(void) { con->service = feedmap_out; else if (strcmp(con->protocol, "sbs_out") == 0) con->service = sbs_out; + else if (strcmp(con->protocol, "asterix_out") == 0) + con->service = asterix_out; + else if (strcmp(con->protocol, "asterix_in") == 0) + con->service = asterix_in; else if (strcmp(con->protocol, "sbs_in") == 0) con->service = sbs_in; else if (strcmp(con->protocol, "sbs_in_mlat") == 0) @@ -1756,6 +1783,1087 @@ static void modesSendRawOutput(struct modesMessage *mm) { completeWrite(&Modes.raw_out, p); } +// +// Read Asterix FSPEC +// +static uint8_t * readFspec(char **p){ + uint8_t* fspec = malloc(24*sizeof(uint8_t*)); + for (int i = 1; i < 24; i++) { + fspec[i] = 0; + } + fspec[0] = **p; + (*p)++; + for (int i = 1; *(*p - 1) & 0x1; i++){ + fspec[i] = *(*p); + (*p)++; + } + return fspec; +} + +// +// Read Asterix Time +// +static uint64_t readAsterixTime(char **p) { + int rawtime = ((*(*p) & 0xff) << 16) + ((*(*p + 1) & 0xff) << 8) + (*(*p + 2) & 0xff); + int mssm = (int)(rawtime / .128); + long midnight = (long)(mstime() / 86400000) * 86400000; + int diff = (int)(midnight + mssm - mstime()); + (*p) += 3; + if (abs(diff) > 43200000){ + return midnight - 86400000 + mssm; + } + return midnight + mssm; +} + +// +// Read Asterix High Precision Time +// +static void readAsterixHighPrecisionTime(uint64_t *timeStamp, char **p) { + uint8_t fsi = (**p & 0xc0) >> 6; + double offset = ((**p & 0x3f) << 24) + ((*(*p + 1) & 0xff) << 16) + ((*(*p + 2) & 0xff) << 8) + (*(*p + 3) & 0xff); + (*p) += 4; + offset = offset * pow(2, -27); + uint64_t wholesecond = (int)(*timeStamp / 1000) * 1000; + switch(fsi) + { + case 1: + wholesecond += 1; + break; + case 2: + wholesecond -= 1; + break; + } + *timeStamp = wholesecond + offset; +} + +// +//========================================================================= +// +// Read ASTERIX input from TCP clients +// +static int decodeAsterixMessage(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb) { + //uint16_t msgLen = (*(p + 1) << 8) + *(p + 2); + //int j; + unsigned char category; + struct modesMessage *mm = netGetMM(mb); + mm->client = c; + MODES_NOTUSED(c); + if (remote >= 64) + mm->source = remote - 64; + else + mm->source = SOURCE_INDIRECT; + mm->remote = 1; + mm->sbs_in = 1; + mm->signalLevel = 0; + category = *p; // Get the category + p += 3; + uint8_t *fspec = readFspec(&p); + mm->receiverId = c->receiverId; + if (unlikely(Modes.incrementId)) { + mm->receiverId += now / (10 * MINUTES); + } + mm->sysTimestamp = -1; + switch(category){ + case 21: // ADS-B Message + if(!(fspec[1] & 0x10)){ // no address. this is useless to us + free(fspec); + return -1; + } + if (fspec[0] & 0x80){ // ID021/010 Data Source Identification + p += 2; + } + uint8_t addrtype = 3; + if (fspec[0] & 0x40){ // ID021/040 Target Report Descriptor + uint8_t *trd = readFspec(&p); + addrtype = (trd[0] & 0xE0) >> 5; + if (!(trd[0] & 0x18)){ + mm->alt_q_bit = 1; + } + if (trd[1] & 0x40){ + mm->airground = AG_GROUND; + } + else { + mm->airground = AG_AIRBORNE; + } + free(trd); + } + if (fspec[0] & 0x20){ // I021/161 Track Number + p += 2; + } + if (fspec[0] & 0x10){ // I021/015 Service Identification + p += 1; + } + if (fspec[0] & 0x8){ // I021/071 Time of Applicability for Position 3 + mm->sysTimestamp = readAsterixTime(&p); + } + if (fspec[0] & 0x4){ // I021/130 Position in WGS-84 co-ordinates + int lat = (*p & 0xff) << 16; + lat += (*(p + 1) & 0xff) << 8; + lat += (*(p + 2) & 0xff); + p += 3; + int lon = (*p & 0xff) << 16; + lon += (*(p + 1) & 0xff) << 8; + lon += (*(p + 2) & 0xff); + p += 3; + if (lat >= 0x800000){ + lat -= 0x1000000; + } + if (lon >= 0x800000){ + lon -= 0x1000000; + } + double latitude = lat * (180 / pow(2, 23)); + double longitude = lon * (180 / pow(2, 23)); + if (latitude <= 90 && latitude >= -90 && longitude >= -180 && longitude <= 180){ + mm->sbs_pos_valid = true; + mm->decoded_lat = latitude; + mm->decoded_lon = longitude; + } + } + if (fspec[0] & 0x2){ // I021/131 Position in WGS-84 co-ordinates, high res. + int lat = (*p & 0xff) << 24; + lat += (*(p + 1) & 0xff) << 16; + lat += (*(p + 2) & 0xff) << 8; + lat += (*(p + 3) & 0xff); + p += 4; + int lon = *p << 24; + lon += (*(p + 1) & 0xff) << 16; + lon += (*(p + 2) & 0xff) << 8; + lon += (*(p + 3) & 0xff); + p += 4; + double latitude = lat * (180 / pow(2, 30)); + double longitude = lon * (180 / pow(2, 30)); + if (latitude <= 90 && latitude >= -90 && longitude >= -180 && longitude <= 180){ + mm->sbs_pos_valid = true; + mm->decoded_lat = latitude; + mm->decoded_lon = longitude; + } + } + if (fspec[1] & 0x80){ // I021/072 Time of Applicability for Velocity + if (mm->sysTimestamp == -1){ + mm->sysTimestamp = readAsterixTime(&p); + } + else { + p += 3; + } + } + if (fspec[1] & 0x40){ // I021/150 Air Speed + uint16_t raw_speed = (*p & 0x7f) << 8; + raw_speed += *(p + 1) & 0xff; + if (*p & 0x80){ //Mach + mm->mach = raw_speed * 0.001; + mm->mach_valid = true; + } + else{ // IAS + mm->ias = (raw_speed * pow(2, -14)) * 3600; + mm->ias_valid = true; + } + p += 2; + } + if (fspec[1] & 0x20){ // I021/151 True Airspeed + uint16_t raw_speed = (*p & 0x7f) << 8; + raw_speed += *(p + 1) & 0xff; + if (!(*p & 0x80)){ + mm->tas_valid = true; + mm->tas = raw_speed; + } + p += 2; + } + // I021/080 Target Address + mm->addr = (((*p & 0xff) << 16) + ((*(p + 1) & 0xff) << 8) + (*(p + 2) & 0xff)) & 0xffffff; + if (addrtype == 3){ + mm->addr |= MODES_NON_ICAO_ADDRESS; + } + p += 3; + if (fspec[1] & 0x8){ // I021/073 Time of Message Reception of Position + if (mm->cpr_decoded || mm->sbs_pos_valid){ + uint64_t ts = readAsterixTime(&p); + if (fspec[1] & 0x4){ // I021/074 Time of Message Reception of Position=High Precision + readAsterixHighPrecisionTime(&ts, &p); + } + if (mm->sysTimestamp == -1){ + mm->sysTimestamp = ts; + } + } + else if (fspec[1] & 0x4) { + p += 7; + } + else { + p += 3; + } + } + if (fspec[1] & 0x2){ // I021/075 Time of Message Reception of Velocity + if (mm->ias_valid || mm->mach_valid || mm->gs_valid){ + uint64_t ts = readAsterixTime(&p); + if (fspec[2] & 0x80){ // I021/076 Time of Message Reception of Velocity=High Precision + readAsterixHighPrecisionTime(&ts, &p); + } + if (mm->sysTimestamp == -1){ + mm->sysTimestamp = ts; + } + } + else if (fspec[2] & 0x80) { + p += 7; + } + else { + p += 3; + } + } + if (fspec[2] & 0x40){ // I021/140 Geometric Height + int16_t raw_alt = (((*p & 0xff) << 8) + (*(p + 1) & 0xff)); + double alt = raw_alt * 6.25; + if (alt >= -1500 && alt <= 150000){ + mm->geom_alt_valid = true; + mm->geom_alt_unit = UNIT_FEET; + mm->geom_alt = alt; + } + p += 2; + } + uint8_t *qi; + //uint8_t nucp_or_nic; + uint8_t nucr_or_nacv; + uint8_t nicbaro = 0; + uint8_t sil; + uint8_t nacp = 0; + uint8_t sils; + uint8_t sda = 0; + uint8_t gva = 0; + //uint8_t pic; + if (fspec[2] & 0x20){ // I021/090 Quality Indicators + qi = readFspec(&p); + //nucp_or_nic = (qi[0] & 0x1e) >> 1; + nucr_or_nacv = (qi[0] & 0xe0) >> 5; + mm->accuracy.nac_v_valid = true; + mm->accuracy.nac_v = nucr_or_nacv; + if (qi[0] & 0x1){ + nicbaro = (qi[1] & 0x80) >> 7; + sil = (qi[1] & 0x60) >> 5; + mm->accuracy.sil = sil; + nacp = (qi[1] & 0x1e) >> 1; + if (qi[1] & 0x1){ + sils = (qi[2] & 0x20) >> 5; + if (sils){ + mm->accuracy.sil_type = SIL_PER_SAMPLE; + } + else{ + mm->accuracy.sil_type = SIL_PER_HOUR; + } + sda = (qi[2] & 0x18) >> 3; + gva = (qi[2] & 0x6) >> 1; + //pic = (qi[3] & 0xf0) >> 4; + } + else{ + mm->accuracy.sil_type = SIL_UNKNOWN; + } + } + free(qi); + } + if (fspec[2] & 0x10){ // I021/210 MOPS Version + mm->opstatus.valid = true; + mm->opstatus.version = ((*p) & 0x38) >> 3; + uint8_t ltt = (*p) & 0x7; + p++; + switch(mm->opstatus.version){ + case 1: + mm->accuracy.nac_p_valid = true; + mm->accuracy.nac_p = nacp; + mm->accuracy.nic_baro_valid = true; + mm->accuracy.nic_baro = nicbaro; + mm->accuracy.sil_type = SIL_UNKNOWN; + break; + case 2: + mm->accuracy.nac_p_valid = true; + mm->accuracy.nac_p = nacp; + mm->accuracy.gva_valid = true; + mm->accuracy.gva = gva; + mm->accuracy.sda_valid = true; + mm->accuracy.sda = sda; + mm->accuracy.nic_baro_valid = true; + mm->accuracy.nic_baro = nicbaro; + break; + } + switch (ltt) { + case 0: + if (!addrtype){ + mm->addrtype = ADDR_TISB_ICAO; + } + else{ + mm->addrtype = ADDR_TISB_OTHER; + } + break; + case 1: + if (!addrtype){ + mm->addrtype = ADDR_ADSR_ICAO; + } + else{ + mm->addrtype = ADDR_ADSR_OTHER; + } + break; + case 2: + if (!addrtype){ + mm->addrtype = ADDR_ADSB_ICAO; + } + else{ + mm->addrtype = ADDR_ADSB_OTHER; + } + break; + default: + mm->addrtype = ADDR_UNKNOWN; + break; + } + } + if (fspec[2] & 0x8){ // I021/070 Mode 3/A Code + mm->squawk += (((*p & 0xe) << 11) + ((*p & 0x1) << 10) + ((*(p + 1) & 0xC0) << 2) + ((*(p + 1) & 0x38) << 1) + ((*(p + 1) & 0x7))) ; + mm->squawk_valid = true; + p += 2; + } + if (fspec[2] & 0x4){ // I021/230 Roll Angle + int16_t roll = ((*p & 0xff) << 8) + (*(p + 1) & 0xff); + mm->roll = roll * 0.01; + mm->roll_valid = true; + p += 2; + } + if (fspec[2] & 0x2){ // I021/145 Flight Level + int16_t alt = ((*p & 0xff) << 8) + (*(p + 1) & 0xff); + mm->baro_alt_valid = true; + mm->baro_alt = alt * 25; + mm->baro_alt_unit = UNIT_FEET; + p += 2; + } + if (fspec[3] & 0x80){ // I021/152 Magnetic Heading + mm->heading_valid = true; + mm->heading_type = HEADING_MAGNETIC; + uint16_t heading = ((*p & 0xff) << 8) + (*(p + 1) & 0xff); + mm->heading = heading * (360 / pow(2, 16)); + p += 2; + } + if (fspec[3] & 0x40){ // I021/200 Target Status + mm->spi_valid = true; + mm->alert_valid = true; + mm->emergency_valid = true; + mm->nav.modes_valid = true; + mm->nav.modes |= (*p & 0b01000000) >> 4; + mm->emergency = (*p & 0b00011100) >> 2; + mm->alert = (*p & 0b11); + mm->spi = (*p & 0b11) == 3; + p++; + } + if (fspec[3] & 0x20){ // ID021/155 Barometric Vertical Rate + if (*p & 0x80){ //range exceeded + p += 2; + } + else{ + int16_t vr = ((*p & 0x7f) << 9) + ((*(p + 1) & 0xff) << 1); + mm->baro_rate_valid = true; + mm->baro_rate = vr * 3.125; + p += 2; + } + } + if (fspec[3] & 0x10){ // ID021/157 Geometric Vertical Rate + if (*p & 0x80){ //range exceeded + p += 2; + } + else{ + int16_t vr = ((*p & 0x7f) << 9) + ((*(p + 1) & 0xff) << 1); + mm->geom_rate_valid = true; + mm->geom_rate = vr * 3.125; + p += 2; + } + } + if (fspec[3] & 0x8){ // ID021/160 Airborne Ground Vector + if (*p & 0x80){ //range exceeded + p += 4; + } + else{ + uint16_t gs = ((*p & 0x7f) << 8) + ((*(p + 1) & 0xff)); + p += 2; + uint16_t ta = ((*p & 0xff) << 8) + ((*(p + 1) & 0xff)); + p += 2; + mm->gs_valid = true; + mm->heading_valid = true; + mm->heading_type = HEADING_GROUND_TRACK; + mm->gs.v0 = gs * pow(2, -14) * 3600; + mm->heading = ta * (360 / pow(2, 16)); + } + } + if (fspec[3] & 0x4){ // ID021/165 Track Angle Rate + p += 2; + } + if (fspec[3] & 0x2){ // ID021/077 Time of Report Transmission + uint64_t tt = readAsterixTime(&p); + if (mm->sysTimestamp == -1){ + mm->sysTimestamp = tt; + } + } + if (fspec[4] & 0x80){ // ID021/170 Target Identification + uint64_t cs = ((uint64_t)(*p & 0xff) << 40) + ((uint64_t)(*(p + 1) & 0xff) << 32) + ((uint64_t)(*(p + 2) & 0xff) << 24) + ((uint64_t)(*(p + 3) & 0xff) << 16) + ((uint64_t)(*(p + 4) & 0xff) << 8) + (uint64_t)(*(p + 5) & 0xff); + char *callsign = mm->callsign; + callsign[0] = ais_charset[((cs & 0xFC0000000000) >> 42)]; + callsign[1] = ais_charset[((cs & 0x3F000000000) >> 36)]; + callsign[2] = ais_charset[((cs & 0xFC0000000) >> 30)]; + callsign[3] = ais_charset[((cs & 0x3F000000) >> 24)]; + callsign[4] = ais_charset[((cs & 0xFC0000) >> 18)]; + callsign[5] = ais_charset[((cs & 0x3F000) >> 12)]; + callsign[6] = ais_charset[((cs & 0xFC0) >> 6)]; + callsign[7] = ais_charset[(cs & 0x3F)]; + callsign[8] = 0; + mm->callsign_valid = 1; + for (int i = 0; i < 8; ++i) { + if ( + (callsign[i] >= 'A' && callsign[i] <= 'Z') + // -./0123456789 + || (callsign[i] >= '-' && callsign[i] <= '9') + || callsign[i] == ' ' + || callsign[i] == '@' + ) { + // valid chars + } else { + mm->callsign_valid = 0; + } + } + p += 6; + } + if (fspec[4] & 0x40){ // ID021/020 Emitter Category + int tc = 0; + int ca = 0; + uint8_t ecat = *p++ & 0xFF; + switch (ecat) { + case 0: + tc = 0x0e; + ca = 0; + break; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + tc = 4; + ca = ecat; + break; + case 10: + tc = 4; + ca = 7; + break; + case 11: + tc = 3; + ca = 1; + break; + case 12: + tc = 3; + ca = 2; + break; + case 13: + tc = 3; + ca = 6; + break; + case 14: + tc = 3; + ca = 7; + break; + case 15: + tc = 3; + ca = 4; + break; + case 16: + tc = 3; + ca = 3; + break; + case 20: + tc = 2; + ca = 1; + break; + case 21: + tc = 2; + ca = 3; + break; + case 22: + tc = 2; + ca = 4; + break; + case 23: + tc = 2; + ca = 5; + break; + case 24: + tc = 2; + ca = 6; + break; + } + mm->category = ((0x0E - tc) << 4) | ca; + mm->category_valid = 1; + } + + if (fspec[4] & 0x20) { // I021/220 Met Information + uint8_t *met = readFspec(&p); + free(met); + } + + if (fspec[4] & 0x10) { // I021/146 Selected Altitude + if (*p & 0x80 && *p & 0x60) { + int16_t alt = (*p & 0x1F) << 8; + alt += *(p + 1) & 0xFF; + if (alt < 0x1000){ + if ((*p & 0x60) == 0x40) { // MCP + mm->nav.mcp_altitude_valid = 1; + mm->nav.mcp_altitude = alt * 25; + } + else if ((*p & 0x60) == 0x60) { //FMS + mm->nav.fms_altitude_valid = 1; + mm->nav.fms_altitude = alt * 25; + } + } + } + p += 2; + } + netUseMessage(mm); + break; + } + free(fspec); + if (mm->sysTimestamp == -1){ + mm->sysTimestamp = mstime(); + } + //mm->decoded_nic = 0; + //mm->decoded_rc = RC_UNKNOWN; + return 0; +} + + + +// +//========================================================================= +// +// Write ASTERIX output to TCP clients +// +static void modesSendAsterixOutput(struct modesMessage *mm, struct net_writer *writer) { + int64_t now = mstime(); + uint8_t category; + unsigned char bytes[Modes.net_output_flush_size * 2]; + for (int i = 0; i < Modes.net_output_flush_size * 2; i++){ + bytes[i] = 0; + } + uint8_t fspec[7]; + for (size_t i = 0; i < 7; i++) + { + fspec[i] = 0; + } + int p = 0; + if (mm->from_mlat) // CAT 20 + return; + if (mm->from_tisb) + return; + else { // CAT 21 + category = 21; + + // I021/010 Data Source Identification + fspec[0] |= 1 << 7; + bytes[p++] = 000; //SAC + bytes[p++] = 001; //SIC + + // I021/040 Target Report Descriptor + fspec[0] |= 1 << 6; + if (mm->addr & MODES_NON_ICAO_ADDRESS){ + bytes[p] |= (3 << 5); + } + else if (mm->addrtype == ADDR_ADSB_OTHER || mm->addrtype == ADDR_TISB_OTHER || mm->addrtype == ADDR_ADSR_OTHER){ + bytes[p] |= (2 << 5); + } + + if (mm->alt_q_bit == 0) + bytes[p] |= (1 << 3); + + if (mm->airground == AG_GROUND) + bytes[p + 1] |= 1 << 6; + if (bytes[p + 1]){ + bytes[p] |= 1; + p++; + } + p++; + + // I021/130 Position in WGS-84 co-ordinates + if (mm->cpr_decoded || mm->sbs_pos_valid){ + fspec[0] |= 1 << 2; + int32_t lat; + int32_t lon; + lat = mm->decoded_lat / (180 / pow(2,23)); + lon = mm->decoded_lon / (180 / pow(2,23)); + if (lat < 0){ + lat += 0x1000000; + } + if (lon < 0){ + lon += 0x1000000; + } + bytes[p++] = (lat & 0xFF0000) >> 16; + bytes[p++] = (lat & 0xFF00) >> 8; + bytes[p++] = (lat & 0xFF); + bytes[p++] = (lon & 0xFF0000) >> 16; + bytes[p++] = (lon & 0xFF00) >> 8; + bytes[p++] = (lon & 0xFF); + } + + // I021/131 Position in WGS-84 co-ordinates, high res. + /* + if (mm->cpr_decoded || mm->sbs_pos_valid){ + fspec[0] |= 1 << 1; + int32_t lat; + int32_t lon; + lat = mm->decoded_lat / (180 / pow(2,30)); + lon = mm->decoded_lon / (180 / pow(2,30)); + bytes[p++] = (lat & 0xFF000000) >> 24; + bytes[p++] = (lat & 0xFF0000) >> 16; + bytes[p++] = (lat & 0xFF00) >> 8; + bytes[p++] = (lat & 0xFF); + bytes[p++] = (lon & 0xFF000000) >> 24; + bytes[p++] = (lon & 0xFF0000) >> 16; + bytes[p++] = (lon & 0xFF00) >> 8; + bytes[p++] = (lon & 0xFF); + } + */ + // I021/150 Air Speed + if(mm->ias_valid || mm->mach_valid){ + fspec[1] |= 1 << 6; + uint16_t speedval; + if (mm->mach_valid){ + bytes[p] = (1 << 7); + speedval = mm->mach * 1000; + } + else{ + speedval = (mm->ias / 3600.0) * pow(2,14); + } + bytes[p++] |= (speedval & 0x7f00) >> 8; + bytes[p++] = (speedval & 0xff); + } + + // I021/151 True Air Speed + if(mm->tas_valid){ + fspec[1] |= 1 << 5; + bytes[p++] = (mm->tas & 0x7f00) >> 8; + bytes[p++] = (mm->tas & 0xff); + } + + // I021/080 Target Address + fspec[1] |= 1 << 4; + bytes[p++] = (mm->addr & 0xff0000) >> 16; + bytes[p++] = (mm->addr & 0xff00) >> 8; + bytes[p++] = (mm->addr & 0xff); + struct aircraft *a = aircraftGet(mm->addr); + if (!a) { // If it's a currently unknown aircraft.... + a = aircraftCreate(mm->addr); // ., create a new record for it, + } + + // I021/073 Time of Message Reception of Position + if (fspec[0] & 0b110){ + fspec[1] |= 1 << 3; + long midnight = (long)(time(NULL) / 86400) * 86400000; + int tsm = (mm->sysTimestamp) - midnight; + if (tsm < 0) + tsm += 86400000; + tsm = (int)(tsm * 0.128); + bytes[p++] = (tsm & 0xff0000) >> 16; + bytes[p++] = (tsm & 0xff00) >> 8; + bytes[p++] = tsm & 0xff; + } + + // I021/075 Time of Message Reception of Velocity + if (mm->gs_valid && mm->heading_valid && mm->heading_type == HEADING_GROUND_TRACK){ + fspec[1] |= 1 << 1; + long midnight = (long)(time(NULL) / 86400) * 86400000; + int tsm = (mm->sysTimestamp) - midnight; + if (tsm < 0) + tsm += 86400000; + tsm = (int)(tsm * 0.128); + bytes[p++] = (tsm & 0xff0000) >> 16; + bytes[p++] = (tsm & 0xff00) >> 8; + bytes[p++] = tsm & 0xff; + } + + // I021/140 Geometric Height + if (mm->geom_alt_valid){ + fspec[2] |= 1 << 6; + int16_t alt; + if(mm->geom_alt_unit == UNIT_FEET) + alt = mm->geom_alt / 6.25; + else + alt = mm->geom_alt / 20.5053; + bytes[p++] = (alt & 0xff00) >> 8; + bytes[p++] = alt & 0xff; + } + else if (mm->geom_delta_valid){ + fspec[2] |= 1 << 6; + int16_t alt = (int)((a->baro_alt + mm->geom_delta) / 6.25); + bytes[p++] = (alt & 0xff00) >> 8; + bytes[p++] = alt & 0xff; + } + // I021/090 Quality Indicators + fspec[2] |= 1 << 5; + if (mm->accuracy.nac_v_valid) + bytes[p] += mm->accuracy.nac_v << 5; + if (mm->cpr_decoded) + bytes[p] |= mm->cpr_nucp << 1; + if (mm->accuracy.nic_baro_valid) + bytes[p + 1] |= mm->accuracy.nic_baro << 7; + if (mm->accuracy.sil_type != SIL_INVALID) + bytes[p + 1] |= mm->accuracy.sil << 5; + if (mm->accuracy.nac_p_valid) + bytes[p + 1] |= mm->accuracy.nac_p << 1; + if (bytes[p + 1]){ + bytes[p] |= 1; + p++; + } + if (mm->accuracy.sil_type == SIL_PER_SAMPLE) + bytes[p + 1] |= 1 << 5; + if (mm->accuracy.sda_valid) + bytes[p + 1] |= mm->accuracy.sda << 3; + if (mm->accuracy.gva_valid) + bytes[p + 1] |= mm->accuracy.gva << 1; + if (bytes[p + 1]){ + bytes[p] |= 1; + p++; + } + p++; + + // I021/210 MOPS Version + if (mm->opstatus.valid){ + fspec[2] |= 1 << 4; + + if (mm->remote) { + switch (mm->addrtype){ + case ADDR_ADSB_ICAO: + case ADDR_ADSB_OTHER: + bytes[p] = 2; + break; + case ADDR_ADSR_ICAO: + case ADDR_ADSR_OTHER: + bytes[p] = 1; + break; + default: + bytes[p] = 0; + break; + } + } + else { + switch (mm->source){ + case SOURCE_ADSB: + bytes[p] = 2; + break; + case SOURCE_ADSR: + bytes[p] = 1; + break; + default: + bytes[p] = 0; + break; + } + } + bytes[p++] |= (mm->opstatus.version) << 3; + } + + // I021/070 Mode 3/A Code + if(mm->squawk_valid){ + fspec[2] |= 1 << 3; + uint16_t squawk = mm->squawk; + bytes[p] |= ((squawk & 0x7000)) >> 11; + bytes[p++] |= ((squawk & 0x0400)) >> 10; + bytes[p] |= ((squawk & 0x0300)) >> 2; + bytes[p] |= ((squawk & 0x0070)) >> 1; + bytes[p++] |= ((squawk & 0x0007)); + } + + // I021/230 Roll Angle + if(mm->roll_valid){ + fspec[2] |= 1 << 2; + int16_t roll = mm->roll * 100; + bytes[p++] = (roll & 0xFF00) >> 8; + bytes[p++] = (roll & 0xFF); + } + + // I021/145 Flight Level + if(mm->baro_alt_valid){ + fspec[2] |= 1 << 1; + int16_t value = mm->baro_alt / 25; + if (mm->baro_alt_unit == UNIT_METERS) + value = (int)(mm-> baro_alt * 3.2808); + bytes[p++] = (value & 0xff00) >> 8; + bytes[p++] = value & 0xff; + } + + // I021/152 Magnetic Heading + if(mm->heading_valid && mm->heading_type == HEADING_MAGNETIC){ + fspec[3] |= 1 << 7; + double adj_trk = mm->heading * 182.0444; + uint16_t trk = (int)adj_trk; + bytes[p++] = (trk & 0xff00) >> 8; + bytes[p++] = trk & 0xff; + } + + // I021/200 Target Status + if (mm->spi_valid || mm->alert_valid || mm->emergency_valid || mm->nav.modes_valid){ + fspec[3] |= 1 << 6; + if (mm->nav.modes_valid){ + if (mm->nav.modes & 0b00000010) + bytes[p] |= 1 << 6; + } + if (mm->emergency_valid) + bytes[p] |= (mm->emergency << 2); + if (mm->alert_valid) + bytes[p] |= (mm->alert); + else if (mm->spi_valid && mm->spi) + bytes[p] |=3; + p++; + } + + // I021/155 Barometric Vertical Rate + if (mm->baro_rate_valid){ + fspec[3] |= 1 << 5; + int value = ((int16_t)(mm->baro_rate / 3.125)) >> 1; + bytes[p++] = (value & 0x7f00) >> 8; + bytes[p++] = value & 0xff; + } + + // I021/157 Geometric Vertical Rate + if (mm->geom_rate_valid){ + fspec[3] |= 1 << 4; + int value = ((int16_t)(mm->geom_rate / 3.125)) >> 1; + bytes[p++] = (value & 0x7f00) >> 8; + bytes[p++] = value & 0xff; + } + + // I021/160 Airborne Ground Vector + if (mm->gs_valid && mm->heading_valid && mm->heading_type == HEADING_GROUND_TRACK){ + fspec[3] |= 1 << 3; + double adj_gs = mm->gs.v0 * 4.5511; + bytes[p++] = ((int)adj_gs & 0x7f00) >> 8; + bytes[p++] = (int)adj_gs & 0xff; + double adj_trk = mm->heading * (pow(2,16) / 360.0); + uint16_t trk = (int)adj_trk; + bytes[p++] = (trk & 0xff00) >> 8; + bytes[p++] = trk & 0xff; + } + + // I021/077 Time of Report Transmission + { + fspec[3] |= 1 << 1; + long midnight = (long)(time(NULL) / 86400) * 86400000; + int tsm = now - midnight; + if (tsm < 0) + tsm += 86400000; + tsm = (int)(tsm * 0.128); + bytes[p++] = (tsm & 0xff0000) >> 16; + bytes[p++] = (tsm & 0xff00) >> 8; + bytes[p++] = tsm & 0xff; + } + + // I021/170 Target Identification + if(mm->callsign_valid){ + fspec[4] |= 1 << 7; + uint64_t enc_callsign = 0; + for (int i = 0; i <= 7; i++) + { + uint8_t ch = char_to_ais(mm->callsign[i]); + enc_callsign = (enc_callsign << 6) + (ch & 0x3F); + } + bytes[p++] = (enc_callsign & 0xff0000000000) >> 40; + bytes[p++] = (enc_callsign & 0xff00000000) >> 32; + bytes[p++] = (enc_callsign & 0xff000000) >> 24; + bytes[p++] = (enc_callsign & 0xff0000) >> 16; + bytes[p++] = (enc_callsign & 0xff00) >> 8; + bytes[p++] = (enc_callsign & 0xff); + } + + // I021/020 Emitter Category + if (mm->category_valid){ + fspec[4] |= 1 << 6; + int tc = 0x0e - ((mm->category & 0x1F0) >> 4); + int ca = mm->category & 7; + if (ca){ + switch (tc) { + case 1: + break; + case 2: + switch (ca){ + case 1: + bytes[p++] = 20; + break; + case 3: + bytes[p++] = 21; + break; + case 4: + case 5: + case 6: + case 7: + bytes[p++] = 22; + break; + } + break; + case 3: + switch (ca){ + case 1: + bytes[p++] = 11; + break; + case 2: + bytes[p++] = 12; + break; + case 3: + bytes[p++] = 16; + break; + case 4: + bytes[p++] = 15; + break; + case 6: + bytes[p++] = 13; + break; + case 7: + bytes[p++] = 14; + break; + } + break; + case 4: + switch (ca){ + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + bytes[p++] = ca; + break; + case 7: + bytes[p++] = 10; + } + break; + } + } else { + bytes[p++] = 0; + } + } + else if (!(a->category)){ + fspec[4] |= 1 << 6; + bytes[p++] = 0; + } + // I021/220 Met Information + //if (ac && ((now < ac->oat_updated + TRACK_EXPIRE) || (now < ac->wind_updated + TRACK_EXPIRE && abs(ac->wind_altitude - ac->baro_alt) < 500))){ + if (mm->wind_valid || mm->oat_valid || mm->turbulence_valid || mm->static_pressure_valid || mm->humidity_valid) { + fspec[4] |= 1 << 5; + bool wind = false; + bool temp = false; + //if (now < ac->wind_updated + TRACK_EXPIRE && abs(ac->wind_altitude - ac->baro_alt) < 500){ + if (mm->wind_valid) { + bytes[p] |= 0xC0; + wind = true; + } + //if (now < ac->oat_updated + TRACK_EXPIRE){ + if (mm->oat_valid) { + bytes[p] |= 0x20; + temp = true; + } + p++; + if (wind){ + uint16_t ws = (int)(mm->wind_speed); + uint16_t wd = (int)(mm->wind_direction); + bytes[p++] = (ws & 0xFF00) >> 8; + bytes[p++] = ws & 0xFF; + bytes[p++] = (wd & 0xFF00) >> 8; + bytes[p++] = wd & 0xFF; + } + if (temp){ + int16_t oat = (int16_t)((mm->oat) * 4); + bytes[p++] = (oat & 0xFF00) >> 8; + bytes[p++] = oat & 0xFF; + } + } + + // I021/146 Selected Altitude + if (mm->nav.fms_altitude_valid || mm->nav.mcp_altitude_valid){ + fspec[4] |= 1 << 4; + int alt = 0; + if (mm->nav.mcp_altitude_valid){ + alt = mm->nav.mcp_altitude; + bytes[p] |= 0xC0; + } + else if (mm->nav.fms_altitude_valid){ + alt = mm->nav.fms_altitude; + bytes[p] |= 0xE0; + } + alt /= 25; + bytes[p++] |= (alt & 0x1F00) >> 8; + bytes[p++] = (alt & 0xFF); + } + + // I021/008 Aircraft Operational Status + if (mm->opstatus.valid){ + if (mm->opstatus.om_acas_ra || mm->opstatus.cc_tc || + mm->opstatus.cc_ts || mm->opstatus.cc_arv || mm->opstatus.cc_cdti + || !(mm->opstatus.cc_acas)){ + fspec[5] |= 1 << 7; + bytes[p] |= (mm->opstatus.om_acas_ra & 0x1) << 7; + bytes[p] |= (mm->opstatus.cc_tc & 0x3) << 5; + bytes[p] |= (mm->opstatus.cc_ts & 0x1) << 4; + bytes[p] |= (mm->opstatus.cc_arv & 0x1) << 3; + bytes[p] |= (mm->opstatus.cc_cdti & 0x1) << 2; + bytes[p] |= (!(mm->opstatus.cc_acas) & 0x1) << 1; + p++; + } + } + + // I021/400 Receiver ID + if (mm->receiverId){ + fspec[5] |= 1 << 2; + bytes[p++] = (mm->receiverId) & 0xFF; + } + + // I021/295 Data Ages + /* + if (fspec[4] == 0b100000){ + fspec[5] |= 1 << 1; + bytes[p++] |= 1; + bytes[p++] |= 1; + bytes[p++] |= 1 << 2; + if((now < ac->wind_updated + TRACK_EXPIRE && abs(ac->wind_altitude - ac->baro_alt) < 500) && + (now < ac->oat_updated + TRACK_EXPIRE)) { + uint64_t wind_age = now - ac->wind_updated; + uint64_t oat_age = now - ac->oat_updated; + if (wind_age > oat_age){ + bytes[p++] = (int)(wind_age / 100); + } + else { + bytes[p++] = (int)(oat_age / 100); + } + } + else if (now < ac->wind_updated + TRACK_EXPIRE && abs(ac->wind_altitude - ac->baro_alt) < 500){ + uint64_t wind_age = now - ac->wind_updated; + bytes[p++] = (int)(wind_age / 100); + } + else if (now < ac->oat_updated + TRACK_EXPIRE){ + uint64_t oat_age = now - ac->oat_updated; + bytes[p++] = (int)(oat_age / 100); + } + } + */ + + int fspec_len = 1; + for (int i = 5; i >= 0; i--) + { + if (fspec[i + 1]){ + fspec[i] |= 1; + fspec_len++; + } + } + + uint16_t msgLen = p + 3 + fspec_len; + uint8_t msgLenA = (msgLen & 0xFF00) >> 8; + uint8_t msgLenB = msgLen & 0xFF; + char *w = prepareWrite(writer, msgLen); + memcpy(w, &category, 1); + w++; + memcpy(w, &msgLenA, 1); + memcpy(w + 1, &msgLenB, 1); + w+=2; + memcpy(w, &fspec, fspec_len); + w += fspec_len; + memcpy(w, &bytes, p); + w += p; + completeWrite(writer, w); + + } +} // //========================================================================= @@ -2213,6 +3321,7 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a, stru completeWrite(writer, p); } + void jsonPositionOutput(struct modesMessage *mm, struct aircraft *a) { MODES_NOTUSED(mm); char *p; @@ -2583,7 +3692,7 @@ static int handleBeastCommand(struct client *c, char *p, int remote, int64_t now // // to save a couple cycles we remove the escapes in the calling function and expect nonescaped messages here static int decodeBinMessage(struct client *c, char *p, int remote, int64_t now, struct messageBuffer *mb) { - int msgLen = 0; + uint16_t msgLen = 0; int j; unsigned char ch; struct modesMessage *mm = netGetMM(mb); @@ -3387,6 +4496,23 @@ static int readAscii(struct client *c, int64_t now, struct messageBuffer *mb) { return 0; } +static int readAsterix(struct client *c, int64_t now, struct messageBuffer *mb) { + + while (c->som < c->eod) { + char *p = c->som; + uint16_t msgLen = (*(p + 1) << 8) + *(p + 2); + char *end = c->som + msgLen; + c->som = end; + if (c->service->read_handler(c, p, c->remote, now, mb)) { + if (Modes.debug_net) { + fprintf(stderr, "%s: Closing connection from %s port %s\n", c->service->descr, c->host, c->port); + } + modesCloseClient(c); + return -1; + } + } + return 0; +} // Spec for Planefinder message. // All messages begin with a DLE and end with a DLE, ETX. DLE cannot appear in the middle of a message unless it's escaped with another DLE (i.e., bit stuffing) @@ -3463,6 +4589,7 @@ static int readPlanefinder(struct client *c, int64_t now, struct messageBuffer * } return 0; } + static int readBeast(struct client *c, int64_t now, struct messageBuffer *mb) { // This is the Beast Binary scanning case. // If there is a complete message still in the buffer, there must be the separator 'sep' @@ -3850,6 +4977,11 @@ static int processClient(struct client *c, int64_t now, struct messageBuffer *mb if (res != 0) { return res; } + } else if (read_mode == READ_MODE_ASTERIX) { + int res = readAsterix(c, now, mb); + if (res != 0) { + return res; + } } else if (read_mode == READ_MODE_PLANEFINDER) { int res = readPlanefinder(c, now, mb); if (res != 0) { @@ -4558,6 +5690,9 @@ static void outputMessage(struct modesMessage *mm) { if (Modes.dump_fw && (!Modes.dump_reduce || mm->reduce_forward)) { modesDumpBeastData(mm); } + if (Modes.asterix_out.connections && (!Modes.asterixReduce || mm->reduce_forward)){ + modesSendAsterixOutput(mm, &Modes.asterix_out); + } } if (mm->sbs_in && Modes.net && ac) { diff --git a/net_io.h b/net_io.h index 20a519de..460d1a88 100644 --- a/net_io.h +++ b/net_io.h @@ -43,6 +43,7 @@ typedef enum READ_MODE_BEAST, READ_MODE_BEAST_COMMAND, READ_MODE_ASCII, + READ_MODE_ASTERIX, READ_MODE_PLANEFINDER } read_mode_t; @@ -227,5 +228,4 @@ void netUseMessage(struct modesMessage *mm); void netDrainMessageBuffers(); struct modesMessage *netGetMM(struct messageBuffer *buf); - #endif diff --git a/readsb.c b/readsb.c index 6f9bb004..7d31502a 100644 --- a/readsb.c +++ b/readsb.c @@ -1342,6 +1342,8 @@ static int make_net_connector(char *arg) { && strcmp(con->protocol, "sbs_out_mlat") != 0 && strcmp(con->protocol, "sbs_out_jaero") != 0 && strcmp(con->protocol, "sbs_out_prio") != 0 + && strcmp(con->protocol, "asterix_out") != 0 + && strcmp(con->protocol, "asterix_in") != 0 && strcmp(con->protocol, "json_out") != 0 && strcmp(con->protocol, "feedmap_out") != 0 && strcmp(con->protocol, "gpsd_in") != 0 @@ -1352,6 +1354,7 @@ static int make_net_connector(char *arg) { fprintf(stderr, "Supported protocols: beast_out, beast_in, beast_reduce_out, beast_reduce_plus_out, raw_out, raw_in, \n" "sbs_out, sbs_out_replay, sbs_out_mlat, sbs_out_jaero, \n" "sbs_in, sbs_in_mlat, sbs_in_jaero, \n" + "sbs_out_prio, asterix_out, asterix_in, \n" "vrs_out, json_out, gpsd_in, uat_in, \n" "planefinder_in\n"); return 1; @@ -1639,6 +1642,17 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) { sfree(Modes.net_input_beast_ports); Modes.net_input_beast_ports = strdup(arg); break; + case OptNetAsterixOutPorts: + sfree(Modes.net_output_asterix_ports); + Modes.net_output_asterix_ports = strdup(arg); + break; + case OptNetAsterixInPorts: + sfree(Modes.net_input_asterix_ports); + Modes.net_input_asterix_ports = strdup(arg); + break; + case OptNetAsterixReduce: + Modes.asterixReduce = 1; + break; case OptNetBeastReducePorts: sfree(Modes.net_output_beast_reduce_ports); Modes.net_output_beast_reduce_ports = strdup(arg); diff --git a/readsb.h b/readsb.h index f10ece8d..9ebbf050 100644 --- a/readsb.h +++ b/readsb.h @@ -256,7 +256,8 @@ typedef enum { COMMB_ACAS_RA, COMMB_VERTICAL_INTENT, COMMB_TRACK_TURN, - COMMB_HEADING_SPEED + COMMB_HEADING_SPEED, + COMMB_METEOROLOGICAL_ROUTINE } commb_format_t; typedef enum @@ -588,6 +589,7 @@ struct _Modes struct net_writer sbs_out_jaero; // SBS-format output struct net_writer sbs_out_prio; // SBS-format output struct net_writer json_out; // SBS-format output + struct net_writer asterix_out; // Asterix output struct net_writer feedmap_out; // SBS-format output struct net_writer vrs_out; // SBS-format output struct net_writer fatsv_out; // FATSV-format output @@ -699,6 +701,7 @@ struct _Modes int8_t jsonLongtype; int8_t viewadsb; int8_t sbsReduce; // apply beast reduce logic to SBS messages + int8_t asterixReduce; // apply beast reduce logic to SBS messages int position_persistence; // Maximum number of consecutive implausible positions from global CPR to invalidate a known position int json_reliable; @@ -750,6 +753,8 @@ struct _Modes char *net_input_beast_ports; // List of Beast input TCP ports char *net_output_beast_ports; // List of Beast output TCP ports char *net_output_beast_reduce_ports; // List of Beast output TCP ports + char *net_output_asterix_ports; // List of Asterix output TCP ports + char *net_input_asterix_ports; // List of Asterix input TCP ports char *net_output_json_ports; char *net_output_api_ports; char *garbage_ports; @@ -950,6 +955,12 @@ struct modesMessage bool alt_q_bit; bool acas_ra_valid; bool geom_alt_derived; + bool wind_valid; + bool oat_valid; + bool static_pressure_valid; + bool turbulence_valid; + bool humidity_valid; + bool met_source_valid; bool squawk_emergency_valid; bool squawk_emergency; @@ -1006,6 +1017,15 @@ struct modesMessage double receiver_distance; // distance to receiver float calculated_track; // set in speed_check, -1 is invalid + // meteorological + int wind_speed; + float wind_direction; + float oat; + int static_pressure; + int turbulence; + float humidity; + int met_source; + commb_format_t commb_format; // Inferred format of a comm-b message // various integrity/accuracy things @@ -1169,6 +1189,9 @@ enum { OptNetJaeroInPorts, OptNetBiPorts, OptNetBoPorts, + OptNetAsterixInPorts, + OptNetAsterixOutPorts, + OptNetAsterixReduce, OptNetBeastReducePorts, OptNetBeastReduceInterval, OptNetBeastReduceFilterAlt, diff --git a/track.c b/track.c index b09ed3a0..5ae1cf07 100644 --- a/track.c +++ b/track.c @@ -2225,6 +2225,18 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) { a->spi = mm->spi; } + if (mm->wind_valid) { + a->wind_speed = a->wind_speed; + a->wind_direction = a->wind_direction; + a->wind_updated = now; + a->wind_altitude = a->baro_alt; + } + + if (mm->oat_valid) { + a->oat = mm->oat; + a->oat_updated = now; + } + // CPR, even if (mm->cpr_valid && !mm->cpr_odd && accept_data(&a->cpr_even_valid, mm->source, mm, a, REDUCE_OFTEN)) { a->cpr_even_type = mm->cpr_type; @@ -3245,8 +3257,10 @@ void to_state_all(struct aircraft *a, struct state_all *new, int64_t now) { } if (now < a->oat_updated + TRACK_EXPIRE) { new->oat = (int) nearbyint(a->oat); - new->tat = (int) nearbyint(a->tat); - new->temp_valid = 1; + if (now < a->tat_updated + TRACK_EXPIRE) { + new->temp_valid = 1; + new->tat = (int) nearbyint(a->tat); + } } if (a->adsb_version < 0) diff --git a/track.h b/track.h index 11dd4909..93f2e8e2 100644 --- a/track.h +++ b/track.h @@ -387,6 +387,7 @@ struct aircraft float oat; int64_t wind_updated; int64_t oat_updated; + int64_t tat_updated; // ----