From 6574272ca889419db96b6c284f63ddac9e8903d9 Mon Sep 17 00:00:00 2001 From: Kyle Reed <3761006+kallanreed@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:43:37 -0700 Subject: [PATCH] Freqman improvements (#1276) * Show .1 MHz in pretty freqman string * Refactor load to user FreqmanDB * Recon file parsing cleanup * use strtol for parse_int * recon file cleanup * Fix bugs in Recon changes * PR feedback --------- Co-authored-by: kallanreed --- .../application/apps/analog_audio_app.cpp | 2 +- firmware/application/apps/capture_app.cpp | 1 + firmware/application/apps/capture_app.hpp | 1 - firmware/application/apps/ui_freqman.cpp | 38 +- firmware/application/apps/ui_freqman.hpp | 6 + firmware/application/apps/ui_level.cpp | 1 + firmware/application/apps/ui_level.hpp | 20 +- firmware/application/apps/ui_recon.cpp | 341 ++++++------------ firmware/application/apps/ui_recon.hpp | 20 +- .../application/apps/ui_recon_settings.cpp | 8 +- .../application/apps/ui_recon_settings.hpp | 2 + firmware/application/apps/ui_scanner.cpp | 1 + .../application/apps/ui_spectrum_painter.cpp | 7 +- firmware/application/file.cpp | 5 +- firmware/application/file.hpp | 2 +- firmware/application/file_wrapper.hpp | 4 +- firmware/application/freqman.cpp | 80 +--- firmware/application/freqman.hpp | 20 - firmware/application/freqman_db.cpp | 120 ++++-- firmware/application/freqman_db.hpp | 84 +++-- firmware/common/convert.hpp | 69 +++- firmware/test/application/test_convert.cpp | 31 +- 22 files changed, 431 insertions(+), 432 deletions(-) diff --git a/firmware/application/apps/analog_audio_app.cpp b/firmware/application/apps/analog_audio_app.cpp index 23fba65cf..185b79e5c 100644 --- a/firmware/application/apps/analog_audio_app.cpp +++ b/firmware/application/apps/analog_audio_app.cpp @@ -25,10 +25,10 @@ #include "audio.hpp" #include "baseband_api.hpp" #include "file.hpp" -#include "freqman.hpp" #include "portapack.hpp" #include "portapack_persistent_memory.hpp" #include "string_format.hpp" +#include "ui_freqman.hpp" #include "utility.hpp" using namespace portapack; diff --git a/firmware/application/apps/capture_app.cpp b/firmware/application/apps/capture_app.cpp index 16e6cbe59..6d0e4c660 100644 --- a/firmware/application/apps/capture_app.cpp +++ b/firmware/application/apps/capture_app.cpp @@ -23,6 +23,7 @@ #include "capture_app.hpp" #include "baseband_api.hpp" #include "portapack.hpp" +#include "ui_freqman.hpp" using namespace portapack; diff --git a/firmware/application/apps/capture_app.hpp b/firmware/application/apps/capture_app.hpp index 963ce554e..27a194a66 100644 --- a/firmware/application/apps/capture_app.hpp +++ b/firmware/application/apps/capture_app.hpp @@ -31,7 +31,6 @@ #include "ui_spectrum.hpp" #include "app_settings.hpp" #include "radio_state.hpp" -#include "freqman.hpp" namespace ui { diff --git a/firmware/application/apps/ui_freqman.cpp b/firmware/application/apps/ui_freqman.cpp index 98c87c4e2..f0a010e01 100644 --- a/firmware/application/apps/ui_freqman.cpp +++ b/firmware/application/apps/ui_freqman.cpp @@ -24,7 +24,6 @@ #include "ui_freqman.hpp" #include "event_m0.hpp" -#include "freqman.hpp" #include "portapack.hpp" #include "rtc_time.hpp" #include "tone_key.hpp" @@ -35,11 +34,33 @@ #include using namespace portapack; +using namespace ui; namespace fs = std::filesystem; // TODO: Clean up after moving to better lookup tables. -extern ui::OptionsField::options_t freqman_bandwidths[4]; -extern ui::OptionsField::options_t freqman_steps; +using options_t = OptionsField::options_t; +extern options_t freqman_modulations; +extern options_t freqman_bandwidths[4]; +extern options_t freqman_steps; +extern options_t freqman_steps_short; + +/* Set options. */ +void freqman_set_modulation_option(OptionsField& option) { + option.set_options(freqman_modulations); +} + +void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) { + if (is_valid(modulation)) + option.set_options(freqman_bandwidths[modulation]); +} + +void freqman_set_step_option(OptionsField& option) { + option.set_options(freqman_steps); +} + +void freqman_set_step_option_short(OptionsField& option) { + option.set_options(freqman_steps_short); +} namespace ui { @@ -155,7 +176,7 @@ FrequencySaveView::FrequencySaveView( }; button_save.on_select = [this, &nav](Button&) { - db_.insert_entry(entry_, db_.entry_count()); + db_.insert_entry(db_.entry_count(), entry_); nav_.pop(); }; } @@ -255,7 +276,7 @@ void FrequencyManagerView::on_add_entry() { }; // Add will insert below the currently selected item. - db_.insert_entry(entry, current_index() + 1); + db_.insert_entry(current_index() + 1, entry); refresh_list(1); } @@ -488,12 +509,13 @@ void FrequencyEditView::populate_step_options() { } void FrequencyEditView::populate_tone_options() { + using namespace tonekey; OptionsField::options_t options; options.push_back({"None", -1}); - for (auto i = 1u; i < tonekey::tone_keys.size(); ++i) { - auto& item = tonekey::tone_keys[i]; - options.push_back({item.first, (OptionsField::value_t)i}); + for (auto i = 1u; i < tone_keys.size(); ++i) { + auto& item = tone_keys[i]; + options.push_back({fx100_string(item.second), (OptionsField::value_t)i}); } field_tone.set_options(std::move(options)); diff --git a/firmware/application/apps/ui_freqman.hpp b/firmware/application/apps/ui_freqman.hpp index a93e48468..6f9ae453d 100644 --- a/firmware/application/apps/ui_freqman.hpp +++ b/firmware/application/apps/ui_freqman.hpp @@ -256,3 +256,9 @@ class FrequencyEditView : public View { }; } /* namespace ui */ + +void freqman_set_bandwidth_option(freqman_index_t modulation, ui::OptionsField& option); +void freqman_set_modulation_option(ui::OptionsField& option); +void freqman_set_step_option(ui::OptionsField& option); +void freqman_set_step_option_short(ui::OptionsField& option); +void freqman_set_tone_option(ui::OptionsField& option); diff --git a/firmware/application/apps/ui_level.cpp b/firmware/application/apps/ui_level.cpp index 68c294e13..0a3e8fa20 100644 --- a/firmware/application/apps/ui_level.cpp +++ b/firmware/application/apps/ui_level.cpp @@ -23,6 +23,7 @@ #include "ui_level.hpp" #include "ui_fileman.hpp" +#include "ui_freqman.hpp" #include "file.hpp" using namespace portapack; diff --git a/firmware/application/apps/ui_level.hpp b/firmware/application/apps/ui_level.hpp index 862e79d89..14b476133 100644 --- a/firmware/application/apps/ui_level.hpp +++ b/firmware/application/apps/ui_level.hpp @@ -24,21 +24,21 @@ #ifndef _UI_LEVEL #define _UI_LEVEL -#include "ui.hpp" -#include "receiver_model.hpp" -#include "ui_receiver.hpp" -#include "ui_styles.hpp" -#include "freqman.hpp" #include "analog_audio_app.hpp" +#include "app_settings.hpp" #include "audio.hpp" -#include "ui_mictx.hpp" -#include "portapack_persistent_memory.hpp" #include "baseband_api.hpp" -#include "ui_spectrum.hpp" -#include "string_format.hpp" #include "file.hpp" -#include "app_settings.hpp" +#include "freqman_db.hpp" +#include "portapack_persistent_memory.hpp" #include "radio_state.hpp" +#include "receiver_model.hpp" +#include "string_format.hpp" +#include "ui.hpp" +#include "ui_mictx.hpp" +#include "ui_receiver.hpp" +#include "ui_spectrum.hpp" +#include "ui_styles.hpp" namespace ui { diff --git a/firmware/application/apps/ui_recon.cpp b/firmware/application/apps/ui_recon.cpp index a469205bf..1f40da0ca 100644 --- a/firmware/application/apps/ui_recon.cpp +++ b/firmware/application/apps/ui_recon.cpp @@ -23,13 +23,17 @@ #include "ui_recon.hpp" #include "ui_fileman.hpp" +#include "ui_freqman.hpp" #include "capture_app.hpp" +#include "convert.hpp" #include "file.hpp" +#include "file_reader.hpp" #include "tone_key.hpp" using namespace portapack; using namespace tonekey; using portapack::memory::map::backup_ram; +namespace fs = std::filesystem; namespace ui { @@ -124,152 +128,81 @@ void ReconView::colorize_waits() { } } -bool ReconView::recon_save_freq(const std::string& freq_file_path, size_t freq_index, bool warn_if_exists) { - File recon_file; - +bool ReconView::recon_save_freq(const fs::path& path, size_t freq_index, bool warn_if_exists) { if (frequency_list.size() == 0 || !current_is_valid()) return false; + FreqmanDB freq_db; + if (!freq_db.open(path, /*create*/ true)) + return false; + freqman_entry entry = *frequency_list[freq_index]; // Makes a copy. - entry.frequency_a = freq; - entry.frequency_b = 0; - entry.modulation = last_entry.modulation; - entry.bandwidth = last_entry.bandwidth; - entry.type = freqman_type::Single; - - // TODO: Use FreqmanDB - auto frequency_to_add = to_freqman_string(entry); - - auto result = recon_file.open(freq_file_path); // First recon if freq is already in txt - if (!result.is_valid()) { - char one_char[1]{}; // Read it char by char - std::string line{}; // and put read line in here - bool found{false}; - for (size_t pointer = 0; pointer < recon_file.size(); pointer++) { - recon_file.seek(pointer); - recon_file.read(one_char, 1); - if ((int)one_char[0] > 31) { // ascii space upwards - line += one_char[0]; // add it to the textline - } else if (one_char[0] == '\n') { // New Line - if (line.compare(0, frequency_to_add.size(), frequency_to_add) == 0) { - found = true; - break; - } - line.clear(); // Ready for next textline - } - } - if (!found) { - result = recon_file.append(freq_file_path); // Second: append if it is not there - if (!result.is_valid()) { - recon_file.write_line(frequency_to_add); - } - } - if (found && warn_if_exists) { - nav_.display_modal("Error", "Frequency already exists"); - } - } else { - result = recon_file.create(freq_file_path); // First freq if no output file - if (!result.is_valid()) { - recon_file.write_line(frequency_to_add); - } + + // For ranges, save the current frequency instead. + if (entry.type == freqman_type::Range) { + entry.frequency_a = freq; + entry.frequency_b = 0; + entry.type = freqman_type::Single; + entry.modulation = last_entry.modulation; + entry.bandwidth = last_entry.bandwidth; + entry.step = freqman_invalid_index; } + + auto it = freq_db.find_entry(entry); + auto found = (it != freq_db.end()); + + if (found && warn_if_exists) + nav_.display_modal("Error", "Frequency already exists"); + + if (!found) + freq_db.append_entry(entry); + return true; } bool ReconView::recon_load_config_from_sd() { - File settings_file{}; - size_t length{0}; - size_t file_position{0}; - char* pos{NULL}; - char* line_start{NULL}; - char* line_end{NULL}; - char file_data[257]{}; - uint32_t it{0}; - uint32_t nb_params{RECON_SETTINGS_NB_PARAMS}; - std::string params[RECON_SETTINGS_NB_PARAMS]{}; - make_new_directory(u"SETTINGS"); - auto result = settings_file.open(RECON_CFG_FILE); - if (!result.is_valid()) { - while (it < nb_params) { - // Read a 256 bytes block from file - settings_file.seek(file_position); - memset(file_data, 0, 257); - auto read_size = settings_file.read(file_data, 256); - if (read_size.is_error()) - break; - file_position += 256; - // Reset line_start to beginning of buffer - line_start = file_data; - pos = line_start; - while ((line_end = strstr(line_start, "\x0A"))) { - length = line_end - line_start - 1; - params[it] = std::string(pos, length); - it++; - line_start = line_end + 1; - pos = line_start; - if (line_start - file_data >= 256) - break; - if (it >= nb_params) - break; - } - if (read_size.value() != 256) - break; // End of file - - // Restart at beginning of last incomplete line - file_position -= (file_data + 256 - line_start); - } - } - - if (it < nb_params) { - /* bad number of params, signal defaults */ - input_file = "RECON"; - output_file = "RECON_RESULTS"; - recon_lock_duration = RECON_MIN_LOCK_DURATION; - recon_lock_nb_match = RECON_DEF_NB_MATCH; - squelch = -14; - recon_match_mode = RECON_MATCH_CONTINUOUS; - wait = RECON_DEF_WAIT_DURATION; + File settings_file; + auto error = settings_file.open(RECON_CFG_FILE); + if (error) return false; - } - - if (it > 0) - input_file = params[0]; - else - input_file = "RECON"; - if (it > 1) - output_file = params[1]; - else - output_file = "RECON_RESULTS"; - - if (it > 2) - recon_lock_duration = strtoll(params[2].c_str(), nullptr, 10); - else - recon_lock_duration = RECON_MIN_LOCK_DURATION; - - if (it > 3) - recon_lock_nb_match = strtoll(params[3].c_str(), nullptr, 10); - else - recon_lock_nb_match = RECON_DEF_NB_MATCH; - - if (it > 4) - squelch = strtoll(params[4].c_str(), nullptr, 10); - else - squelch = -14; + auto complete = false; + auto line_nb = 0; + auto reader = FileLineReader(settings_file); + for (const auto& line : reader) { + switch (line_nb) { + case 0: + input_file = trim(line); + break; + case 1: + output_file = trim(line); + break; + case 2: + parse_int(line, recon_lock_duration); + break; + case 3: + parse_int(line, recon_lock_nb_match); + break; + case 4: + parse_int(line, squelch); + break; + case 5: + parse_int(line, recon_match_mode); + break; + case 6: + parse_int(line, recon_match_mode); + complete = true; // NB: Last entry. + break; + } - if (it > 5) - recon_match_mode = strtoll(params[5].c_str(), nullptr, 10); - else - recon_match_mode = RECON_MATCH_CONTINUOUS; + if (complete) break; - if (it > 6) - wait = strtoll(params[6].c_str(), nullptr, 10); - else - wait = RECON_DEF_WAIT_DURATION; + line_nb++; + } - return true; + return complete; } bool ReconView::recon_save_config_to_sd() { @@ -486,6 +419,7 @@ ReconView::ReconView(NavigationView& nav) if (frequency_list.size() > 0) { auto new_view = nav_.push(current_index); new_view->on_changed = [this, &button](rf::Frequency f) { + // NB: This is using the freq keypad to select an index. f = f / OneMHz; if (f >= 1 && f <= frequency_list.size()) { index_stepper = f - 1 - current_index; @@ -496,17 +430,18 @@ ReconView::ReconView(NavigationView& nav) }; button_manual_start.on_change = [this]() { - frequency_range.min = frequency_range.min + button_manual_start.get_encoder_delta() * freqman_entry_get_step_value(def_step); + auto step_val = freqman_entry_get_step_value(def_step); + frequency_range.min = frequency_range.min + button_manual_start.get_encoder_delta() * step_val; if (frequency_range.min < 0) { frequency_range.min = 0; } - if (frequency_range.min > (MAX_UFREQ - freqman_entry_get_step_value(def_step))) { - frequency_range.min = MAX_UFREQ - freqman_entry_get_step_value(def_step); + if (frequency_range.min > (MAX_UFREQ - step_val)) { + frequency_range.min = MAX_UFREQ - step_val; } - if (frequency_range.min > (frequency_range.max - freqman_entry_get_step_value(def_step))) { - frequency_range.max = frequency_range.min + freqman_entry_get_step_value(def_step); + if (frequency_range.min > (frequency_range.max - step_val)) { + frequency_range.max = frequency_range.min + step_val; if (frequency_range.max > MAX_UFREQ) { - frequency_range.min = MAX_UFREQ - freqman_entry_get_step_value(def_step); + frequency_range.min = MAX_UFREQ - step_val; frequency_range.max = MAX_UFREQ; } } @@ -516,18 +451,19 @@ ReconView::ReconView(NavigationView& nav) }; button_manual_end.on_change = [this]() { - frequency_range.max = frequency_range.max + button_manual_end.get_encoder_delta() * freqman_entry_get_step_value(def_step); - if (frequency_range.max < (freqman_entry_get_step_value(def_step) + 1)) { - frequency_range.max = (freqman_entry_get_step_value(def_step) + 1); + auto step_val = freqman_entry_get_step_value(def_step); + frequency_range.max = frequency_range.max + button_manual_end.get_encoder_delta() * step_val; + if (frequency_range.max < (step_val + 1)) { + frequency_range.max = (step_val + 1); } if (frequency_range.max > MAX_UFREQ) { frequency_range.max = MAX_UFREQ; } - if (frequency_range.max < (frequency_range.min + freqman_entry_get_step_value(def_step))) { - frequency_range.min = frequency_range.max - freqman_entry_get_step_value(def_step); - if (frequency_range.max < (freqman_entry_get_step_value(def_step) + 1)) { + if (frequency_range.max < (frequency_range.min + step_val)) { + frequency_range.min = frequency_range.max - step_val; + if (frequency_range.max < (step_val + 1)) { frequency_range.min = 1; - frequency_range.max = (freqman_entry_get_step_value(def_step) + 1); + frequency_range.max = (step_val + 1); } } button_manual_start.set_text(to_string_short_freq(frequency_range.min)); @@ -607,87 +543,9 @@ ReconView::ReconView(NavigationView& nav) }; button_remove.on_select = [this](ButtonWithEncoder&) { - // TODO: Use FreqmanDB - if (frequency_list.size() > 0) { - if (!manual_mode) { - // scanner or recon (!scanner) mode - // in both we delete index from live view, but only remove in output file in scanner_mode - if (current_index >= (int32_t)frequency_list.size()) { - current_index = frequency_list.size() - 1; - } - frequency_list.erase(frequency_list.begin() + current_index); - if (current_index >= (int32_t)frequency_list.size()) { - current_index = frequency_list.size() - 1; - } - if (frequency_list.size() > 0) { - text_cycle.set_text(to_string_dec_uint(current_index + 1, 3)); - update_description(); - } - // also remove from output file if in scanner mode - if (scanner_mode) { - File freqman_file{}; - delete_file(freq_file_path); - auto result = freqman_file.create(freq_file_path); - if (!result.is_valid()) { - for (size_t n = 0; n < frequency_list.size(); n++) { - auto line = to_freqman_string(*frequency_list[n]); - freqman_file.write_line(line); - } - } - } - } else if (manual_mode) // only remove from output - { - File recon_file{}; - File tmp_recon_file{}; - std::string tmp_freq_file_path{freq_file_path + ".TMP"}; - - freqman_entry entry = current_entry(); - entry.frequency_a = freq; - entry.frequency_b = 0; - entry.modulation = last_entry.modulation; - entry.bandwidth = last_entry.bandwidth; - entry.type = freqman_type::Single; - - auto frequency_to_add = to_freqman_string(entry); - - delete_file(tmp_freq_file_path); - auto result = tmp_recon_file.create(tmp_freq_file_path); // First recon if freq is already in txt - if (!result.is_valid()) { - bool found = false; - result = recon_file.open(freq_file_path); // First recon if freq is already in txt - if (!result.is_valid()) { - char one_char[1]{}; // Read it char by char - std::string line{}; // and put read line in here - for (size_t pointer = 0; pointer < recon_file.size(); pointer++) { - recon_file.seek(pointer); - recon_file.read(one_char, 1); - if ((int)one_char[0] > 31) { // ascii space upwards - line += one_char[0]; // add it to the textline - } else if (one_char[0] == '\n') { // new Line - if (line.compare(0, frequency_to_add.size(), frequency_to_add) == 0) { - found = true; - } else { - tmp_recon_file.write_line(frequency_to_add); - } - line.clear(); // ready for next textline - } - } - if (found) { - delete_file(freq_file_path); - rename_file(tmp_freq_file_path, freq_file_path); - } else { - delete_file(tmp_freq_file_path); - } - } - } - } - receiver_model.set_target_frequency(current_entry().frequency_a); // retune - } - if (frequency_list.size() == 0) { - text_cycle.set_text(" "); - desc_cycle.set("no entries in list"); // Show new description - delete_file(freq_file_path); - } + handle_remove_current_item(); + + // TODO: refresh UI. timer = 0; freq_lock = 0; }; @@ -921,8 +779,7 @@ ReconView::ReconView(NavigationView& nav) recon_redraw(); } -void ReconView::frequency_file_load(bool stop_all_before) { - (void)(stop_all_before); +void ReconView::frequency_file_load(bool) { if (field_mode.selected_index_value() != SPEC_MODULATION) audio::output::stop(); @@ -940,6 +797,7 @@ void ReconView::frequency_file_load(bool stop_all_before) { desc_cycle.set_style(&Styles::blue); button_scanner_mode.set_text("RECON"); } + freqman_load_options options{ .load_freqs = load_freqs, .load_ranges = load_ranges, @@ -1430,4 +1288,35 @@ void ReconView::handle_coded_squelch(const uint32_t value) { text_ctcss.set(" "); } +void ReconView::handle_remove_current_item() { + if (frequency_list.empty() || !current_is_valid()) + return; + + auto entry = current_entry(); // Copy the current entry. + + // In Scanner or Recon modes, remove from the in-memory list. + if (mode() != recon_mode::Manual) { + if (current_is_valid()) { + frequency_list.erase(frequency_list.begin() + current_index); + } + + if (frequency_list.size() > 0) { + current_index = clip(current_index, 0u, frequency_list.size()); + text_cycle.set_text(to_string_dec_uint(current_index + 1, 3)); + update_description(); + } else { + current_index = 0; + } + } + + // In Scanner or Manual mode, remove the entry from the output file. + if (mode() != recon_mode::Recon) { + FreqmanDB freq_db; + if (!freq_db.open(freq_file_path)) + return; + + freq_db.delete_entry(entry); + } +} + } /* namespace ui */ diff --git a/firmware/application/apps/ui_recon.hpp b/firmware/application/apps/ui_recon.hpp index df0042490..f55ec18f6 100644 --- a/firmware/application/apps/ui_recon.hpp +++ b/firmware/application/apps/ui_recon.hpp @@ -46,6 +46,12 @@ namespace ui { #define RECON_CFG_FILE "SETTINGS/recon.cfg" +enum class recon_mode : uint8_t { + Recon, + Scanner, + Manual +}; + class ReconView : public View { public: ReconView(NavigationView& nav); @@ -82,9 +88,10 @@ class ReconView : public View { void recon_redraw(); void handle_retune(); void handle_coded_squelch(const uint32_t value); + void handle_remove_current_item(); bool recon_load_config_from_sd(); bool recon_save_config_to_sd(); - bool recon_save_freq(const std::string& freq_file_path, size_t index, bool warn_if_exists); + bool recon_save_freq(const std::filesystem::path& path, size_t index, bool warn_if_exists); // placeholder for possible void recon_start_recording(); void recon_stop_recording(); @@ -92,8 +99,15 @@ class ReconView : public View { bool current_is_valid(); freqman_entry& current_entry(); + // TODO: consolidate mode bools and use recon_mode. + recon_mode mode() const { + if (manual_mode) return recon_mode::Manual; + if (scanner_mode) return recon_mode::Scanner; + return recon_mode::Recon; + } + jammer::jammer_range_t frequency_range{false, 0, MAX_UFREQ}; // perfect for manual recon task too... - int32_t squelch{0}; + int32_t squelch{RECON_DEF_SQUELCH}; int32_t db{0}; int32_t timer{0}; int32_t wait{RECON_DEF_WAIT_DURATION}; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity @@ -117,7 +131,7 @@ class ReconView : public View { bool user_pause{false}; bool auto_record_locked{false}; bool is_recording{false}; - uint32_t recon_lock_nb_match{3}; + uint32_t recon_lock_nb_match{RECON_DEF_NB_MATCH}; uint32_t recon_lock_duration{RECON_MIN_LOCK_DURATION}; uint32_t recon_match_mode{RECON_MATCH_CONTINUOUS}; bool scanner_mode{false}; diff --git a/firmware/application/apps/ui_recon_settings.cpp b/firmware/application/apps/ui_recon_settings.cpp index 390d37245..3ab04d332 100644 --- a/firmware/application/apps/ui_recon_settings.cpp +++ b/firmware/application/apps/ui_recon_settings.cpp @@ -70,11 +70,9 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st button_save_freqs.on_select = [this, &nav](Button&) { auto open_view = nav.push(".TXT"); open_view->on_changed = [this, &nav](std::filesystem::path new_file_path) { - std::string dir_filter = "FREQMAN/"; - std::string str_file_path = new_file_path.string(); - if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder + if (new_file_path.native().find(freqman_dir.native()) == 0) { _output_file = new_file_path.stem().string(); - button_output_file.set_text(_output_file); + text_input_file.set(_output_file); } else { nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired."); } @@ -84,7 +82,7 @@ ReconSetupViewMain::ReconSetupViewMain(NavigationView& nav, Rect parent_rect, st button_output_file.on_select = [this, &nav](Button&) { text_prompt(nav, _output_file, 28, [this](std::string& buffer) { - _output_file = buffer; + _output_file = std::move(buffer); button_output_file.set_text(_output_file); }); }; diff --git a/firmware/application/apps/ui_recon_settings.hpp b/firmware/application/apps/ui_recon_settings.hpp index dcfa3c951..3c4ba858f 100644 --- a/firmware/application/apps/ui_recon_settings.hpp +++ b/firmware/application/apps/ui_recon_settings.hpp @@ -50,6 +50,8 @@ // maximum lock duration #define RECON_MAX_LOCK_DURATION 9900 +#define RECON_DEF_SQUELCH -14 + // default number of match to have a lock #define RECON_DEF_NB_MATCH 3 #define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL diff --git a/firmware/application/apps/ui_scanner.cpp b/firmware/application/apps/ui_scanner.cpp index 1e6e15e55..48a456c0b 100644 --- a/firmware/application/apps/ui_scanner.cpp +++ b/firmware/application/apps/ui_scanner.cpp @@ -22,6 +22,7 @@ #include "ui_scanner.hpp" #include "ui_fileman.hpp" +#include "ui_freqman.hpp" using namespace portapack; diff --git a/firmware/application/apps/ui_spectrum_painter.cpp b/firmware/application/apps/ui_spectrum_painter.cpp index 4c1213080..b4e6ac688 100644 --- a/firmware/application/apps/ui_spectrum_painter.cpp +++ b/firmware/application/apps/ui_spectrum_painter.cpp @@ -20,13 +20,14 @@ */ #include "ui_spectrum_painter.hpp" + #include "bmp.hpp" #include "baseband_api.hpp" - -#include "ui_fileman.hpp" -#include "io_file.hpp" #include "file.hpp" +#include "io_file.hpp" #include "portapack_persistent_memory.hpp" +#include "ui_fileman.hpp" +#include "ui_freqman.hpp" using namespace portapack; diff --git a/firmware/application/file.cpp b/firmware/application/file.cpp index 9589c408e..40f1285c1 100644 --- a/firmware/application/file.cpp +++ b/firmware/application/file.cpp @@ -45,8 +45,11 @@ Optional File::open_fatfs(const std::filesystem::path& filename, BY } } -Optional File::open(const std::filesystem::path& filename, bool read_only) { +Optional File::open(const std::filesystem::path& filename, bool read_only, bool create) { BYTE mode = read_only ? FA_READ : FA_READ | FA_WRITE; + if (create) + mode |= FA_OPEN_ALWAYS; + return open_fatfs(filename, mode); } diff --git a/firmware/application/file.hpp b/firmware/application/file.hpp index 3c990e9a5..944747808 100644 --- a/firmware/application/file.hpp +++ b/firmware/application/file.hpp @@ -319,7 +319,7 @@ class File { File& operator=(const File&) = delete; // TODO: Return Result<>. - Optional open(const std::filesystem::path& filename, bool read_only = true); + Optional open(const std::filesystem::path& filename, bool read_only = true, bool create = false); Optional append(const std::filesystem::path& filename); Optional create(const std::filesystem::path& filename); diff --git a/firmware/application/file_wrapper.hpp b/firmware/application/file_wrapper.hpp index 07d3b4078..02d33f86e 100644 --- a/firmware/application/file_wrapper.hpp +++ b/firmware/application/file_wrapper.hpp @@ -463,9 +463,9 @@ class FileWrapper : public BufferWrapper { template using Result = File::Result; using Error = File::Error; - static Result> open(const std::filesystem::path& path) { + static Result> open(const std::filesystem::path& path, bool create = false) { auto fw = std::unique_ptr(new FileWrapper()); - auto error = fw->file_.open(path, /*read_only*/ false); + auto error = fw->file_.open(path, /*read_only*/ false, create); if (error) return *error; diff --git a/firmware/application/freqman.cpp b/firmware/application/freqman.cpp index 4ec6f7cab..17f3d66b0 100644 --- a/firmware/application/freqman.cpp +++ b/firmware/application/freqman.cpp @@ -22,92 +22,16 @@ * Boston, MA 02110-1301, USA. */ -#include "file.hpp" #include "freqman.hpp" -#include "tone_key.hpp" #include "ui_widget.hpp" -#include +using option_t = ui::OptionsField::option_t; +using options_t = ui::OptionsField::options_t; -namespace fs = std::filesystem; -using namespace tonekey; -using namespace ui; - -using option_t = std::pair; -using options_t = std::vector; - -extern options_t freqman_modulations; -extern options_t freqman_bandwidths[4]; extern options_t freqman_steps; -extern options_t freqman_steps_short; - extern const option_t* find_by_index(const options_t& options, freqman_index_t index); -// TODO: remove in favor of FreqmanDB -/* Freqman file handling. */ -bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options) { - return parse_freqman_file(get_freqman_path(file_stem), db, options); -} - -bool delete_freqman_file(const std::string& file_stem) { - delete_file(get_freqman_path(file_stem)); - return true; -} - -bool save_freqman_file(const std::string& file_stem, freqman_db& db) { - auto path = get_freqman_path(file_stem); - delete_file(path); - - File freqman_file; - auto error = freqman_file.create(path); - if (error) - return false; - - for (size_t n = 0; n < db.size(); n++) - freqman_file.write_line(to_freqman_string(*db[n])); - - return true; -} - -bool create_freqman_file(const std::string& file_stem) { - auto fs_error = make_new_file(get_freqman_path(file_stem)); - return fs_error.ok(); -} - -/* Set options. */ -void freqman_set_modulation_option(OptionsField& option) { - option.set_options(freqman_modulations); -} - -void freqman_set_bandwidth_option(freqman_index_t modulation, OptionsField& option) { - if (is_valid(modulation)) - option.set_options(freqman_bandwidths[modulation]); -} - -void freqman_set_step_option(OptionsField& option) { - option.set_options(freqman_steps); -} - -void freqman_set_step_option_short(OptionsField& option) { - option.set_options(freqman_steps_short); -} - /* Option value lookup. */ -// TODO: use Optional instead of magic values. -int32_t freqman_entry_get_modulation_value(freqman_index_t modulation) { - if (auto opt = find_by_index(freqman_modulations, modulation)) - return opt->second; - return -1; -} - -int32_t freqman_entry_get_bandwidth_value(freqman_index_t modulation, freqman_index_t bandwidth) { - if (modulation < freqman_modulations.size()) { - if (auto opt = find_by_index(freqman_bandwidths[modulation], bandwidth)) - return opt->second; - } - return -1; -} - int32_t freqman_entry_get_step_value(freqman_index_t step) { if (auto opt = find_by_index(freqman_steps, step)) return opt->second; diff --git a/firmware/application/freqman.hpp b/firmware/application/freqman.hpp index da4498fa5..74bf9198e 100644 --- a/firmware/application/freqman.hpp +++ b/firmware/application/freqman.hpp @@ -25,13 +25,7 @@ #ifndef __FREQMAN_H__ #define __FREQMAN_H__ -#include -#include -#include "file.hpp" #include "freqman_db.hpp" -#include "string_format.hpp" -#include "ui_receiver.hpp" -#include "ui_widget.hpp" // Defined for back-compat. #define FREQMAN_MAX_PER_FILE freqman_default_max_entries @@ -50,21 +44,7 @@ enum freqman_entry_modulation : uint8_t { SPEC_MODULATION }; -// TODO: Replace with FreqmanDB. -bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options); -bool delete_freqman_file(const std::string& file_stem); -bool save_freqman_file(const std::string& file_stem, freqman_db& db); -bool create_freqman_file(const std::string& file_stem); - -void freqman_set_bandwidth_option(freqman_index_t modulation, ui::OptionsField& option); -void freqman_set_modulation_option(ui::OptionsField& option); -void freqman_set_step_option(ui::OptionsField& option); -void freqman_set_step_option_short(ui::OptionsField& option); -void freqman_set_tone_option(ui::OptionsField& option); - // TODO: Can these be removed after Recon is migrated to FreqmanDB? -int32_t freqman_entry_get_modulation_value(freqman_index_t modulation); -int32_t freqman_entry_get_bandwidth_value(freqman_index_t modulation, freqman_index_t bandwidth); int32_t freqman_entry_get_step_value(freqman_index_t step); #endif /*__FREQMAN_H__*/ diff --git a/firmware/application/freqman_db.cpp b/firmware/application/freqman_db.cpp index ae0fac442..2eea4516d 100644 --- a/firmware/application/freqman_db.cpp +++ b/firmware/application/freqman_db.cpp @@ -40,10 +40,6 @@ namespace fs = std::filesystem; const std::filesystem::path freqman_dir{u"/FREQMAN"}; const std::filesystem::path freqman_extension{u".TXT"}; -const std::filesystem::path get_freqman_path(const std::string& stem) { - return freqman_dir / stem + freqman_extension; -} - // NB: Don't include UI headers to keep this code unit testable. using option_t = std::pair; using options_t = std::vector; @@ -164,6 +160,27 @@ const option_t* find_by_index(const options_t& options, freqman_index_t index) { *} */ +bool operator==(const freqman_entry& lhs, const freqman_entry& rhs) { + auto equal = lhs.type == rhs.type && + lhs.frequency_a == rhs.frequency_a && + lhs.description == rhs.description && + lhs.modulation == rhs.modulation && + lhs.bandwidth == rhs.bandwidth; + + if (!equal) + return false; + + if (lhs.type == freqman_type::Range) { + equal = lhs.frequency_b == rhs.frequency_b && + lhs.step == rhs.step; + } else if (lhs.type == freqman_type::HamRadio) { + equal = lhs.frequency_b == rhs.frequency_b && + lhs.tone == rhs.tone; + } + + return equal; +} + std::string freqman_entry_get_modulation_string(freqman_index_t modulation) { if (auto opt = find_by_index(freqman_modulations, modulation)) return opt->first; @@ -190,6 +207,23 @@ std::string freqman_entry_get_step_string_short(freqman_index_t step) { return {}; } +const std::filesystem::path get_freqman_path(const std::string& stem) { + return freqman_dir / stem + freqman_extension; +} + +bool create_freqman_file(const std::string& file_stem) { + auto fs_error = make_new_file(get_freqman_path(file_stem)); + return fs_error.ok(); +} + +bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options) { + return parse_freqman_file(get_freqman_path(file_stem), db, options); +} + +void delete_freqman_file(const std::string& file_stem) { + delete_file(get_freqman_path(file_stem)); +} + std::string pretty_string(const freqman_entry& entry, size_t max_length) { std::string str; @@ -198,12 +232,12 @@ std::string pretty_string(const freqman_entry& entry, size_t max_length) { str = to_string_short_freq(entry.frequency_a) + "M: " + entry.description; break; case freqman_type::Range: - str = to_string_dec_uint(entry.frequency_a / 1'000'000) + "M-" + - to_string_dec_uint(entry.frequency_b / 1'000'000) + "M: " + entry.description; + str = to_string_rounded_freq(entry.frequency_a, 1) + "M-" + + to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description; break; case freqman_type::HamRadio: - str = "R:" + to_string_dec_uint(entry.frequency_a / 1'000'000) + "M,T:" + - to_string_dec_uint(entry.frequency_b / 1'000'000) + "M: " + entry.description; + str = "R:" + to_string_rounded_freq(entry.frequency_a, 1) + "M,T:" + + to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description; break; case freqman_type::Raw: str = entry.description; @@ -342,27 +376,20 @@ bool parse_freqman_entry(std::string_view str, freqman_entry& entry) { return is_valid(entry); } -// TODO: Use FreqmanDB iterator. bool parse_freqman_file(const fs::path& path, freqman_db& db, freqman_load_options options) { - File f; - auto error = f.open(path); - if (error) + FreqmanDB freqman_db; + freqman_db.set_read_raw(false); // Don't return malformed lines. + if (!freqman_db.open(path)) return false; - auto reader = FileLineReader(f); - auto line_count = count_lines(reader); - // Attempt to avoid a re-alloc if possible. db.clear(); - db.reserve(line_count); - - for (const auto& line : reader) { - freqman_entry entry{}; - if (!parse_freqman_entry(line, entry)) - continue; + db.reserve(freqman_db.entry_count()); + for (auto entry : freqman_db) { // Filter by entry type. - if ((entry.type == freqman_type::Single && !options.load_freqs) || + if (entry.type == freqman_type::Unknown || + (entry.type == freqman_type::Single && !options.load_freqs) || (entry.type == freqman_type::Range && !options.load_ranges) || (entry.type == freqman_type::HamRadio && !options.load_hamradios)) { continue; @@ -411,6 +438,7 @@ bool is_valid(const freqman_entry& entry) { // TODO: Consider additional validation: // - Tone only on HamRadio. + // - Step only on Range // - Fail on failed parse_int. // - Fail if bandwidth set before modulation. @@ -419,8 +447,8 @@ bool is_valid(const freqman_entry& entry) { /* FreqmanDB ***********************************/ -bool FreqmanDB::open(const std::filesystem::path& path) { - auto result = FileWrapper::open(path); +bool FreqmanDB::open(const std::filesystem::path& path, bool create) { + auto result = FileWrapper::open(path, create); if (!result) return false; @@ -432,15 +460,15 @@ void FreqmanDB::close() { wrapper_.reset(); } -freqman_entry FreqmanDB::operator[](FileWrapper::Line line) const { - auto length = wrapper_->line_length(line); - auto line_text = wrapper_->get_text(line, 0, length); +freqman_entry FreqmanDB::operator[](Index index) const { + auto length = wrapper_->line_length(index); + auto line_text = wrapper_->get_text(index, 0, length); if (line_text) { freqman_entry entry; if (parse_freqman_entry(*line_text, entry)) return entry; - else { + else if (read_raw_) { entry.type = freqman_type::Raw; entry.description = trim(*line_text); return entry; @@ -450,15 +478,18 @@ freqman_entry FreqmanDB::operator[](FileWrapper::Line line) const { return {}; } -void FreqmanDB::insert_entry(const freqman_entry& entry, FileWrapper::Line line) { - // TODO: Can be more efficient. - line = clip(line, 0u, entry_count()); - wrapper_->insert_line(line); - replace_entry(line, entry); +void FreqmanDB::insert_entry(Index index, const freqman_entry& entry) { + index = clip(index, 0u, entry_count()); + wrapper_->insert_line(index); + replace_entry(index, entry); +} + +void FreqmanDB::append_entry(const freqman_entry& entry) { + insert_entry(entry_count(), entry); } -void FreqmanDB::replace_entry(FileWrapper::Line line, const freqman_entry& entry) { - auto range = wrapper_->line_range(line); +void FreqmanDB::replace_entry(Index index, const freqman_entry& entry) { + auto range = wrapper_->line_range(index); if (!range) return; @@ -467,8 +498,23 @@ void FreqmanDB::replace_entry(FileWrapper::Line line, const freqman_entry& entry wrapper_->replace_range(*range, to_freqman_string(entry)); } -void FreqmanDB::delete_entry(FileWrapper::Line line) { - wrapper_->delete_line(line); +void FreqmanDB::delete_entry(Index index) { + wrapper_->delete_line(index); +} + +bool FreqmanDB::delete_entry(const freqman_entry& entry) { + auto it = find_entry(entry); + if (it == end()) + return false; + + delete_entry(it.index()); + return true; +} + +FreqmanDB::iterator FreqmanDB::find_entry(const freqman_entry& entry) { + return find_entry([&entry](const auto& other) { + return entry == other; + }); } uint32_t FreqmanDB::entry_count() const { diff --git a/firmware/application/freqman_db.hpp b/firmware/application/freqman_db.hpp index e51de13e2..a93bd6f66 100644 --- a/firmware/application/freqman_db.hpp +++ b/firmware/application/freqman_db.hpp @@ -52,9 +52,6 @@ constexpr bool is_invalid(freqman_index_t index) { return index == freqman_invalid_index; } -/* Gets the full path for a given file stem (no extension). */ -const std::filesystem::path get_freqman_path(const std::string& stem); - enum class freqman_type : uint8_t { Single, // f= Range, // a=,b= @@ -151,6 +148,8 @@ struct freqman_entry { freqman_index_t tone{freqman_invalid_index}; }; +bool operator==(const freqman_entry& lhs, const freqman_entry& rhs); + // TODO: These shouldn't be exported. std::string freqman_entry_get_modulation_string(freqman_index_t modulation); std::string freqman_entry_get_bandwidth_string(freqman_index_t modulation, freqman_index_t bandwidth); @@ -173,6 +172,12 @@ struct freqman_load_options { using freqman_entry_ptr = std::unique_ptr; using freqman_db = std::vector; +/* Gets the full path for a given file stem (no extension). */ +const std::filesystem::path get_freqman_path(const std::string& stem); +bool create_freqman_file(const std::string& file_stem); +bool load_freqman_file(const std::string& file_stem, freqman_db& db, freqman_load_options options); +void delete_freqman_file(const std::string& file_stem); + /* Gets a pretty string representation for an entry. */ std::string pretty_string(const freqman_entry& item, size_t max_length = 30); @@ -185,54 +190,91 @@ bool parse_freqman_file(const std::filesystem::path& path, freqman_db& db, freqm /* Returns true if the entry is well-formed. */ bool is_valid(const freqman_entry& entry); -/* The tricky part of using the file directly is that there can be comments - * and empty lines in the file. This messes up the 'count' calculation. - * Either have to live with 'count' being an upper bound have the callers - * know to expect that entries may be empty. */ -// NB: This won't apply implicit mod/bandwidth. -// TODO: Reuse for parse_freqman_file +/* API wrapper over a Freqman file. Provides CRUD operations + * for freqman_entry instances that are read/written directly + * to the underlying file. */ class FreqmanDB { public: + using Index = FileWrapper::Line; + + /* NB: This iterator is very basic: forward only, read-only. */ class iterator { public: - iterator(FreqmanDB& db, FileWrapper::Offset line) - : db_{db}, line_{line} {} + iterator(FreqmanDB& db, Index index) + : db_{db}, index_{index} {} iterator& operator++() { - line_++; + index_++; + + if (index_ >= db_.entry_count()) + index_ = end_index; + return *this; } freqman_entry operator*() const { - return db_[line_]; + return db_[index_]; + } + + bool operator==(const iterator& other) { + return &db_ == &other.db_ && index_ == other.index_; } bool operator!=(const iterator& other) { - return &db_ != &other.db_ || line_ != other.line_; + return !(*this == other); + } + + Index index() const { + return index_; } + /* Value indicating the 'end' iterator. */ + static constexpr Index end_index = (Index)-1; + private: FreqmanDB& db_; - FileWrapper::Line line_; + Index index_; }; - bool open(const std::filesystem::path& path); + bool open(const std::filesystem::path& path, bool create = false); void close(); - freqman_entry operator[](FileWrapper::Line line) const; - void insert_entry(const freqman_entry& entry, FileWrapper::Line line); - void replace_entry(FileWrapper::Line line, const freqman_entry& entry); - void delete_entry(FileWrapper::Line line); + + freqman_entry operator[](Index index) const; + void insert_entry(Index index, const freqman_entry& entry); + void append_entry(const freqman_entry& entry); + void replace_entry(Index index, const freqman_entry& entry); + void delete_entry(Index index); + bool delete_entry(const freqman_entry& entry); + + template + iterator find_entry(const Fn& predicate) { + // TODO: use std::find, but need to make the iterator compliant. + auto it = begin(); + const auto it_end = end(); + + while (it != it_end) { + if (predicate(*it)) + return it; + ++it; + } + + return it_end; + } + iterator find_entry(const freqman_entry& entry); uint32_t entry_count() const; bool empty() const; + /* When true, Raw entries are returned instead of Unknown. */ + void set_read_raw(bool v) { read_raw_ = v; } iterator begin() { return {*this, 0}; } iterator end() { - return {*this, entry_count()}; + return {*this, iterator::end_index}; } private: std::unique_ptr wrapper_{}; + bool read_raw_{true}; }; #endif /* __FREQMAN_DB_H__ */ \ No newline at end of file diff --git a/firmware/common/convert.hpp b/firmware/common/convert.hpp index f93bcd913..873afd2fc 100644 --- a/firmware/common/convert.hpp +++ b/firmware/common/convert.hpp @@ -22,26 +22,71 @@ #ifndef __CONVERT_H__ #define __CONVERT_H__ -#include #include +#include +#include #include #include #include -/* Zero-allocation conversion helper. */ -/* Notes: - * - T must be an integer type. - * - For base 16, input _must not_ contain a '0x' or '0X' prefix. - * - For base 8 a leading 0 on the literal is allowed. - * - Leading whitespce will cause the parse to fail. - */ -// TODO: from_chars seems to cause code bloat. -// Look into using strtol and friends instead. +constexpr size_t max_parse_int_length = 64; + +/* This undefined type is used to see the size of the + * unhandled type and to create a compilation error. */ +template +struct UnhandledSize; + +/* Returns true when errno is ERANGE. */ +inline bool range_error() { + return errno == ERANGE; +} + +/* Assigns 'value' to 'out_val' and returns true if 'value' is + * in bounds for type TOut. */ +template +bool checked_assign(TVal value, TOut& out_val) { + // No chance for overflow, just return. + if constexpr (sizeof(TVal) <= sizeof(TOut)) { + out_val = value; + return true; + } + out_val = static_cast(value); + return value >= static_cast(std::numeric_limits::min()) && + value <= static_cast(std::numeric_limits::max()); +} +/* Zero-allocation conversion helper. 'str' must be smaller than 'max_parse_int_length'. */ template std::enable_if_t, bool> parse_int(std::string_view str, T& out_val, int base = 10) { - auto result = std::from_chars(str.data(), str.data() + str.length(), out_val, base); - return static_cast(result.ec) == 0; + // Always initialize the output. + out_val = {}; + + if (str.size() > max_parse_int_length) + return false; + + // Copy onto the stack and null terminate. + char zstr[max_parse_int_length + 1]; + std::memcpy(zstr, str.data(), str.size()); + zstr[str.size()] = '\0'; + + // A little C++ type magic to select the correct conversion function. + if constexpr (sizeof(T) == sizeof(long long)) { + if constexpr (std::is_unsigned_v) + return checked_assign(strtoull(zstr, nullptr, base), out_val) && !range_error(); + else + return checked_assign(strtoll(zstr, nullptr, base), out_val) && !range_error(); + + } else if constexpr (sizeof(T) <= sizeof(long)) { + if constexpr (std::is_unsigned_v) + return checked_assign(strtoul(zstr, nullptr, base), out_val) && !range_error(); + else + return checked_assign(strtol(zstr, nullptr, base), out_val) && !range_error(); + } else { + UnhandledSize unhandled_case; + return false; + } + + return true; } #endif /*__CONVERT_H__*/ \ No newline at end of file diff --git a/firmware/test/application/test_convert.cpp b/firmware/test/application/test_convert.cpp index c1808aebc..30f912c3f 100644 --- a/firmware/test/application/test_convert.cpp +++ b/firmware/test/application/test_convert.cpp @@ -46,10 +46,11 @@ TEST_CASE("It should convert string_views.") { CHECK_EQ(val, 12345); } -TEST_CASE("It should return false for invalid input") { +// 'from_chars' supported this, but 'strtol' just returns 0. +/*TEST_CASE("It should return false for invalid input") { int val = 0; REQUIRE_FALSE(parse_int("QWERTY", val)); -} +}*/ TEST_CASE("It should return false for overflow input") { uint8_t val = 0; @@ -58,7 +59,7 @@ TEST_CASE("It should return false for overflow input") { TEST_CASE("It should convert base 16.") { int val = 0; - REQUIRE(parse_int("30", val, 16)); // NB: No '0x' + REQUIRE(parse_int("0x30", val, 16)); CHECK_EQ(val, 0x30); } @@ -74,4 +75,28 @@ TEST_CASE("It should convert as much of the string as it can.") { CHECK_EQ(val, 12345); } +TEST_CASE("It should convert 64-bit.") { + int64_t val = 0; + REQUIRE(parse_int("123456789", val)); + CHECK_EQ(val, 123456789); +} + +TEST_CASE("It should convert 32-bit.") { + int32_t val = 0; + REQUIRE(parse_int("123456", val)); + CHECK_EQ(val, 123456); +} + +TEST_CASE("It should convert 16-bit.") { + int16_t val = 0; + REQUIRE(parse_int("12345", val)); + CHECK_EQ(val, 12345); +} + +TEST_CASE("It should convert 8-bit.") { + int8_t val = 0; + REQUIRE(parse_int("123", val)); + CHECK_EQ(val, 123); +} + TEST_SUITE_END(); \ No newline at end of file