Skip to content

Commit

Permalink
add new app BLE Gamepad (#115)
Browse files Browse the repository at this point in the history
* add new app BLE Gamepad

* fix clang-format and cppcheck warnings
  • Loading branch information
k3kukua committed Jun 18, 2024
1 parent 9a5ac4d commit 2d41ed3
Show file tree
Hide file tree
Showing 13 changed files with 645 additions and 0 deletions.
2 changes: 2 additions & 0 deletions firmware/keira/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ lib_deps =
bitbank2/PNGenc @ ^1.1.1
bblanchon/ArduinoJson @ ^7.0.4
bitbank2/AnimatedGIF@^2.1.0
lemmingdev/ESP32-BLE-Gamepad@^0.5.5
lib_extra_dirs = ./lib
build_flags = -D LILKA_VERSION=1
board_build.partitions = ./legacy/v1_partitions.csv
Expand All @@ -32,5 +33,6 @@ lib_deps =
lennarthennigs/ESP Telnet @ ^2.2.1
https://github.com/earlephilhower/ESP8266Audio.git
bitbank2/AnimatedGIF@^2.1.0
lemmingdev/ESP32-BLE-Gamepad@^0.5.5
lib_extra_dirs = ./lib
extra_scripts = targets.py
70 changes: 70 additions & 0 deletions firmware/keira/src/apps/ble_gamepad/app.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include "defines.h"
#include "app.h"
#include "ui.h"

#define EXIT_BUTTON_PRESS_SECONDS 5
#define EXIT_BUTTON_PRESS_DELAY ONE_SECOND* EXIT_BUTTON_PRESS_SECONDS

namespace ble_gamepad_app {

MainApp::MainApp() :
App(BLE_GAMEPAD_APP_NAME), uiFps(UI_FPS_WATCHER, UI_DELAY_MILLIS), lastSecondsToExit(EXIT_BUTTON_PRESS_SECONDS) {
}

void MainApp::run() {
if (!bleGamepadController.start()) {
lilka::serial_err("[%s] Starting BLE error!", LOG_TAG);
bleGamepadController.stop();
}
uiLoop();
}

void MainApp::uiLoop() {
UI ui(canvas->width(), canvas->height());
while (bleGamepadController.isActive()) {
uiFps.onStartFrame();
if (isExitHotkeyPressed()) {
onStop();
return;
}
ui.drawFrame(bleGamepadController.isConnected(), lastSecondsToExit);
canvas->drawCanvas(ui.getFrameBuffer());
queueDraw();
uiFps.onEndFrame();
vTaskDelay(uiFps.getLimitMillis() / portTICK_PERIOD_MS);
if (DEBUG) {
uiFps.logEveryOneSecond();
}
}
}

bool MainApp::isExitHotkeyPressed() {
lilka::State st = lilka::controller.peekState();
lilka::ButtonState hotkeyState = st.select;
if (!hotkeyState.pressed) {
lastSecondsToExit = EXIT_BUTTON_PRESS_SECONDS;
return false;
}
uint64_t hotkeyPressTime = hotkeyState.time;
uint64_t curTime = millis();
uint64_t delta = curTime - hotkeyPressTime;
lastSecondsToExit = EXIT_BUTTON_PRESS_SECONDS - delta / ONE_SECOND;
if (lastSecondsToExit < 0) {
lastSecondsToExit = 0;
}
return lastSecondsToExit == 0;
}

void MainApp::cleanUp() {
bleGamepadController.stop();
}

void MainApp::onStop() {
cleanUp();
}

MainApp::~MainApp() {
cleanUp();
}

} // namespace ble_gamepad_app
26 changes: 26 additions & 0 deletions firmware/keira/src/apps/ble_gamepad/app.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <app.h>
#include "fps.h"
#include "controller.h"

namespace ble_gamepad_app {

class MainApp : public App {
public:
MainApp();
~MainApp() override;

private:
void run() override;
void uiLoop();
void onStop() override;
bool isExitHotkeyPressed();
void cleanUp();

Controller bleGamepadController;
FPS uiFps;
int lastSecondsToExit;
};

} // namespace ble_gamepad_app
189 changes: 189 additions & 0 deletions firmware/keira/src/apps/ble_gamepad/controller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#include <Arduino.h>
#include <NimBLEDevice.h>
#include <lilka.h>
#include "defines.h"
#include "controller.h"

#define BUTTON_A BUTTON_2
#define BUTTON_B BUTTON_1
#define BUTTON_C BUTTON_4
#define BUTTON_D BUTTON_3

namespace ble_gamepad_app {

Controller::Controller() :
BleGamepad(DEVICE_NAME),
active(false),
mainLoopFps(MAIN_LOOP_FPS_WATCHER, CONTROLLER_TIMER_DELAY_MILLIS),
batteryLevel(BATTERY_LEVEL_UNKNOWN),
checkBatteryLevelTimer(ONE_MINUTE) {
}

bool Controller::start() {
lilka::serial_log("[%s] Starting BLE", LOG_TAG);

controllerTimer = xTimerCreate(
"bleControllerTimer",
pdMS_TO_TICKS(CONTROLLER_TIMER_DELAY_MILLIS),
pdTRUE,
static_cast<void*>(this),
Controller::controllerTimerCallback
);
if (controllerTimer != NULL) {
xTimerStart(controllerTimer, 0);

BleGamepadConfiguration cfg;
cfg.setControllerType(CONTROLLER_TYPE_GAMEPAD);
cfg.setAutoReport(false);
cfg.setButtonCount(NUM_OF_BUTTONS);
cfg.setIncludeStart(true);
cfg.setIncludeSelect(true);
cfg.setWhichAxes(false, false, false, false, false, false, false, false);
cfg.setWhichSimulationControls(false, false, false, false, false);
begin(&cfg);

return active = true;
}
return active;
}

bool Controller::isActive() {
return active;
}

void Controller::controllerTimerCallback(TimerHandle_t xTimer) {
Controller* instance = static_cast<Controller*>(pvTimerGetTimerID(xTimer));
if (instance) {
instance->updateControllerState();
}
}

void Controller::updateControllerState() {
mainLoopFps.onStartFrame();
if (!isConnected()) {
return;
}
bool needReport = false;
if (checkBatteryLevelTimer.isTimeOnFirstCall()) {
checkBatteryLevelTimer.go();
needReport |= updateBatteryLevel();
}
needReport |= updateButtons();
if (needReport) {
sendReport();
}
mainLoopFps.onEndFrame();
if (DEBUG) {
mainLoopFps.logEveryOneSecond();
}
}

bool Controller::updateBatteryLevel() {
int newBatteryLevel = lilka::battery.readLevel();
if (batteryLevel == newBatteryLevel || newBatteryLevel < BATTERY_LEVEL_NIN || newBatteryLevel > BATTERY_LEVEL_MAX) {
return false;
}
batteryLevel = newBatteryLevel;
setBatteryLevel(batteryLevel);
if (DEBUG) {
lilka::serial_log("[%s] New battery level: %d", LOG_TAG, batteryLevel);
}
return true;
}

bool Controller::updateButtons() {
lilka::State st = lilka::controller.getState();
bool needReport = st.any.justPressed || st.any.justReleased;
if (st.up.justPressed || st.right.justPressed || st.down.justPressed || st.left.justPressed || st.up.justReleased ||
st.right.justReleased || st.down.justReleased || st.left.justReleased) {
if (!st.up.pressed && !st.right.pressed && !st.down.pressed && !st.left.pressed) {
setHat(DPAD_CENTERED);
} else {
if (st.up.pressed) {
if (st.right.pressed) {
setHat(DPAD_UP_RIGHT);
} else if (st.left.pressed) {
setHat(DPAD_UP_LEFT);
} else {
setHat(DPAD_UP);
}
} else if (st.down.pressed) {
if (st.right.pressed) {
setHat(DPAD_DOWN_RIGHT);
} else if (st.left.pressed) {
setHat(DPAD_DOWN_LEFT);
} else {
setHat(DPAD_DOWN);
}
} else if (st.right.pressed) {
setHat(DPAD_RIGHT);
} else { // st.left.pressed
setHat(DPAD_LEFT);
}
}
}
if (st.a.justPressed) {
press(BUTTON_A);
} else if (st.a.justReleased) {
release(BUTTON_A);
}
if (st.b.justPressed) {
press(BUTTON_B);
} else if (st.b.justReleased) {
release(BUTTON_B);
}
if (st.c.justPressed) {
press(BUTTON_C);
} else if (st.c.justReleased) {
release(BUTTON_C);
}
if (st.d.justPressed) {
press(BUTTON_D);
} else if (st.d.justReleased) {
release(BUTTON_D);
}
if (st.select.justPressed) {
pressSelect();
} else if (st.select.justReleased) {
releaseSelect();
}
if (st.start.justPressed) {
pressStart();
} else if (st.start.justReleased) {
releaseStart();
}
return needReport;
}

void Controller::stop() {
if (!active) {
return;
}
lilka::serial_log("[%s] Stopping BLE", LOG_TAG);
int waitTime = 100 / portTICK_PERIOD_MS;
end();
if (controllerTimer != NULL) {
xTimerStop(controllerTimer, pdFALSE);
xTimerDelete(controllerTimer, 0);
}
vTaskDelay(waitTime);
NimBLEDevice::stopAdvertising();
vTaskDelay(waitTime);
std::list<NimBLEClient*>* clients = NimBLEDevice::getClientList();
for (auto it = clients->begin(); it != clients->end(); ++it) {
NimBLEClient* client = *it;
NimBLEDevice::deleteClient(client);
}
clients = nullptr;
vTaskDelay(waitTime);
NimBLEDevice::deinit(true);
vTaskDelay(waitTime);
esp_restart();
active = false;
}

Controller::~Controller() {
stop();
}

} // namespace ble_gamepad_app
33 changes: 33 additions & 0 deletions firmware/keira/src/apps/ble_gamepad/controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <Arduino.h>
#include <BleGamepad.h>
#include "fps.h"
#include "timer.h"

namespace ble_gamepad_app {

class Controller : public BleGamepad {
public:
Controller();
~Controller();
bool start();
void stop();
static void controllerTimerCallback(TimerHandle_t xTimer);
void updateControllerState();
bool isActive();

private:
static constexpr const int NUM_OF_BUTTONS = 4;

int batteryLevel;
FPS mainLoopFps;
TimerHandle_t controllerTimer;
Timer checkBatteryLevelTimer;
bool active;

bool updateBatteryLevel();
bool updateButtons();
};

} // namespace ble_gamepad_app
15 changes: 15 additions & 0 deletions firmware/keira/src/apps/ble_gamepad/defines.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#define BLE_GAMEPAD_APP_NAME "BLE Геймпад"
#define DEBUG false
#define DEVICE_NAME "Lilka BLE Gamepad"
#define UI_DELAY_MILLIS 68 // 15 fps
#define CONTROLLER_TIMER_DELAY_MILLIS 34 // 30 fps
#define LOG_TAG "BLE Gamepad"
#define UI_FPS_WATCHER "VIEW LOOP"
#define MAIN_LOOP_FPS_WATCHER "MAIN LOOP"
#define ONE_SECOND 1000
#define ONE_MINUTE 60 * ONE_SECOND
#define BATTERY_LEVEL_UNKNOWN -1
#define BATTERY_LEVEL_NIN 0
#define BATTERY_LEVEL_MAX 100
Loading

0 comments on commit 2d41ed3

Please sign in to comment.