mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-09-25 22:23:37 +00:00
Repins microReticulum + microLXMF onto the upstream-0.4.1 graft and adapts pyxis to the new src/microReticulum/ layout and 0.4.x APIs. The far-diverged 0.3.0 fork's Resource/Transport/Identity work is subsumed by upstream's reimplementation; only the still-needed fixes ride on the pinned branches (PKCS7/HMAC/X25519 crypto -- proven byte-identical to python RNS 1.3.1 -- Packet link-proof callback, Identity short-sig guard, and the bz2 layer + decompress-on-receive in Resource::assemble()). Consumer-side changes: - platformio.ini: pin microReticulum @2f21fee (pyxis-fixes-on-0.4.1) and microLXMF @33760d0 (chore/microreticulum-0.4.1-layout); bump microStore ceea8f5 -> c5fb69d (0.4.x requires the new BasicFileStore::init API); -std=gnu++11 -> gnu++17 (upstream requires C++17). - Namespace all microReticulum includes (angle + quote) to <microReticulum/...> for the relocated layout; shim-local Utilities/Stream.h|Print.h preserved. - Interface::send_outgoing now returns bool: update TCP/BLE/SX1262/Auto overrides with correct success/failure returns. - SDArchiveFileSystem::init(bool reformatOnFail=true) to match new microStore. - Static Transport::get_path_table() -> path_table(); instance getter unchanged. - Remove duplicate shim Cryptography/BZ2 (microReticulum provides it now; keep lib/libbz2 as the ESP32 bzlib provider). - patch_littlefs_paths.py: normalize microStore's LittleFS adapter paths to a leading "/" -- ESP32 Arduino LittleFS rejects "./"-prefixed paths, which silently broke the path store (no peer paths learned, all messaging blocked). Validated on T-Deck Plus: builds (RAM 27.5% / Flash 77.7%), boots stable (no WDT/panic), and a full on-device LXMF e2e (DIRECT + OPPORTUNISTIC + bz2-compressed-Resource receive) passes 5/5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
265 lines
9.4 KiB
C++
265 lines
9.4 KiB
C++
// Copyright (c) 2026 Pyxis contributors
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#ifndef HARDWARE_TDECK_SD_ARCHIVE_FS_H
|
|
#define HARDWARE_TDECK_SD_ARCHIVE_FS_H
|
|
|
|
#ifdef ARDUINO
|
|
|
|
#include "SDAccess.h"
|
|
|
|
#include <microStore/File.h>
|
|
#include <microStore/FileSystem.h>
|
|
|
|
#include <Arduino.h>
|
|
#include <SD.h>
|
|
#include <FS.h>
|
|
|
|
namespace Hardware {
|
|
namespace TDeck {
|
|
|
|
// microStore::FileSystem adapter that piggybacks on the SD card already
|
|
// mounted by SDAccess::init() and serializes every operation through
|
|
// the shared SPI bus mutex.
|
|
//
|
|
// The stock microStore SDFileSystem adapter calls SD.begin() in its own
|
|
// init() and uses its own SPIClass instance — that conflicts with the
|
|
// pyxis HW init order (display + LoRa share HSPI). This adapter skips
|
|
// re-mounting and just delegates to the global SD object, wrapping each
|
|
// op with SDAccess::acquire_bus() / release_bus() so it cooperates with
|
|
// display and LoRa traffic.
|
|
//
|
|
// Used as the archive tier for LXMF::MessageStore — the LittleFS hot
|
|
// tier holds the most-recent ~50 messages per conversation; everything
|
|
// older is moved here.
|
|
|
|
namespace _SDArchive {
|
|
|
|
class FileImpl : public microStore::FileImpl {
|
|
private:
|
|
fs::File _file;
|
|
bool _open;
|
|
public:
|
|
FileImpl(fs::File f) : microStore::FileImpl(), _file(f), _open(true) {}
|
|
virtual ~FileImpl() { if (_open) close(); }
|
|
|
|
inline virtual const char* name() const { return _file.name(); }
|
|
inline virtual size_t size() const { return _file.size(); }
|
|
inline virtual void close() {
|
|
if (!_open) return;
|
|
// Only release the bus if we actually took it — otherwise the
|
|
// matching xSemaphoreGive in release_bus() would be unmatched
|
|
// and skew the mutex counter. _file.close() under SPI contention
|
|
// is the lesser evil vs corrupting the bus mutex.
|
|
bool held = SDAccess::acquire_bus(500);
|
|
_file.close();
|
|
if (held) SDAccess::release_bus();
|
|
_open = false;
|
|
}
|
|
inline virtual int read() {
|
|
if (!SDAccess::acquire_bus(500)) return -1;
|
|
int r = _file.read();
|
|
SDAccess::release_bus();
|
|
return r;
|
|
}
|
|
inline virtual size_t read(uint8_t* buf, size_t sz) {
|
|
if (!SDAccess::acquire_bus(500)) return 0;
|
|
size_t r = _file.read(buf, sz);
|
|
SDAccess::release_bus();
|
|
return r;
|
|
}
|
|
inline virtual size_t write(uint8_t b) {
|
|
if (!SDAccess::acquire_bus(500)) return 0;
|
|
size_t w = _file.write(b);
|
|
SDAccess::release_bus();
|
|
return w;
|
|
}
|
|
inline virtual size_t write(const uint8_t* buf, size_t sz) {
|
|
if (!SDAccess::acquire_bus(500)) return 0;
|
|
size_t w = _file.write(buf, sz);
|
|
SDAccess::release_bus();
|
|
return w;
|
|
}
|
|
inline virtual int available() {
|
|
if (!SDAccess::acquire_bus(500)) return 0;
|
|
int a = _file.available();
|
|
SDAccess::release_bus();
|
|
return a;
|
|
}
|
|
inline virtual int peek() {
|
|
if (!SDAccess::acquire_bus(500)) return -1;
|
|
int p = _file.peek();
|
|
SDAccess::release_bus();
|
|
return p;
|
|
}
|
|
inline virtual size_t tell() {
|
|
if (!SDAccess::acquire_bus(500)) return 0;
|
|
size_t t = _file.position();
|
|
SDAccess::release_bus();
|
|
return t;
|
|
}
|
|
inline virtual long seek(uint32_t pos, microStore::SeekMode mode) {
|
|
if (!SDAccess::acquire_bus(500)) return -1;
|
|
fs::SeekMode smode = fs::SeekSet;
|
|
switch (mode) {
|
|
case microStore::SeekMode::SeekModeCur: smode = fs::SeekCur; break;
|
|
case microStore::SeekMode::SeekModeEnd: smode = fs::SeekEnd; break;
|
|
default: smode = fs::SeekSet; break;
|
|
}
|
|
long ok = _file.seek(pos, smode);
|
|
SDAccess::release_bus();
|
|
return ok;
|
|
}
|
|
inline virtual void flush() {
|
|
if (!SDAccess::acquire_bus(500)) return;
|
|
_file.flush();
|
|
SDAccess::release_bus();
|
|
}
|
|
inline virtual bool isValid() const { return _open && _file; }
|
|
};
|
|
|
|
class FileSystemImpl : public microStore::FileSystemImpl {
|
|
public:
|
|
FileSystemImpl() : microStore::FileSystemImpl() {}
|
|
virtual ~FileSystemImpl() {}
|
|
|
|
// SD is already mounted by SDAccess::init() — we don't re-mount.
|
|
virtual bool init(bool reformatOnFail = true) override { return SDAccess::is_ready(); }
|
|
virtual bool format() override { return false; }
|
|
|
|
virtual microStore::File open(const char* path, microStore::File::Mode mode,
|
|
const bool create = false) override {
|
|
const char* pmode = nullptr;
|
|
switch (mode) {
|
|
case microStore::File::ModeRead: pmode = FILE_READ; break;
|
|
case microStore::File::ModeWrite: pmode = FILE_WRITE; break;
|
|
case microStore::File::ModeAppend: pmode = FILE_APPEND; break;
|
|
case microStore::File::ModeReadWrite: pmode = "w+"; break;
|
|
case microStore::File::ModeReadAppend: pmode = "a+"; break;
|
|
default: return {};
|
|
}
|
|
if (!SDAccess::acquire_bus(500)) return {};
|
|
fs::File f = SD.open(path, pmode);
|
|
SDAccess::release_bus();
|
|
if (!f) return {};
|
|
return microStore::File(new FileImpl(f));
|
|
}
|
|
|
|
virtual bool exists(const char* path) override {
|
|
if (!SDAccess::acquire_bus(500)) return false;
|
|
bool r = SD.exists(path);
|
|
SDAccess::release_bus();
|
|
return r;
|
|
}
|
|
virtual bool remove(const char* path) override {
|
|
if (!SDAccess::acquire_bus(500)) return false;
|
|
bool r = SD.remove(path);
|
|
SDAccess::release_bus();
|
|
return r;
|
|
}
|
|
virtual bool rename(const char* from, const char* to) override {
|
|
if (!SDAccess::acquire_bus(500)) return false;
|
|
bool r = SD.rename(from, to);
|
|
SDAccess::release_bus();
|
|
return r;
|
|
}
|
|
virtual bool mkdir(const char* path) override {
|
|
if (!SDAccess::acquire_bus(500)) return false;
|
|
bool r = SD.mkdir(path);
|
|
SDAccess::release_bus();
|
|
return r;
|
|
}
|
|
virtual bool rmdir(const char* path) override {
|
|
if (!SDAccess::acquire_bus(500)) return false;
|
|
bool r = SD.rmdir(path);
|
|
SDAccess::release_bus();
|
|
return r;
|
|
}
|
|
virtual bool isDirectory(const char* path) override {
|
|
if (!SDAccess::acquire_bus(500)) return false;
|
|
fs::File f = SD.open(path, FILE_READ);
|
|
bool r = false;
|
|
if (f) {
|
|
r = f.isDirectory();
|
|
f.close();
|
|
}
|
|
SDAccess::release_bus();
|
|
return r;
|
|
}
|
|
virtual std::list<std::string> listDirectory(const char* path,
|
|
Callbacks::DirectoryListing callback = nullptr) override {
|
|
// Release the SPI bus mutex between directory entries — an archive
|
|
// with thousands of entries would otherwise hold the bus for
|
|
// multiple seconds, blocking LoRa TX/RX and display flush past
|
|
// their 500 ms acquire_bus timeout (LoRa TX fails silently,
|
|
// display tears; under inbound flood the RX FIFO could overflow).
|
|
// SDLib's File cursor lives in the `root` File object, and SD
|
|
// commands are per-block addressed, so other-CS bus users
|
|
// (display, LoRa) between our openNextFile calls don't clobber
|
|
// SD state. The callback is invoked outside the critical section
|
|
// so it can do work of its own without blocking other bus users.
|
|
std::list<std::string> files;
|
|
if (!SDAccess::acquire_bus(500)) return files;
|
|
fs::File root = SD.open(path);
|
|
if (!root) {
|
|
SDAccess::release_bus();
|
|
return files;
|
|
}
|
|
while (true) {
|
|
fs::File f = root.openNextFile();
|
|
if (!f) break;
|
|
bool is_dir = f.isDirectory();
|
|
std::string name;
|
|
if (!is_dir) name = f.name();
|
|
f.close();
|
|
// Drop the bus so other tasks can run between entries.
|
|
SDAccess::release_bus();
|
|
if (!is_dir) {
|
|
if (callback) callback(name.c_str());
|
|
else files.push_back(name);
|
|
}
|
|
// Re-acquire to advance the cursor / read the next entry.
|
|
// If we can't re-acquire within the timeout, stop early
|
|
// rather than spin — caller gets whatever we collected.
|
|
if (!SDAccess::acquire_bus(500)) {
|
|
// root is still open; ensure it gets closed. We can't
|
|
// call f.close() on the directory without the bus, so
|
|
// skip the explicit close — root's destructor on
|
|
// function return will run, but without bus protection;
|
|
// in practice the SD layer tolerates this.
|
|
return files;
|
|
}
|
|
}
|
|
root.close();
|
|
SDAccess::release_bus();
|
|
return files;
|
|
}
|
|
virtual size_t storageSize() override {
|
|
if (!SDAccess::acquire_bus(500)) return 0;
|
|
size_t s = SD.totalBytes();
|
|
SDAccess::release_bus();
|
|
return s;
|
|
}
|
|
virtual size_t storageAvailable() override {
|
|
if (!SDAccess::acquire_bus(500)) return 0;
|
|
size_t s = SD.totalBytes() - SD.usedBytes();
|
|
SDAccess::release_bus();
|
|
return s;
|
|
}
|
|
virtual bool isValid() const override { return SDAccess::is_ready(); }
|
|
};
|
|
|
|
} // namespace _SDArchive
|
|
|
|
class SDArchiveFileSystem : public microStore::FileSystem {
|
|
public:
|
|
SDArchiveFileSystem() : microStore::FileSystem(new _SDArchive::FileSystemImpl()) {}
|
|
virtual ~SDArchiveFileSystem() {}
|
|
};
|
|
|
|
} // namespace TDeck
|
|
} // namespace Hardware
|
|
|
|
#endif // ARDUINO
|
|
#endif // HARDWARE_TDECK_SD_ARCHIVE_FS_H
|