Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new app BLE Gamepad #115

Merged
merged 2 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading