mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 02:29:29 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea8563cb19 | |||
| b3c1c83677 | |||
| fcbc3b4d75 | |||
| 39e1a0ffe8 | |||
| 99bbd8805c | |||
| e7af85ffba | |||
| 75f3277102 | |||
| 216f8b64bf | |||
| bc301c5fdb | |||
| ae9d6de093 | |||
| 7a4c3184a1 | |||
| 58307aee9c | |||
| 409242507c | |||
| 0a8194fa10 | |||
| eaac40df3a | |||
| 58b488b840 |
@@ -100,7 +100,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: build/firmware/portapack-mayhem.ppfw.tar
|
||||
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: mayhem_nightly_${{ steps.version_date.outputs.date }}_OCI.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Firmware Asset
|
||||
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: build/firmware/portapack-mayhem.ppfw.tar
|
||||
asset_path: build/firmware/portapack-mayhem_OCI.ppfw.tar
|
||||
asset_name: mayhem_${{ steps.version.outputs.version }}_OCI.ppfw.tar
|
||||
asset_content_type: application/x-tar
|
||||
- name: Upload Firmware Asset
|
||||
|
||||
@@ -46,7 +46,6 @@ if ("${VERSION}" STREQUAL "")
|
||||
set(VERSION_NOHASH "dev")
|
||||
else()
|
||||
set(VERSION_NOHASH "${VERSION}")
|
||||
set(PPFW_FILENAME "portapack-mayhem.ppfw.tar")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
@@ -55,16 +54,6 @@ execute_process(
|
||||
)
|
||||
set(VERSION_MD5 "0x${VERSION_MD5}")
|
||||
|
||||
if (NOT DEFINED PPFW_FILENAME)
|
||||
# Funny business here: trying to generate unique names to prevent renaming of shared test-drive builds by the browser on download to .ppfw-42.tar as it wont be recognized by the flasher
|
||||
execute_process(
|
||||
COMMAND date "+%y%m%d-%H%M"
|
||||
OUTPUT_VARIABLE PPFW_FILENAME
|
||||
)
|
||||
string(STRIP ${PPFW_FILENAME} PPFW_FILENAME)
|
||||
set(PPFW_FILENAME "portapack-mayhem_${PPFW_FILENAME}_OCI.ppfw.tar")
|
||||
endif()
|
||||
|
||||
message("Building version: ${VERSION} MD5: ${VERSION_MD5}")
|
||||
|
||||
set(LICENSE_PATH ${CMAKE_CURRENT_LIST_DIR}/LICENSE)
|
||||
|
||||
@@ -35,6 +35,7 @@ set(LZ4 lz4)
|
||||
|
||||
set(FIRMWARE_NAME portapack-h1_h2-mayhem)
|
||||
set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin)
|
||||
set(PPFW_FILENAME "portapack-mayhem_OCI.ppfw.tar")
|
||||
|
||||
# In our current build container cmake need a little help to get the version :)
|
||||
if(NOT DEFINED ${CMAKE_CXX_COMPILER_VERSION})
|
||||
@@ -100,7 +101,7 @@ add_custom_command(
|
||||
COMMAND mkdir -p firmware_tar/APPS
|
||||
COMMAND cp application/*.ppma firmware_tar/APPS
|
||||
COMMAND cd firmware_tar && tar -cvaf ../${PPFW_FILENAME} *
|
||||
DEPENDS firmware
|
||||
DEPENDS firmware ${FIRMWARE_FILENAME}
|
||||
# Dont use VERBATIM here as it prevents usage of globbing (*)
|
||||
# There shouldnt be any funny business in the filenames above :)
|
||||
)
|
||||
|
||||
@@ -116,7 +116,7 @@ set(CSRC
|
||||
usb_serial_cdc.c
|
||||
usb_serial_descriptor.c
|
||||
usb_serial_endpoints.c
|
||||
usb_serial_io.c
|
||||
usb_serial_device_to_host.c
|
||||
${HACKRF_PATH}/firmware/common/usb.c
|
||||
${HACKRF_PATH}/firmware/common/usb_queue.c
|
||||
${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c
|
||||
@@ -207,6 +207,7 @@ set(CPPSRC
|
||||
usb_serial_event.cpp
|
||||
usb_serial_thread.cpp
|
||||
usb_serial.cpp
|
||||
usb_serial_host_to_device.cpp
|
||||
qrcodegen.cpp
|
||||
radio.cpp
|
||||
receiver_model.cpp
|
||||
|
||||
@@ -40,7 +40,7 @@ using namespace portapack;
|
||||
|
||||
namespace {
|
||||
fs::path get_settings_path(const std::string& app_name) {
|
||||
return fs::path{u"/SETTINGS"} / app_name + u".ini";
|
||||
return fs::path{SETTINGS_DIR} / app_name + u".ini";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -156,7 +156,7 @@ bool save_settings(std::string_view store_name, const SettingBindings& bindings)
|
||||
File f;
|
||||
auto path = get_settings_path(std::string{store_name});
|
||||
|
||||
make_new_directory(SETTINGS_DIR);
|
||||
ensure_directory(SETTINGS_DIR);
|
||||
auto error = f.create(path);
|
||||
if (error)
|
||||
return false;
|
||||
|
||||
@@ -50,7 +50,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
|
||||
this->firmware_selected(path);
|
||||
}});
|
||||
}
|
||||
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.ppfw.tar")) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(firmware_folder, u"*.tar")) {
|
||||
auto filename = entry.path().filename();
|
||||
auto path = entry.path().native();
|
||||
|
||||
@@ -108,7 +108,7 @@ std::filesystem::path FlashUtilityView::extract_tar(std::filesystem::path::strin
|
||||
}
|
||||
|
||||
void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) {
|
||||
if (endsWith(path, u".ppfw.tar")) {
|
||||
if (endsWith(path, u".tar")) {
|
||||
// extract, then update
|
||||
path = extract_tar(u'/' + path).native();
|
||||
if (path.empty()) return;
|
||||
|
||||
@@ -54,8 +54,6 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
#define RECON_CFG_FILE u"SETTINGS/recon.cfg"
|
||||
|
||||
enum class recon_mode : uint8_t {
|
||||
Recon,
|
||||
Scanner,
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_touch_calibration.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
@@ -42,6 +43,7 @@ namespace fs = std::filesystem;
|
||||
#include "string_format.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "cpld_update.hpp"
|
||||
#include "config_mode.hpp"
|
||||
|
||||
namespace pmem = portapack::persistent_memory;
|
||||
|
||||
@@ -628,6 +630,58 @@ void SetEncoderDialView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* AppSettingsView ************************************/
|
||||
|
||||
AppSettingsView::AppSettingsView(
|
||||
NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&labels,
|
||||
&menu_view});
|
||||
|
||||
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
|
||||
|
||||
ensure_directory(SETTINGS_DIR);
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(SETTINGS_DIR, u"*.ini")) {
|
||||
auto path = (std::filesystem::path)SETTINGS_DIR / entry.path();
|
||||
|
||||
menu_view.add_item({path.filename().string().substr(0, 26),
|
||||
ui::Color::dark_cyan(),
|
||||
&bitmap_icon_file_text,
|
||||
[this, path](KeyEvent) {
|
||||
nav_.push<TextEditorView>(path);
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
void AppSettingsView::focus() {
|
||||
menu_view.focus();
|
||||
}
|
||||
|
||||
/* SetConfigModeView ************************************/
|
||||
|
||||
SetConfigModeView::SetConfigModeView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&checkbox_config_mode_enabled,
|
||||
&button_save,
|
||||
&button_cancel});
|
||||
|
||||
checkbox_config_mode_enabled.set_value(!pmem::config_disable_config_mode());
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
pmem::set_config_disable_config_mode(!checkbox_config_mode_enabled.value());
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void SetConfigModeView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SettingsMenuView **************************************/
|
||||
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
@@ -635,17 +689,19 @@ SettingsMenuView::SettingsMenuView(NavigationView& nav) {
|
||||
add_items({{"..", ui::Color::light_grey(), &bitmap_icon_previous, [&nav]() { nav.pop(); }}});
|
||||
}
|
||||
add_items({
|
||||
{"App Settings", ui::Color::dark_cyan(), &bitmap_icon_notepad, [&nav]() { nav.push<AppSettingsView>(); }},
|
||||
{"Audio", ui::Color::dark_cyan(), &bitmap_icon_speaker, [&nav]() { nav.push<SetAudioView>(); }},
|
||||
{"Calibration", ui::Color::dark_cyan(), &bitmap_icon_options_touch, [&nav]() { nav.push<TouchCalibrationView>(); }},
|
||||
{"Config Mode", ui::Color::dark_cyan(), &bitmap_icon_clk_ext, [&nav]() { nav.push<SetConfigModeView>(); }},
|
||||
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetConverterSettingsView>(); }},
|
||||
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [&nav]() { nav.push<SetDateTimeView>(); }},
|
||||
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [&nav]() { nav.push<SetEncoderDialView>(); }},
|
||||
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetFrequencyCorrectionView>(); }},
|
||||
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [&nav]() { nav.push<SetPersistentMemoryView>(); }},
|
||||
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
|
||||
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [&nav]() { nav.push<SetRadioView>(); }},
|
||||
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav]() { nav.push<SetUIView>(); }},
|
||||
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [&nav]() { nav.push<SetSDCardView>(); }},
|
||||
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [&nav]() { nav.push<SetUIView>(); }},
|
||||
{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [&nav]() { nav.push<SetQRCodeView>(); }},
|
||||
});
|
||||
set_max_rows(2); // allow wider buttons
|
||||
}
|
||||
|
||||
@@ -564,6 +564,53 @@ class SetPersistentMemoryView : public View {
|
||||
};
|
||||
};
|
||||
|
||||
class AppSettingsView : public View {
|
||||
public:
|
||||
AppSettingsView(NavigationView& nav);
|
||||
std::string title() const override { return "App Settings"; };
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
|
||||
Labels labels{
|
||||
{{0, 4}, "Select file to edit:", Color::white()}};
|
||||
|
||||
MenuView menu_view{
|
||||
{0, 2 * 8, 240, 26 * 8},
|
||||
true};
|
||||
};
|
||||
|
||||
class SetConfigModeView : public View {
|
||||
public:
|
||||
SetConfigModeView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Config Mode"; };
|
||||
|
||||
private:
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Controls whether firmware", Color::light_grey()},
|
||||
{{1 * 8, 2 * 16}, "will enter Config Mode", Color::light_grey()},
|
||||
{{1 * 8, 3 * 16}, "after a boot failure.", Color::light_grey()},
|
||||
};
|
||||
|
||||
Checkbox checkbox_config_mode_enabled{
|
||||
{2 * 8, 6 * 16},
|
||||
16,
|
||||
"Config Mode enable"};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SettingsMenuView : public BtnGridView {
|
||||
public:
|
||||
SettingsMenuView(NavigationView& nav);
|
||||
|
||||
@@ -27,15 +27,18 @@
|
||||
void config_mode_blink_until_dfu();
|
||||
|
||||
void config_mode_set() {
|
||||
portapack::persistent_memory::set_config_mode_storage(CONFIG_MODE_GUARD_VALUE);
|
||||
portapack::persistent_memory::set_config_mode_storage_direct(CONFIG_MODE_GUARD_VALUE);
|
||||
}
|
||||
|
||||
bool config_mode_should_enter() {
|
||||
return portapack::persistent_memory::config_mode_storage() == CONFIG_MODE_GUARD_VALUE;
|
||||
if (portapack::persistent_memory::config_disable_config_mode_direct())
|
||||
return false;
|
||||
else
|
||||
return portapack::persistent_memory::config_mode_storage_direct() == CONFIG_MODE_GUARD_VALUE;
|
||||
}
|
||||
|
||||
void config_mode_clear() {
|
||||
portapack::persistent_memory::set_config_mode_storage(CONFIG_MODE_NORMAL_VALUE);
|
||||
portapack::persistent_memory::set_config_mode_storage_direct(CONFIG_MODE_NORMAL_VALUE);
|
||||
}
|
||||
|
||||
uint32_t blink_patterns[] = {
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
void config_mode_set();
|
||||
bool config_mode_should_enter();
|
||||
void config_mode_clear();
|
||||
void config_mode_enable(bool v);
|
||||
bool config_mode_disabled();
|
||||
|
||||
void config_mode_run();
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ bool memory_dump(uint32_t* addr_start, uint32_t num_words, bool stack_flag) {
|
||||
int n{0};
|
||||
bool data_found{false};
|
||||
|
||||
make_new_directory(debug_dir);
|
||||
ensure_directory(debug_dir);
|
||||
filename = next_filename_matching_pattern(debug_dir + "/" + (stack_flag ? "STACK" : "MEMORY") + "_DUMP_????.TXT");
|
||||
error = filename.empty();
|
||||
if (!error)
|
||||
|
||||
@@ -164,9 +164,8 @@ void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
handle_rtc_tick();
|
||||
}
|
||||
|
||||
if (events & EVT_MASK_USB) {
|
||||
handle_usb();
|
||||
}
|
||||
handle_usb_transfer();
|
||||
handle_usb();
|
||||
|
||||
if (events & EVT_MASK_SWITCHES) {
|
||||
handle_switches();
|
||||
@@ -225,6 +224,10 @@ void EventDispatcher::handle_usb() {
|
||||
portapack::usb_serial.dispatch();
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_usb_transfer() {
|
||||
portapack::usb_serial.dispatch_transfer();
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_shell() {
|
||||
if (waiting_for_shellmode) {
|
||||
waiting_for_shellmode = false;
|
||||
|
||||
@@ -122,6 +122,7 @@ class EventDispatcher {
|
||||
void handle_local_queue();
|
||||
void handle_rtc_tick();
|
||||
void handle_usb();
|
||||
void handle_usb_transfer();
|
||||
void handle_shell();
|
||||
|
||||
static ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event);
|
||||
|
||||
@@ -177,7 +177,7 @@ enum class PortaPackModel {
|
||||
static bool save_config(int8_t value) {
|
||||
persistent_memory::set_config_cpld(value);
|
||||
if (sd_card::status() == sd_card::Status::Mounted) {
|
||||
make_new_directory("/hardware");
|
||||
ensure_directory("/hardware");
|
||||
File file;
|
||||
auto sucess = file.create("/hardware/settings.txt");
|
||||
if (!sucess.is_valid()) {
|
||||
|
||||
@@ -65,6 +65,7 @@ GeoPos::GeoPos(
|
||||
set_lon(0);
|
||||
|
||||
const auto changed_fn = [this](int32_t) {
|
||||
// Convert degrees/minutes/seconds fields to decimal (floating point) lat/lon degree
|
||||
float lat_value = lat();
|
||||
float lon_value = lon();
|
||||
|
||||
@@ -84,6 +85,27 @@ GeoPos::GeoPos(
|
||||
field_lon_minutes.on_change = changed_fn;
|
||||
field_lon_seconds.on_change = changed_fn;
|
||||
|
||||
const auto wrapped_lat_seconds = [this](int32_t v) {
|
||||
field_lat_minutes.on_encoder(v);
|
||||
};
|
||||
|
||||
const auto wrapped_lat_minutes = [this](int32_t v) {
|
||||
field_lat_degrees.on_encoder((field_lat_degrees.value() >= 0) ? v : -v);
|
||||
};
|
||||
|
||||
const auto wrapped_lon_seconds = [this](int32_t v) {
|
||||
field_lon_minutes.on_encoder(v);
|
||||
};
|
||||
|
||||
const auto wrapped_lon_minutes = [this](int32_t v) {
|
||||
field_lon_degrees.on_encoder((field_lon_degrees.value() >= 0) ? v : -v);
|
||||
};
|
||||
|
||||
field_lat_seconds.on_wrap = wrapped_lat_seconds;
|
||||
field_lat_minutes.on_wrap = wrapped_lat_minutes;
|
||||
field_lon_seconds.on_wrap = wrapped_lon_seconds;
|
||||
field_lon_minutes.on_wrap = wrapped_lon_minutes;
|
||||
|
||||
text_alt_unit.set(altitude_unit_ ? "m" : "ft");
|
||||
if (speed_unit_ == KMPH) text_speed_unit.set("kmph");
|
||||
if (speed_unit_ == MPH) text_speed_unit.set("mph");
|
||||
@@ -173,12 +195,8 @@ bool GeoMap::on_encoder(const EncoderEvent delta) {
|
||||
if (map_zoom == -2) {
|
||||
map_zoom = 1;
|
||||
} else {
|
||||
map_zoom++;
|
||||
|
||||
// When zooming in, ensure that MOD(240,map_zoom)==0 for the map_zoom_line() function
|
||||
if ((map_zoom > 1) && (geomap_rect_width % map_zoom != 0)) {
|
||||
map_zoom--;
|
||||
}
|
||||
// zoom in faster after exceeding the map resolution limit
|
||||
map_zoom += (map_zoom >= MAP_ZOOM_RESOLUTION_LIMIT) ? map_zoom : 1;
|
||||
}
|
||||
}
|
||||
} else if (delta < 0) {
|
||||
@@ -186,15 +204,21 @@ bool GeoMap::on_encoder(const EncoderEvent delta) {
|
||||
if (map_zoom == 1) {
|
||||
map_zoom = -2;
|
||||
} else {
|
||||
map_zoom--;
|
||||
if (map_zoom > MAP_ZOOM_RESOLUTION_LIMIT)
|
||||
map_zoom /= 2;
|
||||
else
|
||||
map_zoom--;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
map_visible = map_opened && (map_zoom <= MAP_ZOOM_RESOLUTION_LIMIT);
|
||||
zoom_pixel_offset = (map_visible && (map_zoom > 1)) ? (float)map_zoom / 2 : 0.0f;
|
||||
|
||||
// Trigger map redraw
|
||||
markerListUpdated = true;
|
||||
redraw_map = true;
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
@@ -208,7 +232,9 @@ void GeoMap::map_read_line(ui::Color* buffer, uint16_t pixels) {
|
||||
// Zoom in: Expand each pixel to "map_zoom" number of pixels.
|
||||
// Future TODO: Add dithering to smooth out the pixelation.
|
||||
// As long as MOD(width,map_zoom)==0 then we don't need to check buffer overflow case when stretching last pixel;
|
||||
// For 240 width, than means no check is needed for map_zoom values up to 6
|
||||
// For 240 width, than means no check is needed for map_zoom values up to 6.
|
||||
// (Rectangle height must also divide evenly into map_zoom or we get black lines at end of screen)
|
||||
// Note that zooming in results in a map offset of (1/map_zoom) pixels to the right & downward directions (see zoom_pixel_offset).
|
||||
for (int i = (geomap_rect_width / map_zoom) - 1; i >= 0; i--) {
|
||||
for (int j = 0; j < map_zoom; j++) {
|
||||
buffer[(i * map_zoom) + j] = buffer[i];
|
||||
@@ -228,91 +254,152 @@ void GeoMap::map_read_line(ui::Color* buffer, uint16_t pixels) {
|
||||
}
|
||||
|
||||
void GeoMap::draw_markers(Painter& painter) {
|
||||
for (int i = 0; i < markerListLen; ++i) {
|
||||
draw_marker_item(painter, markerList[i], Color::blue(), Color::blue(), Color::magenta());
|
||||
}
|
||||
}
|
||||
|
||||
void GeoMap::draw_marker_item(Painter& painter, GeoMarker& item, const Color color, const Color fontColor, const Color backColor) {
|
||||
const auto r = screen_rect();
|
||||
const ui::Point itemPoint = item_rect_pixel(item);
|
||||
|
||||
if ((itemPoint.x() >= 0) && (itemPoint.x() < r.width()) &&
|
||||
(itemPoint.y() > 10) && (itemPoint.y() < r.height())) // Dont draw within symbol size of top
|
||||
{
|
||||
draw_marker(painter, {itemPoint.x(), itemPoint.y() + r.top()}, item.angle, item.tag, color, fontColor, backColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate screen position of item, adjusted for zoom factor.
|
||||
ui::Point GeoMap::item_rect_pixel(GeoMarker& item) {
|
||||
const auto r = screen_rect();
|
||||
const auto geomap_rect_half_width = r.width() / 2;
|
||||
const auto geomap_rect_half_height = r.height() / 2;
|
||||
|
||||
GeoPoint mapPoint = lat_lon_to_map_pixel(item.lat, item.lon);
|
||||
float x = mapPoint.x - x_pos;
|
||||
float y = mapPoint.y - y_pos;
|
||||
|
||||
if (map_zoom > 1) {
|
||||
x = x * map_zoom + zoom_pixel_offset;
|
||||
y = y * map_zoom + zoom_pixel_offset;
|
||||
} else if (map_zoom < 0) {
|
||||
x = x / (-map_zoom);
|
||||
y = y / (-map_zoom);
|
||||
}
|
||||
|
||||
x += geomap_rect_half_width;
|
||||
y += geomap_rect_half_height;
|
||||
|
||||
return {(int16_t)x, (int16_t)y};
|
||||
}
|
||||
|
||||
// Converts latitude/longitude to pixel coordinates in map file.
|
||||
// (Note that when map_zoom==1, one pixel in map file corresponds to 1 pixel on screen)
|
||||
GeoPoint GeoMap::lat_lon_to_map_pixel(float lat, float lon) {
|
||||
// Using WGS 84/Pseudo-Mercator projection
|
||||
float x = (map_width * (lon + 180) / 360);
|
||||
|
||||
// Latitude calculation based on https://stackoverflow.com/a/10401734/2278659
|
||||
double lat_rad = sin(lat * pi / 180);
|
||||
float y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset));
|
||||
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
// Draw grid in place of map (when zoom-in level is too high).
|
||||
void GeoMap::draw_map_grid() {
|
||||
const auto r = screen_rect();
|
||||
|
||||
for (int i = 0; i < markerListLen; ++i) {
|
||||
GeoMarker& item = markerList[i];
|
||||
double lat_rad = sin(item.lat * pi / 180);
|
||||
int x = (map_width * (item.lon + 180) / 360) - x_pos;
|
||||
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
|
||||
// Grid spacing is just based on zoom at the moment, and centered on screen.
|
||||
// TODO: Maybe align with latitude/longitude seconds instead?
|
||||
int grid_spacing = map_zoom * 2;
|
||||
int x = (r.width() / 2) % grid_spacing;
|
||||
int y = (r.height() / 2) % grid_spacing;
|
||||
|
||||
if (map_zoom > 1) {
|
||||
x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2);
|
||||
y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2);
|
||||
} else if (map_zoom < 0) {
|
||||
x = ((x - (r.width() / 2)) / (-map_zoom)) + (r.width() / 2);
|
||||
y = ((y - (r.height() / 2)) / (-map_zoom)) + (r.height() / 2);
|
||||
}
|
||||
if (map_zoom <= MAP_ZOOM_RESOLUTION_LIMIT)
|
||||
return;
|
||||
|
||||
if ((x >= 0) && (x < r.width()) &&
|
||||
(y > 10) && (y < r.height())) // Dont draw within symbol size of top
|
||||
{
|
||||
ui::Point itemPoint(x, y + r.top());
|
||||
if (y >= 32) { // Dont draw text if it would overlap top
|
||||
// Text and symbol
|
||||
draw_marker(painter, itemPoint, item.angle, item.tag, Color::blue(), Color::blue(), Color::magenta());
|
||||
} else {
|
||||
// Only symbol
|
||||
draw_bearing(itemPoint, item.angle, 10, Color::blue());
|
||||
}
|
||||
}
|
||||
display.fill_rectangle({{0, r.top()}, {r.width(), r.height()}}, Color::black());
|
||||
|
||||
for (uint16_t line = y; line < r.height(); line += grid_spacing) {
|
||||
display.fill_rectangle({{0, r.top() + line}, {r.width(), 1}}, Color::darker_grey());
|
||||
}
|
||||
for (uint16_t column = x; column < r.width(); column += grid_spacing) {
|
||||
display.fill_rectangle({{column, r.top()}, {1, r.height()}}, Color::darker_grey());
|
||||
}
|
||||
}
|
||||
|
||||
void GeoMap::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
std::array<ui::Color, 240> map_line_buffer;
|
||||
std::array<ui::Color, geomap_rect_width> map_line_buffer;
|
||||
int16_t zoom_seek_x, zoom_seek_y;
|
||||
|
||||
// Ony redraw map if it moved by at least 1 pixel
|
||||
// or the markers list was updated
|
||||
int x_diff = abs(x_pos - prev_x_pos);
|
||||
int y_diff = abs(y_pos - prev_y_pos);
|
||||
// Ony redraw map if it moved by at least 1 pixel or the markers list was updated
|
||||
if (map_zoom <= 1) {
|
||||
// Zooming out, or no zoom
|
||||
const int min_diff = abs(map_zoom);
|
||||
if ((int)abs(x_pos - prev_x_pos) >= min_diff)
|
||||
redraw_map = true;
|
||||
else if ((int)abs(y_pos - prev_y_pos) >= min_diff)
|
||||
redraw_map = true;
|
||||
} else {
|
||||
// Zooming in; magnify position differences (utilizing zoom_pixel_offset)
|
||||
if ((int)(abs(x_pos - prev_x_pos) * map_zoom) >= 1)
|
||||
redraw_map = true;
|
||||
else if ((int)(abs(y_pos - prev_y_pos) * map_zoom) >= 1)
|
||||
redraw_map = true;
|
||||
}
|
||||
|
||||
if (markerListUpdated || (x_diff >= 3) || (y_diff >= 3)) {
|
||||
int32_t zoom_seek_x = x_pos;
|
||||
int32_t zoom_seek_y = y_pos;
|
||||
|
||||
// Adjust starting corner position of map per zoom setting
|
||||
if (map_zoom > 1) {
|
||||
zoom_seek_x += (r.width() - (r.width() / map_zoom)) / 2;
|
||||
zoom_seek_y += (r.height() - (r.height() / map_zoom)) / 2;
|
||||
} else if (map_zoom < 0) {
|
||||
zoom_seek_x += (r.width() - (r.width() * (-map_zoom))) / 2;
|
||||
zoom_seek_y += (r.height() - (r.height() * (-map_zoom))) / 2;
|
||||
}
|
||||
|
||||
// Read from map file and display to zoomed scale
|
||||
int duplicate_lines = (map_zoom < 0) ? 1 : map_zoom;
|
||||
for (uint16_t line = 0; line < (r.height() / duplicate_lines); line++) {
|
||||
uint16_t seek_line = zoom_seek_y + ((map_zoom >= 0) ? line : line * (-map_zoom));
|
||||
map_file.seek(4 + ((zoom_seek_x + (map_width * seek_line)) << 1));
|
||||
map_read_line(map_line_buffer.data(), r.width());
|
||||
|
||||
for (uint16_t j = 0; j < duplicate_lines; j++) {
|
||||
display.draw_pixels({0, r.top() + (line * duplicate_lines) + j, r.width(), 1}, map_line_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
prev_x_pos = x_pos;
|
||||
if (redraw_map) {
|
||||
prev_x_pos = x_pos; // Note x_pos/y_pos pixel position in map file now correspond to screen rect CENTER pixel
|
||||
prev_y_pos = y_pos;
|
||||
redraw_map = false;
|
||||
|
||||
// Adjust starting corner position of map per zoom setting;
|
||||
// When zooming in the map should technically by shifted left & up by another map_zoom/2 pixels but
|
||||
// the map_read_line() function doesn't handle that yet so we're adjusting markers instead (see zoom_pixel_offset).
|
||||
if (map_zoom > 1) {
|
||||
zoom_seek_x = x_pos - (float)r.width() / (2 * map_zoom);
|
||||
zoom_seek_y = y_pos - (float)r.height() / (2 * map_zoom);
|
||||
} else {
|
||||
zoom_seek_x = x_pos - (r.width() * abs(map_zoom)) / 2;
|
||||
zoom_seek_y = y_pos - (r.height() * abs(map_zoom)) / 2;
|
||||
}
|
||||
|
||||
if (map_visible) {
|
||||
// Read from map file and display to zoomed scale
|
||||
int duplicate_lines = (map_zoom < 0) ? 1 : map_zoom;
|
||||
for (uint16_t line = 0; line < (r.height() / duplicate_lines); line++) {
|
||||
uint16_t seek_line = zoom_seek_y + ((map_zoom >= 0) ? line : line * (-map_zoom));
|
||||
map_file.seek(4 + ((zoom_seek_x + (map_width * seek_line)) << 1));
|
||||
map_read_line(map_line_buffer.data(), r.width());
|
||||
|
||||
for (uint16_t j = 0; j < duplicate_lines; j++) {
|
||||
display.draw_pixels({0, r.top() + (line * duplicate_lines) + j, r.width(), 1}, map_line_buffer);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No map data or excessive zoom; just draw a grid
|
||||
draw_map_grid();
|
||||
}
|
||||
|
||||
// Draw crosshairs in center in manual panning mode
|
||||
if (manual_panning_) {
|
||||
display.fill_rectangle({r.center() - Point(16, 1), {32, 2}}, Color::red());
|
||||
display.fill_rectangle({r.center() - Point(1, 16), {2, 32}}, Color::red());
|
||||
display.fill_rectangle({r.center() - Point(16, 1) + Point(zoom_pixel_offset, zoom_pixel_offset), {32, 2}}, Color::red());
|
||||
display.fill_rectangle({r.center() - Point(1, 16) + Point(zoom_pixel_offset, zoom_pixel_offset), {2, 32}}, Color::red());
|
||||
}
|
||||
|
||||
// Draw the other markers
|
||||
draw_markers(painter);
|
||||
draw_scale(painter);
|
||||
draw_mypos();
|
||||
markerListUpdated = false;
|
||||
draw_mypos(painter);
|
||||
set_clean();
|
||||
}
|
||||
|
||||
// Draw the marker in the center
|
||||
if (!manual_panning_) {
|
||||
draw_marker(painter, r.center(), angle_, tag_, Color::red(), Color::white(), Color::black());
|
||||
if (!manual_panning_ && !hide_center_marker_) {
|
||||
draw_marker(painter, r.center() + Point(zoom_pixel_offset, zoom_pixel_offset), angle_, tag_, Color::red(), Color::white(), Color::black());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,35 +423,41 @@ bool GeoMap::on_touch(const TouchEvent event) {
|
||||
}
|
||||
|
||||
void GeoMap::move(const float lon, const float lat) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
lon_ = lon;
|
||||
lat_ = lat;
|
||||
|
||||
// Using WGS 84/Pseudo-Mercator projection
|
||||
x_pos = map_width * (lon_ + 180) / 360 - (geomap_rect_width / 2);
|
||||
|
||||
// Latitude calculation based on https://stackoverflow.com/a/10401734/2278659
|
||||
double lat_rad = sin(lat * pi / 180);
|
||||
y_pos = map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset) - 128; // Offset added for the GUI
|
||||
// Calculate x_pos/y_pos in map file corresponding to CENTER pixel of screen rect
|
||||
// (Note there is a 1:1 correspondence between map file pixels and screen pixels when map_zoom=1)
|
||||
GeoPoint mapPoint = lat_lon_to_map_pixel(lat_, lon_);
|
||||
x_pos = mapPoint.x;
|
||||
y_pos = mapPoint.y;
|
||||
|
||||
// Cap position
|
||||
if (x_pos > (map_width - geomap_rect_width))
|
||||
x_pos = map_width - geomap_rect_width;
|
||||
if (y_pos > (map_height + geomap_rect_height))
|
||||
y_pos = map_height - geomap_rect_height;
|
||||
if (x_pos > (map_width - r.width() / 2))
|
||||
x_pos = map_width - r.width() / 2;
|
||||
if (y_pos > (map_height + r.height() / 2))
|
||||
y_pos = map_height - r.height() / 2;
|
||||
|
||||
// Scale calculation
|
||||
float km_per_deg_lon = cos(lat * pi / 180) * 111.321; // 111.321 km/deg longitude at equator, and 0 km at poles
|
||||
pixels_per_km = (geomap_rect_width / 2) / km_per_deg_lon;
|
||||
pixels_per_km = (r.width() / 2) / km_per_deg_lon;
|
||||
}
|
||||
|
||||
bool GeoMap::init() {
|
||||
auto result = map_file.open("ADSB/world_map.bin");
|
||||
if (result.is_valid())
|
||||
return false;
|
||||
map_opened = !result.is_valid();
|
||||
|
||||
map_file.read(&map_width, 2);
|
||||
map_file.read(&map_height, 2);
|
||||
if (map_opened) {
|
||||
map_file.read(&map_width, 2);
|
||||
map_file.read(&map_height, 2);
|
||||
} else {
|
||||
map_width = 32768;
|
||||
map_height = 32768;
|
||||
}
|
||||
|
||||
map_visible = map_opened;
|
||||
map_center_x = map_width >> 1;
|
||||
map_center_y = map_height >> 1;
|
||||
|
||||
@@ -375,7 +468,7 @@ bool GeoMap::init() {
|
||||
map_world_lon = map_width / (2 * pi);
|
||||
map_offset = (map_world_lon / 2 * log((1 + map_bottom) / (1 - map_bottom)));
|
||||
|
||||
return true;
|
||||
return map_opened;
|
||||
}
|
||||
|
||||
void GeoMap::set_mode(GeoMapMode mode) {
|
||||
@@ -391,21 +484,34 @@ bool GeoMap::manual_panning() {
|
||||
}
|
||||
|
||||
void GeoMap::draw_scale(Painter& painter) {
|
||||
uint16_t km = 800;
|
||||
uint16_t scale_width = (map_zoom > 0) ? km * pixels_per_km * map_zoom : km * pixels_per_km / (-map_zoom);
|
||||
const auto r = screen_rect();
|
||||
uint32_t m = 800000;
|
||||
uint32_t scale_width = (map_zoom > 0) ? m * map_zoom * pixels_per_km : m * pixels_per_km / (-map_zoom);
|
||||
ui::Color scale_color = (map_visible) ? Color::black() : Color::white();
|
||||
std::string km_string;
|
||||
|
||||
while (scale_width > screen_width / 2) {
|
||||
while (scale_width > (uint32_t)r.width() * (1000 / 2)) {
|
||||
scale_width /= 2;
|
||||
km /= 2;
|
||||
m /= 2;
|
||||
}
|
||||
scale_width /= 1000;
|
||||
if (m < 1000) {
|
||||
km_string = to_string_dec_uint(m) + "m";
|
||||
} else {
|
||||
uint32_t km = m / 1000;
|
||||
m -= km * 1000;
|
||||
if (m == 0) {
|
||||
km_string = to_string_dec_uint(km) + " km";
|
||||
} else {
|
||||
km_string = to_string_dec_uint(km) + "." + to_string_dec_uint((m + 50) / 100, 1) + "km";
|
||||
}
|
||||
}
|
||||
|
||||
std::string km_string = to_string_dec_uint(km) + " km";
|
||||
display.fill_rectangle({{r.right() - 5 - (uint16_t)scale_width, r.bottom() - 4}, {(uint16_t)scale_width, 2}}, scale_color);
|
||||
display.fill_rectangle({{r.right() - 5, r.bottom() - 8}, {2, 6}}, scale_color);
|
||||
display.fill_rectangle({{r.right() - 5 - (uint16_t)scale_width, r.bottom() - 8}, {2, 6}}, scale_color);
|
||||
|
||||
display.fill_rectangle({{screen_width - 5 - scale_width, screen_height - 4}, {scale_width, 2}}, Color::black());
|
||||
display.fill_rectangle({{screen_width - 5, screen_height - 8}, {2, 6}}, Color::black());
|
||||
display.fill_rectangle({{screen_width - 5 - scale_width, screen_height - 8}, {2, 6}}, Color::black());
|
||||
|
||||
painter.draw_string({(uint16_t)(screen_width - 25 - scale_width - km_string.length() * 5 / 2), screen_height - 10}, ui::font::fixed_5x8, Color::black(), Color::white(), km_string);
|
||||
painter.draw_string({(uint16_t)(r.right() - 25 - scale_width - km_string.length() * 5 / 2), r.bottom() - 10}, ui::font::fixed_5x8, Color::black(), Color::white(), km_string);
|
||||
}
|
||||
|
||||
void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color) {
|
||||
@@ -422,9 +528,13 @@ void GeoMap::draw_bearing(const Point origin, const uint16_t angle, uint32_t siz
|
||||
|
||||
size--;
|
||||
}
|
||||
|
||||
display.draw_pixel(origin, color); // 1 pixel indicating center pivot point of bearing symbol
|
||||
}
|
||||
|
||||
void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color, const Color fontColor, const Color backColor) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
int tagOffset = 10;
|
||||
if (mode_ == PROMPT) {
|
||||
// Cross
|
||||
@@ -442,45 +552,15 @@ void GeoMap::draw_marker(Painter& painter, const ui::Point itemPoint, const uint
|
||||
tagOffset = 8;
|
||||
}
|
||||
// center tag above point
|
||||
if (itemTag.find_first_not_of(' ') != itemTag.npos) { // only draw tag if we have something other than spaces
|
||||
if ((itemPoint.y() - r.top() >= 32) && (itemTag.find_first_not_of(' ') != itemTag.npos)) { // only draw tag if doesn't overlap top & not just spaces
|
||||
painter.draw_string(itemPoint - Point(((int)itemTag.length() * 8 / 2), 14 + tagOffset),
|
||||
style().font, fontColor, backColor, itemTag);
|
||||
}
|
||||
}
|
||||
|
||||
void GeoMap::draw_mypos() {
|
||||
const auto r = screen_rect();
|
||||
if (my_lat >= 200 || my_lon >= 200) return; // invalid
|
||||
int x = (map_width * (my_lon + 180) / 360) - x_pos;
|
||||
double lat_rad = sin(my_lat * pi / 180);
|
||||
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
|
||||
auto color = Color::yellow();
|
||||
|
||||
if (map_zoom > 1) {
|
||||
x = ((x - (r.width() / 2)) * map_zoom) + (r.width() / 2);
|
||||
y = ((y - (r.height() / 2)) * map_zoom) + (r.height() / 2);
|
||||
} else if (map_zoom < 0) {
|
||||
x = ((x - (r.width() / 2)) / (-map_zoom)) + (r.width() / 2);
|
||||
y = ((y - (r.height() / 2)) / (-map_zoom)) + (r.height() / 2);
|
||||
}
|
||||
|
||||
if ((x >= 0) && (x < r.width()) &&
|
||||
(y > 10) && (y < r.height())) // Dont draw within symbol size of top
|
||||
{
|
||||
ui::Point itemPoint(x, y + r.top());
|
||||
if (mode_ == PROMPT) {
|
||||
// Cross
|
||||
display.fill_rectangle({itemPoint - Point(16, 1), {32, 2}}, color);
|
||||
display.fill_rectangle({itemPoint - Point(1, 16), {2, 32}}, color);
|
||||
} else if (my_angle < 360) {
|
||||
// if we have a valid angle draw bearing
|
||||
draw_bearing(itemPoint, my_angle, 10, color);
|
||||
} else {
|
||||
// draw a small cross
|
||||
display.fill_rectangle({itemPoint - Point(8, 1), {16, 2}}, color);
|
||||
display.fill_rectangle({itemPoint - Point(1, 8), {2, 16}}, color);
|
||||
}
|
||||
}
|
||||
void GeoMap::draw_mypos(Painter& painter) {
|
||||
if ((my_pos.lat < INVALID_LAT_LON) && (my_pos.lon < INVALID_LAT_LON))
|
||||
draw_marker_item(painter, my_pos, Color::yellow());
|
||||
}
|
||||
|
||||
void GeoMap::clear_markers() {
|
||||
@@ -488,20 +568,22 @@ void GeoMap::clear_markers() {
|
||||
}
|
||||
|
||||
MapMarkerStored GeoMap::store_marker(GeoMarker& marker) {
|
||||
const auto r = screen_rect();
|
||||
MapMarkerStored ret;
|
||||
|
||||
// Check if it could be on screen
|
||||
// Only checking one direction to reduce CPU
|
||||
double lat_rad = sin(marker.lat * pi / 180);
|
||||
int x = (map_width * (marker.lon + 180) / 360) - x_pos;
|
||||
int y = (map_height - ((map_world_lon / 2 * log((1 + lat_rad) / (1 - lat_rad))) - map_offset)) - y_pos; // Offset added for the GUI
|
||||
if (false == ((x >= 0) && (x < geomap_rect_width) && (y > 10) && (y < geomap_rect_height))) // Dont draw within symbol size of top
|
||||
{
|
||||
// (Shows more distant planes when zoomed out)
|
||||
GeoPoint mapPoint = lat_lon_to_map_pixel(marker.lat, marker.lon);
|
||||
int x_dist = abs((int)mapPoint.x - (int)x_pos);
|
||||
int y_dist = abs((int)mapPoint.y - (int)y_pos);
|
||||
int zoom_out = (map_zoom < 0) ? -map_zoom : 1;
|
||||
|
||||
if ((x_dist >= (zoom_out * r.width() / 2)) || (y_dist >= (zoom_out * r.height() / 2))) {
|
||||
ret = MARKER_NOT_STORED;
|
||||
} else if (markerListLen < NumMarkerListElements) {
|
||||
markerList[markerListLen] = marker;
|
||||
markerListLen++;
|
||||
markerListUpdated = true;
|
||||
redraw_map = true;
|
||||
ret = MARKER_STORED;
|
||||
} else {
|
||||
ret = MARKER_LIST_FULL;
|
||||
@@ -510,16 +592,17 @@ MapMarkerStored GeoMap::store_marker(GeoMarker& marker) {
|
||||
}
|
||||
|
||||
void GeoMap::update_my_position(float lat, float lon, int32_t altitude) {
|
||||
my_lat = lat;
|
||||
my_lon = lon;
|
||||
my_pos.lat = lat;
|
||||
my_pos.lon = lon;
|
||||
my_altitude = altitude;
|
||||
markerListUpdated = true;
|
||||
redraw_map = true;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void GeoMap::update_my_orientation(uint16_t angle, bool refresh) {
|
||||
my_angle = angle;
|
||||
my_pos.angle = angle;
|
||||
if (refresh) {
|
||||
markerListUpdated = true;
|
||||
redraw_map = true;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
@@ -527,7 +610,7 @@ void GeoMap::update_my_orientation(uint16_t angle, bool refresh) {
|
||||
void GeoMapView::focus() {
|
||||
geopos.focus();
|
||||
|
||||
if (!map_opened)
|
||||
if (!geomap.map_file_opened())
|
||||
nav_.display_modal("No map", "No world_map.bin file in\n/ADSB/ directory", ABORT);
|
||||
}
|
||||
|
||||
@@ -627,8 +710,7 @@ GeoMapView::GeoMapView(
|
||||
|
||||
add_child(&geopos);
|
||||
|
||||
map_opened = geomap.init();
|
||||
if (!map_opened) return;
|
||||
geomap.init();
|
||||
|
||||
setup();
|
||||
|
||||
@@ -660,8 +742,7 @@ GeoMapView::GeoMapView(
|
||||
|
||||
add_child(&geopos);
|
||||
|
||||
map_opened = geomap.init();
|
||||
if (!map_opened) return;
|
||||
geomap.init();
|
||||
|
||||
setup();
|
||||
add_child(&button_ok);
|
||||
|
||||
@@ -29,16 +29,30 @@
|
||||
|
||||
#include "portapack.hpp"
|
||||
|
||||
#define MAX_MAP_ZOOM_IN 5
|
||||
#define MAX_MAP_ZOOM_OUT 10
|
||||
|
||||
namespace ui {
|
||||
|
||||
#define MAX_MAP_ZOOM_IN 4000
|
||||
#define MAX_MAP_ZOOM_OUT 10
|
||||
#define MAP_ZOOM_RESOLUTION_LIMIT 5 // Max zoom-in to show map; rect height & width must divide into this evenly
|
||||
|
||||
#define INVALID_LAT_LON 200
|
||||
#define INVALID_ANGLE 400
|
||||
|
||||
#define GEOMAP_BANNER_HEIGHT (3 * 16)
|
||||
#define GEOMAP_RECT_WIDTH 240
|
||||
#define GEOMAP_RECT_HEIGHT (320 - 16 - GEOMAP_BANNER_HEIGHT)
|
||||
|
||||
enum GeoMapMode {
|
||||
DISPLAY,
|
||||
PROMPT
|
||||
};
|
||||
|
||||
struct GeoPoint {
|
||||
public:
|
||||
float x{0};
|
||||
float y{0};
|
||||
};
|
||||
|
||||
struct GeoMarker {
|
||||
public:
|
||||
float lat{0};
|
||||
@@ -133,13 +147,15 @@ class GeoPos : public View {
|
||||
2,
|
||||
{0, 59},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true};
|
||||
NumberField field_lat_seconds{
|
||||
{13 * 8, 1 * 16},
|
||||
2,
|
||||
{0, 59},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true};
|
||||
Text text_lat_decimal{
|
||||
{17 * 8, 1 * 16, 13 * 8, 1 * 16},
|
||||
""};
|
||||
@@ -155,13 +171,15 @@ class GeoPos : public View {
|
||||
2,
|
||||
{0, 59},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true};
|
||||
NumberField field_lon_seconds{
|
||||
{13 * 8, 2 * 16},
|
||||
2,
|
||||
{0, 59},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true};
|
||||
Text text_lon_decimal{
|
||||
{17 * 8, 2 * 16, 13 * 8, 1 * 16},
|
||||
""};
|
||||
@@ -201,26 +219,40 @@ class GeoMap : public Widget {
|
||||
angle_ = new_angle;
|
||||
}
|
||||
|
||||
bool map_file_opened() { return map_opened; }
|
||||
|
||||
void set_hide_center_marker(bool hide) {
|
||||
hide_center_marker_ = hide;
|
||||
}
|
||||
bool hide_center_marker() { return hide_center_marker_; }
|
||||
|
||||
static const int NumMarkerListElements = 30;
|
||||
|
||||
void clear_markers();
|
||||
MapMarkerStored store_marker(GeoMarker& marker);
|
||||
|
||||
static const Dim banner_height = 3 * 16;
|
||||
static const Dim geomap_rect_width = 240;
|
||||
static const Dim geomap_rect_height = 320 - 16 - banner_height;
|
||||
static const Dim banner_height = GEOMAP_BANNER_HEIGHT;
|
||||
static const Dim geomap_rect_width = GEOMAP_RECT_WIDTH;
|
||||
static const Dim geomap_rect_height = GEOMAP_RECT_HEIGHT;
|
||||
|
||||
private:
|
||||
void draw_scale(Painter& painter);
|
||||
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
|
||||
ui::Point item_rect_pixel(GeoMarker& item);
|
||||
GeoPoint lat_lon_to_map_pixel(float lat, float lon);
|
||||
void draw_marker_item(Painter& painter, GeoMarker& item, const Color color, const Color fontColor = Color::white(), const Color backColor = Color::black());
|
||||
void draw_marker(Painter& painter, const ui::Point itemPoint, const uint16_t itemAngle, const std::string itemTag, const Color color = Color::red(), const Color fontColor = Color::white(), const Color backColor = Color::black());
|
||||
void draw_markers(Painter& painter);
|
||||
void draw_mypos();
|
||||
void draw_mypos(Painter& painter);
|
||||
void draw_bearing(const Point origin, const uint16_t angle, uint32_t size, const Color color);
|
||||
void draw_map_grid();
|
||||
void map_read_line(ui::Color* buffer, uint16_t pixels);
|
||||
|
||||
bool manual_panning_{false};
|
||||
bool hide_center_marker_{false};
|
||||
GeoMapMode mode_{};
|
||||
File map_file{};
|
||||
bool map_opened{};
|
||||
bool map_visible{};
|
||||
uint16_t map_width{}, map_height{};
|
||||
int32_t map_center_x{}, map_center_y{};
|
||||
int16_t map_zoom{1};
|
||||
@@ -229,23 +261,22 @@ class GeoMap : public Widget {
|
||||
double map_world_lon{};
|
||||
double map_offset{};
|
||||
|
||||
int32_t x_pos{}, y_pos{};
|
||||
int32_t prev_x_pos{0xFFFF}, prev_y_pos{0xFFFF};
|
||||
float x_pos{}, y_pos{};
|
||||
float prev_x_pos{32767.0f}, prev_y_pos{32767.0f};
|
||||
float lat_{};
|
||||
float lon_{};
|
||||
float zoom_pixel_offset{0.0f};
|
||||
float pixels_per_km{};
|
||||
uint16_t angle_{};
|
||||
std::string tag_{};
|
||||
|
||||
// the portapack's position data ( for example injected from serial )
|
||||
float my_lat{200};
|
||||
float my_lon{200};
|
||||
GeoMarker my_pos{INVALID_LAT_LON, INVALID_LAT_LON, INVALID_ANGLE, ""}; // lat, lon, angle, tag
|
||||
int32_t my_altitude{0};
|
||||
uint16_t my_angle{400};
|
||||
|
||||
int markerListLen{0};
|
||||
GeoMarker markerList[NumMarkerListElements];
|
||||
bool markerListUpdated{false};
|
||||
bool redraw_map{false};
|
||||
};
|
||||
|
||||
class GeoMapView : public View {
|
||||
@@ -303,8 +334,6 @@ class GeoMapView : public View {
|
||||
uint16_t angle_{};
|
||||
std::function<void(void)> on_close_{nullptr};
|
||||
|
||||
bool map_opened{};
|
||||
|
||||
GeoPos geopos{
|
||||
{0, 0},
|
||||
altitude_unit_,
|
||||
|
||||
@@ -146,7 +146,7 @@ std::vector<AppInfoConsole> NavigationView::fixedAppListFC = {
|
||||
{"lookingglass", "Looking Glass", RX}};
|
||||
|
||||
bool NavigationView::StartAppByName(const char* name) {
|
||||
pop();
|
||||
home(false);
|
||||
if (strcmp(name, "adsbrx") == 0) {
|
||||
push<ADSBRxView>();
|
||||
return true;
|
||||
@@ -570,7 +570,7 @@ void SystemStatusView::rtc_battery_workaround() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
make_new_directory(SETTINGS_DIR);
|
||||
ensure_directory(SETTINGS_DIR);
|
||||
make_new_file(DATE_FILEFLAG);
|
||||
|
||||
year = 1980;
|
||||
@@ -660,6 +660,14 @@ void NavigationView::pop(bool trigger_update) {
|
||||
if (on_pop) on_pop();
|
||||
}
|
||||
|
||||
void NavigationView::home(bool trigger_update) {
|
||||
while (view_stack.size() > 1) {
|
||||
pop(false);
|
||||
}
|
||||
|
||||
if (trigger_update) update_view();
|
||||
}
|
||||
|
||||
void NavigationView::display_modal(
|
||||
const std::string& title,
|
||||
const std::string& message) {
|
||||
|
||||
@@ -91,6 +91,7 @@ class NavigationView : public View {
|
||||
View* push_view(std::unique_ptr<View> new_view);
|
||||
void replace(View* v);
|
||||
void pop(bool trigger_update = true);
|
||||
void home(bool trigger_update);
|
||||
|
||||
void display_modal(const std::string& title, const std::string& message);
|
||||
void display_modal(
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
extern "C" {
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
#include "usb_serial_cdc.h"
|
||||
}
|
||||
|
||||
#include "usb_serial_shell.hpp"
|
||||
#include "usb_serial.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "usb_serial_host_to_device.hpp"
|
||||
|
||||
#include <libopencm3/cm3/common.h>
|
||||
#include <libopencm3/lpc43xx/usb.h>
|
||||
@@ -24,6 +25,7 @@ void USBSerial::initialize() {
|
||||
|
||||
init_serial_usb_driver(&SUSBD1);
|
||||
shellInit();
|
||||
init_host_to_device();
|
||||
}
|
||||
|
||||
void USBSerial::dispatch() {
|
||||
@@ -35,7 +37,11 @@ void USBSerial::dispatch() {
|
||||
create_shell(_eventDispatcher);
|
||||
}
|
||||
|
||||
bulk_out_receive();
|
||||
schedule_host_to_device_transfer();
|
||||
}
|
||||
|
||||
void USBSerial::dispatch_transfer() {
|
||||
complete_host_to_device_transfer();
|
||||
}
|
||||
|
||||
void USBSerial::on_channel_opened() {
|
||||
|
||||
@@ -32,6 +32,7 @@ class USBSerial {
|
||||
public:
|
||||
void initialize();
|
||||
void dispatch();
|
||||
void dispatch_transfer();
|
||||
void on_channel_opened();
|
||||
void on_channel_closed();
|
||||
void setEventDispatcher(EventDispatcher* ed) { _eventDispatcher = ed; }
|
||||
|
||||
@@ -31,11 +31,16 @@ static Thread* thread_usb_event = NULL;
|
||||
|
||||
CH_IRQ_HANDLER(USB0_IRQHandler) {
|
||||
CH_IRQ_PROLOGUE();
|
||||
|
||||
const uint32_t status = USB0_USBSTS_D & USB0_USBINTR_D;
|
||||
|
||||
usb0_isr();
|
||||
|
||||
chSysLockFromIsr();
|
||||
chEvtSignalI(thread_usb_event, EVT_MASK_USB);
|
||||
chSysUnlockFromIsr();
|
||||
if (status & USB0_USBSTS_D_UI) {
|
||||
chSysLockFromIsr();
|
||||
chEvtSignalI(thread_usb_event, EVT_MASK_USB);
|
||||
chSysUnlockFromIsr();
|
||||
}
|
||||
|
||||
CH_IRQ_EPILOGUE();
|
||||
}
|
||||
|
||||
+1
-34
@@ -19,7 +19,7 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
|
||||
#include "usb_serial_endpoints.h"
|
||||
|
||||
@@ -40,39 +40,6 @@
|
||||
|
||||
SerialUSBDriver SUSBD1;
|
||||
|
||||
uint8_t usbBulkBuffer[USBSERIAL_BUFFERS_SIZE];
|
||||
|
||||
void bulk_out_receive(void) {
|
||||
int ret;
|
||||
do {
|
||||
ret = usb_transfer_schedule(
|
||||
&usb_endpoint_bulk_out,
|
||||
&usbBulkBuffer[0],
|
||||
USBSERIAL_BUFFERS_SIZE,
|
||||
serial_bulk_transfer_complete,
|
||||
NULL);
|
||||
|
||||
} while (ret != -1);
|
||||
}
|
||||
|
||||
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred) {
|
||||
(void)user_data;
|
||||
|
||||
chSysLockFromIsr();
|
||||
|
||||
for (unsigned int i = 0; i < bytes_transferred; i++) {
|
||||
msg_t ret;
|
||||
do {
|
||||
ret = chIQPutI(&SUSBD1.iqueue, usbBulkBuffer[i]);
|
||||
if (ret == Q_FULL)
|
||||
chThdSleepMilliseconds(1);
|
||||
|
||||
} while (ret == Q_FULL);
|
||||
}
|
||||
|
||||
chSysUnlockFromIsr();
|
||||
}
|
||||
|
||||
static void onotify(GenericQueue* qp) {
|
||||
SerialUSBDriver* sdp = chQGetLink(qp);
|
||||
uint8_t buff[USBSERIAL_BUFFERS_SIZE];
|
||||
+3
-3
@@ -24,8 +24,10 @@
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#define USB_BULK_BUFFER_SIZE 64
|
||||
|
||||
#ifndef USBSERIAL_BUFFERS_SIZE
|
||||
#define USBSERIAL_BUFFERS_SIZE 400
|
||||
#define USBSERIAL_BUFFERS_SIZE 128
|
||||
#endif
|
||||
|
||||
struct SerialUSBDriverVMT {
|
||||
@@ -46,8 +48,6 @@ typedef struct SerialUSBDriver SerialUSBDriver;
|
||||
extern SerialUSBDriver SUSBD1;
|
||||
|
||||
void init_serial_usb_driver(SerialUSBDriver* sdp);
|
||||
void bulk_out_receive(void);
|
||||
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "usb_serial_host_to_device.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
|
||||
extern "C" {
|
||||
#include <common/usb.h>
|
||||
#include <hackrf_usb/usb_device.h>
|
||||
#include <hackrf_usb/usb_endpoint.h>
|
||||
}
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
static Thread* thread_usb_event = NULL;
|
||||
|
||||
struct usb_bulk_buffer_t {
|
||||
uint8_t* data;
|
||||
size_t length;
|
||||
bool completed;
|
||||
};
|
||||
|
||||
std::queue<usb_bulk_buffer_t*> usb_bulk_buffer_queue;
|
||||
std::queue<usb_bulk_buffer_t*> usb_bulk_buffer_spare;
|
||||
|
||||
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred) {
|
||||
usb_bulk_buffer_t* transfer_data = (usb_bulk_buffer_t*)user_data;
|
||||
|
||||
transfer_data->length = bytes_transferred;
|
||||
transfer_data->completed = true;
|
||||
}
|
||||
|
||||
void init_host_to_device() {
|
||||
thread_usb_event = chThdSelf();
|
||||
}
|
||||
|
||||
void schedule_host_to_device_transfer() {
|
||||
if (usb_bulk_buffer_queue.size() >= 8)
|
||||
return;
|
||||
|
||||
static usb_bulk_buffer_t* transfer_data = nullptr;
|
||||
|
||||
int ret;
|
||||
|
||||
do {
|
||||
if (transfer_data == nullptr) {
|
||||
if (usb_bulk_buffer_spare.empty() == false) {
|
||||
transfer_data = usb_bulk_buffer_spare.front();
|
||||
transfer_data->length = 0;
|
||||
transfer_data->completed = false;
|
||||
usb_bulk_buffer_spare.pop();
|
||||
} else {
|
||||
transfer_data = new usb_bulk_buffer_t{
|
||||
.data = new uint8_t[USB_BULK_BUFFER_SIZE],
|
||||
.length = 0,
|
||||
.completed = false};
|
||||
}
|
||||
}
|
||||
|
||||
ret = usb_transfer_schedule(
|
||||
&usb_endpoint_bulk_out,
|
||||
transfer_data->data,
|
||||
USB_BULK_BUFFER_SIZE,
|
||||
serial_bulk_transfer_complete,
|
||||
transfer_data);
|
||||
|
||||
if (ret != -1) {
|
||||
usb_bulk_buffer_queue.push(transfer_data);
|
||||
transfer_data = nullptr;
|
||||
|
||||
if (usb_bulk_buffer_queue.size() >= 8)
|
||||
return;
|
||||
}
|
||||
} while (ret != -1);
|
||||
}
|
||||
|
||||
void complete_host_to_device_transfer() {
|
||||
for (; !usb_bulk_buffer_queue.empty(); usb_bulk_buffer_queue.pop()) {
|
||||
usb_bulk_buffer_t* transfer_data = usb_bulk_buffer_queue.front();
|
||||
|
||||
while (transfer_data->completed == false)
|
||||
return;
|
||||
|
||||
chSysLock();
|
||||
for (unsigned int i = 0; i < transfer_data->length; i++) {
|
||||
msg_t ret;
|
||||
do {
|
||||
ret = chIQPutI(&SUSBD1.iqueue, transfer_data->data[i]);
|
||||
|
||||
if (ret == Q_FULL) {
|
||||
chSysUnlock();
|
||||
chThdSleepMilliseconds(1); // wait for shell thread when buffer is full
|
||||
chSysLock();
|
||||
}
|
||||
|
||||
} while (ret == Q_FULL);
|
||||
}
|
||||
chSysUnlock();
|
||||
|
||||
usb_bulk_buffer_spare.push(transfer_data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
|
||||
#define USB_BULK_BUFFER_SIZE 64
|
||||
|
||||
void init_host_to_device();
|
||||
void serial_bulk_transfer_complete(void* user_data, unsigned int bytes_transferred);
|
||||
void schedule_host_to_device_transfer();
|
||||
void complete_host_to_device_transfer();
|
||||
@@ -35,8 +35,9 @@
|
||||
#include "portapack_cpld_data.hpp"
|
||||
#include "crc.hpp"
|
||||
#include "hackrf_cpld_data.hpp"
|
||||
#include "performance_counter.hpp"
|
||||
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
#include "chprintf.h"
|
||||
#include "chqueues.h"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
@@ -46,6 +47,8 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "usb_serial_shell_filesystem.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <libopencm3/lpc43xx/wwdt.h>
|
||||
@@ -148,7 +151,7 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
nav->display_modal("Flashing", "Flashing from serial.\rPlease wait!\nDevice will restart.");
|
||||
nav->display_modal("Flashing", "Flashing from serial.\r\nPlease wait!\r\nDevice will restart.");
|
||||
// check file extensions
|
||||
if (strEndsWith(path.native(), u".ppfw.tar")) {
|
||||
// extract tar
|
||||
@@ -916,6 +919,103 @@ static void cmd_gotorientation(BaseSequentialStream* chp, int argc, char* argv[]
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_sysinfo(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: sysinfo\r\n";
|
||||
(void)argv;
|
||||
if (argc > 0) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
auto utilisation = get_cpu_utilisation_in_percent();
|
||||
std::string info =
|
||||
"M0 heap: " + to_string_dec_uint(chCoreStatus()) + "\r\n" +
|
||||
"M0 stack: " + to_string_dec_uint((uint32_t)get_free_stack_space()) + "\r\n" +
|
||||
"M0 cpu%: " + to_string_dec_uint(utilisation) + "\r\n" +
|
||||
"M4 heap: " + to_string_dec_uint(shared_memory.m4_heap_usage) + "\r\n" +
|
||||
"M4 stack: " + to_string_dec_uint(shared_memory.m4_stack_usage) + "\r\n" +
|
||||
"M0 cpu%: " + to_string_dec_uint(shared_memory.m4_performance_counter) + "\r\n" +
|
||||
"M4 miss: " + to_string_dec_uint(shared_memory.m4_buffer_missed) + "\r\n" +
|
||||
"uptime: " + to_string_dec_uint(chTimeNow() / 1000) + "\r\n";
|
||||
|
||||
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)info.c_str(), info.length());
|
||||
return;
|
||||
}
|
||||
|
||||
static void cmd_radioinfo(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: radioinfo\r\n";
|
||||
(void)argv;
|
||||
if (argc > 0) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
std::string info =
|
||||
"receiver_model.target_frequency: " + to_string_dec_uint(portapack::receiver_model.target_frequency()) + "\r\n" +
|
||||
"receiver_model.baseband_bandwidth: " + to_string_dec_uint(portapack::receiver_model.baseband_bandwidth()) + "\r\n" +
|
||||
"receiver_model.sampling_rate: " + to_string_dec_uint(portapack::receiver_model.sampling_rate()) + "\r\n" +
|
||||
"receiver_model.modulation: " + to_string_dec_uint((uint32_t)portapack::receiver_model.modulation()) + "\r\n" +
|
||||
"receiver_model.am_configuration: " + to_string_dec_uint(portapack::receiver_model.am_configuration()) + "\r\n" +
|
||||
"receiver_model.nbfm_configuration: " + to_string_dec_uint(portapack::receiver_model.nbfm_configuration()) + "\r\n" +
|
||||
"receiver_model.wfm_configuration: " + to_string_dec_uint(portapack::receiver_model.wfm_configuration()) + "\r\n" +
|
||||
"transmitter_model.target_frequency: " + to_string_dec_uint(portapack::transmitter_model.target_frequency()) + "\r\n" +
|
||||
"transmitter_model.baseband_bandwidth: " + to_string_dec_uint(portapack::transmitter_model.baseband_bandwidth()) + "\r\n" +
|
||||
"transmitter_model.sampling_rate: " + to_string_dec_uint(portapack::transmitter_model.sampling_rate()) + "\r\n";
|
||||
|
||||
fillOBuffer(&((SerialUSBDriver*)chp)->oqueue, (const uint8_t*)info.c_str(), info.length());
|
||||
return;
|
||||
}
|
||||
|
||||
static void cmd_pmemreset(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: pmemreset yes\r\nThis will reset pmem to defaults!\r\n";
|
||||
(void)argv;
|
||||
if (argc != 1 || strcmp(argv[0], "yes") != 0) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
auto evtd = getEventDispatcherInstance();
|
||||
if (!evtd) return;
|
||||
auto top_widget = evtd->getTopWidget();
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
nav->home(true);
|
||||
|
||||
portapack::persistent_memory::cache::defaults();
|
||||
// system refresh
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static void cmd_settingsreset(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
const char* usage = "usage: settingsreset yes\r\nThis will reset all app settings to defaults!\r\n";
|
||||
(void)argv;
|
||||
if (argc != 1 || strcmp(argv[0], "yes") != 0) {
|
||||
chprintf(chp, usage);
|
||||
return;
|
||||
}
|
||||
auto evtd = getEventDispatcherInstance();
|
||||
if (!evtd) return;
|
||||
auto top_widget = evtd->getTopWidget();
|
||||
if (!top_widget) return;
|
||||
auto nav = static_cast<ui::SystemView*>(top_widget)->get_navigation_view();
|
||||
if (!nav) return;
|
||||
nav->home(true); // to exit all running apps
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(SETTINGS_DIR, u"*.ini")) {
|
||||
if (std::filesystem::is_regular_file(entry.status())) {
|
||||
std::filesystem::path pth = SETTINGS_DIR;
|
||||
pth += u"/" + entry.path();
|
||||
chprintf(chp, pth.string().c_str());
|
||||
chprintf(chp, "\r\n");
|
||||
f_unlink(pth.tchar());
|
||||
}
|
||||
}
|
||||
// system refresh
|
||||
StatusRefreshMessage message{};
|
||||
EventDispatcher::send_message(message);
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
static const ShellCommand commands[] = {
|
||||
{"reboot", cmd_reboot},
|
||||
{"dfu", cmd_dfu},
|
||||
@@ -941,6 +1041,10 @@ static const ShellCommand commands[] = {
|
||||
{"appstart", cmd_appstart},
|
||||
{"gotgps", cmd_gotgps},
|
||||
{"gotorientation", cmd_gotorientation},
|
||||
{"sysinfo", cmd_sysinfo},
|
||||
{"radioinfo", cmd_radioinfo},
|
||||
{"pmemreset", cmd_pmemreset},
|
||||
{"settingsreset", cmd_settingsreset},
|
||||
{NULL, NULL}};
|
||||
|
||||
static const ShellConfig shell_cfg1 = {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "usb_serial_shell_filesystem.hpp"
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
|
||||
#include "chprintf.h"
|
||||
#include "string_format.hpp"
|
||||
@@ -172,6 +172,9 @@ void cmd_sd_close(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto error = shell_file->sync();
|
||||
if (report_on_error(chp, error)) return;
|
||||
|
||||
delete shell_file;
|
||||
shell_file = nullptr;
|
||||
|
||||
@@ -348,19 +351,23 @@ void cmd_sd_write_binary(BaseSequentialStream* chp, int argc, char* argv[]) {
|
||||
return;
|
||||
}
|
||||
|
||||
long size = (int)strtol(argv[0], NULL, 10);
|
||||
size_t size = (size_t)strtol(argv[0], NULL, 10);
|
||||
|
||||
chprintf(chp, "send %d bytes\r\n", size);
|
||||
|
||||
uint8_t buffer;
|
||||
uint8_t buffer[USB_BULK_BUFFER_SIZE];
|
||||
|
||||
for (long i = 0; i < size; i++) {
|
||||
if (chSequentialStreamRead(chp, &buffer, 1) == 0)
|
||||
do {
|
||||
size_t bytes_to_read = size > USB_BULK_BUFFER_SIZE ? USB_BULK_BUFFER_SIZE : size;
|
||||
size_t bytes_read = chSequentialStreamRead(chp, &buffer[0], bytes_to_read);
|
||||
if (bytes_read != bytes_to_read)
|
||||
return;
|
||||
|
||||
auto error = shell_file->write(&buffer, 1);
|
||||
auto error = shell_file->write(&buffer[0], bytes_read);
|
||||
if (report_on_error(chp, error)) return;
|
||||
}
|
||||
|
||||
size -= bytes_read;
|
||||
} while (size > 0);
|
||||
|
||||
chprintf(chp, "ok\r\n");
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "optional.hpp"
|
||||
|
||||
#include "chprintf.h"
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
@@ -145,7 +145,7 @@ struct misc_config_t {
|
||||
bool disable_speaker : 1;
|
||||
bool config_disable_external_tcxo : 1;
|
||||
bool config_sdcard_high_speed_io : 1;
|
||||
bool UNUSED_4 : 1;
|
||||
bool config_disable_config_mode : 1;
|
||||
bool UNUSED_5 : 1;
|
||||
bool UNUSED_6 : 1;
|
||||
bool UNUSED_7 : 1;
|
||||
@@ -156,6 +156,8 @@ struct misc_config_t {
|
||||
};
|
||||
static_assert(sizeof(misc_config_t) == sizeof(uint32_t));
|
||||
|
||||
#define MC_CONFIG_DISABLE_CONFIG_MODE 0x00000010 // config_disable_config_mode bit in struct above
|
||||
|
||||
/* IMPORTANT: Update dump_persistent_memory (below) when changing data_t. */
|
||||
|
||||
/* Struct must pack the same way on M4 and M0 cores.
|
||||
@@ -386,6 +388,7 @@ namespace cache {
|
||||
void defaults() {
|
||||
cached_backup_ram = backup_ram_t();
|
||||
|
||||
// If the desired default is 0/false, then no need to set it here (buffer is initialized to 0)
|
||||
set_config_backlight_timer(backlight_config_t{});
|
||||
set_config_splash(true);
|
||||
set_config_disable_external_tcxo(false);
|
||||
@@ -416,8 +419,9 @@ void init() {
|
||||
const auto switches_state = get_switches_state();
|
||||
|
||||
// ignore for valid check
|
||||
auto config_mode_backup = config_mode_storage();
|
||||
set_config_mode_storage(CONFIG_MODE_NORMAL_VALUE);
|
||||
auto config_mode_backup = config_mode_storage_direct();
|
||||
set_config_mode_storage_direct(CONFIG_MODE_NORMAL_VALUE);
|
||||
|
||||
if (!(switches_state[(size_t)ui::KeyEvent::Left] && switches_state[(size_t)ui::KeyEvent::Right]) && backup_ram->is_valid()) {
|
||||
// Copy valid persistent data into cache.
|
||||
cached_backup_ram = *backup_ram;
|
||||
@@ -433,7 +437,7 @@ void init() {
|
||||
// Copy defaults into cache.
|
||||
defaults();
|
||||
}
|
||||
set_config_mode_storage(config_mode_backup);
|
||||
set_config_mode_storage_direct(config_mode_backup);
|
||||
}
|
||||
|
||||
void persist() {
|
||||
@@ -597,6 +601,10 @@ bool config_disable_external_tcxo() {
|
||||
return data->misc_config.config_disable_external_tcxo;
|
||||
}
|
||||
|
||||
bool config_disable_config_mode() {
|
||||
return data->misc_config.config_disable_config_mode;
|
||||
}
|
||||
|
||||
bool config_sdcard_high_speed_io() {
|
||||
return data->misc_config.config_sdcard_high_speed_io;
|
||||
}
|
||||
@@ -666,6 +674,10 @@ void set_config_disable_external_tcxo(bool v) {
|
||||
data->misc_config.config_disable_external_tcxo = v;
|
||||
}
|
||||
|
||||
void set_config_disable_config_mode(bool v) {
|
||||
data->misc_config.config_disable_config_mode = v;
|
||||
}
|
||||
|
||||
void set_config_sdcard_high_speed_io(bool v, bool save) {
|
||||
if (v) {
|
||||
/* 200MHz / (2 * 2) = 50MHz */
|
||||
@@ -947,12 +959,18 @@ void set_encoder_dial_sensitivity(uint8_t v) {
|
||||
// Recovery mode magic value storage
|
||||
static data_t* data_direct_access = reinterpret_cast<data_t*>(memory::map::backup_ram.base());
|
||||
|
||||
uint32_t config_mode_storage() {
|
||||
uint32_t config_mode_storage_direct() {
|
||||
return data_direct_access->config_mode_storage;
|
||||
}
|
||||
void set_config_mode_storage(uint32_t v) {
|
||||
void set_config_mode_storage_direct(uint32_t v) {
|
||||
data_direct_access->config_mode_storage = v;
|
||||
}
|
||||
bool config_disable_config_mode_direct() {
|
||||
// "return data_direct_access->misc_config.config_disable_config_mode"
|
||||
// Casting as U32 as workaround for misaligned memory access
|
||||
uint32_t misc_config_u32 = *(uint32_t*)&data_direct_access->misc_config;
|
||||
return ((misc_config_u32 & MC_CONFIG_DISABLE_CONFIG_MODE) != 0);
|
||||
}
|
||||
|
||||
// PMem to sdcard settings
|
||||
|
||||
@@ -963,7 +981,7 @@ bool should_use_sdcard_for_pmem() {
|
||||
int save_persistent_settings_to_file() {
|
||||
File outfile;
|
||||
|
||||
make_new_directory(SETTINGS_DIR);
|
||||
ensure_directory(SETTINGS_DIR);
|
||||
auto error = outfile.create(PMEM_SETTING_FILE);
|
||||
if (error)
|
||||
return false;
|
||||
@@ -996,7 +1014,7 @@ bool debug_dump() {
|
||||
std::filesystem::path filename{};
|
||||
File pmem_dump_file{};
|
||||
// create new dump file name and DEBUG directory
|
||||
make_new_directory(debug_dir);
|
||||
ensure_directory(debug_dir);
|
||||
filename = next_filename_matching_pattern(debug_dir + "/DEBUG_DUMP_????.TXT");
|
||||
if (filename.empty()) {
|
||||
painter.draw_string({0, 320 - 16}, ui::Styles::red, "COULD NOT GET DUMP NAME !");
|
||||
@@ -1037,9 +1055,9 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("tone_mix: " + to_string_dec_uint(data->tone_mix));
|
||||
pmem_dump_file.write_line("hardware_config: " + to_string_dec_uint(data->hardware_config));
|
||||
pmem_dump_file.write_line("recon_config: 0x" + to_string_hex(data->recon_config, 16));
|
||||
pmem_dump_file.write_line("recon_repeat_nb: " + to_string_dec_int(data->recon_repeat_nb, 16));
|
||||
pmem_dump_file.write_line("recon_repeat_gain:" + to_string_hex(data->recon_config, 16));
|
||||
pmem_dump_file.write_line("recon_repeat_delay:" + to_string_hex(data->recon_config, 16));
|
||||
pmem_dump_file.write_line("recon_repeat_nb: " + to_string_dec_int(data->recon_repeat_nb));
|
||||
pmem_dump_file.write_line("recon_repeat_gain: " + to_string_dec_int(data->recon_repeat_gain));
|
||||
pmem_dump_file.write_line("recon_repeat_delay: " + to_string_dec_int(data->recon_repeat_delay));
|
||||
pmem_dump_file.write_line("converter: " + to_string_dec_int(data->converter));
|
||||
pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(data->updown_converter));
|
||||
pmem_dump_file.write_line("updown_frequency_rx_correction: " + to_string_dec_int(data->updown_frequency_rx_correction));
|
||||
@@ -1054,6 +1072,7 @@ bool debug_dump() {
|
||||
pmem_dump_file.write_line("encoder_dial_sensitivity: " + to_string_dec_uint(data->encoder_dial_sensitivity));
|
||||
// pmem_dump_file.write_line("UNUSED_8: " + to_string_dec_uint(data->UNUSED_8));
|
||||
pmem_dump_file.write_line("headphone_volume_cb: " + to_string_dec_int(data->headphone_volume_cb));
|
||||
pmem_dump_file.write_line("config_mode_storage: 0x" + to_string_hex(data->config_mode_storage, 8));
|
||||
|
||||
// ui_config bits
|
||||
const auto backlight_timer = portapack::persistent_memory::config_backlight_timer();
|
||||
@@ -1086,8 +1105,9 @@ bool debug_dump() {
|
||||
// misc_config bits
|
||||
pmem_dump_file.write_line("misc_config config_audio_mute: " + to_string_dec_int(config_audio_mute()));
|
||||
pmem_dump_file.write_line("misc_config config_speaker_disable: " + to_string_dec_int(config_speaker_disable()));
|
||||
pmem_dump_file.write_line("ui_config config_disable_external_tcxo: " + to_string_dec_uint(config_disable_external_tcxo()));
|
||||
pmem_dump_file.write_line("ui_config config_sdcard_high_speed_io: " + to_string_dec_uint(config_sdcard_high_speed_io()));
|
||||
pmem_dump_file.write_line("misc_config config_disable_external_tcxo: " + to_string_dec_uint(config_disable_external_tcxo()));
|
||||
pmem_dump_file.write_line("misc_config config_sdcard_high_speed_io: " + to_string_dec_uint(config_sdcard_high_speed_io()));
|
||||
pmem_dump_file.write_line("misc_config config_disable_config_mode: " + to_string_dec_uint(config_disable_config_mode()));
|
||||
|
||||
// receiver_model
|
||||
pmem_dump_file.write_line("\n[Receiver Model]");
|
||||
|
||||
@@ -33,10 +33,10 @@
|
||||
#include "serializer.hpp"
|
||||
#include "volume.hpp"
|
||||
|
||||
// persistant memory from/to sdcard flag file
|
||||
// persistent memory from/to sdcard flag file
|
||||
#define PMEM_FILEFLAG u"/SETTINGS/PMEM_FILEFLAG"
|
||||
|
||||
// persistant memory from/to sdcard flag file
|
||||
// persistent memory from/to sdcard flag file
|
||||
#define PMEM_SETTING_FILE u"/SETTINGS/pmem_settings"
|
||||
|
||||
#define PMEM_SIZE_BYTES 256 // total amount of pmem space in bytes, including checksum
|
||||
@@ -178,6 +178,8 @@ void set_config_cpld(uint8_t i);
|
||||
|
||||
bool config_disable_external_tcxo();
|
||||
bool config_sdcard_high_speed_io();
|
||||
bool config_disable_config_mode();
|
||||
|
||||
bool config_splash();
|
||||
bool config_converter();
|
||||
bool config_updown_converter();
|
||||
@@ -198,6 +200,8 @@ void set_save_app_settings(bool v);
|
||||
void set_show_bigger_qr_code(bool v);
|
||||
void set_config_disable_external_tcxo(bool v);
|
||||
void set_config_sdcard_high_speed_io(bool v, bool save);
|
||||
void set_config_disable_config_mode(bool v);
|
||||
|
||||
void set_config_splash(bool v);
|
||||
bool config_converter();
|
||||
bool config_updown_converter();
|
||||
@@ -223,10 +227,11 @@ void set_disable_touchscreen(bool v);
|
||||
uint8_t config_encoder_dial_sensitivity();
|
||||
void set_encoder_dial_sensitivity(uint8_t v);
|
||||
|
||||
#define CONFIG_MODE_GUARD_VALUE 2001
|
||||
#define CONFIG_MODE_NORMAL_VALUE 1999
|
||||
uint32_t config_mode_storage();
|
||||
void set_config_mode_storage(uint32_t v);
|
||||
#define CONFIG_MODE_GUARD_VALUE 0x000007d1
|
||||
#define CONFIG_MODE_NORMAL_VALUE 0x000007cf
|
||||
uint32_t config_mode_storage_direct();
|
||||
void set_config_mode_storage_direct(uint32_t v);
|
||||
bool config_disable_config_mode_direct();
|
||||
|
||||
uint32_t pocsag_last_address();
|
||||
void set_pocsag_last_address(uint32_t address);
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#include "chprintf.h"
|
||||
#include "irq_controls.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "usb_serial_io.h"
|
||||
#include "usb_serial_device_to_host.h"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
@@ -2118,7 +2118,15 @@ bool NumberField::on_key(const KeyEvent key) {
|
||||
}
|
||||
|
||||
bool NumberField::on_encoder(const EncoderEvent delta) {
|
||||
int32_t old_value = value();
|
||||
set_value(value() + (delta * step));
|
||||
|
||||
if (on_wrap) {
|
||||
if ((delta > 0) && (value() < old_value))
|
||||
on_wrap(1);
|
||||
else if ((delta < 0) && (value() > old_value))
|
||||
on_wrap(-1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -783,6 +783,7 @@ class NumberField : public Widget {
|
||||
public:
|
||||
std::function<void(NumberField&)> on_select{};
|
||||
std::function<void(int32_t)> on_change{};
|
||||
std::function<void(int32_t)> on_wrap{};
|
||||
|
||||
using range_t = std::pair<int32_t, int32_t>;
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ class UnTar {
|
||||
}
|
||||
|
||||
static bool create_dir(char* pathname) {
|
||||
char* p;
|
||||
std::filesystem::filesystem_error r;
|
||||
|
||||
if (!isValidName(pathname)) return false;
|
||||
@@ -65,18 +64,7 @@ class UnTar {
|
||||
std::string dirnameStr = u'/' + pathname;
|
||||
std::filesystem::path dirname = dirnameStr;
|
||||
|
||||
r = make_new_directory(dirname);
|
||||
|
||||
if (!r.ok()) {
|
||||
/* On failure, try creating parent directory. */
|
||||
p = strrchr(pathname, '/');
|
||||
if (p != NULL) {
|
||||
*p = '\0';
|
||||
create_dir(pathname);
|
||||
*p = '/';
|
||||
r = make_new_directory(dirname);
|
||||
}
|
||||
}
|
||||
r = ensure_directory(dirname);
|
||||
return (r.ok());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user