From 84e8b99581593431b928a02fa02d67470deb9ed5 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Wed, 25 Feb 2026 23:25:43 -0600 Subject: [PATCH 01/12] Add PS4 lib, set build flag for PS3 --- include/player/controller.h | 34 ++++++++++++++++++++ platformio.ini | 63 ++++++++++++++++++++++++++++++------- src/engine/engine.cpp | 14 ++++++++- src/player/controller.cpp | 49 ++++++++++++++++++++++++++++- 4 files changed, 147 insertions(+), 13 deletions(-) diff --git a/include/player/controller.h b/include/player/controller.h index a05e0c1..d322630 100644 --- a/include/player/controller.h +++ b/include/player/controller.h @@ -1,6 +1,10 @@ #pragma once +#ifdef USE_PS3 #include +#else +#include +#endif namespace Player { @@ -38,6 +42,8 @@ namespace Player Controller() { instance = this; } void begin(String macAddress); +#ifdef USE_PS3 + uint8_t cross() { return instance->controller.data.analog.button.cross; } uint8_t circle() { return instance->controller.data.analog.button.circle; } uint8_t triangle() { return instance->controller.data.analog.button.triangle; } @@ -56,6 +62,28 @@ namespace Player uint8_t start() { return instance->controller.data.button.start; } uint8_t ps() { return instance->controller.data.button.ps; } +#else + uint8_t cross() { return instance->controller.data.button.cross; } + uint8_t circle() { return instance->controller.data.button.circle; } + uint8_t triangle() { return instance->controller.data.button.triangle; } + uint8_t square() { return instance->controller.data.button.square; } + + uint8_t l1() { return instance->controller.data.button.l1; } + uint8_t l2() { return instance->controller.data.button.l2; } + uint8_t r1() { return instance->controller.data.button.r1; } + uint8_t r2() { return instance->controller.data.button.r2; } + + uint8_t up() { return instance->controller.data.button.up; } + uint8_t down() { return instance->controller.data.button.down; } + uint8_t left() { return instance->controller.data.button.left; } + uint8_t right() { return instance->controller.data.button.right; } + + uint8_t start() { return instance->controller.data.button.options; } + uint8_t select() { return instance->controller.data.button.share; } + uint8_t ps() { return instance->controller.data.button.ps; } + +#endif + AnalogStick leftAnalog(); AnalogStick rightAnalog(); @@ -68,7 +96,13 @@ namespace Player void reset(); private: +#ifdef USE_PS3 Ps3Controller controller; +#else + PS4Controller controller; +#endif + // #else + // #ifdef USE_PS4 uint32_t buttonDebouceEvent[17]; static constexpr uint32_t buttonDebounceThreshold = 30; bool buttonLastState[17] = {0}; diff --git a/platformio.ini b/platformio.ini index 3db7412..989e4ab 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,3 +1,13 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + [platformio] name = LumenLab description = "An LED strip gaming and entertainment platform that turns any space into an interactive playground of abstract retro games and animations powered by an ESP32" @@ -6,27 +16,32 @@ default_envs = debug [env] upload_speed = 921600 monitor_speed = 921600 + +[env:virtualization] +platform = espressif32 +board = upesy_wroom +framework = arduino +build_type = debug +build_unflags = -std=gnu++11 +build_flags = -g -DVIRTUALIZATION -std=gnu++2a lib_deps = - jvpernis/PS3 Controller Host@^1.1.0 fastled/FastLED@^3.10.2 adafruit/Adafruit SSD1306@^2.5.15 adafruit/Adafruit GFX Library@^1.12.1 + pablomarquez76/PS4_Controller_Host@^1.1.1 -[env:virtualization] +[env:virtualization_ps3] platform = espressif32 board = upesy_wroom framework = arduino -; debug_tool = esp-prog -; upload_protocol = esp-prog -; debug_init_break = tbreak setup -; debug_extra_cmds = -; set remote hardware-breakpoint-limit 2 -; set remote hardware-watchpoint-limit 1 -; debug_speed = 1000 - build_type = debug build_unflags = -std=gnu++11 -build_flags = -g -DVIRTUALIZATION -std=gnu++2a +build_flags = -g -DVIRTUALIZATION -DUSE_PS3 -std=gnu++2a +lib_deps = + jvpernis/PS3 Controller Host@^1.1.0 + fastled/FastLED@^3.10.2 + adafruit/Adafruit SSD1306@^2.5.15 + adafruit/Adafruit GFX Library@^1.12.1 [env:debug] platform = espressif32 @@ -35,6 +50,11 @@ framework = arduino build_type = debug build_unflags = -std=gnu++11 build_flags = -g -DDEBUG -std=gnu++2a +lib_deps = + fastled/FastLED@^3.10.2 + adafruit/Adafruit SSD1306@^2.5.15 + adafruit/Adafruit GFX Library@^1.12.1 + pablomarquez76/PS4_Controller_Host@^1.1.1 [env:release] platform = espressif32 @@ -47,6 +67,11 @@ build_flags = -DRELEASE -std=gnu++2a -DLUMENLAB_VERSION=\"${sysenv.LUMENLAB_VERSION}\" + lib_deps = + fastled/FastLED@^3.10.2 + adafruit/Adafruit SSD1306@^2.5.15 + adafruit/Adafruit GFX Library@^1.12.1 + pablomarquez76/PS4_Controller_Host@^1.1.1 [env:native] platform = native @@ -57,3 +82,19 @@ build_flags = -I.pio/libdeps/native/googletest/googlemock/include/gmock -pthread -std=gnu++2a +lib_deps = + fastled/FastLED@^3.10.2 + adafruit/Adafruit SSD1306@^2.5.15 + adafruit/Adafruit GFX Library@^1.12.1 + pablomarquez76/PS4_Controller_Host@^1.1.1 + +#### +# To explore later: ESP-Prog debugging +#### +; debug_tool = esp-prog +; upload_protocol = esp-prog +; debug_init_break = tbreak setup +; debug_extra_cmds = +; set remote hardware-breakpoint-limit 2 +; set remote hardware-watchpoint-limit 1 +; debug_speed = 1000 \ No newline at end of file diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 0e0a549..0ecd1c2 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -70,7 +70,7 @@ namespace Engine { SystemCore::Configuration::load(contextManager.memory); - contextManager.stateManager.getPhaseEvasionGameState().loadHighScore(); + contextManager.stateManager.getPhaseEvasionGameState().loadHighScore(); // extract out contextManager.stateManager.getRecallGameState().loadHighScore(); contextManager.controller.begin(SystemCore::Configuration::macAddress()); @@ -96,13 +96,21 @@ namespace Engine logf("boundary_3 = %u", SystemCore::Configuration::recallBoundaries()[2]); #endif + #ifdef USE_PS3 log("Connecting to PS3 controller"); + #else + log("Connecting to PS4 controller"); + #endif // twenty second attempt to connect to PS3 controller int reattempt = 0; while (!contextManager.controller.isConnected() && reattempt < 80) { + #ifdef USE_PS3 log(" Searching for PS3 controller..."); + #else + log(" Searching for PS4 controller..."); + #endif ++reattempt; delay(250); } @@ -135,7 +143,11 @@ namespace Engine contextManager.stateManager.setNextUserSceneChoice(SceneSelection::Canvas); lastRender = micros(); contextManager.stateManager.displayIsVisible = true; + #ifdef USE_PS3 log("PS3 controller connected. Transitioning to Main Menu"); + #else + log("PS4 controller connected. Transitioning to Main Menu"); + #endif return; } diff --git a/src/player/controller.cpp b/src/player/controller.cpp index d81336a..fb10be1 100644 --- a/src/player/controller.cpp +++ b/src/player/controller.cpp @@ -1,10 +1,15 @@ #include #include -#include #include "player/controller.h" #include "logger.h" #include "common.h" +#ifdef USE_PS3 +#include +#else +#include +#endif + namespace Player { // Following Singleton pattern. Only one instance of the game controller can exist @@ -43,6 +48,7 @@ namespace Player if (!instance->connection) return 0; +#ifdef USE_PS3 switch (button) { case ControllerButton::Cross: @@ -82,6 +88,47 @@ namespace Player default: return 0; } +#else + switch (button) + { + case ControllerButton::Cross: + return instance->controller.data.button.cross; + case ControllerButton::Circle: + return instance->controller.data.button.circle; + case ControllerButton::Triangle: + return instance->controller.data.button.triangle; + case ControllerButton::Square: + return instance->controller.data.button.square; + case ControllerButton::Up: + return instance->controller.data.button.up; + case ControllerButton::Down: + return instance->controller.data.button.down; + case ControllerButton::Left: + return instance->controller.data.button.left; + case ControllerButton::Right: + return instance->controller.data.button.right; + case ControllerButton::L1: + return instance->controller.data.button.l1; + case ControllerButton::L2: + return instance->controller.data.button.l2; + case ControllerButton::L3: + return instance->controller.data.button.l3; + case ControllerButton::R1: + return instance->controller.data.button.r1; + case ControllerButton::R2: + return instance->controller.data.button.r2; + case ControllerButton::R3: + return instance->controller.data.button.r3; + case ControllerButton::Start: + return instance->controller.data.button.options; + case ControllerButton::Select: + return instance->controller.data.button.share; + case ControllerButton::Ps: + return instance->controller.data.button.ps; + default: + return 0; + } +#endif } const bool Controller::wasPressed(const ControllerButton button) const From 3059eba28a08dbc4a6000e2205516c59d9af5ee7 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Thu, 26 Feb 2026 23:44:04 -0600 Subject: [PATCH 02/12] Dependency management --- .github/workflows/ci.yaml | 4 ++-- platformio.ini | 44 +++------------------------------------ tools/setup_dev_env.py | 2 +- 3 files changed, 6 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9aed8c4..b3bd1e1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,7 +34,7 @@ jobs: run: pip install --upgrade platformio - name: Build LumenLab - run: pio run + run: pio run -e release - name: Run tests - run: pio test --environment native + run: pio test -e native diff --git a/platformio.ini b/platformio.ini index 989e4ab..9ec66b7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,13 +1,3 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - [platformio] name = LumenLab description = "An LED strip gaming and entertainment platform that turns any space into an interactive playground of abstract retro games and animations powered by an ESP32" @@ -16,32 +6,19 @@ default_envs = debug [env] upload_speed = 921600 monitor_speed = 921600 - -[env:virtualization] -platform = espressif32 -board = upesy_wroom -framework = arduino -build_type = debug -build_unflags = -std=gnu++11 -build_flags = -g -DVIRTUALIZATION -std=gnu++2a lib_deps = + jvpernis/PS3 Controller Host@^1.1.0 fastled/FastLED@^3.10.2 adafruit/Adafruit SSD1306@^2.5.15 adafruit/Adafruit GFX Library@^1.12.1 - pablomarquez76/PS4_Controller_Host@^1.1.1 -[env:virtualization_ps3] +[env:virtualization] platform = espressif32 board = upesy_wroom framework = arduino build_type = debug build_unflags = -std=gnu++11 -build_flags = -g -DVIRTUALIZATION -DUSE_PS3 -std=gnu++2a -lib_deps = - jvpernis/PS3 Controller Host@^1.1.0 - fastled/FastLED@^3.10.2 - adafruit/Adafruit SSD1306@^2.5.15 - adafruit/Adafruit GFX Library@^1.12.1 +build_flags = -g -DVIRTUALIZATION -std=gnu++2a [env:debug] platform = espressif32 @@ -50,11 +27,6 @@ framework = arduino build_type = debug build_unflags = -std=gnu++11 build_flags = -g -DDEBUG -std=gnu++2a -lib_deps = - fastled/FastLED@^3.10.2 - adafruit/Adafruit SSD1306@^2.5.15 - adafruit/Adafruit GFX Library@^1.12.1 - pablomarquez76/PS4_Controller_Host@^1.1.1 [env:release] platform = espressif32 @@ -67,11 +39,6 @@ build_flags = -DRELEASE -std=gnu++2a -DLUMENLAB_VERSION=\"${sysenv.LUMENLAB_VERSION}\" - lib_deps = - fastled/FastLED@^3.10.2 - adafruit/Adafruit SSD1306@^2.5.15 - adafruit/Adafruit GFX Library@^1.12.1 - pablomarquez76/PS4_Controller_Host@^1.1.1 [env:native] platform = native @@ -82,11 +49,6 @@ build_flags = -I.pio/libdeps/native/googletest/googlemock/include/gmock -pthread -std=gnu++2a -lib_deps = - fastled/FastLED@^3.10.2 - adafruit/Adafruit SSD1306@^2.5.15 - adafruit/Adafruit GFX Library@^1.12.1 - pablomarquez76/PS4_Controller_Host@^1.1.1 #### # To explore later: ESP-Prog debugging diff --git a/tools/setup_dev_env.py b/tools/setup_dev_env.py index fab4644..ebb643f 100644 --- a/tools/setup_dev_env.py +++ b/tools/setup_dev_env.py @@ -6,7 +6,7 @@ def setup_python_virtual_environment(): - tools_dir = Path("tools") + tools_dir = Path(".") venv_dir = tools_dir / ".venv" print(f"\n\nCreating a python virtual environment in {venv_dir}...") From 86564d63487d1798b4cf599c28498bf7c9575fa9 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Fri, 27 Feb 2026 17:21:31 -0600 Subject: [PATCH 03/12] Replace PS4 lib, add guards --- include/player/controller.h | 40 ++++++++++++++++++------------------ lumenlab.code-workspace | 8 +++++++- platformio.ini | 10 +++++---- src/core/context-manager.cpp | 2 +- src/engine/engine.cpp | 27 +++++++++++++++--------- src/player/controller.cpp | 36 ++++++++++++++++---------------- 6 files changed, 69 insertions(+), 54 deletions(-) diff --git a/include/player/controller.h b/include/player/controller.h index d322630..5389e96 100644 --- a/include/player/controller.h +++ b/include/player/controller.h @@ -3,7 +3,7 @@ #ifdef USE_PS3 #include #else -#include +#include #endif namespace Player @@ -63,24 +63,24 @@ namespace Player uint8_t ps() { return instance->controller.data.button.ps; } #else - uint8_t cross() { return instance->controller.data.button.cross; } - uint8_t circle() { return instance->controller.data.button.circle; } - uint8_t triangle() { return instance->controller.data.button.triangle; } - uint8_t square() { return instance->controller.data.button.square; } - - uint8_t l1() { return instance->controller.data.button.l1; } - uint8_t l2() { return instance->controller.data.button.l2; } - uint8_t r1() { return instance->controller.data.button.r1; } - uint8_t r2() { return instance->controller.data.button.r2; } - - uint8_t up() { return instance->controller.data.button.up; } - uint8_t down() { return instance->controller.data.button.down; } - uint8_t left() { return instance->controller.data.button.left; } - uint8_t right() { return instance->controller.data.button.right; } - - uint8_t start() { return instance->controller.data.button.options; } - uint8_t select() { return instance->controller.data.button.share; } - uint8_t ps() { return instance->controller.data.button.ps; } + uint8_t cross() { return 5; } // instance->controller.data.button.cross; } + uint8_t circle() { return 5; } // instance->controller.data.button.circle; } + uint8_t triangle() { return 5; } // instance->controller.data.button.triangle; } + uint8_t square() { return 5; } // instance->controller.data.button.square; } + + uint8_t l1() { return 5; } // instance->controller.data.button.l1; } + uint8_t l2() { return 5; } // instance->controller.data.button.l2; } + uint8_t r1() { return 5; } // instance->controller.data.button.r1; } + uint8_t r2() { return 5; } // instance->controller.data.button.r2; } + + uint8_t up() { return 5; } // instance->controller.data.button.up; } + uint8_t down() { return 5; } // instance->controller.data.button.down; } + uint8_t left() { return 5; } // instance->controller.data.button.left; } + uint8_t right() { return 5; } // instance->controller.data.button.right; } + + uint8_t start() { return 5; } // instance->controller.data.button.options; } + uint8_t select() { return 5; } // instance->controller.data.button.share; } + uint8_t ps() { return 5; } // instance->controller.data.button.ps; } #endif @@ -99,7 +99,7 @@ namespace Player #ifdef USE_PS3 Ps3Controller controller; #else - PS4Controller controller; + ::Controller controller; #endif // #else // #ifdef USE_PS4 diff --git a/lumenlab.code-workspace b/lumenlab.code-workspace index aea313b..d48c1c2 100644 --- a/lumenlab.code-workspace +++ b/lumenlab.code-workspace @@ -63,7 +63,13 @@ "cinttypes": "cpp", "typeinfo": "cpp", "any": "cpp", - "variant": "cpp" + "variant": "cpp", + "format": "cpp", + "forward_list": "cpp", + "xhash": "cpp", + "xstring": "cpp", + "xtree": "cpp", + "xutility": "cpp" }, "terminal.integrated.profiles.windows": { "PowerShell": { diff --git a/platformio.ini b/platformio.ini index 9ec66b7..f363e07 100644 --- a/platformio.ini +++ b/platformio.ini @@ -6,7 +6,7 @@ default_envs = debug [env] upload_speed = 921600 monitor_speed = 921600 -lib_deps = +lib_deps = jvpernis/PS3 Controller Host@^1.1.0 fastled/FastLED@^3.10.2 adafruit/Adafruit SSD1306@^2.5.15 @@ -18,7 +18,7 @@ board = upesy_wroom framework = arduino build_type = debug build_unflags = -std=gnu++11 -build_flags = -g -DVIRTUALIZATION -std=gnu++2a +build_flags = -g -DVIRTUALIZATION -DUSE_PS3 -std=gnu++2a [env:debug] platform = espressif32 @@ -26,7 +26,7 @@ board = upesy_wroom framework = arduino build_type = debug build_unflags = -std=gnu++11 -build_flags = -g -DDEBUG -std=gnu++2a +build_flags = -g -DDEBUG -DUSE_PS3 -std=gnu++2a [env:release] platform = espressif32 @@ -37,6 +37,7 @@ build_unflags = -std=gnu++11 build_flags = -Os -DRELEASE + -DUSE_PS3 -std=gnu++2a -DLUMENLAB_VERSION=\"${sysenv.LUMENLAB_VERSION}\" @@ -44,10 +45,11 @@ build_flags = platform = native test_framework = googletest build_unflags = -std=gnu++11 -build_flags = +build_flags = -I.pio/libdeps/native/googletest/googletest/include/gtest -I.pio/libdeps/native/googletest/googlemock/include/gmock -pthread + -DUSE_PS3 -std=gnu++2a #### diff --git a/src/core/context-manager.cpp b/src/core/context-manager.cpp index 76823af..6c416e1 100644 --- a/src/core/context-manager.cpp +++ b/src/core/context-manager.cpp @@ -16,7 +16,7 @@ namespace SystemCore #endif } - ContextManager::~ContextManager() + ContextManager::~ContextManager() { if (application) { diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 0ecd1c2..81a3c17 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -88,29 +88,36 @@ namespace Engine log("Serial connection established."); log("Printing environment variables."); logf("version = %s", SystemCore::Configuration::version()); - logf("macAddress = %s", SystemCore::Configuration::macAddress()); + logf("macAddress = %s", SystemCore::Configuration::macAddress().c_str()); logf("numLeds = %u", SystemCore::Configuration::numLeds()); logf("serialBaud = %u", SystemCore::Configuration::serialBaud()); logf("boundary_1 = %u", SystemCore::Configuration::recallBoundaries()[0]); logf("boundary_2 = %u", SystemCore::Configuration::recallBoundaries()[1]); logf("boundary_3 = %u", SystemCore::Configuration::recallBoundaries()[2]); + logf("Phase Evasion high score: %u", contextManager.stateManager.getPhaseEvasionGameState().highScore); + logf("Recall high score: %u", contextManager.stateManager.getRecallGameState().highScore); +#endif +#ifdef RELEASE + log("NVS memory namespace: lumenlab"); +#else + log("NVS memory namespace: lumenlab-dev"); #endif - #ifdef USE_PS3 +#ifdef USE_PS3 log("Connecting to PS3 controller"); - #else +#else log("Connecting to PS4 controller"); - #endif +#endif // twenty second attempt to connect to PS3 controller int reattempt = 0; while (!contextManager.controller.isConnected() && reattempt < 80) { - #ifdef USE_PS3 +#ifdef USE_PS3 log(" Searching for PS3 controller..."); - #else +#else log(" Searching for PS4 controller..."); - #endif +#endif ++reattempt; delay(250); } @@ -143,11 +150,11 @@ namespace Engine contextManager.stateManager.setNextUserSceneChoice(SceneSelection::Canvas); lastRender = micros(); contextManager.stateManager.displayIsVisible = true; - #ifdef USE_PS3 +#ifdef USE_PS3 log("PS3 controller connected. Transitioning to Main Menu"); - #else +#else log("PS4 controller connected. Transitioning to Main Menu"); - #endif +#endif return; } diff --git a/src/player/controller.cpp b/src/player/controller.cpp index fb10be1..9c402b2 100644 --- a/src/player/controller.cpp +++ b/src/player/controller.cpp @@ -7,7 +7,7 @@ #ifdef USE_PS3 #include #else -#include +#include #endif namespace Player @@ -92,39 +92,39 @@ namespace Player switch (button) { case ControllerButton::Cross: - return instance->controller.data.button.cross; + return 0; // instance->controller.data.button.cross; case ControllerButton::Circle: - return instance->controller.data.button.circle; + return 0; // instance->controller.data.button.circle; case ControllerButton::Triangle: - return instance->controller.data.button.triangle; + return 0; // instance->controller.data.button.triangle; case ControllerButton::Square: - return instance->controller.data.button.square; + return 0; // instance->controller.data.button.square; case ControllerButton::Up: - return instance->controller.data.button.up; + return 0; // instance->controller.data.button.up; case ControllerButton::Down: - return instance->controller.data.button.down; + return 0; // instance->controller.data.button.down; case ControllerButton::Left: - return instance->controller.data.button.left; + return 0; // instance->controller.data.button.left; case ControllerButton::Right: - return instance->controller.data.button.right; + return 0; // instance->controller.data.button.right; case ControllerButton::L1: - return instance->controller.data.button.l1; + return 0; // instance->controller.data.button.l1; case ControllerButton::L2: - return instance->controller.data.button.l2; + return 0; // instance->controller.data.button.l2; case ControllerButton::L3: - return instance->controller.data.button.l3; + return 0; // instance->controller.data.button.l3; case ControllerButton::R1: - return instance->controller.data.button.r1; + return 0; // instance->controller.data.button.r1; case ControllerButton::R2: - return instance->controller.data.button.r2; + return 0; // instance->controller.data.button.r2; case ControllerButton::R3: - return instance->controller.data.button.r3; + return 0; // instance->controller.data.button.r3; case ControllerButton::Start: - return instance->controller.data.button.options; + return 0; // instance->controller.data.button.options; case ControllerButton::Select: - return instance->controller.data.button.share; + return 0; // instance->controller.data.button.share; case ControllerButton::Ps: - return instance->controller.data.button.ps; + return 0; // instance->controller.data.button.ps; default: return 0; } From bff91557e43fb823723833cbf7738b60f9f5968e Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Fri, 27 Feb 2026 20:55:45 -0600 Subject: [PATCH 04/12] Fix bug with high score, buffer size --- .gitignore | 1 + include/games/phase-evasion/state.h | 2 +- include/lights/led-buffer.h | 4 +++- src/engine/engine.cpp | 12 ++++++------ src/games/phase-evasion/driver.cpp | 2 +- src/games/phase-evasion/state.cpp | 13 +++++++------ 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 6d77523..d4c35c8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ tools/.venv activate-virtual-env.* __pycache__ +lumenlab.ino *.drl *.gko diff --git a/include/games/phase-evasion/state.h b/include/games/phase-evasion/state.h index 4427cfe..dd2f687 100644 --- a/include/games/phase-evasion/state.h +++ b/include/games/phase-evasion/state.h @@ -32,7 +32,7 @@ namespace Games::PhaseEvasion void loadHighScore(); uint16_t calculateTotalScore() const; void checkHighScore(); - void updateHighScore(); + void updateHighScore(uint16_t newHighScore); private: SystemCore::ContextManager *contextManager; diff --git a/include/lights/led-buffer.h b/include/lights/led-buffer.h index aee169f..4f8ad6a 100644 --- a/include/lights/led-buffer.h +++ b/include/lights/led-buffer.h @@ -3,13 +3,14 @@ #include #include "core/configuration.h" +#include "lights/color.h" namespace Lights { class LedBuffer { public: - LedBuffer() : leds{new Color[SystemCore::Configuration::numLeds()]} {} + LedBuffer() : leds{new Color[maxLEDs]} {} ~LedBuffer() { delete leds; } LedBuffer(LedBuffer &&other) = delete; LedBuffer &operator=(LedBuffer &&other) = delete; @@ -26,5 +27,6 @@ namespace Lights private: Color *leds; + static constexpr uint16_t maxLEDs = 600; }; } \ No newline at end of file diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 81a3c17..ea091d9 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -24,6 +24,10 @@ namespace Engine void GameEngine::runApplication() { + // Targeting a 120Hz refresh rate. 1/120Hz * 1000 gives us 8.3333ms per frame + constexpr TickType_t frameTicks = pdMS_TO_TICKS(8); + TickType_t lastWakeTime = xTaskGetTickCount(); + while (contextManager.stateManager.isRunning()) { contextManager.leds.reset(); @@ -57,12 +61,8 @@ namespace Engine break; } - uint32_t now = micros(); - if (now - lastRender >= 8333) // Targeting a 120Hz refresh rate. 1/120Hz * 1000 gives us 8.3333ms per frame - { - lastRender += 8333; - renderLedStrip(); - } + renderLedStrip(); + xTaskDelayUntil(&lastWakeTime, frameTicks); } } diff --git a/src/games/phase-evasion/driver.cpp b/src/games/phase-evasion/driver.cpp index e26e448..3dc5f5d 100644 --- a/src/games/phase-evasion/driver.cpp +++ b/src/games/phase-evasion/driver.cpp @@ -199,7 +199,7 @@ namespace Games::PhaseEvasion void Driver::checkIfHighScore() { - GameState state = contextManager->stateManager.getPhaseEvasionGameState(); + GameState &state = contextManager->stateManager.getPhaseEvasionGameState(); state.checkHighScore(); } diff --git a/src/games/phase-evasion/state.cpp b/src/games/phase-evasion/state.cpp index f9f3981..71ba490 100644 --- a/src/games/phase-evasion/state.cpp +++ b/src/games/phase-evasion/state.cpp @@ -21,17 +21,18 @@ namespace Games::PhaseEvasion void GameState::checkHighScore() { - if (calculateTotalScore() > highScore) + const auto currentScore = calculateTotalScore(); + if (currentScore >= highScore) { - updateHighScore(); + updateHighScore(currentScore); + highScore = currentScore; contextManager->stateManager.displayShouldUpdate = true; } } - void GameState::updateHighScore() + void GameState::updateHighScore(uint16_t newHighScore) { - highScore = calculateTotalScore(); - contextManager->memory.putUInt(memoryKeyName, highScore); - logf("High score updated: %u", highScore); + contextManager->memory.putUInt(memoryKeyName, newHighScore); + logf("High score updated: %u", newHighScore); } } \ No newline at end of file From 4f56cb07e130e095d2bb6778dd6939619aed171c Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Sat, 28 Feb 2026 11:10:17 -0600 Subject: [PATCH 05/12] Extract persistent memory initialization --- include/core/context-manager.h | 1 + src/core/context-manager.cpp | 7 +++++++ src/engine/engine.cpp | 5 +---- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/core/context-manager.h b/include/core/context-manager.h index 3098325..b04abec 100644 --- a/include/core/context-manager.h +++ b/include/core/context-manager.h @@ -27,6 +27,7 @@ namespace SystemCore Preferences memory; Display::MenuTileNavigation menuNav; + void initializeSystemMemory(); void navigateMainMenu(); void navigateGameMenu(); void navigateSceneMenu(); diff --git a/src/core/context-manager.cpp b/src/core/context-manager.cpp index 6c416e1..7e11c9c 100644 --- a/src/core/context-manager.cpp +++ b/src/core/context-manager.cpp @@ -25,6 +25,13 @@ namespace SystemCore } } + void ContextManager::initializeSystemMemory() + { + SystemCore::Configuration::load(memory); + stateManager.getPhaseEvasionGameState().loadHighScore(); + stateManager.getRecallGameState().loadHighScore(); + } + void ContextManager::checkExitRequest() { if (controller.wasPressedAndReleased(Player::ControllerButton::Ps)) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index ea091d9..e59a5fd 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -68,10 +68,7 @@ namespace Engine void GameEngine::initializeEngine() { - SystemCore::Configuration::load(contextManager.memory); - - contextManager.stateManager.getPhaseEvasionGameState().loadHighScore(); // extract out - contextManager.stateManager.getRecallGameState().loadHighScore(); + contextManager.initializeSystemMemory(); contextManager.controller.begin(SystemCore::Configuration::macAddress()); // If debugging, ensure serial connection is stable before setting up components From a3aa529d23b38daec748fba7e414089c1d02a1de Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Sat, 28 Feb 2026 11:22:35 -0600 Subject: [PATCH 06/12] Fix issue with display being inactive on late PS3 controller pair --- include/engine/engine.h | 1 - include/engine/state-manager.h | 3 ++- include/lights/led-buffer.h | 2 +- include/player/controller.h | 3 +-- src/core/context-manager.cpp | 1 + src/engine/engine.cpp | 9 ++++++--- src/games/phase-evasion/driver.cpp | 2 +- src/games/recall/driver.cpp | 2 +- src/player/controller.cpp | 13 +++++++++++++ 9 files changed, 26 insertions(+), 10 deletions(-) diff --git a/include/engine/engine.h b/include/engine/engine.h index a5de381..3ed17bb 100644 --- a/include/engine/engine.h +++ b/include/engine/engine.h @@ -14,7 +14,6 @@ namespace Engine private: SystemCore::ContextManager contextManager; - uint32_t lastRender = 0; float disconnectedLedPhaseShift = 0; static constexpr uint16_t menuTileWidth = 12; diff --git a/include/engine/state-manager.h b/include/engine/state-manager.h index 192846c..7728106 100644 --- a/include/engine/state-manager.h +++ b/include/engine/state-manager.h @@ -62,7 +62,8 @@ namespace Engine recallGameState{ctx}, systemState{SystemState::Initialize}, userMainMenuChoice{MainMenuSelection::Games}, - userGameChoice{GameSelection::Recall} + userGameChoice{GameSelection::Recall}, + userSceneChoice{SceneSelection::Canvas} { } diff --git a/include/lights/led-buffer.h b/include/lights/led-buffer.h index 4f8ad6a..ec7b2ef 100644 --- a/include/lights/led-buffer.h +++ b/include/lights/led-buffer.h @@ -11,7 +11,7 @@ namespace Lights { public: LedBuffer() : leds{new Color[maxLEDs]} {} - ~LedBuffer() { delete leds; } + ~LedBuffer() { delete[] leds; } LedBuffer(LedBuffer &&other) = delete; LedBuffer &operator=(LedBuffer &&other) = delete; LedBuffer(const LedBuffer &other) = delete; diff --git a/include/player/controller.h b/include/player/controller.h index 5389e96..845ba49 100644 --- a/include/player/controller.h +++ b/include/player/controller.h @@ -101,8 +101,6 @@ namespace Player #else ::Controller controller; #endif - // #else - // #ifdef USE_PS4 uint32_t buttonDebouceEvent[17]; static constexpr uint32_t buttonDebounceThreshold = 30; bool buttonLastState[17] = {0}; @@ -111,6 +109,7 @@ namespace Player static Controller *instance; bool connection = false; + uint32_t ignoreEventsUntil = 0; void handleOnConnect(); int filterDeadZone(int8_t value, int deadZone = 3); diff --git a/src/core/context-manager.cpp b/src/core/context-manager.cpp index 7e11c9c..cffcf37 100644 --- a/src/core/context-manager.cpp +++ b/src/core/context-manager.cpp @@ -40,6 +40,7 @@ namespace SystemCore stateManager.setNextUserMenuChoice(Engine::MainMenuSelection::Games); stateManager.setNextUserGameChoice(Engine::GameSelection::Recall); stateManager.setNextUserSceneChoice(Engine::SceneSelection::Canvas); + stateManager.displayShouldUpdate = true; log("Exiting to Main Menu."); } } diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e59a5fd..16b3354 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -130,7 +130,6 @@ namespace Engine contextManager.stateManager.displayIsVisible = true; contextManager.controller.reset(); contextManager.controller.poll(); - lastRender = micros(); log("Startup process completed. Transitioning to Main Menu"); } @@ -141,21 +140,25 @@ namespace Engine { if (contextManager.controller.isConnected()) { + contextManager.controller.reset(); + contextManager.controller.poll(); + contextManager.stateManager.setNext(SystemState::MenuHome); contextManager.stateManager.setNextUserMenuChoice(MainMenuSelection::Games); contextManager.stateManager.setNextUserGameChoice(GameSelection::Recall); contextManager.stateManager.setNextUserSceneChoice(SceneSelection::Canvas); - lastRender = micros(); + contextManager.stateManager.displayShouldUpdate = true; contextManager.stateManager.displayIsVisible = true; #ifdef USE_PS3 log("PS3 controller connected. Transitioning to Main Menu"); #else log("PS4 controller connected. Transitioning to Main Menu"); #endif + contextManager.controller.reset(); return; } - for (uint16_t i = 0; i <= SystemCore::Configuration::numLeds(); ++i) + for (uint16_t i = 0; i < SystemCore::Configuration::numLeds(); ++i) { float phase = std::cos((2 * M_PI * i / SystemCore::Configuration::numLeds()) + (2 * M_PI * disconnectedLedPhaseShift / SystemCore::Configuration::numLeds())) * 127 + 128; contextManager.leds.buffer[i] = {static_cast(std::floor(phase)), 0, 0}; diff --git a/src/games/phase-evasion/driver.cpp b/src/games/phase-evasion/driver.cpp index 3dc5f5d..a8ec1be 100644 --- a/src/games/phase-evasion/driver.cpp +++ b/src/games/phase-evasion/driver.cpp @@ -219,7 +219,7 @@ namespace Games::PhaseEvasion void Driver::gameOver() { - for (uint16_t i = 0; i <= SystemCore::Configuration::numLeds(); ++i) + for (uint16_t i = 0; i < SystemCore::Configuration::numLeds(); ++i) { float offset = std::cos((2.0f * M_PI * i / SystemCore::Configuration::numLeds()) - (2.0f * M_PI * static_cast(gameOverPhaseShift) / SystemCore::Configuration::numLeds())); float phase = offset * 127.5 + 127.5; diff --git a/src/games/recall/driver.cpp b/src/games/recall/driver.cpp index be2c80c..3dedcd9 100644 --- a/src/games/recall/driver.cpp +++ b/src/games/recall/driver.cpp @@ -251,7 +251,7 @@ namespace Games::Recall contextManager->stateManager.displayShouldUpdate = true; } - for (uint16_t i = 0; i <= SystemCore::Configuration::numLeds(); ++i) + for (uint16_t i = 0; i < SystemCore::Configuration::numLeds(); ++i) { float phase = std::cos((2.0f * M_PI * i / SystemCore::Configuration::numLeds()) + (2.0f * M_PI * gameOverLedPhaseShift / SystemCore::Configuration::numLeds())) * 127 + 128; contextManager->leds.buffer[i] = {static_cast(std::floor(phase)), 0, 0}; diff --git a/src/player/controller.cpp b/src/player/controller.cpp index 9c402b2..e53d9eb 100644 --- a/src/player/controller.cpp +++ b/src/player/controller.cpp @@ -168,6 +168,18 @@ namespace Player buttonReleasedEvent[i] = false; } + if (millis() < ignoreEventsUntil) + { + for (uint32_t i = 0; i < arraySize(buttonLastState); ++i) + { + auto btn = static_cast(i); + bool raw = rawButtonState(btn) != 0; + buttonLastState[i] = raw; + buttonDebouceEvent[i] = millis(); + } + return; + } + for (uint32_t i = 0; i < arraySize(buttonLastState); ++i) { auto btn = static_cast(i); @@ -199,6 +211,7 @@ namespace Player { log("Game controller connected successfully."); instance->connection = true; + instance->ignoreEventsUntil = millis() + 200; instance->reset(); instance->poll(); } From 69266a55f73bd7e1c76e0d16774cd97a96315c61 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Sat, 28 Feb 2026 15:57:10 -0600 Subject: [PATCH 07/12] First multi-target build with different controllers --- .gitignore | 1 + include/core/configuration.h | 14 ++++++++++++-- include/engine/engine.h | 1 + src/core/configuration.cpp | 6 ------ src/display/display.cpp | 8 ++++++-- src/engine/engine.cpp | 36 ++++++++++++------------------------ src/player/controller.cpp | 10 ++++++++++ 7 files changed, 42 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index d4c35c8..16bbdbf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ tools/.venv activate-virtual-env.* __pycache__ lumenlab.ino +build *.drl *.gko diff --git a/include/core/configuration.h b/include/core/configuration.h index ba283ff..a3ab904 100644 --- a/include/core/configuration.h +++ b/include/core/configuration.h @@ -4,22 +4,32 @@ #include #include +#ifndef LUMENLAB_VERSION +#define LUMENLAB_VERSION "v999.999.999" +#endif +#ifdef USE_PS3 +#define PS_CONTROLLER_TYPE "PS3" +#else +#define PS_CONTROLLER_TYPE "PS4" +#endif + namespace SystemCore { class Configuration { public: - static const String &version() { return _version; } static const String &macAddress() { return _macAddress; } static uint16_t numLeds() { return _numLeds; } static uint32_t serialBaud() { return _serialBaud; } static const std::array &recallBoundaries() { return _recallBoundaries; } + + static constexpr char version[] = LUMENLAB_VERSION; + static constexpr char psControllerType[] = PS_CONTROLLER_TYPE; static constexpr uint8_t ledDimmerGpio = 34; // set from PCB design, should not be configurable static void load(::Preferences &memory); private: - static String _version; static String _macAddress; static uint16_t _numLeds; static uint32_t _serialBaud; diff --git a/include/engine/engine.h b/include/engine/engine.h index 3ed17bb..a5de381 100644 --- a/include/engine/engine.h +++ b/include/engine/engine.h @@ -14,6 +14,7 @@ namespace Engine private: SystemCore::ContextManager contextManager; + uint32_t lastRender = 0; float disconnectedLedPhaseShift = 0; static constexpr uint16_t menuTileWidth = 12; diff --git a/src/core/configuration.cpp b/src/core/configuration.cpp index 36137ac..be68676 100644 --- a/src/core/configuration.cpp +++ b/src/core/configuration.cpp @@ -1,13 +1,8 @@ #include "core/configuration.h" #include -#ifndef LUMENLAB_VERSION -#define LUMENLAB_VERSION "v999.999.999" -#endif - namespace SystemCore { - String Configuration::_version; String Configuration::_macAddress; uint16_t Configuration::_numLeds; uint32_t Configuration::_serialBaud; @@ -15,7 +10,6 @@ namespace SystemCore void Configuration::load(::Preferences &memory) { - _version = LUMENLAB_VERSION; _macAddress = memory.getString("macAddress", "00:00:00:00:00:00"); _numLeds = static_cast(memory.getString("numLeds", "300").toInt()); _serialBaud = static_cast(memory.getString("serialBaud", "921600").toInt()); diff --git a/src/display/display.cpp b/src/display/display.cpp index 000f937..8368b8c 100644 --- a/src/display/display.cpp +++ b/src/display/display.cpp @@ -226,9 +226,13 @@ namespace Display display.clearDisplay(); drawHeader("LumenLab"); - auto startingX = calculateCenterText("PS3 controller"); + char controllerTypeBuffer[32] = ""; + sprintf(controllerTypeBuffer, "%s controller", SystemCore::Configuration::psControllerType); + + auto startingX = calculateCenterText(controllerTypeBuffer); display.setCursor(startingX, 8); - display.print("PS3 controller"); + display.print(controllerTypeBuffer); + startingX = calculateCenterText("not connected"); display.setCursor(startingX, 16); display.print("not connected"); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 16b3354..3d8ac81 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -24,10 +24,6 @@ namespace Engine void GameEngine::runApplication() { - // Targeting a 120Hz refresh rate. 1/120Hz * 1000 gives us 8.3333ms per frame - constexpr TickType_t frameTicks = pdMS_TO_TICKS(8); - TickType_t lastWakeTime = xTaskGetTickCount(); - while (contextManager.stateManager.isRunning()) { contextManager.leds.reset(); @@ -61,8 +57,12 @@ namespace Engine break; } - renderLedStrip(); - xTaskDelayUntil(&lastWakeTime, frameTicks); + uint32_t now = micros(); + if (now - lastRender >= 8333) // Targeting a 120Hz refresh rate. 1/120Hz * 1000 gives us 8.3333ms per frame + { + lastRender += 8333; + renderLedStrip(); + } } } @@ -84,8 +84,8 @@ namespace Engine } log("Serial connection established."); log("Printing environment variables."); - logf("version = %s", SystemCore::Configuration::version()); - logf("macAddress = %s", SystemCore::Configuration::macAddress().c_str()); + logf("version = %s", SystemCore::Configuration::version); + logf("macAddress = %s", SystemCore::Configuration::macAddress); logf("numLeds = %u", SystemCore::Configuration::numLeds()); logf("serialBaud = %u", SystemCore::Configuration::serialBaud()); logf("boundary_1 = %u", SystemCore::Configuration::recallBoundaries()[0]); @@ -100,21 +100,13 @@ namespace Engine log("NVS memory namespace: lumenlab-dev"); #endif -#ifdef USE_PS3 - log("Connecting to PS3 controller"); -#else - log("Connecting to PS4 controller"); -#endif + logf("Connecting to %s controller", SystemCore::Configuration::psControllerType); - // twenty second attempt to connect to PS3 controller + // twenty second attempt to connect to PS3/PS4 controller int reattempt = 0; while (!contextManager.controller.isConnected() && reattempt < 80) { -#ifdef USE_PS3 - log(" Searching for PS3 controller..."); -#else - log(" Searching for PS4 controller..."); -#endif + logf(" Searching for %s controller...", SystemCore::Configuration::psControllerType); ++reattempt; delay(250); } @@ -149,11 +141,7 @@ namespace Engine contextManager.stateManager.setNextUserSceneChoice(SceneSelection::Canvas); contextManager.stateManager.displayShouldUpdate = true; contextManager.stateManager.displayIsVisible = true; -#ifdef USE_PS3 - log("PS3 controller connected. Transitioning to Main Menu"); -#else - log("PS4 controller connected. Transitioning to Main Menu"); -#endif + logf("%s controller connected. Transitioning to Main Menu", SystemCore::Configuration::psControllerType); contextManager.controller.reset(); return; } diff --git a/src/player/controller.cpp b/src/player/controller.cpp index e53d9eb..4593022 100644 --- a/src/player/controller.cpp +++ b/src/player/controller.cpp @@ -17,12 +17,15 @@ namespace Player void Controller::begin(String macAddress) { +#ifdef USE_PS3 instance->controller.begin(macAddress.c_str()); instance->controller.attachOnConnect(&Controller::onConnect); +#endif } AnalogStick Controller::leftAnalog() { +#ifdef USE_PS3 AnalogStick joystick; auto &analog = instance->controller.data.analog.stick; @@ -30,10 +33,14 @@ namespace Player joystick.y = filterDeadZone(analog.ly); return joystick; +#else + return {0, 0}; +#endif } AnalogStick Controller::rightAnalog() { +#ifdef USE_PS3 AnalogStick joystick; auto &analog = instance->controller.data.analog.stick; @@ -41,6 +48,9 @@ namespace Player joystick.y = filterDeadZone(analog.ry); return joystick; +#else + return {0, 0}; +#endif } const uint8_t Controller::rawButtonState(const ControllerButton button) const From cb41f22cc6fd09d84522b6858517feaa20106832 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Sun, 1 Mar 2026 09:05:39 -0600 Subject: [PATCH 08/12] Fix runtime crash on PS3 controller init --- include/core/context-manager.h | 7 +- include/player/controller.h | 106 +++++---------- include/player/ps3-controller.h | 54 ++++++++ src/engine/engine.cpp | 4 +- src/player/controller.cpp | 234 ++------------------------------ src/player/ps3-controller.cpp | 161 ++++++++++++++++++++++ 6 files changed, 268 insertions(+), 298 deletions(-) create mode 100644 include/player/ps3-controller.h create mode 100644 src/player/ps3-controller.cpp diff --git a/include/core/context-manager.h b/include/core/context-manager.h index b04abec..0ffc1d1 100644 --- a/include/core/context-manager.h +++ b/include/core/context-manager.h @@ -4,7 +4,7 @@ #include "engine/layer.h" #include "engine/state-manager.h" -#include "player/controller.h" +#include "player/ps3-controller.h" #include "lights/led-strip.h" #include "display/display.h" #include "display/menu-navigation.h" @@ -21,7 +21,12 @@ namespace SystemCore Engine::Layer *application = nullptr; Engine::StateManager stateManager; +#ifdef USE_PS3 + Player::Ps3Controller controller; +#else + // to implement ps4 ctrl Player::Controller controller; +#endif Lights::LedStrip leds; Display::OledDisplay display; Preferences memory; diff --git a/include/player/controller.h b/include/player/controller.h index 845ba49..cb2367e 100644 --- a/include/player/controller.h +++ b/include/player/controller.h @@ -1,10 +1,7 @@ #pragma once -#ifdef USE_PS3 -#include -#else -#include -#endif +#include +#include namespace Player { @@ -39,84 +36,49 @@ namespace Player class Controller { public: - Controller() { instance = this; } - void begin(String macAddress); - -#ifdef USE_PS3 - - uint8_t cross() { return instance->controller.data.analog.button.cross; } - uint8_t circle() { return instance->controller.data.analog.button.circle; } - uint8_t triangle() { return instance->controller.data.analog.button.triangle; } - uint8_t square() { return instance->controller.data.analog.button.square; } - - uint8_t l1() { return instance->controller.data.analog.button.l1; } - uint8_t l2() { return instance->controller.data.analog.button.l2; } - uint8_t r1() { return instance->controller.data.analog.button.r1; } - uint8_t r2() { return instance->controller.data.analog.button.r2; } - - uint8_t up() { return instance->controller.data.analog.button.up; } - uint8_t down() { return instance->controller.data.analog.button.down; } - uint8_t left() { return instance->controller.data.analog.button.left; } - uint8_t right() { return instance->controller.data.analog.button.right; } - - uint8_t start() { return instance->controller.data.button.start; } - uint8_t ps() { return instance->controller.data.button.ps; } - -#else - uint8_t cross() { return 5; } // instance->controller.data.button.cross; } - uint8_t circle() { return 5; } // instance->controller.data.button.circle; } - uint8_t triangle() { return 5; } // instance->controller.data.button.triangle; } - uint8_t square() { return 5; } // instance->controller.data.button.square; } - - uint8_t l1() { return 5; } // instance->controller.data.button.l1; } - uint8_t l2() { return 5; } // instance->controller.data.button.l2; } - uint8_t r1() { return 5; } // instance->controller.data.button.r1; } - uint8_t r2() { return 5; } // instance->controller.data.button.r2; } - - uint8_t up() { return 5; } // instance->controller.data.button.up; } - uint8_t down() { return 5; } // instance->controller.data.button.down; } - uint8_t left() { return 5; } // instance->controller.data.button.left; } - uint8_t right() { return 5; } // instance->controller.data.button.right; } - - uint8_t start() { return 5; } // instance->controller.data.button.options; } - uint8_t select() { return 5; } // instance->controller.data.button.share; } - uint8_t ps() { return 5; } // instance->controller.data.button.ps; } - -#endif - - AnalogStick leftAnalog(); - AnalogStick rightAnalog(); - - const uint8_t rawButtonState(const ControllerButton button) const; - const bool wasPressed(const ControllerButton button) const; - const bool wasPressedAndReleased(const ControllerButton button) const; - const bool isConnected() const { return instance->connection; } - void poll(); - bool isDown(const ControllerButton button) const; + virtual ~Controller() = default; + virtual void begin(String macAddress) = 0; + + virtual uint8_t cross() = 0; + virtual uint8_t circle() = 0; + virtual uint8_t triangle() = 0; + virtual uint8_t square() = 0; + + virtual uint8_t l1() = 0; + virtual uint8_t l2() = 0; + virtual uint8_t r1() = 0; + virtual uint8_t r2() = 0; + + virtual uint8_t up() = 0; + virtual uint8_t down() = 0; + virtual uint8_t left() = 0; + virtual uint8_t right() = 0; + + virtual uint8_t start() = 0; + virtual uint8_t ps() = 0; + + virtual AnalogStick leftAnalog() = 0; + virtual AnalogStick rightAnalog() = 0; + + virtual const uint8_t rawButtonState(const ControllerButton button) const = 0; + virtual const bool wasPressed(const ControllerButton button) const = 0; + virtual const bool wasPressedAndReleased(const ControllerButton button) const = 0; + virtual const bool isConnected() const = 0; + virtual void poll() = 0; + virtual bool isDown(const ControllerButton button) const = 0; void reset(); - private: -#ifdef USE_PS3 - Ps3Controller controller; -#else - ::Controller controller; -#endif + protected: uint32_t buttonDebouceEvent[17]; static constexpr uint32_t buttonDebounceThreshold = 30; bool buttonLastState[17] = {0}; bool buttonPressedEvent[17] = {0}; bool buttonReleasedEvent[17] = {0}; - static Controller *instance; bool connection = false; uint32_t ignoreEventsUntil = 0; - void handleOnConnect(); + virtual void handleOnConnect() = 0; int filterDeadZone(int8_t value, int deadZone = 3); - static void onConnect() - { - if (instance) - instance->handleOnConnect(); - } }; } \ No newline at end of file diff --git a/include/player/ps3-controller.h b/include/player/ps3-controller.h new file mode 100644 index 0000000..dc2cd9f --- /dev/null +++ b/include/player/ps3-controller.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include "player/controller.h" + +namespace Player +{ + + class Ps3Controller : public Controller + { + public: + Ps3Controller() : controller(&Ps3) { instance = this; } + + void begin(String macAddress) override; + uint8_t cross() override { return instance->controller->data.analog.button.cross; } + uint8_t circle() override { return instance->controller->data.analog.button.circle; } + uint8_t triangle() override { return instance->controller->data.analog.button.triangle; } + uint8_t square() override { return instance->controller->data.analog.button.square; } + + uint8_t l1() override { return instance->controller->data.analog.button.l1; } + uint8_t l2() override { return instance->controller->data.analog.button.l2; } + uint8_t r1() override { return instance->controller->data.analog.button.r1; } + uint8_t r2() override { return instance->controller->data.analog.button.r2; } + + uint8_t up() override { return instance->controller->data.analog.button.up; } + uint8_t down() override { return instance->controller->data.analog.button.down; } + uint8_t left() override { return instance->controller->data.analog.button.left; } + uint8_t right() override { return instance->controller->data.analog.button.right; } + + uint8_t start() override { return instance->controller->data.button.start; } + uint8_t ps() override { return instance->controller->data.button.ps; } + + AnalogStick leftAnalog() override; + AnalogStick rightAnalog() override; + + const uint8_t rawButtonState(const ControllerButton button) const; + const bool wasPressed(const ControllerButton button) const; + const bool wasPressedAndReleased(const ControllerButton button) const; + const bool isConnected() const { return instance->connection; } + void poll(); + bool isDown(const ControllerButton button) const; + + private: + ::Ps3Controller *controller; + static Ps3Controller *instance; + + void handleOnConnect() override; + static void onConnect() + { + if (instance) + instance->handleOnConnect(); + } + }; +} \ No newline at end of file diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 3d8ac81..426cda2 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -69,7 +69,6 @@ namespace Engine void GameEngine::initializeEngine() { contextManager.initializeSystemMemory(); - contextManager.controller.begin(SystemCore::Configuration::macAddress()); // If debugging, ensure serial connection is stable before setting up components #if defined(VIRTUALIZATION) || defined(DEBUG) @@ -85,7 +84,7 @@ namespace Engine log("Serial connection established."); log("Printing environment variables."); logf("version = %s", SystemCore::Configuration::version); - logf("macAddress = %s", SystemCore::Configuration::macAddress); + logf("macAddress = %s", SystemCore::Configuration::macAddress().c_str()); logf("numLeds = %u", SystemCore::Configuration::numLeds()); logf("serialBaud = %u", SystemCore::Configuration::serialBaud()); logf("boundary_1 = %u", SystemCore::Configuration::recallBoundaries()[0]); @@ -100,6 +99,7 @@ namespace Engine log("NVS memory namespace: lumenlab-dev"); #endif + contextManager.controller.begin(SystemCore::Configuration::macAddress()); logf("Connecting to %s controller", SystemCore::Configuration::psControllerType); // twenty second attempt to connect to PS3/PS4 controller diff --git a/src/player/controller.cpp b/src/player/controller.cpp index 4593022..ab22f0e 100644 --- a/src/player/controller.cpp +++ b/src/player/controller.cpp @@ -1,231 +1,8 @@ -#include -#include #include "player/controller.h" -#include "logger.h" #include "common.h" -#ifdef USE_PS3 -#include -#else -#include -#endif - namespace Player { - // Following Singleton pattern. Only one instance of the game controller can exist - Controller *Controller::instance = nullptr; - - void Controller::begin(String macAddress) - { -#ifdef USE_PS3 - instance->controller.begin(macAddress.c_str()); - instance->controller.attachOnConnect(&Controller::onConnect); -#endif - } - - AnalogStick Controller::leftAnalog() - { -#ifdef USE_PS3 - AnalogStick joystick; - auto &analog = instance->controller.data.analog.stick; - - joystick.x = filterDeadZone(analog.lx); - joystick.y = filterDeadZone(analog.ly); - - return joystick; -#else - return {0, 0}; -#endif - } - - AnalogStick Controller::rightAnalog() - { -#ifdef USE_PS3 - AnalogStick joystick; - auto &analog = instance->controller.data.analog.stick; - - joystick.x = filterDeadZone(analog.rx); - joystick.y = filterDeadZone(analog.ry); - - return joystick; -#else - return {0, 0}; -#endif - } - - const uint8_t Controller::rawButtonState(const ControllerButton button) const - { - if (!instance->connection) - return 0; - -#ifdef USE_PS3 - switch (button) - { - case ControllerButton::Cross: - return instance->controller.data.analog.button.cross; - case ControllerButton::Circle: - return instance->controller.data.analog.button.circle; - case ControllerButton::Triangle: - return instance->controller.data.analog.button.triangle; - case ControllerButton::Square: - return instance->controller.data.analog.button.square; - case ControllerButton::Up: - return instance->controller.data.analog.button.up; - case ControllerButton::Down: - return instance->controller.data.analog.button.down; - case ControllerButton::Left: - return instance->controller.data.analog.button.left; - case ControllerButton::Right: - return instance->controller.data.analog.button.right; - case ControllerButton::L1: - return instance->controller.data.analog.button.l1; - case ControllerButton::L2: - return instance->controller.data.analog.button.l2; - case ControllerButton::L3: - return instance->controller.data.button.l3; - case ControllerButton::R1: - return instance->controller.data.analog.button.r1; - case ControllerButton::R2: - return instance->controller.data.analog.button.r2; - case ControllerButton::R3: - return instance->controller.data.button.r3; - case ControllerButton::Start: - return instance->controller.data.button.start; - case ControllerButton::Select: - return instance->controller.data.button.select; - case ControllerButton::Ps: - return instance->controller.data.button.ps; - default: - return 0; - } -#else - switch (button) - { - case ControllerButton::Cross: - return 0; // instance->controller.data.button.cross; - case ControllerButton::Circle: - return 0; // instance->controller.data.button.circle; - case ControllerButton::Triangle: - return 0; // instance->controller.data.button.triangle; - case ControllerButton::Square: - return 0; // instance->controller.data.button.square; - case ControllerButton::Up: - return 0; // instance->controller.data.button.up; - case ControllerButton::Down: - return 0; // instance->controller.data.button.down; - case ControllerButton::Left: - return 0; // instance->controller.data.button.left; - case ControllerButton::Right: - return 0; // instance->controller.data.button.right; - case ControllerButton::L1: - return 0; // instance->controller.data.button.l1; - case ControllerButton::L2: - return 0; // instance->controller.data.button.l2; - case ControllerButton::L3: - return 0; // instance->controller.data.button.l3; - case ControllerButton::R1: - return 0; // instance->controller.data.button.r1; - case ControllerButton::R2: - return 0; // instance->controller.data.button.r2; - case ControllerButton::R3: - return 0; // instance->controller.data.button.r3; - case ControllerButton::Start: - return 0; // instance->controller.data.button.options; - case ControllerButton::Select: - return 0; // instance->controller.data.button.share; - case ControllerButton::Ps: - return 0; // instance->controller.data.button.ps; - default: - return 0; - } -#endif - } - - const bool Controller::wasPressed(const ControllerButton button) const - { - uint32_t idx = static_cast(button); - return instance->buttonPressedEvent[idx]; - } - - const bool Controller::wasPressedAndReleased(const ControllerButton button) const - { - uint32_t idx = static_cast(button); - return instance->buttonReleasedEvent[idx]; - } - - bool Controller::isDown(const ControllerButton button) const - { - uint32_t idx = static_cast(button); - return instance->buttonLastState[idx]; - } - - void Controller::reset() - { - for (uint32_t i = 0; i < arraySize(buttonPressedEvent); ++i) - { - buttonPressedEvent[i] = false; - buttonReleasedEvent[i] = false; - buttonLastState[i] = rawButtonState(static_cast(i)); - buttonDebouceEvent[i] = 0; - } - } - - void Controller::poll() - { - for (uint32_t i = 0; i < arraySize(buttonPressedEvent); ++i) - { - buttonPressedEvent[i] = false; - buttonReleasedEvent[i] = false; - } - - if (millis() < ignoreEventsUntil) - { - for (uint32_t i = 0; i < arraySize(buttonLastState); ++i) - { - auto btn = static_cast(i); - bool raw = rawButtonState(btn) != 0; - buttonLastState[i] = raw; - buttonDebouceEvent[i] = millis(); - } - return; - } - - for (uint32_t i = 0; i < arraySize(buttonLastState); ++i) - { - auto btn = static_cast(i); - bool raw = rawButtonState(btn) != 0; - - uint32_t now = millis(); - if (raw && !buttonLastState[i]) - { - if (now - buttonDebouceEvent[i] > buttonDebounceThreshold) - { - buttonPressedEvent[i] = true; - buttonLastState[i] = true; - buttonDebouceEvent[i] = now; - } - } - else if (!raw && buttonLastState[i]) - { - if (now - buttonDebouceEvent[i] > buttonDebounceThreshold) - { - buttonReleasedEvent[i] = true; - buttonLastState[i] = false; - buttonDebouceEvent[i] = now; - } - } - } - } - - void Controller::handleOnConnect() - { - log("Game controller connected successfully."); - instance->connection = true; - instance->ignoreEventsUntil = millis() + 200; - instance->reset(); - instance->poll(); - } - int Controller::filterDeadZone(int8_t value, int deadZone) { if (std::abs(value) <= deadZone) @@ -243,4 +20,15 @@ namespace Player int output = static_cast(std::round(scaled * static_cast(maxRaw))); return static_cast(sign * output); } + + void Controller::reset() + { + for (uint32_t i = 0; i < arraySize(buttonPressedEvent); ++i) + { + buttonPressedEvent[i] = false; + buttonReleasedEvent[i] = false; + buttonLastState[i] = rawButtonState(static_cast(i)); + buttonDebouceEvent[i] = 0; + } + } } \ No newline at end of file diff --git a/src/player/ps3-controller.cpp b/src/player/ps3-controller.cpp new file mode 100644 index 0000000..5d82e18 --- /dev/null +++ b/src/player/ps3-controller.cpp @@ -0,0 +1,161 @@ +#include +#include +#include "player/ps3-controller.h" +#include "logger.h" +#include "common.h" + +#include + +namespace Player +{ + // Following Singleton pattern. Only one instance of the game controller can exist + Ps3Controller *Ps3Controller::instance = nullptr; + + void Ps3Controller::begin(String macAddress) + { + instance->controller->begin(macAddress.c_str()); + instance->controller->attachOnConnect(&Ps3Controller::onConnect); + } + + AnalogStick Ps3Controller::leftAnalog() + { + AnalogStick joystick; + auto &analog = instance->controller->data.analog.stick; + + joystick.x = filterDeadZone(analog.lx); + joystick.y = filterDeadZone(analog.ly); + + return joystick; + } + + AnalogStick Ps3Controller::rightAnalog() + { + AnalogStick joystick; + auto &analog = instance->controller->data.analog.stick; + + joystick.x = filterDeadZone(analog.rx); + joystick.y = filterDeadZone(analog.ry); + + return joystick; + } + + const uint8_t Ps3Controller::rawButtonState(const ControllerButton button) const + { + if (!instance->connection) + return 0; + + switch (button) + { + case ControllerButton::Cross: + return instance->controller->data.analog.button.cross; + case ControllerButton::Circle: + return instance->controller->data.analog.button.circle; + case ControllerButton::Triangle: + return instance->controller->data.analog.button.triangle; + case ControllerButton::Square: + return instance->controller->data.analog.button.square; + case ControllerButton::Up: + return instance->controller->data.analog.button.up; + case ControllerButton::Down: + return instance->controller->data.analog.button.down; + case ControllerButton::Left: + return instance->controller->data.analog.button.left; + case ControllerButton::Right: + return instance->controller->data.analog.button.right; + case ControllerButton::L1: + return instance->controller->data.analog.button.l1; + case ControllerButton::L2: + return instance->controller->data.analog.button.l2; + case ControllerButton::L3: + return instance->controller->data.button.l3; + case ControllerButton::R1: + return instance->controller->data.analog.button.r1; + case ControllerButton::R2: + return instance->controller->data.analog.button.r2; + case ControllerButton::R3: + return instance->controller->data.button.r3; + case ControllerButton::Start: + return instance->controller->data.button.start; + case ControllerButton::Select: + return instance->controller->data.button.select; + case ControllerButton::Ps: + return instance->controller->data.button.ps; + default: + return 0; + } + } + + const bool Ps3Controller::wasPressed(const ControllerButton button) const + { + uint32_t idx = static_cast(button); + return instance->buttonPressedEvent[idx]; + } + + const bool Ps3Controller::wasPressedAndReleased(const ControllerButton button) const + { + uint32_t idx = static_cast(button); + return instance->buttonReleasedEvent[idx]; + } + + bool Ps3Controller::isDown(const ControllerButton button) const + { + uint32_t idx = static_cast(button); + return instance->buttonLastState[idx]; + } + + void Ps3Controller::poll() + { + for (uint32_t i = 0; i < arraySize(buttonPressedEvent); ++i) + { + buttonPressedEvent[i] = false; + buttonReleasedEvent[i] = false; + } + + if (millis() < ignoreEventsUntil) + { + for (uint32_t i = 0; i < arraySize(buttonLastState); ++i) + { + auto btn = static_cast(i); + bool raw = rawButtonState(btn) != 0; + buttonLastState[i] = raw; + buttonDebouceEvent[i] = millis(); + } + return; + } + + for (uint32_t i = 0; i < arraySize(buttonLastState); ++i) + { + auto btn = static_cast(i); + bool raw = rawButtonState(btn) != 0; + + uint32_t now = millis(); + if (raw && !buttonLastState[i]) + { + if (now - buttonDebouceEvent[i] > buttonDebounceThreshold) + { + buttonPressedEvent[i] = true; + buttonLastState[i] = true; + buttonDebouceEvent[i] = now; + } + } + else if (!raw && buttonLastState[i]) + { + if (now - buttonDebouceEvent[i] > buttonDebounceThreshold) + { + buttonReleasedEvent[i] = true; + buttonLastState[i] = false; + buttonDebouceEvent[i] = now; + } + } + } + } + + void Ps3Controller::handleOnConnect() + { + log("Game controller connected successfully."); + instance->connection = true; + instance->ignoreEventsUntil = millis() + 200; + instance->reset(); + instance->poll(); + } +} \ No newline at end of file From 59fc3865bf4cc4993476efdccbdcc7945db700ae Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Sun, 1 Mar 2026 12:11:14 -0600 Subject: [PATCH 09/12] Multi-target build directives --- .vscode/tasks.json | 55 +++++++++++++++++++- include/core/context-manager.h | 4 +- include/player/controller-properties.h | 33 ++++++++++++ include/player/controller.h | 30 +---------- include/player/ps3-controller.h | 4 +- include/player/ps4-controller.h | 69 ++++++++++++++++++++++++++ lumenlab.code-workspace | 18 +++++-- src/core/context-manager.cpp | 1 + src/games/recall/driver.cpp | 2 +- src/player/ps3-controller.cpp | 5 +- 10 files changed, 183 insertions(+), 38 deletions(-) create mode 100644 include/player/controller-properties.h create mode 100644 include/player/ps4-controller.h diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3711b1d..2d58e38 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -39,7 +39,7 @@ } }, { - "label": "LumenLab - Upload (Virtualization)", + "label": "LumenLab - Upload (PS3 Virtualization)", "type": "shell", "command": "platformio", "args": [ @@ -63,6 +63,21 @@ "panel": "shared" } }, + { + "label": "LumenLab - Upload (PS4 Virtualization)", + "type": "shell", + "command": "echo \"\" > lumenlab.ino && arduino-cli compile --fqbn esp32-bluepad32:esp32:uPesy_wroom --build-property compiler.cpp.extra_flags=\"-Iinclude -Isrc -Os -DVIRTUALIZATION -std=gnu++2a -DLUMENLAB_VERSION=\"${LUMENLAB_VERSION}\"\" --output-dir build", + "options": { + "statusbar": { + "hide": true + } + }, + "presentation": { + "reveal": "always", + "focus": true, + "panel": "shared" + } + }, { "label": "PlatformIO: Upload", "type": "shell", @@ -179,6 +194,42 @@ "focus": true, "panel": "shared" } - } + }, + { + "label": "Upload and Start Virtualization (PS3)", + "dependsOrder": "sequence", + "dependsOn": [ + "LumenLab - Upload (PS3 Virtualization)", + "Start Virtualization" + ], + "options": { + "statusbar": { + "hide": true + } + }, + "presentation": { + "reveal": "always", + "focus": true, + "panel": "shared" + } + }, + { + "label": "Upload and Start Virtualization (PS4)", + "dependsOrder": "sequence", + "dependsOn": [ + "LumenLab - Upload (PS4 Virtualization)", + "Start Virtualization" + ], + "options": { + "statusbar": { + "hide": true + } + }, + "presentation": { + "reveal": "always", + "focus": true, + "panel": "shared" + } + }, ] } \ No newline at end of file diff --git a/include/core/context-manager.h b/include/core/context-manager.h index 0ffc1d1..59cdbc7 100644 --- a/include/core/context-manager.h +++ b/include/core/context-manager.h @@ -5,6 +5,7 @@ #include "engine/layer.h" #include "engine/state-manager.h" #include "player/ps3-controller.h" +#include "player/ps4-controller.h" #include "lights/led-strip.h" #include "display/display.h" #include "display/menu-navigation.h" @@ -24,8 +25,7 @@ namespace SystemCore #ifdef USE_PS3 Player::Ps3Controller controller; #else - // to implement ps4 ctrl - Player::Controller controller; + Player::Ps4Controller controller; #endif Lights::LedStrip leds; Display::OledDisplay display; diff --git a/include/player/controller-properties.h b/include/player/controller-properties.h new file mode 100644 index 0000000..3884fba --- /dev/null +++ b/include/player/controller-properties.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace Player +{ + struct AnalogStick + { + int8_t x = 0; + int8_t y = 0; + }; + + enum class ControllerButton : uint8_t + { + Cross, + Circle, + Triangle, + Square, + Up, + Down, + Left, + Right, + L1, + L2, + L3, + R1, + R2, + R3, + Start, + Select, + Ps + }; +} \ No newline at end of file diff --git a/include/player/controller.h b/include/player/controller.h index cb2367e..197ff33 100644 --- a/include/player/controller.h +++ b/include/player/controller.h @@ -3,36 +3,10 @@ #include #include +#include "player/controller-properties.h" + namespace Player { - - struct AnalogStick - { - int8_t x = 0; - int8_t y = 0; - }; - - enum class ControllerButton : uint8_t - { - Cross, - Circle, - Triangle, - Square, - Up, - Down, - Left, - Right, - L1, - L2, - L3, - R1, - R2, - R3, - Start, - Select, - Ps - }; - class Controller { public: diff --git a/include/player/ps3-controller.h b/include/player/ps3-controller.h index dc2cd9f..a3f73fe 100644 --- a/include/player/ps3-controller.h +++ b/include/player/ps3-controller.h @@ -1,3 +1,4 @@ +#ifdef USE_PS3 #pragma once #include @@ -51,4 +52,5 @@ namespace Player instance->handleOnConnect(); } }; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/include/player/ps4-controller.h b/include/player/ps4-controller.h new file mode 100644 index 0000000..509c77c --- /dev/null +++ b/include/player/ps4-controller.h @@ -0,0 +1,69 @@ +#ifndef USE_PS3 + +#pragma once + +#include + +#include "player/controller-properties.h" + +namespace Player +{ + class Controller + { + public: + Controller() { instance = this; } + void begin(String macAddress); + + uint8_t cross() { return 5; } // instance->controller.data.button.cross; } + uint8_t circle() { return 5; } // instance->controller.data.button.circle; } + uint8_t triangle() { return 5; } // instance->controller.data.button.triangle; } + uint8_t square() { return 5; } // instance->controller.data.button.square; } + + uint8_t l1() { return 5; } // instance->controller.data.button.l1; } + uint8_t l2() { return 5; } // instance->controller.data.button.l2; } + uint8_t r1() { return 5; } // instance->controller.data.button.r1; } + uint8_t r2() { return 5; } // instance->controller.data.button.r2; } + + uint8_t up() { return 5; } // instance->controller.data.button.up; } + uint8_t down() { return 5; } // instance->controller.data.button.down; } + uint8_t left() { return 5; } // instance->controller.data.button.left; } + uint8_t right() { return 5; } // instance->controller.data.button.right; } + + uint8_t start() { return 5; } // instance->controller.data.button.options; } + uint8_t select() { return 5; } // instance->controller.data.button.share; } + uint8_t ps() { return 5; } // instance->controller.data.button.ps; } + + AnalogStick leftAnalog(); + AnalogStick rightAnalog(); + + const uint8_t rawButtonState(const ControllerButton button) const; + const bool wasPressed(const ControllerButton button) const; + const bool wasPressedAndReleased(const ControllerButton button) const; + const bool isConnected() const { return instance->connection; } + void poll(); + bool isDown(const ControllerButton button) const; + void reset(); + + private: + // ::Controller controller; + uint32_t buttonDebouceEvent[17]; + static constexpr uint32_t buttonDebounceThreshold = 30; + bool buttonLastState[17] = {0}; + bool buttonPressedEvent[17] = {0}; + bool buttonReleasedEvent[17] = {0}; + + static Controller *instance; + bool connection = false; + uint32_t ignoreEventsUntil = 0; + + void handleOnConnect(); + int filterDeadZone(int8_t value, int deadZone = 3); + static void onConnect() + { + if (instance) + instance->handleOnConnect(); + } + }; +} + +#endif \ No newline at end of file diff --git a/lumenlab.code-workspace b/lumenlab.code-workspace index d48c1c2..aaa602e 100644 --- a/lumenlab.code-workspace +++ b/lumenlab.code-workspace @@ -137,12 +137,12 @@ }, { "text": "$(debug-alt)", - "tooltip": "PlatformIO: Upload (Virtualization)", + "tooltip": "PlatformIO: Upload (PS3 Virtualization)", "commands": [ { "id": "workbench.action.tasks.runTask", "args": [ - "Upload and Start Virtualization" + "Upload and Start Virtualization (PS3)" ] }, ] @@ -205,7 +205,19 @@ "id": "platformio-ide.serialMonitor" }, ] - } + }, + { + "text": "$(debug-alt)", + "tooltip": "PlatformIO: Upload (PS4 Virtualization)", + "commands": [ + { + "id": "workbench.action.tasks.runTask", + "args": [ + "Upload and Start Virtualization (PS4)" + ] + }, + ] + }, ], } } \ No newline at end of file diff --git a/src/core/context-manager.cpp b/src/core/context-manager.cpp index cffcf37..0e7fe00 100644 --- a/src/core/context-manager.cpp +++ b/src/core/context-manager.cpp @@ -1,4 +1,5 @@ #include "core/context-manager.h" +#include "player/controller-properties.h" #include "games/demo/driver.h" #include "games/recall/driver.h" #include "games/phase-evasion/driver.h" diff --git a/src/games/recall/driver.cpp b/src/games/recall/driver.cpp index 3dedcd9..249ea74 100644 --- a/src/games/recall/driver.cpp +++ b/src/games/recall/driver.cpp @@ -1,5 +1,5 @@ #include "esp_system.h" - +#include "player/controller-properties.h" #include "games/recall/driver.h" #include "common.h" #include "logger.h" diff --git a/src/player/ps3-controller.cpp b/src/player/ps3-controller.cpp index 5d82e18..ef14e97 100644 --- a/src/player/ps3-controller.cpp +++ b/src/player/ps3-controller.cpp @@ -1,3 +1,5 @@ +#ifdef USE_PS3 + #include #include #include "player/ps3-controller.h" @@ -158,4 +160,5 @@ namespace Player instance->reset(); instance->poll(); } -} \ No newline at end of file +} +#endif \ No newline at end of file From 935f38a7af5e97a2a49e281b623bae2d1c028887 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Sun, 1 Mar 2026 13:02:40 -0600 Subject: [PATCH 10/12] Add missing header guard --- include/player/ps4-controller.h | 38 ++++++++++++++------------------- src/player/ps4-controller.cpp | 8 +++++++ 2 files changed, 24 insertions(+), 22 deletions(-) create mode 100644 src/player/ps4-controller.cpp diff --git a/include/player/ps4-controller.h b/include/player/ps4-controller.h index 509c77c..e1913d2 100644 --- a/include/player/ps4-controller.h +++ b/include/player/ps4-controller.h @@ -1,18 +1,19 @@ -#ifndef USE_PS3 +#if !defined(USE_PS3) #pragma once #include +#include "player/controller.h" #include "player/controller-properties.h" namespace Player { - class Controller + class Ps4Controller : public Controller { public: - Controller() { instance = this; } - void begin(String macAddress); + Ps4Controller() : controller(nullptr) { instance = this; } + void begin(String macAddress) {}; uint8_t cross() { return 5; } // instance->controller.data.button.cross; } uint8_t circle() { return 5; } // instance->controller.data.button.circle; } @@ -33,31 +34,24 @@ namespace Player uint8_t select() { return 5; } // instance->controller.data.button.share; } uint8_t ps() { return 5; } // instance->controller.data.button.ps; } - AnalogStick leftAnalog(); - AnalogStick rightAnalog(); + AnalogStick leftAnalog() { return {0, 0}; } + AnalogStick rightAnalog() { return {0, 0}; } - const uint8_t rawButtonState(const ControllerButton button) const; - const bool wasPressed(const ControllerButton button) const; - const bool wasPressedAndReleased(const ControllerButton button) const; + const uint8_t rawButtonState(const ControllerButton button) const override { return 0; } + const bool wasPressed(const ControllerButton button) const override { return 0; } + const bool wasPressedAndReleased(const ControllerButton button) const override { return 0; } const bool isConnected() const { return instance->connection; } - void poll(); - bool isDown(const ControllerButton button) const; - void reset(); + void poll() {} + bool isDown(const ControllerButton button) const { return false; } + void reset() {} private: - // ::Controller controller; - uint32_t buttonDebouceEvent[17]; - static constexpr uint32_t buttonDebounceThreshold = 30; - bool buttonLastState[17] = {0}; - bool buttonPressedEvent[17] = {0}; - bool buttonReleasedEvent[17] = {0}; + ::Controller *controller; - static Controller *instance; + static Ps4Controller *instance; bool connection = false; uint32_t ignoreEventsUntil = 0; - - void handleOnConnect(); - int filterDeadZone(int8_t value, int deadZone = 3); + void handleOnConnect() override {}; static void onConnect() { if (instance) diff --git a/src/player/ps4-controller.cpp b/src/player/ps4-controller.cpp new file mode 100644 index 0000000..ffd87d7 --- /dev/null +++ b/src/player/ps4-controller.cpp @@ -0,0 +1,8 @@ +#if !defined(USE_PS3) +#include "player/ps4-controller.h" + +namespace Player +{ + Ps4Controller *Ps4Controller::instance = nullptr; +} +#endif \ No newline at end of file From a3ce3faa634faf4010071902cfc0f56d01dc5343 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Mon, 2 Mar 2026 19:07:32 -0600 Subject: [PATCH 11/12] Setup multi-target build tasks for local development --- .vscode/tasks.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2d58e38..bc1d53c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -66,7 +66,10 @@ { "label": "LumenLab - Upload (PS4 Virtualization)", "type": "shell", - "command": "echo \"\" > lumenlab.ino && arduino-cli compile --fqbn esp32-bluepad32:esp32:uPesy_wroom --build-property compiler.cpp.extra_flags=\"-Iinclude -Isrc -Os -DVIRTUALIZATION -std=gnu++2a -DLUMENLAB_VERSION=\"${LUMENLAB_VERSION}\"\" --output-dir build", + "command": "echo \"\" > lumenlab.ino && export LUMENLAB_VERSION='v0.0.0' && echo $LUMENLAB_VERSION && arduino-cli compile --fqbn esp32-bluepad32:esp32:uPesy_wroom --build-property compiler.cpp.extra_flags=\"-Iinclude -Isrc -Os -DVIRTUALIZATION -std=gnu++2a -DLUMENLAB_VERSION=\\\"${LUMENLAB_VERSION}\\\"\" --output-dir build && ../lumenlab-installer/Firmware/esptool.exe --chip esp32 --baud 921600 write_flash -z 0x1000 build/lumenlab.ino.bootloader.bin 0x8000 build/lumenlab.ino.partitions.bin 0x10000 build/lumenlab.ino.bin", + "problemMatcher": [ + "$platformio" + ], "options": { "statusbar": { "hide": true From ba838cd96e48c6586b3c7676bc8650cb67134e84 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Mon, 2 Mar 2026 21:48:52 -0600 Subject: [PATCH 12/12] Create ci/cd pipeline for multi-artifact deployment --- .github/scripts/get-pr-details.sh | 33 ++------- .github/workflows/release.yaml | 113 +++++++++++++++++++++++++----- include/core/configuration.h | 15 ++-- src/core/configuration.cpp | 13 ++++ src/engine/engine.cpp | 3 +- 5 files changed, 120 insertions(+), 57 deletions(-) diff --git a/.github/scripts/get-pr-details.sh b/.github/scripts/get-pr-details.sh index 339ac25..feb2233 100644 --- a/.github/scripts/get-pr-details.sh +++ b/.github/scripts/get-pr-details.sh @@ -1,35 +1,16 @@ - #!/usr/bin/env bash - set -euo pipefail -echo "Retrieving PR information..." - -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "GITHUB_TOKEN not set" - exit 1 -fi - -OWNER="${GITHUB_REPOSITORY%%/*}" -REPO="${GITHUB_REPOSITORY##*/}" -SHA="${GITHUB_SHA}" - -PRS_JSON=$(curl -s \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/pulls") - -PR_COUNT=$(echo "$PRS_JSON" | jq length) - -if [ "$PR_COUNT" -eq 0 ]; then - echo "No PR associated with this commit." +if [ -z "${PR_NUMBER:-}" ]; then + echo "No PR number provided, skipping release notes." echo "title=" >> "$GITHUB_OUTPUT" echo "body=" >> "$GITHUB_OUTPUT" exit 0 fi -TITLE=$(echo "$PRS_JSON" | jq -r '.[0].title // ""') -BODY=$(echo "$PRS_JSON" | jq -r '.[0].body // ""') +PR_JSON=$(gh pr view "$PR_NUMBER" --json title,body) +TITLE=$(echo "$PR_JSON" | jq -r '.title // ""') +BODY=$(echo "$PR_JSON" | jq -r '.body // ""') { echo "title<> "$GITHUB_OUTPUT" -echo "PR details captured." -echo "- Title = $TITLE." -echo "- Description = $BODY." \ No newline at end of file +echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2661443..df51570 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,6 +7,7 @@ on: permissions: contents: write + pull-requests: write concurrency: group: release-main @@ -22,13 +23,51 @@ jobs: with: fetch-depth: 0 - - name: Cache pip and PlatformIO + - name: Determine PR number + id: pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=$(gh pr list \ + --state merged \ + --base main \ + --json number,mergeCommit \ + --jq '.[] | select(.mergeCommit.oid=="'"$GITHUB_SHA"'") | .number') + if [[ -z "$PR_NUMBER" ]]; then + echo "No PR associated with this commit; skipping release." + echo "run_release=false" >> $GITHUB_OUTPUT + else + echo "Found PR #$PR_NUMBER" + echo "run_release=true" >> $GITHUB_OUTPUT + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + fi + + - name: Determine next version id from PR labels + id: semver + run: | + PR_NUMBER=${{ steps.pr.outputs.pr_number }} + export PR_NUMBER + bash .github/scripts/compute-semver.sh + echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get release description notes from PR + id: pr_details + run: bash .github/scripts/get-pr-details.sh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.semver.outputs.pr_number }} + + - name: Cache PlatformIO & Arduino CLI uses: actions/cache@v4 with: path: | ~/.cache/pip ~/.platformio/.cache - key: ${{ runner.os }}-pio-${{ hashFiles('**/platformio.ini') }} + ~/.arduino15 + key: ${{ runner.os }}-build-tools-${{ hashFiles('**/platformio.ini') }} - name: Setup Python uses: actions/setup-python@v5 @@ -38,26 +77,65 @@ jobs: - name: Install PlatformIO run: pip install --upgrade platformio - - name: Determine next SemVer from PR labels - id: semver - run: bash .github/scripts/compute-semver.sh - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get release description notes from PR - id: pr_details - run: bash .github/scripts/get-pr-details.sh - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Run tests run: pio test --environment native - - name: Build release + - name: Build PS3 Firmware (PlatformIO) run: pio run -e release env: LUMENLAB_VERSION: ${{ steps.semver.outputs.tag }} + - name: Install Arduino CLI + uses: arduino/setup-arduino-cli@v1 + + - name: Setup Arduino CLI config + run: | + cat > arduino-cli.yaml < lumenlab.ino + arduino-cli compile --fqbn esp32-bluepad32:esp32:uPesy_wroom \ + --build-property compiler.cpp.extra_flags="-Iinclude -Isrc -Os -DRELEASE -std=gnu++2a -DLUMENLAB_VERSION=\"${LUMENLAB_VERSION}\"" \ + --clean --output-dir build + env: + LUMENLAB_VERSION: ${{ steps.semver.outputs.tag }} + + - name: Package PS3 Firmware ZIP + run: | + cp .pio/build/release/firmware.bin ./build/ps3-firmware.bin + cp .pio/build/release/bootloader.bin ./build/ps3-bootloader.bin + cp .pio/build/release/partitions.bin ./build/ps3-partitions.bin + zip -j ./build/ps3-firmware.zip \ + ./build/ps3-firmware.bin \ + ./build/ps3-bootloader.bin \ + ./build/ps3-partitions.bin + + + - name: Package PS4 Firmware ZIP + working-directory: build + run: | + mv ./lumenlab.ino.bin ./ps4-firmware.bin + mv ./lumenlab.ino.bootloader.bin ./ps4-bootloader.bin + mv ./lumenlab.ino.partitions.bin ./ps4-partitions.bin + zip -j ./ps4-firmware.zip \ + ./ps4-firmware.bin \ + ./ps4-bootloader.bin \ + ./ps4-partitions.bin + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: @@ -65,8 +143,7 @@ jobs: name: ${{ steps.semver.outputs.tag }} - ${{ steps.pr_details.outputs.title }} body: ${{ steps.pr_details.outputs.body }} files: | - .pio/build/release/firmware.bin - .pio/build/release/bootloader.bin - .pio/build/release/partitions.bin + ./build/ps3-firmware.zip + ./build/ps4-firmware.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/include/core/configuration.h b/include/core/configuration.h index a3ab904..acd4d00 100644 --- a/include/core/configuration.h +++ b/include/core/configuration.h @@ -4,32 +4,25 @@ #include #include -#ifndef LUMENLAB_VERSION -#define LUMENLAB_VERSION "v999.999.999" -#endif -#ifdef USE_PS3 -#define PS_CONTROLLER_TYPE "PS3" -#else -#define PS_CONTROLLER_TYPE "PS4" -#endif - namespace SystemCore { class Configuration { public: + static const String &version() { return _version; } + static const String &psControllerType() { return _psControllerType; } static const String &macAddress() { return _macAddress; } static uint16_t numLeds() { return _numLeds; } static uint32_t serialBaud() { return _serialBaud; } static const std::array &recallBoundaries() { return _recallBoundaries; } - static constexpr char version[] = LUMENLAB_VERSION; - static constexpr char psControllerType[] = PS_CONTROLLER_TYPE; static constexpr uint8_t ledDimmerGpio = 34; // set from PCB design, should not be configurable static void load(::Preferences &memory); private: + static String _version; + static String _psControllerType; static String _macAddress; static uint16_t _numLeds; static uint32_t _serialBaud; diff --git a/src/core/configuration.cpp b/src/core/configuration.cpp index be68676..383e4ea 100644 --- a/src/core/configuration.cpp +++ b/src/core/configuration.cpp @@ -1,8 +1,19 @@ #include "core/configuration.h" #include +#ifndef LUMENLAB_VERSION +#define LUMENLAB_VERSION "v999.999.999" +#endif +#ifdef USE_PS3 +#define PS_CONTROLLER_TYPE "PS3" +#else +#define PS_CONTROLLER_TYPE "PS4" +#endif + namespace SystemCore { + String Configuration::_version; + String Configuration::_psControllerType; String Configuration::_macAddress; uint16_t Configuration::_numLeds; uint32_t Configuration::_serialBaud; @@ -10,6 +21,8 @@ namespace SystemCore void Configuration::load(::Preferences &memory) { + _version = LUMENLAB_VERSION; + _psControllerType = PS_CONTROLLER_TYPE; _macAddress = memory.getString("macAddress", "00:00:00:00:00:00"); _numLeds = static_cast(memory.getString("numLeds", "300").toInt()); _serialBaud = static_cast(memory.getString("serialBaud", "921600").toInt()); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 426cda2..f8bde2f 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -83,7 +83,8 @@ namespace Engine } log("Serial connection established."); log("Printing environment variables."); - logf("version = %s", SystemCore::Configuration::version); + logf("version = %s", SystemCore::Configuration::version().c_str()); + logf("controller_type = %s", SystemCore::Configuration::psControllerType().c_str()); logf("macAddress = %s", SystemCore::Configuration::macAddress().c_str()); logf("numLeds = %u", SystemCore::Configuration::numLeds()); logf("serialBaud = %u", SystemCore::Configuration::serialBaud());