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.
This commit is contained in:
altsysrq 2025-12-30 21:25:33 -06:00
parent 9db4ef6f4b
commit 1f2380be56
8 changed files with 291 additions and 3 deletions

View File

@ -29,8 +29,13 @@ std::unique_ptr<PageLine> PageLine::deserialize(FsFile& file) {
} }
void PageImage::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) { 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; 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()); Serial.printf("[%lu] [PGI] Failed to open image: %s\n", millis(), cachePath.c_str());
return; return;
} }

View File

@ -83,6 +83,7 @@ class GfxRenderer {
// Grayscale functions // Grayscale functions
void setRenderMode(const RenderMode mode) { this->renderMode = mode; } void setRenderMode(const RenderMode mode) { this->renderMode = mode; }
RenderMode getRenderMode() const { return renderMode; }
void copyGrayscaleLsbBuffers() const; void copyGrayscaleLsbBuffers() const;
void copyGrayscaleMsbBuffers() const; void copyGrayscaleMsbBuffers() const;
void displayGrayBuffer() const; void displayGrayBuffer() const;

View File

@ -12,7 +12,7 @@ CrossPointSettings CrossPointSettings::instance;
namespace { namespace {
constexpr uint8_t SETTINGS_FILE_VERSION = 1; constexpr uint8_t SETTINGS_FILE_VERSION = 1;
// Increment this when adding new persisted settings fields // 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"; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
} // namespace } // namespace
@ -37,6 +37,7 @@ bool CrossPointSettings::saveToFile() const {
serialization::writePod(outputFile, fontFamily); serialization::writePod(outputFile, fontFamily);
serialization::writePod(outputFile, fontSize); serialization::writePod(outputFile, fontSize);
serialization::writePod(outputFile, lineSpacing); serialization::writePod(outputFile, lineSpacing);
serialization::writePod(outputFile, bluetoothEnabled);
outputFile.close(); outputFile.close();
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
@ -83,6 +84,8 @@ bool CrossPointSettings::loadFromFile() {
if (++settingsRead >= fileSettingsCount) break; if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, lineSpacing); serialization::readPod(inputFile, lineSpacing);
if (++settingsRead >= fileSettingsCount) break; if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, bluetoothEnabled);
if (++settingsRead >= fileSettingsCount) break;
} while (false); } while (false);
inputFile.close(); inputFile.close();

View File

@ -62,6 +62,8 @@ class CrossPointSettings {
uint8_t fontFamily = BOOKERLY; uint8_t fontFamily = BOOKERLY;
uint8_t fontSize = MEDIUM; uint8_t fontSize = MEDIUM;
uint8_t lineSpacing = NORMAL; uint8_t lineSpacing = NORMAL;
// Bluetooth settings
uint8_t bluetoothEnabled = 0;
~CrossPointSettings() = default; ~CrossPointSettings() = default;

View File

@ -8,9 +8,11 @@
#include <cstddef> #include <cstddef>
#include "CrossPointSettings.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "NetworkModeSelectionActivity.h" #include "NetworkModeSelectionActivity.h"
#include "WifiSelectionActivity.h" #include "WifiSelectionActivity.h"
#include "activities/util/FullScreenMessageActivity.h"
#include "fontIds.h" #include "fontIds.h"
namespace { namespace {
@ -128,6 +130,15 @@ void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode)
Serial.printf("[%lu] [WEBACT] Network mode selected: %s\n", millis(), Serial.printf("[%lu] [WEBACT] Network mode selected: %s\n", millis(),
mode == NetworkMode::JOIN_NETWORK ? "Join Network" : "Create Hotspot"); 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; networkMode = mode;
isApMode = (mode == NetworkMode::CREATE_HOTSPOT); isApMode = (mode == NetworkMode::CREATE_HOTSPOT);

View File

@ -9,7 +9,7 @@
// Define the static settings list // Define the static settings list
namespace { namespace {
constexpr int settingsCount = 11; constexpr int settingsCount = 12;
const SettingInfo settingsList[settingsCount] = { const SettingInfo settingsList[settingsCount] = {
// Should match with SLEEP_SCREEN_MODE // Should match with SLEEP_SCREEN_MODE
{"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}}, {"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}},
@ -34,6 +34,7 @@ const SettingInfo settingsList[settingsCount] = {
{"Bookerly", "Noto Sans", "Open Dyslexic"}}, {"Bookerly", "Noto Sans", "Open Dyslexic"}},
{"Reader Font Size", SettingType::ENUM, &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}}, {"Reader Font Size", SettingType::ENUM, &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}},
{"Reader Line Spacing", SettingType::ENUM, &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}}, {"Reader Line Spacing", SettingType::ENUM, &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}},
{"Bluetooth", SettingType::TOGGLE, &CrossPointSettings::bluetoothEnabled, {}},
{"Check for updates", SettingType::ACTION, nullptr, {}}, {"Check for updates", SettingType::ACTION, nullptr, {}},
}; };
} // namespace } // namespace

View File

@ -0,0 +1,188 @@
#include "BleFileTransfer.h"
#include <HardwareSerial.h>
#include <SDCardManager.h>
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());
}

View File

@ -0,0 +1,77 @@
#pragma once
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <functional>
#include <memory>
#include <string>
// 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> serverCallbacks;
std::unique_ptr<ControlCallbacks> controlCallbacks;
std::unique_ptr<FileListCallbacks> fileListCallbacks;
};