diff --git a/ubitx_iop/menu.h b/ubitx_iop/menu.h index 57773a2..16cb01d 100644 --- a/ubitx_iop/menu.h +++ b/ubitx_iop/menu.h @@ -5,7 +5,12 @@ #ifndef __menu_h__ #define __menu_h__ -#include +//#include "etl_profile.h" +//#include +#include "etl/array.h" +#include "etl/cstring.h" +#include "etl/delegate.h" +//#include #include #include "rig.h" #include "tx_audio_proc.h" @@ -14,100 +19,193 @@ extern SpeechCompressor speechCompressor; // This should be somewhere else. // 16 characters on display const int MAX_TEXT_LEN = 16; +const int max_text_len = 16; const char MENU_SELECTED_CHAR = '>'; +const char menu_selection_char = '>'; const char blankLine[17] = " "; -class MenuItem { +typedef etl::string Menu_string; + +//====================================================================== +// Menu_item +//====================================================================== + +class Menu_item { public: - virtual ~MenuItem() {} - //virtual const char* text() = 0; - virtual void update() = 0; - virtual MenuItem* select() = 0; - virtual MenuItem* altSelect() = 0; - virtual MenuItem* exit() = 0; - virtual MenuItem* prev() = 0; - virtual MenuItem* next() = 0; + Menu_item(): parent_link(nullptr) {} + virtual ~Menu_item() {} + + virtual const Menu_string& title() const = 0; + virtual void get_title(Menu_string& outstr) const = 0; + virtual void get_text(Menu_string& outstr) const = 0; + + virtual Menu_item* select() = 0; + virtual Menu_item* altSelect() = 0; + virtual Menu_item* exit() = 0; + virtual Menu_item* prev() = 0; + virtual Menu_item* next() = 0; + + void set_parent(Menu_item* item) { + parent_link = item; + } + + const Menu_item* parent() const { + return parent_link; + } + + private: + Menu_item* parent_link; }; //====================================================================== -// ListMenu +// List_menu //====================================================================== -const int MAX_LISTMENU_TEXT_LEN = 15; +const int MAX_LISTMENU_TITLE_LEN = 15; -class ListMenu : public MenuItem { +//const int max_size_of_list_menu = 20; // arbitrary... + +template +class List_menu : public Menu_item { public: - ListMenu(const char* text, std::initializer_list items): - _length(items.size()), _current(0), _dirty(true) - { - char temp[MAX_LISTMENU_TEXT_LEN]; - strncpy(temp, text, MAX_LISTMENU_TEXT_LEN); - temp[MAX_LISTMENU_TEXT_LEN] = '\0'; - - _items = new MenuItem*[_length]; - - int count = 0; - for (auto element : items) { - _items[count] = element; - count++; - } - - sprintf(_text, "%c%s", MENU_SELECTED_CHAR, temp); - } - - virtual ~ListMenu() { - delete [] _items; - } - - virtual void update() - { - if (_dirty) { - sendIOPMenuDisplay(_text); - _dirty = false; + List_menu(const char* title, etl::array items): + Menu_item(), list_title(title), list_items(items), index(0) { + for (auto element : list_items) { + element->set_parent(this); } } - virtual MenuItem* select() - { - _dirty = true; - return _items[_current]; + virtual const Menu_string& title() const { + return list_title; } - virtual MenuItem* altSelect() + virtual void get_title(Menu_string& outstr) const { + outstr.assign(list_title); + } + + virtual void get_text(Menu_string& outstr) const { + list_items[index]->get_text(outstr); + } + + virtual Menu_item* select() + { + return list_items[index]; + } + + virtual Menu_item* altSelect() { - _dirty = true; return this; } - virtual MenuItem* exit() + virtual Menu_item* exit() { - _dirty = true; - return NULL; + return parent(); } - virtual MenuItem* prev() + virtual Menu_item* prev() { - if (--_current < 0) { - _current += _length; + if (--index < 0) { + index += list_items.size(); } - _dirty = true; return this; } - virtual MenuItem* next() + virtual Menu_item* next() { - _current = ++_current % _length; - _dirty = true; + index = ++index % list_items.size(); return this; } private: - bool _dirty; - MenuItem** _items; - int _length; - int _current; - char _text[MAX_LISTMENU_TEXT_LEN+1]; + Menu_string list_title; + etl::array list_items; + int index; +}; + +//====================================================================== +// Config_parm +//====================================================================== + +typedef etl::delegate Update_func; + +template +class Config_parm : public Menu_item { + public: + Config_parm(const char* title, T& parm, T min, T max, T step, Update_func f): + Menu_item(), parm_title(title), parameter(parm), min_val(min), max_val(max), step_val(step), on_update(f) {} + + virtual const Menu_string& title() const { + return parm_title; + } + + virtual void get_title(Menu_string& outstr) const { + outstr.assign(parm_title); + } + + virtual void get_text(Menu_string& outstr) const = 0; + + virtual Menu_item* select() { + on_update(); + return parent(); + } + + virtual Menu_item* altSelect() { + on_update(); + return this; + } + + virtual Menu_item* exit() { + return parent(); + } + + virtual Menu_item* prev() { + parameter -= step_val; + if (parameter < min_val) { + parameter = min_val; + } + return this; + } + + virtual Menu_item* next() { + parameter += step_val; + if (parameter > max_val) { + parameter = max_val; + } + return this; + } + + const T& value() const { + return parameter; + } + + private: + Menu_string parm_title; + T& parameter; + T min_val; + T max_val; + T step_val; + Update_func on_update; +}; + +class Parm_int : public Config_parm { + public: + Parm_int(const char* title, int parm, int min, int max, int step, Update_func f): + Config_parm(title, parm, min, max, step, f) {} + + virtual void get_text(Menu_string& outstr) const { + snprintf(outstr.data(), max_text_len+1, "%-*s %3d", max_text_len-4, title().data(), value()); + } +}; + +class Parm_float : public Config_parm { + public: + Parm_float(const char* title, float parm, float min, float max, float step, Update_func f): + Config_parm(title, parm, min, max, step, f) {} + + virtual void get_text(Menu_string& outstr) const { + snprintf(outstr.data(), max_text_len+1, "%-*s %5.2f", max_text_len-6, title().data(), value()); + } }; //====================================================================== @@ -124,18 +222,34 @@ const char* const filterID[NUM_RIG_MODES][NUM_RX_FILTERS] = { {"2.8", "2.4", "1.8"}, // TTU }; -class TopMenu : public MenuItem { +//====================================================================== +// Top_menu +//====================================================================== + + +class Main_menu : public Menu_item { public: - TopMenu(Rig& rig): _rig(rig), _dirty(false), _visible(false), _adjust_tx(false), _comp_on(false) { - strcpy(_text0, "R: T: "); - strcpy(_text1, "0123456789ABCDEF"); + Main_menu(Rig& rig): menu_title("Main Menu"), rig(rig), adjust_tx(false), comp_on(false) {} + + virtual const Menu_string& title() const { + return menu_title; } -/* virtual const char* text() { - sprintf(_buffer, "%1c-%1c%6s%1c6s", modeID[rig.modeNum], - return _buffer; - }*/ + virtual void get_title(Menu_string& outstr) const { + outstr.assign(menu_title); + } + virtual void get_text(Menu_string& outstr) const { + char text[max_text_len+1]; + sprintf(text, "%1cR:%3s %1cT:%3s ", + adjust_tx ? ' ' : menu_selection_char, + filterID[rig.modeNum()][rig.filterNum()], + adjust_tx ? menu_selection_char : ' ', + rig.isSSBMode() && comp_on ? "CMP" : " "); + outstr.assign(text); + } + +/* virtual void update() { sprintf(_text0, "%1cR:%3s %1cT:%3s ", _adjust_tx ? ' ' : MENU_SELECTED_CHAR, @@ -155,77 +269,55 @@ class TopMenu : public MenuItem { strncpy(_text1, _text0, MAX_TEXT_LEN); } } - - virtual MenuItem* select() { - if (!_visible) { - _visible = true; +*/ + virtual Menu_item* select() { + adjust_tx = !adjust_tx; + return this; + } + + virtual Menu_item* altSelect() { + return this; + } + + virtual Menu_item* exit() { + return nullptr; + } + + virtual Menu_item* prev() { + if (adjust_tx) { + if (rig.isSSBMode()) { + comp_on = !comp_on; + if (comp_on) + speechCompressor.enable(); + else + speechCompressor.disable(); + } } else { - _adjust_tx = !_adjust_tx; - } - _dirty = true; - return this; - } - - virtual MenuItem* altSelect() { - if (_visible) { - _visible = false; - _dirty = true; + rig.switchRxFilter(true); } return this; } - virtual MenuItem* exit() { - if (_visible) { - _visible = false; - _dirty = true; - } - return this; - } - - virtual MenuItem* prev() { - if (_visible) { - if (_adjust_tx) { - if (_rig.isSSBMode()) { - _comp_on = !_comp_on; - if (_comp_on) - speechCompressor.enable(); - else - speechCompressor.disable(); - } - } else { - _rig.switchRxFilter(true); + virtual Menu_item* next() { + if (adjust_tx) { + if (rig.isSSBMode()) { + comp_on = !comp_on; + if (comp_on) + speechCompressor.enable(); + else + speechCompressor.disable(); } - _dirty = true; - } - return this; - } - - virtual MenuItem* next() { - if (_visible) { - if (_adjust_tx) { - if (_rig.isSSBMode()) { - _comp_on = !_comp_on; - if (_comp_on) - speechCompressor.enable(); - else - speechCompressor.disable(); - } - } else { - _rig.switchRxFilter(); - } - _dirty = true; + } else { + rig.switchRxFilter(); } return this; } private: - Rig& _rig; - bool _dirty; - bool _visible; - bool _adjust_tx; - bool _comp_on; - char _text0[MAX_TEXT_LEN+1]; - char _text1[MAX_TEXT_LEN+1]; + Menu_string menu_title; + Rig& rig; + bool adjust_tx; + bool comp_on; }; /* diff --git a/ubitx_iop/menu.ino b/ubitx_iop/menu.ino index e162bfe..07d9d13 100644 --- a/ubitx_iop/menu.ino +++ b/ubitx_iop/menu.ino @@ -6,6 +6,27 @@ #include "config.h" #include "menu.h" +void do_nothing_func() {} +Update_func do_nothing = Update_func::create(); + +List_menu<7> audio_config_menu("Audio Config", { + new Parm_int ("RX ADC Lvl", rigConfig.audio.rxRigInLevel, 0, 15, 1, do_nothing), + new Parm_int ("RX DAC Lvl", rigConfig.audio.rxLineOutLevel, 13, 31, 1, do_nothing), + new Parm_float ("RX Inp Lvl", rigConfig.audio.rxRigInVol, 0.0, 1.0, 0.1, do_nothing), + new Parm_float ("RX Inp Cal", rigConfig.audio.rxRigInCal, 0.0, 99.9, 0.1, do_nothing), + new Parm_float ("RX Spkr Cal", rigConfig.audio.rxSpkrOutCal, 0.0, 99.9, 0.1, do_nothing), + new Parm_float ("RX Line Cal", rigConfig.audio.rxLineOutCal, 0.0, 99.9, 0.1, do_nothing), + new Parm_float ("RX USB Cal", rigConfig.audio.rxUSBOutCal, 0.0, 99.9, 0.1, do_nothing), +}); + +/* commented out til I go back and fix SSBConfig class +List_menu<3> ssb_config_menu("SSB Config", { + Parm_float("Comp Thresh", rigConfig.lsb.comp_threshold, 0.0, 1.0, 0.1, do_nothing), + Parm_float("Comp Ratio", rigConfig.lsb.comp_ratio, 0.0, 50.0, 1.0, do_nothing), + Parm_float("Comp Gain", rigConfig.lsb.comp_gain, 0.0, 99.9, 0.1, do_nothing) +}); +*/ + /* ListMenu("Configuration", ListMenu("LSB Mode", diff --git a/ubitx_iop/ubitx_iop.ino b/ubitx_iop/ubitx_iop.ino index ce6b4b6..b4b2446 100644 --- a/ubitx_iop/ubitx_iop.ino +++ b/ubitx_iop/ubitx_iop.ino @@ -41,10 +41,9 @@ long knobPos = 0; Bounce btn; elapsedMillis btnMillis; -TopMenu topMenu(rig); -MenuItem* menu = &topMenu; +Main_menu main_menu(rig); +Menu_item* menu_item = &main_menu; -elapsedMillis menuUpdateMillis; elapsedMillis frameMillis; unsigned frameTime; unsigned frameCounter; @@ -79,8 +78,6 @@ void setup() { frameCounter = 0; frameMillis = 0; - menuUpdateMillis = 0; - knob.write(0); btn.attach(ENCODER_SW_PIN, INPUT_PULLUP); @@ -101,8 +98,25 @@ void setup() { //====================================================================== +void update_io_menu(Menu_item* item, bool is_active) +{ + Menu_string text; + + if (!is_active || (is_active && item == nullptr)) { + sendIOPMenuInactive(); + } else { + item->get_text(text); + sendIOPMenuDisplay(text.data()); + } +} + +//====================================================================== + void loop() { + static bool menu_is_active = false; + static bool menu_updated = false; + static char frame_status[100]; static char old_mode_text[17] = " "; static bool paddle_loop = false; @@ -166,114 +180,72 @@ void loop() serviceCAT(); -/* - // send current status @ 10 Hz - //if (frame10Hz > 100) { - if ((rig.modeNum() != oldRigMode)) { // || (rxFilter != oldRxFilter)) { - USBDEBUG("mode changed"); - switch(rig.modeNum()) { - case RIG_MODE_LSB: - USBDEBUG("sending LSB mode status"); - sendIOPSSBStatus(rigConfig.lsb); - break; - - case RIG_MODE_USB: - USBDEBUG("sending USB mode status"); - sendIOPSSBStatus(rigConfig.usb); - break; - - case RIG_MODE_DGL: - USBDEBUG("sending DGL mode status"); - sendIOPDGTStatus(rigConfig.dgl); - break; - - case RIG_MODE_DGU: - USBDEBUG("sending DGU mode status"); - sendIOPDGTStatus(rigConfig.dgu); - break; - - case RIG_MODE_CWL: - USBDEBUG("sending CWL mode status"); - sendIOPCWStatus(rigConfig.cwl); - break; - - case RIG_MODE_CWU: - USBDEBUG("sending CWU mode status"); - sendIOPCWStatus(rigConfig.cwu); - break; - - case RIG_MODE_TTL: - USBDEBUG("sending TTL mode status"); - sendIOPTestStatus(); - break; - - case RIG_MODE_TTU: - USBDEBUG("sending TTU mode status"); - sendIOPTestStatus(); - break; - } - //frame10Hz = 0; - } -*/ + menu_updated = false; btn.update(); + knobPos = knob.read(); + if (btn.fell()) { - //sendIOPMenuDisplay("PRESSED ", 2); btnMillis = 0; USBDEBUG("button pressed"); - } else if (btn.rose()) { + // cancel out any knob rotation that occurred during in conjunction with press/release + knob.write(0); + + } else if (btn.rose() && menu_is_active) { + menu_updated = true; if (btnMillis > 1000) { // long press - exit - //sendIOPMenuDisplay("REL-LONG ", 2); - menu->exit(); - USBDEBUG("button released - long (exit)"); - } if (btnMillis > 500) { + menu_item = menu_item->exit(); + USBDEBUG("button released - long (exit)"); + } else if (btnMillis > 500) { // medium press - altSelect - menu->altSelect(); - USBDEBUG("button released - medium (altSelect)"); + menu_item = menu_item->altSelect(); + USBDEBUG("button released - medium (altSelect)"); } else { // short press - select - //sendIOPMenuDisplay("REL-SHORT ", 2); - menu->select(); - USBDEBUG("button released - short (select)"); - } -// menu->update(); - } + menu_item = menu_item->select(); + USBDEBUG("button released - short (select)"); + } + // cancel out any knob rotation that occurred during in conjunction with press/release + knob.write(0); - knobPos = knob.read(); - // wait til we turn greater than 3 pos in either direction, for "debouncing" - - if (knobPos > 3) { + } else if (btn.rose() && !menu_is_active) { + menu_is_active = true; + menu_updated = true; + // cancel out any knob rotation that occurred during in conjunction with press/release + knob.write(0); + USBDEBUG("button released - main menu activated"); + + } else if (knobPos > 3 && menu_is_active) { // left - //sendIOPMenuDisplay("LEFT ", 5); - menu->prev(); -// menu->update(); + menu_item->prev(); knob.write(0); - USBDEBUG("knob turned left"); - } else if (knobPos < -3) { + menu_updated = true; + USBDEBUG("knob turned left"); + + } else if (knobPos < -3 && menu_is_active) { // right - //sendIOPMenuDisplay("RIGHT ", 5); - menu->next(); -// menu->update(); + menu_item->next(); knob.write(0); + menu_updated = true; USBDEBUG("knob turned right"); } -// if (strcmp(old_mode_text, rig.modeText())) { -// menu->update(); -// strcpy(old_mode_text, rig.modeText()); -// } + if (menu_item == nullptr) { + menu_item = &main_menu; + menu_is_active = false; + menu_updated = true; + USBDEBUG("null menu - reset to main menu"); + } + + if (menu_updated) { + update_io_menu(menu_item, menu_is_active); + USBDEBUG("updated main menu"); + } // update the speech compressor. Really should do this elsewhere. if (speechCompressor.isEnabled()) speechCompressor.update(); - // update the menu at a 10 Hz rate - if (menuUpdateMillis > 100) { - menu->update(); - menuUpdateMillis = 0; - - } - if (frameMillis > 5000) { #if defined(DEBUG) frameTime = frameMillis;