diff --git a/.gitignore b/.gitignore index f66430a3..f4f8db83 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /firmware/application/portapack_cpld_data.cpp /firmware/application/hackrf_cpld_data.cpp /firmware/application/hackrf_cpld_data.hpp +/sdcard/ADSB/world_map.bin # Compiled Object files *.slo diff --git a/firmware/application/ui_about.hpp b/firmware/application/ui_about.hpp index 2e02b6cd..eb0d2979 100644 --- a/firmware/application/ui_about.hpp +++ b/firmware/application/ui_about.hpp @@ -94,7 +94,7 @@ private: {"E. Oenal", "", MEMBER_LF}, {"RDS waveform", "C. Jacquet", TITLE_LF}, {"Xy. infos", "cLx", TITLE_LF}, - {"ADS-B map", "D. Strebe", TITLE_LF}, + {"World map", "NASA", TITLE_LF}, {"Thanks", "", SECTION}, {"Rainer Matla", "Keld Norman", TITLE}, {"Giorgio C.", "DC1RDB", TITLE}, diff --git a/firmware/application/ui_adsb_rx.cpp b/firmware/application/ui_adsb_rx.cpp index 9d0ff758..fcaebd7a 100644 --- a/firmware/application/ui_adsb_rx.cpp +++ b/firmware/application/ui_adsb_rx.cpp @@ -74,8 +74,109 @@ void RecentEntriesTable::draw( painter.draw_bitmap(target_rect.location() + Point(15 * 8, 0), bitmap_target, target_color, style.background); } +void ADSBRxDetailsView::focus() { + button_see_map.focus(); +} + +void ADSBRxDetailsView::update(const AircraftRecentEntry& entry) { + entry_copy = entry; + + uint32_t age = entry_copy.age; + + if (age < 60) + text_last_seen.set(to_string_dec_uint(age) + " seconds ago"); + else + text_last_seen.set(to_string_dec_uint(age / 60) + " minutes ago"); + + text_infos.set(entry_copy.info_string); + + text_frame_pos_even.set(to_string_hex_array(entry_copy.frame_pos_even.get_raw_data(), 14)); + text_frame_pos_odd.set(to_string_hex_array(entry_copy.frame_pos_odd.get_raw_data(), 14)); + + if (send_updates) + geomap_view->update_position(entry_copy.pos.latitude, entry_copy.pos.longitude); +} + +ADSBRxDetailsView::~ADSBRxDetailsView() { + on_close_(); +} + +ADSBRxDetailsView::ADSBRxDetailsView( + NavigationView& nav, + const AircraftRecentEntry& entry, + const std::function on_close +) : entry_copy(entry), + on_close_(on_close) +{ + char file_buffer[32] { 0 }; + bool found = false; + std::string airline_code; + size_t c; + + add_children({ + &labels, + &text_callsign, + &text_last_seen, + &text_airline, + &text_country, + &text_infos, + &text_frame_pos_even, + &text_frame_pos_odd, + &button_see_map + }); + + update(entry_copy); + + // The following won't (shouldn't !) change for a given airborne aircraft + // Try getting the airline's name from airlines.db + auto result = db_file.open("ADSB/airlines.db"); + if (!result.is_valid()) { + // Search for 3-letter code in 0x0000~0x2000 + airline_code = entry_copy.callsign.substr(0, 3); + c = 0; + do { + db_file.read(file_buffer, 4); + if (!file_buffer[0]) + break; + if (!airline_code.compare(0, 4, file_buffer)) + found = true; + else + c++; + } while (!found); + + if (found) { + db_file.seek(0x2000 + (c << 6)); + db_file.read(file_buffer, 32); + text_airline.set(file_buffer); + db_file.read(file_buffer, 32); + text_country.set(file_buffer); + } else { + text_airline.set("Unknown"); + text_country.set("Unknown"); + } + } else { + text_airline.set("No airlines.db file"); + text_country.set("No airlines.db file"); + } + + text_callsign.set(entry_copy.callsign); + + button_see_map.on_select = [this, &nav](Button&) { + geomap_view = nav.push( + entry_copy.callsign, + entry_copy.pos.altitude, + entry_copy.pos.latitude, + entry_copy.pos.longitude, + 0, + [this]() { + send_updates = false; + }); + send_updates = true; + }; +}; + void ADSBRxView::focus() { - field_lna.focus(); + field_vga.focus(); } ADSBRxView::~ADSBRxView() { @@ -123,8 +224,8 @@ void ADSBRxView::on_frame(const ADSBFrameMessage * message) { entry.set_info_string(str_info); - if (geomap_view) - geomap_view->update_pos(entry.pos.latitude, entry.pos.longitude); + if (send_updates) + details_view->update(entry); } } } @@ -138,6 +239,8 @@ void ADSBRxView::on_tick_second() { entry.inc_age(); if ((entry.age == 10) || (entry.age == 30)) recent_entries_view.set_dirty(); + if (details_view && send_updates && (entry.key() == detailed_entry_key)) + details_view->update(entry); } } @@ -149,24 +252,18 @@ ADSBRxView::ADSBRxView(NavigationView& nav) { &rssi, &field_lna, &field_vga, - //&text_debug_a, - //&text_debug_b, - //&text_debug_c, &recent_entries_view }); - recent_entries_view.set_parent_rect({ 0, 64, 240, 224 }); + recent_entries_view.set_parent_rect({ 0, 16, 240, 272 }); recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) { - //text_debug_a.set(entry.info_string); - //text_debug_b.set(to_string_hex_array(entry.frame_pos_even.get_raw_data(), 14)); - //text_debug_c.set(to_string_hex_array(entry.frame_pos_odd.get_raw_data(), 14)); - - geomap_view = nav.push( - entry.callsign, - entry.pos.altitude, - entry.pos.latitude, - entry.pos.longitude, - 0.0); + detailed_entry_key = entry.key(); + details_view = nav.push( + entry, + [this]() { + send_updates = false; + }); + send_updates = true; }; signal_token_tick_second = rtc_time::signal_tick_second += [this]() { diff --git a/firmware/application/ui_adsb_rx.hpp b/firmware/application/ui_adsb_rx.hpp index 5d0bd151..60c85649 100644 --- a/firmware/application/ui_adsb_rx.hpp +++ b/firmware/application/ui_adsb_rx.hpp @@ -100,9 +100,80 @@ struct AircraftRecentEntry { using AircraftRecentEntries = RecentEntries; +class ADSBRxDetailsView : public View { +public: + ADSBRxDetailsView(NavigationView&, const AircraftRecentEntry& entry, const std::function on_close); + ~ADSBRxDetailsView(); + + ADSBRxDetailsView(const ADSBRxDetailsView&) = delete; + ADSBRxDetailsView(ADSBRxDetailsView&&) = delete; + ADSBRxDetailsView& operator=(const ADSBRxDetailsView&) = delete; + ADSBRxDetailsView& operator=(ADSBRxDetailsView&&) = delete; + + void focus() override; + + void update(const AircraftRecentEntry& entry); + + std::string title() const override { return "Aircraft details"; }; + +private: + AircraftRecentEntry entry_copy { 0 }; + std::function on_close_ { }; + GeoMapView* geomap_view { nullptr }; + bool send_updates { false }; + File db_file { }; + + Labels labels { + { { 0 * 8, 1 * 16 }, "Callsign:", Color::light_grey() }, + { { 0 * 8, 2 * 16 }, "Last seen:", Color::light_grey() }, + { { 0 * 8, 3 * 16 }, "Airline:", Color::light_grey() }, + { { 0 * 8, 5 * 16 }, "Country:", Color::light_grey() }, + { { 0 * 8, 12 * 16 }, "Even position frame:", Color::light_grey() }, + { { 0 * 8, 14 * 16 }, "Odd position frame:", Color::light_grey() } + }; + + Text text_callsign { + { 9 * 8, 1 * 16, 8 * 8, 16 }, + "-" + }; + + Text text_last_seen { + { 11 * 8, 2 * 16, 19 * 8, 16 }, + "-" + }; + + Text text_airline { + { 0 * 8, 4 * 16, 30 * 8, 16 }, + "-" + }; + + Text text_country { + { 8 * 8, 5 * 16, 22 * 8, 16 }, + "-" + }; + + Text text_infos { + { 0 * 8, 6 * 16, 30 * 8, 16 }, + "-" + }; + Text text_frame_pos_even { + { 0 * 8, 13 * 16, 30 * 8, 16 }, + "-" + }; + Text text_frame_pos_odd { + { 0 * 8, 15 * 16, 30 * 8, 16 }, + "-" + }; + + Button button_see_map { + { 8 * 8, 8 * 16, 14 * 8, 3 * 16 }, + "See on map" + }; +}; + class ADSBRxView : public View { public: - ADSBRxView(NavigationView&); + ADSBRxView(NavigationView& nav); ~ADSBRxView(); ADSBRxView(const ADSBRxView&) = delete; @@ -127,8 +198,14 @@ private: AircraftRecentEntries recent { }; RecentEntriesView> recent_entries_view { columns, recent }; - GeoMapView* geomap_view { }; SignalToken signal_token_tick_second { }; + ADSBRxDetailsView* details_view { nullptr }; + uint32_t detailed_entry_key { 0 }; + bool send_updates { false }; + + Labels labels { + { { 0 * 8, 0 * 8 }, "LNA: VGA: RSSI:", Color::light_grey() } + }; RSSI rssi { { 19 * 8, 4, 10 * 8, 8 }, @@ -142,23 +219,6 @@ private: { 11 * 8, 0 * 16 } }; - Labels labels { - { { 0 * 8, 0 * 8 }, "LNA: VGA: RSSI:", Color::light_grey() } - }; - - /*Text text_debug_a { - { 0 * 8, 1 * 16, 30 * 8, 16 }, - "-" - }; - Text text_debug_b { - { 0 * 8, 2 * 16, 30 * 8, 16 }, - "-" - }; - Text text_debug_c { - { 0 * 8, 3 * 16, 30 * 8, 16 }, - "-" - };*/ - MessageHandlerRegistration message_handler_frame { Message::ID::ADSBFrame, [this](Message* const p) { diff --git a/firmware/application/ui_geomap.cpp b/firmware/application/ui_geomap.cpp index c704c717..9b68a5ce 100644 --- a/firmware/application/ui_geomap.cpp +++ b/firmware/application/ui_geomap.cpp @@ -140,16 +140,6 @@ void GeoMap::paint(Painter& painter) { draw_bearing({ 120, 32 + 144 }, angle_, 16, Color::red()); painter.draw_string({ 120 - ((int)tag_.length() * 8 / 2), 32 + 144 - 32 }, style(), tag_); } - - /*if (has_focus() || highlighted()) - border = style().foreground; - else - border = Color::grey(); - - painter.draw_rectangle( - { r.location().x(), r.location().y(), r.size().width(), r.size().height() }, - border - );*/ } bool GeoMap::on_touch(const TouchEvent event) { @@ -226,7 +216,7 @@ void GeoMapView::focus() { nav_.display_modal("No map", "No world_map.bin file in\n/ADSB/ directory", ABORT, nullptr); } -void GeoMapView::update_pos(float lat, float lon) { +void GeoMapView::update_position(float lat, float lon) { lat_ = lat; lon_ = lon; geomap.move(lon_, lat_); @@ -266,6 +256,12 @@ void GeoMapView::setup() { }; } + +GeoMapView::~GeoMapView() { + if (on_close_) + on_close_(); +} + // Display mode GeoMapView::GeoMapView( NavigationView& nav, @@ -273,12 +269,14 @@ GeoMapView::GeoMapView( int32_t altitude, float lat, float lon, - float angle + float angle, + const std::function on_close ) : nav_ (nav), altitude_ (altitude), lat_ (lat), lon_ (lon), - angle_ (angle) + angle_ (angle), + on_close_(on_close) { mode_ = DISPLAY; diff --git a/firmware/application/ui_geomap.hpp b/firmware/application/ui_geomap.hpp index 467ac33f..8a575fa3 100644 --- a/firmware/application/ui_geomap.hpp +++ b/firmware/application/ui_geomap.hpp @@ -129,8 +129,22 @@ private: class GeoMapView : public View { public: - GeoMapView(NavigationView& nav, const std::string& tag, int32_t altitude, float lat, float lon, float angle); - GeoMapView(NavigationView& nav, int32_t altitude, float lat, float lon, const std::function on_done); + GeoMapView( + NavigationView& nav, + const std::string& tag, + int32_t altitude, + float lat, + float lon, + float angle, + const std::function on_close + ); + GeoMapView(NavigationView& nav, + int32_t altitude, + float lat, + float lon, + const std::function on_done + ); + ~GeoMapView(); GeoMapView(const GeoMapView&) = delete; GeoMapView(GeoMapView&&) = delete; @@ -139,7 +153,7 @@ public: void focus() override; - void update_pos(float lat, float lon); + void update_position(float lat, float lon); std::string title() const override { return "Map view"; }; @@ -156,6 +170,7 @@ private: float lat_ { }; float lon_ { }; float angle_ { }; + std::function on_close_ { nullptr }; bool file_error { }; diff --git a/sdcard/ADSB/airlines.db b/sdcard/ADSB/airlines.db index 88e5ade4..2f515b4d 100644 Binary files a/sdcard/ADSB/airlines.db and b/sdcard/ADSB/airlines.db differ diff --git a/sdcard/ADSB/world_map.jpg b/sdcard/ADSB/world_map.jpg new file mode 100644 index 00000000..a57b8291 Binary files /dev/null and b/sdcard/ADSB/world_map.jpg differ diff --git a/sdcard/README.md b/sdcard/README.md new file mode 100644 index 00000000..0c907437 --- /dev/null +++ b/sdcard/README.md @@ -0,0 +1,9 @@ +# How to generate world_map.bin + +World_map.bin is a huge (~450MB) raw image file in a format that can be easily rendered by the PortaPack. +Since Github doesn't allow uploading such large files, you must generate it yourself from the provided jpg file. + +1. Make sur that `world_map.jpg` is in `/sdcard/ADSB`. +1. Go in `/firmware/tools`. +1. Run 'python adsb_map.py'. Give it some time. +1. `world_map.bin` should appear !