Files
cardboy/Firmware/components/backend-esp/src/esp_backend.cpp
2025-10-11 16:44:48 +02:00

241 lines
7.8 KiB
C++

#include "cardboy/backend/esp_backend.hpp"
#include "cardboy/backend/esp/bat_mon.hpp"
#include "cardboy/backend/esp/buttons.hpp"
#include "cardboy/backend/esp/buzzer.hpp"
#include "cardboy/backend/esp/config.hpp"
#include "cardboy/backend/esp/display.hpp"
#include "cardboy/backend/esp/fs_helper.hpp"
#include "cardboy/backend/esp/i2c_global.hpp"
#include "cardboy/backend/esp/power_helper.hpp"
#include "cardboy/backend/esp/shutdowner.hpp"
#include "cardboy/backend/esp/spi_global.hpp"
#include "cardboy/sdk/display_spec.hpp"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_random.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs.h"
#include "nvs_flash.h"
#include <algorithm>
#include <cstdint>
#include <string>
#include <string_view>
namespace cardboy::backend::esp {
namespace {
void ensureNvsInit() {
static bool nvsReady = false;
if (nvsReady)
return;
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
nvsReady = true;
}
} // namespace
class EspRuntime::BuzzerService final : public cardboy::sdk::IBuzzer {
public:
void tone(std::uint32_t freq, std::uint32_t duration_ms, std::uint32_t gap_ms = 0) override {
Buzzer::get().tone(freq, duration_ms, gap_ms);
}
void beepRotate() override { Buzzer::get().beepRotate(); }
void beepMove() override { Buzzer::get().beepMove(); }
void beepLock() override { Buzzer::get().beepLock(); }
void beepLines(int lines) override { Buzzer::get().beepLines(lines); }
void beepLevelUp(int level) override { Buzzer::get().beepLevelUp(level); }
void beepGameOver() override { Buzzer::get().beepGameOver(); }
void setMuted(bool muted) override { Buzzer::get().setMuted(muted); }
void toggleMuted() override { Buzzer::get().toggleMuted(); }
[[nodiscard]] bool isMuted() const override { return Buzzer::get().isMuted(); }
};
class EspRuntime::BatteryService final : public cardboy::sdk::IBatteryMonitor {
public:
[[nodiscard]] bool hasData() const override { return true; }
[[nodiscard]] float voltage() const override { return BatMon::get().get_voltage(); }
[[nodiscard]] float charge() const override { return BatMon::get().get_charge(); }
[[nodiscard]] float current() const override { return BatMon::get().get_current(); }
};
class EspRuntime::StorageService final : public cardboy::sdk::IStorage {
public:
[[nodiscard]] bool readUint32(std::string_view ns, std::string_view key, std::uint32_t& out) override {
ensureNvsInit();
nvs_handle_t handle;
std::string nsStr(ns);
std::string keyStr(key);
if (nvs_open(nsStr.c_str(), NVS_READONLY, &handle) != ESP_OK)
return false;
std::uint32_t value = 0;
esp_err_t err = nvs_get_u32(handle, keyStr.c_str(), &value);
nvs_close(handle);
if (err != ESP_OK)
return false;
out = value;
return true;
}
void writeUint32(std::string_view ns, std::string_view key, std::uint32_t value) override {
ensureNvsInit();
nvs_handle_t handle;
std::string nsStr(ns);
std::string keyStr(key);
if (nvs_open(nsStr.c_str(), NVS_READWRITE, &handle) != ESP_OK)
return;
nvs_set_u32(handle, keyStr.c_str(), value);
nvs_commit(handle);
nvs_close(handle);
}
};
class EspRuntime::RandomService final : public cardboy::sdk::IRandom {
public:
[[nodiscard]] std::uint32_t nextUint32() override { return esp_random(); }
};
class EspRuntime::HighResClockService final : public cardboy::sdk::IHighResClock {
public:
[[nodiscard]] std::uint64_t micros() override { return static_cast<std::uint64_t>(esp_timer_get_time()); }
};
class EspRuntime::PowerService final : public cardboy::sdk::IPowerManager {
public:
void setSlowMode(bool enable) override { PowerHelper::get().set_slow(enable); }
[[nodiscard]] bool isSlowMode() const override { return PowerHelper::get().is_slow(); }
};
class EspRuntime::FilesystemService final : public cardboy::sdk::IFilesystem {
public:
bool mount() override { return FsHelper::get().mount() == ESP_OK; }
[[nodiscard]] bool isMounted() const override { return FsHelper::get().isMounted(); }
[[nodiscard]] std::string basePath() const override {
const char* path = FsHelper::get().basePath();
return path ? std::string(path) : std::string{};
}
};
EspRuntime::EspRuntime() : framebuffer(), input(), clock() {
initializeHardware();
buzzerService = std::make_unique<BuzzerService>();
batteryService = std::make_unique<BatteryService>();
storageService = std::make_unique<StorageService>();
randomService = std::make_unique<RandomService>();
highResClockService = std::make_unique<HighResClockService>();
powerService = std::make_unique<PowerService>();
filesystemService = std::make_unique<FilesystemService>();
services.buzzer = buzzerService.get();
services.battery = batteryService.get();
services.storage = storageService.get();
services.random = randomService.get();
services.highResClock = highResClockService.get();
services.powerManager = powerService.get();
services.filesystem = filesystemService.get();
}
EspRuntime::~EspRuntime() = default;
cardboy::sdk::Services& EspRuntime::serviceRegistry() { return services; }
void EspRuntime::initializeHardware() {
static bool initialized = false;
if (initialized)
return;
initialized = true;
ensureNvsInit();
PowerHelper::get();
Shutdowner::get();
Buttons::get();
esp_err_t isrErr = gpio_install_isr_service(0);
if (isrErr != ESP_OK && isrErr != ESP_ERR_INVALID_STATE) {
ESP_ERROR_CHECK(isrErr);
}
Shutdowner::get().install_isr();
PowerHelper::get().install_isr();
Buttons::get().install_isr();
I2cGlobal::get();
BatMon::get();
SpiGlobal::get();
SMD::init();
Buzzer::get().init();
FsHelper::get().mount();
}
int EspFramebuffer::width_impl() const { return cardboy::sdk::kDisplayWidth; }
int EspFramebuffer::height_impl() const { return cardboy::sdk::kDisplayHeight; }
void EspFramebuffer::drawPixel_impl(int x, int y, bool on) {
if (x < 0 || y < 0 || x >= width_impl() || y >= height_impl())
return;
SMD::set_pixel(x, y, on);
}
void EspFramebuffer::clear_impl(bool on) {
for (int y = 0; y < height_impl(); ++y)
for (int x = 0; x < width_impl(); ++x)
SMD::set_pixel(x, y, on);
}
void EspFramebuffer::frameReady_impl() { SMD::frame_ready(); }
void EspFramebuffer::sendFrame_impl(bool clearAfterSend) { SMD::send_frame(clearAfterSend); }
bool EspFramebuffer::frameInFlight_impl() const { return SMD::frame_transfer_in_flight(); }
cardboy::sdk::InputState EspInput::readState_impl() {
cardboy::sdk::InputState state{};
const uint8_t pressed = Buttons::get().get_pressed();
if (pressed & BTN_UP)
state.up = true;
if (pressed & BTN_LEFT)
state.left = true;
if (pressed & BTN_RIGHT)
state.right = true;
if (pressed & BTN_DOWN)
state.down = true;
if (pressed & BTN_A)
state.a = true;
if (pressed & BTN_B)
state.b = true;
if (pressed & BTN_SELECT)
state.select = true;
if (pressed & BTN_START)
state.start = true;
return state;
}
std::uint32_t EspClock::millis_impl() {
TickType_t ticks = xTaskGetTickCount();
return static_cast<std::uint32_t>((static_cast<std::uint64_t>(ticks) * 1000ULL) / configTICK_RATE_HZ);
}
void EspClock::sleep_ms_impl(std::uint32_t ms) {
if (ms == 0)
return;
PowerHelper::get().delay(static_cast<int>(ms), static_cast<int>(ms));
}
} // namespace cardboy::backend::esp