Revamped the fledgling menu system using the Embedded Template Library.

It works!

Started building up configuration menus.  Compiles, haven't tested yet.
Still learning about how to use the ETL/STL.
This commit is contained in:
Rob French 2020-06-11 23:50:50 -05:00
parent 2ea9c96e47
commit 0a8e91c4a7
3 changed files with 305 additions and 220 deletions

View File

@ -5,7 +5,12 @@
#ifndef __menu_h__
#define __menu_h__
#include <vector>
//#include "etl_profile.h"
//#include <ArduinoSTL.h>
#include "etl/array.h"
#include "etl/cstring.h"
#include "etl/delegate.h"
//#include <vector>
#include <initializer_list>
#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<max_text_len> 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 <const size_t SIZE>
class List_menu : public Menu_item {
public:
ListMenu(const char* text, std::initializer_list<const MenuItem*> 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<const Menu_item*, SIZE> 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<const Menu_item*, SIZE> list_items;
int index;
};
//======================================================================
// Config_parm
//======================================================================
typedef etl::delegate<void(void)> Update_func;
template<typename T>
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<int> {
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<float> {
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;
};
/*

View File

@ -6,6 +6,27 @@
#include "config.h"
#include "menu.h"
void do_nothing_func() {}
Update_func do_nothing = Update_func::create<do_nothing_func>();
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",

View File

@ -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;