diff --git a/firmware/application/freqman.cpp b/firmware/application/freqman.cpp index fceb560f..840d3a28 100644 --- a/firmware/application/freqman.cpp +++ b/firmware/application/freqman.cpp @@ -21,17 +21,26 @@ */ #include "freqman.hpp" +#include -bool load_freqman_file(std::vector &frequencies) { +#define FREQMAN_MAX_PER_CAT 64 +#define FREQMAN_CAT_MAX_LEN 8 +#define FREQMAN_DESC_MAX_LEN 32 + +bool load_freqman_file(freqman_db &db) { File freqs_file; - size_t end, n = 0; - char * file_buffer; - char * desc_pos; - char desc_buffer[32] = { 0 }; + size_t length, span_end, n = 0; + uint64_t seek_pos = 0; + char * line_end; + int32_t category_id; + char * pos; + char desc_buffer[FREQMAN_DESC_MAX_LEN + 1] = { 0 }; + char category_buffer[FREQMAN_CAT_MAX_LEN + 1] = { 0 }; std::string description; rf::Frequency value; + std::vector::iterator category_find; - file_buffer = (char *)chHeapAlloc(0, 2048); + char file_buffer[256]; while (freqs_file.open("freqman.txt").is_valid()) { auto result = freqs_file.create("freqman.txt"); @@ -39,47 +48,88 @@ bool load_freqman_file(std::vector &frequencies) { return false; } - freqs_file.read(file_buffer, 2048); - - char * pos = file_buffer; - while ((pos = strstr(pos, "f=")) && n < 32) { - pos += 2; + freqs_file.read(file_buffer, 256); + + while ((pos = strstr(file_buffer, "f=")) && (n < FREQMAN_MAX_PER_CAT)) { - value = strtol(pos, nullptr, 10); + // Cut buffer at end of line + line_end = file_buffer; + do { + span_end = strcspn(line_end, "\x0D\x0A"); + line_end += (span_end + 1); + } while (span_end); + *line_end = (char)0; + + pos += 2; + value = strtol(pos, nullptr, 10); // Read frequency - desc_pos = strstr(pos, "d="); - if (desc_pos) { - desc_pos += 2; - end = strcspn(desc_pos, ",\x0D\x0A"); // CR LF - if (end > 31) end = 31; - memcpy(desc_buffer, desc_pos, end); - desc_buffer[end] = (char)0; + pos = strstr(file_buffer, "d="); + if (pos) { + pos += 2; + length = strcspn(pos, ",\x0D\x0A"); // Read description until , or CR LF + if (length > FREQMAN_DESC_MAX_LEN) length = FREQMAN_DESC_MAX_LEN; + memcpy(desc_buffer, pos, length); + desc_buffer[length] = (char)0; description = desc_buffer; - pos = desc_pos; } else { description = "-"; } - frequencies.push_back({ value, "", description }); + pos = strstr(file_buffer, "c="); + if (pos) { + pos += 2; + length = strcspn(pos, ",\x0D\x0A"); // Read category name until , or CR LF + if (length > FREQMAN_CAT_MAX_LEN) length = FREQMAN_CAT_MAX_LEN; + memcpy(category_buffer, pos, length); + category_buffer[length] = (char)0; + + // See if we already know that category + category_find = find(db.categories.begin(), db.categories.end(), category_buffer); + if (category_find == db.categories.end()) { + // Not found: add to list + db.categories.push_back(category_buffer); + category_id = db.categories.size() - 1; + } else { + // Found + category_id = category_find - db.categories.begin(); + } + } else { + category_id = -1; // Uncategorized + } + + db.entries.push_back({ value, "", description, category_id }); + n++; + + seek_pos += (line_end - file_buffer); + + if (freqs_file.seek(seek_pos).value() == seek_pos) + break; + else + freqs_file.read(file_buffer, 256); } - chHeapFree(file_buffer); + //chHeapFree(file_buffer); return true; } -bool save_freqman_file(std::vector &frequencies) { +bool save_freqman_file(freqman_db &db) { File freqs_file; size_t n; std::string item_string; + int32_t category_id; if (!create_freqman_file(freqs_file)) return false; - for (n = 0; n < frequencies.size(); n++) { - item_string = "f=" + to_string_dec_uint(frequencies[n].value); + for (n = 0; n < db.entries.size(); n++) { + item_string = "f=" + to_string_dec_uint(db.entries[n].value); - if (frequencies[n].description.size()) - item_string += ",d=" + frequencies[n].description; + if (db.entries[n].description.size()) + item_string += ",d=" + db.entries[n].description; + + category_id = db.entries[n].category_id; + if ((category_id >= 0) && (category_id < (int32_t)db.categories.size())) + item_string += ",c=" + db.categories[db.entries[n].category_id]; freqs_file.write_line(item_string); } @@ -89,26 +139,27 @@ bool save_freqman_file(std::vector &frequencies) { bool create_freqman_file(File &freqs_file) { auto result = freqs_file.create("freqman.txt"); - if (result.is_valid()) return false; + if (result.is_valid()) + return false; return true; } std::string freqman_item_string(freqman_entry &entry) { std::string item_string, frequency_str, description; - char temp_buffer[32]; rf::Frequency value; value = entry.value; entry.frequency_str = to_string_dec_int(value / 1000000, 4) + "." + to_string_dec_int((value / 100) % 10000, 4, '0'); + item_string = entry.frequency_str + "M: "; + if (entry.description.size() <= 19) { - item_string = entry.frequency_str + "M: " + entry.description; + item_string += entry.description; } else { - memcpy(temp_buffer, entry.description.c_str(), 16); - temp_buffer[16] = (char)0; - item_string = entry.frequency_str + ":" + temp_buffer + "..."; + // Cut if too long + item_string += entry.description.substr(0, 16) + "..."; } return item_string; diff --git a/firmware/application/freqman.hpp b/firmware/application/freqman.hpp index 2b218635..fcfaf50b 100644 --- a/firmware/application/freqman.hpp +++ b/firmware/application/freqman.hpp @@ -42,11 +42,17 @@ struct freqman_entry { rf::Frequency value; std::string frequency_str; std::string description; + int32_t category_id; }; -bool load_freqman_file(std::vector &frequencies); -bool save_freqman_file(std::vector &frequencies); +struct freqman_db { + std::vector entries; + std::vector categories; +}; + +bool load_freqman_file(freqman_db &db); +bool save_freqman_file(freqman_db &db); bool create_freqman_file(File &freqs_file); -std::string freqman_item_string(freqman_entry &frequencies); +std::string freqman_item_string(freqman_entry &item); #endif/*__FREQMAN_H__*/ diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index b1e58d84..d611038e 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -31,8 +31,10 @@ //TEST: Imperial in whipcalc //TEST: Numbers +//TODO: FreqMan: Add and rename categories +//TODO: FreqMan: Sort by category in edit screen +//TODO: FreqMan: Cap entry count per category (only done for total entries right now) //TODO: Script engine ? -//TODO: Morse coder for foxhunts //TODO: Close Call multiple slices (buggy) //TODO: Finish EPAR tx //TODO: IQ replay diff --git a/firmware/application/ui_freqman.cpp b/firmware/application/ui_freqman.cpp index 31e4c5c4..daad19b8 100644 --- a/firmware/application/ui_freqman.cpp +++ b/firmware/application/ui_freqman.cpp @@ -32,13 +32,14 @@ using namespace portapack; namespace ui { void FrequencySaveView::on_save_name(NavigationView& nav) { + // TODO: Here be a bug. textentry(nav, desc_buffer, 28); - frequencies.push_back({ value_, "", desc_buffer }); + database.entries.push_back({ value_, "", desc_buffer, (int32_t)options_category.selected_index_value() }); nav.pop(); } void FrequencySaveView::on_save_timestamp(NavigationView& nav) { - frequencies.push_back({ value_, "", str_timestamp }); + database.entries.push_back({ value_, "", str_timestamp, (int32_t)options_category.selected_index_value() }); nav.pop(); } @@ -61,7 +62,7 @@ void FrequencySaveView::on_tick_second() { FrequencySaveView::~FrequencySaveView() { rtc_time::signal_tick_second -= signal_token_tick_second; - save_freqman_file(frequencies); + save_freqman_file(database); } FrequencySaveView::FrequencySaveView( @@ -70,18 +71,23 @@ FrequencySaveView::FrequencySaveView( ) : nav_ (nav), value_ (value) { + using name_t = std::string; + using value_t = int32_t; + using option_t = std::pair; + using options_t = std::vector; + options_t categories; File freqs_file; size_t n; - if (!load_freqman_file(frequencies)) { + if (!load_freqman_file(database)) { if (!create_freqman_file(freqs_file)) { error = ERROR_ACCESS; return; } } - for (n = 0; n < frequencies.size(); n++) { - if (frequencies[n].value == value_) { + for (n = 0; n < database.entries.size(); n++) { + if (database.entries[n].value == value_) { error = ERROR_DUPLICATE; break; } @@ -97,8 +103,17 @@ FrequencySaveView::FrequencySaveView( &button_save_name, &button_save_timestamp, &text_timestamp, + &text_category, + &options_category, &button_cancel }); + + // Populate categories OptionsField + categories.emplace_back(std::make_pair("No cat.", -1)); + for (n = 0; n < database.categories.size(); n++) + categories.emplace_back(std::make_pair(database.categories[n], n)); + options_category.set_options(categories); + options_category.set_selected_index(0); on_tick_second(); @@ -110,6 +125,7 @@ FrequencySaveView::FrequencySaveView( button_save_timestamp.on_select = [this, &nav](Button&) { on_save_timestamp(nav); }; + button_cancel.on_select = [this, &nav](Button&) { nav.pop(); }; @@ -120,17 +136,23 @@ void FrequencyLoadView::setup_list() { menu_view.clear(); - for (n = 0; n < frequencies.size(); n++) { - menu_view.add_item({ freqman_item_string(frequencies[n]), ui::Color::white(), nullptr, [this](){ on_frequency_select(); } }); + for (n = 0; n < database.entries.size(); n++) { + menu_view.add_item({ + freqman_item_string(database.entries[n]), + ui::Color::white(), + nullptr, + [this](){ + on_frequency_select(); + } + }); } - menu_view.set_parent_rect({ 0, 0, 240, 216 }); menu_view.set_highlighted(menu_view.highlighted()); // Refresh } void FrequencyLoadView::on_frequency_select() { nav_.pop(); - if (on_changed) on_changed(frequencies[menu_view.highlighted()].value); + if (on_changed) on_changed(database.entries[menu_view.highlighted()].value); } void FrequencyLoadView::focus() { @@ -146,12 +168,12 @@ FrequencyLoadView::FrequencyLoadView( NavigationView& nav ) : nav_ (nav) { - if (!load_freqman_file(frequencies)) { + if (!load_freqman_file(database)) { error = ERROR_ACCESS; return; } - if (frequencies.size() == 0) { + if (database.entries.size() == 0) { error = ERROR_EMPTY; return; } @@ -163,45 +185,61 @@ FrequencyLoadView::FrequencyLoadView( setup_list(); + // Just to allow exit on left + menu_view.on_left = [this]() { + on_frequency_select(); + }; + button_cancel.on_select = [this, &nav](Button&) { nav.pop(); }; } void FreqManView::on_frequency_select() { + options_category.set_selected_index(database.entries[menu_view.highlighted()].category_id + 1); button_edit_freq.focus(); } void FreqManView::on_edit_freq(rf::Frequency f) { - frequencies[menu_view.highlighted()].value = f; + database.entries[menu_view.highlighted()].value = f; setup_list(); } void FreqManView::on_edit_desc(NavigationView& nav) { char desc_buffer[32] = { 0 }; - strcpy(desc_buffer, frequencies[menu_view.highlighted()].description.c_str()); + strcpy(desc_buffer, database.entries[menu_view.highlighted()].description.c_str()); textentry(nav, desc_buffer, 28, [this, &desc_buffer](char * buffer) { - frequencies[menu_view.highlighted()].description = buffer; + database.entries[menu_view.highlighted()].description = buffer; setup_list(); }); } void FreqManView::on_delete() { - frequencies.erase(frequencies.begin() + menu_view.highlighted()); + database.entries.erase(database.entries.begin() + menu_view.highlighted()); setup_list(); } +void FreqManView::on_edit_category(int32_t category_id) { + database.entries[menu_view.highlighted()].category_id = category_id; +} + void FreqManView::setup_list() { size_t n; menu_view.clear(); - for (n = 0; n < frequencies.size(); n++) { - menu_view.add_item({ freqman_item_string(frequencies[n]), ui::Color::white(), nullptr, [this](){ on_frequency_select(); } }); + for (n = 0; n < database.entries.size(); n++) { + menu_view.add_item({ + freqman_item_string(database.entries[n]), + ui::Color::white(), + nullptr, + [this](){ + on_frequency_select(); + } + }); } - menu_view.set_parent_rect({ 0, 0, 240, 168 }); menu_view.set_highlighted(menu_view.highlighted()); // Refresh } @@ -215,19 +253,26 @@ void FreqManView::focus() { } FreqManView::~FreqManView() { - save_freqman_file(frequencies); + save_freqman_file(database); } FreqManView::FreqManView( NavigationView& nav ) : nav_ (nav) { - if (!load_freqman_file(frequencies)) { + using name_t = std::string; + using value_t = int32_t; + using option_t = std::pair; + using options_t = std::vector; + options_t categories; + size_t n; + + if (!load_freqman_file(database)) { error = ERROR_ACCESS; return; } - if (frequencies.size() == 0) { + if (database.entries.size() == 0) { error = ERROR_EMPTY; return; } @@ -237,14 +282,32 @@ FreqManView::FreqManView( &text_edit, &button_edit_freq, &button_edit_desc, + &text_category, + &options_category, &button_del, &button_exit }); setup_list(); + // Populate categories OptionsField + categories.emplace_back(std::make_pair("No cat.", -1)); + for (n = 0; n < database.categories.size(); n++) + categories.emplace_back(std::make_pair(database.categories[n], n)); + options_category.set_options(categories); + options_category.set_selected_index(0); + + options_category.on_change = [this](size_t, int32_t category_id) { + on_edit_category(category_id); + }; + + // Just to allow exit on left + menu_view.on_left = [this]() { + on_frequency_select(); + }; + button_edit_freq.on_select = [this, &nav](Button&) { - auto new_view = nav.push(frequencies[menu_view.highlighted()].value); + auto new_view = nav.push(database.entries[menu_view.highlighted()].value); new_view->on_changed = [this](rf::Frequency f) { on_edit_freq(f); }; diff --git a/firmware/application/ui_freqman.hpp b/firmware/application/ui_freqman.hpp index 1f47ead3..9e3b1be6 100644 --- a/firmware/application/ui_freqman.hpp +++ b/firmware/application/ui_freqman.hpp @@ -48,12 +48,13 @@ private: rtc::RTC datetime { }; rf::Frequency value_ { }; std::string str_timestamp { }; + //int32_t category_id_ { -1 }; void on_save_name(NavigationView& nav); void on_save_timestamp(NavigationView& nav); void on_tick_second(); - std::vector frequencies { }; + freqman_db database { }; SignalToken signal_token_tick_second { }; @@ -63,21 +64,31 @@ private: }; Text text_save { - { 88, 120, 8 * 8, 16 }, + { 4 * 8, 15 * 8, 8 * 8, 16 }, "Save as:", }; Button button_save_name { - { 72, 144, 96, 32 }, + { 4 * 8, 18 * 8, 12 * 8, 32 }, "Name (set)" }; Button button_save_timestamp { - { 72, 184, 96, 32 }, + { 4 * 8, 23 * 8, 12 * 8, 32 }, "Timestamp:" }; Text text_timestamp { - { 76, 220, 11 * 8, 16 }, + { 17 * 8, 24 * 8, 11 * 8, 16 }, "MM/DD HH:MM", }; + + Text text_category { + { 4 * 8, 28 * 8, 12 * 8, 16 }, + "In category:", + }; + OptionsField options_category { + { 17 * 8, 28 * 8 }, + 8, + { } + }; Button button_cancel { { 72, 264, 96, 32 }, @@ -102,9 +113,12 @@ private: void on_frequency_select(); void setup_list(); - std::vector frequencies { }; + freqman_db database { }; - MenuView menu_view { }; + MenuView menu_view { + { 0, 0, 240, 216 }, + false + }; Button button_cancel { { 72, 264, 96, 32 }, @@ -130,31 +144,45 @@ private: void on_edit_freq(rf::Frequency f); void on_edit_desc(NavigationView& nav); void on_delete(); + void on_edit_category(int32_t category_id); void setup_list(); - std::vector frequencies { }; + freqman_db database { }; - MenuView menu_view { true }; + MenuView menu_view { + { 0, 0, 240, 168 }, + true + }; Text text_edit { - { 16, 194, 5 * 8, 16 }, + { 2 * 8, 24 * 8, 5 * 8, 16 }, "Edit:" }; Button button_edit_freq { - { 16, 194 + 16, 104, 32 }, + { 2 * 8, 26 * 8, 14 * 8, 32 }, "Frequency" }; Button button_edit_desc { - { 16, 194 + 16 + 34, 104, 32 }, + { 2 * 8, 30 * 8 + 4, 14 * 8, 32 }, "Description" }; + Text text_category { + { 2 * 8, 35 * 8, 9 * 8, 16 }, + "Category:", + }; + OptionsField options_category { + { 12 * 8, 35 * 8 }, + 8, + { } + }; + Button button_del { - { 160, 192, 72, 64 }, + { 20 * 8, 24 * 8, 9 * 8, 48 }, "Delete" }; Button button_exit { - { 160, 264, 72, 32 }, + { 20 * 8, 33 * 8, 9 * 8, 40 }, "Exit" }; }; diff --git a/firmware/application/ui_menu.cpp b/firmware/application/ui_menu.cpp index e2c14ac0..2c0e6d8d 100644 --- a/firmware/application/ui_menu.cpp +++ b/firmware/application/ui_menu.cpp @@ -89,9 +89,15 @@ void MenuItemView::paint(Painter& painter) { /* MenuView **************************************************************/ MenuView::MenuView( + Rect new_parent_rect, bool keep_highlight ) : keep_highlight_ { keep_highlight } { + View::set_parent_rect(new_parent_rect); + + displayed_max_ = (parent_rect().size().height() / 24); + arrow_more.set_parent_rect( { 228, (Coord)(displayed_max_ * item_height), 8, 8 } ); + set_focusable(true); signal_token_tick_second = rtc_time::signal_tick_second += [this]() { @@ -105,6 +111,7 @@ MenuView::MenuView( } MenuView::~MenuView() { + clear(); rtc_time::signal_tick_second -= signal_token_tick_second; } @@ -120,19 +127,15 @@ void MenuView::on_tick_second() { } void MenuView::clear() { - children_.erase(children_.begin() + 1, children_.end()); + for (auto child : children_) { + if (!child->id) { + delete child; + } + } } void MenuView::add_item(const MenuItem item) { add_child(new MenuItemView { item, keep_highlight_ }); -} - -void MenuView::set_parent_rect(const Rect new_parent_rect) { - View::set_parent_rect(new_parent_rect); - - displayed_max_ = new_parent_rect.size().height() / 24; - arrow_more.set_parent_rect( { 228, (Coord)(displayed_max_ * item_height), 8, 8 } ); - update_items(); } @@ -175,13 +178,14 @@ size_t MenuView::highlighted() const { bool MenuView::set_highlighted(int32_t new_value) { int32_t item_count = (int32_t)children_.size() - 1; + if (new_value < 0) return false; if (new_value >= item_count) new_value = item_count - 1; - if ((new_value > offset_) && ((new_value - offset_ + 1) >= displayed_max_)) { + if ((new_value > offset_) && ((new_value - offset_) >= displayed_max_)) { // Shift MenuView up offset_ = new_value - displayed_max_ + 1; update_items(); @@ -191,9 +195,9 @@ bool MenuView::set_highlighted(int32_t new_value) { update_items(); } - item_view(highlighted())->unhighlight(); + item_view(highlighted_)->unhighlight(); highlighted_ = new_value; - item_view(highlighted())->highlight(); + item_view(highlighted_)->highlight(); return true; } diff --git a/firmware/application/ui_menu.hpp b/firmware/application/ui_menu.hpp index e138c3c4..549b3a2f 100644 --- a/firmware/application/ui_menu.hpp +++ b/firmware/application/ui_menu.hpp @@ -71,7 +71,7 @@ class MenuView : public View { public: std::function on_left { }; - MenuView(bool keep_highlight = false); + MenuView(Rect new_parent_rect = { 0, 0, 240, 304 }, bool keep_highlight = false); ~MenuView(); @@ -84,8 +84,6 @@ public: add_item(item); } } - - void set_parent_rect(const Rect new_parent_rect) override; MenuItemView* item_view(size_t index) const; @@ -96,7 +94,6 @@ public: void on_blur() override; bool on_key(const KeyEvent event) override; bool on_encoder(const EncoderEvent event) override; - //bool on_touch(const TouchEvent event) override; private: void update_items(); @@ -116,7 +113,7 @@ private: const size_t item_height = 24; bool blink_ = false; bool more_ = false; - size_t displayed_max_; + size_t displayed_max_ { 0 }; size_t highlighted_ { 0 }; size_t offset_ { 0 }; }; diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index f862bfb5..3d1e7ce4 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -291,8 +291,8 @@ ReceiverMenuView::ReceiverMenuView(NavigationView& nav) { { "Audio", ui::Color::green(), nullptr, [&nav](){ nav.push(); } }, { "CCIR", ui::Color::grey(), nullptr, [&nav](){ nav.push(); } }, { "Nordic/BTLE", ui::Color::grey(), &bitmap_icon_nordic, [&nav](){ nav.push(); } }, - { "POCSAG", ui::Color::cyan(), nullptr, [&nav](){ nav.push(); } }, - { "SIGFOX", ui::Color::grey(), &bitmap_icon_fox, [&nav](){ nav.push(); } }, // SIGFRXView + { "POCSAG", ui::Color::cyan(), &bitmap_icon_pocsag, [&nav](){ nav.push(); } }, + { "SIGFOX", ui::Color::grey(), &bitmap_icon_fox, [&nav](){ nav.push(); } }, // SIGFRXView { "Transponders", ui::Color::green(), nullptr, [&nav](){ nav.push(); } }, } }); on_left = [&nav](){ nav.pop(); }; diff --git a/firmware/application/ui_script.hpp b/firmware/application/ui_script.hpp index 24ef7283..9bd3f8aa 100644 --- a/firmware/application/ui_script.hpp +++ b/firmware/application/ui_script.hpp @@ -63,7 +63,10 @@ private: std::vector script { }; - MenuView menu_view { true }; + MenuView menu_view { + { 0, 0, 240, 168 }, + true + }; Text text_edit { { 16, 194, 5 * 8, 16 }, diff --git a/firmware/application/ui_whistle.hpp b/firmware/application/ui_whistle.hpp index f4552f9d..7f6466d0 100644 --- a/firmware/application/ui_whistle.hpp +++ b/firmware/application/ui_whistle.hpp @@ -40,26 +40,6 @@ public: static void whistle_th(void *p); private: - typedef struct rstchs { - rf::Frequency out[3]; - rf::Frequency in; - } rstchs; - rstchs whistle_chs[14] = { - {{ 467650000, 467700000, 467750000 }, 457700000}, - {{ 467750000, 467825000, 467875000 }, 457825000}, - {{ 467875000, 467925000, 467975000 }, 457925000}, - {{ 467950000, 468000000, 468050000 }, 457800000}, - {{ 467625000, 467675000, 467725000 }, 457675000}, - {{ 467700000, 467750000, 467800000 }, 457750000}, - {{ 467750000, 467800000, 467850000 }, 457800000}, - {{ 467825000, 467875000, 467925000 }, 457875000}, - {{ 467900000, 467950000, 468000000 }, 457950000}, - {{ 468025000, 468075000, 468125000 }, 458075000}, - {{ 468100000, 468150000, 468200000 }, 458150000}, - {{ 468075000, 468125000, 468175000 }, 458125000}, - {{ 468175000, 468225000, 468275000 }, 458225000}, - {{ 468225000, 468275000, 468325000 }, 458275000} - }; rf::Frequency f; Text text_status { diff --git a/firmware/portapack-h1-havoc.bin b/firmware/portapack-h1-havoc.bin index 04559019..1b6a65dd 100644 Binary files a/firmware/portapack-h1-havoc.bin and b/firmware/portapack-h1-havoc.bin differ