mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2024-12-13 11:44:31 +00:00
GeoMap and Jammer clean up
Jammer ranges can now be set with center and width GeoMap can be moved with touch GeoMap negative coordinates bugfix Replay app throws error if no files found instead of crashing
This commit is contained in:
parent
1a6d80cd10
commit
cb880258fb
@ -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) {
|
||||
|
@ -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_;
|
||||
|
@ -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"
|
||||
};
|
||||
};
|
||||
|
@ -22,7 +22,6 @@
|
||||
|
||||
#include "ui_geomap.hpp"
|
||||
|
||||
#include "adsb.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
#include <cstring>
|
||||
@ -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<ui::Color, 240> 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<ui::Color, 240> 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_);
|
||||
|
@ -29,9 +29,14 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
enum GeoMapMode {
|
||||
DISPLAY,
|
||||
PROMPT
|
||||
};
|
||||
|
||||
class GeoPos : public View {
|
||||
public:
|
||||
std::function<void(void)> on_change { };
|
||||
std::function<void(int32_t, float, float)> 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<void(float, float)> 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<void(int32_t, float, float)> on_done { };
|
||||
|
||||
const Dim banner_height = 3 * 16;
|
||||
NavigationView& nav_;
|
||||
const std::function<void(int32_t, float, float)> 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"
|
||||
|
@ -26,12 +26,6 @@
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
|
||||
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<FrequencyKeypadView>(*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<FrequencyKeypadView>(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<FrequencyKeypadView>(*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<FrequencyKeypadView>(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<FrequencyKeypadView>(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<FrequencyKeypadView>(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();
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user