diff --git a/firmware/application/replay_app.cpp b/firmware/application/replay_app.cpp index 0627acb8..b54e2bb3 100644 --- a/firmware/application/replay_app.cpp +++ b/firmware/application/replay_app.cpp @@ -102,7 +102,10 @@ void ReplayAppView::on_hide() { }*/ void ReplayAppView::focus() { - field_frequency.focus(); + if (!file_error) { + field_frequency.focus(); + } else + nav_.display_modal("No files", "No .C16 files in\nSD card root", ABORT, nullptr); } void ReplayAppView::on_target_frequency_changed(rf::Frequency f) { diff --git a/firmware/application/replay_app.hpp b/firmware/application/replay_app.hpp index f8ae50ba..0404350a 100644 --- a/firmware/application/replay_app.hpp +++ b/firmware/application/replay_app.hpp @@ -45,7 +45,7 @@ public: void focus() override; - std::string title() const override { return "Replay (broken)"; }; + std::string title() const override { return "Replay (BETA)"; }; private: NavigationView& nav_; diff --git a/firmware/application/ui_adsb_tx.hpp b/firmware/application/ui_adsb_tx.hpp index c911ef1d..ab5bd97f 100644 --- a/firmware/application/ui_adsb_tx.hpp +++ b/firmware/application/ui_adsb_tx.hpp @@ -86,7 +86,7 @@ private: }; Button button_set_map { - { 7 * 8, 7 * 16, 14 * 8, 2 * 16 }, + { 8 * 8, 6 * 16, 14 * 8, 2 * 16 }, "Set from map" }; }; diff --git a/firmware/application/ui_geomap.cpp b/firmware/application/ui_geomap.cpp index 0b064d6d..bb22faaf 100644 --- a/firmware/application/ui_geomap.cpp +++ b/firmware/application/ui_geomap.cpp @@ -22,7 +22,6 @@ #include "ui_geomap.hpp" -#include "adsb.hpp" #include "portapack.hpp" #include @@ -53,18 +52,23 @@ GeoPos::GeoPos( set_lat(0); set_lon(0); - const auto changed = [this](int32_t) { - if (on_change) - on_change(); + const auto changed_fn = [this](int32_t) { + if (on_change && report_change) + on_change(altitude(), lat(), lon()); }; - field_altitude.on_change = changed; - field_lat_degrees.on_change = changed; - field_lat_minutes.on_change = changed; - field_lat_seconds.on_change = changed; - field_lon_degrees.on_change = changed; - field_lon_minutes.on_change = changed; - field_lon_seconds.on_change = changed; + field_altitude.on_change = changed_fn; + field_lat_degrees.on_change = changed_fn; + field_lat_minutes.on_change = changed_fn; + field_lat_seconds.on_change = changed_fn; + field_lon_degrees.on_change = changed_fn; + field_lon_minutes.on_change = changed_fn; + field_lon_seconds.on_change = changed_fn; +} + +// Stupid hack to avoid an event loop +void GeoPos::set_report_change(bool v) { + report_change = v; } void GeoPos::focus() { @@ -77,14 +81,14 @@ void GeoPos::set_altitude(int32_t altitude) { void GeoPos::set_lat(float lat) { field_lat_degrees.set_value(lat); - field_lat_minutes.set_value((uint32_t)(lat / (1.0 / 60)) % 60); - field_lat_seconds.set_value((uint32_t)(lat / (1.0 / 3600)) % 60); + field_lat_minutes.set_value((uint32_t)abs(lat / (1.0 / 60)) % 60); + field_lat_seconds.set_value((uint32_t)abs(lat / (1.0 / 3600)) % 60); } void GeoPos::set_lon(float lon) { field_lon_degrees.set_value(lon); - field_lon_minutes.set_value((uint32_t)(lon / (1.0 / 60)) % 60); - field_lon_seconds.set_value((uint32_t)(lon / (1.0 / 3600)) % 60); + field_lon_minutes.set_value((uint32_t)abs(lon / (1.0 / 60)) % 60); + field_lon_seconds.set_value((uint32_t)abs(lon / (1.0 / 3600)) % 60); } float GeoPos::lat() { @@ -103,19 +107,102 @@ void GeoPos::set_read_only(bool v) { set_focusable(~v); }; -void GeoMapView::focus() { - if (!file_error) { - geopos.focus(); - move_map(); - } else - nav_.display_modal("No map", "No world_map.bin file in\n/ADSB/ directory", ABORT, nullptr); +GeoMap::GeoMap( + Rect parent_rect +) : Widget { parent_rect } +{ + //set_focusable(true); } -GeoMapView::~GeoMapView() { +void GeoMap::paint(Painter& painter) { + Coord line; + std::array map_line_buffer; + //Color border; + const auto r = screen_rect(); + // Ony redraw map if it moved by at least 1 pixel + if ((x_pos != prev_x_pos) || (y_pos != prev_y_pos)) { + for (line = 0; line < r.height(); line++) { + map_file.seek(4 + ((x_pos + (map_width * (y_pos + line))) << 1)); + map_file.read(map_line_buffer.data(), r.width() << 1); + display.draw_pixels({ 0, r.top() + line, r.width(), 1 }, map_line_buffer); + } + + prev_x_pos = x_pos; + prev_y_pos = y_pos; + } + + if (mode_ == PROMPT) { + // Cross + display.fill_rectangle({ r.center() - Point(16, 1), { 32, 2 } }, Color::red()); + display.fill_rectangle({ r.center() - Point(1, 16), { 2, 32 } }, Color::red()); + } else { + draw_bearing({ 120, 32 + 144 }, angle_, 16, Color::red()); + } + + /*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 + );*/ } -void GeoMapView::draw_bearing(const Point origin, const uint32_t angle, uint32_t size, const Color color) { +bool GeoMap::on_touch(const TouchEvent event) { + if (event.type == TouchEvent::Type::Start) { + set_highlighted(true); + if (on_move) { + Point p = event.point - screen_rect().center(); + on_move(p.x() / 2.0 * lon_ratio, p.y() / 2.0 * lat_ratio); + return true; + } + } + return false; +} + +void GeoMap::move(const float lon, const float lat) { + lon_ = lon; + lat_ = lat; + + Rect map_rect = screen_rect(); + + // Map is in Equidistant "Plate Carrée" projection + x_pos = map_center_x - (map_rect.width() / 2) + (lon_ / lon_ratio); + y_pos = map_center_y - (map_rect.height() / 2) + (lat_ / lat_ratio); + + // Cap position + if (x_pos > (map_width - map_rect.width())) + x_pos = map_width - map_rect.width(); + if (y_pos > (map_height + map_rect.height())) + y_pos = map_height - map_rect.height(); +} + +bool GeoMap::init() { + auto result = map_file.open("ADSB/world_map.bin"); + if (result.is_valid()) { + return false; + } + + map_file.read(&map_width, 2); + map_file.read(&map_height, 2); + + map_center_x = map_width >> 1; + map_center_y = map_height >> 1; + + lon_ratio = 180.0 / map_center_x; + lat_ratio = 90.0 / map_center_y; + + return true; +} + +void GeoMap::set_mode(GeoMapMode mode) { + mode_ = mode; +} + +void GeoMap::draw_bearing(const Point origin, const uint32_t angle, uint32_t size, const Color color) { Point arrow_a, arrow_b, arrow_c; for (size_t thickness = 0; thickness < 3; thickness++) { @@ -131,62 +218,43 @@ void GeoMapView::draw_bearing(const Point origin, const uint32_t angle, uint32_t } } -void GeoMapView::move_map() { - Coord line; - int32_t x_pos, y_pos; - std::array map_line_buffer; - - auto r = screen_rect(); - Rect map_rect = { r.left(), r.top() + banner_height, r.width(), r.height() - banner_height }; - - altitude_ = geopos.altitude(); - lat_ = geopos.lat(); - lon_ = geopos.lon(); - - // Map is in Equidistant "Plate Carrée" projection - x_pos = map_center_x - (map_rect.width() / 2) + ((lat_ * map_center_x) / 180); - y_pos = map_center_y - (map_rect.height() / 2) + ((lon_ * map_center_y) / 90); - - if (x_pos > (map_width - map_rect.width())) - x_pos = map_width - map_rect.width(); - if (y_pos > (map_height + map_rect.height())) - y_pos = map_height - map_rect.height(); - - for (line = 0; line < map_rect.height(); line++) { - map_file.seek(4 + ((x_pos + (map_width * (y_pos + line))) << 1)); - map_file.read(map_line_buffer.data(), map_rect.width() << 1); - display.draw_pixels({ 0, map_rect.top() + line, map_rect.width(), 1 }, map_line_buffer); - } - - if (mode_ == PROMPT) { - display.fill_rectangle({ map_rect.center() - Point(16, 1), { 32, 2 } }, Color::red()); - display.fill_rectangle({ map_rect.center() - Point(1, 16), { 2, 32 } }, Color::red()); - } else { - draw_bearing({ 120, 32 + 144 }, angle_, 16, Color::red()); - } +void GeoMapView::focus() { + if (!file_error) { + geopos.focus(); + } else + nav_.display_modal("No map", "No world_map.bin file in\n/ADSB/ directory", ABORT, nullptr); } void GeoMapView::setup() { - auto result = map_file.open("ADSB/world_map.bin"); - if (result.is_valid()) { - file_error = true; - return; - } - - map_file.read(&map_width, 2); - map_file.read(&map_height, 2); - - map_center_x = map_width >> 1; - map_center_y = map_height >> 1; - - add_child(&geopos); + add_children({ + &geopos, + &geomap + }); geopos.set_altitude(altitude_); geopos.set_lat(lat_); geopos.set_lon(lon_); - geopos.on_change = [this]() { - move_map(); + geopos.on_change = [this](int32_t altitude, float lat, float lon) { + altitude_ = altitude; + lat_ = lat; + lon_ = lon; + geomap.move(lon_, lat_); + geomap.set_dirty(); + }; + + geomap.on_move = [this](float move_x, float move_y) { + lon_ += move_x; + lat_ += move_y; + + // Stupid hack to avoid an event loop + geopos.set_report_change(false); + geopos.set_lon(lon_); + geopos.set_lat(lat_); + geopos.set_report_change(true); + + geomap.move(lon_, lat_); + geomap.set_dirty(); }; } @@ -206,8 +274,15 @@ GeoMapView::GeoMapView( angle_ (angle) { mode_ = DISPLAY; + + file_error = !geomap.init(); + if (file_error) return; + setup(); + geomap.set_mode(mode_); + geomap.move(lon_, lat_); + geopos.set_read_only(true); } @@ -224,10 +299,16 @@ GeoMapView::GeoMapView( lon_ (lon) { mode_ = PROMPT; - setup(); + file_error = !geomap.init(); + if (file_error) return; + + setup(); add_child(&button_ok); + geomap.set_mode(mode_); + geomap.move(lon_, lat_); + button_ok.on_select = [this, on_done, &nav](Button&) { if (on_done) on_done(altitude_, lat_, lon_); diff --git a/firmware/application/ui_geomap.hpp b/firmware/application/ui_geomap.hpp index 32e75acf..96ad8398 100644 --- a/firmware/application/ui_geomap.hpp +++ b/firmware/application/ui_geomap.hpp @@ -29,9 +29,14 @@ namespace ui { +enum GeoMapMode { + DISPLAY, + PROMPT +}; + class GeoPos : public View { public: - std::function on_change { }; + std::function on_change { }; GeoPos(const Point pos); @@ -41,12 +46,15 @@ public: void set_altitude(int32_t altitude); void set_lat(float lat); void set_lon(float lon); + int32_t altitude(); float lat(); float lon(); - int32_t altitude(); + + void set_report_change(bool v); private: bool read_only { false }; + bool report_change { true }; Labels labels_position { { { 2 * 8, 0 * 16 }, "Alt: feet", Color::light_grey() }, @@ -83,6 +91,35 @@ private: }; }; +class GeoMap : public Widget { +public: + std::function on_move { }; + + GeoMap(Rect parent_rect); + + void paint(Painter& painter) override; + + bool on_touch(const TouchEvent event) override; + + bool init(); + void set_mode(GeoMapMode mode); + void move(const float lon, const float lat); + +private: + void draw_bearing(const Point origin, const uint32_t angle, uint32_t size, const Color color); + + GeoMapMode mode_ { }; + File map_file { }; + uint16_t map_width { }, map_height { }; + int32_t map_center_x { }, map_center_y { }; + float lon_ratio { }, lat_ratio { }; + int32_t x_pos { }, y_pos { }; + int32_t prev_x_pos { 0xFFFF }, prev_y_pos { 0xFFFF }; + float lat_ { }; + float lon_ { }; + float angle_ { }; +}; + class GeoMapView : public View { public: GeoMapView(NavigationView& nav, std::string* tag, int32_t altitude, float lat, float lon, float angle); @@ -93,41 +130,35 @@ public: GeoMapView& operator=(const GeoMapView&) = delete; GeoMapView& operator=(GeoMapView&&) = delete; - ~GeoMapView(); - void focus() override; std::string title() const override { return "Map view"; }; private: - enum Mode { - DISPLAY, - PROMPT - }; + NavigationView& nav_; + + void setup(); + + const std::function on_done { }; const Dim banner_height = 3 * 16; - NavigationView& nav_; - const std::function on_done { }; - Mode mode_ { }; + GeoMapMode mode_ { }; std::string* tag_ { }; int32_t altitude_ { }; float lat_ { }; float lon_ { }; float angle_ { }; - File map_file { }; - bool file_error { false }; - uint16_t map_width { }, map_height { }; - int32_t map_center_x { }, map_center_y { }; - - void setup(); - void move_map(); - void draw_bearing(const Point origin, const uint32_t angle, uint32_t size, const Color color); + bool file_error { }; GeoPos geopos { { 0, 0 } }; + GeoMap geomap { + { 0, banner_height, 240, 320 - 16 - banner_height } + }; + Button button_ok { { 20 * 8, 8, 8 * 8, 2 * 16 }, "OK" diff --git a/firmware/application/ui_jammer.cpp b/firmware/application/ui_jammer.cpp index 8b2265bd..69b2194f 100644 --- a/firmware/application/ui_jammer.cpp +++ b/firmware/application/ui_jammer.cpp @@ -26,12 +26,6 @@ #include "baseband_api.hpp" #include "string_format.hpp" -#include "portapack_shared_memory.hpp" -#include "portapack_persistent_memory.hpp" - -#include -#include - using namespace portapack; namespace ui { @@ -39,10 +33,90 @@ namespace ui { void RangeView::focus() { check_enabled.focus(); } - + extern constexpr jammer_range_t RangeView::range_presets[]; extern constexpr Style RangeView::style_info; +void RangeView::update_min(rf::Frequency f) { + // Change everything except max + frequency_range.min = f; + button_min.set_text(to_string_short_freq(f)); + + center = (frequency_range.min + frequency_range.max) / 2; + width = abs(frequency_range.max - frequency_range.min); + + button_center.set_text(to_string_short_freq(center)); + button_width.set_text(to_string_short_freq(width)); +} + +void RangeView::update_max(rf::Frequency f) { + // Change everything except min + frequency_range.max = f; + button_max.set_text(to_string_short_freq(f)); + + center = (frequency_range.min + frequency_range.max) / 2; + width = abs(frequency_range.max - frequency_range.min); + + button_center.set_text(to_string_short_freq(center)); + button_width.set_text(to_string_short_freq(width)); +} + +void RangeView::update_center(rf::Frequency f) { + // Change min/max/center, keep width + center = f; + button_center.set_text(to_string_short_freq(center)); + + rf::Frequency min = center - (width / 2); + rf::Frequency max = min + width; + + frequency_range.min = min; + button_min.set_text(to_string_short_freq(min)); + + frequency_range.max = max; + button_max.set_text(to_string_short_freq(max)); +} + +void RangeView::update_width(uint32_t w) { + // Change min/max/width, keep center + width = w; + + button_width.set_text(to_string_short_freq(width)); + + rf::Frequency min = center - (width / 2); + rf::Frequency max = min + width; + + frequency_range.min = min; + button_min.set_text(to_string_short_freq(min)); + + frequency_range.max = max; + button_max.set_text(to_string_short_freq(max)); +} + +void RangeView::paint(Painter&) { + // Draw lines and arrows + Rect r; + Point p; + Coord c; + + r = button_center.screen_rect(); + p = r.center() + Point(0, r.height() / 2); + + display.draw_line(p, p + Point(0, 10), Color::grey()); + + r = button_width.screen_rect(); + c = r.top() + (r.height() / 2); + + p = {r.left() - 64, c}; + display.draw_line({r.left(), c}, p, Color::grey()); + display.draw_line(p, p + Point(10, -10), Color::grey()); + display.draw_line(p, p + Point(10, 10), Color::grey()); + + p = {r.right() + 64, c}; + display.draw_line({r.right(), c}, p, Color::grey()); + display.draw_line(p, p + Point(-10, -10), Color::grey()); + display.draw_line(p, p + Point(-10, 10), Color::grey()); +} + RangeView::RangeView(NavigationView& nav) { hidden(true); @@ -52,90 +126,58 @@ RangeView::RangeView(NavigationView& nav) { &options_preset, &button_min, &button_max, - &text_info + &button_center, + &button_width }); - check_enabled.set_value(false); - check_enabled.on_select = [this](Checkbox&, bool v) { frequency_range.enabled = v; }; button_min.on_select = [this, &nav](Button& button) { - rf::Frequency * value_ptr; - - value_ptr = &frequency_range.min; - - auto new_view = nav.push(*value_ptr); - new_view->on_changed = [this, value_ptr, &button](rf::Frequency f) { - *value_ptr = f; - update_button(button, f); - update_range(); + auto new_view = nav.push(frequency_range.min); + new_view->on_changed = [this, &button](rf::Frequency f) { + update_min(f); }; //update_button(button, f); }; button_max.on_select = [this, &nav](Button& button) { - rf::Frequency * value_ptr; - - value_ptr = &frequency_range.max; - - auto new_view = nav.push(*value_ptr); - new_view->on_changed = [this, value_ptr, &button](rf::Frequency f) { - *value_ptr = f; - update_button(button, f); - update_range(); + auto new_view = nav.push(frequency_range.max); + new_view->on_changed = [this, &button](rf::Frequency f) { + update_max(f); }; //update_button(button, f); }; - text_info.set_style(&style_info); + button_center.on_select = [this, &nav](Button& button) { + auto new_view = nav.push(frequency_range.max); + new_view->on_changed = [this, &button](rf::Frequency f) { + update_center(f); + }; + + //update_button(button, f); + }; - options_preset.set_selected_index(8); // ISM 868 + button_width.on_select = [this, &nav](Button& button) { + auto new_view = nav.push(frequency_range.max); + new_view->on_changed = [this, &button](rf::Frequency f) { + update_width(f); + }; + + //update_button(button, f); + }; options_preset.on_change = [this](size_t, OptionsField::value_t v) { - frequency_range.min = range_presets[v].min; - frequency_range.max = range_presets[v].max; + update_min(range_presets[v].min); + update_max(range_presets[v].max); check_enabled.set_value(true); - update_button(button_min, frequency_range.min); - update_button(button_max, frequency_range.max); - update_range(); }; -} - -void RangeView::update_button(Button& button, rf::Frequency f) { - std::string label; + options_preset.set_selected_index(11); // ISM 868 - auto f_mhz = to_string_dec_int(f / 1000000, 4); - auto f_hz100 = to_string_dec_int((f / 1000) % 1000, 3, '0'); - - label = f_mhz + "." + f_hz100 + "M"; - - button.set_text(label); -} - -void RangeView::update_range() { - std::string label; - rf::Frequency center, bw_khz; - - center = (frequency_range.min + frequency_range.max) / 2; - bw_khz = abs(frequency_range.max - frequency_range.min) / 1000; - - label = "C:" + to_string_short_freq(center) + "M W:"; - - if (bw_khz < 1000) { - label += to_string_dec_int(bw_khz, 3) + "kHz"; - } else { - bw_khz /= 1000; - label += to_string_dec_int(bw_khz, 3) + "MHz"; - } - - while (label.length() < 23) - label += " "; - - text_info.set(label); + check_enabled.set_value(false); } void JammerView::focus() { @@ -153,6 +195,104 @@ void JammerView::on_retune(const rf::Frequency freq, const uint32_t range) { text_range_number.set(to_string_dec_uint(range, 2)); } } + +void JammerView::set_jammer_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration) { + jammer_channels[i].enabled = true; + jammer_channels[i].width = (width * 0xFFFFFFULL) / 1536000; + jammer_channels[i].center = center; + jammer_channels[i].duration = 30720 * duration; +} + +extern constexpr Style JammerView::style_val; +extern constexpr Style JammerView::style_cancel; + +void JammerView::start_tx() { + uint32_t c, i = 0; + size_t num_channels; + rf::Frequency start_freq, range_bw, range_bw_sub, ch_width; + bool out_of_ranges = false; + + size_t hop_value = options_hop.selected_index_value(); + + // Disable all channels by default + for (c = 0; c < JAMMER_MAX_CH; c++) + jammer_channels[c].enabled = false; + + // Generate jamming channels with JAMMER_MAX_CH maximum width + // Convert ranges min/max to center/bw + for (size_t r = 0; r < 3; r++) { + + if (range_views[r]->frequency_range.enabled) { + range_bw = abs(range_views[r]->frequency_range.max - range_views[r]->frequency_range.min); + + // Get lower bound + if (range_views[r]->frequency_range.min < range_views[r]->frequency_range.max) + start_freq = range_views[r]->frequency_range.min; + else + start_freq = range_views[r]->frequency_range.max; + + if (range_bw >= JAMMER_CH_WIDTH) { + // Split range in multiple channels + num_channels = 0; + range_bw_sub = range_bw; + + do { + range_bw_sub -= JAMMER_CH_WIDTH; + num_channels++; + } while (range_bw_sub >= JAMMER_CH_WIDTH); + + ch_width = range_bw / num_channels; + + for (c = 0; c < num_channels; c++) { + if (i >= JAMMER_MAX_CH) { + out_of_ranges = true; + break; + } + set_jammer_channel(i, ch_width, start_freq + (ch_width / 2) + (ch_width * c), hop_value); + i++; + } + } else { + // Range fits in a single channel + if (i >= JAMMER_MAX_CH) { + out_of_ranges = true; + } else { + set_jammer_channel(i, range_bw, start_freq + (range_bw / 2), hop_value); + i++; + } + } + } + } + + if (!out_of_ranges && i) { + text_range_total.set("/" + to_string_dec_uint(i, 2)); + + jamming = true; + button_transmit.set_style(&style_cancel); + button_transmit.set_text("STOP"); + + transmitter_model.set_sampling_rate(3072000U); + transmitter_model.set_rf_amp(true); + transmitter_model.set_baseband_bandwidth(3500000U); + transmitter_model.set_tx_gain(47); + transmitter_model.enable(); + + baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value()); + } else { + if (out_of_ranges) + nav_.display_modal("Error", "Jamming bandwidth too large.\nMust be less than 24MHz."); + else + nav_.display_modal("Error", "No range enabled."); + } +} + +void JammerView::stop_tx() { + button_transmit.set_style(&style_val); + button_transmit.set_text("START"); + transmitter_model.disable(); + radio::disable(); + baseband::set_jammer(false, JammerType::TYPE_FSK, 0); + jamming = false; +} JammerView::JammerView( NavigationView& nav @@ -161,20 +301,6 @@ JammerView::JammerView( Rect view_rect = { 0, 3 * 8, 240, 80 }; baseband::run_image(portapack::spi_flash::image_tag_jammer); - static constexpr Style style_val { - .font = font::fixed_8x16, - .background = Color::black(), - .foreground = Color::green(), - }; - - static constexpr Style style_cancel { - .font = font::fixed_8x16, - .background = Color::black(), - .foreground = Color::red(), - }; - - JammerChannel * jammer_channels = (JammerChannel*)shared_memory.bb_data.data; - add_children({ &tab_view, &view_range_a, @@ -193,94 +319,16 @@ JammerView::JammerView( view_range_b.set_parent_rect(view_rect); view_range_c.set_parent_rect(view_rect); - options_type.set_selected_index(2); // Sweep + options_type.set_selected_index(3); // Rand CW options_speed.set_selected_index(3); // 10kHz options_hop.set_selected_index(1); // 50ms button_transmit.set_style(&style_val); - button_transmit.on_select = [this, &nav, jammer_channels](Button&) { - uint32_t c, i = 0; - size_t num_channels; - rf::Frequency start_freq, range_bw, range_bw_sub, ch_width; - bool out_of_ranges = false; - - if (jamming) { - button_transmit.set_style(&style_val); - button_transmit.set_text("START"); - transmitter_model.disable(); - radio::disable(); - baseband::set_jammer(false, JammerType::TYPE_FSK, 0); - jamming = false; - } else { - - // Disable all ranges by default - for (c = 0; c < JAMMER_MAX_CH; c++) - jammer_channels[c].enabled = false; - - // Generate jamming "channels", maximum: JAMMER_MAX_CH - // Convert ranges min/max to center/bw - for (size_t r = 0; r < 3; r++) { - - if (range_views[r]->frequency_range.enabled) { - range_bw = abs(range_views[r]->frequency_range.max - range_views[r]->frequency_range.min); - - // Sort - if (range_views[r]->frequency_range.min < range_views[r]->frequency_range.max) - start_freq = range_views[r]->frequency_range.min; - else - start_freq = range_views[r]->frequency_range.max; - - if (range_bw >= JAMMER_CH_WIDTH) { - num_channels = 0; - range_bw_sub = range_bw; - do { - range_bw_sub -= JAMMER_CH_WIDTH; - num_channels++; - } while (range_bw_sub >= JAMMER_CH_WIDTH); - ch_width = range_bw / num_channels; - for (c = 0; c < num_channels; c++) { - if (i >= JAMMER_MAX_CH) { - out_of_ranges = true; - break; - } - jammer_channels[i].enabled = true; - jammer_channels[i].width = (ch_width * 0xFFFFFFULL) / 1536000; - jammer_channels[i].center = start_freq + (ch_width / 2) + (ch_width * c); - jammer_channels[i].duration = 30720 * options_hop.selected_index_value(); - i++; - } - } else { - if (i >= JAMMER_MAX_CH) { - out_of_ranges = true; - } else { - jammer_channels[i].enabled = true; - jammer_channels[i].width = (range_bw * 0xFFFFFFULL) / 1536000; - jammer_channels[i].center = start_freq + (range_bw / 2); - jammer_channels[i].duration = 30720 * options_hop.selected_index_value(); - i++; - } - } - } - } - - if (!out_of_ranges) { - text_range_total.set("/" + to_string_dec_uint(i, 2)); - - jamming = true; - button_transmit.set_style(&style_cancel); - button_transmit.set_text("STOP"); - - transmitter_model.set_sampling_rate(3072000U); - transmitter_model.set_rf_amp(true); - transmitter_model.set_baseband_bandwidth(3500000U); - transmitter_model.set_tx_gain(47); - transmitter_model.enable(); - - baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value()); - } else { - nav.display_modal("Error", "Jamming bandwidth too large.\nMust be less than 24MHz."); - } - } + button_transmit.on_select = [this](Button&) { + if (jamming) + stop_tx(); + else + start_tx(); }; } diff --git a/firmware/application/ui_jammer.hpp b/firmware/application/ui_jammer.hpp index 86416acc..29d20bdc 100644 --- a/firmware/application/ui_jammer.hpp +++ b/firmware/application/ui_jammer.hpp @@ -38,12 +38,18 @@ public: RangeView(NavigationView& nav); void focus() override; + void paint(Painter&) override; - jammer_range_t frequency_range { }; + jammer_range_t frequency_range { false, 0, 0 }; private: - void update_button(Button& button, rf::Frequency f); - void update_range(); + void update_min(rf::Frequency f); + void update_max(rf::Frequency f); + void update_center(rf::Frequency f); + void update_width(uint32_t w); + + uint32_t width { }; + rf::Frequency center { }; static constexpr Style style_info { .font = font::fixed_8x16, @@ -55,17 +61,17 @@ private: // GSM900 Orange { true, 935000000, 945000000 }, // BW:10M // GSM1800 Orange - { false, 1808000000, 1832000000 }, // BW:24M + { true, 1808000000, 1832000000 }, // BW:24M // GSM900 SFR { true, 950000000, 960000000 }, // BW:10M // GSM1800 SFR - { false, 1832000000, 1853000000 }, // BW:21M + { true, 1832000000, 1853000000 }, // BW:21M // GSM900 Bouygues { true, 925000000, 935000000 }, // BW:10M // GSM1800 Bouygues - { false, 1858000000, 1880000000 }, // BW:22M + { true, 1858000000, 1880000000 }, // BW:22M // GSM900 Free { true, 945000000, 950000000 }, // BW:5M @@ -88,7 +94,7 @@ private: // GPS L1 { true, 1575420000 - 500000, 1575420000 + 500000 }, // BW: 1MHz // GPS L2 - { false, 1227600000 - 1000000, 1227600000 + 1000000 }, // BW: 2MHz + { true, 1227600000 - 1000000, 1227600000 + 1000000 }, // BW: 2MHz // WLAN 2.4G CH1 { true, 2412000000 - 11000000, 2412000000 + 11000000}, // BW: 22MHz @@ -119,20 +125,22 @@ private: }; Labels labels { - { { 2 * 8, 5 * 8 }, "Preset:", Color::light_grey() }, - { { 5 * 8, 9 * 8 }, "Start:", Color::light_grey() }, - { { 6 * 8, 13 * 8 }, "Stop:", Color::light_grey() }, + { { 1 * 8, 4 * 8 }, "Preset:", Color::light_grey() }, + { { 2 * 8, 9 * 8 + 4 }, "Start", Color::light_grey() }, + { { 23 * 8, 9 * 8 + 4 }, "Stop", Color::light_grey() }, + { { 12 * 8, 6 * 8 }, "Center", Color::light_grey() }, + { { 12 * 8 + 4, 14 * 8 }, "Width", Color::light_grey() } }; Checkbox check_enabled { - { 44, 1 * 8 }, + { 7 * 8, 4 }, 12, "Enable range", false }; OptionsField options_preset { - { 9 * 8, 5 * 8 }, + { 9 * 8, 4 * 8 }, 19, { { "GSM900 Orange FR", 0 }, @@ -141,41 +149,44 @@ private: { "GSM1800 SFR FR", 3 }, { "GSM900 Bouygues FR", 4 }, { "GSM1800 Bouygues FR", 5 }, - { "GSM Free FR ", 6 }, - { "GSM-R FR ", 7 }, - { "DECT ", 8 }, - { "Optifib ", 9 }, - { "ISM 433 ", 10 }, - { "ISM 868 ", 11 }, - { "GPS L1 ", 12 }, - { "GPS L2 ", 13 }, - { "WLAN 2.4G CH1 ", 14 }, - { "WLAN 2.4G CH2 ", 15 }, - { "WLAN 2.4G CH3 ", 16 }, - { "WLAN 2.4G CH4 ", 17 }, - { "WLAN 2.4G CH5 ", 18 }, - { "WLAN 2.4G CH6 ", 19 }, - { "WLAN 2.4G CH7 ", 20 }, - { "WLAN 2.4G CH8 ", 21 }, - { "WLAN 2.4G CH9 ", 22 }, - { "WLAN 2.4G CH10 ", 23 }, - { "WLAN 2.4G CH11 ", 24 }, - { "WLAN 2.4G CH12 ", 25 }, - { "WLAN 2.4G CH13 ", 26 } + { "GSM Free FR", 6 }, + { "GSM-R FR", 7 }, + { "DECT", 8 }, + { "Optifib", 9 }, + { "ISM 433", 10 }, + { "ISM 868", 11 }, + { "GPS L1", 12 }, + { "GPS L2", 13 }, + { "WLAN 2.4G CH1", 14 }, + { "WLAN 2.4G CH2", 15 }, + { "WLAN 2.4G CH3", 16 }, + { "WLAN 2.4G CH4", 17 }, + { "WLAN 2.4G CH5", 18 }, + { "WLAN 2.4G CH6", 19 }, + { "WLAN 2.4G CH7", 20 }, + { "WLAN 2.4G CH8", 21 }, + { "WLAN 2.4G CH9", 22 }, + { "WLAN 2.4G CH10", 23 }, + { "WLAN 2.4G CH11", 24 }, + { "WLAN 2.4G CH12", 25 }, + { "WLAN 2.4G CH13", 26 } } }; Button button_min { - { 13 * 8, 4 * 16, 120, 28 }, + { 0 * 8, 6 * 16, 11 * 8, 28 }, "" }; Button button_max { - { 13 * 8, 6 * 16, 120, 28 }, + { 19 * 8, 6 * 16, 11 * 8, 28 }, "" }; - - Text text_info { - { 3 * 8, 8 * 16, 25 * 8, 16 }, + Button button_center { + { 76, 4 * 16, 11 * 8, 28 }, + "" + }; + Button button_width { + { 76, 8 * 16, 11 * 8, 28 }, "" }; }; @@ -185,6 +196,11 @@ public: JammerView(NavigationView& nav); ~JammerView(); + JammerView(const JammerView&) = delete; + JammerView(JammerView&&) = delete; + JammerView& operator=(const JammerView&) = delete; + JammerView& operator=(JammerView&&) = delete; + void focus() override; std::string title() const override { return "Jammer"; }; @@ -192,10 +208,25 @@ public: private: NavigationView& nav_; + void start_tx(); + void stop_tx(); + void set_jammer_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration); void on_retune(const rf::Frequency freq, const uint32_t range); + JammerChannel * jammer_channels = (JammerChannel*)shared_memory.bb_data.data; bool jamming { false }; + static constexpr Style style_val { + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::green(), + }; + static constexpr Style style_cancel { + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::red(), + }; + RangeView view_range_a { nav_ }; RangeView view_range_b { nav_ }; RangeView view_range_c { nav_ }; @@ -216,20 +247,21 @@ private: OptionsField options_type { { 9 * 8, 12 * 16 }, - 5, + 8, { - { "FSK ", 0 }, - { "Tone ", 1 }, - { "Sweep", 2 } + { "Rand FSK", 0 }, + { "FM tone", 1 }, + { "CW sweep", 2 }, + { "Rand CW", 3 }, } }; Text text_range_number { - { 18 * 8, 12 * 16, 2 * 8, 16 }, + { 22 * 8, 12 * 16, 2 * 8, 16 }, "--" }; Text text_range_total { - { 20 * 8, 12 * 16, 3 * 8, 16 }, + { 24 * 8, 12 * 16, 3 * 8, 16 }, "/--" }; @@ -260,7 +292,7 @@ private: }; Button button_transmit { - { 1 * 8, 16 * 16, 80, 48 }, + { 9 * 8, 16 * 16, 96, 48 }, "START" };