mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-25 17:09:03 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e90c65a62 | |||
| bfe67a13b7 | |||
| f091db3270 | |||
| a602abf1d8 | |||
| 689224fd8d | |||
| 23cf1c2737 | |||
| c48cbb7e55 | |||
| 3665b3c607 | |||
| 804b7c87b7 | |||
| a30875b582 |
@@ -1 +1 @@
|
||||
v1.9.1
|
||||
v2.0.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
v2.0.0
|
||||
v2.0.1
|
||||
|
||||
@@ -30,8 +30,6 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
|
||||
:heavy_check_mark: Our friends at OpenSourceSDRLab give away five units every three months in our discord (check the badge on top) of their [PortaPack H2](https://www.aliexpress.com/item/4000247041639.html?gatewayAdapt=4itemAdapt), you can support them too by ordering.
|
||||
|
||||
:heavy_check_mark: Another popular option is the clone of the [PortaPack H1](https://s.click.aliexpress.com/e/_Dkbqs2X).
|
||||
|
||||
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
|
||||
|
||||

|
||||
|
||||
@@ -107,7 +107,8 @@ void ADSBLogger::log(const ADSBLogEntry& log_entry) {
|
||||
log_line += " Type:" + to_string_dec_uint(log_entry.vel_type) +
|
||||
" Hdg:" + to_string_dec_uint(log_entry.vel.heading) +
|
||||
" Spd: " + to_string_dec_int(log_entry.vel.speed);
|
||||
|
||||
if (log_entry.sil != 0)
|
||||
log_line += " Sil:" + to_string_dec_uint(log_entry.sil);
|
||||
log_file.write_entry(log_line);
|
||||
}
|
||||
|
||||
@@ -354,11 +355,12 @@ void ADSBRxDetailsView::refresh_ui() {
|
||||
|
||||
text_callsign.set(entry_.callsign);
|
||||
text_infos.set(entry_.info_string);
|
||||
std::string str_sil = (entry_.sil > 0) ? " Sil:" + to_string_dec_uint(entry_.sil) : "";
|
||||
if (entry_.velo.heading < 360 && entry_.velo.speed >= 0)
|
||||
text_info2.set("Hdg:" + to_string_dec_uint(entry_.velo.heading) +
|
||||
" Spd:" + to_string_dec_int(entry_.velo.speed));
|
||||
" Spd:" + to_string_dec_int(entry_.velo.speed) + str_sil);
|
||||
else
|
||||
text_info2.set("");
|
||||
text_info2.set(str_sil);
|
||||
|
||||
text_frame_pos_even.set(to_string_hex_array(entry_.frame_pos_even.get_raw_data(), 14));
|
||||
text_frame_pos_odd.set(to_string_hex_array(entry_.frame_pos_odd.get_raw_data(), 14));
|
||||
@@ -471,7 +473,6 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
||||
"Alt:" + to_string_dec_int(entry.pos.altitude) +
|
||||
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
|
||||
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
|
||||
|
||||
entry.set_info_string(std::move(str_info));
|
||||
}
|
||||
|
||||
@@ -479,6 +480,8 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
|
||||
entry.set_frame_velo(frame);
|
||||
log_entry.vel = entry.velo;
|
||||
log_entry.vel_type = msg_sub;
|
||||
} else if (msg_type == AIRBORNE_OP_STATUS) { // for ver 1
|
||||
entry.sil = frame.get_sil_value();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace ui {
|
||||
#define AIRBORNE_POS_GPS_L 20 // airborne position (lowest type id)
|
||||
#define AIRBORNE_POS_GPS_H 22 // airborne position (highest type id)
|
||||
|
||||
#define AIRBORNE_OP_STATUS 31 // Aircraft operation status
|
||||
|
||||
#define RESERVED_L 23 // reserved for other uses
|
||||
#define RESERVED_H 31 // reserved for other uses
|
||||
|
||||
@@ -102,6 +104,8 @@ struct AircraftRecentEntry {
|
||||
std::string callsign{};
|
||||
std::string info_string{};
|
||||
|
||||
uint8_t sil{0}; // Surveillance integrity level
|
||||
|
||||
AircraftRecentEntry(const uint32_t ICAO_address)
|
||||
: ICAO_address{ICAO_address} {
|
||||
this->icao_str = to_string_hex(ICAO_address, 6);
|
||||
@@ -173,6 +177,7 @@ struct ADSBLogEntry {
|
||||
adsb_pos pos{};
|
||||
adsb_vel vel{};
|
||||
uint8_t vel_type{};
|
||||
uint8_t sil{};
|
||||
};
|
||||
|
||||
// TODO: Make logging optional.
|
||||
|
||||
@@ -62,7 +62,7 @@ std::string truncate(const fs::path& path, size_t max_length) {
|
||||
}
|
||||
|
||||
// Inserts the entry into the entry list sorted directories first then by file name.
|
||||
void insert_sorted(std::vector<fileman_entry>& entries, fileman_entry&& entry) {
|
||||
void insert_sorted(std::list<fileman_entry>& entries, fileman_entry&& entry) {
|
||||
auto it = std::lower_bound(
|
||||
std::begin(entries), std::end(entries), entry,
|
||||
[](const fileman_entry& lhs, const fileman_entry& rhs) {
|
||||
@@ -146,9 +146,82 @@ namespace ui {
|
||||
|
||||
/* FileManBaseView ***********************************************************/
|
||||
|
||||
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
void FileManBaseView::load_directory_contents_unordered(const fs::path& dir_path, size_t file_cnt) {
|
||||
current_path = dir_path;
|
||||
entry_list.clear();
|
||||
menu_view.clear();
|
||||
auto filtering = !extension_filter.empty();
|
||||
bool cxx_file = path_iequal(cxx_ext, extension_filter);
|
||||
|
||||
text_current.set(dir_path.empty() ? "(sd root)" : truncate(dir_path, 24));
|
||||
|
||||
nb_pages = 1 + (file_cnt / items_per_page);
|
||||
size_t start = pagination * items_per_page;
|
||||
size_t stop = start + items_per_page;
|
||||
if (file_cnt < stop) stop = file_cnt;
|
||||
if (start > file_cnt) start = 0; // shouldn't hapen but check against it won't hurt
|
||||
|
||||
size_t curr = 0;
|
||||
|
||||
for (const auto& entry : fs::directory_iterator(dir_path, u"*")) {
|
||||
if (entry_list.size() >= items_per_page) {
|
||||
break;
|
||||
}
|
||||
// Hide files starting with '.' (hidden / tmp).
|
||||
if (!show_hidden_files && is_hidden_file(entry.path()))
|
||||
continue;
|
||||
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path()))) {
|
||||
curr++;
|
||||
if (curr >= start) insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||
}
|
||||
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
curr++;
|
||||
if (curr >= start) insert_sorted(entry_list, {entry.path().string(), 0, true});
|
||||
}
|
||||
}
|
||||
|
||||
// Add "parent" directory if not at the root.
|
||||
if (!dir_path.empty() && pagination == 0)
|
||||
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
|
||||
|
||||
// add next page
|
||||
if (file_cnt > start + items_per_page) {
|
||||
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
|
||||
}
|
||||
|
||||
// add prev page
|
||||
if (pagination > 0) {
|
||||
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
|
||||
}
|
||||
}
|
||||
|
||||
int FileManBaseView::file_count_filtered(const fs::path& directory) {
|
||||
int count{0};
|
||||
auto filtering = !extension_filter.empty();
|
||||
bool cxx_file = path_iequal(cxx_ext, extension_filter);
|
||||
|
||||
for (auto& entry : std::filesystem::directory_iterator(directory, (const TCHAR*)u"*")) {
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
|
||||
++count;
|
||||
} else
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
size_t file_cnt = file_count_filtered(dir_path);
|
||||
if (file_cnt >= max_items_loaded) {
|
||||
load_directory_contents_unordered(dir_path, file_cnt);
|
||||
return;
|
||||
}
|
||||
current_path = dir_path;
|
||||
entry_list.clear();
|
||||
menu_view.clear();
|
||||
auto filtering = !extension_filter.empty();
|
||||
bool cxx_file = path_iequal(cxx_ext, extension_filter);
|
||||
|
||||
@@ -161,19 +234,41 @@ void FileManBaseView::load_directory_contents(const fs::path& dir_path) {
|
||||
|
||||
if (fs::is_regular_file(entry.status())) {
|
||||
if (!filtering || path_iequal(entry.path().extension(), extension_filter) || (cxx_file && is_cxx_capture_file(entry.path())))
|
||||
insert_sorted(entry_list, {entry.path(), (uint32_t)entry.size(), false});
|
||||
insert_sorted(entry_list, {entry.path().string(), (uint32_t)entry.size(), false});
|
||||
} else if (fs::is_directory(entry.status())) {
|
||||
insert_sorted(entry_list, {entry.path(), 0, true});
|
||||
insert_sorted(entry_list, {entry.path().string(), 0, true});
|
||||
}
|
||||
}
|
||||
|
||||
// paginating
|
||||
auto list_size = entry_list.size();
|
||||
nb_pages = 1 + (list_size / items_per_page);
|
||||
size_t start = pagination * items_per_page;
|
||||
size_t stop = start + items_per_page;
|
||||
if (list_size > start) {
|
||||
if (list_size < stop)
|
||||
stop = list_size;
|
||||
entry_list.erase(std::next(entry_list.begin(), stop), entry_list.end());
|
||||
entry_list.erase(entry_list.begin(), std::next(entry_list.begin(), start));
|
||||
}
|
||||
|
||||
// Add "parent" directory if not at the root.
|
||||
if (!dir_path.empty())
|
||||
entry_list.insert(entry_list.begin(), {parent_dir_path, 0, true});
|
||||
if (!dir_path.empty() && pagination == 0)
|
||||
entry_list.insert(entry_list.begin(), {parent_dir_path.string(), 0, true});
|
||||
|
||||
// add next page
|
||||
if (list_size > start + items_per_page) {
|
||||
entry_list.push_back({str_next, (uint32_t)pagination + 1, true});
|
||||
}
|
||||
|
||||
// add prev page
|
||||
if (pagination > 0) {
|
||||
entry_list.insert(entry_list.begin(), {str_back, (uint32_t)pagination - 1, true});
|
||||
}
|
||||
}
|
||||
|
||||
fs::path FileManBaseView::get_selected_full_path() const {
|
||||
if (get_selected_entry().path == parent_dir_path)
|
||||
if (get_selected_entry().path == parent_dir_path.string())
|
||||
return current_path.parent_path();
|
||||
|
||||
return current_path / get_selected_entry().path;
|
||||
@@ -181,7 +276,10 @@ fs::path FileManBaseView::get_selected_full_path() const {
|
||||
|
||||
const fileman_entry& FileManBaseView::get_selected_entry() const {
|
||||
// TODO: return reference to an "empty" entry on OOB?
|
||||
return entry_list[menu_view.highlighted_index()];
|
||||
auto it = entry_list.begin();
|
||||
if (menu_view.highlighted_index() >= 1) std::advance(it, menu_view.highlighted_index());
|
||||
return *it;
|
||||
// return entry_list[menu_view.highlighted_index()];
|
||||
}
|
||||
|
||||
FileManBaseView::FileManBaseView(
|
||||
@@ -230,7 +328,7 @@ void FileManBaseView::push_dir(const fs::path& path) {
|
||||
current_path /= path;
|
||||
saved_index_stack.push_back(menu_view.highlighted_index());
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current();
|
||||
reload_current(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,29 +337,56 @@ void FileManBaseView::pop_dir() {
|
||||
return;
|
||||
|
||||
current_path = current_path.parent_path();
|
||||
reload_current();
|
||||
reload_current(true);
|
||||
menu_view.set_highlighted(saved_index_stack.back());
|
||||
saved_index_stack.pop_back();
|
||||
}
|
||||
|
||||
std::string get_extension(std::string t) {
|
||||
const auto index = t.find_last_of(u'.');
|
||||
if (index == t.npos) {
|
||||
return {};
|
||||
} else {
|
||||
return t.substr(index);
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_stem(std::string t) {
|
||||
const auto index = t.find_last_of(u'.');
|
||||
if (index == t.npos) {
|
||||
return t;
|
||||
} else {
|
||||
return t.substr(0, index);
|
||||
}
|
||||
}
|
||||
std::string get_filename(std::string _s) {
|
||||
const auto index = _s.find_last_of("/");
|
||||
if (index == _s.npos) {
|
||||
return _s;
|
||||
} else {
|
||||
return _s.substr(index + 1);
|
||||
}
|
||||
}
|
||||
void FileManBaseView::refresh_list() {
|
||||
if (on_refresh_widgets)
|
||||
on_refresh_widgets(false);
|
||||
|
||||
auto prev_highlight = menu_view.highlighted_index();
|
||||
prev_highlight = menu_view.highlighted_index();
|
||||
menu_view.clear();
|
||||
|
||||
for (const auto& entry : entry_list) {
|
||||
auto entry_name = truncate(entry.path, 20);
|
||||
auto entry_name = std::string{entry.path.length() <= 20 ? entry.path : entry.path.substr(0, 20)};
|
||||
|
||||
if (entry.is_directory) {
|
||||
auto size_str =
|
||||
(entry.path == parent_dir_path)
|
||||
? ""
|
||||
: to_string_dec_uint(file_count(current_path / entry.path));
|
||||
std::string size_str{};
|
||||
if (entry.path == str_next || entry.path == str_back) {
|
||||
size_str = to_string_dec_uint(1 + entry.size) + "/" + to_string_dec_uint(nb_pages); // show computed number of pages
|
||||
} else {
|
||||
size_str = (entry.path == parent_dir_path.string()) ? "" : to_string_dec_uint(file_count(current_path / entry.path));
|
||||
}
|
||||
|
||||
menu_view.add_item(
|
||||
{entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||
ui::Color::yellow(),
|
||||
&bitmap_icon_dir,
|
||||
[this](KeyEvent key) {
|
||||
@@ -270,11 +395,11 @@ void FileManBaseView::refresh_list() {
|
||||
}});
|
||||
|
||||
} else {
|
||||
const auto& assoc = get_assoc(entry.path.extension());
|
||||
const auto& assoc = get_assoc(get_extension(entry.path));
|
||||
auto size_str = to_string_file_size(entry.size);
|
||||
|
||||
menu_view.add_item(
|
||||
{entry_name + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||
{entry_name.substr(0, max_filename_length) + std::string(21 - entry_name.length(), ' ') + size_str,
|
||||
assoc.color,
|
||||
assoc.icon,
|
||||
[this](KeyEvent key) {
|
||||
@@ -282,16 +407,13 @@ void FileManBaseView::refresh_list() {
|
||||
on_select_entry(key);
|
||||
}});
|
||||
}
|
||||
|
||||
// HACK: Should page menu items instead of limiting the number.
|
||||
if (menu_view.item_count() >= max_items_shown)
|
||||
break;
|
||||
}
|
||||
|
||||
menu_view.set_highlighted(prev_highlight);
|
||||
}
|
||||
|
||||
void FileManBaseView::reload_current() {
|
||||
void FileManBaseView::reload_current(bool reset_pagination) {
|
||||
if (reset_pagination) pagination = 0;
|
||||
load_directory_contents(current_path);
|
||||
refresh_list();
|
||||
}
|
||||
@@ -327,6 +449,21 @@ FileLoadView::FileLoadView(
|
||||
|
||||
on_select_entry = [this](KeyEvent) {
|
||||
if (get_selected_entry().is_directory) {
|
||||
if (get_selected_entry().path == str_full) {
|
||||
return;
|
||||
}
|
||||
if (get_selected_entry().path == str_back) {
|
||||
pagination--;
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current(false);
|
||||
return;
|
||||
}
|
||||
if (get_selected_entry().path == str_next) {
|
||||
pagination++;
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current(false);
|
||||
return;
|
||||
}
|
||||
push_dir(get_selected_entry().path);
|
||||
} else {
|
||||
if (on_changed)
|
||||
@@ -416,10 +553,10 @@ void FileManagerView::on_rename(std::string_view hint) {
|
||||
auto& entry = get_selected_entry();
|
||||
|
||||
// Append the hint to the filename stem as a rename suggestion.
|
||||
name_buffer = entry.path.stem().string();
|
||||
name_buffer = get_stem(entry.path);
|
||||
if (!hint.empty())
|
||||
name_buffer += "_" + std::string{hint};
|
||||
name_buffer += entry.path.extension().string();
|
||||
name_buffer += get_extension(entry.path);
|
||||
|
||||
// Set the rename cursor to before the extension to make renaming simpler.
|
||||
uint32_t cursor_pos = (uint32_t)name_buffer.length();
|
||||
@@ -440,11 +577,11 @@ void FileManagerView::on_rename(std::string_view hint) {
|
||||
auto new_name = renamed_path.replace_extension(partner.extension());
|
||||
rename_file(partner, current_path / new_name);
|
||||
}
|
||||
reload_current();
|
||||
reload_current(false);
|
||||
});
|
||||
|
||||
if (!has_partner)
|
||||
reload_current();
|
||||
reload_current(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -454,7 +591,7 @@ void FileManagerView::on_delete() {
|
||||
return;
|
||||
}
|
||||
|
||||
auto name = get_selected_entry().path.filename().string();
|
||||
auto name = get_filename(get_selected_entry().path);
|
||||
nav_.push<ModalMessageView>(
|
||||
"Delete", "Delete " + name + "\nAre you sure?", YESNO,
|
||||
[this](bool choice) {
|
||||
@@ -466,11 +603,11 @@ void FileManagerView::on_delete() {
|
||||
[this](const fs::path& partner, bool should_delete) {
|
||||
if (should_delete)
|
||||
delete_file(partner);
|
||||
reload_current();
|
||||
reload_current(true);
|
||||
});
|
||||
|
||||
if (!has_partner)
|
||||
reload_current();
|
||||
reload_current(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -495,7 +632,7 @@ void FileManagerView::on_clean() {
|
||||
std::filesystem::path current_full_path = path_name / file_name;
|
||||
delete_file(current_full_path);
|
||||
}
|
||||
reload_current();
|
||||
reload_current(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -504,7 +641,7 @@ void FileManagerView::on_new_dir() {
|
||||
name_buffer = "";
|
||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& dir_name) {
|
||||
make_new_directory(current_path / dir_name);
|
||||
reload_current();
|
||||
reload_current(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -528,14 +665,14 @@ void FileManagerView::on_paste() {
|
||||
clipboard_path = fs::path{};
|
||||
clipboard_mode = ClipboardMode::None;
|
||||
menu_view.focus();
|
||||
reload_current();
|
||||
reload_current(true);
|
||||
}
|
||||
|
||||
void FileManagerView::on_new_file() {
|
||||
name_buffer = "";
|
||||
text_prompt(nav_, name_buffer, max_filename_length, [this](std::string& file_name) {
|
||||
make_new_file(current_path / file_name);
|
||||
reload_current();
|
||||
reload_current(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -558,7 +695,7 @@ bool FileManagerView::handle_file_open() {
|
||||
return true;
|
||||
} else if (path_iequal(bmp_ext, ext)) {
|
||||
nav_.push<SplashViewer>(path);
|
||||
reload_current();
|
||||
reload_current(false);
|
||||
return true;
|
||||
} else if (path_iequal(rem_ext, ext)) {
|
||||
nav_.push<RemoteView>(path);
|
||||
@@ -570,7 +707,7 @@ bool FileManagerView::handle_file_open() {
|
||||
|
||||
bool FileManagerView::selected_is_valid() const {
|
||||
return !entry_list.empty() &&
|
||||
get_selected_entry().path != parent_dir_path;
|
||||
get_selected_entry().path != parent_dir_path.string();
|
||||
}
|
||||
|
||||
FileManagerView::FileManagerView(
|
||||
@@ -602,7 +739,7 @@ FileManagerView::FileManagerView(
|
||||
});
|
||||
|
||||
menu_view.on_highlight = [this]() {
|
||||
if (menu_view.highlighted_index() >= max_items_shown - 1) {
|
||||
if (menu_view.highlighted_index() >= max_items_loaded - 1) { // todo check this if correct
|
||||
text_date.set_style(&Styles::red);
|
||||
text_date.set("Too many files!");
|
||||
} else {
|
||||
@@ -617,13 +754,30 @@ FileManagerView::FileManagerView(
|
||||
refresh_list();
|
||||
|
||||
on_select_entry = [this](KeyEvent key) {
|
||||
if (key == KeyEvent::Select && get_selected_entry().is_directory) {
|
||||
push_dir(get_selected_entry().path);
|
||||
} else if (key == KeyEvent::Select && handle_file_open()) {
|
||||
return;
|
||||
} else {
|
||||
button_rename.focus();
|
||||
if (key == KeyEvent::Select) {
|
||||
if (get_selected_entry().is_directory) {
|
||||
if (get_selected_entry().path == str_full) {
|
||||
return;
|
||||
}
|
||||
if (get_selected_entry().path == str_back) {
|
||||
pagination--;
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current(false);
|
||||
return;
|
||||
}
|
||||
if (get_selected_entry().path == str_next) {
|
||||
pagination++;
|
||||
menu_view.set_highlighted(0);
|
||||
reload_current(false);
|
||||
return;
|
||||
}
|
||||
push_dir(get_selected_entry().path);
|
||||
return;
|
||||
} else if (handle_file_open()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
button_rename.focus();
|
||||
};
|
||||
|
||||
button_rename.on_select = [this]() {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
namespace ui {
|
||||
|
||||
struct fileman_entry {
|
||||
std::filesystem::path path{};
|
||||
std::string path{};
|
||||
uint32_t size{};
|
||||
bool is_directory{};
|
||||
};
|
||||
@@ -61,8 +61,12 @@ class FileManBaseView : public View {
|
||||
void push_dir(const std::filesystem::path& path);
|
||||
|
||||
protected:
|
||||
static constexpr size_t max_filename_length = 64;
|
||||
static constexpr size_t max_items_shown = 100;
|
||||
uint32_t prev_highlight = 0;
|
||||
uint8_t pagination = 0;
|
||||
uint8_t nb_pages = 1;
|
||||
static constexpr size_t max_filename_length = 20;
|
||||
static constexpr size_t max_items_loaded = 75; // too memory hungry, so won't sort it
|
||||
static constexpr size_t items_per_page = 20;
|
||||
|
||||
struct file_assoc_t {
|
||||
std::filesystem::path extension;
|
||||
@@ -85,10 +89,12 @@ class FileManBaseView : public View {
|
||||
std::filesystem::path get_selected_full_path() const;
|
||||
const fileman_entry& get_selected_entry() const;
|
||||
|
||||
int file_count_filtered(const std::filesystem::path& directory);
|
||||
void pop_dir();
|
||||
void refresh_list();
|
||||
void reload_current();
|
||||
void reload_current(bool reset_pagination = false);
|
||||
void load_directory_contents(const std::filesystem::path& dir_path);
|
||||
void load_directory_contents_unordered(const std::filesystem::path& dir_path, size_t file_cnt);
|
||||
const file_assoc_t& get_assoc(const std::filesystem::path& ext) const;
|
||||
|
||||
NavigationView& nav_;
|
||||
@@ -97,11 +103,14 @@ class FileManBaseView : public View {
|
||||
std::function<void(KeyEvent)> on_select_entry{nullptr};
|
||||
std::function<void(bool)> on_refresh_widgets{nullptr};
|
||||
|
||||
const std::string str_back{"<--"};
|
||||
const std::string str_next{"-->"};
|
||||
const std::string str_full{"Can't load more.."};
|
||||
const std::filesystem::path parent_dir_path{u".."};
|
||||
std::filesystem::path current_path{u""};
|
||||
std::filesystem::path extension_filter{u""};
|
||||
|
||||
std::vector<fileman_entry> entry_list{};
|
||||
std::list<fileman_entry> entry_list{};
|
||||
std::vector<uint32_t> saved_index_stack{};
|
||||
|
||||
bool show_hidden_files{false};
|
||||
|
||||
@@ -523,21 +523,6 @@ MicTXView::MicTXView(
|
||||
};
|
||||
check_mic_to_HP.set_value(mic_to_HP_enabled);
|
||||
|
||||
check_rxactive.on_select = [this](Checkbox&, bool v) {
|
||||
// vumeter.set_value(0); //Start with a clean vumeter
|
||||
rx_enabled = v;
|
||||
check_mic_to_HP.hidden(rx_enabled); // Toggle Hide / show "Hear Mic" checkbox depending if we activate or not the receiver. (if RX on => no visible "Mic Hear" option)
|
||||
if ((rx_enabled) && (transmitting))
|
||||
check_mic_to_HP.set_value(transmitting); // Once we activate the "Rx audio" in reception time we disable "Hear Mic", but we allow it again in TX time.
|
||||
|
||||
if (rx_enabled)
|
||||
check_va.set_value(false); // Disallow voice activation during RX audio (for now) - Future TODO: Should allow VOX during RX audio
|
||||
|
||||
rxaudio(v); // Activate-Deactivate audio RX (receiver) accordingly
|
||||
set_dirty(); // Refresh interface
|
||||
};
|
||||
check_rxactive.set_value(rx_enabled);
|
||||
|
||||
tx_button.on_select = [this](Button&) {
|
||||
if (!transmitting) {
|
||||
set_tx(true);
|
||||
@@ -590,6 +575,21 @@ MicTXView::MicTXView(
|
||||
// Trigger receiver to update modulation.
|
||||
if (rx_enabled)
|
||||
receiver_model.set_squelch_level(receiver_model.squelch_level());
|
||||
|
||||
check_rxactive.on_select = [this](Checkbox&, bool v) {
|
||||
// vumeter.set_value(0); //Start with a clean vumeter
|
||||
rx_enabled = v;
|
||||
check_mic_to_HP.hidden(rx_enabled); // Toggle Hide / show "Hear Mic" checkbox depending if we activate or not the receiver. (if RX on => no visible "Mic Hear" option)
|
||||
if ((rx_enabled) && (transmitting))
|
||||
check_mic_to_HP.set_value(transmitting); // Once we activate the "Rx audio" in reception time we disable "Hear Mic", but we allow it again in TX time.
|
||||
|
||||
if (rx_enabled)
|
||||
check_va.set_value(false); // Disallow voice activation during RX audio (for now) - Future TODO: Should allow VOX during RX audio
|
||||
|
||||
rxaudio(v); // Activate-Deactivate audio RX (receiver) accordingly
|
||||
set_dirty(); // Refresh interface
|
||||
};
|
||||
check_rxactive.set_value(rx_enabled);
|
||||
}
|
||||
|
||||
MicTXView::MicTXView(
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_touch_calibration.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
@@ -839,6 +840,60 @@ void SetMenuColorView::focus() {
|
||||
button_save.focus();
|
||||
}
|
||||
|
||||
/* SetAutoStartView ************************************/
|
||||
|
||||
SetAutostartView::SetAutostartView(NavigationView& nav) {
|
||||
add_children({&labels,
|
||||
&button_save,
|
||||
&button_cancel,
|
||||
&options});
|
||||
|
||||
button_save.on_select = [&nav, this](Button&) {
|
||||
autostart_app = "";
|
||||
if (selected != 0) {
|
||||
auto it = full_app_list.find(selected);
|
||||
if (it != full_app_list.end())
|
||||
autostart_app = it->second;
|
||||
}
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
button_cancel.on_select = [&nav, this](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
// options
|
||||
i = 0;
|
||||
OptionsField::option_t o{"-none-", i};
|
||||
opts.emplace_back(o);
|
||||
for (auto& app : NavigationView::appList) {
|
||||
if (app.id == nullptr) continue;
|
||||
i++;
|
||||
o = {app.displayName, i};
|
||||
opts.emplace_back(o);
|
||||
full_app_list.emplace(i, app.id);
|
||||
if (autostart_app == app.id) selected = i;
|
||||
}
|
||||
ExternalItemsMenuLoader::load_all_external_items_callback([this](ui::AppInfoConsole& app) {
|
||||
if (app.appCallName == nullptr) return;
|
||||
i++;
|
||||
OptionsField::option_t o = {app.appFriendlyName, i};
|
||||
opts.emplace_back(o);
|
||||
full_app_list.emplace(i, app.appCallName);
|
||||
if (autostart_app == app.appCallName) selected = i;
|
||||
});
|
||||
|
||||
options.set_options(opts);
|
||||
options.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
selected = v;
|
||||
};
|
||||
options.set_selected_index(selected);
|
||||
}
|
||||
|
||||
void SetAutostartView::focus() {
|
||||
options.focus();
|
||||
}
|
||||
|
||||
/* SettingsMenuView **************************************/
|
||||
|
||||
SettingsMenuView::SettingsMenuView(NavigationView& nav)
|
||||
@@ -866,6 +921,7 @@ void SettingsMenuView::on_populate() {
|
||||
//{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [this]() { nav_.push<SetQRCodeView>(); }},
|
||||
{"Brightness", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetFakeBrightnessView>(); }},
|
||||
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetMenuColorView>(); }},
|
||||
{"Autostart", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetAutostartView>(); }},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -799,6 +799,42 @@ class SetMenuColorView : public View {
|
||||
};
|
||||
};
|
||||
|
||||
class SetAutostartView : public View {
|
||||
public:
|
||||
SetAutostartView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Autostart"; };
|
||||
|
||||
private:
|
||||
int32_t i = 0;
|
||||
std::string autostart_app{""};
|
||||
OptionsField::options_t opts{};
|
||||
std::map<int32_t, std::string> full_app_list{}; // looking table
|
||||
int32_t selected = 0;
|
||||
SettingsStore nav_setting{
|
||||
"nav"sv,
|
||||
{{"autostart_app"sv, &autostart_app}}};
|
||||
Labels labels{
|
||||
{{1 * 8, 1 * 16}, "Select app to start on boot", Color::light_grey()},
|
||||
{{2 * 8, 2 * 16}, "(an SD Card is required)", Color::light_grey()}};
|
||||
|
||||
Button button_save{
|
||||
{2 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Save"};
|
||||
|
||||
OptionsField options{
|
||||
{8 * 8, 4 * 16},
|
||||
30,
|
||||
{}};
|
||||
|
||||
Button button_cancel{
|
||||
{16 * 8, 16 * 16, 12 * 8, 32},
|
||||
"Cancel",
|
||||
};
|
||||
};
|
||||
|
||||
class SettingsMenuView : public BtnGridView {
|
||||
public:
|
||||
SettingsMenuView(NavigationView& nav);
|
||||
|
||||
@@ -158,6 +158,7 @@ static void event_loop() {
|
||||
event_dispatcher.set_display_sleep(true);
|
||||
}};
|
||||
portapack::setEventDispatcherToUSBSerial(&event_dispatcher);
|
||||
system_view.get_navigation_view()->handle_autostart();
|
||||
event_dispatcher.run();
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +107,10 @@
|
||||
#include "file_reader.hpp"
|
||||
#include "png_writer.hpp"
|
||||
#include "file_path.hpp"
|
||||
#include "ff.h"
|
||||
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
|
||||
using portapack::receiver_model;
|
||||
using portapack::transmitter_model;
|
||||
@@ -708,6 +712,23 @@ bool NavigationView::set_on_pop(std::function<void()> on_pop) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void NavigationView::handle_autostart() {
|
||||
std::string autostart_app{""};
|
||||
SettingsStore nav_setting{
|
||||
"nav"sv,
|
||||
{{"autostart_app"sv, &autostart_app}}};
|
||||
if (!autostart_app.empty()) {
|
||||
if (StartAppByName(autostart_app.c_str())) return;
|
||||
// if returned false, check for external apps by that name, and try to start it
|
||||
std::string appwithpath = "/" + apps_dir.string() + "/";
|
||||
appwithpath += autostart_app;
|
||||
appwithpath += ".ppma";
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conv;
|
||||
std::filesystem::path pth = conv.from_bytes(appwithpath.c_str());
|
||||
ui::ExternalItemsMenuLoader::run_external_app(*this, pth);
|
||||
}
|
||||
}
|
||||
|
||||
/* Helpers **************************************************************/
|
||||
|
||||
static void add_apps(NavigationView& nav, BtnGridView& grid, app_location_t loc) {
|
||||
|
||||
@@ -136,6 +136,8 @@ class NavigationView : public View {
|
||||
static const AppList appList;
|
||||
|
||||
bool StartAppByName(const char* name); // Starts a View (app) by name stored in appListFC. This is to start apps from console
|
||||
void handle_autostart();
|
||||
|
||||
private:
|
||||
struct ViewState {
|
||||
std::unique_ptr<View> view;
|
||||
|
||||
@@ -46,6 +46,10 @@ class ADSBFrame {
|
||||
return (raw_data[4] & 7);
|
||||
}
|
||||
|
||||
uint8_t get_sil_value() {
|
||||
return ((raw_data[10] >> 6) & 0b11); // 83-84 bits
|
||||
}
|
||||
|
||||
uint32_t get_ICAO_address() {
|
||||
return (raw_data[1] << 16) + (raw_data[2] << 8) + raw_data[3];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Common SSTV frequencies
|
||||
# frequencies marked Active have more frequent transmissions.
|
||||
# 'Active' => most used
|
||||
f=3640000,d=SSTV 80M
|
||||
f=7043000,d=SSTV 40M - Active
|
||||
f=7170000,d=SSTV 40M - Active
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
76,95,FM BROADCAST JAPAN
|
||||
65,74,FM BROADCAST RUSSIA
|
||||
# CITIZENS BAND RADIO
|
||||
26,28,CITIZENS BAND RADIO
|
||||
26,28,CB RADIO
|
||||
# COMMON PUBLIC SERVICE BANDS
|
||||
150,160, PUBLIC SERVICE 155
|
||||
450,470,PUBLIC SERVICE 460
|
||||
769,775,PUBLIC SERVICE 770
|
||||
# CubeSats
|
||||
144,146,145 CubeSats
|
||||
435,438,436 CubeSats
|
||||
# DECT
|
||||
1879,1931,DECT
|
||||
# FRS/GMRS
|
||||
|
||||
Reference in New Issue
Block a user