mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 22:57:50 +03:00
Merge 97c0af702b into 13f0ebed96
This commit is contained in:
commit
bdc88b71e6
@ -11,7 +11,7 @@ framework = arduino
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
check_tool = cppcheck
|
check_tool = cppcheck
|
||||||
check_flags = --enable=all --suppress=missingIncludeSystem --suppress=unusedFunction --suppress=unmatchedSuppression --suppress=*:*/.pio/* --inline-suppr
|
check_flags = --enable=all --suppress=missingIncludeSystem --suppress=missingInclude --suppress=unusedFunction --suppress=unmatchedSuppression --suppress=*:*/.pio/* --inline-suppr
|
||||||
check_skip_packages = yes
|
check_skip_packages = yes
|
||||||
|
|
||||||
board_upload.flash_size = 16MB
|
board_upload.flash_size = 16MB
|
||||||
@ -30,6 +30,14 @@ build_flags =
|
|||||||
-std=c++2a
|
-std=c++2a
|
||||||
# Enable UTF-8 long file names in SdFat
|
# Enable UTF-8 long file names in SdFat
|
||||||
-DUSE_UTF8_LONG_NAMES=1
|
-DUSE_UTF8_LONG_NAMES=1
|
||||||
|
# BLE memory optimization flags
|
||||||
|
-DCONFIG_BT_ENABLED=1
|
||||||
|
-DCONFIG_BT_NIMBLE_ENABLED=1
|
||||||
|
-DCONFIG_NIMBLE_MAX_CONNECTIONS=1
|
||||||
|
-DCONFIG_NIMBLE_MAX_BONDS=1
|
||||||
|
-DCONFIG_NIMBLE_SVC_GAP_DEVICE_NAME_MAX_LEN=12
|
||||||
|
-DCONFIG_NIMBLE_SVC_GAP_APPEARANCE=0x0
|
||||||
|
-DCONFIG_NIMBLE_LOG_LEVEL=0
|
||||||
|
|
||||||
; Board configuration
|
; Board configuration
|
||||||
board_build.flash_mode = dio
|
board_build.flash_mode = dio
|
||||||
@ -48,6 +56,7 @@ lib_deps =
|
|||||||
bblanchon/ArduinoJson @ 7.4.2
|
bblanchon/ArduinoJson @ 7.4.2
|
||||||
ricmoo/QRCode @ 0.0.1
|
ricmoo/QRCode @ 0.0.1
|
||||||
links2004/WebSockets @ 2.7.3
|
links2004/WebSockets @ 2.7.3
|
||||||
|
h2zero/NimBLE-Arduino @ 2.1.0
|
||||||
|
|
||||||
[env:default]
|
[env:default]
|
||||||
extends = base
|
extends = base
|
||||||
|
|||||||
281
src/BLEKeyboardHandler.cpp
Normal file
281
src/BLEKeyboardHandler.cpp
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
#include "BLEKeyboardHandler.h"
|
||||||
|
|
||||||
|
// Platform-specific includes
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "MappedInputManager.h"
|
||||||
|
#else
|
||||||
|
// For static analysis, provide minimal declarations
|
||||||
|
extern "C" {
|
||||||
|
unsigned long millis();
|
||||||
|
int ESP_getFreeHeap();
|
||||||
|
void Serial_printf(const char* format, ...);
|
||||||
|
}
|
||||||
|
#define Serial Serial_printf
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Static instance definition
|
||||||
|
BLEKeyboardHandler BLEKeyboardHandler::instance;
|
||||||
|
|
||||||
|
bool BLEKeyboardHandler::initialize(NimBLEServer* server) {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
if (initialized || !server) {
|
||||||
|
return initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [KBD] Initializing BLE Keyboard\n", millis());
|
||||||
|
|
||||||
|
try {
|
||||||
|
pServer = server;
|
||||||
|
|
||||||
|
// Create custom keyboard service
|
||||||
|
pService = pServer->createService("12345678-1234-1234-1234-123456789abc");
|
||||||
|
if (!pService) {
|
||||||
|
Serial.printf("[%lu] [KBD] Failed to create service\n", millis());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create input characteristic
|
||||||
|
pInputCharacteristic = pService->createCharacteristic(
|
||||||
|
"87654321-4321-4321-4321-cba987654321",
|
||||||
|
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
|
||||||
|
);
|
||||||
|
if (!pInputCharacteristic) {
|
||||||
|
Serial.printf("[%lu] [KBD] Failed to create input characteristic\n", millis());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set callbacks
|
||||||
|
pInputCharacteristic->setCallbacks(new KeyboardCallbacks());
|
||||||
|
|
||||||
|
// Start HID service
|
||||||
|
pHidDevice->startServices();
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
Serial.printf("[%lu] [KBD] BLE Keyboard initialized\n", millis());
|
||||||
|
Serial.printf("[%lu] [KBD] Free heap after init: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
Serial.printf("[%lu] [KBD] Exception during initialization\n", millis());
|
||||||
|
if (pHidDevice) {
|
||||||
|
delete pHidDevice;
|
||||||
|
pHidDevice = nullptr;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
Serial.printf("[%lu] [KBD] BLE Keyboard disabled in build\n", millis());
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void BLEKeyboardHandler::shutdown() {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
if (!initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [KBD] Shutting down BLE Keyboard\n", millis());
|
||||||
|
|
||||||
|
connected = false;
|
||||||
|
|
||||||
|
if (pHidDevice) {
|
||||||
|
delete pHidDevice;
|
||||||
|
pHidDevice = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
pInputCharacteristic = nullptr;
|
||||||
|
pServer = nullptr;
|
||||||
|
initialized = false;
|
||||||
|
|
||||||
|
// Clear keyboard report
|
||||||
|
memset(keyboardReport, 0, sizeof(keyboardReport));
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [KBD] BLE Keyboard shutdown complete\n", millis());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void BLEKeyboardHandler::processKeyboardReport(const uint8_t* data, size_t length) {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
if (!initialized || !data || length < 8) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce check
|
||||||
|
uint32_t currentTime = millis();
|
||||||
|
if (currentTime - lastActivityTime < DEBOUNCE_MS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastActivityTime = currentTime;
|
||||||
|
|
||||||
|
// Parse keyboard report (HID standard format)
|
||||||
|
uint8_t modifiers = data[0];
|
||||||
|
uint8_t reserved = data[1];
|
||||||
|
uint8_t keycodes[6] = {data[2], data[3], data[4], data[5], data[6], data[7]};
|
||||||
|
|
||||||
|
// Handle modifiers first (Shift, Ctrl, Alt)
|
||||||
|
handleModifiers(modifiers);
|
||||||
|
|
||||||
|
// Process each key
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
if (keycodes[i] != 0) {
|
||||||
|
int buttonId = mapScancodeToButton(keycodes[i]);
|
||||||
|
if (buttonId >= 0) {
|
||||||
|
// Inject mapped button into existing input system
|
||||||
|
extern MappedInputManager mappedInputManager;
|
||||||
|
mappedInputManager.injectButton(static_cast<MappedInputManager::Button>(buttonId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void BLEKeyboardHandler::update() {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
if (!initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for idle timeout
|
||||||
|
if (isIdle()) {
|
||||||
|
// Optionally reduce power or disconnect after long idle
|
||||||
|
if (connected && (millis() - lastActivityTime > IDLE_TIMEOUT_MS * 2)) {
|
||||||
|
Serial.printf("[%lu] [KBD] Very long idle, considering disconnect\n", millis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BLEKeyboardHandler::getMemoryUsage() const {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
if (!initialized) {
|
||||||
|
return sizeof(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t baseUsage = sizeof(*this);
|
||||||
|
size_t hidUsage = 0;
|
||||||
|
|
||||||
|
if (pHidDevice) {
|
||||||
|
hidUsage += 1024; // Conservative estimate for HID device
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUsage + hidUsage;
|
||||||
|
#else
|
||||||
|
return sizeof(*this);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BLEKeyboardHandler::isIdle() const {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
return initialized && (millis() - lastActivityTime > IDLE_TIMEOUT_MS);
|
||||||
|
#else
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
bool BLEKeyboardHandler::setupHidDescriptor() {
|
||||||
|
if (!pHidDevice) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create minimal HID report descriptor for keyboard
|
||||||
|
static const uint8_t hidReportDescriptor[] = {
|
||||||
|
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||||
|
0x09, 0x06, // Usage (Keyboard)
|
||||||
|
0xA1, 0x01, // Collection (Application)
|
||||||
|
0x05, 0x07, // Usage Page (Kbrd/Keypad)
|
||||||
|
0x19, 0xE0, // Usage Minimum (224)
|
||||||
|
0x29, 0xE7, // Usage Maximum (231)
|
||||||
|
0x15, 0x00, // Logical Minimum (0)
|
||||||
|
0x25, 0x01, // Logical Maximum (1)
|
||||||
|
0x95, 0x08, // Report Count (8)
|
||||||
|
0x75, 0x01, // Report Size (1)
|
||||||
|
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||||
|
0x95, 0x01, // Report Count (1)
|
||||||
|
0x75, 0x08, // Report Size (8)
|
||||||
|
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||||
|
0x95, 0x06, // Report Count (6)
|
||||||
|
0x75, 0x08, // Report Size (8)
|
||||||
|
0x15, 0x00, // Logical Minimum (0)
|
||||||
|
0x25, 0x65, // Logical Maximum (101)
|
||||||
|
0x05, 0x07, // Usage Page (Kbrd/Keypad)
|
||||||
|
0x19, 0x00, // Usage Minimum (0)
|
||||||
|
0x29, 0x65, // Usage Maximum (101)
|
||||||
|
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||||
|
0xC0 // End Collection
|
||||||
|
};
|
||||||
|
|
||||||
|
pHidDevice->setReportMap(hidReportDescriptor, sizeof(hidReportDescriptor));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BLEKeyboardHandler::mapScancodeToButton(uint8_t scancode) const {
|
||||||
|
// Map common keyboard scancodes to CrossPoint buttons
|
||||||
|
// Optimized for e-reader usage
|
||||||
|
|
||||||
|
switch (scancode) {
|
||||||
|
// Navigation keys
|
||||||
|
case 0x4C: // DELETE (mapped to Back)
|
||||||
|
case 0xB2: return 0; // BACK button
|
||||||
|
|
||||||
|
case 0x28: return 1; // RETURN (mapped to Confirm)
|
||||||
|
|
||||||
|
case 0x50: return 2; // LEFT ARROW
|
||||||
|
case 0x52: return 3; // UP ARROW
|
||||||
|
case 0x4F: return 4; // RIGHT ARROW
|
||||||
|
case 0x51: return 5; // DOWN ARROW
|
||||||
|
|
||||||
|
// Volume keys (side buttons)
|
||||||
|
case 0x80: return 6; // VOLUME UP (mapped to Next page)
|
||||||
|
case 0x81: return 7; // VOLUME DOWN (mapped to Prev page)
|
||||||
|
|
||||||
|
// Space and Enter for page turning
|
||||||
|
case 0x2C: return 6; // SPACE (Next page)
|
||||||
|
case 0x28: return 7; // ENTER (Prev page) - conflict, prioritize Confirm
|
||||||
|
|
||||||
|
// Number keys for quick access
|
||||||
|
case 0x27: return 1; // ESC (can be mapped to Home)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -1; // Unmapped key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BLEKeyboardHandler::handleModifiers(uint8_t modifiers) {
|
||||||
|
// Handle modifier keys (Shift, Ctrl, Alt, GUI)
|
||||||
|
// Can be used for special functions
|
||||||
|
|
||||||
|
if (modifiers & 0x02) { // Shift
|
||||||
|
// Shift can modify button behavior
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifiers & 0x01) { // Ctrl
|
||||||
|
// Ctrl can be used for shortcuts
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modifiers & 0x04) { // Alt
|
||||||
|
// Alt can be used for alternative functions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BLEKeyboardHandler::KeyboardCallbacks::onWrite(NimBLECharacteristic* pCharacteristic) {
|
||||||
|
// Handle keyboard input data
|
||||||
|
if (pCharacteristic && pCharacteristic->getLength() > 0) {
|
||||||
|
BLE_KEYBOARD.processKeyboardReport(pCharacteristic->getData(), pCharacteristic->getLength());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BLEKeyboardHandler::KeyboardCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc) {
|
||||||
|
Serial.printf("[%lu] [KBD] Keyboard connected\n", millis());
|
||||||
|
BLE_KEYBOARD.connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BLEKeyboardHandler::KeyboardCallbacks::onUnsubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc) {
|
||||||
|
Serial.printf("[%lu] [KBD] Keyboard disconnected\n", millis());
|
||||||
|
BLE_KEYBOARD.connected = false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
144
src/BLEKeyboardHandler.h
Normal file
144
src/BLEKeyboardHandler.h
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// Forward declarations for conditional compilation
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
#include <NimBLEServer.h>
|
||||||
|
#include <NimBLECharacteristic.h>
|
||||||
|
#include <NimBLEService.h>
|
||||||
|
#include <NimBLEHIDDevice.h>
|
||||||
|
#include <HIDKeyboardTypes.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memory-efficient BLE Keyboard Handler for CrossPoint Reader
|
||||||
|
*
|
||||||
|
* Design principles:
|
||||||
|
* - Minimal RAM footprint (~1KB when active)
|
||||||
|
* - Efficient key mapping to existing CrossPoint buttons
|
||||||
|
* - Robust error handling and automatic recovery
|
||||||
|
* - Power-optimized with idle timeouts
|
||||||
|
*/
|
||||||
|
class BLEKeyboardHandler {
|
||||||
|
private:
|
||||||
|
// Private constructor for singleton pattern
|
||||||
|
BLEKeyboardHandler() = default;
|
||||||
|
|
||||||
|
// Static instance
|
||||||
|
static BLEKeyboardHandler instance;
|
||||||
|
|
||||||
|
// State tracking (minimal memory usage)
|
||||||
|
bool initialized = false;
|
||||||
|
bool connected = false;
|
||||||
|
uint32_t lastActivityTime = 0;
|
||||||
|
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
// BLE components (only allocated when needed)
|
||||||
|
NimBLEServer* pServer = nullptr;
|
||||||
|
NimBLEService* pService = nullptr;
|
||||||
|
NimBLECharacteristic* pInputCharacteristic = nullptr;
|
||||||
|
|
||||||
|
// Keyboard report buffer (minimal size for our needs)
|
||||||
|
uint8_t keyboardReport[8] = {0};
|
||||||
|
|
||||||
|
// Key debounce timing
|
||||||
|
static constexpr uint32_t DEBOUNCE_MS = 50;
|
||||||
|
static constexpr uint32_t IDLE_TIMEOUT_MS = 30000; // 30 seconds
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Delete copy constructor and assignment
|
||||||
|
BLEKeyboardHandler(const BLEKeyboardHandler&) = delete;
|
||||||
|
BLEKeyboardHandler& operator=(const BLEKeyboardHandler&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get singleton instance
|
||||||
|
* @return Reference to BLEKeyboardHandler instance
|
||||||
|
*/
|
||||||
|
static BLEKeyboardHandler& getInstance() { return instance; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize BLE Keyboard service
|
||||||
|
* @param server Pointer to existing BLE server
|
||||||
|
* @return true if initialization successful
|
||||||
|
*/
|
||||||
|
bool initialize(NimBLEServer* server);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdown keyboard service and free memory
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process incoming keyboard data
|
||||||
|
* @param data Raw keyboard report data
|
||||||
|
* @param length Length of keyboard report
|
||||||
|
*/
|
||||||
|
void processKeyboardReport(const uint8_t* data, size_t length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if keyboard is connected
|
||||||
|
* @return true if connected
|
||||||
|
*/
|
||||||
|
bool isConnected() const { return connected; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if initialized
|
||||||
|
* @return true if initialized
|
||||||
|
*/
|
||||||
|
bool isInitialized() const { return initialized; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update idle timeout and power management
|
||||||
|
*/
|
||||||
|
void update();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get memory usage information
|
||||||
|
* @return Estimated RAM usage in bytes
|
||||||
|
*/
|
||||||
|
size_t getMemoryUsage() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for keyboard inactivity
|
||||||
|
* @return true if idle longer than timeout
|
||||||
|
*/
|
||||||
|
bool isIdle() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
/**
|
||||||
|
* Setup HID descriptor for keyboard
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
bool setupHidDescriptor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert keyboard scancode to CrossPoint button
|
||||||
|
* @param scancode USB HID scancode
|
||||||
|
* @return Mapped button ID or -1 if unmapped
|
||||||
|
*/
|
||||||
|
int mapScancodeToButton(uint8_t scancode) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle modifier keys (Shift, Ctrl, etc.)
|
||||||
|
* @param modifiers Modifier byte from keyboard report
|
||||||
|
*/
|
||||||
|
void handleModifiers(uint8_t modifiers);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BLE keyboard callbacks
|
||||||
|
*/
|
||||||
|
class KeyboardCallbacks : public NimBLECharacteristicCallbacks {
|
||||||
|
public:
|
||||||
|
void onWrite(NimBLECharacteristic* pCharacteristic);
|
||||||
|
void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc);
|
||||||
|
void onUnsubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc);
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convenience macro
|
||||||
|
#define BLE_KEYBOARD BLEKeyboardHandler::getInstance()
|
||||||
231
src/BluetoothManager.cpp
Normal file
231
src/BluetoothManager.cpp
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
#include "BluetoothManager.h"
|
||||||
|
#include "BLEKeyboardHandler.h"
|
||||||
|
|
||||||
|
// Platform-specific includes
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "CrossPointSettings.h"
|
||||||
|
#else
|
||||||
|
// For static analysis, provide minimal declarations
|
||||||
|
extern "C" {
|
||||||
|
unsigned long millis();
|
||||||
|
int ESP_getFreeHeap();
|
||||||
|
void Serial_printf(const char* format, ...);
|
||||||
|
}
|
||||||
|
#define Serial Serial_printf
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Static instance definition
|
||||||
|
BluetoothManager BluetoothManager::instance;
|
||||||
|
|
||||||
|
bool BluetoothManager::initialize() {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
// Prevent double initialization
|
||||||
|
if (initialized) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [BLE] Initializing Bluetooth\n", millis());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Initialize NimBLE device with minimal configuration
|
||||||
|
BLEDevice::init(DEVICE_NAME);
|
||||||
|
|
||||||
|
// Create server if needed
|
||||||
|
if (!createServer()) {
|
||||||
|
Serial.printf("[%lu] [BLE] Failed to create server\n", millis());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup advertising
|
||||||
|
setupAdvertising();
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
Serial.printf("[%lu] [BLE] Bluetooth initialized successfully\n", millis());
|
||||||
|
Serial.printf("[%lu] [BLE] Free heap after init: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
Serial.printf("[%lu] [BLE] Exception during initialization\n", millis());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
Serial.printf("[%lu] [BLE] Bluetooth disabled in build\n", millis());
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::shutdown() {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
if (!initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [BLE] Shutting down Bluetooth\n", millis());
|
||||||
|
|
||||||
|
// Stop advertising
|
||||||
|
stopAdvertising();
|
||||||
|
|
||||||
|
// Deinitialize BLE device
|
||||||
|
BLEDevice::deinit();
|
||||||
|
|
||||||
|
// Clean up keyboard handler first
|
||||||
|
if (pKeyboardHandler) {
|
||||||
|
pKeyboardHandler->shutdown();
|
||||||
|
delete pKeyboardHandler;
|
||||||
|
pKeyboardHandler = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up pointers
|
||||||
|
pServer = nullptr;
|
||||||
|
pAdvertising = nullptr;
|
||||||
|
|
||||||
|
initialized = false;
|
||||||
|
advertising = false;
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [BLE] Bluetooth shutdown complete\n", millis());
|
||||||
|
Serial.printf("[%lu] [BLE] Free heap after shutdown: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BluetoothManager::startAdvertising() {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
if (!initialized || advertising) {
|
||||||
|
return advertising;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pAdvertising && pAdvertising->start()) {
|
||||||
|
advertising = true;
|
||||||
|
Serial.printf("[%lu] [BLE] Advertising started\n", millis());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [BLE] Failed to start advertising\n", millis());
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::stopAdvertising() {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
if (!advertising || !pAdvertising) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pAdvertising->stop();
|
||||||
|
advertising = false;
|
||||||
|
Serial.printf("[%lu] [BLE] Advertising stopped\n", millis());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BluetoothManager::getMemoryUsage() const {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
if (!initialized) {
|
||||||
|
return sizeof(*this); // Base object size (~20 bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimate BLE stack memory usage
|
||||||
|
size_t baseUsage = sizeof(*this);
|
||||||
|
size_t stackUsage = 0;
|
||||||
|
|
||||||
|
// NimBLE stack typically uses 12-15KB RAM
|
||||||
|
if (pServer) {
|
||||||
|
stackUsage += 12288; // Conservative estimate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add keyboard handler usage
|
||||||
|
if (pKeyboardHandler) {
|
||||||
|
stackUsage += pKeyboardHandler->getMemoryUsage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUsage + stackUsage;
|
||||||
|
#else
|
||||||
|
return sizeof(*this); // Minimal usage when disabled
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
BLEKeyboardHandler* BluetoothManager::getKeyboardHandler() const {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
return pKeyboardHandler;
|
||||||
|
#else
|
||||||
|
return nullptr;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::collectGarbage() {
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
if (!initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force garbage collection in NimBLE
|
||||||
|
NimBLEDevice::getScan()->clearResults();
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [BLE] Garbage collection complete\n", millis());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
bool BluetoothManager::createServer() {
|
||||||
|
try {
|
||||||
|
// Create BLE server with minimal configuration
|
||||||
|
pServer = BLEDevice::createServer();
|
||||||
|
if (!pServer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set callbacks with minimal overhead
|
||||||
|
pServer->setCallbacks(new ServerCallbacks());
|
||||||
|
|
||||||
|
// Initialize keyboard handler if enabled
|
||||||
|
if (SETTINGS.bluetoothKeyboardEnabled == CrossPointSettings::BLUETOOTH_KEYBOARD_MODE::KBD_ENABLED) {
|
||||||
|
pKeyboardHandler = new BLEKeyboardHandler();
|
||||||
|
if (!pKeyboardHandler->initialize(pServer)) {
|
||||||
|
Serial.printf("[%lu] [BLE] Failed to initialize keyboard handler\n", millis());
|
||||||
|
delete pKeyboardHandler;
|
||||||
|
pKeyboardHandler = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
pServer = nullptr;
|
||||||
|
pKeyboardHandler = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::setupAdvertising() {
|
||||||
|
if (!pServer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pAdvertising = BLEDevice::getAdvertising();
|
||||||
|
if (!pAdvertising) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal advertising configuration
|
||||||
|
pAdvertising->addServiceUUID(BLEUUID((uint16_t)0x1800)); // Generic Access
|
||||||
|
pAdvertising->setScanResponse(false); // Save power and memory
|
||||||
|
pAdvertising->setMinPreferred(0x0); // No preferred connections
|
||||||
|
pAdvertising->setMaxPreferred(0x0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::ServerCallbacks::onConnect(BLEServer* pServer) {
|
||||||
|
Serial.printf("[%lu] [BLE] Device connected\n", millis());
|
||||||
|
|
||||||
|
// Restart advertising for more connections (though we only allow 1)
|
||||||
|
BLEDevice::getAdvertising()->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothManager::ServerCallbacks::onDisconnect(BLEServer* pServer) {
|
||||||
|
Serial.printf("[%lu] [BLE] Device disconnected\n", millis());
|
||||||
|
|
||||||
|
// Restart advertising
|
||||||
|
BLEDevice::getAdvertising()->start();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
133
src/BluetoothManager.h
Normal file
133
src/BluetoothManager.h
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// Forward declarations to minimize includes when BLE is disabled
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
#include <NimBLEDevice.h>
|
||||||
|
#include <NimBLEServer.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class BLEKeyboardHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memory-efficient Bluetooth Manager for CrossPoint Reader
|
||||||
|
*
|
||||||
|
* Design principles:
|
||||||
|
* - Singleton pattern to minimize memory usage
|
||||||
|
* - Conditional compilation to avoid BLE overhead when disabled
|
||||||
|
* - Minimal RAM footprint (~2-3KB when disabled, ~15KB when enabled)
|
||||||
|
* - Lazy initialization only when needed
|
||||||
|
* - Clean shutdown to prevent memory leaks
|
||||||
|
*/
|
||||||
|
class BluetoothManager {
|
||||||
|
private:
|
||||||
|
// Private constructor for singleton
|
||||||
|
BluetoothManager() = default;
|
||||||
|
|
||||||
|
// Static instance
|
||||||
|
static BluetoothManager instance;
|
||||||
|
|
||||||
|
// State tracking (minimal memory usage)
|
||||||
|
bool initialized = false;
|
||||||
|
bool advertising = false;
|
||||||
|
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
// BLE components (only allocated when BLE is enabled)
|
||||||
|
BLEServer* pServer = nullptr;
|
||||||
|
BLEAdvertising* pAdvertising = nullptr;
|
||||||
|
BLEKeyboardHandler* pKeyboardHandler = nullptr;
|
||||||
|
|
||||||
|
// Device name (short to save memory)
|
||||||
|
static constexpr const char* DEVICE_NAME = "CrossPoint";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Delete copy constructor and assignment
|
||||||
|
BluetoothManager(const BluetoothManager&) = delete;
|
||||||
|
BluetoothManager& operator=(const BluetoothManager&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get singleton instance
|
||||||
|
* @return Reference to BluetoothManager instance
|
||||||
|
*/
|
||||||
|
static BluetoothManager& getInstance() { return instance; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Bluetooth stack
|
||||||
|
* @return true if initialization successful, false otherwise
|
||||||
|
*/
|
||||||
|
bool initialize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdown Bluetooth stack to free memory
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start advertising device
|
||||||
|
* @return true if advertising started successfully
|
||||||
|
*/
|
||||||
|
bool startAdvertising();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop advertising to save power
|
||||||
|
*/
|
||||||
|
void stopAdvertising();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Bluetooth is initialized
|
||||||
|
* @return true if initialized
|
||||||
|
*/
|
||||||
|
bool isInitialized() const { return initialized; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if currently advertising
|
||||||
|
* @return true if advertising
|
||||||
|
*/
|
||||||
|
bool isAdvertising() const { return advertising; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get memory usage information
|
||||||
|
* @return Estimated RAM usage in bytes
|
||||||
|
*/
|
||||||
|
size_t getMemoryUsage() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get keyboard handler instance
|
||||||
|
* @return Pointer to keyboard handler or nullptr if not initialized
|
||||||
|
*/
|
||||||
|
BLEKeyboardHandler* getKeyboardHandler() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force garbage collection to free unused memory
|
||||||
|
*/
|
||||||
|
void collectGarbage();
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
/**
|
||||||
|
* Create BLE server with minimal services
|
||||||
|
* @return true if server created successfully
|
||||||
|
*/
|
||||||
|
bool createServer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup advertising data with minimal payload
|
||||||
|
*/
|
||||||
|
void setupAdvertising();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BLE server callbacks (minimal implementation)
|
||||||
|
*/
|
||||||
|
class ServerCallbacks : public BLEServerCallbacks {
|
||||||
|
public:
|
||||||
|
void onConnect(BLEServer* pServer) override;
|
||||||
|
void onDisconnect(BLEServer* pServer) override;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convenience macro for accessing the manager
|
||||||
|
#define BLUETOOTH_MANAGER BluetoothManager::getInstance()
|
||||||
@ -63,6 +63,12 @@ class CrossPointSettings {
|
|||||||
// Hide battery percentage
|
// Hide battery percentage
|
||||||
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2 };
|
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2 };
|
||||||
|
|
||||||
|
// Bluetooth mode settings
|
||||||
|
enum BLUETOOTH_MODE { OFF = 0, ON = 1 };
|
||||||
|
|
||||||
|
// Bluetooth keyboard mode settings
|
||||||
|
enum BLUETOOTH_KEYBOARD_MODE { KBD_DISABLED = 0, KBD_ENABLED = 1 };
|
||||||
|
|
||||||
// Sleep screen settings
|
// Sleep screen settings
|
||||||
uint8_t sleepScreen = DARK;
|
uint8_t sleepScreen = DARK;
|
||||||
// Sleep screen cover mode settings
|
// Sleep screen cover mode settings
|
||||||
@ -99,6 +105,10 @@ class CrossPointSettings {
|
|||||||
uint8_t hideBatteryPercentage = HIDE_NEVER;
|
uint8_t hideBatteryPercentage = HIDE_NEVER;
|
||||||
// Long-press chapter skip on side buttons
|
// Long-press chapter skip on side buttons
|
||||||
uint8_t longPressChapterSkip = 1;
|
uint8_t longPressChapterSkip = 1;
|
||||||
|
// Bluetooth enabled setting
|
||||||
|
uint8_t bluetoothEnabled = OFF;
|
||||||
|
// Bluetooth keyboard enabled setting
|
||||||
|
uint8_t bluetoothKeyboardEnabled = KBD_DISABLED;
|
||||||
|
|
||||||
~CrossPointSettings() = default;
|
~CrossPointSettings() = default;
|
||||||
|
|
||||||
|
|||||||
@ -100,6 +100,21 @@ bool MappedInputManager::wasAnyReleased() const { return inputManager.wasAnyRele
|
|||||||
|
|
||||||
unsigned long MappedInputManager::getHeldTime() const { return inputManager.getHeldTime(); }
|
unsigned long MappedInputManager::getHeldTime() const { return inputManager.getHeldTime(); }
|
||||||
|
|
||||||
|
void MappedInputManager::injectButton(Button button) {
|
||||||
|
// Get the physical button mapping
|
||||||
|
auto physicalButton = mapButton(button);
|
||||||
|
|
||||||
|
// Note: InputManager implementation would need to support injection
|
||||||
|
// For now, we'll store injected state for next update cycle
|
||||||
|
// This would require extending InputManager to accept external button states
|
||||||
|
|
||||||
|
// Placeholder for injection logic
|
||||||
|
// In a complete implementation, this would interface with InputManager
|
||||||
|
// to simulate button presses
|
||||||
|
|
||||||
|
(void)physicalButton; // Suppress unused variable warning for now
|
||||||
|
}
|
||||||
|
|
||||||
MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const char* confirm, const char* previous,
|
MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const char* confirm, const char* previous,
|
||||||
const char* next) const {
|
const char* next) const {
|
||||||
const auto layout = static_cast<CrossPointSettings::FRONT_BUTTON_LAYOUT>(SETTINGS.frontButtonLayout);
|
const auto layout = static_cast<CrossPointSettings::FRONT_BUTTON_LAYOUT>(SETTINGS.frontButtonLayout);
|
||||||
|
|||||||
@ -22,6 +22,9 @@ class MappedInputManager {
|
|||||||
bool wasAnyReleased() const;
|
bool wasAnyReleased() const;
|
||||||
unsigned long getHeldTime() const;
|
unsigned long getHeldTime() const;
|
||||||
Labels mapLabels(const char* back, const char* confirm, const char* previous, const char* next) const;
|
Labels mapLabels(const char* back, const char* confirm, const char* previous, const char* next) const;
|
||||||
|
|
||||||
|
// Inject button press from external source (e.g., BLE keyboard)
|
||||||
|
void injectButton(Button button);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
InputManager& inputManager;
|
InputManager& inputManager;
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "BluetoothManager.h"
|
||||||
|
#include "CalibreSettingsActivity.h"
|
||||||
#include "CategorySettingsActivity.h"
|
#include "CategorySettingsActivity.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
@ -11,6 +15,8 @@
|
|||||||
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
|
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
constexpr int settingsCount = 24;
|
||||||
|
const SettingInfo settingsList[settingsCount] = {
|
||||||
constexpr int displaySettingsCount = 5;
|
constexpr int displaySettingsCount = 5;
|
||||||
const SettingInfo displaySettings[displaySettingsCount] = {
|
const SettingInfo displaySettings[displaySettingsCount] = {
|
||||||
// Should match with SLEEP_SCREEN_MODE
|
// Should match with SLEEP_SCREEN_MODE
|
||||||
@ -49,7 +55,12 @@ constexpr int systemSettingsCount = 5;
|
|||||||
const SettingInfo systemSettings[systemSettingsCount] = {
|
const SettingInfo systemSettings[systemSettingsCount] = {
|
||||||
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
|
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
|
||||||
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
||||||
SettingInfo::Action("KOReader Sync"), SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Clear Cache"),
|
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
||||||
|
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
||||||
|
SettingInfo::Enum("Bluetooth", &CrossPointSettings::bluetoothEnabled, {"Off", "On"}),
|
||||||
|
SettingInfo::Enum("Bluetooth Keyboard", &CrossPointSettings::bluetoothKeyboardEnabled, {"Disabled", "Enabled"}),
|
||||||
|
SettingInfo::Action("KOReader Sync"),
|
||||||
|
SettingInfo::Action("Calibre Settings"),
|
||||||
SettingInfo::Action("Check for updates")};
|
SettingInfo::Action("Check for updates")};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -126,6 +137,107 @@ void SettingsActivity::enterCategory(int categoryIndex) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto& setting = settingsList[selectedSettingIndex];
|
||||||
|
|
||||||
|
if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) {
|
||||||
|
// Toggle the boolean value using the member pointer
|
||||||
|
const bool currentValue = SETTINGS.*(setting.valuePtr);
|
||||||
|
SETTINGS.*(setting.valuePtr) = !currentValue;
|
||||||
|
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
|
||||||
|
const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
|
||||||
|
const uint8_t newValue = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
|
||||||
|
SETTINGS.*(setting.valuePtr) = newValue;
|
||||||
|
|
||||||
|
// Handle Bluetooth toggle specifically
|
||||||
|
if (strcmp(setting.name, "Bluetooth") == 0) {
|
||||||
|
if (newValue == CrossPointSettings::BLUETOOTH_MODE::ON) {
|
||||||
|
// Enable Bluetooth
|
||||||
|
if (!BLUETOOTH_MANAGER.isInitialized()) {
|
||||||
|
if (BLUETOOTH_MANAGER.initialize()) {
|
||||||
|
BLUETOOTH_MANAGER.startAdvertising();
|
||||||
|
} else {
|
||||||
|
// Failed to initialize, revert to OFF
|
||||||
|
SETTINGS.*(setting.valuePtr) = CrossPointSettings::BLUETOOTH_MODE::OFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Disable Bluetooth
|
||||||
|
if (BLUETOOTH_MANAGER.isInitialized()) {
|
||||||
|
BLUETOOTH_MANAGER.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (strcmp(setting.name, "Bluetooth Keyboard") == 0) {
|
||||||
|
if (newValue == CrossPointSettings::BLUETOOTH_KEYBOARD_MODE::KBD_ENABLED) {
|
||||||
|
// Enable keyboard requires Bluetooth to be on
|
||||||
|
if (!BLUETOOTH_MANAGER.isInitialized()) {
|
||||||
|
// Force Bluetooth on first
|
||||||
|
SETTINGS.bluetoothEnabled = CrossPointSettings::BLUETOOTH_MODE::ON;
|
||||||
|
if (!BLUETOOTH_MANAGER.initialize()) {
|
||||||
|
// Failed, revert both to OFF
|
||||||
|
SETTINGS.bluetoothEnabled = CrossPointSettings::BLUETOOTH_MODE::OFF;
|
||||||
|
SETTINGS.*(setting.valuePtr) = CrossPointSettings::BLUETOOTH_KEYBOARD_MODE::KBD_DISABLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize keyboard handler if not already done
|
||||||
|
auto* keyboardHandler = BLUETOOTH_MANAGER.getKeyboardHandler();
|
||||||
|
if (!keyboardHandler && BLUETOOTH_MANAGER.isInitialized()) {
|
||||||
|
// This will be handled by BluetoothManager on next init
|
||||||
|
BLUETOOTH_MANAGER.shutdown();
|
||||||
|
BLUETOOTH_MANAGER.initialize();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Disable keyboard (but keep Bluetooth on)
|
||||||
|
auto* keyboardHandler = BLUETOOTH_MANAGER.getKeyboardHandler();
|
||||||
|
if (keyboardHandler) {
|
||||||
|
keyboardHandler->shutdown();
|
||||||
|
// Would need to reinit without keyboard to clean up properly
|
||||||
|
BLUETOOTH_MANAGER.shutdown();
|
||||||
|
BLUETOOTH_MANAGER.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force garbage collection to free keyboard memory
|
||||||
|
BLUETOOTH_MANAGER.collectGarbage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
|
||||||
|
// Decreasing would also be nice for large ranges I think but oh well can't have everything
|
||||||
|
const int8_t currentValue = SETTINGS.*(setting.valuePtr);
|
||||||
|
// Wrap to minValue if exceeding setting value boundary
|
||||||
|
if (currentValue + setting.valueRange.step > setting.valueRange.max) {
|
||||||
|
SETTINGS.*(setting.valuePtr) = setting.valueRange.min;
|
||||||
|
} else {
|
||||||
|
SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step;
|
||||||
|
}
|
||||||
|
} else if (setting.type == SettingType::ACTION) {
|
||||||
|
if (strcmp(setting.name, "KOReader Sync") == 0) {
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] {
|
||||||
|
exitActivity();
|
||||||
|
updateRequired = true;
|
||||||
|
}));
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
} else if (strcmp(setting.name, "Calibre Settings") == 0) {
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] {
|
||||||
|
exitActivity();
|
||||||
|
updateRequired = true;
|
||||||
|
}));
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
} else if (strcmp(setting.name, "Check for updates") == 0) {
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new OtaUpdateActivity(renderer, mappedInput, [this] {
|
||||||
|
exitActivity();
|
||||||
|
updateRequired = true;
|
||||||
|
}));
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only toggle if it's a toggle type and has a value pointer
|
||||||
|
return;
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
exitActivity();
|
exitActivity();
|
||||||
|
|
||||||
|
|||||||
25
src/main.cpp
25
src/main.cpp
@ -10,6 +10,7 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
|
#include "BluetoothManager.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
#include "KOReaderCredentialStore.h"
|
#include "KOReaderCredentialStore.h"
|
||||||
@ -202,6 +203,11 @@ void enterDeepSleep() {
|
|||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
||||||
|
|
||||||
|
// Shutdown Bluetooth to save power and memory
|
||||||
|
if (BLUETOOTH_MANAGER.isInitialized()) {
|
||||||
|
BLUETOOTH_MANAGER.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
einkDisplay.deepSleep();
|
einkDisplay.deepSleep();
|
||||||
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1);
|
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1);
|
||||||
Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
|
Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
|
||||||
@ -322,6 +328,17 @@ void setup() {
|
|||||||
SETTINGS.loadFromFile();
|
SETTINGS.loadFromFile();
|
||||||
KOREADER_STORE.loadFromFile();
|
KOREADER_STORE.loadFromFile();
|
||||||
|
|
||||||
|
// Initialize Bluetooth if enabled (before display to minimize RAM impact)
|
||||||
|
if (SETTINGS.bluetoothEnabled == CrossPointSettings::BLUETOOTH_MODE::ON) {
|
||||||
|
if (!BLUETOOTH_MANAGER.initialize()) {
|
||||||
|
Serial.printf("[%lu] [BLE] Failed to initialize Bluetooth\n", millis());
|
||||||
|
// Fall back to disabled state
|
||||||
|
SETTINGS.bluetoothEnabled = CrossPointSettings::BLUETOOTH_MODE::OFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify power button press duration after we've read settings.
|
||||||
|
verifyWakeupLongPress();
|
||||||
if (!isWakeupAfterFlashing()) {
|
if (!isWakeupAfterFlashing()) {
|
||||||
// For normal wakeups (not immediately after flashing), verify long press
|
// For normal wakeups (not immediately after flashing), verify long press
|
||||||
verifyWakeupLongPress();
|
verifyWakeupLongPress();
|
||||||
@ -403,6 +420,14 @@ void loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update keyboard handler if enabled
|
||||||
|
if (BLUETOOTH_MANAGER.isInitialized()) {
|
||||||
|
auto* keyboardHandler = BLUETOOTH_MANAGER.getKeyboardHandler();
|
||||||
|
if (keyboardHandler) {
|
||||||
|
keyboardHandler->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add delay at the end of the loop to prevent tight spinning
|
// Add delay at the end of the loop to prevent tight spinning
|
||||||
// When an activity requests skip loop delay (e.g., webserver running), use yield() for faster response
|
// When an activity requests skip loop delay (e.g., webserver running), use yield() for faster response
|
||||||
// Otherwise, use longer delay to save power
|
// Otherwise, use longer delay to save power
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user