From 1f2380be56d9d25499a71907ef86d8ab321503d1 Mon Sep 17 00:00:00 2001 From: altsysrq Date: Tue, 30 Dec 2025 21:25:33 -0600 Subject: [PATCH] Refactor image rendering and add Bluetooth setting - Added logic to render images only in BW mode in Page.cpp. - Implemented getRenderMode() in GfxRenderer.h. - Increased SETTINGS_COUNT and added bluetoothEnabled field in CrossPointSettings. - Updated saveToFile and loadFromFile methods to handle the new Bluetooth setting. - Added Bluetooth toggle in SettingsActivity. --- lib/Epub/Epub/Page.cpp | 7 +- lib/GfxRenderer/GfxRenderer.h | 1 + src/CrossPointSettings.cpp | 5 +- src/CrossPointSettings.h | 2 + .../network/CrossPointWebServerActivity.cpp | 11 + src/activities/settings/SettingsActivity.cpp | 3 +- src/bluetooth/BleFileTransfer.cpp | 188 ++++++++++++++++++ src/bluetooth/BleFileTransfer.h | 77 +++++++ 8 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 src/bluetooth/BleFileTransfer.cpp create mode 100644 src/bluetooth/BleFileTransfer.h diff --git a/lib/Epub/Epub/Page.cpp b/lib/Epub/Epub/Page.cpp index d238e9c7..d30b13be 100644 --- a/lib/Epub/Epub/Page.cpp +++ b/lib/Epub/Epub/Page.cpp @@ -29,8 +29,13 @@ std::unique_ptr PageLine::deserialize(FsFile& file) { } void PageImage::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) { + // Only render images in BW mode, skip grayscale passes to keep images sharp + if (renderer.getRenderMode() != GfxRenderer::BW) { + return; + } + FsFile imageFile; - if (!SdMan.openFileForRead("PGI", cachePath, imageFile)) { + if (!SdMan.openFileForRead("PGI", cachePath.c_str(), imageFile)) { Serial.printf("[%lu] [PGI] Failed to open image: %s\n", millis(), cachePath.c_str()); return; } diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index a636e367..62a03db0 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -83,6 +83,7 @@ class GfxRenderer { // Grayscale functions void setRenderMode(const RenderMode mode) { this->renderMode = mode; } + RenderMode getRenderMode() const { return renderMode; } void copyGrayscaleLsbBuffers() const; void copyGrayscaleMsbBuffers() const; void displayGrayBuffer() const; diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 74a95959..3951b486 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -12,7 +12,7 @@ CrossPointSettings CrossPointSettings::instance; namespace { constexpr uint8_t SETTINGS_FILE_VERSION = 1; // Increment this when adding new persisted settings fields -constexpr uint8_t SETTINGS_COUNT = 10; +constexpr uint8_t SETTINGS_COUNT = 11; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; } // namespace @@ -37,6 +37,7 @@ bool CrossPointSettings::saveToFile() const { serialization::writePod(outputFile, fontFamily); serialization::writePod(outputFile, fontSize); serialization::writePod(outputFile, lineSpacing); + serialization::writePod(outputFile, bluetoothEnabled); outputFile.close(); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); @@ -83,6 +84,8 @@ bool CrossPointSettings::loadFromFile() { if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, lineSpacing); if (++settingsRead >= fileSettingsCount) break; + serialization::readPod(inputFile, bluetoothEnabled); + if (++settingsRead >= fileSettingsCount) break; } while (false); inputFile.close(); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 108aecdf..2070738f 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -62,6 +62,8 @@ class CrossPointSettings { uint8_t fontFamily = BOOKERLY; uint8_t fontSize = MEDIUM; uint8_t lineSpacing = NORMAL; + // Bluetooth settings + uint8_t bluetoothEnabled = 0; ~CrossPointSettings() = default; diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp index 875d6e48..46c2e560 100644 --- a/src/activities/network/CrossPointWebServerActivity.cpp +++ b/src/activities/network/CrossPointWebServerActivity.cpp @@ -8,9 +8,11 @@ #include +#include "CrossPointSettings.h" #include "MappedInputManager.h" #include "NetworkModeSelectionActivity.h" #include "WifiSelectionActivity.h" +#include "activities/util/FullScreenMessageActivity.h" #include "fontIds.h" namespace { @@ -128,6 +130,15 @@ void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode) Serial.printf("[%lu] [WEBACT] Network mode selected: %s\n", millis(), mode == NetworkMode::JOIN_NETWORK ? "Join Network" : "Create Hotspot"); + // Check for WiFi/BLE mutual exclusion + if (SETTINGS.bluetoothEnabled) { + Serial.printf("[%lu] [WEBACT] ERROR: Cannot start WiFi while Bluetooth is enabled\n", millis()); + exitActivity(); + enterNewActivity(new FullScreenMessageActivity( + renderer, mappedInput, "Disable Bluetooth first\n\nGo to Settings > Bluetooth")); + return; + } + networkMode = mode; isApMode = (mode == NetworkMode::CREATE_HOTSPOT); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 7218eaec..0c4c9017 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -9,7 +9,7 @@ // Define the static settings list namespace { -constexpr int settingsCount = 11; +constexpr int settingsCount = 12; const SettingInfo settingsList[settingsCount] = { // Should match with SLEEP_SCREEN_MODE {"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}}, @@ -34,6 +34,7 @@ const SettingInfo settingsList[settingsCount] = { {"Bookerly", "Noto Sans", "Open Dyslexic"}}, {"Reader Font Size", SettingType::ENUM, &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}}, {"Reader Line Spacing", SettingType::ENUM, &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}}, + {"Bluetooth", SettingType::TOGGLE, &CrossPointSettings::bluetoothEnabled, {}}, {"Check for updates", SettingType::ACTION, nullptr, {}}, }; } // namespace diff --git a/src/bluetooth/BleFileTransfer.cpp b/src/bluetooth/BleFileTransfer.cpp new file mode 100644 index 00000000..c9f6cb41 --- /dev/null +++ b/src/bluetooth/BleFileTransfer.cpp @@ -0,0 +1,188 @@ +#include "BleFileTransfer.h" + +#include +#include + +namespace { +// BLE Service UUIDs (custom UUIDs for CrossPoint file transfer) +constexpr const char* SERVICE_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b"; +constexpr const char* FILE_LIST_UUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8"; +constexpr const char* FILE_DATA_UUID = "1c95d5e3-d8f7-413a-bf3d-7a2e5d7be87e"; +constexpr const char* CONTROL_UUID = "d7e72d4c-3f8e-4b4a-9c5d-8e3f7a2b1c9d"; + +constexpr int BLE_MTU = 512; // BLE Maximum Transmission Unit +} // namespace + +BleFileTransfer::BleFileTransfer() + : running(false), pServer(nullptr), pFileService(nullptr), pFileListChar(nullptr), pFileDataChar(nullptr), + pControlChar(nullptr) {} + +BleFileTransfer::~BleFileTransfer() { + stop(); +} + +bool BleFileTransfer::begin(const std::string& deviceName) { + if (running) { + Serial.printf("[%lu] [BLE] Already running\n", millis()); + return true; + } + + Serial.printf("[%lu] [BLE] Starting BLE service...\n", millis()); + Serial.printf("[%lu] [BLE] [MEM] Free heap before init: %d bytes\n", millis(), ESP.getFreeHeap()); + + // Initialize BLE + BLEDevice::init(deviceName); + + // Set MTU size for larger transfers + BLEDevice::setMTU(BLE_MTU); + + // Create BLE Server + pServer = BLEDevice::createServer(); + if (!pServer) { + Serial.printf("[%lu] [BLE] ERROR: Failed to create server\n", millis()); + return false; + } + + serverCallbacks.reset(new ServerCallbacks(this)); + pServer->setCallbacks(serverCallbacks.get()); + + // Create File Transfer Service + pFileService = pServer->createService(SERVICE_UUID); + if (!pFileService) { + Serial.printf("[%lu] [BLE] ERROR: Failed to create service\n", millis()); + return false; + } + + // Create File List Characteristic (READ) + pFileListChar = pFileService->createCharacteristic(FILE_LIST_UUID, BLECharacteristic::PROPERTY_READ); + fileListCallbacks.reset(new FileListCallbacks(this)); + pFileListChar->setCallbacks(fileListCallbacks.get()); + + // Create File Data Characteristic (READ | WRITE | NOTIFY) + pFileDataChar = + pFileService->createCharacteristic(FILE_DATA_UUID, BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_NOTIFY); + pFileDataChar->addDescriptor(new BLE2902()); + + // Create Control Characteristic (WRITE) + pControlChar = pFileService->createCharacteristic(CONTROL_UUID, BLECharacteristic::PROPERTY_WRITE); + controlCallbacks.reset(new ControlCallbacks(this)); + pControlChar->setCallbacks(controlCallbacks.get()); + + // Start the service + pFileService->start(); + + // Start advertising + BLEAdvertising* pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(true); + pAdvertising->setMinPreferred(0x06); // helps with iPhone connections + pAdvertising->setMinPreferred(0x12); + BLEDevice::startAdvertising(); + + running = true; + Serial.printf("[%lu] [BLE] Service started successfully\n", millis()); + Serial.printf("[%lu] [BLE] Device name: %s\n", millis(), deviceName.c_str()); + Serial.printf("[%lu] [BLE] [MEM] Free heap after init: %d bytes\n", millis(), ESP.getFreeHeap()); + + return true; +} + +void BleFileTransfer::stop() { + if (!running) { + return; + } + + Serial.printf("[%lu] [BLE] Stopping BLE service...\n", millis()); + + running = false; + + // Stop advertising + BLEDevice::getAdvertising()->stop(); + + // Clean up characteristics + pFileListChar = nullptr; + pFileDataChar = nullptr; + pControlChar = nullptr; + + // Clean up service + pFileService = nullptr; + + // Clean up server + pServer = nullptr; + + // Clean up callbacks + serverCallbacks.reset(); + controlCallbacks.reset(); + fileListCallbacks.reset(); + + // Deinitialize BLE + BLEDevice::deinit(true); + + Serial.printf("[%lu] [BLE] Service stopped\n", millis()); + Serial.printf("[%lu] [BLE] [MEM] Free heap after cleanup: %d bytes\n", millis(), ESP.getFreeHeap()); +} + +uint32_t BleFileTransfer::getConnectedCount() const { + if (pServer) { + return pServer->getConnectedCount(); + } + return 0; +} + +std::string BleFileTransfer::getFileList() { + // Return a simple JSON-like list of files in the root directory + // Format: "file1.epub,file2.epub,file3.epub" + // For a full implementation, this would traverse SD card directories + + // Placeholder implementation - would need to integrate with SDCardManager + return "example1.epub,example2.epub,example3.epub"; +} + +void BleFileTransfer::handleControlCommand(const std::string& command) { + Serial.printf("[%lu] [BLE] Control command: %s\n", millis(), command.c_str()); + + // Parse and handle commands + // Commands could be: "LIST", "GET:filename", "PUT:filename", "DELETE:filename", etc. + // For a full implementation, this would handle file operations via SDCardManager + + if (command == "LIST") { + // Refresh file list + Serial.printf("[%lu] [BLE] Refreshing file list\n", millis()); + } else if (command.rfind("GET:", 0) == 0) { + std::string filename = command.substr(4); + Serial.printf("[%lu] [BLE] Request to download: %s\n", millis(), filename.c_str()); + // Would implement file read and send via pFileDataChar notifications + } else if (command.rfind("PUT:", 0) == 0) { + std::string filename = command.substr(4); + Serial.printf("[%lu] [BLE] Request to upload: %s\n", millis(), filename.c_str()); + // Would implement file write from pFileDataChar writes + } +} + +// Server callbacks +void BleFileTransfer::ServerCallbacks::onConnect(BLEServer* pServer) { + Serial.printf("[%lu] [BLE] Client connected (total: %u)\n", millis(), pServer->getConnectedCount()); +} + +void BleFileTransfer::ServerCallbacks::onDisconnect(BLEServer* pServer) { + Serial.printf("[%lu] [BLE] Client disconnected (total: %u)\n", millis(), pServer->getConnectedCount()); + // Restart advertising to allow new connections + BLEDevice::startAdvertising(); +} + +// Control callbacks +void BleFileTransfer::ControlCallbacks::onWrite(BLECharacteristic* pCharacteristic) { + std::string value = pCharacteristic->getValue(); + if (value.length() > 0) { + parent->handleControlCommand(value); + } +} + +// File list callbacks +void BleFileTransfer::FileListCallbacks::onRead(BLECharacteristic* pCharacteristic) { + std::string fileList = parent->getFileList(); + pCharacteristic->setValue(fileList); + Serial.printf("[%lu] [BLE] File list requested (%zu bytes)\n", millis(), fileList.length()); +} diff --git a/src/bluetooth/BleFileTransfer.h b/src/bluetooth/BleFileTransfer.h new file mode 100644 index 00000000..4808e685 --- /dev/null +++ b/src/bluetooth/BleFileTransfer.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +// BLE File Transfer Service +// Provides file upload/download over Bluetooth Low Energy +// Designed for memory-constrained ESP32-C3 environment +class BleFileTransfer { + public: + BleFileTransfer(); + ~BleFileTransfer(); + + // Start the BLE service + bool begin(const std::string& deviceName = "CrossPoint-Reader"); + + // Stop the BLE service and free resources + void stop(); + + // Check if service is running + bool isRunning() const { return running; } + + // Get number of connected clients + uint32_t getConnectedCount() const; + + private: + bool running; + BLEServer* pServer; + BLEService* pFileService; + BLECharacteristic* pFileListChar; + BLECharacteristic* pFileDataChar; + BLECharacteristic* pControlChar; + + // Server callbacks + class ServerCallbacks : public BLEServerCallbacks { + public: + ServerCallbacks(BleFileTransfer* parent) : parent(parent) {} + void onConnect(BLEServer* pServer) override; + void onDisconnect(BLEServer* pServer) override; + + private: + BleFileTransfer* parent; + }; + + // Control characteristic callbacks + class ControlCallbacks : public BLECharacteristicCallbacks { + public: + ControlCallbacks(BleFileTransfer* parent) : parent(parent) {} + void onWrite(BLECharacteristic* pCharacteristic) override; + + private: + BleFileTransfer* parent; + }; + + // File list characteristic callbacks + class FileListCallbacks : public BLECharacteristicCallbacks { + public: + FileListCallbacks(BleFileTransfer* parent) : parent(parent) {} + void onRead(BLECharacteristic* pCharacteristic) override; + + private: + BleFileTransfer* parent; + }; + + void handleControlCommand(const std::string& command); + std::string getFileList(); + + std::unique_ptr serverCallbacks; + std::unique_ptr controlCallbacks; + std::unique_ptr fileListCallbacks; +};