diff --git a/firmware/application/ui_adsb_rx.cpp b/firmware/application/ui_adsb_rx.cpp index 3df854587..d7f26c6bd 100644 --- a/firmware/application/ui_adsb_rx.cpp +++ b/firmware/application/ui_adsb_rx.cpp @@ -276,7 +276,7 @@ ADSBRxView::ADSBRxView(NavigationView& nav) { };*/ /*button_ffw.on_select = [this, &nav](Button&) { - //nav.push(); + //nav.push(GeoMapView::Mode::SHOW); while (!analyze(f_offset)) { f_offset++; } diff --git a/firmware/application/ui_adsb_tx.cpp b/firmware/application/ui_adsb_tx.cpp index a1f4ac925..4294682ef 100644 --- a/firmware/application/ui_adsb_tx.cpp +++ b/firmware/application/ui_adsb_tx.cpp @@ -22,6 +22,7 @@ #include "ui_adsb_tx.hpp" #include "ui_alphanum.hpp" +#include "ui_geomap.hpp" #include "manchester.hpp" #include "string_format.hpp" @@ -49,7 +50,7 @@ Point Compass::polar_to_point(uint32_t angle, uint32_t distance) { void Compass::set_value(uint32_t new_value) { Point center = screen_pos() + Point(32, 32); - new_value = clamp_value(new_value); + new_value = range.clip(new_value); display.draw_line( center, @@ -77,12 +78,141 @@ void Compass::paint(Painter&) { set_value(value_); } -uint32_t Compass::clamp_value(uint32_t value) { - return range.clip(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) { + set_type("position"); + + add_children({ + &labels_position, + &field_altitude, + &field_lat_degrees, + &field_lat_minutes, + &field_lat_seconds, + &field_lon_degrees, + &field_lon_minutes, + &field_lon_seconds, + &button_set_map + }); + + field_altitude.set_value(36000); + field_lat_degrees.set_value(0); + field_lat_minutes.set_value(0); + field_lat_seconds.set_value(0); + field_lon_degrees.set_value(0); + field_lon_minutes.set_value(0); + field_lon_seconds.set_value(0); + + button_set_map.on_select = [this, &nav](Button&) { + nav.push(GeoMapView::Mode::SET); + }; +} + +void ADSBPositionView::collect_frames(const uint32_t ICAO_address, std::vector& frame_list) { + ADSBFrame temp_frame; + + encode_frame_pos(temp_frame, ICAO_address, field_altitude.value(), + field_lat_degrees.value(), field_lon_degrees.value(), 0); + + frame_list.emplace_back(temp_frame); + + encode_frame_pos(temp_frame, ICAO_address, field_altitude.value(), + field_lat_degrees.value(), field_lon_degrees.value(), 1); + + frame_list.emplace_back(temp_frame); +} + +ADSBCallsignView::ADSBCallsignView(NavigationView& nav) { + set_type("callsign"); + + add_children({ + &labels_callsign, + &button_callsign + }); + + set_enabled(true); + + button_callsign.set_text(callsign); + + button_callsign.on_select = [this, &nav](Button&) { + text_prompt(nav, &callsign, 8); + }; +} + +void ADSBCallsignView::collect_frames(const uint32_t ICAO_address, std::vector& frame_list) { + ADSBFrame temp_frame; + + encode_frame_id(temp_frame, ICAO_address, callsign); + + frame_list.emplace_back(temp_frame); +} + +ADSBSpeedView::ADSBSpeedView() { + set_type("speed"); + + add_children({ + &labels_speed, + &compass, + &field_angle, + &field_speed + }); + + field_angle.set_value(0); + field_speed.set_value(400); + + field_angle.on_change = [this](int32_t v) { + compass.set_value(v); + }; +} + +void ADSBSpeedView::collect_frames(const uint32_t ICAO_address, std::vector& frame_list) { + ADSBFrame temp_frame; + + encode_frame_velo(temp_frame, ICAO_address, field_speed.value(), + field_angle.value(), 0); // TODO: v_rate + + frame_list.emplace_back(temp_frame); +} + +ADSBSquawkView::ADSBSquawkView() { + set_type("squawk"); + + add_children({ + &labels_squawk, + &field_squawk + }); +} + +void ADSBSquawkView::collect_frames(const uint32_t ICAO_address, std::vector& frame_list) { + ADSBFrame temp_frame; + (void)ICAO_address; + + encode_frame_squawk(temp_frame, field_squawk.value_dec_u32()); + + frame_list.emplace_back(temp_frame); } void ADSBTxView::focus() { - button_callsign.focus(); + tab_view.focus(); } ADSBTxView::~ADSBTxView() { @@ -91,21 +221,19 @@ ADSBTxView::~ADSBTxView() { } void ADSBTxView::generate_frames() { - uint8_t * bin_ptr = shared_memory.bb_data.data; + const uint32_t ICAO_address = sym_icao.value_hex_u64(); - encode_frame_id(frames[0], sym_icao.value_hex_u64(), callsign); - - encode_frame_pos(frames[1], sym_icao.value_hex_u64(), field_altitude.value(), - field_lat_degrees.value(), field_lon_degrees.value(), 0); - encode_frame_pos(frames[2], sym_icao.value_hex_u64(), field_altitude.value(), - field_lat_degrees.value(), field_lon_degrees.value(), 1); + view_position.collect_frames(ICAO_address, frames); + view_callsign.collect_frames(ICAO_address, frames); + view_speed.collect_frames(ICAO_address, frames); + view_squawk.collect_frames(ICAO_address, frames); - memset(bin_ptr, 0, 240); + //memset(bin_ptr, 0, 240); - auto raw_ptr = frames[0].get_raw_data(); + //auto raw_ptr = frames[0].get_raw_data(); // The preamble isn't manchester encoded - memcpy(bin_ptr, adsb_preamble, 16); + //memcpy(bin_ptr, adsb_preamble, 16); // Convert to binary with manchester encoding (1 byte per bit, faster for baseband code) /*for (c = 0; c < 112; c++) { @@ -118,12 +246,12 @@ void ADSBTxView::generate_frames() { } }*/ - manchester_encode(bin_ptr + 16, raw_ptr, 112, 0); + /*manchester_encode(bin_ptr + 16, raw_ptr, 112, 0); // Display in hex for debug text_frame.set(to_string_hex_array(frames[0].get_raw_data(), 14)); - button_callsign.set_text(callsign); + button_callsign.set_text(callsign);*/ } void ADSBTxView::start_tx() { @@ -138,22 +266,22 @@ void ADSBTxView::start_tx() { } void ADSBTxView::on_txdone(const bool v) { + (void)v; /*if (v) { transmitter_model.disable(); tx_view.set_transmitting(false); }*/ } -/*void ADSBTxView::rotate_frames() { - // DEBUG +void ADSBTxView::rotate_frames() { uint8_t * bin_ptr = shared_memory.bb_data.data; uint8_t * raw_ptr; - uint32_t frame_index = 0, plane_index = 0; - uint32_t c, regen = 0; - float offs = 0; + uint32_t frame_index = 0; //, plane_index = 0; + uint32_t c; //, regen = 0; + //float offs = 0; for (;;) { - if (!regen) { + /*if (!regen) { regen = 10; encode_frame_id(frames[0], plane_index, "DEMO" + to_string_dec_uint(plane_index)); @@ -167,7 +295,7 @@ void ADSBTxView::on_txdone(const bool v) { plane_index++; offs += 0.001; - } + }*/ memset(bin_ptr, 0, 240); @@ -188,91 +316,50 @@ void ADSBTxView::on_txdone(const bool v) { baseband::set_adsb(); - chThdSleepMilliseconds(5); + chThdSleepMilliseconds(50); - if (frame_index == 3) { + if (frame_index == frames.size()) { frame_index = 0; - if (regen) - regen--; + //if (regen) + // regen--; } else { frame_index++; } } -}*/ +} -ADSBTxView::ADSBTxView(NavigationView& nav) { +ADSBTxView::ADSBTxView( + NavigationView& nav +) : nav_ { nav } +{ + Rect view_rect = { 0, 7 * 8, 240, 192 }; baseband::run_image(portapack::spi_flash::image_tag_adsb_tx); add_children({ - &labels, &tab_view, - //&options_format, + &labels, &sym_icao, - &button_callsign, - &field_altitude, - &field_lat_degrees, - &field_lat_minutes, - &field_lat_seconds, - &field_lon_degrees, - &field_lon_minutes, - &field_lon_seconds, - &compass, - &field_angle, - &field_speed, - &check_emergency, - &field_squawk, + &view_position, + &view_callsign, + &view_speed, + &view_squawk, &text_frame, &tx_view }); - /*options_format.set_by_value(17); // Mode S - - options_format.on_change = [this](size_t, int32_t) { - generate_frames(); - };*/ + view_position.set_parent_rect(view_rect); + view_callsign.set_parent_rect(view_rect); + view_speed.set_parent_rect(view_rect); + view_squawk.set_parent_rect(view_rect); sym_icao.on_change = [this]() { generate_frames(); }; - button_callsign.on_select = [this, &nav](Button&) { - text_prompt(nav, &callsign, 8); - }; - - field_altitude.set_value(36000); - field_lat_degrees.set_value(0); - field_lat_minutes.set_value(0); - field_lat_seconds.set_value(0); - field_lon_degrees.set_value(0); - field_lon_minutes.set_value(0); - field_lon_seconds.set_value(0); - field_angle.set_value(0); - field_speed.set_value(0); - - const auto update_fn = [this](int32_t) { - generate_frames(); - }; - - field_altitude.on_change = update_fn; - field_lat_degrees.on_change = update_fn; - field_lat_minutes.on_change = update_fn; - field_lat_seconds.on_change = update_fn; - field_lon_degrees.on_change = update_fn; - field_lon_minutes.on_change = update_fn; - field_lon_seconds.on_change = update_fn; - - field_angle.on_change = [this](int32_t v) { - compass.set_value(v); - generate_frames(); - }; - - field_speed.on_change = update_fn; - - generate_frames(); tx_view.on_start = [this]() { start_tx(); tx_view.set_transmitting(true); - //rotate_frames(); + rotate_frames(); }; tx_view.on_stop = [this]() { diff --git a/firmware/application/ui_adsb_tx.hpp b/firmware/application/ui_adsb_tx.hpp index 7d15105e2..88f4831c4 100644 --- a/firmware/application/ui_adsb_tx.hpp +++ b/firmware/application/ui_adsb_tx.hpp @@ -51,8 +51,137 @@ private: const range_t range { 0, 359 }; uint32_t value_ { 0 }; +}; - uint32_t clamp_value(uint32_t value); +class ADSBView : public View { +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); + + void collect_frames(const uint32_t ICAO_address, std::vector& frame_list); + +private: + Labels labels_position { + { { 2 * 8, 2 * 16 }, "Alt: feet", Color::light_grey() }, + { { 2 * 8, 4 * 16 }, "Lat: * ' \"", Color::light_grey() }, // No ° symbol in 8x16 font + { { 2 * 8, 5 * 16 }, "Lon: * ' \"", Color::light_grey() }, + }; + + NumberField field_altitude { + { 7 * 8, 2 * 16 }, + 5, + { -1000, 50000 }, + 250, + ' ' + }; + + NumberField field_lat_degrees { + { 7 * 8, 4 * 16 }, 4, { -90, 90 }, 1, ' ' + }; + NumberField field_lat_minutes { + { 12 * 8, 4 * 16 }, 2, { 0, 59 }, 1, ' ' + }; + NumberField field_lat_seconds { + { 15 * 8, 4 * 16 }, 2, { 0, 59 }, 1, ' ' + }; + + NumberField field_lon_degrees { + { 7 * 8, 5 * 16 }, 4, { -180, 180 }, 1, ' ' + }; + NumberField field_lon_minutes { + { 12 * 8, 5 * 16 }, 2, { 0, 59 }, 1, ' ' + }; + NumberField field_lon_seconds { + { 15 * 8, 5 * 16 }, 2, { 0, 59 }, 1, ' ' + }; + + Button button_set_map { + { 7 * 8, 7 * 16, 14 * 8, 2 * 16 }, + "Set from map" + }; +}; + +class ADSBCallsignView : public ADSBView { +public: + ADSBCallsignView(NavigationView& nav); + + void collect_frames(const uint32_t ICAO_address, std::vector& frame_list); + +private: + std::string callsign = "TEST1234"; + + Labels labels_callsign { + { { 2 * 8, 5 * 8 }, "Callsign:", Color::light_grey() } + }; + + Button button_callsign { + { 12 * 8, 2 * 16, 10 * 8, 2 * 16 }, + "" + }; +}; + +class ADSBSpeedView : public ADSBView { +public: + ADSBSpeedView(); + + void collect_frames(const uint32_t ICAO_address, std::vector& frame_list); + +private: + Labels labels_speed { + { { 1 * 8, 6 * 16 }, "Speed: kn Bearing: *", Color::light_grey() } + }; + + Compass compass { + { 21 * 8, 2 * 16 } + }; + + NumberField field_angle { + { 21 * 8 + 20, 6 * 16 }, 3, { 0, 359 }, 1, ' ', true + }; + + NumberField field_speed { + { 8 * 8, 6 * 16 }, 3, { 0, 999 }, 5, ' ' + }; +}; + +class ADSBSquawkView : public ADSBView { +public: + ADSBSquawkView(); + + void collect_frames(const uint32_t ICAO_address, std::vector& frame_list); + +private: + Labels labels_squawk { + { { 2 * 8, 2 * 16 }, "Squawk:", Color::light_grey() } + }; + + SymField field_squawk { + { 10 * 8, 2 * 16 }, + 4, + SymField::SYMFIELD_OCT + }; }; class ADSBTxView : public View { @@ -101,132 +230,35 @@ private: }; tx_modes tx_mode = IDLE; - - std::string callsign = "TEST1234"; - - ADSBFrame frames[4] { }; + NavigationView& nav_; + std::vector frames { }; void start_tx(); void generate_frames(); - //void rotate_frames(); + void rotate_frames(); void on_txdone(const bool v); - Labels labels { - //{ { 2 * 8, 2 * 8 }, "Format: 17 (ADS-B)", Color::light_grey() }, - { { 2 * 8, 4 * 8 }, "ICAO24:", Color::light_grey() }, - { { 2 * 8, 7 * 8 }, "Callsign:", Color::light_grey() }, - { { 1 * 8, 11 * 8 }, "Alt: feet", Color::light_grey() }, - { { 1 * 8, 13 * 8 }, "Lat: * ' \"", Color::light_grey() }, // No ° symbol in 8x16 font - { { 1 * 8, 15 * 8 }, "Lon: * ' \"", Color::light_grey() }, - { { 1 * 8, 18 * 8 }, "Speed: kn Bearing: *", Color::light_grey() }, - { { 16 * 8, 22 * 8 }, "Squawk:", Color::light_grey() } - }; - - // Only ADS-B is implemented right now - /*OptionsField options_format { - { 10 * 8, 1 * 16 }, - 9, - { - { "17: ADS-B", 17 }, - { "18: TIS-B", 18 }, - { "19: MIL ", 19 }, - } - };*/ + ADSBPositionView view_position { nav_ }; + ADSBCallsignView view_callsign { nav_ }; + ADSBSpeedView view_speed { }; + ADSBSquawkView view_squawk { }; TabView tab_view { - { "Position#", Color::cyan(), - { - &labels, - &field_altitude, - &field_lat_degrees, - &field_lat_minutes, - &field_lat_seconds, - &field_lon_degrees, - &field_lon_minutes, - &field_lon_seconds - } - }, - { "Callsign", Color::green(), - { - &button_callsign, - } - }, - { "Speed", Color::yellow(), - { - &compass, - &field_angle, - &field_speed - } - }, - { "Squawk", Color::orange(), - { - &check_emergency, - &field_squawk - } - } + { "Position", Color::cyan(), &view_position }, + { "Callsign", Color::green(), &view_callsign }, + { "Speed", Color::yellow(), &view_speed }, + { "Squawk", Color::orange(), &view_squawk } + }; + + Labels labels { + { { 2 * 8, 4 * 8 }, "ICAO24:", Color::light_grey() } }; SymField sym_icao { - { 10 * 8, 2 * 16 }, + { 10 * 8, 4 * 8 }, 6, SymField::SYMFIELD_HEX }; - - Button button_callsign { - { 12 * 8, 3 * 16 + 4, 10 * 8, 24 }, - "" - }; - - NumberField field_altitude { - { 6 * 8, 11 * 8 }, - 5, - { -1000, 50000 }, - 250, - ' ' - }; - - NumberField field_lat_degrees { - { 6 * 8, 13 * 8 }, 4, { -90, 90 }, 1, ' ' - }; - NumberField field_lat_minutes { - { 11 * 8, 13 * 8 }, 2, { 0, 59 }, 1, ' ' - }; - NumberField field_lat_seconds { - { 14 * 8, 13 * 8 }, 2, { 0, 59 }, 1, ' ' - }; - - NumberField field_lon_degrees { - { 6 * 8, 15 * 8 }, 4, { -180, 180 }, 1, ' ' - }; - NumberField field_lon_minutes { - { 11 * 8, 15 * 8 }, 2, { 0, 59 }, 1, ' ' - }; - NumberField field_lon_seconds { - { 14 * 8, 15 * 8 }, 2, { 0, 59 }, 1, ' ' - }; - - Compass compass { - { 21 * 8, 5 * 16 } - }; - NumberField field_angle { - { 21 * 8 + 20, 9 * 16 }, 3, { 0, 359 }, 1, ' ', true - }; - - NumberField field_speed { - { 8 * 8, 18 * 8 }, 3, { 0, 999 }, 5, ' ' - }; - - Checkbox check_emergency { - { 2 * 8, 11 * 16 - 4 }, - 9, - "Emergency", - false - }; - SymField field_squawk { - { 24 * 8, 11 * 16 }, - 4, - SymField::SYMFIELD_OCT - }; Text text_frame { { 1 * 8, 29 * 8, 14 * 8, 16 }, diff --git a/firmware/application/ui_geomap.cpp b/firmware/application/ui_geomap.cpp index 8278f031f..a276458b9 100644 --- a/firmware/application/ui_geomap.cpp +++ b/firmware/application/ui_geomap.cpp @@ -96,8 +96,10 @@ void GeoMapView::move_map() { } GeoMapView::GeoMapView( - NavigationView& nav -) : nav_ (nav) + NavigationView& nav, + Mode mode +) : nav_ (nav), + mode_ (mode) { auto result = map_file.open("ADSB/world_map.bin"); if (result.is_valid()) { @@ -113,19 +115,22 @@ GeoMapView::GeoMapView( add_children({ &field_xpos, - &field_ypos, - &field_angle + &field_ypos }); + if (mode_ == SHOW) { + add_child(&field_angle); + field_angle.on_change = [this](int32_t) { + move_map(); + }; + } + field_xpos.on_change = [this](int32_t) { move_map(); }; field_ypos.on_change = [this](int32_t) { move_map(); }; - field_angle.on_change = [this](int32_t) { - move_map(); - }; } } /* namespace ui */ diff --git a/firmware/application/ui_geomap.hpp b/firmware/application/ui_geomap.hpp index 63004d99d..d37ad5324 100644 --- a/firmware/application/ui_geomap.hpp +++ b/firmware/application/ui_geomap.hpp @@ -31,7 +31,12 @@ namespace ui { class GeoMapView : public View { public: - GeoMapView(NavigationView& nav); + enum Mode { + SHOW, + SET + }; + + GeoMapView(NavigationView& nav, Mode mode); ~GeoMapView(); void focus() override; @@ -40,6 +45,7 @@ public: private: NavigationView& nav_; + Mode mode_ { }; File map_file { }; bool file_error { false }; diff --git a/firmware/application/ui_tabview.cpp b/firmware/application/ui_tabview.cpp index 6c714b191..91cb650b2 100644 --- a/firmware/application/ui_tabview.cpp +++ b/firmware/application/ui_tabview.cpp @@ -47,12 +47,12 @@ void Tab::set( void Tab::paint(Painter& painter) { const auto rect = screen_rect(); - const Color color = highlighted() ? Color::black() : Color::light_grey(); + const Color color = highlighted() ? Color::black() : Color::grey(); painter.fill_rectangle({ rect.left(), rect.top(), rect.width() - 8, rect.height() }, color); if (!highlighted()) - painter.draw_hline({ rect.left(), rect.top() }, rect.width() - 9, Color::white()); + painter.draw_hline({ rect.left(), rect.top() }, rect.width() - 9, Color::light_grey()); painter.draw_bitmap( { rect.right() - 8, rect.top() }, @@ -105,50 +105,38 @@ void TabView::set_selected(uint16_t index) { if (index >= n_tabs) return; - //if (index == current_tab) - // return; - - // Hide all widgets - for (const auto widget : parent()->children()) { - widget->hidden(true); - } - - // Except this one :) - hidden(false); + // Hide previous view + views[current_tab]->hidden(true); tab = &tabs[current_tab]; tab->set_highlighted(false); tab->set_focusable(true); tab->set_dirty(); - // Show new tab's widgets - for (auto widget : widget_lists[index]) { - widget->hidden(false); - } + // Show new view + views[index]->hidden(false); tab = &tabs[index]; current_tab = index; tab->set_highlighted(true); tab->set_focusable(false); tab->set_dirty(); - - parent()->set_dirty(); -} - -void TabView::focus() { - tabs[current_tab].focus(); } void TabView::on_show() { set_selected(current_tab); } + +void TabView::focus() { + views[current_tab]->focus(); +} TabView::TabView(std::initializer_list tab_definitions) { size_t i = 0; n_tabs = tab_definitions.size(); - if (n_tabs > 5) - n_tabs = 5; + if (n_tabs > MAX_TABS) + n_tabs = MAX_TABS; size_t tab_width = 240 / n_tabs; @@ -156,12 +144,10 @@ TabView::TabView(std::initializer_list tab_definitions) { for (auto &tab_definition : tab_definitions) { tabs[i].set(i, tab_width, tab_definition.text, tab_definition.color); - for (auto widget : tab_definition.widget_list) { - widget_lists[i].emplace_back(widget); - } + views[i] = tab_definition.view; add_child(&tabs[i]); i++; - if (i == 5) break; + if (i == MAX_TABS) break; } } diff --git a/firmware/application/ui_tabview.hpp b/firmware/application/ui_tabview.hpp index 15843a84b..6919ddec3 100644 --- a/firmware/application/ui_tabview.hpp +++ b/firmware/application/ui_tabview.hpp @@ -31,6 +31,8 @@ #include "ui_font_fixed_8x16.hpp" namespace ui { + +#define MAX_TABS 5 class Tab : public Widget { public: @@ -56,7 +58,7 @@ public: struct TabDef { std::string text; ui::Color color; - std::initializer_list widget_list; + View* view; }; TabView(std::initializer_list tab_definitions); @@ -69,8 +71,8 @@ public: private: size_t n_tabs { }; - std::array tabs { }; - std::array, 5> widget_lists { }; + std::array tabs { }; + std::array views { }; uint32_t current_tab { 0 }; }; diff --git a/firmware/common/adsb.hpp b/firmware/common/adsb.hpp index 332f2d741..6c3c8a3e4 100644 --- a/firmware/common/adsb.hpp +++ b/firmware/common/adsb.hpp @@ -74,8 +74,8 @@ void encode_frame_pos(ADSBFrame& frame, const uint32_t ICAO_address, const uint3 const float latitude, const float longitude, const uint32_t time_parity); 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_identity(ADSBFrame& frame, const uint32_t ICAO_address, const uint32_t code); +//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 */