add autoopen

This commit is contained in:
Aleksejs Popovs 2026-01-21 20:09:16 -05:00
parent 6473689e3e
commit 2a1f7873f7
5 changed files with 61 additions and 11 deletions

View File

@ -24,11 +24,24 @@
} \ } \
} while (0) } while (0)
void BluetoothActivity::taskTrampoline(void* param) { void BluetoothActivity::displayTaskTrampoline(void* param) {
auto* self = static_cast<BluetoothActivity*>(param); auto* self = static_cast<BluetoothActivity*>(param);
self->displayTaskLoop(); self->displayTaskLoop();
} }
void BluetoothActivity::reportTaskTrampoline(void* param) {
auto* self = static_cast<BluetoothActivity*>(param);
self->report();
vTaskDelete(nullptr);
}
void BluetoothActivity::report() {
if (state != STATE_DONE) {
return;
}
onFileReceived(OUTPUT_DIRECTORY "/" + filename);
}
void BluetoothActivity::startAdvertising() { void BluetoothActivity::startAdvertising() {
NimBLEDevice::startAdvertising(); NimBLEDevice::startAdvertising();
} }
@ -65,7 +78,7 @@ void BluetoothActivity::onEnter() {
state = STATE_INITIALIZING; state = STATE_INITIALIZING;
intoState(STATE_WAITING); intoState(STATE_WAITING);
xTaskCreate(&BluetoothActivity::taskTrampoline, "BluetoothTask", xTaskCreate(&BluetoothActivity::displayTaskTrampoline, "BluetoothTask",
// TODO: figure out how much stack we actually need // TODO: figure out how much stack we actually need
4096, // Stack size 4096, // Stack size
this, // Parameters this, // Parameters
@ -89,6 +102,16 @@ void BluetoothActivity::intoState(State newState) {
// caller sets filename, totalBytes, file, txnId // caller sets filename, totalBytes, file, txnId
receivedBytes = 0; receivedBytes = 0;
break; break;
case STATE_DONE:
// we cannot call onFileReceived here directly because it might cause onExit to be called,
// which calls NimBLEDevice::deinit, which cannot be called from inside a NimBLE callback.
xTaskCreate(&BluetoothActivity::reportTaskTrampoline, "BluetoothReportTask",
2048, // Stack size
this, // Parameters
1, // Priority,
nullptr
);
break;
case STATE_ERROR: case STATE_ERROR:
{ {
// caller sets errorMessage // caller sets errorMessage
@ -131,7 +154,7 @@ void BluetoothActivity::loop() {
return; return;
} }
if (state == STATE_ERROR) { if (state == STATE_ERROR || state == STATE_DONE) {
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
// restart // restart
intoState(STATE_WAITING); intoState(STATE_WAITING);
@ -207,7 +230,7 @@ void BluetoothActivity::render() const {
// Draw help text at bottom // Draw help text at bottom
const auto labels = mappedInput.mapLabels( const auto labels = mappedInput.mapLabels(
"« Back", "« Back",
(state == STATE_ERROR) ? "Restart" : "", (state == STATE_ERROR || state == STATE_DONE) ? "Restart" : "",
"", "",
"" ""
); );
@ -227,8 +250,9 @@ void BluetoothActivity::ServerCallbacks::onDisconnect(NimBLEServer* pServer, Nim
} }
void BluetoothActivity::onConnected(bool isConnected) { void BluetoothActivity::onConnected(bool isConnected) {
if (state == STATE_ERROR) { if (state == STATE_ERROR || state == STATE_DONE) {
// stay in error state so the user can read the error message even after disconnect // stay in error state so the user can read the error message even after disconnect.
// stay in done state so the user can see the transfer complete message.
return; return;
} }
@ -310,7 +334,6 @@ void BluetoothActivity::onRequest(lfbt_message* msg, size_t msg_len) {
if (receivedBytes >= totalBytes) { if (receivedBytes >= totalBytes) {
PROTOCOL_ASSERT(receivedBytes == totalBytes, "Got more bytes than expected: %zu > %zu", receivedBytes, totalBytes); PROTOCOL_ASSERT(receivedBytes == totalBytes, "Got more bytes than expected: %zu > %zu", receivedBytes, totalBytes);
PROTOCOL_ASSERT(file.close(), "Couldn't finalize writing the file"); PROTOCOL_ASSERT(file.close(), "Couldn't finalize writing the file");
// TODO: automatically open file in reader
intoState(STATE_DONE); intoState(STATE_DONE);
} else { } else {
intoState(STATE_RECEIVING); intoState(STATE_RECEIVING);

View File

@ -45,17 +45,22 @@ typedef struct __attribute__((packed)) {
* BluetoothActivity receives files over a custom BLE protocol and stores them on the SD card. * BluetoothActivity receives files over a custom BLE protocol and stores them on the SD card.
* *
* The onCancel callback is called if the user presses back. * The onCancel callback is called if the user presses back.
* onFileReceived is called when a file is successfully received with the path to the file.
*/ */
class BluetoothActivity final : public Activity { class BluetoothActivity final : public Activity {
TaskHandle_t displayTaskHandle = nullptr; TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr; SemaphoreHandle_t renderingMutex = nullptr;
bool updateRequired = false; bool updateRequired = false;
const std::function<void()> onCancel; const std::function<void()> onCancel;
const std::function<void(const std::string&)> onFileReceived;
static void taskTrampoline(void* param); static void displayTaskTrampoline(void* param);
[[noreturn]] void displayTaskLoop(); [[noreturn]] void displayTaskLoop();
void render() const; void render() const;
static void reportTaskTrampoline(void* param);
void report();
void onConnected(bool isConnected); void onConnected(bool isConnected);
void onRequest(lfbt_message *msg, size_t msg_len); void onRequest(lfbt_message *msg, size_t msg_len);
@ -113,8 +118,9 @@ class BluetoothActivity final : public Activity {
public: public:
explicit BluetoothActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, explicit BluetoothActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::function<void()>& onCancel) const std::function<void()>& onCancel,
: Activity("Bluetooth", renderer, mappedInput), onCancel(onCancel), const std::function<void(const std::string&)>& onFileReceived)
: Activity("Bluetooth", renderer, mappedInput), onCancel(onCancel), onFileReceived(onFileReceived),
serverCallbacks(this), requestCallbacks(this) {} serverCallbacks(this), requestCallbacks(this) {}
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;

View File

@ -26,6 +26,7 @@
#include "activities/settings/SettingsActivity.h" #include "activities/settings/SettingsActivity.h"
#include "activities/util/FullScreenMessageActivity.h" #include "activities/util/FullScreenMessageActivity.h"
#include "fontIds.h" #include "fontIds.h"
#include "util/StringUtils.h"
#define SPI_FQ 40000000 #define SPI_FQ 40000000
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults) // Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
@ -229,7 +230,17 @@ void onGoToFileTransfer() {
void onGoToBluetooth() { void onGoToBluetooth() {
exitActivity(); exitActivity();
enterNewActivity(new BluetoothActivity(renderer, mappedInputManager, onGoHome)); enterNewActivity(new BluetoothActivity(
renderer,
mappedInputManager,
onGoHome,
[](const std::string& filepath) {
Serial.printf("[%lu] [ ] File received over Bluetooth: %s\n", millis(), filepath.c_str());
if (StringUtils::readableFileExtension(filepath)) {
onGoToReader(filepath, MyLibraryActivity::Tab::Recent);
}
}
));
} }
void onGoToSettings() { void onGoToSettings() {

View File

@ -61,6 +61,11 @@ bool checkFileExtension(const String& fileName, const char* extension) {
return localFile.endsWith(localExtension); return localFile.endsWith(localExtension);
} }
bool readableFileExtension(const std::string& fileName) {
return (StringUtils::checkFileExtension(fileName, ".epub") || StringUtils::checkFileExtension(fileName, ".xtch") ||
StringUtils::checkFileExtension(fileName, ".xtc") || StringUtils::checkFileExtension(fileName, ".txt"));
}
std::pair<std::string, std::string> splitFileName(const std::string& name) { std::pair<std::string, std::string> splitFileName(const std::string& name) {
size_t lastDot = name.find_last_of('.'); size_t lastDot = name.find_last_of('.');
if (lastDot == std::string::npos) { if (lastDot == std::string::npos) {

View File

@ -19,6 +19,11 @@ std::string sanitizeFilename(const std::string& name, size_t maxLength = 100);
bool checkFileExtension(const std::string& fileName, const char* extension); bool checkFileExtension(const std::string& fileName, const char* extension);
bool checkFileExtension(const String& fileName, const char* extension); bool checkFileExtension(const String& fileName, const char* extension);
/**
* Check if the given filename ends with an extension we can open.
*/
bool readableFileExtension(const std::string& fileName);
/** /**
* Split a filename into base name and extension. * Split a filename into base name and extension.
* If there is no extension, the second element of the pair will be an empty string. * If there is no extension, the second element of the pair will be an empty string.