diff --git a/firmware/application/protocols/rds.cpp b/firmware/application/protocols/rds.cpp index bad588e8f..342f82e5b 100644 --- a/firmware/application/protocols/rds.cpp +++ b/firmware/application/protocols/rds.cpp @@ -25,6 +25,7 @@ #include "portapack_shared_memory.hpp" // RDS infos: +// One frame = X groups (as necessary) // One group = 4 blocks = 4 * 26 bits // One block = 26 bits (16 bits data + 10 bits checkword) // Sent MSB first @@ -44,11 +45,6 @@ uint32_t make_block(uint32_t data, uint16_t offset) { return (data << 10) | (CRC ^ offset); } -// Todo: -// Make PI -// TA/TP flags -// Group selection - // Boolean to binary uint8_t b2b(const bool in) { if (in) @@ -58,27 +54,36 @@ uint8_t b2b(const bool in) { } // Type 0B groups are like 0A groups but without alternative frequency data -void make_0B_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA, +RDSGroup make_0B_group(const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA, const bool MS, const bool DI, const uint8_t C, const std::string chars) { - group->block[0] = PI_code; - group->block[1] = (0x0 << 12) | (1 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | (b2b(TA) << 4) | (b2b(MS) << 3) | (b2b(DI) << 2) | (C & 3); - group->block[2] = PI_code; - group->block[3] = (chars[0] << 8) | chars[1]; + RDSGroup group; + + group.block[0] = PI_code; + group.block[1] = (0x0 << 12) | (1 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | (b2b(TA) << 4) | (b2b(MS) << 3) | (b2b(DI) << 2) | (C & 3); + group.block[2] = PI_code; + group.block[3] = (chars[0] << 8) | chars[1]; + + return group; } // For RadioText, up to 64 chars with 2A, 32 chars with 2B -void make_2A_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB, +RDSGroup make_2A_group(const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB, const uint8_t segment, const std::string chars) { - group->block[0] = PI_code; - group->block[1] = (0x2 << 12) | (0 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | (b2b(AB) << 4) | (segment & 15); - group->block[2] = (chars[0] << 8) | chars[1]; - group->block[3] = (chars[2] << 8) | chars[3]; + RDSGroup group; + + group.block[0] = PI_code; + group.block[1] = (0x2 << 12) | (0 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | (b2b(AB) << 4) | (segment & 15); + group.block[2] = (chars[0] << 8) | chars[1]; + group.block[3] = (chars[2] << 8) | chars[3]; + + return group; } // Time and date - usually one message per minute - Month: 1~12 - Day: 1~31 - Hour/Minute: 0~59 - Local offset: -12/+12 from UTC -void make_4A_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY, +RDSGroup make_4A_group(const uint16_t PI_code, const bool TP, const uint8_t PTY, const uint16_t year, const uint8_t month, const uint8_t day, const uint8_t hour, const uint8_t minute, const int8_t local_offset) { + RDSGroup group; uint32_t L = 0; uint32_t day_code; @@ -86,80 +91,64 @@ void make_4A_group(RDS_group * group, const uint16_t PI_code, const bool TP, con day_code = 14956 + day + (uint32_t)((float)(year - 1900 - L) * 365.25) + uint16_t((float)((month + 1) + L * 12) * 30.6001); - group->block[0] = PI_code; - group->block[1] = (0x4 << 12) | (0 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | ((day_code & 0x18000) >> 15); - group->block[2] = ((day_code & 0x7FFF) << 1) | (hour >> 4); - group->block[3] = ((hour & 15) << 12) | ((minute & 0x3F) << 6) | (local_offset & 0x3F); + group.block[0] = PI_code; + group.block[1] = (0x4 << 12) | (0 << 11) | (b2b(TP) << 10) | ((PTY & 0x1F) << 5) | ((day_code & 0x18000) >> 15); + group.block[2] = ((day_code & 0x7FFF) << 1) | (hour >> 4); + group.block[3] = ((hour & 15) << 12) | ((minute & 0x3F) << 6) | (local_offset & 0x3F); + + return group; } -uint16_t gen_PSN(const std::string & psname, const RDS_flags * rds_flags) { +void gen_PSN(std::vector& frame, const std::string& psname, const RDS_flags * rds_flags) { uint8_t c; - RDS_group groups[4] = { 0 }; + RDSGroup group; + + frame.clear(); // 4 groups with 2 PSN characters in each - for (c = 0; c < 4; c++) - make_0B_group(&groups[c], rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, rds_flags->TA, rds_flags->MS, rds_flags->DI, c, psname.substr(c * 2, 2)); - - // Generate checkbits for each block of each group for (c = 0; c < 4; c++) { - groups[c].block[0] = make_block(groups[c].block[0], RDS_OFFSET_A); - groups[c].block[1] = make_block(groups[c].block[1], RDS_OFFSET_B); - groups[c].block[2] = make_block(groups[c].block[2], RDS_OFFSET_Cp); // C' ! - groups[c].block[3] = make_block(groups[c].block[3], RDS_OFFSET_D); + group = make_0B_group(rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, rds_flags->TA, rds_flags->MS, rds_flags->DI, c, psname.substr(c * 2, 2)); + group.block[0] = make_block(group.block[0], RDS_OFFSET_A); + group.block[1] = make_block(group.block[1], RDS_OFFSET_B); + group.block[2] = make_block(group.block[2], RDS_OFFSET_Cp); // C' ! + group.block[3] = make_block(group.block[3], RDS_OFFSET_D); + frame.emplace_back(group); } - - uint32_t * tx_data_u32 = (uint32_t*)shared_memory.bb_data.data; - - // Copy to tx_data for baseband - for (c = 0; c < 4 * 4; c++) - tx_data_u32[c] = groups[c >> 2].block[c & 3]; - - return 4 * 4 * 26; } -uint16_t gen_RadioText(const std::string & text, const bool AB, const RDS_flags * rds_flags) { +void gen_RadioText(std::vector& frame, const std::string& text, const bool AB, const RDS_flags * rds_flags) { size_t c; - RDS_group * groups_ptr; + //RDSGroup * groups_ptr; std::string radiotext_buffer = text; - size_t rt_length, groups; + size_t rt_length, group_count; + RDSGroup group; radiotext_buffer += 0x0D; rt_length = radiotext_buffer.length(); rt_length = (rt_length + 3) & 0xFC; - groups = rt_length >> 2; // 4 characters per group + group_count = rt_length >> 2; // 4 characters per group - groups_ptr = (RDS_group*)chHeapAlloc(0, groups * sizeof(RDS_group)); + //groups_ptr = (RDSGroup*)chHeapAlloc(0, group_count * sizeof(RDSGroup)); - for (c = 0; c < groups; c++) - make_2A_group(&groups_ptr[c], rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, AB, c, radiotext_buffer.substr(c * 4, 4)); - - // Generate checkbits for each block of each group - for (c = 0; c < groups; c++) { - groups_ptr[c].block[0] = make_block(groups_ptr[c].block[0], RDS_OFFSET_A); - groups_ptr[c].block[1] = make_block(groups_ptr[c].block[1], RDS_OFFSET_B); - groups_ptr[c].block[2] = make_block(groups_ptr[c].block[2], RDS_OFFSET_C); - groups_ptr[c].block[3] = make_block(groups_ptr[c].block[3], RDS_OFFSET_D); + frame.clear(); + + for (c = 0; c < group_count; c++) { + group = make_2A_group(rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, AB, c, radiotext_buffer.substr(c * 4, 4)); + group.block[0] = make_block(group.block[0], RDS_OFFSET_A); + group.block[1] = make_block(group.block[1], RDS_OFFSET_B); + group.block[2] = make_block(group.block[2], RDS_OFFSET_C); + group.block[3] = make_block(group.block[3], RDS_OFFSET_D); + frame.emplace_back(group); } - - uint32_t * tx_data_u32 = (uint32_t*)shared_memory.bb_data.data; - - // Copy to tx_data for baseband - for (c = 0; c < groups * 4; c++) - tx_data_u32[c] = groups_ptr[c >> 2].block[c & 3]; - - chHeapFree(groups_ptr); - - return groups * 4 * 26; } -uint16_t gen_ClockTime(const RDS_flags * rds_flags, +void gen_ClockTime(std::vector& frame, const RDS_flags * rds_flags, const uint16_t year, const uint8_t month, const uint8_t day, const uint8_t hour, const uint8_t minute, const int8_t local_offset) { - uint8_t c; - RDS_group group = { 0 }; + RDSGroup group; - make_4A_group(&group, rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, year, month, day, hour, minute, local_offset); + group = make_4A_group(rds_flags->PI_code, rds_flags->TP, rds_flags->PTY, year, month, day, hour, minute, local_offset); // Generate checkbits for each block group.block[0] = make_block(group.block[0], RDS_OFFSET_A); @@ -167,13 +156,8 @@ uint16_t gen_ClockTime(const RDS_flags * rds_flags, group.block[2] = make_block(group.block[2], RDS_OFFSET_C); group.block[3] = make_block(group.block[3], RDS_OFFSET_D); - uint32_t * tx_data_u32 = (uint32_t*)shared_memory.bb_data.data; - - // Copy to tx_data for baseband - for (c = 0; c < 4; c++) - tx_data_u32[c] = group.block[c]; - - return 4 * 26; + frame.clear(); + frame.emplace_back(group); } } /* namespace rds */ diff --git a/firmware/application/protocols/rds.hpp b/firmware/application/protocols/rds.hpp index 6b7f5b2f9..f7657767d 100644 --- a/firmware/application/protocols/rds.hpp +++ b/firmware/application/protocols/rds.hpp @@ -20,8 +20,8 @@ * Boston, MA 02110-1301, USA. */ -#include #include +#include #include "ch.h" #ifndef __RDS_H__ @@ -44,24 +44,24 @@ struct RDS_flags { bool MS; }; -struct RDS_group { +struct RDSGroup { uint32_t block[4]; }; uint32_t make_block(uint32_t blockdata, uint16_t offset); uint8_t b2b(const bool in); -void make_0B_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA, +RDSGroup make_0B_group(const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool TA, const bool MS, const bool DI, const uint8_t C, const std::string chars); -void make_2A_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB, +RDSGroup make_2A_group(const uint16_t PI_code, const bool TP, const uint8_t PTY, const bool AB, const uint8_t segment, const std::string chars); -void make_4A_group(RDS_group * group, const uint16_t PI_code, const bool TP, const uint8_t PTY, +RDSGroup make_4A_group(const uint16_t PI_code, const bool TP, const uint8_t PTY, const uint16_t year, const uint8_t month, const uint8_t day, const uint8_t hour, const uint8_t minute, const int8_t local_offset); -uint16_t gen_PSN(const std::string & psname, const RDS_flags * rds_flags); -uint16_t gen_RadioText(const std::string & text, const bool AB, const RDS_flags * rds_flags); -uint16_t gen_ClockTime(const RDS_flags * rds_flags, +void gen_PSN(std::vector& frame, const std::string& psname, const RDS_flags * rds_flags); +void gen_RadioText(std::vector& frame, const std::string& text, const bool AB, const RDS_flags * rds_flags); +void gen_ClockTime(std::vector& frame, const RDS_flags * rds_flags, const uint16_t year, const uint8_t month, const uint8_t day, const uint8_t hour, const uint8_t minute, const int8_t local_offset); diff --git a/firmware/application/ui_adsb_rx.cpp b/firmware/application/ui_adsb_rx.cpp index d7f26c6bd..4d5497adf 100644 --- a/firmware/application/ui_adsb_rx.cpp +++ b/firmware/application/ui_adsb_rx.cpp @@ -45,12 +45,16 @@ void RecentEntriesTable::draw( Painter& painter, const Style& style ) { - painter.draw_string(target_rect.location(), style, to_string_hex_array((uint8_t*)entry.raw_data, 10) + " " + entry.time); + painter.draw_string( + target_rect.location(), + style, + to_string_hex(entry.ICAO_address, 6) + " " + entry.callsign + " " + (entry.hits <= 9999 ? to_string_dec_uint(entry.hits, 5) : "9999+") + " " + entry.time + //to_string_hex_array((uint8_t*)entry.raw_data, 10) + " " + entry.time + ); } void ADSBRxView::focus() { - offset_field.focus(); - offset_field.set_value(13179); + field_lna.focus(); } ADSBRxView::~ADSBRxView() { @@ -58,205 +62,50 @@ ADSBRxView::~ADSBRxView() { baseband::shutdown(); } -/*bool ADSBRxView::analyze(uint64_t offset) { - Coord lcd_x = 0, lcd_y = 0; - int8_t re, im; - adsb_frame frame; - int16_t file_data[128]; // 256 bytes / 2 IQ / 16 bits = 64 samples - complex8_t iq_data[256]; // 256 samples - uint64_t file_offset = 0; - uint8_t data_put = 0, data_get = 0; - int16_t f_re, f_im; - uint32_t c; - uint8_t level, bit, byte; - Color mark_color; - size_t preamble_count = 0, null_count = 0, bit_count = 0, sample_count = 0; - bool decoding = false; - float prev_mag = 0, mag; - float threshold, threshold_low, threshold_high; - std::string bits; - std::string hex_str; - bool confidence, first_in_window, last_in_window; - std::pair shifter[ADSB_PREAMBLE_LENGTH]; - - iq_file.seek(offset * 2048); // 256 - - for (;;) { - if (data_put == data_get) { - auto result = iq_file.read(file_data, 256); - if (!result.is_error()) { - // Convert file's C16 to C8 - for (c = 0; c < (result.value() / 4); c++) { - f_re = file_data[(c * 2) + 0] >> 5; // >> 8 (<< 3 amp.) - f_im = file_data[(c * 2) + 1] >> 5; - iq_data[data_put] = { (int8_t)f_re, (int8_t)f_im }; - data_put++; - } - - file_offset += result.value(); - - if (file_offset >= 2048) { - //text_debug_e.set("Read @ " + to_string_dec_uint(offset * 256 / 2000 / 4) + "ms "); - break; - } - } else { - text_debug_a.set("Read error"); - return false; - } - } - - re = iq_data[data_get].real(); - im = iq_data[data_get].imag(); - mag = __builtin_sqrtf((re * re) + (im * im)) * k; - data_get++; - - // Only used for preamble detection and visualisation - level = (mag < 0.3) ? 0 : // Blank weak signals - (mag > prev_mag) ? 1 : 0; - - if (decoding) { - // Decode - mark_color = Color::grey(); - - // 1 bit lasts 2 samples - if (sample_count & 1) { - if ((prev_mag < threshold_low) && (mag < threshold_low)) { - // Both under window, silence. - mark_color = Color::black(); - if (null_count > 3) { - text_debug_b.set("Bits:" + bits.substr(0, 25)); - text_debug_c.set("Hex:" + hex_str.substr(0, 26)); - text_debug_d.set("DF=" + to_string_dec_uint(frame.get_DF()) + " ICAO=" + to_string_hex(frame.get_ICAO_address(), 6)); - if ((frame.get_DF() == 17) && (frame.get_msg_type() >= 1) && (frame.get_msg_type() <= 4)) { - text_debug_a.set("Callsign:" + frame.get_callsign()); - return true; - } else { - text_debug_a.set("No ID data"); - return false; - } - - decoding = false; - } else - null_count++; - - confidence = false; - if (prev_mag > mag) - bit = 1; - else - bit = 0; - - mark_color = bit ? Color::dark_red() : Color::dark_green(); - - } else { - - null_count = 0; - - first_in_window = ((prev_mag >= threshold_low) && (prev_mag <= threshold_high)); - last_in_window = ((mag >= threshold_low) && (mag <= threshold_high)); - - if ((first_in_window && !last_in_window) || (!first_in_window && last_in_window)) { - confidence = true; - if (prev_mag > mag) - bit = 1; - else - bit = 0; - } else { - confidence = false; - if (prev_mag > mag) - bit = 1; - else - bit = 0; - } - - mark_color = bit ? Color::red() : Color::green(); - } - - bits.append(bit ? "1" : "0"); // DEBUG - byte = bit | (byte << 1); - bit_count++; - if (!(bit_count & 7)) { - // Got one byte - hex_str += to_string_hex(byte, 2); // DEBUG - frame.push_byte(byte); - } - } - sample_count++; - } else { - // Look for preamble - mark_color = Color::white(); - - // Shift - for (c = 0; c < (ADSB_PREAMBLE_LENGTH - 1); c++) - shifter[c] = shifter[c + 1]; - shifter[15] = std::make_pair(mag, level); - - // Compare - for (c = 0; c < ADSB_PREAMBLE_LENGTH; c++) { - if (shifter[c].second != adsb_preamble[c]) - break; - } - - if (c == ADSB_PREAMBLE_LENGTH) { - preamble_count++; - if (preamble_count == 1) { - // Try decoding the first frame found - decoding = true; - sample_count = 0; - - // Compute preamble pulses power to set thresholds - threshold = (shifter[0].first + shifter[2].first + shifter[7].first + shifter[9].first) / 4; - threshold_high = threshold * 1.414; // +3dB - threshold_low = threshold * 0.707; // -3dB - } - } - } - - prev_mag = mag; - - if (preamble_count) { - if (lcd_y < 188) { - mag *= 16; - // Background - display.fill_rectangle({lcd_x, 100 + lcd_y, 2, 32 - (int)mag}, decoding ? mark_color : Color::grey()); - // Bar - display.fill_rectangle({lcd_x, 132 + lcd_y - (int)mag, 2, (int)mag}, Color::white()); - // Level - display.fill_rectangle({lcd_x, 132 + lcd_y, 2, 4}, decoding ? ((sample_count & 1) ? Color::white() : Color::light_grey()) : (level ? Color::white() : Color::dark_blue())); - if (lcd_x == 238) { - lcd_x = 0; - lcd_y += 40; - } else { - lcd_x += 2; - } - } - } - } - - return false; -}*/ - void ADSBRxView::on_frame(const ADSBFrameMessage * message) { rtc::RTC datetime; std::string str_timestamp; - auto frame = message->frame; - auto& entry = ::on_packet(recent, frame.get_ICAO_address()); + std::string callsign; - rtcGetTime(&RTCD1, &datetime); - str_timestamp = to_string_dec_uint(datetime.hour(), 2, '0') + ":" + - to_string_dec_uint(datetime.minute(), 2, '0') + ":" + - to_string_dec_uint(datetime.second(), 2, '0'); - entry.set_time(str_timestamp); - entry.set_raw(frame.get_raw_data()); - recent_entries_view.set_dirty(); + auto frame = message->frame; + + uint32_t ICAO_address = frame.get_ICAO_address(); + + + if (frame.check_CRC() && frame.get_ICAO_address()) { + auto& entry = ::on_packet(recent, ICAO_address); + + rtcGetTime(&RTCD1, &datetime); + str_timestamp = to_string_dec_uint(datetime.hour(), 2, '0') + ":" + + to_string_dec_uint(datetime.minute(), 2, '0') + ":" + + to_string_dec_uint(datetime.second(), 2, '0'); + + entry.set_time(str_timestamp); + entry.set_raw(frame.get_raw_data()); + entry.inc_hit(); + + if (frame.get_DF() == DF_ADSB) { + if (frame.get_msg_type() == TC_IDENT) { + callsign = decode_frame_id(frame); + entry.set_callsign(callsign); + } else if (frame.get_msg_type() == TC_AIRBORNE_POS) { + callsign = "Altitude: " + to_string_dec_uint(decode_frame_pos(frame)) + "ft"; + entry.set_pos(callsign); + } + } + + recent_entries_view.set_dirty(); + } } ADSBRxView::ADSBRxView(NavigationView& nav) { baseband::run_image(portapack::spi_flash::image_tag_adsb_rx); add_children({ - //&labels, - &offset_field, - //&button_ffw, + &labels, + &rssi, + &field_lna, + &field_vga, &text_debug_a, &text_debug_b, &text_debug_c, @@ -264,29 +113,16 @@ ADSBRxView::ADSBRxView(NavigationView& nav) { }); recent_entries_view.set_parent_rect({ 0, 64, 240, 224 }); - - // File must be 16bit complex @ 2Msps ! - /*auto result = iq_file.open("ADSB.C16"); - if (result.is_valid()) { - text_debug_a.set("Can't open file"); - }*/ - - /*offset_field.on_change = [this, &nav](int32_t value) { - // TODO - };*/ - - /*button_ffw.on_select = [this, &nav](Button&) { - //nav.push(GeoMapView::Mode::SHOW); - while (!analyze(f_offset)) { - f_offset++; - } - offset_field.set_value(f_offset); - f_offset++; - };*/ + recent_entries_view.on_select = [this](const ADSBRecentEntry& entry) { + text_debug_a.set(entry.geo_pos); + }; baseband::set_adsb(); receiver_model.set_tuning_frequency(1090000000); + receiver_model.set_rf_amp(true); + field_lna.set_value(40); + field_vga.set_value(40); receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis); receiver_model.set_sampling_rate(2000000); receiver_model.set_baseband_bandwidth(2500000); diff --git a/firmware/application/ui_adsb_rx.hpp b/firmware/application/ui_adsb_rx.hpp index 5aabfb108..516a9999f 100644 --- a/firmware/application/ui_adsb_rx.hpp +++ b/firmware/application/ui_adsb_rx.hpp @@ -22,14 +22,11 @@ #include "ui.hpp" #include "file.hpp" -//#include "ui_textentry.hpp" -//#include "ui_widget.hpp" -#include "ui_navigation.hpp" +#include "ui_receiver.hpp" #include "ui_font_fixed_8x16.hpp" #include "recent_entries.hpp" #include "message.hpp" -#include "portapack.hpp" namespace ui { @@ -38,9 +35,12 @@ struct ADSBRecentEntry { static constexpr Key invalid_key = 0xffffffff; - uint32_t ICAO_address; + uint32_t ICAO_address { }; + uint16_t hits { 0 }; uint8_t raw_data[14] { }; // 112 bits at most + std::string callsign { " " }; std::string time { "" }; + std::string geo_pos { "" }; ADSBRecentEntry( ) : ADSBRecentEntry { 0 } @@ -57,6 +57,18 @@ struct ADSBRecentEntry { return ICAO_address; } + void set_callsign(std::string& new_callsign) { + callsign = new_callsign; + } + + void inc_hit() { + hits++; + } + + void set_pos(std::string& new_pos) { + geo_pos = new_pos; + } + void set_time(std::string& new_time) { time = new_time; } @@ -75,34 +87,34 @@ public: void focus() override; - std::string title() const override { return "ADS-B debug"; }; + std::string title() const override { return "ADS-B receive"; }; private: - //static constexpr float k = 1.0f / 128.0f; - - //File iq_file { }; - //size_t f_offset { 0 }; - - //bool analyze(uint64_t offset); void on_frame(const ADSBFrameMessage * message); const RecentEntriesColumns columns { { - { "Raw", 21 }, - { "Time", 8 }, + { "ICAO", 6 }, + { "Callsign", 8 }, + { "Hits", 5 }, + { "Time", 8 } } }; ADSBRecentEntries recent { }; RecentEntriesView> recent_entries_view { columns, recent }; - /*Labels labels { - { { 0 * 8, 0 * 8 }, "Test", Color::light_grey() } - };*/ + RSSI rssi { + { 19 * 8, 4, 10 * 8, 8 }, + }; - NumberField offset_field { - { 0, 0 }, - 6, - { 0, 819200 }, // * 256 -> file offset - 1, - '0' + LNAGainField field_lna { + { 4 * 8, 0 * 16 } + }; + + VGAGainField field_vga { + { 11 * 8, 0 * 16 } + }; + + Labels labels { + { { 0 * 8, 0 * 8 }, "LNA: VGA: RSSI:", Color::light_grey() } }; Text text_debug_a { @@ -118,11 +130,6 @@ private: "-" }; - /*Button button_ffw { - { 184, 0 * 16, 56, 16 }, - "FFW" - };*/ - MessageHandlerRegistration message_handler_frame { Message::ID::ADSBFrame, [this](Message* const p) { diff --git a/firmware/application/ui_adsb_tx.cpp b/firmware/application/ui_adsb_tx.cpp index 53f51b1e6..ada4d99c1 100644 --- a/firmware/application/ui_adsb_tx.cpp +++ b/firmware/application/ui_adsb_tx.cpp @@ -73,28 +73,10 @@ void Compass::paint(Painter&) { set_value(value_); } -ADSBView::ADSBView() { - add_child(&check_enable); - hidden(true); - - check_enable.on_select = [this](Checkbox&, bool value) { - enabled = value; - }; -} - -void ADSBView::set_enabled(bool value) { - check_enable.set_value(value); -} - -void ADSBView::set_type(std::string type) { - check_enable.set_text("Transmit " + type); -} - -void ADSBView::focus() { - check_enable.focus(); -} - -ADSBPositionView::ADSBPositionView(NavigationView& nav) { +ADSBPositionView::ADSBPositionView( + NavigationView& nav, Rect parent_rect +) : OptionTabView(parent_rect) +{ set_type("position"); add_children({ @@ -133,7 +115,10 @@ void ADSBPositionView::collect_frames(const uint32_t ICAO_address, std::vector(receiver_model.tuning_frequency()); new_view->on_changed = [this](rf::Frequency f) { diff --git a/firmware/application/ui_adsb_tx.hpp b/firmware/application/ui_adsb_tx.hpp index 98011d7fc..927d543f1 100644 --- a/firmware/application/ui_adsb_tx.hpp +++ b/firmware/application/ui_adsb_tx.hpp @@ -22,14 +22,10 @@ #include "ui.hpp" #include "adsb.hpp" -#include "utility.hpp" #include "ui_textentry.hpp" -#include "ui_widget.hpp" #include "ui_geomap.hpp" #include "ui_tabview.hpp" -#include "ui_navigation.hpp" #include "ui_transmitter.hpp" - #include "message.hpp" #include "transmitter_model.hpp" #include "portapack.hpp" @@ -51,32 +47,9 @@ private: uint32_t value_ { 0 }; }; -class ADSBView : public View { +class ADSBPositionView : public OptionTabView { public: - ADSBView(); - - void focus() override; - - void set_type(std::string type); - void collect_frames(const uint32_t ICAO_address, std::vector& frame_list); - -protected: - bool enabled { false }; - - void set_enabled(bool value); - -private: - Checkbox check_enable { - { 2 * 8, 0 * 16 }, - 20, - "", - false - }; -}; - -class ADSBPositionView : public ADSBView { -public: - ADSBPositionView(NavigationView& nav); + ADSBPositionView(NavigationView& nav, Rect parent_rect); void collect_frames(const uint32_t ICAO_address, std::vector& frame_list); @@ -91,9 +64,9 @@ private: }; }; -class ADSBCallsignView : public ADSBView { +class ADSBCallsignView : public OptionTabView { public: - ADSBCallsignView(NavigationView& nav); + ADSBCallsignView(NavigationView& nav, Rect parent_rect); void collect_frames(const uint32_t ICAO_address, std::vector& frame_list); @@ -110,9 +83,9 @@ private: }; }; -class ADSBSpeedView : public ADSBView { +class ADSBSpeedView : public OptionTabView { public: - ADSBSpeedView(); + ADSBSpeedView(Rect parent_rect); void collect_frames(const uint32_t ICAO_address, std::vector& frame_list); @@ -134,9 +107,9 @@ private: }; }; -class ADSBSquawkView : public ADSBView { +class ADSBSquawkView : public OptionTabView { public: - ADSBSquawkView(); + ADSBSquawkView(Rect parent_rect); void collect_frames(const uint32_t ICAO_address, std::vector& frame_list); @@ -223,10 +196,12 @@ private: void start_tx(); void generate_frames(); - ADSBPositionView view_position { nav_ }; - ADSBCallsignView view_callsign { nav_ }; - ADSBSpeedView view_speed { }; - ADSBSquawkView view_squawk { }; + Rect view_rect = { 0, 7 * 8, 240, 192 }; + + ADSBPositionView view_position { nav_, view_rect }; + ADSBCallsignView view_callsign { nav_, view_rect }; + ADSBSpeedView view_speed { view_rect }; + ADSBSquawkView view_squawk { view_rect }; TabView tab_view { { "Position", Color::cyan(), &view_position }, diff --git a/firmware/application/ui_encoders.cpp b/firmware/application/ui_encoders.cpp index d0b30892c..232bcad7d 100644 --- a/firmware/application/ui_encoders.cpp +++ b/firmware/application/ui_encoders.cpp @@ -190,8 +190,6 @@ void EncodersView::start_tx(const bool scan) { transmitter_model.set_sampling_rate(2280000U); transmitter_model.set_rf_amp(true); - transmitter_model.set_lna(40); - transmitter_model.set_vga(40); transmitter_model.set_baseband_bandwidth(1750000); transmitter_model.enable(); diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 18d46e322..2b4666d2b 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -282,7 +282,7 @@ void NavigationView::focus() { ReceiversMenuView::ReceiversMenuView(NavigationView& nav) { add_items({ - { "ADS-B: Planes", ui::Color::yellow(),&bitmap_icon_adsb, [&nav](){ nav.push(); }, }, + { "ADS-B: Planes", ui::Color::green(),&bitmap_icon_adsb, [&nav](){ nav.push(); }, }, { "AIS: Boats", ui::Color::green(), &bitmap_icon_ais, [&nav](){ nav.push(); } }, { "APRS", ui::Color::grey(), &bitmap_icon_aprs, [&nav](){ nav.push(); } }, { "Audio", ui::Color::green(), &bitmap_icon_speaker, [&nav](){ nav.push(false); } }, @@ -301,7 +301,7 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) { TransmittersMenuView::TransmittersMenuView(NavigationView& nav) { add_items({ - { "ADS-B Mode S", ui::Color::orange(), &bitmap_icon_adsb, [&nav](){ nav.push(); } }, + { "ADS-B Mode S", ui::Color::yellow(), &bitmap_icon_adsb, [&nav](){ nav.push(); } }, { "APRS", ui::Color::grey(), &bitmap_icon_aprs, [&nav](){ nav.push(); } }, { "BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav](){ nav.push(); } }, { "Jammer", ui::Color::yellow(), &bitmap_icon_jammer, [&nav](){ nav.push(); } }, diff --git a/firmware/application/ui_rds.cpp b/firmware/application/ui_rds.cpp index 55244d196..fc4a65c3d 100644 --- a/firmware/application/ui_rds.cpp +++ b/firmware/application/ui_rds.cpp @@ -33,12 +33,145 @@ using namespace rds; namespace ui { -void RDSView::focus() { - button_editpsn.focus(); +RDSPSNView::RDSPSNView( + NavigationView& nav, + Rect parent_rect +) : OptionTabView(parent_rect) +{ + set_type("PSN"); + + add_children({ + &labels, + &text_psn, + &button_set, + &check_mono_stereo, + &check_TA, + &check_MS + }); + + set_enabled(true); + + check_TA.set_value(true); + + check_mono_stereo.on_select = [this](Checkbox&, bool value) { + mono_stereo = value; + }; + check_TA.on_select = [this](Checkbox&, bool value) { + TA = value; + }; + check_MS.on_select = [this](Checkbox&, bool value) { + MS = value; + }; + + button_set.on_select = [this, &nav](Button&) { + text_prompt( + nav, + &PSN, + 8, + [this](std::string* s) { + text_psn.set(*s); + } + ); + }; } -void RDSView::on_tuning_frequency_changed(rf::Frequency f) { - transmitter_model.set_tuning_frequency(f); +RDSRadioTextView::RDSRadioTextView( + NavigationView& nav, + Rect parent_rect +) : OptionTabView(parent_rect) +{ + set_type("Radiotext"); + + add_children({ + &labels, + &button_set, + &text_radiotext + }); + + button_set.on_select = [this, &nav](Button&){ + text_prompt( + nav, + &radiotext, + 28, + [this](std::string* s) { + text_radiotext.set(*s); + } + ); + }; +} + +RDSDateTimeView::RDSDateTimeView( + Rect parent_rect +) : OptionTabView(parent_rect) +{ + set_type("date & time"); + + add_children({ + &labels + }); +} + +RDSAudioView::RDSAudioView( + Rect parent_rect +) : OptionTabView(parent_rect) +{ + set_type("audio"); + + add_children({ + &labels + }); +} + +RDSThread::RDSThread( + std::vector** frames +) : frames_ { std::move(frames) } +{ + thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, RDSThread::static_fn, this); +} + +RDSThread::~RDSThread() { + if( thread ) { + chThdTerminate(thread); + chThdWait(thread); + thread = nullptr; + } +} + +msg_t RDSThread::static_fn(void* arg) { + auto obj = static_cast(arg); + obj->run(); + return 0; +} + +void RDSThread::run() { + std::vector* frame_ptr; + size_t block_count, c; + uint32_t * tx_data_u32 = (uint32_t*)shared_memory.bb_data.data; + uint32_t frame_index = 0; + + while( !chThdShouldTerminate() ) { + + do { + frame_ptr = frames_[frame_index]; + + if (frame_index == 2) { + frame_index = 0; + } else { + frame_index++; + } + } while(!(block_count = frame_ptr->size() * 4)); + + for (c = 0; c < block_count; c++) + tx_data_u32[c] = frame_ptr->at(c >> 2).block[c & 3]; + + baseband::set_rds_data(block_count * 26); + + chThdSleepMilliseconds(1000); + } +} + +void RDSView::focus() { + tab_view.focus(); } RDSView::~RDSView() { @@ -47,80 +180,58 @@ RDSView::~RDSView() { } void RDSView::start_tx() { + rds_flags.PI_code = sym_pi_code.value_hex_u64(); + rds_flags.PTY = options_pty.selected_index_value(); + rds_flags.DI = view_PSN.mono_stereo ? 1 : 0; + rds_flags.TP = check_TP.value(); + rds_flags.TA = view_PSN.TA; + rds_flags.MS = view_PSN.MS; + + if (view_PSN.is_enabled()) + gen_PSN(frame_psn, view_PSN.PSN, &rds_flags); + else + frame_psn.clear(); + + if (view_radiotext.is_enabled()) + gen_RadioText(frame_radiotext, view_radiotext.radiotext, 0, &rds_flags); + else + frame_radiotext.clear(); + + // DEBUG + if (view_datetime.is_enabled()) + gen_ClockTime(frame_datetime, &rds_flags, 2016, 12, 1, 9, 23, 2); + else + frame_datetime.clear(); + transmitter_model.set_sampling_rate(2280000U); transmitter_model.set_rf_amp(true); transmitter_model.set_baseband_bandwidth(1750000); transmitter_model.enable(); - baseband::set_rds_data(message_length); + tx_thread = std::make_unique(frames); } -void RDSView::paint(Painter&) { - text_psn.set(PSN); - text_radiotexta.set(RadioText.substr(0, 16)); - text_radiotextb.set(RadioText.substr(16, 16)); -} - -RDSView::RDSView(NavigationView& nav) { +RDSView::RDSView( + NavigationView& nav +) : nav_ { nav } +{ baseband::run_image(portapack::spi_flash::image_tag_rds); - PSN = "TEST1234"; - RadioText = "Radiotext test ABCD1234"; - add_children({ + &tab_view, &labels, - &options_pty, - &options_countrycode, - &options_coverage, - &options_tx, - &check_mono_stereo, - &check_TA, - &check_TP, - &check_MS, &sym_pi_code, - &button_editpsn, - &text_psn, - &button_editradiotext, - &text_radiotexta, - &text_radiotextb, + &check_TP, + &options_pty, + &view_PSN, + &view_radiotext, + &view_datetime, + &view_audio, + //&options_countrycode, + //&options_coverage, &tx_view, }); - tx_view.on_edit_frequency = [this, &nav]() { - auto new_view = nav.push(receiver_model.tuning_frequency()); - new_view->on_changed = [this](rf::Frequency f) { - receiver_model.set_tuning_frequency(f); - this->on_tuning_frequency_changed(f); - }; - }; - - tx_view.on_start = [this]() { - tx_view.set_transmitting(true); - rds_flags.PI_code = sym_pi_code.value_hex_u64(); - rds_flags.PTY = options_pty.selected_index_value(); - rds_flags.DI = check_mono_stereo.value() ? 1 : 0; - rds_flags.TP = check_TP.value(); - rds_flags.TA = check_TA.value(); - rds_flags.MS = check_MS.value(); - - if (options_tx.selected_index() == 0) - message_length = gen_PSN(PSN, &rds_flags); - else if (options_tx.selected_index() == 1) - message_length = gen_RadioText(RadioText, 0, &rds_flags); - else - message_length = gen_ClockTime(&rds_flags, 2016, 12, 1, 9, 23, 2); - - txing = true; - start_tx(); - }; - - tx_view.on_stop = [this]() { - tx_view.set_transmitting(false); - transmitter_model.disable(); - txing = false; - }; - - check_TA.set_value(true); check_TP.set_value(true); sym_pi_code.set_sym(0, 0xF); @@ -132,15 +243,26 @@ RDSView::RDSView(NavigationView& nav) { }; options_pty.set_selected_index(0); // None - options_countrycode.set_selected_index(18); // Baguette du fromage - options_coverage.set_selected_index(0); // Local + //options_countrycode.set_selected_index(18); // Baguette du fromage + //options_coverage.set_selected_index(0); // Local - button_editpsn.on_select = [this, &nav](Button&) { - text_prompt(nav, &PSN, 8); + tx_view.on_edit_frequency = [this, &nav]() { + auto new_view = nav.push(receiver_model.tuning_frequency()); + new_view->on_changed = [this](rf::Frequency f) { + receiver_model.set_tuning_frequency(f); + }; }; - button_editradiotext.on_select = [this, &nav](Button&){ - text_prompt(nav, &RadioText, 24); + tx_view.on_start = [this]() { + start_tx(); + tx_view.set_transmitting(true); + txing = true; + }; + + tx_view.on_stop = [this]() { + tx_view.set_transmitting(false); + transmitter_model.disable(); + txing = false; }; } diff --git a/firmware/application/ui_rds.hpp b/firmware/application/ui_rds.hpp index 85467bb8a..6e6e07978 100644 --- a/firmware/application/ui_rds.hpp +++ b/firmware/application/ui_rds.hpp @@ -21,52 +21,169 @@ */ #include "ui.hpp" -#include "ui_menu.hpp" -#include "ui_navigation.hpp" -#include "ui_font_fixed_8x16.hpp" -#include "ui_receiver.hpp" #include "ui_transmitter.hpp" #include "ui_textentry.hpp" -#include "message.hpp" +#include "ui_tabview.hpp" + #include "rds.hpp" using namespace rds; namespace ui { +class RDSPSNView : public OptionTabView { +public: + RDSPSNView(NavigationView& nav, Rect parent_rect); + + std::string PSN { "TEST1234" }; + bool mono_stereo { false }; + bool TA { false }; + bool MS { false }; + +private: + Labels labels { + { { 1 * 8, 3 * 8 }, "Program Service Name", Color::light_grey() }, + { { 2 * 8, 7 * 8 }, "PSN:", Color::light_grey() } + }; + + Button button_set { + { 18 * 8, 3 * 16, 80, 32 }, + "Set" + }; + Text text_psn { + { 6 * 8, 3 * 16 + 8, 8 * 8, 16 }, + "" + }; + + Checkbox check_mono_stereo { + { 2 * 8, 12 * 8 }, + 6, + "Stereo" + }; + Checkbox check_MS { + { 14 * 8, 12 * 8 }, + 5, + "Music" + }; + Checkbox check_TA { + { 2 * 8, 16 * 8 }, + 20, + "Traffic announcement" + }; +}; + +class RDSRadioTextView : public OptionTabView { +public: + RDSRadioTextView(NavigationView& nav, Rect parent_rect); + + std::string radiotext { "Radiotext test ABCD1234" }; +private: + Labels labels { + { { 2 * 8, 3 * 8 }, "Radiotext", Color::light_grey() }, + { { 1 * 8, 6 * 8 }, "Text:", Color::light_grey() } + }; + + Text text_radiotext { + { 1 * 8, 4 * 16, 28 * 8, 16 }, + "-" + }; + Button button_set { + { 88, 6 * 16, 64, 32 }, + "Set" + }; +}; + +class RDSDateTimeView : public OptionTabView { +public: + RDSDateTimeView(Rect parent_rect); + +private: + Labels labels { + { { 44, 5 * 16 }, "Not yet implemented", Color::red() } + }; +}; + +class RDSAudioView : public OptionTabView { +public: + RDSAudioView(Rect parent_rect); + +private: + Labels labels { + { { 44, 5 * 16 }, "Not yet implemented", Color::red() } + }; +}; + +class RDSThread { +public: + RDSThread(std::vector** frames); + ~RDSThread(); + + RDSThread(const RDSThread&) = delete; + RDSThread(RDSThread&&) = delete; + RDSThread& operator=(const RDSThread&) = delete; + RDSThread& operator=(RDSThread&&) = delete; + +private: + std::vector** frames_ { }; + Thread* thread { nullptr }; + + static msg_t static_fn(void* arg); + + void run(); +}; + class RDSView : public View { public: RDSView(NavigationView& nav); ~RDSView(); + RDSView(const RDSView&) = delete; + RDSView(RDSView&&) = delete; + RDSView& operator=(const RDSView&) = delete; + RDSView& operator=(RDSView&&) = delete; + void focus() override; - void paint(Painter& painter) override; std::string title() const override { return "RDS transmit"; }; private: - std::string PSN { 0 }; - std::string RadioText { 0 }; - bool txing = false; + NavigationView& nav_; RDS_flags rds_flags { }; + std::vector frame_psn { }; + std::vector frame_radiotext { }; + std::vector frame_datetime { }; + std::vector* frames[3] { &frame_psn, &frame_radiotext, &frame_datetime }; + + bool txing = false; + uint16_t message_length { 0 }; void start_tx(); - void on_tuning_frequency_changed(rf::Frequency f); + Rect view_rect = { 0, 8 * 8, 240, 192 }; + + RDSPSNView view_PSN { nav_, view_rect }; + RDSRadioTextView view_radiotext { nav_, view_rect }; + RDSDateTimeView view_datetime { view_rect }; + RDSAudioView view_audio { view_rect }; + + TabView tab_view { + { "Name", Color::cyan(), &view_PSN }, + { "Text", Color::green(), &view_radiotext }, + { "Time", Color::yellow(), &view_datetime }, + { "Audio", Color::orange(), &view_audio } + }; + Labels labels { - { { 1 * 8, 16 + 8 }, "PTY:", Color::light_grey() }, - { { 14 * 8, 16 + 8 }, "CC:", Color::light_grey() }, - { { 1 * 8, 32 + 8 }, "PI:", Color::light_grey() }, - { { 13 * 8, 32 + 8 }, "Cov:", Color::light_grey() }, - { { 2 * 8, 13 * 8 }, "PSN:", Color::light_grey() }, - { { 2 * 8, 8 * 16 }, "RadioText:", Color::light_grey() }, - { { 2 * 8, 14 * 16 }, "TX group:", Color::light_grey() } + { { 0 * 8, 28 }, "Program type:", Color::light_grey() }, + //{ { 14 * 8, 16 + 8 }, "CC:", Color::light_grey() }, + { { 2 * 8, 28 + 16 }, "Program ID:", Color::light_grey() }, + //{ { 13 * 8, 32 + 8 }, "Cov:", Color::light_grey() }, }; OptionsField options_pty { - { 5 * 8, 16 + 8 }, + { 13 * 8, 28 }, 8, { { "None", 0 }, @@ -104,7 +221,7 @@ private: } }; - OptionsField options_countrycode { + /*OptionsField options_countrycode { { 17 * 8, 16 + 8 }, 11, { @@ -171,15 +288,15 @@ private: { "Vatican", 4 }, { "Yugoslavia", 13 } } - }; + };*/ SymField sym_pi_code { - { 4 * 8, 32 + 8 }, + { 13 * 8, 28 + 16 }, 4, SymField::SYMFIELD_HEX }; - OptionsField options_coverage { + /*OptionsField options_coverage { { 17 * 8, 32 + 8 }, 12, { @@ -200,66 +317,21 @@ private: { "R111", 14 }, { "R112", 15 } } - }; + };*/ - Checkbox check_mono_stereo { - { 1 * 8, 4 * 16 }, - 6, - "Stereo" - }; - Checkbox check_TA { - { 12 * 8, 4 * 16 }, - 2, - "TA" - }; Checkbox check_TP { - { 18 * 8, 4 * 16 }, + { 23 * 8, 4 * 8 }, 2, "TP" }; - Checkbox check_MS { - { 24 * 8, 4 * 16 }, - 2, - "MS" - }; - - Button button_editpsn { - { 22 * 8, 5 * 16 + 20, 48, 24 }, - "Set" - }; - Text text_psn { - { 6 * 8, 13 * 8, 4 * 8, 16 }, - "" - }; - - Text text_radiotexta { - { 2 * 8, 9 * 16, 19 * 8, 16 }, - "-" - }; - Text text_radiotextb { - { 2 * 8, 10 * 16, 19 * 8, 16 }, - "-" - }; - Button button_editradiotext { - { 22 * 8, 8 * 16 + 12, 48, 24 }, - "Set" - }; - - OptionsField options_tx { - { 11 * 8, 14 * 16 }, - 11, - { - { "PSN", 0 }, - { "RadioText", 1 }, - { "Date & time", 2 } - } - }; TransmitterView tx_view { 16 * 16, 50000, 9 }; + + std::unique_ptr tx_thread { }; }; } /* namespace ui */ diff --git a/firmware/application/ui_transmitter.cpp b/firmware/application/ui_transmitter.cpp index 3a7a861de..7509ca6ad 100644 --- a/firmware/application/ui_transmitter.cpp +++ b/firmware/application/ui_transmitter.cpp @@ -141,7 +141,7 @@ TransmitterView::TransmitterView( on_edit_frequency(); }; field_frequency.on_change = [this](rf::Frequency f) { - transmitter_model.set_tuning_frequency(f); + receiver_model.set_tuning_frequency(f); }; button_start.on_select = [this](Button&){ diff --git a/firmware/baseband/proc_adsbrx.hpp b/firmware/baseband/proc_adsbrx.hpp index 3ab8b89f5..e41d0599b 100644 --- a/firmware/baseband/proc_adsbrx.hpp +++ b/firmware/baseband/proc_adsbrx.hpp @@ -25,6 +25,7 @@ #include "baseband_processor.hpp" #include "baseband_thread.hpp" +#include "rssi_thread.hpp" #include "adsb_frame.hpp" @@ -44,6 +45,7 @@ private: static constexpr size_t baseband_fs = 2000000; BasebandThread baseband_thread { baseband_fs, this, NORMALPRIO + 20, baseband::Direction::Receive }; + RSSIThread rssi_thread { NORMALPRIO + 10 }; ADSBFrame frame { }; bool configured { false }; diff --git a/firmware/common/adsb.cpp b/firmware/common/adsb.cpp index 388966a89..84343710f 100644 --- a/firmware/common/adsb.cpp +++ b/firmware/common/adsb.cpp @@ -70,6 +70,27 @@ void encode_frame_id(ADSBFrame& frame, const uint32_t ICAO_address, const std::s frame.make_CRC(); } +std::string decode_frame_id(ADSBFrame& frame) { + std::string callsign = ""; + uint8_t * raw_data = frame.get_raw_data(); + uint64_t callsign_coded = 0; + uint32_t c; + + // Frame bytes to long + for (c = 5; c < 11; c++) { + callsign_coded <<= 8; + callsign_coded |= raw_data[c]; + } + + // Long to 6-bit characters + for (c = 0; c < 8; c++) { + callsign.append(1, icao_id_lut[(callsign_coded >> 42) & 0x3F]); + callsign_coded <<= 6; + } + + return callsign; +} + /*void generate_frame_emergency(ADSBFrame& frame, const uint32_t ICAO_address, const uint8_t code) { make_frame_mode_s(frame, ICAO_address); @@ -141,26 +162,6 @@ int cpr_N(float lat, int is_odd) { return nl; } -// Decoding method (from dump1090): -// index int j = floor(((59 * latcprE - 60 * latcprO) / 131072) + 0.50) -// latE = DlatE * (cpr_mod(j, 60) + (latcprE / 131072)) -// latO = DlatO * (cpr_mod(j, 59) + (latcprO / 131072)) -// if latE >= 270 -> latE -= 360 -// if latO >= 270 -> latO -= 360 -// if (cpr_NL(latE) != cpr_NL(latO)) return; - -// int ni = cpr_N(latE ,0); -// int m = floor((((loncprE * (cpr_NL(latE) - 1)) - (loncprO * cpr_NL(latE))) / 131072) + 0.5) -// lon = cpr_Dlon(latE, 0) * (cpr_mod(m, ni) + loncprE / 131072); -// lat = latE; -// ... or ... -// int ni = cpr_N(latO ,0); -// int m = floor((((loncprE * (cpr_NL(latO) - 1)) - (loncprO * cpr_NL(latO))) / 131072) + 0.5) -// lon = cpr_Dlon(latO, 0) * (cpr_mod(m, ni) + loncprO / 131072); -// lat = latO; -// ... and ... -// if (lon > 180) lon -= 360; - void encode_frame_pos(ADSBFrame& frame, const uint32_t ICAO_address, const int32_t altitude, const float latitude, const float longitude, const uint32_t time_parity) { @@ -202,6 +203,37 @@ void encode_frame_pos(ADSBFrame& frame, const uint32_t ICAO_address, const int32 frame.make_CRC(); } +// Decoding method (from dump1090): +// index int j = floor(((59 * latcprE - 60 * latcprO) / 131072) + 0.50) +// latE = DlatE * (cpr_mod(j, 60) + (latcprE / 131072)) +// latO = DlatO * (cpr_mod(j, 59) + (latcprO / 131072)) +// if latE >= 270 -> latE -= 360 +// if latO >= 270 -> latO -= 360 +// if (cpr_NL(latE) != cpr_NL(latO)) return; + +// int ni = cpr_N(latE ,0); +// int m = floor((((loncprE * (cpr_NL(latE) - 1)) - (loncprO * cpr_NL(latE))) / 131072) + 0.5) +// lon = cpr_Dlon(latE, 0) * (cpr_mod(m, ni) + loncprE / 131072); +// lat = latE; +// ... or ... +// int ni = cpr_N(latO ,0); +// int m = floor((((loncprE * (cpr_NL(latO) - 1)) - (loncprO * cpr_NL(latO))) / 131072) + 0.5) +// lon = cpr_Dlon(latO, 0) * (cpr_mod(m, ni) + loncprO / 131072); +// lat = latO; +// ... and ... +// if (lon > 180) lon -= 360; + +// Only altitude is decoded for now +uint32_t decode_frame_pos(ADSBFrame& frame) { + uint8_t * raw_data = frame.get_raw_data(); + + // Q-bit is present + if (raw_data[5] & 1) + return ((((raw_data[5] >> 1) << 4) | ((raw_data[6] & 0xF0) >> 4)) * 25) - 1000; + + return 0; +} + // speed is in knots // vertical rate is in ft/min void encode_frame_velo(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t speed, diff --git a/firmware/common/adsb.hpp b/firmware/common/adsb.hpp index 8e348bd2e..8ba942893 100644 --- a/firmware/common/adsb.hpp +++ b/firmware/common/adsb.hpp @@ -68,12 +68,19 @@ const float adsb_lat_lut[58] = { }; void make_frame_adsb(ADSBFrame& frame, const uint32_t ICAO_address); + void encode_frame_id(ADSBFrame& frame, const uint32_t ICAO_address, const std::string& callsign); +std::string decode_frame_id(ADSBFrame& frame); + void encode_frame_pos(ADSBFrame& frame, const uint32_t ICAO_address, const int32_t altitude, const float latitude, const float longitude, const uint32_t time_parity); +uint32_t decode_frame_pos(ADSBFrame& frame); + void encode_frame_velo(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t speed, const float angle, const int32_t v_rate); + //void encode_frame_emergency(ADSBFrame& frame, const uint32_t ICAO_address, const uint8_t code); + void encode_frame_squawk(ADSBFrame& frame, const uint32_t squawk); } /* namespace adsb */ diff --git a/firmware/common/adsb_frame.hpp b/firmware/common/adsb_frame.hpp index c15204a14..3ed83daf4 100644 --- a/firmware/common/adsb_frame.hpp +++ b/firmware/common/adsb_frame.hpp @@ -60,39 +60,41 @@ public: uint8_t * get_raw_data() { return raw_data; } - - std::string get_callsign() { - uint64_t callsign_coded = 0; - uint32_t c; - std::string callsign = ""; - - // Frame bytes to long - for (c = 5; c < 11; c++) { - callsign_coded <<= 8; - callsign_coded |= raw_data[c]; - } - - // Long to 6-bit characters - for (c = 0; c < 8; c++) { - callsign.append(1, icao_id_lut[(callsign_coded >> 42) & 0x3F]); - callsign_coded <<= 6; - } - - return callsign; - } void make_CRC() { - uint8_t adsb_crc[14]; // Temp buffer + uint32_t computed_CRC = compute_CRC(); + + // Insert CRC in frame + raw_data[11] = (computed_CRC >> 16) & 0xFF; + raw_data[12] = (computed_CRC >> 8) & 0xFF; + raw_data[13] = computed_CRC & 0xFF; + } + + bool check_CRC() { + uint32_t computed_CRC = compute_CRC(); + + if (raw_data[11] != ((computed_CRC >> 16) & 0xFF)) return false; + if (raw_data[12] != ((computed_CRC >> 8) & 0xFF)) return false; + if (raw_data[13] != (computed_CRC & 0xFF)) return false; + + return true; + } + +private: + static const uint8_t adsb_preamble[16]; + static const char icao_id_lut[65]; + alignas(4) uint8_t index { 0 }; + alignas(4) uint8_t raw_data[14] { }; // 112 bits at most + + uint32_t compute_CRC() { + uint8_t adsb_crc[14] = { 0 }; // Temp buffer uint8_t b, c, s, bitn; const uint32_t crc_poly = 0x1205FFF; - // Clear CRC - raw_data[11] = 0x00; - raw_data[12] = 0x00; - raw_data[13] = 0x00; + // Copy frame data + memcpy(adsb_crc, raw_data, 11); // Compute CRC - memcpy(adsb_crc, raw_data, 14); for (c = 0; c < 11; c++) { for (b = 0; b < 8; b++) { if ((adsb_crc[c] << b) & 0x80) { @@ -104,15 +106,8 @@ public: } } - // Insert CRC in frame - memcpy(&raw_data[11], &adsb_crc[11], 3); + return (adsb_crc[11] << 16) + (adsb_crc[12] << 8) + adsb_crc[13]; } - -private: - static const uint8_t adsb_preamble[16]; - static const char icao_id_lut[65]; - alignas(4) uint8_t index { 0 }; - alignas(4) uint8_t raw_data[14] { }; // 112 bits at most }; } /* namespace adsb */ diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 122c0faa1..3edc04966 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -276,6 +276,35 @@ std::string View::title() const { return ""; }; +/* OptionTabView *********************************************************/ + +OptionTabView::OptionTabView(Rect parent_rect) { + set_parent_rect(parent_rect); + + add_child(&check_enable); + hidden(true); + + check_enable.on_select = [this](Checkbox&, bool value) { + enabled = value; + }; +} + +void OptionTabView::set_enabled(bool value) { + check_enable.set_value(value); +} + +bool OptionTabView::is_enabled() { + return check_enable.value(); +} + +void OptionTabView::set_type(std::string type) { + check_enable.set_text("Transmit " + type); +} + +void OptionTabView::focus() { + check_enable.focus(); +} + /* Rectangle *************************************************************/ Rectangle::Rectangle( diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 884242a07..3620cfbe6 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -603,6 +603,29 @@ private: bool show_max_; }; +class OptionTabView : public View { +public: + OptionTabView(Rect parent_rect); + + void focus() override; + + bool is_enabled(); + void set_type(std::string type); + +protected: + bool enabled { false }; + + void set_enabled(bool value); + +private: + Checkbox check_enable { + { 2 * 8, 0 * 16 }, + 20, + "", + false + }; +}; + } /* namespace ui */ #endif/*__UI_WIDGET_H__*/ diff --git a/firmware/portapack-h1-havoc.bin b/firmware/portapack-h1-havoc.bin index 582c037b2..5adf36d18 100644 Binary files a/firmware/portapack-h1-havoc.bin and b/firmware/portapack-h1-havoc.bin differ