Add menu backend

This commit is contained in:
Frederik Walk 2023-07-06 20:55:26 +02:00
parent 0f1fd87a07
commit 7a6bb4e182
9 changed files with 458 additions and 4 deletions

View File

@ -36,6 +36,7 @@ class StatusLed {
StatusLed(const Config &config);
void setInputState(const Utils::InputState input_state);
void setBrightness(const uint8_t brightness);
void update();
};

View File

@ -57,6 +57,8 @@ struct InputState {
InputState();
usb_report_t getReport(usb_mode_t mode);
bool checkHotkey();
};
} // namespace Doncon::Utils

87
include/utils/Menu.h Normal file
View File

@ -0,0 +1,87 @@
#ifndef _UTILS_MENU_H_
#define _UTILS_MENU_H_
#include "utils/InputState.h"
#include "utils/SettingsStore.h"
#include <map>
#include <memory>
#include <stddef.h>
#include <string>
#include <vector>
namespace Doncon::Utils {
class Menu {
public:
enum class Page {
None,
Main,
DeviceMode,
LedBrightness,
Bootsel,
BootselMsg,
};
struct State {
Page page;
uint8_t selection;
};
struct Descriptor {
enum class Type {
Root,
Selection,
Value,
RebootInfo,
};
enum class Action {
None,
GotoPageDeviceMode,
GotoPageLedBrightness,
GotoPageBootsel,
ChangeUsbModeSwitchTatacon,
ChangeUsbModeSwitchHoripad,
ChangeUsbModeDS3,
ChangeUsbModePS4Tatacon,
ChangeUsbModeDS4,
ChangeUsbModeXbox360,
ChangeUsbModeDebug,
SetLedBrightness,
DoRebootToBootsel,
};
Type type;
std::string name;
std::vector<std::pair<std::string, Action>> items;
Page parent;
};
const static std::map<Page, const Descriptor> descriptors;
private:
std::shared_ptr<SettingsStore> m_store;
bool m_active;
State m_state;
uint8_t getCurrentSelection(Page page);
void gotoPage(Page page);
void performSelectionAction(Descriptor::Action action);
void performValueAction(Descriptor::Action action, uint8_t value);
public:
Menu(std::shared_ptr<SettingsStore> settings_store);
void activate();
void update(const InputState::Controller &controller_state);
bool active();
State getState();
};
} // namespace Divacon::Utils
#endif // _UTILS_MENU_H_

View File

