Codex added unified library, all work
This commit is contained in:
parent
18a1d1558c
commit
8370e546ff
25 changed files with 2935 additions and 0 deletions
14
lib/tbeam_storage/library.json
Normal file
14
lib/tbeam_storage/library.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "tbeam_storage",
|
||||
"version": "0.1.0",
|
||||
"description": "Reusable SD mount/watch and file storage service for LilyGO T-Beam Supreme exercises.",
|
||||
"frameworks": "arduino",
|
||||
"platforms": "espressif32",
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "XPowersLib",
|
||||
"owner": "lewisxhe",
|
||||
"version": "0.3.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
526
lib/tbeam_storage/src/TBeamStorage.cpp
Normal file
526
lib/tbeam_storage/src/TBeamStorage.cpp
Normal file
|
|
@ -0,0 +1,526 @@
|
|||
#include "TBeamStorage.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include "driver/gpio.h"
|
||||
|
||||
namespace tbeam {
|
||||
|
||||
TBeamStorage::TBeamStorage(Print& diagnostic) : diagnostic_(diagnostic) {}
|
||||
|
||||
bool TBeamStorage::begin(const StorageConfig& config, SdEventCallback callback) {
|
||||
config_ = config;
|
||||
callback_ = callback;
|
||||
clearError();
|
||||
|
||||
forceSpiDeselected();
|
||||
dumpSdPins("early");
|
||||
|
||||
if (!initPmuForSdPower()) {
|
||||
setStateAbsent();
|
||||
return false;
|
||||
}
|
||||
|
||||
cycleSdRail(config_.recoveryRailOffMs, config_.recoveryRailOnSettleMs);
|
||||
delay(config_.startupWarmupMs);
|
||||
|
||||
bool mounted = false;
|
||||
for (uint8_t i = 0; i < 3; ++i) {
|
||||
if (mountPreferred(false)) {
|
||||
mounted = true;
|
||||
break;
|
||||
}
|
||||
delay(200);
|
||||
}
|
||||
|
||||
if (!mounted) {
|
||||
logf("SD: preferred mount failed, trying full scan");
|
||||
cycleSdRail(400, 1200);
|
||||
delay(config_.startupWarmupMs);
|
||||
mounted = mountCardFullScan();
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setStateMounted();
|
||||
} else {
|
||||
setError("SD mount failed");
|
||||
setStateAbsent();
|
||||
}
|
||||
return mounted;
|
||||
}
|
||||
|
||||
void TBeamStorage::update() {
|
||||
const uint32_t now = millis();
|
||||
const uint32_t pollInterval =
|
||||
(state_ == SdState::MOUNTED) ? config_.pollIntervalMountedMs : config_.pollIntervalAbsentMs;
|
||||
|
||||
if ((uint32_t)(now - lastPollMs_) < pollInterval) {
|
||||
return;
|
||||
}
|
||||
lastPollMs_ = now;
|
||||
|
||||
if (state_ == SdState::MOUNTED) {
|
||||
if (verifyMountedCard()) {
|
||||
presentVotes_ = 0;
|
||||
absentVotes_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mountPreferred(false) && verifyMountedCard()) {
|
||||
presentVotes_ = 0;
|
||||
absentVotes_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
++absentVotes_;
|
||||
presentVotes_ = 0;
|
||||
if (absentVotes_ >= config_.votesToAbsent) {
|
||||
closeLog();
|
||||
setError("SD removed or unreadable");
|
||||
setStateAbsent();
|
||||
absentVotes_ = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool mounted = mountPreferred(false);
|
||||
if (!mounted && (uint32_t)(now - lastFullScanMs_) >= config_.fullScanIntervalMs) {
|
||||
lastFullScanMs_ = now;
|
||||
if (config_.recoveryRailCycleOnFullScan) {
|
||||
cycleSdRail(config_.recoveryRailOffMs, config_.recoveryRailOnSettleMs);
|
||||
delay(150);
|
||||
}
|
||||
mounted = mountCardFullScan();
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
++presentVotes_;
|
||||
absentVotes_ = 0;
|
||||
if (presentVotes_ >= config_.votesToPresent) {
|
||||
clearError();
|
||||
setStateMounted();
|
||||
presentVotes_ = 0;
|
||||
}
|
||||
} else {
|
||||
++absentVotes_;
|
||||
presentVotes_ = 0;
|
||||
if (absentVotes_ >= config_.votesToAbsent) {
|
||||
setStateAbsent();
|
||||
absentVotes_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TBeamStorage::consumeMountedEvent() {
|
||||
const bool out = mountedEventPending_;
|
||||
mountedEventPending_ = false;
|
||||
return out;
|
||||
}
|
||||
|
||||
bool TBeamStorage::consumeRemovedEvent() {
|
||||
const bool out = removedEventPending_;
|
||||
removedEventPending_ = false;
|
||||
return out;
|
||||
}
|
||||
|
||||
bool TBeamStorage::forceRemount() {
|
||||
closeLog();
|
||||
presentVotes_ = 0;
|
||||
absentVotes_ = 0;
|
||||
lastPollMs_ = 0;
|
||||
lastFullScanMs_ = millis();
|
||||
|
||||
cycleSdRail(config_.recoveryRailOffMs, config_.recoveryRailOnSettleMs);
|
||||
delay(config_.startupWarmupMs);
|
||||
|
||||
if (mountCardFullScan()) {
|
||||
clearError();
|
||||
setStateMounted();
|
||||
return true;
|
||||
}
|
||||
|
||||
setError("manual SD remount failed");
|
||||
setStateAbsent();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TBeamStorage::ensureDirRecursive(const char* path) {
|
||||
if (!ready() || !path || path[0] == '\0') {
|
||||
setError("ensureDirRecursive invalid state");
|
||||
return false;
|
||||
}
|
||||
|
||||
char normalized[128];
|
||||
if (!normalizePath(path, normalized, sizeof(normalized))) {
|
||||
setError("directory path too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
char partial[128] = "/";
|
||||
const char* p = normalized + 1;
|
||||
while (*p) {
|
||||
const char* slash = strchr(p, '/');
|
||||
const size_t len = slash ? (size_t)(slash - normalized) : strlen(normalized);
|
||||
if (len >= sizeof(partial)) {
|
||||
setError("directory path too long");
|
||||
return false;
|
||||
}
|
||||
memcpy(partial, normalized, len);
|
||||
partial[len] = '\0';
|
||||
if (!SD.exists(partial) && !SD.mkdir(partial)) {
|
||||
setError("SD.mkdir failed");
|
||||
return false;
|
||||
}
|
||||
if (!slash) {
|
||||
break;
|
||||
}
|
||||
p = slash + 1;
|
||||
}
|
||||
clearError();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TBeamStorage::makeUniqueLogPath(const char* prefix, const char* extension, char* out, size_t outSize) {
|
||||
if (!out || outSize == 0) {
|
||||
return false;
|
||||
}
|
||||
out[0] = '\0';
|
||||
if (!ready() && !forceRemount()) {
|
||||
return false;
|
||||
}
|
||||
if (!ensureDirRecursive(config_.logDir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* safePrefix = (prefix && prefix[0]) ? prefix : "log";
|
||||
const char* safeExt = (extension && extension[0]) ? extension : ".log";
|
||||
char base[80];
|
||||
snprintf(base, sizeof(base), "%s_%lu", safePrefix, (unsigned long)(millis() / 1000UL));
|
||||
|
||||
for (uint16_t suffix = 0; suffix < 1000; ++suffix) {
|
||||
const int n = suffix == 0
|
||||
? snprintf(out, outSize, "%s/%s%s", config_.logDir, base, safeExt)
|
||||
: snprintf(out, outSize, "%s/%s_%03u%s", config_.logDir, base, (unsigned)suffix, safeExt);
|
||||
if (n < 0 || (size_t)n >= outSize) {
|
||||
setError("log path buffer too small");
|
||||
out[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
if (!SD.exists(out)) {
|
||||
clearError();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
setError("could not allocate unique log path");
|
||||
out[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TBeamStorage::openLog(const char* path) {
|
||||
closeLog();
|
||||
if (!ready() && !forceRemount()) {
|
||||
return false;
|
||||
}
|
||||
if (!path || path[0] == '\0') {
|
||||
setError("openLog missing path");
|
||||
return false;
|
||||
}
|
||||
|
||||
char normalized[sizeof(currentLogPath_)];
|
||||
if (!normalizePath(path, normalized, sizeof(normalized))) {
|
||||
setError("log path too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* slash = strrchr(normalized, '/');
|
||||
if (slash && slash != normalized) {
|
||||
char dir[sizeof(currentLogPath_)];
|
||||
const size_t len = (size_t)(slash - normalized);
|
||||
memcpy(dir, normalized, len);
|
||||
dir[len] = '\0';
|
||||
if (!ensureDirRecursive(dir)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
logFile_ = SD.open(normalized, FILE_APPEND);
|
||||
if (!logFile_) {
|
||||
logFile_ = SD.open(normalized, FILE_WRITE);
|
||||
}
|
||||
if (!logFile_) {
|
||||
setError("SD.open log failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
strlcpy(currentLogPath_, normalized, sizeof(currentLogPath_));
|
||||
clearError();
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t TBeamStorage::write(const uint8_t* data, size_t len) {
|
||||
if (!data || len == 0 || !logFile_) {
|
||||
return 0;
|
||||
}
|
||||
const size_t wrote = logFile_.write(data, len);
|
||||
if (wrote != len) {
|
||||
setError("SD log write short");
|
||||
}
|
||||
return wrote;
|
||||
}
|
||||
|
||||
size_t TBeamStorage::print(const char* text) {
|
||||
if (!text) {
|
||||
return 0;
|
||||
}
|
||||
return write((const uint8_t*)text, strlen(text));
|
||||
}
|
||||
|
||||
size_t TBeamStorage::println(const char* text) {
|
||||
size_t wrote = print(text);
|
||||
static const char newline[] = "\n";
|
||||
wrote += write((const uint8_t*)newline, 1);
|
||||
return wrote;
|
||||
}
|
||||
|
||||
bool TBeamStorage::flush() {
|
||||
if (!logFile_) {
|
||||
return false;
|
||||
}
|
||||
logFile_.flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
void TBeamStorage::closeLog() {
|
||||
if (logFile_) {
|
||||
logFile_.flush();
|
||||
logFile_.close();
|
||||
}
|
||||
currentLogPath_[0] = '\0';
|
||||
}
|
||||
|
||||
void TBeamStorage::listFiles(Print& out, const char* path, uint8_t depth) {
|
||||
if (!ready()) {
|
||||
out.println("SD not mounted");
|
||||
return;
|
||||
}
|
||||
|
||||
char normalized[128];
|
||||
if (!normalizePath(path ? path : "/", normalized, sizeof(normalized))) {
|
||||
out.println("path too long");
|
||||
return;
|
||||
}
|
||||
|
||||
File root = SD.open(normalized, FILE_READ);
|
||||
if (!root) {
|
||||
out.printf("open failed: %s\r\n", normalized);
|
||||
return;
|
||||
}
|
||||
if (!root.isDirectory()) {
|
||||
out.printf("%s %lu\r\n", normalized, (unsigned long)root.size());
|
||||
root.close();
|
||||
return;
|
||||
}
|
||||
listFilesRecursive(out, root, normalized, depth);
|
||||
root.close();
|
||||
}
|
||||
|
||||
bool TBeamStorage::removeFile(const char* path) {
|
||||
if (!ready()) {
|
||||
setError("SD not mounted");
|
||||
return false;
|
||||
}
|
||||
|
||||
char normalized[128];
|
||||
if (!normalizePath(path, normalized, sizeof(normalized))) {
|
||||
setError("remove path invalid");
|
||||
return false;
|
||||
}
|
||||
if (!SD.exists(normalized)) {
|
||||
setError("remove path missing");
|
||||
return false;
|
||||
}
|
||||
if (!SD.remove(normalized)) {
|
||||
setError("SD.remove failed");
|
||||
return false;
|
||||
}
|
||||
clearError();
|
||||
return true;
|
||||
}
|
||||
|
||||
void TBeamStorage::logf(const char* fmt, ...) {
|
||||
char msg[196];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(msg, sizeof(msg), fmt, args);
|
||||
va_end(args);
|
||||
diagnostic_.printf("[%10lu][storage:%06lu] %s\r\n",
|
||||
(unsigned long)millis(),
|
||||
(unsigned long)logSeq_++,
|
||||
msg);
|
||||
}
|
||||
|
||||
void TBeamStorage::notify(SdEvent event, const char* message) {
|
||||
if (callback_) {
|
||||
callback_(event, message);
|
||||
}
|
||||
}
|
||||
|
||||
void TBeamStorage::setError(const char* message) {
|
||||
strlcpy(lastError_, message ? message : "", sizeof(lastError_));
|
||||
}
|
||||
|
||||
void TBeamStorage::clearError() {
|
||||
lastError_[0] = '\0';
|
||||
}
|
||||
|
||||
void TBeamStorage::forceSpiDeselected() {
|
||||
pinMode(tbeam_supreme::sdCs(), OUTPUT);
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
pinMode(tbeam_supreme::imuCs(), OUTPUT);
|
||||
digitalWrite(tbeam_supreme::imuCs(), HIGH);
|
||||
}
|
||||
|
||||
void TBeamStorage::dumpSdPins(const char* tag) {
|
||||
if (!config_.enablePinDumps) {
|
||||
return;
|
||||
}
|
||||
const gpio_num_t cs = (gpio_num_t)tbeam_supreme::sdCs();
|
||||
const gpio_num_t sck = (gpio_num_t)tbeam_supreme::sdSck();
|
||||
const gpio_num_t miso = (gpio_num_t)tbeam_supreme::sdMiso();
|
||||
const gpio_num_t mosi = (gpio_num_t)tbeam_supreme::sdMosi();
|
||||
logf("PINS(%s): CS=%d SCK=%d MISO=%d MOSI=%d",
|
||||
tag ? tag : "?",
|
||||
gpio_get_level(cs),
|
||||
gpio_get_level(sck),
|
||||
gpio_get_level(miso),
|
||||
gpio_get_level(mosi));
|
||||
}
|
||||
|
||||
bool TBeamStorage::initPmuForSdPower() {
|
||||
if (!tbeam_supreme::initPmuForPeripherals(pmu_, &diagnostic_)) {
|
||||
setError("PMU init failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TBeamStorage::cycleSdRail(uint32_t offMs, uint32_t onSettleMs) {
|
||||
if (!config_.enableSdRailCycle || !pmu_) {
|
||||
return;
|
||||
}
|
||||
forceSpiDeselected();
|
||||
pmu_->disablePowerOutput(XPOWERS_BLDO1);
|
||||
delay(offMs);
|
||||
pmu_->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
|
||||
pmu_->enablePowerOutput(XPOWERS_BLDO1);
|
||||
delay(onSettleMs);
|
||||
}
|
||||
|
||||
bool TBeamStorage::tryMountWithBus(SPIClass& bus, const char* busName, uint32_t hz, bool verbose) {
|
||||
SD.end();
|
||||
bus.end();
|
||||
delay(10);
|
||||
forceSpiDeselected();
|
||||
|
||||
bus.begin(tbeam_supreme::sdSck(), tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi(), tbeam_supreme::sdCs());
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
delay(2);
|
||||
for (uint8_t i = 0; i < 10; ++i) {
|
||||
bus.transfer(0xFF);
|
||||
}
|
||||
delay(2);
|
||||
|
||||
if (verbose) {
|
||||
logf("SD: trying bus=%s freq=%lu Hz", busName, (unsigned long)hz);
|
||||
}
|
||||
|
||||
if (!SD.begin(tbeam_supreme::sdCs(), bus, hz)) {
|
||||
return false;
|
||||
}
|
||||
if (SD.cardType() == CARD_NONE) {
|
||||
SD.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
sdSpi_ = &bus;
|
||||
clearError();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TBeamStorage::mountPreferred(bool verbose) {
|
||||
return tryMountWithBus(sdSpiH_, "HSPI", 400000, verbose);
|
||||
}
|
||||
|
||||
bool TBeamStorage::mountCardFullScan() {
|
||||
const uint32_t freqs[] = {400000, 1000000, 4000000, 10000000};
|
||||
for (uint8_t i = 0; i < sizeof(freqs) / sizeof(freqs[0]); ++i) {
|
||||
if (tryMountWithBus(sdSpiH_, "HSPI", freqs[i], true)) {
|
||||
logf("SD: mounted on HSPI");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (uint8_t i = 0; i < sizeof(freqs) / sizeof(freqs[0]); ++i) {
|
||||
if (tryMountWithBus(sdSpiF_, "FSPI", freqs[i], true)) {
|
||||
logf("SD: mounted on FSPI");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TBeamStorage::verifyMountedCard() {
|
||||
if (SD.cardType() == CARD_NONE) {
|
||||
return false;
|
||||
}
|
||||
File root = SD.open("/", FILE_READ);
|
||||
const bool ok = root && root.isDirectory();
|
||||
root.close();
|
||||
return ok;
|
||||
}
|
||||
|
||||
void TBeamStorage::setStateMounted() {
|
||||
if (state_ != SdState::MOUNTED) {
|
||||
logf("SD: mounted");
|
||||
mountedEventPending_ = true;
|
||||
notify(SdEvent::CARD_MOUNTED, "SD mounted");
|
||||
}
|
||||
state_ = SdState::MOUNTED;
|
||||
}
|
||||
|
||||
void TBeamStorage::setStateAbsent() {
|
||||
if (state_ == SdState::MOUNTED) {
|
||||
removedEventPending_ = true;
|
||||
notify(SdEvent::CARD_REMOVED, "SD removed");
|
||||
} else if (state_ == SdState::UNKNOWN) {
|
||||
notify(SdEvent::NO_CARD, "SD not mounted");
|
||||
}
|
||||
state_ = SdState::ABSENT;
|
||||
}
|
||||
|
||||
void TBeamStorage::listFilesRecursive(Print& out, File& dir, const char* parentPath, uint8_t depth) {
|
||||
File entry = dir.openNextFile();
|
||||
while (entry) {
|
||||
const char* name = entry.name();
|
||||
if (entry.isDirectory()) {
|
||||
out.printf("%s/\r\n", name);
|
||||
if (depth > 0) {
|
||||
listFilesRecursive(out, entry, name, depth - 1);
|
||||
}
|
||||
} else {
|
||||
out.printf("%s %lu\r\n", name, (unsigned long)entry.size());
|
||||
}
|
||||
entry.close();
|
||||
entry = dir.openNextFile();
|
||||
}
|
||||
(void)parentPath;
|
||||
}
|
||||
|
||||
bool TBeamStorage::normalizePath(const char* input, char* out, size_t outSize) const {
|
||||
if (!input || !out || outSize < 2) {
|
||||
return false;
|
||||
}
|
||||
const int n = input[0] == '/' ? snprintf(out, outSize, "%s", input) : snprintf(out, outSize, "/%s", input);
|
||||
return n > 0 && (size_t)n < outSize;
|
||||
}
|
||||
|
||||
} // namespace tbeam
|
||||
114
lib/tbeam_storage/src/TBeamStorage.h
Normal file
114
lib/tbeam_storage/src/TBeamStorage.h
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SD.h>
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
#include <XPowersLib.h>
|
||||
#include "tbeam_supreme_adapter.h"
|
||||
|
||||
namespace tbeam {
|
||||
|
||||
enum class SdState : uint8_t {
|
||||
UNKNOWN = 0,
|
||||
ABSENT,
|
||||
MOUNTED
|
||||
};
|
||||
|
||||
enum class SdEvent : uint8_t {
|
||||
NO_CARD = 0,
|
||||
CARD_MOUNTED,
|
||||
CARD_REMOVED
|
||||
};
|
||||
|
||||
using SdEventCallback = void (*)(SdEvent event, const char* message);
|
||||
|
||||
struct StorageConfig {
|
||||
const char* logDir = "/logs";
|
||||
bool enableSdRailCycle = true;
|
||||
bool enablePinDumps = false;
|
||||
bool recoveryRailCycleOnFullScan = true;
|
||||
uint32_t recoveryRailOffMs = 250;
|
||||
uint32_t recoveryRailOnSettleMs = 700;
|
||||
uint32_t startupWarmupMs = 1500;
|
||||
uint32_t pollIntervalAbsentMs = 1000;
|
||||
uint32_t pollIntervalMountedMs = 2000;
|
||||
uint32_t fullScanIntervalMs = 10000;
|
||||
uint32_t defaultFlushIntervalMs = 2000;
|
||||
uint8_t votesToPresent = 2;
|
||||
uint8_t votesToAbsent = 5;
|
||||
};
|
||||
|
||||
class TBeamStorage {
|
||||
public:
|
||||
explicit TBeamStorage(Print& diagnostic = Serial);
|
||||
|
||||
bool begin(const StorageConfig& config = StorageConfig{}, SdEventCallback callback = nullptr);
|
||||
void update();
|
||||
|
||||
bool ready() const { return state_ == SdState::MOUNTED; }
|
||||
bool mounted() const { return ready(); }
|
||||
SdState state() const { return state_; }
|
||||
const char* lastError() const { return lastError_; }
|
||||
const char* logDir() const { return config_.logDir; }
|
||||
|
||||
bool consumeMountedEvent();
|
||||
bool consumeRemovedEvent();
|
||||
bool forceRemount();
|
||||
|
||||
bool ensureDirRecursive(const char* path);
|
||||
bool makeUniqueLogPath(const char* prefix, const char* extension, char* out, size_t outSize);
|
||||
bool openLog(const char* path);
|
||||
bool isLogOpen() const { return (bool)logFile_; }
|
||||
const char* currentLogPath() const { return currentLogPath_; }
|
||||
size_t write(const uint8_t* data, size_t len);
|
||||
size_t print(const char* text);
|
||||
size_t println(const char* text);
|
||||
bool flush();
|
||||
void closeLog();
|
||||
|
||||
void listFiles(Print& out, const char* path = "/", uint8_t depth = 4);
|
||||
bool removeFile(const char* path);
|
||||
|
||||
private:
|
||||
void logf(const char* fmt, ...);
|
||||
void notify(SdEvent event, const char* message);
|
||||
void setError(const char* message);
|
||||
void clearError();
|
||||
void forceSpiDeselected();
|
||||
void dumpSdPins(const char* tag);
|
||||
bool initPmuForSdPower();
|
||||
void cycleSdRail(uint32_t offMs, uint32_t onSettleMs);
|
||||
bool tryMountWithBus(SPIClass& bus, const char* busName, uint32_t hz, bool verbose);
|
||||
bool mountPreferred(bool verbose);
|
||||
bool mountCardFullScan();
|
||||
bool verifyMountedCard();
|
||||
void setStateMounted();
|
||||
void setStateAbsent();
|
||||
void listFilesRecursive(Print& out, File& dir, const char* parentPath, uint8_t depth);
|
||||
bool normalizePath(const char* input, char* out, size_t outSize) const;
|
||||
|
||||
Print& diagnostic_;
|
||||
StorageConfig config_{};
|
||||
SdEventCallback callback_ = nullptr;
|
||||
|
||||
SPIClass sdSpiH_{HSPI};
|
||||
SPIClass sdSpiF_{FSPI};
|
||||
SPIClass* sdSpi_ = nullptr;
|
||||
XPowersLibInterface* pmu_ = nullptr;
|
||||
|
||||
SdState state_ = SdState::UNKNOWN;
|
||||
uint8_t presentVotes_ = 0;
|
||||
uint8_t absentVotes_ = 0;
|
||||
uint32_t lastPollMs_ = 0;
|
||||
uint32_t lastFullScanMs_ = 0;
|
||||
uint32_t logSeq_ = 0;
|
||||
bool mountedEventPending_ = false;
|
||||
bool removedEventPending_ = false;
|
||||
|
||||
File logFile_;
|
||||
char currentLogPath_[128] = {};
|
||||
char lastError_[160] = {};
|
||||
};
|
||||
|
||||
} // namespace tbeam
|
||||
Loading…
Add table
Add a link
Reference in a new issue