diff --git a/include/peripherals/StatusLed.h b/include/peripherals/StatusLed.h index a5b67dd..82a11dc 100644 --- a/include/peripherals/StatusLed.h +++ b/include/peripherals/StatusLed.h @@ -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(); }; diff --git a/include/utils/InputState.h b/include/utils/InputState.h index 0f239fc..8722e5f 100644 --- a/include/utils/InputState.h +++ b/include/utils/InputState.h @@ -57,6 +57,8 @@ struct InputState { InputState(); usb_report_t getReport(usb_mode_t mode); + + bool checkHotkey(); }; } // namespace Doncon::Utils diff --git a/include/utils/Menu.h b/include/utils/Menu.h new file mode 100644 index 0000000..b070eb1 --- /dev/null +++ b/include/utils/Menu.h @@ -0,0 +1,87 @@ +#ifndef _UTILS_MENU_H_ +#define _UTILS_MENU_H_ + +#include "utils/InputState.h" +#include "utils/SettingsStore.h" + +#include +#include +#include +#include +#include + +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> items; + Page parent; + }; + + const static std::map descriptors; + + private: + std::shared_ptr 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 settings_store); + + void activate(); + void update(const InputState::Controller &controller_state); + bool active(); + State getState(); +}; +} // namespace Divacon::Utils + +#endif // _UTILS_MENU_H_ \ No newline at end of file diff --git a/include/utils/SettingsStore.h b/include/utils/SettingsStore.h index 73681b6..6069c3d 100644 --- a/include/utils/SettingsStore.h +++ b/include/utils/SettingsStore.h @@ -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(); diff --git a/src/main.cpp b/src/main.cpp index 758d63d..4d269e1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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::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); diff --git a/src/peripherals/StatusLed.cpp b/src/peripherals/StatusLed.cpp index f78ae0a..0f6b499 100644 --- a/src/peripherals/StatusLed.cpp +++ b/src/peripherals/StatusLed.cpp @@ -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(UINT8_MAX); diff --git a/src/utils/InputState.cpp b/src/utils/InputState.cpp index 6bbc4ab..ece5095 100644 --- a/src/utils/InputState.cpp +++ b/src/utils/InputState.cpp @@ -257,4 +257,24 @@ usb_report_t InputState::getDebugReport() { return {(uint8_t *)m_debug_report.c_str(), static_cast(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 diff --git a/src/utils/Menu.cpp b/src/utils/Menu.cpp new file mode 100644 index 0000000..23a7108 --- /dev/null +++ b/src/utils/Menu.cpp @@ -0,0 +1,295 @@ +#include "utils/Menu.h" + +namespace Doncon::Utils { + +const std::map 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 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(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 diff --git a/src/utils/SettingsStore.cpp b/src/utils/SettingsStore.cpp index 69ee133..ae50aac 100644 --- a/src/utils/SettingsStore.cpp +++ b/src/utils/SettingsStore.cpp @@ -11,7 +11,11 @@ namespace Doncon::Utils { static uint8_t read_byte(uint32_t offset) { return *(reinterpret_cast(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();