mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-05 07:07:38 +03:00
basic bluetooth: on/off
This commit is contained in:
parent
6d68466891
commit
68bd425822
@ -30,6 +30,14 @@ build_flags =
|
||||
-std=c++2a
|
||||
# Enable UTF-8 long file names in SdFat
|
||||
-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_build.flash_mode = dio
|
||||
@ -48,6 +56,7 @@ lib_deps =
|
||||
bblanchon/ArduinoJson @ 7.4.2
|
||||
ricmoo/QRCode @ 0.0.1
|
||||
links2004/WebSockets @ 2.7.3
|
||||
h2zero/NimBLE-Arduino @ 2.1.0
|
||||
|
||||
[env:default]
|
||||
extends = base
|
||||
|
||||
186
src/BluetoothManager.cpp
Normal file
186
src/BluetoothManager.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
#include "BluetoothManager.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
// 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 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
|
||||
}
|
||||
|
||||
return baseUsage + stackUsage;
|
||||
#else
|
||||
return sizeof(*this); // Minimal usage when disabled
|
||||
#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());
|
||||
|
||||
return true;
|
||||
|
||||
} catch (...) {
|
||||
pServer = 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
|
||||
124
src/BluetoothManager.h
Normal file
124
src/BluetoothManager.h
Normal file
@ -0,0 +1,124 @@
|
||||
#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
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* 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()
|
||||
@ -58,6 +58,9 @@ class CrossPointSettings {
|
||||
// Hide battery percentage
|
||||
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2 };
|
||||
|
||||
// Bluetooth mode settings
|
||||
enum BLUETOOTH_MODE { OFF = 0, ON = 1 };
|
||||
|
||||
// Sleep screen settings
|
||||
uint8_t sleepScreen = DARK;
|
||||
// Sleep screen cover mode settings
|
||||
@ -94,6 +97,8 @@ class CrossPointSettings {
|
||||
uint8_t hideBatteryPercentage = HIDE_NEVER;
|
||||
// Long-press chapter skip on side buttons
|
||||
uint8_t longPressChapterSkip = 1;
|
||||
// Bluetooth enabled setting
|
||||
uint8_t bluetoothEnabled = OFF;
|
||||
|
||||
~CrossPointSettings() = default;
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "BluetoothManager.h"
|
||||
#include "CalibreSettingsActivity.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "KOReaderSettingsActivity.h"
|
||||
@ -14,7 +15,7 @@
|
||||
|
||||
// Define the static settings list
|
||||
namespace {
|
||||
constexpr int settingsCount = 22;
|
||||
constexpr int settingsCount = 23;
|
||||
const SettingInfo settingsList[settingsCount] = {
|
||||
// Should match with SLEEP_SCREEN_MODE
|
||||
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
||||
@ -43,6 +44,7 @@ const SettingInfo settingsList[settingsCount] = {
|
||||
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
||||
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
||||
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
||||
SettingInfo::Enum("Bluetooth", &CrossPointSettings::bluetoothEnabled, {"Off", "On"}),
|
||||
SettingInfo::Action("KOReader Sync"),
|
||||
SettingInfo::Action("Calibre Settings"),
|
||||
SettingInfo::Action("Check for updates")};
|
||||
@ -130,7 +132,28 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
SETTINGS.*(setting.valuePtr) = !currentValue;
|
||||
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
|
||||
const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
|
||||
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
|
||||
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 (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);
|
||||
|
||||
15
src/main.cpp
15
src/main.cpp
@ -10,6 +10,7 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "BluetoothManager.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "CrossPointState.h"
|
||||
#include "KOReaderCredentialStore.h"
|
||||
@ -200,6 +201,11 @@ void enterDeepSleep() {
|
||||
exitActivity();
|
||||
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
||||
|
||||
// Shutdown Bluetooth to save power and memory
|
||||
if (BLUETOOTH_MANAGER.isInitialized()) {
|
||||
BLUETOOTH_MANAGER.shutdown();
|
||||
}
|
||||
|
||||
einkDisplay.deepSleep();
|
||||
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1);
|
||||
Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
|
||||
@ -292,6 +298,15 @@ void setup() {
|
||||
SETTINGS.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();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user