mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-06-22 05:44:31 -04:00
SymField rewrite (#1444)
* First WIP symfield * Cleanup widget code * Rebase and format * Fix 'to_integer' bug, fix siggen UI. * to_string_hex fix, unit tests for string code * Pass instance in callback * Fix on_change callbacks * Fix keyfob (not active) * to_byte_array, coaster tx cleanup * Add to_byte_array tests * Changes in ui_numbers * Fix ui_encoders * Format * Fix modemsetup view's symfields * Remove header * Format
This commit is contained in:
parent
70e0f2913f
commit
af424aa5f8
30 changed files with 607 additions and 371 deletions
|
@ -71,6 +71,7 @@ POCSAGSettingsView::POCSAGSettingsView(
|
||||||
check_hide_bad.set_value(settings_.hide_bad_data);
|
check_hide_bad.set_value(settings_.hide_bad_data);
|
||||||
check_hide_addr_only.set_value(settings_.hide_addr_only);
|
check_hide_addr_only.set_value(settings_.hide_addr_only);
|
||||||
check_ignore.set_value(settings_.enable_ignore);
|
check_ignore.set_value(settings_.enable_ignore);
|
||||||
|
|
||||||
field_ignore.set_value(settings_.address_to_ignore);
|
field_ignore.set_value(settings_.address_to_ignore);
|
||||||
|
|
||||||
button_save.on_select = [this, &nav](Button&) {
|
button_save.on_select = [this, &nav](Button&) {
|
||||||
|
@ -81,7 +82,7 @@ POCSAGSettingsView::POCSAGSettingsView(
|
||||||
settings_.hide_bad_data = check_hide_bad.value();
|
settings_.hide_bad_data = check_hide_bad.value();
|
||||||
settings_.hide_addr_only = check_hide_addr_only.value();
|
settings_.hide_addr_only = check_hide_addr_only.value();
|
||||||
settings_.enable_ignore = check_ignore.value();
|
settings_.enable_ignore = check_ignore.value();
|
||||||
settings_.address_to_ignore = field_ignore.value();
|
settings_.address_to_ignore = field_ignore.to_integer();
|
||||||
|
|
||||||
nav.pop();
|
nav.pop();
|
||||||
};
|
};
|
||||||
|
@ -155,7 +156,7 @@ POCSAGAppView::~POCSAGAppView() {
|
||||||
|
|
||||||
// Save pmem settings.
|
// Save pmem settings.
|
||||||
pmem::set_pocsag_ignore_address(settings_.address_to_ignore);
|
pmem::set_pocsag_ignore_address(settings_.address_to_ignore);
|
||||||
pmem::set_pocsag_last_address(pocsag_state.address); // For POCSAG TX.
|
pmem::set_pocsag_last_address(last_address); // For POCSAG TX.
|
||||||
}
|
}
|
||||||
|
|
||||||
void POCSAGAppView::refresh_ui() {
|
void POCSAGAppView::refresh_ui() {
|
||||||
|
|
|
@ -171,12 +171,11 @@ class POCSAGSettingsView : public View {
|
||||||
22,
|
22,
|
||||||
"Enable Ignored Address"};
|
"Enable Ignored Address"};
|
||||||
|
|
||||||
NumberField field_ignore{
|
SymField field_ignore{
|
||||||
{7 * 8, 13 * 16 + 8},
|
{7 * 8, 13 * 16 + 8},
|
||||||
7,
|
7,
|
||||||
{0, 9999999},
|
SymField::Type::Dec,
|
||||||
1,
|
true /*explicit_edit*/};
|
||||||
'0'};
|
|
||||||
|
|
||||||
Button button_save{
|
Button button_save{
|
||||||
{11 * 8, 16 * 16, 10 * 8, 2 * 16},
|
{11 * 8, 16 * 16, 10 * 8, 2 * 16},
|
||||||
|
@ -223,7 +222,7 @@ class POCSAGAppView : public View {
|
||||||
void on_packet(const POCSAGPacketMessage* message);
|
void on_packet(const POCSAGPacketMessage* message);
|
||||||
void on_stats(const POCSAGStatsMessage* stats);
|
void on_stats(const POCSAGStatsMessage* stats);
|
||||||
|
|
||||||
uint32_t last_address = 0xFFFFFFFF;
|
uint32_t last_address = 0;
|
||||||
pocsag::EccContainer ecc{};
|
pocsag::EccContainer ecc{};
|
||||||
pocsag::POCSAGState pocsag_state{&ecc};
|
pocsag::POCSAGState pocsag_state{&ecc};
|
||||||
POCSAGLogger logger{};
|
POCSAGLogger logger{};
|
||||||
|
|
|
@ -186,13 +186,12 @@ ADSBSquawkView::ADSBSquawkView(
|
||||||
&field_squawk});
|
&field_squawk});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSBSquawkView::collect_frames(const uint32_t ICAO_address, std::vector<ADSBFrame>& frame_list) {
|
void ADSBSquawkView::collect_frames(const uint32_t /*ICAO_address*/, std::vector<ADSBFrame>& frame_list) {
|
||||||
if (!enabled) return;
|
if (!enabled) return;
|
||||||
|
|
||||||
ADSBFrame temp_frame;
|
ADSBFrame temp_frame;
|
||||||
(void)ICAO_address;
|
|
||||||
|
|
||||||
encode_frame_squawk(temp_frame, field_squawk.concatenate_4_octal_u16());
|
encode_frame_squawk(temp_frame, field_squawk.to_integer());
|
||||||
|
|
||||||
frame_list.emplace_back(temp_frame);
|
frame_list.emplace_back(temp_frame);
|
||||||
}
|
}
|
||||||
|
@ -277,7 +276,7 @@ ADSBTxView::~ADSBTxView() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSBTxView::generate_frames() {
|
void ADSBTxView::generate_frames() {
|
||||||
const uint32_t ICAO_address = sym_icao.value_hex_u64();
|
const uint32_t ICAO_address = sym_icao.to_integer();
|
||||||
|
|
||||||
frames.clear();
|
frames.clear();
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@ class ADSBSquawkView : public OptionTabView {
|
||||||
SymField field_squawk{
|
SymField field_squawk{
|
||||||
{10 * 8, 2 * 16},
|
{10 * 8, 2 * 16},
|
||||||
4,
|
4,
|
||||||
SymField::SYMFIELD_OCT};
|
SymField::Type::Oct};
|
||||||
};
|
};
|
||||||
|
|
||||||
class ADSBTXThread {
|
class ADSBTXThread {
|
||||||
|
@ -235,7 +235,7 @@ class ADSBTxView : public View {
|
||||||
SymField sym_icao{
|
SymField sym_icao{
|
||||||
{10 * 8, 4 * 8},
|
{10 * 8, 4 * 8},
|
||||||
6,
|
6,
|
||||||
SymField::SYMFIELD_HEX};
|
SymField::Type::Hex};
|
||||||
|
|
||||||
Text text_frame{
|
Text text_frame{
|
||||||
{1 * 8, 29 * 8, 14 * 8, 16},
|
{1 * 8, 29 * 8, 14 * 8, 16},
|
||||||
|
|
|
@ -48,9 +48,10 @@ APRSTXView::~APRSTXView() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void APRSTXView::start_tx() {
|
void APRSTXView::start_tx() {
|
||||||
|
// TODO: Clean up this API to take string_views to avoid allocations.
|
||||||
make_aprs_frame(
|
make_aprs_frame(
|
||||||
sym_source.value_string().c_str(), num_ssid_source.value(),
|
sym_source.to_string().c_str(), num_ssid_source.value(),
|
||||||
sym_dest.value_string().c_str(), num_ssid_dest.value(),
|
sym_dest.to_string().c_str(), num_ssid_dest.value(),
|
||||||
payload);
|
payload);
|
||||||
|
|
||||||
// uint8_t * bb_data_ptr = shared_memory.bb_data.data;
|
// uint8_t * bb_data_ptr = shared_memory.bb_data.data;
|
||||||
|
|
|
@ -68,7 +68,8 @@ class APRSTXView : public View {
|
||||||
SymField sym_source{
|
SymField sym_source{
|
||||||
{7 * 8, 1 * 16},
|
{7 * 8, 1 * 16},
|
||||||
6,
|
6,
|
||||||
SymField::SYMFIELD_ALPHANUM};
|
SymField::Type::Alpha};
|
||||||
|
|
||||||
NumberField num_ssid_source{
|
NumberField num_ssid_source{
|
||||||
{19 * 8, 1 * 16},
|
{19 * 8, 1 * 16},
|
||||||
2,
|
2,
|
||||||
|
@ -79,7 +80,7 @@ class APRSTXView : public View {
|
||||||
SymField sym_dest{
|
SymField sym_dest{
|
||||||
{7 * 8, 2 * 16},
|
{7 * 8, 2 * 16},
|
||||||
6,
|
6,
|
||||||
SymField::SYMFIELD_ALPHANUM};
|
SymField::Type::Alpha};
|
||||||
|
|
||||||
NumberField num_ssid_dest{
|
NumberField num_ssid_dest{
|
||||||
{19 * 8, 2 * 16},
|
{19 * 8, 2 * 16},
|
||||||
|
|
|
@ -42,12 +42,12 @@ CoasterPagerView::~CoasterPagerView() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoasterPagerView::generate_frame() {
|
void CoasterPagerView::generate_frame() {
|
||||||
uint8_t frame[19];
|
constexpr uint8_t frame_bytes = 19;
|
||||||
uint32_t c;
|
uint8_t frame[frame_bytes];
|
||||||
|
|
||||||
// Preamble (8 bytes)
|
// Preamble (8 bytes)
|
||||||
for (c = 0; c < 8; c++)
|
for (uint8_t c = 0; c < 8; c++)
|
||||||
frame[c] = 0x55; // Isn't this 0xAA ?
|
frame[c] = 0x55;
|
||||||
|
|
||||||
// Sync word
|
// Sync word
|
||||||
frame[8] = 0x2D;
|
frame[8] = 0x2D;
|
||||||
|
@ -57,11 +57,11 @@ void CoasterPagerView::generate_frame() {
|
||||||
frame[10] = 8;
|
frame[10] = 8;
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
for (c = 0; c < 8; c++)
|
auto data_bytes = to_byte_array(sym_data.to_integer());
|
||||||
frame[c + 11] = (sym_data.get_sym(c * 2) << 4) | sym_data.get_sym(c * 2 + 1);
|
memcpy(&frame[11], data_bytes.data(), data_bytes.size());
|
||||||
|
|
||||||
// Copy for baseband
|
// Copy for baseband
|
||||||
memcpy(shared_memory.bb_data.data, frame, 19);
|
memcpy(shared_memory.bb_data.data, frame, frame_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoasterPagerView::start_tx() {
|
void CoasterPagerView::start_tx() {
|
||||||
|
@ -72,12 +72,7 @@ void CoasterPagerView::start_tx() {
|
||||||
baseband::set_fsk_data(19 * 8, 2280000 / 1000, 5000, 32);
|
baseband::set_fsk_data(19 * 8, 2280000 / 1000, 5000, 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoasterPagerView::on_tx_progress(const uint32_t progress, const bool done) {
|
void CoasterPagerView::on_tx_progress(const uint32_t /*progress*/, const bool done) {
|
||||||
(void)progress;
|
|
||||||
|
|
||||||
uint16_t address = 0;
|
|
||||||
uint32_t c;
|
|
||||||
|
|
||||||
if (done) {
|
if (done) {
|
||||||
if (tx_mode == SINGLE) {
|
if (tx_mode == SINGLE) {
|
||||||
transmitter_model.disable();
|
transmitter_model.disable();
|
||||||
|
@ -85,18 +80,12 @@ void CoasterPagerView::on_tx_progress(const uint32_t progress, const bool done)
|
||||||
tx_view.set_transmitting(false);
|
tx_view.set_transmitting(false);
|
||||||
} else if (tx_mode == SCAN) {
|
} else if (tx_mode == SCAN) {
|
||||||
// Increment address
|
// Increment address
|
||||||
|
uint64_t data = sym_data.to_integer();
|
||||||
for (c = 0; c < 4; c++) {
|
uint16_t address = data & 0xFFFF;
|
||||||
address <<= 4;
|
|
||||||
address |= sym_data.get_sym(12 + c);
|
|
||||||
}
|
|
||||||
|
|
||||||
address++;
|
address++;
|
||||||
|
data = (data & 0xFFFFFFFFFFFF0000) + address;
|
||||||
for (c = 0; c < 4; c++) {
|
sym_data.set_value(data);
|
||||||
sym_data.set_sym(15 - c, address & 0x0F);
|
|
||||||
address >>= 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
start_tx();
|
start_tx();
|
||||||
}
|
}
|
||||||
|
@ -104,9 +93,6 @@ void CoasterPagerView::on_tx_progress(const uint32_t progress, const bool done)
|
||||||
}
|
}
|
||||||
|
|
||||||
CoasterPagerView::CoasterPagerView(NavigationView& nav) {
|
CoasterPagerView::CoasterPagerView(NavigationView& nav) {
|
||||||
const uint8_t data_init[8] = {0x44, 0x01, 0x3B, 0x30, 0x30, 0x30, 0x34, 0xBC};
|
|
||||||
uint32_t c;
|
|
||||||
|
|
||||||
baseband::run_image(portapack::spi_flash::image_tag_fsktx);
|
baseband::run_image(portapack::spi_flash::image_tag_fsktx);
|
||||||
|
|
||||||
add_children({&labels,
|
add_children({&labels,
|
||||||
|
@ -115,9 +101,7 @@ CoasterPagerView::CoasterPagerView(NavigationView& nav) {
|
||||||
&text_message,
|
&text_message,
|
||||||
&tx_view});
|
&tx_view});
|
||||||
|
|
||||||
// Bytes to nibbles
|
sym_data.set_value(0x44013B30303034BC);
|
||||||
for (c = 0; c < 16; c++)
|
|
||||||
sym_data.set_sym(c, (data_init[c >> 1] >> ((c & 1) ? 0 : 4)) & 0x0F);
|
|
||||||
|
|
||||||
checkbox_scan.set_value(false);
|
checkbox_scan.set_value(false);
|
||||||
|
|
||||||
|
|
|
@ -68,17 +68,19 @@ class CoasterPagerView : public View {
|
||||||
|
|
||||||
SymField sym_data{
|
SymField sym_data{
|
||||||
{7 * 8, 8 * 8},
|
{7 * 8, 8 * 8},
|
||||||
16, // 14 ? 12 ?
|
16,
|
||||||
SymField::SYMFIELD_HEX};
|
SymField::Type::Hex};
|
||||||
|
|
||||||
Checkbox checkbox_scan{
|
Checkbox checkbox_scan{
|
||||||
{10 * 8, 14 * 8},
|
{10 * 8, 14 * 8},
|
||||||
4,
|
4,
|
||||||
"Scan"};
|
"Scan"};
|
||||||
|
|
||||||
/*ProgressBar progressbar {
|
/*
|
||||||
{ 5 * 8, 12 * 16, 20 * 8, 16 },
|
ProgressBar progressbar {
|
||||||
};*/
|
{ 5 * 8, 12 * 16, 20 * 8, 16 }};
|
||||||
|
*/
|
||||||
|
|
||||||
Text text_message{
|
Text text_message{
|
||||||
{5 * 8, 13 * 16, 20 * 8, 16},
|
{5 * 8, 13 * 16, 20 * 8, 16},
|
||||||
""};
|
""};
|
||||||
|
|
|
@ -49,7 +49,6 @@ EncodersConfigView::EncodersConfigView(
|
||||||
&field_clk_step,
|
&field_clk_step,
|
||||||
&field_frameduration,
|
&field_frameduration,
|
||||||
&field_frameduration_step,
|
&field_frameduration_step,
|
||||||
&symfield_word,
|
|
||||||
&text_format,
|
&text_format,
|
||||||
&waveform});
|
&waveform});
|
||||||
|
|
||||||
|
@ -64,10 +63,6 @@ EncodersConfigView::EncodersConfigView(
|
||||||
options_enctype.set_options(enc_options);
|
options_enctype.set_options(enc_options);
|
||||||
options_enctype.set_selected_index(0);
|
options_enctype.set_selected_index(0);
|
||||||
|
|
||||||
symfield_word.on_change = [this]() {
|
|
||||||
generate_frame();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Selecting input clock changes symbol and word duration
|
// Selecting input clock changes symbol and word duration
|
||||||
field_clk.on_change = [this](int32_t value) {
|
field_clk.on_change = [this](int32_t value) {
|
||||||
// value is in kHz, new_value is in us
|
// value is in kHz, new_value is in us
|
||||||
|
@ -98,33 +93,46 @@ void EncodersConfigView::focus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EncodersConfigView::on_type_change(size_t index) {
|
void EncodersConfigView::on_type_change(size_t index) {
|
||||||
std::string format_string = "";
|
// Remove existing SymField controls.
|
||||||
size_t word_length;
|
for (auto& symfield : symfields_word)
|
||||||
char symbol_type;
|
remove_child(symfield.get());
|
||||||
|
symfields_word.clear();
|
||||||
|
|
||||||
encoder_def = &encoder_defs[index];
|
encoder_def = &encoder_defs[index];
|
||||||
|
|
||||||
field_clk.set_value(encoder_def->default_speed / 1000);
|
field_clk.set_value(encoder_def->default_speed / 1000);
|
||||||
field_repeat_min.set_value(encoder_def->repeat_min);
|
field_repeat_min.set_value(encoder_def->repeat_min);
|
||||||
|
|
||||||
// SymField setup
|
// Add new SymFields.
|
||||||
word_length = encoder_def->word_length;
|
Point pos{2 * 8, 9 * 8};
|
||||||
symfield_word.set_length(word_length);
|
std::string format_string;
|
||||||
size_t n = 0, i = 0;
|
uint8_t word_length = encoder_def->word_length;
|
||||||
while (n < word_length) {
|
auto on_change_handler = [this](SymField&) {
|
||||||
symbol_type = encoder_def->word_format[i++];
|
generate_frame();
|
||||||
if (symbol_type == 'A') {
|
};
|
||||||
symfield_word.set_symbol_list(n++, encoder_def->address_symbols);
|
|
||||||
format_string += 'A';
|
for (uint8_t i = 0; i < word_length; i++) {
|
||||||
} else if (symbol_type == 'D') {
|
auto symbol_type = encoder_def->word_format[i];
|
||||||
symfield_word.set_symbol_list(n++, encoder_def->data_symbols);
|
symfields_word.push_back(std::make_unique<SymField>(pos, 1));
|
||||||
format_string += 'D';
|
auto& symfield = symfields_word.back();
|
||||||
|
symfield->on_change = on_change_handler;
|
||||||
|
|
||||||
|
switch (symbol_type) {
|
||||||
|
case 'A':
|
||||||
|
symfield->set_symbol_list(encoder_def->address_symbols);
|
||||||
|
format_string += 'A';
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
symfield->set_symbol_list(encoder_def->data_symbols);
|
||||||
|
format_string += 'D';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_child(symfield.get());
|
||||||
|
pos += Point{8, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ugly :( Pad to erase
|
// Ugly :( Pad to erase
|
||||||
format_string.append(24 - format_string.size(), ' ');
|
format_string.append(24 - format_string.size(), ' ');
|
||||||
|
|
||||||
text_format.set(format_string);
|
text_format.set(format_string);
|
||||||
|
|
||||||
generate_frame();
|
generate_frame();
|
||||||
|
@ -146,17 +154,18 @@ void EncodersConfigView::draw_waveform() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EncodersConfigView::generate_frame() {
|
void EncodersConfigView::generate_frame() {
|
||||||
size_t i = 0;
|
|
||||||
|
|
||||||
frame_fragments.clear();
|
frame_fragments.clear();
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
for (auto c : encoder_def->word_format) {
|
for (auto c : encoder_def->word_format) {
|
||||||
if (c == 'S')
|
if (c == 'S')
|
||||||
frame_fragments += encoder_def->sync;
|
frame_fragments += encoder_def->sync;
|
||||||
else if (!c)
|
else if (c == '\0')
|
||||||
break;
|
break;
|
||||||
else
|
else {
|
||||||
frame_fragments += encoder_def->bit_format[symfield_word.get_sym(i++)];
|
auto offset = symfields_word[i++]->get_offset(0);
|
||||||
|
frame_fragments += encoder_def->bit_format[offset];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_waveform();
|
draw_waveform();
|
||||||
|
|
|
@ -29,6 +29,9 @@
|
||||||
#include "app_settings.hpp"
|
#include "app_settings.hpp"
|
||||||
#include "radio_state.hpp"
|
#include "radio_state.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using namespace encoders;
|
using namespace encoders;
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
@ -113,10 +116,7 @@ class EncodersConfigView : public View {
|
||||||
{"100", 100},
|
{"100", 100},
|
||||||
{"1000", 1000}}};
|
{"1000", 1000}}};
|
||||||
|
|
||||||
SymField symfield_word{
|
std::vector<std::unique_ptr<SymField>> symfields_word{};
|
||||||
{2 * 8, 9 * 8},
|
|
||||||
20,
|
|
||||||
SymField::SYMFIELD_DEF};
|
|
||||||
|
|
||||||
Text text_format{
|
Text text_format{
|
||||||
{2 * 8, 11 * 8, 24 * 8, 16},
|
{2 * 8, 11 * 8, 24 * 8, 16},
|
||||||
|
|
|
@ -97,12 +97,12 @@ size_t KeyfobView::generate_frame() {
|
||||||
uint64_t payload;
|
uint64_t payload;
|
||||||
|
|
||||||
// Symfield word to frame
|
// Symfield word to frame
|
||||||
payload = field_payload_a.value_hex_u64();
|
payload = field_payload_a.to_integer();
|
||||||
for (size_t i = 0; i < 5; i++) {
|
for (size_t i = 0; i < 5; i++) {
|
||||||
frame[4 - i] = payload & 0xFF;
|
frame[4 - i] = payload & 0xFF;
|
||||||
payload >>= 8;
|
payload >>= 8;
|
||||||
}
|
}
|
||||||
payload = field_payload_b.value_hex_u64();
|
payload = field_payload_b.to_integer();
|
||||||
for (size_t i = 0; i < 5; i++) {
|
for (size_t i = 0; i < 5; i++) {
|
||||||
frame[9 - i] = payload & 0xFF;
|
frame[9 - i] = payload & 0xFF;
|
||||||
payload >>= 8;
|
payload >>= 8;
|
||||||
|
@ -136,9 +136,6 @@ void KeyfobView::focus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyfobView::~KeyfobView() {
|
KeyfobView::~KeyfobView() {
|
||||||
// save app settings
|
|
||||||
settings.save("tx_keyfob", &app_settings);
|
|
||||||
|
|
||||||
transmitter_model.disable();
|
transmitter_model.disable();
|
||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
}
|
}
|
||||||
|
@ -167,12 +164,12 @@ void KeyfobView::on_make_change(size_t index) {
|
||||||
// DEBUG
|
// DEBUG
|
||||||
void KeyfobView::update_symfields() {
|
void KeyfobView::update_symfields() {
|
||||||
for (size_t i = 0; i < 5; i++) {
|
for (size_t i = 0; i < 5; i++) {
|
||||||
field_payload_a.set_sym(i << 1, frame[i] >> 4);
|
field_payload_a.set_offset(i << 1, frame[i] >> 4);
|
||||||
field_payload_a.set_sym((i << 1) + 1, frame[i] & 0x0F);
|
field_payload_a.set_offset((i << 1) + 1, frame[i] & 0x0F);
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < 5; i++) {
|
for (size_t i = 0; i < 5; i++) {
|
||||||
field_payload_b.set_sym(i << 1, frame[5 + i] >> 4);
|
field_payload_b.set_offset(i << 1, frame[5 + i] >> 4);
|
||||||
field_payload_b.set_sym((i << 1) + 1, frame[5 + i] & 0x0F);
|
field_payload_b.set_offset((i << 1) + 1, frame[5 + i] & 0x0F);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,13 +209,6 @@ KeyfobView::KeyfobView(
|
||||||
&progressbar,
|
&progressbar,
|
||||||
&tx_view});
|
&tx_view});
|
||||||
|
|
||||||
// load app settings
|
|
||||||
auto rc = settings.load("tx_keyfob", &app_settings);
|
|
||||||
if (rc == SETTINGS_OK) {
|
|
||||||
transmitter_model.set_rf_amp(app_settings.tx_amp);
|
|
||||||
transmitter_model.set_tx_gain(app_settings.tx_gain);
|
|
||||||
}
|
|
||||||
|
|
||||||
frame[0] = 0x55;
|
frame[0] = 0x55;
|
||||||
update_symfields();
|
update_symfields();
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ class KeyfobView : public View {
|
||||||
OOK_SAMPLERATE /* sampling rate */
|
OOK_SAMPLERATE /* sampling rate */
|
||||||
};
|
};
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"tx_keyfob", , app_settings::Mode::TX};
|
"tx_keyfob", app_settings::Mode::TX};
|
||||||
|
|
||||||
// 1013210ns / bit
|
// 1013210ns / bit
|
||||||
static constexpr uint32_t subaru_samples_per_bit = (OOK_SAMPLERATE * 0.00101321);
|
static constexpr uint32_t subaru_samples_per_bit = (OOK_SAMPLERATE * 0.00101321);
|
||||||
|
@ -98,11 +98,11 @@ class KeyfobView : public View {
|
||||||
SymField field_payload_a{
|
SymField field_payload_a{
|
||||||
{2 * 8, 5 * 16},
|
{2 * 8, 5 * 16},
|
||||||
10,
|
10,
|
||||||
SymField::SYMFIELD_HEX};
|
SymField::Type::Hex};
|
||||||
SymField field_payload_b{
|
SymField field_payload_b{
|
||||||
{13 * 8, 5 * 16},
|
{13 * 8, 5 * 16},
|
||||||
10,
|
10,
|
||||||
SymField::SYMFIELD_HEX};
|
SymField::Type::Hex};
|
||||||
|
|
||||||
Text text_status{
|
Text text_status{
|
||||||
{2 * 8, 13 * 16, 128, 16},
|
{2 * 8, 13 * 16, 128, 16},
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
#include "ui_modemsetup.hpp"
|
#include "ui_modemsetup.hpp"
|
||||||
|
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
|
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
@ -41,15 +40,20 @@ ModemSetupView::ModemSetupView(
|
||||||
using options_t = std::vector<option_t>;
|
using options_t = std::vector<option_t>;
|
||||||
options_t modem_options;
|
options_t modem_options;
|
||||||
|
|
||||||
add_children({&labels,
|
add_children({
|
||||||
&field_baudrate,
|
&labels,
|
||||||
&field_mark,
|
&field_baudrate,
|
||||||
&field_space,
|
&field_mark,
|
||||||
&field_repeat,
|
&field_space,
|
||||||
&options_modem,
|
&field_repeat,
|
||||||
&button_set_modem,
|
&options_modem,
|
||||||
&sym_format,
|
&button_set_modem,
|
||||||
&button_save});
|
&sym_format_data,
|
||||||
|
&sym_format_parity,
|
||||||
|
&sym_format_stop,
|
||||||
|
&sym_format_msb,
|
||||||
|
&button_save,
|
||||||
|
});
|
||||||
|
|
||||||
// Only list AFSK modems for now
|
// Only list AFSK modems for now
|
||||||
for (size_t i = 0; i < MODEM_DEF_COUNT; i++) {
|
for (size_t i = 0; i < MODEM_DEF_COUNT; i++) {
|
||||||
|
@ -59,15 +63,10 @@ ModemSetupView::ModemSetupView(
|
||||||
options_modem.set_options(modem_options);
|
options_modem.set_options(modem_options);
|
||||||
options_modem.set_selected_index(0);
|
options_modem.set_selected_index(0);
|
||||||
|
|
||||||
sym_format.set_symbol_list(0, "6789"); // Data bits
|
sym_format_data.set_offset(0, persistent_memory::serial_format().data_bits - 6);
|
||||||
sym_format.set_symbol_list(1, "NEo"); // Parity
|
sym_format_parity.set_offset(0, persistent_memory::serial_format().parity);
|
||||||
sym_format.set_symbol_list(2, "012"); // Stop bits
|
sym_format_stop.set_offset(0, persistent_memory::serial_format().stop_bits);
|
||||||
sym_format.set_symbol_list(3, "ML"); // MSB/LSB first
|
sym_format_msb.set_offset(0, persistent_memory::serial_format().bit_order);
|
||||||
|
|
||||||
sym_format.set_sym(0, persistent_memory::serial_format().data_bits - 6);
|
|
||||||
sym_format.set_sym(1, persistent_memory::serial_format().parity);
|
|
||||||
sym_format.set_sym(2, persistent_memory::serial_format().stop_bits);
|
|
||||||
sym_format.set_sym(3, persistent_memory::serial_format().bit_order);
|
|
||||||
|
|
||||||
field_mark.set_value(persistent_memory::afsk_mark_freq());
|
field_mark.set_value(persistent_memory::afsk_mark_freq());
|
||||||
field_space.set_value(persistent_memory::afsk_space_freq());
|
field_space.set_value(persistent_memory::afsk_space_freq());
|
||||||
|
@ -84,18 +83,17 @@ ModemSetupView::ModemSetupView(
|
||||||
};
|
};
|
||||||
|
|
||||||
button_save.on_select = [this, &nav](Button&) {
|
button_save.on_select = [this, &nav](Button&) {
|
||||||
serial_format_t serial_format;
|
|
||||||
|
|
||||||
persistent_memory::set_afsk_mark(field_mark.value());
|
persistent_memory::set_afsk_mark(field_mark.value());
|
||||||
persistent_memory::set_afsk_space(field_space.value());
|
persistent_memory::set_afsk_space(field_space.value());
|
||||||
|
|
||||||
persistent_memory::set_modem_baudrate(field_baudrate.value());
|
persistent_memory::set_modem_baudrate(field_baudrate.value());
|
||||||
persistent_memory::set_modem_repeat(field_repeat.value());
|
persistent_memory::set_modem_repeat(field_repeat.value());
|
||||||
|
|
||||||
serial_format.data_bits = sym_format.get_sym(0) + 6;
|
serial_format_t serial_format{};
|
||||||
serial_format.parity = (parity_enum)sym_format.get_sym(1);
|
serial_format.data_bits = sym_format_data.get_offset(0) + 6;
|
||||||
serial_format.stop_bits = sym_format.get_sym(2);
|
serial_format.parity = (parity_enum)sym_format_parity.get_offset(0);
|
||||||
serial_format.bit_order = (order_enum)sym_format.get_sym(3);
|
serial_format.stop_bits = sym_format_stop.get_offset(0);
|
||||||
|
serial_format.bit_order = (order_enum)sym_format_msb.get_offset(0);
|
||||||
|
|
||||||
persistent_memory::set_serial_format(serial_format);
|
persistent_memory::set_serial_format(serial_format);
|
||||||
|
|
||||||
|
|
|
@ -79,10 +79,25 @@ class ModemSetupView : public View {
|
||||||
7,
|
7,
|
||||||
{}};
|
{}};
|
||||||
|
|
||||||
SymField sym_format{
|
SymField sym_format_data{
|
||||||
{16 * 8, 22 * 8},
|
{16 * 8, 22 * 8},
|
||||||
4,
|
1,
|
||||||
SymField::SYMFIELD_DEF};
|
"6789"};
|
||||||
|
|
||||||
|
SymField sym_format_parity{
|
||||||
|
{17 * 8, 22 * 8},
|
||||||
|
1,
|
||||||
|
"NEo"};
|
||||||
|
|
||||||
|
SymField sym_format_stop{
|
||||||
|
{18 * 8, 22 * 8},
|
||||||
|
1,
|
||||||
|
"012"};
|
||||||
|
|
||||||
|
SymField sym_format_msb{
|
||||||
|
{19 * 8, 22 * 8},
|
||||||
|
1,
|
||||||
|
"ML"};
|
||||||
|
|
||||||
Button button_set_modem{
|
Button button_set_modem{
|
||||||
{23 * 8, 6 * 8 - 4, 6 * 8, 24},
|
{23 * 8, 6 * 8 - 4, 6 * 8, 24},
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ui_numbers.hpp"
|
#include "ui_numbers.hpp"
|
||||||
|
#include "ui_styles.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
|
@ -75,7 +76,7 @@ void NumbersStationView::prepare_audio() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
code = symfield_code.get_sym(code_index);
|
code = symfield_code.get_offset(code_index);
|
||||||
|
|
||||||
if (code >= 10) {
|
if (code >= 10) {
|
||||||
memset(audio_buffer, 0, 1024);
|
memset(audio_buffer, 0, 1024);
|
||||||
|
@ -144,7 +145,7 @@ void NumbersStationView::on_tick_second() {
|
||||||
armed_blink = not armed_blink;
|
armed_blink = not armed_blink;
|
||||||
|
|
||||||
if (armed_blink)
|
if (armed_blink)
|
||||||
check_armed.set_style(&style_red);
|
check_armed.set_style(&Styles::red);
|
||||||
else
|
else
|
||||||
check_armed.set_style(&style());
|
check_armed.set_style(&style());
|
||||||
|
|
||||||
|
@ -152,14 +153,12 @@ void NumbersStationView::on_tick_second() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NumbersStationView::on_voice_changed(size_t index) {
|
void NumbersStationView::on_voice_changed(size_t index) {
|
||||||
std::string code_list = "";
|
std::string code_list;
|
||||||
|
|
||||||
for (const auto& wavs : voices[index].available_wavs)
|
for (const auto& wavs : voices[index].available_wavs)
|
||||||
code_list += wavs.code;
|
code_list += wavs.code;
|
||||||
|
|
||||||
for (uint32_t c = 0; c < 25; c++)
|
symfield_code.set_symbol_list(code_list);
|
||||||
symfield_code.set_symbol_list(c, code_list);
|
|
||||||
|
|
||||||
current_voice = &voices[index];
|
current_voice = &voices[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,17 +264,17 @@ NumbersStationView::NumbersStationView(
|
||||||
};
|
};
|
||||||
|
|
||||||
// DEBUG
|
// DEBUG
|
||||||
symfield_code.set_sym(0, 10);
|
symfield_code.set_offset(0, 10);
|
||||||
symfield_code.set_sym(1, 3);
|
symfield_code.set_offset(1, 3);
|
||||||
symfield_code.set_sym(2, 4);
|
symfield_code.set_offset(2, 4);
|
||||||
symfield_code.set_sym(3, 11);
|
symfield_code.set_offset(3, 11);
|
||||||
symfield_code.set_sym(4, 6);
|
symfield_code.set_offset(4, 6);
|
||||||
symfield_code.set_sym(5, 1);
|
symfield_code.set_offset(5, 1);
|
||||||
symfield_code.set_sym(6, 9);
|
symfield_code.set_offset(6, 9);
|
||||||
symfield_code.set_sym(7, 7);
|
symfield_code.set_offset(7, 7);
|
||||||
symfield_code.set_sym(8, 8);
|
symfield_code.set_offset(8, 8);
|
||||||
symfield_code.set_sym(9, 0);
|
symfield_code.set_offset(9, 0);
|
||||||
symfield_code.set_sym(10, 12); // End
|
symfield_code.set_offset(10, 12); // End
|
||||||
|
|
||||||
/*
|
/*
|
||||||
rtc::RTC datetime;
|
rtc::RTC datetime;
|
||||||
|
|
|
@ -142,16 +142,19 @@ class NumbersStationView : public View {
|
||||||
SymField symfield_code{
|
SymField symfield_code{
|
||||||
{1 * 8, 10 * 8},
|
{1 * 8, 10 * 8},
|
||||||
25,
|
25,
|
||||||
SymField::SYMFIELD_DEF};
|
SymField::Type::Custom};
|
||||||
|
|
||||||
Checkbox check_armed{
|
Checkbox check_armed{
|
||||||
{2 * 8, 13 * 16},
|
{2 * 8, 13 * 16},
|
||||||
5,
|
5,
|
||||||
"Armed"};
|
"Armed"};
|
||||||
/*Button button_tx_now {
|
|
||||||
{ 18 * 8, 13 * 16, 10 * 8, 32 },
|
/*
|
||||||
"TX now"
|
Button button_tx_now {
|
||||||
};*/
|
{ 18 * 8, 13 * 16, 10 * 8, 32 },
|
||||||
|
"TX now"};
|
||||||
|
*/
|
||||||
|
|
||||||
Button button_exit{
|
Button button_exit{
|
||||||
{21 * 8, 16 * 16, 64, 32},
|
{21 * 8, 16 * 16, 64, 32},
|
||||||
"Exit"};
|
"Exit"};
|
||||||
|
|
|
@ -56,7 +56,7 @@ bool POCSAGTXView::start_tx() {
|
||||||
pocsag::BitRate bitrate;
|
pocsag::BitRate bitrate;
|
||||||
std::vector<uint32_t> codewords;
|
std::vector<uint32_t> codewords;
|
||||||
|
|
||||||
address = field_address.value_dec_u32();
|
address = field_address.to_integer();
|
||||||
if (address > 0x1FFFFFU) {
|
if (address > 0x1FFFFFU) {
|
||||||
nav_.display_modal("Bad address", "Address must be less\nthan 2097152.");
|
nav_.display_modal("Bad address", "Address must be less\nthan 2097152.");
|
||||||
return false;
|
return false;
|
||||||
|
@ -137,12 +137,7 @@ POCSAGTXView::POCSAGTXView(
|
||||||
options_bitrate.set_selected_index(1); // 1200bps
|
options_bitrate.set_selected_index(1); // 1200bps
|
||||||
options_type.set_selected_index(0); // Address only
|
options_type.set_selected_index(0); // Address only
|
||||||
|
|
||||||
// TODO: set_value for whole symfield
|
field_address.set_value(persistent_memory::pocsag_last_address());
|
||||||
uint32_t reload_address = persistent_memory::pocsag_last_address();
|
|
||||||
for (uint32_t c = 0; c < 7; c++) {
|
|
||||||
field_address.set_sym(6 - c, reload_address % 10);
|
|
||||||
reload_address /= 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
options_type.on_change = [this](size_t, int32_t i) {
|
options_type.on_change = [this](size_t, int32_t i) {
|
||||||
if (i == 2)
|
if (i == 2)
|
||||||
|
|
|
@ -94,8 +94,7 @@ class POCSAGTXView : public View {
|
||||||
|
|
||||||
SymField field_address{
|
SymField field_address{
|
||||||
{11 * 8, 6 * 8},
|
{11 * 8, 6 * 8},
|
||||||
7,
|
7};
|
||||||
SymField::SYMFIELD_DEC};
|
|
||||||
|
|
||||||
OptionsField options_type{
|
OptionsField options_type{
|
||||||
{11 * 8, 8 * 8},
|
{11 * 8, 8 * 8},
|
||||||
|
|
|
@ -164,7 +164,7 @@ RDSView::~RDSView() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RDSView::start_tx() {
|
void RDSView::start_tx() {
|
||||||
rds_flags.PI_code = sym_pi_code.value_hex_u64();
|
rds_flags.PI_code = sym_pi_code.to_integer();
|
||||||
rds_flags.PTY = options_pty.selected_index_value();
|
rds_flags.PTY = options_pty.selected_index_value();
|
||||||
rds_flags.DI = view_PSN.mono_stereo ? 1 : 0;
|
rds_flags.DI = view_PSN.mono_stereo ? 1 : 0;
|
||||||
rds_flags.TP = check_TP.value();
|
rds_flags.TP = check_TP.value();
|
||||||
|
@ -212,12 +212,9 @@ RDSView::RDSView(
|
||||||
|
|
||||||
check_TP.set_value(true);
|
check_TP.set_value(true);
|
||||||
|
|
||||||
sym_pi_code.set_sym(0, 0xF);
|
sym_pi_code.set_value(0xF3E0);
|
||||||
sym_pi_code.set_sym(1, 0x3);
|
sym_pi_code.on_change = [this](SymField&) {
|
||||||
sym_pi_code.set_sym(2, 0xE);
|
rds_flags.PI_code = sym_pi_code.to_integer();
|
||||||
sym_pi_code.set_sym(3, 0x0);
|
|
||||||
sym_pi_code.on_change = [this]() {
|
|
||||||
rds_flags.PI_code = sym_pi_code.value_hex_u64();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
options_pty.set_selected_index(0); // None
|
options_pty.set_selected_index(0); // None
|
||||||
|
|
|
@ -287,7 +287,7 @@ class RDSView : public View {
|
||||||
SymField sym_pi_code{
|
SymField sym_pi_code{
|
||||||
{13 * 8, 28 + 16},
|
{13 * 8, 28 + 16},
|
||||||
4,
|
4,
|
||||||
SymField::SYMFIELD_HEX};
|
SymField::Type::Hex};
|
||||||
|
|
||||||
/*OptionsField options_coverage {
|
/*OptionsField options_coverage {
|
||||||
{ 17 * 8, 32 + 8 },
|
{ 17 * 8, 32 + 8 },
|
||||||
|
|
|
@ -50,7 +50,7 @@ void SigGenView::update_config() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SigGenView::update_tone() {
|
void SigGenView::update_tone() {
|
||||||
baseband::set_siggen_tone(symfield_tone.value_dec_u32());
|
baseband::set_siggen_tone(symfield_tone.to_integer());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SigGenView::start_tx() {
|
void SigGenView::start_tx() {
|
||||||
|
@ -97,8 +97,8 @@ SigGenView::SigGenView(
|
||||||
|
|
||||||
field_stop.set_value(1);
|
field_stop.set_value(1);
|
||||||
|
|
||||||
symfield_tone.set_sym(1, 1); // Default: 1000 Hz
|
symfield_tone.set_value(1000); // Default: 1000 Hz
|
||||||
symfield_tone.on_change = [this]() {
|
symfield_tone.on_change = [this](SymField&) {
|
||||||
if (auto_update)
|
if (auto_update)
|
||||||
update_tone();
|
update_tone();
|
||||||
};
|
};
|
||||||
|
|
|
@ -92,9 +92,8 @@ class SigGenView : public View {
|
||||||
""};
|
""};
|
||||||
|
|
||||||
SymField symfield_tone{
|
SymField symfield_tone{
|
||||||
{13 * 8, 7 * 8},
|
{12 * 8, 7 * 8},
|
||||||
5,
|
5};
|
||||||
SymField::SYMFIELD_DEC};
|
|
||||||
|
|
||||||
Button button_update{
|
Button button_update{
|
||||||
{5 * 8, 10 * 8, 8 * 8, 3 * 8},
|
{5 * 8, 10 * 8, 8 * 8, 3 * 8},
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
/* This takes a pointer to the end of a buffer
|
/* This takes a pointer to the end of a buffer
|
||||||
* and fills it backwards towards the front.
|
* and fills it backwards towards the front.
|
||||||
* The return value 'q' is a pointer to the start.
|
* The return value 'q' is a pointer to the start.
|
||||||
|
@ -230,29 +232,31 @@ std::string to_string_time_ms(const uint32_t ms) {
|
||||||
return final_str;
|
return final_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void to_string_hex_internal(char* p, const uint64_t n, const int32_t l) {
|
static char* to_string_hex_internal(char* ptr, uint64_t value, uint8_t length) {
|
||||||
const uint32_t d = n & 0xf;
|
if (length == 0)
|
||||||
p[l] = (d > 9) ? (d + 55) : (d + 48);
|
return ptr;
|
||||||
if (l > 0) {
|
|
||||||
to_string_hex_internal(p, n >> 4, l - 1);
|
*(--ptr) = uint_to_char(value & 0xF, 16);
|
||||||
}
|
return to_string_hex_internal(ptr, value >> 4, length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string to_string_hex(const uint64_t n, int32_t l) {
|
std::string to_string_hex(uint64_t value, int32_t length) {
|
||||||
char p[32];
|
constexpr uint8_t buffer_length = 33;
|
||||||
|
char buffer[buffer_length];
|
||||||
|
|
||||||
l = std::min<int32_t>(l, 31);
|
char* ptr = &buffer[buffer_length - 1];
|
||||||
to_string_hex_internal(p, n, l - 1);
|
*ptr = '\0';
|
||||||
p[l] = 0;
|
|
||||||
return p;
|
length = std::min<uint8_t>(buffer_length - 1, length);
|
||||||
|
return to_string_hex_internal(ptr, value, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string to_string_hex_array(uint8_t* const array, const int32_t l) {
|
std::string to_string_hex_array(uint8_t* array, int32_t length) {
|
||||||
std::string str_return = "";
|
std::string str_return;
|
||||||
uint8_t bytes;
|
str_return.reserve(length);
|
||||||
|
|
||||||
for (bytes = 0; bytes < l; bytes++)
|
for (uint8_t i = 0; i < length; i++)
|
||||||
str_return += to_string_hex(array[bytes], 2);
|
str_return += to_string_hex(array[i], 2);
|
||||||
|
|
||||||
return str_return;
|
return str_return;
|
||||||
}
|
}
|
||||||
|
@ -364,3 +368,26 @@ std::string trimr(std::string_view str) {
|
||||||
std::string truncate(std::string_view str, size_t length) {
|
std::string truncate(std::string_view str, size_t length) {
|
||||||
return std::string{str.length() <= length ? str : str.substr(0, length)};
|
return std::string{str.length() <= length ? str : str.substr(0, length)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t char_to_uint(char c, uint8_t radix) {
|
||||||
|
uint8_t v = 0;
|
||||||
|
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
v = c - '0';
|
||||||
|
else if (c >= 'A' && c <= 'F')
|
||||||
|
v = c - 'A' + 10; // A is dec: 10
|
||||||
|
else if (c >= 'a' && c <= 'f')
|
||||||
|
v = c - 'a' + 10; // A is dec: 10
|
||||||
|
|
||||||
|
return v < radix ? v : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char uint_to_char(uint8_t val, uint8_t radix) {
|
||||||
|
if (val >= radix)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (val < 10)
|
||||||
|
return '0' + val;
|
||||||
|
else
|
||||||
|
return 'A' + val - 10; // A is dec: 10
|
||||||
|
}
|
||||||
|
|
|
@ -50,14 +50,19 @@ char* to_string_dec_uint(uint64_t n, StringFormatBuffer& buffer, size_t& length)
|
||||||
std::string to_string_dec_int(int64_t n);
|
std::string to_string_dec_int(int64_t n);
|
||||||
std::string to_string_dec_uint(uint64_t n);
|
std::string to_string_dec_uint(uint64_t n);
|
||||||
|
|
||||||
// TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp...
|
|
||||||
std::string to_string_bin(const uint32_t n, const uint8_t l = 0);
|
std::string to_string_bin(const uint32_t n, const uint8_t l = 0);
|
||||||
std::string to_string_dec_uint(const uint32_t n, const int32_t l, const char fill = ' ');
|
std::string to_string_dec_uint(const uint32_t n, const int32_t l, const char fill = ' ');
|
||||||
std::string to_string_dec_int(const int32_t n, const int32_t l, const char fill = 0);
|
std::string to_string_dec_int(const int32_t n, const int32_t l, const char fill = 0);
|
||||||
std::string to_string_decimal(float decimal, int8_t precision);
|
std::string to_string_decimal(float decimal, int8_t precision);
|
||||||
|
|
||||||
std::string to_string_hex(const uint64_t n, const int32_t l = 0);
|
std::string to_string_hex(uint64_t n, int32_t length);
|
||||||
std::string to_string_hex_array(uint8_t* const array, const int32_t l = 0);
|
std::string to_string_hex_array(uint8_t* array, int32_t length);
|
||||||
|
|
||||||
|
/* Helper to select length based on type size. */
|
||||||
|
template <typename T>
|
||||||
|
std::string to_string_hex(T n) {
|
||||||
|
return to_string_hex(n, sizeof(T) * 2); // Two digits/byte.
|
||||||
|
}
|
||||||
|
|
||||||
std::string to_string_freq(const uint64_t f);
|
std::string to_string_freq(const uint64_t f);
|
||||||
std::string to_string_short_freq(const uint64_t f);
|
std::string to_string_short_freq(const uint64_t f);
|
||||||
|
@ -78,4 +83,12 @@ std::string trim(std::string_view str); // Remove whitespace at ends.
|
||||||
std::string trimr(std::string_view str); // Remove trailing spaces
|
std::string trimr(std::string_view str); // Remove trailing spaces
|
||||||
std::string truncate(std::string_view, size_t length);
|
std::string truncate(std::string_view, size_t length);
|
||||||
|
|
||||||
|
/* Gets the int value for a character given the radix.
|
||||||
|
* e.g. '5' => 5, 'D' => 13. Out of bounds => 0. */
|
||||||
|
uint8_t char_to_uint(char c, uint8_t radix = 10);
|
||||||
|
|
||||||
|
/* Gets the int value for a character given the radix.
|
||||||
|
* e.g. 5 => '5', 13 => 'D'. Out of bounds => '\0'. */
|
||||||
|
char uint_to_char(uint8_t val, uint8_t radix = 10);
|
||||||
|
|
||||||
#endif /*__STRING_FORMAT_H__*/
|
#endif /*__STRING_FORMAT_H__*/
|
||||||
|
|
|
@ -37,6 +37,29 @@ struct Style {
|
||||||
Style invert() const;
|
Style invert() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Sometimes mutation is just the more readable thing... */
|
||||||
|
struct MutableStyle {
|
||||||
|
const Font* font;
|
||||||
|
Color background;
|
||||||
|
Color foreground;
|
||||||
|
|
||||||
|
MutableStyle(const Style& s)
|
||||||
|
: font{&s.font},
|
||||||
|
background{s.background},
|
||||||
|
foreground{s.foreground} {}
|
||||||
|
|
||||||
|
void invert() {
|
||||||
|
std::swap(background, foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
operator Style() const {
|
||||||
|
return {
|
||||||
|
.font = *font,
|
||||||
|
.background = background,
|
||||||
|
.foreground = foreground};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class Widget;
|
class Widget;
|
||||||
|
|
||||||
class Painter {
|
class Painter {
|
||||||
|
|
|
@ -1813,7 +1813,7 @@ void NumberField::set_value(int32_t new_value, bool trigger_change) {
|
||||||
new_value = range.second + new_value + 1;
|
new_value = range.second + new_value + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_value = clip_value(new_value);
|
new_value = clip(new_value, range.first, range.second);
|
||||||
|
|
||||||
if (new_value != value()) {
|
if (new_value != value()) {
|
||||||
value_ = new_value;
|
value_ = new_value;
|
||||||
|
@ -1868,169 +1868,182 @@ bool NumberField::on_touch(const TouchEvent event) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t NumberField::clip_value(int32_t value) {
|
|
||||||
if (value > range.second) {
|
|
||||||
value = range.second;
|
|
||||||
}
|
|
||||||
if (value < range.first) {
|
|
||||||
value = range.first;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SymField **************************************************************/
|
/* SymField **************************************************************/
|
||||||
|
|
||||||
SymField::SymField(
|
SymField::SymField(
|
||||||
Point parent_pos,
|
Point parent_pos,
|
||||||
size_t length,
|
size_t length,
|
||||||
symfield_type type)
|
Type type,
|
||||||
: Widget{{parent_pos, {static_cast<ui::Dim>(8 * length), 16}}},
|
bool explicit_edits)
|
||||||
length_{length},
|
: Widget{{parent_pos, {char_width * (int)length, 16}}},
|
||||||
type_{type} {
|
type_{type},
|
||||||
uint32_t c;
|
explicit_edits_{explicit_edits} {
|
||||||
|
if (length == 0)
|
||||||
|
length = 1;
|
||||||
|
|
||||||
// Auto-init
|
selected_ = length - 1;
|
||||||
if (type == SYMFIELD_OCT) {
|
value_.resize(length);
|
||||||
for (c = 0; c < length; c++)
|
|
||||||
set_symbol_list(c, "01234567");
|
switch (type) {
|
||||||
} else if (type == SYMFIELD_DEC) {
|
case Type::Oct:
|
||||||
for (c = 0; c < length; c++)
|
set_symbol_list("01234567");
|
||||||
set_symbol_list(c, "0123456789");
|
break;
|
||||||
} else if (type == SYMFIELD_HEX) {
|
|
||||||
for (c = 0; c < length; c++)
|
case Type::Dec:
|
||||||
set_symbol_list(c, "0123456789ABCDEF");
|
set_symbol_list("0123456789");
|
||||||
} else if (type == SYMFIELD_ALPHANUM) {
|
break;
|
||||||
for (c = 0; c < length; c++)
|
|
||||||
set_symbol_list(c, " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
case Type::Hex:
|
||||||
|
set_symbol_list("0123456789ABCDEF");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Type::Alpha:
|
||||||
|
set_symbol_list(" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
set_symbol_list("01");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_focusable(true);
|
set_focusable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t SymField::concatenate_4_octal_u16() {
|
SymField::SymField(
|
||||||
// input array 4 octal digits , return 12 bits, same order, trippled A4-A2-A1|B4-B2-B1|C4-C2-C1|D4-D2-D1
|
Point parent_pos,
|
||||||
uint32_t mul = 1;
|
size_t length,
|
||||||
uint16_t v = 0;
|
std::string symbol_list,
|
||||||
|
bool explicit_edits)
|
||||||
if (type_ == SYMFIELD_OCT) {
|
: SymField{parent_pos, length, Type::Custom, explicit_edits} {
|
||||||
for (uint32_t c = 0; c < length_; c++) {
|
set_symbol_list(std::move(symbol_list));
|
||||||
v += values_[(length_ - 1 - c)] * mul;
|
|
||||||
mul *= 8; // shift 3 bits to the right every new octal squawk digit
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
} else
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t SymField::value_dec_u32() {
|
char SymField::get_symbol(size_t index) const {
|
||||||
uint32_t mul = 1, v = 0;
|
if (index >= value_.length())
|
||||||
|
|
||||||
if (type_ == SYMFIELD_DEC) {
|
|
||||||
for (uint32_t c = 0; c < length_; c++) {
|
|
||||||
v += values_[(length_ - 1 - c)] * mul;
|
|
||||||
mul *= 10;
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
} else
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
return value_[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t SymField::value_hex_u64() {
|
void SymField::set_symbol(size_t index, char symbol) {
|
||||||
|
if (index >= value_.length())
|
||||||
|
return;
|
||||||
|
|
||||||
|
set_symbol_internal(index, ensure_valid(symbol));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SymField::get_offset(size_t index) const {
|
||||||
|
if (index >= value_.length())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// NB: Linear search - symbol lists are small.
|
||||||
|
return symbols_.find(value_[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SymField::set_offset(size_t index, size_t offset) {
|
||||||
|
if (index >= value_.length() || offset >= symbols_.length())
|
||||||
|
return;
|
||||||
|
|
||||||
|
set_symbol_internal(index, symbols_[offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SymField::set_symbol_list(std::string symbol_list) {
|
||||||
|
if (symbol_list.length() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
symbols_ = std::move(symbol_list);
|
||||||
|
ensure_all_symbols();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SymField::set_value(uint64_t value) {
|
||||||
|
auto v = value;
|
||||||
|
uint8_t radix = get_radix();
|
||||||
|
|
||||||
|
for (int i = value_.length() - 1; i >= 0; --i) {
|
||||||
|
uint8_t temp = v % radix;
|
||||||
|
value_[i] = uint_to_char(temp, radix);
|
||||||
|
v /= radix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SymField::set_value(std::string_view value) {
|
||||||
|
// Is new value too long?
|
||||||
|
// TODO: Truncate instead? Which end?
|
||||||
|
if (value.length() > value_.length())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Right-align string in field.
|
||||||
|
auto left_padding = value_.length() - value.length();
|
||||||
|
value_ = std::string(static_cast<size_t>(left_padding), '\0') + std::string{value};
|
||||||
|
ensure_all_symbols();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t SymField::to_integer() const {
|
||||||
uint64_t v = 0;
|
uint64_t v = 0;
|
||||||
|
uint64_t mul = 1;
|
||||||
|
uint8_t radix = get_radix();
|
||||||
|
|
||||||
if (type_ != SYMFIELD_DEF) {
|
for (int i = value_.length() - 1; i >= 0; --i) {
|
||||||
for (uint32_t c = 0; c < length_; c++)
|
auto temp = char_to_uint(value_[i], radix);
|
||||||
v += (uint64_t)(values_[c]) << (4 * (length_ - 1 - c));
|
v += temp * mul;
|
||||||
return v;
|
mul *= radix;
|
||||||
} else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SymField::value_string() {
|
|
||||||
std::string return_string{""};
|
|
||||||
|
|
||||||
if (type_ == SYMFIELD_ALPHANUM) {
|
|
||||||
for (uint32_t c = 0; c < length_; c++) {
|
|
||||||
return_string += symbol_list_[0][values_[c]];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return return_string;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t SymField::get_sym(const uint32_t index) {
|
const std::string& SymField::to_string() const {
|
||||||
if (index >= length_) return 0;
|
return value_;
|
||||||
|
|
||||||
return values_[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
void SymField::set_sym(const uint32_t index, const uint32_t new_value) {
|
|
||||||
if (index >= length_) return;
|
|
||||||
|
|
||||||
uint32_t clipped_value = clip_value(index, new_value);
|
|
||||||
|
|
||||||
if (clipped_value != values_[index]) {
|
|
||||||
values_[index] = clipped_value;
|
|
||||||
if (on_change) {
|
|
||||||
on_change();
|
|
||||||
}
|
|
||||||
set_dirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SymField::set_length(const uint32_t new_length) {
|
|
||||||
if ((new_length <= 32) && (new_length != length_)) {
|
|
||||||
prev_length_ = length_;
|
|
||||||
length_ = new_length;
|
|
||||||
|
|
||||||
// Clip eventual garbage from previous shorter word
|
|
||||||
for (size_t n = 0; n < length_; n++)
|
|
||||||
set_sym(n, values_[n]);
|
|
||||||
|
|
||||||
erase_prev_ = true;
|
|
||||||
set_dirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SymField::set_symbol_list(const uint32_t index, const std::string symbol_list) {
|
|
||||||
if (index >= length_) return;
|
|
||||||
|
|
||||||
symbol_list_[index] = symbol_list;
|
|
||||||
|
|
||||||
// Re-clip symbol's value
|
|
||||||
set_sym(index, values_[index]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymField::paint(Painter& painter) {
|
void SymField::paint(Painter& painter) {
|
||||||
Point pt_draw = screen_pos();
|
Point p = screen_pos();
|
||||||
|
|
||||||
if (erase_prev_) {
|
for (size_t n = 0; n < value_.length(); n++) {
|
||||||
painter.fill_rectangle({pt_draw, {(int)prev_length_ * 8, 16}}, Color::black());
|
auto c = value_[n];
|
||||||
erase_prev_ = false;
|
MutableStyle paint_style{style()};
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t n = 0; n < length_; n++) {
|
// Only highlight while focused.
|
||||||
const auto text = symbol_list_[n].substr(values_[n], 1);
|
if (has_focus()) {
|
||||||
|
if (explicit_edits_) {
|
||||||
|
// Invert the whole field on focus if explicit edits is enabled.
|
||||||
|
paint_style.invert();
|
||||||
|
} else if (n == selected_) {
|
||||||
|
// Otherwise only highlight the selected symbol.
|
||||||
|
paint_style.invert();
|
||||||
|
}
|
||||||
|
|
||||||
const auto paint_style = (has_focus() && (n == selected_)) ? style().invert() : style();
|
if (editing_ && n == selected_) {
|
||||||
|
// Use 'bg_blue' style to indicate in editing mode.
|
||||||
|
paint_style.foreground = Color::white();
|
||||||
|
paint_style.background = Color::blue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
painter.draw_string(
|
painter.draw_char(p, paint_style, c);
|
||||||
pt_draw,
|
p += {8, 0};
|
||||||
paint_style,
|
|
||||||
text);
|
|
||||||
|
|
||||||
pt_draw += {8, 0};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SymField::on_key(const KeyEvent key) {
|
bool SymField::on_key(KeyEvent key) {
|
||||||
|
// If explicit edits are enabled, only Select is handled when not in edit mode.
|
||||||
|
if (explicit_edits_ && !editing_) {
|
||||||
|
switch (key) {
|
||||||
|
case KeyEvent::Select:
|
||||||
|
editing_ = true;
|
||||||
|
set_dirty();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case KeyEvent::Select:
|
case KeyEvent::Select:
|
||||||
if (on_select) {
|
editing_ = !editing_;
|
||||||
on_select(*this);
|
set_dirty();
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KeyEvent::Left:
|
case KeyEvent::Left:
|
||||||
if (selected_ > 0) {
|
if (selected_ > 0) {
|
||||||
|
@ -2041,13 +2054,27 @@ bool SymField::on_key(const KeyEvent key) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyEvent::Right:
|
case KeyEvent::Right:
|
||||||
if (selected_ < (length_ - 1)) {
|
if (selected_ < (value_.length() - 1)) {
|
||||||
selected_++;
|
selected_++;
|
||||||
set_dirty();
|
set_dirty();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case KeyEvent::Up:
|
||||||
|
if (editing_) {
|
||||||
|
on_encoder(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KeyEvent::Down:
|
||||||
|
if (editing_) {
|
||||||
|
on_encoder(-1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2055,29 +2082,66 @@ bool SymField::on_key(const KeyEvent key) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SymField::on_encoder(const EncoderEvent delta) {
|
bool SymField::on_encoder(EncoderEvent delta) {
|
||||||
int32_t new_value = (int)values_[selected_] + delta;
|
if (explicit_edits_ && !editing_)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (new_value >= 0)
|
// TODO: Wrapping or carrying might be nice.
|
||||||
set_sym(selected_, values_[selected_] + delta);
|
int offset = get_offset(selected_) + delta;
|
||||||
|
|
||||||
|
offset = clip<int>(offset, 0, symbols_.length() - 1);
|
||||||
|
set_offset(selected_, offset);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SymField::on_touch(const TouchEvent event) {
|
bool SymField::on_touch(TouchEvent event) {
|
||||||
if (event.type == TouchEvent::Type::Start) {
|
if (event.type == TouchEvent::Type::Start)
|
||||||
focus();
|
focus();
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t SymField::clip_value(const uint32_t index, const uint32_t value) {
|
char SymField::ensure_valid(char symbol) const {
|
||||||
size_t symbol_count = symbol_list_[index].length() - 1;
|
// NB: Linear search - symbol lists are small.
|
||||||
|
auto pos = symbols_.find(symbol);
|
||||||
|
return pos != std::string::npos ? symbol : symbols_[0];
|
||||||
|
}
|
||||||
|
|
||||||
if (value > symbol_count)
|
void SymField::ensure_all_symbols() {
|
||||||
return symbol_count;
|
auto temp = value_;
|
||||||
else
|
|
||||||
return value;
|
for (auto& c : value_)
|
||||||
|
c = ensure_valid(c);
|
||||||
|
|
||||||
|
if (temp != value_) {
|
||||||
|
if (on_change)
|
||||||
|
on_change(*this);
|
||||||
|
set_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SymField::set_symbol_internal(size_t index, char symbol) {
|
||||||
|
if (value_[index] == symbol)
|
||||||
|
return;
|
||||||
|
|
||||||
|
value_[index] = symbol;
|
||||||
|
if (on_change)
|
||||||
|
on_change(*this);
|
||||||
|
set_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t SymField::get_radix() const {
|
||||||
|
switch (type_) {
|
||||||
|
case Type::Oct:
|
||||||
|
return 8;
|
||||||
|
case Type::Dec:
|
||||||
|
return 10;
|
||||||
|
case Type::Hex:
|
||||||
|
return 16;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Waveform **************************************************************/
|
/* Waveform **************************************************************/
|
||||||
|
|
|
@ -751,52 +751,93 @@ class NumberField : public Widget {
|
||||||
const char fill_char;
|
const char fill_char;
|
||||||
int32_t value_{0};
|
int32_t value_{0};
|
||||||
bool can_loop{};
|
bool can_loop{};
|
||||||
|
|
||||||
int32_t clip_value(int32_t value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* A widget that allows for character-by-character editing of its value. */
|
||||||
class SymField : public Widget {
|
class SymField : public Widget {
|
||||||
public:
|
public:
|
||||||
std::function<void(SymField&)> on_select{};
|
std::function<void(SymField&)> on_change{};
|
||||||
std::function<void()> on_change{};
|
|
||||||
|
|
||||||
enum symfield_type {
|
enum class Type {
|
||||||
SYMFIELD_OCT,
|
Custom,
|
||||||
SYMFIELD_DEC,
|
Oct,
|
||||||
SYMFIELD_HEX,
|
Dec,
|
||||||
SYMFIELD_ALPHANUM,
|
Hex,
|
||||||
SYMFIELD_DEF // User DEFined
|
Alpha,
|
||||||
};
|
};
|
||||||
|
|
||||||
SymField(Point parent_pos, size_t length, symfield_type type);
|
/* When "explicit_edits" is true, the field must be selected before it can
|
||||||
|
* be modified. This means that "slots" are not individually highlighted.
|
||||||
|
* This makes navigation on Views with SymFields easier because the
|
||||||
|
* whole control can be skipped over instead of one "slot" at a time. */
|
||||||
|
|
||||||
|
SymField(
|
||||||
|
Point parent_pos,
|
||||||
|
size_t length,
|
||||||
|
Type type = Type::Dec,
|
||||||
|
bool explicit_edits = false);
|
||||||
|
|
||||||
|
SymField(
|
||||||
|
Point parent_pos,
|
||||||
|
size_t length,
|
||||||
|
std::string symbol_list,
|
||||||
|
bool explicit_edits = false);
|
||||||
|
|
||||||
SymField(const SymField&) = delete;
|
SymField(const SymField&) = delete;
|
||||||
SymField(SymField&&) = delete;
|
SymField(SymField&&) = delete;
|
||||||
|
|
||||||
uint32_t get_sym(const uint32_t index);
|
/* Gets the symbol (character) at the specified index. */
|
||||||
void set_sym(const uint32_t index, const uint32_t new_value);
|
char get_symbol(size_t index) const;
|
||||||
void set_length(const uint32_t new_length);
|
|
||||||
void set_symbol_list(const uint32_t index, const std::string symbol_list);
|
/* Sets the symbol (character) at the specified index. */
|
||||||
uint16_t concatenate_4_octal_u16();
|
void set_symbol(size_t index, char symbol);
|
||||||
uint32_t value_dec_u32();
|
|
||||||
uint64_t value_hex_u64();
|
/* Gets the symbol's offset in the symbol list at the specified index. */
|
||||||
std::string value_string();
|
size_t get_offset(size_t index) const;
|
||||||
|
|
||||||
|
/* Sets the symbol's offset in the symbol list at the specified index. */
|
||||||
|
void set_offset(size_t index, size_t offset);
|
||||||
|
|
||||||
|
/* Sets the list of allowable symbols for the field. */
|
||||||
|
void set_symbol_list(std::string symbol_list);
|
||||||
|
|
||||||
|
/* Sets the integer value of the field. Only works for OCT/DEC/HEX. */
|
||||||
|
void set_value(uint64_t value);
|
||||||
|
|
||||||
|
/* Sets the string value of the field. Characters that are not in
|
||||||
|
* the symbol list will be set to the first symbol in the symbol list. */
|
||||||
|
void set_value(std::string_view value);
|
||||||
|
|
||||||
|
/* Gets the integer value of the field for OCT/DEC/HEX. */
|
||||||
|
uint64_t to_integer() const;
|
||||||
|
|
||||||
|
/* Gets the string value of the field. */
|
||||||
|
const std::string& to_string() const;
|
||||||
|
|
||||||
void paint(Painter& painter) override;
|
void paint(Painter& painter) override;
|
||||||
|
bool on_key(KeyEvent key) override;
|
||||||
bool on_key(const KeyEvent key) override;
|
bool on_encoder(EncoderEvent delta) override;
|
||||||
bool on_encoder(const EncoderEvent delta) override;
|
bool on_touch(TouchEvent event) override;
|
||||||
bool on_touch(const TouchEvent event) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string symbol_list_[32] = {"01"}; // Failsafe init
|
/* Ensure the specified symbol is in the symbol list. */
|
||||||
uint32_t values_[32] = {0};
|
char ensure_valid(char symbol) const;
|
||||||
uint32_t selected_ = 0;
|
|
||||||
size_t length_, prev_length_ = 0;
|
|
||||||
bool erase_prev_ = false;
|
|
||||||
symfield_type type_{};
|
|
||||||
|
|
||||||
int32_t clip_value(const uint32_t index, const uint32_t value);
|
/* Ensures all the symbols are valid. */
|
||||||
|
void ensure_all_symbols();
|
||||||
|
|
||||||
|
/* Sets without validation and fires on_change if necessary. */
|
||||||
|
void set_symbol_internal(size_t index, char symbol);
|
||||||
|
|
||||||
|
/* Gets the radix for the current Type. */
|
||||||
|
uint8_t get_radix() const;
|
||||||
|
|
||||||
|
std::string symbols_{};
|
||||||
|
std::string value_{};
|
||||||
|
Type type_ = Type::Custom;
|
||||||
|
size_t selected_ = 0;
|
||||||
|
bool editing_ = false;
|
||||||
|
bool explicit_edits_ = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Waveform : public Widget {
|
class Waveform : public Widget {
|
||||||
|
|
|
@ -22,12 +22,13 @@
|
||||||
#ifndef __UTILITY_H__
|
#ifndef __UTILITY_H__
|
||||||
#define __UTILITY_H__
|
#define __UTILITY_H__
|
||||||
|
|
||||||
#include <type_traits>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <complex>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <algorithm>
|
|
||||||
#include <complex>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#define LOCATE_IN_RAM __attribute__((section(".ramtext")))
|
#define LOCATE_IN_RAM __attribute__((section(".ramtext")))
|
||||||
|
|
||||||
|
@ -141,6 +142,16 @@ constexpr std::enable_if_t<is_flags_type_v<TEnum>, bool> flags_enabled(TEnum val
|
||||||
return (i_value & i_flags) == i_flags;
|
return (i_value & i_flags) == i_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Constrain to integrals?
|
||||||
|
/* Converts an integer into a byte array. */
|
||||||
|
template <typename T, size_t N = sizeof(T)>
|
||||||
|
constexpr std::array<uint8_t, N> to_byte_array(T value) {
|
||||||
|
std::array<uint8_t, N> bytes{};
|
||||||
|
for (size_t i = 0; i < N; ++i)
|
||||||
|
bytes[i] = (value >> ((N - i - 1) * 8)) & 0xFF;
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
/* Returns value constrained to min and max. */
|
/* Returns value constrained to min and max. */
|
||||||
template <class T>
|
template <class T>
|
||||||
constexpr const T& clip(const T& value, const T& minimum, const T& maximum) {
|
constexpr const T& clip(const T& value, const T& minimum, const T& maximum) {
|
||||||
|
|
|
@ -83,6 +83,54 @@ TEST_CASE("to_string_rounded_freq returns correct value.") {
|
||||||
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 9), "1234.567891");
|
CHECK_EQ(to_string_rounded_freq(1'234'567'891, 9), "1234.567891");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("to_string_hex returns correct value.") {
|
||||||
|
CHECK_EQ(to_string_hex(0x0, 1), "0");
|
||||||
|
CHECK_EQ(to_string_hex(0x0, 4), "0000");
|
||||||
|
CHECK_EQ(to_string_hex(0x1, 1), "1");
|
||||||
|
CHECK_EQ(to_string_hex(0xA, 1), "A");
|
||||||
|
CHECK_EQ(to_string_hex(0xA0, 2), "A0");
|
||||||
|
CHECK_EQ(to_string_hex(0xA1, 2), "A1");
|
||||||
|
CHECK_EQ(to_string_hex(0xA1FE, 2), "FE"); // NB: Truncates front.
|
||||||
|
CHECK_EQ(to_string_hex(0x12345678, 8), "12345678");
|
||||||
|
CHECK_EQ(to_string_hex(0x9ABCDEF0, 8), "9ABCDEF0");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("to_string_hex returns correct value based on type.") {
|
||||||
|
CHECK_EQ(to_string_hex<uint8_t>(0xA), "0A");
|
||||||
|
CHECK_EQ(to_string_hex<uint8_t>(0xFE), "FE");
|
||||||
|
CHECK_EQ(to_string_hex<uint16_t>(0x00FE), "00FE");
|
||||||
|
CHECK_EQ(to_string_hex<uint16_t>(0xCAFE), "CAFE");
|
||||||
|
CHECK_EQ(to_string_hex<uint32_t>(0xCAFE), "0000CAFE");
|
||||||
|
CHECK_EQ(to_string_hex<uint32_t>(0xCAFEBEEF), "CAFEBEEF");
|
||||||
|
CHECK_EQ(to_string_hex<uint64_t>(0xCAFEBEEF), "00000000CAFEBEEF");
|
||||||
|
CHECK_EQ(to_string_hex<uint64_t>(0xC0FFEE00CAFEBABE), "C0FFEE00CAFEBABE");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("char_to_uint returns correct value.") {
|
||||||
|
CHECK_EQ(char_to_uint('0', 10), 0);
|
||||||
|
CHECK_EQ(char_to_uint('5', 10), 5);
|
||||||
|
CHECK_EQ(char_to_uint('8', 8), 0); // Bad value given radix.
|
||||||
|
CHECK_EQ(char_to_uint('8', 10), 8);
|
||||||
|
CHECK_EQ(char_to_uint('8', 16), 8);
|
||||||
|
CHECK_EQ(char_to_uint('A', 10), 0); // Bad value given radix.
|
||||||
|
CHECK_EQ(char_to_uint('A', 16), 0xA);
|
||||||
|
CHECK_EQ(char_to_uint('a', 16), 0xA); // Lowercase.
|
||||||
|
CHECK_EQ(char_to_uint('f', 16), 0xF); // Lowercase.
|
||||||
|
CHECK_EQ(char_to_uint('G', 16), 0); // Bad value given radix.
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("uint_to_char returns correct value.") {
|
||||||
|
CHECK_EQ(uint_to_char(0, 10), '0');
|
||||||
|
CHECK_EQ(uint_to_char(5, 10), '5');
|
||||||
|
CHECK_EQ(uint_to_char(8, 8), '\0'); // Bad value given radix.
|
||||||
|
CHECK_EQ(uint_to_char(8, 10), '8');
|
||||||
|
CHECK_EQ(uint_to_char(8, 16), '8');
|
||||||
|
CHECK_EQ(uint_to_char(10, 10), '\0'); // Bad value given radix.
|
||||||
|
CHECK_EQ(uint_to_char(10, 16), 'A');
|
||||||
|
CHECK_EQ(uint_to_char(15, 16), 'F');
|
||||||
|
CHECK_EQ(uint_to_char(16, 16), '\0'); // Bad value given radix.
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("trim removes whitespace.") {
|
TEST_CASE("trim removes whitespace.") {
|
||||||
CHECK(trim(" foo\n") == "foo");
|
CHECK(trim(" foo\n") == "foo");
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,4 +71,22 @@ TEST_CASE("ms_duration should return duration.") {
|
||||||
|
|
||||||
TEST_CASE("ms_duration not fault when passed zero.") {
|
TEST_CASE("ms_duration not fault when passed zero.") {
|
||||||
CHECK_EQ(ms_duration(0, 0, 0), 0);
|
CHECK_EQ(ms_duration(0, 0, 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("to_byte_array returns correct size and values.") {
|
||||||
|
auto arr1 = to_byte_array<uint8_t>(0xAB);
|
||||||
|
REQUIRE_EQ(std::size(arr1), 1);
|
||||||
|
CHECK_EQ(arr1[0], 0xAB);
|
||||||
|
|
||||||
|
auto arr2 = to_byte_array<uint16_t>(0xABCD);
|
||||||
|
REQUIRE_EQ(std::size(arr2), 2);
|
||||||
|
CHECK_EQ(arr2[0], 0xAB);
|
||||||
|
CHECK_EQ(arr2[1], 0xCD);
|
||||||
|
|
||||||
|
auto arr4 = to_byte_array<uint32_t>(0xABCD1234);
|
||||||
|
REQUIRE_EQ(std::size(arr4), 4);
|
||||||
|
CHECK_EQ(arr4[0], 0xAB);
|
||||||
|
CHECK_EQ(arr4[1], 0xCD);
|
||||||
|
CHECK_EQ(arr4[2], 0x12);
|
||||||
|
CHECK_EQ(arr4[3], 0x34);
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue