commit 43012077ca5b4869fa82ce155bf01d08adceda3e Author: wyc <1104749580@qq.com> Date: Tue Jun 16 11:04:03 2026 +0800 首次提交 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..510869e --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,252 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..369389b --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/mode-s.iml b/.idea/mode-s.iml new file mode 100644 index 0000000..bc2cd87 --- /dev/null +++ b/.idea/mode-s.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..7c3556b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/SSR/ACAS/ACAS.cpp b/SSR/ACAS/ACAS.cpp new file mode 100644 index 0000000..0d0343c --- /dev/null +++ b/SSR/ACAS/ACAS.cpp @@ -0,0 +1,2 @@ +#include "ACAS.h" + diff --git a/SSR/ACAS/ACAS.h b/SSR/ACAS/ACAS.h new file mode 100644 index 0000000..98e7de2 --- /dev/null +++ b/SSR/ACAS/ACAS.h @@ -0,0 +1,24 @@ +#pragma once +#include "ACAS.hpp" +namespace SSR::ACAS { + // 这些是监视询问 是发送给飞机的 + template + using SI = ACAS_T::Surveillance_Interrogation_112; + ADD_56_1(Short_Surveillance_Interrogation, ACAS_T::Surveillance_Interrogation_56) + ADD_112_2(Surveillance_Interrogation30, SI, ACAS_T::UDS_3_0_Coordination_Interrogation) + ADD_112_2(Surveillance_Interrogation31, SI, ACAS_T::UDS_3_1_Coordination_Interrogation) + ADD_112_3(Surveillance_Interrogation31_1, SI, + ACAS_T::UDS_3_1_Coordination_Interrogation, ACAS_T::UDS_3_1_ARA1) + ADD_112_3(Surveillance_Interrogation31_2, SI, + ACAS_T::UDS_3_1_Coordination_Interrogation, ACAS_T::UDS_3_1_ARA2) + ADD_112_2(Surveillance_Interrogation32, SI, ACAS_T::UDS_3_2_Coordination_Interrogation) + + // 询问恢复 + ADD_56_1(Short_Reply, ACAS_T::Reply_56) + ADD_112_2(Reply_3_0, ACAS_T::Reply_112, ACAS_T::VDS_3_0_Coordination_Reply) + ADD_112_3(Reply_3_0_1, ACAS_T::Reply_112, ACAS_T::VDS_3_0_Coordination_Reply, ACAS_T::VDS_3_0_ARA1) + ADD_112_3(Reply_3_0_2, ACAS_T::Reply_112, ACAS_T::VDS_3_0_Coordination_Reply, ACAS_T::VDS_3_0_ARA2) + // 剩余的VDS 由 Commb回复 +} + + diff --git a/SSR/ACAS/ACAS.hpp b/SSR/ACAS/ACAS.hpp new file mode 100644 index 0000000..15c4fe4 --- /dev/null +++ b/SSR/ACAS/ACAS.hpp @@ -0,0 +1,407 @@ +#ifndef ACAS_HPP_H +#define ACAS_HPP_H +#include "../Mode_S/Mode_S.hpp" +namespace SSR::ACAS_T { + + template + struct Surveillance_Interrogation_56 : with_AP, BDS_ { + PROP_T(std::string, icao, "01ABCD") + PROP_T(Uplink_Format, uf, Uplink_Format::Short_Air_Air_Surveillance_0) + PROP_Bit_T(1, RL, 0) // 应答长度 (RL):1 位,它定义了所需的应答格式:0 需要 DF=0 的应答,而 1 需要 DF=16 的应答 + PROP_Bit_T(1, AQ, 0) // 它包含控制应答中 RI 字段内容的代码 + PROP_Bit_T(8, BDS, 0) // 8 位,表示 DF=16 响应的 MV 内容的 BDS 码 + void set(std::string& msg_bin) { + set_bin<0, 5>(msg_bin, static_cast(uf)); + set_bin<8, 1>(msg_bin, RL); + set_bin<13, 1>(msg_bin, AQ); + set_bin<14, 8>(msg_bin, BDS); + } + void parse(std::string& msg_bin) { + icao = SSR::decode_icao(msg_bin); + uf = static_cast(get_bin<0, 5>(msg_bin)); + RL = get_bin<8, 1>(msg_bin); + AQ = get_bin<13, 1>(msg_bin); + BDS = get_bin<14, 8>(msg_bin); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(icao) + Ret_J(uf) + Ret_J(RL) + Ret_J(AQ) + Ret_J(BDS) + return ret; + } + }; + //T_Parser(Surveillance_Interrogation_56) + + template + struct Reply_56 : with_AP, BDS_ { + PROP_T(std::string, icao, "01ABCD") + PROP_T(Downlink_Format, df, Downlink_Format::Short_Air_Air_Surveillance_0) + PROP_T(ACAS::Vertical_Status, vertical_status, ACAS::Vertical_Status::Airborne) + PROP_T(ACAS::CrossLink_Capability, Cross_link_capability, ACAS::CrossLink_Capability::NotSupported) // 是指在 UF=0 请求时应答 DF=16 的能力。当此 1 位字段设置为 1 时,支持交叉链接。否则,该字段将设置为 0。 + PROP_Bit_T(3, sensitivity_level, 0) // 灵敏度等级 (SL):3 位,它代表 ACAS 系统的灵敏度等级,但 0 表示 ACAS 不工作。 + PROP_T(ACAS::Reply_Information, ri, ACAS::Reply_Information::NoACASOperation) //回复ACAS类型 + PROP_Bit_T(13, altitude_code, 0) + void set(std::string& msg_bin) { + set_bin<0, 5>(msg_bin, static_cast(df)); + set_bin<5, 1>(msg_bin, static_cast(vertical_status)); + set_bin<8, 3>(msg_bin, static_cast(sensitivity_level)); + set_bin<13, 4>(msg_bin, static_cast(ri)); + set_bin<19, 13>(msg_bin, altitude_code); + } + void parse(std::string& msg_bin) { + icao = SSR::decode_icao(msg_bin); + get_bin<0, 5, Downlink_Format>(msg_bin, df); + get_bin<5, 1, ACAS::Vertical_Status>(msg_bin, vertical_status); + get_bin<8, 3>(msg_bin, sensitivity_level); + get_bin<13, 4, ACAS::Reply_Information>(msg_bin, ri); + get_bin<19, 13>(msg_bin, altitude_code); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(icao) + Ret_J(df) + Ret_J(vertical_status) + Ret_J(sensitivity_level) + Ret_J(ri) + Ret_J(altitude_code) + return ret; + } + }; + T_Parser(Reply_56) + + template + struct Surveillance_Interrogation_112 : with_AP, BDS_ { + PROP_T(std::string, icao, "01ABCD") + PROP_T(Uplink_Format, uf, Uplink_Format::Long_Air_Air_Surveillance_16) + PROP_Bit_T(1, RL, 0) // 应答长度 (RL):1 位,它定义了所需的应答格式:0 需要 DF=0 的应答,而 1 需要 DF=16 的应答 + PROP_Bit_T(1, AQ, 0) // 它包含控制应答中 RI 字段内容的代码 + void set(std::string& msg_bin) { + set_bin<0, 5>(msg_bin, static_cast(uf)); + set_bin<8, 1>(msg_bin, static_cast(RL)); + set_bin<13, 1>(msg_bin, static_cast(AQ)); + } + void parse(std::string& msg_bin) { + icao = SSR::decode_icao(msg_bin); + get_bin<0, 5, Uplink_Format>(msg_bin, uf); + get_bin<8, 1>(msg_bin, RL); + get_bin<13, 1>(msg_bin, AQ); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(icao) + Ret_J(uf) + Ret_J(RL) + Ret_J(AQ) + return ret; + } + }; + //T_Parser(Surveillance_Interrogation_112) + + template + struct Reply_112 : with_AP, BDS_ { + PROP_T(std::string, icao, "01ABCD") + PROP_T(Downlink_Format, df, Downlink_Format::Long_Air_Air_Surveillance_16) + PROP_T(ACAS::Vertical_Status, vertical_status, ACAS::Vertical_Status::Airborne) + PROP_Bit_T(3, sensitivity_level, 0) // 灵敏度等级 (SL):3 位,它代表 ACAS 系统的灵敏度等级,但 0 表示 ACAS 不工作。 + PROP_T(ACAS::Reply_Information, ri, ACAS::Reply_Information::NoACASOperation) //回复ACAS类型 + PROP_Bit_T(13, altitude_code, 0) + void set(std::string& msg_bin) { + set_bin<0, 5>(msg_bin, static_cast(df)); + set_bin<5, 1>(msg_bin, static_cast(vertical_status)); + set_bin<8, 3>(msg_bin, static_cast(sensitivity_level)); + set_bin<13, 4>(msg_bin, static_cast(ri)); + set_bin<19, 13>(msg_bin, altitude_code); + } + void parse(std::string& msg_bin) { + icao = SSR::decode_icao(msg_bin); + get_bin<0, 5, Downlink_Format>(msg_bin, df); + get_bin<5, 1, ACAS::Vertical_Status>(msg_bin, vertical_status); + get_bin<8, 3>(msg_bin, sensitivity_level); + get_bin<13, 4, ACAS::Reply_Information>(msg_bin, ri); + get_bin<19, 13>(msg_bin, altitude_code); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(icao) + Ret_J(df) + Ret_J(vertical_status) + Ret_J(sensitivity_level) + Ret_J(ri) + Ret_J(altitude_code) + return ret; + } + }; + T_Parser(Reply_112) + + template + struct UDS_3_0_Coordination_Interrogation { + static bool is(std::string& msg_bin) { + return get_bin<32, 4>(msg_bin) == 3 && get_bin<36, 4>(msg_bin) == 0; + } + PROP_Bit_T(1, multiple_threat, 0) //多重威胁位 (MTB):1 位,表示是否存在多重威胁。 + PROP_T(ACAS::Cancel_Vertical_RAC, cancel_vertical_RAC, ACAS::Cancel_Vertical_RAC::NoCancellation) + PROP_T(ACAS::Vertical_RAC, vertical_RAC, ACAS::Vertical_RAC::NoInformation) + PROP_T(ACAS::Cancel_Horizontal_RAC, cancel_horizontal_RAC, ACAS::Cancel_Horizontal_RAC::NoCancellation) + PROP_T(ACAS::Horizontal_RAC, horizontal_RAC, ACAS::Horizontal_RAC::NoInformation) + // 水平感应位 (HSB):5 位,使用汉明码和一个额外的奇偶校验位来检测 CHC 和 HRC 字段中的错误(最多 3 位)。 + PROP_Bit_T(5, HSB, 0) + // 垂直感应位 (VSB):4 位,使用汉明码和一个额外的奇偶校验位来检测 CVC 和 VRC 字段中的错误(最多 3 位)。 + PROP_Bit_T(5, VSB, 0) + // 飞机地址 (MID):24 位,包含查询 ACAS 飞机的 24 位飞机应答器地址。 + PROP_Bit_T(24, aircraft_address, 0) + void set(std::string& msg_bin) { + set_bin<32, 4>(msg_bin, 3); + set_bin<36, 4>(msg_bin, 0); + set_bin<41, 1>(msg_bin, multiple_threat); + set_bin<42, 2>(msg_bin, static_cast(cancel_vertical_RAC)); + set_bin<44, 2>(msg_bin, static_cast(vertical_RAC)); + set_bin<46, 3>(msg_bin, static_cast(cancel_horizontal_RAC)); + set_bin<49, 3>(msg_bin, static_cast(horizontal_RAC)); + set_bin<55, 5>(msg_bin, HSB); + set_bin<60, 4>(msg_bin, VSB); + set_bin<64, 24>(msg_bin, aircraft_address); + } + void parse(std::string& msg_bin) { + get_bin<41, 1>(msg_bin, multiple_threat); + get_bin<42, 2, ACAS::Cancel_Vertical_RAC>(msg_bin, cancel_vertical_RAC); + get_bin<44, 2, ACAS::Vertical_RAC>(msg_bin, vertical_RAC); + get_bin<46, 3, ACAS::Cancel_Horizontal_RAC>(msg_bin, cancel_horizontal_RAC); + get_bin<49, 3, ACAS::Horizontal_RAC>(msg_bin, horizontal_RAC); + get_bin<55, 5>(msg_bin, HSB); + get_bin<60, 4>(msg_bin, VSB); + get_bin<64, 24>(msg_bin, aircraft_address); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(multiple_threat) + Ret_J(cancel_vertical_RAC) + Ret_J(vertical_RAC) + Ret_J(cancel_horizontal_RAC) + Ret_J(horizontal_RAC) + Ret_J(HSB) + Ret_J(VSB) + Ret_J(aircraft_address) + return ret; + } + }; + //T_Parser(UDS_3_0_Coordination_Interrogation) + + // 表明多个或一个威胁 + // 当 ARA 第一位(MSG 位 41)为 1 且 MTE 为 0 或 1 时: + template + struct UDS_3_1_ARA1 { + PROP_Bit_T(1, MTE, 0) // 设为1多个威胁 设为0一个威胁 + PROP_Bit_T(1, corrective_preventive, 0) // Bit 42: RA is corrective (1) or preventive (0) + PROP_Bit_T(1, downward_upward, 0) // Bit 43: RA is downward sense (1) or upward sense (0) + PROP_Bit_T(1, increased_rate, 0) // Bit 44: RA is increased rate (1) or not (0) + PROP_Bit_T(1, sense_reversal, 0) // Bit 45: RA is a sense reversal (1) or not (0) + PROP_Bit_T(1, altitude_crossing, 0) // Bit 46: RA is altitude crossing (1) or not (0) + PROP_Bit_T(1, positive_vertical_speed_limit, 0) //Bit 47: RA is positive (1) or vertical speed limit (0) + static bool is(std::string& msg_bin) { + return get_bin<40, 1>(msg_bin) == 1; + } + void set(std::string& msg_bin) { + set_bin<40, 1>(msg_bin, 1); + set_bin<59, 1>(msg_bin, MTE); + set_bin<41, 1>(msg_bin, corrective_preventive); + set_bin<42, 1>(msg_bin, downward_upward); + set_bin<43, 1>(msg_bin, increased_rate); + set_bin<44, 1>(msg_bin, sense_reversal); + set_bin<45, 1>(msg_bin, altitude_crossing); + set_bin<46, 1>(msg_bin, positive_vertical_speed_limit); + } + void parse(std::string& msg_bin) { + get_bin<59, 1>(msg_bin, MTE); + get_bin<41, 1>(msg_bin, corrective_preventive); + get_bin<42, 1>(msg_bin, downward_upward); + get_bin<43, 1>(msg_bin, increased_rate); + get_bin<44, 1>(msg_bin, sense_reversal); + get_bin<45, 1>(msg_bin, altitude_crossing); + get_bin<46, 1>(msg_bin, positive_vertical_speed_limit); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(corrective_preventive) + Ret_J(downward_upward) + Ret_J(increased_rate) + Ret_J(sense_reversal) + Ret_J(altitude_crossing) + Ret_J(positive_vertical_speed_limit) + return ret; + } + }; + //T_Parser(UDS_3_1_ARA1) + + + + + // 当 ARA 第一位(MSG 位 41)为 0 且 MTE 为 1 时: + template + struct UDS_3_1_ARA2 { + PROP_Bit_T(1, upward_correction, 0)//Bit 42: RA requires a correction in the upward sense (1) or not (0) + PROP_Bit_T(1, climb, 0)//Bit 43: RA requires a positive climb (1) or not (0) + PROP_Bit_T(1, downward_correction, 0)//Bit 44: RA requires a correction in the downward sense (1) or not (0) + PROP_Bit_T(1, descent, 0)//Bit 45: RA requires a positive descent (1) or not (0) + PROP_Bit_T(1, crossing, 0)//Bit 46: RA requires a crossing (1) or not (0) + PROP_Bit_T(1, reversal, 0)//Bit 47: RA is a sense reversal (1) or not (0) + static bool is(std::string& msg_bin) { + return get_bin<40, 1>(msg_bin) == 0 && get_bin<59, 1>(msg_bin) == 1; + } + void set(std::string& msg_bin) { + set_bin<40, 1>(msg_bin, 0); + set_bin<59, 1>(msg_bin, 1); //MTE + set_bin<41, 1>(msg_bin, upward_correction); + set_bin<42, 1>(msg_bin, climb); + set_bin<43, 1>(msg_bin, downward_correction); + set_bin<44, 1>(msg_bin, descent); + set_bin<45, 1>(msg_bin, crossing); + set_bin<46, 1>(msg_bin, reversal); + } + void parse(std::string& msg_bin) { + get_bin<41, 1>(msg_bin, upward_correction); + get_bin<42, 1>(msg_bin, climb); + get_bin<43, 1>(msg_bin, downward_correction); + get_bin<44, 1>(msg_bin, descent); + get_bin<45, 1>(msg_bin, crossing); + get_bin<46, 1>(msg_bin, reversal); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(upward_correction); + Ret_J(climb); + Ret_J(downward_correction); + Ret_J(descent); + Ret_J(crossing); + Ret_J(reversal); + return ret; + } + }; + //T_Parser(UDS_3_1_ARA2) + // 当 ARA 第一位(MSG 位 41)为 0 且 MTE 为 0 时,不会生成垂直 RA。 + + template + struct UDS_3_1_Coordination_Interrogation { + PROP_Bit_T(1, Do_not_pass_below, 0) + PROP_Bit_T(1, Do_not_pass_above, 0) + PROP_Bit_T(1, Do_not_pass_left, 0) + PROP_Bit_T(1, Do_not_pass_right, 0) + PROP_Bit_T(1, RA_terminated_indicator, 0) + PROP_Bit_T(13, Mode_A_identity_code, 0) + PROP_Bit_T(13, Mode_C_altitude_code, 0) + static bool is(std::string& msg_bin) { + return get_bin<32, 4>(msg_bin) == 3 && get_bin<36, 4>(msg_bin) == 1; + } + void set(std::string& msg_bin) { + set_bin<32, 4>(msg_bin, 3); + set_bin<36, 4>(msg_bin, 1); + set_bin<54, 1>(msg_bin, Do_not_pass_below); + set_bin<55, 1>(msg_bin, Do_not_pass_above); + set_bin<56, 1>(msg_bin, Do_not_pass_left); + set_bin<57, 1>(msg_bin, Do_not_pass_right); + set_bin<58, 1>(msg_bin, RA_terminated_indicator); + set_bin<62, 13>(msg_bin, Mode_A_identity_code); + set_bin<75, 13>(msg_bin, Mode_C_altitude_code); + } + void parse(std::string& msg_bin) { + get_bin<54, 1>(msg_bin, Do_not_pass_below); + get_bin<55, 1>(msg_bin, Do_not_pass_above); + get_bin<56, 1>(msg_bin, Do_not_pass_left); + get_bin<57, 1>(msg_bin, Do_not_pass_right); + get_bin<58, 1>(msg_bin, RA_terminated_indicator); + get_bin<62, 13>(msg_bin, Mode_A_identity_code); + get_bin<75, 13>(msg_bin, Mode_C_altitude_code); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(Do_not_pass_below) + Ret_J(Do_not_pass_above) + Ret_J(Do_not_pass_left) + Ret_J(Do_not_pass_right) + Ret_J(RA_terminated_indicator) + Ret_J(Mode_A_identity_code) + Ret_J(Mode_C_altitude_code) + return ret; + } + }; + //T_Parser(UDS_3_1_Coordination_Interrogation) + + + template + struct UDS_3_2_Coordination_Interrogation { + PROP_Bit_T(4, U_definition_subfield_1, 0) + PROP_Bit_T(4, U_definition_subfield_2, 0) + PROP_Bit_T(24, Aircraft_address, 0) + void set(std::string& msg_bin) { + set_bin<32, 4>(msg_bin, U_definition_subfield_1); + set_bin<36, 4>(msg_bin, U_definition_subfield_2); + set_bin<64, 24>(msg_bin, Aircraft_address); + } + void parse(std::string& msg_bin) { + get_bin<32, 4>(msg_bin, U_definition_subfield_1); + get_bin<36, 4>(msg_bin, U_definition_subfield_2); + get_bin<64, 24>(msg_bin, Aircraft_address); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(U_definition_subfield_1) + Ret_J(U_definition_subfield_2) + Ret_J(Aircraft_address) + return ret; + } + }; + //T_Parser(UDS_3_2_Coordination_Interrogation) + + + template + struct VDS_3_0_ARA1 : UDS_3_1_ARA1 {}; + T_Parser(VDS_3_0_ARA1) + + template + struct VDS_3_0_ARA2 : UDS_3_1_ARA2 {}; + T_Parser(VDS_3_0_ARA2) + + + + template + struct VDS_3_0_Coordination_Reply { + PROP_Bit_T(1, Do_not_pass_below, 0) + PROP_Bit_T(1, Do_not_pass_above, 0) + PROP_Bit_T(1, Do_not_pass_left, 0) + PROP_Bit_T(1, Do_not_pass_right, 0) + PROP_Bit_T(1, RA_terminated_indicator, 0) + static bool is(std::string& msg_bin) { + return get_bin<32, 4>(msg_bin) == 3 && get_bin<36, 4>(msg_bin) == 0; + } + void set(std::string& msg_bin) { + set_bin<32, 4>(msg_bin, 3); + set_bin<36, 4>(msg_bin, 0); + set_bin<54, 1>(msg_bin, Do_not_pass_below); + set_bin<55, 1>(msg_bin, Do_not_pass_above); + set_bin<56, 1>(msg_bin, Do_not_pass_left); + set_bin<57, 1>(msg_bin, Do_not_pass_right); + set_bin<58, 1>(msg_bin, RA_terminated_indicator); + } + void parse(std::string& msg_bin) { + get_bin<54, 1>(msg_bin, Do_not_pass_below); + get_bin<55, 1>(msg_bin, Do_not_pass_above); + get_bin<56, 1>(msg_bin, Do_not_pass_left); + get_bin<57, 1>(msg_bin, Do_not_pass_right); + get_bin<58, 1>(msg_bin, RA_terminated_indicator); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(Do_not_pass_below) + Ret_J(Do_not_pass_above) + Ret_J(Do_not_pass_left) + Ret_J(Do_not_pass_right) + Ret_J(RA_terminated_indicator) + return ret; + } + }; + T_Parser(VDS_3_0_Coordination_Reply) +} + +#endif diff --git a/SSR/ACAS/ACAS_9_56.hpp b/SSR/ACAS/ACAS_9_56.hpp new file mode 100644 index 0000000..7496b3d --- /dev/null +++ b/SSR/ACAS/ACAS_9_56.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "../Mode_S/Mode_S.h" + +namespace SSR { +// common b adsb 都有用到 +template +struct ACAS_Active_Resolution_Advisory_9_56{ + // resolution advisory complements (RAC) + PROP_Bit_T(1, Do_not_pass_below, 0) + PROP_Bit_T(1, Do_not_pass_above, 0) + PROP_Bit_T(1, Do_not_turn_left, 0) + PROP_Bit_T(1, Do_not_turn_right, 0) + PROP_Bit_T(1, RA_terminated, 0) //RA终止位指示先前生成的RA是否已终止。 + PROP_T(Threat_Type_Indicator, threat_Type_Indicator, Threat_Type_Indicator::No_Identity_Data) + PROP_Bit_T(26, threat_data, 0) + + ACAS_SubType parse_sum(std::string& msg_bin) { + int m9 = get_bin<32 + 9, 1>(msg_bin); + int m28 = get_bin<32 + 28, 1>(msg_bin); + if (m9 == 0 && m28 == 0) return ACAS_SubType::NO_RA; + if (m9 == 0 && m28 == 1) return ACAS_SubType::Mult_below_above; + if (m9 == 1 && m28 == 0) return ACAS_SubType::One_threat; + if (m9 == 1 && m28 == 1) return ACAS_SubType::Mult_same_direction; + std::cerr << "BDS30_ACAS_SubType parse_sum(std::string& msg) parse error!\n"; + return ACAS_SubType::NO_RA; + } + + + void parse(std::string& msg_bin) { + get_bin<32 + 22, 1>(msg_bin, Do_not_pass_below); + get_bin<32 + 23, 1>(msg_bin, Do_not_pass_above); + get_bin<32 + 24, 1>(msg_bin, Do_not_turn_left); + get_bin<32 + 25, 1>(msg_bin, Do_not_turn_right); + get_bin<32 + 28, 2, Threat_Type_Indicator>(msg_bin, threat_Type_Indicator); + get_bin<32 + 30, 26>(msg_bin, threat_data); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(Do_not_pass_below) + Ret_J(Do_not_pass_above) + Ret_J(Do_not_turn_left) + Ret_J(Do_not_turn_right) + Ret_J(threat_Type_Indicator) + Ret_J(threat_data) + return ret; + } + + void set(std::string& msg_bin) { + set_bin<32, 4>(msg_bin, 3); + set_bin<36, 4>(msg_bin, 0); + set_bin<32 + 22, 1>(msg_bin, Do_not_pass_below); + set_bin<32 + 23, 1>(msg_bin, Do_not_pass_above); + set_bin<32 + 24, 1>(msg_bin, Do_not_turn_left); + set_bin<32 + 25, 1>(msg_bin, Do_not_turn_right); + set_bin<60, 2>(msg_bin, static_cast(threat_Type_Indicator)); + set_bin<62, 26>(msg_bin, threat_data); + } +}; +T_Parser(ACAS_Active_Resolution_Advisory_9_56) + +} \ No newline at end of file diff --git a/SSR/ADS_B/ADS_B.cpp b/SSR/ADS_B/ADS_B.cpp new file mode 100644 index 0000000..a441957 --- /dev/null +++ b/SSR/ADS_B/ADS_B.cpp @@ -0,0 +1,4 @@ +#include "ADS_B.h" +namespace ADS_B { + +} diff --git a/SSR/ADS_B/ADS_B.h b/SSR/ADS_B/ADS_B.h new file mode 100644 index 0000000..176459a --- /dev/null +++ b/SSR/ADS_B/ADS_B.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include "ADS_B.hpp" +// DF = 17 18 19 + +namespace SSR::ADS_B { + + template using Base = ADS_B_T::ADS_B_Base_Builder; + template using BDS09 = ADS_B_T::BDS09_Base; + template using BDS65 = ADS_B_T::BDS65_Base; + template using BDS61 = ADS_B_T::BDS61_Base; + // 身份 + ADD_112_2(Aircraft_identification_BDS08, ADS_B_T::ADS_B_Base_Builder, ADS_B_T::BDS08_Aircraft_identification) + + // 空中位置 + ADD_112_3(Airborne_Position_BDS05_v01, Base, ADS_B_T::BDS05_Airborne_Position, ADS_B_T::BDS05_v01) + ADD_112_3(Airborne_Position_BDS05_v2, Base, ADS_B_T::BDS05_Airborne_Position, ADS_B_T::BDS05_v2) + + // 地表位置 + ADD_112_2(Surface_Position_BDS06, Base, ADS_B_T::BDS06_Surface_Position) + + // 速度消息 + ADD_112_4(Ground_Speed_BDS09_v0, Base, BDS09, ADS_B_T::BDS09_Ground_Speed, ADS_B_T::BDS09_v0) + ADD_112_4(Ground_Speed_BDS09_v12, Base, BDS09, ADS_B_T::BDS09_Ground_Speed, ADS_B_T::BDS09_v12) + ADD_112_4(Airspeed_BDS09_v0, Base, BDS09, ADS_B_T::BDS09_Airspeed, ADS_B_T::BDS09_v0) + ADD_112_4(Airspeed_BDS09_v12, Base, BDS09, ADS_B_T::BDS09_Airspeed, ADS_B_T::BDS09_v12) + + // ADS-B 版本消息 + ADD_112_4(Airborne_Operation_Status_BDS65_ver1, Base, BDS65, ADS_B_T::BDS65_Airborne, + ADS_B_T::BDS65_ver1) + ADD_112_4(Airborne_Operation_Status_BDS65_ver2, Base, BDS65, ADS_B_T::BDS65_Airborne, + ADS_B_T::BDS65_ver2) + ADD_112_4(Surface_Operation_Status_BDS65_ver1, Base, BDS65, ADS_B_T::BDS65_Surface, + ADS_B_T::BDS65_ver1) + ADD_112_4(Surface_Operation_Status_BDS65_ver2, Base, BDS65, ADS_B_T::BDS65_Surface, + ADS_B_T::BDS65_ver2) + + // tc=28 飞机状态消息 + ADD_112_3(Extended_Squitter_Aircraft_Status_Mode_A, Base, BDS61, ADS_B_T::BDS61_Mode_A) + ADD_112_3(Extended_Squitter_Aircraft_Status_RA, Base, BDS61, ADS_B_T::BDS61_TCAS_ACAS_RA_Broadcast) + + +} diff --git a/SSR/ADS_B/ADS_B.hpp b/SSR/ADS_B/ADS_B.hpp new file mode 100644 index 0000000..cb53f3f --- /dev/null +++ b/SSR/ADS_B/ADS_B.hpp @@ -0,0 +1,891 @@ +#ifndef ADS_B_HPP +#define ADS_B_HPP +#include +#include + +#include "../ACAS/ACAS_9_56.hpp" +#include "../Mode_S/Mode_S.hpp" +#include "SSR/Msg.h" +#include "SSR/export_type.h" + + +namespace SSR::ADS_B_T { + template + struct ADS_B_Base_Builder : with_PI { + PROP_T(std::string, icao, "01ABCD") + PROP_T(Downlink_Format, df, Downlink_Format::Extended_Squitter_17) + PROP_T(int, tc, 0) + PROP_T(Transponder_Capability, transponder_capability, Transponder_Capability::Level_2_plus_airborne) + virtual Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(icao) + Ret_J(df) + ret.append(Psc::JSON("tc", std::to_string(tc) + " " + Psc::to_string(type_code_to_enum(tc)))); + Ret_J(transponder_capability) return ret; + } + virtual void parse(std::string& msg_bin) { + this->df = SSR::decode_df(msg_bin); + this->transponder_capability = SSR::decode_transponder_capability(msg_bin); + this->icao = SSR::decode_icao(msg_bin); + tc = SSR::type_code(msg_bin); + } + void set(std::string& msg_bin) { + check_icao(icao); + SSR::set_df(msg_bin, this->df); + SSR::set_transponder_capability(msg_bin, this->transponder_capability); + SSR::set_icao(msg_bin, this->icao); + SSR::set_type_code(msg_bin, this->tc); + } + }; + T_Parser(ADS_B_Base_Builder) + + template struct CPR_Air; + template struct CPR_Surface; + + + struct CPR_Data { + using time_t = double; + struct Info { + // 解码cpr判断过期使用的时间 + // 可以使用报文的时间 或者收到消息的系统的时间(当一批报文一起来,起不到筛选作用) + time_t time{}; + std::shared_ptr msg; + void reset() { + msg = nullptr; + time = 0; + } + + Psc::JSON to_json() + { + auto ret = Psc::JSON::object(); + ret.append({"time", time}); + ret.append({"msg", msg ? msg->to_Json() : nullptr}); + return ret; + } + + std::string to_string() + { + if (msg == nullptr) + { + return "nullptr"; + } + return std::to_string(time) + " " + msg->to_string(); + } + } even_msg, odd_msg; + time_t last_parse_pos_time = 0; + + std::function(const std::string&, const std::string&, time_t, time_t)> global_parse = nullptr; + std::function(const std::string&, double lat, double lon, time_t, time_t)> locale_parse = nullptr; + std::function encode = nullptr; + std::optional pos; + std::optional cache_ok_pos; + CPR::Frame cpr_format = CPR::Frame::Even; + void set_pos(std::optional new_pos, time_t cur_time) { + pos = new_pos; + if (new_pos != std::nullopt) { + cache_ok_pos = new_pos; + } + last_parse_pos_time = cur_time; + } + }; + + + template + struct CPR_MSG : CPR_Data { + virtual ~CPR_MSG() = default; + PROP_T_FUN(RET, lon, std::nullopt) + PROP_T_FUN(RET, lat, std::nullopt) + PROP_T_FUN(CPR::Frame, cpr_format, CPR::Frame::Even); + std::string current_state() { + std::ostringstream oss; + oss << "{" << std::endl; + oss << "\tpos:" << (pos.has_value() ? pos->to_string() : "null") << std::endl; + oss << "\todd_msg: " + odd_msg.to_string() << std::endl; + oss << "\teven_msg: " + even_msg.to_string() << std::endl; + oss << "}" << std::endl; + return oss.str(); + } + + Psc::JSON current_state_json() { + Psc::JSON ret = Psc::JSON::object(); + ret.append({"pos", (pos.has_value() ? pos->to_string() : "null")}); + ret.append({"odd_msg", odd_msg.to_json()}); + ret.append({"even_msg", even_msg.to_json()}); + return ret; + } + + + std::string get_cpr_log(std::string type, std::string icao, std::string msg_hex, CPR::Frame f, std::string before_state, std::string after_state) { + return + "\n===============[" + type + "][" + icao +"]======================\n" + "当前收到的消息 msg: " + msg_hex + " frame:" + Psc::to_string(f) + " \n" + + "先前的状态:\n" + before_state + + "现在的状态:\n" + current_state() + + "\n===============[" + type + "][" + icao +"]======================\n" + ; + } + + + // 虽然消息已经存在当天的秒数 + RET parse_message(const std::shared_ptr& msg, double time_s, CPR::D max_speed_m_s, CPR::D max_time_s, const SSR::CPR_CB& cb) { + const std::string& msg_bin = msg->msg_bin; + std::string msg_hex = bin2hex(msg_bin); + std::string icao = msg_hex.substr(2, 6); + Info *that, *other; + auto f = get_cpr_format(); + auto before_state = current_state(); + if (f == CPR::Frame::Even) { + //std::cout << "even\n"; + that = &even_msg; + other = &odd_msg; + } else { + //std::cout << "odd\n"; + that = &odd_msg; + other = &even_msg; + } + that->msg = msg; + that->time = time_s; + Log_Type log_type({}, {{"ICAO", icao}, {"Frame_type", Psc::to_string(f)}}); + std::optional& old_pos = pos; + if (old_pos.has_value()) { + if (std::abs(that->time - last_parse_pos_time) < max_time_s) { + auto new_pos = locale_parse(that->msg->msg_bin, old_pos->lat, old_pos->lon, that->time, last_parse_pos_time); + if (new_pos.has_value()) { + auto distance_m = CPR::haversine(old_pos.value(), new_pos.value()); + auto used_time_s = time_s - last_parse_pos_time; + auto max_distance = used_time_s * max_speed_m_s; + if (distance_m < max_distance) { + set_pos(new_pos, time_s); + cb(SSR::CPR_Error_Type::Normal, get_cpr_log("本地位置解码", icao, msg_hex, f, before_state, current_state()), msg); + // 解码成功返回 + if (!new_pos.has_value()) return std::nullopt; + set_pos(new_pos, time_s); + return Pos_Ret(new_pos.value(), that->msg, old_pos.value()); + } else + { + // 尝试全局解码 + cb(SSR::CPR_Error_Type::Local_Speed_Error, get_cpr_log("本地位置解码速度异常舍弃, 尝试全局解码", icao, msg_hex, f, before_state, current_state()) + + " " + VAR_STR_4(distance_m, max_distance, used_time_s, max_speed_m_s), msg); + } + } else { + cb(SSR::CPR_Error_Type::Unknow_Error, get_cpr_log("未知错误", icao, msg_hex, f, before_state, current_state()), msg); + } + } else { + cb(SSR::CPR_Error_Type::Local_Time_Space_Too_Long, get_cpr_log("本地位置解码时间异常, 尝试全局解码", icao, msg_hex, f, before_state, current_state()), msg); + } + } + // 本地解码失败走到这里 尝试全局解码 + if(other->msg){ + if (std::abs(that->time - other->time) < max_time_s) { + RET new_pos = global_parse(that->msg->msg_bin, other->msg->msg_bin, that->time, other->time); + if (new_pos.has_value()) { + if (old_pos.has_value()) { + auto distance = CPR::haversine(old_pos.value(), new_pos.value()); + auto used_time_s = time_s - other->time; + auto max_distance = used_time_s * max_speed_m_s; + if (distance < max_distance) + { + cb(SSR::CPR_Error_Type::Normal, get_cpr_log("全局位置解码", icao, msg_hex, f, before_state, current_state()), msg); + last_parse_pos_time = time_s; + set_pos(new_pos, time_s); + return Pos_Ret(new_pos.value(), that->msg, other->msg); + } else + { + // 视为失败 + cb(SSR::CPR_Error_Type::Global_Speed_Error, get_cpr_log("全局位置解码速度异常,直接舍弃", icao, msg_hex, f, before_state, current_state()) + + " " + VAR_STR_4(distance, max_distance, used_time_s, max_speed_m_s), msg); + } + } else { + // 第一次全局解码的位置,直接视为成功 + set_pos(new_pos, time_s); + return Pos_Ret(new_pos.value(), that->msg, other->msg); + } + } else { + cb(SSR::CPR_Error_Type::Unknow_Error, get_cpr_log("未知错误", icao, msg_hex, f, before_state, current_state()) + + " " + , msg); + } + + } else { + cb(SSR::CPR_Error_Type::Global_Time_Space_Too_Long, get_cpr_log("本地位置解码时间异常, 尝试全局解码", icao, msg_hex, f, before_state, current_state()), msg); + } + } else { + // 消息不足 放弃解码 + cb(SSR::CPR_Error_Type::Msg_Lack, get_cpr_log(" 消息不足 放弃解码", icao, msg_hex, f, before_state, current_state()), msg); + } + // 两种解码尝试都失败 尝试这里 + last_parse_pos_time = 0; + pos = std::nullopt; + other->reset(); + return std::nullopt; + } + + virtual void parse(std::string& msg_bin) { + get_bin<53, 1, CPR::Frame>(msg_bin, cpr_format); + } + virtual Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(pos) + Ret_J(cpr_format) + return ret; + } + virtual void set(std::string& msg_bin) { + set_bin<53, 1>(msg_bin, static_cast(this->cpr_format)); + if (pos.has_value()) + { + set_bin<54, 34>(msg_bin, encode(this->pos->lat, this->pos->lon, this->cpr_format)); + } + } + private: + std::string sub_type_name() { + if (std::is_base_of, That>::value) { + return "airbore_postation"; + } else if (std::is_base_of, That>::value) { + return "surface_postation"; + } else { + return "unknown_CPR"; + } + } + }; + + template + struct CPR_Air : CPR_MSG { + CPR_Air() { + this->global_parse = CPR::airborne_position; + this->locale_parse = CPR::airborne_position_with_ref; + this->encode = CPR::lat_lon_airborne_34; + } + ~CPR_Air() override = default; + }; + + template + struct CPR_Surface : CPR_MSG { + CPR_Surface() { + this->global_parse = [this](const std::string& msg0, const std::string& msg1, time_t t0, time_t t1)->std::optional{ + if(!base_station_pos.has_value()){ + return std::nullopt; + } + return CPR::surface_position(msg0, msg1, t0, t1, base_station_pos.value()); + }; + this->locale_parse = CPR::surface_position_with_ref; + this->encode = CPR::lat_lon_surface_34; + } + std::optional base_station_pos; + }; + + + template + struct BDS05_v01 { + PROP_Bit_T(1, single_antenna_flag, 0) + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(single_antenna_flag) + return ret; + } + void parse(std::string& msg_bin) { single_antenna_flag = get_bin<39, 1>(msg_bin); } + void set(std::string& msg_bin) { set_bin<39, 1>(msg_bin, single_antenna_flag); } + }; + T_Parser(BDS05_v01) + template + struct BDS05_v2 { + PROP_Bit_T(1, NICb, 0) + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(NICb) + return ret; + } + void parse(std::string& msg_bin) { NICb = get_bin<39, 1>(msg_bin); } + void set(std::string& msg_bin) { set_bin<39, 1>(msg_bin, NICb); } + }; + T_Parser(BDS05_v2) + + // TC=9–18:空中位置,气压高度编码(以英尺为单位) + // TC=20–22:机载位置,GNSS 高度编码(以米为单位) + template + struct BDS05_Airborne_Position : BDS05, CPR_Air { + PROP_T(Altitude_type, altitude_type, Altitude_type::Baro); + PROP_T(double, alt_meter, 0); + PROP_Bit_T(1, time, 0) + PROP_T(Surveillance_Status, surveillance_status, Surveillance_Status::NoCondition) + void set(std::string& msg_bin) override { + CPR_Air::set(msg_bin); + set_type_code(msg_bin, altitude_type == Altitude_type::Baro ? 9 : 20); + set_bin<37, 2>(msg_bin, static_cast(surveillance_status)); + set_bin<40, 12>(msg_bin, encode_altitude_code_12(alt_meter, altitude_type)); + set_bin<52, 1>(msg_bin, time); + } + void parse(std::string& msg_bin) override { + CPR_Air::parse(msg_bin); + auto tc = get_type_code(msg_bin); + if (tc >= 9 && tc <= 18) { + altitude_type = Altitude_type::Baro; + } + if (tc >= 20 && tc <= 22) { + altitude_type = Altitude_type::GNSS; + } + get_bin<37, 2, Surveillance_Status>(msg_bin, surveillance_status); + // 40 在版本1 2的区别中 + alt_meter = decode_altitude_12_meter(msg_bin.substr(40, 12), altitude_type); + get_bin<52, 1>(msg_bin, time); + } + Psc::JSON toJson() override { + Psc::JSON ret = Psc::JSON::object(); + ret.append_list(CPR_Air::toJson().children); + Ret_J(surveillance_status) + Ret_J(alt_meter) + Ret_J(time) + return ret; + } + }; + T_Parser(BDS05_Airborne_Position) + template + struct BDS06_Surface_Position : BDS06, CPR_Surface { + PROP_T(Altitude_type, altitude_type, Altitude_type::Baro) + PROP_T(double, altitude, 0) + PROP_T(double, movement, 0) + PROP_Bit_T(1, time, 0) + PROP_Range_T(double, 0.0, 360.0, track_degree, 0) + void parse(std::string& msg_bin) override { + CPR_Surface::parse(msg_bin); + auto tc = get_type_code(msg_bin); + if (tc >= 9 && tc <= 18) { + altitude_type = Altitude_type::Baro; + } + if (tc >= 20 && tc <= 22) { + altitude_type = Altitude_type::GNSS; + } + movement = decode_movement_7(msg_bin.substr(37, 7)); + track_degree = decode_ground_track_7(msg_bin.substr(44, 8)); + get_bin<52, 1>(msg_bin, time); + } + Psc::JSON toJson() override { + Psc::JSON ret = Psc::JSON::object(); + ret.append_list(CPR_Surface::toJson().children); + Ret_J(altitude_type) + Ret_J(altitude) + Ret_J(movement) + Ret_J(time) + return ret; + } + void set(std::string& msg_bin) override { + CPR_Surface::set(msg_bin); + set_type_code(msg_bin, altitude_type == Altitude_type::Baro ? 9 : 20); + set_bin<37, 7>(msg_bin, encode_movement_7(movement)); + set_bin<44, 1>(msg_bin, 1); + set_bin<45, 7>(msg_bin, encode_ground_track_7(track_degree)); + set_bin<52, 1>(msg_bin, time); + } + }; + T_Parser(BDS06_Surface_Position) + template + struct BDS08_Aircraft_identification : BDS08 { + // 八位 #ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789###### + // 不同时间飞行同一航线的不同飞机将共享相同的呼号。 + PROP_T(std::string, call_sign, "???") + PROP_T(Vortex_Type, vortex_type, Vortex_Type::Glider) + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(call_sign) + Ret_J(vortex_type) + return ret; + } + void parse(std::string& msg_bin) { + call_sign = SSR::get_call_sign(msg_bin); + vortex_type = SSR::parse_vortex_type(msg_bin); + } + void set(std::string& msg_bin) { + if (call_sign.size() != 8 || !std::all_of(call_sign.begin(), call_sign.end(), [](char c) { + //std::isxdigit(c) + return std::isupper(c) || std::isdigit(c); + })) + { + CE(true, "不合法的call_sign:" + call_sign) + } + SSR::set_call_sign(msg_bin, call_sign); + auto tc_ca = map1[vortex_type]; + set_type_code(msg_bin, tc_ca.first); + set_category(msg_bin, tc_ca.second); + } + }; + T_Parser(BDS08_Aircraft_identification) + + // BDS09 tc=19 速度报文 + template + struct BDS09_v0 { + PROP_Bit_T(3, NUCv, 0) + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(NUCv) + return ret; + } + void parse(std::string& msg_bin) { NUCv = get_bin<42, 3>(msg_bin); } + void set(std::string& msg_bin) { set_bin<42, 3>(msg_bin, NUCv); } + }; + T_Parser(BDS09_v0) + template + struct BDS09_v12 { + PROP_Bit_T(3, NACv, 0) + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(NACv) + return ret; + } + void parse(std::string& msg_bin) { NACv = get_bin<42, 3>(msg_bin); } + void set(std::string& msg_bin) { set_bin<42, 3>(msg_bin, NACv); } + }; + T_Parser(BDS09_v12) + template + struct BDS09_Base : BDS09 { + PROP_T(Source, vertical_rate_source, Source::GNSS) + PROP_T(RET, vertical_rate, std::nullopt) + PROP_Bit_T(1, intent_change_flag, 0) + PROP_Bit_T(1, IFR_capability_flag, 0) + PROP_T(RET, GNSS_barometric_altitudes_difference, std::nullopt); + void set(std::string& msg_bin) { + set_bin<32, 5>(msg_bin, 19); + set_bin<40, 1>(msg_bin, intent_change_flag); + set_bin<41, 1>(msg_bin, IFR_capability_flag); + set_bin<67, 1>(msg_bin, static_cast(vertical_rate_source)); + if (vertical_rate.has_value()) set_bin<68, 10>(msg_bin, vertical_rate_code_10(vertical_rate.value())); + if (GNSS_barometric_altitudes_difference.has_value()) + set_bin<80, 8>( + msg_bin, GNSS_barometric_altitudes_difference_8(GNSS_barometric_altitudes_difference.value())); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(intent_change_flag) + Ret_J(IFR_capability_flag) + Ret_J(vertical_rate_source) + Ret_J(vertical_rate) + Ret_J(GNSS_barometric_altitudes_difference) + return ret; + } + void parse(std::string& msg_bin) { + intent_change_flag = get_bin<40, 1>(msg_bin); + IFR_capability_flag = get_bin<41, 1>(msg_bin); + vertical_rate_source = get_bin<67, 1, Source>(msg_bin); + int vertical_rate_v = get_bin<69, 9>(msg_bin); + if (vertical_rate_v != 0) + { + int sign = get_bin<68, 1>(msg_bin) ? -1 : 1; + vertical_rate = sign * (vertical_rate_v - 1) * 64; + } + int diff_v = get_bin<81, 7>(msg_bin); + if (diff_v != 0) + { + int sign = get_bin<80, 1>(msg_bin) ? -1 : 1; + GNSS_barometric_altitudes_difference = sign * diff_v; + } + } + }; + T_Parser(BDS09_Base) + + + template + struct BDS09_Ground_Speed { + PROP_T(Ground_Speed_Type, sub_type, Ground_Speed_Type::Ground_Normal); + PROP_Range_T(double, 0.0, 360.0, magnetic_heading, 0.0); + PROP_T(double, speed, 0); + void set(std::string& msg_bin) { + set_bin<37, 3>(msg_bin, static_cast(sub_type)); + set_bin<45, 22>(msg_bin, encode_velocity_22(sub_type, magnetic_heading, speed)); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(sub_type) + Ret_J(magnetic_heading) + Ret_J(speed) + return ret; + } + void parse(std::string& msg_bin) { + sub_type = get_bin<37, 3, Ground_Speed_Type>(msg_bin); + Ground_Speed ground_speed = surface_velocity(msg_bin, sub_type); + magnetic_heading = ground_speed.angle; + speed = ground_speed.speed; + } + }; + T_Parser(BDS09_Ground_Speed) + template + struct BDS09_Airspeed { + PROP_Range_T(RET, 0.0, 360.0, magnetic_heading, std::nullopt); + PROP_T(Air_Speed_Code_Type, sub_type, Air_Speed_Code_Type::Air_Normal); + PROP_T(Airspeed_type, airspeed_type, Airspeed_type::Indicated_airspeed_IAS); + PROP_T(RET, speed, std::nullopt); + void set(std::string& msg_bin) { + set_bin<37, 3>(msg_bin, static_cast(sub_type)); + if (magnetic_heading.has_value()) { + int encoded_heading = static_cast(std::round(magnetic_heading.value() * 1024 / 360)); + std::bitset<10> HDG_bits(encoded_heading); + set_bin<45, 11>(msg_bin, "1" + HDG_bits.to_string()); + } + set_bin<56, 1>(msg_bin, static_cast(airspeed_type)); + if (speed.has_value()) { + double airspeed = speed.value(); + int encoded_airspeed = 0; + if (sub_type == Air_Speed_Code_Type::Air_Normal) { + // Sub-type 1: Speed = Decimal value - 1 + encoded_airspeed = static_cast(std::round(airspeed)) + 1; + } else if (sub_type == Air_Speed_Code_Type::Air_Supersonic) { + // Sub-type 2: Speed = 4 x (Decimal value - 1) + encoded_airspeed = static_cast(std::round(airspeed / 4)) + 1; + } + set_bin<57, 10>(msg_bin, std::bitset<10>(encoded_airspeed).to_string()); + } + } + void parse(std::string& msg_bin) { + sub_type = get_bin<37, 3, Air_Speed_Code_Type>(msg_bin); + if (get_bin<45, 1>(msg_bin) == 1) { + int magnetic_heading_code = get_bin<46, 10>(msg_bin); + magnetic_heading = magnetic_heading_code * 360.0 / 1024.0; + } + airspeed_type = static_cast(get_bin<56, 1>(msg_bin)); + if (sub_type == Air_Speed_Code_Type::Air_Normal) { + speed = get_bin<57, 10>(msg_bin) - 1; + } + if (sub_type == Air_Speed_Code_Type::Air_Supersonic) { + speed = 4 * (get_bin<57, 10>(msg_bin) - 1); + } + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(sub_type) + Ret_J(magnetic_heading) + Ret_J(speed) + return ret; + } + }; + T_Parser(BDS09_Airspeed) + // 操作状态 + template + struct BDS65_ver1 { + PROP_Bit_T(1, NICs, 0) + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(NICs) + return ret; + } + void parse(std::string& msg_bin) { NICs = get_bin<76, 1>(msg_bin); } + void set(std::string& msg_bin) { set_bin<76, 1>(msg_bin, NICs); } + }; + T_Parser(BDS65_ver1) + template + struct BDS65_ver2 { + PROP_Bit_T(1, NICa, 0) + PROP_Bit_T(1, NICc, 0) + PROP_Bit_T(1, SILs, 0) + void set(std::string& msg_bin) { + set_bin<76, 1>(msg_bin, NICa); + set_bin<51, 1>(msg_bin, NICc); + set_bin<87, 1>(msg_bin, SILs); + } + void parse(std::string& msg_bin) { + NICa = get_bin<76, 1>(msg_bin); + NICc = get_bin<51, 1>(msg_bin); + SILs = get_bin<87, 1>(msg_bin); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(NICa) + Ret_J(NICc) + Ret_J(SILs) + return ret; + } + }; + T_Parser(BDS65_ver2) + template + struct BDS65_Base : BDS65 { + PROP_Bit_T(4, NACp_4, 0) + PROP_Bit_T(2, SIL, 0) + PROP_Bit_T(1, Horizontal_Reference_direction_1, 0) + PROP_Range_T(int, 1, 2, ADS_B_version, 0) + void set(std::string& msg_bin) { + set_bin<32, 5>(msg_bin, 31); + set_bin<76, 4>(msg_bin, NACp_4); + set_bin<82, 2>(msg_bin, SIL); + set_bin<85, 1>(msg_bin, Horizontal_Reference_direction_1); + set_bin<72, 3>(msg_bin, ADS_B_version); + } + void parse(std::string& msg_bin) { + get_bin<76, 4>(msg_bin, NACp_4); + get_bin<82, 2>(msg_bin, SIL); + get_bin<85, 1>(msg_bin, Horizontal_Reference_direction_1); + get_bin<72, 3>(msg_bin, ADS_B_version); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(NACp_4) + Ret_J(SIL) + Ret_J(Horizontal_Reference_direction_1) + Ret_J(ADS_B_version) + return ret; + } + }; + T_Parser(BDS65_Base) + template + struct BDS65_Airborne { + PROP_Bit_T(16, Airborne_Operational_mode_codes, 0) + PROP_Bit_T(2, Geometric_vertical_accuracy, 0) + PROP_Bit_T(2, Barometric_Altitude_Integrity, 0) + void set(std::string& msg_bin) { + set_bin<37, 3>(msg_bin, 0); + set_bin<40, 16>(msg_bin, Airborne_Operational_mode_codes); + set_bin<80, 2>(msg_bin, Geometric_vertical_accuracy); + set_bin<84, 1>(msg_bin, Barometric_Altitude_Integrity); + } + void parse(std::string& msg_bin) { + get_bin<40, 16>(msg_bin, Airborne_Operational_mode_codes); + get_bin<80, 2>(msg_bin, Geometric_vertical_accuracy); + get_bin<84, 1>(msg_bin, Barometric_Altitude_Integrity); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(Airborne_Operational_mode_codes) + Ret_J(Geometric_vertical_accuracy) + Ret_J(Barometric_Altitude_Integrity) + return ret; + } + }; + T_Parser(BDS65_Airborne) + template + struct BDS65_Surface { + PROP_Bit_T(16, Surface_Operational_mode_codes, 0) + PROP_Bit_T(2, Track_angle_or_heading, 0) + void set(std::string& msg_bin) { + set_bin<37, 3>(msg_bin, 1); + set_bin<40, 16>(msg_bin, Surface_Operational_mode_codes); + set_bin<84, 1>(msg_bin, Track_angle_or_heading); + } + void parse(std::string& msg_bin) { + get_bin<40, 16>(msg_bin, Surface_Operational_mode_codes); + get_bin<84, 1>(msg_bin, Track_angle_or_heading); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(Surface_Operational_mode_codes) + Ret_J(Track_angle_or_heading) + return ret; + } + }; + T_Parser(BDS65_Surface) + +// 61 1 6 +// Extended Squitter Aircraft Status 1 .0 s +// 62 1 6 +// Target State and Status Information 0.5 s + // tc = 28 Extended squitter aircraft emergency priority status + // Extended_Squitter_Aircraft_Status + + + template + struct BDS61_Base : BDS61 { + PROP_T(Emergency_Type, emergency_state, Emergency_Type::No_Emergency) + PROP_Bit_T(2, sub_type, 0) + void set(std::string& msg_bin) { + set_bin<32, 5>(msg_bin, 28); + set_bin<32 + 5, 3>(msg_bin, 1); + set_bin<32 + 8, 3>(msg_bin, static_cast(emergency_state)); + } + void parse(std::string& msg_bin) { + get_bin<32 + 8, 4>(msg_bin, emergency_state); + get_bin<32 + 5, 3>(msg_bin, sub_type); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(emergency_state) + Ret_J(sub_type) + return ret; + } + }; + T_Parser(BDS61_Base) + + template + struct BDS61_Mode_A { + //PROP_Bit_32T(13, mode_a, 0) + PROP_T(std::string, mode_a, "??") + void set(std::string& msg_bin) { + set_bin<32 + 5, 3>(msg_bin, 1); + set_bin<32 + 11, 13>(msg_bin, encode_squawk13(mode_a)); + } + void parse(std::string& msg_bin) { + //get_bin<32 + 11, 13>(msgmsg_bin, mode_a); + mode_a = decode_squawk13(std::bitset<13>(get_bin<32 + 11, 13>(msg_bin)).to_string()); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(mode_a) + return ret; + } + }; + T_Parser(BDS61_Mode_A) + + template + struct BDS61_TCAS_ACAS_RA_Broadcast : ACAS_Active_Resolution_Advisory_9_56 {}; + T_Parser(BDS61_TCAS_ACAS_RA_Broadcast) + + + + enum Alt_Source { + MCP_FCU, + FMS + }; + // Target_State_and_Status_Information + + template + struct BDS62_Base : BDS62 { + PROP_Bit_T(2, sub_type, 0) + void set(std::string& msg_bin) { + set_bin<32, 5>(msg_bin, 29); + } + void parse(std::string& msg_bin) { + get_bin<32 + 5, 2>(msg_bin, sub_type); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(sub_type) + return ret; + } + }; + T_Parser(BDS62_Base) + + template + struct BDS62_ver1 { + PROP_Bit_T(2, Vertical_Data_Available_Source_Indicator, 0) // §B.2.3.9.3 + PROP_Bit_T(1, Target_Altitude_Type, 0) //§B.2.3.9.4 + PROP_Bit_T(1, Backward_Compatibility_Flag, 0) + PROP_Bit_T(2, Target_Altitude_Capability, 0) // §B.2.3.9.5 + PROP_Bit_T(2, Vertical_Mode_Indicator, 0) // §B.2.3.9.6 + PROP_Bit_T(10, Target_Altitude, 0) // §B.2.3.9.7 + PROP_Bit_T(2, Horizontal_Data_Source_Indicator, 0) // §B.2.3.9.8 + PROP_Bit_T(2, Track_Angle, 0) // §B.2.3.9.9 + PROP_Bit_T(1, Track_Indicator, 0) // §B.2.3.9.10 + PROP_Bit_T(2, Horizontal_Mode_Indicator, 0) // §B.2.3.9.11 + PROP_Bit_T(4, NACp, 0) // §B.2.3.9.12 + PROP_Bit_T(4, NIC_BARO, 0) // §B.2.3.9.13 + PROP_Bit_T(2, SIL, 0) // §B.2.3.9.14 + PROP_Bit_T(2, Capability_Mode_Codes, 0) // §B.2.3.9.15 + PROP_Bit_T(2, Emergency_Priority_Status, 0) // §B.2.3.9.16 + + void set(std::string& msg_bin) { + set_bin<32 + 5, 2>(msg_bin, 0); + } + void parse(std::string& msg_bin) { + + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + return ret; + } + }; + T_Parser(BDS62_ver1) + + template + struct BDS62_ver2 { + PROP_Bit_T(1, SIL_SUPPLEMENT, 0) // (0 = Per Hour, 1 = Per Sample) + PROP_Bit_T(1, selected_altitude_type, 0) // (0 = MCP/FCU, 1 = FMS) + PROP_T(std::optional, selected_altitude, 0) + PROP_Range_T(std::optional, 0, 408.0, barometric_pressure, 0); + PROP_Range_T(std::optional, -180.0, 180.0, select_heading, 0); + PROP_Bit_T(4, NACp, 0) + PROP_Bit_T(1, NIC_BARO, 0) + PROP_Bit_T(2, SIL, 0) + PROP_Bit_T(2, MCP_FCU_State, 0) + PROP_Bit_T(1, auto_pilot_engaged, 0) + PROP_Bit_T(1, VANV_mode_engaged, 0) + PROP_Bit_T(1, altitude_hold_mode_engaged, 0) + PROP_Bit_T(1, Reserved_ADS_R_Flag, 0) + PROP_Bit_T(1, approach_mode_engaged, 0) + PROP_Bit_T(1, TCAS_ACAS_operational, 0) + PROP_Bit_T(1, LNAV_mode_engaged, 0) + void set(std::string& msg_bin) { + set_bin<32 + 5, 2>(msg_bin, 1); + set_bin<32 + 7, 1>(msg_bin, SIL_SUPPLEMENT); + set_bin<32 + 8, 1>(msg_bin, selected_altitude_type); + if (selected_altitude.has_value()) { + if (selected_altitude_type == 0) { //MCP/FCU + set_bin<32 + 9, 11>(msg_bin, selected_altitude.value()/32 + 1); + } else { // FMS + set_bin<32 + 9, 11>(msg_bin, selected_altitude.value()); + } + } + if (barometric_pressure.has_value()) { + set_bin<32 + 20, 9>(msg_bin, barometric_pressure.value()/0.8 + 1); + } + if (select_heading.has_value()) { + set_bin<32 + 29, 1>(msg_bin, 1); + int sign = select_heading.value() > 0 ? 0 : 1; + set_bin<32 + 30, 1>(msg_bin, sign); + int select_heading_code = std::abs(select_heading.value() * 256.0/180.0); + set_bin<32 + 31, 8>(msg_bin, select_heading_code); + } else { + set_bin<32 + 29, 1>(msg_bin, 0); + } + set_bin<32 + 39, 4>(msg_bin, NACp); + set_bin<32 + 43, 1>(msg_bin, NIC_BARO); + set_bin<32 + 44, 2>(msg_bin, SIL); + set_bin<32 + 46, 1>(msg_bin, MCP_FCU_State); + set_bin<32 + 47, 1>(msg_bin, auto_pilot_engaged); + set_bin<32 + 48, 1>(msg_bin, VANV_mode_engaged); + set_bin<32 + 49, 1>(msg_bin, altitude_hold_mode_engaged); + set_bin<32 + 50, 1>(msg_bin, Reserved_ADS_R_Flag); + set_bin<32 + 51, 1>(msg_bin, approach_mode_engaged); + set_bin<32 + 52, 1>(msg_bin, TCAS_ACAS_operational); + set_bin<32 + 53, 1>(msg_bin, LNAV_mode_engaged); + } + void parse(std::string& msg_bin) { + get_bin<32 + 7, 1>(msg_bin, SIL_SUPPLEMENT); + get_bin<32 + 8, 1>(msg_bin, selected_altitude_type); + int selected_altitude_code = get_bin<32 + 9, 11>(msg_bin); + if (selected_altitude_code != 0) { + if (selected_altitude_type == 0) { //MCP/FCU + selected_altitude = (selected_altitude_code - 1) * 32; + } else { // FMS + selected_altitude = selected_altitude_code; + } + } + auto barometric_pressure_code = get_bin<32 + 20, 9>(msg_bin); + if (barometric_pressure_code != 0) { + barometric_pressure = (get_bin<32 + 20, 9>(msg_bin) - 1) * 0.8; + } + bool enable = get_bin<32 + 29, 1>(msg_bin); + if (enable) { + double sign = get_bin<32 + 30, 1>(msg_bin) == 0 ? 1.0 : -1.0; + int select_heading_code = get_bin<32 + 31, 8>(msg_bin); + select_heading = sign * select_heading_code * 180.0/256.0; + } + get_bin<32 + 39, 4>(msg_bin, NACp); + get_bin<32 + 43, 1>(msg_bin, NIC_BARO); + get_bin<32 + 44, 2>(msg_bin, SIL); + get_bin<32 + 46, 1>(msg_bin, MCP_FCU_State); + get_bin<32 + 47, 1>(msg_bin, auto_pilot_engaged); + get_bin<32 + 48, 1>(msg_bin, VANV_mode_engaged); + get_bin<32 + 49, 1>(msg_bin, altitude_hold_mode_engaged); + get_bin<32 + 50, 1>(msg_bin, Reserved_ADS_R_Flag); + get_bin<32 + 51, 1>(msg_bin, approach_mode_engaged); + get_bin<32 + 52, 1>(msg_bin, TCAS_ACAS_operational); + get_bin<32 + 53, 1>(msg_bin, LNAV_mode_engaged); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(SIL_SUPPLEMENT) + Ret_J(selected_altitude_type) + Ret_J(selected_altitude) + Ret_J(barometric_pressure) + Ret_J(select_heading) + Ret_J(NACp) + Ret_J(NIC_BARO) + Ret_J(SIL) + Ret_J(MCP_FCU_State) + Ret_J(auto_pilot_engaged) + Ret_J(VANV_mode_engaged) + Ret_J(altitude_hold_mode_engaged) + Ret_J(Reserved_ADS_R_Flag) + Ret_J(approach_mode_engaged) + Ret_J(TCAS_ACAS_operational) + Ret_J(LNAV_mode_engaged) + return ret; + } + }; + T_Parser(BDS62_ver2) + +} +#endif diff --git a/SSR/Aircraft_Info.cpp b/SSR/Aircraft_Info.cpp new file mode 100644 index 0000000..ad1d32d --- /dev/null +++ b/SSR/Aircraft_Info.cpp @@ -0,0 +1,561 @@ +#include "Aircraft_Info.h" +#include "Core/Statistics/Frequency_Limit.h" +#include "convert/SBS.h" +#include "export.h" +using namespace Psc; +#define Parse_no_json(t, Field) std::lock_guard g(info.Field.mtx.mtx); \ +init_that(info, #Field, info.Field, mode_s_msg); \ +auto& t = info.Field.value.value(); + + +#define Parse(t, Field) std::lock_guard mtx_##Field(info.Field.mtx.mtx); \ +init_that(info, #Field, info.Field, mode_s_msg); \ +auto& t = info.Field.value.value(); \ +msg_json.append_list(t.toJson().children); + + + +#define checkLength(len) \ +if (msg_hex.size() != len) { \ + parse_call_back(Parse_Call_Back_Type::Length_Error, nullptr, to_string(df), {}, nullptr, mode_s_msg); \ + return false; \ +} + + + +namespace SSR { + Parse_Call_Back parse_call_back = []( + SSR::Parse_Call_Back_Type type, Aircraft_Info* info, + const std::string& field_name, std::vector bds_list, void*, SSR::P_S mode_s_msg) { + if (type == Parse_Call_Back_Type::Normal_Block) return; + auto& msg_hex = mode_s_msg->msg_hex; + auto& msg = mode_s_msg->msg_bin; + auto& df = mode_s_msg->df; + auto& icao = mode_s_msg->icao; + std::string bds_list_str; + for (size_t i = 0; i < bds_list.size(); i++) { + bds_list_str += bds_list[i] + "_"; + } + if (!bds_list_str.empty()) { + bds_list_str.pop_back(); + } + Log_Type lt({}, { + {"Parse_Call_Back_Type", to_string(type)}, + {"bds_list_str", bds_list_str}, + {"ICAO", icao}, + {"DF", to_string(df)}, + {"signal_level", std::to_string((int)mode_s_msg->signal_level)}, + {"mlat", mode_s_msg->mlat_timestamp.to_string()}, + {"msg", msg_hex} + }); + if (type == Parse_Call_Back_Type::Length_Error) { + mode_s_logger->error(to_string(df) + " length error", {}, + "should:" + std::to_string(mode_s_msg->msg_hex.size()) + " msg_hex_size:" + std::to_string(msg_hex.size()) + " icao:" + + icao + "; msg_hex:" + msg_hex); + } + mode_s_logger->error("", lt, ""); + }; + Parse_Pos_Call_Back parse_pos_call_back = [](Aircraft_Info* info, P_S mode_s_msg, bool surface) { + if (surface) { + mode_s_logger->debug("surface/地面轨迹", {}, info->surface_pos_track_list.to_string()); + } else { + mode_s_logger->debug("air/轨迹", {}, info->air_pos_track_list.to_string()); + } + }; + + Parse_OK_Json_Call_Back parse_ok_json = [](Aircraft_Info* aircraft_info, P_S mode_s_msg, Psc::JSON msg_json) { + auto& icao = mode_s_msg->icao; + auto& msg_hex = mode_s_msg->msg_hex; + auto& df = mode_s_msg->df; + Log_Type lt({}, { + {"ICAO", icao}, + {"DF", std::to_string(static_cast(df))}, + {"signal_level", std::to_string((int)mode_s_msg->signal_level)}, + {"mlat", mode_s_msg->mlat_timestamp.to_string()}, + {"msg", msg_hex} + }); + mode_s_logger->debug("mode_s/json", lt, msg_json.to_json_string()); + }; + void set_logger(BaseLogger* logger) { + delete mode_s_logger; + mode_s_logger = logger; + } + + + bool check_new_prase_pos(ADS_B_T::CPR_Data & t, std::optional& tp, const std::optional& last, SSR::P_S mode_s_msg) { + if (tp == std::nullopt) return false; + if (last == std::nullopt) return true; + return true; + } + constexpr double s_ns = 1000000000.0; + + + + // cpr回调 + CPR_CB cpr_cb = [](CPR_Error_Type t, const std::string& log, P_S msg) { + if (mode_s_logger) mode_s_logger->debug("CPR/"+ to_string(t), {}, log); + }; + bool parse_absb_17(SSR::Data_Source_Interface* src, SSR::P_S mode_s_msg, JSON& msg_json, + std::optional base_station_pos, CPR::D max_speed_m_s, CPR::Time air_pos_time_out, CPR::Time surface_pos_time_out) { + bool new_create; + std::shared_ptr a = src->get_aircraft(mode_s_msg->icao); + if (!a) { + a = src->create_aircraft(mode_s_msg->icao); + } + Aircraft_Info& info = *a; + info.refresh_pos(false); + std::string& msg_bin = mode_s_msg->msg_bin; + MLAT_timestamp* mlat_timestamp = &mode_s_msg->mlat_timestamp; + Parse(base2, ADS_B_base) + int tc = type_code(msg_bin); + if (tc >= 1 && tc <= 4) { + Parse(t, BDS08_id) + } else if (tc >= 5 && tc <= 8) { + Parse_no_json(t, BDS06_surface_position) + info.refresh_pos(true); + t.base_station_pos = base_station_pos; + auto tp = t.parse_message(mode_s_msg, mode_s_msg->time().abs_sec(), max_speed_m_s, surface_pos_time_out, cpr_cb); + if (tp.has_value()) { + auto t2 = t.odd_msg; + Position_Info p(tp.value(), info.altitude_meter().value_or(0)); + info.surface_pos_track_list.push(p); + info.mlat = false; + parse_pos_call_back(&info, mode_s_msg, true); + msg_json.append_list(t.toJson().children); + } + } else if ((tc >= 9 && tc <= 18) || (tc >= 20 && tc <= 22)) { + Parse_no_json(t, BDS05_airborne_position) + info.refresh_pos(true); + if (info.absb_version == 0 || info.absb_version == 1) { + Parse(t2, BDS05_airborne_Position_v01) + } else if (info.absb_version == 2) { + Parse(t2, BDS05_airborne_Position_v2) + } + using namespace std::chrono; + auto tp = t.parse_message(mode_s_msg, mode_s_msg->time().abs_sec(), max_speed_m_s, air_pos_time_out, cpr_cb); + if (tp.has_value()) { + auto alt = t.get_alt_meter(); + Position_Info p(tp.value(), alt); + info.air_pos_track_list.push(p); + info.mlat = false; + parse_pos_call_back(&info, mode_s_msg, false); + msg_json.append_list(t.toJson().children); + } + } else if (tc == 19) { + //速度 + Parse(t, BDS09_Base) + int subType = get_bin<37, 3>(msg_bin); + if (subType == 1 || subType == 2) { + Parse(s12, ground_speed) + } else if (subType == 3 || subType == 4) { + Parse(s34, air_speed) + } else { + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::Unknown_tc, nullptr, VAR_STR_1(subType) + "超出范围 tc == 19 error! ", + {}, nullptr, mode_s_msg); + } + if (info.absb_version == 0) { + Parse(v0, BDS09_v0) + } else if (info.absb_version == 1 || info.absb_version == 2) { + Parse(v12, BDS09_v12) + } + } else if (tc == 28) { + Parse(t, BDS61) + int st = t.get_sub_type(); + if (st == 1) { + Parse(st1, BDS61_mode_a) + } + if (st == 2) { + Parse(st2, BDS61_mode_TCAS_ACAS_RA_Broadcast) + } + } else if (tc == 29) { + Parse(t, BDS62) + auto st = t.get_sub_type(); + if (st == 0) { + Parse(tv1, BDS62_ver1) + } + if (st == 1) { + Parse(tv2, BDS62_ver2) + } + } else if (tc == 31) { + Parse(t, BDS65_Base) + int st = get_bin<37, 3>(msg_bin); + int ver = t.get_ADS_B_version(); + info.absb_version = ver; + if (st == 0) { + Parse(t2, BDS65_Airborne) + } else if (st == 1) { + Parse(t2, BDS65_Surface) + } + if (ver == 1) { + Parse(t2, BDS65_v1) + } else if (ver == 2) { + Parse(t2, BDS65_v2) + } + } else { + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::Unknown_tc, nullptr, "commb bds not one", + {}, nullptr, mode_s_msg); + } + return true; + } + bool parse_Message_commb(SSR::Data_Source_Interface* src, const std::string& BDS, SSR::P_S mode_s_msg, + JSON& msg_json) { + std::string& msg = mode_s_msg->msg_bin; + Downlink_Format& df = mode_s_msg->df; + bool new_create; + std::shared_ptr a = src->get_aircraft(mode_s_msg->icao); + if (!a) { + a = src->create_aircraft(mode_s_msg->icao); + } + Aircraft_Info& info = *a; + Parse(rb, Reply_Base_builder) + if (df == Downlink_Format::Comm_B_Altitude_Reply_20) { + Parse(alt, alt) + } + if (df == Downlink_Format::Comm_B_Identity_Reply_21) { + Parse(id, id) + } + if (BDS == "10") { + Parse(t, BDS10) + } else if (BDS == "17") { + Parse(t, BDS17) + } else if (BDS == "20") { + Parse(t, BDS20) + } else if (BDS == "30") { + Parse(t, BDS30) + ACAS_SubType st = t.parse_sum(msg); + msg_json.append({"ACAS_SubType st", to_string(st)}); + switch (st) { + case ACAS_SubType::NO_RA: break; + case ACAS_SubType::One_threat: { + Parse(t2, one) + break; + } + case ACAS_SubType::Mult_below_above: + case ACAS_SubType::Mult_same_direction: { + Parse(t2, muti) + break; + } + } + Threat_Type_Indicator ti = t.get_threat_Type_Indicator(); + msg_json.append({"Threat_Type_Indicator",to_string(ti)}); + switch (ti) { + case Threat_Type_Indicator::No_Identity_Data: break; + case Threat_Type_Indicator::Mode_S_Transponder_Address: { + Parse(t2, transponder_address) + break; + } + case Threat_Type_Indicator::Altitude_Range_Bearing: { + Parse(t2, Altitude_Range_Bearing) + break; + } + } + } else if (BDS == "40") { + Parse(t, BDS40) + } else if (BDS == "50") { + Parse(t, BDS50) + } else if (BDS == "60") { + Parse(t, BDS60) + } else if (BDS == "44") { + Parse(t, BDS44) + } else if (BDS == "45") { + Parse(t, BDS45) + } + return true; + } + bool parse_mode_s_bin(Data_Source_Interface* src, P_S mode_s_msg, std::optional base_station_pos, CPR::D max_speed_m_s, CPR::Time air_pos_time_out, CPR::Time surface_pos_time_out) { + auto& msg_hex = mode_s_msg->msg_hex; + auto& msg = mode_s_msg->msg_bin; + auto& df = mode_s_msg->df; + auto& icao = mode_s_msg->icao; + JSON msg_json = JSON::object(); + bool ok = true; + if (df == Downlink_Format::Extended_Squitter_17) { + checkLength(28) + if (!with_PI::check_crc(msg, icao)) { + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::CRC_Error, nullptr, + "parse_absb_17 crc_error", {}, nullptr, mode_s_msg); + return false; + } + ok = parse_absb_17(src, mode_s_msg, msg_json, base_station_pos, max_speed_m_s, air_pos_time_out, surface_pos_time_out); + } else if (df == Downlink_Format::Comm_B_Altitude_Reply_20 || df == Downlink_Format::Comm_B_Identity_Reply_21) { + checkLength(28) + if (!src->get_aircraft(icao)) { + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::ICAO_Error, nullptr, "", + {}, nullptr, mode_s_msg); + return false; + } + std::vector bds_list = infer2(msg, true); + if (bds_list.empty()) { + msg_json.append({"bds", "bds_empty"}); + std::string bds_list_str; + for (auto& bds : bds_list) { + bds_list_str += "_" + bds; + } + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::Cannot_Choose_BDS, nullptr, "commb bds zero", + {}, nullptr, mode_s_msg); + return false; + } + if (bds_list.size() > 1) { + std::string that; + for (const std::string& bds : bds_list) { + that.append(bds + "_"); + } + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::More_than_one_BDS, nullptr, "commb bds not one", + {}, nullptr, mode_s_msg); + + } + std::string BDS = bds_list[0]; + if (!with_AP::check_crc(msg, icao, BDS)) { + msg_json.append({"crc", "invalid crc"}); + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::CRC_Error, nullptr, "commb_crc", + {}, nullptr, mode_s_msg); + return false; + } + ok = parse_Message_commb(src, BDS, mode_s_msg, msg_json); + } else if (df == Downlink_Format::Surveillance_Altitude_Reply_4 || df == + Downlink_Format::Surveillance_Identity_Reply_5) { + checkLength(14) + if (!src->get_aircraft(icao)) { + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::ICAO_Error, nullptr, "", + {}, nullptr, mode_s_msg); + return false; + } + if (!with_AP::check_crc(msg, icao)) { + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::CRC_Error, nullptr, "Surveillance crc_error", + {},nullptr, mode_s_msg); + return false; + } + + std::shared_ptr a = src->get_aircraft(mode_s_msg->icao); + Aircraft_Info& info = *a; + Parse(rb, Reply_Base_builder) + if (df == Downlink_Format::Surveillance_Altitude_Reply_4) { + Parse(alt, alt) + } + if (df == Downlink_Format::Surveillance_Identity_Reply_5) { + Parse(id, id) + } + } else if (df == Downlink_Format::All_Call_Reply_11) { + checkLength(14) + if (!src->get_aircraft(icao)) { + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::ICAO_Error, nullptr, "", + {}, nullptr, mode_s_msg); + return false; + } + // if (!with_PI::check_crc(msg, icao, "")) { + // if (error_call_back) error_call_back(info, "All_Call_Reply crc_error", mode_s_msg); + // return false; + // } + // https://mode-s.org/1090mhz/content/mode-s/1-basics.html + + std::shared_ptr a = src->get_aircraft(mode_s_msg->icao); + if (a) { + Aircraft_Info& info = *a; + Parse(all_call_reply, all_call_reply) + } + } else if (df == Downlink_Format::Short_Air_Air_Surveillance_0) { + checkLength(14) + if (!src->get_aircraft(icao)) { + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::ICAO_Error, nullptr, "", + {}, nullptr, mode_s_msg); + return false; + } + if (!with_AP::check_crc(msg, icao)) { + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::CRC_Error, nullptr, + "Short_Air_Air_Surveillance crc_error", {}, nullptr, mode_s_msg); + return false; + } + std::shared_ptr a = src->get_aircraft(mode_s_msg->icao); + if (a) { + Aircraft_Info& info = *a; + Parse(s_acas, short_ACAS) + } + } else if (df == Downlink_Format::Long_Air_Air_Surveillance_16) { + checkLength(28) + if (!src->get_aircraft(icao)) { + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::ICAO_Error, nullptr, "", + {}, nullptr, mode_s_msg); + return false; + } + if (!with_AP::check_crc(msg, icao)) { + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::CRC_Error, nullptr, + "Long_Air_Air_Surveillance crc_error", {}, nullptr, mode_s_msg); + return false; + } + std::shared_ptr a = src->get_aircraft(mode_s_msg->icao); + if (a) { + Aircraft_Info& info = *a; + Parse(s_acas, long_ACAS) + if (ACAS_T::Parser_VDS_3_0_Coordination_Reply::is(msg)) { + Parse(base, VDS_3_0_Base) + if (ACAS_T::Parser_VDS_3_0_ARA1::is(msg)) { + Parse(base1, VDS_3_0_1) + } + if (ACAS_T::Parser_VDS_3_0_ARA2::is(msg)) { + Parse(base2, VDS_3_0_2) + } + } + } + } else { + // 没找到能解的df + if (parse_call_back) parse_call_back(Parse_Call_Back_Type::Unknown_DF, nullptr, "unknown df error", {}, nullptr, + mode_s_msg); + return false; + } + std::shared_ptr a = src->get_aircraft(mode_s_msg->icao); + if (a) { + ++a->times; + } + parse_ok_json(a.get(), mode_s_msg, msg_json); + return ok; + } + + + +bool Monitor_Mode_ACS_Message_Num = false; +struct Live_Report { + explicit Live_Report(const std::string& name) : name(name) {} + void add() { + ++live_num; + if (l.test()) { + report(); + return; + } + } + void remove() { + --live_num; + if (l.test()) { + report(); + return; + } + // if (live_num < 10) { + // report(); + // } + } + void report() { + std::ostringstream oss; + oss << "[" << name << "]: " << total++; + oss << " current_num==" << live_num << std::endl; + std::cout << oss.str() << std::flush; + } +protected: + std::string name = "null"; + Frequency_Limit l; + std::atomic live_num{}; //存活的对象数量 + std::atomic total{}; +}; +Live_Report ac_mode("ac"); +Live_Report s_mode("s"); +Live_Report plane("plane"); +// 以字节为单位 +// 1 + 1 + 6 + 1 + 2/7/14 +// 1a + type time_stamp signal_level data +Mode_Msg::Mode_Msg(const std::shared_ptr& source, const std::string& packet) : Msg(source, packet) { + mlat_timestamp = MLAT_timestamp(packet.substr(2, 6)); + signal_level = packet[8]; + std::string memory = packet.substr(9); + msg_hex = mem2hex(memory); + msg_bin = hex2bin(msg_hex); + if (mlat_timestamp.daysec == 0 && mlat_timestamp.nanosec == 0) { + std::time_t tp = std::time(nullptr); + mlat_timestamp.daysec = tp % (3600 * 24); + } +} +Mode_Msg::~Mode_Msg() {} +Mode_AC_Msg::Mode_AC_Msg(const std::shared_ptr& source, const std::string& packet) : Mode_Msg( + source, packet) { + if (packet.size() != AC_len) { + std::string log = "0x31 Mode_S_Msg 长度错误!" + packet; + std::cout << log << std::endl; + mode_s_logger->error("", {}, log); + Psc::fail_fast(); + } + if (Monitor_Mode_ACS_Message_Num) { + ac_mode.add(); + } +} +Mode_AC_Msg::~Mode_AC_Msg() { + if (Monitor_Mode_ACS_Message_Num) { + ac_mode.remove(); + } +} +Mode_S_Msg::Mode_S_Msg(const std::shared_ptr& source, + const std::string& packet) : Mode_Msg(source, packet) { + if (type == S7) { + if (packet.size() != S7_len) { + std::string log = "0x32 Mode_S_Msg 长度错误!" + packet; + std::cout << log << std::endl; + mode_s_logger->error("", {}, log); + Psc::fail_fast(); + } + } + if (type == S14) { + if (packet.size() != S14_len) { + std::string log = "0x33 Mode_S_Msg 长度错误!" + packet; + std::cout << log << std::endl; + mode_s_logger->error("", {}, log); + Psc::fail_fast(); + } + } + df = decode_df(hex2bin(msg_hex.substr(0, 2))); + icao = SSR::decode_icao(msg_bin); + if (Monitor_Mode_ACS_Message_Num) { + s_mode.add(); + } +} +Mode_S_Msg::~Mode_S_Msg() { + if (Monitor_Mode_ACS_Message_Num) { + s_mode.remove(); + } +} + + + + +Psc::JSON POS_List::get_last_array_json(std::uint64_t last_seq) { + std::lock_guard g(mtx.mtx); + + Psc::JSON ret = Psc::JSON::object(); + Psc::JSON arr = Psc::JSON::array(); + + std::uint64_t cur_seq = seq; + + + if (last_seq >= cur_seq) { + ret.append({"sta_seq", last_seq}); + ret.append({"end_seq", cur_seq}); + ret.append({"size", 0}); + ret.append({"list", arr}); + return ret; + } + + std::uint64_t delta = cur_seq - last_seq; + std::uint64_t available = std::min(delta, count); + + for (std::uint64_t i = available; i > 0; --i) { + std::size_t idx = (head + max_size - i) % max_size; + auto cur = buf[idx].to_json(); + cur.append({"idx", idx}); + arr.append(cur); + } + + ret.append({"sta_seq", last_seq}); + ret.append({"end_seq", cur_seq}); + ret.append({"size", arr.children.size()}); + ret.append({"list", arr}); + return ret; +} +std::string POS_List::to_string() { + return get_last_array_json(0).to_json_string(); +} +Aircraft_Info::Aircraft_Info(std::string icao) : icao(std::move(icao)) { + if (Monitor_Mode_ACS_Message_Num) { + plane.add(); + } +} +Aircraft_Info::~Aircraft_Info() { + if (Monitor_Mode_ACS_Message_Num) { + plane.remove(); + } +} +} \ No newline at end of file diff --git a/SSR/Aircraft_Info.h b/SSR/Aircraft_Info.h new file mode 100644 index 0000000..776611a --- /dev/null +++ b/SSR/Aircraft_Info.h @@ -0,0 +1,311 @@ +#pragma once +#include "../../Core/Core/system/export.h" +#include "ACAS/ACAS.h" +#include "ADS_B/ADS_B.h" +#include "Commb/Commb.h" +#include "Mode_S/Mode_S.h" +#include "Msg.h" +#include "convert/DataOutputFormats.h" +#include + +namespace SSR { + +#define GET_PRE(name) \ +std::lock_guard lock(name.mtx.mtx); \ +if (!name.value.has_value()) \ +{ \ +return std::nullopt; \ +} \ +auto& w = name.value.value(); +// #define EJ2(NAME) \ +// if (NAME.value.has_value()) ret.append(Psc::Json(STRINGIFY(timestamp_##NAME), NAME.time)); \ +// ret.append(NAME.value.has_value() ? Psc::Json(#NAME, NAME.value.value().toJson()) : Psc::Json(#NAME, nullptr)); +template +void d(Psc::JSON& ret, T& t, const std::string& name) { + std::lock_guard g(t.mtx.mtx); + if (!t.value.has_value()) { + ret.append(Psc::JSON(name, nullptr)); + return; + } + auto tt = t.value.value().toJson(); + tt.prepend(Psc::JSON("time", Psc::utc_2_local_time(t.time, "%H:%M:%S"))); + tt.prepend(Psc::JSON("总数", t.sum)); + tt.prepend(Psc::JSON("每秒数量", t.per_second_num)); + ret.append({name, tt}); +} +template +struct Time { + long long time = 0; + RET value; + size_t sum{}; + size_t per_second_num{}; + NoneCopyLock mtx; +}; + +#define EJ2(NAME) d(ret, NAME, #NAME); +class Aircraft_Info { +public: + explicit Aircraft_Info(std::string icao); + virtual ~Aircraft_Info(); + + void push_all_bds_info(Psc::JSON& ret) { + EJ2(ADS_B_base) + EJ2(BDS08_id) + EJ2(BDS06_surface_position) + EJ2(BDS05_airborne_position) + EJ2(BDS05_airborne_Position_v01) + EJ2(BDS05_airborne_Position_v2) + EJ2(ground_speed) + EJ2(air_speed) + EJ2(BDS09_v0) + EJ2(BDS09_v12) + EJ2(BDS65_Base) + EJ2(BDS65_Airborne) + EJ2(BDS65_Surface) + EJ2(BDS65_v1) + EJ2(BDS65_v2) + EJ2(all_call_reply) + EJ2(Reply_Base_builder) + EJ2(alt) + EJ2(id) + EJ2(BDS20) + EJ2(BDS10) + EJ2(BDS17) + EJ2(BDS30) + EJ2(muti) + EJ2(one) + EJ2(transponder_address) + EJ2(Altitude_Range_Bearing) + EJ2(BDS40) + EJ2(BDS50) + EJ2(BDS60) + EJ2(BDS44) + EJ2(BDS45) + EJ2(short_ACAS) + EJ2(long_ACAS) + EJ2(VDS_3_0_Base) + EJ2(VDS_3_0_1) + EJ2(VDS_3_0_2) + } + + + virtual Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(icao) + ret.append({"每秒位置数", get_pest()}); + ret.append({"飞机位置轨迹点数", air_pos_track_list.size()}); + ret.append({"地面位置轨迹点数", surface_pos_track_list.size()}); + ret.append({"信号强度", signal_level}); + ret.append({"时间", Psc::utc_2_local_time(timestamp)}); + Psc::JSON j("多点定位数据源", mlat); + ret.append({"多点定位数据源", mlat}); + push_all_bds_info(ret); + return ret; + } + + + POS_List air_pos_track_list; + POS_List surface_pos_track_list; + std::string icao; + std::atomic mlat = false; + time_t timestamp = std::time(nullptr); + + int get_pest() { + std::lock_guard lock(pest_mutex.mtx); + return pest; + } + void refresh_pos(bool pos) { + std::lock_guard lock(pest_mutex.mtx); + auto cur = std::time(nullptr); + if (cur == pest_timestamp) { + if (pos) { + pest++; + } + } else { + pest = 0; + pest_timestamp = cur; + } + } + unsigned char absb_version = 0; + MLAT_timestamp mlat_timestamp; + int signal_level{}; // signal level in dBm + std::optional SIL() { + GET_PRE(BDS65_Base) + return w.get_SIL(); + } + std::optional NACp() { + GET_PRE(BDS65_Base) + return w.get_NACp_4(); + } + std::optional ADS_B_Ver() { + GET_PRE(BDS65_Base) + return w.get_ADS_B_version(); + } + std::optional wind_speed() { + GET_PRE(BDS44) + return w.wind_speed; + } + std::optional temperature() { + GET_PRE(BDS44) + return static_cast(w.temperature); + } + std::optional wind_direction() { + GET_PRE(BDS44) + return w.wind_direction; + } + + + // 航班号 + RET flight() { + GET_PRE(BDS08_id) + return w.get_call_sign(); + } + + + + RET alart() { + GET_PRE(BDS05_airborne_position) + Surveillance_Status s = w.get_surveillance_status(); + return s == Surveillance_Status::TemporaryAlert || s == Surveillance_Status::PermanentAlert; + } + + // 空中交通管制(ATC)用来识别飞机的四位数代码 + // Squawk 代码有很多用途,包括特定的安全指令,如紧急情况时使用的应答机代码(如7500表示劫机,7700表示紧急状况等) + RET squawk() { + GET_PRE(id) + if (!id.value.has_value()) { + return std::nullopt; + } + return id.value.value().get_ATC_Squawk(); + } + RET bds20_call_sign() { + GET_PRE(BDS20) + return BDS20.value.value().get_call_sign(); + } + RET is_on_ground() { + GET_PRE(Reply_Base_builder) + if (w.get_flight_status() == Flight_Status::alert_on_ground || w.get_flight_status() == + Flight_Status::no_alert_on_ground) { + return true; + } + return false; + } + RET speed() { + GET_PRE(ground_speed) + return w.get_speed() * aero::kts; + } + // ft/min + RET vertical_rate() { + GET_PRE(BDS09_Base) + auto v = w.get_vertical_rate(); + if (!v.has_value()) return std::nullopt; + return v.value() * aero::fpm; + } + + + RET pos() { + // GET_PRE(BDS05_airborne_position) + // auto& it = BDS05_airborne_position.value.value(); + // if (it.get_lat().has_value() && it.get_lon().has_value()) { + // return CPR::Position{it.get_lat().value(), it.get_lon().value()}; + // } + if (air_pos_track_list.empty()) return std::nullopt; + return air_pos_track_list.last(); + } + RET select_heading() { + GET_PRE(BDS60) + return w.get_magnetic_heading(); + } + RET air_magnetic_heading() { + GET_PRE(air_speed) + return w.get_magnetic_heading(); + } + RET surface_magnetic_heading() { + GET_PRE(ground_speed) + return w.get_magnetic_heading(); + } + RET altitude_meter() { + GET_PRE(BDS05_airborne_position) + return w.get_alt_meter(); + } + RET vortex_type() { + GET_PRE(BDS08_id) + return w.get_vortex_type(); + } + RET get_airspeed() { + GET_PRE(air_speed) + return w; + } + RET get_speed_base() { + GET_PRE(BDS09_Base) + return w; + } + // 用于给外部输出统计信息 + // info, field_name, &t, msg + std::atomic times = 0; + time_t pest_timestamp{}; + int pest{}; // 每秒位置数 + NoneCopyLock pest_mutex; + Time ADS_B_base; + // 身份模块 + Time BDS08_id; + // 位置模块 + Time BDS06_surface_position; + Time BDS05_airborne_position; + Time BDS05_airborne_Position_v01; + Time BDS05_airborne_Position_v2; + // 速度模块 + Time BDS09_Base; + Time ground_speed; + Time air_speed; + Time BDS09_v0; + Time BDS09_v12; + // 版本解码 + Time BDS65_Base; + Time BDS65_Airborne; + Time BDS65_Surface; + Time BDS65_v1; + Time BDS65_v2; + // mode-s 其他 + Time all_call_reply; + Time Reply_Base_builder; + Time alt; + Time id; + Time BDS20; + Time BDS10; + Time BDS17; + Time BDS30; + Time muti; + Time one; + Time transponder_address; + Time Altitude_Range_Bearing; + Time BDS40; + Time BDS50; + Time BDS60; + Time BDS44; + Time BDS45; + Time BDS61; + Time BDS61_mode_a; + Time BDS61_mode_TCAS_ACAS_RA_Broadcast; + Time BDS62; + Time BDS62_ver1; + Time BDS62_ver2; + Time short_ACAS; + Time long_ACAS; + Time VDS_3_0_Base; + Time VDS_3_0_1; + Time VDS_3_0_2; + void update(const std::shared_ptr& mode_s_msg) { + MLAT_timestamp* t = &mode_s_msg->mlat_timestamp; + this->mlat_timestamp = *t; + this->signal_level = mode_s_msg->signal_level; + this->timestamp = std::time(nullptr); + } + +}; + + + +} + + diff --git a/SSR/CPR/CPR.cpp b/SSR/CPR/CPR.cpp new file mode 100644 index 0000000..2cf124b --- /dev/null +++ b/SSR/CPR/CPR.cpp @@ -0,0 +1,379 @@ +#include "CPR.h" +#include +#include +#include +#include +#include + +#include "SSR/global.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif +namespace SSR { +namespace CPR { + + std::string Position::to_string() const { + return "[" + std::to_string(lat) + "," + std::to_string(lon) + "]"; + } + + // D MOD(D x, D y) { + // if (y == 0) { + // std::cerr << "Error: Division by zero" << std::endl; + // return 0; + // } + // return x - y * std::floor(x / y); + // } + D MOD(D x, D y) { + if (y == 0) { + std::cerr << "Error: Division by zero" << std::endl; + return 0; + } + return x - y * std::floor(x / y); // 否则按标准模运算 + } + + I NL2(D lat) { + auto abs_lat = std::abs(lat); + if (abs_lat < 1e-6) return 59; + if (abs_lat == 87) return 2; + if (abs_lat > 87) return 1; + D a = 1 - std::cos(M_PI / (2 * NZ)); + auto b = std::cos(M_PI / 180 * abs_lat); + D nl = 2 * M_PI / std::acos(1 - a / (b * b)); + return floor(nl); + } + + + I NL(D lat) { + if (lat > 90.0) lat -= 360.0; + if (lat < 0) lat = -lat; /* Table is simmetric about the equator. */ + // if (lat > 90 + 0.1) { + // Log_Type t({}, {{"POS", LOG_POS}}); + // mode_s_logger->error("nl error!", t, "lat 居然超过90度!"); + // std::cout << "lat: " << lat << std::endl; + // exit(-1011); + // } + if (lat < 10.47047130) return 59; + if (lat < 14.82817437) return 58; + if (lat < 18.18626357) return 57; + if (lat < 21.02939493) return 56; + if (lat < 23.54504487) return 55; + if (lat < 25.82924707) return 54; + if (lat < 27.93898710) return 53; + if (lat < 29.91135686) return 52; + if (lat < 31.77209708) return 51; + if (lat < 33.53993436) return 50; + if (lat < 35.22899598) return 49; + if (lat < 36.85025108) return 48; + if (lat < 38.41241892) return 47; + if (lat < 39.92256684) return 46; + if (lat < 41.38651832) return 45; + if (lat < 42.80914012) return 44; + if (lat < 44.19454951) return 43; + if (lat < 45.54626723) return 42; + if (lat < 46.86733252) return 41; + if (lat < 48.16039128) return 40; + if (lat < 49.42776439) return 39; + if (lat < 50.67150166) return 38; + if (lat < 51.89342469) return 37; + if (lat < 53.09516153) return 36; + if (lat < 54.27817472) return 35; + if (lat < 55.44378444) return 34; + if (lat < 56.59318756) return 33; + if (lat < 57.72747354) return 32; + if (lat < 58.84763776) return 31; + if (lat < 59.95459277) return 30; + if (lat < 61.04917774) return 29; + if (lat < 62.13216659) return 28; + if (lat < 63.20427479) return 27; + if (lat < 64.26616523) return 26; + if (lat < 65.31845310) return 25; + if (lat < 66.36171008) return 24; + if (lat < 67.39646774) return 23; + if (lat < 68.42322022) return 22; + if (lat < 69.44242631) return 21; + if (lat < 70.45451075) return 20; + if (lat < 71.45986473) return 19; + if (lat < 72.45884545) return 18; + if (lat < 73.45177442) return 17; + if (lat < 74.43893416) return 16; + if (lat < 75.42056257) return 15; + if (lat < 76.39684391) return 14; + if (lat < 77.36789461) return 13; + if (lat < 78.33374083) return 12; + if (lat < 79.29428225) return 11; + if (lat < 80.24923213) return 10; + if (lat < 81.19801349) return 9; + if (lat < 82.13956981) return 8; + if (lat < 83.07199445) return 7; + if (lat < 83.99173563) return 6; + if (lat < 84.89166191) return 5; + if (lat < 85.75541621) return 4; + if (lat < 86.53536998) return 3; + if (lat < 87.00000000) return 2; + else return 1; + } + + + std::optional> global_decode(I YZ0, I YZ1, I XZ0, I XZ1, I _2, D r) { + D p2 = 2 << (_2 - 1); + D p_YZ0 = static_cast(YZ0)/p2; + D p_YZ1 = static_cast(YZ1)/p2; + D p_XZ0 = static_cast(XZ0)/p2; + D p_XZ1 = static_cast(XZ1)/p2; + D j_pure = 59.0 * p_YZ0 - 60.0 * p_YZ1; + D j = floor(j_pure + 0.5); + D Dlat_0 = r / 60.0; + D Dlat_1 = r / 59.0; + D Rlat_0 = Dlat_0 * (MOD(j, 60 - 0) + p_YZ0); + D Rlat_1 = Dlat_1 * (MOD(j, 60 - 1) + p_YZ1); + bool southern0 = Rlat_0 > 270.0 && Rlat_0 < 360.0; + bool southern1 = Rlat_1 > 270.0 && Rlat_1 < 360.0; + bool northern0 = Rlat_0 > 0.0 && Rlat_0 < 90.0; + bool northern1 = Rlat_1 > 0.0 && Rlat_1 < 90.0; + if (northern0 && northern1) { + } else if (southern0 && southern1) { + Rlat_0 -= 360.0; + Rlat_1 -= 360.0; + } else { + Log_Type type({}, {{"POS", LOG_POS}}); + mode_s_logger->debug("", type, VAR_STR_7(YZ0, YZ1, XZ0, XZ1, _2, r, j)); + return std::nullopt; + } + I nl0 = NL(Rlat_0); + I nl1 = NL(Rlat_1); + if(nl0 != nl1) { + return std::nullopt; + } + D nl = static_cast(nl0); + D ni_0 = nl; + D ni_1 = (nl0 == 1 ? 1 : nl - 1); + D m_pure = p_XZ0 * ni_1 - p_XZ1 * ni_0; + D m = floor(m_pure + 0.5); + D Dlon_0 = r / ni_0; + D Dlon_1 = r / ni_1; + D Rlon_0 = Dlon_0 * (MOD(m, ni_0) + p_XZ0); + D Rlon_1 = Dlon_1 * (MOD(m, ni_1) + p_XZ1); + return std::tuple{Position{Rlat_0, Rlon_0}, Position{Rlat_1, Rlon_1}}; + } + + + + POS locale_decode(D lat_s, D lon_s, I YZi, I XZi, I i, I _2, D r) { + D p2 = 2 << (_2 - 1); + D p_YZ = static_cast(YZi)/p2; + D p_XZ = static_cast(XZi)/p2; + D Dlat_i = r / static_cast(4 * NZ - i); + I j = floor(lat_s / Dlat_i) + floor(0.5 + MOD(lat_s, Dlat_i)/Dlat_i - p_YZ); + D Rlat_i = Dlat_i * (static_cast(j) + p_YZ); + I nl = NL(Rlat_i); + I nl_s = NL(lat_s); + if(nl != nl_s) { + return std::nullopt; + } + D Dlon_i{}; + if(nl - i > 0){ + Dlon_i = r / static_cast((nl - i == 0) ? 1 : (nl - i)); + } else if(nl - i == 0) { + Dlon_i = r; + } else { + // 不可能到达这 + } + I m = floor(lon_s/Dlon_i) + floor(0.5 + MOD(lon_s, Dlon_i)/Dlon_i - p_XZ); + D Rlon_i = Dlon_i * (static_cast(m) + p_XZ); + return Position{Rlat_i, Rlon_i}; + } + + std::tuple encode(D lat, D lon, I Nb, I i) { + D p2 = 2 << (Nb - 1); + D Dlat_i = 360.0 / static_cast(4 * NZ - i); + if (lat < 0) { + lat += 360; + } + if (lon < 0) { + lon += 360.0; + } + D YZi = floor(p2 * MOD(lat, Dlat_i)/Dlat_i + 0.5); + D Rlat_i = Dlat_i * (YZi/p2 + floor(lat/Dlat_i)); + I nl = NL(Rlat_i); + D Dlon_i{}; + + if(nl - i > 0){ + auto ii = static_cast(std::max((I)1, nl - i)); + Dlon_i = 360.0 / ii; + } else if(nl - i == 0){ + Dlon_i = 360.0; + } else { + // 不可能到达这 + } + D XZi = floor(p2 * MOD(lon, Dlon_i)/Dlon_i + 0.5); + return {static_cast(YZi), static_cast(XZi)}; + } + + std::tuple get(const std::string& msg_bin){ + std::string mb = msg_bin.substr(32); + auto YZ = bin2(mb.substr(22, 17)); + auto XZ = bin2(mb.substr(39, 17)); + CPR::I i = mb[21] - '0'; + return {YZ, XZ, i}; + } + + + + std::string lat_lon_airborne_34(double latitude, double longitude, CPR::Frame cpr_format) { + auto [YZ, XZ] = CPR::encode(latitude, longitude, 17, cpr_format); + return std::bitset<17>(YZ).to_string() + std::bitset<17>(XZ).to_string(); + } + + + std::optional airborne_position(const std::string& msg0, const std::string& msg1, Time t0, Time t1) { + auto [YZ0, XZ0, i0] = CPR::get(msg0); + auto [YZ1, XZ1, i1] = CPR::get(msg1); + if (i0 == i1){ + return std::nullopt; + } + if(i0 == 1 && i1 == 0){ + std::swap(YZ0, YZ1); + std::swap(XZ0, XZ1); + std::swap(t0, t1); + std::swap(i0, i1); + } + auto poses = CPR::global_decode(YZ0, YZ1, XZ0, XZ1, 17, 360.0); + if(!poses.has_value()) return std::nullopt; + auto [pos0, pos1] = poses.value(); + + std::string info = pos0.to_string() + " @@@@ " + pos1.to_string(); + + // std::cout << info << std::endl; + mode_s_logger->debug("CPR_global_decode", {}, info); + return (t0 > t1) ? pos0 : pos1; + } + + + std::optional airborne_position_with_ref(const std::string& msg_bin, double lat_ref, double lon_ref, Time t, Time t_ref) { + auto [YZ, XZ, i] = CPR::get(msg_bin); + auto pos = CPR::locale_decode(lat_ref, lon_ref, YZ, XZ, i, 17); + if(!pos.has_value()) return std::nullopt; + auto [lat, lon] = pos.value(); + return Position{(double)lat, (double)lon}; + } + + + std::string lat_lon_surface_34(double latitude, double longitude, CPR::Frame cpr_format) { + auto [YZ, XZ] = CPR::surface_encode(latitude, longitude, cpr_format); + return std::bitset<17>(YZ).to_string() + std::bitset<17>(XZ).to_string(); + } + + std::optional surface_position(const std::string& msg0, const std::string& msg1, Time t0, Time t1, const CPR::Position& base_station_pos) { + auto [YZ0, XZ0, i0] = CPR::get(msg0); + auto [YZ1, XZ1, i1] = CPR::get(msg1); + if (i0 == i1){ + return std::nullopt; + } + if(i0 == 1 && i1 == 0){ + std::swap(YZ0, YZ1); + std::swap(XZ0, XZ1); + std::swap(t0, t1); + std::swap(i0, i1); + } + + auto& pos = base_station_pos; + auto ref_lat = pos.lat; + auto ref_lon = pos.lon; + auto poses = CPR::surface_global_decode_ref(YZ0, YZ1, XZ0, XZ1, ref_lat, ref_lon); + if(!poses.has_value()) return std::nullopt; + auto [pos0, pos1] = poses.value(); + return (t0 > t1) ? pos0 : pos1; + } + + + std::optional surface_position_with_ref(const std::string& msg_bin, double lat_ref, double lon_ref, Time t, Time t_ref) { + auto [YZ, XZ, i] = CPR::get(msg_bin); + auto pos = CPR::surface_locale_decode(lat_ref, lon_ref, YZ, XZ, i); + if(!pos.has_value()) return std::nullopt; + auto [lat, lon] = pos.value(); + return Position{lat, lon}; + } + + const D RADIUS = 6371000.0; // 6371 km + D haversine(const Position& p1, const Position& p2) { + // 将角度转换为弧度 + D lat1 = p1.lat * M_PI / 180.0; + D lon1 = p1.lon * M_PI / 180.0; + D lat2 = p2.lat * M_PI / 180.0; + D lon2 = p2.lon * M_PI / 180.0; + // 经度和纬度差 + D delta_lat = lat2 - lat1; + D delta_lon = lon2 - lon1; + // 哈弗辛公式的计算 + D a = std::sin(delta_lat / 2) * std::sin(delta_lat / 2) + + std::cos(lat1) * std::cos(lat2) * std::sin(delta_lon / 2) * std::sin(delta_lon / 2); + D c = 2 * std::atan2(std::sqrt(a), std::sqrt(1 - a)); + // 计算两点之间的距离 + return RADIUS * c; + } + + std::tuple light_time_second_nano(const Position& p1, const Position& p2) { + // 计算两点之间的距离(单位:米) + D distance = haversine(p1, p2); // 单位:米 + // 计算光速传播的时间,单位:秒 + D time_seconds = distance / LIGHT_SPEED; + // 计算整数秒部分 + int seconds = static_cast(time_seconds); + // 计算剩余部分转化为纳秒 + int nanoseconds = static_cast((time_seconds - seconds) * 1e9); + return std::make_tuple(seconds, nanoseconds); + } + + + // WGS84 转换成 笛卡尔坐标系 + constexpr double DEG_TO_RAD_LOCAL = 3.1415926535897932 / 180.0; + constexpr double RAD_TO_DEG_LOCAL = 180.0 /3.1415926535897932; + + + void WGS84_LBH_to_XYZ(double longitude, double latitude, double height, double& X, double& Y, double& Z) + { + double lon = longitude * DEG_TO_RAD_LOCAL; //经度 + double lat = latitude * DEG_TO_RAD_LOCAL; + double hei = height; + + // variable + double a = 6378137.0; //地球赤道半径 ,单位是 m + double b = 6356752.31424518; //地球短半轴 ,单位是 m + //double E = (a * a - b * b) / (a * a); // E = e^2 + //std::cout << "E= "<< E << std::endl; + //double N = a/(sqrt(1-E*sin(lat)*sin(lat))); + double N = a/(sqrt(1-((a * a - b * b) / (a * a)) * sin(lat) * sin(lat))); + //std::cout << "N= "<< N << std::endl; + + X =(N + hei ) * cos(lat) * cos(lon); + Y =(N + hei ) * cos(lat) * sin(lon); + Z =((b * b * N)/ (a * a) + hei) * sin(lat) ; + } + + void XYZ_to_WGS84_LBH(double X, double Y, double Z,double& longitude, double& latitude,double& altitude) + { + double a, b, c, d; + // double Longitude;// 经度 + // double Latitude;// 纬度 + // double Altitude;// 海拔高度 + double p, q; + double N; + a = 6378137.0; + b = 6356752.31424518; + c = sqrt(((a * a) - (b * b)) / (a * a)); + d = sqrt(((a * a) - (b * b)) / (b * b)); + p = sqrt((X * X) + (Y * Y)); + q = atan2((Z * a), (p * b)); + + longitude = atan2(Y, X); + N = a / sqrt(1 - ((c * c) * sin(latitude)* sin(latitude))); + altitude = (p / cos(latitude)) - N; + latitude = atan2((Z + (d * d) * b * pow(sin(q), 3)), (p - (c * c) * a * pow(cos(q), 3))); + + longitude = longitude * RAD_TO_DEG_LOCAL; + latitude = latitude * RAD_TO_DEG_LOCAL; + } +} // namespace CPR + +} \ No newline at end of file diff --git a/SSR/CPR/CPR.h b/SSR/CPR/CPR.h new file mode 100644 index 0000000..3686dc7 --- /dev/null +++ b/SSR/CPR/CPR.h @@ -0,0 +1,140 @@ +#pragma once + +#include "../../../Core/Core/Base/JSON.h" +#include +#include +#include +#include +#include +#include +#include +namespace SSR { +namespace CPR { + using Time = long double; + using D = long double; + using I = std::int64_t; + struct Position { + Position(D lat, D lon) : lat(lat), lon(lon) {} + D lat; + D lon; + [[nodiscard]] std::string to_string() const; + }; + + enum Frame { + Even = 0, //偶数帧 + ODD = 1 //奇数帧 + }; + + using POS = std::optional; + const I NZ = 15; + template + T floor(D x){ + return static_cast(std::floor(x)); + } + D MOD(D x, D y); + I NL(D lat); + I NL2(D lat); + std::tuple encode(D lat, D lon, I Nb, I i); + std::optional> global_decode(I YZ0, I YZ1, I XZ0, I XZ1, I _2, D r = 360.0); + POS locale_decode(D lat_s, D lon_s, I YZi, I XZi, I i, I _2, D r = 360.0); + + + + // 地面位置解码特殊处理 +// static I Nb = 19; +// static I Nb2 = 17; +// static I air_time = 10; +// static I surface_time = 10 * 4; +// static I air_time = 8; +// static I surface_time = 8 * 4; + + static I Nb1 = 19; + static I Nb2 = 17; + inline std::tuple surface_encode(D lat, D lon, I i) { + auto [YZ, XZ] = encode(lat, lon, Nb1, i); + I base = 2 << (Nb2 - 1); + YZ %= base; + XZ %= base; + return {YZ, XZ}; + } + + + + inline I zone_index(D lon){ + return (lon + 180.0)/90.0; + } + + + inline D bb(D lon, D lon_s){ + auto j = zone_index(lon); + auto i = zone_index(lon_s); + D change =(i - j) * 90.0; + D lon2 = lon + change; + return lon2; + } + + + inline std::optional> surface_global_decode(I YZ0, I YZ1, I XZ0, I XZ1) { + auto poses = global_decode(YZ0, YZ1, XZ0, XZ1, Nb2, 90.0); + //auto pos = global_decode(YZ0, YZ1, XZ0, XZ1, t0, t1,Nb, 360.0); + if (!poses.has_value()) { + return std::nullopt; + } + auto [pos0, pos1] = poses.value(); + return std::tuple(pos0, pos1); + } + + inline std::optional> surface_global_decode_ref(I YZ0, I YZ1, I XZ0, I XZ1, D lat_s, D lon_s){ + auto poses = surface_global_decode(YZ0, YZ1, XZ0, XZ1); + if(!poses.has_value()) return std::nullopt; + auto [pos0, pos1] = poses.value(); + auto p0 = Position{bb(pos0.lat, lat_s), bb(pos0.lon, lon_s)}; + auto p1 = Position{bb(pos1.lat, lat_s), bb(pos1.lon, lon_s)}; + return std::tuple(p0, p1); + } + + inline POS surface_locale_decode(D lat_s, D lon_s, I YZi, I XZi, I i) { + auto pos = locale_decode(lat_s, lon_s, YZi, XZi, i, Nb2, 90.0); + //auto pos = locale_decode(lat_s, lon_s, YZi, XZi, i, Nb, 360.0); + if (!pos.has_value()) { + return std::nullopt; + } + auto [lat, lon] = pos.value(); + return Position{bb(lat, lat_s), bb(lon, lon_s)}; + } + + + // time + std::string lat_lon_airborne_34(double latitude, double longitude, CPR::Frame cpr_format); + std::optional airborne_position(const std::string& msg0, const std::string& msg1, Time t0, Time t1); + std::optional airborne_position_with_ref(const std::string& msg_bin, double lat_ref, double lon_ref, Time t, Time t_ref); + + std::string lat_lon_surface_34(double latitude, double longitude, CPR::Frame cpr_format); + std::optional surface_position(const std::string& msg0, const std::string& msg1, Time t0, Time t1, const CPR::Position& base_station_pos); + std::optional surface_position_with_ref(const std::string& msg_bin, double lat_ref, double lon_ref, Time t, Time t_ref); + + + + // YZ, NZ, i + std::tuple get(const std::string& msg_bin); + + + // 计算两点之间的距离 单位 米 + D haversine(const Position& p1, const Position& p2); + + // 单位秒 + const D LIGHT_SPEED = 3.0e8; // 光速,单位:米/秒(300,000,000 m/s) + + // 单位 秒 纳秒 + std::tuple light_time_second_nano(const Position& p1, const Position& p2); + + //地心地固坐标系WGS84经纬度与笛卡尔直角坐标系相互转换的推导 https://blog.csdn.net/abanchao/article/details/128304152 + // L (Latitude):表示纬度,用来指示一个点在地球表面距离赤道的角度。它的范围是从 -90° 到 +90°。 + // B (Longitude):表示经度,用来指示一个点在地球表面相对于本初子午线的角度。它的范围是从 -180° 到 +180°。 + // H (Height):表示高度,通常指的是该点相对于海平面的高度,单位通常是米(m)。 + void WGS84_LBH_to_XYZ(double longitude, double latitude, double height, double& X, double& Y, double& Z); + void XYZ_to_WGS84_LBH(double X, double Y, double Z,double& longitude, double& latitude,double& altitude); +} + + +} diff --git a/SSR/CPR/CPR_TEST.cpp b/SSR/CPR/CPR_TEST.cpp new file mode 100644 index 0000000..bf44271 --- /dev/null +++ b/SSR/CPR/CPR_TEST.cpp @@ -0,0 +1,189 @@ +#pragma once +#include +#include "CPR.h" +#include "Earth.h" +#include "SSR/ADS_B/ADS_B.hpp" + +#include + +#ifdef _USE_GTEST +#include +namespace SSR { + +using namespace CPR; + + +TEST(CPR, _base_func) { + EXPECT_EQ(CPR::floor(3.8), 3); + EXPECT_EQ(CPR::floor(-3.8), -4); + EXPECT_NEAR(MOD(-40, 6), 2, 0.1); + EXPECT_NEAR(MOD(320, 6), 2, 0.1); + EXPECT_EQ(NL(0), 59); + + double a = std::abs(87.0 - 1e-9); + double b = std::abs(87.0 + 1e-9); + EXPECT_EQ(NL(a), 2); + EXPECT_EQ(NL(-a), 2); + EXPECT_EQ(NL(b), 1); + EXPECT_EQ(NL(-b), 1); + + int num = 10000; + double step = 180.0 / num; + for (int i = 0; i < num; ++i) { + double lat = -90.0 + i * step; + EXPECT_EQ(NL(lat), NL2(lat)); + } +} + + +TEST(CPR, 1) { + double lat = 37, lon = -80; + auto [YZ0, XZ0] = encode(lat, lon, 17, 0); + auto [YZ1, XZ1] = encode(lat, lon, 17, 1); + + auto poses = global_decode(YZ0, YZ1, XZ0, XZ1, 17); + if (poses.has_value()) { + auto [pos0, pos1] = poses.value(); + std::cout << pos0.to_string() << " " << pos1.to_string() << std::endl; + } + +} + + +void test_surface_position(double lat, double lon, bool output = false); +void test_position(double lat, double lon, bool output = false); +TEST(CPR, Normal) { + + std::vector lat_list{ + -50, + -50, 10, 50 + }; + std::vector lon_list{ + 80, + 80, 80, 150 + }; + for(auto lat : lat_list) { + for(auto lon : lon_list) { + test_position(lat, lon); + test_surface_position(lat, lon); + } + } + + + // 范围在 0 ~ 90 的 + test_surface_position(37, 55); + test_surface_position(37, -55); + test_surface_position(-37, -55); + test_surface_position(-37, 55); +} + + +void test_surface_position(double lat, double lon, bool output){ + auto [YZ0, XZ0] = surface_encode(lat, lon, 0); + auto [YZ1, XZ1] = surface_encode(lat, lon, 1); + double diff = 2.5; + { + auto poses = surface_global_decode_ref(YZ0, YZ1, XZ0, XZ1, lat, lon); + if (poses.has_value()) {; + auto [pos0, pos1] = poses.value(); + if(output) { + std::cout << pos0.to_string() << " " << pos1.to_string() << std::endl; + } + EXPECT_NEAR(pos0.lat, lat, diff); + EXPECT_NEAR(pos0.lon, lon, diff); + EXPECT_NEAR(pos1.lat, lat, diff); + EXPECT_NEAR(pos1.lon, lon, diff); + } + } + { + auto pos = surface_locale_decode(lat, lon, YZ0, XZ0, 0); + if (pos.has_value()) { + auto [decode_lat, decode_lon] = pos.value(); + if(output){ + std::cout << "lat: " << lat << " decode_lat: " << decode_lat << std::endl; + std::cout << "lon: " << lon << " decode_lon: " << decode_lon << std::endl; + } + EXPECT_NEAR(decode_lat, lat, diff); + EXPECT_NEAR(decode_lon, lon, diff); + } + } + { + auto pos = surface_locale_decode(lat, lon, YZ1, XZ1, 1); + if (pos.has_value()) { + auto [decode_lat, decode_lon] = pos.value(); + if(output){ + std::cout << "lat: " << lat << " decode_lat: " << decode_lat << std::endl; + std::cout << "lon: " << lon << " decode_lon: " << decode_lon << std::endl; + } + EXPECT_NEAR(decode_lat, lat, diff); + EXPECT_NEAR(decode_lon, lon, diff); + } + } +} + + + + +void test_position(double lat, double lon, bool output){ + int Nb = 17; + double diff = 2.5; + auto [YZ0, XZ0] = encode(lat, lon, Nb, 0); + auto [YZ1, XZ1] = encode(lat, lon, Nb, 1); + { + auto poses = global_decode(YZ0, YZ1, XZ0, XZ1, Nb); + if (poses.has_value()) { + auto [pos0, pos1] = poses.value(); + if(output) { + std::cout << "test_position" << VAR_STR_4(YZ0, XZ0, YZ1, XZ1) << std::endl; + std::cout << "test_position" << VAR_STR_4(Position(lat, lon), lon, pos0, pos1) << std::endl; + } else { + EXPECT_NEAR(pos0.lat, lat, diff); + // EXPECT_NEAR(pos0.lon, lon, diff); + EXPECT_NEAR(pos1.lat, lat, diff); + // EXPECT_NEAR(pos1.lon, lon, diff); + } + } + } + { + auto pos = locale_decode(lat, lon, YZ0, XZ0, 0, Nb); + if (pos.has_value()) { + auto [decode_lat, decode_lon] = pos.value(); + if(output){ + std::cout << "lat: " << lat << " decode_lat: " << decode_lat << std::endl; + std::cout << "lon: " << lon << " decode_lon: " << decode_lon << std::endl; + } else { + EXPECT_NEAR(decode_lat, lat, diff); + // EXPECT_NEAR(decode_lon, lon, diff); + } + + } + } + { + auto pos = locale_decode(lat, lon, YZ1, XZ1, 1, Nb); + if (pos.has_value()) { + auto [decode_lat, decode_lon] = pos.value(); + if(output){ + std::cout << "lat: " << lat << " decode_lat: " << decode_lat << std::endl; + // std::cout << "lon: " << lon << " decode_lon: " << decode_lon << std::endl; + } else { + EXPECT_NEAR(decode_lat, lat, diff); + // EXPECT_NEAR(decode_lon, lon, diff); + } + + } + } +} + +struct MSG_Test : ADS_B_T::CPR_MSG { + +}; + +struct Line_Data { + std::string msg; + time_t t; +}; + + +} + +#endif \ No newline at end of file diff --git a/SSR/CPR/Earth.h b/SSR/CPR/Earth.h new file mode 100644 index 0000000..37611c5 --- /dev/null +++ b/SSR/CPR/Earth.h @@ -0,0 +1,159 @@ +#pragma once + + +#include "CPR.h" +#include +#include +#include +namespace SSR { +namespace CPR{ + + struct Range{ + double lower, upper; + std::string to_string(){ + return "[" + std::to_string(lower) + "," + std::to_string(upper) + "]"; + }; + }; + + struct TT{ + std::vector area; + std::vector edge; + std::string to_string(){ + std::ostringstream oss; + std::string ret; + oss << "{\n"; + oss << "\tarea size=" + std::to_string(area.size()) + "\n"; + oss << "\tedge size=" + std::to_string(edge.size()) + "\n\t"; + for(auto e : edge){ + oss << std::fixed << std::setprecision(2) << e << " "; + } + oss << "\n}\n"; + return oss.str(); + }; + }; + + struct LAT_AREA{ + double upper, lower; + TT lon_areas0; + TT lon_areas1; + }; + + + struct Earth{ + + // 从赤道到北极 纬度从0.0到90.0共有60条分割线 划分59个区域 + std::vector lat_edge; + + + + TT lat_0; // 60条区域 分辨率为 360.0/60.0 = 6.0 + TT lat_1; // 59条区域 分辨率为 360.0/59.0 = 6.101694915254237 + + Earth() { + for(int i = 0; i < 59; ++i){ + LAT_AREA lat; + lat.lower = lon_num_boundary[i]; + lat.upper = lon_num_boundary[i + 1]; + lat.lon_areas0 = create_Range(59 - i, -180, 180); + lat.lon_areas1 = create_Range(59 - i - 1, -180, 180); + lat_edge.push_back(lat); + } + int lat_0_num = 4 * NZ - 0; + int lat_1_num = 4 * NZ - 1; + lat_0 = create_Range(lat_0_num, 0, 360.0); + lat_1= create_Range(lat_1_num, 0, 360.0); + } + static TT create_Range(int num, double lower, double upper){ + TT ret; + ret.area.resize(num); + ret.edge.resize(num + 1); + double step = (upper - lower) / static_cast(num); + ret.edge[0] = lower; + for(int i = 0; i < num; ++i){ + double cur_lower = step * i; + double cur_upper = cur_lower + step; + ret.area[i] = {cur_lower, cur_upper}; + ret.edge[i + 1] = cur_upper; + cur_lower = cur_upper; + } + return ret; + } + // 共60个 59 个间隔 59 - 1 + + std::string lat_str(){ + std::ostringstream oss; + oss << "{\n\tlon_num_boundary:" << lon_num_boundary.size() << "\n\t"; + for(auto& e : lon_num_boundary){ + oss << std::fixed << std::setprecision(2) << e << " "; + } + oss << "\n}"; + return oss.str(); + } + + std::vector lon_num_boundary { + 0.00000000, + 10.47047130, + 14.82817437, + 18.18626357, + 21.02939493, + 23.54504487, + 25.82924707, + 27.93898710, + 29.91135686, + 31.77209708, + 33.53993436, + 35.22899598, + 36.85025108, + 38.41241892, + 39.92256684, + 41.38651832, + 42.80914012, + 44.19454951, + 45.54626723, + 46.86733252, + 48.16039128, + 49.42776439, + 50.67150166, + 51.89342469, + 53.09516153, + 54.27817472, + 55.44378444, + 56.59318756, + 57.72747354, + 58.84763776, + 59.95459277, + 61.04917774, + 62.13216659, + 63.20427479, + 64.26616523, + 65.31845310, + 66.36171008, + 67.39646774, + 68.42322022, + 69.44242631, + 70.45451075, + 71.45986473, + 72.45884545, + 73.45177442, + 74.43893416, + 75.42056257, + 76.39684391, + 77.36789461, + 78.33374083, + 79.29428225, + 80.24923213, + 81.19801349, + 82.13956981, + 83.07199445, + 83.99173563, + 84.89166191, + 85.75541621, + 86.53536998, + 87.00000000, + 90.00000000, + }; + }; +} + + +} \ No newline at end of file diff --git a/SSR/Commb/Commb.cpp b/SSR/Commb/Commb.cpp new file mode 100644 index 0000000..182e526 --- /dev/null +++ b/SSR/Commb/Commb.cpp @@ -0,0 +1,2 @@ +#include "Commb.h" + diff --git a/SSR/Commb/Commb.h b/SSR/Commb/Commb.h new file mode 100644 index 0000000..0aeacf2 --- /dev/null +++ b/SSR/Commb/Commb.h @@ -0,0 +1,57 @@ +#pragma once + +#include "Commb.hpp" + +namespace SSR::Comm_B { +template +struct Base_Builder : T::Reply_Base_Builder, with_AP { + +}; +template +using Base = Base_Builder; +template +using Alt = Comm_B_Altitude_Reply; +template +using Id = Comm_B_Identity_Reply; +namespace ELS { +ADD_112_3(Altitude_Data_Link_Capability_Report_BDS_1_0, Base, Alt, ELS_T::BDS10_Data_Link_Capability_Report) +ADD_112_3(Identity_Data_Link_Capability_Report_BDS_1_0, Base, Id, ELS_T::BDS10_Data_Link_Capability_Report) +ADD_112_3(Altitude_Common_usage_GICB_Capability_Report_BDS_1_7, Base, Alt, + ELS_T::BDS17_Common_usage_GICB_Capability_Report) +ADD_112_3(Identity_Common_usage_GICB_Capability_Report_BDS_1_7, Base, Id, + ELS_T::BDS17_Common_usage_GICB_Capability_Report) +ADD_112_3(Altitude_Aircraft_Identification_BDS_2_0, Base, Alt, ELS_T::BDS20_Aircraft_Identification) +ADD_112_3(Identity_Aircraft_Identification_BDS_2_0, Base, Id, ELS_T::BDS20_Aircraft_Identification) +template +using One = ELS_T::One_threat; +template +using Muti = ELS_T::Muti_threat; +ADD_112_3(ACAS_alt_one, Base, Alt, One) +ADD_112_4(ACAS_alt_one_ta, Base, Alt, One, ELS_T::Mode_S_transponder_address) +ADD_112_4(ACAS_alt_one_ARB, Base, Alt, One, ELS_T::Altitude_Range_Bearing) +ADD_112_3(ACAS_alt_Muti, Base, Alt, Muti) +ADD_112_4(ACAS_alt_Muti_ta, Base, Alt, Muti, ELS_T::Mode_S_transponder_address) +ADD_112_4(ACAS_alt_Muti_ARB, Base, Alt, Muti, ELS_T::Altitude_Range_Bearing) +ADD_112_3(ACAS_id_one, Base, Id, One) +ADD_112_4(ACAS_id_one_ta, Base, Id, One, ELS_T::Mode_S_transponder_address) +ADD_112_4(ACAS_id_one_ARB, Base, Id, One, ELS_T::Altitude_Range_Bearing) +ADD_112_3(ACAS_id_Muti, Base, Id, Muti) +ADD_112_4(ACAS_id_Muti_ta, Base, Id, Muti, ELS_T::Mode_S_transponder_address) +ADD_112_4(ACAS_id_Muti_ARB, Base, Id, Muti, ELS_T::Altitude_Range_Bearing) +} +namespace EHS { +ADD_112_3(Alt_Selected_Vertical_Intention_BDS_4_0, Base, Alt, EHS_T::BDS40_Selected_Vertical_Intention) +ADD_112_3(Id_Selected_Vertical_Intention_BDS_4_0, Base, Id, EHS_T::BDS40_Selected_Vertical_Intention) +ADD_112_3(Alt_Track_And_Turn_Report_BDS_5_0, Base, Alt, EHS_T::BDS50_Track_And_Turn_Report) +ADD_112_3(Id_Track_And_Turn_Report_BDS_5_0, Base, Id, EHS_T::BDS50_Track_And_Turn_Report) +ADD_112_3(Alt_Track_And_Turn_Report_BDS_6_0, Base, Alt, EHS_T::BDS60_Heading_And_Speed_Report) +ADD_112_3(Id_Track_And_Turn_Report_BDS_6_0, Base, Id, EHS_T::BDS60_Heading_And_Speed_Report) +} +namespace MS { +ADD_112_3(Alt_Meteorological_routine_air_report_BDS_4_4, Base, Alt, MS_T::BDS44_Meteorological_routine_air_report) +ADD_112_3(Id_Meteorological_routine_air_report_BDS_4_4, Base, Id, MS_T::BDS44_Meteorological_routine_air_report) +ADD_112_3(Alt_Meteorological_hazard_report_BDS_4_5, Base, Alt, MS_T::BDS45_Meteorological_hazard_report) +ADD_112_3(Id_Meteorological_hazard_report_BDS_4_5, Base, Id, MS_T::BDS45_Meteorological_hazard_report) +} +} + diff --git a/SSR/Commb/Commb.hpp b/SSR/Commb/Commb.hpp new file mode 100644 index 0000000..f1ab702 --- /dev/null +++ b/SSR/Commb/Commb.hpp @@ -0,0 +1,836 @@ +#pragma once +#include "../ACAS/ACAS_9_56.hpp" +#include "../Mode_S/Mode_S.h" +#include "../Mode_S/Mode_S.hpp" +namespace SSR::Comm_B { + template + struct Comm_B_Altitude_Reply : T::Alt { + void set(std::string& msg_bin) { set_df(msg_bin, Downlink_Format::Comm_B_Altitude_Reply_20); } + }; + template + struct Comm_B_Identity_Reply : T::Id { + void set(std::string& msg_bin) { set_df(msg_bin, Downlink_Format::Comm_B_Identity_Reply_21); } + }; + namespace ELS_T { + // 数据链路能力报告 + template + struct BDS10_Data_Link_Capability_Report : BDS10 { + PROP_Bit_T(1, configuration_flag, 0) + PROP_Bit_T(1, OCC, 0) //表示应答器是否支持 BDS overlay (Data Parity) + PROP_T(ELS::ACAS_Status, ACAS_status, ELS::ACAS_Status::Operating) + PROP_T(ELS::Mode_S_Subnetwork_Version, mode_s_subnetwork_version, + ELS::Mode_S_Subnetwork_Version::NotAvailable) + PROP_Bit_T(1, transponder_enhanced_protocol_indicator, 0) + //当应答器增强协议指示位设置为 1 时,应答器是 5 级应答器。值 0 表示 2 到 4 级应答器。 + PROP_Bit_T(1, mode_s_specific_services_capability, 0) //当 S 模式 特定服务功能位设置为 1 时,至少支持一种 S 模式特定服务 + PROP_Bit_T(3, uplink_ELM_throughput, 0) + PROP_Bit_T(4, downlink_ELM_throughput, 0) + PROP_Bit_T(1, aircraft_identification_capability, 0) //飞机识别功能表明身份识别(呼号)的可用性。 + PROP_Bit_T(1, SCS, 0) //当 Squitter 功能子字段位设置为 1 时,BDS 0,5 和 0,6 寄存器都已在过去 9 到 11 秒内更新。 + PROP_Bit_T(1, SIC, 0) //监控识别码 确定应答器是否具有监控识别码功能。 + PROP_Bit_T(1, GICB_capability_report, 0) + // 每次更改 GICB 容量报告 (BDS 1,7) 时,常用 GICB 功能报告都设置为 1。寄存器 1,7 每分钟采样一次以检查更改。 + PROP_T(ELS::Hybrid_Surveillance_Status, hybrid_surveillance_status, + ELS::Hybrid_Surveillance_Status::Not_Operational) + PROP_T(ELS::ACAS_TA_RA_Status, ACAS_TA_RA_status, ELS::ACAS_TA_RA_Status::Generating_TAs_Only) + PROP_T(ELS::RTCA_Version_Status, RTCA_version_status, ELS::RTCA_Version_Status::RTCA_DO_185_Pre_ACAS) + PROP_Bit_T(16, DTE_status, 0) + void parse(std::string& msg_bin) { + get_bin<40, 1>(msg_bin, configuration_flag); + get_bin<46, 1>(msg_bin, OCC); + get_bin<47, 1, ELS::ACAS_Status>(msg_bin, ACAS_status); + get_bin<48, 7, ELS::Mode_S_Subnetwork_Version>(msg_bin, mode_s_subnetwork_version); + get_bin<55, 1>(msg_bin, transponder_enhanced_protocol_indicator); + get_bin<56, 1>(msg_bin, mode_s_specific_services_capability); + get_bin<57, 3>(msg_bin, uplink_ELM_throughput); + get_bin<60, 4>(msg_bin, downlink_ELM_throughput); + get_bin<64, 1>(msg_bin, aircraft_identification_capability); + get_bin<65, 1>(msg_bin, SCS); + get_bin<66, 1>(msg_bin, SIC); + get_bin<67, 1>(msg_bin, GICB_capability_report); + get_bin<68, 1, ELS::Hybrid_Surveillance_Status>(msg_bin, hybrid_surveillance_status); + get_bin<69, 1, ELS::ACAS_TA_RA_Status>(msg_bin, ACAS_TA_RA_status); + get_bin<70, 2, ELS::RTCA_Version_Status>(msg_bin, RTCA_version_status); + get_bin<72, 16>(msg_bin, DTE_status); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(configuration_flag) + Ret_J(OCC) + Ret_J(ACAS_status) + Ret_J(mode_s_subnetwork_version) + Ret_J(transponder_enhanced_protocol_indicator) + Ret_J(uplink_ELM_throughput) + Ret_J(downlink_ELM_throughput) + Ret_J(aircraft_identification_capability) + Ret_J(SCS) + Ret_J(SIC) + Ret_J(GICB_capability_report) + Ret_J(hybrid_surveillance_status) + Ret_J(ACAS_TA_RA_status) + Ret_J(RTCA_version_status) + Ret_J(DTE_status) + return ret; + } + + void set(std::string& msg_bin) { + set_bin<32, 4>(msg_bin, 1); + set_bin<36, 4>(msg_bin, 0); + set_bin<40, 1>(msg_bin, configuration_flag); + set_bin<46, 1>(msg_bin, OCC); + set_bin<47, 1>(msg_bin, static_cast(ACAS_status)); + set_bin<48, 7>(msg_bin, static_cast(mode_s_subnetwork_version)); + set_bin<55, 1>(msg_bin, transponder_enhanced_protocol_indicator); + set_bin<56, 1>(msg_bin, mode_s_specific_services_capability); + set_bin<57, 3>(msg_bin, uplink_ELM_throughput); + set_bin<60, 4>(msg_bin, downlink_ELM_throughput); + set_bin<64, 1>(msg_bin, aircraft_identification_capability); + set_bin<65, 1>(msg_bin, SCS); + set_bin<66, 1>(msg_bin, SIC); + set_bin<67, 1>(msg_bin, GICB_capability_report); + set_bin<68, 1>(msg_bin, static_cast(hybrid_surveillance_status)); + set_bin<69, 1>(msg_bin, static_cast(ACAS_TA_RA_status)); + set_bin<70, 2>(msg_bin, static_cast(RTCA_version_status)); + set_bin<72, 16>(msg_bin, DTE_status); + } + }; + T_Parser(BDS10_Data_Link_Capability_Report) + // 常见用法GICB能力报告 + template + struct BDS17_Common_usage_GICB_Capability_Report : BDS17 { + PROP_Bit_T(1, Extended_squitter_airborne_position_0_5, 0) + PROP_Bit_T(1, Extended_squitter_surface_position_position_0_6, 0) + PROP_Bit_T(1, Extended_squitter_status_0_7, 0) + PROP_Bit_T(1, Extended_squitter_identification_and_category_0_8, 0) + PROP_Bit_T(1, Extended_squitter_airborne_velocity_information_0_9, 0) + PROP_Bit_T(1, Extended_squitter_event_driven_information_0_A, 0) + PROP_Bit_T(1, Aircraft_identification_2_0, 0) + PROP_Bit_T(1, Aircraft_registration_number_2_1, 0) + PROP_Bit_T(1, Selected_vertical_intention_4_0, 0) + PROP_Bit_T(1, Next_waypoint_identifier_4_1, 0) + PROP_Bit_T(1, Next_waypoint_positionr_4_2, 0) + PROP_Bit_T(1, Next_waypoint_information_4_3, 0) + PROP_Bit_T(1, Meteorological_routine_report_4_4, 0) + PROP_Bit_T(1, Meteorological_hazard_report_4_5, 0) + PROP_Bit_T(1, VHF_channel_report_4_8, 0) + PROP_Bit_T(1, Track_and_turn_report_5_0, 0) + PROP_Bit_T(1, Position_coarse_5_1, 0) + PROP_Bit_T(1, Position_fine_5_2, 0) + PROP_Bit_T(1, Air_referenced_state_vector_5_3, 0) + PROP_Bit_T(1, Waypoint_1_5_4, 0) + PROP_Bit_T(1, Waypoint_2_5_5, 0) + PROP_Bit_T(1, Waypoint_3_5_6, 0) + PROP_Bit_T(1, Quasi_static_parameter_monitoring_5_F, 0) + PROP_Bit_T(1, Heading_and_speed_report_6_0, 0) + PROP_Bit_T(1, Military_applications_F_1, 0) + void parse(std::string& msg_bin) { + get_bin<32, 1>(msg_bin, Extended_squitter_airborne_position_0_5); + get_bin<33, 1>(msg_bin, Extended_squitter_surface_position_position_0_6); + get_bin<34, 1>(msg_bin, Extended_squitter_status_0_7); + get_bin<35, 1>(msg_bin, Extended_squitter_identification_and_category_0_8); + get_bin<36, 1>(msg_bin, Extended_squitter_airborne_velocity_information_0_9); + get_bin<37, 1>(msg_bin, Extended_squitter_event_driven_information_0_A); + get_bin<38, 1>(msg_bin, Aircraft_identification_2_0); + get_bin<39, 1>(msg_bin, Aircraft_registration_number_2_1); + get_bin<40, 1>(msg_bin, Selected_vertical_intention_4_0); + get_bin<41, 1>(msg_bin, Next_waypoint_identifier_4_1); + get_bin<42, 1>(msg_bin, Next_waypoint_positionr_4_2); + get_bin<43, 1>(msg_bin, Next_waypoint_information_4_3); + get_bin<44, 1>(msg_bin, Meteorological_routine_report_4_4); + get_bin<45, 1>(msg_bin, Meteorological_hazard_report_4_5); + get_bin<46, 1>(msg_bin, VHF_channel_report_4_8); + get_bin<47, 1>(msg_bin, Track_and_turn_report_5_0); + get_bin<48, 1>(msg_bin, Position_coarse_5_1); + get_bin<49, 1>(msg_bin, Position_fine_5_2); + get_bin<50, 1>(msg_bin, Air_referenced_state_vector_5_3); + get_bin<51, 1>(msg_bin, Waypoint_1_5_4); + get_bin<52, 1>(msg_bin, Waypoint_2_5_5); + get_bin<53, 1>(msg_bin, Waypoint_3_5_6); + get_bin<54, 1>(msg_bin, Quasi_static_parameter_monitoring_5_F); + get_bin<55, 1>(msg_bin, Heading_and_speed_report_6_0); + get_bin<60, 1>(msg_bin, Military_applications_F_1); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(Extended_squitter_airborne_position_0_5) + Ret_J(Extended_squitter_surface_position_position_0_6) + Ret_J(Extended_squitter_status_0_7) + Ret_J(Extended_squitter_identification_and_category_0_8) + Ret_J(Extended_squitter_airborne_velocity_information_0_9) + Ret_J(Extended_squitter_event_driven_information_0_A) + Ret_J(Aircraft_identification_2_0) + Ret_J(Aircraft_registration_number_2_1) + Ret_J(Selected_vertical_intention_4_0) + Ret_J(Next_waypoint_identifier_4_1) + Ret_J(Next_waypoint_positionr_4_2) + Ret_J(Next_waypoint_information_4_3) + Ret_J(Meteorological_routine_report_4_4) + Ret_J(Meteorological_hazard_report_4_5) + Ret_J(VHF_channel_report_4_8) + Ret_J(Track_and_turn_report_5_0) + Ret_J(Position_coarse_5_1) + Ret_J(Position_fine_5_2) + Ret_J(Air_referenced_state_vector_5_3) + Ret_J(Waypoint_1_5_4) + Ret_J(Waypoint_2_5_5) + Ret_J(Waypoint_3_5_6) + Ret_J(Quasi_static_parameter_monitoring_5_F) + Ret_J(Heading_and_speed_report_6_0) + Ret_J(Military_applications_F_1) + return ret; + } + + void set(std::string& msg_bin) { + set_bin<32, 1>(msg_bin, Extended_squitter_airborne_position_0_5); + set_bin<33, 1>(msg_bin, Extended_squitter_surface_position_position_0_6); + set_bin<34, 1>(msg_bin, Extended_squitter_status_0_7); + set_bin<35, 1>(msg_bin, Extended_squitter_identification_and_category_0_8); + set_bin<36, 1>(msg_bin, Extended_squitter_airborne_velocity_information_0_9); + set_bin<37, 1>(msg_bin, Extended_squitter_event_driven_information_0_A); + set_bin<38, 1>(msg_bin, Aircraft_identification_2_0); + set_bin<39, 1>(msg_bin, Aircraft_registration_number_2_1); + set_bin<40, 1>(msg_bin, Selected_vertical_intention_4_0); + set_bin<41, 1>(msg_bin, Next_waypoint_identifier_4_1); + set_bin<42, 1>(msg_bin, Next_waypoint_positionr_4_2); + set_bin<43, 1>(msg_bin, Next_waypoint_information_4_3); + set_bin<44, 1>(msg_bin, Meteorological_routine_report_4_4); + set_bin<45, 1>(msg_bin, Meteorological_hazard_report_4_5); + set_bin<46, 1>(msg_bin, VHF_channel_report_4_8); + set_bin<47, 1>(msg_bin, Track_and_turn_report_5_0); + set_bin<48, 1>(msg_bin, Position_coarse_5_1); + set_bin<49, 1>(msg_bin, Position_fine_5_2); + set_bin<50, 1>(msg_bin, Air_referenced_state_vector_5_3); + set_bin<51, 1>(msg_bin, Waypoint_1_5_4); + set_bin<52, 1>(msg_bin, Waypoint_2_5_5); + set_bin<53, 1>(msg_bin, Waypoint_3_5_6); + set_bin<54, 1>(msg_bin, Quasi_static_parameter_monitoring_5_F); + set_bin<55, 1>(msg_bin, Heading_and_speed_report_6_0); + set_bin<60, 1>(msg_bin, Military_applications_F_1); + } + }; + T_Parser(BDS17_Common_usage_GICB_Capability_Report) + // 飞机识别 + template + struct BDS20_Aircraft_Identification : BDS20 { + PROP_T(std::string, call_sign, "???"); + void parse(std::string& msg_bin) { + call_sign = SSR::get_call_sign(msg_bin); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(call_sign) + return ret; + } + void set(std::string& msg_bin) { + set_bin<32, 4>(msg_bin, 2); + set_bin<36, 4>(msg_bin, 0); + SSR::set_call_sign(msg_bin, call_sign); + } + }; + T_Parser(BDS20_Aircraft_Identification) + // ACAS主动解决咨询 + + + template + struct BDS30_ACAS_Active_Resolution_Advisory : ACAS_Active_Resolution_Advisory_9_56, BDS30 { + + }; + T_Parser(BDS30_ACAS_Active_Resolution_Advisory) + + + + template + struct Mode_S_transponder_address { + PROP_T(std::string, transponder_address, "000000") + void parse(std::string& msg_bin) { + transponder_address = bin2hex(msg_bin.substr(62, 24)); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(transponder_address) + return ret; + } + void set(std::string& msg_bin) { + std::string newIcao = hex2bin(transponder_address); + set_bin<62, 24>(msg_bin, newIcao); + set_bin<86, 2>(msg_bin, 0); + } + }; + T_Parser(Mode_S_transponder_address) + template + struct Altitude_Range_Bearing { + PROP_T(int, alt13, 0) + PROP_T(double, threat_range, -1) + PROP_T(double, threat_bearing, -1) + void parse(std::string& msg_bin) { + alt13 = altitude13_ft(msg_bin.substr(62, 13)); + { + double r = get_bin<75, 7>(msg_bin); + if (r == 127) + { + threat_range = 12.55; + } else + { + threat_range = (r - 1.0) / 10.0; + } + } + threat_bearing = (get_bin<82, 6>(msg_bin) - 1.0) * 6.0; + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(alt13) + Ret_J(threat_range) + Ret_J(threat_bearing) + return ret; + } + + void set(std::string& msg_bin) { + set_bin<62, 13>(msg_bin, encode_altitude13_feet(alt13)); + if (threat_range != -1) + { + set_bin<75, 7>(msg_bin, threat_range > 12.55 ? 127 : threat_range * 10 + 1); + } + if (threat_bearing != -1) + { + int value = threat_bearing / 6 + 1; + if (value > 60) value = 60; + set_bin<82, 6>(msg_bin, value); + } + } + }; + T_Parser(Altitude_Range_Bearing) + + + // MB:9=1 + template + struct One_threat : BDS30 { + PROP_Bit_T(1, type, 0) // 0: 预防性的 (Preventive), 1: 纠正性的 (Corrective) + PROP_Bit_T(1, sense, 0) // 0: 上升方向 (Upward sense), 1: 下降方向 (Downward sense) + PROP_Bit_T(1, rate, 0) // 0: 非增速 (Not increased rate), 1: 增速 (Increased rate) + PROP_Bit_T(1, reversal, 0) // 0: 非反向 (Not a sense reversal), 1: 反向 (Sense reversal) + PROP_Bit_T(1, altitude_crossing, 0) // 0: 非高度穿越 (Not altitude crossing), 1: 高度穿越 (Altitude crossing) + PROP_Bit_T(1, vertical_speed, 0) // 0: 垂直速度限制 (Vertical speed limit), 1: 正值 (Positive) + void parse(std::string& msg_bin) { + get_bin<32 + 9, 1>(msg_bin, type); + get_bin<32 + 10, 1>(msg_bin, sense); + get_bin<32 + 11, 1>(msg_bin, rate); + get_bin<32 + 12, 1>(msg_bin, reversal); + get_bin<32 + 13, 1>(msg_bin, altitude_crossing); + get_bin<32 + 14, 1>(msg_bin, vertical_speed); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(type) + Ret_J(sense) + Ret_J(rate) + Ret_J(reversal) + Ret_J(altitude_crossing) + Ret_J(vertical_speed) + return ret; + } + + void set(std::string& msg_bin) { + set_bin<32 + 8, 1>(msg_bin, 1); + set_bin<32 + 9, 1>(msg_bin, type); + set_bin<32 + 10, 1>(msg_bin, sense); + set_bin<32 + 11, 1>(msg_bin, rate); + set_bin<32 + 12, 1>(msg_bin, reversal); + set_bin<32 + 13, 1>(msg_bin, altitude_crossing); + set_bin<32 + 14, 1>(msg_bin, vertical_speed); + } + }; + T_Parser(One_threat) + + // MB:9=0 and MB:28=1 + template + struct Muti_threat : BDS30 { + PROP_Bit_T(1, upward_correction, 0) + // 0: RA 不需要上升方向的纠正 (Does not require a correction in the upward sense), 1: 需要上升方向的纠正 (Requires a correction in the upward sense) + PROP_Bit_T(1, positive_climb, 0) + // 0: RA 不需要正爬升 (Does not require a positive climb), 1: 需要正爬升 (Requires a positive climb) + PROP_Bit_T(1, downward_correction, 0) + // 0: RA 不需要下降方向的纠正 (Does not require a correction in the downward sense), 1: 需要下降方向的纠正 (Requires a correction in the downward sense) + PROP_Bit_T(1, positive_descend, 0) + // 0: RA 不需要正下降 (Does not require a positive descend), 1: 需要正下降 (Requires a positive descend) + PROP_Bit_T(1, crossing_required, 0) + // 0: RA 不需要穿越 (Does not require a crossing), 1: 需要穿越 (Requires a crossing) + PROP_Bit_T(1, sense_reversal, 0) // 0: RA 不是方向反转 (Is not a sense reversal), 1: 是方向反转 (Is a sense reversal) + void parse(std::string& msg_bin) { + get_bin<32 + 9, 1>(msg_bin, upward_correction); + get_bin<32 + 10, 1>(msg_bin, positive_climb); + get_bin<32 + 11, 1>(msg_bin, downward_correction); + get_bin<32 + 12, 1>(msg_bin, positive_descend); + get_bin<32 + 13, 1>(msg_bin, crossing_required); + get_bin<32 + 14, 1>(msg_bin, sense_reversal); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(upward_correction) + Ret_J(positive_climb) + Ret_J(downward_correction) + Ret_J(positive_descend) + Ret_J(crossing_required) + Ret_J(sense_reversal) + return ret; + } + + void set(std::string& msg_bin) { + set_bin<32 + 8, 1>(msg_bin, 0); + set_bin<32 + 9, 1>(msg_bin, upward_correction); + set_bin<32 + 10, 1>(msg_bin, positive_climb); + set_bin<32 + 11, 1>(msg_bin, downward_correction); + set_bin<32 + 12, 1>(msg_bin, positive_descend); + set_bin<32 + 13, 1>(msg_bin, crossing_required); + set_bin<32 + 14, 1>(msg_bin, sense_reversal); + } + }; + T_Parser(Muti_threat) + } + namespace EHS_T { + //Mode S enhanced surveillance S 模式增强监控 + using namespace EHS; + // 选定的垂直意图 + template + struct BDS40_Selected_Vertical_Intention : BDS40 { + PROP_Range_T(int, 0, 65520, MCP_FCU_height, -1) + PROP_Range_T(int, 0, 65520, FMS_height, -1) + PROP_Range_T(double, 0, 410, barometric_pressure, -1) + PROP_Bit_T(1, MCP_FCU_state, 0) + PROP_Bit_T(1, MCP_VNAV_state, 0) + PROP_Bit_T(1, Alt_Hold_state, 0) + PROP_Bit_T(1, Approach_state, 0) + PROP_Bit_T(1, target_altitude_source_state, 0) + PROP_T(EHS::Target_Altitude_Source, target_altitude_source_type, EHS::Target_Altitude_Source::Unknown) + + void set(std::string& msg_bin) { + if (MCP_FCU_height != -1) + { + set_bin<32, 1>(msg_bin, 1); + set_bin<33, 12>(msg_bin, MCP_FCU_height / 16); + } + if (FMS_height != -1) + { + set_bin<45, 1>(msg_bin, 1); + set_bin<46, 12>(msg_bin, FMS_height / 16); + } + if (barometric_pressure != -1) + { + set_bin<58, 1>(msg_bin, barometric_pressure); + set_bin<59, 12>(msg_bin, barometric_pressure * 10 + 8000); + } + set_bin<79, 1>(msg_bin, MCP_FCU_state); + set_bin<80, 1>(msg_bin, MCP_VNAV_state); + set_bin<81, 1>(msg_bin, Alt_Hold_state); + set_bin<82, 1>(msg_bin, Approach_state); + set_bin<85, 1>(msg_bin, target_altitude_source_state); + set_bin<86, 2>(msg_bin, static_cast(target_altitude_source_type)); + } + void parse(std::string& msg_bin) { + if (get_bin<32, 1>(msg_bin) != 0) + { + MCP_FCU_height = get_bin<33, 12>(msg_bin) * 16; + } + if (get_bin<45, 1>(msg_bin) != 0) + { + FMS_height = get_bin<46, 12>(msg_bin) * 16; + } + if (get_bin<58, 1>(msg_bin) != 0) + { + barometric_pressure = get_bin<59, 12>(msg_bin) - 800; + } + get_bin<79, 1>(msg_bin, MCP_FCU_state); + get_bin<80, 1>(msg_bin, MCP_VNAV_state); + get_bin<81, 1>(msg_bin, Alt_Hold_state); + get_bin<82, 1>(msg_bin, Approach_state); + get_bin<85, 1>(msg_bin, target_altitude_source_state); + get_bin<86, 2, Target_Altitude_Source>(msg_bin, target_altitude_source_type); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(MCP_FCU_height) + Ret_J(FMS_height) + Ret_J(barometric_pressure) + Ret_J(MCP_FCU_state) + Ret_J(MCP_VNAV_state) + Ret_J(Alt_Hold_state) + Ret_J(Approach_state) + Ret_J(target_altitude_source_state) + Ret_J(target_altitude_source_type) + return ret; + } + }; + T_Parser(BDS40_Selected_Vertical_Intention) + // 跟踪和转向报告 + template + struct BDS50_Track_And_Turn_Report : BDS50 { + PROP_Range_T(double, -90.0, 90.0, roll_angle, -1) + PROP_Range_T(double, -180.0, 180.0, track_angle, -1) + PROP_Range_T(int, 0, 2046, Ground_speed, -1) + PROP_Range_T(double, -16.0, 16.0, track_angle_rate, -1) + PROP_Range_T(int, 0, 2046, true_airspeed, -1) + + void set(std::string& msg_bin) { + if (roll_angle != -1) + { + double step = 45.0 / 256.0; + set_bin<32, 1>(msg_bin, 1); + set_bin<33, 1>(msg_bin, roll_angle >= 0); + set_bin<34, 9>(msg_bin, std::abs(roll_angle) / step); + } + if (track_angle != -1) + { + double step = 90.0 / 512.0; + set_bin<43, 1>(msg_bin, 1); + set_bin<44, 1>(msg_bin, track_angle >= 0); + set_bin<45, 10>(msg_bin, std::abs(track_angle) / step); + } + if (Ground_speed != -1) + { + set_bin<55, 1>(msg_bin, 1); + set_bin<56, 10>(msg_bin, Ground_speed / 2); + } + if (track_angle_rate != -1) + { + double step = 8.0 / 256.0; + set_bin<67, 1>(msg_bin, 1); + set_bin<68, 9>(msg_bin, std::abs(track_angle_rate) / step); + } + if (true_airspeed != -1) + { + set_bin<77, 1>(msg_bin, 1); + set_bin<78, 10>(msg_bin, true_airspeed / 2); + } + } + void parse(std::string& msg_bin) { + if (get_bin<32, 1>(msg_bin) != 0) + { + double step = 45.0 / 256.0; + int sign = get_bin<33, 1>(msg_bin) ? 1 : -1; + roll_angle = sign * get_bin<34, 9>(msg_bin) * step; + } + if (get_bin<43, 1>(msg_bin) != 0) + { + double step = 90.0 / 512.0; + int sign = get_bin<44, 1>(msg_bin) ? 1 : -1; + track_angle = sign * get_bin<45, 10>(msg_bin) * step; + } + if (get_bin<55, 1>(msg_bin) != 0) + { + int step = 2; + int sign = get_bin<55, 1>(msg_bin) ? 1 : -1; + Ground_speed = sign * get_bin<56, 10>(msg_bin) * step; + } + if (get_bin<67, 1>(msg_bin) != 0) + { + double step = 8.0 / 256.0; + int sign = get_bin<67, 1>(msg_bin) ? 1 : -1; + track_angle_rate = sign * get_bin<68, 9>(msg_bin) * step; + } + if (get_bin<77, 1>(msg_bin) != 0) + { + int step = 2; + true_airspeed = get_bin<77, 10>(msg_bin) * step; + } + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(roll_angle) + Ret_J(track_angle) + Ret_J(Ground_speed) + Ret_J(track_angle_rate) + Ret_J(true_airspeed) + return ret; + } + }; + T_Parser(BDS50_Track_And_Turn_Report) + // 航向和速度报告 + + + template + struct BDS60_Heading_And_Speed_Report : BDS60 { + PROP_Range_T(double, -180.0, 180.0, magnetic_heading, -1) + PROP_Range_T(int, 0, 1023, indicated_airspeed, -1) + PROP_Range_T(double, 0, 4.092, mach, -1) + PROP_Range_T(int, -16384, 16352, barometric_altitude_rate, -1) + PROP_Range_T(int, -16384, 16352, inertial_vertical_velocity, -1) + void set(std::string& msg_bin) { + if (magnetic_heading != -1) + { + double step = 90.0 / 512.0; + set_bin<32, 1>(msg_bin, 1); + set_bin<33, 1>(msg_bin, magnetic_heading >= 0); + set_bin<34, 10>(msg_bin, std::abs(magnetic_heading) / step); + } + if (indicated_airspeed != -1) + { + set_bin<44, 1>(msg_bin, 1); + set_bin<45, 10>(msg_bin, indicated_airspeed); + } + if (mach != -1) + { + double step = 0.004; + set_bin<55, 1>(msg_bin, 1); + set_bin<56, 10>(msg_bin, mach / step); + } + if (barometric_altitude_rate != -1) + { + int step = 32; + set_bin<66, 1>(msg_bin, 1); + set_bin<67, 1>(msg_bin, barometric_altitude_rate >= 0); + set_bin<68, 9>(msg_bin, std::abs(barometric_altitude_rate) / step); + } + if (inertial_vertical_velocity != -1) + { + int step = 32; + set_bin<77, 1>(msg_bin, 1); + set_bin<78, 1>(msg_bin, inertial_vertical_velocity >= 0); + set_bin<79, 9>(msg_bin, std::abs(inertial_vertical_velocity) / step); + } + } + void parse(std::string& msg_bin) { + if (get_bin<32, 1>(msg_bin) != 0) + { + double step = 90.0 / 512.0; + int sign = get_bin<33, 1>(msg_bin) ? 1 : -1; + magnetic_heading = sign * step * get_bin<34, 10>(msg_bin); + } + if (get_bin<44, 1>(msg_bin) != 0) + { + indicated_airspeed = get_bin<45, 10>(msg_bin); + } + if (get_bin<55, 1>(msg_bin) != 0) + { + double step = 0.004; + mach = step * get_bin<56, 10>(msg_bin); + } + if (get_bin<66, 1>(msg_bin) != 0) + { + int step = 32; + int sign = get_bin<67, 1>(msg_bin) ? 1 : -1; + barometric_altitude_rate = sign * step * sign * step * get_bin<79, 9>(msg_bin); + } + if (get_bin<77, 1>(msg_bin) != 0) + { + int step = 32; + int sign = get_bin<78, 1>(msg_bin) ? 1 : -1; + inertial_vertical_velocity = sign * step * get_bin<79, 9>(msg_bin); + } + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(magnetic_heading) + Ret_J(indicated_airspeed) + Ret_J(mach) + Ret_J(barometric_altitude_rate) + Ret_J(inertial_vertical_velocity) + return ret; + } + }; + T_Parser(BDS60_Heading_And_Speed_Report) + /* + 气压高度率 Barometric altitude rate + 1. 大气数据系统 Air Data System + 2. 惯性参考系统/飞行管理系统 Inertial Reference System/Flight Management System + + 惯性垂直速度 Inertial vertical velocity + 1.飞行管理计算机/ GNSS集成 Flight Management Computer / GNSS integrated + 2. 飞行管理计算机(通用) Flight Management Computer (General) + 3. 惯性参考系统/飞行管理系统 Inertial Reference System/Flight Management System + */ + } + namespace MS_T { + // 气象常规空气报告 + template + struct BDS44_Meteorological_routine_air_report : BDS44 { + PROP_T(Figure_Merit_Source, figure_merit_source, Figure_Merit_Source::Invalid) + //温度使用 10 位编码,其中最低有效位值应为 0.125 + //。但是,根据官方文件 [ICAO 2008],LSB 值为 0.25 + //。国际民航组织计划 在未来将其更新为 0.125。但是,大多数当前实现仍然使用 0.25, 这里仍用0.25 + void set_wind(int wind_speed, double wind_direction, double temperature) { + Handle_Error3(wind_speed < 0 || wind_speed > 100, + "wind speed error! wind speed==" + std::to_string(wind_speed)); + Handle_Error3(wind_direction < 0 || wind_direction, + "wind direction error! wind direction == " + std::to_string(wind_direction)); + Handle_Error3(temperature < 0 || temperature > 100, + "wind temperature error! wind temperature == " + std::to_string(temperature)); + this->useWind = true; + this->wind_speed = wind_speed; + this->wind_direction = wind_direction; + this->temperature = temperature; + } + PROP_Range_T(int, 0, 2048, pressure, -1) + PROP_T(Severity_Level, turbulence, Severity_Level::NIL) + PROP_Range_T(double, 0, 100, humidity, -1) + + void set(std::string& msg_bin) { + set_bin<32, 4>(msg_bin, static_cast(figure_merit_source)); + if (useWind) + { + set_bin<36, 1>(msg_bin, wind_speed); + set_bin<37, 9>(msg_bin, wind_speed); + set_bin<46, 9>(msg_bin, wind_direction * 256.0 / 180.0); + set_bin<55, 1>(msg_bin, temperature >= 0); + set_bin<56, 10>(msg_bin, std::abs(temperature) / 0.25); + } + if (pressure != -1) + { + set_bin<66, 1>(msg_bin, 1); + set_bin<67, 11>(msg_bin, pressure); + } + if (turbulence != Severity_Level::NIL) + { + set_bin<78, 1>(msg_bin, 1); + set_bin<79, 2>(msg_bin, static_cast(turbulence)); + } + if (humidity != -1) + { + set_bin<81, 1>(msg_bin, 1); + set_bin<82, 6>(msg_bin, humidity * 0.64); + } + } + void parse(std::string& msg_bin) { + get_bin<32, 4, Figure_Merit_Source>(msg_bin, figure_merit_source); + if (get_bin<36, 1>(msg_bin) != 0) + { + get_bin<37, 9>(msg_bin, wind_speed); + wind_direction = get_bin<46, 9>(msg_bin) * 180.0 / 256.0; + int sign = get_bin<55, 1>(msg_bin) ? 1 : -1; + temperature = sign * 0.25 * get_bin<56, 10>(msg_bin); + } + if (get_bin<66, 1>(msg_bin) != 0) + { + get_bin<67, 11>(msg_bin, pressure); + } + if (get_bin<78, 1>(msg_bin) != 0) + { + get_bin<79, 2, Severity_Level>(msg_bin, turbulence); + } + if (get_bin<81, 1>(msg_bin) != 0) + { + humidity = get_bin<82, 6>(msg_bin) / 0.64; + } + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(wind_speed) + Ret_J(wind_direction) + Ret_J(temperature) + Ret_J(pressure) + Ret_J(turbulence) + Ret_J(humidity) + return ret; + } + bool useWind = false; + int wind_speed = -1; + double wind_direction = -1; + double temperature = -1; + }; + T_Parser(BDS44_Meteorological_routine_air_report) + // 气象灾害报告 + template + struct BDS45_Meteorological_hazard_report : BDS45 { + PROP_T(Severity_Level, turbulence, Severity_Level::NIL) + PROP_T(Severity_Level, wind_shear, Severity_Level::NIL) + PROP_T(Severity_Level, microburst, Severity_Level::NIL) + PROP_T(Severity_Level, icing, Severity_Level::NIL) + PROP_T(Severity_Level, wake_vortex, Severity_Level::NIL) + PROP_Range_T(double, -128.0, 128.0, temperature, -200) + PROP_Range_T(int, 0, 2048, pressure, -1) + PROP_Range_T(int, 0, 65528, radio_height, -1) + + void set(std::string& msg_bin) { + if (turbulence != Severity_Level::NIL) + { + set_bin<32, 1>(msg_bin, 1); + set_bin<33, 2>(msg_bin, static_cast(turbulence)); + } + if (wind_shear != Severity_Level::NIL) + { + set_bin<35, 1>(msg_bin, 1); + set_bin<36, 2>(msg_bin, static_cast(wind_shear)); + } + if (microburst != Severity_Level::NIL) + { + set_bin<38, 1>(msg_bin, 1); + set_bin<39, 2>(msg_bin, static_cast(microburst)); + } + if (icing != Severity_Level::NIL) + { + set_bin<41, 1>(msg_bin, 1); + set_bin<42, 2>(msg_bin, static_cast(icing)); + } + if (wake_vortex != Severity_Level::NIL) + { + set_bin<44, 1>(msg_bin, 1); + set_bin<45, 2>(msg_bin, static_cast(wake_vortex)); + } + if (temperature != -200) + { + set_bin<47, 1>(msg_bin, 1); + set_bin<48, 1>(msg_bin, temperature >= 0); + set_bin<49, 9>(msg_bin, std::abs(temperature) / 0.25); + } + if (pressure != -1) + { + set_bin<58, 1>(msg_bin, 1); + set_bin<59, 11>(msg_bin, pressure); + } + if (radio_height != -1) + { + set_bin<70, 1>(msg_bin, 1); + set_bin<71, 12>(msg_bin, radio_height / 16); + } + } + void parse(std::string& msg_bin) { + if (get_bin<32, 1>(msg_bin) != 0) + { + get_bin<33, 2, Severity_Level>(msg_bin, turbulence); + } + if (get_bin<35, 1>(msg_bin) != 0) + { + get_bin<36, 2, Severity_Level>(msg_bin, wind_shear); + } + if (get_bin<38, 1>(msg_bin) != 0) + { + get_bin<39, 2, Severity_Level>(msg_bin, microburst); + } + if (get_bin<41, 1>(msg_bin) != 0) + { + get_bin<42, 2, Severity_Level>(msg_bin, icing); + } + if (get_bin<44, 1>(msg_bin) != 0) + { + get_bin<45, 2, Severity_Level>(msg_bin, wake_vortex); + } + if (get_bin<47, 1>(msg_bin) != 0) + { + double step = 0.25; + int sign = get_bin<48, 1>(msg_bin) ? 1 : -1; + temperature = sign * step * get_bin<49, 9>(msg_bin); + } + if (get_bin<58, 1>(msg_bin) != 0) + { + get_bin<59, 11>(msg_bin, pressure); + } + if (get_bin<70, 1>(msg_bin) != 0) + { + int step = 16; + radio_height = step * get_bin<71, 12>(msg_bin); + } + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(turbulence) + Ret_J(wind_shear) + Ret_J(icing) + Ret_J(wake_vortex) + Ret_J(temperature) + Ret_J(pressure) + Ret_J(radio_height) + return ret; + } + }; + T_Parser(BDS45_Meteorological_hazard_report) + } +} + diff --git a/SSR/HPP.h b/SSR/HPP.h new file mode 100644 index 0000000..9f84355 --- /dev/null +++ b/SSR/HPP.h @@ -0,0 +1,5 @@ +#ifndef HPP_H +#define HPP_H +#include "enum.h" +#include "py_Mode_S/Common.h" +#endif diff --git a/SSR/ICAO_9871_2012/1090.hpp b/SSR/ICAO_9871_2012/1090.hpp new file mode 100644 index 0000000..7f90db6 --- /dev/null +++ b/SSR/ICAO_9871_2012/1090.hpp @@ -0,0 +1,6 @@ +#ifndef yC_1090_H +#define yC_1090_H + + + +#endif \ No newline at end of file diff --git a/SSR/ICAO_9871_2012/GLOSSARY.h b/SSR/ICAO_9871_2012/GLOSSARY.h new file mode 100644 index 0000000..663cea4 --- /dev/null +++ b/SSR/ICAO_9871_2012/GLOSSARY.h @@ -0,0 +1,9 @@ +#ifndef yC_GLOSSARY_H +#define yC_GLOSSARY_H + +/* + +*/ + + +#endif diff --git a/SSR/Mode_S/Mode_S.cpp b/SSR/Mode_S/Mode_S.cpp new file mode 100644 index 0000000..20e55ae --- /dev/null +++ b/SSR/Mode_S/Mode_S.cpp @@ -0,0 +1,19 @@ +#include "Mode_S.h" +#include "../py_Mode_S/Common.h" + +namespace SSR { +ModeA ModeA::Builder::build() { + PSC_ASSERT([this]()->bool { + if (squawk.length() != 4) { return false; } for (char c : squawk) { if (!std::isdigit(c)) { return false; } } + int code = std::stoi(squawk); if (code < 0 || code > 7777) { return false; } return true; }(), ""); + ModeA ret; + ret.msg_bin = encode_mode_a(squawk); + return ret; +} +ModeC ModeC::Builder::build() { + ModeC ret; + ret.msg_bin = encode_mode_c(altitude); + return ret; +} + +} \ No newline at end of file diff --git a/SSR/Mode_S/Mode_S.h b/SSR/Mode_S/Mode_S.h new file mode 100644 index 0000000..d552131 --- /dev/null +++ b/SSR/Mode_S/Mode_S.h @@ -0,0 +1,71 @@ +#pragma once +#include "Mode_S.hpp" + + +namespace SSR { +struct ModeA { + struct Builder { + PROP(std::string, squawk, std::string{}); + ModeA build(); + }; + std::string msg_bin; +}; +struct ModeC { + struct Builder { + PROP(double, altitude, 0); + ModeC build(); + }; + std::string msg_bin; +}; +namespace All_Call_Reply_T { + template + struct All_Call_Reply_T : BDS_, with_PI { + PROP_T(Transponder_Capability, transponder_capability, Transponder_Capability::Level_2_plus_airborne) + // 询问器标识符 + PROP_T(std::string, interrogator_identifier, std::string{}) + PROP_T(std::string, icao, std::string{}); + PROP_T(std::string, transponder_address, std::string{}); + void set(std::string& msg) { + SSR::set_df(msg, Downlink_Format::All_Call_Reply_11); + SSR::set_transponder_capability(msg, transponder_capability); + SSR::set_icao(msg, icao); + } + void parse(std::string& msg_bin) { + icao = decode_icao(msg_bin); + transponder_capability = decode_transponder_capability(msg_bin); + interrogator_identifier = interrogator_allCall(msg_bin); + transponder_address = parse_transponder_address(msg_bin, icao); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(icao) + Ret_J(transponder_capability) + Ret_J(interrogator_identifier) + Ret_J(transponder_address) + return ret; + } + std::string get_crc_transponder_address() override { + return transponder_address; + } + }; + T_Parser(All_Call_Reply_T) +} +namespace All_Call_Reply { + ADD_56_1(All_Call_Reply, All_Call_Reply_T::All_Call_Reply_T) +} +// DF = 4, DF = 5 + + +namespace Surveillance_Reply { + template + struct Base_Builder : T::Reply_Base_Builder, with_AP {}; + + ADD_56_2(Altitude, Base_Builder, + T::Surveillance_Altitude_Reply) + + ADD_56_2(Identity, Base_Builder, + T::Surveillance_Identity_Reply) +} + + +} \ No newline at end of file diff --git a/SSR/Mode_S/Mode_S.hpp b/SSR/Mode_S/Mode_S.hpp new file mode 100644 index 0000000..d9baf31 --- /dev/null +++ b/SSR/Mode_S/Mode_S.hpp @@ -0,0 +1,96 @@ +#pragma once +#include "Core/Base/global_include.h" +#include "../HPP.h" +namespace SSR { +void check_icao(const std::string& icao); +namespace T { + template + struct Reply_Base_Builder { + PROP_T(std::string, icao, std::string{}); + PROP_T(Downlink_Format, df, Downlink_Format::Unknown) + PROP_T(Transponder_Capability, transponder_capability, Transponder_Capability::Level_2_plus_airborne) + PROP_T(Flight_Status, flight_status, Flight_Status::not_assigned) + PROP_T(Downlink_Request, downlink_request, Downlink_Request::no_request) + PROP_T(IDS, ids, IDS::no_information) + PROP_Bit_T(4, IIS, 0) // 询问器标识符代码 + + void parse(std::string& msg_bin) { + icao = decode_icao(msg_bin); + df = decode_df(msg_bin); + transponder_capability = decode_transponder_capability(msg_bin); + get_bin<5, 3, Flight_Status>(msg_bin, flight_status); + get_bin<8, 5, Downlink_Request>(msg_bin, downlink_request); + get_bin<13, 4>(msg_bin, IIS); + get_bin<17, 2, IDS>(msg_bin, ids); + } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(icao); + Ret_J(transponder_capability); + Ret_J(flight_status); + Ret_J(ids); + Ret_J(IIS); + return ret; + } + protected: + // void set_crc(std::string& msg_bin, std::string& icao) { + // auto crc = std::bitset<24>(::crc(msg_bin, true)).to_string(); + // auto crcHex = bin2hex(crc); + // std::string last24 = xorHex(crcHex, icao); + // msg_bin.replace(msg_bin.size() - 24, 24, xorBin(crc, hex2bin(icao))); + // } + void set(std::string& msg_bin) { + check_icao(icao); + PSC_ASSERT(msg_bin.size() == 56 || msg_bin.size() == 112, + " Reply_Base_Builder error size == " + std::to_string(msg_bin.size())); + SSR::set_transponder_capability(msg_bin, transponder_capability); + set_bin<5, 3>(msg_bin, static_cast(flight_status)); + set_bin<8, 5>(msg_bin, static_cast(downlink_request)); + set_bin<13, 4>(msg_bin, IIS); + set_bin<17, 2>(msg_bin, static_cast(ids)); + } + }; + T_Parser(Reply_Base_Builder) + + + template + struct Alt { + PROP_T(int, altitude, 0) + void parse(std::string& msg_bin) { altitude = altcode(msg_bin); } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(altitude); + return ret; + } + protected: + void set(std::string& msg_bin) { + set_bin<19, 13>(msg_bin, encode_altitude13_feet(altitude)); + } + }; + T_Parser(Alt) + template + struct Id { + // ATC Mode A 所使用的代码 + PROP_T(std::string, ATC_Squawk, "0000") + void parse(std::string& msg_bin) { ATC_Squawk = idcode(msg_bin); } + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(ATC_Squawk); + return ret; + } + protected: + void set(std::string& msg_bin) { + set_bin<19, 13>(msg_bin, encode_squawk13(ATC_Squawk)); + } + }; + T_Parser(Id) + template + struct Surveillance_Altitude_Reply : Alt, BDS_ { + void set(std::string& msg_bin) { set_df(msg_bin, Downlink_Format::Surveillance_Altitude_Reply_4); } + }; + template + struct Surveillance_Identity_Reply : Id, BDS_ { + void set(std::string& msg_bin) { set_df(msg_bin, Downlink_Format::Surveillance_Identity_Reply_5); } + }; +} +} diff --git a/SSR/Msg.cpp b/SSR/Msg.cpp new file mode 100644 index 0000000..382aa4a --- /dev/null +++ b/SSR/Msg.cpp @@ -0,0 +1 @@ +#include "Msg.h" \ No newline at end of file diff --git a/SSR/Msg.h b/SSR/Msg.h new file mode 100644 index 0000000..88cf29f --- /dev/null +++ b/SSR/Msg.h @@ -0,0 +1,307 @@ +#pragma once +#include "Core/Base/global_include.h" +#include "CPR/CPR.h" +#include "convert/DataOutputFormats.h" +#include "enum.h" +#include +#include +#define STRINGIFY(x) #x +// 硬件接收来的 二进制消息族 + + +namespace SSR { +class Data_Source_Interface; +static std::atomic_int64_t msg_num = 0; +extern bool Monitor_Mode_ACS_Message_Num; +class Msg { +public: + enum T : unsigned char { + AC = 0x31, // 49 + S7 = 0x32, // 50 + S14 = 0x33, // 51 + Radarcape_status = 0x34, // 52 + HULC_Status = 0x48, // 72 + UNKNOWN = 0xFF + }; + enum Len { + AC_len = 9 + 2, + S7_len = 9 + 7, + S14_len = 9 + 14, + Radarcape_status_len = 2 + 3, + HULC_len = 28, + }; + std::shared_ptr source{}; + T type = UNKNOWN; + std::string packet; // 收到的原始消息 + explicit Msg(const std::shared_ptr &source, + const std::string &packet) + : source(source), packet(packet) { + type = static_cast(packet[1]); + // std::cerr << "Msg msg_num "<< ++msg_num << std::endl; + } + virtual Psc::JSON to_Json() { + Psc::JSON ret = Psc::JSON::object(); + ret.append({"type", Psc::to_string((T)(type))}); + return ret; + } + virtual ~Msg() = default; + virtual std::string to_string() { + return VAR_STR_2(type, packet); + } +}; + +struct Play_Back_Time_Point { + std::uint64_t day_num{}; + std::uint64_t day_sec{}; // 0..86399 + + struct HMS { + std::uint32_t hour; + std::uint32_t minute; + std::uint32_t second; + std::string to_string() const { + std::ostringstream oss; + oss << std::setw(2) << std::setfill('0') << hour << ':' << std::setw(2) + << std::setfill('0') << minute << ':' << std::setw(2) + << std::setfill('0') << second; + return oss.str(); + } + }; + + HMS hms() noexcept { + HMS hms{}; + hms.hour = static_cast(day_sec / 3600); + day_sec %= 3600; + hms.minute = static_cast(day_sec / 60); + hms.second = static_cast(day_sec % 60); + return hms; + } + + std::string to_string() { + return "Play_Back_Time_Point[" + Psc::to_string(day_num) + "," + + hms().to_string() + "]"; + } + + static constexpr std::uint64_t SECS_PER_DAY = 86400ULL; + + // 归一化:保证 day_sec < 86400 + void normalize() noexcept { + if (day_sec >= SECS_PER_DAY) { + day_num += day_sec / SECS_PER_DAY; + day_sec %= SECS_PER_DAY; + } + } + + bool operator<(const Play_Back_Time_Point &other) const noexcept { + if (other == *this) + return false; + if (day_num != other.day_num) + return day_num < other.day_num; + return day_sec < other.day_sec; + } + + bool operator==(const Play_Back_Time_Point &other) const noexcept { + return day_num == other.day_num && day_sec == other.day_sec; + } + + bool operator>(const Play_Back_Time_Point &other) const noexcept { + if (other == *this) + return false; + return other < *this; + } + + std::uint64_t abs_sec() const noexcept { + return day_num * SECS_PER_DAY + day_sec; + } + + void add_sec(std::uint64_t sec) noexcept { + day_sec += sec; + normalize(); + } +}; + +class Mode_Msg : public Msg { +public: + std::uint64_t day_num = 0; // 自己维护的字段 + MLAT_timestamp mlat_timestamp; + std::string msg_hex; + std::string msg_bin; + char signal_level{}; + explicit Mode_Msg(const std::shared_ptr &source, + const std::string &packet); + Psc::JSON to_Json() override { + Psc::JSON ret = Msg::to_Json(); + Ret_J(msg_hex) Ret_J(signal_level) + ret.append({"mlat_timestamp", mlat_timestamp.to_string()}); + Ret_J(day_num) return ret; + } + ~Mode_Msg() override; + Play_Back_Time_Point time() { return {day_num, mlat_timestamp.daysec}; } + + std::string to_string() override { + auto& time = mlat_timestamp; + return VAR_STR_3(signal_level, time, msg_hex); + } +}; +class Mode_AC_Msg : public Mode_Msg { +public: + explicit Mode_AC_Msg(const std::shared_ptr &source, + const std::string &packet); + ~Mode_AC_Msg() override; +}; +// 1 + 1 + 6 + 1 + 2/7/14 +// 1a + type time_stamp signal_level data +// 用于数据发送 +class Mode_S_Msg : public Mode_Msg { +public: + Downlink_Format df = Downlink_Format::Unknown; + std::string icao; + Psc::JSON to_Json() override { + Psc::JSON ret = Mode_Msg::to_Json(); + ret.append({"df", Psc::to_string(df)}); + ret.append({"icao", icao}); + return ret; + } + // 1a 31 19 b2dd1c7af671 3220 + explicit Mode_S_Msg(const std::shared_ptr &source, + const std::string &packet); + ~Mode_S_Msg() override; + + std::string to_string() override { + auto& time = mlat_timestamp; + return VAR_STR_5(df, icao, signal_level, time, msg_hex); + } +}; + +class NoneCopyLock { +public: + std::mutex mtx; // 互斥量,不能被复制 + NoneCopyLock() = default; + NoneCopyLock(const NoneCopyLock &other) = delete; + NoneCopyLock &operator=(const NoneCopyLock &other) { return *this; } +}; + +struct Position_3D : CPR::Position { + CPR::D alt; + Position_3D(CPR::D lat, CPR::D lon, CPR::D alt) + : CPR::Position(lat, lon), alt(alt) {} +}; + + + + +// 这个返回值的意义在于 解码失败后 返回值能反应出是 位置解码失败 还是奇偶报文解码位置失败 +class Pos_Ret { +public: + CPR::Position pos; + std::shared_ptr that; + using Extra = std::variant, CPR::Position>; + Extra extra; + Pos_Ret(CPR::Position p, std::shared_ptr t, std::shared_ptr o) + : pos(p), that(std::move(t)),extra(std::move(o)) {} + + Pos_Ret(CPR::Position p, std::shared_ptr t, CPR::Position old) + : pos(p), that(std::move(t)), extra(old) {} +}; + + +// 展示出最详细的信息 +struct Position_Info : Position_3D { + + std::int64_t sys_day_ns{}; + std::uint64_t utc{}; + std::uint64_t odd_even_diff_ns{}; + Position_Info() : Position_3D(0.0, 0.0, 0.0) {} + Pos_Ret::Extra extra; + std::shared_ptr that; + Position_Info(const Pos_Ret& r, CPR::D alt) : Position_3D(r.pos.lat, r.pos.lon, alt), that(r.that), extra(r.extra) { + using namespace std::chrono; + + utc = + static_cast( + duration_cast(system_clock::now().time_since_epoch()).count() + ); + } + + Psc::JSON to_json() { + Psc::JSON ret = Psc::JSON::object(); + Ret_J(lat) + Ret_J(lon) + Ret_J(alt) + Ret_J(utc) + Ret_J(that->mlat_timestamp) + Ret_J(that->msg_hex) + Ret_J(that->signal_level) + if (auto p = std::get_if>(&extra)) { + std::shared_ptr other = *p; + Ret_J(other->mlat_timestamp) + Ret_J(other->msg_hex) + Ret_J(other->signal_level) + return ret; + } + if (auto p = std::get_if(&extra)) { + CPR::Position old = *p; + Ret_J(old) + return ret; + } + return ret; + } +}; + +struct POS_List { + NoneCopyLock mtx; + size_t max_size; + + POS_List() {} + + void reset() { + std::lock_guard g(mtx.mtx); + seq = 0; + head = 0; + count = 0; + } + + void init(size_t _max_size) { + this->max_size = _max_size; + buf.resize(_max_size); + } + + size_t size() { + std::lock_guard g(mtx.mtx); + return count; + } + + bool empty() { + std::lock_guard g(mtx.mtx); + return count == 0; + } + + std::optional last() { + std::lock_guard g(mtx.mtx); + if (count == 0) + return std::nullopt; + std::size_t idx = (head + max_size - 1) % max_size; + return buf[idx]; + } + + void push(Position_Info new_position) { + std::lock_guard g(mtx.mtx); + + buf[head] = std::move(new_position); + head = (head + 1) % max_size; + + if (count < max_size) + ++count; + + ++seq; + } + + Psc::JSON get_last_array_json(std::uint64_t last_seq); + std::string to_string(); + +protected: + std::uint64_t seq = 0; + std::uint64_t head = 0; + std::uint64_t count = 0; + std::vector buf; +}; +} // namespace SSR \ No newline at end of file diff --git a/SSR/convert/DataOutputFormats.cpp b/SSR/convert/DataOutputFormats.cpp new file mode 100644 index 0000000..36f9d88 --- /dev/null +++ b/SSR/convert/DataOutputFormats.cpp @@ -0,0 +1,111 @@ +#include "DataOutputFormats.h" + +#include "Core/Base/global_include.h" + +#include +#include + +using namespace Psc; + +namespace SSR { + +MLAT_timestamp::MLAT_timestamp() { + std::chrono::time_point t = std::chrono::high_resolution_clock::now(); + // 获取当前的系统时间(秒) + std::chrono::duration duration_since_epoch = t.time_since_epoch(); + // 获取秒数和纳秒数 + auto seconds = std::chrono::duration_cast(duration_since_epoch); + auto nanoseconds = std::chrono::duration_cast(duration_since_epoch); + // 获取当天的秒数和纳秒数 + this->daysec = seconds.count() % 86400; // 86400 = 24 * 60 * 60 (一天的秒数) + this->nanosec = nanoseconds.count() % 86400000000000; // 1秒 = 10^9纳秒 + memory = bin2mem(to_bin_string()); + parse_hhmmss(); +} + + +MLAT_timestamp::MLAT_timestamp(const std::string& memory_6) : memory(memory_6) { + assert(memory_6.size() == 6); + auto msg_bin = hex2bin(mem2hex(memory)); + daysec = std::bitset<18>(msg_bin.substr(0, 18)).to_ulong(); + nanosec = std::bitset<30>(msg_bin.substr(18, 30)).to_ulong(); + parse_hhmmss(); + + PSC_ASSERT(memory_6 == bin2mem(to_bin_string()), "MLAT_timestamp,内存转换错误!"); +} + +MLAT_timestamp::MLAT_timestamp(std::uint32_t daysec, std::uint32_t nanosec) : daysec(daysec), nanosec(nanosec) { + parse_hhmmss(); +} + + +std::string MLAT_timestamp::to_bin_string() const { + std::bitset<18> daysec_bits = daysec; + std::bitset<30> nanosec_bits = nanosec; + return daysec_bits.to_string() + nanosec_bits.to_string(); +} + +// std::string MLAT_timestamp::to_memory() { +// return bin2memory(to_bin_string()); +// } + +std::string MLAT_timestamp::to_memory() const { + return memory; +} + +void MLAT_timestamp::parse_hhmmss() { + hh = (daysec/3600) % 24; + mm = (daysec/60) % 60; + ss = daysec % 60; +} +std::string packet_to_escape_format(std::string t) { + std::string result = "\x1a"; + for (size_t i = 1; i < t.size(); ++i) { + result.push_back(t[i]); // 插入当前字符 + if (t[i] == '\x1a') { + result.push_back('\x1a'); // 如果是 0x1a,插入另一个 0x1a + } + } + return result; +} +void Binary_Format_handle_buffer(std::string& buffer, const std::string& data, const std::function& callback) { + size_t i = 0; + while (i < data.size()) { + if (data[i] == 0x1A) { + // 如果遇到起始标记 0x1A + if (i + 1 < data.size() && data[i + 1] == 0x1A) { + // 如果是转义的 0x1A 0x1A,继续处理 + buffer.push_back(0x1A); // 添加一个正常的 0x1A 字节 + i += 2; // 跳过这两个字节 + } else { + // 检测到起始标记 0x1A,认为是新消息的开始 + if (!buffer.empty()) { + // 如果当前消息 buffer 中已有数据,调用回调函数处理当前消息 + callback(buffer); + buffer.clear(); // 清空当前消息的缓存 + } + buffer.push_back(0x1A); + // 跳过当前的 SOP 标记 (0x1A) + i++; + } + } else + { + // 如果不是 0x1A,直接将数据添加到 buffer 中 + buffer.push_back(data[i]); + i++; + } + } +} + + + +// 9 + 2 +// 9 + 7 +// 9 + 14 +std::string mode_s_msg_packet_to_server_packet(std::uint32_t id, std::string packet) { + std::string id_code = Psc::platform_2_big_endian((char*)&id, sizeof(id)); + PSC_ASSERT(id_code.size() == 4, "id.size != 4"); + packet.insert(1, id_code.c_str(), 4); + return packet; +} +} \ No newline at end of file diff --git a/SSR/convert/DataOutputFormats.h b/SSR/convert/DataOutputFormats.h new file mode 100644 index 0000000..a53782f --- /dev/null +++ b/SSR/convert/DataOutputFormats.h @@ -0,0 +1,532 @@ +#pragma once +#include "base_format.h" + + +namespace SSR { + +// AVR format +// *02E99619FACDAE; +// *8D3C5EE69901BD9540078D37335F; +// *7700; +inline std::string create_AVR_format(const std::string& msg_hex) { + return "*" + msg_hex + ";"; +} + +// @016CE3671C7423FFE7AB7BFCAB; +// @016CE3671AA8A800199A8BB80030A8000628F400; +// @016CE3671C747700; + +// @5500068BA46CB402c18d34fc8000; +inline std::string create_MLAT_AVR_format(const std::string& msg_hex, MLAT_timestamp* timestamp) { + //std::string signal_level_str = bin2hex(std::bitset<8>(signal_level).to_string()); + //std::cout << "signal:" << signal_level_str << std::endl; + return "@" + bin2hex(timestamp->to_bin_string()) + msg_hex + ";"; +} + + +// Binary Format +// "1" : 6 byte MLAT timestamp, 1 byte signal level, 2 byte Mode-AC +// "2" : 6 byte MLAT timestamp, 1 byte signal level, 7 byte Mode-S short frame +// "3" : 6 byte MLAT timestamp, 1 byte signal level, 14 byte Mode-S long frame +// "4" : 6 byte MLAT timestamp, 1 byte unused, DIP switch configuration settings, time stamp error ticks as int8_t (1 tick is 15ns) (message "4" not on Mode-S Beast classic) +// : true 0x1a +// is 0x1a, and "1", "2" and "3" are 0x31, 0x32 and 0x33 +inline std::string create_Binary_Format_memory(const std::string& msg_hex, char signal_level, MLAT_timestamp* timestamp) { + std::string type; + switch (msg_hex.size()) { + case 4: type = '1'; + break; + case 14: type = '2'; + break; + case 28: type = '3'; + break; + default: std::cerr << msg_hex << "create_Binary_Format error is not a valid binary format." << std::endl; + throw std::runtime_error("create_Binary_Format error!"); + } + + + std::string t = timestamp->to_memory() + signal_level + Psc::hex2mem(msg_hex); + + // std::cout << "msg_hex.size():" << msg_hex.size() << std::endl; + // std::cout << "hex2memory(msg_hex):" << hex2memory(msg_hex).size() << std::endl; + // std::cout << t.size() << std::endl; + std::string result; + + // 遍历原始字符串 + for (size_t i = 0; i < t.size(); ++i) { + result.push_back(t[i]); // 插入当前字符 + if (t[i] == '\x1a') { + result.push_back('\x1a'); // 如果是 0x1a,插入另一个 0x1a + } + } + + return char(0x1a) + type + result; +} + +// 把要传递的信息转化成转义字符 二进制==>转义二进制 +std::string packet_to_escape_format(std::string t); + +inline std::string unescape_packet(std::string t) { + std::string result; + for (size_t i = 0; i < t.size(); ++i) { + if (t[i] == '\x1a' && i + 1 < t.size() && t[i + 1] == '\x1a') { + result.push_back('\x1a'); // 如果是连续的两个 \x1a,只添加一个 + ++i; // 跳过下一个 \x1a + } else { + result.push_back(t[i]); // 否则,直接添加当前字符 + } + } + return result; +} + + +// 用于dip_setting +// FPGA switch settings +// Bit 0: Data output mode(port 10002 only) Binary AVR hexdump +// Bit 1: Raw data frame types DF11, DF17 and DF18 only no pre-filtering +// Bit 2: Timestamps within AVR format enabled disabled +// Bit 3: not used +// Bit 4: Timestamp mode GPS legacy 12MHz +// Bit 5: FPGA/USB handshake disabled enabled +// Bit 6: 1 bit forward error correction (FEC) disabled enabled +// Bit 7: Mode-A/C decoding enabled disabled +// GPS status information +// Bit 0: Antenna status good fail +// Bit 1: Tracking at least 1 satellite true false +// Bit 2: Tracking at least 3 satellites true false +// Bit 3: Time value base UTC time GPS time (8sec offset) +// Bit 4: unused +// Bit 5: unused +// Bit 6: unused +// Bit 7: Daysec value range 0 ... 86399 1 ... 86400 +inline std::string create_Binary_Format_4(MLAT_timestamp timestamp, char signal_level, char dip_setting, char time_stamp_error_ticks) { + return char(0x1a) + "4" + timestamp.to_bin_string() + char(0xaa) + dip_setting + time_stamp_error_ticks; +} + +//相对时间戳(无 GPS):在没有连接 GPS 接收器的情况下,时间戳是一个 48 位无符号数字, +//计数 12 MHz 周期。时间戳大约每 271 天换行一次。 +struct TCXO_Clock { + unsigned long long times; + + explicit TCXO_Clock(const std::string& hex) { + times = std::bitset<48>(hex2bin(hex)).to_ullong(); + } + + [[nodiscard]] std::string to_string() const { + // 每个周期的时间是 1 / 12,000,000 秒 + double seconds = static_cast(times) / 12000000.0; + // 计算小时、分钟、秒 + int hours = static_cast(seconds / 3600); + seconds -= hours * 3600; + int minutes = static_cast(seconds / 60); + seconds -= minutes * 60; + int secs = static_cast(seconds); + // 格式化为时分秒字符串 + std::ostringstream oss; + oss.fill('0'); + oss << hours << ":"; + oss.width(2); + oss << minutes << ":"; + oss.width(2); + oss << secs; + return oss.str(); + } +}; + +/* +Periodic status message which is emitted every second +0x1A 0x48 0x01 0x18 SerNum Flags I.U. xTime Lat Lon Alt Sat HDOP +Fields: + SerNum 32-bit(1) Device Serial Number + Flags 16-bit(1) Status Flags* + I.U. 16-bit Internal Use + xTime 32-bit(1) Unix-Timestamp (Seconds since midnight 1970-01-01, UTC) + Lat 32-bit(1) Latitude as 32-bit BAM 2, thus 232 ≡ 360° + Lon 32-bit(1) Latitude as 32-bit BAM 2 + Alt 16-bit(1) Signed altitude meters + Sat 8-bit Number of satellites used in fix + HDOP 8-bit HDOP * 10, thus a value of 12 is HDOP 1.2 +1) MSB-first +2) https://en.wikipedia.org/wiki/Binary_scaling#Binary_angles + Status Flags: + 1xxx xxxx xxxx xxxx - GPS device detected -> Using absolute timestamp + x1xx xxxx xxxx xxxx - GPS valid (GGA, RMC present) + xx1x xxxx xxxx xxxx - GPS currently has a valid fix + xxx1 xxxx xxxx xxxx - High accuracy absolute time is available via PPS + xxxx xxxx 1xxx xxxx - TX-Queue overflow since start-up (normal if HW-handshaking is used) + xxxx xxxx x1xx xxxx - TX-Queue overflow during last second (probably due to HW-handshaking) + xxxx xxxx xx1x xxxx - Excessive NMEA found (other than GGA/RMC) +*/ +// +// All HULC messages (Type 0x48) have the following structure: +// 0x1A 0x48 ID LEN +// Fields: +// ID: HULC Message ID +// LEN: Number of bytes following +struct HULC_Message { + std::uint8_t head; + std::uint8_t id; + std::uint8_t len; // 指示下面的长度 +}; + + + + +struct HULC_Status_Message : HULC_Message { + std::uint32_t SerNum; + std::uint16_t Flags; // MSB大端字节序 + std::uint16_t I_U_; // Internal Use + std::uint32_t xTime; // Unix-Timestamp (Seconds since midnight 1970-01-01, UTC) + std::int32_t Lat; + std::int32_t Lon; + std::int16_t Alt; + std::uint8_t Sat; // Sat字段记录了定位过程中使用的卫星数量 + std::uint8_t HDOP; + + [[nodiscard]] double get_HDOP() const { + // HDOP(Horizontal Dilution of Precision) 描述的是 水平精度的衰减,即 GPS 系统的水平定位精度与卫星的几何布局有关。HDOP 数值越低,说明卫星的几何布局越好,定位精度越高。 + // 较低的 HDOP 值(例如 1-2)表示卫星信号较强,且卫星的分布较为理想,因此定位精度较高。 + // 较高的 HDOP 值(例如 5 或更高)表示卫星信号较弱,或卫星的分布较差,可能导致较差的定位精度。 + return double(HDOP) / 10.0; + } + + void set_latitude(double latitude) { + // 根据经纬度设置 Lat 值 + Lat = static_cast((latitude / 360.0) * pow(2, 32)); + } + + void set_longitude(double longitude) { + // 根据经纬度设置 Lon 值 + Lon = static_cast((longitude / 360.0) * pow(2, 32)); + } + + [[nodiscard]] double get_latitude() const { + return (static_cast(Lat) / pow(2, 32)) * 360.0; + } + + [[nodiscard]] double get_longitude() const { + return (static_cast(Lon) / pow(2, 32)) * 360.0; + } + + [[nodiscard]] std::string get_latitude_d_m_s() const{ + return decimalToDMS(get_latitude()); + } + + [[nodiscard]] std::string get_longitude_d_m_s() const{ + return decimalToDMS(get_longitude()); + } + + [[nodiscard]] std::string get_timestamp_string() const{ + return convert_timestamp_to_string(xTime); + } + + static std::string convert_timestamp_to_string(std::uint32_t time32) { + std::time_t time = time32; + std::stringstream ss; + ss << std::put_time(std::gmtime(&time), "%Y-%m-%d %H:%M:%S"); // 使用 std::put_time 格式化日期 + return ss.str( + + ); + // 返回格式化后的字符串 + // std::time_t time = time32; + // std::stringstream ss; + // // 将时间戳转换为本地时间 + // ss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S"); // 使用 std::localtime 格式化日期 + // return ss.str(); // 返回格式化后的字符串 + // // Unix Timestamp + // std::time_t timestamp = xTime; + // + // std::tm* ptm = std::gmtime(×tamp); + // + // return std::asctime(ptm); + } + + + // 转化经纬度变成 度 分 秒 + static std::string decimalToDMS(double decimalDegree) { + int degrees = static_cast(decimalDegree); + double minutesDouble = (decimalDegree - degrees) * 60; + int minutes = static_cast(minutesDouble); + int seconds = static_cast((minutesDouble - minutes) * 60.0); + return "(" + std::to_string(degrees) + "°" + std::to_string(minutes) + "'" + std::to_string(seconds) + "\")"; + } + /* + Status Flags: + 1xxx xxxx xxxx xxxx - GPS device detected -> Using absolute timestamp + x1xx xxxx xxxx xxxx - GPS valid (GGA, RMC present) + xx1x xxxx xxxx xxxx - GPS currently has a valid fix + xxx1 xxxx xxxx xxxx - High accuracy absolute time is available via PPS + xxxx xxxx 1xxx xxxx - TX-Queue overflow since start-up (normal if HW-handshaking is used) + xxxx xxxx x1xx xxxx - TX-Queue overflow during last second (probably due to HW-handshaking) + xxxx xxxx xx1x xxxx - Excessive NMEA found (other than GGA/RMC) + */ + [[nodiscard]] bool GPS_device_detected() const { + return Flags & (1 << 15); // 检查第 15 位,GPS 设备是否检测到 + } + + [[nodiscard]] bool GPS_valid() const { + return Flags & (1 << 14); // 检查第 14 位,GPS 数据是否有效 + } + + [[nodiscard]] bool GPS_has_valid_fix() const { + return Flags & (1 << 13); // 检查第 13 位,GPS 是否有有效定位 + } + + [[nodiscard]] bool GPS_high_accuracy_time() const { + return Flags & (1 << 12); // 检查第 12 位,是否通过 PPS 提供高精度绝对时间 + } + + [[nodiscard]] bool TX_queue_overflow_since_startup() const { + return Flags & (1 << 7); // 检查第 7 位,自启动以来是否发生 TX 队列溢出 + } + + [[nodiscard]] bool TX_queue_overflow_last_second() const { + return Flags & (1 << 6); // 检查第 6 位,过去一秒内是否发生 TX 队列溢出 + } + + [[nodiscard]] bool excessive_NMEA_found() const { + return Flags & (1 << 5); // 检查第 5 位,是否发现过多的 NMEA 数据(非 GGA/RMC) + } + + [[nodiscard]] Psc::JSON toJson() const { + Psc::JSON ret = Psc::JSON::object(); + ret.append({"序列号", SerNum}); + ret.append({"状态标志", Flags}); + ret.append({"内部使用", I_U_}); + ret.append({"时间戳", get_timestamp_string()}); + ret.append({"纬度", std::to_string(get_latitude()) + " " + get_latitude_d_m_s()}); + ret.append({"经度", std::to_string(get_longitude()) + " " + get_longitude_d_m_s()}); + ret.append({"高度", Alt}); + ret.append({"卫星数量", Sat}); + ret.append({"HDOP", get_HDOP()}); + ret.append({"GPS设备检测到", GPS_device_detected()}); + ret.append({"GPS有效", GPS_valid()}); + ret.append({"GPS有效定位", GPS_has_valid_fix()}); + ret.append({"GPS高精度时间", GPS_high_accuracy_time()}); + ret.append({"启动以来TX队列溢出", TX_queue_overflow_since_startup()}); + ret.append({"过去一秒TX队列溢出", TX_queue_overflow_last_second()}); + ret.append({"发现过多NMEA数据", excessive_NMEA_found()}); + return ret; + } + + + static double decodeLatitudeFromBAM(const std::string& bigdian4) { + std::uint32_t value; + Psc::big_endian_2_platform(bigdian4, (char*)&value); + // BAM 格式解码公式 + // return (static_cast(value) / pow(2, 32)) * 360.0; + return (static_cast(value) / pow(2, 32)) * 360.0; + } + std::string toBinary() { + std::string binary = {0x1a, 0x48, 0x01, 0x18}; + binary.append(Psc::platform_2_big_endian(&SerNum, 4)); + binary.append(Psc::platform_2_big_endian(&Flags, 2)); + binary.append(std::string((char*)&I_U_, 2)); // 内部使用没说什么序 默认大端序 + binary.append(Psc::platform_2_big_endian(&xTime, 4)); + binary.append(Psc::platform_2_big_endian(&Lat, 4)); + binary.append(Psc::platform_2_big_endian(&Lon, 4)); + binary.append(Psc::platform_2_big_endian(&Alt, 2)); + // 没说什么序 一字节 大端小端 等同 + binary.append({(char)Sat}); + binary.append({(char)HDOP}); + return binary; + } +}; + + + +static void hulc_big_endian_set(const std::string& msg, void* field, int start, int len) { + auto str = msg.substr(start, len); + Psc::big_endian_2_platform(str, (char*)field); +} + +static void hulc_set2(const std::string& msg, void* field, int start, int len) { + auto it = msg.substr(start, len); + std::memcpy(field, it.data(), len); +} +// msg 是文本二进制字符串 转义后的 1a34 +static HULC_Status_Message create_HULC_Status_Message(const std::string& msg) { + HULC_Status_Message ret{}; + hulc_big_endian_set(msg, &ret.SerNum, 4, 4); + hulc_big_endian_set(msg, &ret.Flags, 8, 2); + hulc_set2(msg, &ret.I_U_, 10, 2); + hulc_big_endian_set(msg, &ret.xTime, 12, 4); + hulc_big_endian_set(msg, &ret.Lat, 16, 4); + hulc_big_endian_set(msg, &ret.Lon, 20, 4); + hulc_big_endian_set(msg, &ret.Alt, 24, 2); + hulc_set2(msg, &ret.Sat, 26, 1); + hulc_set2(msg, &ret.HDOP, 27, 1); + return ret; +} + + +struct HULC_Reply_to_command : HULC_Message { + std::uint8_t CMD; + std::uint8_t POO_14; +}; + +// 0x1A 0x48 0x01 0x18 SerNum Flags I.U. xTime Lat Lon Alt Sat HDOP +inline std::uint8_t get_id(const std::string& msg) { + return msg[2]; +} + +inline std::uint8_t get_len(const std::string& msg) { + return msg[3]; +} + +// Type Payload Size Description +// 0x31 4 bytes Mode-A/C raw data +// 0x32 7 bytes Mode-S Short Squitter raw data +// 0x33 14 bytes Mode-S Extended Squitter raw data +// 0x48 variable HULC Message, see below for details +// HULC Protocol uses the byte 0x1A as start-of-packet (SOP) marker. In order to avoid misinterpretation +// of normal data bytes with value 0x1A as SOP, each occurrence of data byte 0x1A is doubled in the +// data stream during transmission ('escaping'). It is thus necessary during reception to detect every +// 0x1A not followed by another 0x1A as an SOP marker and to reduce every occurrence of a double +// 0x1A to a single 0x1A data byte ('un-escaping'). +// Example of a 4-byte long packet including SOP marker and 0x1A data bytes: +// Before Escaping: 1A 32 27 1A E8 57 F0 1A 6C +// During Transmission: 1A 32 27 1A 1A E8 57 F0 1A 1A 6C +// After Un-Escaping: 1A 32 27 1A E8 57 F0 1A 6C +void Binary_Format_handle_buffer(std::string& buffer, const std::string& data, const std::function& callback); +/* Command Message (Host ==> Device) +The command-message has a simple ASCII based structure. Each message starts with a ‘#’ (0x23) and +ends with ‘’ (0x0D, 0x0A). In-between are one to 16 bytes in 2-digit hexadecimal representation +using upper-case letters, separated by ‘-’ (0x2D). +For example: + ‘#00’ or hexadecimal 0x23 0x30 0x30 0x0D 0x0A + ‘#43-02’ or hexadecimal 0x23 0x34 0x33 0x2D 0x30 0x32 0x0D 0x0A + The first byte is mandatory and holds the command while the remaining bytes are parameters. + Available commands depend on the selected protocol and are discussed in the respective protocol + section. +*/ + +std::string mode_s_msg_packet_to_server_packet(std::uint32_t id, std::string packet); + + + +// 1a34 radarcape 状态帧消息 +// https://wiki.jetvision.de/wiki/Radarcape:Version_History#Status_Frame_0x34_contents_were_extended_with_GPS_and_UTC_information +// 状态帧0x34内容已扩展为 GPS 和 UTC 信息 +// 当选择 GPS 时间戳时,状态帧由 1PPS 脉冲触发,在传统 12MHz 时间戳的情况下随机触发。 +// 状态帧包含有效的时间戳 +// 信号电平值为零/保留 + + +#define SETGET(name, pos) void set_##name(bool value) { set(pos, value); } \ +bool get_##name() { return get(pos); } + +#define SETGET2(Type, name, pos) void set_##name(Type value) { set(pos, (uint8_t)value); } \ +Type get_##name() { return (Type)get(pos); } + +struct BYTE_Settings { +protected: + uint8_t settings{}; // 8 位的字段,存储所有设置 + void set(uint8_t pos, bool value) { + if (value) { + // 设置指定位置的位为 1 + settings |= (1 << pos); + } else { + // 设置指定位置的位为 0 + settings &= ~(1 << pos); + } + } + [[nodiscard]] bool get(uint8_t pos) const { + // 获取指定位置的位值 + return (settings >> pos) & 0x01; + } +}; +struct FPGA_Setting : BYTE_Settings { + enum class Data_Output_Mode { AVR_hexdump, Binary }; + enum class Raw_Data_Frame_Type { No_Pre_Filtering, DF11_17_18_only }; + enum class Timestamp_Mode { Legacy_12MHz, GPS }; + SETGET2(Data_Output_Mode, data_output_mode, 0) + SETGET2(Raw_Data_Frame_Type, raw_data_frame_type, 1) + SETGET(timestamps_within_AVR_format, 2) + SETGET2(Timestamp_Mode, timestamp_mode, 4) + SETGET(serial_flow_control, 5) + SETGET(FEC, 6) // 1位转发错误校正 + SETGET(mode_ac_decoding , 7) + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + ret.append({"Data_Output_Mode", Psc::to_string(get_data_output_mode())}); + ret.append({"Raw_Data_Frame_Type", Psc::to_string(get_raw_data_frame_type())}); + ret.append({"timestamps_within_AVR_format", get_timestamps_within_AVR_format()}); + ret.append({"timestamps_mode", Psc::to_string(get_timestamp_mode())}); + ret.append({"serial_flow_control", get_serial_flow_control()}); + ret.append({"1位转发错误校正", get_FEC()}); + ret.append({"mode_ac_decoding", get_mode_ac_decoding()}); + return ret; + } + +}; + +struct GPS_Setting : BYTE_Settings { + enum class Time_Value_Base { UTC, GPS }; + enum class Daysec_Range { R_0_86399, R_1_86400 }; + SETGET(antenna_status, 0) // 位 0: 天线状态 + SETGET(tracking_at_least_1_satellite, 1) // 位 1: 至少跟踪 1 颗卫星 + SETGET(tracking_at_least_3_satellites, 2) // 位 2: 至少跟踪 3 颗卫星 + SETGET2(Time_Value_Base, time_value_base, 3) // 位 3: 时间值基数 (UTC/GPS) + SETGET(unused4, 4) // 位 4: 保留 + SETGET(unused5, 5) // 位 5: 保留 + SETGET(unused6, 6) // 位 6: 保留 + SETGET2(Daysec_Range, daysec_range, 7) // 位 7: Daysec 值范围 + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); // 创建 JSON 对象 + ret.append({"antenna_status", get_antenna_status()}); + ret.append({"至少跟踪 1 颗卫星", get_tracking_at_least_1_satellite()}); + ret.append({"至少跟踪 3 颗卫星", get_tracking_at_least_3_satellites()}); + ret.append({"timestamps_mode", Psc::to_string(get_time_value_base())}); + ret.append({"daysec_range", Psc::to_string(get_daysec_range())}); + return ret; + } +}; + +struct Radarcape_STATUS_Message { + FPGA_Setting fpga_settings; + std::uint8_t pps_nanosecond_offset{}; // 包含 1PPS 事件时纳秒计时器的偏移量,单位为 15.625ns。正常值范围为 -3 ... +3。 + GPS_Setting gps_settings; + void set_pps_nanosecond(double value) { + if (value < 0.0 || value > 4000) { + std::cerr << "Invalid value. Must be in range [0.0, 4000.0]." << std::endl; + return; + } + pps_nanosecond_offset = static_cast((value)/15.625); + } + [[nodiscard]] double get_pps_nanosecond() const { + return (double)pps_nanosecond_offset * 15.625; + } + std::string toBinary() { + std::string binary = {0x1a, 0x34}; + binary.append(reinterpret_cast(&fpga_settings), 1); + binary.append(reinterpret_cast(&pps_nanosecond_offset), 1); + binary.append(reinterpret_cast(&gps_settings), 1); + return binary; + } + + Psc::JSON toJson() { + Psc::JSON ret = Psc::JSON::object(); + ret.append({"pps_nanosecond_offset", get_pps_nanosecond()}); + ret.append({"fpga_settings", fpga_settings.toJson()}); + ret.append({"gps_settings", gps_settings.toJson()}); + return ret; + } + +}; + +// 消息5字节 +// +static Radarcape_STATUS_Message create_Radarcape_STATUS_Message(const std::string& msg_5) { + std::string msg = Psc::big_endian_2_platform(msg_5.substr(2)); + Radarcape_STATUS_Message ret{}; + std::memcpy(&ret, (void*)(msg.data()), 3); + return ret; +} + + + +#undef SETGET +#undef SETGET2 + +} \ No newline at end of file diff --git a/SSR/convert/HULC.h b/SSR/convert/HULC.h new file mode 100644 index 0000000..1647551 --- /dev/null +++ b/SSR/convert/HULC.h @@ -0,0 +1,5 @@ + +#ifndef HULC_H +#define HULC_H + +#endif diff --git a/SSR/convert/KML_output.cpp b/SSR/convert/KML_output.cpp new file mode 100644 index 0000000..0457ec2 --- /dev/null +++ b/SSR/convert/KML_output.cpp @@ -0,0 +1,5 @@ +// +// Created by wyc on 2024/11/27. +// + +#include "KML_output.h" diff --git a/SSR/convert/KML_output.h b/SSR/convert/KML_output.h new file mode 100644 index 0000000..639633d --- /dev/null +++ b/SSR/convert/KML_output.h @@ -0,0 +1,12 @@ +#pragma once + +namespace SSR { +// 此输出专为具有KML功能的阅读器(例如Google Earth)使用而设计。 +// 对于列表中显示的同一架飞机,将生成其轨迹的3D显示。显示器会自动刷新,这将导致实时显示。 +// https://wiki.jetvision.de/wiki/Radarcape:Software_Features#3D_Data_KML.2FKMZ_Output +class KML_output { + +}; +} + + diff --git a/SSR/convert/SBS.cpp b/SSR/convert/SBS.cpp new file mode 100644 index 0000000..a7c0fa5 --- /dev/null +++ b/SSR/convert/SBS.cpp @@ -0,0 +1,123 @@ +#include "SBS.h" +#include "SSR/Aircraft_Info.h" +namespace SSR { +/* +STA,,5,179,400AE7,10103,2008/11/28,14:58:51.153,2008/11/28,14:58:51.153,RM +MSG,4,5,211,4CA2D6,10057,2008/11/28,14:53:49.986,2008/11/28,14:58:51.153,,,408.3,146.4,,,64,,,,, +MSG,8,5,211,4CA2D6,10057,2008/11/28,14:53:50.391,2008/11/28,14:58:51.153,,,,,,,,,,,,0 +MSG,4,5,211,4CA2D6,10057,2008/11/28,14:53:50.391,2008/11/28,14:58:51.153,,,408.3,146.4,,,64,,,,, +MSG,3,5,211,4CA2D6,10057,2008/11/28,14:53:50.594,2008/11/28,14:58:51.153,,37000,,,51.45735,-1.02826,,,0,0,0,0 +MSG,8,5,812,ABBEE3,10095,2008/11/28,14:53:50.594,2008/11/28,14:58:51.153,,,,,,,,,,,,0 +MSG,3,5,276,4010E9,10088,2008/11/28,14:53:49.986,2008/11/28,14:58:51.153,,28000,,,53.02551,-2.91389,,,0,0,0,0 +MSG,4,5,276,4010E9,10088,2008/11/28,14:53:50.188,2008/11/28,14:58:51.153,,,459.4,20.2,,,64,,,,, +MSG,8,5,276,4010E9,10088,2008/11/28,14:53:50.594,2008/11/28,14:58:51.153,,,,,,,,,,,,0 +MSG,3,5,276,4010E9,10088,2008/11/28,14:53:50.594,2008/11/28,14:58:51.153,,28000,,,53.02677,-2.91310,,,0,0,0,0 +MSG,4,5,769,4CA2CB,10061,2008/11/28,14:53:50.188,2008/11/28,14:58:51.153,,,367.7,138.6,,,-2432,,,,, +MSG,8,5,769,4CA2CB,10061,2008/11/28,14:53:50.391,2008/11/28,14:58:51.153,,,,,,,,,,,,0 +*/ + +// 函数1:获取当前日期信息(年、月、日),返回格式为 "yyyy/mm/dd" +std::string get_current_date() { + // 获取当前时间 + auto now = std::chrono::system_clock::now(); + std::time_t t = std::chrono::system_clock::to_time_t(now); + + // 转换为 tm 结构 + std::tm tm = *std::localtime(&t); + + // 用字符串流格式化日期 + std::ostringstream dateStream; + dateStream << std::setw(4) << std::setfill('0') << (tm.tm_year + 1900) << "/" // 年份 + << std::setw(2) << std::setfill('0') << (tm.tm_mon + 1) << "/" // 月份 + << std::setw(2) << std::setfill('0') << tm.tm_mday; // 日期 + + return dateStream.str(); +} + +// 函数2:获取当前时间信息(时、分、秒、毫秒),返回格式为 "hh:mm:ss.sss" +std::string get_current_time() { + // 获取当前时间 + auto now = std::chrono::system_clock::now(); + auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; + + // 获取时间的 tm 结构 + std::time_t t = std::chrono::system_clock::to_time_t(now); + std::tm tm = *std::localtime(&t); + + // 用字符串流格式化时间 + std::ostringstream timeStream; + timeStream << std::setw(2) << std::setfill('0') << tm.tm_hour << ":" // 小时 + << std::setw(2) << std::setfill('0') << tm.tm_min << ":" // 分钟 + << std::setw(2) << std::setfill('0') << tm.tm_sec << "." // 秒 + << std::setw(3) << std::setfill('0') << ms.count(); // 毫秒 + + return timeStream.str(); +} + +void SBS_MSG::set(Aircraft_Info* info) { + icao = info->icao; + date_message_generated = get_current_date(); + time_message_generated = get_current_time(); + date_message_logged = get_current_date(); + time_message_logged = get_current_time(); + { + auto t = info->bds20_call_sign(); + if (t.has_value()) + { + call_sign = t.value(); + } + } + { + auto t = info->altitude_meter(); + if (t.has_value()) + { + altitude_ft = t.value() / aero::ft; + } + } + { + auto t = info->speed(); + if (t.has_value()) + { + ground_speed = t.value(); + } + } + { + auto t = info->is_on_ground(); + if (t.has_value()) is_on_ground = t.value(); + } + { + auto t = info->surface_magnetic_heading(); + if (t.has_value()) + { + this->track = t.value(); + } + } + { + auto t = info->pos(); + if (t.has_value()) { + latitude = t.value().lat; + longitude = t.value().lon; + } + } + { + auto t = info->vertical_rate(); + if (t.has_value()) vertical_rate = t.value(); + } + { + auto it = info->squawk(); + if (it.has_value()) { + squawk = it.value(); + } + } + // alert_squawk_change + // emergency + // spi + { + auto it = info->is_on_ground(); + if (it.has_value()) + { + is_on_ground = it.value(); + } + } +} +} \ No newline at end of file diff --git a/SSR/convert/SBS.h b/SSR/convert/SBS.h new file mode 100644 index 0000000..072d3c5 --- /dev/null +++ b/SSR/convert/SBS.h @@ -0,0 +1,168 @@ +#pragma once + +#include +#include +#include + + +namespace SSR { +// SBS文档 port 30003 format +// https://wiki.jetvision.de/wiki/Radarcape:Software_Features#Output_of_FLARM_data_on_port_30003 +// http://woodair.net/sbs/Article/Barebones42_Socket_Data.htm + +/* + + +MSG,1,111,11111,781322,111111,2026/01/07,00:53:35.507,2026/01/07,00:53:35.507,GCR7881,,,,,,,,,,, + +MSG,3,111,11111,7816EA,111111,2026/01/07,00:53:35.426,2026/01/07,00:53:35.426,,24600,,,36.20987,120.70609,,,0,0,0,0 +MSG,4,111,11111,780DE6,111111,2026/01/07,00:53:35.491,2026/01/07,00:53:35.491,,,464,179,,,-64,,,,, +MSG,5,111,11111,780CBE,111111,2026/01/07,00:53:35.431,2026/01/07,00:53:35.431,,30100,,,,,,,0,,0,0 +MSG,6,111,11111,780CBE,111111,2026/01/07,00:53:35.441,2026/01/07,00:53:35.441,,30100,,,,,,3621,0,0,0,0 +MSG,8,111,11111,780E33,111111,2026/01/07,00:53:35.441,2026/01/07,00:53:35.441,,,,,,,,,,,,0 + + +*/ + + + +std::string get_current_date(); +std::string get_current_time(); + +class Aircraft_Info; +struct SBS_MSG { + SBS_MSG() { + enable.resize(12); + } + virtual ~SBS_MSG() = default; + + // MT 消息类型 + // TT 子类型 + std::string session_id; // SID Database Session record number + std::string aircraft_id; // AID Database Aircraft record number + std::string icao; // HEX Aircraft Mode S hexadecimal code + std::string flight_id; // FID Database Flight record number + std::string date_message_generated; //DMG + std::string time_message_generated; // TMG + std::string date_message_logged; // DML + std::string time_message_logged; // TML + virtual void set(Aircraft_Info* info); + + // 其他的可选字段 + std::vector enable; + std::string call_sign; // 呼号 八位数的航班 ID - 可以是航班号或注册(甚至没有) + int altitude_ft{}; // 模式 C 高度。相对于 1013.2mb (飞行高度) 的高度。不是高度 AMSL.. + int ground_speed{}; // 对地速度(未指示空速) + int track{}; // 飞机的轨迹(非航向)。由速度 E/W 和速度 N/S 导出 + double latitude{}; // 北部和东部为正。南部和西部为负。 + double longitude{}; // 北部和东部为正。南部和西部为负。 + int vertical_rate{} ; // 64 英尺分辨率 + std::string squawk ; // 已分配模式 Squawk 代码 + bool alert_squawk_change{} ; // 指示 squawk 已更改的标志 + int emergency{}; // 指示已设置紧急代码的标志 + int spi{}; // 指示应答器 Ident 已激活的标志。 + bool is_on_ground{}; // 指示地面下蹲开关处于活动状态的标志 + + void change(int t) { + if (t == 1) enable = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + if (t == 2) enable = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1}; + if (t == 3) enable = {0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1}; + if (t == 4) enable = {0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1}; + if (t == 5) enable = {0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1}; + if (t == 6) enable = {0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1}; + if (t == 7) enable = {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + if (t == 8) enable = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + } + + + std::string to_SEL() { + std::string ret = to_base("SEL"); + ret += "," + call_sign; + return ret; + } + + std::string to_ID() { + std::string ret = to_base("ID"); + ret += "," + call_sign; + return ret; + } + + std::string to_AIR() { + std::string ret = to_base("AIR"); + return ret; + } + + std::string to_STA() { + std::string ret = to_base("STA"); + // 基站呼号 + ret += ","; + return ret; + } + + std::string to_CLK() { + std::string ret = "ID"; + ret += ",," + session_id; + ret += ",-1,,-1"; + ret += "," + date_message_generated; + ret += "," + time_message_generated; + ret += "," + date_message_logged; + ret += "," + time_message_logged; + return ret; + } + + std::string to_msg(int t) { + std::string ret = "MSG"; + change(t); + ret += "," + std::to_string(t); + ret += "," + session_id; + ret += "," + aircraft_id; + ret += "," + icao; + ret += "," + flight_id; + ret += "," + date_message_generated; + ret += "," + time_message_generated; + ret += "," + date_message_logged; + ret += "," + time_message_logged; + ret += ","; + if (enable[0]) ret += call_sign; + ret += ","; + if (enable[1]) ret += std::to_string(altitude_ft); + ret += ","; + if (enable[2]) ret += std::to_string(ground_speed); + ret += ","; + if (enable[3]) ret += std::to_string(track); + ret += ","; + if (enable[4]) ret += std::to_string(latitude); + ret += ","; + if (enable[5]) ret += std::to_string(longitude); + ret += ","; + if (enable[6]) ret += std::to_string(vertical_rate); + ret += ","; + if (enable[7]) ret += squawk; + ret += ","; + if (enable[8]) ret += std::to_string(alert_squawk_change); + ret += ","; + if (enable[9]) ret += std::to_string(emergency); + ret += ","; + if (enable[10]) ret += std::to_string(spi); + ret += ","; + if (enable[11]) ret += std::to_string(is_on_ground); + return ret; + } +protected: + std::string to_base(std::string name) { + std::string ret = std::move(name); + ret += ",," + session_id; + ret += "," + aircraft_id; + ret += "," + icao; + ret += "," + flight_id; + ret += "," + date_message_generated; + ret += "," + time_message_generated; + ret += "," + date_message_logged; + ret += "," + time_message_logged; + return ret; + } +}; + + +} + diff --git a/SSR/convert/base_format.h b/SSR/convert/base_format.h new file mode 100644 index 0000000..761dfaf --- /dev/null +++ b/SSR/convert/base_format.h @@ -0,0 +1,56 @@ +#pragma once + +#include "Core/Base/JSON.h" + +#include "SSR/global.h" +#include + +namespace SSR { + + + + + // 1纳秒是 10 -9次方秒 + struct MLAT_timestamp { + explicit MLAT_timestamp(); + explicit MLAT_timestamp(const std::string& memory_6); + MLAT_timestamp(std::uint32_t daysec, std::uint32_t nanosec); + std::uint32_t daysec{}; // 当天的秒数 高18位 + std::uint32_t nanosec{}; // 当天的纳秒数 低30位 + std::string to_bin_string() const; + std::string to_memory() const; + unsigned long hh{}; + unsigned long mm{}; + unsigned long ss{}; + std::string memory{}; + bool operator<(const MLAT_timestamp& other) const { + // 比较 daysec(高18位) + if (daysec != other.daysec) { + return daysec < other.daysec; + } + // 如果 daysec 相等,比较 nanosec(低30位) + return nanosec < other.nanosec; + } + + + [[nodiscard]] Psc::JSON toJson() const { + Psc::JSON ret = Psc::JSON::object(); + ret.append({"秒", daysec}); + ret.append({"纳秒", nanosec}); + ret.append({"时间", std::to_string(hh) + ":" + std::to_string(mm) + ":" + std::to_string(ss)}); + return ret; + } + + [[nodiscard]] std::string to_string() const { + return + "{" + std::to_string(daysec) + "," + std::to_string(nanosec) + "}_" + + "[" + + std::to_string(hh) + "时:" + + std::to_string(mm) + "分:" + + std::to_string(ss) + "秒" + "]"; + } + + void parse_hhmmss(); + }; +} \ No newline at end of file diff --git a/SSR/enum.cpp b/SSR/enum.cpp new file mode 100644 index 0000000..a044e90 --- /dev/null +++ b/SSR/enum.cpp @@ -0,0 +1,161 @@ +#include "enum.h" + +namespace SSR { + +// spdlog 的默认日志级别是 info + +// trace: 用于非常细节的日志,通常用于程序内部的详细跟踪。 +// debug: 用于开发时调试的日志信息,通常不会出现在生产环境中。 +// info: 一般的信息性日志,程序正常运行时的信息。 +// warn: 警告信息,表示可能发生问题,但程序仍能继续运行。 +// error: 错误信息,表示程序遇到了错误,需要关注。 +// critical: 严重错误,程序可能无法继续执行。 + +void check_icao(const std::string& icao) { + if (icao.size() != 6 || !std::all_of(icao.begin(), icao.end(), [](char c) { + return std::isxdigit(c); + })) + { + CE(true, "不合法的icao:" + icao) + } +} + + +std::unordered_map BDS_map = { + {"BDS10", "Data link capability"}, + {"BDS17", "GICB capability"}, + {"BDS20", "Aircraft identification"}, + {"BDS30", "ACAS resolution"}, + {"BDS40", "Vertical intention report"}, + {"BDS50", "Track and turn report"}, + {"BDS60", "Heading and speed report"}, + {"BDS44", "Meteorological routine air report"}, + {"BDS45", "Meteorological hazard report"}, + {"EMPTY", "[No information available]"} +}; + + +std::map> map1; +std::map, Vortex_Type> map2; +std::string Vortex_Type_to_radarcape_code(Vortex_Type vortex_type) { + switch (vortex_type) { + case Vortex_Type::No_Category_Information: + return "A0"; // No category information + case Vortex_Type::Surface_Emergency_Vehicle: + return "C1"; // Surface vehicle - emergency + case Vortex_Type::Surface_Service_Vehicle: + return "C2"; // Surface vehicle - service + case Vortex_Type::Ground_Obstruction_4: + case Vortex_Type::Ground_Obstruction_5: + case Vortex_Type::Ground_Obstruction_6: + case Vortex_Type::Ground_Obstruction_7: + return "C3"; // Obstacle - point or line + case Vortex_Type::Glider: + return "B1"; // Glider/Sailplane + case Vortex_Type::Lighter_Than_Air: + return "B2"; // Lighter than air + case Vortex_Type::Parachutist: + return "B3"; // Parachutist/Skydiver + case Vortex_Type::Ultralight_HangGlider_Paraglider: + return "B4"; // Ultralight/Hang glider/Paraglider + case Vortex_Type::Unmanned_Aerial_Vehicle: + return "B6"; // UAV + case Vortex_Type::Space_Transatmospheric_Vehicle: + return "B7"; // Space/Trans-atmospheric vehicle + case Vortex_Type::Light_Aircraft: + return "A1"; // Light (< 15500 lbs) + case Vortex_Type::Medium_1_Aircraft: + return "A2"; // Small (15500 >= lbs < 75000) + case Vortex_Type::Medium_2_Aircraft: + return "A3"; // Large (75000 >= lbs < 300000) + case Vortex_Type::High_Vortex_Aircraft: + return "A4"; // Large (high vortex) + case Vortex_Type::Heavy_Aircraft: + return "A5"; // Heavy (>= 300000 lbs) + case Vortex_Type::High_Performance_Aircraft: + return "A6"; // High performance + case Vortex_Type::Rotorcraft: + return "A7"; // Rotorcraft + default: + return "Unknown"; // 不明类型 + } +} + +ADSB_Content_Type type_code_to_enum(int type_code) { + switch (type_code) { + case 1: + case 2: + case 3: + case 4: + return ADSB_Content_Type::Aircraft_Identification; // 1-4 + case 5: + case 6: + case 7: + case 8: + return ADSB_Content_Type::Surface_Position; // 5-8 + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + return ADSB_Content_Type::Airborne_Position_With_Baro_Altitude; // 9-18 + case 19: + return ADSB_Content_Type::Airborne_Velocity; // 19 + case 20: + case 21: + case 22: + return ADSB_Content_Type::Airborne_Position_With_GNSS_Height; // 20-22 + case 23: + case 24: + case 25: + case 26: + case 27: + return ADSB_Content_Type::Reserved; // 23-27 + case 28: + return ADSB_Content_Type::Aircraft_Status; // 28 + case 29: + return ADSB_Content_Type::Target_State_And_Status_Info; // 29 + case 31: + return ADSB_Content_Type::Aircraft_Operation_Status; // 31 + default: + return ADSB_Content_Type::Unknown; + } +} + +struct Init { + Init() { + if(map1.empty()) { + map1 = { + {Vortex_Type::No_Category_Information, {1, 0}}, + {Vortex_Type::Surface_Emergency_Vehicle, {2, 1}}, + {Vortex_Type::Surface_Service_Vehicle, {2, 3}}, + {Vortex_Type::Ground_Obstruction_4, {2, 4}}, + {Vortex_Type::Ground_Obstruction_5, {2, 5}}, + {Vortex_Type::Ground_Obstruction_6, {2, 6}}, + {Vortex_Type::Ground_Obstruction_7, {2, 7}}, + {Vortex_Type::Glider, {3, 1}}, + {Vortex_Type::Lighter_Than_Air, {3, 2}}, + {Vortex_Type::Parachutist, {3, 3}}, + {Vortex_Type::Ultralight_HangGlider_Paraglider, {3, 4}}, + {Vortex_Type::Reserved_3_5, {3, 5}}, + {Vortex_Type::Unmanned_Aerial_Vehicle, {3, 6}}, + {Vortex_Type::Space_Transatmospheric_Vehicle, {3, 7}}, + {Vortex_Type::Light_Aircraft, {4, 1}}, + {Vortex_Type::Medium_1_Aircraft, {4, 2}}, + {Vortex_Type::Medium_2_Aircraft, {4, 3}}, + {Vortex_Type::High_Vortex_Aircraft, {4, 4}}, + {Vortex_Type::Heavy_Aircraft, {4, 5}}, + {Vortex_Type::High_Performance_Aircraft, {4, 6}}, + {Vortex_Type::Rotorcraft, {4, 7}} + }; + for (const auto& pair : map1) map2[pair.second] = pair.first; + } + } +} init; + +} \ No newline at end of file diff --git a/SSR/enum.h b/SSR/enum.h new file mode 100644 index 0000000..4f68e75 --- /dev/null +++ b/SSR/enum.h @@ -0,0 +1,307 @@ +#pragma once +#include "global.h" + +namespace SSR { +extern std::unordered_map BDS_map; +enum class Uplink_Format : std::uint8_t { + Short_Air_Air_Surveillance_0 = 0, // 0 短空对空监视(ACAS) + Surveillance_Altitude_Request_4 = 4, // 4 监视,高度请求 + Surveillance_Identity_Request_5 = 5, // 5 监视,身份请求 + Mode_S_All_Call_11 = 11, // 11 模式 S 全叫 + Long_Air_Air_Surveillance_16 = 16, // 16 长空对空监视(ACAS) + Extended_Squitter_17 = 17, // 17 扩展询问 + Extended_Squitter_Non_Transponder_18 = 18, // 18 扩展询问/非转发器 + Military_Extended_Squitter_19 = 19, // 19 军事扩展询问 + Comm_A_Altitude_Request_20 = 20, // 20 通信-A,高度请求 + Comm_A_Identity_Request_21 = 21, // 21 通信-A,身份请求 + Comm_C_ELM_24 = 24, // 24 通信-C(ELM) + Unknown = 25 +}; + + +enum class Downlink_Format : std::uint8_t { + Short_Air_Air_Surveillance_0 = 0, // 0 短空对空监视(ACAS) + Surveillance_Altitude_Reply_4 = 4, // 4 监视,高度回复 + Surveillance_Identity_Reply_5 = 5, // 5 监视,身份回复 + All_Call_Reply_11 = 11, // 11 全叫回复 + Long_Air_Air_Surveillance_16 = 16, // 16 长 空对空监视(ACAS) + Extended_Squitter_17 = 17, // 17 扩展询问 + Extended_Squitter_Non_Transponder_18 = 18, // 18 扩展询问/非转发器 + Military_Extended_Squitter_19 = 19, // 19 军事扩展询问 + Comm_B_Altitude_Reply_20 = 20, // 20 通信-B,高度回复 + Comm_B_Identity_Reply_21 = 21, // 21 通信-B,身份回复 + Comm_D_ELM_24 = 24, // 24 通信-D(ELM) + Unknown = 25 +}; + + +enum class Transponder_Capability : std::uint8_t { + Level_1_Transponder = 0, // 0 级别 1 转发器 + // 1-3 保留 + Level_2_plus_on_ground = 4, // 4 级别 2+ 转发器,能够将 CA 设置为 7,且在地面上 + Level_2_plus_airborne = 5, // 5 级别 2+ 转发器,能够将 CA 设置为 7,且在空中 + Level_2_plus_either = 6, // 6 级别 2+ 转发器,能够将 CA 设置为 7,既可在地面也可在空中 + Downlink_Request_Flight_Status = 7 // 7 表示下行请求值为 0,或飞行状态为 2、3、4 或 5,无论是在空中还是在地面 +}; + +enum class Surveillance_Status : std::uint8_t { + NoCondition = 0, // 无条件 + PermanentAlert = 1, // 永久警报 + TemporaryAlert = 2, // 临时警报 + SPICondition = 3 // SPI 条件 +}; + + +enum class Altitude_type : std::uint8_t { + Baro, // 大气压高度 + GNSS // GNSS高度 +}; + + +enum class Vortex_Type : std::uint8_t { + No_Category_Information = 1, // 1 无类别信息 + // TC = 2 + Surface_Emergency_Vehicle = 2, // 2 地面应急车 + Surface_Service_Vehicle = 3, // 3 地面服务车 + Ground_Obstruction_4 = 4, // 4 地面障碍物 + Ground_Obstruction_5 = 5, // 5 地面障碍物 + Ground_Obstruction_6 = 6, // 6 地面障碍物 + Ground_Obstruction_7 = 7, // 7 地面障碍物 + // TC = 3 + Glider = 8, // 1 滑翔机,滑翔飞机 + Lighter_Than_Air = 9, // 2 比空气轻 + Parachutist = 10, // 3 跳伞运动员 + Ultralight_HangGlider_Paraglider = 11, // 4 超轻型,悬挂式滑翔机,滑翔伞 + Reserved_3_5 = 12, // 5 保留 + Unmanned_Aerial_Vehicle = 13, // 6 无人机 + Space_Transatmospheric_Vehicle = 14, // 7 太空或跨大气层飞行器 + // TC = 4 + Light_Aircraft = 15, // 1 轻型飞机(小于 7000 公斤) + Medium_1_Aircraft = 16, // 2 中型 1(7000 公斤到 34000 公斤之间) + Medium_2_Aircraft = 17, // 3 中型 2(34000 公斤到 136000 公斤之间) + High_Vortex_Aircraft = 18, // 4 高涡旋飞机 + Heavy_Aircraft = 19, // 5 重型飞机(大于 136000 公斤) + High_Performance_Aircraft = 20, // 6 高性能(>5 g 加速度)和高速(>400 节) + Rotorcraft = 21 // 7 旋翼机 +}; +extern std::map> map1; +extern std::map, Vortex_Type> map2; +std::string Vortex_Type_to_radarcape_code(Vortex_Type vortex_type); + +enum class ADSB_Content_Type : std::uint8_t { + Unknown = 0, // 1-4 飞机识别 + Aircraft_Identification = 1, // 1-4 飞机识别 + Surface_Position = 5, // 5-8 地面位置 + Airborne_Position_With_Baro_Altitude = 9,// 9-18 空中位置(带气压高度) + Airborne_Velocity = 19, // 19 空中速度 + Airborne_Position_With_GNSS_Height = 20,// 20-22 空中位置(带GNSS高度) + Reserved = 23, // 23-27 保留 + Aircraft_Status = 28, // 28 飞机状态 + Target_State_And_Status_Info = 29, // 29 目标状态和状态信息 + Aircraft_Operation_Status = 31 // 31 飞机操作状态 +}; +ADSB_Content_Type type_code_to_enum(int type_code); + + + +enum class Source : std::uint8_t { + GNSS = 0, + Baro = 1 +}; + + +enum class Ground_Speed_Type : std::uint8_t { + Ground_Normal = 1, + Ground_Supersonic = 2, //超音速 +}; + + +enum class Air_Speed_Code_Type : std::uint8_t { + Air_Normal = 3, + Air_Supersonic = 4 +}; + +enum class Airspeed_type : std::uint8_t { + Indicated_airspeed_IAS = 0, + True_airspeed_TAS = 1 +}; + +// 定义航班状态(FS)枚举类,使用3个位来表示航班状态 +enum class Flight_Status : std::uint8_t { + no_alert_airborne = 0b000, // 000: 无警报,无SPI,飞机在空中 + no_alert_on_ground = 0b001, // 001: 无警报,无SPI,飞机在地面 + alert_airborne = 0b010, // 010: 警报,无SPI,飞机在空中 + alert_on_ground = 0b011, // 011: 警报,无SPI,飞机在地面 + alert_spi_airborne = 0b100, // 100: 警报,SPI,飞机在空中或地面 + no_alert_spi = 0b101, // 101: 无警报,SPI,飞机在空中或地面 + reserved = 0b110, // 110: 保留 + not_assigned = 0b111 // 111: 未分配 +}; + + +// 定义下行请求(DR)枚举类,使用5个位来表示下行请求类型 +enum class Downlink_Request : std::uint8_t { + no_request = 0b00000, // 00000: 无下行请求 + send_comm_b = 0b00001, // 00001: 请求发送Comm-B消息 + comm_b_msg1_available = 0b00100,// 00100: Comm-B广播消息1可用 + comm_b_msg2_available = 0b00101 // 00101: Comm-B广播消息2可用 +}; + +// 定义实用消息(UM)枚举类,使用6个位来表示转发器通信状态信息 +enum class IDS : std::uint8_t { + no_information = 0b00, // 00: 无信息 + comm_b_iis = 0b01, // 01: IIS包含Comm-B询问者标识符代码 + comm_c_iis = 0b10, // 10: IIS包含Comm-C询问者标识符代码 + comm_d_iis = 0b11 // 11: IIS包含Comm-D询问者标识符代码 +}; + + +// 定义紧急情况的类型枚举,使用下划线分割单词 +enum class Emergency_Type : std::uint8_t { + No_Emergency = 0, // 无紧急情况 + General_Emergency = 1, // 一般紧急情况 + Lifeguard_Medical_Emergency = 2, // 救生员/医疗紧急情况 + Minimum_Fuel = 3, // 最低燃料 + No_Communications = 4, // 无通讯 + Unlawful_Interference = 5, // 非法干扰 + Downed_Aircraft = 6, // 飞机坠毁 + Reserved = 7 // 保留 +}; + +// 转换函数:将 EmergencyType 枚举值转换为下划线连接的字符串 + + +enum class Vertical_Horizontal_Type : std::uint8_t { + Acquiring_Mode = 1, // 获取模式 + Capturing_Maintaining_Mode = 2, // 捕获/保持模式 + Reserved = 3 +}; +//std::string vertical_horizontal_type_to_string(Vertical_Horizontal_Type type); + +enum class ACAS_SubType : std::uint8_t { + NO_RA, + One_threat, + Mult_below_above, + Mult_same_direction +}; + +enum class Threat_Type_Indicator : std::uint8_t { + No_Identity_Data = 0b00, // 00: 威胁身份数据中没有身份数据 + Mode_S_Transponder_Address = 0b01, // 01: 威胁身份数据包含 Mode S 应答机地址 + Altitude_Range_Bearing = 0b10, // 10: 威胁身份数据包含高度、距离和方位 + // 11: 未分配 +}; + +namespace Comm_B { + namespace ELS { + enum class ACAS_Status : std::uint8_t { + Failed_Or_On_Standby = 0, // 0: ACAS failed or on standby + Operating = 1 // 1: ACAS operating + }; + + enum class Hybrid_Surveillance_Status : std::uint8_t { + Not_Operational = 0, // 0: Hybrid surveillance not operational + Fitted_And_Operational = 1 // 1: Hybrid surveillance fitted and operational + }; + + enum class ACAS_TA_RA_Status : std::uint8_t { + Generating_TAs_Only = 0, // 0: ACAS generating TAs only + Generating_TAs_And_RAs = 1 // 1: ACAS generating TAs and RAs + }; + + enum class RTCA_Version_Status : std::uint8_t { + RTCA_DO_185_Pre_ACAS = 0b00, // 0|0: RTCA/DO-185 (pre-ACAS) + RTCA_DO_185A = 0b01, // 0|1: RTCA/DO-185A + RTCA_DO_185B_OR_EUROCAE_ED_143 = 0b10, // 1|0: RTCA/DO-185B or EUROCAE ED 143 + ReservedForFutureVersions = 0b11 // 1|1: Reserved for future versions + }; + + enum class Mode_S_Subnetwork_Version : std::uint8_t { + NotAvailable = 0, // 0: Subnetwork not available + ICAO_Doc_9688_1996 = 1, // 1: ICAO Doc 9688 (1996) + ICAO_Doc_9688_1998 = 2, // 2: ICAO Doc 9688 (1998) + ICAO_Annex_10_Vol_III_Amendment_77 = 3, // 3: ICAO Annex 10, Vol III, Amdt 77 + ICAO_Doc_9871_Ed_1_RTCA_DO_181D_EUROCAE_ED_73C = 4, // 4: ICAO Doc 9871 (Ed 1), RTCA DO-181D, EUROCAE ED-73C + ICAO_Doc_9871_Ed_2_RTCA_DO_181E_EUROCAE_ED_73E = 5 // 5: ICAO Doc 9871 (Ed 2), RTCA DO-181E, EUROCAE ED-73E + }; + } + namespace EHS { + enum class Target_Altitude_Source : std::uint8_t { + Unknown = 0b00, // 00: 未知来源 (Unknown source) + Aircraft_Altitude = 0b01, // 01: 飞机高度 (Aircraft altitude) + FCU_MCP_Selected_Altitude = 0b10, // 10: FCU/MCP 选定高度 (FCU/MCP selected altitude) + FMS_Selected_Altitude = 0b11 // 11: FMS 选定高度 (FMS selected altitude) + }; + } + + + enum class Figure_Merit_Source : std::uint8_t { + Invalid = 0, // 0: 无效 (Invalid) + Inertial_System = 1, // 1: 惯性系统(INS)(Inertial system) + GNSS = 2, // 2: 全球导航卫星系统(GNSS)(Global Navigation Satellite System) + DME_Based_Navigation = 3, // 3: 基于测距设备的导航(DME/DME)(Distance measuring equipment-based navigation) + VOR_DME_Based_Navigation = 4 // 4: 基于甚高频全向测距/测距设备导航(VOR/DME)(Very High Frequency omnidirectional range/distance measuring equipment based navigation) + }; + + enum class Severity_Level : std::uint8_t { + NIL = 0b00, // 00: 无 (NIL) + LIGHT = 0b01, // 01: 轻度 (LIGHT) + MODERATE = 0b10, // 10: 中等 (MODERATE) + SEVERE = 0b11 // 11: 重度 (SEVERE) + }; +} + +namespace ACAS { + + + enum class Reply_Information : std::uint8_t { + NoACASOperation = 0b0000, // 无运行 ACAS + SuppressACASWithSeparation = 0b0010, // 抑制具有分离能力的 ACAS + VerticalResolutionOnly = 0b0011, // 具有仅垂直分辨率功能的 ACAS + VerticalAndHorizontalResolution = 0b0111 // 具有垂直和水平分辨率能力的 ACAS + }; + + + enum class Vertical_Status : std::uint8_t { + Airborne = 0, // 机载 + OnGround = 1 // 在地面 + }; + + // 交联能力 (CC):1 位,是指在 UF=0 请求时应答 DF=16 的能力。当此 1 位字段设置为 1 时,支持交叉链接。否则,该字段将设置为 0。 + enum class CrossLink_Capability : std::uint8_t { + NotSupported = 0, // 不支持交链 + Supported = 1 // 支持交链 + }; + + enum class Vertical_RAC : std::uint8_t { + NoInformation = 0b00, // 无垂直 RAC 信息 + DoNotPassBelow = 0b01, // 请勿通过以下 + DoNotPassAbove = 0b10, // 不要通过上面 + NotAssigned = 0b11 // 未分配 + }; + + enum class Cancel_Vertical_RAC : std::uint8_t{ + NoCancellation = 0b00, // 无取消信息 + CancelDoNotPassBelow = 0b01, // 取消 “Do not pass below” + CancelDoNotPassAbove = 0b10, // 取消 “Do not pass above” + NotAssigned = 0b11 // 未分配 + }; + + enum class Horizontal_RAC : std::uint8_t { + NoInformation = 0b000, // 无信息 + DoNotTurnLeft_OtherSenseLeft = 0b001, // 其他 ACAS 感应为左转;不要左转 + DoNotTurnRight_OtherSenseLeft = 0b010, // 其他 ACAS 感应为左转;不要右转 + DoNotTurnLeft_OtherSenseRight = 0b101, // 其他 ACAS 感应为右转;不要左转 + DoNotTurnRight_OtherSenseRight = 0b110, // 其他 ACAS 感应为右转;不要右转 + }; + + enum class Cancel_Horizontal_RAC : std::int8_t { + NoCancellation = 0b000, // 无取消信息 + CancelDoNotTurnLeft = 0b001, // 取消 “请勿左转” + CancelDoNotTurnRight = 0b010, // 取消 “请勿右转” + NotAssigned = -1 // 未分配 + }; +} + + +} \ No newline at end of file diff --git a/SSR/export.cpp b/SSR/export.cpp new file mode 100644 index 0000000..815b762 --- /dev/null +++ b/SSR/export.cpp @@ -0,0 +1,11 @@ +#include "export.h" +#include +#include + +namespace SSR { +BaseLogger* mode_s_logger = nullptr; +void init_mode_s_log(std::string path, int num, int size_MB) { + +} + +} \ No newline at end of file diff --git a/SSR/export.h b/SSR/export.h new file mode 100644 index 0000000..2357c23 --- /dev/null +++ b/SSR/export.h @@ -0,0 +1,62 @@ +#ifndef Mode_S_export_H +#define Mode_S_export_H +#include +#include +#include "convert/DataOutputFormats.h" +#include "convert/KML_output.h" +#include "convert/SBS.h" +#include "Aircraft_Info.h" +#include "py_Mode_S/Common.h" +#include "export_type.h" + +namespace SSR { + void set_logger(BaseLogger* logger); + + + using Parse_Call_Back = std::function bds_list, + void* cur_parser, + P_S mode_s_msg + )>; + using Parse_Pos_Call_Back = std::function; + using Parse_OK_Json_Call_Back = std::function; + using P_A = const std::shared_ptr&; + extern Parse_Pos_Call_Back parse_pos_call_back; + extern Parse_OK_Json_Call_Back parse_ok_json; + extern CPR_CB cpr_cb; + extern Parse_Call_Back parse_call_back; + + + class Data_Source_Interface { + public: + virtual ~Data_Source_Interface() = default; + virtual std::string get_key() = 0; + virtual std::shared_ptr get_aircraft(const std::string& icao) = 0; + virtual std::shared_ptr create_aircraft(const std::string& icao) = 0; + // std::shared_ptr get_create_aircraft(const std::string& icao, bool& ok) { + // auto iter = get_aircraft(icao); + // if (iter == nullptr) { + // ok = false; + // } else { + // ok = true; + // } + // return create_aircraft(icao); + // } + }; + bool parse_mode_s_bin(Data_Source_Interface* src, P_S mode_s_msg, std::optional base_station_pos, CPR::D max_speed_m_s, CPR::D air_pos_time_out, CPR::D surface_pos_time_out); +} + +#include "export.hpp" +#endif + diff --git a/SSR/export.hpp b/SSR/export.hpp new file mode 100644 index 0000000..1c83ee1 --- /dev/null +++ b/SSR/export.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "export.h" +namespace SSR { +template +void init_that(Aircraft_Info& info, const std::string& field_name, T& t, SSR::P_S msg) { + auto& _t = t.value; + if (t.time != info.timestamp) { + t.time = info.timestamp; + t.per_second_num = 0; + } + t.sum++; + t.per_second_num++; + if (!_t.has_value()) _t = typename std::remove_reference_t::value_type(); + _t.value().parse(msg->msg_bin); + if (SSR::parse_call_back) { + SSR::parse_call_back(SSR::Parse_Call_Back_Type::Normal_Block, &info, field_name, {}, &t, msg); + } +} +} diff --git a/SSR/export_type.h b/SSR/export_type.h new file mode 100644 index 0000000..9e49f97 --- /dev/null +++ b/SSR/export_type.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include + +namespace SSR { + enum class Parse_Call_Back_Type { + Normal_Block, + CRC_Error, + Length_Error, + Unknown_DF, + Cannot_Choose_BDS, + More_than_one_BDS, + ICAO_Error, + Unknown_tc + }; + enum class CPR_Error_Type { + Normal, + Msg_Lack, + Local_Speed_Error, + Global_Speed_Error, + Local_Time_Space_Too_Long, + Global_Time_Space_Too_Long, + Unknow_Error, + }; + using P_S = const std::shared_ptr&; + + using CPR_CB = std::function; + + +} \ No newline at end of file diff --git a/SSR/global.h b/SSR/global.h new file mode 100644 index 0000000..9750e8e --- /dev/null +++ b/SSR/global.h @@ -0,0 +1,495 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Core/Base/global_include.h" +#include "Core/spdlog/export.h" +namespace SSR { +extern BaseLogger* mode_s_logger; +std::string crc_hex(std::string msg_bin, bool encode); +int crc(const std::string& msg_bin, bool encode = false); +std::string decode_icao(const std::string& msg_bin); + +template +void handle_range_error(const std::string& log_pos, const std::string& field_name, T value, double min, double max) { + std::string range = "[" + std::to_string(min) + "," + std::to_string(max) + "]"; + std::string error_info = "ERROR:" + field_name + ":" + std::to_string(value) + "超出范围:" + range + "\n"; + Log_Type type({}, {{"POS", log_pos}}); + mode_s_logger->error("out of range", type, error_info); +// #ifdef WIN32 +// Psc::fail_fast("一个只是在windows上崩溃的本质是穿的错误数据 或解析的错误数据"); +// #endif + // throw std::runtime_error(error_info); +} + +template +void handle_range_error(const std::string& log_pos, const std::string& field_name, std::optional value, double min, double max) { + handle_range_error(log_pos, field_name, value.value(), min, max); +} + + +#define PROP(TYPE, FIELD, DEFAULT) \ +private: \ +TYPE FIELD = DEFAULT; \ +public: \ +Builder& set_##FIELD(TYPE value) { \ +this->FIELD = std::move(value); \ +return *this; \ +} \ +TYPE get_##FIELD() { \ +return this->FIELD; \ +} + + +#define PROP_Bit(bits, FIELD, DEFAULT) \ +private: \ +int FIELD = DEFAULT; \ +public: \ +Builder& set_##FIELD(int value) { \ +int MIN = 0; \ +int MAX = (1 << bits); \ +if (value < MIN || value >= MAX) { \ + handle_range_error(LOG_POS, #FIELD, value, MIN, MAX); \ +} \ +this->FIELD = value; \ +return *this; \ +} \ +int get_##FIELD() { \ +int& value = this->FIELD; \ +int MIN = 0; \ +int MAX = (1 << bits); \ +if (value < MIN || value >= MAX) { \ + handle_range_error(LOG_POS, #FIELD, value, MIN, MAX); \ +} \ +return value; \ +} + + +#define PROP_T_FUN(TYPE, FIELD, DEFAULT) \ +public: \ +That& set_##FIELD(TYPE value) { \ +static_cast(this)->FIELD = std::move(value); \ +return *static_cast(this); \ +} \ +TYPE get_##FIELD() { \ +TYPE& value = static_cast(this)->FIELD; \ +return value; \ +} + +#define PROP_T(TYPE, FIELD, DEFAULT) \ +protected: \ +TYPE FIELD = DEFAULT; \ +public: \ +That& set_##FIELD(TYPE value) { \ +static_cast(this)->FIELD = std::move(value); \ +return *static_cast(this); \ +} \ +TYPE get_##FIELD() { \ +TYPE& value = static_cast(this)->FIELD; \ +return value; \ +} + + +#define PROP_Range(TYPE, MIN, MAX, FIELD, DEFAULT) \ +private: \ +TYPE FIELD = DEFAULT; \ +public: \ +Builder& set_##FIELD(const TYPE& value) { \ +if (value < MIN || value > MAX) { \ + handle_range_error(LOG_POS, #FIELD, value, MIN, MAX); \ +} \ +this->FIELD = value; \ +return *this; \ +} \ +TYPE get_##FIELD() { \ +TYPE& value = this->FIELD; \ +if (value < MIN || value > MAX) { \ + handle_range_error(LOG_POS #FIELD, value, MIN, MAX); \ +} \ +return value; \ +} + + +// [0, 2^n-1] 区间 +#define PROP_Bit_T_Base(Type, bits, FIELD, DEFAULT) \ +protected: \ +Type FIELD = DEFAULT; \ +public: \ +That& set_##FIELD(Type value) { \ +int MIN = 0; \ +int MAX = (1 << bits); \ +if (value < MIN || value >= MAX) { \ + handle_range_error(LOG_POS, #FIELD, value, MIN, MAX); \ +} \ +static_cast(this)->FIELD = value; \ +return *static_cast(this); \ +} \ +Type get_##FIELD() { \ +Type& value = static_cast(this)->FIELD; \ +int MIN = 0; \ +int MAX = (1 << bits); \ +if (value < MIN || value >= MAX) { \ + handle_range_error(LOG_POS, #FIELD, value, MIN, MAX); \ +} \ +return value; \ +} +#define PROP_Bit_T(bits, FIELD, DEFAULT) PROP_Bit_T_Base(unsigned char, bits, FIELD, DEFAULT) +#define PROP_Bit_32T(bits, FIELD, DEFAULT) PROP_Bit_T_Base(unsigned int, bits, FIELD, DEFAULT) + + + + + + + + +#define PROP_Range_T(TYPE, MIN, MAX, FIELD, DEFAULT) \ +private: \ +TYPE FIELD = DEFAULT; \ +public: \ +That& set_##FIELD(const TYPE& value) { \ +if (value < MIN - std::numeric_limits::epsilon() || value > MAX + std::numeric_limits::epsilon()) { \ + handle_range_error(LOG_POS, #FIELD, value, MIN, MAX); \ +} \ +static_cast(this)->FIELD = value; \ + return *static_cast(this); \ +} \ +TYPE get_##FIELD() { \ +TYPE& value = static_cast(this)->FIELD; \ +if (value < MIN - std::numeric_limits::epsilon() || value > MAX + std::numeric_limits::epsilon()) { \ + handle_range_error(LOG_POS, #FIELD, value, MIN, MAX); \ +} \ +return value; \ +} + +#define BaseADD1(NAME, MSGBase, _T1) \ + struct NAME : MSGBase { \ + struct Builder; \ + using T1 = _T1; \ + struct Builder : T1 { \ + NAME build() { \ + NAME ret; \ + T1::set(ret.msg); \ + this->set_crc(ret.msg, this->icao, this->bds()); \ + return ret; \ + } \ + void parse(std::string& msg) { \ + T1::parse(msg); \ + } \ + Psc::JSON toJson() { \ + Psc::JSON ret = Psc::JSON::object(); \ + ret.append_list(T1::toJson().children); \ + return ret; \ + } \ + }; \ +}; + +#define BaseADD2(NAME, MSGBase, _T1, _T2) \ + struct NAME : MSGBase { \ + struct Builder; \ + using T1 = _T1; \ + using T2 = _T2; \ + struct Builder : T1, T2 { \ + NAME build() { \ + NAME ret; \ + T1::set(ret.msg); \ + T2::set(ret.msg); \ + this->set_crc(ret.msg, this->icao, this->bds()); \ + return ret; \ + } \ + void parse(std::string& msg) { \ + T1::parse(msg); \ + T2::parse(msg); \ + } \ + Psc::JSON toJson() { \ + Psc::JSON ret = Psc::JSON::object(); \ + ret.append_list(T1::toJson().children); \ + ret.append_list(T2::toJson().children); \ + return ret; \ + } \ + }; \ +}; + +#define BaseADD3(NAME, MSGBase, _T1, _T2, _T3) \ +struct NAME : MSGBase { \ + struct Builder; \ + using T1 = _T1; \ + using T2 = _T2; \ + using T3 = _T3; \ + struct Builder : T1, T2, T3 { \ + NAME build() { \ + NAME ret; \ + T1::set(ret.msg); \ + T2::set(ret.msg); \ + T3::set(ret.msg); \ + this->set_crc(ret.msg, this->icao, this->bds()); \ + return ret; \ + } \ + void parse(std::string& msg) { \ + T1::parse(msg); \ + T2::parse(msg); \ + T3::parse(msg); \ + } \ + Psc::JSON toJson() { \ + Psc::JSON ret = Psc::JSON::object(); \ + ret.append_list(T1::toJson().children); \ + ret.append_list(T2::toJson().children); \ + ret.append_list(T3::toJson().children); \ + return ret; \ + } \ + }; \ +}; + + +#define BaseADD4(NAME, MSGBase, _T1, _T2, _T3, _T4) \ +struct NAME : MSGBase { \ + struct Builder; \ + using T1 = _T1; \ + using T2 = _T2; \ + using T3 = _T3; \ + using T4 = _T4; \ + struct Builder : T1, T2, T3, T4 { \ + NAME build() { \ + NAME ret; \ + T1::set(ret.msg); \ + T2::set(ret.msg); \ + T3::set(ret.msg); \ + T4::set(ret.msg); \ + this->set_crc(ret.msg, this->icao, this->bds()); \ + return ret; \ + } \ + void parse(std::string& msg) { \ + T1::parse(msg); \ + T2::parse(msg); \ + T3::parse(msg); \ + T4::parse(msg); \ + } \ + Psc::JSON toJson() { \ + Psc::JSON ret = Psc::JSON::object(); \ + ret.append_list(T1::toJson().children); \ + ret.append_list(T2::toJson().children); \ + ret.append_list(T3::toJson().children); \ + ret.append_list(T4::toJson().children); \ + return ret; \ + } \ + }; \ +}; + +struct Base_56 { + std::string msg; + Base_56() { + msg.resize(56, '0'); + } +}; +struct Base_112 { + std::string msg; + Base_112() { + msg.resize(112, '0'); + } +}; +#define ADD_112_1(NAME, _T1) BaseADD1(NAME, Base_112, _T1) +#define ADD_112_2(NAME, _T1, _T2) BaseADD2(NAME, Base_112, _T1, _T2) +#define ADD_112_3(NAME, _T1, _T2, _T3) BaseADD3(NAME, Base_112, _T1, _T2, _T3) +#define ADD_112_4(NAME, _T1, _T2, _T3, _T4) BaseADD4(NAME, Base_112, _T1, _T2, _T3, _T4) + +#define ADD_56_1(NAME, _T1) BaseADD1(NAME, Base_56, _T1) +#define ADD_56_2(NAME, _T1, _T2) BaseADD2(NAME, Base_56, _T1, _T2) +#define ADD_56_3(NAME, _T1, _T2, _T3) BaseADD3(NAME, Base_56, _T1, _T2, _T3) +#define ADD_56_4(NAME, _T1, _T2, _T3, _T4) BaseADD4(NAME, Base_56, _T1, _T2, _T3, _T4) + +#define T_Parser(Template_name) struct Parser_##Template_name : Template_name{}; + + + +// 模式 S 使用三种类型的奇偶校验。 +// 第一个只是 CRC 提醒,用于扩展 squitter 中的 ADS-B(参见第 [章:adsb_parity])。 +// 其他两种类型的奇偶校验是地址奇偶校验 (AP) +// 和数据奇偶校验 (DP)。 + + +// commB Parity +// all call reply ADS-B +// ACAS Address parity AP +// Surveillance replies DF=4,5 AP + + +// All call reply + + + +struct with_PI { + virtual ~with_PI() = default; + void set_crc(std::string& msg, std::string icao, std::string bds) { + icao.resize(6, '0'); + bds.resize(6, '0'); + int size = static_cast(msg.size()); + auto crc_24 = std::bitset<24>(crc(msg, true)).to_string(); + auto transponder_address = get_crc_transponder_address(); + msg.replace(size - 24, 24, Psc::xorBin(crc_24, transponder_address)); + } + // All call reply 是基于审讯的报文,PI是 询问器设备码 和 crc 异或而成的 + // 所以,必须知道询问器标识符 才能校验是否有错误 + // 每个设备应该只有一个询问器标识符 + static bool check_crc(std::string& msg_bin, std::string icao_hex, std::string bds ="") { + auto crc_24 = std::bitset<24>(crc(msg_bin, true)).to_string(); + auto last_24 = msg_bin.substr(msg_bin.size() - 24); + return crc_24 == last_24; + } + static std::string parse_transponder_address(std::string& msg_bin, std::string icao_hex) { + int size = static_cast(msg_bin.size()); + icao_hex.resize(6, '0'); + auto crc_24 = std::bitset<24>(crc(msg_bin, true)).to_string(); + auto PA = Psc::hex2bin(icao_hex); + auto last_24 = msg_bin.substr(size - 24); + return Psc::bin2hex(Psc::xorBin(crc_24, Psc::xorBin(PA, last_24))); + } +protected: + virtual std::string get_crc_transponder_address() { return Psc::hex2bin(std::string("000000"));} + // ADS-B 是自发的,所以解不出询问器地址 询问器地址都是0 + +}; + +using Psc::xorBin; +using Psc::hex2bin; +using Psc::bin2hex; +using Psc::set_bin; +using Psc::get_bin; +using Psc::bin2; + +// icao = crc0 xor last +// crc === icao xor last === crc0 + +struct with_AP { + void set_crc(std::string& msg, std::string& icao, std::string bds) { + icao.resize(6, '0'); + bds.resize(6, '0'); + auto crc_24 = std::bitset<24>(crc(msg, true)).to_string(); + auto PA = xorBin(crc_24, std::bitset<24>(icao).to_string()); + msg.replace(msg.size() - 24, 24, PA); + } + static bool check_crc(std::string& msg_bin, std::string icao_hex, std::string bds = "") { + int size = static_cast(msg_bin.size()); + icao_hex.resize(6, '0'); + bds.resize(6, '0'); + auto icao_24 = hex2bin(icao_hex); + auto last_24 = msg_bin.substr(size - 24); + std::string crc_code = xorBin(last_24, icao_24); + std::string temp = msg_bin; + temp.replace(size - 24, 24, crc_code); + bool ret = crc(temp) == 0; + if (!ret) { + return false; + } + return ret; + } +}; + +// ICAO: DD33AA 1101 1101 0011 0011 1010 1010 +// +// XOR +// +// BDS 4,4 440000 0100 0100 0000 0000 0000 0000 +// ------------------------------------------------------- +// Modified AA 9933AA 1001 1001 0011 0011 1010 1010 + +// 不知道什么消息用这个校验 +// +struct with_DP { + void set_crc(std::string& msg, std::string& icao, std::string bds) { + icao.resize(6, '0'); + bds.resize(6, '0'); + auto crc_24 = std::bitset<24>(crc(msg, true)).to_string(); + auto icao_24 = hex2bin(icao); + auto bds_24 = hex2bin(bds); + auto transponder_address = hex2bin(std::string("000000")); + auto last24 = xorBin(xorBin(crc_24, icao_24), xorBin(bds_24, transponder_address)); + msg.replace(msg.size() - 24, 24, last24); + } + + static bool check_crc(std::string& msg_bin, std::string icao_hex, std::string bds) { + return true; + } + + static std::string transponder_address(std::string& msg, std::string icao, std::string bds) { + int size = static_cast(msg.size()); + icao.resize(6, '0'); + bds.resize(6, '0'); + auto crc_24 = std::bitset<24>(crc(msg, true)).to_string(); + auto last_24 = msg.substr(size - 24); + auto icao_bin = hex2bin(icao); + auto bds_bin = hex2bin(bds); + return bin2hex(xorBin(xorBin(last_24, crc_24), xorBin(icao_bin, bds_bin))); + } +}; + + + + +#define D_BDS(number) struct BDS##number { \ +static std::string bds() {return #number;} \ +}; \ + + + + + +D_BDS(_) //凑数的 +// ADS-B +D_BDS(05) // 机载位置 +D_BDS(06) // 表面位置 +D_BDS(08) // 飞机识别和类别 +D_BDS(09) // 空降速度 +D_BDS(61) // tc = 28 +D_BDS(62) // tc = 29 +D_BDS(65) // 飞机运行状态 +// Commb ELS Elementary surveillance +D_BDS(10) // 数据链路能力报告 +D_BDS(17) // 常用 GICB 能力报告 +D_BDS(20) // 飞机识别 +D_BDS(30) // ACAS 主动解决方案咨询 +// Commb EHS Enhanced surveillance +D_BDS(40) // 选定的垂直意图 +D_BDS(50) // 跟踪和转弯报告 +D_BDS(60) // 航向和速度报告 +// Commb MS Meteorological services +D_BDS(44) // 气象例行空气报告 +D_BDS(45) // 气象灾害报告 + + + +//TODO: 1111 +#define CE(cond, msg) if(cond) { \ + static Log_Type log_type({}, {{"POS", LOG_POS}}); \ + SSR::mode_s_logger->error("", log_type, msg); \ +} + + + +#define CE2(cond, msg) if(cond) { \ + static Log_Type log_type({}, {{"POS", LOG_POS}}); \ + SSR::mode_s_logger->error("", log_type, msg); \ + return std::nullopt; \ +} + + + +} \ No newline at end of file diff --git a/SSR/py_Mode_S/Common.cpp b/SSR/py_Mode_S/Common.cpp new file mode 100644 index 0000000..f4f4db6 --- /dev/null +++ b/SSR/py_Mode_S/Common.cpp @@ -0,0 +1,1131 @@ +#include "Common.h" + +#include "Core/Base/global_include.h" +#undef max +#undef min + +namespace SSR { + +Downlink_Format decode_df(const std::string& msg) { + return static_cast(bin2(msg.substr(0, 5))); +} +void set_df(std::string& msg, int df) { + msg.replace(0 , 5, std::bitset<5>(df).to_string()); +} +void set_df(std::string& msg, Downlink_Format df) { + set_df(msg, static_cast(df)); +} + +Transponder_Capability decode_transponder_capability(const std::string& msg) { + return static_cast(bin2(msg.substr(5, 3))); +} + +void set_transponder_capability(std::string& msg, Transponder_Capability transponder_capability) { + msg.replace(5 , 3, std::bitset<3>(static_cast(transponder_capability)).to_string()); +} + + +int crc2(std::vector mem, bool encode) { + std::vector G = {0xFF, 0xFA, 0x04, 0x80}; // CRC多项式 + + // 如果编码模式开启,清除最后24位,并填充为0 + if (encode) { + for (size_t i = mem.size() - 3; i < mem.size(); ++i) { + mem[i] = 0; // 清空最后3个字节 + } + } + + // CRC 计算过程 + for (size_t ibyte = 0; ibyte < mem.size() - 3; ++ibyte) { + for (int ibit = 0; ibit < 8; ++ibit) { + int mask = 0x80 >> ibit; // 通过掩码提取当前字节的当前位 + int bits = mem[ibyte] & mask; // 提取当前字节的当前位 + if (bits > 0) { // 如果该位为1 + mem[ibyte] ^= (G[0] >> ibit); + mem[ibyte + 1] ^= (0xFF & ((G[0] << (8 - ibit)) | (G[1] >> ibit))); + mem[ibyte + 2] ^= (0xFF & ((G[1] << (8 - ibit)) | (G[2] >> ibit))); + mem[ibyte + 3] ^= (0xFF & ((G[2] << (8 - ibit)) | (G[3] >> ibit))); + } + } + } + + // 计算最终的 CRC 校验和 + int result = (mem[mem.size() - 3] << 16) | (mem[mem.size() - 2] << 8) | mem[mem.size() - 1]; + return result; +} + + +/** + * @brief Mode-S 循环冗余校验。 + * + * 检测 Mode-S 消息中是否发生位错误。当编码选项开启时,将生成校验和。 + * + * @param msg_bin 112位二进制字符串。 + * @param encode 如果为真,则仅编码数据并返回校验和。 + * @return int 消息校验和或奇偶校验位(编码器)。 + */ + +int crc_old(const std::string& msg_bin, bool encode) { + std::string s = bin2hex(msg_bin); + std::vector d; + d.resize(s.size()); + std::memcpy(d.data(), s.data(), s.size()); + return crc2(d, encode); +} + + + +// 编译期计算 CRC 查找表 +constexpr uint32_t generate_crc(uint32_t crc, uint8_t byte, const std::vector& generator) { + for (uint32_t j = 0; j < 8; ++j) { + if (crc & 0x80000000) { + crc = (crc << 1) ^ ((generator[0] << 24) | (generator[1] << 16) | (generator[2] << 8) | generator[3]); + } else { + crc = crc << 1; + } + } + return crc; +} + +// 编译期生成 CRC 查找表 +constexpr void generate_crc_table(std::vector& table, const std::vector& generator) { + for (uint32_t i = 0; i < 256; ++i) { + table[i] = generate_crc(i, static_cast(i), generator); + } +} + +std::string crc_hex(std::string msg_bin, bool encode) { + std::ostringstream oss; + int ret = crc(msg_bin, encode); + oss << std::hex << std::uppercase << ret; + return oss.str(); +} + +int crc(const std::string& msg_bin, bool encode) { + std::vector G = {0xFF, 0xFA, 0x04, 0x80}; + std::string mem = Psc::bin2mem(msg_bin); + auto M = (std::uint8_t*)mem.data(); + int n = mem.size(); + + if (encode) { + M[n - 1] = 0; + M[n - 2] = 0; + M[n - 3] = 0; + } + + // 对字节数组执行 CRC 计算 + for (size_t i = 0; i < n - 3; ++i) { + for (std::uint32_t j = 0; j < 8; ++j) { + std::uint32_t mask = 0x80 >> j; // 创建掩码 + std::uint32_t bits = M[i] & mask; // 获取当前位的值 + if (bits > 0) { // 如果当前位为 1 + M[i] ^= (G[0] >> j); // 对当前字节异或多项式的第一部分 + M[i + 1] ^= (0xFF & ((G[0] << (8 - j)) | (G[1] >> j))); // 异或第二部分 + M[i + 2] ^= (0xFF & ((G[1] << (8 - j)) | (G[2] >> j))); // 异或第三部分 + M[i + 3] ^= (0xFF & ((G[2] << (8 - j)) | (G[3] >> j))); // 异或第四部分 + } + } + } + int result = (M[n - 3] << 16) | (M[n - 2] << 8) | M[n - 1]; + return result; +} + +// int crc(const std::string& msg_bin, bool encode) { +// std::vector G = {0xFF, 0xFA, 0x04, 0x80}; +// std::string mem = Psc::bin2mem(msg_bin); +// auto M = (std::uint8_t*)mem.data(); +// int n = mem.size(); +// +// if (encode) { +// M[n - 1] = 0; +// M[n - 2] = 0; +// M[n - 3] = 0; +// } +// +// // 对字节数组执行 CRC 计算 +// for (size_t i = 0; i < n - 3; ++i) { +// for (std::uint32_t j = 0; j < 8; ++j) { +// std::uint32_t mask = 0x80 >> j; // 创建掩码 +// std::uint32_t bits = M[i] & mask; // 获取当前位的值 +// if (bits > 0) { // 如果当前位为 1 +// M[i] ^= (G[0] >> j); // 对当前字节异或多项式的第一部分 +// M[i + 1] ^= (0xFF & ((G[0] << (8 - j)) | (G[1] >> j))); // 异或第二部分 +// M[i + 2] ^= (0xFF & ((G[1] << (8 - j)) | (G[2] >> j))); // 异或第三部分 +// M[i + 3] ^= (0xFF & ((G[2] << (8 - j)) | (G[3] >> j))); // 异或第四部分 +// } +// } +// } +// int result = (M[n - 3] << 16) | (M[n - 2] << 8) | M[n - 1]; +// return result; +// } + + + +/** + * @brief 从 Mode-S 消息计算 ICAO 地址。 + * + * 仅适用于 DF4、DF5、DF20、DF21 消息。 + * + * @param msg_bin 112位二进制消息字符串 + * @return ICAO 地址,6 字节的十六进制字符串 + */ +std::string decode_icao(const std::string& msg_bin) { + std::string addr; + auto DF = decode_df(msg_bin); + if (DF == Downlink_Format::All_Call_Reply_11 || + DF == Downlink_Format::Extended_Squitter_17 || + DF == Downlink_Format::Extended_Squitter_Non_Transponder_18) + { + addr = msg_bin.substr(8, 24); + return bin2hex(addr); + } else if ( + DF == Downlink_Format::Short_Air_Air_Surveillance_0 || + DF == Downlink_Format::Surveillance_Altitude_Reply_4 || + DF == Downlink_Format::Surveillance_Identity_Reply_5 || + DF == Downlink_Format::Long_Air_Air_Surveillance_16 || + DF == Downlink_Format::Comm_B_Altitude_Reply_20 || + DF == Downlink_Format::Comm_B_Identity_Reply_21) + { + unsigned long long c0 = crc(msg_bin, true); // Assuming crc function is defined elsewhere + unsigned long long c1 = std::stoull(msg_bin.substr(msg_bin.size() - 24), nullptr, 2); + std::ostringstream oss; + auto ret = (c0 ^ c1); + oss << std::uppercase << std::hex << std::setw(6) << std::setfill('0') << ret; + // oss << std::uppercase << std::hex << ret; + addr = oss.str(); + return addr; + } + return ""; +} + + +/** + * @brief 设置消息中的 ICAO 地址。 + * + * 该函数根据消息的下行格式 (DF) 修改 ADS-B/Mode-S 消息中的 ICAO 地址。 + * 对于 DF 11、17 和 18,直接替换消息中的 ICAO 地址。 + * 对于 DF 0、4、5、16、20 和 21,先替换 ICAO 地址,然后重新计算 CRC 校验码, + * 以确保消息完整性。 + * + * @param msg 输入的 ADS-B/Mode-S 消息,使用二进制字符串表示(由 '0' 和 '1' 组成的字符串), + * 该消息将在函数内修改以包含新的 ICAO 地址。 + * @param icaoHex 要设置的新 ICAO 地址,使用十六进制字符串表示(例如 "ABCDEF"), + * 该地址将被转换为二进制形式并插入到消息的合适位置。 + * + * @return 返回修改后的消息,其中包含更新后的 ICAO 地址和(如果适用)重新计算的 CRC 校验码。 + */ +std::string set_icao(std::string& msg, const std::string& icaoHex) { + auto DF = decode_df(msg); // 获取DF值,假设df函数已经定义 + std::string newIcao = hex2bin(icaoHex); // 假设有hex2bin函数,将ICAO地址转换为二进制 + if (icaoHex.size() != 6) + { + std::cout << "set_icao:" << icaoHex.size() << LOG_POS_SIMPLE << std::endl; + Psc::fail_fast(); + } + if ( + DF == Downlink_Format::All_Call_Reply_11 || + DF == Downlink_Format::Extended_Squitter_17 || + DF == Downlink_Format::Extended_Squitter_Non_Transponder_18) { + // 替换消息中ICAO地址部分 (从第9位到第32位,也就是msg的第8到31索引) + msg.replace(8, 24, newIcao); // 直接替换消息中的ICAO地址 + } else if ( + DF == Downlink_Format::Short_Air_Air_Surveillance_0 || + DF == Downlink_Format::Surveillance_Altitude_Reply_4 || + DF == Downlink_Format::Surveillance_Identity_Reply_5 || + DF == Downlink_Format::Long_Air_Air_Surveillance_16 || + DF == Downlink_Format::Comm_B_Altitude_Reply_20 || + DF == Downlink_Format::Comm_B_Identity_Reply_21){ + unsigned long long newCrc = crc(msg, true); // 计算新的 CRC + std::ostringstream oss; + oss << std::bitset<24>(newCrc ^ bin2(newIcao)); // 将新的 CRC 转换为 24 位二进制 + std::string crcBits = oss.str(); + msg.replace(msg.size() - 24, 24, crcBits); + } + return msg; +} + +/** + * @brief 检查 ICAO 地址是否已分配(附件 10,卷 3)。 + */ +bool is_icao_assigned(const std::string& icao) { + if (icao.empty() || icao.length() != 6) return false; + int icaoint = std::stoi(icao, nullptr, 16); + if (0x200000 < icaoint && icaoint < 0x27FFFF) return false; // AFI + if (0x280000 < icaoint && icaoint < 0x28FFFF) return false; // SAM + if (0x500000 < icaoint && icaoint < 0x5FFFFF) return false; // EUR, NAT + if (0x600000 < icaoint && icaoint < 0x67FFFF) return false; // MID + if (0x680000 < icaoint && icaoint < 0x6F0000) return false; // ASIA + if (0x900000 < icaoint && icaoint < 0x9FFFFF) return false; // NAM, PAC + if (0xB00000 < icaoint && icaoint < 0xBFFFFF) return false; // CAR + if (0xD00000 < icaoint && icaoint < 0xDFFFFF) return false; // future + if (0xF00000 < icaoint && icaoint < 0xFFFFFF) return false; // future + return true; +} + +/** + * @brief ADS-B 消息的类型代码 + * + * @param msg 112位二进制字符串 + * @return int 类型代码数字 + */ +int type_code(const std::string& msg) { + CE(decode_df(msg) != Downlink_Format::Extended_Squitter_17 && + decode_df(msg) != Downlink_Format::Extended_Squitter_Non_Transponder_18, "df error!") + return bin2(msg.substr(32, 5)); +} +void set_type_code(std::string& msg, int type_code) { + msg.replace(32, 5, std::bitset<5>(type_code).to_string()); +} +int get_type_code(std::string& msg) { + return bin2(msg.substr(32, 5)); +} + + +// 不同纬度下 精度区块的数量 + + +/** + * @brief 计算编码在 DF5 或 DF21 消息中的身份代码(应答)。 + * + * @param msg 112位二进制字符串 + * @return string 应答代码 + */ +std::string idcode(const std::string& msg) { + CE(decode_df(msg) != Downlink_Format::Surveillance_Identity_Reply_5 && + decode_df(msg) != Downlink_Format::Comm_B_Identity_Reply_21, + "Message must be Downlink Format 5 or Downlink_Type::Comm_B_Identity_Reply.") + std::string idcodebin = msg.substr(19, 13); + return decode_squawk13(idcodebin); // Assuming squawk function is defined elsewhere +} + +/** + * @brief 解码 13 位身份(应答)代码。 + * + * @param binstr 13 位二进制字符串 + * @return string 应答代码 + */ +std::string decode_squawk13(const std::string& binstr) { + CE(binstr.length() != 13 || std::count(binstr.begin(), binstr.end(), '0') + std::count( + binstr.begin(), binstr.end(), '1') != 13, + "squawk ! Input must be 13 bits binary string") + std::string C1 = std::string(1, binstr[0]); + std::string A1 = std::string(1, binstr[1]); + std::string C2 = std::string(1, binstr[2]); + std::string A2 = std::string(1, binstr[3]); + std::string C4 = std::string(1, binstr[4]); + std::string A4 = std::string(1, binstr[5]); + std::string B1 = std::string(1, binstr[7]); + std::string D1 = std::string(1, binstr[8]); + std::string B2 = std::string(1, binstr[9]); + std::string D2 = std::string(1, binstr[10]); + std::string B4 = std::string(1, binstr[11]); + std::string D4 = std::string(1, binstr[12]); + int byte1 = std::stoi(A4 + A2 + A1, nullptr, 2); + int byte2 = std::stoi(B4 + B2 + B1, nullptr, 2); + int byte3 = std::stoi(C4 + C2 + C1, nullptr, 2); + int byte4 = std::stoi(D4 + D2 + D1, nullptr, 2); + return std::to_string(byte1) + std::to_string(byte2) + std::to_string(byte3) + std::to_string(byte4); +} +/** + * @brief 编码 13 位身份(应答)代码。 + * + * @param squawk 4位八进制应答代码字符串 + * @return std::string 13 位二进制字符串 + */ +std::string encode_squawk13(const std::string& squawk) { + // 检查输入长度和合法性 + if (squawk.length() != 4) { + throw std::invalid_argument("Squawk code must be 4 digits."); + } + + // 检查每个字符是否在0到7之间 + for (char digit : squawk) { + if (digit < '0' || digit > '7') { + throw std::invalid_argument("Squawk code must contain octal digits (0-7)."); + } + } + + std::string binstr = "0000000000000"; // 初始化13位二进制字符串 + + // 将每个八进制数字转换为3位二进制 + std::string a_bin = std::bitset<3>(squawk[0] - '0').to_string(); // A4 + std::string b_bin = std::bitset<3>(squawk[1] - '0').to_string(); // B4 + std::string c_bin = std::bitset<3>(squawk[2] - '0').to_string(); // C4 + std::string d_bin = std::bitset<3>(squawk[3] - '0').to_string(); // D4 + + // 构建完整的13位二进制字符串 + binstr[0] = c_bin[2]; // C1 + binstr[1] = a_bin[2]; // A1 + binstr[2] = c_bin[1]; // C2 + binstr[3] = a_bin[1]; // A2 + binstr[4] = c_bin[0]; // C4 + binstr[5] = a_bin[0]; // A4 + binstr[6] = '0'; // X位(默认为0) + binstr[7] = b_bin[2]; // B1 + binstr[8] = d_bin[2]; // D1 + binstr[9] = b_bin[1]; // B2 + binstr[10] = d_bin[1]; // D2 + binstr[11] = b_bin[0]; // B4 + binstr[12] = d_bin[0]; // D4 + + return binstr; +} +/** + * @brief 计算 DF4 或 DF20 消息中编码的高度。 + * + * @param msg 112位二进制字符串 + * @return int 高度(英尺) + */ +int altcode(const std::string& msg) { + CE(decode_df(msg) != Downlink_Format::Short_Air_Air_Surveillance_0 && + decode_df(msg) != Downlink_Format::Surveillance_Altitude_Reply_4 && + decode_df(msg) != Downlink_Format::Long_Air_Air_Surveillance_16 && + decode_df(msg) != Downlink_Format::Comm_B_Altitude_Reply_20, + "Message must be Downlink Format 0, 4, 16, or 20.") + std::string altitude_code = msg.substr(19, 13); + return altitude13_ft(altitude_code); +} + +/** + * @brief 解码 13 位高度编码。 + * + * @param binstr 13 位二进制字符串 + * @return int 高度(英尺) + */ +int altitude13_ft(const std::string& binstr) { + CE( + binstr.length() != 13 || std::count(binstr.begin(), binstr.end(), '0') + std::count( binstr.begin(), binstr.end( + ), '1') != 13, "parse altitude error! Input must be 13 bits binary string") + std::string Mbit = std::string(1, binstr[6]); + std::string Qbit = std::string(1, binstr[8]); + int alt; + if (bin2(binstr) == 0) + { + // altitude unknown or invalid + return -1; + } else if (Mbit == "0") + { + // unit in ft + if (Qbit == "1") + { + + // 25ft interval + std::string vbin = binstr.substr(0, 6) + binstr[7] + binstr.substr(9); + alt = bin2(vbin) * 25 - 1000; + } else if (Qbit == "0") + { + // 100ft interval, above 50187.5ft + std::string C1 = std::string(1, binstr[0]); + std::string A1 = std::string(1, binstr[1]); + std::string C2 = std::string(1, binstr[2]); + std::string A2 = std::string(1, binstr[3]); + std::string C4 = std::string(1, binstr[4]); + std::string A4 = std::string(1, binstr[5]); + std::string B1 = std::string(1, binstr[7]); + std::string B2 = std::string(1, binstr[9]); + std::string D2 = std::string(1, binstr[10]); + std::string B4 = std::string(1, binstr[11]); + std::string D4 = std::string(1, binstr[12]); + std::string graystr = D2 + D4 + A1 + A2 + A4 + B1 + B2 + B4 + C1 + C2 + C4; + alt = gray2alt(graystr); + + + } + } else if (Mbit == "1") + { + // unit in meter + std::string vbin = binstr.substr(0, 6) + binstr.substr(7); + alt = static_cast(bin2(vbin) * 3.28084); // convert to ft + } + + + return alt; +} + + +std::string encode_altitude13_meter(int alt) { + std::string bit12 = std::bitset<12>(alt).to_string(); + return bit12.substr(0, 6) + "1" + bit12.substr(6); +} + +std::string encode_altitude13_feet(int alt) { + std::string bit11 = std::bitset<11>(alt/25 + 40).to_string(); + return bit11.substr(0, 6) + "0" + bit11.substr(6, 1) + "1" + bit11.substr(7); +} + +std::string encode_altitude_13_above_50187(int alt_50187) { + std::string binstr(13, '0'); // 初始化为13个'0' + binstr[6] = 0; + binstr[8] = 0; + + return "????"; +} + + +int gray2alt(const std::string& binstr) { + PSC_ASSERT(binstr.size() == 11, "gray2alt binstr.size != 11"); + std::string gc500 = binstr.substr(0, 8); + int n500 = gray2int(gc500); + // in 100-ft step must be converted first + std::string gc100 = binstr.substr(8); + int n100 = gray2int(gc100); + if (n100 == 0 || n100 == 5 || n100 == 6) + { + return -1; + } + if (n100 == 7) + { + n100 = 5; + } + if (n500 % 2) + { + n100 = 6 - n100; + } + int alt = (n500 * 500 + n100 * 100) - 1300; + return alt; +} +int gray2int(const std::string& binstr) { + int num = bin2(binstr); + num ^= num >> 8; + num ^= num >> 4; + num ^= num >> 2; + num ^= num >> 1; + return num; +} +std::string databin(const std::string& msg) { + return msg.substr(32, 56); // bytes 9 to 22 (14 bytes total) +} +bool allzeros(const std::string& msg) { + return msg.find_first_not_of('0') == std::string::npos; +} + +// 从1开始计数 1-based +// data = "01001101" +// msb = 3, lsb = 6 +// substr = "0011" +// value = 3 +// 如果 状态位 == 0 且 数值 != 0 +// → 错误状态 +// 否则 +// → 正常 +bool wrongstatus(const std::string& data, int sb, int msb, int lsb) { + int status = static_cast(data[sb - 1] - '0'); + int value = bin2(data.substr(msb - 1, lsb - msb + 1)); + if (!status) + { + if (value != 0) + { + return true; + } + } + return false; +} +/* + + +""" +Functions for aeronautics in this module + +- physical quantities always in SI units +- lat,lon,course and heading in degrees + +International Standard Atmosphere +:: + + p,rho,T = atmos(H) # atmos as function of geopotential altitude H [m] + a = vsound(H) # speed of sound [m/s] as function of H[m] + p = pressure(H) # calls atmos but returns only pressure [Pa] + T = temperature(H) # calculates temperature [K] + rho = density(H) # calls atmos but returns only pressure [Pa] + +Speed conversion at altitude H[m] in ISA +:: + + Mach = tas2mach(Vtas,H) # true airspeed (Vtas) to mach number conversion + Vtas = mach2tas(Mach,H) # mach number to true airspeed (Vtas) conversion + Vtas = eas2tas(Veas,H) # equivalent airspeed to true airspeed, H in [m] + Veas = tas2eas(Vtas,H) # true airspeed to equivent airspeed, H in [m] + Vtas = cas2tas(Vcas,H) # Vcas to Vtas conversion both m/s, H in [m] + Vcas = tas2cas(Vtas,H) # Vtas to Vcas conversion both m/s, H in [m] + Vcas = mach2cas(Mach,H) # Mach to Vcas conversion Vcas in m/s, H in [m] + Mach = cas2mach(Vcas,H) # Vcas to mach copnversion Vcas in m/s, H in [m] + +""" +*/ + +namespace aero { + // Function implementations + + Atmos atmos(double H) { + double T = std::max(288.15 - 0.0065 * H, 216.65); + double rhotrop = 1.225 * std::pow(T / 288.15, 4.256848030018761); + double dhstrat = std::max(0.0, H - 11000.0); + double rho = rhotrop * std::exp(-dhstrat / 6341.552161); + double p = rho * R * T; + return {p, rho, T}; + } + double temperature(double H) { + auto t = atmos(H); + return t.T; + } + double pressure(double H) { + auto t = atmos(H); + return t.p; + } + double density(double H) { + auto t = atmos(H); + return t.rho; + } + double vsound(double H) { + double T = temperature(H); + return std::sqrt(gamma * R * T); + } + double distance(double lat1, double lon1, double lat2, double lon2, double H) { + double phi1 = M_PI / 180.0 * (90 - lat1); + double phi2 = M_PI / 180.0 * (90 - lat2); + double theta1 = M_PI / 180.0 * lon1; + double theta2 = M_PI / 180.0 * lon2; + double cos_value = std::sin(phi1) * std::sin(phi2) * std::cos(theta1 - theta2) + std::cos(phi1) * + std::cos(phi2); + cos_value = std::min(cos_value, 1.0); + double arc = std::acos(cos_value); + return arc * (r_earth + H); // meters + } + double bearing(double lat1, double lon1, double lat2, double lon2) { + lat1 = M_PI / 180.0 * lat1; + lon1 = M_PI / 180.0 * lon1; + lat2 = M_PI / 180.0 * lat2; + lon2 = M_PI / 180.0 * lon2; + double x = std::sin(lon2 - lon1) * std::cos(lat2); + double y = std::cos(lat1) * std::sin(lat2) - std::sin(lat1) * std::cos(lat2) * std::cos(lon2 - lon1); + double initial_bearing = std::atan2(y, x); + initial_bearing = initial_bearing * 180.0 / M_PI; + return fmod((initial_bearing + 360), 360); + } + double tas2mach(double Vtas, double H) { + double a = vsound(H); + return Vtas / a; + } + double mach2tas(double Mach, double H) { + double a = vsound(H); + return Mach * a; + } + double eas2tas(double Veas, double H) { + double rho = density(H); + return Veas * std::sqrt(rho0 / rho); + } + double tas2eas(double Vtas, double H) { + double rho = density(H); + return Vtas * std::sqrt(rho / rho0); + } + double cas2tas(double Vcas, double H) { + auto a = atmos(H); + auto p = a.p; + auto rho = a.rho; + auto T = a.T; + double qdyn = p0 * std::pow((1 + rho0 * Vcas * Vcas / (7 * p0)), 3.5) - 1.0; + return std::sqrt(7 * p / rho * (std::pow((1 + qdyn / p), 2.0 / 7.0) - 1.0)); + } + double tas2cas(double Vtas, double H) { + auto a = atmos(H); + auto p = a.p; + auto rho = a.rho; + auto T = a.T; + double qdyn = p * (std::pow((1 + rho * Vtas * Vtas / (7 * p)), 3.5) - 1.0); + return std::sqrt(7 * p0 / rho0 * (std::pow((qdyn / p0 + 1.0), 2.0 / 7.0) - 1.0)); + } + double mach2cas(double Mach, double H) { + double Vtas = mach2tas(Mach, H); + return tas2cas(Vtas, H); + } + double cas2mach(double Vcas, double H) { + double Vtas = cas2tas(Vcas, H); + return tas2mach(Vtas, H); + } +} + +/** + * @brief 解码 DF 4、5、20 和 21 的飞行状态。 + * + * @param msg 56位二进制消息字符串 + * @return int 飞行状态,描述 + */ +std::tuple fly_state(const std::string& msg) { + int flight_status = bin2(msg.substr(5, 3)); // Extracting bits 5 to 7 + std::string text; + switch (flight_status) + { + case 0: text = "no alert, no SPI, aircraft is airborne"; + break; + case 1: text = "no alert, no SPI, aircraft is on-ground"; + break; + case 2: text = "alert, no SPI, aircraft is airborne"; + break; + case 3: text = "alert, no SPI, aircraft is on-ground"; + break; + case 4: text = "alert, SPI, aircraft is airborne or on-ground"; + break; + case 5: text = "no alert, SPI, aircraft is airborne or on-ground"; + break; + default: text = "unknown flight status"; // Optional: handle unknown status + break; + } + return std::make_tuple(flight_status, text); +} + +/** + * @brief 解码 DF 4、5、20 和 21 的下行请求。 + * + * @param msg 56位二进制消息字符串 + * @return int 下行请求,描述 + */ + +std::tuple dr(const std::string& msg) { + int downlink_request = bin2(msg.substr(8, 5)); // Extracting bits 8 to 12 + std::string text; + switch (downlink_request) + { + case 0: text = "no downlink request"; + break; + case 1: text = "request to send Comm-B message"; + break; + case 4: text = "Comm-B broadcast 1 available"; + break; + case 5: text = "Comm-B broadcast 2 available"; + break; + default: if (downlink_request >= 16) + { + text = "ELM downlink segments available: " + std::to_string(downlink_request - 15); + } else + { + text = "unknown downlink request"; // Optional: handle unknown request + } + break; + } + return std::make_tuple(downlink_request, text); +} + +/** + * @brief 解码 DF 4、5、20 和 21 的实用信息消息。 + * + * 实用信息消息包含询问者标识符和保留类型。 + * + * @param msg 56位二进制消息字符串 + * @return int 触发回复的询问者标识符代码,以及 + * 询问者所做的保留类型 + */ +std::tuple um(const std::string& msg) { + int iis = bin2(msg.substr(13, 4)); // Extracting bits 13 to 16 + int ids = bin2(msg.substr(17, 2)); // Extracting bits 17 to 18 + std::string ids_text; + switch (ids) + { + case 0: ids_text = ""; + break; + case 1: ids_text = "Comm-B interrogator identifier code"; + break; + case 2: ids_text = "Comm-C interrogator identifier code"; + break; + case 3: ids_text = "Comm-D interrogator identifier code"; + break; + default: ids_text = ""; // Handle unknown case if necessary + break; + } + return std::make_tuple(iis, ids, ids_text); +} + +std::string tell(const std::string& msg) { + std::string output; + auto print = [&output](const std::string& label, const std::string& value, const std::string& unit = "") { + std::ostringstream oss; + // Format the label and value + oss << std::left << std::setw(30) << label << ": " << value; + // Add unit if not empty + if (!unit.empty()) + { + oss << " " << unit << "\n"; + } else + { + oss << "\n"; + } + // Append formatted string to output + output += oss.str(); + }; + auto df = decode_df(msg); + std::string icao = decode_icao(msg); + print("Message", bin2hex(msg)); + print("ICAO address", icao); + print("Downlink Format", std::to_string(static_cast(df))); + if (df == Downlink_Format::Extended_Squitter_17) + { + print("Protocol", "Mode-S Extended Squitter (ADS-B)"); + int tc = type_code(msg); + // Handle different type codes + if (tc >= 1 && tc <= 4) + { + // callsign + std::string cs = get_call_sign(msg); + print("Type", "Identification and category"); + print("Callsign", cs); + } else if (tc >= 5 && tc <= 8) + { + // surface position + // print("Type", "Surface position"); + // bool oe = oe_flag(msg); + // double cprlat = bin2(msg.substr(54, 17)) / 131072.0; + // double cprlon = bin2(msg.substr(71, 17)) / 131072.0; + // Ground_Speed v = surface_velocity(msg, ); + // print("CPR format", oe ? "Odd" : "Even"); + // print("CPR Latitude", std::to_string(cprlat)); + // print("CPR Longitude", std::to_string(cprlon)); + // print("Speed", std::to_string(v.speed), "knots"); + // print("Track", std::to_string(v.angle), "degrees"); + } else if (tc >= 9 && tc <= 18) + { + // airborne position + print("Type", "Airborne position (with barometric altitude)"); + //double alt = altitude_adsb(msg); + double alt = -1; + bool oe = oe_flag(msg); + double cprlat = bin2(msg.substr(54, 17)) / 131072.0; + double cprlon = bin2(msg.substr(71, 17)) / 131072.0; + print("CPR format", oe ? "Odd" : "Even"); + print("CPR Latitude", std::to_string(cprlat)); + print("CPR Longitude", std::to_string(cprlon)); + print("Altitude", std::to_string(alt), "feet"); + } else if (tc == 19) + { + // airborne velocity + print("Type", "Airborne velocity"); + Air_Speed velocity = airborne_velocity(msg); + double spd = velocity.speed; + double trk = velocity.angle; + double vr = velocity.vertical_rate; + std::unordered_map types = {{0, "Ground speed"}, {1, "True airspeed"}}; + print("Speed", std::to_string(spd), "knots"); + print("Track", std::to_string(trk), "degrees"); + print("Vertical rate", std::to_string(vr), "feet/minute"); + print("Type", velocity.speed_type); + } else if (tc >= 20 && tc <= 22) + { + // airborne position (with GNSS altitude) + print("Type", "Airborne position (with GNSS altitude)"); + double alt = -1; + bool oe = oe_flag(msg); + double cprlat = bin2(msg.substr(54, 17)) / 131072.0; + double cprlon = bin2(msg.substr(71, 17)) / 131072.0; + print("CPR format", oe ? "Odd" : "Even"); + print("CPR Latitude", std::to_string(cprlat)); + print("CPR Longitude", std::to_string(cprlon)); + print("Altitude", std::to_string(alt), "feet"); + } else if (tc == 29) + { + // target state and status + print("Type", "Target State and Status"); + int subtype = bin2(msg.substr(32, 2)); + print("Subtype", std::to_string(subtype)); + int tcas_operational = SSR::tcas_operational(msg); + std::unordered_map types_29 = {{0, "Not Engaged"}, {1, "Engaged"}}; + std::unordered_map tcas_operational_types = {{0, "Not Operational"}, {1, "Operational"}}; + if (subtype == 0) + { + std::unordered_map emergency_types = { + {0, "No emergency"}, + {1, "General emergency"}, + {2, "Lifeguard/medical emergency"}, + {3, "Minimum fuel"}, + {4, "No communications"}, + {5, "Unlawful interference"}, + {6, "Downed aircraft"}, + {7, "Reserved"} + }; + std::unordered_map vertical_horizontal_types = { + {1, "Acquiring mode"}, + {2, "Capturing/Maintaining mode"} + }; + std::unordered_map tcas_ra_types = {{0, "Not active"}, {1, "Active"}}; + int alt; + std::string alt_source, alt_ref; + std::tie(alt, alt_source, alt_ref) = target_altitude(msg); + int angle; + std::string angle_type, angle_source; + std::tie(angle, angle_type, angle_source) = target_angle(msg); + Vertical_Horizontal_Type vertical_mode = SSR::vertical_mode(msg); + Vertical_Horizontal_Type horizontal_mode = SSR::horizontal_mode(msg); + int tcas_ra = SSR::tcas_ra(msg); + Emergency_Type emergency_status = SSR::emergency_status(msg); + print("Target altitude", std::to_string(alt), "feet"); + print("Altitude source", alt_source); + print("Altitude reference", alt_ref); + print("Angle", std::to_string(angle), "°"); + print("Angle Type", angle_type); + print("Angle Source", angle_source); + print("Vertical mode", Psc::to_string(vertical_mode)); + print("Horizontal mode", Psc::to_string(horizontal_mode)); + print("TCAS/ACAS", tcas_operational_types[tcas_operational]); + print("TCAS/ACAS RA", tcas_ra_types[tcas_ra]); + print("Emergency status", Psc::to_string(emergency_status)); + } else + { + double alt; + std::string alt_source; + std::tie(alt, alt_source) = selected_altitude(msg); + double baro = baro_pressure_setting(msg); + double hdg = selected_heading(msg); + int autopilot = SSR::autopilot(msg); + int vnav = vnav_mode(msg); + int alt_hold = altitude_hold_mode(msg); + int app = approach_mode(msg); + int lnav = lnav_mode(msg); + print("Selected altitude", std::to_string(alt), "feet"); + print("Altitude source", alt_source); + print("Barometric pressure setting", std::to_string(baro), static_cast(baro) ? "millibars" : ""); + print("Selected Heading", std::to_string(hdg), "°"); + if (bin2(msg.substr(32, 1)) != 0) + { + print("Autopilot", types_29[autopilot]); + print("VNAV mode", types_29[vnav]); + print("Altitude hold mode", types_29[alt_hold]); + print("Approach mode", types_29[app]); + print("TCAS/ACAS", tcas_operational_types[tcas_operational]); + print("LNAV mode", types_29[lnav]); + } + } + } else + { + // 未知的tc + } + } + if (df == Downlink_Format::Comm_B_Altitude_Reply_20) + { + print("Protocol", "Mode-S Comm-B altitude reply"); + print("Altitude", std::to_string(altcode(msg)), "feet"); + } + if (df == Downlink_Format::Comm_B_Identity_Reply_21) + { + print("Protocol", "Mode-S Comm-B identity reply"); + print("Squawk code", idcode(msg)); + } + if (df == Downlink_Format::Comm_B_Altitude_Reply_20 || df == Downlink_Format::Comm_B_Identity_Reply_21) + { + std::unordered_map labels = { + {"BDS10", "Data link capability"}, + {"BDS17", "GICB capability"}, + {"BDS20", "Aircraft identification"}, + {"BDS30", "ACAS resolution"}, + {"BDS40", "Vertical intention report"}, + {"BDS50", "Track and turn report"}, + {"BDS60", "Heading and speed report"}, + {"BDS44", "Meteorological routine air report"}, + {"BDS45", "Meteorological hazard report"}, + {"EMPTY", "[No information available]"} + }; + std::string BDS = infer(msg, true); + if (!BDS.empty() && labels.count(BDS)) + { + print("BDS", BDS + " " + labels[BDS]); + } else + { + print("BDS", BDS); + } + if (BDS == "BDS20") + { + std::string callsign = cs20(msg); + print("Callsign", callsign); + } + if (BDS == "BDS40") + { + print("MCP target alt", std::to_string(selalt40mcp(msg).value()), "feet"); + print("FMS Target alt", std::to_string(selalt40fms(msg).value()), "feet"); + print("Pressure", std::to_string(p40baro(msg).value()), "毫巴(millibar)"); + } + if (BDS == "BDS50") + { + print("Roll angle", std::to_string(roll50(msg)), "degrees"); + print("Track angle", std::to_string(trk50(msg)), "degrees"); + print("Track rate", std::to_string(rtrk50(msg)), "degree/second"); + print("Ground speed", std::to_string(gs50(msg)), "knots"); + print("True airspeed", std::to_string(tas50(msg)), "knots"); + } + if (BDS == "BDS60") + { + print("Magnetic Heading", std::to_string(hdg60(msg)), "degrees"); + print("Indicated airspeed", std::to_string(ias60(msg)), "knots"); + print("Mach number", std::to_string(mach60(msg))); + print("Vertical rate (Baro)", std::to_string(vr60baro(msg)), "feet/minute"); + print("Vertical rate (INS)", std::to_string(vr60ins(msg)), "feet/minute"); + } + if (BDS == "BDS44") + { + auto wind = wind44(msg).value(); + print("Wind speed", std::to_string(wind.wind_speed), "knots"); + print("Wind direction", std::to_string(wind.wind_speed), "degrees"); + auto temp = temp44(msg); + print("Temperature 1", std::to_string(temp.temperature), "Celsius"); + print("Temperature 2", std::to_string(temp.temperature_alternative), "Celsius"); + print("Pressure", std::to_string(p44(msg)), "hPa"); + print("Humidity", std::to_string(hum44(msg)), "%"); + print("Turbulence", std::to_string(turb44(msg))); + } + if (BDS == "BDS45") + { + print("Turbulence", std::to_string(turb45(msg))); + print("Wind shear", std::to_string(ws45(msg))); + print("Microbust", std::to_string(mb45(msg))); + print("Icing", std::to_string(ic45(msg))); + print("Wake vortex", std::to_string(wv45(msg))); + print("Temperature", std::to_string(temp45(msg)), "Celsius"); + print("Pressure", std::to_string(p45(msg)), "hPa"); + print("Radio height", std::to_string(rh45(msg)), "feet"); + } + } + return output; +} + +std::string grayToBinary(std::string gray) { + std::string binary = ""; + binary += gray[0]; // 第一个二进制位和格雷码的第一位相同 + + // 根据格雷码推导出二进制码 + for (size_t i = 1; i < gray.length(); i++) { + // 当前位与前一位二进制位异或 + if (gray[i] == '0') { + binary += binary[i - 1]; + } else { + binary += (binary[i - 1] == '0') ? '1' : '0'; + } + } + + return binary; +} + +std::string binaryToGray(std::string binary) { + std::string gray = ""; + gray += binary[0]; // 格雷码的第一位和二进制的第一位相同 + + // 逐位计算格雷码 + for (size_t i = 1; i < binary.length(); i++) { + // 当前位与前一位二进制位异或 + if (binary[i] == binary[i - 1]) { + gray += '0'; + } else { + gray += '1'; + } + } + + return gray; +} + + +int binaryToGray(int num) { + return num ^ (num >> 1); +} +int grayToBinary(int gray) { + int binary = gray; + while (gray >>= 1) { + binary ^= gray; + } + return binary; +} + + +// 根据输入高度生成Mode C编码 +std::string encode_mode_c(int altitude) { + // 初始高度为-1200英尺 + int baseAltitude = -1200; + // 高度差,步进以100英尺为单位 + int deltaAltitude = altitude - baseAltitude; + CE(altitude > 100000, "Error: Altitude exceeds 100,000 feet. Invalid Mode C code!") + // C组码,步进单位为100英尺 + int cGroup = (deltaAltitude / 100) % 5; + // D1到B4 格雷码,步进单位为500英尺 + int grayCodeIndex = (deltaAltitude / 500); + // 生成对应的格雷码 + int grayCode = binaryToGray(grayCodeIndex); + return std::bitset<9>(grayCode).to_string() + std::bitset<3>(cGroup).to_string(); +} + + +// decode_mode_c实现 +int decode_mode_c(const std::string& bit12) { + CE(bit12.size() != 12, + "Error: Input bit string is not 12 bits!") + // 前9位为Gray code + std::string grayCodeStr = bit12.substr(0, 9); + int grayCode = std::bitset<9>(grayCodeStr).to_ulong(); + // 最后3位为C组码 + std::string cGroupStr = bit12.substr(9, 3); + int cGroup = std::bitset<3>(cGroupStr).to_ulong(); + // 将Gray code转换为二进制 + int grayCodeIndex = grayToBinary(grayCode); + // 基础高度为-1200英尺 + int baseAltitude = -1200; + // 使用Gray code得到的高度,以500英尺为步进 + int altitude = baseAltitude + grayCodeIndex * 500; + // C组码为100英尺步进 + altitude += cGroup * 100; + return altitude; +} + + + +std::string encode_mode_a(const std::string& squawk) { + if (squawk.length() != 4 || !std::isdigit(squawk[0]) || !std::isdigit(squawk[1]) || + !std::isdigit(squawk[2]) || !std::isdigit(squawk[3])) { + return ""; // 输入无效 + } + std::bitset<12> mode_a_bits; // 用于存储12位的Mode A编码 + // Squawk code 4个数字,分别对每一位进行位操作以映射到 Mode A 的各个组 + int digits[4]; + for (int i = 0; i < 4; ++i) { + digits[i] = squawk[i] - '0'; // 将字符数字转换为整数 + } + + // C组编码 + mode_a_bits[11] = (digits[0] & 0b001); // C1 + mode_a_bits[10] = (digits[0] & 0b010) >> 1; // C2 + mode_a_bits[9] = (digits[0] & 0b100) >> 2; // C4 + + // A组编码 + mode_a_bits[8] = (digits[1] & 0b001); // A1 + mode_a_bits[7] = (digits[1] & 0b010) >> 1; // A2 + mode_a_bits[6] = (digits[1] & 0b100) >> 2; // A4 + + // B组编码 + mode_a_bits[5] = (digits[2] & 0b001); // B1 + mode_a_bits[4] = (digits[2] & 0b010) >> 1; // B2 + mode_a_bits[3] = (digits[2] & 0b100) >> 2; // B4 + + // D组编码 + mode_a_bits[2] = (digits[3] & 0b001); // D1 + mode_a_bits[1] = (digits[3] & 0b010) >> 1; // D2 + mode_a_bits[0] = (digits[3] & 0b100) >> 2; // D4 + + return mode_a_bits.to_string(); +} + +std::string decode_mode_a(const std::string& msg) { + if (msg.length() != 12) { + std::cerr << "Invalid input: Mode A message must be 12 bits long." << std::endl; + return ""; + } + + std::bitset<12> mode_a_bits(msg); // 将字符串转换为12位的bitset + + // 解析每个组的值 + int c_group = (mode_a_bits[11] << 0) | (mode_a_bits[10] << 1) | (mode_a_bits[9] << 2); + int a_group = (mode_a_bits[8] << 0) | (mode_a_bits[7] << 1) | (mode_a_bits[6] << 2); + int b_group = (mode_a_bits[5] << 0) | (mode_a_bits[4] << 1) | (mode_a_bits[3] << 2); + int d_group = (mode_a_bits[2] << 0) | (mode_a_bits[1] << 1) | (mode_a_bits[0] << 2); + + // 将各组转换回 Squawk Code + std::string squawk = std::to_string(c_group) + std::to_string(a_group) + + std::to_string(b_group) + std::to_string(d_group); + return squawk; +} + + + + + +} \ No newline at end of file diff --git a/SSR/py_Mode_S/Common.h b/SSR/py_Mode_S/Common.h new file mode 100644 index 0000000..2dbab47 --- /dev/null +++ b/SSR/py_Mode_S/Common.h @@ -0,0 +1,363 @@ +#pragma once + +#include "Core/Base/global_include.h" +#include "../CPR/CPR.h" +#include "SSR/enum.h" +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif +#define Handle_Error(msg) std::cerr << msg; \ +return std::nullopt; +#define Handle_Error2(cond, msg) if(cond) { \ +std::cerr << msg; \ +return std::nullopt; } +#define Handle_Error3(cond, msg) if(cond) { \ +std::cerr << msg;} +#define Exec(var, expr) auto _opt_##var = type_code(msg); \ + if(!_opt_##var.has_value()) return std::nullopt; \ + auto var = _opt_##var.value(); +#define Exec2(var, expr) auto _opt_##var = expr; \ + auto var = _opt_##var.value(); + + +// 定义一个宏 release模式输出core dump信息到控制台,到文件,这个文件名还要是自增的,这样不丢, 然后直接崩溃能看调用栈 + + +// Debug模式下,然后抛出异常,异常在最顶层集中处理,没有异常才把该报文的信息汇总 + + +namespace SSR { + +struct Air_Speed { + double speed = -1; // kt + double angle = -1; // degree + int vertical_rate = -1; // ft/min + std::string speed_type; // Speed type: 'GS' for ground speed, 'AS' for airspeed + std::string direction_source; // Optional: Direction source ('TRUE_NORTH' or 'MAGNETIC_NORTH') + std::string vertical_rate_source; // Optional: Vertical rate source ('BARO' or 'GNSS') + Psc::JSON toJson() { + return Psc::JSON::object({ + {"speed", speed}, + {"angle", angle}, + {"vertical_rate", vertical_rate}, + {"speed_type", speed_type}, + {"direction_source", direction_source}, + {"vertical_rate_source", vertical_rate_source}, + }); + } +}; + +struct Ground_Speed { + double speed = -1; // kt + double angle = -1; // degree + Psc::JSON toJson() { + return Psc::JSON::object({ + {"speed", speed}, + {"angle", angle}, + }); + } +}; + +// common +Downlink_Format decode_df(const std::string& msg); +void set_df(std::string& msg, Downlink_Format df); + + + + +// 奇偶校验 + +// uint32_t modesChecksum(unsigned char *msg, int bits); +// static std::string generator = "1111111111111010000001001"; // 25 bits +// static auto xorOperation = [](const std::string& data, const std::string& generator, size_t start) { +// std::string result = data; +// for (size_t i = 0; i < generator.size(); ++i) { +// // Perform XOR operation +// result[start + i] = (data[start + i] == generator[i]) ? '0' : '1'; +// } +// return result; +// }; + + +Transponder_Capability decode_transponder_capability(const std::string& msg); +void set_transponder_capability(std::string& msg, Transponder_Capability transponder_capability); + +int crc(const std::string& msg_bin, bool encode); + + + +std::string decode_icao(const std::string& msg_bin); +std::string set_icao(std::string& msg, const std::string& icaoHex); +bool is_icao_assigned(const std::string& icao); +int type_code(const std::string& msg); +void set_type_code(std::string& msg, int type_code); +int get_type_code(std::string& msg); + + +std::string idcode(const std::string& msg); +void set_idcode(std::string& msg, std::string idcode); +std::string decode_squawk13(const std::string& binstr); +std::string encode_squawk13(const std::string& squawk); +int altcode(const std::string& msg); + + +int altitude13_ft(const std::string& binstr); +std::string encode_altitude13_meter(int alt); +std::string encode_altitude13_feet(int alt); +//std::string encode_altitude_13_above_50187(int alt_50187); + + +int gray2int(const std::string& binstr); +std::string databin(const std::string& msg); +bool allzeros(const std::string& msg); +bool wrongstatus(const std::string& data, int sb, int msb, int lsb); +int gray2alt(const std::string& binstr); + +std::string grayToBinary(std::string gray); +std::string binaryToGray(std::string binary); + +//Squawk Code 的取值范围是 0000 到 7777 +std::string encode_mode_a(const std::string& squawk); +std::string decode_mode_a(const std::string& msg); +std::string encode_mode_c(int altitude); +int decode_mode_c(const std::string& msg); + + +namespace aero { + const double kts = 0.514444; // knot -> m/s + const double ft = 0.3048; // ft -> m + const double fpm = 0.00508; // ft/min -> m/s + const double inch = 0.0254; // inch -> m + const double sqft = 0.09290304; // 1 square foot + const double nm = 1852; // nautical mile -> m + const double lbs = 0.453592; // pound -> kg + const double g0 = 9.80665; // m/s², Sea level gravity constant + const double R = 287.05287; // m²/(s² x K), gas constant, sea level ISA + const double p0 = 101325; // Pa, air pressure, sea level ISA + const double rho0 = 1.225; // kg/m³, air density, sea level ISA + const double T0 = 288.15; // K, temperature, sea level ISA + const double gamma = 1.40; // cp/cv for air + const double gamma1 = 0.2; // (gamma-1)/2 for air + const double gamma2 = 3.5; // gamma/(gamma-1) for air + const double beta = -0.0065; // [K/m] ISA temp gradient below tropopause + const double r_earth = 6371000; // m, average earth radius + const double a0 = 340.293988; // m/s, sea level speed of sound ISA + struct Atmos { + double p; + double rho; + double T; + }; + + Atmos atmos(double H); + double temperature(double H); + double pressure(double H); + double density(double H); + double vsound(double H); + double distance(double lat1, double lon1, double lat2, double lon2, double H = 0); + double bearing(double lat1, double lon1, double lat2, double lon2); + double tas2mach(double Vtas, double H); + double mach2tas(double Mach, double H); + double eas2tas(double Veas, double H); + double tas2eas(double Vtas, double H); + double cas2tas(double Vcas, double H); + double tas2cas(double Vtas, double H); + double mach2cas(double Mach, double H); + double cas2mach(double Vcas, double H); +} +/* +------------------------------------------ + BDS 0,5 + ADS-B TC=9-18 + Airborne position +------------------------------------------ +*/ +double decode_altitude_12_meter(const std::string& msg_bin, Altitude_type altitude_type); +std::string encode_altitude_code_12(double height, Altitude_type altitude_type); +Vortex_Type parse_vortex_type(const std::string& msg); + + +// ------------------------------------------ +// BDS 0,6 +// ADS-B TC=5-8 +// Surface movement +// ------------------------------------------ + +Ground_Speed surface_velocity(const std::string& msg, Ground_Speed_Type st); +double decode_movement_7(const std::string& code); +std::string encode_movement_7(double movement); +double decode_ground_track_7(const std::string& code); +std::string encode_ground_track_7(double degree); + +// ------------------------------------------ +// BDS 0,8 +// ADS-B TC=1-4 +// Aircraft identification and category +// ------------------------------------------ +int category(const std::string& msg); +void set_category(std::string& msg, int ca); +void set_call_sign(std::string& msg, const std::string& call_sign); +std::string get_call_sign_code(const std::string& call_sign); +std::string get_call_sign(const std::string& msg_bin); + +// ------------------------------------------ +// BDS 0,9 +// ADS-B TC=19 +// Aircraft Airborne velocity +// ------------------------------------------ +Air_Speed airborne_velocity(const std::string& msg); +double altitude_diff(const std::string& msg); +std::string vertical_rate_code_10(double vertical_rate); +double decode_vertical_rate_10(std::string vertical_rate); + +std::string GNSS_barometric_altitudes_difference_8(double altitude_difference); +std::string encode_velocity_22(Ground_Speed_Type subType, double track_angle, double ground_speed); +std::string encode_magnetic_heading_11(double heading); +std::string encode_airspeed_10(Air_Speed_Code_Type subtype, double airspeed); + +// ------------------------------------------ +// BDS 1,0 +// Data link capability report +// ------------------------------------------ +bool is10(const std::string& msg); +int ovc10(const std::string& msg); +// ------------------------------------------ +// BDS 1,7 +// Common usage GICB capability report +// ------------------------------------------ +std::vector cap17(const std::string& msg); +bool is17(const std::string& msg); +// ------------------------------------------ +// BDS 2,0 +// Aircraft identification +// ------------------------------------------ +bool is20(const std::string& msg); +std::string cs20(const std::string& msg); +// ------------------------------------------ +// BDS 3,0 +// ACAS active resolution advisory +// ------------------------------------------ +bool is30(const std::string& msg); +// ------------------------------------------ +// BDS 4,0 +// Selected vertical intention +// ------------------------------------------ +bool is40(const std::string& msg); +RET selalt40mcp(const std::string& msg); +RET selalt40fms(const std::string& msg); +RET p40baro(const std::string& msg); +RET alt40mcp(const std::string& msg); +RET alt40fms(const std::string& msg); + +// ------------------------------------------ +// BDS 4,4 +// Meteorological routine air report +// ------------------------------------------ +bool is44(const std::string& msg); +struct Wind { + int wind_speed; + double direction; +}; +RET wind44(const std::string& msg); +struct Temperature { + double temperature; + double temperature_alternative; +}; +Temperature temp44(const std::string& msg); +int p44(const std::string& msg); +double hum44(const std::string& msg); +int turb44(const std::string& msg); +// ------------------------------------------ +// BDS 4,5 +// Meteorological hazard report +// ------------------------------------------ +bool is45(const std::string& msg); +int turb45(const std::string& msg); +int ws45(const std::string& msg); +int mb45(const std::string& msg); +int ic45(const std::string& msg); +int wv45(const std::string& msg); +double temp45(const std::string& msg); +int p45(const std::string& msg); +int rh45(const std::string& msg); +// ------------------------------------------ +// BDS 5,0 +// Track and turn report +// ------------------------------------------ +double roll50(const std::string& msg); +double trk50(const std::string& msg); +double gs50(const std::string& msg); +double rtrk50(const std::string& msg); +double tas50(const std::string& msg); +bool is50(const std::string& msg); +// ------------------------------------------ +// BDS 5,3 +// Air-referenced state vector +// ------------------------------------------ +bool is53(const std::string& msg); +double hdg53(const std::string& msg); +int ias53(const std::string& msg); +double mach53(const std::string& msg); +double tas53(const std::string& msg); +int vr53(const std::string& msg); +// ------------------------------------------ +// BDS 6,0 +// Heading and speed report +// ------------------------------------------ +bool is60(const std::string& msg); +double hdg60(const std::string& msg); +int ias60(const std::string& msg); +double mach60(const std::string& msg); +int vr60baro(const std::string& msg); +int vr60ins(const std::string& msg); + +// ------------------------------------------ +// BDS 6,1 +// ADS-B TC=28 +// Aircraft Airborne status +// ------------------------------------------ +bool is_emergency(const std::string& msg); +int emergency_state(const std::string& msg); +std::string emergency_squawk(const std::string& msg); +// ------------------------------------------ +// BDS 6,2 +// ADS-B TC=29 +// Target State and Status BDS Code 6,2 +// ------------------------------------------ +std::tuple selected_altitude(const std::string& msg); +std::tuple target_altitude(const std::string& msg); +Vertical_Horizontal_Type vertical_mode(const std::string& msg); +Vertical_Horizontal_Type horizontal_mode(const std::string& msg); +double selected_heading(const std::string& msg); +std::tuple target_angle(const std::string& msg); +double baro_pressure_setting(const std::string& msg); +bool autopilot(const std::string& msg); +bool vnav_mode(const std::string& msg); +bool altitude_hold_mode(const std::string& msg); +bool approach_mode(const std::string& msg); +bool lnav_mode(const std::string& msg); +bool tcas_operational(const std::string& msg); +bool tcas_ra(const std::string& msg); +Emergency_Type emergency_status(const std::string& msg); +// ------------------------------------------ +// BDS +// ------------------------------------------ +std::string is50or60(const std::string& msg, double spd_ref, double trk_ref, double alt_ref); +std::string infer(const std::string& msg_bin, bool mrar = false); +std::vector infer2(const std::string& msg, bool mrar = false); +// ADS-B + +//Air_Speed velocity(const std::string& msg, bool source = false); +// std::tuple speed_heading(const std::string& msg); +int oe_flag(const std::string& msg); +std::tuple fly_state(const std::string& msg); +std::tuple dr(const std::string& msg); +std::tuple um(const std::string& msg); + +// All call Reply +std::string interrogator_allCall(const std::string& msg); +std::string AA_allCall(const std::string& msg); + +std::string tell(const std::string& msg); + + +} \ No newline at end of file diff --git a/SSR/py_Mode_S/LICENSE b/SSR/py_Mode_S/LICENSE new file mode 100644 index 0000000..30ace6a --- /dev/null +++ b/SSR/py_Mode_S/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/SSR/py_Mode_S/acas.cpp b/SSR/py_Mode_S/acas.cpp new file mode 100644 index 0000000..bbcb716 --- /dev/null +++ b/SSR/py_Mode_S/acas.cpp @@ -0,0 +1,5 @@ +//""" +//Decoding Air-Air Surveillance (ACAS) DF=0/16 +// +//[To be implemented] +//""" diff --git a/SSR/py_Mode_S/ads_b.cpp b/SSR/py_Mode_S/ads_b.cpp new file mode 100644 index 0000000..1cc1e74 --- /dev/null +++ b/SSR/py_Mode_S/ads_b.cpp @@ -0,0 +1,146 @@ +#include "../CPR/CPR.h" +#include "Common.h" +//- bds05: ``airborne_position()``, ``airborne_position_with_ref()``, +//``altitude()`` +//- bds06: ``surface_position()``, ``surface_position_with_ref()``, +//``surface_velocity()`` +//- bds08: ``category()``, ``callsign()`` +//- bds09: ``airborne_velocity()``, ``altitude_diff()`` + +using namespace Psc; + +namespace SSR { + +double decode_altitude_12_meter(const std::string& msg_bin, Altitude_type altitude_type) { + if (altitude_type == Altitude_type::Baro) { + // The first bit will tell us which category of altitude encoding + if (msg_bin[7] == '1') { + // Baro altitude for < 50175 feet + std::bitset<11> h_bits(msg_bin.substr(0, 7) + msg_bin.substr(8)); + double height = (h_bits.to_ulong() * 25) - 1000; + return height * aero::ft; // Convert to meters + } else { + // Baro altitude for >= 50175 feet + std::bitset<11> h_bits(grayToBinary(msg_bin.substr(0, 7) + msg_bin.substr(8))); + double height = (h_bits.to_ulong() * 100) + 50175; + return height * aero::ft; // Convert to meters + } + } else if (altitude_type == Altitude_type::GNSS) { + // GNSS altitude is stored as binary (assuming it's in meters already) + std::bitset<12> h_bits(msg_bin); + double height = h_bits.to_ulong(); // GNSS altitude in meters + return height; + } else { + CE(true, "Unknown altitude type") + return -1; + } +} + + +std::string encode_altitude_code_12(double height, Altitude_type altitude_type) { + if(altitude_type == Altitude_type::Baro) { + if(height < 50175) { + std::bitset<11> h(static_cast((height + 1000) / 25)); + std::string bits = h.to_string(); + return bits.substr(0, 7) + "1" + bits.substr(7); + } + std::bitset<11> h(static_cast((height - 50175) / 100)); + std::string bits = h.to_string(); + bits = binaryToGray(bits); + return bits.substr(0, 7) + "0" + bits.substr(7); + } + if(altitude_type == Altitude_type::GNSS) { + return to_bin<12>(height * aero::ft); + } + return {12, '0'}; +} + + + +Vortex_Type parse_vortex_type(const std::string& msg) { + int ca = SSR::category(msg); + int tc = SSR::type_code(msg); + return map2[{tc, ca}]; +} + + +/** + * @brief 计算速度、航向和垂直速度(可处理空中或地面消息)。 + * + * @param msg 112位二进制字符串 + * @param source 是否在返回值中包含方向和垂直速度的来源。 + * 默认为 False。 + * 如果设置为 True,函数将返回六个值而不是四个。 + * + * @return int, float, int, string, [string], [string]: + * - 速度(节,kt) + * - 角度(度),地面航迹或航向 + * - 垂直速度(英尺/分钟,ft/min) + * - 速度类型('GS' 表示地速,'AS' 表示空速) + * - [可选] 方向来源('TRUE_NORTH' 或 'MAGNETIC_NORTH') + * - [可选] 垂直速度来源('BARO' 或 'GNSS') + * + * 对于地面消息,垂直速度及其来源设置为 None。 + */ +// Air_Speed velocity(const std::string& msg, bool source) { +// int tc = type_code(msg); +// if (tc >= 5 && tc <= 8) +// { +// return surface_velocity(msg); +// } else if (tc == 19) +// { +// return airborne_velocity(msg); +// } else +// { +// CE(true, "incorrect or inconsistent message types, expecting 4 speed_heading(const std::string& msg) { +// // 调用 velocity 函数并解码消息 +// auto decoded = velocity(msg); +// +// +// // 解码结果 +// double spd = decoded.speed; +// double trk_or_hdg = decoded.angle; +// +// // 返回速度和地面航迹或航向 +// return std::make_tuple(spd, trk_or_hdg); +// } + +/** + * @brief 检查奇/偶标志。第54位,0表示偶数帧,1表示奇数帧。 + * + * @param msg 112位二进制字符串 + * + * @return 0 或 1,分别表示偶数或奇数帧。 + */ +int oe_flag(const std::string& msg) { + // Assume hex2bin is a function that converts hex string to binary string + return static_cast(msg[53] - '0'); // Convert char to int +} + +/** + * @brief 获取ADS-B版本。 + * + * @param msg 112位二进制字符串,TC = 31。 + * + * @return 版本号。 + */ +int version(const std::string& msg) { + // Assume typecode is a function that retrieves the type code + int tc = type_code(msg); + CE(tc != 31, msg + ": Not a status operation message, expecting TC = 31") + int version = bin2(msg.substr(72, 3)); // Get bits 72 to 74 + return version; +} +} \ No newline at end of file diff --git a/SSR/py_Mode_S/allcall.cpp b/SSR/py_Mode_S/allcall.cpp new file mode 100644 index 0000000..6b3efce --- /dev/null +++ b/SSR/py_Mode_S/allcall.cpp @@ -0,0 +1,44 @@ +#include "Common.h" + + +namespace SSR { + +// Decode all-call reply messages, with downlink format 11 + + +/** + * @brief 解码询问器标识符代码。 + * + * @param msg 56位二进制消息字符串 + * + * @return 询问器标识符代码。 + * + * @note CRC余数包含CL和IC字段。前三位是CL字段,最后四位是IC字段。 + */ +std::string interrogator_allCall(const std::string& msg) { + auto df = SSR::decode_df(msg); + CE(df != Downlink_Format::All_Call_Reply_11, "DF error should: Downlink_Type::All_Call_Reply , but got " + Psc::to_string(df)) + // 解码 interrogator identifier code + // 计算 CRC 余数,包含 CL 和 IC 字段 + // 前三位是 CL 字段,最后四位是 IC 字段 + int remainder = crc(msg); + std::string IC; + if (remainder > 79) + { + IC = "corrupt IC"; + } else if (remainder < 16) + { + IC = "II" + std::to_string(remainder); + } else + { + IC = "SI" + std::to_string(remainder - 16); + } + return IC; +} + +std::string AA_allCall(const std::string& msg) { + auto df = SSR::decode_df(msg); + CE(df != Downlink_Format::All_Call_Reply_11, "DF error should: Downlink_Type::All_Call_Reply , but got " + Psc::to_string(df)) + return bin2hex(msg.substr(9, 24)); +} +} \ No newline at end of file diff --git a/SSR/py_Mode_S/bds.cpp b/SSR/py_Mode_S/bds.cpp new file mode 100644 index 0000000..8a7d23d --- /dev/null +++ b/SSR/py_Mode_S/bds.cpp @@ -0,0 +1,2241 @@ +#include "./Common.h" +#include "SSR/CPR/CPR.h" +#undef max +#undef min +using namespace Psc; + + + +namespace SSR { + + +static constexpr double cpr_divisor = 131072.0; +static constexpr double PI = 3.14159265358979323846; +/* +------------------------------------------ + BDS 0,5 + ADS-B TC=9-18 + Airborne position +------------------------------------------ +*/ + + +/** + * @brief 解码飞机高度。 + * + * @param msg 112位二进制字符串。 + * + * @return 高度,单位为英尺。 + */ + +// df 17 tc 9-18 tc=20-22 Airborne position 报文 +// int altitude(const std::string& msg) { +// int tc = type_code(msg); +// CE(!tc || tc < 9 || tc == 19 || tc > 22, msg + ": Not an airborne position message") +// std::string mb = msg.substr(32); +// std::string altbin = mb.substr(8, 12); // 12 bits from index 8 to 20 +// if (tc < 19) +// { +// std::string altcode = altbin.substr(0, 6) + "0" + altbin.substr(6); +// int alt = altitude13_ft(altcode); +// if (alt != -999999) +// { +// return alt; +// } +// CE(true, "alt" + std::to_string(alt) + ": Not an airborne position message") +// return -1; +// } +// return static_cast(bin2(altbin) * 3.28084); // convert to feet +// } + + +// ------------------------------------------ +// BDS 0,6 +// ADS-B TC=5-8 +// Surface movement +// ------------------------------------------ + + + +// Ground_Speed surface_velocity(const std::string& msg, Ground_Speed_Type st) { +// double we_speed = 0.000001; +// int we_speed_code = bin2(msg.substr(46, 10)); +// if (we_speed_code != 0) { +// int sign = (msg.at(45) == '0') ? 1 : -1 ; +// if (st == Ground_Speed_Type::Ground_Normal) { +// we_speed = we_speed_code - 1; +// } else { +// we_speed = 4 * (we_speed_code - 1); +// } +// we_speed *= sign; +// } +// double sn_speed = 0.000001; +// int sn_speed_code = bin2(msg.substr(57, 10)); +// if (sn_speed_code != 0) { +// int sign = (msg.at(56) == '0') ? 1 : -1 ; +// if (st == Ground_Speed_Type::Ground_Normal) { +// sn_speed = sn_speed_code - 1; +// } else { +// sn_speed = 4 * (sn_speed_code - 1); +// } +// sn_speed *= sign; +// } +// double heading_rad = atan2(sn_speed, we_speed); // 弧度 +// double heading_deg = heading_rad * 180.0 / M_PI; // 转换为度数 +// if (heading_deg < 0) { +// heading_deg += 360.0; +// } +// double speed = sqrt(we_speed * we_speed + sn_speed * sn_speed); +// Ground_Speed ret; +// ret.angle = heading_deg; +// ret.speed = speed; +// return ret; +// } +Ground_Speed surface_velocity(const std::string& msg, Ground_Speed_Type st) { + double we_speed = 0.000001; + int we_speed_code = bin2(msg.substr(46, 10)); + if (we_speed_code != 0) { + int sign = (msg.at(45) == '0') ? 1 : -1 ; + if (st == Ground_Speed_Type::Ground_Normal) { + we_speed = we_speed_code - 1; + } else { + we_speed = 4 * (we_speed_code - 1); + } + we_speed *= sign; + } + double sn_speed = 0.000001; + int sn_speed_code = bin2(msg.substr(57, 10)); + if (sn_speed_code != 0) { + int sign = (msg.at(56) == '0') ? 1 : -1 ; + if (st == Ground_Speed_Type::Ground_Normal) { + sn_speed = sn_speed_code - 1; + } else { + sn_speed = 4 * (sn_speed_code - 1); + } + sn_speed *= sign; + } + + // 以正北为起点,计算方向 + double heading_rad = atan2(we_speed, sn_speed); // 弧度 + double heading_deg = heading_rad * 180.0 / M_PI; // 转换为度数 + if (heading_deg < 0) { + heading_deg += 360.0; + } + double speed = sqrt(we_speed * we_speed + sn_speed * sn_speed); + + Ground_Speed ret; + ret.angle = heading_deg; + ret.speed = speed; + return ret; +} + + +double decode_ground_track_7(const std::string& code) { + auto encodedSpeed = bin2(code); + return encodedSpeed / 360.0 * 128.0; +} + + +double decode_movement_7(const std::string& code) { + int encodedSpeed = bin2(code); + + double decodedSpeed = -1; // -1 表示速度不可用 + + if (encodedSpeed == 0) { + decodedSpeed = -1; // 无速度信息 + } + else if (encodedSpeed == 1) { + decodedSpeed = 0.125; // 停止,速度为 0.125 kt + } + else if (encodedSpeed >= 2 && encodedSpeed <= 8) { + decodedSpeed = 0.125 * (encodedSpeed - 1); // 步长为 0.125 kt + } + else if (encodedSpeed >= 9 && encodedSpeed <= 12) { + decodedSpeed = 1.0 + 0.25 * (encodedSpeed - 8); // 步长为 0.25 kt + } + else if (encodedSpeed >= 13 && encodedSpeed <= 38) { + decodedSpeed = 2.0 + 0.5 * (encodedSpeed - 12); // 步长为 0.5 kt + } + else if (encodedSpeed >= 39 && encodedSpeed <= 93) { + decodedSpeed = 15.0 + (encodedSpeed - 38); // 步长为 1 kt + } + else if (encodedSpeed >= 94 && encodedSpeed <= 108) { + decodedSpeed = 70.0 + 2.0 * (encodedSpeed - 93); // 步长为 2 kt + } + else if (encodedSpeed >= 109 && encodedSpeed <= 123) { + decodedSpeed = 100.0 + 5.0 * (encodedSpeed - 108); // 步长为 5 kt + } + else if (encodedSpeed == 124) { + decodedSpeed = 175.0; // 或者某种其他大于 175 kt 的速度 + } + else if (encodedSpeed >= 125 && encodedSpeed <= 127) { + decodedSpeed = -1; // 保留值,不使用 + } + + return decodedSpeed; +} +std::string encode_movement_7(double v) { + unsigned long long movement_code; + if (v < 0) { + // 负速度不合逻辑,这里处理成0 + movement_code = 0; + } else if (v < 0.125) { + movement_code = 1; + } else if (v < 1) { + movement_code = 2 + static_cast((v - 0.125) / 0.125); + } else if (v < 2) { + movement_code = 9 + static_cast((v - 1) / 0.25); + } else if (v < 15) { + movement_code = 13 + static_cast((v - 2) / 0.5); + } else if (v < 70) { + movement_code = 39 + static_cast((v - 15) / 1); + } else if (v < 100) { + movement_code = 94 + static_cast((v - 70) / 2); + } else if (v < 175) { + movement_code = 109 + static_cast((v - 100) / 5); + } else { + movement_code = 124; + } + return to_bin<7>(movement_code); +} + +std::string encode_ground_track_7(double degree) { + return to_bin<7>(degree * 128.0/360.0); +} + +// ------------------------------------------ +// BDS 0,8 +// ADS-B TC=1-4 +// Aircraft identification and category +// ------------------------------------------ + +/** + * @brief 获取飞机类别编号。 + * + * @param msg 112位二进制字符串。 + * + * @return 飞机类别编号。 + */ +int category(const std::string& msg) { + int tc = type_code(msg); + CE(!tc || tc < 1 || tc > 4, bin2hex(msg) + ": Not an identification message") + std::string msgbin = msg; + std::string mebin = msgbin.substr(32, 55); // 55 bits from index 32 to 87 + return bin2(mebin.substr(5, 3)); // 3 bits from index 5 to 8 +} + +/** + * @brief 设置消息的分类代码。 + * + * 该函数将给定的分类代码 (CA) 设置到指定的 112 位二进制消息字符串中。 + * 分类代码由 3 位二进制组成,位于消息的第 38 到 40 位之间。 + * 如果分类代码不在 0 到 7 的范围内(即 CA 的有效值),函数将抛出异常。 + * + * @param msg 输入和输出的 112 位二进制字符串,代表 ADS-B 消息,该消息将在函数内部被修改以 + * 包含新的分类代码。 + * @param ca 要设置的分类代码,范围应为 0 到 7。 + * + * @throw std::out_of_range 如果 CA 超出有效范围(0 到 7)。 + */ +void set_category(std::string& msg, int ca) { + if (ca < 0 || ca > 7) { + throw std::out_of_range("CA must be in the range of 0 to 7"); + } + msg.replace(37, 3, std::bitset<3>(ca).to_string()); +} + + + + + + +std::string get_call_sign(const std::string& msg_bin) { + // int tc = type_code(msg); + // CE(!tc || tc < 1 || tc > 4, + "call_sign : Not an identification message " + bin2hex(msg)) + const std::string chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######"; + // const std::string& msgbin = msg; + // std::string csbin = msgbin.substr(40, 56); // 56 bits from index 40 to 96 + std::string cs; + for (int i = 0; i < 8; ++i) + { + cs += chars[bin2(msg_bin.substr(32 + 8 + i * 6, 6))]; // 6 bits each + } + // Clean string, remove spaces and marks, if any. + cs.erase(std::remove(cs.begin(), cs.end(), '#'), cs.end()); + return cs; +} + +void set_call_sign(std::string& msg, const std::string& call_sign) { + msg.replace(40, 48, get_call_sign_code(call_sign)); +} + +std::string get_call_sign_code(const std::string& call_sign) { + std::string cs = call_sign; + cs.resize(8, '#'); // 如果呼号不足8个字符,用 '#' 填充 + std::string csbin; + const std::string chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"; + for (char c : cs) { + auto pos = chars.find(c); + if (pos != std::string::npos) { + // 将字符的索引转换为6位二进制 + csbin += std::bitset<6>(pos).to_string(); + } else { + throw std::runtime_error("Invalid character in call sign: " + std::string(1, c)); + } + } + return csbin; +} + + +// ------------------------------------------ +// BDS 0,9 +// ADS-B TC=19 +// Aircraft Airborne velocity +// ------------------------------------------ + + + +Air_Speed airborne_velocity(const std::string& msg) { + + CE(type_code(msg) != 19, msg + ": Not an airborne velocity message, expecting TC=19") + + std::string mb = msg.substr(32); + auto subtype = static_cast(bin2(mb.substr(5, 3))); + Air_Speed result; + result.speed_type = "GS"; // default value + + // std::cout << "14, == " << (bin2(mb.substr(14, 10)) == 0) << std::endl; + // std::cout << "25,10 == " << bin2(mb.substr(25, 10)) << std::endl; + // std::cout << "25,10 == " << (bin2(mb.substr(25, 10)) == 0) << std::endl; + // std::cout << "subtype = " << subtype << std::endl; + if (bin2(mb.substr(14, 10)) == 0 || bin2(mb.substr(25, 10)) == 0) + { + //std::cout << "1111111111111111122222222222" << subtype << std::endl; + return result; // Return early with defaults + } + + if (subtype == 1 || subtype == 2) + { + // std::cout << "11111111111111111" << subtype << std::endl; + auto v_ew = bin2(mb.substr(14, 10)); + auto v_ns = bin2(mb.substr(25, 10)); + // std::cout << "v_ew = " << v_ew << std::endl; + // std::cout << "v_ns = " << v_ns << std::endl; + if (v_ew != 0 && v_ns != 0) + { + int v_ew_sign = (mb[13] == '1') ? -1 : 1; + v_ew -= 1; // east-west velocity + if (subtype == 2) + { + // Supersonic + v_ew *= 4; + } + long long v_ns_sign = (mb[24] == '1') ? -1 : 1; + v_ns -= 1; // north-south velocity + if (subtype == 2) + { + // Supersonic + v_ns *= 4; + } + long long v_we = v_ew_sign * v_ew; + long long v_sn = v_ns_sign * v_ns; + // std::cout << "v_we == " << v_we << std::endl; + // std::cout << "v_sn == " << v_sn << std::endl; + result.speed = static_cast(std::sqrt(v_sn * v_sn + v_we * v_we)); // unit in kts + double trk = std::atan2(v_we, v_sn); + // std::cout << trk << std::endl; + trk = (trk * 180.0 / M_PI); // convert to degrees + trk = (trk >= 0) ? trk : trk + 360; // no negative val + result.angle = trk; + result.direction_source = "TRUE_NORTH"; + } + } else + { + if (mb[13] == '1') + { + result.angle = bin2(mb.substr(14, 10)) / 1024.0 * 360.0; + } + int raw_spd = bin2(mb.substr(25, 10)); + if (raw_spd != 0) + { + result.speed = raw_spd - 1; + } + if (subtype == 4 && raw_spd != 0) + { + result.speed = (raw_spd - 1) * 4; + } + result.speed_type = (mb[24] == '0') ? "IAS" : "TAS"; + result.direction_source = "MAGNETIC_NORTH"; + } + // Vertical rate processing + std::string vr_source = (mb[35] == '0') ? "GNSS" : "BARO"; + int vr_sign = (mb[36] == '1') ? -1 : 1; + int vr = bin2(mb.substr(37, 9)); // Assume 9 bits for vertical rate + // std::cout << "vr == " << vr << std::endl; + result.vertical_rate = (vr == 0) ? 0 : vr_sign * (vr - 1) * 64; // 直接赋值为 0 + result.vertical_rate_source = vr_source; + return result; +} + +/** + * @brief 解码GNSS与气压高度之间的差异。 + * + * @param msg 112位二进制字符串,TC=19。 + * + * @return 高度差,单位为英尺。如果返回负值,表示GNSS高度低于气压高度。 + */ +double altitude_diff(const std::string& msg) { + auto tc_opt = type_code(msg); + CE(!tc_opt || tc_opt != 19, msg + ": Not an airborne velocity message, expecting TC=19") + const std::string& msgbin = msg; + int sign = (msgbin[80] == '1') ? -1 : 1; + auto value = bin2(msgbin.substr(81, 7)); + CE(value == 0 || value == 127, "value error!") + return sign * (value - 1) * 25; // in ft +} +std::string vertical_rate_code_10(double vertical_rate) { + if (vertical_rate == 0) { + return "0000000000"; + } + int sign_bit = vertical_rate > 0 ? 0 : 1; + vertical_rate = std::abs(vertical_rate); + int decimal_value = static_cast(std::round(vertical_rate / 64)) + 1; + + if (decimal_value > 511) { + decimal_value = 511; // Maximum value for 9 bits is 511 + } + std::bitset<9> vr_bits(decimal_value); + return std::bitset<1>(sign_bit).to_string() + vr_bits.to_string(); +} + + +std::string GNSS_barometric_altitudes_difference_8(double altitude_difference) { + if (altitude_difference == 0) { + return "000000000"; // All zeros indicate no information + } + int sign_bit = (altitude_difference < 0) ? 1 : 0; // 1 for negative, 0 for positive + altitude_difference = std::abs(altitude_difference); + int decimal_value = static_cast(std::round(altitude_difference / 25)); + if (decimal_value > 127) { + decimal_value = 127; // Cap at the maximum value for 7 bits + } + std::bitset<7> magnitude_bits(decimal_value); + std::bitset<1> sign_bitset(sign_bit); + return sign_bitset.to_string() + magnitude_bits.to_string(); +} + +// Helper function to generate 10-bit velocity block from velocity component +int generate_velocity_component(Ground_Speed_Type subType, double velocity_component) { + // Special case: if velocity component is 0, return 0 (no velocity information) + if (velocity_component == 0) { + return 0; + } + + // For supersonic, divide velocity by 4 + double velocity = subType == Ground_Speed_Type::Ground_Supersonic ? velocity_component / 4.0 : velocity_component; + + // Round and ensure it's within the 10-bit range + int encoded_velocity = static_cast(std::round(std::abs(velocity))); + + // Ensure the value fits in a 10-bit block (0-1023) + if (encoded_velocity > 1023) { + encoded_velocity = 1023; // Cap at maximum value for 10 bits + } + + return encoded_velocity; +} + +std::string encode_velocity_22(Ground_Speed_Type subType, double track_angle, double ground_speed) { + + double V_x = ground_speed * std::cos(track_angle * PI / 180.0); // West-East component + double V_y = ground_speed * std::sin(track_angle * PI / 180.0); // South-North component + + + // Step 2: Determine sign bits + int Sew = (V_x >= 0) ? 0 : 1; // 0 for East, 1 for West + int Sns = (V_y >= 0) ? 0 : 1; // 0 for North, 1 for South + + // Step 3: Generate the 10-bit velocity blocks + int Vew = generate_velocity_component(subType, V_x); // East-West component (10 bits) + int Vns = generate_velocity_component(subType, V_y); // North-South component (10 bits) + + // Step 4: Create the final encoded result as a binary string + std::bitset<10> Vew_bits(Vew); // 10-bit binary for East-West velocity + std::bitset<10> Vns_bits(Vns); // 10-bit binary for North-South velocity + + // Convert sign bits and velocity bits into a string representation + std::string encoded_result = std::to_string(Sew) + "" + Vew_bits.to_string() + "" + + std::to_string(Sns) + "" + Vns_bits.to_string(); + + return encoded_result; +} + +std::string encode_magnetic_heading_11(double heading) { + // Step 1: Set heading status (SH) + + + // Step 2: Encode the magnetic heading (HDG) + // Ensure the heading is within the range of 0 to 360 + if (heading < 0 || heading >= 360) { + std::cerr << "Invalid heading value. It must be in the range of 0 to 360 degrees." << std::endl; + return ""; + } + + // Calculate the decimal value based on LSB (360/1024 degrees) + int encoded_heading = static_cast(std::round(heading * 1024 / 360)); + + // Ensure the value is within 10-bit range (0-1023) + if (encoded_heading > 1023) { + std::cerr << "Encoded heading value out of range." << std::endl; + return ""; + } + + // Convert the decimal value to 10-bit binary + std::bitset<10> HDG_bits(encoded_heading); + return "1" + HDG_bits.to_string(); +} + +std::string encode_airspeed_10(Air_Speed_Code_Type subtype, double airspeed) { + // Step 2: Encode the airspeed (AS) + int encoded_airspeed = 0; + if (subtype == Air_Speed_Code_Type::Air_Normal) { + // Sub-type 1: Speed = Decimal value - 1 + encoded_airspeed = static_cast(std::round(airspeed)) + 1; + } else if (subtype == Air_Speed_Code_Type::Air_Supersonic) { + // Sub-type 2: Speed = 4 x (Decimal value - 1) + encoded_airspeed = static_cast(std::round(airspeed / 4)) + 1; + } else { + std::cerr << "Invalid subtype. Must be 1 (subsonic) or 2 (supersonic)." << std::endl; + return ""; + } + + // Ensure the encoded airspeed is within 10-bit range (0-1023) + if (encoded_airspeed < 0 || encoded_airspeed > 1023) { + std::cerr << "Encoded airspeed value out of range." << std::endl; + return ""; + } + return std::bitset<10>(encoded_airspeed).to_string(); +} + +// ------------------------------------------ +// BDS 1,0 +// Data link capability report +// ------------------------------------------ + + +/** + * @brief 检查消息是否可能是BDS代码1,0。 + * + * @param msg 112位二进制字符串。 + * + * @return 如果是BDS代码1,0则返回 true,否则返回 false。 + */ +bool is10(const std::string& msg) { + if (allzeros(msg)) + { + return false; + } + std::string d = databin(msg); + // first 8 bits must be 0x10 + if (d.substr(0, 8) != "00010000") + { + return false; + } + // bit 10 to 14 are reserved + if (bin2(d.substr(9, 5)) != 0) + { + return false; + } + // overlay capability conflict + if (d[14] == '1' && bin2(d.substr(16, 7)) < 5) + { + return false; + } + if (d[14] == '0' && bin2(d.substr(16, 7)) > 4) + { + return false; + } + return true; +} + +/** + * @brief 返回覆盖控制能力。 + * + * @param msg 112位二进制字符串。 + * + * @return 整数:指示应答机是否具备OVC能力。 + */ +int ovc10(const std::string& msg) { + std::string d = databin(msg); + return static_cast(d[14] - '0'); +} +// ------------------------------------------ +// BDS 1,7 +// Common usage GICB capability report +// ------------------------------------------ + +/** + * @brief 返回覆盖控制能力。 + * + * @param msg 112位二进制字符串。 + * + * @return 整数:指示应答机是否具备OVC能力。 + */ +bool is17(const std::string& msg) { + if (allzeros(msg)) + { + return false; + } + std::string d = databin(msg); + if (!allzeros(d.substr(24, 32))) + { + return false; + } + auto caps = cap17(msg); + // at least you can respond who you are + if (std::find(caps.begin(), caps.end(), "BDS20") == caps.end()) + { + return false; + } + return true; +} + + +/** + * @brief 从BDS 1,7消息中提取能力。 + * + * @param msg 112位二进制字符串。 + * + * @return 支持的BDS代码列表。 + */ +std::vector cap17(const std::string& msg) { + std::vector allbds = { + "05", + "06", + "07", + "08", + "09", + "0A", + "20", + "21", + "40", + "41", + "42", + "43", + "44", + "45", + "48", + "50", + "51", + "52", + "53", + "54", + "55", + "56", + "5F", + "60" + }; + std::string d = databin(msg); + std::vector capacity; + for (size_t i = 0; i < 24; ++i) + { + if (d[i] == '1') + { + capacity.push_back("BDS" + allbds[i]); + } + } + return capacity; +} + +// ------------------------------------------ +// BDS 2,0 +// Aircraft identification +// ------------------------------------------ + +/** + * @brief 检查消息是否可能是BDS代码2,0。 + * + * @param msg 112位二进制字符串。 + * + * @return 如果是BDS代码2,0则返回 true,否则返回 false。 + */ +bool is20(const std::string& msg) { + if (allzeros(msg)) + { + return false; + } + std::string d = databin(msg); + if (d.substr(0, 8) != "00100000") + { + return false; + } + // allow empty callsign + if (bin2(d.substr(8, 48)) == 0) + { + return true; + } + if (cs20(msg).find('#') != std::string::npos) + { + return false; + } + return true; +} + +/** + * @brief 获取飞机呼号。 + * + * @param msg 112位二进制字符串。 + * + * @return 呼号,最多 8 个字符。 + */ +std::string cs20(const std::string& msg) { + const std::string chars = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ#####_###############0123456789######"; + std::string d = databin(msg); + std::string cs; + for (size_t i = 0; i < 8; ++i) + { + cs += chars[bin2(d.substr(8 + i * 6, 6))]; + } + return cs; +} + +// ------------------------------------------ +// BDS 3,0 +// ACAS active resolution advisory +// ------------------------------------------ + +/** + * @brief 检查消息是否可能是BDS代码3,0。 + * + * @param msg 112位二进制字符串。 + * + * @return 如果是BDS代码3,0则返回 true,否则返回 false。 + */ +bool is30(const std::string& msg) { + if (allzeros(msg)) + { + return false; + } + std::string d = databin(msg); + if (d.substr(0, 8) != "00110000") + { + return false; + } + // threat type 3 not assigned + if (d.substr(28, 2) == "11") + { + return false; + } + // reserved for ACAS III, in far future + if (bin2(d.substr(15, 7)) >= 48) + { + return false; + } + return true; +} +// ------------------------------------------ +// BDS 4,0 +// Selected vertical intention +// ------------------------------------------ + +/** + * @brief 检查消息是否可能是BDS代码4,0。 + * + * @param msg 112位二进制字符串。 + * + * @return 如果是BDS代码4,0则返回 true,否则返回 false。 + */ +bool is40(const std::string& msg) { + if (allzeros(msg)) + { + return false; + } + std::string d = databin(msg); + // status bit 1, 14, and 27 + if (wrongstatus(d, 1, 2, 13) || wrongstatus(d, 14, 15, 26) || wrongstatus(d, 27, 28, 39) || + wrongstatus(d, 48, 49, 51) || wrongstatus(d, 54, 55, 56)) + { + return false; + } + // bits 40-47 and 52-53 shall all be zero + if (bin2(d.substr(39, 8)) != 0 || bin2(d.substr(51, 2)) != 0) + { + return false; + } + return true; +} +/** + * @brief 获取选定的高度,来自MCP/FCU。 + * + * @param msg 112位二进制字符串。 + * + * @return 高度,单位为英尺。 + */ +RET selalt40mcp(const std::string& msg) { + std::string d = databin(msg); + if(d[0] == '0') {return std::nullopt;} + int alt = bin2(d.substr(1, 12)) * 16; // ft + return alt; +} + + + + +/** + * @brief 获取选定的高度,来自FMS。 + * + * @param msg 112位二进制字符串。 + * + * @return 高度,单位为英尺。 + */ +RET selalt40fms(const std::string& msg) { + std::string d = databin(msg); + if(d[0] == '0') {return std::nullopt;} + int alt = bin2(d.substr(14, 12)) * 16; // ft + return alt; +} + +/** + * @brief 获取气压设置。 + * + * @param msg 112位二进制字符串。 + * + * @return 气压,单位为毫巴(millibar)。 + */ +RET p40baro(const std::string& msg) { + std::string d = databin(msg); + if(d[26] == '0') {return std::nullopt;} + double p = bin2(d.substr(27, 12)) * 0.1 + 800; // millibar + return p; +} + + +RET alt40mcp(const std::string& msg) { + // Deprecated warning + return selalt40mcp(msg); +} + +RET alt40fms(const std::string& msg) { + // Deprecated warning + return selalt40fms(msg); +} +// ------------------------------------------ +// BDS 4,4 +// Meteorological routine air report +// ------------------------------------------ +/** + * @brief 检查消息是否可能是BDS代码4,4。 + * + * 气象常规航空报告。 + * + * @param msg 112位二进制字符串。 + * + * @return 如果是BDS代码4,4则返回 true,否则返回 false。 + */ +bool is44(const std::string& msg) { + if (allzeros(msg)) + { + return false; + } + std::string d = databin(msg); + // status bit 5, 35, 47, 50 + if (wrongstatus(d, 5, 6, 23) || wrongstatus(d, 35, 36, 46) || wrongstatus(d, 47, 48, 49) || wrongstatus( + d, 50, 51, 56)) + { + return false; + } + // Bits 1-4 indicate source, values > 4 reserved + if (bin2(d.substr(0, 4)) > 4) + { + return false; + } + + auto w = wind44(msg); + if (w.has_value()) + { + auto wind = w.value(); + int vw = wind.wind_speed; + int dw = wind.direction; + if (vw > 250) + { + return false; + } + } + + + auto tem = temp44(msg); + + if (std::min(tem.temperature, tem.temperature_alternative) > 60 || std::max(tem.temperature, tem.temperature_alternative) < -80) + { + return false; + } + return true; +} + +/** + * @brief 获取风速和风向。 + * + * @param msg 112位二进制字符串。 + * + * @return (int, float): 风速(节,kt)、风向(度)。 + */ +RET wind44(const std::string& msg) { + std::string d = databin(msg); + int status = static_cast(d[4] - '0'); + if (status == 0) { + return std::nullopt; + } + int speed = bin2(d.substr(5, 9)); // knots + double direction = bin2(d.substr(14, 9)) * 180.0 / 256.0; // degrees + return Wind{speed, direction}; +} + + +/** + * @brief 获取静态空气温度。 + * + * @param msg 112位二进制字符串。 + * + * @return float, float: 温度和替代温度,单位为摄氏度(Celsius)。 + * 注意:返回两个值是由于ICAO 9871 (2008) 附录 A-67 中似乎存在的不一致性错误。 + */ +Temperature temp44(const std::string& msg) { + std::string d = databin(msg); + int sign = static_cast(d[23] - '0'); + int value = bin2(d.substr(24, 10)); + if (sign) + { + value -= 1024; + } + double temp = value * 0.25; // Celsius + double temp_alternative = value * 0.125; // Celsius + return {temp, temp_alternative}; +} + +/** + * @brief 获取静态压力。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 静态压力,单位为 hPa。 + */ +int p44(const std::string& msg) { + std::string d = databin(msg); + if(d[34] == '0') {return -1;} + int p = bin2(d.substr(35, 11)); // hPa + return p; +} + +/** + * @brief 获取湿度。 + * + * @param msg 112位二进制字符串。 + * + * @return float: 湿度百分比,范围为 [0 - 100] %。 + */ +double hum44(const std::string& msg) { + std::string d = databin(msg); + if(d[49] == '0') {return -1;} + double hm = bin2(d.substr(50, 6)) * 100.0 / 64.0; // % + return hm; +} + +/** + * @brief 获取湍流等级。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 湍流等级。0 = 无湍流 (NIL),1 = 轻度 (Light),2 = 中度 (Moderate),3 = 严重 (Severe)。 + */ +int turb44(const std::string& msg) { + std::string d = databin(msg); + if(d[46] == '0') {return -1;} + int turb = bin2(d.substr(47, 2)); + return turb; +} +// ------------------------------------------ +// BDS 4,5 +// Meteorological hazard report +// ------------------------------------------ +/** + * @brief 检查消息是否可能是BDS代码4,5。 + * + * 气象危害报告。 + * + * @param msg 112位二进制字符串。 + * + * @return 如果是BDS代码4,5则返回 true,否则返回 false。 + */ +bool is45(const std::string& msg) { + if (allzeros(msg)) + { + return false; + } + std::string d = databin(msg); + // status bit 1, 4, 7, 10, 13, 16, 27, 39 + if (wrongstatus(d, 1, 2, 3) || wrongstatus(d, 4, 5, 6) || wrongstatus(d, 7, 8, 9) || wrongstatus(d, 10, 11, 12) || + wrongstatus(d, 13, 14, 15) || wrongstatus(d, 16, 17, 26) || wrongstatus(d, 27, 28, 38) || wrongstatus( + d, 39, 40, 51)) + { + return false; + } + // reserved + if (bin2(d.substr(51, 5)) != 0) + { + return false; + } + auto temp = temp45(msg); + if (temp && (temp > 60 || temp < -80)) + { + return false; + } + return true; +} + + +/** + * @brief 获取湍流等级。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 湍流等级。0 = 无湍流 (NIL),1 = 轻度 (Light),2 = 中度 (Moderate),3 = 严重 (Severe)。 + */ +int turb45(const std::string& msg) { + std::string d = databin(msg); + if(d[0] == '0') {return -1;} + int turb = bin2(d.substr(1, 2)); + return turb; +} + +/** + * @brief 获取风切变等级。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 风切变等级。0 = 无风切变 (NIL),1 = 轻度 (Light),2 = 中度 (Moderate),3 = 严重 (Severe)。 + */ +int ws45(const std::string& msg) { + std::string d = databin(msg); + if(d[3] == '0') {return -1;} + int ws = bin2(d.substr(4, 2)); + return ws; +} + +/** + * @brief 获取风切变等级。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 风切变等级。0 = 无风切变 (NIL),1 = 轻度 (Light),2 = 中度 (Moderate),3 = 严重 (Severe)。 + */ +int mb45(const std::string& msg) { + std::string d = databin(msg); + if(d[0] == '0') {return -1;} + int mb = bin2(d.substr(7, 2)); + return mb; +} + +/** + * @brief 获取结冰等级。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 结冰等级。0 = 无结冰 (NIL),1 = 轻度 (Light),2 = 中度 (Moderate),3 = 严重 (Severe)。 + */ +int ic45(const std::string& msg) { + std::string d = databin(msg); + if (d[9] == '0') + { + return -1; + } + int ic = bin2(d.substr(10, 2)); + return ic; +} + +/** + * @brief 获取尾流涡旋等级。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 尾流涡旋等级。0 = 无尾流涡旋 (NIL),1 = 轻度 (Light),2 = 中度 (Moderate),3 = 严重 (Severe)。 + */ +int wv45(const std::string& msg) { + std::string d = databin(msg); + if (d[12] == '0') + { + return -1; + } + int ws = bin2(d.substr(13, 2)); + return ws; +} + +/** + * @brief 获取静态空气温度。 + * + * @param msg 112位二进制字符串。 + * + * @return float: 温度,单位为摄氏度(Celsius)。 + */ +double temp45(const std::string& msg) { + std::string d = databin(msg); + int sign = static_cast(d[16] - '0'); + int value = bin2(d.substr(17, 9)); + if (sign) + { + value -= 512; + } + double temp = value * 0.25; // Celsius + return temp; +} + +/** + * @brief 获取平均静态压力。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 静态压力,单位为 hPa。 + */ +int p45(const std::string& msg) { + std::string d = databin(msg); + if (d[26] == '0') + { + return -1; + } + int p = bin2(d.substr(27, 11)); // hPa + return p; +} + +/** + * @brief 获取无线电高度。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 无线电高度,单位为英尺(ft)。 + */ +int rh45(const std::string& msg) { + std::string d = databin(msg); + if (d[38] == '0') + { + return -1; + } + int rh = bin2(d.substr(39, 12)) * 16; // ft + return rh; +} +// ------------------------------------------ +// BDS 5,0 +// Track and turn report +// ------------------------------------------ +/** + * @brief 检查消息是否可能是BDS代码5,0(轨迹和转向报告)。 + * + * @param msg 112位二进制字符串。 + * + * @return bool: 如果是BDS代码5,0则返回 true,否则返回 false。 + */ +bool is50(const std::string& msg) { + if (allzeros(msg)) + { + return false; + } + std::string d = databin(msg); + // status bit 1, 12, 24, 35, 46 + if (wrongstatus(d, 1, 3, 11) || wrongstatus(d, 12, 13, 23) || wrongstatus(d, 24, 25, 34) || + wrongstatus(d, 35, 36, 45) || wrongstatus(d, 46, 47, 56)) + { + return false; + } + auto roll = roll50(msg); + if (std::abs(roll) > 50) + { + return false; + } + auto gs = gs50(msg); + if (gs != -1 && gs > 600) + { + return false; + } + auto tas = tas50(msg); + if (tas!=-1 && tas > 500) + { + return false; + } + if (gs!=-1 && std::abs(tas - gs) > 200) + { + return false; + } + return true; +} + +/** + * @brief 获取滚转角度,来自BDS 5,0消息。 + * + * @param msg 112位二进制字符串。 + * + * @return float: 角度(度)。 + * 负值表示左翼下沉,正值表示右翼下沉。 + */ +double roll50(const std::string& msg) { + std::string d = databin(msg); + if (d[0] == '0') + { + return -1; + } + int sign = static_cast(d[1] - '0'); // 1 -> left wing down + int value = bin2(d.substr(2, 9)); + if (sign) + { + value -= 512; + } + double angle = value * 45 / 256.0; // degrees + return angle; +} + +/** + * @brief 获取真航向角,来自BDS 5,0消息。 + * + * @param msg 112位二进制字符串。 + * + * @return float: 角度(度),相对于真北(范围从 0 到 360)。 + */ +double trk50(const std::string& msg) { + std::string d = databin(msg); + if (d[11] == '0') + { + return -1; + } + int sign = static_cast(d[12] - '0'); // 1 -> west + int value = bin2(d.substr(13, 10)); + if (sign) + { + value -= 1024; + } + double trk = value * 90 / 512.0; + // convert from [-180, 180] to [0, 360] + if (trk < 0) + { + trk = 360 + trk; + } + return trk; +} +/** + * @brief 获取地面速度,来自BDS 5,0消息。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 地面速度,单位为节(knots)。 + */ +double gs50(const std::string& msg) { + std::string d = databin(msg); + if (d[23] == '0') + { + return -1; + } + double spd = bin2(d.substr(24, 10)) * 2; // kts + return spd; +} +/** + * @brief 获取航向角变化率,来自BDS 5,0消息。 + * + * @param msg 112位二进制字符串。 + * + * @return float: 角度变化率(度/秒)。 + */ +double rtrk50(const std::string& msg) { + std::string d = databin(msg); + if (d[34] == '0' || d.substr(36, 9) == "111111111") + { + return -1; + } + int sign = static_cast(d[35] - '0'); // 1 -> negative value + int value = bin2(d.substr(36, 9)); + if (sign) + { + value -= 512; + } + double angle = value * 8 / 256.0; // degrees/sec + return angle; +} + +/** + * @brief 获取飞机的真实空速,来自BDS 5,0消息。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 真实空速,单位为节(knots)。 + */ +double tas50(const std::string& msg) { + std::string d = databin(msg); + if (d[45] == '0') { + return -1; + } + double tas = bin2(d.substr(46, 10)) * 2; // kts + return tas; +} + +// ------------------------------------------ +// BDS 5,3 +// Air-referenced state vector +// ------------------------------------------ +/** + * @brief 检查消息是否可能是BDS代码5,3(航空参考状态矢量)。 + * + * @param msg 112位二进制字符串。 + * + * @return bool: 如果是BDS代码5,3则返回 true,否则返回 false。 + */ +bool is53(const std::string& msg) { + if (allzeros(msg)) + { + return false; + } + std::string d = databin(msg); + // status bit 1, 13, 24, 34, 47 + if (wrongstatus(d, 1, 3, 12) || wrongstatus(d, 13, 14, 23) || wrongstatus(d, 24, 25, 33) || + wrongstatus(d, 34, 35, 46) || wrongstatus(d, 47, 49, 56)) + { + return false; + } + double ias = ias53(msg); + if (ias!=-1 && ias > 500) + { + return false; + } + double mach = mach53(msg); + if (mach!=-1 && mach > 1) + { + return false; + } + double tas = tas53(msg); + if (tas!=-1 && tas > 500) + { + return false; + } + int vr = vr53(msg); + if (vr!=-1 && abs(vr) > 8000) + { + return false; + } + return true; +} +/** + * @brief 获取磁航向角,来自BDS 5,3消息。 + * + * @param msg 112位二进制字符串。 + * + * @return float: 角度(度),相对于真北(范围从 0 到 360)。 + */ +double hdg53(const std::string& msg) { + std::string d = databin(msg); + if (d[0] == '0') + { + return -1; + } + int sign = d[1] - '0'; // 1 -> west + int value = bin2(d.substr(2, 10)); + if (sign) + { + value -= 1024; + } + double hdg = static_cast(value) * 90.0 / 512.0; // degree + // convert from [-180, 180] to [0, 360] + if (hdg < 0) + { + hdg += 360; + } + return hdg; +} + +/** + * @brief 获取指示空速,来自BDS 5,3消息。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 指示空速,单位为节(knots)。 + */ +int ias53(const std::string& msg) { + std::string d = databin(msg); + if (d[12] == '0') + { + return -1; + } + return bin2(d.substr(13, 10)); // knots +} + +/** + * @brief 获取MACH数,来自BDS 5,3消息。 + * + * @param msg 112位二进制字符串。 + * + * @return float: MACH数。 + */ +double mach53(const std::string& msg) { + std::string d = databin(msg); + if (d[23] == '0') + { + return -1; + } + return bin2(d.substr(24, 9)) * 0.008; +} + +/** + * @brief 获取飞机的真实空速,来自BDS 5,3消息。 + * + * @param msg 112位二进制字符串。 + * + * @return float: 真实空速,单位为节(knots)。 + */ +double tas53(const std::string& msg) { + std::string d = databin(msg); + if (d[33] == '0') + { + return -1; + } + return bin2(d.substr(34, 12)) * 0.5; // kts +} + +/** + * @brief 获取垂直速率。 + * + * @param msg 112位二进制字符串(BDS60)。 + * + * @return int: 垂直速率,单位为英尺/分钟。 + */ +int vr53(const std::string& msg) { + std::string d = databin(msg); + if (d[46] == '0') + { + return -1; + } + int sign = d[47] - '0'; // 1 -> negative value, two's complement + int value = bin2(d.substr(48, 8)); + if (value == 0 || value == 255) + { + // all zeros or all ones + return 0; + } + value = sign ? value - 256 : value; + return value * 64; // feet/min +} + +// ------------------------------------------ +// BDS 6,0 +// Heading and speed report +// ------------------------------------------ +/** + * @brief 检查消息是否可能是BDS代码6,0。 + * + * @param msg 112位二进制字符串。 + * + * @return bool: 如果是BDS代码6,0则返回true,否则返回false。 + */ +bool is60(const std::string& msg) { + if (allzeros(msg)) + { + return false; + } + std::string d = databin(msg); + // status bit 1, 13, 24, 35, 46 + if (wrongstatus(d, 1, 2, 12) || wrongstatus(d, 13, 14, 23) || wrongstatus(d, 24, 25, 34) || + wrongstatus(d, 35, 36, 45) || wrongstatus(d, 46, 47, 56)) + { + return false; + } + double ias = ias60(msg); + if (ias!=-1 && ias > 500) + { + return false; + } + double mach = mach60(msg); + if (mach!=-1 && mach > 1) + { + return false; + } + int vr_baro = vr60baro(msg); + if (vr_baro!=-1 && abs(vr_baro) > 6000) + { + return false; + } + int vr_ins = vr60ins(msg); + if (vr_ins!=-1 && abs(vr_ins) > 6000) + { + return false; + } + // additional check knowing altitude + if (mach!=-1 && ias && (decode_df(msg) == Downlink_Format::Comm_B_Altitude_Reply_20)) + { + int alt = altcode(msg); + if (alt) + { + double ias_ = aero::mach2cas(mach, alt * aero::ft) / aero::kts; + if (abs(ias - ias_) > 20) + { + return false; + } + } + } + return true; +} + + +/** + * @brief 获取飞机的磁航向。 + * + * @param msg 112位二进制字符串。 + * + * @return float: 磁航向角度(度),相对于磁北(范围从 0 到 360)。 + */ +double hdg60(const std::string& msg) { + std::string d = databin(msg); + if (d[0] == '0') { + return -1; + } + int sign = d[1] - '0'; // 1 -> west + int value = bin2(d.substr(2, 10)); + if (sign) + { + value -= 1024; + } + double hdg = static_cast(value) * 90.0f / 512.0f; // degree + // convert from [-180, 180] to [0, 360] + if (hdg < 0) + { + hdg += 360; + } + return hdg; +} + +/** + * @brief 获取指示空速。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 指示空速,单位为节(knots)。 + */ +int ias60(const std::string& msg) { + std::string d = databin(msg); + if (d[12] == '0') + { + return -1; + } + return bin2(d.substr(13, 10)); // kts +} + +/** + * @brief 获取飞机的MACH数。 + * + * @param msg 112位二进制字符串。 + * + * @return float: MACH数。 + */ +double mach60(const std::string& msg) { + std::string d = databin(msg); + if (d[23] == '0') + { + return -1; + } + return bin2(d.substr(24, 10)) * 2.048 / 512.0; +} + +/** + * @brief 从气压测量获取垂直速率,此值可能会非常嘈杂。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 垂直速率,单位为英尺/分钟。 + */ +int vr60baro(const std::string& msg) { + std::string d = databin(msg); + if (d[34] == '0') + { + return -1; + } + int sign = d[35] - '0'; // 1 -> negative value, two's complement + int value = bin2(d.substr(36, 9)); + if (value == 0 || value == 511) + { + // all zeros or all ones + return 0; + } + value = sign ? value - 512 : value; + return value * 32; // feet/min +} + +/** + * @brief 由机载设备(IRS,AHRS)测量的垂直速率。 + * + * @param msg 112位二进制字符串。 + * + * @return int: 垂直速率,单位为英尺/分钟。 + */ +int vr60ins(const std::string& msg) { + std::string d = databin(msg); + if (d[45] == '0') + { + return -1; + } + int sign = d[46] - '0'; // 1 -> negative value, two's complement + int value = bin2(d.substr(47, 9)); + if (value == 0 || value == 511) + { + // all zeros or all ones + return 0; + } + value = sign ? value - 512 : value; + return value * 32; // feet/min +} +// ------------------------------------------ +// BDS 6,1 +// ADS-B TC=28 +// Aircraft Airborne status +// ------------------------------------------ + + +/** + * @brief 检查飞机是否报告紧急情况。 + * + * 非紧急情况的子类型为零(无信息)或子类型为一且值为零(无紧急情况)。 + * 子类型 = 2 表示 ACAS RA 广播,参见 BDS 3,0。 + * + * @param msg 112位二进制消息字符串。 + * @return bool: 飞机是否已声明紧急情况。 + */ +bool is_emergency(const std::string& msg) { + CE(type_code(msg) != 28, msg + ": Not an airborne status message, expecting TC=28") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 3)); + CE(subtype == 2, msg + ": Emergency message is ACAS-RA, not implemented") + int emergencyState = bin2(mb.substr(8, 3)); + return (subtype == 1 && emergencyState == 1); +} + +/** + * @brief 解码飞机紧急状态。 + * + * 值 含义 + * --- ----------------------- + * 0 无紧急情况 + * 1 一般紧急情况 + * 2 救护/医疗 + * 3 燃料不足 + * 4 无通信 + * 5 非法通信 + * 6-7 保留 + * + * @param msg 112位二进制消息字符串。 + * @return int: 紧急状态。 + */ +int emergency_state(const std::string& msg) { + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 3)); + CE(subtype == 2, msg + ": Emergency message is ACAS-RA, not implemented") + return bin2(mb.substr(8, 3)); +} + +/** + * @brief 解码应答机代码。 + * + * 紧急值 1: 应答机代码 7700。 + * 紧急值 4: 应答机代码 7600。 + * 紧急值 5: 应答机代码 7500。 + * + * @param msg 112位二进制消息字符串。 + * @return string: 飞机应答机代码。 + */ +std::string emergency_squawk(const std::string& msg) { + CE(type_code(msg) != 28, msg + msg + ": Not an airborne status message, expecting TC=28") + auto msgbin = msg; + auto idcode = msgbin.substr(43, 13); // 13 bits Mode A ID code + return decode_squawk13(idcode); +} +// ------------------------------------------ +// BDS 6,2 +// ADS-B TC=29 +// Target State and Status +// ------------------------------------------ +/** + * @brief 解码选定高度。 + * + * @param msg 56位二进制消息字符串 + * @return int: 选定高度(英尺)。 + * @return string: 来源('MCP/FCU' 或 'FMS')。 + */ +std::tuple selected_altitude(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 0, + msg + + ": ADS-B version 1 target state and status message does not contain selected altitude, use target altitude instead") + int alt = bin2(mb.substr(9, 11)); + if (alt == 0) + { + return std::make_tuple(0.0, msg); + } + alt = (alt - 1) * 32; + std::string altSource = (mb[8] == '0') ? "MCP/FCU" : "FMS"; + return std::make_tuple(alt, altSource); +} + +/** + * @brief 解码目标高度。 + * + * @param msg 56位二进制消息字符串 + * @return int: 目标高度(英尺)。 + * @return string: 来源('MCP/FCU'、'保持模式' 或 'FMS/RNAV')。 + * @return string: 高度参考,可以是压力高度或气压修正高度('FL' 或 'MSL')。 + */ +std::tuple target_altitude(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 1, + msg + + ": ADS-B version 2 target state and status message does not contain target altitude, use selected altitude instead") + int altAvail = bin2(mb.substr(7, 2)); + if (altAvail == 0) + { + return std::make_tuple(-1, "N/A", ""); + } + std::string altSource; + if (altAvail == 1) + { + altSource = "MCP/FCU"; + } else if (altAvail == 2) + { + altSource = "Holding mode"; + } else + { + altSource = "FMS/RNAV"; + } + std::string altRef = (mb[9] == '0') ? "FL" : "MSL"; + int alt = -1000 + bin2(mb.substr(15, 10)) * 100; + return std::make_tuple(alt, altSource, altRef); +} + +/** + * @brief 解码垂直模式。 + * + * 值 含义 + * ----- ----------------------- + * 1 "获取"模式 + * 2 "捕获"或"维持"模式 + * 3 保留 + * + * @param msg 56位二进制消息字符串 + * @return int: 垂直模式。 + */ +Vertical_Horizontal_Type vertical_mode(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 1, + msg + + ": ADS-B version 2 target state and status message does not contain vertical mode, use vnav mode instead") + int verticalModeValue = bin2(mb.substr(13, 2)); + if (verticalModeValue == 0) + { + return Vertical_Horizontal_Type::Reserved; + } + return static_cast(verticalModeValue); +} + +/** + * @brief 解码水平模式。 + * + * 值 含义 + * ----- ----------------------- + * 1 "获取"模式 + * 2 "捕获"或"维持"模式 + * 3 保留 + * + * @param msg 56位二进制消息字符串 + * @return int: 水平模式。 + */ +Vertical_Horizontal_Type horizontal_mode(const std::string& msg) { + int tc = type_code(msg); + CE(tc, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 1, + msg + + ": ADS-B version 2 target state and status message does not contain horizontal mode, use lnav mode instead") + int horizontalModeValue = bin2(mb.substr(25, 2)); + if (horizontalModeValue == 0) + { + return Vertical_Horizontal_Type::Reserved; + } + return static_cast(horizontalModeValue); +} + +/** + * @brief 解码选定的航向。 + * + * @param msg 112位二进制消息字符串。 + * @return float: 选定的航向(度)。 + */ +double selected_heading(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 0, + msg + + ": ADS-B version 1 target state and status message does not contain selected heading, use target angle instead") + if (mb[29] == '0') + { + return -1; + } else + { + int hdg_sign = mb[30] - '0'; + double hdg = (hdg_sign + 1) * bin2(mb.substr(31, 8)) * (180.0 / 256.0); + return hdg; + } +} + +/** + * @brief 解码目标航向/航迹角。 + * + * @param msg 112位二进制消息字符串。 + * @return int: 目标角度(度)。 + * @return string: 角度类型('航向'或'航迹')。 + * @return string: 来源('MCP/FCU'、'自动驾驶模式'或'FMS/RNAV')。 + */ +std::tuple target_angle(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 1, + msg + + ": ADS-B version 2 target state and status message does not contain target angle, use selected heading instead") + int angle_avail = bin2(mb.substr(25, 2)); + if (angle_avail == 0) + { + return std::make_tuple(-1, "", "N/A"); + } else + { + int angle = bin2(mb.substr(27, 9)); + std::string angle_source; + if (angle_avail == 1) + { + angle_source = "MCP/FCU"; + } else if (angle_avail == 2) + { + angle_source = "Autopilot mode"; + } else + { + angle_source = "FMS/RNAV"; + } + std::string angle_type = (mb[36] == '0') ? "Heading" : "Track"; + return std::make_tuple(angle, angle_type, angle_source); + } +} + +/** + * @brief 解码气压设置。 + * + * @param msg 56位二进制消息字符串 + * @return float: 气压设置(毫巴)。 + */ +double baro_pressure_setting(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 0, + msg + + ": ADS-B version 1 target state and status message does not contain barometric pressure setting") + int baro = bin2(mb.substr(20, 9)); + if (baro == 0) + { + return -1; + } + return 800.0 + static_cast((baro - 1)) * 0.8; +} + +/** + * @brief 解码自动驾驶仪接通状态。 + * + * @param msg 56位二进制消息字符串 + * @return bool: 自动驾驶仪是否已接通。 + */ +bool autopilot(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 0, + msg + ": ADS-B version 1 target state and status message does not contain autopilot engagement") + if (mb[46] == '0') + { + return false; + } + bool engaged = (mb[47] == '1'); + return engaged; +} + +/** + * @brief 解码垂直导航(VNAV)模式。 + * + * @param msg 56位二进制消息字符串 + * @return bool: VNAV模式是否已接通。 + */ +bool vnav_mode(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 0, + msg + + ": ADS-B version 1 target state and status message does not contain vnav mode, use vertical mode instead") + if (mb[46] == '0') + { + return false; + } + bool engaged = (mb[48] == '1'); + return engaged; +} + +/** + * @brief 解码高度保持模式。 + * + * @param msg 56位二进制消息字符串 + * @return bool: 高度保持模式是否已接通。 + */ +bool altitude_hold_mode(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 0, + msg + ": ADS-B version 1 target state and status message does not contain altitude hold mode") + if (mb[46] == '0') + { + return false; + } + bool altHoldMode = (mb[49] == '1'); + return altHoldMode; +} + +/** + * @brief 解码进近模式。 + * + * @param msg 56位二进制消息字符串 + * @return bool 进近模式是否已接通。 + */ +bool approach_mode(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 0, + msg + ": ADS-B version 1 target state and status message does not contain approach mode") + if (mb[46] == '0') + { + return false; + } + bool appMode = (mb[51] == '1'); + return appMode; +} + +/** + * @brief 解码LNAV模式。 + * + * @param msg 112位二进制消息字符串 + * @return bool 如果LNAV模式已启用,则返回true;否则返回false + */ +bool lnav_mode(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 0, + msg + + ": ADS-B version 1 target state and status message does not contain lnav mode, use horizontal mode instead") + if (mb[46] == '0') + { + return false; + } + bool lnavMode = (mb[53] == '1'); + return lnavMode; +} + +/** + * @brief 解码TCAS/ACAS操作状态。 + * + * @param msg 112位二进制消息字符串 + * @return bool 如果TCAS/ACAS处于操作状态,则返回true;否则返回false + */ +bool tcas_operational(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + bool tcas; + if (subtype == 0) + { + tcas = (mb[51] == '0'); + } else + { + tcas = (mb[52] == '1'); + } + return tcas; +} +/** + * @brief 解码TCAS/ACAS解决咨询状态。 + * + * @param msg 112位二进制消息字符串 + * @return bool 如果TCAS/ACAS解决咨询处于激活状态,则返回true;否则返回false + */ +bool tcas_ra(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 1, msg + ": ADS-B version 2 target state and status message does not contain TCAS/ACAS RA") + return (mb[52] == '1'); +} + +/** + * @brief 解码飞机的紧急状态。 + * + * 根据输入的28字节十六进制消息字符串,解码出飞机的紧急状态。 + * + * 紧急状态值的意义如下: + * - 0: 无紧急状态 + * - 1: 一般紧急状态 + * - 2: 救生/医疗紧急状态 + * - 3: 最低油量 + * - 4: 无法通信 + * - 5: 非法干扰 + * - 6: 坠机 + * - 7: 保留 + * + * @param msg 112位二进制消息字符串 + * @return int 紧急状态值(0-7) + */ +Emergency_Type emergency_status(const std::string& msg) { + int tc = type_code(msg); + CE(tc != 29, msg + ": Not a target state and status message, expecting TC=29") + auto mb = msg.substr(32); + int subtype = bin2(mb.substr(5, 2)); + CE(subtype == 1, + msg + ": ADS-B version 2 target state and status message does not contain emergency status") + return static_cast(bin2(mb.substr(53, 3))); +} + + + + + +// init + +/** + * @brief 使用参考地速和航向来确定BDS50和BDS60。 + * + * @param msg 56位二进制消息字符串 + * @param spd_ref 参考速度(ADS-B地速),单位:节(kts) + * @param trk_ref 参考航向(ADS-B航向角),单位:度(deg) + * @param alt_ref 参考高度(ADS-B高度),单位:英尺(ft) + * + * @return 字符串或None:BDS版本,或可能的版本, + * 如果没有匹配的则返回None。 + */ +std::string is50or60(const std::string& msg, double spd_ref, double trk_ref, double alt_ref) { + static auto vxy = [](double v, double angle)->std::tuple{ + double vx = v * sin(angle * M_PI / 180.0); + double vy = v * cos(angle * M_PI / 180.0); + return {vx, vy}; + }; + if (!(is50(msg) && is60(msg))) + { + return ""; + } + auto h60 = hdg60(msg); + auto m60 = mach60(msg); + auto i60 = ias60(msg); + if (static_cast(m60) && i60) + { + double ias_ = aero::mach2cas(m60, alt_ref) / 1.94384; // Convert ft/s to knots + if (std::abs(i60 - ias_) > 20) + { + return "BDS50"; + } + } + if (!static_cast(h60) || !(static_cast(m60) && !i60)) + { + return "BDS50,BDS60"; + } + // Assume BDS50 + auto h50 = trk50(msg); + auto v50 = gs50(msg); + if (!static_cast(h50) || !static_cast(v50)) + { + return "BDS50,BDS60"; + } + auto XY5 = vxy(v50 * 1.94384, h50); // Convert knots to ft/s + auto XY6m = vxy(aero::mach2tas(m60, alt_ref), h60); + auto XY6i = vxy(aero::cas2tas(i60 * 1.94384, alt_ref), h60); + std::array, 3> X = {XY5, XY6m, XY6i}; + auto Mu = vxy(spd_ref * 1.94384, trk_ref); // Convert knots to ft/s + std::array dist{}; + for (size_t i = 0; i < X.size(); ++i) + { + dist[i] = std::sqrt( + std::pow(std::get<0>(X[i]) - std::get<0>(Mu), 2) + std::pow(std::get<1>(X[i]) - std::get<1>(Mu), 2)); + } + auto min_index = std::distance(dist.begin(), std::min_element(dist.begin(), dist.end())); + return (min_index == 0) ? "BDS50" : "BDS60"; // Adjust based on your logic +} + +/** + * @brief 估计消息的最可能BDS代码。 + * + * 根据提供的112位二进制字符串消息,以及是否推断MRAR(BDS 44)和MHR(BDS 45)的指示, + * 估计消息的最可能BDS代码版本。 + * + * @param msg_bin 112位二进制字符串 + * @param mrar 是否推断MRAR(BDS 44)和MHR(BDS 45),默认为False + * @return String or None: BDS版本,或可能的版本,如果没有匹配的则返回None + */ +std::string infer(const std::string& msg_bin, bool mrar) { + + auto df = SSR::decode_df(msg_bin); + if (allzeros(msg_bin)) + { + return "EMPTY"; + } + // For ADS-B / Mode-S extended squitter + if (df == Downlink_Format::Extended_Squitter_17) + { + auto tc = type_code(msg_bin); + if (!tc) + { + return ""; + } + if (tc >= 1 && tc <= 4) return "BDS08"; // identification and category + if (tc >= 5 && tc <= 8) return "BDS06"; // surface movement + if (tc >= 9 && tc <= 18) return "BDS05"; // airborne position, baro-alt + if (tc == 19) return "BDS09"; // airborne velocity + if (tc >= 20 && tc <= 22) return "BDS05"; // airborne position, gnss-alt + if (tc == 28) return "BDS61"; // aircraft status + if (tc == 29) return "BDS62"; // target state and status + if (tc == 31) return "BDS65"; // operational status + } + // For Comm-B replies + bool IS10 = is10(msg_bin); + bool IS17 = is17(msg_bin); + bool IS20 = is20(msg_bin); + bool IS30 = is30(msg_bin); + bool IS40 = is40(msg_bin); + bool IS50 = is50(msg_bin); + bool IS60 = is60(msg_bin); + bool IS44 = is44(msg_bin); + bool IS45 = is45(msg_bin); + std::vector bds_result; + if (mrar) + { + std::array allbds = { + "BDS10", + "BDS17", + "BDS20", + "BDS30", + "BDS40", + "BDS44", + "BDS45", + "BDS50", + "BDS60" + }; + bool mask[] = {IS10, IS17, IS20, IS30, IS40, IS44, IS45, IS50, IS60}; + for (size_t i = 0; i < 9; ++i) + { + if (mask[i]) + { + bds_result.push_back(allbds[i]); + } + } + } else + { + std::array allbds = {"BDS10", "BDS17", "BDS20", "BDS30", "BDS40", "BDS50", "BDS60"}; + bool mask[] = {IS10, IS17, IS20, IS30, IS40, IS50, IS60}; + for (size_t i = 0; i < 7; ++i) + { + if (mask[i]) + { + bds_result.push_back(allbds[i]); + } + } + } + std::sort(bds_result.begin(), bds_result.end()); + std::string result = std::accumulate(bds_result.begin(), bds_result.end(), std::string(), + [](const std::string& a, const std::string& b) { + return a + (a.empty() ? "" : ",") + b; + }); + return result.empty() ? "" : std::string(result); +} +std::vector infer2(const std::string& msg, bool mrar) { + auto df = SSR::decode_df(msg); + if (allzeros(msg)) + { + return {}; + } + // For ADS-B / Mode-S extended squitter + if (df == Downlink_Format::Extended_Squitter_17) + { + auto tc = type_code(msg); + if (!tc) + { + return {}; + } + if (tc >= 1 && tc <= 4) return {"08"}; // identification and category + if (tc >= 5 && tc <= 8) return {"06"}; // surface movement + if (tc >= 9 && tc <= 18) return {"05"}; // airborne position, baro-alt + if (tc == 19) return {"09"}; // airborne velocity + if (tc >= 20 && tc <= 22) return {"05"}; // airborne position, gnss-alt + if (tc == 28) return {"61"}; // aircraft status + if (tc == 29) return {"62"}; // target state and status + if (tc == 31) return {"65"}; // operational status + } + // For Comm-B replies + bool IS10 = is10(msg); + bool IS17 = is17(msg); + bool IS20 = is20(msg); + bool IS30 = is30(msg); + bool IS40 = is40(msg); + bool IS50 = is50(msg); + bool IS60 = is60(msg); + bool IS44 = is44(msg); + bool IS45 = is45(msg); + std::vector bds_result; + if (mrar) + { + std::array allbds = { "10", "17", "20", "30", "40", "44", "45", "50", "60" + }; + bool mask[] = {IS10, IS17, IS20, IS30, IS40, IS44, IS45, IS50, IS60}; + for (size_t i = 0; i < 9; ++i) + { + if (mask[i]) + { + bds_result.push_back(allbds[i]); + } + } + } else + { + std::array allbds = {"10", "17", "20", "30", "40", "50", "60"}; + bool mask[] = {IS10, IS17, IS20, IS30, IS40, IS50, IS60}; + for (size_t i = 0; i < 7; ++i) + { + if (mask[i]) + { + bds_result.push_back(allbds[i]); + } + } + } + return bds_result; +} + + +} \ No newline at end of file diff --git a/SSR/py_Mode_S/commb.cpp b/SSR/py_Mode_S/commb.cpp new file mode 100644 index 0000000..be4b369 --- /dev/null +++ b/SSR/py_Mode_S/commb.cpp @@ -0,0 +1,25 @@ +//"""Comm-B module. +// +//The Comm-B module imports all functions from the following modules: +// +//ELS - elementary surveillance +// +//- pyModeS.decoder.bds.bds10 +//- pyModeS.decoder.bds.bds17 +//- pyModeS.decoder.bds.bds20 +//- pyModeS.decoder.bds.bds30 +// +//EHS - enhanced surveillance +// +//- pyModeS.decoder.bds.bds40 +//- pyModeS.decoder.bds.bds50 +//- pyModeS.decoder.bds.bds60 +// +//MRAR and MHR +// +//- pyModeS.decoder.bds.bds44 +//- pyModeS.decoder.bds.bds45 +// +//""" +// +//# ELS - elementary surveillance diff --git a/SSR/py_Mode_S/ehs.cpp b/SSR/py_Mode_S/ehs.cpp new file mode 100644 index 0000000..9ea2a2d --- /dev/null +++ b/SSR/py_Mode_S/ehs.cpp @@ -0,0 +1,10 @@ +//"""EHS Wrapper. +// +//``pyModeS.ehs`` is deprecated, please use ``pyModeS.commb`` instead. +// +//The EHS wrapper imports all functions from the following modules: +// - pyModeS.decoder.bds.bds40 +// - pyModeS.decoder.bds.bds50 +// - pyModeS.decoder.bds.bds60 +// +//""" \ No newline at end of file diff --git a/SSR/py_Mode_S/els.cpp b/SSR/py_Mode_S/els.cpp new file mode 100644 index 0000000..5477915 --- /dev/null +++ b/SSR/py_Mode_S/els.cpp @@ -0,0 +1,10 @@ +//"""EHS Wrapper. +// +//``pyModeS.ehs`` is deprecated, please use ``pyModeS.commb`` instead. +// +//The EHS wrapper imports all functions from the following modules: +// - pyModeS.decoder.bds.bds40 +// - pyModeS.decoder.bds.bds50 +// - pyModeS.decoder.bds.bds60 +// +//""" diff --git a/SSR/py_Mode_S/surv.cpp b/SSR/py_Mode_S/surv.cpp new file mode 100644 index 0000000..910cd23 --- /dev/null +++ b/SSR/py_Mode_S/surv.cpp @@ -0,0 +1,2 @@ +#include "Common.h" + diff --git a/SSR/py_Mode_S/uncertainty.cpp b/SSR/py_Mode_S/uncertainty.cpp new file mode 100644 index 0000000..e69de29 diff --git a/SSR/py_Mode_S/uplink.cpp b/SSR/py_Mode_S/uplink.cpp new file mode 100644 index 0000000..e69de29 diff --git a/SSR/uncertainty.h b/SSR/uncertainty.h new file mode 100644 index 0000000..c118d60 --- /dev/null +++ b/SSR/uncertainty.h @@ -0,0 +1,157 @@ +#pragma once + + +#include "global.h" + +namespace SSR { +// Indicator Acronym Version Values +// Navigation uncertainty category–position NUCp 0 0–9 +// Navigation uncertainty category–rate (velocity) NUCr 0 0–4 +// Navigation accuracy category–position NACp 1, 2 0–11 +// Navigation accuracy category–velocity NACv 1, 2 0–4 +// Navigation integrity category NIC 1, 2 0–11 +// Surveillance integrity level SIL 1, 2 0–3 + + +enum class NUCp_0 +{ // TC NUCp HPL Rc/μ Rc/v + _00 = 0, // 0 0 N/A N/A N/A + _05 = 5, // 5 9 <7.5 m <3 m N/A + _06 = 6, // 6 8 <25 m <10 m N/A + _07 = 7, // 7 7 <0.1 NM (185 m) <0.05 NM (93 m) N/A + _08 = 8, // 8 6 >0.1 NM (185 m) >0.05 NM (93 m) N/A + _09 = 9, // 9 9 <7.5 m <3 m N/A + _10 = 10, // 10 8 <25 m <10 m N/A + _11 = 11, // 11 7 <0.1 NM (185 m) <0.05 NM (93 m) N/A + _12 = 12, // 12 6 <0.2 NM (370 m) <0.1 NM (185 m) N/A + _13 = 13, // 13 5 <0.5 NM (926 m) <0.25 NM (463 m) N/A + _14 = 14, // 14 4 <1 NM (1852 m) <0.5 NM (926 m) N/A + _15 = 15, // 15 3 <2 NM (3704 m) <1 NM (1852 m) N/A + _16 = 16, // 16 2 <10 NM (18520 m) <5 NM (9260 m) N/A + _17 = 17, // 17 1 <20 NM (37040 m) <10 NM (18520 m) N/A + _18 = 18, // 18 0 >20 NM (37040 m) >10 NM (18520 m) N/A + _20 = 20, // 20 9 <7.5 m <3 m <4 m + _21 = 21, // 21 8 <25 m <10 m <15 m + _22 = 22 // 22 0 >25 m >10 m >15 m +}; + + +enum class NUCv_0 +{ // NUCv HVE (95%) VVE (95%) + _0 = 0, // 0 N/A N/A + _1 = 1, // 1 <10 m/s <15.2 m/s (50 fps) + _2 = 2, // 2 <3 m/s <4.5 m/s (15 fps) + _3 = 3, // 3 <1 m/s <1.5 m/s (5 fps) + _4 = 4 // 4 <0.3 m/s <0.46 m/s (1.5 fps) +}; +// NUCv: TC=19 43-45 + + +// NIC is designed to replace NUCp from version 0 +enum class NIC_1 +{ + // TC NICs NIC RC VPL + _00,// 0 N/A N/A N/A N/A + _01,// 5 0 11 <7.5 m N/A + _02,// 6 0 10 <25 m N/A + _03,// 7 1 9 <75 m N/A + _04,// 7 0 8 <0.1 NM (185 m) N/A + _05,// 8 0 0 >0.1 NM or Unknown N/A + _06,// 9 0 11 <7.5 m <11 m + _07,// 10 0 10 <25 m <37.5 m + _08,// 11 1 9 <75 m <112 m + _09,// 11 0 8 <0.1 NM (185 m) N/A + _10,// 12 0 7 <0.2 NM (370 m) N/A + _11,// 13 0 6 <0.5 NM (926 m) N/A + _12,// 13 1 6 <0.6 NM (1111 m) N/A + _13,// 14 0 5 <1.0 NM (1852 m) N/A + _14,// 15 0 4 <2 NM (3704 m) N/A + _15,// 16 1 3 <4 NM (7408 m) N/A + _16,// 16 0 2 <8 NM (14.8 km) N/A + _17,// 17 0 1 <20 NM (37.0 km) N/A + _18,// 18 0 0 >20 NM or Unknown N/A + _19,// 20 0 11 <7.5 m <11 m + _20,// 21 0 10 <25 m <37.5 m + _21,// 22 0 0 >25 m >112 m +}; +// NICs TC=31 76 + + + +enum class NIC_2 { + // TC NICa NICb NICc NIC Rc + _00, // 5 0 N/A 0 11 <7.5 m + _01, // 6 0 N/A 0 10 <25 m + _02, // 7 1 N/A 0 9 <75 m + _03, // 7 0 N/A 0 8 <0.1 NM (185 m) + _04, // 8 1 N/A 1 7 <0.2 NM (370 m) + _05, // 8 1 N/A 0 6 <0.3 NM (556 m) + _06, // 8 0 N/A 1 6 <0.6 NM (1111 m) + _07, // 8 0 N/A 0 0 >0.6 NM or unknown + _08, // 9 0 0 N/A 11 <7.5 m + _09, // 10 0 0 N/A 10 <25 m + _10, // 11 1 1 N/A 9 <75 m + _11, // 11 0 0 N/A 8 <0.1 NM (185 m) + _12, // 12 0 0 N/A 7 <0.2 NM (370 m) + _13, // 13 0 1 N/A 6 <0.3 NM (556 m) + _14, // 13 0 0 N/A 6 <0.5 NM (926 m) + _15, // 13 1 1 N/A 6 <0.6 NM (1111 m) + _16, // 14 0 0 N/A 5 <1.0 NM (1852 m) + _17, // 15 0 0 N/A 4 <2 NM (3704 m) + _18, // 16 1 1 N/A 3 <4 NM (7408 m) + _19, // 16 0 0 N/A 2 <8 NM (14.8 km) + _20, // 17 0 0 N/A 1 <20 NM (37.0 km) + _21, // 18 0 0 N/A 0 >20 NM or unknown + _22, // 20 N/A N/A N/A 11 <7.5 m + _23, // 21 N/A N/A N/A 10 <25 m + _24, // 22 N/A N/A N/A 0 >25 m +}; +// NICa TC=31 76 +// NICb TC=9–18 40 +// NICc TC=31 52 + + + +enum class NACp_12 { + // NACp EPU (or HFOM) VEPU (or VFOM) + _11 = 11, // 11 <3 m <4 m + _10 = 10, // 10 <10 m <15 m + _09 = 9, // 9 <30 m <45 m + _08 = 8, // 8 <0.05 NM (93 m) N/A + _07 = 7, // 7 <0.1 NM (185 m) N/A + _06 = 6, // 6 <0.3 NM (556 m) N/A + _05 = 5, // 5 <0.5 NM (926 m) N/A + _04 = 4, // 4 <1.0 NM (1852 m) N/A + _03 = 3, // 3 <2 NM (3704 m) N/A + _02 = 2, // 2 <4 NM (7408 m) N/A + _01 = 0, // 1 <10 NM (18520 m) N/A + _00 = 0 // 0 >10 NM or Unknown N/A +}; +// NACp: TC=31 77–80 + + +enum class NACv_12 { + // NACv HFOMr VFOMr + _0, // 0 N/A N/A + _1, // 1 <10 m/s <15.2 m/s (50 fps) + _2, // 2 <3 m/s <4.5 m/s (15 fps) + _3, // 3 <1 m/s <1.5 m/s (5 fps) + _4 // 4 <0.3 m/s <0.46 m/s (1.5 fps) +}; +// NACv: TC=19 43-45 + +enum class SIL_1 { + // SIL P-RC P-VPL + _0, // 0 unknown unknown + _1, // 1 <10^-3 <10^-3 + _2, // 2 <10^-5 <10^-5 + _3, // 3 <10^-7 <10^-7 +}; +// SIL: TC=31 83–84 + + +// SILs: TC=31 87 +// SILs=0: probability is per hour based +// SILs=1: probability is per sample based +} + diff --git a/main.cmake b/main.cmake new file mode 100644 index 0000000..3fe8277 --- /dev/null +++ b/main.cmake @@ -0,0 +1,16 @@ +_create(interface SSR) + +set(Core Core_Static) + + +_attach_include_dir("SSR" "${CMAKE_CURRENT_LIST_DIR}") +_attach_source_dir("SSR" "${CMAKE_CURRENT_LIST_DIR}/SSR") +_attach_source_dir("SSR" "${CMAKE_CURRENT_LIST_DIR}/test") +_depend(SSR ${Core}) + + + +_create(dll readsb2) +_attach_include_dir(readsb2 "${CMAKE_CURRENT_LIST_DIR}") +_attach_source_dir(readsb2 "${CMAKE_CURRENT_LIST_DIR}/readsb2") + diff --git a/pyModeS/LICENSE b/pyModeS/LICENSE new file mode 100644 index 0000000..30ace6a --- /dev/null +++ b/pyModeS/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/readsb/comm_b.cpp b/readsb/comm_b.cpp new file mode 100644 index 0000000..8e0b8f8 --- /dev/null +++ b/readsb/comm_b.cpp @@ -0,0 +1,962 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// comm_b.c: Comm-B message decoding +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2017 FlightAware, LLC +// Copyright (c) 2017 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "readsb.h" +#include "ais_charset.h" +#define M_PI 3.14159265358979323846 + +typedef int (*CommBDecoderFn)(struct modesMessage *, bool); + +static int decodeEmptyResponse(struct modesMessage *mm, bool store); +static int decodeBDS10(struct modesMessage *mm, bool store); +static int decodeBDS17(struct modesMessage *mm, bool store); +static int decodeBDS20(struct modesMessage *mm, bool store); +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, + &decodeBDS10, + &decodeBDS20, + &decodeBDS30, + &decodeBDS17, + &decodeBDS40, + &decodeBDS50, + &decodeBDS60, + &decodeBDS44 +}; + +void decodeCommB(struct modesMessage *mm) { + mm->commb_format = COMMB_UNKNOWN; + + // If DR or UM are set, this message is _probably_ noise + // as nothing really seems to use the multisite broadcast stuff? + // Also skip anything that had errors corrected + if (mm->DR != 0 || mm->UM != 0 || mm->correctedbits > 0) { + return; + } + + // This is a bit hairy as we don't know what the requested register was + int bestScore = 0; + CommBDecoderFn bestDecoder = NULL; + int ambiguous = 0; + + for (unsigned i = 0; i < (sizeof (comm_b_decoders) / sizeof (comm_b_decoders[0])); ++i) { + int score = comm_b_decoders[i](mm, false); + if (score > bestScore) { + bestScore = score; + bestDecoder = comm_b_decoders[i]; + ambiguous = 0; + } else if (score == bestScore) { + ambiguous = 1; + } + } + + if (bestDecoder) { + if (ambiguous) { + mm->commb_format = COMMB_AMBIGUOUS; + } else { + // decode it + bestDecoder(mm, true); + } + } +} + +static int decodeEmptyResponse(struct modesMessage *mm, bool store) { + for (unsigned i = 0; i < 7; ++i) { + if (mm->MB[i] != 0) { + return 0; + } + } + + if (store) { + mm->commb_format = COMMB_EMPTY_RESPONSE; + } + + return 56; +} + +// BDS1,0 Datalink capabilities + +static int decodeBDS10(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + // BDS identifier + if (msg[0] != 0x10) { + return 0; + } + + // Reserved bits + if (getbits(msg, 10, 14) != 0) { + return 0; + } + + // Looks plausible. + + if (store) { + mm->commb_format = COMMB_DATALINK_CAPS; + } + + return 56; +} + +// BDS1,7 Common usage GICB capability report + +static int decodeBDS17(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + // reserved bits + if (getbits(msg, 25, 56) != 0) { + return 0; + } + + int score = 0; + if (getbit(msg, 7)) { + score += 1; // 2,0 aircraft identification + } else { + // BDS2,0 is on almost everything + score -= 2; + } + + // unlikely bits + if (getbit(msg, 10)) { // 4,1 next waypoint identifier + score -= 2; + } + if (getbit(msg, 11)) { // 4,2 next waypoint position + score -= 2; + } + if (getbit(msg, 12)) { // 4,3 next waypoint information + score -= 2; + } + if (getbit(msg, 13)) { // 4,4 meterological routine report + score -= 2; + } + if (getbit(msg, 14)) { // 4,4 meterological hazard report + score -= 2; + } + if (getbit(msg, 20)) { // 5,4 waypoint 1 + score -= 2; + } + if (getbit(msg, 21)) { // 5,5 waypoint 2 + score -= 2; + } + if (getbit(msg, 22)) { // 5,6 waypoint 3 + score -= 2; + } + + if (getbit(msg, 1) && getbit(msg, 2) && getbit(msg, 3) && getbit(msg, 4) && getbit(msg, 5)) { + // looks like ES capable + score += 5; + if (getbit(msg, 6)) { + // ES EDI + score += 1; + } + } else if (!getbit(msg, 1) && !getbit(msg, 2) && !getbit(msg, 3) && !getbit(msg, 4) && !getbit(msg, 5) && !getbit(msg, 6)) { + // not ES capable + score += 1; + } else { + // partial ES support, unlikely + score -= 12; + } + + if (getbit(msg, 16) && getbit(msg, 24)) { + // track/turn, heading/speed + score += 2; + if (getbit(msg, 9)) { + // vertical intent + score += 1; + } + } else if (!getbit(msg, 16) && !getbit(msg, 24) && !getbit(msg, 9)) { + // neither + score += 1; + } else { + // unlikely + score -= 6; + } + + if (store) { + mm->commb_format = COMMB_GICB_CAPS; + } + + return score; +} + +// BDS2,0 Aircraft identification + +static int decodeBDS20(struct modesMessage *mm, bool store) { + char callsign[sizeof(mm->callsign)]; + unsigned char *msg = mm->MB; + + // BDS identifier + if (msg[0] != 0x20) { + return 0; + } + + callsign[0] = ais_charset[getbits(msg, 9, 14)]; + callsign[1] = ais_charset[getbits(msg, 15, 20)]; + callsign[2] = ais_charset[getbits(msg, 21, 26)]; + callsign[3] = ais_charset[getbits(msg, 27, 32)]; + callsign[4] = ais_charset[getbits(msg, 33, 38)]; + callsign[5] = ais_charset[getbits(msg, 39, 44)]; + callsign[6] = ais_charset[getbits(msg, 45, 50)]; + callsign[7] = ais_charset[getbits(msg, 51, 56)]; + callsign[8] = 0; + + // score based on number of valid characters + int score = 8; + int 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 + score += 6; + } else { + // Invalid + if (Modes.debug_callsign) { + fprintf(stderr, "%06x %8s (len: %d)\n", mm->addr, callsign, (int) strlen(callsign)); + } + return 0; + } + } + + if (store) { + mm->commb_format = COMMB_AIRCRAFT_IDENT; + if (valid) { + memcpy(mm->callsign, callsign, sizeof(mm->callsign)); + mm->callsign_valid = 1; + } + } + + return score; +} + +// check if the payload is a valid ACAS payload +// https://mode-s.org/decode/book-the_1090mhz_riddle-junzi_sun.pdf +int checkAcasRaValid(unsigned char *msg, struct modesMessage *mm, int debug) { + + bool ara = getbit(msg, 9); + bool rat = getbit(msg, 27); + bool mte = getbit(msg, 28); + + // not a valid RA if none of the bits are set + if (!ara && !rat && !mte) + return 0; + + if (getbits(msg, 9, 28) == 0) + return 0; // these are the bits that contain the info, all zero it's not an RA + if (getbit(msg, 23) && getbit(msg, 24)) + return 0; // complementary bits, both set is invalid (above / below) + if (getbit(msg, 25) && getbit(msg, 26)) + return 0; // complementary bits, both set is invalid (left / right) + + if (mm->msgtype == 16) { + if (getbits(msg, 29, 56) != 0) + return 0; // in DF16 messages msg bits 29 to 56 are reserved + + return 1; + } + + // some extra restrictions for DF != 16 below + + if (getbit(msg, 25) || getbit(msg, 26)) + return 0; // left / right isn't used, require zero + + // for COMMB messages let's check if the thread indicator makes sense + + int tti = getbits(msg, 29, 30); + // thread type indicator + // 00 No identity data in threat identity data + if (tti == 0) { + if (getbits(msg, 31, 56) != 0) + return 0; + + return 1; + } + // When the threat type indicator is 01 , MB bits 31-54 contain the 24-bit Mode S transponder address and the last two bits are set to zero. + if (tti == 1) { + if (getbits(msg, 55, 56) != 0) + return 0; + + uint32_t addr = getbits(msg, 31, 54); + if (icaoFilterTest(addr)) + return 1; + + return debug; // zero unless debug + } + + // 10 Threat identity data contains altitude, range, and bearing + if (tti == 2) { + if (mm->metype == 28)// allow for DF17 + return 1; + // hard to tell if used and separate from garbage, don't mark valid for the moment + return debug; // zero unless debug + } + + // 11 Not assigned + if (tti == 3) { + return 0; + } + + return 0; +} + +// BDS3,0 ACAS RA + +static int decodeBDS30(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + // BDS identifier + if (msg[0] != 0x30) { + return 0; + } + + if (store) { + mm->commb_format = COMMB_ACAS_RA; + mm->acas_ra_valid = 1; + } + + // just accept it. + return 56; +} + +// BDS4,0 Selected vertical intention + +static int decodeBDS40(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + unsigned mcp_valid = getbit(msg, 1); + unsigned mcp_raw = getbits(msg, 2, 13); + unsigned fms_valid = getbit(msg, 14); + unsigned fms_raw = getbits(msg, 15, 26); + unsigned baro_valid = getbit(msg, 27); + unsigned baro_raw = getbits(msg, 28, 39); + unsigned reserved_1 = getbits(msg, 40, 47); + unsigned mode_valid = getbit(msg, 48); + unsigned mode_raw = getbits(msg, 49, 51); + unsigned reserved_2 = getbits(msg, 52, 53); + unsigned source_valid = getbit(msg, 54); + unsigned source_raw = getbits(msg, 55, 56); + + if (!mcp_valid && !fms_valid && !baro_valid && !mode_valid && !source_valid) { + return 0; + } + + int score = 0; + + unsigned mcp_alt = 0; + if (mcp_valid && mcp_raw != 0) { + mcp_alt = mcp_raw * 16; + if (mcp_alt >= 1000 && mcp_alt <= 50000) { + score += 13; + } else { + // unlikely altitude + return 0; + } + } else if (!mcp_valid && mcp_raw == 0) { + score += 1; + } else { + return 0; + } + + unsigned fms_alt = 0; + if (fms_valid && fms_raw != 0) { + fms_alt = fms_raw * 16; + if (fms_alt >= 1000 && fms_alt <= 50000) { + score += 13; + } else { + // unlikely altitude + return 0; + } + } else if (!fms_valid && fms_raw == 0) { + score += 1; + } else { + return 0; + } + + float baro_setting = 0; + if (baro_valid && baro_raw != 0) { + baro_setting = 800 + baro_raw * 0.1; + if (baro_setting >= 900 && baro_setting <= 1100) { + score += 13; + } else { + // unlikely pressure setting + return 0; + } + } else if (!baro_valid && baro_raw == 0) { + score += 1; + } else { + return 0; + } + + if (reserved_1 != 0) { + return 0; + } + + if (mode_valid) { + score += 4; + } else if (!mode_valid && mode_raw == 0) { + score += 1; + } else { + return 0; + } + + if (reserved_2 != 0) { + return 0; + } + + if (source_valid) { + score += 3; + } else if (!source_valid && source_raw == 0) { + score += 1; + } else { + return 0; + } + + // small penalty for inconsistent data + if (mcp_valid && fms_valid && mcp_alt != fms_alt) { + score -= 4; + } + + if (mcp_valid) { + unsigned remainder = mcp_alt % 500; + if (!(remainder < 16 || remainder > 484)) { + // mcp altitude is not a multiple of 500 + score -= 4; + } + } + + if (fms_valid) { + unsigned remainder = fms_alt % 500; + if (!(remainder < 16 || remainder > 484)) { + // fms altitude is not a multiple of 500 + score -= 4; + } + } + + if (store) { + mm->commb_format = COMMB_VERTICAL_INTENT; + + if (mcp_valid) { + mm->nav.mcp_altitude_valid = 1; + mm->nav.mcp_altitude = mcp_alt; + } + + if (fms_valid) { + mm->nav.fms_altitude_valid = 1; + mm->nav.fms_altitude = fms_alt; + } + + if (baro_valid) { + mm->nav.qnh_valid = 1; + mm->nav.qnh = baro_setting; + } + + if (mode_valid) { + mm->nav.modes_valid = 1; + mm->nav.modes = + ((mode_raw & 4) ? NAV_MODE_VNAV : 0) | + ((mode_raw & 2) ? NAV_MODE_ALT_HOLD : 0) | + ((mode_raw & 1) ? NAV_MODE_APPROACH : 0); + } + + if (source_valid) { + switch (source_raw) { + case 0: + mm->nav.altitude_source = NAV_ALT_UNKNOWN; + break; + case 1: + mm->nav.altitude_source = NAV_ALT_AIRCRAFT; + break; + case 2: + mm->nav.altitude_source = NAV_ALT_MCP; + break; + case 3: + mm->nav.altitude_source = NAV_ALT_FMS; + break; + default: + mm->nav.altitude_source = NAV_ALT_INVALID; + break; + } + } else { + mm->nav.altitude_source = NAV_ALT_INVALID; + } + } + + return score; +} + +// BDS5,0 Track and turn report + +static int decodeBDS50(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + unsigned roll_valid = getbit(msg, 1); + unsigned roll_sign = getbit(msg, 2); + unsigned roll_raw = getbits(msg, 3, 11); + + unsigned track_valid = getbit(msg, 12); + unsigned track_sign = getbit(msg, 13); + unsigned track_raw = getbits(msg, 14, 23); + + unsigned gs_valid = getbit(msg, 24); + unsigned gs_raw = getbits(msg, 25, 34); + + unsigned track_rate_valid = getbit(msg, 35); + unsigned track_rate_sign = getbit(msg, 36); + unsigned track_rate_raw = getbits(msg, 37, 45); + + unsigned tas_valid = getbit(msg, 46); + unsigned tas_raw = getbits(msg, 47, 56); + + if (!roll_valid || !track_valid || !gs_valid || !tas_valid) { + return 0; + } + + int score = 0; + + float roll = 0; + if (roll_valid) { + roll = roll_raw * 45.0 / 256.0; + if (roll_sign) { + roll -= 90.0; + } + + if (roll >= -40 && roll < 40) { + score += 11; + } else { + return 0; + } + } else if (!roll_valid && roll_raw == 0 && !roll_sign) { + score += 1; + } else { + return 0; + } + + float track = 0; + if (track_valid) { + score += 12; + track = track_raw * 90.0 / 512.0; + if (track_sign) { + track += 180.0; + } + } else if (!track_valid && track_raw == 0 && !track_sign) { + score += 1; + } else { + return 0; + } + + unsigned gs = 0; + if (gs_valid && gs_raw != 0) { + gs = gs_raw * 2; + + if (gs >= 50 && gs <= 700) { + score += 11; + } else { + return 0; + } + } else if (!gs_valid && gs_raw == 0) { + score += 1; + } else { + return 0; + } + + float track_rate = 0; + if (track_rate_valid) { + track_rate = track_rate_raw * 8.0 / 256.0; + if (track_rate_sign) { + track_rate -= 16; + } + + if (track_rate >= -10.0 && track_rate <= 10.0) { + score += 11; + } else { + return 0; + } + } else if (!track_rate_valid && track_rate_raw == 0 && !track_rate_sign) { + score += 1; + } else { + return 0; + } + + unsigned tas = 0; + if (tas_valid && tas_raw != 0) { + tas = tas_raw * 2; + + if (tas >= 50 && tas <= 700) { + score += 11; + } else { + return 0; + } + } else if (!tas_valid && tas_raw == 0) { + score += 1; + } else { + return 0; + } + + // small penalty for inconsistent data + if (gs_valid && tas_valid) { + int delta = abs((int)gs_valid - (int)tas_valid); + if (delta > 150) { + score -= 6; + } + } + + // compute the theoretical turn rate and compare to track angle rate + if (roll_valid && tas_valid && tas > 0 && track_rate_valid) { + double turn_rate = 68625 * tan(roll * M_PI / 180.0) / (tas * 20 * M_PI); + double delta = fabs(turn_rate - track_rate); + if (delta > 2.0) { + score -= 6; + } + } + + if (store) { + mm->commb_format = COMMB_TRACK_TURN; + + if (roll_valid) { + mm->roll_valid = 1; + mm->roll = roll; + } + + if (track_valid) { + mm->heading_valid = 1; + mm->heading = track; + mm->heading_type = HEADING_GROUND_TRACK; + } + + if (gs_valid) { + mm->gs_valid = 1; + mm->gs.v0 = mm->gs.v2 = mm->gs.selected = gs; + } + + if (track_rate_valid) { + mm->track_rate_valid = 1; + mm->track_rate = track_rate; + } + + if (tas_valid) { + mm->tas_valid = 1; + mm->tas = tas; + } + } + + return score; +} + +// BDS6,0 Heading and speed report + +static int decodeBDS60(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + unsigned heading_valid = getbit(msg, 1); + unsigned heading_sign = getbit(msg, 2); + unsigned heading_raw = getbits(msg, 3, 12); + + unsigned ias_valid = getbit(msg, 13); + unsigned ias_raw = getbits(msg, 14, 23); + + unsigned mach_valid = getbit(msg, 24); + unsigned mach_raw = getbits(msg, 25, 34); + + unsigned baro_rate_valid = getbit(msg, 35); + unsigned baro_rate_sign = getbit(msg, 36); + unsigned baro_rate_raw = getbits(msg, 37, 45); + + unsigned inertial_rate_valid = getbit(msg, 46); + unsigned inertial_rate_sign = getbit(msg, 47); + unsigned inertial_rate_raw = getbits(msg, 48, 56); + + if (!heading_valid || !ias_valid || !mach_valid || (!baro_rate_valid && !inertial_rate_valid)) { + return 0; + } + + int score = 0; + + float heading = 0; + if (heading_valid) { + heading = heading_raw * 90.0 / 512.0; + if (heading_sign) { + heading += 180.0; + } + score += 12; + } else if (!heading_valid && heading_raw == 0 && !heading_sign) { + score += 1; + } else { + return 0; + } + + unsigned ias = 0; + if (ias_valid && ias_raw != 0) { + ias = ias_raw; + if (ias >= 50 && ias <= 700) { + score += 11; + } else { + return 0; + } + } else if (!ias_valid && ias_raw == 0) { + score += 1; + } else { + return 0; + } + + float mach = 0; + if (mach_valid && mach_raw != 0) { + mach = mach_raw * 2.048 / 512; + if (mach >= 0.1 && mach <= 0.9) { + score += 11; + } else { + return 0; + } + } else if (!mach_valid && mach_raw == 0) { + score += 1; + } else { + return 0; + } + + int baro_rate = 0; + if (baro_rate_valid) { + baro_rate = baro_rate_raw * 32; + if (baro_rate_sign) { + baro_rate -= 16384; + } + + if (baro_rate >= -6000 && baro_rate <= 6000) { + score += 11; + } else { + return 0; + } + } else if (!baro_rate_valid && baro_rate_raw == 0) { + score += 1; + } else { + return 0; + } + + int inertial_rate = 0; + if (inertial_rate_valid) { + inertial_rate = inertial_rate_raw * 32; + if (inertial_rate_sign) { + inertial_rate -= 16384; + } + + if (inertial_rate >= -6000 && inertial_rate <= 6000) { + score += 11; + } else { + return 0; + } + } else if (!inertial_rate_valid && inertial_rate_raw == 0) { + score += 1; + } else { + return 0; + } + + // small penalty for inconsistent data + + // Should check IAS vs Mach at given altitude, but the maths is a little involved + + if (baro_rate_valid && inertial_rate_valid) { + int delta = abs(baro_rate - inertial_rate); + if (delta > 2000) { + score -= 12; + } + } + + if (store) { + mm->commb_format = COMMB_HEADING_SPEED; + + if (heading_valid) { + mm->heading_valid = 1; + mm->heading = heading; + mm->heading_type = HEADING_MAGNETIC; + } + + if (ias_valid) { + mm->ias_valid = 1; + mm->ias = ias; + } + + if (mach_valid) { + mm->mach_valid = 1; + mm->mach = mach; + } + + if (baro_rate_valid) { + mm->baro_rate_valid = 1; + mm->baro_rate = baro_rate; + } + + if (inertial_rate_valid) { + // INS-derived data is treated as a "geometric rate" / "geometric altitude" + // elsewhere, so do the same here. + mm->geom_rate_valid = 1; + mm->geom_rate = inertial_rate; + } + } + + 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/readsb/comm_b.h b/readsb/comm_b.h new file mode 100644 index 0000000..74913c4 --- /dev/null +++ b/readsb/comm_b.h @@ -0,0 +1,293 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// comm_b.h: Comm-B message decoding (header) +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2017 FlightAware, LLC +// Copyright (c) 2017 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef COMM_B_H +#define COMM_B_H + + + +struct modesMessage +{ + // Generic fields + unsigned char msg[MODES_LONG_MSG_BYTES]; // Binary message. + unsigned char verbatim[MODES_LONG_MSG_BYTES]; // Binary message, as originally received before correction + double signalLevel; // RSSI, in the range [0..1], as a fraction of full-scale power + struct client *client; // network client this message came from, NULL otherwise + struct aircraft *aircraft; // tracked aircraft associated with this message or NULL + struct messageBuffer *messageBuffer; + + int64_t timestamp; // Timestamp of the message (12MHz clock) + int64_t sysTimestamp; // Timestamp of the message (system time) + uint64_t receiverId; // zero if not transmitted + int msgtype; // Downlink format # + int msgbits; // Number of bits in message + int score; // Scoring from scoreModesMessage, if used + int receiverCountMlat; // number of receivers for MLAT messages + int mlatEPU; // estimated position uncertainty + int correctedbits; // No. of bits corrected + int decodeResult; + uint32_t crc; // Message CRC + uint32_t addr; // Address Announced + uint32_t maybe_addr; // probably the address, good chance to be wrong + addrtype_t addrtype; // address format / source + int8_t remote; // If set this message is from a remote station + int8_t sbs_in; // Signifies this message is coming from basestation input + int8_t address_reliable; + int8_t sbsMsgType; // SBS message type + int8_t reduce_forward; // forward this message for reduced beast output + int8_t garbage; // from garbage receiver + int8_t duplicate; // associated position is a duplicate + int8_t duplicate_checked; // duplicate check done + int8_t pos_bad; // speed_check failed + int8_t pos_ignore; // associated position is old / delayed / misc error + int8_t pos_old; // associated position is old / delayed / misc error + int8_t pos_receiver_range_exceeded; + int8_t trackUnreliable; + int8_t speedUnreliable; + int8_t in_disc_cache; + int8_t jsonPositionOutputEmit; + datasource_t source; // Characterizes the overall message source + // Raw data, just extracted directly from the message + // The names reflect the field names in Annex 4 + unsigned IID; // extracted from CRC of DF11s + unsigned AA; + unsigned AC; + unsigned CA; + unsigned CC; + unsigned CF; + unsigned DR; + unsigned FS; + unsigned ID; + unsigned KE; + unsigned ND; + unsigned RI; + unsigned SL; + unsigned UM; + unsigned VS; + unsigned metype; // DF17/18 ME type + unsigned mesub; // DF17/18 ME subtype + + unsigned char MB[7]; + unsigned char MD[10]; + unsigned char ME[7]; + unsigned char MV[7]; + + // Decoded data + bool baro_alt_valid; + bool geom_alt_valid; + bool track_valid; + bool track_rate_valid; + bool heading_valid; + bool roll_valid; + bool gs_valid; + bool ias_valid; + bool tas_valid; + bool mach_valid; + bool baro_rate_valid; + bool geom_rate_valid; + bool squawk_valid; + bool callsign_valid; + bool cpr_valid; + bool cpr_odd; + bool cpr_decoded; + bool cpr_relative; + bool category_valid; + bool geom_delta_valid; + bool from_mlat; + bool from_tisb; + bool spi_valid; + bool spi; + bool alert_valid; + bool alert; + bool emergency_valid; + bool sbs_pos_valid; + 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; + + // valid if baro_alt_valid: + int baro_alt; // Altitude in either feet or meters + altitude_unit_t baro_alt_unit; // the unit used for altitude + + // valid if geom_alt_valid: + int geom_alt; // Altitude in either feet or meters + altitude_unit_t geom_alt_unit; // the unit used for altitude + + // following fields are valid if the corresponding _valid field is set: + int geom_delta; // Difference between geometric and baro alt + float heading; // ground track or heading, degrees (0-359). Reported directly or computed from from EW and NS velocity + heading_type_t heading_type; // how to interpret 'track_or_heading' + float track_rate; // Rate of change of track, degrees/second + float roll; // Roll, degrees, negative is left roll + + struct + { + // Groundspeed, kts, reported directly or computed from from EW and NS velocity + // For surface movement, this has different interpretations for v0 and v2; both + // fields are populated. The tracking layer will update "gs.selected". + float v0; + float v2; + float selected; + } gs; + unsigned ias; // Indicated airspeed, kts + unsigned tas; // True airspeed, kts + double mach; // Mach number + int baro_rate; // Rate of change of barometric altitude, feet/minute + int geom_rate; // Rate of change of geometric (GNSS / INS) altitude, feet/minute + char callsign[16]; // 8 chars flight number, NUL-terminated + uint32_t squawkHex; // 13 bits identity (Squawk), encoded as 4 hex digits + uint32_t squawkDec; // Squawk as a decimal number + unsigned category; // A0 - D7 encoded as a single hex byte + emergency_t emergency; // emergency/priority status + + // valid if cpr_valid + cpr_type_t cpr_type; // The encoding type used (surface, airborne, coarse TIS-B) + uint32_t cpr_lat; // Non decoded latitude. + uint32_t cpr_lon; // Non decoded longitude. + uint32_t cpr_nucp; // NUCp/NIC value implied by message type + + airground_t airground; // air/ground state + + // valid if cpr_decoded: + double decoded_lat; + double decoded_lon; + unsigned decoded_nic; + unsigned decoded_rc; + + double distance_traveled; // set in speed_check, zero is invalid + 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 + + struct + { + bool nic_a_valid; + bool nic_b_valid; + bool nic_c_valid; + bool nic_baro_valid; + bool nac_p_valid; + bool nac_v_valid; + bool gva_valid; + bool sda_valid; + + bool nic_a; // if nic_a_valid + bool nic_b; // if nic_b_valid + bool nic_c; // if nic_c_valid + bool nic_baro; // if nic_baro_valid + + unsigned nac_p; // if nac_p_valid + unsigned nac_v; // if nac_v_valid + + unsigned sil; // if sil_type != SIL_INVALID + + unsigned gva; // if gva_valid + + unsigned sda; // if sda_valid + sil_type_t sil_type; + } accuracy; + + // Operational Status + + struct + { + sil_type_t sil_type; + heading_type_t tah; + heading_type_t hrd; + enum + { + ANGLE_HEADING, ANGLE_TRACK + } track_angle; + + unsigned cc_lw; + unsigned cc_antenna_offset; + + unsigned valid : 1; + unsigned version : 3; + + unsigned om_acas_ra : 1; + unsigned om_ident : 1; + unsigned om_atc : 1; + unsigned om_saf : 1; + + unsigned cc_acas : 1; + unsigned cc_cdti : 1; + unsigned cc_1090_in : 1; + unsigned cc_arv : 1; + unsigned cc_ts : 1; + unsigned cc_tc : 2; + unsigned cc_uat_in : 1; + unsigned cc_poa : 1; + unsigned cc_b2_low : 1; + unsigned cc_lw_valid : 1; + } opstatus; + + // combined: + // Target State & Status (ADS-B V2 only) + // Comm-B BDS4,0 Vertical Intent + + struct + { + unsigned fms_altitude; // FMS selected altitude + unsigned mcp_altitude; // MCP/FCU selected altitude + float qnh; // altimeter setting (QFE or QNH/QNE), millibars + float heading; // heading, degrees (0-359) (could be magnetic or true heading; magnetic recommended) + bool heading_valid; + bool fms_altitude_valid; + bool mcp_altitude_valid; + bool qnh_valid; + bool modes_valid; + heading_type_t heading_type; + + nav_altitude_source_t altitude_source; + + nav_modes_t modes; + } nav; +}; + + +void decodeCommB (struct modesMessage *mm); +int checkAcasRaValid(unsigned char *MV, struct modesMessage *mm, int debug); + +#endif diff --git a/readsb2/ais_charset.cpp b/readsb2/ais_charset.cpp new file mode 100644 index 0000000..2da9f6e --- /dev/null +++ b/readsb2/ais_charset.cpp @@ -0,0 +1,26 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// ais_charset.c: Valid character set. +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2017 FlightAware, LLC +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ais_charset.h" + +char ais_charset[65] = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?"; diff --git a/readsb2/ais_charset.h b/readsb2/ais_charset.h new file mode 100644 index 0000000..4a8b3a2 --- /dev/null +++ b/readsb2/ais_charset.h @@ -0,0 +1,29 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// ais_charset.h: Valid character set. (header) +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2017 FlightAware, LLC +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef AIS_CHARSET_H +#define AIS_CHARSET_H + +extern char ais_charset[65]; + +#endif diff --git a/readsb2/comm_b.cpp b/readsb2/comm_b.cpp new file mode 100644 index 0000000..c0b8123 --- /dev/null +++ b/readsb2/comm_b.cpp @@ -0,0 +1,962 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// comm_b.c: Comm-B message decoding +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2017 FlightAware, LLC +// Copyright (c) 2017 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ais_charset.h" +#include "readsb.h" +#define M_PI 3.14159265358979323846 + +typedef int (*CommBDecoderFn)(struct modesMessage *, bool); + +static int decodeEmptyResponse(struct modesMessage *mm, bool store); +static int decodeBDS10(struct modesMessage *mm, bool store); +static int decodeBDS17(struct modesMessage *mm, bool store); +static int decodeBDS20(struct modesMessage *mm, bool store); +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, + &decodeBDS10, + &decodeBDS20, + &decodeBDS30, + &decodeBDS17, + &decodeBDS40, + &decodeBDS50, + &decodeBDS60, + &decodeBDS44 +}; + +void decodeCommB(struct modesMessage *mm) { + mm->commb_format = COMMB_UNKNOWN; + + // If DR or UM are set, this message is _probably_ noise + // as nothing really seems to use the multisite broadcast stuff? + // Also skip anything that had errors corrected + if (mm->DR != 0 || mm->UM != 0 || mm->correctedbits > 0) { + return; + } + + // This is a bit hairy as we don't know what the requested register was + int bestScore = 0; + CommBDecoderFn bestDecoder = NULL; + int ambiguous = 0; + + for (unsigned i = 0; i < (sizeof (comm_b_decoders) / sizeof (comm_b_decoders[0])); ++i) { + int score = comm_b_decoders[i](mm, false); + if (score > bestScore) { + bestScore = score; + bestDecoder = comm_b_decoders[i]; + ambiguous = 0; + } else if (score == bestScore) { + ambiguous = 1; + } + } + + if (bestDecoder) { + if (ambiguous) { + mm->commb_format = COMMB_AMBIGUOUS; + } else { + // decode it + bestDecoder(mm, true); + } + } +} + +static int decodeEmptyResponse(struct modesMessage *mm, bool store) { + for (unsigned i = 0; i < 7; ++i) { + if (mm->MB[i] != 0) { + return 0; + } + } + + if (store) { + mm->commb_format = COMMB_EMPTY_RESPONSE; + } + + return 56; +} + +// BDS1,0 Datalink capabilities + +static int decodeBDS10(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + // BDS identifier + if (msg[0] != 0x10) { + return 0; + } + + // Reserved bits + if (getbits(msg, 10, 14) != 0) { + return 0; + } + + // Looks plausible. + + if (store) { + mm->commb_format = COMMB_DATALINK_CAPS; + } + + return 56; +} + +// BDS1,7 Common usage GICB capability report + +static int decodeBDS17(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + // reserved bits + if (getbits(msg, 25, 56) != 0) { + return 0; + } + + int score = 0; + if (getbit(msg, 7)) { + score += 1; // 2,0 aircraft identification + } else { + // BDS2,0 is on almost everything + score -= 2; + } + + // unlikely bits + if (getbit(msg, 10)) { // 4,1 next waypoint identifier + score -= 2; + } + if (getbit(msg, 11)) { // 4,2 next waypoint position + score -= 2; + } + if (getbit(msg, 12)) { // 4,3 next waypoint information + score -= 2; + } + if (getbit(msg, 13)) { // 4,4 meterological routine report + score -= 2; + } + if (getbit(msg, 14)) { // 4,4 meterological hazard report + score -= 2; + } + if (getbit(msg, 20)) { // 5,4 waypoint 1 + score -= 2; + } + if (getbit(msg, 21)) { // 5,5 waypoint 2 + score -= 2; + } + if (getbit(msg, 22)) { // 5,6 waypoint 3 + score -= 2; + } + + if (getbit(msg, 1) && getbit(msg, 2) && getbit(msg, 3) && getbit(msg, 4) && getbit(msg, 5)) { + // looks like ES capable + score += 5; + if (getbit(msg, 6)) { + // ES EDI + score += 1; + } + } else if (!getbit(msg, 1) && !getbit(msg, 2) && !getbit(msg, 3) && !getbit(msg, 4) && !getbit(msg, 5) && !getbit(msg, 6)) { + // not ES capable + score += 1; + } else { + // partial ES support, unlikely + score -= 12; + } + + if (getbit(msg, 16) && getbit(msg, 24)) { + // track/turn, heading/speed + score += 2; + if (getbit(msg, 9)) { + // vertical intent + score += 1; + } + } else if (!getbit(msg, 16) && !getbit(msg, 24) && !getbit(msg, 9)) { + // neither + score += 1; + } else { + // unlikely + score -= 6; + } + + if (store) { + mm->commb_format = COMMB_GICB_CAPS; + } + + return score; +} + +// BDS2,0 Aircraft identification + +static int decodeBDS20(struct modesMessage *mm, bool store) { + char callsign[sizeof(mm->callsign)]; + unsigned char *msg = mm->MB; + + // BDS identifier + if (msg[0] != 0x20) { + return 0; + } + + callsign[0] = ais_charset[getbits(msg, 9, 14)]; + callsign[1] = ais_charset[getbits(msg, 15, 20)]; + callsign[2] = ais_charset[getbits(msg, 21, 26)]; + callsign[3] = ais_charset[getbits(msg, 27, 32)]; + callsign[4] = ais_charset[getbits(msg, 33, 38)]; + callsign[5] = ais_charset[getbits(msg, 39, 44)]; + callsign[6] = ais_charset[getbits(msg, 45, 50)]; + callsign[7] = ais_charset[getbits(msg, 51, 56)]; + callsign[8] = 0; + + // score based on number of valid characters + int score = 8; + int 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 + score += 6; + } else { + // Invalid + if (Modes.debug_callsign) { + fprintf(stderr, "%06x %8s (len: %d)\n", mm->addr, callsign, (int) strlen(callsign)); + } + return 0; + } + } + + if (store) { + mm->commb_format = COMMB_AIRCRAFT_IDENT; + if (valid) { + memcpy(mm->callsign, callsign, sizeof(mm->callsign)); + mm->callsign_valid = 1; + } + } + + return score; +} + +// check if the payload is a valid ACAS payload +// https://mode-s.org/decode/book-the_1090mhz_riddle-junzi_sun.pdf +int checkAcasRaValid(unsigned char *msg, struct modesMessage *mm, int debug) { + + bool ara = getbit(msg, 9); + bool rat = getbit(msg, 27); + bool mte = getbit(msg, 28); + + // not a valid RA if none of the bits are set + if (!ara && !rat && !mte) + return 0; + + if (getbits(msg, 9, 28) == 0) + return 0; // these are the bits that contain the info, all zero it's not an RA + if (getbit(msg, 23) && getbit(msg, 24)) + return 0; // complementary bits, both set is invalid (above / below) + if (getbit(msg, 25) && getbit(msg, 26)) + return 0; // complementary bits, both set is invalid (left / right) + + if (mm->msgtype == 16) { + if (getbits(msg, 29, 56) != 0) + return 0; // in DF16 messages msg bits 29 to 56 are reserved + + return 1; + } + + // some extra restrictions for DF != 16 below + + if (getbit(msg, 25) || getbit(msg, 26)) + return 0; // left / right isn't used, require zero + + // for COMMB messages let's check if the thread indicator makes sense + + int tti = getbits(msg, 29, 30); + // thread type indicator + // 00 No identity data in threat identity data + if (tti == 0) { + if (getbits(msg, 31, 56) != 0) + return 0; + + return 1; + } + // When the threat type indicator is 01 , MB bits 31-54 contain the 24-bit Mode S transponder address and the last two bits are set to zero. + if (tti == 1) { + if (getbits(msg, 55, 56) != 0) + return 0; + + uint32_t addr = getbits(msg, 31, 54); + if (icaoFilterTest(addr)) + return 1; + + return debug; // zero unless debug + } + + // 10 Threat identity data contains altitude, range, and bearing + if (tti == 2) { + if (mm->metype == 28)// allow for DF17 + return 1; + // hard to tell if used and separate from garbage, don't mark valid for the moment + return debug; // zero unless debug + } + + // 11 Not assigned + if (tti == 3) { + return 0; + } + + return 0; +} + +// BDS3,0 ACAS RA + +static int decodeBDS30(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + // BDS identifier + if (msg[0] != 0x30) { + return 0; + } + + if (store) { + mm->commb_format = COMMB_ACAS_RA; + mm->acas_ra_valid = 1; + } + + // just accept it. + return 56; +} + +// BDS4,0 Selected vertical intention + +static int decodeBDS40(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + unsigned mcp_valid = getbit(msg, 1); + unsigned mcp_raw = getbits(msg, 2, 13); + unsigned fms_valid = getbit(msg, 14); + unsigned fms_raw = getbits(msg, 15, 26); + unsigned baro_valid = getbit(msg, 27); + unsigned baro_raw = getbits(msg, 28, 39); + unsigned reserved_1 = getbits(msg, 40, 47); + unsigned mode_valid = getbit(msg, 48); + unsigned mode_raw = getbits(msg, 49, 51); + unsigned reserved_2 = getbits(msg, 52, 53); + unsigned source_valid = getbit(msg, 54); + unsigned source_raw = getbits(msg, 55, 56); + + if (!mcp_valid && !fms_valid && !baro_valid && !mode_valid && !source_valid) { + return 0; + } + + int score = 0; + + unsigned mcp_alt = 0; + if (mcp_valid && mcp_raw != 0) { + mcp_alt = mcp_raw * 16; + if (mcp_alt >= 1000 && mcp_alt <= 50000) { + score += 13; + } else { + // unlikely altitude + return 0; + } + } else if (!mcp_valid && mcp_raw == 0) { + score += 1; + } else { + return 0; + } + + unsigned fms_alt = 0; + if (fms_valid && fms_raw != 0) { + fms_alt = fms_raw * 16; + if (fms_alt >= 1000 && fms_alt <= 50000) { + score += 13; + } else { + // unlikely altitude + return 0; + } + } else if (!fms_valid && fms_raw == 0) { + score += 1; + } else { + return 0; + } + + float baro_setting = 0; + if (baro_valid && baro_raw != 0) { + baro_setting = 800 + baro_raw * 0.1; + if (baro_setting >= 900 && baro_setting <= 1100) { + score += 13; + } else { + // unlikely pressure setting + return 0; + } + } else if (!baro_valid && baro_raw == 0) { + score += 1; + } else { + return 0; + } + + if (reserved_1 != 0) { + return 0; + } + + if (mode_valid) { + score += 4; + } else if (!mode_valid && mode_raw == 0) { + score += 1; + } else { + return 0; + } + + if (reserved_2 != 0) { + return 0; + } + + if (source_valid) { + score += 3; + } else if (!source_valid && source_raw == 0) { + score += 1; + } else { + return 0; + } + + // small penalty for inconsistent data + if (mcp_valid && fms_valid && mcp_alt != fms_alt) { + score -= 4; + } + + if (mcp_valid) { + unsigned remainder = mcp_alt % 500; + if (!(remainder < 16 || remainder > 484)) { + // mcp altitude is not a multiple of 500 + score -= 4; + } + } + + if (fms_valid) { + unsigned remainder = fms_alt % 500; + if (!(remainder < 16 || remainder > 484)) { + // fms altitude is not a multiple of 500 + score -= 4; + } + } + + if (store) { + mm->commb_format = COMMB_VERTICAL_INTENT; + + if (mcp_valid) { + mm->nav.mcp_altitude_valid = 1; + mm->nav.mcp_altitude = mcp_alt; + } + + if (fms_valid) { + mm->nav.fms_altitude_valid = 1; + mm->nav.fms_altitude = fms_alt; + } + + if (baro_valid) { + mm->nav.qnh_valid = 1; + mm->nav.qnh = baro_setting; + } + + if (mode_valid) { + mm->nav.modes_valid = 1; + mm->nav.modes = + ((mode_raw & 4) ? NAV_MODE_VNAV : 0) | + ((mode_raw & 2) ? NAV_MODE_ALT_HOLD : 0) | + ((mode_raw & 1) ? NAV_MODE_APPROACH : 0); + } + + if (source_valid) { + switch (source_raw) { + case 0: + mm->nav.altitude_source = NAV_ALT_UNKNOWN; + break; + case 1: + mm->nav.altitude_source = NAV_ALT_AIRCRAFT; + break; + case 2: + mm->nav.altitude_source = NAV_ALT_MCP; + break; + case 3: + mm->nav.altitude_source = NAV_ALT_FMS; + break; + default: + mm->nav.altitude_source = NAV_ALT_INVALID; + break; + } + } else { + mm->nav.altitude_source = NAV_ALT_INVALID; + } + } + + return score; +} + +// BDS5,0 Track and turn report + +static int decodeBDS50(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + unsigned roll_valid = getbit(msg, 1); + unsigned roll_sign = getbit(msg, 2); + unsigned roll_raw = getbits(msg, 3, 11); + + unsigned track_valid = getbit(msg, 12); + unsigned track_sign = getbit(msg, 13); + unsigned track_raw = getbits(msg, 14, 23); + + unsigned gs_valid = getbit(msg, 24); + unsigned gs_raw = getbits(msg, 25, 34); + + unsigned track_rate_valid = getbit(msg, 35); + unsigned track_rate_sign = getbit(msg, 36); + unsigned track_rate_raw = getbits(msg, 37, 45); + + unsigned tas_valid = getbit(msg, 46); + unsigned tas_raw = getbits(msg, 47, 56); + + if (!roll_valid || !track_valid || !gs_valid || !tas_valid) { + return 0; + } + + int score = 0; + + float roll = 0; + if (roll_valid) { + roll = roll_raw * 45.0 / 256.0; + if (roll_sign) { + roll -= 90.0; + } + + if (roll >= -40 && roll < 40) { + score += 11; + } else { + return 0; + } + } else if (!roll_valid && roll_raw == 0 && !roll_sign) { + score += 1; + } else { + return 0; + } + + float track = 0; + if (track_valid) { + score += 12; + track = track_raw * 90.0 / 512.0; + if (track_sign) { + track += 180.0; + } + } else if (!track_valid && track_raw == 0 && !track_sign) { + score += 1; + } else { + return 0; + } + + unsigned gs = 0; + if (gs_valid && gs_raw != 0) { + gs = gs_raw * 2; + + if (gs >= 50 && gs <= 700) { + score += 11; + } else { + return 0; + } + } else if (!gs_valid && gs_raw == 0) { + score += 1; + } else { + return 0; + } + + float track_rate = 0; + if (track_rate_valid) { + track_rate = track_rate_raw * 8.0 / 256.0; + if (track_rate_sign) { + track_rate -= 16; + } + + if (track_rate >= -10.0 && track_rate <= 10.0) { + score += 11; + } else { + return 0; + } + } else if (!track_rate_valid && track_rate_raw == 0 && !track_rate_sign) { + score += 1; + } else { + return 0; + } + + unsigned tas = 0; + if (tas_valid && tas_raw != 0) { + tas = tas_raw * 2; + + if (tas >= 50 && tas <= 700) { + score += 11; + } else { + return 0; + } + } else if (!tas_valid && tas_raw == 0) { + score += 1; + } else { + return 0; + } + + // small penalty for inconsistent data + if (gs_valid && tas_valid) { + int delta = abs((int)gs_valid - (int)tas_valid); + if (delta > 150) { + score -= 6; + } + } + + // compute the theoretical turn rate and compare to track angle rate + if (roll_valid && tas_valid && tas > 0 && track_rate_valid) { + double turn_rate = 68625 * tan(roll * M_PI / 180.0) / (tas * 20 * M_PI); + double delta = fabs(turn_rate - track_rate); + if (delta > 2.0) { + score -= 6; + } + } + + if (store) { + mm->commb_format = COMMB_TRACK_TURN; + + if (roll_valid) { + mm->roll_valid = 1; + mm->roll = roll; + } + + if (track_valid) { + mm->heading_valid = 1; + mm->heading = track; + mm->heading_type = HEADING_GROUND_TRACK; + } + + if (gs_valid) { + mm->gs_valid = 1; + mm->gs.v0 = mm->gs.v2 = mm->gs.selected = gs; + } + + if (track_rate_valid) { + mm->track_rate_valid = 1; + mm->track_rate = track_rate; + } + + if (tas_valid) { + mm->tas_valid = 1; + mm->tas = tas; + } + } + + return score; +} + +// BDS6,0 Heading and speed report + +static int decodeBDS60(struct modesMessage *mm, bool store) { + unsigned char *msg = mm->MB; + + unsigned heading_valid = getbit(msg, 1); + unsigned heading_sign = getbit(msg, 2); + unsigned heading_raw = getbits(msg, 3, 12); + + unsigned ias_valid = getbit(msg, 13); + unsigned ias_raw = getbits(msg, 14, 23); + + unsigned mach_valid = getbit(msg, 24); + unsigned mach_raw = getbits(msg, 25, 34); + + unsigned baro_rate_valid = getbit(msg, 35); + unsigned baro_rate_sign = getbit(msg, 36); + unsigned baro_rate_raw = getbits(msg, 37, 45); + + unsigned inertial_rate_valid = getbit(msg, 46); + unsigned inertial_rate_sign = getbit(msg, 47); + unsigned inertial_rate_raw = getbits(msg, 48, 56); + + if (!heading_valid || !ias_valid || !mach_valid || (!baro_rate_valid && !inertial_rate_valid)) { + return 0; + } + + int score = 0; + + float heading = 0; + if (heading_valid) { + heading = heading_raw * 90.0 / 512.0; + if (heading_sign) { + heading += 180.0; + } + score += 12; + } else if (!heading_valid && heading_raw == 0 && !heading_sign) { + score += 1; + } else { + return 0; + } + + unsigned ias = 0; + if (ias_valid && ias_raw != 0) { + ias = ias_raw; + if (ias >= 50 && ias <= 700) { + score += 11; + } else { + return 0; + } + } else if (!ias_valid && ias_raw == 0) { + score += 1; + } else { + return 0; + } + + float mach = 0; + if (mach_valid && mach_raw != 0) { + mach = mach_raw * 2.048 / 512; + if (mach >= 0.1 && mach <= 0.9) { + score += 11; + } else { + return 0; + } + } else if (!mach_valid && mach_raw == 0) { + score += 1; + } else { + return 0; + } + + int baro_rate = 0; + if (baro_rate_valid) { + baro_rate = baro_rate_raw * 32; + if (baro_rate_sign) { + baro_rate -= 16384; + } + + if (baro_rate >= -6000 && baro_rate <= 6000) { + score += 11; + } else { + return 0; + } + } else if (!baro_rate_valid && baro_rate_raw == 0) { + score += 1; + } else { + return 0; + } + + int inertial_rate = 0; + if (inertial_rate_valid) { + inertial_rate = inertial_rate_raw * 32; + if (inertial_rate_sign) { + inertial_rate -= 16384; + } + + if (inertial_rate >= -6000 && inertial_rate <= 6000) { + score += 11; + } else { + return 0; + } + } else if (!inertial_rate_valid && inertial_rate_raw == 0) { + score += 1; + } else { + return 0; + } + + // small penalty for inconsistent data + + // Should check IAS vs Mach at given altitude, but the maths is a little involved + + if (baro_rate_valid && inertial_rate_valid) { + int delta = abs(baro_rate - inertial_rate); + if (delta > 2000) { + score -= 12; + } + } + + if (store) { + mm->commb_format = COMMB_HEADING_SPEED; + + if (heading_valid) { + mm->heading_valid = 1; + mm->heading = heading; + mm->heading_type = HEADING_MAGNETIC; + } + + if (ias_valid) { + mm->ias_valid = 1; + mm->ias = ias; + } + + if (mach_valid) { + mm->mach_valid = 1; + mm->mach = mach; + } + + if (baro_rate_valid) { + mm->baro_rate_valid = 1; + mm->baro_rate = baro_rate; + } + + if (inertial_rate_valid) { + // INS-derived data is treated as a "geometric rate" / "geometric altitude" + // elsewhere, so do the same here. + mm->geom_rate_valid = 1; + mm->geom_rate = inertial_rate; + } + } + + 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/readsb2/comm_b.h b/readsb2/comm_b.h new file mode 100644 index 0000000..495e1a7 --- /dev/null +++ b/readsb2/comm_b.h @@ -0,0 +1,31 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// comm_b.h: Comm-B message decoding (header) +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2017 FlightAware, LLC +// Copyright (c) 2017 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef COMM_B_H +#define COMM_B_H + +void decodeCommB (struct modesMessage *mm); +int checkAcasRaValid(unsigned char *MV, struct modesMessage *mm, int debug); + +#endif diff --git a/readsb2/cpr.cpp b/readsb2/cpr.cpp new file mode 100644 index 0000000..b3ba359 --- /dev/null +++ b/readsb2/cpr.cpp @@ -0,0 +1,374 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// cpr.c - Compact Position Reporting decoder and tests +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2014,2015 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// This file incorporates work covered by the following copyright and +// license: +// +// Copyright (C) 2012 by Salvatore Sanfilippo +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include + +// +//========================================================================= +// +// Always positive MOD operation, used for CPR decoding. +// +static int cprModInt(int a, int b) { + int res = a % b; + if (res < 0) res += b; + return res; +} + +static double cprModDouble(double a, double b) { + double res = fmod(a, b); + if (res < 0) res += b; + return res; +} + +// +//========================================================================= +// +// The NL function uses the precomputed table from 1090-WP-9-14 +// +static int cprNLFunction(double lat) { + if (lat < 0) lat = -lat; // Table is simmetric about the equator + if (lat > 60) goto L60; + if (lat > 44.2) goto L442; + if (lat > 30) goto L30; + if (lat < 10.47047130) return 59; + if (lat < 14.82817437) return 58; + if (lat < 18.18626357) return 57; + if (lat < 21.02939493) return 56; + if (lat < 23.54504487) return 55; + if (lat < 25.82924707) return 54; + if (lat < 27.93898710) return 53; + if (lat < 29.91135686) return 52; +L30: + if (lat < 31.77209708) return 51; + if (lat < 33.53993436) return 50; + if (lat < 35.22899598) return 49; + if (lat < 36.85025108) return 48; + if (lat < 38.41241892) return 47; + if (lat < 39.92256684) return 46; + if (lat < 41.38651832) return 45; + if (lat < 42.80914012) return 44; + if (lat < 44.19454951) return 43; +L442: + if (lat < 45.54626723) return 42; + if (lat < 46.86733252) return 41; + if (lat < 48.16039128) return 40; + if (lat < 49.42776439) return 39; + if (lat < 50.67150166) return 38; + if (lat < 51.89342469) return 37; + if (lat < 53.09516153) return 36; + if (lat < 54.27817472) return 35; + if (lat < 55.44378444) return 34; + if (lat < 56.59318756) return 33; + if (lat < 57.72747354) return 32; + if (lat < 58.84763776) return 31; + if (lat < 59.95459277) return 30; +L60: + if (lat < 61.04917774) return 29; + if (lat < 62.13216659) return 28; + if (lat < 63.20427479) return 27; + if (lat < 64.26616523) return 26; + if (lat < 65.31845310) return 25; + if (lat < 66.36171008) return 24; + if (lat < 67.39646774) return 23; + if (lat < 68.42322022) return 22; + if (lat < 69.44242631) return 21; + if (lat < 70.45451075) return 20; + if (lat < 71.45986473) return 19; + if (lat < 72.45884545) return 18; + if (lat < 73.45177442) return 17; + if (lat < 74.43893416) return 16; + if (lat < 75.42056257) return 15; + if (lat < 76.39684391) return 14; + if (lat < 77.36789461) return 13; + if (lat < 78.33374083) return 12; + if (lat < 79.29428225) return 11; + if (lat < 80.24923213) return 10; + if (lat < 81.19801349) return 9; + if (lat < 82.13956981) return 8; + if (lat < 83.07199445) return 7; + if (lat < 83.99173563) return 6; + if (lat < 84.89166191) return 5; + if (lat < 85.75541621) return 4; + if (lat < 86.53536998) return 3; + if (lat < 87.00000000) return 2; + else return 1; +} +// +//========================================================================= +// +static int cprNFunction(double lat, int fflag) { + int nl = cprNLFunction(lat) - (fflag ? 1 : 0); + if (nl < 1) nl = 1; + return nl; +} +// +//========================================================================= +// +static double cprDlonFunction(double lat, int fflag, int surface) { + return (surface ? 90.0 : 360.0) / cprNFunction(lat, fflag); +} +// +//========================================================================= +// +// This algorithm comes from: +// http://www.lll.lu/~edward/edward/adsb/DecodingADSBposition.html. +// +// A few remarks: +// 1) 131072 is 2^17 since CPR latitude and longitude are encoded in 17 bits. +// +int decodeCPRairborne(int even_cprlat, int even_cprlon, + int odd_cprlat, int odd_cprlon, + int fflag, + double *out_lat, double *out_lon) { + double AirDlat0 = 360.0 / 60.0; + double AirDlat1 = 360.0 / 59.0; + double lat0 = even_cprlat; + double lat1 = odd_cprlat; + double lon0 = even_cprlon; + double lon1 = odd_cprlon; + + double rlat, rlon; + + // Compute the Latitude Index "j" + int j = (int) floor(((59 * lat0 - 60 * lat1) / 131072) + 0.5); + double rlat0 = AirDlat0 * (cprModInt(j, 60) + lat0 / 131072); + double rlat1 = AirDlat1 * (cprModInt(j, 59) + lat1 / 131072); + + if (rlat0 >= 270) rlat0 -= 360; + if (rlat1 >= 270) rlat1 -= 360; + + // Check to see that the latitude is in range: -90 .. +90 + if (rlat0 < -90 || rlat0 > 90 || rlat1 < -90 || rlat1 > 90) + return (-2); // bad data + + // Check that both are in the same latitude zone, or abort. + if (cprNLFunction(rlat0) != cprNLFunction(rlat1)) + return (-1); // positions crossed a latitude zone, try again later + + // Compute ni and the Longitude Index "m" + if (fflag) { // Use odd packet. + int ni = cprNFunction(rlat1, 1); + int m = (int) floor((((lon0 * (cprNLFunction(rlat1) - 1)) - + (lon1 * cprNLFunction(rlat1))) / 131072.0) + 0.5); + rlon = cprDlonFunction(rlat1, 1, 0) * (cprModInt(m, ni) + lon1 / 131072); + rlat = rlat1; + } else { // Use even packet. + int ni = cprNFunction(rlat0, 0); + int m = (int) floor((((lon0 * (cprNLFunction(rlat0) - 1)) - + (lon1 * cprNLFunction(rlat0))) / 131072) + 0.5); + rlon = cprDlonFunction(rlat0, 0, 0) * (cprModInt(m, ni) + lon0 / 131072); + rlat = rlat0; + } + + // Renormalize to -180 .. +180 + rlon -= floor((rlon + 180) / 360) * 360; + + *out_lat = rlat; + *out_lon = rlon; + + return 0; +} + +int decodeCPRsurface(double reflat, double reflon, + int even_cprlat, int even_cprlon, + int odd_cprlat, int odd_cprlon, + int fflag, + double *out_lat, double *out_lon) { + double AirDlat0 = 90.0 / 60.0; + double AirDlat1 = 90.0 / 59.0; + double lat0 = even_cprlat; + double lat1 = odd_cprlat; + double lon0 = even_cprlon; + double lon1 = odd_cprlon; + double rlon, rlat; + + // Compute the Latitude Index "j" + int j = (int) floor(((59 * lat0 - 60 * lat1) / 131072) + 0.5); + double rlat0 = AirDlat0 * (cprModInt(j, 60) + lat0 / 131072); + double rlat1 = AirDlat1 * (cprModInt(j, 59) + lat1 / 131072); + + // Pick the quadrant that's closest to the reference location - + // this is not necessarily the same quadrant that contains the + // reference location. + // + // There are also only two valid quadrants: -90..0 and 0..90; + // no correct message would try to encoding a latitude in the + // ranges -180..-90 and 90..180. + // + // If the computed latitude is more than 45 degrees north of + // the reference latitude (using the northern hemisphere + // solution), then the southern hemisphere solution will be + // closer to the refernce latitude. + // + // e.g. reflat=0, rlat=44, use rlat=44 + // reflat=0, rlat=46, use rlat=46-90 = -44 + // reflat=40, rlat=84, use rlat=84 + // reflat=40, rlat=86, use rlat=86-90 = -4 + // reflat=-40, rlat=4, use rlat=4 + // reflat=-40, rlat=6, use rlat=6-90 = -84 + + // As a special case, -90, 0 and +90 all encode to zero, so + // there's a little extra work to do there. + + if (rlat0 == 0) { + if (reflat < -45) + rlat0 = -90; + else if (reflat > 45) + rlat0 = 90; + } else if ((rlat0 - reflat) > 45) { + rlat0 -= 90; + } + + if (rlat1 == 0) { + if (reflat < -45) + rlat1 = -90; + else if (reflat > 45) + rlat1 = 90; + } else if ((rlat1 - reflat) > 45) { + rlat1 -= 90; + } + + // Check to see that the latitude is in range: -90 .. +90 + if (rlat0 < -90 || rlat0 > 90 || rlat1 < -90 || rlat1 > 90) + return (-2); // bad data + + // Check that both are in the same latitude zone, or abort. + if (cprNLFunction(rlat0) != cprNLFunction(rlat1)) + return (-1); // positions crossed a latitude zone, try again later + + // Compute ni and the Longitude Index "m" + if (fflag) { // Use odd packet. + int ni = cprNFunction(rlat1, 1); + int m = (int) floor((((lon0 * (cprNLFunction(rlat1) - 1)) - + (lon1 * cprNLFunction(rlat1))) / 131072.0) + 0.5); + rlon = cprDlonFunction(rlat1, 1, 1) * (cprModInt(m, ni) + lon1 / 131072); + rlat = rlat1; + } else { // Use even packet. + int ni = cprNFunction(rlat0, 0); + int m = (int) floor((((lon0 * (cprNLFunction(rlat0) - 1)) - + (lon1 * cprNLFunction(rlat0))) / 131072) + 0.5); + rlon = cprDlonFunction(rlat0, 0, 1) * (cprModInt(m, ni) + lon0 / 131072); + rlat = rlat0; + } + + // Pick the quadrant that's closest to the reference location - + // this is not necessarily the same quadrant that contains the + // reference location. Unlike the latitude case, all four + // quadrants are valid. + + // if reflon is more than 45 degrees away, move some multiple of 90 degrees towards it + rlon += floor((reflon - rlon + 45) / 90) * 90; // this might move us outside (-180..+180), we fix this below + + // Renormalize to -180 .. +180 + rlon -= floor((rlon + 180) / 360) * 360; + + *out_lat = rlat; + *out_lon = rlon; + return 0; +} + +// +//========================================================================= +// +// This algorithm comes from: +// 1090-WP29-07-Draft_CPR101 (which also defines decodeCPR() ) +// +// Despite what the earlier comment here said, we should *not* be using trunc(). +// See Figure 5-5 / 5-6 and note that floor is applied to (0.5 + fRP - fEP), not +// directly to (fRP - fEP). Eq 38 is correct. +// +int decodeCPRrelative(double reflat, double reflon, + int cprlat, int cprlon, + int fflag, int surface, + double *out_lat, double *out_lon) { + double AirDlat; + double AirDlon; + double fractional_lat = cprlat / 131072.0; + double fractional_lon = cprlon / 131072.0; + double rlon, rlat; + int j, m; + + AirDlat = (surface ? 90.0 : 360.0) / (fflag ? 59.0 : 60.0); + + // Compute the Latitude Index "j" + j = (int) (floor(reflat / AirDlat) + + floor(0.5 + cprModDouble(reflat, AirDlat) / AirDlat - fractional_lat)); + rlat = AirDlat * (j + fractional_lat); + if (rlat >= 270) rlat -= 360; + + // Check to see that the latitude is in range: -90 .. +90 + if (rlat < -90 || rlat > 90) { + return (-1); // Time to give up - Latitude error + } + + // Check to see that answer is reasonable - ie no more than 1/2 cell away + if (fabs(rlat - reflat) > (AirDlat / 2)) { + return (-1); // Time to give up - Latitude error + } + + // Compute the Longitude Index "m" + AirDlon = cprDlonFunction(rlat, fflag, surface); + m = (int) (floor(reflon / AirDlon) + + floor(0.5 + cprModDouble(reflon, AirDlon) / AirDlon - fractional_lon)); + rlon = AirDlon * (m + fractional_lon); + if (rlon > 180) rlon -= 360; + + // Check to see that answer is reasonable - ie no more than 1/2 cell away + if (fabs(rlon - reflon) > (AirDlon / 2)) + return (-1); // Time to give up - Longitude error + + *out_lat = rlat; + *out_lon = rlon; + return (0); +} diff --git a/readsb2/cpr.h b/readsb2/cpr.h new file mode 100644 index 0000000..0d2a0bf --- /dev/null +++ b/readsb2/cpr.h @@ -0,0 +1,43 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// cpr.h - Compact Position Reporting prototypes +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2014,2015 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef DUMP1090_CPR_H +#define DUMP1090_CPR_H + +int decodeCPRairborne (int even_cprlat, int even_cprlon, + int odd_cprlat, int odd_cprlon, + int fflag, + double *out_lat, double *out_lon); + +int decodeCPRsurface (double reflat, double reflon, + int even_cprlat, int even_cprlon, + int odd_cprlat, int odd_cprlon, + int fflag, + double *out_lat, double *out_lon); + +int decodeCPRrelative (double reflat, double reflon, + int cprlat, int cprlon, + int fflag, int surface, + double *out_lat, double *out_lon); + +#endif diff --git a/readsb2/crc.cpp b/readsb2/crc.cpp new file mode 100644 index 0000000..ed47cdf --- /dev/null +++ b/readsb2/crc.cpp @@ -0,0 +1,566 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// crc.h: Mode S CRC calculation and error correction. +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2014,2015 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "readsb.h" +#include + +// Errorinfo for "no errors" +static struct errorinfo NO_ERRORS; + +// Generator polynomial for the Mode S CRC: +#define MODES_GENERATOR_POLY 0xfff409U + +// CRC values for all single-byte messages; +// used to speed up CRC calculation. +ALIGNED static uint32_t crc_table[256]; + +// Syndrome values for all single-bit errors; +// used to speed up construction of error- +// correction tables. +ALIGNED static uint32_t single_bit_syndrome[112]; + +static void initLookupTables() { + int i; + uint8_t msg[112 / 8]; + + for (i = 0; i < 256; ++i) { + uint32_t c = i << 16; + int j; + for (j = 0; j < 8; ++j) { + if (c & 0x800000) + c = (c << 1) ^ MODES_GENERATOR_POLY; + else + c = (c << 1); + } + + crc_table[i] = c & 0x00ffffff; + } + + memset(msg, 0, sizeof (msg)); + for (i = 0; i < 112; ++i) { + msg[i / 8] ^= 1 << (7 - (i & 7)); + single_bit_syndrome[i] = modesChecksum(msg, 112); + msg[i / 8] ^= 1 << (7 - (i & 7)); + } +} + +uint32_t modesChecksum(uint8_t *message, int bits) { + uint32_t rem = 0; + int i; + int n = bits / 8; + + assert(bits % 8 == 0); + assert(n >= 3); + + for (i = 0; i < n - 3; ++i) { + rem = (rem << 8) ^ crc_table[message[i] ^ ((rem & 0xff0000) >> 16)]; + rem = rem & 0xffffff; + } + + rem = rem ^ (message[n - 3] << 16) ^ (message[n - 2] << 8) ^ (message[n - 1]); + return rem; +} + +static struct errorinfo *bitErrorTable_short; +static int bitErrorTableSize_short; + +static struct errorinfo *bitErrorTable_long; +static int bitErrorTableSize_long; + +// compare two errorinfo structures +static int syndrome_compare(const void *x, const void *y) { + struct errorinfo *ex = (struct errorinfo*) x; + struct errorinfo *ey = (struct errorinfo*) y; + return (int) ex->syndrome - (int) ey->syndrome; +} + +// (n k), the number of ways of selecting k distinct items from a set of n items +static int combinations(int n, int k) { + int result = 1, i; + + if (k == 0 || k == n) + return 1; + + if (k > n) + return 0; + + for (i = 1; i <= k; ++i) { + result = result * n / i; + n = n - 1; + } + + return result; +} + +// Recursively populates an errorinfo table with error syndromes +// +// in: +// table: the table to fill +// n: first entry to fill +// maxSize: max size of table +// offset: start bit offset for checksum calculation +// startbit: first bit to introduce errors into +// endbit: (one past) last bit to introduce errors info +// base_entry: template entry to start from +// error_bit: how many error bits have already been set +// max_errors: maximum total error bits to set +// out: +// returns: the next free entry in the table +// table: has been populated between [n, return value) +static int prepareSubtable(struct errorinfo *table, int n, int maxsize, int offset, int startbit, int endbit, struct errorinfo *base_entry, int error_bit, int max_errors) { + int i = 0; + + if (error_bit >= max_errors) + return n; + + for (i = startbit; i < endbit; ++i) { + assert(n < maxsize); + + table[n] = *base_entry; + table[n].syndrome ^= single_bit_syndrome[i + offset]; + table[n].errors = error_bit + 1; + table[n].bit[error_bit] = i; + + ++n; + n = prepareSubtable(table, n, maxsize, offset, i + 1, endbit, &table[n - 1], error_bit + 1, max_errors); + } + + return n; +} + +static int flagCollisions(struct errorinfo *table, int tablesize, int offset, int startbit, int endbit, uint32_t base_syndrome, int error_bit, int first_error, int last_error) { + int i = 0; + int count = 0; + + if (error_bit > last_error) + return 0; + + for (i = startbit; i < endbit; ++i) { + struct errorinfo ei; + + ei.syndrome = base_syndrome ^ single_bit_syndrome[i + offset]; + + if (error_bit >= first_error) { + struct errorinfo *collision = bsearch(&ei, table, tablesize, sizeof (struct errorinfo), syndrome_compare); + if (collision != NULL && collision->errors != -1) { + ++count; + collision->errors = -1; + } + } + + count += flagCollisions(table, tablesize, offset, i + 1, endbit, ei.syndrome, error_bit + 1, first_error, last_error); + } + + return count; +} + + +// Allocate and build an error table for messages of length "bits" (max 112) +// returns a pointer to the new table and sets *size_out to the table length +static struct errorinfo *prepareErrorTable(int bits, int max_correct, int max_detect, int *size_out) { + int maxsize, usedsize; + struct errorinfo *table; + struct errorinfo base_entry; + int i, j; + + assert(bits >= 0 && bits <= 112); + assert(max_correct >= 0 && max_correct <= MODES_MAX_BITERRORS); + assert(max_detect >= max_correct); + + if (!max_correct) { + *size_out = 0; + return NULL; + } + + maxsize = 0; + for (i = 1; i <= max_correct; ++i) { + maxsize += combinations(bits - 5, i); // space needed for all i-bit errors + } + +#ifdef CRCDEBUG + fprintf(stderr, "Preparing syndrome table to correct up to %d-bit errors (detecting %d-bit errors) in a %d-bit message (max %d entries)\n", max_correct, max_detect, bits, maxsize); +#endif + + table = cmalloc(maxsize * sizeof (struct errorinfo)); + base_entry.syndrome = 0; + base_entry.errors = 0; + for (i = 0; i < MODES_MAX_BITERRORS; ++i) + base_entry.bit[i] = -1; + + // ignore the first 5 bits (DF type) + usedsize = prepareSubtable(table, 0, maxsize, 112 - bits, 5, bits, &base_entry, 0, max_correct); + +#ifdef CRCDEBUG + fprintf(stderr, "%d syndromes (expected %d).\n", usedsize, maxsize); + fprintf(stderr, "Sorting syndromes..\n"); +#endif + + qsort(table, usedsize, sizeof (struct errorinfo), syndrome_compare); + +#ifdef CRCDEBUG + { + // Show the table stats + fprintf(stderr, "Undetectable errors:\n"); + for (i = 1; i <= max_correct; ++i) { + int j, count; + + count = 0; + for (j = 0; j < usedsize; ++j) + if (table[j].errors == i && table[j].syndrome == 0) + ++count; + + fprintf(stderr, " %d undetectable %d-bit errors\n", count, i); + } + } +#endif + + // Handle ambiguous cases, where there is more than one possible error pattern + // that produces a given syndrome (this happens with >2 bit errors). + +#ifdef CRCDEBUG + fprintf(stderr, "Finding collisions..\n"); +#endif + for (i = 0, j = 0; i < usedsize; ++i) { + if (i < usedsize - 1 && table[i + 1].syndrome == table[i].syndrome) { + // skip over this entry and all collisions + while (i < usedsize && table[i + 1].syndrome == table[i].syndrome) + ++i; + + // now table[i] is the last duplicate + continue; + } + + if (i != j) + table[j] = table[i]; + ++j; + } + + if (j < usedsize) { +#ifdef CRCDEBUG + fprintf(stderr, "Discarded %d collisions.\n", usedsize - j); +#endif + usedsize = j; + } + + // Flag collisions we want to detect but not correct + if (max_detect > max_correct) { + int flagged; + +#ifdef CRCDEBUG + fprintf(stderr, "Flagging collisions between %d - %d bits..\n", max_correct + 1, max_detect); +#endif + + flagged = flagCollisions(table, usedsize, 112 - bits, 5, bits, 0, 1, max_correct + 1, max_detect); + +#ifdef CRCDEBUG + fprintf(stderr, "Flagged %d collisions for removal.\n", flagged); +#else +#endif + + if (flagged > 0) { + for (i = 0, j = 0; i < usedsize; ++i) { + if (table[i].errors != -1) { + if (i != j) + table[j] = table[i]; + ++j; + } + } + +#ifdef CRCDEBUG + fprintf(stderr, "Discarded %d flagged collisions.\n", usedsize - j); +#endif + usedsize = j; + } + } + + if (usedsize < maxsize) { +#ifdef CRCDEBUG + fprintf(stderr, "Shrinking table from %d to %d..\n", maxsize, usedsize); + table = realloc(table, usedsize * sizeof (struct errorinfo)); +#endif + } + + *size_out = usedsize; + +#ifdef CRCDEBUG + { + // Check the table. + unsigned char *msg = cmalloc(bits / 8); + + for (i = 0; i < usedsize; ++i) { + int j; + struct errorinfo *ei; + uint32_t result; + + memset(msg, 0, bits / 8); + ei = &table[i]; + for (j = 0; j < ei->errors; ++j) { + msg[ei->bit[j] >> 3] ^= 1 << (7 - (ei->bit[j]&7)); + } + + result = modesChecksum(msg, bits); + if (result != ei->syndrome) { + fprintf(stderr, "PROBLEM: entry %6d/%6d syndrome %06x errors %d bits ", i, usedsize, ei->syndrome, ei->errors); + for (j = 0; j < ei->errors; ++j) + fprintf(stderr, "%3d ", ei->bit[j]); + fprintf(stderr, " checksum %06x\n", result); + } + } + free(msg); + + // Show the table stats + fprintf(stderr, "Syndrome table summary:\n"); + for (i = 1; i <= max_correct; ++i) { + int j, count, possible; + + count = 0; + for (j = 0; j < usedsize; ++j) + if (table[j].errors == i) + ++count; + + possible = combinations(bits - 5, i); + fprintf(stderr, " %d entries for %d-bit errors (%d possible, %d%% coverage)\n", count, i, possible, 100 * count / possible); + } + + fprintf(stderr, " %d entries total\n", usedsize); + } +#endif + + return table; +} + +// Precompute syndrome tables for 56- and 112-bit messages. +void modesChecksumInit(int fixBits) { + initLookupTables(); + + switch (fixBits) { + case 0: + bitErrorTable_short = bitErrorTable_long = NULL; + bitErrorTableSize_short = bitErrorTableSize_long = 0; + break; + + case 1: + // For 1 bit correction, we have 100% coverage up to 4 bit detection, so don't bother + // with flagging collisions there. + bitErrorTable_short = prepareErrorTable(MODES_SHORT_MSG_BITS, 1, 1, &bitErrorTableSize_short); + bitErrorTable_long = prepareErrorTable(MODES_LONG_MSG_BITS, 1, 1, &bitErrorTableSize_long); + break; + + default: + // Detect out to 4 bit errors; this reduces our 2-bit coverage to about 65%. + // This can take a little while - tell the user. + fprintf(stderr, "Preparing error correction tables.. "); + bitErrorTable_short = prepareErrorTable(MODES_SHORT_MSG_BITS, 2, 4, &bitErrorTableSize_short); + bitErrorTable_long = prepareErrorTable(MODES_LONG_MSG_BITS, 2, 4, &bitErrorTableSize_long); + fprintf(stderr, "done.\n"); + break; + } +} + +// Given an error syndrome and message length, return +// an error-correction descriptor, or NULL if the +// syndrome is uncorrectable +struct errorinfo *modesChecksumDiagnose(uint32_t syndrome, int bitlen) { + struct errorinfo *table; + int tablesize; + + struct errorinfo ei; + + if (syndrome == 0) + return &NO_ERRORS; + + assert(bitlen == 56 || bitlen == 112); + if (bitlen == 56) { + table = bitErrorTable_short; + tablesize = bitErrorTableSize_short; + } else { + table = bitErrorTable_long; + tablesize = bitErrorTableSize_long; + } + + if (!table) + return NULL; + + ei.syndrome = syndrome; + return bsearch(&ei, table, tablesize, sizeof (struct errorinfo), syndrome_compare); +} + +// Given a message and an error-correction descriptor, +// apply the error correction to the given message. +void modesChecksumFix(uint8_t *msg, struct errorinfo *info) { + int i; + + if (!info) + return; + + for (i = 0; i < info->errors; ++i) + msg[info->bit[i] >> 3] ^= 1 << (7 - (info->bit[i] & 7)); +} + +/* + * Clean CRC LUTs on exit. + * + */ +void crcCleanupTables(void) { + if (bitErrorTable_short != NULL) + free(bitErrorTable_short); + + if (bitErrorTable_long != NULL) + free(bitErrorTable_long); +} + +#ifdef CRCDEBUG + +int main(int argc, char **argv) { + int shortlen, longlen; + int i; + struct errorinfo *shorttable, *longtable; + + if (argc < 3) { + fprintf(stderr, "syntax: crctests \n"); + return 1; + } + + initLookupTables(); + shorttable = prepareErrorTable(MODES_SHORT_MSG_BITS, atoi(argv[1]), atoi(argv[2]), &shortlen); + longtable = prepareErrorTable(MODES_LONG_MSG_BITS, atoi(argv[1]), atoi(argv[2]), &longlen); + + // check for DF11 correction syndromes where there is a syndrome with lower 7 bits all zero + // (which would be used for DF11 error correction), but there's also a syndrome which has + // the same upper 17 bits but nonzero lower 7 bits. + + // empirically, with ncorrect=1 ndetect=2 we get no ambiguous syndromes; + // for ncorrect=2 ndetect=4 we get 11 ambiguous syndromes: + + /* + syndrome 1 = 000C00 bits=[ 44 45 ] + syndrome 2 = 000C1B bits=[ 30 43 ] + + syndrome 1 = 001400 bits=[ 43 45 ] + syndrome 2 = 00141B bits=[ 30 44 ] + + syndrome 1 = 001800 bits=[ 43 44 ] + syndrome 2 = 00181B bits=[ 30 45 ] + + syndrome 1 = 001800 bits=[ 43 44 ] + syndrome 2 = 001836 bits=[ 29 42 ] + + syndrome 1 = 002400 bits=[ 42 45 ] + syndrome 2 = 00242D bits=[ 29 30 ] + + syndrome 1 = 002800 bits=[ 42 44 ] + syndrome 2 = 002836 bits=[ 29 43 ] + + syndrome 1 = 003000 bits=[ 42 43 ] + syndrome 2 = 003036 bits=[ 29 44 ] + + syndrome 1 = 003000 bits=[ 42 43 ] + syndrome 2 = 00306C bits=[ 28 41 ] + + syndrome 1 = 004800 bits=[ 41 44 ] + syndrome 2 = 00485A bits=[ 28 29 ] + + syndrome 1 = 005000 bits=[ 41 43 ] + syndrome 2 = 00506C bits=[ 28 42 ] + + syndrome 1 = 006000 bits=[ 41 42 ] + syndrome 2 = 00606C bits=[ 28 43 ] + */ + + // So in the DF11 correction logic, we just discard messages that require more than a 1 bit fix. + + fprintf(stderr, "checking %d syndromes for DF11 collisions..\n", shortlen); + for (i = 0; i < shortlen; ++i) { + if ((shorttable[i].syndrome & 0xFF) == 0) { + int j; + // all syndromes with the same first 17 bits should sort immediately after entry i, + // so this is fairly easy + for (j = i + 1; j < shortlen; ++j) { + if ((shorttable[i].syndrome & 0xFFFF80) == (shorttable[j].syndrome & 0xFFFF80)) { + int k; + int mismatch = 0; + + // we don't care if the only differences are in bits that lie in the checksum + for (k = 0; k < shorttable[i].errors; ++k) { + int l, matched = 0; + + if (shorttable[i].bit[k] >= 49) + continue; // bit is in the final 7 bits, we don't care + + for (l = 0; l < shorttable[j].errors; ++l) { + if (shorttable[i].bit[k] == shorttable[j].bit[l]) { + matched = 1; + break; + } + } + + if (!matched) + mismatch = 1; + } + + for (k = 0; k < shorttable[j].errors; ++k) { + int l, matched = 0; + + if (shorttable[j].bit[k] >= 49) + continue; // bit is in the final 7 bits, we don't care + + for (l = 0; l < shorttable[i].errors; ++l) { + if (shorttable[j].bit[k] == shorttable[i].bit[l]) { + matched = 1; + break; + } + } + + if (!matched) + mismatch = 1; + } + + if (mismatch) { + fprintf(stderr, + "DF11 correction collision: \n" + " syndrome 1 = %06X bits=[", + shorttable[i].syndrome); + for (k = 0; k < shorttable[i].errors; ++k) + fprintf(stderr, " %d", shorttable[i].bit[k]); + fprintf(stderr, " ]\n"); + + fprintf(stderr, + " syndrome 2 = %06X bits=[", + shorttable[j].syndrome); + for (k = 0; k < shorttable[j].errors; ++k) + fprintf(stderr, " %d", shorttable[j].bit[k]); + fprintf(stderr, " ]\n"); + } + } else { + break; + } + } + } + } + + free(shorttable); + free(longtable); + + return 0; +} +#endif diff --git a/readsb2/crc.h b/readsb2/crc.h new file mode 100644 index 0000000..a5cda76 --- /dev/null +++ b/readsb2/crc.h @@ -0,0 +1,46 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// crc.h: Mode S checksum prototypes. +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2014,2015 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef DUMP1090_CRC_H +#define DUMP1090_CRC_H + +#include + +// Global max for fixable bit erros +#define MODES_MAX_BITERRORS 2 + +struct errorinfo +{ + uint32_t syndrome; // CRC syndrome + int errors; // number of errors + int8_t bit[MODES_MAX_BITERRORS]; // bit positions to fix (-1 = no bit) + uint16_t padding; +}; + +void modesChecksumInit (int fixBits); +uint32_t modesChecksum (uint8_t *msg, int bitlen); +struct errorinfo *modesChecksumDiagnose (uint32_t syndrome, int bitlen); +void modesChecksumFix (uint8_t *msg, struct errorinfo *info); +void crcCleanupTables (void); + +#endif diff --git a/readsb2/icao_filter.cpp b/readsb2/icao_filter.cpp new file mode 100644 index 0000000..ac1f4fd --- /dev/null +++ b/readsb2/icao_filter.cpp @@ -0,0 +1,154 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// icao_filter.c: hashtable for ICAO addresses +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2014,2015 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "readsb.h" + +// Open-addressed hash table with linear probing. + +// Maintain two tables and switch between them to age out entries. + +static uint32_t filterBits; +static uint32_t filterBuckets; +static size_t filterSize; +static uint32_t *icao_filter_a; +static uint32_t *icao_filter_b; +static uint32_t *icao_filter_active; + +static uint32_t occupied; + +static inline uint32_t filterHash(uint32_t addr) { + return addrHash(addr, filterBits); +} + +#define EMPTY 0xFFFFFFFF +#define MINBITS 8 +#define MAXBITS 20 + +void icaoFilterInit() { + filterBits = MINBITS; + filterBuckets = 1ULL << filterBits; + filterSize = filterBuckets * sizeof(uint32_t); + occupied = 0; + sfree(icao_filter_a); + sfree(icao_filter_b); + icao_filter_a = cmalloc(filterSize); + icao_filter_b = cmalloc(filterSize); + memset(icao_filter_a, 0xFF, filterSize); + memset(icao_filter_b, 0xFF, filterSize); + icao_filter_active = icao_filter_a; +} +void icaoFilterDestroy() { + sfree(icao_filter_a); + sfree(icao_filter_b); +} + +static void icaoFilterResize(uint32_t bits) { + uint32_t oldBuckets = filterBuckets; + uint32_t *oldActive = icao_filter_active; + uint32_t *oldA = icao_filter_a; + uint32_t *oldB = icao_filter_b; + + filterBits = bits; + filterBuckets = 1ULL << filterBits; + filterSize = filterBuckets * sizeof(uint32_t); + + if (filterBuckets > 256000) + fprintf(stderr, "icao_filter: changing size to %d!\n", (int) filterBuckets); + + icao_filter_a = cmalloc(filterSize); + icao_filter_b = cmalloc(filterSize); + memset(icao_filter_a, 0xFF, filterSize); + memset(icao_filter_b, 0xFF, filterSize); + + // reset occupied count + occupied = 0; + icao_filter_active = icao_filter_a; + for (uint32_t i = 0; i < oldBuckets; i++) { + if (oldActive[i] != EMPTY) { + icaoFilterAdd(oldActive[i]); + } + } + sfree(oldA); + sfree(oldB); +} + +// call this periodically: +void icaoFilterExpire() { + if (occupied < filterBuckets / 9 && filterBits > MINBITS) { + icaoFilterResize(filterBits - 1); + } + // reset occupied count + occupied = 0; + if (icao_filter_active == icao_filter_a) { + + memset(icao_filter_b, 0xFF, filterSize); + icao_filter_active = icao_filter_b; + } else { + memset(icao_filter_a, 0xFF, filterSize); + icao_filter_active = icao_filter_a; + } +} + +void icaoFilterAdd(uint32_t addr) { + uint32_t h, h0; + h0 = h = filterHash(addr); + while (icao_filter_active[h] != EMPTY && icao_filter_active[h] != addr) { + h = (h + 1) & (filterBuckets - 1); + if (h == h0) { + fprintf(stderr, "ICAO hash table full, this shouldn't happen\n"); + return; + } + } + if (icao_filter_active[h] == EMPTY) { + occupied++; + icao_filter_active[h] = addr; + } + + if (occupied > filterBuckets / 3 && filterBits < 20) { + icaoFilterResize(filterBits + 1); + } +} + +int icaoFilterTest(uint32_t addr) { + uint32_t h, h0; + + h0 = h = filterHash(addr); + while (icao_filter_a[h] != EMPTY && icao_filter_a[h] != addr) { + h = (h + 1) & (filterBuckets - 1); + if (h == h0) + break; + } + if (icao_filter_a[h] == addr) + return 1; + + h = h0; + while (icao_filter_b[h] != EMPTY && icao_filter_b[h] != addr) { + h = (h + 1) & (filterBuckets - 1); + if (h == h0) + break; + } + if (icao_filter_b[h] == addr) + return 1; + + return 0; +} diff --git a/readsb2/icao_filter.h b/readsb2/icao_filter.h new file mode 100644 index 0000000..746e9c7 --- /dev/null +++ b/readsb2/icao_filter.h @@ -0,0 +1,48 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// icao_filter.c: prototypes for ICAO address hashtable +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2014,2015 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef DUMP1090_ICAO_FILTER_H +#define DUMP1090_ICAO_FILTER_H + +#include + +// Call once: +void icaoFilterInit (); +void icaoFilterDestroy(); + +// Add an address to the filter +void icaoFilterAdd (uint32_t addr); + +// Test if the given address matches the filter +int icaoFilterTest (uint32_t addr); + +// Test if the top 16 bits match any previously added address. +// If they do, returns an arbitrary one of the matched +// addresses. Returns 0 on failure. +uint32_t icaoFilterTestFuzzy (uint32_t partial); + +// Call this periodically to allow the filter to expire +// old entries. +void icaoFilterExpire (); + +#endif diff --git a/readsb2/mode_ac.cpp b/readsb2/mode_ac.cpp new file mode 100644 index 0000000..c33797e --- /dev/null +++ b/readsb2/mode_ac.cpp @@ -0,0 +1,205 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// mode_ac.c: Mode A/C decoder. +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// This file incorporates work covered by the following copyright and +// license: +// Copyright (C) 2012 by Salvatore Sanfilippo +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "readsb.h" +#include "track.h" + +#include +// +//========================================================================= +// +// Input format is : 00:A4:A2:A1:00:B4:B2:B1:00:C4:C2:C1:00:D4:D2:D1 +// + +static int modeAToCTable[4096]; +static unsigned modeCToATable[4096]; +static int internalModeAToModeC(unsigned int ModeA); + +void modeACInit() { + for (unsigned i = 0; i < 4096; ++i) { + unsigned modeA = indexToModeA(i); + int modeC = internalModeAToModeC(modeA); + modeAToCTable[i] = modeC; + + modeC += 13; + if (modeC >= 0 && modeC < 4096) { + assert(modeCToATable[modeC] == 0); + modeCToATable[modeC] = modeA; + } + } +} + +// Given a mode A value (hex-encoded, see above) +// return the mode C value (signed multiple of 100s of feet) +// or INVALID_ALITITUDE if not a valid mode C value +int modeAToModeC(unsigned modeA) { + unsigned i = modeAToIndex(modeA); + if (i >= 4096) + return INVALID_ALTITUDE; + + return modeAToCTable[i]; +} + +// Given a mode C value (signed multiple of 100s of feet) +// return the mode A value, or 0 if not a valid mode C value +unsigned modeCToModeA(int modeC) { + modeC += 13; + if (modeC < 0 || modeC >= 4096) + return 0; + + return modeCToATable[modeC]; +} + +static int internalModeAToModeC(unsigned int ModeA) { + unsigned int FiveHundreds = 0; + unsigned int OneHundreds = 0; + + if ((ModeA & 0xFFFF8889) != 0 || // check zero bits are zero, D1 set is illegal + (ModeA & 0x000000F0) == 0) { // C1,,C4 cannot be Zero + return INVALID_ALTITUDE; + } + + if (ModeA & 0x0010) { + OneHundreds ^= 0x007; + } // C1 + if (ModeA & 0x0020) { + OneHundreds ^= 0x003; + } // C2 + if (ModeA & 0x0040) { + OneHundreds ^= 0x001; + } // C4 + + // Remove 7s from OneHundreds (Make 7->5, snd 5->7). + if ((OneHundreds & 5) == 5) { + OneHundreds ^= 2; + } + + // Check for invalid codes, only 1 to 5 are valid + if (OneHundreds > 5) { + return INVALID_ALTITUDE; + } + + //if (ModeA & 0x0001) {FiveHundreds ^= 0x1FF;} // D1 never used for altitude + if (ModeA & 0x0002) { + FiveHundreds ^= 0x0FF; + } // D2 + if (ModeA & 0x0004) { + FiveHundreds ^= 0x07F; + } // D4 + + if (ModeA & 0x1000) { + FiveHundreds ^= 0x03F; + } // A1 + if (ModeA & 0x2000) { + FiveHundreds ^= 0x01F; + } // A2 + if (ModeA & 0x4000) { + FiveHundreds ^= 0x00F; + } // A4 + + if (ModeA & 0x0100) { + FiveHundreds ^= 0x007; + } // B1 + if (ModeA & 0x0200) { + FiveHundreds ^= 0x003; + } // B2 + if (ModeA & 0x0400) { + FiveHundreds ^= 0x001; + } // B4 + + // Correct order of OneHundreds. + if (FiveHundreds & 1) { + OneHundreds = 6 - OneHundreds; + } + + return ((FiveHundreds * 5) + OneHundreds - 13); +} +// +//========================================================================= +// +void decodeModeAMessage(struct modesMessage *mm, int ModeA) { + mm->source = SOURCE_MODE_AC; + mm->addrtype = ADDR_MODE_A; + mm->msgtype = DFTYPE_MODEAC; // Valid Mode S DF's are DF-00 to DF-31. + // so use DFTYPE_MODEAC (77) to indicate Mode A/C + + mm->msgbits = 16; // Fudge up a Mode S style data stream + mm->msg[0] = mm->verbatim[0] = (ModeA >> 8); + mm->msg[1] = mm->verbatim[1] = (ModeA); + + // Fudge an address based on Mode A (remove the Ident bit) + mm->addr = (ModeA & 0x0000FF7F) | MODES_NON_ICAO_ADDRESS; + + // Set the Identity field to ModeA + mm->squawkHex = ModeA & 0x7777; + mm->squawkDec = squawkHex2Dec(mm->squawkHex); + mm->squawk_valid = 1; + + // Flag ident in flight status + mm->spi = (ModeA & 0x0080) ? 1 : 0; + mm->spi_valid = 1; + + // Decode an altitude if this looks like a possible mode C + if (!mm->spi) { + int modeC = modeAToModeC(ModeA); + if (modeC != INVALID_ALTITUDE) { + mm->baro_alt = modeC * 100; + mm->baro_alt_unit = UNIT_FEET; + mm->baro_alt_valid = 1; + } + } + + // Not much else we can tell from a Mode A/C reply. + // Just fudge up a few bits to keep other code happy + mm->correctedbits = 0; +} +// +// ===================== Mode A/C detection and decoding =================== +// diff --git a/readsb2/mode_s.cpp b/readsb2/mode_s.cpp new file mode 100644 index 0000000..36ee005 --- /dev/null +++ b/readsb2/mode_s.cpp @@ -0,0 +1,2251 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// mode_s.c: Mode S message decoding. +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2014-2016 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// This file incorporates work covered by the following copyright and +// license: +// +// Copyright (C) 2012 by Salvatore Sanfilippo +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ais_charset.h" +#include "readsb.h" + +/* for PRIX64 */ +#include + +#define M_PI 3.14159265358979323846 +// +// ===================== Mode S detection and decoding =================== +// +// +// + +// +//========================================================================= +// +// In the squawk (identity) field bits are interleaved as follows in +// (message bit 20 to bit 32): +// +// C1-A1-C2-A2-C4-A4-ZERO-B1-D1-B2-D2-B4-D4 +// +// So every group of three bits A, B, C, D represent an integer from 0 to 7. +// +// The actual meaning is just 4 octal numbers, but we convert it into a hex +// number tha happens to represent the four octal numbers. +// +// For more info: http://en.wikipedia.org/wiki/Gillham_code +// + +static int decodeID13Field(int ID13Field) { + int hexGillham = 0; + + if (ID13Field & 0x1000) {hexGillham |= 0x0010;} // Bit 12 = C1 + if (ID13Field & 0x0800) {hexGillham |= 0x1000;} // Bit 11 = A1 + if (ID13Field & 0x0400) {hexGillham |= 0x0020;} // Bit 10 = C2 + if (ID13Field & 0x0200) {hexGillham |= 0x2000;} // Bit 9 = A2 + if (ID13Field & 0x0100) {hexGillham |= 0x0040;} // Bit 8 = C4 + if (ID13Field & 0x0080) {hexGillham |= 0x4000;} // Bit 7 = A4 + //if (ID13Field & 0x0040) {hexGillham |= 0x0800;} // Bit 6 = X or M + if (ID13Field & 0x0020) {hexGillham |= 0x0100;} // Bit 5 = B1 + if (ID13Field & 0x0010) {hexGillham |= 0x0001;} // Bit 4 = D1 or Q + if (ID13Field & 0x0008) {hexGillham |= 0x0200;} // Bit 3 = B2 + if (ID13Field & 0x0004) {hexGillham |= 0x0002;} // Bit 2 = D2 + if (ID13Field & 0x0002) {hexGillham |= 0x0400;} // Bit 1 = B4 + if (ID13Field & 0x0001) {hexGillham |= 0x0004;} // Bit 0 = D4 + + return (hexGillham); +} + +// +//========================================================================= +// +// Decode the 13 bit AC altitude field (in DF 20 and others). +// Returns the altitude, and set 'unit' to either UNIT_METERS or UNIT_FEET. +// + +static int decodeAC13Field(int AC13Field, altitude_unit_t *unit, unsigned *mm_q_bit) { + int m_bit = AC13Field & 0x0040; // set = meters, clear = feet + int q_bit = AC13Field & 0x0010; // set = 25 ft encoding, clear = Gillham Mode C encoding + + if (q_bit) + *mm_q_bit = 1; + + if (!m_bit) { + *unit = UNIT_FEET; + if (q_bit) { + // N is the 11 bit integer resulting from the removal of bit Q and M + int n = ((AC13Field & 0x1F80) >> 2) | + ((AC13Field & 0x0020) >> 1) | + (AC13Field & 0x000F); + // The final altitude is resulting number multiplied by 25, minus 1000. + return ((n * 25) - 1000); + } else { + // N is an 11 bit Gillham coded altitude + int n = modeAToModeC(decodeID13Field(AC13Field)); + if (n < -12) { + return INVALID_ALTITUDE; + } + + return (100 * n); + } + } else { + *unit = UNIT_METERS; + // TODO: Implement altitude when meter unit is selected + return INVALID_ALTITUDE; + } +} + +// +//========================================================================= +// +// Decode the 12 bit AC altitude field (in DF 17 and others). +// + +static int decodeAC12Field(int AC12Field, altitude_unit_t *unit, unsigned *mm_q_bit) { + int q_bit = AC12Field & 0x10; // Bit 48 = Q + + if (q_bit) + *mm_q_bit = 1; + + *unit = UNIT_FEET; + if (q_bit) { + /// N is the 11 bit integer resulting from the removal of bit Q at bit 4 + int n = ((AC12Field & 0x0FE0) >> 1) | + (AC12Field & 0x000F); + // The final altitude is the resulting number multiplied by 25, minus 1000. + return ((n * 25) - 1000); + } else { + // Make N a 13 bit Gillham coded altitude by inserting M=0 at bit 6 + int n = ((AC12Field & 0x0FC0) << 1) | + (AC12Field & 0x003F); + n = modeAToModeC(decodeID13Field(n)); + if (n < -12) { + return INVALID_ALTITUDE; + } + + return (100 * n); + } +} + +// +//========================================================================= +// +// Decode the 7 bit ground movement field PWL exponential style scale (ADS-B v2) +// + +static float decodeMovementFieldV2(unsigned movement) { + // Note : movement codes 0,125,126,127 are all invalid, but they are + // trapped for before this function is called. + + // Each movement value is a range of speeds; + // we return the midpoint of the range (rounded to the nearest integer) + if (movement >= 125) return 0; // invalid + else if (movement == 124) return 180; // gs > 175kt, pick a value.. + else if (movement >= 109) return 100 + (movement - 109 + 0.5) * 5; // 100 < gs <= 175 in 5kt steps + else if (movement >= 94) return 70 + (movement - 94 + 0.5) * 2; // 70 < gs <= 100 in 2kt steps + else if (movement >= 39) return 15 + (movement - 39 + 0.5) * 1; // 15 < gs <= 70 in 1kt steps + else if (movement >= 13) return 2 + (movement - 13 + 0.5) * 0.50; // 2 < gs <= 15 in 0.5kt steps + else if (movement >= 9) return 1 + (movement - 9 + 0.5) * 0.25; // 1 < gs <= 2 in 0.25kt steps + else if (movement >= 3) return 0.125 + (movement - 3 + 0.5) * 0.875 / 6; // 0.125 < gs <= 1 in 0.875/6 kt step + else if (movement >= 2) return 0.125 / 2; // 0 < gs <= 0.125 + // 1: stopped, gs = 0 + // 0: no data + else return 0; +} + +// +//========================================================================= +// +// Decode the 7 bit ground movement field PWL exponential style scale (ADS-B v0) +// + +static float decodeMovementFieldV0(unsigned movement) { + // Note : movement codes 0,125,126,127 are all invalid, but they are + // trapped for before this function is called. + + // Each movement value is a range of speeds; + // we return the midpoint of the range + if (movement >= 125) return 0; // invalid + else if (movement == 124) return 180; // gs >= 175kt, pick a value.. + else if (movement >= 109) return 100 + (movement - 109 + 0.5) * 5; // 100 < gs <= 175 in 5kt steps + else if (movement >= 94) return 70 + (movement - 94 + 0.5) * 2; // 70 < gs <= 100 in 2kt steps + else if (movement >= 39) return 15 + (movement - 39 + 0.5) * 1; // 15 < gs <= 70 in 1kt steps + else if (movement >= 13) return 2 + (movement - 13 + 0.5) * 0.50; // 2 < gs <= 15 in 0.5kt steps + else if (movement >= 9) return 1 + (movement - 9 + 0.5) * 0.25; // 1 < gs <= 2 in 0.25kt steps + else if (movement >= 2) return 0.125 + (movement - 2 + 0.5) * 0.125; // 0.125 < gs <= 1 in 0.125kt step + // 1: stopped, gs < 0.125kt + // 0: no data + else return 0; +} + +// Correct a decoded native-endian Address Announced field +// (from bits 8-31) if it is affected by the given error +// syndrome. Updates *addr and returns >0 if changed, 0 if +// it was unaffected. + +static int correct_aa_field(uint32_t *addr, struct errorinfo *ei) { + int i; + int addr_errors = 0; + + if (!ei) + return 0; + + for (i = 0; i < ei->errors; ++i) { + if (ei->bit[i] >= 8 && ei->bit[i] <= 31) { + *addr ^= 1 << (31 - ei->bit[i]); + ++addr_errors; + } + } + + return addr_errors; +} + +// Score how plausible this ModeS message looks. +// The more positive, the more reliable the message is + +// 1000: DF 0/4/5/16/24 with a CRC-derived address matching a known aircraft + +// 1800: DF17/18 with good CRC and an address matching a known aircraft +// 1400: DF17/18 with good CRC and an address not matching a known aircraft +// 900: DF17/18 with 1-bit error and an address matching a known aircraft +// 700: DF17/18 with 1-bit error and an address not matching a known aircraft +// 450: DF17/18 with 2-bit error and an address matching a known aircraft +// 350: DF17/18 with 2-bit error and an address not matching a known aircraft + +// 1600: DF11 with IID==0, good CRC and an address matching a known aircraft +// 1000: DF11 with IID!=0, good CRC and an address matching a known aircraft +// 800: DF11 with 1-bit error and an address matching a known aircraft +// 750: DF11 with IID==0, good CRC and an address not matching a known aircraft + + +// 1000: DF20/21 with a CRC-derived address matching a known aircraft +// 500: DF20/21 with a CRC-derived address matching a known aircraft (bottom 16 bits only - overlay control in use) + +// -1: message might be valid, but we couldn't validate the CRC against a known ICAO +// -2: bad message or unrepairable CRC error + +static unsigned char all_zeros[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + +// fix possible 1 bit errors in the DF type for DF17 only +// return orig first msg byte if we changed the msgtype / first byte +static inline unsigned char fixDF17msgtype(unsigned char *msg, int *msgtype) { + if (!Modes.fixDF || !Modes.nfix_crc) { + return 0; + } + unsigned char origByte; + switch (*msgtype) { + // possible values for msgtype with a single bit error of DF17 + case 1: case 25: case 21: case 19: case 16: + + origByte = msg[0]; + msg[0] &= 7; // keep last 3 bits, set first 5 bits to 0 + msg[0] |= (17 << 3); // set first 5 bits to a value of 17 + + int res = modesChecksum(msg, MODES_LONG_MSG_BITS); + if (res == 0) { + *msgtype = 17; + return origByte; + } + + msg[0] = origByte; + return 0; + + default: + return 0; + } +} + +static void setSquawkFromID13(struct modesMessage *mm, int ID13Field) { + mm->squawkHex = decodeID13Field(ID13Field); + mm->squawkDec = squawkHex2Dec(mm->squawkHex); + mm->squawk_valid = 1; +} + +int scoreModesMessage(unsigned char *msg, int validbits) { + int msgtype, msgbits, crc, iid; + uint32_t addr; + struct errorinfo *ei; + + if (validbits < 56) + return -2; + + msgtype = getbits(msg, 1, 5); // Downlink Format + + if (validbits >= MODES_LONG_MSG_BITS) { + unsigned char origByte = fixDF17msgtype(msg, &msgtype); + if (origByte) { + msg[0] = origByte; // restore byte + // DF17 message with 1 bit error, return the correct score + if (icaoFilterTest(getbits(msg, 9, 32))) // check addr + return 1800 / 2; + else + return 1400 / 2; + } + } + + msgbits = modesMessageLenByType(msgtype); + + if (validbits < msgbits) + return -2; + + // discard messages which have only zeros in the first short msg bytes + if (!memcmp(all_zeros, msg, MODES_SHORT_MSG_BYTES)) + return -2; + + crc = modesChecksum(msg, msgbits); + + switch (msgtype) { + case 0: // short air-air surveillance + case 4: // surveillance, altitude reply + case 5: // surveillance, altitude reply + case 16: // long air-air surveillance + case 20: // Comm-B, altitude reply + case 21: // Comm-B, identity reply +#ifdef ENABLE_DF24 + case 24: // Comm-D (ELM) + case 25: // Comm-D (ELM) + case 26: // Comm-D (ELM) + case 27: // Comm-D (ELM) + case 28: // Comm-D (ELM) + case 29: // Comm-D (ELM) + case 30: // Comm-D (ELM) + case 31: // Comm-D (ELM) +#endif + return icaoFilterTest(crc) ? 1000 : -1; + + case 11: // All-call reply + iid = crc & 0x7f; + addr = getbits(msg, 9, 32); + + if (crc & 0xffff80) { + // Try to diagnose based on the _full_ CRC + // i.e. under the assumption that IID = 0 + ei = modesChecksumDiagnose(crc, msgbits); + if (!ei) + return -2; // can't correct errors + + // see crc.c comments: we do not attempt to fix + // more than single-bit errors, as two-bit + // errors are ambiguous in DF11. + if (ei->errors > 1) + return -2; // can't correct errors + + // fix any errors in the address field + correct_aa_field(&addr, ei); + + // here, IID = 0 implicitly + if (icaoFilterTest(addr)) + return 800; + else + return -1; + } + + // CRC was correct (ish) + if (iid == 0) { + if (icaoFilterTest(addr)) + return 1600; + else + return 750; + } else { // iid != 0 + if (icaoFilterTest(addr)) + return 1000; + else + return -1; + } + + case 17: // Extended squitter + case 18: // Extended squitter/non-transponder + ei = modesChecksumDiagnose(crc, msgbits); + if (!ei) + return -2; // can't correct errors + + // fix any errors in the address field + addr = getbits(msg, 9, 32); + correct_aa_field(&addr, ei); + + if (icaoFilterTest(addr)) + return 1800 / (ei->errors + 1); + else + return 1400 / (ei->errors + 1); + default: + // unknown message type + return -2; + } +} + +// +//========================================================================= +// +// Decode a raw Mode S message demodulated as a stream of bytes by detectModeS(), +// and split it into fields populating a modesMessage structure. +// + +static void decodeExtendedSquitter(struct modesMessage *mm); + +// return 0 if all OK +// -1: message might be valid, but we couldn't validate the CRC against a known ICAO +// -2: bad message or unrepairable CRC error + + +#define decode_return(X) \ + do { \ + mm->decodeResult = X; \ + if (!Modes.decode_all) { \ + return mm->decodeResult; \ + } \ + } while(0) + +int decodeModesMessage(struct modesMessage *mm) { + if (Modes.net_verbatim) { + // Preserve the original uncorrected copy for later forwarding + memcpy(mm->verbatim, mm->msg, MODES_LONG_MSG_BYTES); + } + unsigned char* msg = mm->msg; + mm->decodeResult = 0; + + // discard messages which have only zeros in the first short msg bytes + if (!memcmp(all_zeros, msg, MODES_SHORT_MSG_BYTES)) { + decode_return(-2); + } + + // Get the message type ASAP as other operations depend on this + mm->msgtype = getbits(msg, 1, 5); // Downlink Format + mm->correctedbits = 0; + + // try and fix possible bit errors in DF type that could be DF17 + if (fixDF17msgtype(msg, &mm->msgtype)) { + mm->correctedbits = 1; + } + + mm->msgbits = modesMessageLenByType(mm->msgtype); + mm->crc = modesChecksum(msg, mm->msgbits); + mm->addr = HEX_UNKNOWN; // set non zero default address + mm->maybe_addr = HEX_UNKNOWN; // set non zero default address + mm->addrtype = ADDR_UNKNOWN; + + // Do checksum work and set fields that depend on the CRC + switch (mm->msgtype) { + case 0: // short air-air surveillance + case 4: // surveillance, altitude reply + case 5: // surveillance, altitude reply + case 16: // long air-air surveillance + case 24: // Comm-D (ELM) + case 25: // Comm-D (ELM) + case 26: // Comm-D (ELM) + case 27: // Comm-D (ELM) + case 28: // Comm-D (ELM) + case 29: // Comm-D (ELM) + case 30: // Comm-D (ELM) + case 31: // Comm-D (ELM) + // These message types use Address/Parity, i.e. our CRC syndrome is the sender's ICAO address. + // We can't tell if the CRC is correct or not as we don't know the correct address. + // Accept the message if it appears to be from a previously-seen aircraft + mm->maybe_addr = mm->crc; + if (!icaoFilterTest(mm->crc)) { + decode_return(-1); + } + mm->source = SOURCE_MODE_S; + mm->addr = mm->crc; + mm->addrtype = ADDR_MODE_S; + break; + + case 11: // All-call reply + // This message type uses Parity/Interrogator, i.e. our CRC syndrome is CL + IC from the uplink message + // which we can't see. So we don't know if the CRC is correct or not. + // + // however! CL + IC only occupy the lower 7 bits of the CRC. So if we ignore those bits when testing + // the CRC we can still try to detect/correct errors. + + mm->IID = mm->crc & 0x7f; + mm->maybe_addr = getbits(msg, 9, 32); + if (mm->crc & 0xffff80) { + // Try to diagnose based on the _full_ CRC + // i.e. under the assumption that IID = 0 + + struct errorinfo *ei = modesChecksumDiagnose(mm->crc, mm->msgbits); + if (!ei) { + decode_return(-2); // couldn't fix it + } else { + // see crc.c comments: we do not attempt to fix + // more than single-bit errors, as two-bit + // errors are ambiguous in DF11. + if (ei->errors > 1) { + decode_return(-2); // can't correct errors + } else { + mm->correctedbits = ei->errors; + mm->IID = 0; + modesChecksumFix(msg, ei); + + // check whether the corrected message looks sensible + // we are conservative here: only accept corrected messages that + // match an existing aircraft. + int addr = getbits(msg, 9, 32); + mm->maybe_addr = addr; + if (!icaoFilterTest(addr)) { + decode_return(-1); + } + } + } + } + mm->source = SOURCE_MODE_S_CHECKED; + mm->addrtype = ADDR_MODE_S; + break; + + case 17: // Extended squitter + case 18: + { // Extended squitter/non-transponder + struct errorinfo *ei; + int addr1, addr2; + + // These message types use Parity/Interrogator, but are specified to set II=0 + + if (mm->crc != 0) { + ei = modesChecksumDiagnose(mm->crc, mm->msgbits); + if (!ei) { + decode_return(-2); // couldn't fix it + } else { + addr1 = getbits(msg, 9, 32); + mm->correctedbits = ei->errors; + modesChecksumFix(msg, ei); + addr2 = getbits(msg, 9, 32); + + mm->maybe_addr = addr2; + // we are conservative here: only accept corrected messages that + // match an existing aircraft. + if (addr1 != addr2 && !icaoFilterTest(addr2)) { + decode_return(-1); + } + } + } + + mm->addrtype = ADDR_ADSB_ICAO; + mm->source = SOURCE_ADSB; // TIS-B decoding will override this if needed + break; + } + + case 20: // Comm-B, altitude reply + case 21: // Comm-B, identity reply + // These message types either use Address/Parity (see DF0 etc) + // or Data Parity where the requested BDS is also xored into the top byte. + // So not only do we not know whether the CRC is right, we also don't know if + // the ICAO is right! Ow. + + mm->maybe_addr = mm->crc; + // Try an exact match + if (icaoFilterTest(mm->crc)) { + // OK. + mm->addrtype = ADDR_MODE_S; + mm->source = SOURCE_MODE_S; + mm->addr = mm->crc; + break; + } + + // BDS / overlay control just doesn't work out. + + decode_return(-1); // no good + break; + + default: + // All other message types, we don't know how to handle their CRCs, give up + decode_return(-2); + } + + // decode the bulk of the message + + // AA (Address announced) + if (mm->msgtype == 11 || mm->msgtype == 17 || mm->msgtype == 18) { + mm->AA = getbits(msg, 9, 32); + mm->maybe_addr = mm->AA; + if (mm->decodeResult == 0) + mm->addr = mm->AA; + } + + // AC (Altitude Code) + if (mm->msgtype == 0 || mm->msgtype == 4 || mm->msgtype == 16 || mm->msgtype == 20) { + mm->AC = getbits(msg, 20, 32); + if (mm->AC) { // Only attempt to decode if a valid (non zero) altitude is present + unsigned q_bit = 0; + mm->baro_alt = decodeAC13Field(mm->AC, &mm->baro_alt_unit, &q_bit); + if (mm->baro_alt != INVALID_ALTITUDE) { + mm->alt_q_bit = q_bit; + mm->baro_alt_valid = 1; + } + } + } + + // AF (DF19 Application Field) not decoded + + // CA (Capability) + if (mm->msgtype == 11 || mm->msgtype == 17) { + mm->CA = getbits(msg, 6, 8); + + switch (mm->CA) { + case 0: + mm->airground = AG_UNCERTAIN; + break; + case 4: + mm->airground = AG_GROUND; + break; + case 5: + mm->airground = AG_AIRBORNE; + break; + case 6: + mm->airground = AG_UNCERTAIN; + break; + case 7: + mm->airground = AG_UNCERTAIN; + break; + } + } + + // CC (Cross-link capability) + if (mm->msgtype == 0) { + mm->CC = getbit(msg, 7); + } + + // CF (Control field) + if (mm->msgtype == 18) { + mm->CF = getbits(msg, 6, 8); + } + + // DR (Downlink Request) + if (mm->msgtype == 4 || mm->msgtype == 5 || mm->msgtype == 20 || mm->msgtype == 21) { + mm->DR = getbits(msg, 9, 13); + } + + // FS (Flight Status) + if (mm->msgtype == 4 || mm->msgtype == 5 || mm->msgtype == 20 || mm->msgtype == 21) { + mm->FS = getbits(msg, 6, 8); + mm->alert_valid = 1; + mm->spi_valid = 1; + + switch (mm->FS) { + case 0: + mm->airground = AG_UNCERTAIN; + break; + case 1: + mm->airground = AG_GROUND; + break; + case 2: + mm->airground = AG_UNCERTAIN; + mm->alert = 1; + break; + case 3: + mm->airground = AG_GROUND; + mm->alert = 1; + break; + case 4: + mm->airground = AG_UNCERTAIN; + mm->alert = 1; + mm->spi = 1; + break; + case 5: + mm->airground = AG_UNCERTAIN; + mm->spi = 1; + break; + default: + mm->spi_valid = 0; + mm->alert_valid = 0; + break; + } + } + + // ID (Identity) + if (mm->msgtype == 5 || mm->msgtype == 21) { + // Gillham encoded Squawk + mm->ID = getbits(msg, 20, 32); + if (mm->ID) { + setSquawkFromID13(mm, mm->ID); + } + } + + // KE (Control, ELM) + if (mm->msgtype >= 24 && mm->msgtype <= 31) { + mm->KE = getbit(msg, 4); + } + + // MB (messsage, Comm-B) + if (mm->msgtype == 20 || mm->msgtype == 21) { + memcpy(mm->MB, &msg[4], 7); + decodeCommB(mm); + } + + // MD (message, Comm-D) + if (mm->msgtype >= 24 && mm->msgtype <= 31) { + memcpy(mm->MD, &msg[1], 10); + } + + // ME (message, extended squitter) + if (mm->msgtype == 17 || mm->msgtype == 18) { + memcpy(mm->ME, &msg[4], 7); + decodeExtendedSquitter(mm); + } + + // MV (message, ACAS) + if (mm->msgtype == 16) { + memcpy(mm->MV, &msg[4], 7); + if (mm->MV[0] == 0x30) { + mm->acas_ra_valid = 1; + } + } + + // ND (number of D-segment, Comm-D) + if (mm->msgtype >= 24 && mm->msgtype <= 31) { + mm->ND = getbits(msg, 5, 8); + } + + // RI (Reply information, ACAS) + if (mm->msgtype == 0 || mm->msgtype == 16) { + mm->RI = getbits(msg, 14, 17); + } + + // SL (Sensitivity level, ACAS) + if (mm->msgtype == 0 || mm->msgtype == 16) { + mm->SL = getbits(msg, 9, 11); + } + + // UM (Utility Message) + if (mm->msgtype == 4 || mm->msgtype == 5 || mm->msgtype == 20 || mm->msgtype == 21) { + mm->UM = getbits(msg, 14, 19); + } + + // VS (Vertical Status) + if (mm->msgtype == 0 || mm->msgtype == 16) { + mm->VS = getbit(msg, 6); + if (mm->VS) + mm->airground = AG_GROUND; + else + mm->airground = AG_UNCERTAIN; + } + + if (mm->decodeResult == 0 + && !mm->correctedbits + && (mm->msgtype == 17 || (mm->msgtype == 11 && mm->IID == 0)) + ) + { + // No CRC errors seen, and either it was an DF17 extended squitter + // or a DF11 acquisition squitter with II = 0. We probably have the right address. + + // Don't do this for DF18, as a DF18 transmitter doesn't necessarily have a + // Mode S transponder. + + // NB this is the only place that adds addresses! + icaoFilterAdd(mm->addr); + } + + // MLAT overrides all other sources + if (mm->remote && mm->timestamp == MAGIC_MLAT_TIMESTAMP) { + mm->source = SOURCE_MLAT; + mm->addrtype = ADDR_MLAT; + } + + // these are messages of general bad quality, treat them as garbage when garbage_ports is in use. + if ((Modes.netIngest || Modes.garbage_ports) && mm->remote && mm->timestamp == 0 && (mm->msgtype != 18 || mm->addrtype == ADDR_ADSB_ICAO_NT)) { + mm->garbage = 1; + mm->source = SOURCE_SBS; + if (mm->addrtype >= ADDR_OTHER) + mm->addrtype = ADDR_OTHER; + } + // ignore DF18 from this hexrange, bogus hexes set + // i'd like to not have such exceptions in this source but rather configure them some other way + // for the time being still gonna do it this way + if (mm->remote && mm->msgtype == 18 && mm->addr >= 0x899000 && mm->addr < 0x899200 && Modes.garbage_ports) { + mm->garbage = 1; + } + + // all done + return mm->decodeResult; +} +#undef decode_return + +static void decodeESIdentAndCategory(struct modesMessage *mm) { + // Aircraft Identification and Category + unsigned char *me = mm->ME; + + mm->mesub = getbits(me, 6, 8); + + char *callsign = mm->callsign; + + callsign[0] = ais_charset[getbits(me, 9, 14)]; + callsign[1] = ais_charset[getbits(me, 15, 20)]; + callsign[2] = ais_charset[getbits(me, 21, 26)]; + callsign[3] = ais_charset[getbits(me, 27, 32)]; + callsign[4] = ais_charset[getbits(me, 33, 38)]; + callsign[5] = ais_charset[getbits(me, 39, 44)]; + callsign[6] = ais_charset[getbits(me, 45, 50)]; + callsign[7] = ais_charset[getbits(me, 51, 56)]; + 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; + } + } + if (mm->callsign_valid == 0 && Modes.debug_callsign) { + fprintf(stderr, "%06x %8s (len: %d)\n", mm->addr, callsign, (int) strlen(callsign)); + } + + mm->category = ((0x0E - mm->metype) << 4) | mm->mesub; + mm->category_valid = 1; +} + +// Handle setting a non-ICAO address + +static void setIMF(struct modesMessage *mm) { + mm->addr |= MODES_NON_ICAO_ADDRESS; + switch (mm->addrtype) { + case ADDR_ADSB_ICAO: + case ADDR_ADSB_ICAO_NT: + // Shouldn't happen, but let's try to handle it + mm->addrtype = ADDR_ADSB_OTHER; + break; + + case ADDR_TISB_ICAO: + mm->addrtype = ADDR_TISB_TRACKFILE; + break; + + case ADDR_ADSR_ICAO: + mm->addrtype = ADDR_ADSR_OTHER; + break; + + default: + // Nothing. + break; + } +} + +static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf) { + // Airborne Velocity Message + unsigned char *me = mm->ME; + + // 1-5: ME type + // 6-8: ME subtype + mm->mesub = getbits(me, 6, 8); + + if (mm->mesub < 1 || mm->mesub > 4) + return; + + // 9: IMF or Intent Change + if (check_imf && getbit(me, 9)) + setIMF(mm); + + // 10: reserved + + // 11-13: NACv (NUCr in v0, maps directly to NACv in v2) + mm->accuracy.nac_v_valid = 1; + mm->accuracy.nac_v = getbits(me, 11, 13); + + // 14-35: speed/velocity depending on subtype + switch (mm->mesub) { + case 1: case 2: + { + // 14: E/W direction + // 15-24: E/W speed + // 25: N/S direction + // 26-35: N/S speed + unsigned ew_raw = getbits(me, 15, 24); + unsigned ns_raw = getbits(me, 26, 35); + + if (ew_raw && ns_raw) { + int ew_vel = (ew_raw - 1) * (getbit(me, 14) ? -1 : 1) * ((mm->mesub == 2) ? 4 : 1); + int ns_vel = (ns_raw - 1) * (getbit(me, 25) ? -1 : 1) * ((mm->mesub == 2) ? 4 : 1); + + // Compute velocity and angle from the two speed components + mm->gs.v0 = mm->gs.v2 = mm->gs.selected = sqrtf((ns_vel * ns_vel) + (ew_vel * ew_vel) + 0.5); + mm->gs_valid = 1; + + if (mm->gs.selected > 0) { + float ground_track = atan2f(ew_vel, ns_vel) * 180.0f / (float) M_PI; + // We don't want negative values but a 0-360 scale + if (ground_track < 0) + ground_track += 360; + mm->heading = ground_track; + mm->heading_type = HEADING_GROUND_TRACK; + mm->heading_valid = 1; + } + } + break; + } + + case 3: case 4: + { + // 14: heading status + // 15-24: heading + if (getbit(me, 14)) { + mm->heading_valid = 1; + mm->heading = getbits(me, 15, 24) * 360.0 / 1024.0; + mm->heading_type = HEADING_MAGNETIC_OR_TRUE; + } + + // 25: airspeed type + // 26-35: airspeed + unsigned airspeed = getbits(me, 26, 35); + if (airspeed) { + unsigned speed = (airspeed - 1) * (mm->mesub == 4 ? 4 : 1); + if (getbit(me, 25)) { + mm->tas_valid = 1; + mm->tas = speed; + } else { + mm->ias_valid = 1; + mm->ias = speed; + } + } + + break; + } + } + + // 36: vert rate source + // 37: vert rate sign + // 38-46: vert rate magnitude + unsigned vert_rate = getbits(me, 38, 46); + unsigned vert_rate_is_baro = getbit(me, 36); + if (vert_rate) { + int rate = (vert_rate - 1) * (getbit(me, 37) ? -64 : 64); + if (vert_rate_is_baro) { + mm->baro_rate = rate; + mm->baro_rate_valid = 1; + } else { + mm->geom_rate = rate; + mm->geom_rate_valid = 1; + } + } + + // 47-48: reserved + + // 49: baro/geom delta sign + // 50-56: baro/geom delta magnitude + unsigned raw_delta = getbits(me, 50, 56); + if (raw_delta) { + mm->geom_delta_valid = 1; + mm->geom_delta = (raw_delta - 1) * (getbit(me, 49) ? -25 : 25); + } +} + +static void decodeESSurfacePosition(struct modesMessage *mm, int check_imf) { + // Surface position and movement + unsigned char *me = mm->ME; + + mm->airground = AG_GROUND; // definitely. + mm->cpr_valid = 1; + mm->cpr_type = CPR_SURFACE; + + // 6-12: Movement + unsigned movement = getbits(me, 6, 12); + if (movement > 0 && movement < 125) { + mm->gs_valid = 1; + mm->gs.selected = mm->gs.v0 = decodeMovementFieldV0(movement); // assumed v0 until told otherwise + mm->gs.v2 = decodeMovementFieldV2(movement); + } + + // 13: Heading/track status + // 14-20: Heading/track + if (getbit(me, 13)) { + mm->heading_valid = 1; + mm->heading = getbits(me, 14, 20) * 360.0 / 128.0; + mm->heading_type = HEADING_TRACK_OR_HEADING; + } + + // 21: IMF or T flag + if (check_imf && getbit(me, 21)) + setIMF(mm); + + // 22: F flag (odd/even) + mm->cpr_odd = getbit(me, 22); + + // 23-39: CPR encoded latitude + mm->cpr_lat = getbits(me, 23, 39); + // 40-56: CPR encoded longitude + mm->cpr_lon = getbits(me, 40, 56); +} + +static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf) { + // Airborne position and altitude + unsigned char *me = mm->ME; + + // 6-7: surveillance status + switch (getbits(me, 6, 7)) { + case 0: + // no status + mm->alert_valid = mm->spi_valid = 1; + mm->alert = mm->spi = 0; + break; + case 1: // permanent alert + case 2: // temporary alert + mm->alert_valid = 1; + mm->alert = 1; + // states 1/2 override state 3, so we don't know SPI status here. + break; + case 3: // SPI + // we know there's no alert in this case + mm->alert_valid = mm->spi_valid = 1; + mm->alert = 0; + mm->spi = 1; + break; + } + + // 8: IMF or NIC supplement-B + + if (check_imf) { + if (getbit(me, 8)) + setIMF(mm); + } else { + // NIC-B (v2) or SAF (v0/v1) + mm->accuracy.nic_b_valid = 1; + mm->accuracy.nic_b = getbit(me, 8); + } + + // 9-20: altitude + unsigned AC12Field = getbits(me, 9, 20); + + if (mm->metype == 0) { + // no position information + } else { + // 21: T flag (UTC sync or not) + // 22: F flag (odd or even) + // 23-39: CPR encoded latitude + // 40-56: CPR encoded longitude + + mm->cpr_lat = getbits(me, 23, 39); + mm->cpr_lon = getbits(me, 40, 56); + + // Catch some common failure modes and don't mark them as valid + // (so they won't be used for positioning) + if (AC12Field == 0 && mm->cpr_lon == 0 && (mm->cpr_lat & 0x0fff) == 0 && mm->metype == 15) { + // Seen from at least: + // 400F3F (Eurocopter ECC155 B1) - Bristow Helicopters + // 4008F3 (BAE ATP) - Atlantic Airlines + // 400648 (BAE ATP) - Atlantic Airlines + // altitude == 0, longitude == 0, type == 15 and zeros in latitude LSB. + // Can alternate with valid reports having type == 14 + Modes.stats_current.cpr_filtered++; + } else { + // Otherwise, assume it's valid. + mm->cpr_valid = 1; + mm->cpr_type = CPR_AIRBORNE; + mm->cpr_odd = getbit(me, 22); + } + } + + if (AC12Field && mm->airground != AG_GROUND) {// Only attempt to decode if a valid (non zero) altitude is present and not on ground + altitude_unit_t unit; + unsigned q_bit = 0; + int alt = decodeAC12Field(AC12Field, &unit, &q_bit); + if (alt != INVALID_ALTITUDE) { + mm->alt_q_bit = q_bit; + if (mm->metype == 20 || mm->metype == 21 || mm->metype == 22) { + mm->geom_alt = alt; + mm->geom_alt_unit = unit; + mm->geom_alt_valid = 1; + } else { + mm->baro_alt = alt; + mm->baro_alt_unit = unit; + mm->baro_alt_valid = 1; + } + } + } +} + +static void decodeESTestMessage(struct modesMessage *mm) { + unsigned char *me = mm->ME; + + mm->mesub = getbits(me, 6, 8); + + if (mm->mesub == 7) { // (see 1090-WP-15-20) + int ID13Field = getbits(me, 9, 21); + if (ID13Field) { + setSquawkFromID13(mm, ID13Field); + } + } +} + +static void decodeESAircraftStatus(struct modesMessage *mm, int check_imf) { + // Extended Squitter Aircraft Status (mm->metype == 28) + unsigned char *me = mm->ME; + + mm->mesub = getbits(me, 6, 8); + + if (mm->mesub == 1) { // Emergency status squawk field + mm->emergency_valid = 1; + mm->emergency = (emergency_t) getbits(me, 9, 11); + + unsigned ID13Field = getbits(me, 12, 24); + if (ID13Field) { + setSquawkFromID13(mm, ID13Field); + } + + if (check_imf && getbit(me, 56)) + setIMF(mm); + } + + if (mm->mesub == 2) { // ACAS RA Broadcast + mm->acas_ra_valid = 1; + } +} + +static void decodeESTargetStatus(struct modesMessage *mm, int check_imf) { + unsigned char *me = mm->ME; + + mm->mesub = getbits(me, 6, 7); // an unusual message: only 2 bits of subtype + + if (check_imf && getbit(me, 51)) + setIMF(mm); + + if (mm->mesub == 0 && getbit(me, 11) == 0) { // Target state and status, V1 + // 8-9: vertical source + switch (getbits(me, 8, 9)) { + case 1: + mm->nav.altitude_source = NAV_ALT_MCP; + break; + case 2: + mm->nav.altitude_source = NAV_ALT_AIRCRAFT; + break; + case 3: + mm->nav.altitude_source = NAV_ALT_FMS; + break; + default: + // nothing + break; + } + // 10: target altitude type (MSL or Baro, ignored) + // 11: backward compatibility bit, always 0 + // 12-13: target alt capabilities (ignored) + // 14-15: vertical mode + switch (getbits(me, 14, 15)) { + case 1: // "acquiring" + mm->nav.modes_valid = 1; + if (mm->nav.altitude_source == NAV_ALT_FMS) { + mm->nav.modes |= NAV_MODE_VNAV; + } else { + mm->nav.modes |= NAV_MODE_AUTOPILOT; + } + break; + case 2: // "maintaining" + mm->nav.modes_valid = 1; + if (mm->nav.altitude_source == NAV_ALT_FMS) { + mm->nav.modes |= NAV_MODE_VNAV; + } else if (mm->nav.altitude_source == NAV_ALT_AIRCRAFT) { + mm->nav.modes |= NAV_MODE_ALT_HOLD; + } else { + mm->nav.modes |= NAV_MODE_AUTOPILOT; + } + break; + default: + // nothing + break; + } + + // 16-25: target altitude + int alt = -1000 + 100 * getbits(me, 16, 25); + switch (mm->nav.altitude_source) { + case NAV_ALT_MCP: + mm->nav.mcp_altitude_valid = 1; + mm->nav.mcp_altitude = alt; + break; + case NAV_ALT_FMS: + mm->nav.fms_altitude_valid = 1; + mm->nav.fms_altitude = alt; + break; + default: + // nothing + break; + } + // 26-27: horizontal data source + unsigned h_source = getbits(me, 26, 27); + if (h_source != 0) { + // 28-36: target heading/track + mm->nav.heading_valid = 1; + mm->nav.heading = getbits(me, 28, 36); + // 37: track vs heading + if (getbit(me, 37)) { + mm->nav.heading_type = HEADING_GROUND_TRACK; + } else { + mm->nav.heading_type = HEADING_MAGNETIC_OR_TRUE; + } + } + // 38-39: horizontal mode + switch (getbits(me, 38, 39)) { + case 1: // acquiring + case 2: // maintaining + mm->nav.modes_valid = 1; + if (h_source == 3) { // FMS + mm->nav.modes |= NAV_MODE_LNAV; + } else { + mm->nav.modes |= NAV_MODE_AUTOPILOT; + } + break; + default: + // nothing + break; + } + + // 40-43: NACp + mm->accuracy.nac_p_valid = 1; + mm->accuracy.nac_p = getbits(me, 40, 43); + + // 44: NICbaro + mm->accuracy.nic_baro_valid = 1; + mm->accuracy.nic_baro = getbit(me, 44); + + // 45-46: SIL + mm->accuracy.sil = getbits(me, 45, 46); + mm->accuracy.sil_type = SIL_UNKNOWN; + + // 47-51: reserved + + // 52-53: TCAS status + switch (getbits(me, 52, 53)) { + case 1: + mm->nav.modes_valid = 1; + // no tcas + break; + case 2: + case 3: + mm->nav.modes_valid = 1; + mm->nav.modes |= NAV_MODE_TCAS; + break; + case 0: + // assume TCAS if we had any other modes + // but don't enable modes just for this + mm->nav.modes |= NAV_MODE_TCAS; + break; + default: + // nothing + break; + } + + + // 54-56: emergency/priority + mm->emergency_valid = 1; + mm->emergency = (emergency_t) getbits(me, 54, 56); + } else if (mm->mesub == 1) { // Target state and status, V2 + // 8: SIL + unsigned is_fms = getbit(me, 9); + + unsigned alt_bits = getbits(me, 10, 20); + if (alt_bits != 0) { + if (is_fms) { + mm->nav.fms_altitude_valid = 1; + mm->nav.fms_altitude = (alt_bits - 1) * 32; + } else { + mm->nav.mcp_altitude_valid = 1; + mm->nav.mcp_altitude = (alt_bits - 1) * 32; + } + } + + unsigned baro_bits = getbits(me, 21, 29); + if (baro_bits != 0) { + mm->nav.qnh_valid = 1; + mm->nav.qnh = 800.0 + (baro_bits - 1) * 0.8; + } + + if (getbit(me, 30)) { + mm->nav.heading_valid = 1; + // two's complement -180..+180, which is conveniently + // also the same as unsigned 0..360 + mm->nav.heading = getbits(me, 31, 39) * 180.0 / 256.0; + mm->nav.heading_type = HEADING_MAGNETIC_OR_TRUE; + } + + // 40-43: NACp + mm->accuracy.nac_p_valid = 1; + mm->accuracy.nac_p = getbits(me, 40, 43); + + // 44: NICbaro + mm->accuracy.nic_baro_valid = 1; + mm->accuracy.nic_baro = getbit(me, 44); + + // 45-46: SIL + mm->accuracy.sil = getbits(me, 45, 46); + mm->accuracy.sil_type = SIL_UNKNOWN; + + // 47: mode bits validity + if (getbit(me, 47)) { + // 48-54: mode bits + mm->nav.modes_valid = 1; + mm->nav.modes = + (getbit(me, 48) ? NAV_MODE_AUTOPILOT : 0) | + (getbit(me, 49) ? NAV_MODE_VNAV : 0) | + (getbit(me, 50) ? NAV_MODE_ALT_HOLD : 0) | + // 51: IMF + (getbit(me, 52) ? NAV_MODE_APPROACH : 0) | + (getbit(me, 53) ? NAV_MODE_TCAS : 0) | + (getbit(me, 54) ? NAV_MODE_LNAV : 0); + } + + // 55-56 reserved + } +} + +static void decodeESOperationalStatus(struct modesMessage *mm, int check_imf) { + unsigned char *me = mm->ME; + + mm->mesub = getbits(me, 6, 8); + + // Aircraft Operational Status + if (check_imf && getbit(me, 56)) + setIMF(mm); + + if (mm->mesub == 0 || mm->mesub == 1) { + mm->opstatus.valid = 1; + mm->opstatus.version = getbits(me, 41, 43); + + switch (mm->opstatus.version) { + case 0: + if (mm->mesub == 0 && getbits(me, 9, 10) == 0) { + mm->opstatus.cc_acas = !getbit(me, 12); + mm->opstatus.cc_cdti = getbit(me, 13); + } + break; + + case 1: + if (getbits(me, 25, 26) == 0) { + mm->opstatus.om_acas_ra = getbit(me, 27); + mm->opstatus.om_ident = getbit(me, 28); + mm->opstatus.om_atc = getbit(me, 29); + } + + if (mm->mesub == 0 && getbits(me, 9, 10) == 0 && getbits(me, 13, 14) == 0) { + // airborne + mm->opstatus.cc_acas = !getbit(me, 11); + mm->opstatus.cc_cdti = getbit(me, 12); + mm->opstatus.cc_arv = getbit(me, 15); + mm->opstatus.cc_ts = getbit(me, 16); + mm->opstatus.cc_tc = getbits(me, 17, 18); + } else if (mm->mesub == 1 && getbits(me, 9, 10) == 0 && getbits(me, 13, 14) == 0) { + // surface + mm->opstatus.cc_poa = getbit(me, 11); + mm->opstatus.cc_cdti = getbit(me, 12); + mm->opstatus.cc_b2_low = getbit(me, 15); + mm->opstatus.cc_lw_valid = 1; + mm->opstatus.cc_lw = getbits(me, 21, 24); + } + + mm->accuracy.nic_a_valid = 1; + mm->accuracy.nic_a = getbit(me, 44); + mm->accuracy.nac_p_valid = 1; + mm->accuracy.nac_p = getbits(me, 45, 48); + mm->accuracy.sil_type = SIL_UNKNOWN; + mm->accuracy.sil = getbits(me, 51, 52); + + mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; + + if (mm->mesub == 0) { + mm->accuracy.nic_baro_valid = 1; + mm->accuracy.nic_baro = getbit(me, 53); + } else { + // see DO=260B §2.2.3.2.7.2.12 + // TAH=0 : surface movement reports ground track + // TAH=1 : surface movement reports aircraft heading + mm->opstatus.tah = getbit(me, 53) ? mm->opstatus.hrd : HEADING_GROUND_TRACK; + } + break; + + case 2: + if (getbits(me, 25, 26) == 0) { + mm->opstatus.om_acas_ra = getbit(me, 27); + mm->opstatus.om_ident = getbit(me, 28); + mm->opstatus.om_atc = getbit(me, 29); + mm->opstatus.om_saf = getbit(me, 30); + mm->accuracy.sda_valid = 1; + mm->accuracy.sda = getbits(me, 31, 32); + } + + if (mm->mesub == 0 && getbits(me, 9, 10) == 0) { + // airborne + mm->opstatus.cc_acas = getbit(me, 11); // nb inverted sense versus v0/v1 + mm->opstatus.cc_1090_in = getbit(me, 12); + mm->opstatus.cc_arv = getbit(me, 15); + mm->opstatus.cc_ts = getbit(me, 16); + mm->opstatus.cc_tc = getbits(me, 17, 18); + mm->opstatus.cc_uat_in = getbit(me, 19); + } else if (mm->mesub == 1 && getbits(me, 9, 10) == 0) { + // surface + mm->opstatus.cc_poa = getbit(me, 11); + mm->opstatus.cc_1090_in = getbit(me, 12); + mm->opstatus.cc_b2_low = getbit(me, 15); + mm->opstatus.cc_uat_in = getbit(me, 16); + mm->accuracy.nac_v_valid = 1; + mm->accuracy.nac_v = getbits(me, 17, 19); + mm->accuracy.nic_c_valid = 1; + mm->accuracy.nic_c = getbit(me, 20); + mm->opstatus.cc_lw_valid = 1; + mm->opstatus.cc_lw = getbits(me, 21, 24); + mm->opstatus.cc_antenna_offset = getbits(me, 33, 40); + } + + mm->accuracy.nic_a_valid = 1; + mm->accuracy.nic_a = getbit(me, 44); + mm->accuracy.nac_p_valid = 1; + mm->accuracy.nac_p = getbits(me, 45, 48); + mm->accuracy.sil = getbits(me, 51, 52); + mm->accuracy.sil_type = getbit(me, 55) ? SIL_PER_SAMPLE : SIL_PER_HOUR; + mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; + if (mm->mesub == 0) { + mm->accuracy.gva_valid = 1; + mm->accuracy.gva = getbits(me, 49, 50); + mm->accuracy.nic_baro_valid = 1; + mm->accuracy.nic_baro = getbit(me, 53); + } else { + // see DO=260B §2.2.3.2.7.2.12 + // TAH=0 : surface movement reports ground track + // TAH=1 : surface movement reports aircraft heading + mm->opstatus.tah = getbit(me, 53) ? mm->opstatus.hrd : HEADING_GROUND_TRACK; + } + break; + } + } +} + +static void decodeExtendedSquitter(struct modesMessage *mm) { + unsigned char *me = mm->ME; + unsigned metype = mm->metype = getbits(me, 1, 5); + unsigned check_imf = 0; + + // Check CF on DF18 to work out the format of the ES and whether we need to look for an IMF bit + if (mm->msgtype == 18) { + switch (mm->CF) { + case 0: // ADS-B Message from a non-transponder device, AA field holds 24-bit ICAO aircraft address + mm->addrtype = ADDR_ADSB_ICAO_NT; + break; + + case 1: // Reserved for ADS-B Message in which the AA field holds anonymous address or ground vehicle address or fixed obstruction address + mm->addrtype = ADDR_ADSB_OTHER; + mm->addr |= MODES_NON_ICAO_ADDRESS; + break; + + case 2: // Fine TIS-B Message + // IMF=0: AA field contains the 24-bit ICAO aircraft address + // IMF=1: AA field contains the 12-bit Mode A code followed by a 12-bit track file number + mm->source = SOURCE_TISB; + mm->addrtype = ADDR_TISB_ICAO; + check_imf = 1; + break; + + case 3: // Coarse TIS-B airborne position and velocity. + // IMF=0: AA field contains the 24-bit ICAO aircraft address + // IMF=1: AA field contains the 12-bit Mode A code followed by a 12-bit track file number + + // For now we only look at the IMF bit. + mm->source = SOURCE_TISB; + mm->addrtype = ADDR_TISB_ICAO; + if (getbit(me, 1)) + setIMF(mm); + return; + + case 5: // Fine TIS-B Message, AA field contains a non-ICAO 24-bit address + mm->addrtype = ADDR_TISB_OTHER; + mm->source = SOURCE_TISB; + mm->addr |= MODES_NON_ICAO_ADDRESS; + break; + + case 6: // Rebroadcast of ADS-B Message from an alternate data link + // IMF=0: AA field holds 24-bit ICAO aircraft address + // IMF=1: AA field holds anonymous address or ground vehicle address or fixed obstruction address + mm->addrtype = ADDR_ADSR_ICAO; + mm->source = SOURCE_ADSR; + check_imf = 1; + break; + + default: // All others, we don't know the format. + mm->addrtype = ADDR_UNKNOWN; + mm->addr |= MODES_NON_ICAO_ADDRESS; // assume non-ICAO + return; + } + } + + switch (metype) { + case 1: case 2: case 3: case 4: + decodeESIdentAndCategory(mm); + break; + + case 19: + decodeESAirborneVelocity(mm, check_imf); + break; + + case 5: case 6: case 7: case 8: + decodeESSurfacePosition(mm, check_imf); + break; + + case 0: // Airborne position, baro altitude only + case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: // Airborne position, baro + case 20: case 21: case 22: // Airborne position, geometric altitude (HAE or MSL) + decodeESAirbornePosition(mm, check_imf); + break; + + case 23: + decodeESTestMessage(mm); + break; + + case 24: // Reserved for Surface System Status + break; + + case 28: + decodeESAircraftStatus(mm, check_imf); + break; + + case 29: + decodeESTargetStatus(mm, check_imf); + break; + + case 30: // Aircraft Operational Coordination + break; + + case 31: + decodeESOperationalStatus(mm, check_imf); + break; + + default: + break; + } +} + +static const char *df_names[33] = { + /* 0 */ "Short Air-Air Surveillance", + /* 1 */ NULL, + /* 2 */ NULL, + /* 3 */ NULL, + /* 4 */ "Survelliance, Altitude Reply", + /* 5 */ "Survelliance, Identity Reply", + /* 6 */ NULL, + /* 7 */ NULL, + /* 8 */ NULL, + /* 9 */ NULL, + /* 10 */ NULL, + /* 11 */ "All Call Reply", + /* 12 */ NULL, + /* 13 */ NULL, + /* 14 */ NULL, + /* 15 */ NULL, + /* 16 */ "Long Air-Air ACAS", + /* 17 */ "Extended Squitter", + /* 18 */ "Extended Squitter (Non-Transponder)", + /* 19 */ "Extended Squitter (Military)", + /* 20 */ "Comm-B, Altitude Reply", + /* 21 */ "Comm-B, Identity Reply", + /* 22 */ "Military Use", + /* 23 */ NULL, + /* 24 */ "Comm-D Extended Length Message", + /* 25 */ "Comm-D Extended Length Message", + /* 26 */ "Comm-D Extended Length Message", + /* 27 */ "Comm-D Extended Length Message", + /* 28 */ "Comm-D Extended Length Message", + /* 29 */ "Comm-D Extended Length Message", + /* 30 */ "Comm-D Extended Length Message", + /* 31 */ "Comm-D Extended Length Message", + /* 32 */ "Mode A/C Reply", +}; + +static const char *df_to_string(unsigned df) { + if (df == DFTYPE_MODEAC) + return "modeac"; + if (df > 32) + return "out of range"; + if (!df_names[df]) + return "reserved"; + return df_names[df]; +} + +static const char *altitude_unit_to_string(altitude_unit_t unit) { + switch (unit) { + case UNIT_FEET: + return "ft"; + case UNIT_METERS: + return "m"; + default: + return "(unknown altitude unit)"; + } +} + +static const char *heading_type_to_string(heading_type_t type) { + switch (type) { + case HEADING_GROUND_TRACK: + return "Ground track"; + case HEADING_MAGNETIC: + return "Mag heading"; + case HEADING_TRUE: + return "True heading"; + case HEADING_MAGNETIC_OR_TRUE: + return "Heading"; + case HEADING_TRACK_OR_HEADING: + return "Track/Heading"; + default: + return "unknown heading type"; + } +} + +static const char *commb_format_to_string(commb_format_t format) { + switch (format) { + case COMMB_EMPTY_RESPONSE: + return "empty response"; + case COMMB_AMBIGUOUS: + return "ambiguous format"; + case COMMB_DATALINK_CAPS: + return "BDS1,0 Datalink capabilities"; + case COMMB_GICB_CAPS: + return "BDS1,7 Common usage GICB capabilities"; + case COMMB_AIRCRAFT_IDENT: + return "BDS2,0 Aircraft identification"; + case COMMB_ACAS_RA: + return "BDS3,0 ACAS resolution advisory"; + case COMMB_VERTICAL_INTENT: + return "BDS4,0 Selected vertical intention"; + case COMMB_TRACK_TURN: + 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"; + } +} + +static const char *nav_modes_to_string(nav_modes_t flags) { + static char buf[128]; + + buf[0] = 0; + if (flags & NAV_MODE_AUTOPILOT) + strcat(buf, "autopilot "); + if (flags & NAV_MODE_VNAV) + strcat(buf, "vnav "); + if (flags & NAV_MODE_ALT_HOLD) + strcat(buf, "althold "); + if (flags & NAV_MODE_APPROACH) + strcat(buf, "approach "); + if (flags & NAV_MODE_LNAV) + strcat(buf, "lnav "); + if (flags & NAV_MODE_TCAS) + strcat(buf, "tcas "); + + if (buf[0] != 0) + buf[strlen(buf) - 1] = 0; + + return buf; +} + +static const char *sil_type_to_string(sil_type_t type) { + switch (type) { + case SIL_UNKNOWN: return "unknown type"; + case SIL_PER_HOUR: return "per flight hour"; + case SIL_PER_SAMPLE: return "per sample"; + default: return "invalid type"; + } +} + +static const char *emergency_to_string(emergency_t emergency) { + switch (emergency) { + case EMERGENCY_NONE: return "no emergency"; + case EMERGENCY_GENERAL: return "general emergency (7700)"; + case EMERGENCY_LIFEGUARD: return "lifeguard / medical emergency"; + case EMERGENCY_MINFUEL: return "minimum fuel"; + case EMERGENCY_NORDO: return "no communications (7600)"; + case EMERGENCY_UNLAWFUL: return "unlawful interference (7500)"; + case EMERGENCY_DOWNED: return "downed aircraft"; + default: return "reserved"; + } +} + +static void print_hex_bytes(unsigned char *data, size_t len) { + size_t i; + for (i = 0; i < len; ++i) { + printf("%02X", (unsigned) data[i]); + } +} + +static int esTypeHasSubtype(unsigned metype) { + if (metype <= 18) { + return 0; + } + + if (metype >= 20 && metype <= 22) { + return 0; + } + + return 1; +} + +static const char *esTypeName(unsigned metype, unsigned mesub) { + switch (metype) { + case 0: + return "No position information (airborne or surface)"; + + case 1: case 2: case 3: case 4: + return "Aircraft identification and category"; + + case 5: case 6: case 7: case 8: + return "Surface position"; + + case 9: case 10: case 11: case 12: + case 13: case 14: case 15: case 16: + case 17: case 18: + return "Airborne position (barometric altitude)"; + + case 19: + switch (mesub) { + case 1: + return "Airborne velocity over ground, subsonic"; + case 2: + return "Airborne velocity over ground, supersonic"; + case 3: + return "Airspeed and heading, subsonic"; + case 4: + return "Airspeed and heading, supersonic"; + default: + return "Unknown"; + } + + case 20: case 21: case 22: + return "Airborne position (geometric altitude)"; + + case 23: + switch (mesub) { + case 0: + return "Test message"; + case 7: + return "National use / 1090-WP-15-20 Mode A squawk"; + default: + return "Unknown"; + } + + case 24: + return "Reserved for surface system status"; + + case 27: + return "Reserved for trajectory change"; + + case 28: + switch (mesub) { + case 1: + return "Emergency/priority status"; + case 2: + return "ACAS RA broadcast"; + default: + return "Unknown"; + } + + case 29: + switch (mesub) { + case 0: + return "Target state and status (V1)"; + case 1: + return "Target state and status (V2)"; + default: + return "Unknown"; + } + + case 30: + return "Aircraft Operational Coordination"; + + case 31: // Aircraft Operational Status + switch (mesub) { + case 0: + return "Aircraft operational status (airborne)"; + case 1: + return "Aircraft operational status (surface)"; + default: + return "Unknown"; + } + + default: + return "Unknown"; + } +} + +void displayModesMessage(struct modesMessage *mm) { + int j; + + if (0 && mm->cpr_valid && mm->cpr_decoded) { + printf("systemTime: %.3fs\n", (mm->sysTimestamp % (5*MINUTES)) / 1000.0); + printf(" CPR odd flag: %s\n", + mm->cpr_odd ? "odd" : "even"); + + printf(" CPR latitude: %.6f (%u)\n" + " CPR longitude: %.6f (%u)\n" + " CPR decoding: %s\n", + mm->decoded_lat, + mm->cpr_lat, + mm->decoded_lon, + mm->cpr_lon, + mm->cpr_relative ? "local" : "global"); + } + + + // Handle only addresses mode first. + if (Modes.onlyaddr) { + printf("%06x\n", mm->addr); + return; // Enough for --onlyaddr mode + } + + // Show the raw message. + if (Modes.mlat && mm->timestamp) { + printf("@%012" PRIX64, mm->timestamp); + } else { + printf("*"); + } + + for (j = 0; j < mm->msgbits / 8; j++) printf("%02x", mm->msg[j]); + printf(";\n"); + + if (Modes.raw) { + fflush(stdout); // Provide data to the reader ASAP + return; // Enough for --raw mode + } + + char addr[64]; + addr[0] = 0; + if (mm->msgtype < 32) { + if (mm->addr != HEX_UNKNOWN) + sprintf(addr, "%s%06x ", (mm->addr & MODES_NON_ICAO_ADDRESS) ? "~" : " ", mm->addr & 0xFFFFFF); + else if (mm->maybe_addr != HEX_UNKNOWN) + sprintf(addr, "%s%06x ?", (mm->maybe_addr & MODES_NON_ICAO_ADDRESS) ? "~" : " ", mm->maybe_addr & 0xFFFFFF); + else + sprintf(addr, " unknown "); + printf("hex: %s ", addr); + printf("CRC: %06x ", mm->crc); + printf("fixed bits: %d ", mm->correctedbits); + printf("decode: %s\n", mm->decodeResult >= 0 ? "ok" : (mm->decodeResult == -1 ? "reject unknown hex" : "reject bad" )); + } + + if (mm->signalLevel > 0) + printf("RSSI: %8.1f dBFS ", 10 * log10(mm->signalLevel)); + + printf("reduce_forward: %d\n", mm->reduce_forward); + + if (mm->score) + printf("Score: %d\n", mm->score); + + if (mm->receiverId && !Modes.debug_bogus) { + char uuid[32]; // needs 18 chars and null byte + sprint_uuid1(mm->receiverId, uuid); + printf("receiverId: %s\n", uuid); + } + + if (mm->timestamp == MAGIC_MLAT_TIMESTAMP) + printf("This is a synthetic MLAT message.\n"); + else if (mm->timestamp == MAGIC_UAT_TIMESTAMP) + printf("This is a synthetic UAT message.\n"); + else if (mm->timestamp == MAGIC_NOFORWARD_TIMESTAMP) + printf("This is a no_forward message.\n"); + else + printf("receiverTime: %27.2fus\n", mm->timestamp / 12.0); + + if (1 || !Modes.debug_bogus) { + time_t nowTime = floor(mm->sysTimestamp / 1000.0); + struct tm local; + +#if defined(_WIN32) + // Windows: gmtime_s(result, &time) + time_t t; + gmtime_s(&local, &t); +#else + // POSIX: gmtime_r(&time, result) + gmtime_r(&t, &local); +#endif + char timebuf[512]; + strftime(timebuf, 128, "%T", &local); + printf("utcTime: %s.%03lld epoch: %.3f\n", timebuf, (long long) mm->sysTimestamp % 1000, mm->sysTimestamp / 1000.0); + } + + if (mm->sbs_in) { + printf("SBS addr:%s\n", addr); + } else { + switch (mm->msgtype) { + case 0: + printf("DF: 0 addr:%s VS:%u CC:%u SL:%u RI:%u AC:%u\n", + addr, mm->VS, mm->CC, mm->SL, mm->RI, mm->AC); + break; + + case 4: + printf("DF: 4 addr:%s FS:%u DR:%u UM:%u AC:%u\n", + addr, mm->FS, mm->DR, mm->UM, mm->AC); + break; + + case 5: + printf("DF: 5 addr:%s FS:%u DR:%u UM:%u ID:%u\n", + addr, mm->FS, mm->DR, mm->UM, mm->ID); + break; + + case 11: + printf("DF: 11 AA:%06X IID:%u CA:%u\n", + mm->AA, mm->IID, mm->CA); + break; + + case 16: + printf("DF: 16 addr:%s VS:%u SL:%u RI:%u AC:%u MV:", + addr, mm->VS, mm->SL, mm->RI, mm->AC); + print_hex_bytes(mm->MV, sizeof (mm->MV)); + printf("\n"); + + if (mm->acas_ra_valid) + printACASInfoShort(mm->addr, mm->MV, NULL, mm, mm->sysTimestamp); + break; + + case 17: + printf("DF: 17 AA:%06X CA:%u ME:", + mm->AA, mm->CA); + print_hex_bytes(mm->ME, sizeof (mm->ME)); + printf("\n"); + break; + + case 18: + printf("DF: 18 AA:%06X CF:%u ME:", + mm->AA, mm->CF); + print_hex_bytes(mm->ME, sizeof (mm->ME)); + printf("\n"); + break; + + case 20: + printf("DF: 20 addr:%s FS:%u DR:%u UM:%u AC:%u MB:", + addr, mm->FS, mm->DR, mm->UM, mm->AC); + print_hex_bytes(mm->MB, sizeof (mm->MB)); + printf("\n"); + break; + + case 21: + printf("DF: 21 addr:%s FS:%u DR:%u UM:%u ID:%u MB:", + addr, mm->FS, mm->DR, mm->UM, mm->ID); + print_hex_bytes(mm->MB, sizeof (mm->MB)); + printf("\n"); + break; + + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + printf("DF: 24 addr:%s KE:%u ND:%u MD:", + addr, mm->KE, mm->ND); + print_hex_bytes(mm->MD, sizeof (mm->MD)); + printf("\n"); + break; + } + } + + if (!mm->sbs_in) + printf(" %s", df_to_string(mm->msgtype)); + + if (mm->msgtype == 17 || mm->msgtype == 18) { + if (esTypeHasSubtype(mm->metype)) { + printf(" %s (%u/%u)", + esTypeName(mm->metype, mm->mesub), + mm->metype, + mm->mesub); + } else { + printf(" %s (%u)", + esTypeName(mm->metype, mm->mesub), + mm->metype); + } + } + if (!mm->sbs_in) + printf("\n"); + + if (mm->msgtype == 20 || mm->msgtype == 21) { + printf(" Comm-B format: %s\n", commb_format_to_string(mm->commb_format)); + } + + if (mm->addr != HEX_UNKNOWN) { + if (mm->addr & MODES_NON_ICAO_ADDRESS) { + printf(" Other Address: %06X (%s)\n", mm->addr & 0xFFFFFF, addrtype_enum_string(mm->addrtype)); + } else { + printf(" ICAO Address: %06X (%s)\n", mm->addr, addrtype_enum_string(mm->addrtype)); + } + } + + if (mm->airground != AG_INVALID) { + printf(" Air/Ground: %s\n", + airground_to_string(mm->airground)); + } + + if (mm->baro_alt_valid) { + printf(" Baro altitude: %8d %s\n", + mm->baro_alt, + altitude_unit_to_string(mm->baro_alt_unit)); + } + + if (mm->geom_alt_valid) { + printf(" Geom altitude: %8d %s\n", + mm->geom_alt, + altitude_unit_to_string(mm->geom_alt_unit)); + } + if (mm->geom_alt_derived) { + printf(" Geom altitude (derived): %8d %s\n", + mm->geom_alt, + altitude_unit_to_string(mm->geom_alt_unit)); + } + + if (mm->geom_delta_valid) { + printf(" Geom - baro: %8d ft\n", + mm->geom_delta); + } + + if (mm->heading_valid) { + printf(" %-13s %5.1f\n", heading_type_to_string(mm->heading_type), mm->heading); + } + + if (mm->track_rate_valid) { + printf(" Track rate: %6.2f deg/sec %s\n", mm->track_rate, mm->track_rate < 0 ? "left" : mm->track_rate > 0 ? "right" : ""); + } + + if (mm->roll_valid) { + printf(" Roll: %5.1f degrees %s\n", mm->roll, mm->roll < -0.05 ? "left" : mm->roll > 0.05 ? "right" : ""); + } + + if (mm->gs_valid) { + printf(" Groundspeed: %5.1f kt", mm->gs.selected); + if (mm->gs.v0 != mm->gs.selected) { + printf(" (v0: %.1f kt)", mm->gs.v0); + } + if (mm->gs.v2 != mm->gs.selected) { + printf(" (v2: %.1f kt)", mm->gs.v2); + } + printf("\n"); + } + + if (mm->ias_valid) { + printf(" IAS: %3u kt\n", mm->ias); + } + + if (mm->tas_valid) { + printf(" TAS: %3u kt\n", mm->tas); + } + + if (mm->mach_valid) { + printf(" Mach number: %.3f\n", mm->mach); + } + + if (mm->baro_rate_valid) { + printf(" Baro rate: %6d ft/min\n", mm->baro_rate); + } + + if (mm->geom_rate_valid) { + printf(" Geom rate: %6d ft/min\n", mm->geom_rate); + } + + if (mm->squawk_valid) { + printf(" Squawk: %04d\n", + mm->squawkDec); + } + + if (mm->callsign_valid) { + printf(" Ident: %s\n", + mm->callsign); + } + + if (mm->category_valid) { + printf(" Category: %02X\n", + mm->category); + } + + if (mm->cpr_valid) { + printf(" CPR type: %s\n" + " CPR odd flag: %s\n", + cpr_type_string(mm->cpr_type), + mm->cpr_odd ? "odd" : "even"); + + if (mm->cpr_decoded) { + printf(" CPR latitude: %11.6f (%5u)\n" + " CPR longitude: %11.6f (%5u)\n" + " CPR decoding: %s\n" + " NIC: %u\n" + " Rc: %.3f km / %.1f NM\n", + mm->decoded_lat, + mm->cpr_lat, + mm->decoded_lon, + mm->cpr_lon, + mm->cpr_relative ? "local" : "global", + mm->decoded_nic, + mm->decoded_rc / 1000.0, + mm->decoded_rc / 1852.0); + } else { + printf(" CPR latitude: (%u)\n" + " CPR longitude: (%u)\n" + " CPR decoding: none\n", + mm->cpr_lat, + mm->cpr_lon); + } + } + if (mm->sbs_pos_valid) { + printf(" Latitude: %.6f\n" + " Longitude: %.6f\n", + mm->decoded_lat, + mm->decoded_lon); + } + + if (mm->accuracy.nic_a_valid) { + printf(" NIC-A: %d\n", mm->accuracy.nic_a); + } + if (mm->accuracy.nic_b_valid) { + printf(" NIC-B: %d\n", mm->accuracy.nic_b); + } + if (mm->accuracy.nic_c_valid) { + printf(" NIC-C: %d\n", mm->accuracy.nic_c); + } + if (mm->accuracy.nic_baro_valid) { + printf(" NIC-baro: %d\n", mm->accuracy.nic_baro); + } + if (mm->accuracy.nac_p_valid) { + printf(" NACp: %d\n", mm->accuracy.nac_p); + } + if (mm->accuracy.nac_v_valid) { + printf(" NACv: %d\n", mm->accuracy.nac_v); + } + if (mm->accuracy.gva_valid) { + printf(" GVA: %d\n", mm->accuracy.gva); + } + if (mm->accuracy.sil_type != SIL_INVALID) { + const char *sil_description; + switch (mm->accuracy.sil) { + case 1: + sil_description = "p <= 0.1%"; + break; + case 2: + sil_description = "p <= 0.001%"; + break; + case 3: + sil_description = "p <= 0.00001%"; + break; + default: + sil_description = "p > 0.1%"; + break; + } + printf(" SIL: %d (%s, %s)\n", + mm->accuracy.sil, + sil_description, + sil_type_to_string(mm->accuracy.sil_type)); + } + if (mm->accuracy.sda_valid) { + printf(" SDA: %d\n", mm->accuracy.sda); + } + + if (mm->mlatEPU) { + printf(" mlatEPU: %d\n", mm->mlatEPU); + } + + if (mm->opstatus.valid) { + printf(" Aircraft Operational Status:\n"); + printf(" Version: %d\n", mm->opstatus.version); + + printf(" Capability classes: "); + if (mm->opstatus.cc_acas) printf("ACAS "); + if (mm->opstatus.cc_cdti) printf("CDTI "); + if (mm->opstatus.cc_1090_in) printf("1090IN "); + if (mm->opstatus.cc_arv) printf("ARV "); + if (mm->opstatus.cc_ts) printf("TS "); + if (mm->opstatus.cc_tc) printf("TC=%d ", mm->opstatus.cc_tc); + if (mm->opstatus.cc_uat_in) printf("UATIN "); + if (mm->opstatus.cc_poa) printf("POA "); + if (mm->opstatus.cc_b2_low) printf("B2-LOW "); + if (mm->opstatus.cc_lw_valid) printf("L/W=%d ", mm->opstatus.cc_lw); + if (mm->opstatus.cc_antenna_offset) printf("GPS-OFFSET=%d ", mm->opstatus.cc_antenna_offset); + printf("\n"); + + printf(" Operational modes: "); + if (mm->opstatus.om_acas_ra) printf("ACASRA "); + if (mm->opstatus.om_ident) printf("IDENT "); + if (mm->opstatus.om_atc) printf("ATC "); + if (mm->opstatus.om_saf) printf("SAF "); + printf("\n"); + + if (mm->mesub == 1) + printf(" Track/heading: %s\n", heading_type_to_string(mm->opstatus.tah)); + printf(" Heading ref dir: %s\n", heading_type_to_string(mm->opstatus.hrd)); + } + + if (mm->nav.heading_valid) + printf(" Selected heading: %5.1f\n", mm->nav.heading); + if (mm->nav.fms_altitude_valid) + printf(" FMS selected altitude: %5u ft\n", mm->nav.fms_altitude); + if (mm->nav.mcp_altitude_valid) + printf(" MCP selected altitude: %5u ft\n", mm->nav.mcp_altitude); + if (mm->nav.qnh_valid) + printf(" QNH: %.1f millibars\n", mm->nav.qnh); + if (mm->nav.altitude_source != NAV_ALT_INVALID) { + printf(" Target altitude source: "); + switch (mm->nav.altitude_source) { + case NAV_ALT_AIRCRAFT: + printf("aircraft altitude\n"); + break; + case NAV_ALT_MCP: + printf("MCP selected altitude\n"); + break; + case NAV_ALT_FMS: + printf("FMS selected altitude\n"); + break; + default: + printf("unknown\n"); + } + } + + if (mm->nav.modes_valid) { + printf(" Nav modes: %s\n", nav_modes_to_string(mm->nav.modes)); + } + + if (mm->emergency_valid) { + printf(" Emergency/priority: %s\n", emergency_to_string(mm->emergency)); + } + + printf("\n"); + fflush(stdout); +} + +// +// ===================== Mode S detection and decoding =================== +// diff --git a/readsb2/mode_s.h b/readsb2/mode_s.h new file mode 100644 index 0000000..987b0a4 --- /dev/null +++ b/readsb2/mode_s.h @@ -0,0 +1,128 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// mode_s.h: Mode S message decoding (header) +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2017 FlightAware, LLC +// Copyright (c) 2017 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef MODE_S_H +#define MODE_S_H + +#include + +// +// Functions exported from mode_s.c +// +int scoreModesMessage (unsigned char *msg, int validbits); +int decodeModesMessage (struct modesMessage *mm); +void displayModesMessage (struct modesMessage *mm); + +// datafield extraction helpers + +// The first bit (MSB of the first byte) is numbered 1, for consistency +// with how the specs number them. + +// Extract one bit from a message. + +static inline unsigned +getbit (unsigned char *data, unsigned bitnum) +{ + unsigned bi = bitnum - 1; + unsigned by = bi >> 3; + unsigned mask = 1 << (7 - (bi & 7)); + + return (data[by] & mask) != 0; +} + +// Extract some bits (firstbit .. lastbit inclusive) from a message. + +static inline unsigned +getbits (unsigned char *data, unsigned firstbit, unsigned lastbit) +{ + unsigned fbi = firstbit - 1; + unsigned lbi = lastbit - 1; + unsigned nbi = (lastbit - firstbit + 1); + + unsigned fby = fbi >> 3; + unsigned lby = lbi >> 3; + unsigned nby = (lby - fby) + 1; + + unsigned shift = 7 - (lbi & 7); + unsigned topmask = 0xFF >> (fbi & 7); + + assert (fbi <= lbi); + assert (nbi <= 32); + assert (nby <= 5); + + if (nby == 5) + { + return + ((data[fby] & topmask) << (32 - shift)) | + (data[fby + 1] << (24 - shift)) | + (data[fby + 2] << (16 - shift)) | + (data[fby + 3] << (8 - shift)) | + (data[fby + 4] >> shift); + } + else if (nby == 4) + { + return + ((data[fby] & topmask) << (24 - shift)) | + (data[fby + 1] << (16 - shift)) | + (data[fby + 2] << (8 - shift)) | + (data[fby + 3] >> shift); + } + else if (nby == 3) + { + return + ((data[fby] & topmask) << (16 - shift)) | + (data[fby + 1] << (8 - shift)) | + (data[fby + 2] >> shift); + } + else if (nby == 2) + { + return + ((data[fby] & topmask) << (8 - shift)) | + (data[fby + 1] >> shift); + } + else if (nby == 1) + { + return + (data[fby] & topmask) >> shift; + } + else + { + return 0; + } +} + +//========================================================================= +// +// Given the Downlink Format (DF) of the message, return the message length in bits. +// +// All known DF's 16 or greater are long. All known DF's 15 or less are short. +// There are lots of unused codes in both category, so we can assume ICAO will stick to +// these rules, meaning that the most significant bit of the DF indicates the length. +// + +static inline int modesMessageLenByType(int type) { + return (type & 0x10) ? MODES_LONG_MSG_BITS : MODES_SHORT_MSG_BITS; +} + +#endif diff --git a/readsb2/readsb.h b/readsb2/readsb.h new file mode 100644 index 0000000..a729849 --- /dev/null +++ b/readsb2/readsb.h @@ -0,0 +1,1355 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// readsb.h: main program header +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2014-2016 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// This file incorporates work covered by the following copyright and +// license: +// +// Copyright (C) 2012 by Salvatore Sanfilippo +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef __DUMP1090_H +#define __DUMP1090_H + +#ifndef ALL_JSON +#define ALL_JSON 0 +#endif + +// Default version number, if not overriden by the Makefile +#ifndef MODES_READSB_VERSION +#define MODES_READSB_VERSION "Unknown" +#endif + +#ifndef MODES_READSB_VARIANT +#define MODES_READSB_VARIANT "readsb" +#endif + +#define VERSION_STRING MODES_READSB_VARIANT " version: " MODES_READSB_VERSION + +// ============================= Include files ========================== + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +// #include "threadpool.h" +// +// +// +// +// +// #include "compat/compat.h" + +#define SILENT 0 +#define VERBOSE 1 + +// ============================= #defines =============================== + +#define MODES_DEFAULT_FREQ 1090000000 +#define MODES_RTL_BUFFERS 16 // Number of RTL buffers +#define MODES_MAG_BUFFERS 12 // Number of magnitude buffers (should be smaller than RTL_BUFFERS for flowcontrol to work) +#define MODES_AUTO_GAIN -100 // Use automatic gain +#define MODES_MAX_GAIN 999999 // Use max available gain +#define MODES_RTL_AGC 590 // Use rtl tuner agc +#define MODEAC_MSG_BYTES 2 + +#define MODES_PREAMBLE_US 8 // microseconds = bits +#define MODES_PREAMBLE_SAMPLES (MODES_PREAMBLE_US * 2) +#define MODES_PREAMBLE_SIZE (MODES_PREAMBLE_SAMPLES * sizeof(uint16_t)) +#define MODES_LONG_MSG_BYTES 14 +#define MODES_SHORT_MSG_BYTES 7 +#define MODES_LONG_MSG_BITS (MODES_LONG_MSG_BYTES * 8) +#define MODES_SHORT_MSG_BITS (MODES_SHORT_MSG_BYTES * 8) +#define MODES_LONG_MSG_SAMPLES (MODES_LONG_MSG_BITS * 2) +#define MODES_SHORT_MSG_SAMPLES (MODES_SHORT_MSG_BITS * 2) +#define MODES_LONG_MSG_SIZE (MODES_LONG_MSG_SAMPLES * sizeof(uint16_t)) +#define MODES_SHORT_MSG_SIZE (MODES_SHORT_MSG_SAMPLES * sizeof(uint16_t)) + +#define MODES_OS_PREAMBLE_SAMPLES (20) +#define MODES_OS_PREAMBLE_SIZE (MODES_OS_PREAMBLE_SAMPLES * sizeof(uint16_t)) +#define MODES_OS_LONG_MSG_SAMPLES (268) +#define MODES_OS_SHORT_MSG_SAMPLES (135) +#define MODES_OS_LONG_MSG_SIZE (MODES_LONG_MSG_SAMPLES * sizeof(uint16_t)) +#define MODES_OS_SHORT_MSG_SIZE (MODES_SHORT_MSG_SAMPLES * sizeof(uint16_t)) + +#define MODES_OUT_FLUSH_INTERVAL (500) // max flush interval + +#define MODES_NET_SNDBUF_SIZE (8*1024) +#define MODES_NET_SNDBUF_MAX (7) + +#define HEX_UNKNOWN (0xDEADBEEF) + +#define DFTYPE_MODEAC 77 + +#define INVALID_ALTITUDE (-9999) + +#define CANARY (0x665225ca79e653a3) + +#define MESSAGE_RATE_CALC_POINTS (2) + +// size of various on stack buffers used across the code, let's just be conservative and assume 1 MB of stack +// without heavy recursion 3 of those stack buffers can be in use at the same time, at most we expect to to be in use +#define QUARTER_STACK (256 * 1024) + +/* Where did a bit of data arrive from? In order of increasing priority */ +typedef enum +{ + SOURCE_INVALID, /* data is not valid */ + SOURCE_INDIRECT, /* data is of unknown quality */ + SOURCE_MODE_AC, /* A/C message */ + SOURCE_SBS, /* data is of unknown quality */ + SOURCE_MLAT, /* derived from mlat */ + SOURCE_MODE_S, /* data from a Mode S message, no full CRC */ + SOURCE_JAERO, /* data is from satellite ADS-C */ + SOURCE_MODE_S_CHECKED, /* data from a Mode S message with full CRC */ + SOURCE_TISB, /* data from a TIS-B extended squitter message */ + SOURCE_ADSR, /* data from a ADS-R extended squitter message */ + SOURCE_ADSB, /* data from a ADS-B extended squitter message */ + SOURCE_PRIO, /* priority input */ +} datasource_t; + +/* What sort of address is this and who sent it? + * (Earlier values are higher priority) + */ +typedef enum +{ + ADDR_ADSB_ICAO = 0, /* ADS-B, ICAO address, transponder sourced */ + ADDR_ADSB_ICAO_NT = 1, /* ADS-B, ICAO address, non-transponder */ + ADDR_ADSR_ICAO = 2, /* ADS-R, ICAO address */ + ADDR_TISB_ICAO = 3, /* TIS-B, ICAO address */ + + ADDR_JAERO = 4, + ADDR_MLAT = 5, + ADDR_OTHER = 6, + ADDR_MODE_S = 7, + + ADDR_ADSB_OTHER = 8, /* ADS-B, other address format */ + ADDR_ADSR_OTHER = 9, /* ADS-R, other address format */ + ADDR_TISB_TRACKFILE = 10, /* TIS-B, Mode A code + track file number */ + ADDR_TISB_OTHER = 11, /* TIS-B, other address format */ + + ADDR_MODE_A = 12, /* Mode A */ + + ADDR_UNKNOWN = 15/* unknown address format */ +} addrtype_t; + +// number of types as defined above +#define NUM_TYPES 14 + + +typedef enum +{ + UNIT_FEET, + UNIT_METERS +} altitude_unit_t; + +typedef enum +{ + ALTITUDE_BARO, + ALTITUDE_GEOM +} altitude_source_t; + +typedef enum +{ + AG_INVALID = 0, + AG_GROUND = 1, + AG_AIRBORNE = 2, + AG_UNCERTAIN = 3 +} airground_t; + +typedef enum +{ + SIL_INVALID, SIL_UNKNOWN, SIL_PER_SAMPLE, SIL_PER_HOUR +} sil_type_t; + +typedef enum +{ + CPR_INVALID, CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE +} cpr_type_t; + +typedef enum +{ + CPR_NONE, CPR_LOCAL, CPR_GLOBAL +} cpr_local_t; + +typedef enum +{ + HEADING_INVALID, // Not set + HEADING_GROUND_TRACK, // Direction of track over ground, degrees clockwise from true north + HEADING_TRUE, // Heading, degrees clockwise from true north + HEADING_MAGNETIC, // Heading, degrees clockwise from magnetic north + HEADING_MAGNETIC_OR_TRUE, // HEADING_MAGNETIC or HEADING_TRUE depending on the HRD bit in opstatus + HEADING_TRACK_OR_HEADING // GROUND_TRACK / MAGNETIC / TRUE depending on the TAH bit in opstatus +} heading_type_t; + +typedef enum { + COMMB_UNKNOWN, + COMMB_AMBIGUOUS, + COMMB_EMPTY_RESPONSE, + COMMB_DATALINK_CAPS, + COMMB_GICB_CAPS, + COMMB_AIRCRAFT_IDENT, + COMMB_ACAS_RA, + COMMB_VERTICAL_INTENT, + COMMB_TRACK_TURN, + COMMB_HEADING_SPEED, + COMMB_METEOROLOGICAL_ROUTINE +} commb_format_t; + +typedef enum +{ + NAV_MODE_AUTOPILOT = 1, + NAV_MODE_VNAV = 2, + NAV_MODE_ALT_HOLD = 4, + NAV_MODE_APPROACH = 8, + NAV_MODE_LNAV = 16, + NAV_MODE_TCAS = 32 +} nav_modes_t; + +// Matches encoding of the ES type 28/1 emergency/priority status subfield + +typedef enum +{ + EMERGENCY_NONE = 0, + EMERGENCY_GENERAL = 1, + EMERGENCY_LIFEGUARD = 2, + EMERGENCY_MINFUEL = 3, + EMERGENCY_NORDO = 4, + EMERGENCY_UNLAWFUL = 5, + EMERGENCY_DOWNED = 6, + EMERGENCY_RESERVED = 7 +} emergency_t; + +typedef enum { + NAV_ALT_INVALID, + NAV_ALT_UNKNOWN, + NAV_ALT_AIRCRAFT, + NAV_ALT_MCP, + NAV_ALT_FMS +} nav_altitude_source_t; + +#define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses +#define BADDR (0xff123456) // invalid address used to set stuff like cpr_focus and show_only default value + +#define MODES_INTERACTIVE_REFRESH_TIME 500 // Milliseconds +#define MODES_INTERACTIVE_DISPLAY_TTL 60000 // Delete from display after 60 seconds + +#define MODES_NET_HEARTBEAT_INTERVAL 60000 // milliseconds + +//#define ENABLE_DF24 + +#define HISTORY_SIZE 120 +#define HISTORY_INTERVAL 30000 + +#define MODES_NOTUSED(V) ((void) V) + +#ifndef AIRCRAFT_HASH_BITS +#define AIRCRAFT_HASH_BITS 14 +#endif + +#define MODES_ICAO_FILTER_TTL 60000 + +#define DB_HASH_BITS 19 +#define DB_BUCKETS (1 << DB_HASH_BITS) // this is critical for hashing purposes + +#define STATE_BLOBS 256 // change naming scheme if increasing this +#define LOCK_THREADS_MAX 64 +#define PERIODIC_UPDATE (1 * SECONDS) +#define REMOVE_STALE_INTERVAL (1 * SECONDS) + +#define STAT_BUCKETS 90 // 90 * 10 seconds = 15 min (max interval in stats.json) + +#define RANGEDIRS_BUCKETS 360 +#define RANGEDIRS_IVALS 64 + +#define PING_REJECT (3 * SECONDS) +#define PING_DISCONNECT (15 * SECONDS) +#define PING_BUCKETS 20 // statistics on round trip time +#define PING_BUCKETBASE (24) // milliseconds of first bucket +#define PING_BUCKETMULT (1.2) // each bucket will grow by that factor + +#define PING_REDUCE (1500) // 1.5 seconds +#define PING_REDUCE_DURATION (15 * SECONDS) + +#define GARBAGE_THRESHOLD (512) + +/* A timestamp that indicates the data is synthetic, created from a + * multilateration result + */ +#define MAGIC_MLAT_TIMESTAMP 0xFF004D4C4154LL +#define MAGIC_UAT_TIMESTAMP 0xFF004D4C4155LL +#define MAGIC_NOFORWARD_TIMESTAMP 0xFF004D4C4160LL + +#define MAGIC_ANY_TIMESTAMP 0xFFFFFFFFFFFFULL + +#define likely(x) __builtin_expect((x),1) +#define unlikely(x) __builtin_expect((x),0) + + +#if defined(__llvm__) + +#define _unroll_8 _Pragma ("unroll 8") +#define _unroll_16 _Pragma ("unroll 16") +#define _unroll_32 _Pragma ("unroll 32") + +#elif defined(__GNUC__) + +#if __GNUC__ >= 8 +#define _unroll_8 _Pragma ("GCC unroll 8") +#define _unroll_16 _Pragma ("GCC unroll 16") +#define _unroll_32 _Pragma ("GCC unroll 32") +#else +#define _unroll_8 +#define _unroll_16 +#define _unroll_32 +#endif + +#else + +#define _unroll_8 +#define _unroll_16 +#define _unroll_32 + +#endif + +void setExit(int arg); +int priorityTasksPending(); +void priorityTasksRun(); + +#define MemoryAlignment 32 +#define ALIGNED __attribute__((aligned(MemoryAlignment))) + +static inline void *malloc_or_exit(size_t alignment, size_t size, const char *file, int line) { + return NULL; +} +static inline void *calloc_or_exit(size_t alignment, size_t size, const char *file, int line) { + return NULL; +} + +static inline void *mmap_or_exit(size_t size, const char *file, int line) { + return NULL; +} +static inline void munmap_or_exit(void *ptr, size_t size, const char *file, int line) { + +} + +#define cmMmap(size) mmap_or_exit(size, __FILE__, __LINE__) +#define cmMunmap(ptr, size) munmap_or_exit(ptr, size, __FILE__, __LINE__) + +// disable this ... maybe it test in the future if it makes a diff if i'm bored +#if 0 +#define cmalloc(size) malloc_or_exit(MemoryAlignment, size, __FILE__, __LINE__) +#define cmCalloc(size) calloc_or_exit(MemoryAlignment, size, __FILE__, __LINE__) +#else +#define cmalloc(size) malloc_or_exit(0, size, __FILE__, __LINE__) +#define cmCalloc(size) calloc_or_exit(0, size, __FILE__, __LINE__) +#endif + +// Include subheaders after all the #defines are in place + +// #include "toString.h" +// #include "util.h" +// #include "fasthash.h" +// #include "anet.h" +// #include "net_io.h" +#include "crc.h" +// #include "demod_2400.h" +// #include "stats.h" +#include "cpr.h" +// #include "icao_filter.h" +// #include "convert.h" +// #include "sdr.h" +// #include "aircraft.h" +// #include "globe_index.h" +// #include "receiver.h" +// #include "geomag.h" +// #include "json_out.h" +// #include "api.h" + +//======================== structure declarations ========================= + +typedef enum +{ + SDR_NONE = 0, SDR_IFILE, SDR_RTLSDR, SDR_BLADERF, SDR_MICROBLADERF, SDR_HACKRF, SDR_MODESBEAST, SDR_PLUTOSDR, SDR_SOAPYSDR, SDR_GNS +} sdr_type_t; + +// Structure representing one magnitude buffer + +struct mag_buf +{ + int64_t sampleTimestamp; // Clock timestamp of the start of this block, 12MHz clock + double mean_level; // Mean of normalized (0..1) signal level + double mean_power; // Mean of normalized (0..1) power level + uint32_t dropped; // Number of dropped samples preceding this buffer + unsigned length; // Number of valid samples _after_ overlap. Total buffer length is buf->length + Modes.trailing_samples. + int64_t sysTimestamp; // Estimated system time at start of block + int64_t sysMicroseconds; // sysTimestamp in microseconds + uint32_t loudEvents; + uint32_t noiseLowSamples; + uint32_t noiseHighSamples; + uint32_t padding2; + uint16_t *data; // Magnitude data. Starts with Modes.trailing_samples worth of overlap from the previous block +#if defined(__arm__) + /*padding 4 bytes*/ + uint32_t padding; +#endif +}; + +// Program global state + +struct _Threads { + threadT upkeep; // runs priorityTasksUpdate, locks most other threads when doing its thing + threadT decode; // thread doing demodulation, decoding and networking + + threadT reader; + + threadT json; // thread writing json + threadT globeJson; // thread writing json + threadT globeBin; // thread writing binCraft + threadT misc; + threadT apiUpdate; +}; +extern struct _Threads Threads; + +struct modeMessage; + +struct messageBuffer { + struct modesMessage *msg; + int len; + int alloc; + int id; + struct client *activeClient; +}; + +struct _Modes +{ // Internal state + std::mutex traceDebugMutex; + + int num_procs; + int allPoolSize; + threadpool_t *allPool; + task_group_t *allTasks; + + uint32_t sdr_buf_size; + uint32_t sdr_buf_samples; + + int64_t traceWriteTimelimit; + int tracePoolSize; + threadpool_t *tracePool; + task_group_t *traceTasks; + + int triggerPastDayTraceWrite; + + int lockThreadsCount; + threadT *lockThreads[LOCK_THREADS_MAX]; + + struct timespec hungTimer1; + struct timespec hungTimer2; + std::mutex hungTimerMutex; + char *currentTask; + int64_t joinTimeout; + + unsigned first_free_buffer; // Entry in mag_buffers that will next be filled with input. + unsigned first_filled_buffer; // Entry in mag_buffers that has valid data and will be demodulated next. If equal to next_free_buffer, there is no unprocessed data. + unsigned trailing_samples; // extra trailing samples in magnitude buffers + int volatile exit; // Exit from the main loop when true + int volatile exitSoon; + int fd; // --ifile option file descriptor + input_format_t input_format; // --iformat option + iq_convert_fn converter_function; + char * dev_name; + std::mutex sdrControlMutex; + int8_t sdrInitialized; + int8_t sdrOpenFailed; + int8_t increaseGain; + int8_t lowerGain; + int8_t autoGain; + int8_t gainQuiet; + int8_t gainStartup; + char *gainArg; + uint32_t loudThreshold; + uint32_t noiseLowThreshold; + uint32_t noiseHighThreshold; + int minGain; + int gain; + int dc_filter; // should we apply a DC filter? + int enable_agc; + sdr_type_t sdr_type; // where are we getting data from? + int freq; + int ppm_error; + char aneterr[ANET_ERR_LEN]; + struct net_service_group services_in; // Active services which primarily receive data + struct net_service_group services_out; // Active services which primarily send data + int exitNowEventfd; + int exitSoonEventfd; + + int net_epfd; // epoll fd used for most network stuff + int net_event_count; + int net_maxEvents; + + struct epoll_event *net_events; + + struct messageBuffer *netMessageBuffer; + int decodeThreads; + threadpool_t *decodePool; + task_group_t *decodeTasks; + std::mutex decodeLock; + std::mutex trackLock; + std::mutex outputLock; + + int max_fds; + int max_fds_api; + int max_fds_net; + int modesClientCount; + int api_fds_per_thread; + int total_aircraft_count; + int json_aircraft_count; + float estimated_ppm; + uint64_t trace_chunk_size; + uint64_t trace_cache_size; + uint64_t trace_current_size; + uint64_t trace_last_size; + uint64_t aircraft_data_size; + + size_t volatile state_chunk_size; + size_t volatile state_chunk_size_read; + + + int acHashBits; + int acBuckets; + struct aircraft **aircraft; + struct aircraftBack *aircraftBack; + struct aircraft *aircraftBackFree; + std::mutex aircraftBackMutex; + std::mutex aircraftCreateMutex; + std::atomic_int aircraftBackSpinlock; + + struct craftArray *globeLists; + + int receiver_table_hash_bits; + int receiver_table_size; + struct receiver **receiverTable; + struct craftArray aircraftActive; + dbEntry *db; + dbEntry **dbIndex; + struct char_buffer dbRaw; + dbEntry *db2; + dbEntry **db2Index; + struct char_buffer db2Raw; + int64_t dbModificationTime; + int64_t receiverCount; + struct net_writer raw_out; // Raw output + struct net_writer beast_out; // Beast-format output + struct net_writer beast_reduce_out; // Reduced data Beast-format output + struct net_writer beast_in; // for sending pings to clients sending us beast data + struct net_writer garbage_out; // Beast-format output + struct net_writer sbs_out; // SBS-format output + struct net_writer sbs_out_replay; // SBS-format output + struct net_writer sbs_out_mlat; // SBS-format output + 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 + struct net_writer gpsd_in; // for sending 1 line to gpsd + struct net_writer uat_replay_out; // UAT replay output + struct net_service *beast_in_service; + struct net_service *uat_in_service; + + struct hexInterval* deleteTrace; + + int write_state_blob; + int writeInternalState; + char *replace_state_blob; + int64_t replace_state_inhibit_traces_until; + int64_t network_time_limit; + uint32_t currentPing; + + int8_t apiUpdate; // creates json snippets also by non api stuff + int8_t api; // enable api output + int8_t apiBufferInitDone; + int apiThreadCount; + std::atomic_int apiWorkerCpuMicro; + atomic_uint apiRequestCounter; + std::atomic_int recentTraceWrites; + std::atomic_int fullTraceWrites; + std::atomic_int permTraceWrites; + struct net_service apiService; + struct apiCon **apiListeners; + + struct apiBuffer apiBuffer[2]; + std::atomic_int *apiFlip; + struct apiThread *apiThread; + std::mutex apiFlipMutex; // mutex to read apiFlip + + float messageRate; + uint32_t messageRateAcc[MESSAGE_RATE_CALC_POINTS]; + int64_t nextMessageRateCalc; + + // Configuration + int8_t nfix_crc; // Number of crc bit error(s) to correct + int8_t fixDF; // fix message type single bit errors that become DF17 + int8_t check_crc; // Only display messages with good CRC + int8_t raw; // Raw output format + int8_t mode_ac; // Enable decoding of SSR Modes A & C + int8_t mode_ac_auto; // allow toggling of A/C by Beast commands + int8_t debug_net; + int8_t debug_serial; + int8_t debug_flush; + int8_t debug_no_discard; + int8_t debug_nextra; + int8_t debug_cpr; + int8_t debug_speed_check; + int8_t debug_garbage; + int8_t debug_receiver; + int8_t debug_rough_receiver_location; + int8_t debug_traceCount; + int8_t debug_traceAlloc; + int8_t debug_sampleCounter; + int8_t debug_dbJson; + int8_t debug_ACAS; + int8_t debug_api; + int8_t debug_recent; + int8_t debug_squawk; + int8_t debug_ping; + int8_t debug_callsign; + int8_t debug_nogps; + int8_t debug_uuid; + int8_t debug_bogus; + int8_t decode_all; + int8_t debug_maxRange; + int8_t debug_removeStaleDuration; + int8_t debug_receiverRangeLimit; + int8_t debug_yeet; + int8_t debug_7700; + int8_t debug_send_uuid; + int8_t debug_provoke_segfault; + int8_t debug_position_timing; + int8_t debug_lastStatus; + int8_t debug_gps; + int8_t debug_planefinder; + int8_t debug_zstd; + int8_t legacy_history; + int8_t enable_zstd; + int8_t incrementId; + int8_t omitGlobeFiles; + int8_t tar1090_no_globe; + int8_t enableAcasCsv; + int8_t enableAcasJson; + int8_t dump_accept_synthetic_now; + int8_t dump_ignore_synthetic_now; + int8_t syntethic_now_suppress_errors; + int8_t tar1090_use_api; + int8_t verbose; + + int8_t net_verbatim; // if true, send the original message, not the CRC-corrected one + int8_t netReceiverId; + int8_t ping; + int8_t netReceiverIdPrint; + int8_t netReceiverIdJson; + int8_t netIngest; + int8_t readProxy; + int8_t enableClientsJson; + int8_t forward_mlat; // forward beast mlat messages to beast output ports + int8_t forward_mlat_sbs; // forward mlat messages to sbs output ports + int8_t beast_forward_noforward; + int8_t beast_set_noforward_timestamp; + int8_t quiet; // Suppress stdout + int8_t interactive; // Interactive mode + int8_t stats_range_histo; // Collect/show a range histogram? + int8_t outline_json; // write a range outline json file + int8_t onlyaddr; // Print only ICAO addresses + int8_t metric; // Use metric units + int8_t use_gnss; // Use GNSS altitudes with H suffix ("HAE", though it isn't always) when available + int8_t mlat; // Use Beast ascii format for raw data output, i.e. @...; iso *...; + int8_t json_location_accuracy; // Accuracy of location metadata: 0=none, 1=approx, 2=exact + + int8_t net; // Enable networking + int8_t net_only; // Enable just networking + 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 + int8_t beast_reduce_optimize_mlat; // keep all messages relevant to mlat-client (best-effort) + // use mmap for certain allocations to try and take advantage of transparent hugepages + int8_t thp; + + int ingestLimitRate; + int position_persistence; // Maximum number of consecutive implausible positions from global CPR to invalidate a known position + int json_reliable; + + uint32_t filterDF; // Only show messages with certain DF types + uint32_t filterDFbitset; // Bitset, Only show messages with these DF types + + int64_t trackExpireJaero; + int64_t trackExpireMax; + + uint32_t cpr_focus; + uint32_t trace_focus; + uint32_t leg_focus; + uint32_t show_only; // Only show messages from this ICAO + uint32_t process_only; // Only process messages from this ICAO + uint64_t receiver_focus; + + uint32_t preambleThreshold; + uint32_t net_forward_min_messages; + int net_output_flush_size; // Minimum Size of output data + int writerBufSize; // Maximum Size of output data + int32_t net_output_beast_reduce_interval; // Position update interval for data reduction + int32_t ping_reduce; + int32_t ping_reject; + int32_t log_usb_jitter; + int32_t devel_log_ppm; + int32_t traceLastMax; + int32_t beforeLandHighRes; + int32_t afterGroundTransitionHighRes; + float beast_reduce_filter_distance; + float beast_reduce_filter_altitude; + int64_t net_connector_delay; + int64_t net_connector_delay_min; + int64_t next_reconnect_callback; + int64_t last_connector_fail; + int32_t net_heartbeat_interval; // TCP heartbeat interval (milliseconds) + int32_t net_output_flush_interval; // Maximum interval (in milliseconds) between outputwrites + int32_t net_output_flush_interval_beast_reduce; // Maximum interval (in milliseconds) between outputwrites + double fUserLat; // Users receiver/antenna lat/lon needed for initial surface location + double fUserLon; // Users receiver/antenna lat/lon needed for initial surface location + double fUserAlt; // receiver altitude AMSL in meters + double maxRange; // Absolute maximum decoding range, in *metres* + char *latString; + char *lonString; + double sample_rate; // actual sample rate in use (in hz) + int64_t interactive_display_ttl; // Interactive mode: TTL display + int64_t json_interval; // Interval between rewriting the json aircraft file, in milliseconds; also the advertised map refresh interval + int64_t stats_display_interval; // Interval (millis) between stats dumps, + int64_t range_outline_duration; + int64_t writeTracesActualDuration; // how long the trace writing cycle took + int64_t auto_exit; + double mlatForceDistance; + int64_t mlatForceInterval; + char *db_file; + 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_uat_replay_ports; // List of UAT replay output TCP ports + char *net_input_uat_ports; // List of UAT input TCP ports + char *net_input_planefinder_ports; // List of planefinder input TCP ports + char *net_output_sbs_ports; // List of SBS output TCP ports + char *net_input_sbs_ports; // List of SBS input TCP ports + char *net_output_jaero_ports; // jaero SBS output ports + char *net_input_jaero_ports; // jaero SBS input 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_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; + char *net_output_vrs_ports; // List of VRS output TCP ports + int64_t net_output_vrs_interval; + int64_t net_output_json_interval; + int net_output_json_include_nopos; + struct net_connector *net_connectors; // client connectors + int net_connectors_count; + int net_connectors_size; + int64_t synthetic_now; + char *uuidFile; + char *filename; // Input form file, --ifile option + char *net_bind_address; // Bind address + char *json_dir; // Path to json base directory, or NULL not to write json. + char *globe_history_dir; + char *fullTraceDir; // reduce memory usage in /run tmpfs by writing full traces to this directory + char *state_dir; + char *state_parent_dir; + char *dump_beast_dir; // write raw beast with a timestamp every millisecond for low level replay + zstd_fw_t *dump_fw; + int64_t dump_next_ts; // last timestamp sent + int32_t dump_interval; + int32_t dump_beast_index; + uint64_t dump_lastReceiverId; + int8_t dump_compressionLevel; + int8_t writeTraces; + int8_t dump_reduce; // only dump beast that would be sent out according to reduce_interval + int8_t state_only_on_exit; + int8_t quickFree; + int64_t state_write_interval; + char *prom_file; + int64_t heatmap_current_interval; + int64_t heatmap_interval; // don't change data type + int heatmap; + char *heatmap_dir; + int64_t keep_traces; // how long traces are saved in internal memory + int64_t json_trace_interval; // max time ignoring new positions for trace + int32_t traceMax; // max trace length + int32_t traceReserve; // grow trace allocation if we have less than traceReserve free spots + int32_t traceRecentPoints; + int32_t traceCachePoints; + int32_t traceChunkPoints; + int32_t traceChunkMaxBytes; + int json_globe_index; // Enable extra globe indexed json files. + int acasFD1; // file descriptor to write acasFDs to + int acasFD2; + struct tile *json_globe_special_tiles; + int32_t *json_globe_indexes; + int32_t json_globe_indexes_len; + int specialTileCount; + int json_gzip; // Enable extra globe indexed json files. + + int beast_fd; // Local Modes-S Beast handler + int beast_baudrate; // Mode-S beast and similar baud rate + char *beast_serial; // Modes-S Beast device path + struct client *serial_client; + + int net_sndbuf_size; // TCP output buffer size (2^n multiplier) + int netBufSize; // TCP output buffer size + int json_aircraft_history_next; + int json_aircraft_history_full; + int trace_hist_only; + int sbsOverrideSquawk; + float messageRateMult; + uint32_t binCraftVersion; // never change the type for this variable + int8_t userLocationValid; + int8_t userLocationRef; + int8_t biastee; + int8_t triggerPermWriteDay; + int8_t acasDay; + int8_t traceDay; + int8_t onlyBin; // only write binCraft for globe (1) and also aircraft.json (2) + int8_t enableBinGz; + + int8_t updateStats; + int8_t staleStop; + + int8_t tcpBuffersAuto; + + struct timespec reader_cpu_accumulator; // CPU time used by the reader thread, copied out and reset by the main thread under the mutex + ALIGNED struct mag_buf mag_buffers[MODES_MAG_BUFFERS]; // Converted magnitude buffers from RTL or file input + + int64_t startup_time; + int64_t startup_time_mono; + int64_t next_stats_update; + int64_t next_stats_display; + int64_t next_api_update; + int64_t next_remove_stale; + int stats_bucket; // index that has just been writte to + ALIGNED struct stats stats_10[STAT_BUCKETS]; + struct stats stats_current; + struct stats stats_alltime; + struct stats stats_periodic; + struct stats stats_1min; + struct stats stats_5min; + struct stats stats_15min; + + struct statsCount globalStatsCount; + + int lastRangeDirHour; + struct distCoords (*rangeDirs)[RANGEDIRS_BUCKETS]; + + int64_t apiShutdownDelay; +}; + +extern struct _Modes Modes; + +// The struct we use to store information about a decoded message. + +struct modesMessage +{ + // Generic fields + unsigned char msg[MODES_LONG_MSG_BYTES]; // Binary message. + unsigned char verbatim[MODES_LONG_MSG_BYTES]; // Binary message, as originally received before correction + double signalLevel; // RSSI, in the range [0..1], as a fraction of full-scale power + struct client *client; // network client this message came from, NULL otherwise + struct aircraft *aircraft; // tracked aircraft associated with this message or NULL + struct messageBuffer *messageBuffer; + + int64_t timestamp; // Timestamp of the message (12MHz clock) + int64_t sysTimestamp; // Timestamp of the message (system time) + uint64_t receiverId; // zero if not transmitted + int msgtype; // Downlink format # + int msgbits; // Number of bits in message + int score; // Scoring from scoreModesMessage, if used + int receiverCountMlat; // number of receivers for MLAT messages + int mlatEPU; // estimated position uncertainty + int correctedbits; // No. of bits corrected + int decodeResult; + uint32_t crc; // Message CRC + uint32_t addr; // Address Announced + uint32_t maybe_addr; // probably the address, good chance to be wrong + addrtype_t addrtype; // address format / source + int8_t remote; // If set this message is from a remote station + int8_t sbs_in; // Signifies this message is coming from basestation input + int8_t address_reliable; + int8_t sbsMsgType; // SBS message type + int8_t reduce_forward; // forward this message for reduced beast output + int8_t garbage; // from garbage receiver + int8_t duplicate; // associated position is a duplicate + int8_t duplicate_checked; // duplicate check done + int8_t pos_bad; // speed_check failed + int8_t pos_ignore; // associated position is old / delayed / misc error + int8_t pos_old; // associated position is old / delayed / misc error + int8_t pos_receiver_range_exceeded; + int8_t trackUnreliable; + int8_t speedUnreliable; + int8_t in_disc_cache; + int8_t jsonPositionOutputEmit; + datasource_t source; // Characterizes the overall message source + // Raw data, just extracted directly from the message + // The names reflect the field names in Annex 4 + unsigned IID; // extracted from CRC of DF11s + unsigned AA; + unsigned AC; + unsigned CA; + unsigned CC; + unsigned CF; + unsigned DR; + unsigned FS; + unsigned ID; + unsigned KE; + unsigned ND; + unsigned RI; + unsigned SL; + unsigned UM; + unsigned VS; + unsigned metype; // DF17/18 ME type + unsigned mesub; // DF17/18 ME subtype + + unsigned char MB[7]; + unsigned char MD[10]; + unsigned char ME[7]; + unsigned char MV[7]; + + // Decoded data + bool baro_alt_valid; + bool geom_alt_valid; + bool track_valid; + bool track_rate_valid; + bool heading_valid; + bool roll_valid; + bool gs_valid; + bool ias_valid; + bool tas_valid; + bool mach_valid; + bool baro_rate_valid; + bool geom_rate_valid; + bool squawk_valid; + bool callsign_valid; + bool cpr_valid; + bool cpr_odd; + bool cpr_decoded; + bool cpr_relative; + bool category_valid; + bool geom_delta_valid; + bool from_mlat; + bool from_tisb; + bool spi_valid; + bool spi; + bool alert_valid; + bool alert; + bool emergency_valid; + bool sbs_pos_valid; + 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; + + // valid if baro_alt_valid: + int baro_alt; // Altitude in either feet or meters + altitude_unit_t baro_alt_unit; // the unit used for altitude + + // valid if geom_alt_valid: + int geom_alt; // Altitude in either feet or meters + altitude_unit_t geom_alt_unit; // the unit used for altitude + + // following fields are valid if the corresponding _valid field is set: + int geom_delta; // Difference between geometric and baro alt + float heading; // ground track or heading, degrees (0-359). Reported directly or computed from from EW and NS velocity + heading_type_t heading_type; // how to interpret 'track_or_heading' + float track_rate; // Rate of change of track, degrees/second + float roll; // Roll, degrees, negative is left roll + + struct + { + // Groundspeed, kts, reported directly or computed from from EW and NS velocity + // For surface movement, this has different interpretations for v0 and v2; both + // fields are populated. The tracking layer will update "gs.selected". + float v0; + float v2; + float selected; + } gs; + unsigned ias; // Indicated airspeed, kts + unsigned tas; // True airspeed, kts + double mach; // Mach number + int baro_rate; // Rate of change of barometric altitude, feet/minute + int geom_rate; // Rate of change of geometric (GNSS / INS) altitude, feet/minute + char callsign[16]; // 8 chars flight number, NUL-terminated + uint32_t squawkHex; // 13 bits identity (Squawk), encoded as 4 hex digits + uint32_t squawkDec; // Squawk as a decimal number + unsigned category; // A0 - D7 encoded as a single hex byte + emergency_t emergency; // emergency/priority status + + // valid if cpr_valid + cpr_type_t cpr_type; // The encoding type used (surface, airborne, coarse TIS-B) + uint32_t cpr_lat; // Non decoded latitude. + uint32_t cpr_lon; // Non decoded longitude. + uint32_t cpr_nucp; // NUCp/NIC value implied by message type + + airground_t airground; // air/ground state + + // valid if cpr_decoded: + double decoded_lat; + double decoded_lon; + unsigned decoded_nic; + unsigned decoded_rc; + + double distance_traveled; // set in speed_check, zero is invalid + 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 + + struct + { + bool nic_a_valid; + bool nic_b_valid; + bool nic_c_valid; + bool nic_baro_valid; + bool nac_p_valid; + bool nac_v_valid; + bool gva_valid; + bool sda_valid; + + bool nic_a; // if nic_a_valid + bool nic_b; // if nic_b_valid + bool nic_c; // if nic_c_valid + bool nic_baro; // if nic_baro_valid + + unsigned nac_p; // if nac_p_valid + unsigned nac_v; // if nac_v_valid + + unsigned sil; // if sil_type != SIL_INVALID + + unsigned gva; // if gva_valid + + unsigned sda; // if sda_valid + sil_type_t sil_type; + } accuracy; + + // Operational Status + + struct + { + sil_type_t sil_type; + heading_type_t tah; + heading_type_t hrd; + enum + { + ANGLE_HEADING, ANGLE_TRACK + } track_angle; + + unsigned cc_lw; + unsigned cc_antenna_offset; + + unsigned valid : 1; + unsigned version : 3; + + unsigned om_acas_ra : 1; + unsigned om_ident : 1; + unsigned om_atc : 1; + unsigned om_saf : 1; + + unsigned cc_acas : 1; + unsigned cc_cdti : 1; + unsigned cc_1090_in : 1; + unsigned cc_arv : 1; + unsigned cc_ts : 1; + unsigned cc_tc : 2; + unsigned cc_uat_in : 1; + unsigned cc_poa : 1; + unsigned cc_b2_low : 1; + unsigned cc_lw_valid : 1; + } opstatus; + + // combined: + // Target State & Status (ADS-B V2 only) + // Comm-B BDS4,0 Vertical Intent + + struct + { + unsigned fms_altitude; // FMS selected altitude + unsigned mcp_altitude; // MCP/FCU selected altitude + float qnh; // altimeter setting (QFE or QNH/QNE), millibars + float heading; // heading, degrees (0-359) (could be magnetic or true heading; magnetic recommended) + bool heading_valid; + bool fms_altitude_valid; + bool mcp_altitude_valid; + bool qnh_valid; + bool modes_valid; + heading_type_t heading_type; + + nav_altitude_source_t altitude_source; + + nav_modes_t modes; + } nav; +}; + +/* All the program options */ +enum { + OptDeviceType = 700, + OptDevice, + OptGain, + OptFreq, + OptInteractive, + OptNoInteractive, + OptInteractiveTTL, + OptRaw, + OptPreambleThreshold, + OptModeAc, + OptModeAcAuto, + OptForwardMlat, + OptForwardMlatSbs, + OptLat, + OptLon, + OptMaxRange, + OptFix, + OptNoFix, + OptNoFixDf, + OptAggressive, + OptMlat, + OptAutoExit, + OptStats, + OptStatsRange, + OptStatsEvery, + OptRangeOutlineDuration, + OptOnlyAddr, + OptMetric, + OptGnss, + OptSnip, + OptDebug, + OptDevel, + OptReceiverFocus, + OptCprFocus, + OptLegFocus, + OptTraceFocus, + OptQuiet, + OptShowOnly, + OptProcessOnly, + OptFilterDF, + OptJsonDir, + OptJsonGzip, + OptJsonOnlyBin, + OptEnableBinGz, + OptJsonReliable, + OptPositionPersistence, + OptJaeroTimeout, + OptDbFile, + OptDbFileLongtype, + OptPromFile, + OptGlobeHistoryDir, + OptStateDir, + OptStateInterval, + OptStateOnlyOnExit, + OptHeatmap, + OptHeatmapDir, + OptDumpBeastDir, + OptJsonTime, + OptJsonLocAcc, + OptJsonGlobeIndex, + OptAcHashBits, + OptJsonTraceInt, + OptJsonTraceHistOnly, + OptFullTraceDir, + OptDcFilter, + OptBiasTee, + OptNet, + OptNetOnly, + OptNetBindAddr, + OptNetRiPorts, + OptNetRoPorts, + OptNetUatReplayPorts, + OptNetUatInPorts, + OptNetSbsPorts, + OptNetSbsInPorts, + OptNetJaeroPorts, + OptNetJaeroInPorts, + OptNetBiPorts, + OptNetBoPorts, + OptNetAsterixInPorts, + OptNetAsterixOutPorts, + OptNetAsterixReduce, + OptNetBeastReducePorts, + OptNetBeastReduceInterval, + OptNetBeastReduceOptimizeMlat, + OptNetBeastReduceFilterAlt, + OptNetBeastReduceFilterDist, + OptNetSbsReduce, + OptNetVRSPorts, + OptNetVRSInterval, + OptNetJsonPorts, + OptNetJsonPortInterval, + OptNetJsonPortNoPos, + OptNetApiPorts, + OptApiShutdownDelay, + OptTar1090UseApi, + OptNetRoSize, + OptNetRoInterval, + OptNetRoIntervalBeastReduce, + OptNetConnector, + OptNetConnectorDelay, + OptNetHeartbeat, + OptNetBuffer, + OptTcpBuffersAuto, + OptNetVerbatim, + OptNetReceiverId, + OptNetReceiverIdJson, + OptNetIngest, + OptSdrBufSize, + OptGarbage, + OptDecodeThreads, + OptUuidFile, + OptRtlSdrEnableAgc, + OptRtlSdrPpm, + OptBeastSerial, + OptBeastBaudrate, + OptBeastDF1117, + OptBeastDF045, + OptBeastMlatTimeOff, + OptBeastCrcOff, + OptBeastFecOff, + OptBeastModeAc, + OptIfileName, + OptIfileFormat, + OptIfileThrottle, + OptBladeFpgaDir, + OptBladeDecim, + OptBladeBw, + OptHackRfGainEnable, + OptHackRfVgaGain, + OptPlutoUri, + OptPlutoNetwork, + OptSoapyAntenna, + OptSoapyBandwith, + OptSoapyEnableAgc, + OptSoapyGainElement, +}; + + +// This one needs modesMessage: +#include "track.h" +#include "mode_s.h" +#include "comm_b.h" + +// ======================== function declarations ========================= + +#ifdef __cplusplus +extern "C" +{ +#endif + + // + // Functions exported from mode_ac.c + // + int detectModeA (uint16_t *m, struct modesMessage *mm); + void decodeModeAMessage (struct modesMessage *mm, int ModeA); + void modeACInit (); + int modeAToModeC (unsigned int modeA); + unsigned modeCToModeA (int modeC); + + // + // Functions exported from interactive.c + // + void interactiveInit (void); + void interactiveShowData (void); + void interactiveCleanup (void); + + // Provided by readsb.c & viewadsb.c + void receiverPositionChanged (float lat, float lon, float alt); + +#ifdef __cplusplus +} +#endif + +#endif // __DUMP1090_H + diff --git a/readsb2/track.cpp b/readsb2/track.cpp new file mode 100644 index 0000000..b840d49 --- /dev/null +++ b/readsb2/track.cpp @@ -0,0 +1,3994 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// track.c: aircraft state tracking +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2014-2016 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// This file incorporates work covered by the following copyright and +// license: +// +// Copyright (C) 2012 by Salvatore Sanfilippo +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "readsb.h" + +uint32_t modeAC_count[4096]; +uint32_t modeAC_lastcount[4096]; +uint32_t modeAC_match[4096]; +uint32_t modeAC_age[4096]; + +#define PPforward if (0) {fprintf(stderr, "%s %d\n", __FILE__, __LINE__);} + +static void showPositionDebug(struct aircraft *a, struct modesMessage *mm, int64_t now, double bad_lat, double bad_lon); +static void position_bad(struct modesMessage *mm, struct aircraft *a); +static void calc_wind(struct aircraft *a, int64_t now); +static void calc_temp(struct aircraft *a, int64_t now); +static inline int declination(struct aircraft *a, double *dec, int64_t now); +static const char *source_string(datasource_t source); +static void incrementReliable(struct aircraft *a, struct modesMessage *mm, int64_t now, int odd); + +static uint16_t simpleHash(uint64_t receiverId) { + uint16_t simpleHash = receiverId; + simpleHash ^= (uint16_t) (receiverId >> 16); + simpleHash ^= (uint16_t) (receiverId >> 32); + simpleHash ^= (uint16_t) (receiverId >> 48); + if (simpleHash == 0) + return 1; + + return simpleHash; +} + +static float knots_to_meterpersecond = (1852.0 / 3600.0); + +static void calculateMessageRateGlobal(int64_t now) { + float sum = 0.0f; + float mult = REMOVE_STALE_INTERVAL / 1000.0f; + float multSum = 0.0f; + for (int k = 0; k < MESSAGE_RATE_CALC_POINTS; k++) { + sum += Modes.messageRateAcc[k] * mult; + multSum += mult; + mult *= 0.7f; + } + + Modes.messageRate = sum / multSum * Modes.messageRateMult; + Modes.nextMessageRateCalc = now + REMOVE_STALE_INTERVAL; + + memmove(&Modes.messageRateAcc[1], &Modes.messageRateAcc[0], sizeof(uint32_t) * (MESSAGE_RATE_CALC_POINTS - 1)); + Modes.messageRateAcc[0] = 0; + + //fprintf(stderr, "%.3f\n", Modes.messageRate); +} + +static void calculateMessageRate(struct aircraft *a, int64_t now) { + float sum = 0.0f; + float mult = REMOVE_STALE_INTERVAL / 1000.0f; + float multSum = 0.0f; + for (int k = 0; k < MESSAGE_RATE_CALC_POINTS; k++) { + sum += a->messageRateAcc[k] * mult; + multSum += mult; + mult *= 0.7f; + } + + a->messageRate = sum / multSum * Modes.messageRateMult; + a->nextMessageRateCalc = now + REMOVE_STALE_INTERVAL; + + memmove(&a->messageRateAcc[1], &a->messageRateAcc[0], sizeof(uint16_t) * (MESSAGE_RATE_CALC_POINTS - 1)); + a->messageRateAcc[0] = 0; +} + + +// Should we accept some new data from the given source? +// If so, update the validity and return 1 + +static inline int32_t currentReduceInterval(int64_t now) { + MODES_NOTUSED(now); + return Modes.net_output_beast_reduce_interval; +} + +static inline int will_accept_data(data_validity *d, datasource_t source, struct modesMessage *mm, struct aircraft *a) { + int64_t now = mm->sysTimestamp; + if (source == SOURCE_INVALID) { + return 0; + } + + if (now < d->updated) { + return 0; + } + + // increase timeout to use lower quality data by the time no CRC reliable (addressReliable()) has been seen: + // if (source < d->source && (now < d->updated + TRACK_STALE + (now - a->seen))) { + // this simplifies to: + // basically an reliable CRC needs to have been received TRACK_STALE (15s) after the higher + // quality info was received + if (source < d->source && (a->seen < d->updated + TRACK_STALE)) { + return 0; + } + + // this is a position and will be wholly reverted if it's not accepted + //int is_pos = (mm->sbs_pos_valid || mm->cpr_valid || d == &a->pos_reliable_valid || d == &a->position_valid || d == &a->cpr_odd_valid || d == &a->cpr_even_valid || d == &a->mlat_pos_valid); + int is_pos = (mm->sbs_pos_valid || mm->cpr_valid); + + // if we have a jaero position, don't allow non-position data to have a lesser source + if (!is_pos && a->pos_reliable_valid.source == SOURCE_JAERO && source < SOURCE_JAERO) { + return 0; + } + + // prevent JAERO from disrupting other data sources too quickly + if (source == SOURCE_JAERO && a->pos_reliable_valid.last_source != SOURCE_JAERO) { + if (Modes.trackExpireJaero >= 10 * MINUTES && now < d->updated + 5 * MINUTES) { + return 0; + } + } + + // if we have recent data and a recent position, only accept data from the last couple receivers that contributed a position + // this hopefully reduces data jitter introduced by differing receiver latencies + // it's important to excluded data coming in alongside positions, that extra data if accepted is discarded via the scratch mechanism + + if (Modes.netReceiverId && !is_pos && a->pos_reliable_valid.source >= SOURCE_TISB && now - d->updated < 5 * SECONDS && now - a->seenPosReliable < 2200 * MS) { + uint16_t hash = simpleHash(mm->receiverId); + uint32_t found = 0; + for (int i = 0; i < RECEIVERIDBUFFER; i++) { + found += (a->receiverIds[i] == hash); + } + if (!found) { + return 0; + } + if (Modes.netIngest) { + // ingest: forward all nonposition messages from receivers on the receiverIds list + // receivers on the receiverIds list are the receivers with the lowest latency + mm->reduce_forward = 1; + } + } + + if (0 && is_pos && a->addr == Modes.cpr_focus) { + //fprintf(stderr, "%d %p %p\n", mm->duplicate, d, &a->pos_reliable_valid); + fprintf(stderr, "%d %s", mm->duplicate, source_string(mm->source)); + } + + return 1; +} + +static int accept_data(data_validity *d, datasource_t source, struct modesMessage *mm, struct aircraft *a, int reduce_often) { + + if (!will_accept_data(d, source, mm, a)) { + return 0; + } + + int64_t now = mm->sysTimestamp; + + d->source = source; + if (unlikely(source == SOURCE_PRIO)) { + d->source = SOURCE_ADSB; + } + + d->last_source = d->source; + + d->updated = now; + d->stale = 0; + + if (now > d->next_reduce_forward || mm->reduce_forward) { + int32_t reduceInterval = currentReduceInterval(now); + + if (reduce_often == REDUCE_DOUBLE) { + reduceInterval = reduceInterval * 3 / 8; + } else if (reduce_often == REDUCE_OFTEN) { + reduceInterval = reduceInterval * 7 / 8; + } else if (reduce_often == REDUCE_RARE) { + reduceInterval = reduceInterval * 4; + } + + if (mm->cpr_valid && reduceInterval > 7000) { + // make sure global CPR stays possible even at high interval: + reduceInterval = 7000; + } + + d->next_reduce_forward = now + reduceInterval; + + mm->reduce_forward = 1; + PPforward; + } + return 1; +} + + +// Given two datasources, produce a third datasource for data combined from them. + +static void combine_validity(data_validity *to, const data_validity *from1, const data_validity *from2, int64_t now) { + if (from1->source == SOURCE_INVALID) { + *to = *from2; + return; + } + + if (from2->source == SOURCE_INVALID) { + *to = *from1; + return; + } + + to->source = (from1->source < from2->source) ? from1->source : from2->source; // the worse of the two input sources + to->last_source = to->source; + to->updated = (from1->updated > from2->updated) ? from1->updated : from2->updated; // the *later* of the two update times + to->stale = (now > to->updated + TRACK_STALE); +} + +static int compare_validity(const data_validity *lhs, const data_validity *rhs) { + if (!lhs->stale && lhs->source > rhs->source) + return 1; + else if (!rhs->stale && lhs->source < rhs->source) + return -1; + else if (lhs->updated >= rhs->updated) + return 1; + else if (lhs->updated < rhs->updated) + return -1; + else + return 0; +} + +static void update_range_histogram(struct aircraft *a, int64_t now) { + if (!Modes.outline_json) { + return; + } + + int rangeDirIval = (now * (RANGEDIRS_IVALS - 1) / Modes.range_outline_duration) % RANGEDIRS_IVALS; + if (rangeDirIval != Modes.lastRangeDirHour) { + //log_with_timestamp("rangeDirIval: %d", rangeDirIval); + // when the current interval changes, reset the data for it + memset(Modes.rangeDirs[rangeDirIval], 0, RANGEDIRS_BUCKETS * sizeof(struct distCoords)); + Modes.lastRangeDirHour = rangeDirIval; + } + + if (!a) { + return; + } + + double lat = a->lat; + double lon = a->lon; + + double range = a->receiver_distance; + + int rangeDirDirection = ((int) nearbyint(a->receiver_direction)) % RANGEDIRS_BUCKETS; + + struct distCoords *current = &(Modes.rangeDirs[rangeDirIval][rangeDirDirection]); + + + // if the position isn't proper reliable, only allow it if the range in that direction is increased by less than 25 nmi compared to the maximum of the last 24h + if (range > current->distance && (a->pos_reliable_odd < 2 || a->pos_reliable_even < 2)) { + float directionMax = 0; + for (int i = 0; i < RANGEDIRS_IVALS; i++) { + if (Modes.rangeDirs[i][rangeDirDirection].distance > directionMax) + directionMax = Modes.rangeDirs[i][rangeDirDirection].distance; + } + directionMax += 50.0f * 1852.0f; // allow 50 nmi more than recorded for that direction in the last 24h + if (range > directionMax && !Modes.debug_bogus && Modes.json_reliable > 0) { + return; + } + //fprintf(stderr, "actual %.1f max %.1f\n", range / 1852.0f, (directionMax / 1852.0f)); + } + + if (range > current->distance) { + current->distance = range; + current->lat = lat; + current->lon = lon; + if (trackDataValid(&a->baro_alt_valid) || !trackDataValid(&a->geom_alt_valid)) { + current->alt = a->baro_alt; + } else { + current->alt = a->geom_alt; + } + } + + if (range > Modes.stats_current.distance_max) + Modes.stats_current.distance_max = range; + if (range < Modes.stats_current.distance_min) + Modes.stats_current.distance_min = range; + + int bucket = round(range / Modes.maxRange * RANGE_BUCKET_COUNT); + + if (bucket < 0) + bucket = 0; + else if (bucket >= RANGE_BUCKET_COUNT) + bucket = RANGE_BUCKET_COUNT - 1; + + ++Modes.stats_current.range_histogram[bucket]; +} + +static int cpr_duplicate_check(int64_t now, struct aircraft *a, struct modesMessage *mm) { + + struct cpr_cache *cpr; + uint32_t inCache = 0; + uint32_t cpr_lat = mm->cpr_lat; + uint32_t cpr_lon = mm->cpr_lon; + uint64_t receiverId = mm->receiverId; + for (int i = 0; i < CPR_CACHE; i++) { + cpr = &a->cpr_cache[i]; + if ( + ( + now - cpr->ts < 2 * SECONDS + && cpr->cpr_lat == cpr_lat + && cpr->cpr_lon == cpr_lon + && cpr->receiverId != receiverId + ) + ) { + inCache += 1; + } + } + if (inCache > 0) { + mm->duplicate = 1; + return 1; + } else { + // CPR not yet known to cpr cache + + a->cpr_cache_index = (a->cpr_cache_index + 1) % CPR_CACHE; + + cpr = &a->cpr_cache[a->cpr_cache_index]; + cpr->ts = now; + cpr->cpr_lat = cpr_lat; + cpr->cpr_lon = cpr_lon; + cpr->receiverId = receiverId; + + return 0; + } +} + +static int duplicate_check(int64_t now, struct aircraft *a, double new_lat, double new_lon, struct modesMessage *mm) { + if (mm->duplicate_checked || mm->duplicate) { + // already checked + return mm->duplicate; + } + mm->duplicate_checked = 1; + + // if the last position is older than 2 seconds we don't consider it a duplicate + if (now > a->seen_pos + 2 * SECONDS) { + return 0; + } + // duplicate + if (a->lat == new_lat && a->lon == new_lon) { + mm->duplicate = 1; + return 1; + } + + // if the previous position is older than 2 seconds we don't consider it a duplicate + if (now > a->prev_pos_time + 2 * SECONDS) { + return 0; + } + // duplicate (this happens either with some transponder or delayed data arrival due to odd / even CPR, not certain) + if (a->prev_lat == new_lat && a->prev_lon == new_lon) { + mm->duplicate = 1; + return 1; + } + + return 0; +} + +static int uat2esnt_duplicate(int64_t now, struct aircraft *a, struct modesMessage *mm) { + return ( + mm->cpr_valid && mm->cpr_odd && mm->msgtype == 18 + && (mm->timestamp == MAGIC_UAT_TIMESTAMP || mm->timestamp == 0) + && now - a->seenPosReliable < 2500 + ); +} + +static int inDiscCache(int64_t now, struct aircraft *a, struct modesMessage *mm) { + struct cpr_cache *disc; + uint32_t inCache = 0; + uint32_t cpr_lat = mm->cpr_lat; + uint32_t cpr_lon = mm->cpr_lon; + uint64_t receiverId = mm->receiverId; + for (int i = 0; i < DISCARD_CACHE; i++) { + disc = &a->disc_cache[i]; + // don't decrement pos_reliable if we already got the same bad position within the last second + // rate limit reliable decrement per receiver + if ( + ( + now - disc->ts < 4 * SECONDS + && disc->cpr_lat == cpr_lat + && disc->cpr_lon == cpr_lon + ) + || + ( + now - disc->ts < 300 + && disc->receiverId == receiverId + ) + ) { + inCache += 1; + } + } + if (inCache > 0) { + return 1; + } else { + return 0; + } +} + +// return true if it's OK for the aircraft to have travelled from its last known position +// to a new position at (lat,lon,surface) at a time of now. + +static int speed_check(struct aircraft *a, datasource_t source, double lat, double lon, struct modesMessage *mm, cpr_local_t cpr_local) { + int64_t now = mm->sysTimestamp; + int64_t elapsed = trackDataAge(now, &a->position_valid); + int receiverRangeExceeded = 0; + + if (0 && mm->sbs_in && a->addr == Modes.cpr_focus) { + fprintf(stderr, "."); + } + + if (duplicate_check(now, a, lat, lon, mm)) { + // don't use duplicate positions + mm->pos_ignore = 1; + // but count it as a received position towards receiver heuristics + if (!Modes.userLocationRef) { + receiverPositionReceived(a, mm, lat, lon, now); + } + if (elapsed > 200 && a->receiverId == mm->receiverId && (Modes.debug_cpr || Modes.debug_speed_check || a->addr == Modes.cpr_focus)) { + // let speed_check continue for displaying this duplicate (at least for non-aggregated receivers) + } else { + // omit rest of speed check to save on cycles + return 1; + } + } + if (0 && (a->prev_lat == lat && a->prev_lon == lon) && (Modes.debug_cpr || Modes.debug_speed_check || a->addr == Modes.cpr_focus)) { + fprintf(stderr, "%06x now - seen_pos %6.3f now - prev_pos_time %6.3f\n", + a->addr, + (now - a->seen_pos) / 1000.0, + (now - a->prev_pos_time) / 1000.0 + ); + } + + if (mm->cpr_valid && inDiscCache(now, a, mm)) { + mm->in_disc_cache = 1; + } + + float distance = -1; + float range = -1; + float speed = -1; + float transmitted_speed = -1; + float calc_track = -1; + int inrange; + int override = 0; + double oldLat = a->lat; + double oldLon = a->lon; + + + int surface = trackDataValid(&a->airground_valid) + && a->airground == AG_GROUND + && a->pos_surface + && (!mm->cpr_valid || mm->cpr_type == CPR_SURFACE); + + + // json_reliable == -1 disables the speed check + if (Modes.json_reliable == -1 || mm->source == SOURCE_PRIO) { + override = 1; + } else if (bogus_lat_lon(lat, lon) || + (mm->cpr_valid && mm->cpr_lat == 0 && mm->cpr_lon == 0) + || ( + mm->cpr_valid && (mm->cpr_lat == 0 || mm->cpr_lon == 0) + && (a->position_valid.source < SOURCE_TISB || !posReliable(a)) + ) + ) { + mm->pos_ignore = 1; // don't decrement pos_reliable + } else if (a->pos_reliable_odd < 0.01f || a->pos_reliable_even < 0.01f) { + override = 1; + } else if (now - a->position_valid.updated > POS_RELIABLE_TIMEOUT) { + override = 1; // no reference or older than 60 minutes, assume OK + } else if (source > a->position_valid.source && source > a->position_valid.last_source && source > a->pos_reliable_valid.source) { + override = 1; // data is better quality, OVERRIDE + } else if (source > a->position_valid.source && a->position_valid.source == SOURCE_INDIRECT) { + override = 1; // data is better quality, OVERRIDE + } else if (source <= SOURCE_MLAT && elapsed > 45 * SECONDS) { + override = 1; + } else if (a->addr == 0xa19b53) { + // Virgin SS2 + override = 1; + } + + if (mm->in_disc_cache) { + override = 0; // don't override if in discard cache + } + + if (trackDataValid(&a->gs_valid)) { + // use the larger of the current and earlier speed + speed = (a->gs_last_pos > a->gs) ? a->gs_last_pos : a->gs; + // add 3 knots for every second we haven't known the speed and the position + speed = speed + (3 * trackDataAge(now, &a->gs_valid)/1000.0f) + (3 * trackDataAge(now, &a->position_valid)/1000.0f); + } else if (trackDataValid(&a->tas_valid)) { + speed = a->tas * 4 / 3; + } else if (trackDataValid(&a->ias_valid)) { + speed = a->ias * 2; + } + transmitted_speed = speed; + + // find actual distance + distance = greatcircle(oldLat, oldLon, lat, lon, 0); + mm->distance_traveled = distance; + + float track_diff = -1; + float track_bonus = 0; + int64_t track_max_age = 5 * SECONDS; + int64_t track_age = -1; + float track = -1; + if (trackDataAge(now, &a->track_valid) < track_max_age) { + track = a->track; + track_age = trackDataAge(now, &a->track_valid); + } else if (trackDataAge(now, &a->true_heading_valid) < track_max_age) { + track = a->true_heading; + track_age = trackDataAge(now, &a->true_heading_valid); + } + + if (distance > 2.5f) { + calc_track = bearing(oldLat, oldLon, lat, lon); + mm->calculated_track = calc_track; + if (source != SOURCE_MLAT + && track > -1 + && trackDataAge(now, &a->position_valid) < 7 * SECONDS + ) { + track_diff = fabs(norm_diff(track - calc_track, 180)); + } + } + + if (track_diff > 70.0f && speed > 10) { + mm->trackUnreliable = +1; + } else if (track_diff > -1) { + mm->trackUnreliable = -1; + } + + if (!posReliable(a)) { + // don't use track_diff for further calculations unless position is already reliable + track_diff = -1; + } + + if (speed < 0 || a->speedUnreliable > 8) { + speed = surface ? 120 : 900; // guess + } + + if (speed > 10 && track_diff > -1 && a->trackUnreliable < 8) { + track_bonus = speed * (90.0f - track_diff) / 90.0f; + track_bonus *= (surface ? 0.9f : 1.0f) * (1.0f - track_age / track_max_age); + if (a->gs < 10) { + // don't allow negative "bonus" below 10 knots speed + track_bonus = fmaxf(0.0f, track_bonus); + speed += 2; + } + speed += track_bonus; + if (track_diff > 160) { + mm->pos_old = 1; // don't decrement pos_reliable + } + // allow relatively big forward jumps + if (speed > 40 && track_diff < 10) { + range += 2e3; + } + } else { + // Work out a reasonable speed to use: + // current speed + 1/3 + speed = speed * 1.3f; + } + if (surface) { + range += 10; + } else { + range += 30; + } + + // same TCP packet (2 ms), two positions from same receiver id, allow plenty of extra range + if (elapsed < 2 && a->receiverId == mm->receiverId && source > SOURCE_MLAT) { + range += 500; // 500 m extra in this case + } + + // cap speed at 2000 knots .. + speed = fmin(speed, 2000); + + if (source == SOURCE_MLAT) { + speed *= 1.4; + speed += 50; + range += 250; + } + + if (transmitted_speed < 0) { + mm->speedUnreliable = -1; + } else if (distance > 2.5f && (track_diff < 70 || track_diff == -1)) { + if (distance <= range + (((float) elapsed + 50.0f) * (1.0f / 1000.0f)) * (transmitted_speed * knots_to_meterpersecond)) { + mm->speedUnreliable = -1; + } else if (distance > range + (((float) elapsed + 400.0f) * (1.0f / 1000.0f)) * (transmitted_speed * knots_to_meterpersecond)) { + mm->speedUnreliable = +1; + } + } + + // plus distance covered at the given speed for the elapsed time + 0.2 seconds. + range += (((float) elapsed + 200.0f) * (1.0f / 1000.0f)) * (speed * (1852.0f / 3600.0f)); + inrange = (distance <= range); + + // don't allow going backwards in the air contrary to good track information + // when only a short time has elapsed + // when the receiverId differs + // this mostly happens due to static range allowed + if (!surface && a->gs > 10 && track_diff > 135 && elapsed < 2 * SECONDS && trackDataAge(now, &a->track_valid) < 2 * SECONDS && a->receiverId != mm->receiverId) { + inrange = 0; + } + + float backInTimeSeconds = 0; + if (!inrange && a->gs > 10 && track_diff > 135 && trackDataAge(now, &a->gs_valid) < 10 * SECONDS) { + backInTimeSeconds = distance / (a->gs * (1852.0f / 3600.0f)); + } + + if (!Modes.userLocationRef && (inrange || override)) { + if (receiverPositionReceived(a, mm, lat, lon, now) == RECEIVER_RANGE_BAD) { + // far outside receiver area + receiverRangeExceeded = 1; + } + } + + if (!Modes.userLocationRef && !override && mm->source == SOURCE_ADSB) { + if (!receiverRangeExceeded && !inrange + && (distance - range > 800 || backInTimeSeconds > 3) && track_diff > 45 + && a->pos_reliable_odd >= Modes.position_persistence * 3 / 4 + && a->pos_reliable_even >= Modes.position_persistence * 3 / 4 + && a->trackUnreliable < 3 + ) { + struct receiver *r = receiverBad(mm->receiverId, a->addr, now); + if (r && Modes.debug_garbage && r->badCounter > 6) { + fprintf(stderr, "hex: %06x id: %016"PRIx64" #good: %6d #bad: %3.0f trackDiff: %3.0f: %7.2fkm/%7.2fkm in %4.1f s, max %4.0f kt\n", + a->addr, r->id, r->goodCounter, r->badCounter, + track_diff, + distance / 1000.0, + range / 1000.0, + elapsed / 1000.0, + speed + ); + + } + } + + } + + if ( + ( + ((!inrange && track_diff < 160) || (!surface && (a->speedUnreliable > 8 || a->trackUnreliable > 8))) + && source == a->position_valid.source && source > SOURCE_MLAT && (Modes.debug_cpr || Modes.debug_speed_check) + ) + || (a->addr == Modes.cpr_focus && source >= a->position_valid.source) + || (Modes.debug_maxRange && track_diff > 90) + || (receiverRangeExceeded && Modes.debug_receiverRangeLimit) + ) { + if (uat2esnt_duplicate(now, a, mm) || (!inrange && !override && mm->in_disc_cache) || mm->garbage) { + // don't show debug + } else { + char *failMessage; + if (inrange) { + failMessage = "pass"; + } else if (override) { + failMessage = "ovrd"; + } else if (receiverRangeExceeded) { + failMessage = "RcvR"; + } else { + failMessage = "FAIL"; + } + char uuid[32]; // needs 18 chars and null byte + sprint_uuid1(mm->receiverId, uuid); + fprintTime(stderr, now); + fprintf(stderr, " %06x R%3.1f|%3.1f %s %2.0f %s %s %s %4.0f%%%2ds%2dt %3.0f/%3.0f td %3.0f %8.3fkm in%4.1fs, %4.0fkt %11.6f,%11.6f->%11.6f,%11.6f biT %4.1f s %s rId %s\n", + a->addr, + a->pos_reliable_odd, a->pos_reliable_even, + mm->cpr_odd ? "O" : "E", + mm->cpr_odd ? fmin(99, (now - a->cpr_even_valid.updated) / 1000.0) : fmin(99, (now - a->cpr_odd_valid.updated) / 1000.0), + cpr_local == CPR_LOCAL ? "L" : (cpr_local == CPR_GLOBAL ? "G" : "S"), + (surface ? "S" : "A"), + failMessage, + fmin(9001.0, 100.0 * distance / range), + a->speedUnreliable, + a->trackUnreliable, + track, + calc_track, + track_diff, + fmin(9001.0, distance / 1000.0), + elapsed / 1000.0, + fmin(9001.0, distance / elapsed * 1000.0 / 1852.0 * 3600.0), + oldLat, oldLon, lat, lon, + backInTimeSeconds, source_string(mm->source), uuid); + } + } + + // override, this allows for printing stuff instead of returning + if (override) { + if (!inrange) { + a->lastOverrideTs = now; + } + inrange = override; + } + + if (receiverRangeExceeded && Modes.garbage_ports) { + mm->pos_receiver_range_exceeded = 1; + inrange = 0; // far outside receiver area + mm->pos_ignore = 1; + mm->pos_bad = 1; + } + + + return inrange; +} + +/* debug code for surface CPR decoding ... might be useful and reduce typing at some point + if (Modes.debug_receiver && Modes.debug_speed_check && receiver && a->seen_pos + && *lat < 89 + && *lat > -89 + && (fabs(a->lat - *lat) > 35 || fabs(a->lon - *lon) > 35 || fabs(reflat - *lat) > 35 || fabs(reflon - *lon) > 35) + && !bogus_lat_lon(*lat, *lon) + ) { +//struct receiver *r = receiver; +//fprintf(stderr, "id: %016"PRIx64" #pos: %9"PRIu64" lat min:%4.0f max:%4.0f lon min:%4.0f max:%4.0f\n", +// r->id, r->positionCounter, +// r->latMin, r->latMax, +// r->lonMin, r->lonMax); +int sc = speed_check(a, mm->source, *lat, *lon, mm, CPR_GLOBAL); +fprintf(stderr, "%s%06x surface CPR rec. ref.: %4.0f %4.0f sc: %d result: %7.2f %7.2f --> %7.2f %7.2f\n", +(a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : " ", +a->addr, reflat, reflon, sc, a->lat, a->lon, *lat, *lon); +} + +if (Modes.debug_receiver && receiver && a->addr == Modes.cpr_focus) +fprintf(stderr, "%06x using reference: %4.0f %4.0f result: %7.2f %7.2f\n", a->addr, reflat, reflon, *lat, *lon); +*/ + +static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, double *lon, unsigned *nic, unsigned *rc) { + int result; + int fflag = mm->cpr_odd; + int surface = (mm->cpr_type == CPR_SURFACE); + struct receiver *receiver; + double reflat, reflon; + + // derive NIC, Rc from the worse of the two position + // smaller NIC is worse; larger Rc is worse + *nic = (a->cpr_even_nic < a->cpr_odd_nic ? a->cpr_even_nic : a->cpr_odd_nic); + *rc = (a->cpr_even_rc > a->cpr_odd_rc ? a->cpr_even_rc : a->cpr_odd_rc); + + if (surface) { + // surface global CPR + // find reference location + + int ref = 0; + if (Modes.userLocationRef) { + reflat = Modes.fUserLat; + reflon = Modes.fUserLon; + ref = 1; + } else if ((receiver = receiverGetReference(mm->receiverId, &reflat, &reflon, a, 0))) { + //function sets reflat and reflon on success, nothing to do here. + ref = 2; + } else if (a->seen_pos && a->surfaceCPR_allow_ac_rel) { + reflat = a->latReliable; + reflon = a->lonReliable; + ref = 3; + } else { + // No local reference, give up + return (-1); + } + + result = decodeCPRsurface(reflat, reflon, + a->cpr_even_lat, a->cpr_even_lon, + a->cpr_odd_lat, a->cpr_odd_lon, + fflag, + lat, lon); + double refDistance = greatcircle(reflat, reflon, *lat, *lon, 0); + if (refDistance > 450e3) { + if (0 && (a->addr == Modes.cpr_focus || Modes.debug_cpr)) { + fprintf(stderr, "%06x CPRsurface ref %d refDistance: %4.0f km (%4.0f, %4.0f) allow_ac_rel %d\n", a->addr, ref, refDistance / 1000.0, reflat, reflon, a->surfaceCPR_allow_ac_rel); + } + // change to failure which doesn't decrement reliable + result = -1; + return result; + } + } else { + // airborne global CPR + result = decodeCPRairborne(a->cpr_even_lat, a->cpr_even_lon, + a->cpr_odd_lat, a->cpr_odd_lon, + fflag, + lat, lon); + } + + if (result < 0) { + if (!mm->duplicate && (a->addr == Modes.cpr_focus || Modes.debug_cpr) && !inDiscCache(mm->sysTimestamp, a, mm)) { + int64_t now = mstime(); + fprintTime(stderr, now); + fprintf(stderr, "CPR: decode failure for %06x (%d): even: %6d %6d %4.1fs odd: %6d %6d %4.1fs fflag: %s\n", + a->addr, result, + a->cpr_even_lat, a->cpr_even_lon, + fmin(999, ((double) now - a->cpr_even_valid.updated) / 1000.0), + a->cpr_odd_lat, a->cpr_odd_lon, + fmin(999, ((double) now - a->cpr_odd_valid.updated) / 1000.0), + fflag ? "odd" : "even" + ); + } + return result; + } + + // check max range + if (Modes.maxRange > 0 && Modes.userLocationValid) { + mm->receiver_distance = greatcircle(Modes.fUserLat, Modes.fUserLon, *lat, *lon, 0); + if (mm->receiver_distance > Modes.maxRange) { + if (a->addr == Modes.cpr_focus || Modes.debug_bogus) { + fprintf(stdout, "%5llu %5.1f Global range check failed: %06x %.3f,%.3f, max range %.1fkm, actual %.1fkm\n", + (long long) mm->timestamp % 65536, + 10 * log10(mm->signalLevel), + a->addr, *lat, *lon, Modes.maxRange / 1000.0, mm->receiver_distance / 1000.0); + } + + if (mm->source != SOURCE_MLAT) { + Modes.stats_current.cpr_global_range_checks++; + if (Modes.debug_maxRange) { + showPositionDebug(a, mm, mm->sysTimestamp, *lat, *lon); + } + } + return (-2); // we consider an out-of-range value to be bad data + } + } + + // check speed limit + if (!speed_check(a, mm->source, *lat, *lon, mm, CPR_GLOBAL)) { + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_global_speed_checks++; + return -2; + } + + return result; +} + +static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, double *lon, unsigned *nic, unsigned *rc) { + // relative CPR + // find reference location + double reflat, reflon; + double range_limit = 0; + int result; + int fflag = mm->cpr_odd; + int surface = (mm->cpr_type == CPR_SURFACE); + int relative_to = 0; // aircraft(1) or receiver(2) relative + + if (fflag) { + *nic = a->cpr_odd_nic; + *rc = a->cpr_odd_rc; + } else { + *nic = a->cpr_even_nic; + *rc = a->cpr_even_rc; + } + + int64_t now = mm->sysTimestamp; + if (now - a->seenPosReliable < 10 * MINUTES && a->localCPR_allow_ac_rel) { + reflat = a->lat; + reflon = a->lon; + + if (a->pos_nic < *nic) + *nic = a->pos_nic; + if (a->pos_rc < *rc) + *rc = a->pos_rc; + + range_limit = 1852*100; // 100NM + // 100 NM in the 10 minutes of position validity means 600 knots which + // is fast but happens even for commercial airliners. + // It's not a problem if this limitation fails every now and then. + // A wrong relative position decode would require the aircraft to + // travel 360-100=260 NM in the 10 minutes of position validity. + // This is impossible for planes slower than 1560 knots/Mach 2.3 over the ground. + // Thus this range limit combined with the 10 minutes of position + // validity should not provide bad positions (1 cell away). + + relative_to = 1; + } else if (!surface && Modes.userLocationRef) { + reflat = Modes.fUserLat; + reflon = Modes.fUserLon; + + // The cell size is at least 360NM, giving a nominal + // max range of 180NM (half a cell). + // + // If the receiver range is more than half a cell + // then we must limit this range further to avoid + // ambiguity. (e.g. if we receive a position report + // at 200NM distance, this may resolve to a position + // at (200-360) = 160NM in the wrong direction) + + if (Modes.maxRange == 0) { + return (-1); // Can't do receiver-centered checks at all + } else if (Modes.maxRange <= 1852 * 180) { + range_limit = Modes.maxRange; + } else if (Modes.maxRange < 1852 * 360) { + range_limit = (1852 * 360) - Modes.maxRange; + } else { + return (-1); // Can't do receiver-centered checks at all + } + relative_to = 2; + } else { + // No local reference, give up + return (-1); + } + + result = decodeCPRrelative(reflat, reflon, + mm->cpr_lat, + mm->cpr_lon, + fflag, surface, + lat, lon); + if (result < 0) { + return result; + } + + // check range limit + if (range_limit > 0) { + double range = greatcircle(reflat, reflon, *lat, *lon, 0); + if (range > range_limit) { + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_local_range_checks++; + return (-1); + } + } + + // check max range + if (Modes.maxRange > 0 && Modes.userLocationValid) { + mm->receiver_distance = greatcircle(Modes.fUserLat, Modes.fUserLon, *lat, *lon, 0); + if (mm->receiver_distance > Modes.maxRange) { + if (a->addr == Modes.cpr_focus || Modes.debug_bogus) { + fprintf(stdout, "%5llu %5.1f Global range check failed: %06x %.3f,%.3f, max range %.1fkm, actual %.1fkm\n", + (long long) mm->timestamp % 65536, + 10 * log10(mm->signalLevel), + a->addr, *lat, *lon, Modes.maxRange / 1000.0, mm->receiver_distance / 1000.0); + } + + if (mm->source != SOURCE_MLAT) { + Modes.stats_current.cpr_local_range_checks++; + if (Modes.debug_maxRange) { + showPositionDebug(a, mm, mm->sysTimestamp, *lat, *lon); + } + } + return (-2); // we consider an out-of-range value to be bad data + } + } + + + // check speed limit + if (!speed_check(a, mm->source, *lat, *lon, mm, CPR_LOCAL)) { + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_local_speed_checks++; + return -2; + } + + return relative_to; +} + +static int64_t time_between(int64_t t1, int64_t t2) { + if (t1 >= t2) + return t1 - t2; + else + return t2 - t1; +} +static void addReceiverId(struct aircraft *a, struct modesMessage *mm, int64_t elapsed) { + a->receiverIdsNext = (a->receiverIdsNext + 1) % RECEIVERIDBUFFER; + a->receiverIds[a->receiverIdsNext] = simpleHash(mm->receiverId); + + int64_t advance = imin(RECEIVERIDBUFFER * 500, elapsed); + int iterations = 0; + while (advance > 750) { + iterations++; + // ADS-B positions nominally come in every 500 ms receiver id + // buffer has 12 positions, if the positions don't come in + // often enough we zero them so it's only roughly the receiver + // ids for the last 6 seconds + advance -= 500; + a->receiverIdsNext = (a->receiverIdsNext + 1) % RECEIVERIDBUFFER; + a->receiverIds[a->receiverIdsNext] = 0; + } + if (iterations > 20) { + fprintf(stderr, "%06x\n", a->addr); + } + if (0 && a->addr == Modes.cpr_focus) { + fprintf(stderr, "%u\n", simpleHash(mm->receiverId)); + } + + uint16_t *set1 = a->receiverIds; + uint16_t set2[RECEIVERIDBUFFER] = { 0 }; + int div = 0; + for (int k = 0; k < RECEIVERIDBUFFER; k++) { + int unequal = 0; + for (int j = 0; j < div; j++) { + unequal += (set1[k] != set2[j]); + } + if (unequal == div && set1[k]) { + set2[div++] = set1[k]; + } + } + a->receiverCount = div; +} + +static void setPosition(struct aircraft *a, struct modesMessage *mm, int64_t now) { + if (0 && a->addr == Modes.cpr_focus) { + showPositionDebug(a, mm, now, 0, 0); + } + + // if we get the same position again but from an inferior source, assume it's delayed and treat as duplicate + if (now < a->seen_pos + 10 * MINUTES && mm->source < a->position_valid.last_source && mm->distance_traveled < 20) { + if (a->addr == Modes.cpr_focus) { + fprintf(stderr, "%06x less than 20 m\n", a->addr); + } + mm->duplicate = 1; + mm->pos_ignore = 1; + } + if (duplicate_check(now, a, mm->decoded_lat, mm->decoded_lon, mm)) { + // don't use duplicate positions + mm->pos_ignore = 1; + } + + if (bogus_lat_lon(mm->decoded_lat, mm->decoded_lon)) { + if (0 && (fabs(mm->decoded_lat) >= 90.0 || fabs(mm->decoded_lon) >= 180.0)) { + fprintf(stderr, "%06x lat,lon out of bounds: %.2f,%.2f source: %s\n", a->addr, mm->decoded_lat, mm->decoded_lon, source_enum_string(mm->source)); + } + return; + } + + // for UAT messages converted by uat2esnt each position becomes a odd / even message pair + // only update the position for the odd message if we've recently seen a reliable position + if (uat2esnt_duplicate(now, a, mm)) { + return; + } + + Modes.stats_current.pos_by_type[mm->addrtype]++; + Modes.stats_current.pos_all++; + + // mm->pos_bad should never arrive here, handle it just in case + if (mm->cpr_valid && (mm->garbage || mm->pos_bad)) { + Modes.stats_current.pos_garbage++; + return; + } + + if (mm->client) { + mm->client->positionCounter++; + } + +#if defined(PRINT_UUIDS) + { + int64_t oldestTime = now; + int64_t overwriteOlder = now - 60 * SECONDS; + idTime *overwrite = &a->recentReceiverIds[0]; + for (int i = 0; i < RECENT_RECEIVER_IDS; i++) { + idTime *entry = &a->recentReceiverIds[i]; + if (entry->id == mm->receiverId) { + overwrite = entry; + break; + } + // if we already found an entry to overwrite (older than 60 seconds) + // then look no further for an entry to overwrite + if (oldestTime > overwriteOlder && entry->time < oldestTime) { + oldestTime = entry->time; + overwrite = entry; + } + } + overwrite->id = mm->receiverId; + overwrite->time = now; + } +#endif + + if (mm->duplicate) { + Modes.stats_current.pos_duplicate++; + return; + } + + a->receiverId = mm->receiverId; + + if (mm->source != SOURCE_JAERO && mm->distance_traveled >= 100) { + if (mm->calculated_track != -1) + a->calc_track = mm->calculated_track; + else + a->calc_track = bearing(a->lat, a->lon, mm->decoded_lat, mm->decoded_lon); + } + + if (mm->source == SOURCE_JAERO && a->seenPosReliable) { + if ((a->position_valid.last_source == SOURCE_JAERO || now > a->seenPosReliable + 10 * MINUTES) + && mm->distance_traveled > 2e3) { + if (accept_data(&a->track_valid, SOURCE_JAERO, mm, a, REDUCE_OFTEN)) { + a->calc_track = a->track = bearing(a->latReliable, a->lonReliable, mm->decoded_lat, mm->decoded_lon); + //fprintf(stderr, "%06x track %0.1f\n", a->addr, a->track); + } + } else if (trackDataAge(now, &a->track_valid) < 10 * MINUTES) { + accept_data(&a->track_valid, SOURCE_JAERO, mm, a, REDUCE_OFTEN); + } + } + + // Update aircraft state + a->prev_lat = a->lat; + a->prev_lon = a->lon; + a->prev_pos_time = a->seen_pos; + + a->lat = mm->decoded_lat; + a->lon = mm->decoded_lon; + a->pos_nic = mm->decoded_nic; + a->pos_rc = mm->decoded_rc; + a->seen_pos = now; + + + a->pos_surface = trackDataValid(&a->airground_valid) && a->airground == AG_GROUND; + + // due to the position deduplication logic we won't put receivers + // into the receiver list which aren't the first ones to send us the position + if (!Modes.netReceiverId) { + a->receiverCount = 1; + } else { + if (mm->source == SOURCE_MLAT && mm->receiverCountMlat) { + a->receiverCount = mm->receiverCountMlat; + } else { + addReceiverId(a, mm, now - a->prev_pos_time); + } + } + + if (Modes.netReceiverId && posReliable(a)) { + + int64_t valid_elapsed = now - a->pos_reliable_valid.updated; + int64_t override_elapsed = now - a->lastOverrideTs; + int64_t status_elapsed = now - a->lastStatusTs; + + if ( + (valid_elapsed > 10 * MINUTES || override_elapsed < 10 * MINUTES) + && (mm->msgtype == 17 || (mm->addrtype == ADDR_ADSB_ICAO_NT && mm->cpr_type != CPR_SURFACE + && !a->is_df18_exception && ((a->addr >= 0xa00000 && a->addr <= 0xafffff) || (a->dbFlags & (1 << 0))) )) + && mm->cpr_valid + && status_elapsed > 5 * MINUTES + ) { + double dist = greatcircle(a->latReliable, a->lonReliable, mm->decoded_lat, mm->decoded_lon, 0); + double estimate = a->gs_reliable * knots_to_meterpersecond * (valid_elapsed * 1e-3); + if (dist > 200e3 && fabs(estimate - dist) > 100e3) { + a->lastStatusDiscarded++; + if (((Modes.debug_lastStatus & 1) && a->lastStatusDiscarded > 8) || (Modes.debug_lastStatus & 4)) { + char uuid[32]; // needs 18 chars and null byte + sprint_uuid1(mm->receiverId, uuid); + fprintf(stderr, "%06x dist: %4d status_e: %4d valid_e: %4d over_e: %4d rCount: %d uuid: %s dbFlags: %u DF: %d\n", + a->addr, + (int) imin(9999, (dist / 1000)), + (int) imin(9999, (status_elapsed / 1000)), + (int) imin(9999, (valid_elapsed / 1000)), + (int) imin(9999, (override_elapsed / 1000)), + a->receiverCount, + uuid, + a->dbFlags, + mm->msgtype); + } + if (!(Modes.debug_lastStatus & 2)) { + return; + } + } + } + } + + if (posReliable(a) && accept_data(&a->pos_reliable_valid, mm->source, mm, a, REDUCE_OFTEN)) { + + a->lastStatusDiscarded = 0; + + // when accepting crappy positions, invalidate the data indicating position accuracy + if (mm->source < SOURCE_TISB) { + // a->nic_baro_valid.source = SOURCE_INVALID; + a->nac_p_valid.source = SOURCE_INVALID; + a->nac_v_valid.source = SOURCE_INVALID; + a->sil_valid.source = SOURCE_INVALID; + a->gva_valid.source = SOURCE_INVALID; + a->sda_valid.source = SOURCE_INVALID; + a->sil_type = SIL_INVALID; + } + + set_globe_index(a, globe_index(a->lat, a->lon)); + + if (0 && a->addr == Modes.trace_focus) { + fprintf(stderr, "%5.1fs traceAdd: %06x\n", (now % (600 * SECONDS)) / 1000.0, a->addr); + } + + a->lastPosReceiverId = mm->receiverId; + + // update addrtype, we use the type from the accepted position. + a->addrtype = mm->addrtype; + a->addrtype_updated = now; + + if (now < a->seenPosReliable) { + fprintf(stderr, "%06x now < seenPosReliable ??? mstime: %.3f now: %.3f seenPosReliabe: %.3f\n", a->addr, mstime() / 1000.0, now / 1000.0, a->seenPosReliable / 1000.0); + } + int stale = (now > a->seenPosReliable + TRACE_STALE); + a->seenPosReliable = now; + + a->latReliable = mm->decoded_lat; + a->lonReliable = mm->decoded_lon; + a->pos_nic_reliable = mm->decoded_nic; + a->pos_rc_reliable = mm->decoded_rc; + a->surfaceCPR_allow_ac_rel = 1; // allow ac relative CPR for ground positions + + + if (trackDataValid(&a->gs_valid)) { + a->gs_reliable = a->gs; + } + if (trackDataValid(&a->track_valid)) { + a->track_reliable = a->track; + } + + traceAdd(a, mm, now, stale); + + if ( + mm->source == SOURCE_ADSB + && trackDataValid(&a->nac_p_valid) && a->nac_p >= 4 // 1 nmi + ) { + a->seenAdsbReliable = now; + a->seenAdsbLat = mm->decoded_lat; + a->seenAdsbLon = mm->decoded_lon; + } + + if (Modes.userLocationValid) { + + if (mm->receiver_distance == 0) { + mm->receiver_distance = greatcircle(Modes.fUserLat, Modes.fUserLon, a->lat, a->lon, 0); + } + + a->receiver_distance = mm->receiver_distance; + a->receiver_direction = bearing(Modes.fUserLat, Modes.fUserLon, a->lat, a->lon); + + // nac_p >= 2 accuracy better than 4 nmi + // decoded_rc less than 5 * nmi + if (mm->source == SOURCE_ADSB + && trackDataValid(&a->nac_p_valid) && a->nac_p >= 2 + && mm->decoded_rc != 0 && mm->decoded_rc < 5 * 1852 + ) { + update_range_histogram(a, now); + } else if (mm->source == SOURCE_ADSR) { + update_range_histogram(a, now); + } + + } + if (now > a->nextJsonPortOutput) { + a->nextJsonPortOutput = now + Modes.net_output_json_interval; + mm->jsonPositionOutputEmit = 1; + } + } + + if (0 && a->addr == Modes.cpr_focus) { + fprintf(stderr, "%06x: reliability odd: %3.1f even: %3.1f status: %d\n", a->addr, a->pos_reliable_odd, a->pos_reliable_even, posReliable(a)); + } +} + +static int64_t cpr_global_airborne_max_elapsed(int64_t now, struct aircraft *a) { + if (!trackDataValid(&a->gs_valid) || trackDataAge(now, &a->gs_valid) > 20 * SECONDS) { + return 10 * SECONDS; + } + int speed = imax((int64_t) a->gs, 1); + // max time for 500 knots gs + int64_t ref = 19 * SECONDS; // empirically tested + int64_t ival = (ref * 500) / speed; + // never return more than 30 seconds + ival = imin(30 * SECONDS, ival); + //fprintf(stderr, "%lld\n", (long long) ival); + return ival; +} + +static void updatePosition(struct aircraft *a, struct modesMessage *mm, int64_t now) { + int location_result = -1; + int globalCPR = 0; + int64_t max_elapsed; + double new_lat = 0, new_lon = 0; + unsigned new_nic = 0; + unsigned new_rc = 0; + int surface; + + surface = (mm->cpr_type == CPR_SURFACE); + a->pos_surface = trackDataValid(&a->airground_valid) && a->airground == AG_GROUND; + + if (surface) { + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_surface++; + + // Surface: 25 seconds if >25kt or speed unknown, 50 seconds otherwise + if (mm->gs_valid && mm->gs.selected <= 25) + max_elapsed = 50000; + else + max_elapsed = 25000; + } else { + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_airborne++; + + // Airborne: determine depending on speed, fallback 10 seconds + max_elapsed = cpr_global_airborne_max_elapsed(now, a); + } + + // If we have enough recent data, try global CPR + if (trackDataValid(&a->cpr_odd_valid) && trackDataValid(&a->cpr_even_valid) && + a->cpr_odd_valid.source == a->cpr_even_valid.source && + a->cpr_odd_type == a->cpr_even_type && + time_between(a->cpr_odd_valid.updated, a->cpr_even_valid.updated) <= max_elapsed) { + + location_result = doGlobalCPR(a, mm, &new_lat, &new_lon, &new_nic, &new_rc); + + if (0 && Modes.debug_cpr && location_result == -2) { + fprintTime(stderr, now); + fprintf(stderr, " mm->cpr: (%6d) (%6d) %s %s, %s age: %0.1f sources o: %s %s e: %s %s lpos src: %s \n", + mm->cpr_lat, mm->cpr_lon, + mm->cpr_odd ? " odd" : "even", cpr_type_string(mm->cpr_type), mm->cpr_odd ? "even" : " odd", + mm->cpr_odd ? fmin(999, ((double) now - a->cpr_even_valid.updated) / 1000.0) : fmin(999, ((double) now - a->cpr_odd_valid.updated) / 1000.0), + source_enum_string(a->cpr_odd_valid.source), cpr_type_string(a->cpr_odd_type), + source_enum_string(a->cpr_even_valid.source), cpr_type_string(a->cpr_even_type), + source_enum_string(a->position_valid.last_source)); + } + //if (a->addr == Modes.cpr_focus) + // fprintf(stderr, "%06x globalCPR result: %d\n", a->addr, location_result); + + if (location_result == -2) { + // Global CPR failed because the position produced implausible results. + // This is bad data. + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_global_bad++; + + mm->pos_bad = 1; + + // still note which position we decoded + mm->decoded_lat = new_lat; + mm->decoded_lon = new_lon; + } else if (location_result == -1) { + if (a->addr == Modes.cpr_focus || Modes.debug_cpr) { + if (mm->source == SOURCE_MLAT) { + fprintf(stderr, "CPR skipped from MLAT (%06x).\n", a->addr); + } + } + // No local reference for surface position available, or the two messages crossed a zone. + // Nonfatal, try again later. + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_global_skipped++; + } else { + if (accept_data(&a->position_valid, mm->source, mm, a, REDUCE_DOUBLE)) { + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_global_ok++; + + globalCPR = 1; + } else { + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_global_skipped++; + location_result = -2; + } + } + } + + // Otherwise try relative CPR. + if (location_result == -1) { + location_result = doLocalCPR(a, mm, &new_lat, &new_lon, &new_nic, &new_rc); + //if (a->addr == Modes.cpr_focus) + // fprintf(stderr, "%06x: localCPR: %d\n", a->addr, location_result); + + + if (location_result == -2) { + // Local CPR failed because the position produced implausible results. + // This is bad data. + + mm->pos_bad = 1; + + // still note which position we decoded + mm->decoded_lat = new_lat; + mm->decoded_lon = new_lon; + } else if (location_result >= 0 && accept_data(&a->position_valid, mm->source, mm, a, REDUCE_DOUBLE)) { + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_local_ok++; + mm->cpr_relative = 1; + + if (location_result == 1) { + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_local_aircraft_relative++; + } + if (location_result == 2) { + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_local_receiver_relative++; + } + } else { + if (mm->source != SOURCE_MLAT) + Modes.stats_current.cpr_local_skipped++; + location_result = -1; + } + } + + if (location_result >= 0) { + // If we sucessfully decoded, back copy the results to mm + mm->cpr_decoded = 1; + mm->decoded_lat = new_lat; + mm->decoded_lon = new_lon; + mm->decoded_nic = new_nic; + mm->decoded_rc = new_rc; + + if (trackDataValid(&a->gs_valid)) + a->gs_last_pos = a->gs; + + if (globalCPR) + incrementReliable(a, mm, now, mm->cpr_odd); + + setPosition(a, mm, now); + } else if (location_result == -1 && a->addr == Modes.cpr_focus && !mm->duplicate) { + fprintTime(stderr, now); + fprintf(stderr, " mm->cpr: (%d) (%d) %s %s, %s age: %0.1f sources o: %s %s e: %s %s lpos src: %s \n", + mm->cpr_lat, mm->cpr_lon, + mm->cpr_odd ? " odd" : "even", cpr_type_string(mm->cpr_type), mm->cpr_odd ? "even" : " odd", + mm->cpr_odd ? fmin(999, ((double) now - a->cpr_even_valid.updated) / 1000.0) : fmin(999, ((double) now - a->cpr_odd_valid.updated) / 1000.0), + source_enum_string(a->cpr_odd_valid.source), cpr_type_string(a->cpr_odd_type), + source_enum_string(a->cpr_even_valid.source), cpr_type_string(a->cpr_even_type), + source_enum_string(a->position_valid.last_source)); + } +} + +static unsigned compute_nic(unsigned metype, unsigned version, unsigned nic_a, unsigned nic_b, unsigned nic_c) { + switch (metype) { + case 5: // surface + case 9: // airborne + case 20: // airborne, GNSS altitude + return 11; + + case 6: // surface + case 10: // airborne + case 21: // airborne, GNSS altitude + return 10; + + case 7: // surface + if (version == 2) { + if (nic_a && !nic_c) { + return 9; + } else { + return 8; + } + } else if (version == 1) { + if (nic_a) { + return 9; + } else { + return 8; + } + } else { + return 8; + } + + case 8: // surface + if (version == 2) { + if (nic_a && nic_c) { + return 7; + } else if (nic_a && !nic_c) { + return 6; + } else if (!nic_a && nic_c) { + return 6; + } else { + return 0; + } + } else { + return 0; + } + + case 11: // airborne + if (version == 2) { + if (nic_a && nic_b) { + return 9; + } else { + return 8; + } + } else if (version == 1) { + if (nic_a) { + return 9; + } else { + return 8; + } + } else { + return 8; + } + + case 12: // airborne + return 7; + + case 13: // airborne + return 6; + + case 14: // airborne + return 5; + + case 15: // airborne + return 4; + + case 16: // airborne + if (nic_a && nic_b) { + return 3; + } else { + return 2; + } + + case 17: // airborne + return 1; + + default: + return 0; + } +} + +static unsigned compute_rc(unsigned metype, unsigned version, unsigned nic_a, unsigned nic_b, unsigned nic_c) { + switch (metype) { + case 5: // surface + case 9: // airborne + case 20: // airborne, GNSS altitude + return 8; // 7.5m + + case 6: // surface + case 10: // airborne + case 21: // airborne, GNSS altitude + return 25; + + case 7: // surface + if (version == 2) { + if (nic_a && !nic_c) { + return 75; + } else { + return 186; // 185.2m, 0.1NM + } + } else if (version == 1) { + if (nic_a) { + return 75; + } else { + return 186; // 185.2m, 0.1NM + } + } else { + return 186; // 185.2m, 0.1NM + } + + case 8: // surface + if (version == 2) { + if (nic_a && nic_c) { + return 371; // 370.4m, 0.2NM + } else if (nic_a && !nic_c) { + return 556; // 555.6m, 0.3NM + } else if (!nic_a && nic_c) { + return 926; // 926m, 0.5NM + } else { + return RC_UNKNOWN; + } + } else { + return RC_UNKNOWN; + } + + case 11: // airborne + if (version == 2) { + if (nic_a && nic_b) { + return 75; + } else { + return 186; // 370.4m, 0.2NM + } + } else if (version == 1) { + if (nic_a) { + return 75; + } else { + return 186; // 370.4m, 0.2NM + } + } else { + return 186; // 370.4m, 0.2NM + } + + case 12: // airborne + return 371; // 370.4m, 0.2NM + + case 13: // airborne + if (version == 2) { + if (!nic_a && nic_b) { + return 556; // 555.6m, 0.3NM + } else if (!nic_a && !nic_b) { + return 926; // 926m, 0.5NM + } else if (nic_a && nic_b) { + return 1112; // 1111.2m, 0.6NM + } else { + return RC_UNKNOWN; // bad combination, assume worst Rc + } + } else if (version == 1) { + if (nic_a) { + return 1112; // 1111.2m, 0.6NM + } else { + return 926; // 926m, 0.5NM + } + } else { + return 926; // 926m, 0.5NM + } + + case 14: // airborne + return 1852; // 1.0NM + + case 15: // airborne + return 3704; // 2NM + + case 16: // airborne + if (version == 2) { + if (nic_a && nic_b) { + return 7408; // 4NM + } else { + return 14816; // 8NM + } + } else if (version == 1) { + if (nic_a) { + return 7408; // 4NM + } else { + return 14816; // 8NM + } + } else { + return 18520; // 10NM + } + + case 17: // airborne + return 37040; // 20NM + + default: + return RC_UNKNOWN; + } +} + +// Map ADS-B v0 position message type to NACp value +// returned computed NACp, or -1 if not a suitable message type +static int compute_v0_nacp(struct modesMessage *mm) +{ + if (mm->msgtype != 17 && mm->msgtype != 18) { + return -1; + } + + // ED-102A Table N-7 + switch (mm->metype) { + case 0: return 0; + case 5: return 11; + case 6: return 10; + case 7: return 8; + case 8: return 0; + case 9: return 11; + case 10: return 10; + case 11: return 8; + case 12: return 7; + case 13: return 6; + case 14: return 5; + case 15: return 4; + case 16: return 1; + case 17: return 1; + case 18: return 0; + case 20: return 11; + case 21: return 10; + case 22: return 0; + default: return -1; + } +} + +// Map ADS-B v0 position message type to SIL value +// returned computed SIL, or -1 if not a suitable message type +static int compute_v0_sil(struct modesMessage *mm) +{ + if (mm->msgtype != 17 && mm->msgtype != 18) { + return -1; + } + + // ED-102A Table N-8 + switch (mm->metype) { + case 0: + return 0; + + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + return 2; + + case 18: + return 0; + + case 20: + case 21: + return 2; + + case 22: + return 0; + + default: + return -1; + } +} + +static void compute_nic_rc_from_message(struct modesMessage *mm, struct aircraft *a, unsigned *nic, unsigned *rc) { + int nic_a = (trackDataValid(&a->nic_a_valid) && a->nic_a); + int nic_b = (mm->accuracy.nic_b_valid && mm->accuracy.nic_b); + int nic_c = (trackDataValid(&a->nic_c_valid) && a->nic_c); + + *nic = compute_nic(mm->metype, a->adsb_version, nic_a, nic_b, nic_c); + *rc = compute_rc(mm->metype, a->adsb_version, nic_a, nic_b, nic_c); +} + +static int altitude_to_feet(int raw, altitude_unit_t unit) { + switch (unit) { + case UNIT_METERS: + return raw / 0.3048; + case UNIT_FEET: + return raw; + default: + return 0; + } +} + +// check if we trust that this message is actually from the aircraft with this address +// similar reasoning to icaoFilterAdd in mode_s.c +static int addressReliable(struct modesMessage *mm) { + if (mm->msgtype == 17 || mm->msgtype == 18 || (mm->msgtype == 11 && mm->IID == 0) || mm->sbs_in) { + return 1; + } + return 0; +} + +static inline void focusGroundstateChange(struct aircraft *a, struct modesMessage *mm, int arg, int64_t now, airground_t airground) { + if (a->airground != mm->airground) { + a->lastAirGroundChange = now; + } + //if (a->airground != mm->airground) { + if (a->addr == Modes.cpr_focus && a->airground != airground) { + fprintf(stderr, "%02d:%04.1f %06x Ground state change %d: Source: %s, %s -> %s\n", + (int) (now / (60 * SECONDS) % 60), + (now % (60 * SECONDS)) / 1000.0, + a->addr, + arg, + source_enum_string(mm->source), + airground_to_string(a->airground), + airground_to_string(airground)); + displayModesMessage(mm); + } +} +static void updateAltitude(int64_t now, struct aircraft *a, struct modesMessage *mm) { + + int alt = altitude_to_feet(mm->baro_alt, mm->baro_alt_unit); + if (a->modeC_hit) { + int new_modeC = (a->baro_alt + 49) / 100; + int old_modeC = (alt + 49) / 100; + if (new_modeC != old_modeC) { + a->modeC_hit = 0; + } + } + + int delta = alt - a->baro_alt; + int fpm = 0; + + int max_fpm = 12500; + int min_fpm = -12500; + + int lowDelta = 300; + int old_reliable = a->alt_reliable; + int wasReliable = altBaroReliable(a); + // + int64_t baroAge = trackDataAge(now, &a->baro_alt_valid); + datasource_t baroSource = a->baro_alt_valid.source; + + int debug = 0; + if (0 && a->position_valid.source == SOURCE_ADSB && !wasReliable) { + debug = 1; + } + + if (abs(delta) >= lowDelta) { + fpm = delta*60*10/(abs((int)baroAge/100)+10); + if (trackDataValid(&a->geom_rate_valid) && trackDataAge(now, &a->geom_rate_valid) < trackDataAge(now, &a->baro_rate_valid)) { + min_fpm = a->geom_rate - 1500 - imin(11000, ((int)trackDataAge(now, &a->geom_rate_valid)/2)); + max_fpm = a->geom_rate + 1500 + imin(11000, ((int)trackDataAge(now, &a->geom_rate_valid)/2)); + } else if (trackDataValid(&a->baro_rate_valid)) { + min_fpm = a->baro_rate - 1500 - imin(11000, ((int)trackDataAge(now, &a->baro_rate_valid)/2)); + max_fpm = a->baro_rate + 1500 + imin(11000, ((int)trackDataAge(now, &a->baro_rate_valid)/2)); + } + if (trackDataValid(&a->baro_alt_valid) && baroAge < 30 * SECONDS) { + a->alt_reliable = imin( + ALTITUDE_BARO_RELIABLE_MAX - (ALTITUDE_BARO_RELIABLE_MAX*baroAge/(30 * SECONDS)), + a->alt_reliable); + } else { + a->alt_reliable = 0; + } + } + + int good_crc = 0; + + // just trust messages with this source implicitely and rate the altitude as max reliable + // if we get the occasional altitude excursion that's acceptable and preferable to not capturing implausible altitude changes for example before a crash + if (mm->crc == 0 && mm->source > SOURCE_MODE_S_CHECKED) { + // only trust implicitely if we are getting quick updates + if (baroAge < 2 * SECONDS) { + good_crc = ALTITUDE_BARO_RELIABLE_MAX; + } else { + good_crc = ALTITUDE_BARO_RELIABLE_MAX/3; + } + } + if (mm->source == SOURCE_JAERO || mm->source == SOURCE_SBS) { + // trust those data sources they are often slow to update and we want to show an altitude + // for those + good_crc = ALTITUDE_BARO_RELIABLE_MAX; + } + if (mm->source == SOURCE_MLAT) { + if (mm->receiverCountMlat > 2) { + // this is an mlat result with altitude from a version of mlat-server that hopefully + // only sets the altitude when it was received from the plane via multiple receivers + good_crc = ALTITUDE_BARO_RELIABLE_MAX/2 - 1; + } else { + // this is typically mlat results from mlat-client + // this is a terrible altitude source, ignore it completely + // better to show "no altitude" + // exception: anonymous FA mlat results look a bit stupid without altitude + // exception: when trying to visualize local MLAT results, make that work by using the + // MLAT sbs input instead of the MLAT results as beast + if (mm->sbs_in || (a->addr & MODES_NON_ICAO_ADDRESS)) { + good_crc = 0; + } else { + // ignore altitude from this message completely + return; + } + } + } + + if (a->baro_alt > 50175 && mm->alt_q_bit && a->alt_reliable > ALTITUDE_BARO_RELIABLE_MAX/4) { + good_crc = 0; + //fprintf(stderr, "q_bit == 1 && a->alt > 50175: %06x\n", a->addr); + goto discard_alt; + } + + int reason = 0; + if (abs(delta) < lowDelta) { + reason = 1; + goto accept_alt; + } + if (fpm < max_fpm && fpm > min_fpm) { + reason = 2; + goto accept_alt; + } + + // accept the message if the good_crc score is better than the current alt reliable score + if (good_crc >= a->alt_reliable) { + a->alt_reliable = 0; // reset alt_reliable + reason = 3; + goto accept_alt; + } + // accept the altitude if the source is better than the current one + if (mm->source > a->baro_alt_valid.source) { + a->alt_reliable = 0; // reset alt_reliable + reason = 4; + goto accept_alt; + } + + + goto discard_alt; + int score_add; +accept_alt: + if (Modes.netReceiverId && mm->source == SOURCE_MODE_S && now - a->baro_alt_valid.updated > 10 * SECONDS) { + score_add = 0; + } else { + score_add = good_crc + 1; + } + if (mm->source == SOURCE_MODE_S && a->position_valid.source == SOURCE_MLAT + && baroAge > 5 * SECONDS && trackDataAge(now, &a->position_valid) < 15 * SECONDS && a->receiverCount > 1) { + // when running with certain mlat-server versions, set altitude to inreliable when we get them too infrequently + score_add = -1; + } + + if (accept_data(&a->baro_alt_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->alt_reliable = imin(ALTITUDE_BARO_RELIABLE_MAX , a->alt_reliable + score_add); + if (a->alt_reliable < 0) { + a->alt_reliable = 0; + } + // || wasReliable != altBaroReliable(a) + if ( + debug + || (0 && baroAge < 60 * SECONDS && (abs(delta) > 5000 || alt > 50000)) + ) { + fprintf(stderr, "Alt check S: %06x: %2d -> %2d %6d ->%6d, %s->%s, min %5.1f kfpm, max %5.1f kfpm, actual %7.1f kfpm, reason %1d, reliable %1d\n", + a->addr, old_reliable, a->alt_reliable, a->baro_alt, alt, + source_string(baroSource), + source_string(mm->source), + min_fpm/1000.0, max_fpm/1000.0, fpm/1000.0, + reason, altBaroReliable(a)); + } + a->baro_alt = alt; + } + return; +discard_alt: + if (!will_accept_data(&a->baro_alt_valid, mm->source, mm, a)) { + return; + } + a->alt_reliable = a->alt_reliable - (good_crc+1); + if ( + debug + || (Modes.debug_bogus + && trackDataAge(now, &a->baro_rate_valid) < 20 * SECONDS + && trackDataAge(now, &a->baro_alt_valid) < 20 * SECONDS + ) + ) { + fprintf(stdout, "%6llx %5.1f Alt check F: %06x %2d %6d ->%6d, %s->%s, min %.1f kfpm, max %.1f kfpm, actual %.1f kfpm\n", + (long long) mm->timestamp % 0x1000000, + 10 * log10(mm->signalLevel), + a->addr, a->alt_reliable, a->baro_alt, alt, + source_string(a->baro_alt_valid.source), + source_string(mm->source), + min_fpm/1000.0, max_fpm/1000.0, fpm/1000.0); + } + if (a->alt_reliable <= 0) { + //fprintf(stdout, "Altitude INVALIDATED: %06x\n", a->addr); + a->alt_reliable = 0; + if (a->pos_reliable_valid.source != SOURCE_JAERO) { + a->baro_alt_valid.source = SOURCE_INVALID; + } + } + if (Modes.garbage_ports) + mm->source = SOURCE_INVALID; + return; +} + +static int accept_cpr(struct aircraft *a, struct modesMessage *mm) { + // 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; + a->cpr_even_lat = mm->cpr_lat; + a->cpr_even_lon = mm->cpr_lon; + compute_nic_rc_from_message(mm, a, &a->cpr_even_nic, &a->cpr_even_rc); + if (0 && a->addr == Modes.cpr_focus) + fprintf(stderr, "E \n"); + return 1; + } + + // CPR, odd + if (mm->cpr_valid && mm->cpr_odd && accept_data(&a->cpr_odd_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->cpr_odd_type = mm->cpr_type; + a->cpr_odd_lat = mm->cpr_lat; + a->cpr_odd_lon = mm->cpr_lon; + compute_nic_rc_from_message(mm, a, &a->cpr_odd_nic, &a->cpr_odd_rc); + if (0 && a->addr == Modes.cpr_focus) + fprintf(stderr, "O \n"); + return 1; + } + return 0; +} + +// +//========================================================================= +// +// Receive new messages and update tracked aircraft state +// + +struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) { + struct aircraft *res = NULL; + int64_t now = mm->sysTimestamp; + + ++Modes.stats_current.messages_total; + + Modes.messageRateAcc[0]++; + if (now > Modes.nextMessageRateCalc) { + calculateMessageRateGlobal(now); + } + + if (0) { + static int64_t lastPrint; + static int msgAcc; + int64_t printIval = 50; + + msgAcc++; + + if (now > lastPrint + printIval) { + int64_t elapsed = now - lastPrint; + double rate = msgAcc / (elapsed * 0.001); + int width = rate / 20; + + unsigned char bar[1024]; + for (int i = 0; i < (int) sizeof(bar); i++) { + bar[i] = 219; // ascii block + } + + if (width >= (int) sizeof(bar)) { + width = (int) sizeof(bar) - 1; + } + bar[width] = '\0'; + + fprintTimePrecise(stderr, now); + fprintf(stderr, " %4d %s\n", (int) rate, bar); + //fprintf(stderr, "%5.0f\n", rate / 100); + lastPrint = now; + msgAcc = 0; + } + } + + if (mm->msgtype == DFTYPE_MODEAC) { + // Mode A/C, just count it (we ignore SPI) + modeAC_count[modeAToIndex(mm->squawkHex)]++; + res = NULL; + goto exit; + } + if (mm->decodeResult < 0) { + res = NULL; + goto exit; + } + + struct aircraft *a; + unsigned int cpr_new = 0; + mm->calculated_track = -1; + + if (CHECK_APPROXIMATIONS) { + // great circle random testing stuff ... + for (int i = 0; i < 100; i++) { + double la1 = 2 * random() / (double) INT_MAX - 1; + double la2 = 2 * random() / (double) INT_MAX - 1; + double lo1 = 2 * random() / (double) INT_MAX - 1; + double lo2 = 2 * random() / (double) INT_MAX - 1; + la1 *= 90; + lo1 *= 180; + la2 = la1 + 90 * la2; + lo2 = lo1 + 90 * lo2; + if (greatcircle(la1, lo1, la2, lo2, 0)) { + } + if (bearing(la1, lo1, la2, lo2)) { + } + } + } + + mm->address_reliable = addressReliable(mm); + + // Lookup our aircraft or create a new one + a = aircraftGet(mm->addr); + if (!a) { // If it's a currently unknown aircraft.... + if (mm->address_reliable) { + a = aircraftCreate(mm->addr); // ., create a new record for it, + } else { + //fprintf(stderr, "%06x: !a && !addressReliable(mm)\n", mm->addr); + res = NULL; + goto exit; + } + } + + struct aircraft scratch; + bool haveScratch = false; + if (mm->cpr_valid || mm->sbs_pos_valid) { + memcpy(&scratch, a, offsetof(struct aircraft, traceCache)); + haveScratch = true; + // messages from receivers classified garbage with position get processed to see if they still send garbage + } else if (mm->garbage) { + res = NULL; + goto exit; + } + + // only count the aircraft as "seen" for reliable messages with CRC + if (mm->address_reliable) { + int64_t elapsed_seen = now - a->seen; + if (elapsed_seen > 5 * MINUTES) { + Modes.stats_current.unique_aircraft++; + if ( + (elapsed_seen > 15 * MINUTES && a->addrtype != ADDR_JAERO) + || (elapsed_seen > Modes.trackExpireJaero && a->addrtype == ADDR_JAERO) + ) { + // if an aircraft hasn't been active in a bit, reset its message count + a->messages = 0; + } + } + + a->seen = now; + + if (now - a->seen_pos > 500 * RECEIVERIDBUFFER) { + addReceiverId(a, mm, elapsed_seen); + } + } + + // don't use messages with unreliable CRC too long after receiving a reliable address from an aircraft + if (now - a->seen > TRACK_STALE) { + res = NULL; + goto exit; + } + + a->last_message_crc_fixed = (mm->correctedbits > 0) ? 1 : 0; + + + a->messageRateAcc[0]++; + if (now > a->nextMessageRateCalc) { + calculateMessageRate(a, now); + } + + if (mm->signalLevel > 0) { + + a->signalLevel[a->signalNext % 8] = mm->signalLevel; + a->signalNext++; + //fprintf(stderr, "%0.4f\n",mm->signalLevel); + + a->lastSignalTimestamp = now; + } else { + //fprintf(stderr, "signal zero: %06x; %s\n", a->addr, source_string(mm->source)); + // if we haven't received a message with signal level for a bit, set it to zero + if (now - a->lastSignalTimestamp > 15 * SECONDS && a->signalNext > 0) { + a->signalNext = 0; + //fprintf(stderr, "no_sig_thresh: %06x; %d; %d\n", a->addr, (int) a->no_signal_count, (int) a->signalNext); + } + } + + // reset to 100000 on overflow ... avoid any low message count checks + if (a->messages == UINT32_MAX) + a->messages = UINT16_MAX; + + a->messages++; + + if (mm->client) { + if (!mm->garbage) { + mm->client->messageCounter++; + } + mm->client->recentMessages++; + } + + // update addrtype + float newType = mm->addrtype == ADDR_MODE_S ? 4.5 : mm->addrtype; + float oldType = a->addrtype == ADDR_MODE_S ? 4.5 : a->addrtype; + // change type ranking without messing with enum :/ + if ( + (newType <= oldType && now - a->addrtype_updated > TRACK_EXPIRE * 3 / 4) + || (newType > oldType && now - a->addrtype_updated > TRACK_EXPIRE * 3 / 2) + ) { + + if (mm->addrtype == ADDR_ADSB_ICAO && a->position_valid.source != SOURCE_ADSB) { + // don't set to ADS-B without a position + if (mm->msgtype == 17) { + a->addrtype = ADDR_MODE_S; // set type ModeS for DF17 messages when not knowing position + a->addrtype_updated = now; + } + } else { + a->addrtype = mm->addrtype; + a->addrtype_updated = now; + } + + if (a->addrtype > ADDR_ADSB_ICAO_NT) { + a->adsb_version = -1; // reset ADS-B version if a non ADS-B message type is received + } + } + + // decide on where to stash the version + int dummy_version = -1; // used for non-adsb/adsr/tisb messages + int *message_version; + + switch (mm->source) { + case SOURCE_ADSB: + message_version = &a->adsb_version; + break; + case SOURCE_TISB: + message_version = &a->tisb_version; + break; + case SOURCE_ADSR: + message_version = &a->adsr_version; + break; + default: + message_version = &dummy_version; + break; + } + + // assume version 0 until we see something else + if (*message_version < 0) { + *message_version = 0; + } + + if (mm->category_valid) { + a->category = mm->category; + a->category_updated = now; + } + + // operational status message + // done early to update version / HRD / TAH + if (mm->opstatus.valid) { + *message_version = mm->opstatus.version; + + if (mm->opstatus.hrd != HEADING_INVALID) { + a->adsb_hrd = mm->opstatus.hrd; + } + if (mm->opstatus.tah != HEADING_INVALID) { + a->adsb_tah = mm->opstatus.tah; + } + } + + // fill in ADS-B v0 NACp, SIL from position message type + if (*message_version == 0 && !mm->accuracy.nac_p_valid) { + int computed_nacp = compute_v0_nacp(mm); + if (computed_nacp != -1) { + mm->accuracy.nac_p_valid = 1; + mm->accuracy.nac_p = computed_nacp; + } + } + + if (*message_version == 0 && mm->accuracy.sil_type == SIL_INVALID) { + int computed_sil = compute_v0_sil(mm); + if (computed_sil != -1) { + mm->accuracy.sil_type = SIL_UNKNOWN; + mm->accuracy.sil = computed_sil; + } + } + + if (mm->baro_alt_valid && + (mm->source >= a->baro_alt_valid.source + || Modes.debug_bogus + || (trackDataAge(now, &a->baro_alt_valid) > 10 * SECONDS + && a->baro_alt_valid.source != SOURCE_JAERO + && a->baro_alt_valid.source != SOURCE_SBS) + || trackDataAge(now, &a->baro_alt_valid) > 30 * SECONDS + ) + ) { + updateAltitude(now, a, mm); + } + + if (mm->squawk_valid) { + uint32_t oldsquawk = a->squawk; + + int changeTentative = 0; + int changeActual = 0; + if (a->squawkTentative != mm->squawkHex && now - a->seen < 15 * SECONDS && will_accept_data(&a->squawk_valid, mm->source, mm, a)) { + PPforward; + changeTentative = 1; + } + if ( + (mm->source == SOURCE_JAERO || (a->squawkTentative == mm->squawkHex && now - a->squawkTentativeChanged > 250)) + && accept_data(&a->squawk_valid, mm->source, mm, a, REDUCE_RARE)) { + changeActual = 1; + if (mm->squawkHex != a->squawk) { + a->modeA_hit = 0; + } + a->squawk = mm->squawkHex; + } + // try and forward as soon as possible when the squawk changes + // but not if the squawk is flipflopping very quickly + if ((changeTentative || changeActual) && now - a->squawkTentativeChanged > currentReduceInterval(now)) { + a->squawk_valid.next_reduce_forward = now + currentReduceInterval(now); + mm->reduce_forward = 1; + } + if (changeTentative) { + a->squawkTentative = mm->squawkHex; + a->squawkTentativeChanged = now; + } + + if (Modes.debug_squawk + && (a->squawk == 0x7500 || a->squawk == 0x7600 || a->squawk == 0x7700 + || a->squawkTentative == 0x7500 || a->squawkTentative == 0x7600 || a->squawkTentative == 0x7700 + || oldsquawk == 0x7500 || oldsquawk == 0x7600 || oldsquawk == 0x7700) + ) { + char uuid[32]; // needs 18 chars and null byte + sprint_uuid1(mm->receiverId, uuid); + if (changeTentative) { + if (1) { + fprintf(stderr, "%06x DF: %02d a->squawk: %04x tentative %04x (receiverId: %s)\n", + a->addr, + mm->msgtype, + a->squawk, + mm->squawkHex, + uuid); + } + } else { + static int64_t antiSpam; + if (now > antiSpam + 15 * SECONDS || oldsquawk != a->squawk) { + antiSpam = now; + fprintf(stderr, "%06x DF: %02d a->squawk: %04x ---> %04x (receiverId: %s)\n", + a->addr, + mm->msgtype, + oldsquawk, + a->squawk, + uuid); + } + } + } + } + + if (mm->emergency_valid && accept_data(&a->emergency_valid, mm->source, mm, a, REDUCE_RARE)) { + if (a->emergency != mm->emergency) { + PPforward; + } + a->emergency = mm->emergency; + } + + if (mm->geom_alt_valid && accept_data(&a->geom_alt_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->geom_alt = altitude_to_feet(mm->geom_alt, mm->geom_alt_unit); + } + + if (mm->geom_delta_valid && accept_data(&a->geom_delta_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->geom_delta = mm->geom_delta; + } + + if (mm->heading_valid) { + heading_type_t htype = mm->heading_type; + if (htype == HEADING_MAGNETIC_OR_TRUE) { + htype = a->adsb_hrd; + } else if (htype == HEADING_TRACK_OR_HEADING) { + htype = a->adsb_tah; + } + + if (htype == HEADING_GROUND_TRACK && accept_data(&a->track_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->track = mm->heading; + } else if (htype == HEADING_MAGNETIC) { + double dec; + int err = declination(a, &dec, now); + if (accept_data(&a->mag_heading_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->mag_heading = mm->heading; + + // don't accept more than 45 degree crab when deriving the true heading + if ( + (!trackDataValid(&a->track_valid) || fabs(norm_diff(mm->heading + dec - a->track, 180)) < 45) + && !err && accept_data(&a->true_heading_valid, SOURCE_INDIRECT, mm, a, REDUCE_OFTEN) + ) { + a->true_heading = norm_angle(mm->heading + dec, 180); + calc_wind(a, now); + } + } + } else if (htype == HEADING_TRUE && accept_data(&a->true_heading_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->true_heading = mm->heading; + } + } + + if (mm->track_rate_valid && accept_data(&a->track_rate_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->track_rate = mm->track_rate; + } + + if (mm->roll_valid && accept_data(&a->roll_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->roll = mm->roll; + } + + if (mm->gs_valid) { + mm->gs.selected = (*message_version == 2 ? mm->gs.v2 : mm->gs.v0); + if (accept_data(&a->gs_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->gs = mm->gs.selected; + } + } + + if (mm->ias_valid && accept_data(&a->ias_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->ias = mm->ias; + } + + if (mm->tas_valid + && !(trackDataValid(&a->ias_valid) && mm->tas < a->ias) + && accept_data(&a->tas_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->tas = mm->tas; + calc_temp(a, now); + calc_wind(a, now); + } + + if (mm->mach_valid && accept_data(&a->mach_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->mach = mm->mach; + calc_temp(a, now); + } + + if (mm->baro_rate_valid && accept_data(&a->baro_rate_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->baro_rate = mm->baro_rate; + } + + if (mm->geom_rate_valid && accept_data(&a->geom_rate_valid, mm->source, mm, a, REDUCE_OFTEN)) { + a->geom_rate = mm->geom_rate; + } + + if ( + mm->airground != AG_INVALID + // only consider changing ground state if the message has information about ground state + && (mm->source >= a->airground_valid.source || mm->source >= SOURCE_MODE_S_CHECKED || trackDataAge(now, &a->airground_valid) > 2 * TRACK_EXPIRE) + // don't accept lower quality data until our state is 2 minutes old + // usually lower quality data is allowed after 15 seconds by accept_data, this doesn't make sense for ground state + ) { + // If our current state is UNCERTAIN or INVALID, accept new data + // If our current state is certain but new data is not, don't accept the new data + if (a->airground == AG_INVALID || a->airground == AG_UNCERTAIN || mm->airground != AG_UNCERTAIN + // also accept ground to air transitions for an uncertain ground state message if the plane is moving fast enough + // and the baro altitude is in a reliable state + || (mm->airground == AG_UNCERTAIN + && a->airground == AG_GROUND + && altBaroReliable(a) + && trackDataAge(now, &a->gs_valid) < 3 * SECONDS + && a->gs > 80 + ) + ) { + if ( + (a->airground == AG_AIRBORNE || a->airground == AG_GROUND) + && (a->last_cpr_type == CPR_SURFACE || a->last_cpr_type == CPR_AIRBORNE) + && trackDataAge(now, &a->cpr_odd_valid) < 20 * SECONDS + && trackDataAge(now, &a->cpr_even_valid) < 20 * SECONDS + && now < a->seenPosReliable + 20 * SECONDS + && trackDataAge(now, &a->airground_valid) < 20 * SECONDS + ) { + // if we have very recent CPR / position data ... + // those are more reliable in an aggregation situation, + // ignore other airground status indication + } else if (accept_data(&a->airground_valid, mm->source, mm, a, REDUCE_RARE)) { + focusGroundstateChange(a, mm, 1, now, mm->airground); + if (mm->airground != a->airground) { + mm->reduce_forward = 1; + PPforward; + } + a->airground = mm->airground; + } + } + } + + if (mm->callsign_valid && accept_data(&a->callsign_valid, mm->source, mm, a, REDUCE_RARE)) { + memcpy(a->callsign, mm->callsign, sizeof (a->callsign)); + } + + if (mm->nav.mcp_altitude_valid && accept_data(&a->nav_altitude_mcp_valid, mm->source, mm, a, REDUCE_RARE)) { + a->nav_altitude_mcp = mm->nav.mcp_altitude; + } + + if (mm->nav.fms_altitude_valid && accept_data(&a->nav_altitude_fms_valid, mm->source, mm, a, REDUCE_RARE)) { + a->nav_altitude_fms = mm->nav.fms_altitude; + } + + if (mm->nav.altitude_source != NAV_ALT_INVALID && accept_data(&a->nav_altitude_src_valid, mm->source, mm, a, REDUCE_RARE)) { + a->nav_altitude_src = mm->nav.altitude_source; + } + + if (mm->nav.heading_valid && accept_data(&a->nav_heading_valid, mm->source, mm, a, REDUCE_RARE)) { + a->nav_heading = mm->nav.heading; + } + + if (mm->nav.modes_valid && accept_data(&a->nav_modes_valid, mm->source, mm, a, REDUCE_RARE)) { + a->nav_modes = mm->nav.modes; + } + + if (mm->nav.qnh_valid && accept_data(&a->nav_qnh_valid, mm->source, mm, a, REDUCE_RARE)) { + a->nav_qnh = mm->nav.qnh; + } + + if (mm->alert_valid && accept_data(&a->alert_valid, mm->source, mm, a, REDUCE_RARE)) { + a->alert = mm->alert; + } + + if (mm->spi_valid && accept_data(&a->spi_valid, mm->source, mm, a, REDUCE_RARE)) { + 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; + } + + if (mm->cpr_valid && accept_cpr(a, mm)) { + cpr_new = 1; + } + + if (mm->cpr_valid) { + cpr_duplicate_check(now, a, mm); + } + + if (!mm->duplicate && !mm->garbage) { + int setLastStatus = 0; + if (mm->msgtype == 17) { + if ((mm->metype >= 1 && mm->metype <= 4) || mm->metype >= 23) { + setLastStatus = 1; + } + } + if (mm->msgtype == 11) { + setLastStatus = 1; + } + if (!mm->squawk_valid) { + if ((mm->msgtype == 0 || mm->msgtype == 4 || mm->msgtype == 20) && a->airground == AG_GROUND) { + setLastStatus = 1; + } + } + + if (setLastStatus) { + a->lastStatusTs = now; + if (now > a->next_reduce_forward_status) { + a->next_reduce_forward_status = now + 4 * currentReduceInterval(now); + mm->reduce_forward = 1; + PPforward; + } + } + } + + + if (Modes.beast_reduce_optimize_mlat) { + if (mm->cpr_valid || a->position_valid.source < SOURCE_ADSR) { + mm->reduce_forward = 1; + } + } + + if (mm->acas_ra_valid) { + + unsigned char *bytes = NULL; + if (mm->msgtype == 16) { + bytes = mm->MV; + } else if (mm->metype == 28 && mm->mesub == 2) { + bytes = mm->ME; + } else { + bytes = mm->MB; + } + + if (bytes && (checkAcasRaValid(bytes, mm, 0))) { + if (accept_data(&a->acas_ra_valid, mm->source, mm, a, REDUCE_OFTEN)) { + if (memcmp(a->acas_ra, bytes, sizeof(a->acas_ra)) != 0) { + mm->reduce_forward = 1; + PPforward; + } + memcpy(a->acas_ra, bytes, sizeof(a->acas_ra)); + logACASInfoShort(mm->addr, bytes, a, mm, mm->sysTimestamp); + } + } else if (bytes && Modes.debug_ACAS + && checkAcasRaValid(bytes, mm, 1) + && (getbit(bytes, 9) || getbit(bytes, 27) || getbit(bytes, 28))) { + // getbit checks for ARA/RAT/MTE, at least one must be set + logACASInfoShort(mm->addr, bytes, a, mm, mm->sysTimestamp); + } + } + + if (mm->accuracy.sda_valid && accept_data(&a->sda_valid, mm->source, mm, a, REDUCE_RARE)) { + a->sda = mm->accuracy.sda; + } + + if (mm->accuracy.nic_a_valid && accept_data(&a->nic_a_valid, mm->source, mm, a, REDUCE_RARE)) { + a->nic_a = mm->accuracy.nic_a; + } + + if (mm->accuracy.nic_c_valid && accept_data(&a->nic_c_valid, mm->source, mm, a, REDUCE_RARE)) { + a->nic_c = mm->accuracy.nic_c; + } + + if (mm->accuracy.nic_baro_valid && accept_data(&a->nic_baro_valid, mm->source, mm, a, REDUCE_RARE)) { + a->nic_baro = mm->accuracy.nic_baro; + } + + if (mm->accuracy.nac_p_valid && accept_data(&a->nac_p_valid, mm->source, mm, a, REDUCE_RARE)) { + a->nac_p = mm->accuracy.nac_p; + } + + if (mm->accuracy.nac_v_valid && accept_data(&a->nac_v_valid, mm->source, mm, a, REDUCE_RARE)) { + a->nac_v = mm->accuracy.nac_v; + } + + if (mm->accuracy.sil_type != SIL_INVALID && accept_data(&a->sil_valid, mm->source, mm, a, REDUCE_RARE)) { + a->sil = mm->accuracy.sil; + if (a->sil_type == SIL_INVALID || mm->accuracy.sil_type != SIL_UNKNOWN) { + a->sil_type = mm->accuracy.sil_type; + } + } + + if (mm->accuracy.gva_valid && accept_data(&a->gva_valid, mm->source, mm, a, REDUCE_RARE)) { + a->gva = mm->accuracy.gva; + } + + if (mm->accuracy.sda_valid && accept_data(&a->sda_valid, mm->source, mm, a, REDUCE_RARE)) { + a->sda = mm->accuracy.sda; + } + + // Now handle derived data + + // derive geometric altitude if we have baro + delta + if ( + (a->baro_alt_valid.updated > a->geom_alt_valid.updated || a->geom_delta_valid.updated > a->geom_alt_valid.updated) + && altBaroReliable(a) + && compare_validity(&a->baro_alt_valid, &a->geom_alt_valid) > 0 + && trackDataValid(&a->geom_delta_valid) + && a->geom_delta_valid.source >= a->geom_alt_valid.source) { + // Baro is more recent than geometric, derive geometric from baro + delta + mm->geom_alt = a->baro_alt + a->geom_delta; + mm->geom_alt_unit = UNIT_FEET; + mm->geom_alt_derived = 1; + a->geom_alt = mm->geom_alt; + combine_validity(&a->geom_alt_valid, &a->baro_alt_valid, &a->geom_delta_valid, now); + } + + // to keep the barometric altitude consistent with geometric altitude, save a derived geom_delta if all data are current + if (mm->geom_alt_valid + && a->geom_alt_valid.updated > a->geom_delta_valid.updated + && altBaroReliable(a) + && trackDataAge(now, &a->baro_alt_valid) < 1 * SECONDS + && accept_data(&a->geom_delta_valid, mm->source, mm, a, REDUCE_OFTEN) + ) { + combine_validity(&a->geom_delta_valid, &a->baro_alt_valid, &a->geom_alt_valid, now); + a->geom_delta = a->geom_alt - a->baro_alt; + } + + // If we've got a new cpr_odd or cpr_even + if (cpr_new) { + // this is in addition to the normal air / ground handling + // making especially sure we catch surface -> airborne transitions + if (mm->addrtype >= a->addrtype) { + //if (a->last_cpr_type == CPR_SURFACE && mm->cpr_type == CPR_AIRBORNE + if (mm->cpr_type == CPR_AIRBORNE + && (a->last_cpr_type == CPR_SURFACE || mm->airground == AG_AIRBORNE) + && accept_data(&a->airground_valid, mm->source, mm, a, REDUCE_RARE)) { + focusGroundstateChange(a, mm, 2, now, AG_AIRBORNE); + if (mm->airground != a->airground) { + mm->reduce_forward = 1; + PPforward; + } + a->airground = AG_AIRBORNE; + } + // old or shitty transponders can continue sending CPR_AIRBORNE while on the ground + // but the risk to wrongly show good transponders on the ground is too damn great + // thus set AG_UNCERTAIN if we get CPR_AIRBORNE and are currently AG_GROUND + if (mm->cpr_type == CPR_AIRBORNE + && mm->airground == AG_UNCERTAIN + && a->airground == AG_GROUND + && accept_data(&a->airground_valid, mm->source, mm, a, REDUCE_RARE)) { + focusGroundstateChange(a, mm, 2, now, AG_UNCERTAIN); + if (mm->airground != a->airground) { + mm->reduce_forward = 1; + PPforward; + } + a->airground = AG_UNCERTAIN; + } + if (mm->cpr_type == CPR_SURFACE + && accept_data(&a->airground_valid, mm->source, mm, a, REDUCE_RARE)) { + focusGroundstateChange(a, mm, 2, now, AG_GROUND); + if (mm->airground != a->airground) { + mm->reduce_forward = 1; + PPforward; + } + a->airground = AG_GROUND; + } + } + + updatePosition(a, mm, now); + if (0 && a->addr == Modes.cpr_focus) { + fprintf(stderr, "%06x: age: odd %"PRIu64" even %"PRIu64"\n", + a->addr, + trackDataAge(mm->sysTimestamp, &a->cpr_odd_valid), + trackDataAge(mm->sysTimestamp, &a->cpr_even_valid)); + } + } + + if (mm->sbs_in && mm->sbs_pos_valid) { + int old_jaero = 0; + if (mm->source == SOURCE_JAERO && a->trace_len > 0) { + spinLock(&a->traceLock); + for (int i = imax(0, a->trace_current_len - 10); i < a->trace_current_len; i++) { + if ( (int32_t) (mm->decoded_lat * 1E6) == getState(a->trace_current, i)->lat + && (int32_t) (mm->decoded_lon * 1E6) == getState(a->trace_current, i)->lon ) + old_jaero = 1; + } + spinRelease(&a->traceLock); + } + if (Modes.maxRange > 0 && Modes.userLocationValid) { + mm->receiver_distance = greatcircle(Modes.fUserLat, Modes.fUserLon, mm->decoded_lat, mm->decoded_lon, 0); + } + if (old_jaero) { + // avoid using already received positions for JAERO input + } else if (mm->receiver_distance > Modes.maxRange && mm->source != SOURCE_JAERO) { + // ignore positions out of receiver range unless it's jaero + } else if (mm->source == SOURCE_MLAT && mm->mlatEPU > 2 * a->mlatEPU + && imin((int)(3000.0f * logf((float)mm->mlatEPU / (float)a->mlatEPU)), TRACE_STALE * 3 / 4) > (int64_t) trackDataAge(mm->sysTimestamp, &a->pos_reliable_valid) + ) { + // don't use less accurate MLAT positions unless some time has elapsed + // only works with SBS input MLAT data coming from some versions of mlat-server + //fprintf(stderr, "%06x: kasdhflkdshf\n", a->addr); + } else { + if (mm->source == SOURCE_MLAT && accept_data(&a->mlat_pos_valid, mm->source, mm, a, REDUCE_OFTEN)) { + if (0 && greatcircle(a->mlat_lat, a->mlat_lon, mm->decoded_lat, mm->decoded_lon, 1) > 5000) { + a->mlat_pos_valid.source = SOURCE_INVALID; + } + a->mlat_lat = mm->decoded_lat; + a->mlat_lon = mm->decoded_lon; + if (mm->mlatEPU) { + a->mlatEPU += 0.5 * mm->mlatEPU - a->mlatEPU; + } + if (0 && a->pos_reliable_valid.source > SOURCE_MLAT) { + fprintf(stderr, "%06x: %d\n", a->addr, mm->reduce_forward); + } + } + int usePosition = 0; + //fprintf(stderr, "%06x: mlat pos diff: %6.0f\n", a->addr, greatcircle(a->latReliable, a->lonReliable, mm->decoded_lat, mm->decoded_lon, 1)); + // + if (mm->source == SOURCE_MLAT + && now - a->lastMlatForce > Modes.mlatForceInterval + && a->pos_reliable_valid.source > SOURCE_MLAT + && greatcircle(a->latReliable, a->lonReliable, mm->decoded_lat, mm->decoded_lon, 1) > Modes.mlatForceDistance + ) { + a->lastMlatForce = now; + usePosition = 1; + + // force accept_data + a->position_valid.source = SOURCE_INVALID; + a->pos_reliable_valid.source = SOURCE_INVALID; + int res = accept_data(&a->position_valid, mm->source, mm, a, REDUCE_OFTEN); + incrementReliable(a, mm, now, 3); // force reliable + if (0) { + fprintTime(stderr, now); + fprintf(stderr, " %06x: mlat force %d\n", a->addr, res); + } + + } else if ( + mm->source == SOURCE_MLAT + && now - a->seenPosReliable > TRACK_STALE + && accept_data(&a->position_valid, mm->source, mm, a, REDUCE_OFTEN) + ) { + // no speed check for MLAT data if position older than TRACK_STALE + usePosition = 1; + } else if (!speed_check(a, mm->source, mm->decoded_lat, mm->decoded_lon, mm, CPR_NONE)) { + mm->pos_bad = 1; + // speed check failed, do nothing + } else if (accept_data(&a->position_valid, mm->source, mm, a, REDUCE_DOUBLE)) { + usePosition = 1; + } + if (usePosition) { + + incrementReliable(a, mm, now, 2); + + setPosition(a, mm, now); + } + } + } + + if (mm->msgtype == 11 && mm->IID == 0 && mm->correctedbits == 0) { + double reflat; + double reflon; + struct receiver *r = receiverGetReference(mm->receiverId, &reflat, &reflon, a, 1); + if (r) { + if (now - a->rr_seen < 600 * SECONDS && fabs(a->lon - reflon) < 5 && fabs(a->lon - reflon) < 5) { + a->rr_lat = 0.1 * reflat + 0.9 * a->rr_lat; + a->rr_lon = 0.1 * reflon + 0.9 * a->rr_lon; + } else { + a->rr_lat = reflat; + a->rr_lon = reflon; + } + a->rr_seen = now; + if (Modes.debug_rough_receiver_location) { + if ( + (a->position_valid.last_source == SOURCE_INDIRECT && trackDataAge(now, &a->position_valid) > TRACK_EXPIRE_ROUGH - 30 * SECONDS) + || (a->position_valid.last_source != SOURCE_INDIRECT && a->position_valid.source == SOURCE_INVALID) + ) { + if (accept_data(&a->position_valid, SOURCE_INDIRECT, mm, a, REDUCE_OFTEN)) { + a->addrtype_updated = now; + a->addrtype = ADDR_MODE_S; + if (0 && a->position_valid.last_source > SOURCE_INDIRECT && a->position_valid.source == SOURCE_INVALID) { + mm->decoded_lat = a->lat; + mm->decoded_lon = a->lon; + } else { + mm->decoded_lat = a->rr_lat; + mm->decoded_lon = a->rr_lon; + } + set_globe_index(a, globe_index(mm->decoded_lat, mm->decoded_lon)); + setPosition(a, mm, now); + } + } + } + } + } + + if (mm->msgtype == 17) { + int oldNogpsCounter = a->nogpsCounter; + if ( + // no position info or uncertainty >= 4 nmi + (mm->metype == 0 || (mm->accuracy.nac_p_valid && mm->accuracy.nac_p <= 2) ) + && a->gs > 50 + && a->airground != AG_GROUND + && now < a->seenAdsbReliable + NOGPS_DWELL + && now > a->seenAdsbReliable + 10 * SECONDS + && a->nogpsCounter < NOGPS_MAX + ) { + a->nogpsCounter++; + } + if (a->nogpsCounter > 0) { + if (now > a->seenAdsbReliable + NOGPS_DWELL) { + a->nogpsCounter = 0; + } + + // position info during last 10 seconds and uncertainty <= 1 nmi + if (mm->source == SOURCE_ADSB && mm->accuracy.nac_p_valid && mm->accuracy.nac_p >= 4 + && now < a->seenAdsbReliable + 10 * SECONDS) { + a->nogpsCounter--; + } + } + if (Modes.debug_nogps && oldNogpsCounter != a->nogpsCounter) { + fprintf(stderr, "%06x nogps %d -> %d\n", a->addr, oldNogpsCounter, a->nogpsCounter); + } + } + + if (mm->msgtype == 11 && mm->IID == 0 && mm->correctedbits == 0) { + if (Modes.net_output_json_include_nopos && now > a->nextJsonPortOutput + && now - a->seenPosReliable > 10 * SECONDS && now - a->seenPosReliable > 2 * Modes.net_output_json_interval) { + a->nextJsonPortOutput = now + Modes.net_output_json_interval; + mm->jsonPositionOutputEmit = 1; + } + // forward DF0/DF11 every 2 * beast_reduce_interval for beast_reduce + if (now > a->next_reduce_forward_DF11) { + a->next_reduce_forward_DF11 = now + 4 * currentReduceInterval(now); + mm->reduce_forward = 1; + PPforward; + } + } + + if (0 && a->addr == Modes.cpr_focus && mm->cpr_valid) { + displayModesMessage(mm); + } + + if (cpr_new) { + a->last_cpr_type = mm->cpr_type; + } + + if (haveScratch && (mm->garbage || mm->pos_bad || mm->duplicate)) { + memcpy(a, &scratch, offsetof(struct aircraft, traceCache)); + } + + if (!(mm->source < a->position_valid.source || mm->in_disc_cache || mm->garbage || mm->pos_ignore || mm->pos_receiver_range_exceeded)) { + if (mm->pos_bad) { + position_bad(mm, a); + } + a->speedUnreliable += mm->speedUnreliable; + a->trackUnreliable += mm->trackUnreliable; + a->speedUnreliable = imax(0, imin(16, a->speedUnreliable)); + a->trackUnreliable = imax(0, imin(16, a->trackUnreliable)); + } + + if (!a->onActiveList && includeAircraftJson(now, a)) { + updateValidities(a, now); + ca_add(&Modes.aircraftActive, a); + a->onActiveList = 1; + //fprintf(stderr, "active len %d\n", Modes.aircraftActive.len); + } + + if (mm->reduce_forward) { + // don't reduce forward duplicate / garbage / bad positions when garbage ports is active + if ((mm->duplicate || mm->garbage || mm->pos_bad) && Modes.garbage_ports) { + mm->reduce_forward = 0; + } + if (Modes.beast_reduce_filter_distance != -1 + && now < a->seenPosReliable + 1 * MINUTES + && Modes.userLocationValid + && greatcircle(Modes.fUserLat, Modes.fUserLon, a->latReliable, a->lonReliable, 0) > Modes.beast_reduce_filter_distance) { + mm->reduce_forward = 0; + //fprintf(stderr, "%.0f %0.f\n", greatcircle(Modes.fUserLat, Modes.fUserLon, a->latReliable, a->lonReliable, 0) / 1852.0, Modes.beast_reduce_filter_distance / 1852.0); + } + if (Modes.beast_reduce_filter_altitude != -1 + && altBaroReliable(a) + && a->airground != AG_GROUND + && a->baro_alt > Modes.beast_reduce_filter_altitude) { + mm->reduce_forward = 0; + //fprintf(stderr, "%.0f %.0f\n", (double) a->baro_alt, Modes.beast_reduce_filter_altitude); + } + } + + // forward all CPRs to the apex for faster garbage detection and such + // even the duplicates and the garbage + // unless garbage ports are configured + if (Modes.netIngest && mm->cpr_valid && !(Modes.garbage_ports && mm->garbage)) { + mm->reduce_forward = 1; + PPforward; + } + + mm->aircraft = a; + res = a; + + struct aircraft *ac; + +exit: + + ac = res; + + //fprintf(stderr, "epoch: %.6f\n", mm->sysTimestamp / 1000.0); + + // In non-interactive non-quiet mode, display messages on standard output + if ( + !Modes.quiet + || (Modes.show_only != BADDR && (mm->addr == Modes.show_only || mm->maybe_addr == Modes.show_only)) + || (Modes.debug_7700 && ac && ac->squawk == 0x7700 && trackDataValid(&ac->squawk_valid)) + ) { + // filter messages with unwanted DF types (sbs_in are unknown DF type, filter them all, this is arbitrary but no one cares anyway) + if (!(Modes.filterDF && (mm->sbs_in || !(Modes.filterDFbitset & (1 << mm->msgtype))))) { + displayModesMessage(mm); + } + } + + if (Modes.debug_bogus) { + + if (!Modes.synthetic_now) { + Modes.startup_time = mstime() - mm->timestamp / 12000U; + } + Modes.synthetic_now = Modes.startup_time + mm->timestamp / 12000U; + + if (mm->addr != HEX_UNKNOWN && !(mm->addr & MODES_NON_ICAO_ADDRESS)) { + ac = aircraftCreate(mm->addr); + } + if (ac && ac->messages == 1 && ac->registration[0] == 0) { + fprintf(stdout, "%6llx %5.1f not in DB: %06x\n", + (long long) mm->timestamp % 0x1000000, + 10 * log10(mm->signalLevel), + mm->addr); + //displayModesMessage(mm); + } else if (0 && !ac) { + if (mm->addr != HEX_UNKNOWN && !dbGet(mm->addr, Modes.dbIndex)) + displayModesMessage(mm); + if (mm->addr == HEX_UNKNOWN && !dbGet(mm->maybe_addr, Modes.dbIndex)) + displayModesMessage(mm); + } + } + return res; +} + +// +// Periodic updates of tracking state +// + +// Periodically match up mode A/C results with mode S results + +void trackMatchAC(int64_t now) { + // clear match flags + for (unsigned i = 0; i < 4096; ++i) { + modeAC_match[i] = 0; + } + + // scan aircraft list, look for matches + struct craftArray *ca = &Modes.aircraftActive; + struct aircraft *a; + + ca_lock_read(ca); + for (int i = 0; i < ca->len; i++) { + a = ca->list[i]; + if (a == NULL) continue; + if ((now - a->seen) > 5000) { + continue; + } + + // match on Mode A + if (trackDataValid(&a->squawk_valid)) { + unsigned i = modeAToIndex(a->squawk); + if ((modeAC_count[i] - modeAC_lastcount[i]) >= TRACK_MODEAC_MIN_MESSAGES) { + a->modeA_hit = 1; + modeAC_match[i] = (modeAC_match[i] ? 0xFFFFFFFF : a->addr); + } + } + + // match on Mode C (+/- 100ft) + if (trackDataValid(&a->baro_alt_valid)) { + int modeC = (a->baro_alt + 49) / 100; + + unsigned modeA = modeCToModeA(modeC); + unsigned i = modeAToIndex(modeA); + if (modeA && (modeAC_count[i] - modeAC_lastcount[i]) >= TRACK_MODEAC_MIN_MESSAGES) { + a->modeC_hit = 1; + modeAC_match[i] = (modeAC_match[i] ? 0xFFFFFFFF : a->addr); + } + + modeA = modeCToModeA(modeC + 1); + i = modeAToIndex(modeA); + if (modeA && (modeAC_count[i] - modeAC_lastcount[i]) >= TRACK_MODEAC_MIN_MESSAGES) { + a->modeC_hit = 1; + modeAC_match[i] = (modeAC_match[i] ? 0xFFFFFFFF : a->addr); + } + + modeA = modeCToModeA(modeC - 1); + i = modeAToIndex(modeA); + if (modeA && (modeAC_count[i] - modeAC_lastcount[i]) >= TRACK_MODEAC_MIN_MESSAGES) { + a->modeC_hit = 1; + modeAC_match[i] = (modeAC_match[i] ? 0xFFFFFFFF : a->addr); + } + } + } + ca_unlock_read(ca); + + // reset counts for next time + for (unsigned i = 0; i < 4096; ++i) { + if (!modeAC_count[i]) + continue; + + if ((modeAC_count[i] - modeAC_lastcount[i]) < TRACK_MODEAC_MIN_MESSAGES) { + if (++modeAC_age[i] > 15) { + // not heard from for a while, clear it out + modeAC_lastcount[i] = modeAC_count[i] = modeAC_age[i] = 0; + } + } else { + // this one is live + // set a high initial age for matches, so they age out rapidly + // and don't show up on the interactive display when the matching + // mode S data goes away or changes + if (modeAC_match[i]) { + modeAC_age[i] = 10; + } else { + modeAC_age[i] = 0; + } + } + + modeAC_lastcount[i] = modeAC_count[i]; + } +} + +/* +static void updateAircraft() { + for (int j = 0; j < Modes.acBuckets; j++) { + for (struct aircraft *a = Modes.aircraft[j]; a; a = a->next) { + } + } +} +*/ + +// +//========================================================================= +// +// If we don't receive new nessages within TRACK_AIRCRAFT_TTL +// we remove the aircraft from the list. +// + +static void removeStaleRange(void *arg, threadpool_threadbuffers_t * buffer_group) { + readsb_task_t *info = (readsb_task_t *) arg; + + int64_t now = info->now; + //fprintf(stderr, "%9d %9d %9d\n", info->from, info->to, Modes.acBuckets); + + // timeout for aircraft with position + int64_t posTimeout = now - 1 * HOURS; + + // timeout for non-ICAO aircraft with position + int64_t nonIcaoPosTimeout = now - 30 * MINUTES; + + if (Modes.writeTraces) { + posTimeout = now - 26 * HOURS; + nonIcaoPosTimeout = now - 26 * HOURS; + } + if (Modes.state_dir && !Modes.userLocationRef) { + posTimeout = now - 6 * 28 * 24 * HOURS; // 6 months + nonIcaoPosTimeout = now - 26 * HOURS; + } + if (Modes.debug_rough_receiver_location) { + posTimeout = now - 2 * 24 * HOURS; + nonIcaoPosTimeout = now - 26 * HOURS; + } + + // aircraft with valid position are never pruned (fix for very long jaero-timeout settings) + + // timeout for aircraft without position + int64_t noposTimeout = now - 5 * MINUTES; + int64_t jaeroTimeout = now - Modes.trackExpireJaero; + + for (int j = info->from; j < info->to; j++) { + struct aircraft **nextPointer = &(Modes.aircraft[j]); + while (*nextPointer) { + struct aircraft *a = *nextPointer; + if ( + ((!a->seenPosReliable && a->seen < noposTimeout) + || ( + a->seenPosReliable && + (a->seenPosReliable < posTimeout || ((a->addr & MODES_NON_ICAO_ADDRESS) && a->seenPosReliable < nonIcaoPosTimeout)) + ) + ) + && (a->addrtype != ADDR_JAERO || a->seen < jaeroTimeout) + ) { + // Count aircraft where we saw only one message before reaping them. + // These are likely to be due to messages with bad addresses. + if (a->messages == 1) + Modes.stats_current.single_message_aircraft++; + + if (a->addr == Modes.cpr_focus) + fprintf(stderr, "del: %06x seen: %.1f seen_pos: %.1f\n", a->addr, (now - a->seen) / 1000.0, (now - a->seen_pos) / 1000.0); + + // Remove the element from the linked list + *nextPointer = a->next; + + freeAircraft(a); + + } else { + if (Modes.keep_traces) { + traceMaintenance(a, now, &buffer_group->buffers[0]); + } + + nextPointer = &(a->next); + } + } + } +} + +static void activeUpdateRange(void *arg, threadpool_threadbuffers_t * buffer_group) { + readsb_task_t *info = (readsb_task_t *) arg; + int64_t now = info->now; + struct craftArray *ca = &Modes.aircraftActive; + + for (int i = info->from; i < info->to; i++) { + struct aircraft *a = ca->list[i]; + if (!a) { + continue; + } + updateValidities(a, now); + if (Modes.keep_traces) { + traceMaintenance(a, now, &buffer_group->buffers[0]); + } + } + //fprintf(stderr, "%9d %9d %9d\n", info->from, info->to, ca->len); +} + +// update active Aircraft +static void activeUpdate(int64_t now) { + struct craftArray *ca = &Modes.aircraftActive; + pthread_mutex_lock(&ca->change_mutex); + for (int i = 0; i < ca->len; i++) { + + struct aircraft *a = ca->list[i]; + if (!a) { + continue; + } + + if (!includeAircraftJson(now, a) && now - a->seen > TRACK_EXPIRE_LONG + 1 * MINUTES) { + a->onActiveList = 0; + + quickRemove(a); + + if (a->globe_index >= 0) { + set_globe_index(a, -5); + } + + // we have the lock and are already scannign the array, remove without ca_remove() + // also keep this array compact + + if (i == ca->len - 1) { + ca->list[i] = NULL; + ca->len--; + } else if (ca->list[ca->len - 1]) { + ca->list[i] = ca->list[ca->len - 1]; + ca->list[ca->len - 1] = NULL; + ca->len--; + i--; // take a step back + continue; + } + } + } + quickInit(); + pthread_mutex_unlock(&ca->change_mutex); +} + +// run activeUpdate and remove stale aircraft for a fraction of the entire hashtable +void trackRemoveStale(int64_t now) { + + struct timespec watch; + startWatch(&watch); + + // update range histogram to progressively clear it if no positions are coming in + update_range_histogram(NULL, now); + + if (now > Modes.nextMessageRateCalc) { + calculateMessageRateGlobal(now); + } + + // update the active aircraft list + //fprintf(stderr, "activeUpdate\n"); + activeUpdate(now); + + + int64_t elapsed1 = lapWatch(&watch); + + int taskCount; + threadpool_task_t *tasks; + readsb_task_t *infos; + + taskCount = imin(Modes.allPoolSize, Modes.allTasks->task_count); + tasks = Modes.allTasks->tasks; + infos = Modes.allTasks->infos; + + // tasks to maintain validities / traces in active list + struct craftArray *ca = &Modes.aircraftActive; + + int section_len = ca->len / taskCount; + int extra = ca->len % taskCount; + // assign tasks + for (int i = 0; i < taskCount; i++) { + threadpool_task_t *task = &tasks[i]; + readsb_task_t *range = &infos[i]; + + range->now = now; + range->from = i * section_len + imin(extra, i); + range->to = range->from + section_len + (i < extra ? 1 : 0); + + if (range->to > ca->len || (i == taskCount - 1 && range->to != ca->len)) { + range->to = ca->len; + fprintf(stderr, "check trackRemoveStale distribution\n"); + } + + task->function = activeUpdateRange; + task->argument = range; + + //fprintf(stderr, "%d %d\n", range->from, range->to); + } + + ca_lock_read(ca); + threadpool_run(Modes.allPool, tasks, taskCount); + ca_unlock_read(ca); + + int64_t elapsed2 = lapWatch(&watch); + + // don't mix tasks above and below + // don't mix tasks above and below + // don't mix tasks above and below + + + // tasks to maintain aircraft in list of all aircraft + + static int part = 0; + // iterate entire list of aircraft roughly every minute + // (assuming this runs every second which it should) + int n_parts = 64 * taskCount; + + section_len = Modes.acBuckets / n_parts; + extra = Modes.acBuckets % n_parts; + + + //fprintf(stderr, "part %d\n", part); + + // assign tasks + for (int i = 0; i < taskCount; i++) { + threadpool_task_t *task = &tasks[i]; + readsb_task_t *range = &infos[i]; + + range->now = now; + + range->from = part * section_len + imin(extra, part); + range->to = range->from + section_len + (part < extra ? 1 : 0); + + if (range->to > Modes.acBuckets || (part == n_parts - 1 && range->to != Modes.acBuckets)) { + range->to = Modes.acBuckets; + fprintf(stderr, "check trackRemoveStale distribution\n"); + } + + task->function = removeStaleRange; + task->argument = range; + + //fprintf(stderr, "%d %d\n", range->from, range->to); + + if (++part >= n_parts) { + //fprintf(stderr, "removestaleRange finished\n"); + part = 0; + } + } + //fprintf(stderr, "removeStaleRange start\n"); + + // run tasks + threadpool_run(Modes.allPool, tasks, taskCount); + + //fprintf(stderr, "removeStaleRange done\n"); + + int64_t elapsed3 = lapWatch(&watch); + + if (elapsed1 + elapsed2 + elapsed3 > 25) { + fprintf(stderr, "trackRemoveStale elapsed: %4ld %4ld %4ld\n", (long) elapsed1, (long) elapsed2, (long) elapsed3); + } +} + +/* +static void adjustExpire(struct aircraft *a, int64_t timeout) { +#define F(f,s,e) do { a->f##_valid.stale_interval = (s) * 1000; a->f##_valid.expire_interval = (e) * 1000; } while (0) + F(callsign, 60, timeout); // ADS-B or Comm-B + F(baro_alt, 15, timeout); // ADS-B or Mode S + F(altitude_geom, 30, timeout); // ADS-B only + F(geom_delta, 30, timeout); // ADS-B only + F(gs, 30, timeout); // ADS-B or Comm-B + F(ias, 30, timeout); // ADS-B (rare) or Comm-B + F(tas, 30, timeout); // ADS-B (rare) or Comm-B + F(mach, 30, timeout); // Comm-B only + F(track, 30, timeout); // ADS-B or Comm-B + F(track_rate, 30, timeout); // Comm-B only + F(roll, 30, timeout); // Comm-B only + F(mag_heading, 30, timeout); // ADS-B (rare) or Comm-B + F(true_heading, 30, timeout); // ADS-B only (rare) + F(baro_rate, 30, timeout); // ADS-B or Comm-B + F(geom_rate, 30, timeout); // ADS-B or Comm-B + F(squawk, 15, timeout); // ADS-B or Mode S + F(airground, 15, timeout); // ADS-B or Mode S + F(nav_qnh, 30, timeout); // Comm-B only + F(nav_altitude_mcp, 30, timeout); // ADS-B or Comm-B + F(nav_altitude_fms, 30, timeout); // ADS-B or Comm-B + F(nav_altitude_src, 30, timeout); // ADS-B or Comm-B + F(nav_heading, 30, timeout); // ADS-B or Comm-B + F(nav_modes, 30, timeout); // ADS-B or Comm-B + F(cpr_odd, 10, timeout); // ADS-B only + F(cpr_even, 10, timeout); // ADS-B only + F(position, 10, timeout); // ADS-B only + F(nic_a, 30, timeout); // ADS-B only + F(nic_c, 30, timeout); // ADS-B only + F(nic_baro, 30, timeout); // ADS-B only + F(nac_p, 30, timeout); // ADS-B only + F(nac_v, 30, timeout); // ADS-B only + F(sil, 30, timeout); // ADS-B only + F(gva, 30, timeout); // ADS-B only + F(sda, 30, timeout); // ADS-B only +#undef F +} +*/ + +static void calc_wind(struct aircraft *a, int64_t now) { + uint32_t focus = 0xc0ffeeba; + + if (a->addr == focus) + fprintf(stderr, "%"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64"\n", trackDataAge(now, &a->tas_valid), trackDataAge(now, &a->true_heading_valid), + trackDataAge(now, &a->gs_valid), trackDataAge(now, &a->track_valid)); + + if (!trackDataValid(&a->position_valid) || a->airground == AG_GROUND) + return; + + if (now < a->wind_updated + 1 * SECONDS) { + // don't do wind calculation more often than necessary, precision isn't THAT good anyhow + return; + } + + if (trackDataAge(now, &a->tas_valid) > TRACK_WT_TIMEOUT + || trackDataAge(now, &a->gs_valid) > TRACK_WT_TIMEOUT + || trackDataAge(now, &a->track_valid) > TRACK_WT_TIMEOUT / 2 + || trackDataAge(now, &a->true_heading_valid) > TRACK_WT_TIMEOUT / 2 + ) { + return; + } + + // don't use this code for now + /* + if (a->trace && a->trace_len >= 2) { + struct state *last = &(a->trace[a->trace_len-1]); + if (now + 1500 < last->timestamp) + last = &(a->trace[a->trace_len-2]); + float track_diff = fabs(a->track - last->track / 10.0); + if (last->track_valid && track_diff > 0.5) + return; + } + */ + + double trk = (M_PI / 180) * a->track; + double hdg = (M_PI / 180) * a->true_heading; + double tas = a->tas; + double gs = a->gs; + double crab = norm_diff(hdg - trk, M_PI); + + double hw = tas - cos(crab) * gs; + double cw = sin(crab) * gs; + double ws = sqrt(hw * hw + cw * cw); + double wd = hdg + atan2(cw, hw); + + wd = norm_angle(wd, M_PI); + + wd *= (180 / M_PI); + crab *= (180 / M_PI); + + //if (a->addr == focus) + //fprintf(stderr, "%06x: %.1f %.1f %.1f %.1f %.1f\n", a->addr, ws, wd, gs, tas, crab); + if (ws > 250) { + // Filter out wildly unrealistic wind speeds + return; + } + a->wind_speed = ws; + a->wind_direction = wd; + a->wind_updated = now; + a->wind_altitude = a->baro_alt; +} +static void calc_temp(struct aircraft *a, int64_t now) { + if (a->airground == AG_GROUND) + return; + if (trackDataAge(now, &a->tas_valid) > TRACK_WT_TIMEOUT || trackDataAge(now, &a->mach_valid) > TRACK_WT_TIMEOUT) + return; + + if (a->mach < 0.395) + return; + + double fraction = a->tas / 661.47 / a->mach; + double oat = (fraction * fraction * 288.15) - 273.15; + double tat = -273.15 + ((oat + 273.15) * (1 + 0.2 * a->mach * a->mach)); + + a->oat = oat; + a->tat = tat; + a->oat_updated = now; +} + +static inline int declination(struct aircraft *a, double *dec, int64_t now) { + // only update delination every 30 seconds (per plane) + // it doesn't change that much assuming the plane doesn't move huge distances in that time + if (now < a->updatedDeclination + 5 * SECONDS) { + *dec = a->magneticDeclination; + return 0; + } + + double year; + time_t now_t = now/1000; + + struct tm utc; + gmtime_r(&now_t, &utc); + + year = 1900.0 + utc.tm_year + utc.tm_yday / 365.0; + + double dip; + double ti; + double gv; + + int res = geomag_calc(a->baro_alt * 0.0003048, a->lat, a->lon, year, dec, &dip, &ti, &gv); + if (res) { + *dec = 0.0; + } else { + a->updatedDeclination = now; + a->magneticDeclination = *dec; + } + return res; +} + +/* +{ + int64_t timestamp:48; + // 16 bits of flags + + int32_t lat; + int32_t lon; + + uint16_t gs; + uint16_t track; + int16_t baro_alt; + int16_t baro_rate; + + int16_t geom_alt; + int16_t geom_rate; + unsigned ias:12; + int roll:12; + addrtype_t addrtype:5; + int padding:3; +#if defined(TRACKS_UUID) + uint64_t receiverId; + */ +void to_state(struct aircraft *a, struct state *new, int64_t now, int on_ground, float track, int stale) { + memset(new, 0, sizeof(struct state)); + + new->timestamp = now; + + new->lat = (int32_t) nearbyint(a->latReliable * 1E6); + new->lon = (int32_t) nearbyint(a->lonReliable * 1E6); + + new->stale = stale; + + if (on_ground) + new->on_ground = 1; + + if (trackVState(now, &a->gs_valid, &a->pos_reliable_valid)) { + new->gs_valid = 1; + new->gs = (uint16_t) nearbyint(a->gs * _gs_factor); + } + if (track > -1) { + new->track = (uint16_t) nearbyint(track * _track_factor); + new->track_valid = 1; + } + if (altBaroReliableTrace(now, a)) { + new->baro_alt_valid = 1; + new->baro_alt = (int16_t) nearbyint(a->baro_alt * _alt_factor); + } + + if (trackVState(now, &a->baro_rate_valid, &a->pos_reliable_valid)) { + new->baro_rate_valid = 1; + new->baro_rate = (int16_t) nearbyint(a->baro_rate * _rate_factor); + } + + if (trackVState(now, &a->geom_alt_valid, &a->pos_reliable_valid)) { + new->geom_alt_valid = 1; + new->geom_alt = (int16_t) nearbyint(a->geom_alt * _alt_factor); + } + if (trackVState(now, &a->geom_rate_valid, &a->pos_reliable_valid)) { + new->geom_rate_valid = 1; + new->geom_rate = (int16_t) nearbyint(a->geom_rate * _rate_factor); + } + + /* + unsigned ias:12; + int roll:12; + addrtype_t addrtype:5; + */ + + if (trackVState(now, &a->ias_valid, &a->pos_reliable_valid)) { + new->ias = a->ias; + new->ias_valid = 1; + } + if (trackVState(now, &a->roll_valid, &a->pos_reliable_valid)) { + new->roll = (int16_t) nearbyint(a->roll * _roll_factor); + new->roll_valid = 1; + } + + new->addrtype = a->addrtype; + +#if defined(TRACKS_UUID) + new->receiverId = (uint32_t) (a->lastPosReceiverId >> 32); +#endif +} + +void to_state_all(struct aircraft *a, struct state_all *new, int64_t now) { + memset(new, 0, sizeof(struct state_all)); + for (int i = 0; i < 8; i++) + new->callsign[i] = a->callsign[i]; + + new->pos_nic = a->pos_nic_reliable; + new->pos_rc = a->pos_rc_reliable; + + new->tas = a->tas; + + new->squawk = a->squawk; + new->category = a->category; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset + new->nav_altitude_mcp = (int16_t) nearbyint(a->nav_altitude_mcp / 4.0f); + new->nav_altitude_fms = (int16_t) nearbyint(a->nav_altitude_fms / 4.0f); + + new->nav_qnh = (int16_t) nearbyint(a->nav_qnh * 10.0f); + new->mach = (int16_t) nearbyint(a->mach * 1000.0f); + + new->track_rate = (int16_t) nearbyint(a->track_rate * 100.0f); + + new->mag_heading = (uint16_t) nearbyint(a->mag_heading * _track_factor); + new->true_heading = (uint16_t) nearbyint(a->true_heading * _track_factor); + new->nav_heading = (uint16_t) nearbyint(a->nav_heading * _track_factor); + + new->emergency = a->emergency; + new->airground = a->airground; + new->nav_modes = a->nav_modes; + new->nav_altitude_src = a->nav_altitude_src; + new->sil_type = a->sil_type; + + if (now < a->wind_updated + TRACK_EXPIRE && abs(a->wind_altitude - a->baro_alt) < 500) { + new->wind_direction = (int) nearbyint(a->wind_direction); + new->wind_speed = (int) nearbyint(a->wind_speed); + new->wind_valid = 1; + } + if (now < a->oat_updated + TRACK_EXPIRE) { + new->oat = (int) nearbyint(a->oat); + if (now < a->tat_updated + TRACK_EXPIRE) { + new->temp_valid = 1; + new->tat = (int) nearbyint(a->tat); + } + } + + if (a->adsb_version < 0) + new->adsb_version = 15; + else + new->adsb_version = a->adsb_version; + + if (a->adsr_version < 0) + new->adsr_version = 15; + else + new->adsr_version = a->adsr_version; + + if (a->tisb_version < 0) + new->tisb_version = 15; + else + new->tisb_version = a->tisb_version; + + new->nic_a = a->nic_a; + new->nic_c = a->nic_c; + new->nic_baro = a->nic_baro; + new->nac_p = a->nac_p; + new->nac_v = a->nac_v; + new->sil = a->sil; + new->gva = a->gva; + new->sda = a->sda; + new->alert = a->alert; + new->spi = a->spi; + + new->position_valid = trackDataValid(&a->pos_reliable_valid); + +#define F(f) do { new->f = trackVState(now, &a->f, &a->pos_reliable_valid); } while (0) + F(callsign_valid); + F(tas_valid); + F(mach_valid); + F(track_rate_valid); + F(mag_heading_valid); + F(true_heading_valid); + F(nic_a_valid); + F(nic_c_valid); + F(nic_baro_valid); + F(nac_p_valid); + F(nac_v_valid); + F(sil_valid); + F(gva_valid); + F(sda_valid); + F(squawk_valid); + F(emergency_valid); + F(airground_valid); + F(nav_qnh_valid); + F(nav_altitude_mcp_valid); + F(nav_altitude_fms_valid); + F(nav_altitude_src_valid); + F(nav_heading_valid); + F(nav_modes_valid); + F(alert_valid); + F(spi_valid); +#undef F +} + +void from_state_all(struct state_all *in, struct state *in2, struct aircraft *a , int64_t ts) { + for (int i = 0; i < 8; i++) + a->callsign[i] = in->callsign[i]; + a->callsign[8] = '\0'; + + a->pos_nic = in->pos_nic; + a->pos_rc = in->pos_rc; + + a->pos_nic_reliable = in->pos_nic; + a->pos_rc_reliable = in->pos_rc; + + a->tas = in->tas; + + a->squawk = in->squawk; + a->category = in->category; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset + a->nav_altitude_mcp = in->nav_altitude_mcp * 4.0f; + a->nav_altitude_fms = in->nav_altitude_fms * 4.0f; + + a->nav_qnh = in->nav_qnh / 10.0f; + a->mach = in->mach / 1000.0f; + + a->track_rate = in->track_rate / 100.0f; + a->mag_heading = in->mag_heading / _track_factor; + a->true_heading = in->true_heading / _track_factor; + a->nav_heading = in->nav_heading / _track_factor; + + a->emergency = in->emergency; + a->airground = in->airground; + a->nav_modes = in->nav_modes; + a->nav_altitude_src = in->nav_altitude_src; + a->sil_type = in->sil_type; + + a->geom_alt = in2->geom_alt / _alt_factor; + a->baro_alt = in2->baro_alt / _alt_factor; + + if (in->wind_valid) { + a->wind_direction = in->wind_direction; + a->wind_speed = in->wind_speed; + a->wind_updated = ts - 5000; + a->wind_altitude = a->baro_alt; + } + if (in->temp_valid) { + a->oat = in->oat; + a->tat = in->tat; + a->oat_updated = ts - 5000; + } + + if (in->adsb_version == 15) + a->adsb_version = -1; + else + a->adsb_version = in->adsb_version; + + if (in->adsr_version == 15) + a->adsr_version = -1; + else + a->adsr_version = in->adsr_version; + + if (in->tisb_version == 15) + a->tisb_version = -1; + else + a->tisb_version = in->tisb_version; + + a->nic_a = in->nic_a; + a->nic_c = in->nic_c; + a->nic_baro = in->nic_baro; + a->nac_p = in->nac_p; + a->nac_v = in->nac_v; + a->sil = in->sil; + a->gva = in->gva; + a->sda = in->sda; + a->alert = in->alert; + a->spi = in->spi; + + a->addrtype = in2->addrtype; + a->ias = in2->ias; + + a->baro_rate = in2->baro_rate / _rate_factor; + a->geom_rate = in2->geom_rate / _rate_factor; + + a->gs = in2->gs / _gs_factor; + a->roll = in2->roll / _roll_factor; + a->track = in2->track / _track_factor; + + a->baro_alt_valid.source = (in2->baro_alt_valid ? SOURCE_INDIRECT : SOURCE_INVALID); + a->baro_alt_valid.updated = ts - 5000; + a->geom_alt_valid.source = (in2->geom_alt_valid ? SOURCE_INDIRECT : SOURCE_INVALID); + a->geom_alt_valid.updated = ts - 5000; + +#define F(f) do { a->f.source = (in2->f ? SOURCE_INDIRECT : SOURCE_INVALID); a->f.updated = ts - 5000; } while (0) + F(baro_rate_valid); + F(geom_rate_valid); + F(ias_valid); + F(roll_valid); + F(gs_valid); + F(track_valid); +#undef F + + a->pos_reliable_valid.source = a->position_valid.source = in->position_valid ? SOURCE_INDIRECT : SOURCE_INVALID; + a->pos_reliable_valid.updated = a->position_valid.updated = ts - 5000; + + // giving this a timestamp is kinda hacky, do it anyway + // we want to be able to reuse the sprintAircraft routine for printing aircraft details +#define F(f) do { a->f.source = (in->f ? SOURCE_INDIRECT : SOURCE_INVALID); a->f.updated = ts - 5000; } while (0) + F(callsign_valid); + F(tas_valid); + F(mach_valid); + F(track_rate_valid); + F(mag_heading_valid); + F(true_heading_valid); + F(nic_a_valid); + F(nic_c_valid); + F(nic_baro_valid); + F(nac_p_valid); + F(nac_v_valid); + F(sil_valid); + F(gva_valid); + F(sda_valid); + F(squawk_valid); + F(emergency_valid); + F(airground_valid); + F(nav_qnh_valid); + F(nav_altitude_mcp_valid); + F(nav_altitude_fms_valid); + F(nav_altitude_src_valid); + F(nav_heading_valid); + F(nav_modes_valid); + F(alert_valid); + F(spi_valid); +#undef F +} + +/* +static const char *cpr_string(cpr_type_t type) { + switch (type) { + case CPR_INVALID: return "INVALID "; + case CPR_SURFACE: return "SURFACE "; + case CPR_AIRBORNE: return "AIRBORNE"; + case CPR_COARSE: return "COARSE "; + default: return "ERR "; + } +} +*/ +static const char *source_string(datasource_t source) { + switch (source) { + case SOURCE_INVALID: + return "INVALID "; + case SOURCE_INDIRECT: + return "INDIRECT"; + case SOURCE_MODE_AC: + return "MODE_AC "; + case SOURCE_SBS: + return "SBS "; + case SOURCE_MLAT: + return "MLAT "; + case SOURCE_MODE_S: + return "MODE_S "; + case SOURCE_JAERO: + return "JAERO "; + case SOURCE_MODE_S_CHECKED: + return "MODE_CH "; + case SOURCE_TISB: + return "TISB "; + case SOURCE_ADSR: + return "ADSR "; + case SOURCE_ADSB: + return "ADSB "; + case SOURCE_PRIO: + return "PRIO "; + default: + return "ERROR "; + } +} + +void updateValidities(struct aircraft *a, int64_t now) { + int64_t elapsed_pos = now - a->seen_pos; + + if (now - a->seen > 500 * RECEIVERIDBUFFER && a->receiverCount > 0) { + a->receiverCount = 0; + for (int i = 0; i < RECEIVERIDBUFFER; i++) { + a->receiverIds[i] = 0; + } + } + + if (a->globe_index >= 0 && elapsed_pos > Modes.trackExpireMax) { + set_globe_index(a, -5); + } + + if (a->category != 0 && now > a->category_updated + Modes.trackExpireMax) + a->category = 0; + + // reset position reliability when no position was received for 60 minutes + if (a->pos_reliable_odd != 0 && a->pos_reliable_even != 0 && elapsed_pos > POS_RELIABLE_TIMEOUT) { + a->pos_reliable_odd = 0; + a->pos_reliable_even = 0; + } + if (a->tracePosBuffered && now > a->seenPosReliable + TRACE_STALE) { + traceUsePosBuffered(a); + } + + updateValidity(&a->baro_alt_valid, now, TRACK_EXPIRE); + + if (a->alt_reliable != 0 && a->baro_alt_valid.source == SOURCE_INVALID) { + a->alt_reliable = 0; + } + + updateValidity(&a->callsign_valid, now, TRACK_EXPIRE_LONG); + updateValidity(&a->geom_alt_valid, now, TRACK_EXPIRE); + updateValidity(&a->geom_delta_valid, now, TRACK_EXPIRE); + updateValidity(&a->gs_valid, now, TRACK_EXPIRE); + updateValidity(&a->ias_valid, now, TRACK_EXPIRE); + updateValidity(&a->tas_valid, now, TRACK_EXPIRE); + updateValidity(&a->mach_valid, now, TRACK_EXPIRE); + + updateValidity(&a->track_valid, now, TRACK_EXPIRE); + updateValidity(&a->track_rate_valid, now, TRACK_EXPIRE); + updateValidity(&a->roll_valid, now, TRACK_EXPIRE); + updateValidity(&a->mag_heading_valid, now, TRACK_EXPIRE); + updateValidity(&a->true_heading_valid, now, TRACK_EXPIRE); + updateValidity(&a->baro_rate_valid, now, TRACK_EXPIRE); + updateValidity(&a->geom_rate_valid, now, TRACK_EXPIRE); + updateValidity(&a->nic_a_valid, now, TRACK_EXPIRE); + + updateValidity(&a->nic_c_valid, now, TRACK_EXPIRE); + updateValidity(&a->nic_baro_valid, now, TRACK_EXPIRE); + updateValidity(&a->nac_p_valid, now, TRACK_EXPIRE); + updateValidity(&a->nac_v_valid, now, TRACK_EXPIRE); + updateValidity(&a->sil_valid, now, TRACK_EXPIRE); + updateValidity(&a->gva_valid, now, TRACK_EXPIRE); + updateValidity(&a->sda_valid, now, TRACK_EXPIRE); + updateValidity(&a->squawk_valid, now, TRACK_EXPIRE); + + updateValidity(&a->emergency_valid, now, TRACK_EXPIRE); + updateValidity(&a->airground_valid, now, TRACK_EXPIRE_LONG); + updateValidity(&a->nav_qnh_valid, now, TRACK_EXPIRE); + updateValidity(&a->nav_altitude_mcp_valid, now, TRACK_EXPIRE); + updateValidity(&a->nav_altitude_fms_valid, now, TRACK_EXPIRE); + updateValidity(&a->nav_altitude_src_valid, now, TRACK_EXPIRE); + updateValidity(&a->nav_heading_valid, now, TRACK_EXPIRE); + updateValidity(&a->nav_modes_valid, now, TRACK_EXPIRE); + + updateValidity(&a->cpr_odd_valid, now, TRACK_EXPIRE); + updateValidity(&a->cpr_even_valid, now, TRACK_EXPIRE); + updateValidity(&a->position_valid, now, TRACK_EXPIRE); + updateValidity(&a->alert_valid, now, TRACK_EXPIRE); + updateValidity(&a->spi_valid, now, TRACK_EXPIRE); + + updateValidity(&a->acas_ra_valid, now, TRACK_EXPIRE); + updateValidity(&a->mlat_pos_valid, now, TRACK_EXPIRE); + updateValidity(&a->pos_reliable_valid, now, TRACK_EXPIRE); + + + if (now > a->nextMessageRateCalc) { + calculateMessageRate(a, now); + } +} + +static void showPositionDebug(struct aircraft *a, struct modesMessage *mm, int64_t now, double bad_lat, double bad_lon) { + + fprintf(stderr, "%06x ", a->addr); + fprintf(stderr, "elapsed %4.1f ", (now - a->seen_pos) / 1000.0); + if (mm->receiver_distance > 0) { + fprintf(stderr, "receiver_distance: %7.1f nmi ", mm->receiver_distance / 1852.0); + } + + if (mm->sbs_in) { + fprintf(stderr, "SBS, "); + if (mm->source == SOURCE_JAERO) + fprintf(stderr, "JAERO, "); + if (mm->source == SOURCE_MLAT) + fprintf(stderr, "MLAT, "); + } else { + fprintf(stderr, "%s%s", + (mm->cpr_type == CPR_SURFACE) ? "surf, " : "air, ", + mm->cpr_odd ? "odd, " : "even, "); + } + + if (mm->sbs_in) { + fprintf(stderr, + "lat: %.6f," + "lon: %.6f", + mm->decoded_lat, + mm->decoded_lon); + } else if (mm->cpr_decoded) { + fprintf(stderr,"lat: %11.6f (%u)," + " lon: %11.6f (%u)," + " relative: %d," + " NIC: %u," + " Rc: %.3f km", + mm->decoded_lat, + mm->cpr_lat, + mm->decoded_lon, + mm->cpr_lon, + mm->cpr_relative, + mm->decoded_nic, + mm->decoded_rc / 1000.0); + } else { + fprintf(stderr,"lat: %11.6f (%u)," + " lon: %11.6f (%u)," + " CPR decoding: failed", + bad_lat, + mm->cpr_lat, + bad_lon, + mm->cpr_lon); + } + fprintf(stderr, "\n"); +} + +static void incrementReliable(struct aircraft *a, struct modesMessage *mm, int64_t now, int odd) { + a->localCPR_allow_ac_rel = 1; + + float threshold = Modes.json_reliable; + if (a->seenPosReliable && now - a->seenPosReliable > POS_RELIABLE_TIMEOUT && mm->source > SOURCE_JAERO + && a->pos_reliable_odd < threshold && a->pos_reliable_even < threshold + ) { + double distance = greatcircle(a->latReliable, a->lonReliable, mm->decoded_lat, mm->decoded_lon, 0); + // if aircraft is within 50 km of last reliable position, treat new position as reliable immediately. + // pos_reliable is mostly to filter out bad decodes which usually show a much larger offset than 50km + // it's very unlikely to get a bad decode that's in a range of 50 km of the last known position + // at this point the position has already passed the speed check + if (distance < 50e3) { + a->pos_reliable_odd = fmaxf(1, threshold); + a->pos_reliable_even = fmaxf(1, threshold); + if (a->addr == Modes.cpr_focus) + fprintf(stderr, "%06x: fast track json_reliable\n", a->addr); + } + } + + float increment = 1.0f; + if (mm->pos_receiver_range_exceeded) { + increment = 0.25f; + } + if (odd == 3) { + increment = Modes.json_reliable; + } + + if (odd) + a->pos_reliable_odd = fminf(a->pos_reliable_odd + increment, Modes.position_persistence); + + if (!odd || odd == 2 || odd == 3) + a->pos_reliable_even = fminf(a->pos_reliable_even + increment, Modes.position_persistence); + + if (a->pos_reliable_odd < increment) { + a->pos_reliable_odd = increment; + } + if (a->pos_reliable_even < increment) { + a->pos_reliable_even = increment; + } +} + +static void position_bad(struct modesMessage *mm, struct aircraft *a) { + + int64_t now = mm->sysTimestamp; + + if (mm->cpr_valid) { + struct cpr_cache *disc; + a->disc_cache_index = (a->disc_cache_index + 1) % DISCARD_CACHE; + + // most recent discarded position which led to decrementing reliability and timestamp (speed_check) + disc = &a->disc_cache[a->disc_cache_index]; + disc->ts = now; + disc->cpr_lat = mm->cpr_lat; + disc->cpr_lon = mm->cpr_lon; + disc->receiverId = mm->receiverId; + } + + a->pos_reliable_odd -= 0.26f; + a->pos_reliable_even -= 0.26f; + + if (a->pos_reliable_odd < 0.1f || a->pos_reliable_even < 0.1f) { + + // when we reach zero, reset reliable state + a->pos_reliable_odd = 0; + a->pos_reliable_even = 0; + + // invalidate CPRs to start fresh + a->cpr_even_valid.source = SOURCE_INVALID; + a->cpr_odd_valid.source = SOURCE_INVALID; + + // accept the CPR we just got to get going again a bit quicker + if (mm->cpr_valid) { + accept_cpr(a, mm); + } + } + + + if (0 && a->addr == Modes.cpr_focus) + fprintf(stderr, "%06x: position_bad %.1f %.1f %u %u\n", a->addr, a->pos_reliable_odd, a->pos_reliable_even, mm->cpr_lat, mm->cpr_lon); +} + +int nogps(int64_t now, struct aircraft *a) { + return (a->nogpsCounter >= NOGPS_SHOW && now < a->seenAdsbReliable + NOGPS_DWELL && now > a->seenAdsbReliable + 15 * SECONDS); +} diff --git a/readsb2/track.h b/readsb2/track.h new file mode 100644 index 0000000..5c77506 --- /dev/null +++ b/readsb2/track.h @@ -0,0 +1,796 @@ +// Part of readsb, a Mode-S/ADSB/TIS message decoder. +// +// track.h: aircraft state tracking prototypes +// +// Copyright (c) 2019 Michael Wolf +// +// This code is based on a detached fork of dump1090-fa. +// +// Copyright (c) 2014-2016 Oliver Jowett +// +// This file is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This file is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// This file incorporates work covered by the following copyright and +// license: +// +// Copyright (C) 2012 by Salvatore Sanfilippo +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef DUMP1090_TRACK_H +#define DUMP1090_TRACK_H + +/* Minimum number of repeated Mode A/C replies with a particular Mode A code needed in a + * 1 second period before accepting that code. + */ +#define TRACK_MODEAC_MIN_MESSAGES 4 + +/* Special value for Rc unknown */ +#define RC_UNKNOWN 0 + +#define ALTITUDE_BARO_RELIABLE_MAX 20 + +#define POS_RELIABLE_TIMEOUT (60 * MINUTES) + +#define TRACK_STALE (15*SECONDS) +#define TRACK_EXPIRE (60*SECONDS) +#define TRACK_EXPIRE_LONG (180*SECONDS) +#define TRACK_EXPIRE_JAERO (33*MINUTES) +#define TRACK_EXPIRE_ROUGH (2 * MINUTES) + +#define NOGPS_DWELL (15 * MINUTES) +#define NOGPS_MAX (20) +#define NOGPS_SHOW (17) + +int nogps(int64_t now, struct aircraft *a); + +// 2.5 seconds maximum between messages used for calculating wind / temperature +#define TRACK_WT_TIMEOUT (2500) + +#define RECEIVERIDBUFFER (12) + +#ifndef RECENT_RECEIVER_IDS +#define RECENT_RECEIVER_IDS (32) +#endif + +typedef struct +{ + uint64_t id; + int64_t time; +} idTime; + +#define DISCARD_CACHE (4) +#define CPR_CACHE (4) + +typedef enum +{ + REDUCE_RARE, + REDUCE_OFTEN, + REDUCE_DOUBLE, +} reduce_freq_t; + +// data moves through three states: +// fresh: data is valid. Updates from a less reliable source are not accepted. +// stale: data is valid. Updates from a less reliable source are accepted. +// expired: data is not valid. + +typedef struct +{ + int64_t updated; /* when it arrived */ + int64_t next_reduce_forward; /* when to next forward the data for reduced beast output */ + datasource_t source:8; /* where the data came from */ + datasource_t last_source:8; /* where the data came from */ + int8_t stale; /* if it's stale 1 / 0 */ + unsigned padding:8; + int padding2; +} data_validity; +// size must be multiple of 64 bits so it can be aligned in struct aircraft. + +// uint16: 0 to 65535 +#define _gs_factor (10.0f) // 6000 to 60000 +#define _track_factor (100.0f) // 360 -> 36000 + +// int16: -32768 to 32767 +#define _alt_factor (1/6.25f) // 200000 to 32000 +#define _rate_factor (1/8.0f) // 262136 to 32767 +#define _roll_factor (10.0f) // 180 to 1800 + +/* Structure representing one point in the aircraft trace */ +struct state +{ + int64_t timestamp:48; + //struct state_flags flags; // 16 bits + + unsigned on_ground:1; + unsigned stale:1; + unsigned leg_marker:1; + unsigned gs_valid:1; + unsigned track_valid:1; + unsigned baro_alt_valid:1; + unsigned baro_rate_valid:1; + unsigned geom_alt_valid:1; + unsigned geom_rate_valid:1; + unsigned roll_valid:1; + unsigned ias_valid:1; + unsigned padding:5; + + int32_t lat; + int32_t lon; + + uint16_t gs; + uint16_t track; + int16_t baro_alt; + int16_t baro_rate; + + int16_t geom_alt; + int16_t geom_rate; + unsigned ias:12; + int roll:12; + addrtype_t addrtype:5; + int padding2:3; +#if defined(TRACKS_UUID) + uint32_t receiverId; +#endif +} __attribute__ ((__packed__)); + +struct state_all +{ + char callsign[8]; // Flight number + + uint16_t squawk; // Squawk + int16_t nav_altitude_mcp; // FCU/MCP selected altitude + int16_t nav_altitude_fms; // FMS selected altitude + + int16_t nav_qnh; // Altimeter setting (QNH/QFE), millibars + uint16_t nav_heading; // target heading, degrees (0-359) + uint16_t mach; + + int16_t track_rate; // Rate of change of ground track, degrees/second + uint16_t mag_heading; // Magnetic heading + uint16_t true_heading; // True heading + + int wind_direction:10; + int wind_speed:10; + int oat:10; + int tat:10; + + unsigned category:8; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset + + unsigned pos_nic:8; // NIC of last computed position + unsigned pos_rc:16; // Rc of last computed position + emergency_t emergency:3; // Emergency/priority status + nav_modes_t nav_modes:7; // enabled modes (autopilot, vnav, etc) + airground_t airground:2; // air/ground status + nav_altitude_source_t nav_altitude_src:3; // source of altitude used by automation + sil_type_t sil_type:3; // SIL supplement from TSS or opstatus + + unsigned tas:12; + + unsigned adsb_version:4; // ADS-B version (from ADS-B operational status); -1 means no ADS-B messages seen + unsigned adsr_version:4; // As above, for ADS-R messages + unsigned tisb_version:4; // As above, for TIS-B messages + + unsigned nic_a : 1; // NIC supplement A from opstatus + unsigned nic_c : 1; // NIC supplement C from opstatus + unsigned nic_baro : 1; // NIC baro supplement from TSS or opstatus + unsigned nac_p : 4; // NACp from TSS or opstatus + unsigned nac_v : 3; // NACv from airborne velocity or opstatus + unsigned sil : 2; // SIL from TSS or opstatus + unsigned gva : 2; // GVA from opstatus + unsigned sda : 2; // SDA from opstatus + unsigned alert : 1; // FS Flight status alert bit + unsigned spi : 1; // FS Flight status SPI (Special Position Identification) bit + + unsigned callsign_valid:1; + unsigned tas_valid:1; + unsigned mach_valid:1; + unsigned track_valid:1; + unsigned track_rate_valid:1; + unsigned mag_heading_valid:1; + unsigned true_heading_valid:1; + unsigned nic_a_valid:1; + unsigned nic_c_valid:1; + unsigned nic_baro_valid:1; + unsigned nac_p_valid:1; + unsigned nac_v_valid:1; + unsigned sil_valid:1; + unsigned gva_valid:1; + unsigned sda_valid:1; + unsigned squawk_valid:1; + unsigned emergency_valid:1; + unsigned airground_valid:1; + unsigned nav_qnh_valid:1; + unsigned nav_altitude_mcp_valid:1; + unsigned nav_altitude_fms_valid:1; + unsigned nav_altitude_src_valid:1; + unsigned nav_heading_valid:1; + unsigned nav_modes_valid:1; + unsigned position_valid:1; // used for position accuracy stuff, position is in small state struct + unsigned alert_valid:1; + unsigned spi_valid:1; + unsigned wind_valid:1; + unsigned temp_valid:1; + +} __attribute__ ((__packed__)); + +#define SFOUR (4) + +typedef struct fourState { + struct state no[SFOUR]; + struct state_all zeroAll; +} fourState; + +typedef struct traceBuffer { + int len; + fourState *trace; +} traceBuffer; + +static inline int getFourStates(int points) { + return ((points + SFOUR - 1) / SFOUR); +} + +static inline int alignSFOUR(int value) { + return ((value + SFOUR - 1) / SFOUR) * SFOUR; +} + +static inline ssize_t stateBytes(int points) { + return getFourStates(points) * sizeof(fourState); +} + +static inline struct state *getState(fourState *buffer, int position) { + return &buffer[position / SFOUR].no[position % SFOUR]; +} + +static inline struct state_all *getStateAll(fourState *buffer, int position) { + if (position % SFOUR == 0) + return &buffer[position / SFOUR].zeroAll; + else + return NULL; +} +typedef struct stateChunk { + unsigned char *compressed; + int32_t compressed_size; + int32_t numStates; + int64_t firstTimestamp; + int64_t lastTimestamp; +} stateChunk; + +struct cpr_cache { + uint32_t cpr_lat; + uint32_t cpr_lon; + int64_t ts; + uint64_t receiverId; +}; + +struct traceCacheEntry { + int64_t ts; + int32_t offset; + int32_t len; + int32_t leg_marker; +}; + +struct traceCache { + int32_t entriesLen; + int32_t json_max; + int32_t firstRecentCache; + int32_t totalAlloc; + int64_t referenceTs; + struct traceCacheEntry *entries; + char *json; +}; + +/* Structure used to describe the state of one tracked aircraft */ +struct aircraft +{ + struct aircraft *next; // Next aircraft in our linked list + uint32_t addr; // ICAO address + addrtype_t addrtype; // highest priority address type seen for this aircraft + int64_t seen; // Time (millis) at which the last packet with reliable address was received + int64_t seen_pos; // Time (millis) at which the last position was received + + uint32_t messages; // Number of Mode S messages received + int32_t onActiveList; + uint32_t receiverCount; + uint32_t category; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset + // + int64_t category_updated; + + // ---- + + int64_t trace_next_mw; // timestamp for next full trace write to /run (tmpfs) + int64_t trace_next_perm; // timestamp for next trace write to history_dir (disk) + int64_t lastSignalTimestamp; // timestamp the last message with RSSI was received + int64_t trace_perm_last_timestamp; // timestamp for last trace point written to disk + + fourState *trace_current; // uncompressed most recent points in the trace + stateChunk *trace_chunks; // compressed chunks of trace + + int32_t trace_current_max; + int32_t trace_current_len; // number of points in our uncompressed most recent trace portion + int32_t trace_len; // total number of points in the trace + int32_t trace_chunk_len; // how many stateChunks are saved for this aircraft + int32_t trace_write; // signal for writing the trace + + int32_t trace_writeCounter; // how many points where added since the complete trace was written to memory + int32_t baro_alt; // Altitude (Baro) + int32_t alt_reliable; + int32_t geom_alt; // Altitude (Geometric) + + int32_t geom_delta; // Difference between Geometric and Baro altitudes + uint32_t signalNext; // next index of signalLevel to use + + // ---- + + double signalLevel[8]; // Last 8 Signal Amplitudes + + // ---- + + float rr_lat; // very rough receiver latitude + float rr_lon; // very rough receiver longitude + int64_t rr_seen; // when we noted this rough position + + + int64_t seenAdsbReliable; // last time we saw a reliable SOURCE_ADSB positions from this aircraft + int64_t addrtype_updated; + float tat; + uint16_t nogpsCounter; + uint16_t receiverIdsNext; + int64_t seenPosReliable; // last time we saw a reliable position + uint64_t lastPosReceiverId; + + // ---- the following section has 9 instead of 8 times 8 bytes. but that's not critical as long as the 8 byte alignment is ok + + uint32_t pos_nic; // NIC of last computed position + uint32_t pos_rc; // Rc of last computed position + double lat; // Coordinates obtained from CPR encoded data + double lon; // Coordinates obtained from CPR encoded data + float pos_reliable_odd; // Number of good global CPRs, indicates position reliability + float pos_reliable_even; + int16_t traceWrittenForYesterday; // the permanent trace has been written for the previous day + uint16_t mlatEPU; + float gs_last_pos; // Save a groundspeed associated with the last position + + float wind_speed; + float wind_direction; + int32_t wind_altitude; + float oat; + int64_t wind_updated; + int64_t oat_updated; + int64_t tat_updated; + + // ---- + + int32_t baro_rate; // Vertical rate (barometric) + int32_t geom_rate; // Vertical rate (geometric) + uint32_t ias; + uint32_t tas; + uint32_t squawk; // Squawk + uint32_t squawkTentative; // require the same squawk code twice to accept it + uint32_t nav_altitude_mcp; // FCU/MCP selected altitude + uint32_t nav_altitude_fms; // FMS selected altitude + uint32_t cpr_odd_lat; + uint32_t cpr_odd_lon; + uint32_t cpr_odd_nic; + uint32_t cpr_odd_rc; + uint32_t cpr_even_lat; + uint32_t cpr_even_lon; + uint32_t cpr_even_nic; + uint32_t cpr_even_rc; + + // ---- + + float nav_qnh; // Altimeter setting (QNH/QFE), millibars + float nav_heading; // target heading, degrees (0-359) + float gs; + float mach; + float track; // Ground track + float track_rate; // Rate of change of ground track, degrees/second + float roll; // Roll angle, degrees right + float mag_heading; // Magnetic heading + + float true_heading; // True heading + float calc_track; // Calculated Ground track + int64_t next_reduce_forward_DF11; + char callsign[16]; // Flight number + + // ---- + + emergency_t emergency; // Emergency/priority status + airground_t airground; // air/ground status + nav_modes_t nav_modes; // enabled modes (autopilot, vnav, etc) + cpr_type_t cpr_odd_type; + cpr_type_t cpr_even_type; + nav_altitude_source_t nav_altitude_src; // source of altitude used by automation + int32_t modeA_hit; // did our squawk match a possible mode A reply in the last check period? + int32_t modeC_hit; // did our altitude match a possible mode C reply in the last check period? + + // data extracted from opstatus etc + int32_t adsb_version; // ADS-B version (from ADS-B operational status); -1 means no ADS-B messages seen + int32_t adsr_version; // As above, for ADS-R messages + int32_t tisb_version; // As above, for TIS-B messages + heading_type_t adsb_hrd; // Heading Reference Direction setting (from ADS-B operational status) + heading_type_t adsb_tah; // Track Angle / Heading setting (from ADS-B operational status) + int32_t globe_index; // custom index of the planes area on the globe + sil_type_t sil_type; // SIL supplement from TSS or opstatus + + uint32_t nic_a : 1; // nic supplement a from opstatus + uint32_t nic_c : 1; // nic supplement c from opstatus + uint32_t nic_baro : 1; // nic baro supplement from tss or opstatus + uint32_t nac_p : 4; // nacp from tss or opstatus + uint32_t nac_v : 3; // nacv from airborne velocity or opstatus + uint32_t sil : 2; // sil from tss or opstatus + uint32_t gva : 2; // gva from opstatus + uint32_t sda : 2; // sda from opstatus + // 16 bit + uint32_t alert : 1; // fs flight status alert bit + uint32_t spi : 1; // fs flight status spi (special position identification) bit + uint32_t pos_surface : 1; // (a->airground == ag_ground) associated with current position + uint32_t last_cpr_type : 2; // mm->cpr_type associated with current position + uint32_t tracePosBuffered : 1; // denotes if a->trace[a->trace_len] has a valid state buffered in it + uint32_t surfaceCPR_allow_ac_rel : 1; // allow surface cpr relative to last known aircraft location + uint32_t localCPR_allow_ac_rel : 1; // allow local cpr relative to last known aircraft location + // 24 bit + uint32_t last_message_crc_fixed : 1; + uint32_t is_df18_exception : 1; + uint32_t chunkRecompressed : 1; + uint32_t padding_b : 5; + // 32 bit !! + + // ---- + + data_validity callsign_valid; + data_validity baro_alt_valid; + data_validity geom_alt_valid; + data_validity geom_delta_valid; + data_validity gs_valid; + data_validity ias_valid; + data_validity tas_valid; + data_validity mach_valid; + + data_validity track_valid; + data_validity track_rate_valid; + data_validity roll_valid; + data_validity mag_heading_valid; + data_validity true_heading_valid; + data_validity baro_rate_valid; + data_validity geom_rate_valid; + data_validity nic_a_valid; + + data_validity nic_c_valid; + data_validity nic_baro_valid; + data_validity nac_p_valid; + data_validity nac_v_valid; + data_validity sil_valid; + data_validity gva_valid; + data_validity sda_valid; + data_validity squawk_valid; + + data_validity emergency_valid; + data_validity airground_valid; + data_validity nav_qnh_valid; + data_validity nav_altitude_mcp_valid; + data_validity nav_altitude_fms_valid; + data_validity nav_altitude_src_valid; + data_validity nav_heading_valid; + data_validity nav_modes_valid; + + data_validity cpr_odd_valid; // Last seen even CPR message + data_validity cpr_even_valid; // Last seen odd CPR message + data_validity position_valid; + data_validity alert_valid; + data_validity spi_valid; + + int64_t lastAirGroundChange; + double latReliable; // last reliable position based on json_reliable threshold + double lonReliable; // last reliable position based on json_reliable threshold + char typeCode[4]; + char registration[12]; + char typeLong[64]; + uint16_t receiverIds[RECEIVERIDBUFFER]; // RECEIVERIDBUFFER = 12 + + int64_t next_reduce_forward_status; + unsigned char acas_ra[7]; // mm->MV from last acas RA message + unsigned char acas_flags; // maybe use for some flags, would be padding otherwise + data_validity acas_ra_valid; + float gs_reliable; + float track_reliable; + int64_t lastMlatForce; + int64_t squawkTentativeChanged; + double magneticDeclination; + int64_t updatedDeclination; + + uint16_t pos_nic_reliable; + uint16_t pos_rc_reliable; + int32_t trackUnreliable; + + uint64_t receiverId; + + // previous position and timestamp + double prev_lat; // previous latitude + double prev_lon; // previous longitude + int64_t prev_pos_time; // time the previous position was received + + int32_t speedUnreliable; + uint32_t lastStatusDiscarded; + + int64_t nextJsonPortOutput; + + float receiver_distance; + float receiver_direction; + + data_validity mlat_pos_valid; + double mlat_lat; + double mlat_lon; + + data_validity pos_reliable_valid; + + // last reliable SOURCE_ADSB positions from this aircraft + double seenAdsbLat; + double seenAdsbLon; + + int64_t lastStatusTs; + int64_t lastOverrideTs; + + fourState *traceLast; + int32_t traceLastNext; + + // DANGER, this section is zeroed when saving and loading data + + char zeroStart; + + uint32_t disc_cache_index; + uint32_t cpr_cache_index; + struct cpr_cache disc_cache[DISCARD_CACHE]; + struct cpr_cache cpr_cache[CPR_CACHE]; + + // DANGER, everything above this section is possibly rolled back in trackUpdateFromMessage using + // the scratch mechanism + + struct traceCache traceCache; + + char ownOp[64]; + char year[4]; + uint16_t dbFlags; + + int8_t initialTraceWriteDone; + std::atomic_int traceLock; + uint32_t trace_chunk_overall_bytes; + + float messageRate; + uint16_t messageRateAcc[MESSAGE_RATE_CALC_POINTS]; + int64_t nextMessageRateCalc; + +#if defined(PRINT_UUIDS) + int recentReceiverIdsNext; + idTime recentReceiverIds[RECENT_RECEIVER_IDS]; +#endif + + char zeroEnd; +}; + +#define aircraftBackAlloc (2 * 1024 * 1024) +#define aircraftBackCap (int64_t) ((aircraftBackAlloc - 3 * sizeof(void*)) / sizeof(struct aircraft)) +struct aircraftBack { + struct aircraftBack *prev; + struct aircraftBack *next; + int64_t used; + struct aircraft store[aircraftBackCap]; +}; + +/* Mode A/C tracking is done separately, not via the aircraft list, + * and via a flat array rather than a list since there are only 4k possible values + * (nb: we ignore the ident/SPI bit when tracking) + */ +extern uint32_t modeAC_count[4096]; +extern uint32_t modeAC_match[4096]; +extern uint32_t modeAC_age[4096]; + +/* is this bit of data valid? */ +static inline void +updateValidity (data_validity *v, int64_t now, int64_t expiration_timeout) +{ + if (v->source == SOURCE_INVALID) + return; + int stale = (now > v->updated + TRACK_STALE); + if (stale != v->stale) + v->stale = stale; + + if (v->source == SOURCE_JAERO) { + if (now > v->updated + Modes.trackExpireJaero) + v->source = SOURCE_INVALID; + } else if (v->source == SOURCE_INDIRECT && Modes.debug_rough_receiver_location) { + if (now > v->updated + TRACK_EXPIRE_ROUGH) + v->source = SOURCE_INVALID; + } else { + if (now > v->updated + expiration_timeout) + v->source = SOURCE_INVALID; + } +} + +/* is this bit of data valid? */ +static inline int +trackDataValid (const data_validity *v) +{ + return (v->source != SOURCE_INVALID); +} + +static inline int posReliable(struct aircraft *a) { + if (!trackDataValid(&a->position_valid)) { + return 0; + } + if (a->position_valid.source == SOURCE_JAERO + || a->position_valid.source == SOURCE_MLAT + || a->position_valid.source == SOURCE_INDIRECT) { + return 1; + } + int reliable = Modes.json_reliable; + // disable this extra requirement for the moment: + if (0 && Modes.position_persistence > reliable && reliable > 1 && (a->addr & MODES_NON_ICAO_ADDRESS || a->addrtype == ADDR_TISB_ICAO || a->addrtype == ADDR_ADSR_ICAO)) { + reliable += 1; // require additional reliability for non-icao hex addresses + } + + if (a->pos_reliable_odd >= reliable && a->pos_reliable_even >= reliable) { + return 1; + } + + return 0; +} +static inline int altBaroReliable(struct aircraft *a) { + if (!trackDataValid(&a->baro_alt_valid)) + return 0; + if (a->position_valid.source == SOURCE_JAERO) + return 1; + if (a->alt_reliable >= 2 * Modes.json_reliable) + return 1; + + return 0; +} + +static inline int +trackVState (int64_t now, const data_validity *v, const data_validity *pos_valid) +{ + if (pos_valid->source <= SOURCE_JAERO) { + // allow normal expiration time for shitty position sources + return (v->source != SOURCE_INVALID); + } + // reduced expiration time for good sources + return (v->source != SOURCE_INVALID && now < v->updated + 35 * SECONDS); + +} + +static inline int altBaroReliableTrace(int64_t now, struct aircraft *a) { + if (altBaroReliable(a) && trackVState(now, &a->baro_alt_valid, &a->pos_reliable_valid)) + return 1; + else + return 0; +} + +/* what's the age of this data, in milliseconds? */ +static inline int64_t +trackDataAge (int64_t now, const data_validity *v) +{ + if (v->updated >= now) + return 0; + return (now - v->updated); +} +void to_state(struct aircraft *a, struct state *new, int64_t now, int on_ground, float track, int stale); +void to_state_all(struct aircraft *a, struct state_all *new, int64_t now); +void from_state_all(struct state_all *in, struct state *in2, struct aircraft *a , int64_t ts); + +/* Update aircraft state from data in the provided mesage. + * Return the tracked aircraft. + */ +struct modesMessage; +struct aircraft *trackUpdateFromMessage (struct modesMessage *mm); + +void trackMatchAC(int64_t now); +void trackRemoveStale(int64_t now); + +void updateValidities(struct aircraft *a, int64_t now); + +struct aircraft *trackFindAircraft(uint32_t addr); + +/* Convert from a (hex) mode A value to a 0-4095 index */ +static inline unsigned +modeAToIndex (unsigned modeA) +{ + return (modeA & 0x0007) | ((modeA & 0x0070) >> 1) | ((modeA & 0x0700) >> 2) | ((modeA & 0x7000) >> 3); +} + +/* Convert from a 0-4095 index to a (hex) mode A value */ +static inline unsigned +indexToModeA (unsigned index) +{ + return (index & 0007) | ((index & 0070) << 1) | ((index & 0700) << 2) | ((index & 07000) << 3); +} + +/* convert from (hex) squawk to (dec) squawk */ +static inline uint32_t squawkHex2Dec(uint32_t s) { + return ( (s & 0xf000) / 0x1000 * 1000 + (s & 0x0f00) / 0x100 * 100 + (s & 0x00f0) / 0x10 * 10 + (s & 0x000f) / 0x1 * 1); +} + +/* convert from (dec) squawk to (hex) squawk */ +static inline uint32_t squawkDec2Hex(uint32_t s) { + return ( (s / 1000 % 10) * 16*16*16 + (s / 100 % 10) * 16*16 + (s / 10 % 10) * 16 + (s % 10) ); +} + +static inline int bogus_lat_lon(double lat, double lon) { + if (fabs(lat) >= 90.0 || fabs(lon) >= 180.0) + return 1; + if (lat == 0 && (lon == -90 || lon == 90 || lon == 0)) + return 1; + if (fabs(lat) < 0.01 && fabs(lon) < 0.01) + return 1; + return 0; +} + +static inline float signalRaw(struct aircraft *a) { + float sum; + if (a->signalNext >= 8) { + sum = a->signalLevel[0] + a->signalLevel[1] + a->signalLevel[2] + a->signalLevel[3] + + a->signalLevel[4] + a->signalLevel[5] + a->signalLevel[6] + a->signalLevel[7]; + return sum / 8.0f; + } else if (a->signalNext >= 4) { + sum = 0; + for (uint32_t i = 0; i < a->signalNext; i++) { + sum += a->signalLevel[i]; + } + return sum / a->signalNext; + } + return 0; +} + +static inline float getSignal(struct aircraft *a) { + return 10.0f * log10f(signalRaw(a) + 1.125e-5f); +} + +static inline int get8bitSignal(struct aircraft *a) { + float signal = sqrtf(signalRaw(a)) * 255.0f; + if (signal > 255.0f) signal = 255.0f; + if (signal < 1.0f && signal > 0.0f) signal = 1.0f; + return (int) nearbyintf(signal); +} + +static inline const char *nonIcaoSpace(struct aircraft *a) { + if (a->addr & MODES_NON_ICAO_ADDRESS) { + return ""; + } else { + return " "; + } +} + +#endif diff --git a/test/full_test_adsb.h b/test/full_test_adsb.h new file mode 100644 index 0000000..538e845 --- /dev/null +++ b/test/full_test_adsb.h @@ -0,0 +1,103 @@ +#ifdef _USE_GTEST +#ifndef FULL_TEST_ADSB_H +#define FULL_TEST_ADSB_H +#include "global.h" + +using namespace SSR; +TEST(ADSBTest, Icao33) { + EXPECT_EQ(decode_icao(hex2bin(std::string("8D406B902015A678D4D220AA4BDA"))), "406B90"); + auto builder = ADS_B::Aircraft_identification_BDS08::Builder() + .set_call_sign("12345678") + .set_vortex_type(Vortex_Type::Glider); + // std::cout << builder.toJson().to_json_string(); + + +} + +// [2026-01-08 14:56:56.227] [info] [func~] [line:~27] [icao~FD1558] [hex~A4802CB8FACA4524FEF7E0EEF137] [df~20] crc ok +TEST(ADSBTest, check_crc) { + std::string msg = "A4802CB8FACA4524FEF7E0EEF137"; + std::string msg_bin = Psc::hex2bin(msg); + auto icao = decode_icao(Psc::hex2bin(msg)); + auto ok = with_AP::check_crc(msg_bin, icao); +} + +// [2026-01-08 14:56:56.223] [info] [func~] [line:~13] [icao~780626] [hex~8D780626F8230004004978C9A11E] [df~17] crc ok +TEST(ADSBTest, check_crc2) { + // std::string msg = "8D780626F8230004004978C9A11E"; + // std::string msg_bin = Psc::hex2bin(msg); + // auto icao = decode_icao(Psc::hex2bin(msg)); + // auto ok = with_PI::check_crc(msg_bin, icao); + // + // + // auto crc_3 = bin2hex(std::bitset<24>(crc(msg_bin, true)).to_string()); + // auto last_3 = bin2hex(msg_bin.substr(msg_bin.size() - 24)); + // + + std::vector msgs = { + "8D7803CD58974612397DF090B5C6", + "8D7802AC58B5436112DDD2A2C3EC", + "8D7821F058A1400500DF7FF72C5F", + "8D78143BF8210002004AB86F2C06", + "8D780C9BE100A5000000006F554B", + "8D7821F058A1479D8630303B0454", + "8D7821F099080BB4F804973C45DF", + "8D78127B9908A0B118088DCBDE88", + "8D7809259914892A90048D467E59", + "8D7813A699087238F0048C502CD3", + "8D780D59F82100020049B8810833", + "8D782167EA2E3858013C087B37B3", + "8D78035458A5C2B60511DD1735DF", + "8D780D59F82100020049B8810833", + "8D7805E09910223718089DC66FFA", + "8D780892990D3598983C94DBA47D", + "8D7813ACEA302858013C08072876", + "8D781DA6588F77601352F1846CC3", + "8D78092558770369A8DC164AF3A0", + "8D7819B7EA2C3864313C085F561E", + "8D7803CD5897460AD17F0D224330", + "8D7809FA5881043EC8A5CA9058F8", + "8D780F769908A0B530088DCC999E", + "8D781DA9EA249858013C08C4DA00", + "8D7802EF589743709CDC2711A541", + "8D7804219909362EB8089AEC3BFE", + "8D7805E0EA44A867FF3C08FAC9C6", + "8D78143B58A1473A87855D0BE3D9", + "8D4CA1AA990D4F17D804A71DC08F", + "8D780D59584547409F803DEEE599", + "8D780D59998D4A84D824878943FD", + "8D78108D588106A09DAF06E195B7", + "8D7809B45885840D9B6C097A9E70", + "8D7809255877036C06DB9DF16680", + "8D71C355EA360866C31C089D63F2", + "8D780572E1190C00000000F6C9C0", + "8D782167990CF6A438088E878C02", + "8D7802EF58974373A8DBF5EC926C", + "8D71C326582F67D9AD63E4D0C896", + "8D7819B799087D32304493B1EC5B", + "8D7813A6588B02E7D074DC67FB00", + "8D7802ACE10D29000000002C0AD0", + "8D780575586157C4F574CE33082B", + "8D782167230C21F8E73DA0818111", + "8D78143B58A143A418313C7B5658", + "8D78089258AB334C4A714D501734", + "8D78127B99089FB0B0048CD6C061", + "8D7809B45885840A696BD9644570", + }; + + for (auto msg : msgs) { + std::string msg_bin = Psc::hex2bin(msg); + auto c1 = crc_hex(msg_bin, true); + auto c2 = crc_hex(msg_bin, false); + + // EXPECT_EQ(oss.str(), msg.substr(msg.size() - 6)); + std::cout << std::hex << std::uppercase << c1 << std::endl; + std::cout << std::hex << std::uppercase << c2 << std::endl; + + } + +} + + +#endif +#endif diff --git a/test/global.h b/test/global.h new file mode 100644 index 0000000..a0cf1a3 --- /dev/null +++ b/test/global.h @@ -0,0 +1,14 @@ + + +#ifdef _USE_GTEST + + + +#include +#include +#include +#include "SSR/export.h" + + + +#endif diff --git a/test/main2.cpp b/test/main2.cpp new file mode 100644 index 0000000..ff09176 --- /dev/null +++ b/test/main2.cpp @@ -0,0 +1,44 @@ +#ifdef _USE_GTEST +#include "full_test_adsb.h" +#include "test_c_common.h" +#include "test_bds_inference.h" +#include "test_adsb.h" +#include "test_allcall.h" +#include "test_tell.h" +#include "test_surv.h" +#include "test_mode_a_c.h" + + +TEST(MLAT, timestamp_build) { + // Timestamp of 1019UTC: 0x244bbb9ac9f0 + // lower 30 bits are 0x3b9ac9f0 => Nanosecond = 999999984 * 1e-9 + // upper 18 bits are 0x912e => daysec = 37166 + // hour = 37166/3600 = 10 + // minute = 37166 / 60 modulo 60 = 19 + // second = 37166 modulo 60 = 26 + // So time is 10:19:26.999999984 after UTC midnight + std::vector list; + list.emplace_back(37166, 999999984); + list.emplace_back(Psc::hex2mem(std::string("244BBB9AC9F0"))); + for (auto &t : list) { + auto it = bin2hex(t.to_bin_string()); + EXPECT_STREQ(it.c_str(), "244BBB9AC9F0"); + EXPECT_EQ(t.daysec, 37166); + EXPECT_EQ(t.nanosec, 999999984); + EXPECT_EQ(t.hh, 10); + EXPECT_EQ(t.mm, 19); + EXPECT_EQ(t.ss, 26); + } + MLAT_timestamp t; + + // std::cout << t.toJson().toJsonString() << std::endl; +} + + + + +//int main(int argc, char **argv) { +// ::testing::InitGoogleTest(&argc, argv); +// return RUN_ALL_TESTS(); +//} +#endif \ No newline at end of file diff --git a/test/test_adsb.h b/test/test_adsb.h new file mode 100644 index 0000000..52348d8 --- /dev/null +++ b/test/test_adsb.h @@ -0,0 +1,263 @@ +#ifdef _USE_GTEST +#ifndef TEST_ADSB_H +#define TEST_ADSB_H +#include "global.h" + +// === TEST ADS-B package === +// === TEST ADS-B package === + +// 8DAABB04482380B0B458CB3C4B5F +// 8DAABB044823845899AC759ABFCB + + + +using namespace ADS_B; +using namespace CPR; + + + +TEST(ADSBTest, Icao2) { + // std::string msg1 = hex2bin("8DAABB04482380B0B458CB3C4B5F"); + // std::string msg2 = hex2bin("8DAABB044823845899AC759ABFCB"); + // + // // TODO 改成测 CPR + // auto it = airborne_position(msg1, msg2, 0 , 1).value(); + // + // + // std::cout<< it.lat << " " << it.lon << std::endl; + // + + EXPECT_EQ(decode_icao(hex2bin("8D406B902015A678D4D220AA4BDA")), "406B90"); + +} +TEST(ADSBTest, Category) { + std::string msg = hex2bin("8D406B902015A678D4D220AA4BDA"); + EXPECT_EQ(category(msg), 0); + set_category(msg, 5); + EXPECT_EQ(category(msg), 5); +} + +TEST(ADSBTest, Callsign) { + std::string msg = hex2bin("8D406B902015A678D4D220AA4BDA"); + EXPECT_EQ(get_call_sign(msg), "EZY85MH "); + set_call_sign(msg, "ADS12345"); + EXPECT_EQ(get_call_sign(msg), "ADS12345"); + set_call_sign(msg, "ADS"); + EXPECT_EQ(get_call_sign(msg), "ADS"); +} +TEST(ADSBTest, Position) { +// auto pos = position(hex2bin("8D40058B58C901375147EFD09357"), hex2bin("8D40058B58C904A87F402D3B8C59"), 1446332400, 1446332405, -1, -1).value(); +// EXPECT_NEAR(pos.latitude, 49.81755, 0.001); +// EXPECT_NEAR(pos.longitude, 6.08442, 0.001); +} +TEST(ADSBTest, PositionSwapOddEven) { +// auto pos = position(hex2bin("8D40058B58C904A87F402D3B8C59"), hex2bin("8D40058B58C901375147EFD09357"), 1446332405, 1446332400, -1, -1).value(); +// EXPECT_NEAR(pos.latitude, 49.81755, 0.001); +// EXPECT_NEAR(pos.longitude, 6.08442, 0.001); +} + +TEST(ADSBTest, Altitude) { + Altitude_type altitude_type; + auto msg_bin = hex2bin("8D40058B58C901375147EFD09357"); + auto tc = get_type_code(msg_bin); + if (tc >= 9 && tc <= 18) { + altitude_type = Altitude_type::Baro; + } + if (tc >= 20 && tc <= 22) { + altitude_type = Altitude_type::GNSS; + } + + // 40 在版本1 2的区别中 + auto alt_baro = decode_altitude_12_meter(msg_bin.substr(40, 12), altitude_type) / aero::ft; + + EXPECT_EQ(alt_baro, 39000); +} + + + +TEST(ADSBTest, Velocity) { + auto vgs = airborne_velocity(hex2bin("8D485020994409940838175B284F")); + EXPECT_NEAR(vgs.speed, 159.0, 0.1); + EXPECT_NEAR(vgs.angle, 182.88, 0.1); + EXPECT_NEAR(vgs.vertical_rate, -832, 0.1); + EXPECT_STREQ(vgs.speed_type.c_str(), "GS"); + + auto vas = airborne_velocity(hex2bin("8DA05F219B06B6AF189400CBC33F")); + EXPECT_NEAR(vas.speed, 375.0, 0.1); + EXPECT_NEAR(vas.angle, 243.98, 0.1); + EXPECT_NEAR(vas.vertical_rate, -2304, 0.1); + EXPECT_STREQ(vas.speed_type.c_str(), "TAS"); + + + auto mb = hex2bin("8FC8200A3AB8F5F893096B000000"); + auto subtype = static_cast(bin2(mb.substr(5, 3))); + Ground_Speed_Type type; + if (subtype == 3) { + type = Ground_Speed_Type::Ground_Normal; + } else if (subtype == 4) { + type = Ground_Speed_Type::Ground_Supersonic; + } + + // TODO: 感觉源码是错的 这个地方文档和源码对不上 + // C:\Users\wyc\AppData\Roaming\Python\Python312\site-packages\pyModeS\decoder\bds\bds06.py 141 surface_velocity + // auto vgs_surface = surface_velocity(mb, type); + // EXPECT_NEAR(vgs_surface.speed, 19.0, 0.1); + // EXPECT_NEAR(vgs_surface.angle, 42.2, 0.1); + // std::cout << vas.toString() << std::endl; + // std::cout << vgs_surface.toString(); +} +TEST(ADSBTest, Emergency) { + // 检查是否紧急状态 + EXPECT_FALSE(is_emergency(hex2bin("8DA2C1B6E112B600000000760759"))); + // 检查紧急状态代码 + EXPECT_EQ(emergency_state(hex2bin("8DA2C1B6E112B600000000760759")), 0); + // 检查紧急Squawk代码 + EXPECT_EQ(emergency_squawk(hex2bin("8DA2C1B6E112B600000000760759")), "6513"); +} +TEST(ADSBTest, TargetStateStatus) { + // 测试 selected_altitude 函数 + auto sel_alt = selected_altitude(hex2bin("8DA05629EA21485CBF3F8CADAEEB")); + EXPECT_EQ(std::get<0>(sel_alt), 16992); // 高度选择 + EXPECT_EQ(std::get<1>(sel_alt), "MCP/FCU"); // 模式控制面板或飞行管理单元 + EXPECT_NEAR(baro_pressure_setting(hex2bin("8DA05629EA21485CBF3F8CADAEEB")), 1012.8, 0.1); + EXPECT_NEAR(selected_heading(hex2bin("8DA05629EA21485CBF3F8CADAEEB")), 66.8, 0.1); + EXPECT_TRUE(autopilot(hex2bin("8DA05629EA21485CBF3F8CADAEEB"))); + EXPECT_TRUE(vnav_mode(hex2bin("8DA05629EA21485CBF3F8CADAEEB"))); + EXPECT_FALSE(altitude_hold_mode(hex2bin("8DA05629EA21485CBF3F8CADAEEB"))); + EXPECT_FALSE(approach_mode(hex2bin("8DA05629EA21485CBF3F8CADAEEB"))); + EXPECT_TRUE(tcas_operational(hex2bin("8DA05629EA21485CBF3F8CADAEEB"))); + EXPECT_TRUE(lnav_mode(hex2bin("8DA05629EA21485CBF3F8CADAEEB"))); + + // std::cout << altitude_hold_mode(hex2bin("8DA05629EA21485CBF3F8CADAEEB")).value(); + // std::cout << lnav_mode(hex2bin("8DA05629EA21485CBF3F8CADAEEB")).value(); +} + +TEST(ADSBTest, Aircraft_identification_BDS08) { + +} + +TEST(ADSBTest, Airborne_Position_BDS05) { + // auto BDS08 = Aircraft_identification_BDS08::Builder( + // "406B90", + // "ADS12345", + // Vortex_Type::Glider) + // .build(); + // + // + // //std::cout << bin2hex(BDS08.msg) << std::endl; + // + // //std::cout << tell(BDS08.msg); + // + // double lat = 37, lon = 102; + // auto BDS05 = Airborne_Position_BDS05_v01::Builder( + // "406B90", + // lat, + // lon, + // Altitude_type::GNSS, + // 900, + // CPR_Format::odd_frame + // ).build(); + // //std::cout << tell(BDS05.msg) << std::endl; + // + // auto BDS052 = Airborne_Position_BDS05_v01::Builder( + // "406B90", + // lat, + // lon, + // Altitude_type::Baro, + // 900, + // CPR_Format::even_frame + // ).build(); + + //std::cout << tell(BDS052.msg) << std::endl; + + // auto [rlat, rlon] = airborne_position(BDS05.msg, BDS052.msg, 0, 1); + // + // EXPECT_NEAR(rlat, lat, 10); + // EXPECT_NEAR(rlon, lon, 10); +} + +TEST(ADSBTest, PositionWithRef) { + auto pos = CPR::airborne_position_with_ref(hex2bin("8D40058B58C901375147EFD09357"), 50.0, 6.0, 0, 1).value(); + EXPECT_NEAR(pos.lat, 49.82410, 0.1); + EXPECT_NEAR(pos.lon, 6.06785, 0.1); + // 这个和python的结果有浮点误差 + pos = surface_position_with_ref(hex2bin("8FC8200A3AB8F5F893096B000000"), -43.5, 172.5, 0, 1).value(); + EXPECT_NEAR(pos.lat, -43.48564, 0.3); + EXPECT_NEAR(pos.lon, 172.53942, 0.3); + + std::string msg1 = hex2bin("8C4841753AAB238733C8CD4020B1"); + std::string msg2 = hex2bin("8C4841753A8A35323FAEBDAC702D"); + //double lat_cef = 51.990, lon_cef = 4.375; + double lat_cef = 51.990, lon_cef = 4.375; + CPR::Position pos_ref(lat_cef, lon_cef); + + auto p = surface_position(msg1, msg2, 1457996410, 1457996412, pos_ref).value(); + double rlat = p.lat; + double rlon = p.lon; + + //std::cout << rlat << " " << rlon << std::endl; + EXPECT_NEAR(52.3206, rlat, 5); + EXPECT_NEAR(4.73473, rlon, 5); + +} + +TEST(ADSBTest, Surface_Position_BDS06) { + // double lat = 52.320607, lon = 4.734735; + // auto t = Surface_Position_BDS06::Builder( + // "406B90", + // lat, + // lon, + // CPR_Format::even_frame, + // 100).set_track(100).build(); + // auto t2 = Surface_Position_BDS06::Builder( + // "406B90", + // lat, + // lon, + // CPR_Format::odd_frame, + // 100).set_track(100).build(); + + + + //std::cout << tell(t.msg) << std::endl; + + //std::cout << tell(BDS052.msg) << std::endl; + + // std::cout << bin2hex(t.msg) << std::endl; + // std::cout << bin2hex(t2.msg) << std::endl; + + + // auto [rlat, rlon] = surface_position(t.msg, t2.msg, 1457996410, 1457996412, 51.990, 4.375); + // + // // std::cout << rlat << " " << rlon << std::endl; + // EXPECT_NEAR(rlat, lat, 52.320607); + // EXPECT_NEAR(rlon, lon, 4.734735); +} + +TEST(ADSBTest, Ground_Speed_BDS_BDS09) { + // auto Ground_Speed = Ground_Speed_BDS09_v0::Builder("ABCDEF", 45, 640) + // .set_vertical_rate(448) + // .set_GNSS_barometric_altitudes_difference(20) + // .build().msg; + // std::cout << tell(Ground_Speed) << std::endl; +} + +TEST(ADSBTest, Airspeed_BDS_BDS09) { + // auto Airspeed = Airspeed_BDS09_v12::Builder("ABCDEF", 45, 1000) + // .set_vertical_rate(1000) + // .set_GNSS_barometric_altitudes_difference(20) + // .build().msg; + //std::cout << tell(Airspeed) << std::endl; +} + + +TEST(ADSBTest, Surface_Operation_Status_Ver2_BDS65) { + + // auto sops = Surface_Operation_Status_Ver2_BDS65::Builder("ABCDEF") + // .build(); + + //std::cout << tell(sops.msg) << std::endl; +} + +#endif + +#endif diff --git a/test/test_allcall.h b/test/test_allcall.h new file mode 100644 index 0000000..e011009 --- /dev/null +++ b/test/test_allcall.h @@ -0,0 +1,59 @@ +#ifdef _USE_GTEST +#ifndef TEST_ALLCALL_H +#define TEST_ALLCALL_H +#include "global.h" +using namespace All_Call_Reply; +TEST(AllCallTest, Icao) { + // 测试 allcall::icao 函数 + EXPECT_EQ(decode_icao(hex2bin("5D484FDEA248F5")), "484FDE"); +} +TEST(AllCallTest, Interrogator) { + // 测试 allcall::interrogator 函数 + EXPECT_EQ(interrogator_allCall(hex2bin("5D484FDEA248F5")), "SI6"); +} +TEST(AllCallTest, create) { + // 测试 allcall::capability 函数,检查第一个值是否为 5 + // int ca = std::get<0>(*capability_allCall(hex2bin("5D484FDEA248F5"))); + // EXPECT_EQ(ca, 5); + // auto r = All_Call_Reply::All_Call_Reply_BDS::Builder("484FDE") + // .set_interrogator_identifier(22) + // .build(); + // + // std::cout << r.interrogator_identifier() << std::endl; + // //std::cout << tell(r.msg) << std::endl; + // std::cout << "crc msg == " << crc(r.msg) << std::endl; + //std::cout << icao(r.msg) << std::endl; + std::string msg = hex2bin("5D484FDEA248F5"); + //std::cout << crc(msg) << std::endl; + + + auto last_24 = msg.substr(56-24); + + auto crc_24 = std::bitset<24>(crc(msg, true)).to_string(); + auto icao = ::decode_icao(msg); + + + icao.resize(6, '0'); + auto icao_24 = hex2bin(icao); + // 22 = 16 + 4 + 2 + std::string id = "000000000000000000010110"; + + + // 101000100100100011110101 + // 101000100100100011110101 + + // std::cout << "icao === " << bin2hex(xorBin(crc_24, id)) << std::endl; + // std::cout << "icao === " << xorBin(crc_24, id) << std::endl; + + + //std::cout << "icao == " << icao; + + + // std::cout << "interrogator == " << interrogator_allCall(r.msg).value(); + //std::cout << interrogator_allCall(r.msg).value() << std::endl; + // auto t = Comm_B::ELS::Altitude_Data_Link_Capability_Report_BDS_1_0::Builder("ASD").build(); + // std::cout << t.msg; +} +#endif + +#endif \ No newline at end of file diff --git a/test/test_bds_inference.h b/test/test_bds_inference.h new file mode 100644 index 0000000..9b423b1 --- /dev/null +++ b/test/test_bds_inference.h @@ -0,0 +1,62 @@ +#ifdef _USE_GTEST +#ifndef TEST_BDS_INFERENCE_H +#define TEST_BDS_INFERENCE_H +#include "global.h" + + + + +class BdsTest : public ::testing::Test { +protected: + void SetUp() override { + // Any necessary setup can go here + } +}; + +TEST_F(BdsTest, TestBdsInfer) { + EXPECT_EQ(infer(hex2bin("8D406B902015A678D4D220AA4BDA")), "BDS08"); + EXPECT_EQ(infer(hex2bin("8FC8200A3AB8F5F893096B000000")), "BDS06"); + EXPECT_EQ(infer(hex2bin("8D40058B58C901375147EFD09357")), "BDS05"); + EXPECT_EQ(infer(hex2bin("8D485020994409940838175B284F")), "BDS09"); + EXPECT_EQ(infer(hex2bin("A800178D10010080F50000D5893C")), "BDS10"); + EXPECT_EQ(infer(hex2bin("A0000638FA81C10000000081A92F")), "BDS17"); + EXPECT_EQ(infer(hex2bin("A0001838201584F23468207CDFA5")), "BDS20"); + EXPECT_EQ(infer(hex2bin("A0001839CA3800315800007448D9")), "BDS40"); + EXPECT_EQ(infer(hex2bin("A000139381951536E024D4CCF6B5")), "BDS50"); + EXPECT_EQ(infer(hex2bin("A00004128F39F91A7E27C46ADC21")), "BDS60"); + + // std::cout << infer("A0001838201584F23468207CDFA5").value(); +} + +TEST_F(BdsTest, TestBdsIs50Or60) { + auto result = is50or60(hex2bin("A0001838201584F23468207CDFA5"), 0, 0, 0); + EXPECT_EQ(result, ""); // Check if the optional is empty + EXPECT_EQ(is50or60(hex2bin("A8001EBCFFFB23286004A73F6A5B"), 320, 250, 14000), "BDS50"); + EXPECT_EQ(is50or60(hex2bin("A8001EBCFE1B29287FDCA807BCFC"), 320, 250, 14000), "BDS50"); +} + +TEST_F(BdsTest, TestSurfacePosition) { + std::string msg0 = hex2bin("8FE48C033A9FA184B934E744C6FD"); + std::string msg1 = hex2bin("8FE48C033A9FA68F7C3D39B1C2F0"); + + unsigned long long t0 = 1565608663102; + unsigned long long t1 = 1565608666214; + + float lat_ref = -23.4265448; + float lon_ref = -46.4816258; + CPR::Position pos_ref(lat_ref, lon_ref); + + + + // auto pos2 = CPR::surface_position(msg0, msg1, t0, t0, pos_ref).value(); + auto pos = CPR::surface_position_with_ref(msg0, lat_ref, lon_ref, t0, t0).value(); + + // 为什么差那么 多 + //std::cout << lat << " " << lon << std::endl; + EXPECT_LT(std::abs(lon_ref - pos.lon), 0.05); +} + + + +#endif +#endif diff --git a/test/test_c_common.h b/test/test_c_common.h new file mode 100644 index 0000000..36ed35f --- /dev/null +++ b/test/test_c_common.h @@ -0,0 +1,101 @@ +#ifdef _USE_GTEST +#ifndef test_c_common_H +#define test_c_common_H +#include "global.h" + + +TEST(ConversionsTest, HexToBin) { + + + EXPECT_EQ(hex2bin("6E"), "01101110"); + EXPECT_EQ(bin2hex("01101110"), "6E"); + EXPECT_EQ(bin2hex("1101110"), "6E"); +} + +TEST(CrcDecodeTest, BasicTests) { + EXPECT_EQ(crc(hex2bin("8D406B902015A678D4D220AA4BDA")), 0); + EXPECT_EQ(crc(hex2bin("8D406B902015A678D4D220AA4BDA")), 0); + EXPECT_EQ(crc(hex2bin("8d8960ed58bf053cf11bc5932b7d")), 0); + EXPECT_EQ(crc(hex2bin("8d45cab390c39509496ca9a32912")), 0); + EXPECT_EQ(crc(hex2bin("8d49d3d4e1089d00000000744c3b")), 0); + EXPECT_EQ(crc(hex2bin("8d74802958c904e6ef4ba0184d5c")), 0); + EXPECT_EQ(crc(hex2bin("8d4400cd9b0000b4f87000e71a10")), 0); + EXPECT_EQ(crc(hex2bin("8d4065de58a1054a7ef0218e226a")), 0); + EXPECT_EQ(crc(hex2bin("c80b2dca34aa21dd821a04cb64d4")), 10719924); + EXPECT_EQ(crc(hex2bin("a800089d8094e33a6004e4b8a522")), 4805588); + EXPECT_EQ(crc(hex2bin("a8000614a50b6d32bed000bbe0ed")), 5659991); + EXPECT_EQ(crc(hex2bin("a0000410bc900010a40000f5f477")), 11727682); + EXPECT_EQ(crc(hex2bin("8d4ca251204994b1c36e60a5343d")), 16); + EXPECT_EQ(crc(hex2bin("b0001718c65632b0a82040715b65")), 353333); +} + +TEST(CrcEncodeTest, EncodeTest) { + EXPECT_EQ(crc(hex2bin("8D406B902015A678D4D220AA4BDA"), true), 11160538); +} + +TEST(IcaoTest, IcaoAddress) { + std::vector msgList = { + hex2bin("8D406B902015A678D4D220AA4BDA"), + hex2bin("A0001839CA3800315800007448D9"), + hex2bin("A000139381951536E024D4CCF6B5"), + hex2bin("A000029CFFBAA11E2004727281F1") + }; + std::vector icaoList = { + "406B90", "400940", "3C4DD2", "4243D0" + }; + for (int i = 0; i < msgList.size(); i++) { + std::string& msg = msgList[i]; + EXPECT_EQ(::decode_icao(msg), icaoList[i]); + // std::cout << df(msg) << std::endl; + set_icao(msg, "ABCDEF"); + EXPECT_EQ(decode_icao(msg), "ABCDEF"); + } +} + +// 测试 set_icao 函数 +TEST(SetIcaoTest, SetICAODirectlyForDF11To18) { + // 模拟一个 DF17 消息 + std::string msg = "10001010101110111011100000000000000000000000000000000000"; + std::string icaoHex = "ABCDEF"; + + // 验证 DF 值 + EXPECT_EQ(decode_df(msg), Downlink_Format::Extended_Squitter_17); + + // 将 ICAO 地址转换为二进制 + std::string newIcaoBin = hex2bin(icaoHex); + msg.replace(8, 24, newIcaoBin); + + EXPECT_EQ(set_icao(msg, icaoHex), msg); +} + + + + +TEST(AltcodeTest, AltitudeCode) { + EXPECT_EQ(altcode(hex2bin("A02014B400000000000000F9D514")), 32300); +} + +TEST(IdcodeTest, IdCode) { + EXPECT_EQ(idcode(hex2bin("A800292DFFBBA9383FFCEB903D01")), "1346"); +} + +TEST(GraycodeToAltitudeTest, Graycode) { + EXPECT_EQ(gray2alt("00000000010"), -1000); + EXPECT_EQ(gray2alt("00000001010"), -500); + EXPECT_EQ(gray2alt("00000011011"), -100); + EXPECT_EQ(gray2alt("00000011010"), 0); + EXPECT_EQ(gray2alt("00000011110"), 100); + EXPECT_EQ(gray2alt("00000010011"), 600); + EXPECT_EQ(gray2alt("00000110010"), 1000); + EXPECT_EQ(gray2alt("00001001001"), 5800); + EXPECT_EQ(gray2alt("00011100100"), 10300); + EXPECT_EQ(gray2alt("01100011010"), 32000); + EXPECT_EQ(gray2alt("01110000100"), 46300); + EXPECT_EQ(gray2alt("01010101100"), 50200); + EXPECT_EQ(gray2alt("11011110100"), 73200); + EXPECT_EQ(gray2alt("10000000011"), 126600); + EXPECT_EQ(gray2alt("10000000001"), 126700); +} + +#endif +#endif diff --git a/test/test_mode_a_c.h b/test/test_mode_a_c.h new file mode 100644 index 0000000..a8a9f18 --- /dev/null +++ b/test/test_mode_a_c.h @@ -0,0 +1,45 @@ +#ifdef _USE_GTEST + +#ifndef test_mode_a_c_H +#define test_mode_a_c_H +#include "global.h" + + +class ModeACTest : public ::testing::Test { +protected: + void SetUp() override { + + } + + void TearDown() override { + // 如果有清理操作,可以放在这里 + } +}; + +// 测试encode_mode_c函数 +TEST_F(ModeACTest, ModeC) { + // 测试一个有效的高度 + { + std::string result = encode_mode_c(10020); + //std::cout << decode_mode_c(result) << std::endl; + } + { + std::string result = encode_mode_c(2020); + //std::cout << decode_mode_c(result) << std::endl; + } + { + std::string result = encode_mode_c(10220); + //std::cout << decode_mode_c(result) << std::endl; + } +} + +TEST_F(ModeACTest, ModeA) { + // 测试一个有效的高度 + { + std::string result = encode_mode_a("1122"); + //std::cout << decode_mode_a(result) << std::endl; + } +} +#endif + +#endif diff --git a/test/test_surv.h b/test/test_surv.h new file mode 100644 index 0000000..8af62ec --- /dev/null +++ b/test/test_surv.h @@ -0,0 +1,90 @@ +#ifdef _USE_GTEST +#ifndef TEST_SURV_H +#define TEST_SURV_H +#include "global.h" + +// TEST(SurvTest, FlightStatus) { +// EXPECT_EQ(surv::fs(hex2bin("2A00516D492B80"))[0], 2); +// } +// +// TEST(SurvTest, DownlinkRequest) { +// EXPECT_EQ(surv::dr(hex2bin("2A00516D492B80"))[0], 0); +// } +// +// TEST(SurvTest, UtilityMessage) { +// auto um_result = surv::um(hex2bin("200CBE4ED80137")); +// EXPECT_EQ(um_result[0], 9); +// EXPECT_EQ(um_result[1], 1); +// } +// +// TEST(SurvTest, Identity) { +// EXPECT_EQ(surv::identity(hex2bin("2A00516D492B80")), "0356"); +// } +// +// TEST(SurvTest, Altitude) { +// EXPECT_EQ(surv::altitude(hex2bin("20001718029FCD")), 36000); +// +// } + +TEST(SurvTest, Altitude) { + // std::string squawk = "0356"; // 示例应答代码 + // try { + // std::string encoded = encode_squawk(squawk); + // std::cout << ::squawk(encoded).value() << std::endl; + // std::cout << "Encoded 13-bit squawk: " << encoded << std::endl; // 输出编码后的13位二进制字符串 + // } catch (const std::invalid_argument& e) { + // std::cerr << "Error: " << e.what() << std::endl; // 输出错误信息 + // } + + // auto it = Surveillance_Reply::Surveillance_Altitude_Reply::Builder("4CA7E8") + // .set_altitude(36000); + // + // auto alt = Surveillance_Reply::Surveillance_Altitude_Reply::Builder("4CA7E8") + // .set_altitude(36000) + + // .set_flight_status(Flight_Status::no_alert_airborne) + // .set_utility_message(Utility_Message::no_information) + // .set_downlink_request(Downlink_Request::no_request) + // .build(); + + // std::cout << "1111111111111111111" << std::endl; + // + // std::cout << "t==" << bin2hex(alt.msg) << std::endl; + // std::cout << icao(alt.msg) << std::endl; + + + // + // std::cout << tell(alt.msg) << std::endl; + // + // auto identity = Surveillance_Reply::Surveillance_Identity_Reply::Builder("4CA7E8") + // .set_flight_status(Flight_Status::no_alert_airborne) + // .set_utility_message(Utility_Message::no_information) + // .set_downlink_request(Downlink_Request::no_request) + // .set_Identity("0356") + // .build(); + // + // std::cout << tell(identity.msg) << std::endl; + + + +} + + +// AP +TEST(SurvTest, ICAO) { + // 解析icao + std::string msg = hex2bin("A0001838CA380031440000F24177"); + std::string crc = std::bitset<24>(::crc(msg, true)).to_string(); + std::string icao = Psc::xorHex(crc, hex2bin("F24177")); + EXPECT_STREQ("CE2CA7", bin2hex(crc).c_str()); + EXPECT_STREQ("3C6DD0", bin2hex(icao).c_str()); + EXPECT_STREQ("3C6DD0", ::decode_icao(msg).c_str()); + + + // 生成后面24位 + std::string last24 = xorBin(crc, icao); + EXPECT_STREQ("F24177", bin2hex(last24).c_str()); + +} +#endif +#endif \ No newline at end of file diff --git a/test/test_tell.h b/test/test_tell.h new file mode 100644 index 0000000..e3d6082 --- /dev/null +++ b/test/test_tell.h @@ -0,0 +1,66 @@ +#ifdef _USE_GTEST +#ifndef TEST_CPP_H +#define TEST_CPP_H +#include "global.h" + + +// Define the test fixture +class TellTest : public ::testing::Test { +protected: + std::vector messages = { + "8D406B902015A678D4D220AA4BDA", + "8FC8200A3AB8F5F893096B000000", + "8D40058B58C901375147EFD09357", + "8D485020994409940838175B284F", + "A000083E202CC371C31DE0AA1CCF", + "A8001E2520053332C1A820363386", + "A000029C85E42F313000007047D3", + "A5DC282C2A0108372CA6DA9693B0", + "A00015B8C26A00328400004242DA", + "A000139381951536E024D4CCF6B5", + "A00004128F39F91A7E27C46ADC21" + }; +}; + +// 7BD014; msg:A000149CBEB00030A200009FF2A6 +// 7BD014; msg:A000149CBEB00030A200009FF2A6 +// 7BD014; msg:A8000134E80A2F30601400CD499D +// 7BD014; msg:A000149CBEB00030A200009FF2A6 +TEST_F(TellTest, ProcessMessages) { + //auto it = hex2bin("A000149CBEB00030A200009FF2A6"); + auto it = hex2bin("A0001838CA380031440000F24177"); + //std::cout << tell(it) << std::endl; + // for (auto it2 : infer2(it)) + // { + // std::cout << it2 << std::endl; + // } + //std::cout << "dp check " << with_DP::check_crc(it, ::decode_icao(it), "40") << std::endl; + //std::cout << "Ap check " << with_AP::check_crc(it, ::decode_icao(it), "40") << std::endl; + + //std::cout << tell(hex2bin("A00011308014C73CFFFCC6B41B14")) << std::endl; + //std::cout << tell(hex2bin("A0001130A9AA1929A027FF4B6582")) << std::endl; + //std::cout << tell(hex2bin("02E19130115129")) << std::endl; + // std::cout << tell(hex2bin("5D781BD49E7ABE")) << std::endl; + // std::cout << tell(hex2bin("A00011308014C73CE00CC67DD4F4")) << std::endl; + // std::cout << tell(hex2bin("A80017BCB3FD9FF0A80000B30D25")) << std::endl; + // std::cout << tell(hex2bin("A0001130B3FD9FF0A800003C4AF0")) << std::endl; + // std::cout << tell(hex2bin("5D781BD49E7ABE")) << std::endl; + // std::cout << tell(hex2bin("8D781BD4EA3408654D3C08355231")) << std::endl; + //std::cout << tell(hex2bin("8D781BD4588B04830107A904023E")) << std::endl; + // std::cout << tell(hex2bin("8D781BD49909D0927804ABF00996")) << std::endl; + //std::cout << tell(hex2bin("8D781BD4F82100020049B8C1FAD8")) << std::endl; + //std::cout << tell(hex2bin("8D781BD4588B00ECFDB4ED938DF7")) << std::endl; + + //auto pos = airborne_position(hex2bin("8D781BD4588B04830107A904023E"), hex2bin("8D781BD4588B00ECFDB4ED938DF7"), 0, 1).value(); + //std::cout << pos.latitude << " " << pos.longitude << std::endl; + //std::cout << tell(hex2bin("8D781BD49909D0927804ABF00996")) << std::endl; + //std::cout << tell(hex2bin("A0001130B3FD9FF0A800003C4AF0")) << std::endl; + //std::cout << tell(hex2bin("280017BC94F468")) << std::endl; + //std::cout << tell(hex2bin("A0001130B3FD9FF0A800003C4AF0")) << std::endl; + +} + + +#endif + +#endif \ No newline at end of file