diff --git a/dump1090.c b/dump1090.c index ffc930a..aa23b73 100644 --- a/dump1090.c +++ b/dump1090.c @@ -295,6 +295,7 @@ static void showHelp(void) "--net-sbs-port TCP BaseStation output listen ports (default: 30003)\n" "--net-bi-port TCP Beast input listen ports (default: 30004,30104)\n" "--net-bo-port TCP Beast output listen ports (default: 30005)\n" +"--net-stratux-port TCP Stratux output listen ports (default: disabled)\n" "--net-ro-size TCP output minimum size (default: 0)\n" "--net-ro-interval TCP output memory flush rate in seconds (default: 0)\n" "--net-heartbeat TCP heartbeat rate in seconds (default: 60 sec; 0 to disable)\n" @@ -548,6 +549,10 @@ int main(int argc, char **argv) { Modes.net = 1; free(Modes.net_output_sbs_ports); Modes.net_output_sbs_ports = strdup(argv[++j]); + } else if (!strcmp(argv[j],"--net-stratux-port") && more) { + Modes.net = 1; + free(Modes.net_output_stratux_ports); + Modes.net_output_stratux_ports = strdup(argv[++j]); } else if (!strcmp(argv[j],"--net-buffer") && more) { Modes.net_sndbuf_size = atoi(argv[++j]); } else if (!strcmp(argv[j],"--net-verbatim")) { diff --git a/dump1090.h b/dump1090.h index b692df3..088d3f6 100644 --- a/dump1090.h +++ b/dump1090.h @@ -303,6 +303,7 @@ struct _Modes { // Internal state struct net_writer beast_verbatim_out; // Beast-format output, verbatim mode struct net_writer beast_cooked_out; // Beast-format output, "cooked" mode struct net_writer sbs_out; // SBS-format output + struct net_writer stratux_out; // Stratux-format output struct net_writer fatsv_out; // FATSV-format output #ifdef _WIN32 @@ -324,6 +325,7 @@ struct _Modes { // Internal state char *net_output_raw_ports; // List of raw output TCP ports char *net_input_raw_ports; // List of raw input TCP ports char *net_output_sbs_ports; // List of SBS output TCP ports + char *net_output_stratux_ports; // List of Stratux output TCP ports char *net_input_beast_ports; // List of Beast input TCP ports char *net_output_beast_ports; // List of Beast output TCP ports char *net_bind_address; // Bind address diff --git a/net_io.c b/net_io.c index ffafed1..f0754c4 100644 --- a/net_io.c +++ b/net_io.c @@ -77,6 +77,7 @@ static void moveNetClient(struct client *c, struct net_service *new_service); static void send_raw_heartbeat(struct net_service *service); static void send_beast_heartbeat(struct net_service *service); static void send_sbs_heartbeat(struct net_service *service); +static void send_stratux_heartbeat(struct net_service *service); static void writeBeastMessage(struct net_writer *writer, uint64_t timestamp, double signalLevel, unsigned char *msg, int msgLen); @@ -85,6 +86,11 @@ static void writeFATSVPositionUpdate(float lat, float lon, float alt); static void autoset_modeac(); +__attribute__ ((format (printf,3,0))) static char *safe_vsnprintf(char *p, char *end, const char *format, va_list ap); +__attribute__ ((format (printf,3,4))) static char *safe_snprintf(char *p, char *end, const char *format, ...); + +static const char *jsonEscapeString(const char *str); + // //========================================================================= // @@ -268,6 +274,9 @@ void modesInitNet(void) { s = serviceInit("Basestation TCP output", &Modes.sbs_out, send_sbs_heartbeat, READ_MODE_IGNORE, NULL, NULL); serviceListen(s, Modes.net_bind_address, Modes.net_output_sbs_ports); + s = serviceInit("Stratux TCP output", &Modes.stratux_out, send_stratux_heartbeat, READ_MODE_IGNORE, NULL, NULL); + serviceListen(s, Modes.net_bind_address, Modes.net_output_stratux_ports); + s = serviceInit("Raw TCP input", NULL, NULL, READ_MODE_ASCII, "\n", decodeHexMessage); serviceListen(s, Modes.net_bind_address, Modes.net_input_raw_ports); @@ -776,6 +785,207 @@ static void send_sbs_heartbeat(struct net_service *service) completeWrite(service->writer, data + len); } +// +//========================================================================= +// +// Write Stratux output to TCP clients +// + +#define STRATUX_MAX_PACKET_SIZE 1000 +static void modesSendStratuxOutput(struct modesMessage *mm, struct aircraft *a) { + char *p; + + // We require a tracked aircraft for Stratux output + if (!a) + return; + + // Don't ever forward 2-bit-corrected messages via Stratux output. + if (mm->correctedbits >= 2) + return; + + // Don't ever forward mlat messages via Stratux output. + if (mm->source == SOURCE_MLAT) + return; + + // Don't ever send unreliable messages via Stratux output + if (!mm->reliable && !a->reliable) + return; + + p = prepareWrite(&Modes.stratux_out, STRATUX_MAX_PACKET_SIZE); // larger buffer size needed vs SBS + if (!p) + return; + + char *end = p + STRATUX_MAX_PACKET_SIZE; + + // Begin populating the traffic.go fields. + // ICAO address, Mode S message types, and signal level + + int cacf = 0; // overload the JSON "CA" field to report CA (DF11 or DF17), CF (DF18), or zero (all other DF types) + if ((mm->msgtype == 11) || (mm->msgtype == 17)) { + cacf = mm->CA; + } else if (mm->msgtype == 18) { + cacf = mm->CF; + } + + p = safe_snprintf(p, end, + "{\"Icao_addr\":%d," + "\"DF\":%d,\"CA\":%d," + "\"TypeCode\":%d," + "\"SubtypeCode\":%d," + "\"SignalLevel\":%f,", + mm->addr, + mm->msgtype, cacf, + mm->metype, + mm->mesub, + mm->signalLevel); // what precision and range is needed for RSSI? + + //// callsign + if (mm->callsign_valid) + p = safe_snprintf(p, end, "\"Tail\":\"%s\",", jsonEscapeString(mm->callsign)); + else + p = safe_snprintf(p, end, "\"Tail\":null,"); + + //// altitude & gnss + bool alt_is_geom; + if (mm->altitude_baro_valid) { + p = safe_snprintf(p, end, "\"Alt\":%d,",mm->altitude_baro); + alt_is_geom = false; + } else if (mm->altitude_geom_valid) { + p = safe_snprintf(p, end, "\"Alt\":%d,",mm->altitude_geom); + alt_is_geom = true; + } else { + p = safe_snprintf(p, end, "\"Alt\":null,"); + alt_is_geom = false; + } + + // altitude source + if (alt_is_geom) + p = safe_snprintf(p, end, "\"AltIsGNSS\":true,"); + else + p = safe_snprintf(p, end, "\"AltIsGNSS\":false,"); + + // GNSS alt. delta From baro alt. + if (trackDataValid(&a->geom_delta_valid)) + p = safe_snprintf(p, end, "\"GnssDiffFromBaroAlt\":%d,",a->geom_delta); + else + p = safe_snprintf(p, end, "\"GnssDiffFromBaroAlt\":null,"); + //// + + //// ground speed and track + if (mm->gs_valid) + p = safe_snprintf(p, end, "\"Speed_valid\":true,\"Speed\":%.0f,", mm->gs.selected); + else + p = safe_snprintf(p, end, "\"Speed_valid\":false,\"Speed\":null,"); + + //// ground heading + if (mm->heading_valid && mm->heading_type == HEADING_GROUND_TRACK) + p = safe_snprintf(p, end, "\"Track\":%.0f,", mm->heading); + else + p = safe_snprintf(p, end, "\"Track\":null,"); + + //// position + if (mm->cpr_decoded) + p = safe_snprintf(p, end, "\"Lat\":%.6f,\"Lng\":%.6f,\"Position_valid\":true,", + mm->decoded_lat, mm->decoded_lon); + else + p = safe_snprintf(p, end, "\"Lat\":null,\"Lng\":null,\"Position_valid\":false,"); + + //// vrate (use barometric if possible) + if (mm->baro_rate_valid) + p = safe_snprintf(p, end, "\"Vvel\":%d,", mm->baro_rate); + else if (mm->geom_rate_valid) + p = safe_snprintf(p, end, "\"Vvel\":%d,", mm->geom_rate); + else + p = safe_snprintf(p, end, "\"Vvel\":null,"); + + //// squawk + if (mm->squawk_valid) + p = safe_snprintf(p, end, "\"Squawk\":%x,", mm->squawk); + else + p = safe_snprintf(p, end, "\"Squawk\":null,"); + + // TODO: squawk changing alert support in stratux? + // TODO: squawk emergency flag? + // TODO: squawk ident flag? + + // airground + switch (mm->airground) { + case AG_GROUND: + p = safe_snprintf(p, end, "\"OnGround\":true,"); + break; + case AG_AIRBORNE: + p = safe_snprintf(p, end, "\"OnGround\":false,"); + break; + default: + p = safe_snprintf(p, end, "\"OnGround\":null,"); + } + + // navigation accuracy category - position + if (mm->accuracy.nac_p_valid) { + p = safe_snprintf(p, end, "\"NACp\":%d,", mm->accuracy.nac_p); + } else { + p = safe_snprintf(p, end, "\"NACp\":null,"); + } + + // emitter type + int emitter = -1; + if ((mm->msgtype == 17) || (mm->msgtype == 18)) { + switch (mm->metype) { + case 1: + emitter = ((mm->mesub) | 0x18); + break; + case 2: + emitter = ((mm->mesub) | 0x10); + break; + case 3: + emitter = ((mm->mesub) | 0x08); + break; + case 4: + emitter = (mm->mesub); + break; + } + } + + if (emitter >= 0) + p = safe_snprintf(p, end, "\"Emitter_category\":%d,", emitter); + else + p = safe_snprintf(p, end, "\"Emitter_category\":null,"); + + // Time message received (based on system clock). Format is 2016-02-20T06:35:43.155Z + struct tm stTime_receive; + time_t received = (time_t) (mm->sysTimestampMsg / 1000); + gmtime_r(&received, &stTime_receive); + p = safe_snprintf(p, end, "\"Timestamp\":\"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ\"", + (stTime_receive.tm_year+1900),(stTime_receive.tm_mon+1), + stTime_receive.tm_mday, stTime_receive.tm_hour, + stTime_receive.tm_min, stTime_receive.tm_sec, + (unsigned)(mm->sysTimestampMsg % 1000)); + + p = safe_snprintf(p, end, "}\r\n"); + + if (p < end) + completeWrite(&Modes.stratux_out, p); + else + fprintf(stderr, "stratux: output too large (max %d, overran by %d)\n", STRATUX_MAX_PACKET_SIZE, (int) (p - end)); +} + +static void send_stratux_heartbeat(struct net_service *service) +{ + static char *heartbeat_message = "{\"Icao_addr\":134217727}\r\n"; // 0x07FFFFFF. Overflows 24-bit ICAO to signal invalic #, need to validate that this won't cause problems with traffic.go + char *data; + int len = strlen(heartbeat_message); + + if (!service->writer) + return; + + data = prepareWrite(service->writer, len); + if (!data) + return; + + memcpy(data, heartbeat_message, len); + completeWrite(service->writer, data + len); +} + // //========================================================================= // @@ -783,6 +993,7 @@ void modesQueueOutput(struct modesMessage *mm, struct aircraft *a) { // Delegate to the format-specific outputs, each of which makes its own decision about filtering messages modesSendSBSOutput(mm, a); + modesSendStratuxOutput(mm, a); modesSendRawOutput(mm, a); modesSendBeastVerbatimOutput(mm, a); modesSendBeastCookedOutput(mm, a);