From bce2aee6471cc7d73910bd7550e9a0e0377fffe3 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Wed, 4 Mar 2026 22:46:41 -0600 Subject: [PATCH 1/3] Map PS4 buttons, fix initialization --- .vscode/tasks.json | 2 +- include/core/context-manager.h | 3 + include/player/controller.h | 2 - include/player/ps4-controller.h | 89 +++++++------- src/display/display.cpp | 2 +- src/engine/engine.cpp | 10 +- src/player/ps4-controller.cpp | 212 ++++++++++++++++++++++++++++++++ 7 files changed, 267 insertions(+), 53 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index bc1d53c..6cc2f71 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -66,7 +66,7 @@ { "label": "LumenLab - Upload (PS4 Virtualization)", "type": "shell", - "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", + "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 -DDEBUG -std=gnu++2a -DLUMENLAB_VERSION=\\\"${LUMENLAB_VERSION}\\\"\" --output-dir build --jobs 6 && ../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" ], diff --git a/include/core/context-manager.h b/include/core/context-manager.h index 59cdbc7..7315315 100644 --- a/include/core/context-manager.h +++ b/include/core/context-manager.h @@ -4,8 +4,11 @@ #include "engine/layer.h" #include "engine/state-manager.h" +#ifdef USE_PS3 #include "player/ps3-controller.h" +#else #include "player/ps4-controller.h" +#endif #include "lights/led-strip.h" #include "display/display.h" #include "display/menu-navigation.h" diff --git a/include/player/controller.h b/include/player/controller.h index 197ff33..755db95 100644 --- a/include/player/controller.h +++ b/include/player/controller.h @@ -11,7 +11,6 @@ namespace Player { public: virtual ~Controller() = default; - virtual void begin(String macAddress) = 0; virtual uint8_t cross() = 0; virtual uint8_t circle() = 0; @@ -52,7 +51,6 @@ namespace Player bool connection = false; uint32_t ignoreEventsUntil = 0; - virtual void handleOnConnect() = 0; int filterDeadZone(int8_t value, int deadZone = 3); }; } \ No newline at end of file diff --git a/include/player/ps4-controller.h b/include/player/ps4-controller.h index e1913d2..43d5366 100644 --- a/include/player/ps4-controller.h +++ b/include/player/ps4-controller.h @@ -1,63 +1,62 @@ #if !defined(USE_PS3) - #pragma once #include - #include "player/controller.h" -#include "player/controller-properties.h" namespace Player { + class Ps4Controller : public Controller { public: - 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; } - 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() { return {0, 0}; } - AnalogStick rightAnalog() { return {0, 0}; } - - 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 { return false; } - void reset() {} + Ps4Controller() { instance = this; }; + + void begin(); + void poll() override; + + uint8_t cross() override { return button(0x1); } + uint8_t circle() override { return button(0x2); } + uint8_t square() override { return button(0x4); } + uint8_t triangle() override { return button(0x8); } + + uint8_t l1() { return button(0x10); } + uint8_t r1() { return button(0x20); } + uint8_t l2() override; + uint8_t r2() override; + + uint8_t up() override { return dpad(0x1); } + uint8_t down() override { return dpad(0x2); } + uint8_t right() override { return dpad(0x4); } + uint8_t left() override { return dpad(0x8); } + + uint8_t ps() override { return misc(0x1); } + uint8_t start() override { return misc(0x4); } + + AnalogStick leftAnalog() override; + AnalogStick rightAnalog() override; + + const uint8_t rawButtonState(const ControllerButton button) const override; + const bool wasPressed(const ControllerButton button) const override; + const bool wasPressedAndReleased(const ControllerButton button) const override; + const bool isConnected() const override { return connection; } + bool isDown(const ControllerButton button) const override; private: - ::Controller *controller; + ControllerPtr controllers[BP32_MAX_GAMEPADS] = {nullptr}; + ControllerPtr active = nullptr; + + void onConnectedInternal(ControllerPtr ctl); + void onDisconnectedInternal(ControllerPtr ctl); + + static void onConnectedController(ControllerPtr ctl); + static void onDisconnectedController(ControllerPtr ctl); + uint8_t button(uint16_t mask); + uint8_t dpad(uint16_t mask); + uint8_t misc(uint16_t mask); static Ps4Controller *instance; - bool connection = false; - uint32_t ignoreEventsUntil = 0; - void handleOnConnect() override {}; - static void onConnect() - { - if (instance) - instance->handleOnConnect(); - } }; -} +} #endif \ No newline at end of file diff --git a/src/display/display.cpp b/src/display/display.cpp index 8368b8c..ef23ac3 100644 --- a/src/display/display.cpp +++ b/src/display/display.cpp @@ -227,7 +227,7 @@ namespace Display drawHeader("LumenLab"); char controllerTypeBuffer[32] = ""; - sprintf(controllerTypeBuffer, "%s controller", SystemCore::Configuration::psControllerType); + sprintf(controllerTypeBuffer, "%s controller", SystemCore::Configuration::psControllerType().c_str()); auto startingX = calculateCenterText(controllerTypeBuffer); display.setCursor(startingX, 8); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index f8bde2f..d869364 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -100,15 +100,17 @@ namespace Engine log("NVS memory namespace: lumenlab-dev"); #endif - contextManager.controller.begin(SystemCore::Configuration::macAddress()); - logf("Connecting to %s controller", SystemCore::Configuration::psControllerType); + // contextManager.controller.begin(SystemCore::Configuration::macAddress()); + contextManager.controller.begin(); + logf("Connecting to %s controller", SystemCore::Configuration::psControllerType().c_str()); // twenty second attempt to connect to PS3/PS4 controller int reattempt = 0; while (!contextManager.controller.isConnected() && reattempt < 80) { - logf(" Searching for %s controller...", SystemCore::Configuration::psControllerType); + logf(" Searching for %s controller...", SystemCore::Configuration::psControllerType().c_str()); ++reattempt; + contextManager.controller.poll(); delay(250); } @@ -142,7 +144,7 @@ namespace Engine contextManager.stateManager.setNextUserSceneChoice(SceneSelection::Canvas); contextManager.stateManager.displayShouldUpdate = true; contextManager.stateManager.displayIsVisible = true; - logf("%s controller connected. Transitioning to Main Menu", SystemCore::Configuration::psControllerType); + logf("%s controller connected. Transitioning to Main Menu", SystemCore::Configuration::psControllerType().c_str()); contextManager.controller.reset(); return; } diff --git a/src/player/ps4-controller.cpp b/src/player/ps4-controller.cpp index ffd87d7..0ee27fc 100644 --- a/src/player/ps4-controller.cpp +++ b/src/player/ps4-controller.cpp @@ -1,8 +1,220 @@ #if !defined(USE_PS3) +#include #include "player/ps4-controller.h" +#include "player/controller-properties.h" +#include "common.h" namespace Player { + Ps4Controller *Ps4Controller::instance = nullptr; + + void Ps4Controller::begin() + { + BP32.setup(&onConnectedController, &onDisconnectedController); + } + + const uint8_t Ps4Controller::rawButtonState(const ControllerButton button) const + { + if (!active || !active->isConnected()) + return 0; + + switch (button) + { + case ControllerButton::Cross: + return instance->cross(); + case ControllerButton::Circle: + return instance->circle(); + case ControllerButton::Square: + return instance->square(); + case ControllerButton::Triangle: + return instance->triangle(); + + case ControllerButton::L1: + return instance->l1(); + case ControllerButton::R1: + return instance->r1(); + case ControllerButton::L2: + return instance->l2(); + case ControllerButton::R2: + return instance->r2(); + + case ControllerButton::Up: + return instance->up(); + case ControllerButton::Down: + return instance->down(); + case ControllerButton::Left: + return instance->left(); + case ControllerButton::Right: + return instance->right(); + + case ControllerButton::Ps: + return instance->ps(); + case ControllerButton::Start: + return instance->start(); + + default: + return 0; + } + } + + void Ps4Controller::poll() + { + BP32.update(); + 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 Ps4Controller::onConnectedController(ControllerPtr ctl) + { + if (instance) + instance->onConnectedInternal(ctl); + } + + void Ps4Controller::onDisconnectedController(ControllerPtr ctl) + { + if (instance) + instance->onDisconnectedInternal(ctl); + } + + void Ps4Controller::onConnectedInternal(ControllerPtr ctl) + { + for (int i = 0; i < BP32_MAX_GAMEPADS; i++) + { + if (!controllers[i]) + { + instance->controllers[i] = ctl; + active = ctl; + connection = true; + break; + } + } + } + + void Ps4Controller::onDisconnectedInternal(ControllerPtr ctl) + { + for (int i = 0; i < BP32_MAX_GAMEPADS; i++) + { + if (controllers[i] == ctl) + { + controllers[i] = nullptr; + if (active == ctl) + active = nullptr; + break; + } + } + } + + const bool Ps4Controller::wasPressed(const ControllerButton button) const + { + return buttonPressedEvent[static_cast(button)]; + } + + const bool Ps4Controller::wasPressedAndReleased(const ControllerButton button) const + { + return buttonReleasedEvent[static_cast(button)]; + } + + bool Ps4Controller::isDown(const ControllerButton button) const + { + return rawButtonState(button); + } + + uint8_t Ps4Controller::l2() + { + if (!controllers[0]) + return 0; + + return controllers[0]->brake(); + } + + uint8_t Ps4Controller::r2() + { + if (!controllers[0]) + return 0; + + return controllers[0]->throttle(); + } + + AnalogStick Ps4Controller::leftAnalog() + { + if (!active) + return {0, 0}; + + return {active->axisX(), active->axisY()}; + } + + AnalogStick Ps4Controller::rightAnalog() + { + if (!active) + return {0, 0}; + + return {active->axisRX(), active->axisRY()}; + } + + uint8_t Ps4Controller::button(uint16_t mask) + { + if (!controllers[0]) + return 0; + + return (controllers[0]->buttons() & mask) ? 255 : 0; + } + + uint8_t Ps4Controller::dpad(uint16_t mask) + { + if (!controllers[0]) + return 0; + + return (controllers[0]->dpad() & mask) ? 255 : 0; + } + + uint8_t Ps4Controller::misc(uint16_t mask) + { + if (!controllers[0]) + return 0; + + return (controllers[0]->miscSystem() & mask) ? 255 : 0; + } } #endif \ No newline at end of file From 1f8a6f8e3f0bf4df49732333d7489d23cae160f9 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Thu, 5 Mar 2026 19:34:10 -0600 Subject: [PATCH 2/3] Fully integrated PS4 controller. Massive latency --- .vscode/tasks.json | 2 +- include/player/controller-properties.h | 4 ++-- include/player/ps3-controller.h | 4 ++-- include/player/ps4-controller.h | 2 +- src/engine/engine.cpp | 4 ++-- src/player/ps4-controller.cpp | 8 ++++---- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6cc2f71..941a7a6 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -66,7 +66,7 @@ { "label": "LumenLab - Upload (PS4 Virtualization)", "type": "shell", - "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 -DDEBUG -std=gnu++2a -DLUMENLAB_VERSION=\\\"${LUMENLAB_VERSION}\\\"\" --output-dir build --jobs 6 && ../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", + "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 --jobs 6 && ../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" ], diff --git a/include/player/controller-properties.h b/include/player/controller-properties.h index 3884fba..a150ea8 100644 --- a/include/player/controller-properties.h +++ b/include/player/controller-properties.h @@ -6,8 +6,8 @@ namespace Player { struct AnalogStick { - int8_t x = 0; - int8_t y = 0; + int16_t x = 0; + int16_t y = 0; }; enum class ControllerButton : uint8_t diff --git a/include/player/ps3-controller.h b/include/player/ps3-controller.h index a3f73fe..f0ffd78 100644 --- a/include/player/ps3-controller.h +++ b/include/player/ps3-controller.h @@ -12,7 +12,7 @@ namespace Player public: Ps3Controller() : controller(&Ps3) { instance = this; } - void begin(String macAddress) override; + void begin(String macAddress); 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; } @@ -45,7 +45,7 @@ namespace Player ::Ps3Controller *controller; static Ps3Controller *instance; - void handleOnConnect() override; + void handleOnConnect(); static void onConnect() { if (instance) diff --git a/include/player/ps4-controller.h b/include/player/ps4-controller.h index 43d5366..98cc2c5 100644 --- a/include/player/ps4-controller.h +++ b/include/player/ps4-controller.h @@ -12,7 +12,7 @@ namespace Player public: Ps4Controller() { instance = this; }; - void begin(); + void begin(String macAddress); void poll() override; uint8_t cross() override { return button(0x1); } diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index d869364..9f43c42 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -100,8 +100,8 @@ namespace Engine log("NVS memory namespace: lumenlab-dev"); #endif - // contextManager.controller.begin(SystemCore::Configuration::macAddress()); - contextManager.controller.begin(); + contextManager.controller.begin(SystemCore::Configuration::macAddress()); + logf("Connecting to %s controller", SystemCore::Configuration::psControllerType().c_str()); // twenty second attempt to connect to PS3/PS4 controller diff --git a/src/player/ps4-controller.cpp b/src/player/ps4-controller.cpp index 0ee27fc..e1a452b 100644 --- a/src/player/ps4-controller.cpp +++ b/src/player/ps4-controller.cpp @@ -9,7 +9,7 @@ namespace Player Ps4Controller *Ps4Controller::instance = nullptr; - void Ps4Controller::begin() + void Ps4Controller::begin(String macAddress) { BP32.setup(&onConnectedController, &onDisconnectedController); } @@ -182,7 +182,7 @@ namespace Player if (!active) return {0, 0}; - return {active->axisX(), active->axisY()}; + return {active->axisX() / 4, active->axisY() / 4}; } AnalogStick Ps4Controller::rightAnalog() @@ -190,7 +190,7 @@ namespace Player if (!active) return {0, 0}; - return {active->axisRX(), active->axisRY()}; + return {active->axisRX() / 4, active->axisRY() / 4}; } uint8_t Ps4Controller::button(uint16_t mask) @@ -214,7 +214,7 @@ namespace Player if (!controllers[0]) return 0; - return (controllers[0]->miscSystem() & mask) ? 255 : 0; + return (controllers[0]->miscButtons() & mask) ? 255 : 0; } } #endif \ No newline at end of file From e62539fb8265a0a37a0948bd5dc820ca21e36117 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Thu, 5 Mar 2026 20:01:06 -0600 Subject: [PATCH 3/3] Disable PS4 controller release in pipeline --- .github/workflows/release.yaml | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index df51570..2300186 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -60,13 +60,12 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ steps.semver.outputs.pr_number }} - - name: Cache PlatformIO & Arduino CLI + - name: Cache PlatformIO and Python uses: actions/cache@v4 with: path: | ~/.cache/pip ~/.platformio/.cache - ~/.arduino15 key: ${{ runner.os }}-build-tools-${{ hashFiles('**/platformio.ini') }} - name: Setup Python @@ -86,9 +85,11 @@ jobs: LUMENLAB_VERSION: ${{ steps.semver.outputs.tag }} - name: Install Arduino CLI + if: false uses: arduino/setup-arduino-cli@v1 - name: Setup Arduino CLI config + if: false run: | cat > arduino-cli.yaml < lumenlab.ino arduino-cli compile --fqbn esp32-bluepad32:esp32:uPesy_wroom \ @@ -116,16 +121,17 @@ jobs: - 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 + cp .pio/build/release/firmware.bin ./build/lumenlab-firmware.bin + cp .pio/build/release/bootloader.bin ./build/lumenlab-bootloader.bin + cp .pio/build/release/partitions.bin ./build/lumenlab-partitions.bin + zip -j ./build/lumenlab-firmware.zip \ + ./build/lumenlab-firmware.bin \ + ./build/lumenlab-bootloader.bin \ + ./build/lumenlab-partitions.bin - name: Package PS4 Firmware ZIP + if: false working-directory: build run: | mv ./lumenlab.ino.bin ./ps4-firmware.bin @@ -143,7 +149,6 @@ jobs: name: ${{ steps.semver.outputs.tag }} - ${{ steps.pr_details.outputs.title }} body: ${{ steps.pr_details.outputs.body }} files: | - ./build/ps3-firmware.zip - ./build/ps4-firmware.zip + ./build/lumenlab-firmware.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}