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)
void BluetoothActivity::taskTrampoline(void* param) {
void BluetoothActivity::displayTaskTrampoline(void* param) {
auto* self = static_cast<BluetoothActivity*>(param);
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() {
NimBLEDevice::startAdvertising();
}
@ -65,7 +78,7 @@ void BluetoothActivity::onEnter() {
state = STATE_INITIALIZING;
intoState(STATE_WAITING);
xTaskCreate(&BluetoothActivity::taskTrampoline, "BluetoothTask",
xTaskCreate(&BluetoothActivity::displayTaskTrampoline, "BluetoothTask",
// TODO: figure out how much stack we actually need
4096, // Stack size
this, // Parameters
@ -89,6 +102,16 @@ void BluetoothActivity::intoState(State newState) {
// caller sets filename, totalBytes, file, txnId
receivedBytes = 0;
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:
{
// caller sets errorMessage
@ -131,7 +154,7 @@ void BluetoothActivity::loop() {
return;
}
if (state == STATE_ERROR) {
if (state == STATE_ERROR || state == STATE_DONE) {
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
// restart
intoState(STATE_WAITING);
@ -207,7 +230,7 @@ void BluetoothActivity::render() const {
// Draw help text at bottom
const auto labels = mappedInput.mapLabels(
"« 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) {
if (state == STATE_ERROR) {
// stay in error state so the user can read the error message even after disconnect
if (state == STATE_ERROR || state == STATE_DONE) {
// 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;
}
@ -310,7 +334,6 @@ void BluetoothActivity::onRequest(lfbt_message* msg, size_t msg_len) {
if (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");
// TODO: automatically open file in reader
intoState(STATE_DONE);
} else {
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.
*
* 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 {
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
bool updateRequired = false;
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();
void render() const;
static void reportTaskTrampoline(void* param);
void report();
void onConnected(bool isConnected);
void onRequest(lfbt_message *msg, size_t msg_len);
@ -113,8 +118,9 @@ class BluetoothActivity final : public Activity {
public:
explicit BluetoothActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::function<void()>& onCancel)
: Activity("Bluetooth", renderer, mappedInput), onCancel(onCancel),
const std::function<void()>& onCancel,
const std::function<void(const std::string&)>& onFileReceived)
: Activity("Bluetooth", renderer, mappedInput), onCancel(onCancel), onFileReceived(onFileReceived),
serverCallbacks(this), requestCallbacks(this) {}
void onEnter() override;
void onExit() override;

View File

@ -26,6 +26,7 @@
#include "activities/settings/SettingsActivity.h"
#include "activities/util/FullScreenMessageActivity.h"
#include "fontIds.h"
#include "util/StringUtils.h"
#define SPI_FQ 40000000
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
@ -229,7 +230,17 @@ void onGoToFileTransfer() {
void onGoToBluetooth() {
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() {

View File

@ -61,6 +61,11 @@ bool checkFileExtension(const String& fileName, const char* extension) {
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) {
size_t lastDot = name.find_last_of('.');
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 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.
* If there is no extension, the second element of the pair will be an empty string.