@ -20,9 +20,10 @@ class SettingsStore {
uint8_t in_use;
usb_mode_t usb_mode;
Peripherals::Drum::Config::Thresholds trigger_thresholds;
uint8_t led_brightness;
uint8_t _padding[m_store_size - sizeof(uint8_t) - sizeof(usb_mode_t) -
sizeof(Peripherals::Drum::Config::Thresholds)];
sizeof(Peripherals::Drum::Config::Thresholds) - sizeof(uint8_t)];
};
static_assert(sizeof(Storecache) == m_store_size);
@ -49,6 +50,9 @@ class SettingsStore {
void setTriggerThresholds(Peripherals::Drum::Config::Thresholds thresholds);
Peripherals::Drum::Config::Thresholds getTriggerThresholds();
void setLedBrightness(uint8_t brightness);
uint8_t getLedBrightness();
void scheduleReboot(bool bootsel = false);
void store();

View File

@ -3,6 +3,7 @@
#include "peripherals/Drum.h"
#include "peripherals/StatusLed.h"
#include "usb/usb_driver.h"
#include "utils/Menu.h"
#include "utils/SettingsStore.h"
#include "GlobalConfiguration.h"
@ -22,6 +23,7 @@ queue_t controller_input_queue;
enum class ControlCommand {
SetUsbMode,
SetPlayerLed,
SetLedBrightness,
};
struct ControlMessage {
@ -29,6 +31,7 @@ struct ControlMessage {
union {
usb_mode_t usb_mode;
usb_player_led_t player_led;
uint8_t brightness;
} data;
};
@ -60,6 +63,9 @@ void core1_task() {
} else if (control_msg.data.player_led.type == USB_PLAYER_LED_COLOR) {
}
break;
case ControlCommand::SetLedBrightness:
led.setBrightness(control_msg.data.brightness);
break;
}
}
@ -87,6 +93,7 @@ int main() {
Utils::InputState input_state;
auto settings_store = std::make_shared<Utils::SettingsStore>();
Utils::Menu menu(settings_store);
Peripherals::Drum drum(Config::Default::drum_config);
@ -104,16 +111,40 @@ int main() {
ctrl_message = {ControlCommand::SetUsbMode, {.usb_mode = mode}};
queue_add_blocking(&control_queue, &ctrl_message);
ctrl_message = {ControlCommand::SetLedBrightness, {.brightness = settings_store->getLedBrightness()}};
queue_add_blocking(&control_queue, &ctrl_message);
};
readSettings();
while (true) {
drum.updateInputState(input_state);
queue_try_remove(&controller_input_queue, &input_state.controller);
usb_driver_send_and_receive_report(input_state.getReport(mode));
if (menu.active()) {
menu.update(input_state.controller);
if (menu.active()) {
// auto display_msg = menu.getState();
// queue_add_blocking(&menu_display_queue, &display_msg);
} else {
settings_store->store();
// ControlMessage ctrl_message = {ControlCommand::ExitMenu, {}};
// queue_add_blocking(&control_queue, &ctrl_message);
}
readSettings();
} else if (input_state.checkHotkey()) {
menu.activate();
// ControlMessage ctrl_message{ControlCommand::EnterMenu, {}};
// queue_add_blocking(&control_queue, &ctrl_message);
} else {
usb_driver_send_and_receive_report(input_state.getReport(mode));
}
usb_driver_task();
queue_try_add(&drum_input_queue, &input_state);

View File

@ -15,6 +15,8 @@ StatusLed::StatusLed(const Config &config) : m_config(config) {
void StatusLed::setInputState(const Utils::InputState input_state) { m_input_state = input_state; }
void StatusLed::setBrightness(const uint8_t brightness) { m_config.brightness = brightness; }
void StatusLed::update() {
static float brightness_factor = m_config.brightness / static_cast<float>(UINT8_MAX);

View File

@ -257,4 +257,24 @@ usb_report_t InputState::getDebugReport() {
return {(uint8_t *)m_debug_report.c_str(), static_cast<uint16_t>(m_debug_report.size() + 1)};
}
bool InputState::checkHotkey() {
static uint32_t hold_since = 0;
static bool hold_active = false;
static const uint32_t hold_timeout = 2000;
if (controller.buttons.start && controller.buttons.select) {
uint32_t now = to_ms_since_boot(get_absolute_time());
if (!hold_active) {
hold_active = true;
hold_since = now;
} else if ((now - hold_since) > hold_timeout) {
hold_active = false;
return true;
}
} else {
hold_active = false;
}
return false;
}
} // namespace Doncon::Utils

295
src/utils/Menu.cpp Normal file
View File

@ -0,0 +1,295 @@
#include "utils/Menu.h"
namespace Doncon::Utils {
const std::map<Menu::Page, const Menu::Descriptor> Menu::descriptors = {
{Menu::Page::Main, //
{Menu::Descriptor::Type::Root, //
"Settings", //
{{"Mode", Menu::Descriptor::Action::GotoPageDeviceMode}, //
{"Brightness", Menu::Descriptor::Action::GotoPageLedBrightness}, //
{"BOOTSEL", Menu::Descriptor::Action::GotoPageBootsel}}, //
Menu::Page::None}}, //
{Menu::Page::DeviceMode, //
{Menu::Descriptor::Type::Selection, //
"Mode", //
{{"Swtch Tata", Menu::Descriptor::Action::ChangeUsbModeSwitchTatacon}, //
{"Swtch Pro", Menu::Descriptor::Action::ChangeUsbModeSwitchHoripad}, //
{"Dualshock3", Menu::Descriptor::Action::ChangeUsbModeDS3}, //
{"PS4 Tata", Menu::Descriptor::Action::ChangeUsbModePS4Tatacon}, //
{"Dualshock4", Menu::Descriptor::Action::ChangeUsbModeDS4}, //
{"Xbox 360", Menu::Descriptor::Action::ChangeUsbModeXbox360}, //
{"Debug", Menu::Descriptor::Action::ChangeUsbModeDebug}}, //
Menu::Page::Main}}, //
{Menu::Page::LedBrightness, //
{Menu::Descriptor::Type::Value, //
"LED Brightness", //
{{"", Menu::Descriptor::Action::SetLedBrightness}}, //
Menu::Page::Main}}, //
{Menu::Page::Bootsel, //
{Menu::Descriptor::Type::Selection, //
"Reboot to BOOTSEL", //
{{"Reboot?", Menu::Descriptor::Action::DoRebootToBootsel}}, //
Menu::Page::Main}}, //
{Menu::Page::BootselMsg, //
{Menu::Descriptor::Type::RebootInfo, //
"Ready to Flash...", //
{{"BOOTSEL", Menu::Descriptor::Action::None}}, //
Menu::Page::Main}}, //
};
Menu::Menu(std::shared_ptr<SettingsStore> settings_store)
: m_store(settings_store), m_active(false), m_state({Page::Main, 0}){};
void Menu::activate() {
m_state = {Page::Main, 0};
m_active = true;
}
static InputState::Controller::Buttons checkPressed(const InputState::Controller &controller_state) {
struct ButtonState {
enum State {
Idle,
RepeatDelay,
Repeat,
};
State state;
uint32_t pressed_since;
uint32_t last_repeat;
};
static const uint32_t repeat_delay = 1000;
static const uint32_t repeat_interval = 20;
static ButtonState state_north = {ButtonState::State::Idle, 0, 0};
static ButtonState state_east = {ButtonState::State::Idle, 0, 0};
static ButtonState state_south = {ButtonState::State::Idle, 0, 0};
static ButtonState state_west = {ButtonState::State::Idle, 0, 0};
InputState::Controller::Buttons result{false, false, false, false, false, false, false, false, false, false};
auto handle_button = [](ButtonState &button_state, bool input_state) {
bool result = false;
if (input_state) {
uint32_t now = to_ms_since_boot(get_absolute_time());
switch (button_state.state) {
case ButtonState::State::Idle:
result = true;
button_state.state = ButtonState::State::RepeatDelay;
button_state.pressed_since = now;
break;
case ButtonState::State::RepeatDelay:
if ((now - button_state.pressed_since) > repeat_delay) {
result = true;
button_state.state = ButtonState::State::Repeat;
button_state.last_repeat = now;
} else {
result = false;
}
break;
case ButtonState::State::Repeat:
if ((now - button_state.last_repeat) > repeat_interval) {
result = true;
button_state.last_repeat = now;
} else {
result = false;
}
break;
}
} else {
result = false;
button_state.state = ButtonState::State::Idle;
}
return result;
};
result.north = handle_button(state_north, controller_state.buttons.north);
result.east = handle_button(state_east, controller_state.buttons.east);
result.south = handle_button(state_south, controller_state.buttons.south);
result.west = handle_button(state_west, controller_state.buttons.west);
return result;
}
uint8_t Menu::getCurrentSelection(Menu::Page page) {
switch (page) {
case Page::DeviceMode:
return static_cast<uint8_t>(m_store->getUsbMode());
break;
case Page::LedBrightness:
return m_store->getLedBrightness();
break;
case Page::Main:
case Page::Bootsel:
case Page::BootselMsg:
case Page::None:
break;
}
return 0;
}
void Menu::gotoPage(Menu::Page page) {
m_state.page = page;
m_state.selection = getCurrentSelection(page);
}
void Menu::performSelectionAction(Menu::Descriptor::Action action) {
auto descriptor_it = descriptors.find(m_state.page);
if (descriptor_it == descriptors.end()) {
assert(false);
return;
}
switch (action) {
case Descriptor::Action::GotoPageDeviceMode:
gotoPage(Page::DeviceMode);
break;
case Descriptor::Action::GotoPageLedBrightness:
gotoPage(Page::LedBrightness);
break;
case Descriptor::Action::GotoPageBootsel:
gotoPage(Page::Bootsel);
break;
case Descriptor::Action::ChangeUsbModeSwitchTatacon:
m_store->setUsbMode(USB_MODE_SWITCH_TATACON);
gotoPage(descriptor_it->second.parent);
break;
case Descriptor::Action::ChangeUsbModeSwitchHoripad:
m_store->setUsbMode(USB_MODE_SWITCH_HORIPAD);
gotoPage(descriptor_it->second.parent);
break;
case Descriptor::Action::ChangeUsbModeDS3:
m_store->setUsbMode(USB_MODE_DUALSHOCK3);
gotoPage(descriptor_it->second.parent);
break;
case Descriptor::Action::ChangeUsbModePS4Tatacon:
m_store->setUsbMode(USB_MODE_PS4_TATACON);
gotoPage(descriptor_it->second.parent);
break;
case Descriptor::Action::ChangeUsbModeDS4:
m_store->setUsbMode(USB_MODE_DUALSHOCK4);
gotoPage(descriptor_it->second.parent);
break;
case Descriptor::Action::ChangeUsbModeXbox360:
m_store->setUsbMode(USB_MODE_XBOX360);
gotoPage(descriptor_it->second.parent);
break;
case Descriptor::Action::ChangeUsbModeDebug:
m_store->setUsbMode(USB_MODE_DEBUG);
gotoPage(descriptor_it->second.parent);
break;
case Descriptor::Action::SetLedBrightness:
gotoPage(descriptor_it->second.parent);
break;
case Descriptor::Action::DoRebootToBootsel:
m_store->scheduleReboot(true);
gotoPage(Page::BootselMsg);
break;
default:
break;
}
}
void Menu::performValueAction(Menu::Descriptor::Action action, uint8_t value) {
auto descriptor_it = descriptors.find(m_state.page);
if (descriptor_it == descriptors.end()) {
assert(false);
return;
}
switch (action) {
case Descriptor::Action::SetLedBrightness:
m_store->setLedBrightness(value);
break;
default:
break;
}
}
void Menu::update(const InputState::Controller &controller_state) {
InputState::Controller::Buttons pressed = checkPressed(controller_state);
auto descriptor_it = descriptors.find(m_state.page);
if (descriptor_it == descriptors.end()) {
assert(false);
return;
}
if (descriptor_it->second.type == Descriptor::Type::RebootInfo) {
m_active = false;
} else if (pressed.north) {
switch (descriptor_it->second.type) {
case Descriptor::Type::Value:
if (m_state.selection > 0) {
m_state.selection--;
performValueAction(descriptor_it->second.items.at(0).second, m_state.selection);
}
break;
case Descriptor::Type::Selection:
case Descriptor::Type::Root:
if (m_state.selection == 0) {
m_state.selection = descriptor_it->second.items.size() - 1;
} else {
m_state.selection--;
}
break;
case Descriptor::Type::RebootInfo:
break;
}
} else if (pressed.west) {
switch (descriptor_it->second.type) {
case Descriptor::Type::Value:
if (m_state.selection < UINT8_MAX) {
m_state.selection++;
performValueAction(descriptor_it->second.items.at(0).second, m_state.selection);
}
break;
case Descriptor::Type::Selection:
case Descriptor::Type::Root:
if (m_state.selection == descriptor_it->second.items.size() - 1) {
m_state.selection = 0;
} else {
m_state.selection++;
}
break;
case Descriptor::Type::RebootInfo:
break;
}
} else if (pressed.south) {
switch (descriptor_it->second.type) {
case Descriptor::Type::Value:
case Descriptor::Type::Selection:
gotoPage(descriptor_it->second.parent);
break;
case Descriptor::Type::Root:
m_active = false;
break;
case Descriptor::Type::RebootInfo:
break;
}
} else if (pressed.east) {
switch (descriptor_it->second.type) {
case Descriptor::Type::Value:
performSelectionAction(descriptor_it->second.items.at(0).second);
break;
case Descriptor::Type::Selection:
case Descriptor::Type::Root:
performSelectionAction(descriptor_it->second.items.at(m_state.selection).second);
break;
case Descriptor::Type::RebootInfo:
break;
}
}
}
bool Menu::active() { return m_active; }
Menu::State Menu::getState() { return m_state; }
} // namespace Doncon::Utils

View File

@ -11,7 +11,11 @@ namespace Doncon::Utils {
static uint8_t read_byte(uint32_t offset) { return *(reinterpret_cast<uint8_t *>(XIP_BASE + offset)); }
SettingsStore::SettingsStore()
: m_store_cache({m_magic_byte, Config::Default::usb_mode, Config::Default::drum_config.trigger_thresholds, {}}),
: m_store_cache({m_magic_byte,
Config::Default::usb_mode,
Config::Default::drum_config.trigger_thresholds,
Config::Default::led_config.brightness,
{}}),
m_dirty(true), m_scheduled_reboot(RebootType::None) {
uint32_t current_page = m_flash_offset + m_flash_size - m_store_size;
bool found_valid = false;
@ -53,6 +57,14 @@ void SettingsStore::setTriggerThresholds(Peripherals::Drum::Config::Thresholds t
}
Peripherals::Drum::Config::Thresholds SettingsStore::getTriggerThresholds() { return m_store_cache.trigger_thresholds; }
void SettingsStore::setLedBrightness(uint8_t brightness) {
if (m_store_cache.led_brightness != brightness) {
m_store_cache.led_brightness = brightness;
m_dirty = true;
}
}
uint8_t SettingsStore::getLedBrightness() { return m_store_cache.led_brightness; }
void SettingsStore::store() {
if (m_dirty) {
multicore_lockout_start_blocking();