Burn down Arduino SD backend

This commit is contained in:
liu weikai
2026-06-22 18:14:38 +08:00
parent 185bf07968
commit 2a4b580a8a
17 changed files with 434 additions and 1272 deletions
+1 -2
View File
@@ -6,7 +6,6 @@
#include <AudioFileSourcePROGMEM.h>
#include <AudioGeneratorRTTTL.h>
#include <AudioOutputI2S.h>
#include <SD.h>
#include <Wire.h>
#include <ctime>
#include <driver/gpio.h>
@@ -652,7 +651,7 @@ bool TDeckBoard::installSD()
extra_cs_count = 1;
#endif
uint8_t cardType = CARD_NONE;
uint8_t cardType = sdutil::kCardNone;
uint32_t cardSizeMB = 0;
bool ok = sdutil::installSpiSd(*this, SD_CS, SD_SPI_FREQUENCY, "/sd",
extra_cs, extra_cs_count,
+20 -4
View File
@@ -6,7 +6,6 @@
#include <AudioFileSourcePROGMEM.h>
#include <AudioGeneratorRTTTL.h>
#include <AudioOutputI2S.h>
#include <SD.h>
#include <SPI.h>
#include <Wire.h>
#include <ctime>
@@ -16,6 +15,8 @@
#include <limits>
#include <sys/time.h>
#include "platform/esp/arduino_common/storage/sd_card_runtime.h"
namespace boards::tdeck_pro
{
@@ -26,6 +27,7 @@ constexpr time_t kMinValidEpochSeconds = 1577836800; // 2020-01-01 UTC
constexpr uint8_t kKeyboardRows = 4;
constexpr uint8_t kKeyboardCols = 10;
constexpr uint32_t kEpdSpiHz = 2000000;
constexpr uint32_t kSdSpiHz = 4000000;
constexpr uint8_t kFlushLogLimit = 8;
constexpr uint32_t kRadioTxMaxTimeoutMs = 120000;
SemaphoreHandle_t g_shared_spi_mutex = nullptr;
@@ -346,16 +348,30 @@ bool TDeckProBoard::installSD()
{
pinMode(profile().sd.cs, OUTPUT);
digitalWrite(profile().sd.cs, HIGH);
static const int extra_cs_pins[] = {
profile().lora.cs,
profile().epd.cs,
};
for (int pin : extra_cs_pins)
{
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
}
sharedSpiLock();
sharedSpiPrepareDevice(profile().sd.cs);
const bool ok = SD.begin(profile().sd.cs, SPI);
const bool ok = ::platform::esp::arduino_common::storage::mount_sd_card(
profile().sd.cs,
SPI,
kSdSpiHz,
"/sd",
8);
sharedSpiUnlock();
return ok;
}
void TDeckProBoard::uninstallSD()
{
SD.end();
::platform::esp::arduino_common::storage::unmount_sd_card();
}
uint32_t TDeckProBoard::begin(uint32_t disable_hw_init)
@@ -453,7 +469,7 @@ int TDeckProBoard::getBatteryLevel()
bool TDeckProBoard::isCardReady()
{
return sd_ready_ && SD.cardType() != CARD_NONE;
return sd_ready_ && ::platform::esp::arduino_common::storage::sd_card_ready();
}
void TDeckProBoard::vibrator()
@@ -10,7 +10,6 @@
#include <AW9364LedDriver.hpp>
#include <Arduino.h>
#include <SD.h>
#include <SPI.h>
#include <Wire.h>
#include <memory>
+5 -2
View File
@@ -772,10 +772,13 @@ bool TLoRaPagerBoard::installSD()
// Ensure SPI pins are initialized
initShareSPIPins();
uint8_t card_type = CARD_NONE;
uint8_t card_type = sdutil::kCardNone;
uint32_t card_size_mb = 0;
static const int extra_cs_pins[] = {NFC_CS, LORA_CS};
bool ok = sdutil::installSpiSd(*this, SD_CS, SD_SPI_FREQUENCY, "/sd",
nullptr, 0, &card_type, &card_size_mb);
extra_cs_pins,
sizeof(extra_cs_pins) / sizeof(extra_cs_pins[0]),
&card_type, &card_size_mb);
if (!ok)
{
log_w("SD card initialization failed");
@@ -738,12 +738,17 @@
#define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/* TrailMate registers its own nonblocking shared-SPI SD driver on A:.
* Do not also enable LVGL's built-in POSIX A: driver; duplicate A: drivers
* make SD routing ambiguous and can bypass the shared-SPI backpressure path.
*/
#define TRAIL_MATE_LVGL_SD_FS_LETTER 'A'
#define TRAIL_MATE_LVGL_SD_FS_PATH "/sd"
/*API for open, read, etc*/
#define LV_USE_FS_POSIX 1
#define LV_USE_FS_POSIX 0
#if LV_USE_FS_POSIX
#define LV_FS_POSIX_LETTER 'A' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_POSIX_PATH "/sd" /* /sd : SD Card /fs: FFat */
#define LV_FS_POSIX_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#error "TrailMate ESP builds must use the custom A: SD driver, not LVGL POSIX."
#endif
/*API for CreateFile, ReadFile, etc*/
@@ -1,124 +0,0 @@
/**
* @file log_store.h
* @brief Per-conversation ring log store (SD-based)
*/
#pragma once
#include "chat/ports/i_chat_store.h"
#include <FS.h>
#include <SD.h>
#include <array>
#include <vector>
namespace chat
{
/**
* @brief SD-backed append-only log with per-channel ring index.
*
* Layout:
* - Conversation file: /chat/<protocol>_n_<peer>.log or /chat/<protocol>_broadcast_<name>.log
* - Index file: /chat/index.bin (conversation metadata)
*/
class LogStore : public IChatStore
{
public:
static constexpr const char* kDir = "/chat";
static constexpr const char* kIndexFile = "/chat/index.bin";
static constexpr size_t kMaxMessagesPerConv = 100;
static constexpr size_t kMaxTextLen = 233;
static constexpr size_t kPreviewLen = 48;
LogStore() : fs_(nullptr) {}
~LogStore() override = default;
/**
* @brief Initialize storage. Expects SD already mounted.
* @param fs Filesystem (e.g., SD)
* @return true if ready
*/
bool begin(fs::FS& fs);
void append(const ChatMessage& msg) override;
std::vector<ChatMessage> loadRecent(const ConversationId& conv, size_t n) override;
std::vector<ConversationMeta> loadConversationPage(size_t offset,
size_t limit,
size_t* total) override;
void setUnread(const ConversationId& conv, int unread) override;
int getUnread(const ConversationId& conv) const override;
void clearConversation(const ConversationId& conv) override;
void clearAll() override;
bool updateMessageStatus(MessageId msg_id, MessageStatus status) override;
bool getMessage(MessageId msg_id, ChatMessage* out) const override;
private:
fs::FS* fs_;
struct FileHeader
{
uint32_t magic;
uint16_t version;
uint16_t head;
uint16_t count;
uint16_t reserved;
} __attribute__((packed));
struct Record
{
uint8_t protocol;
uint8_t channel;
uint8_t status;
uint16_t text_len;
uint32_t from;
uint32_t peer;
uint32_t msg_id;
uint32_t timestamp;
char text[kMaxTextLen];
} __attribute__((packed));
struct IndexHeader
{
uint32_t magic;
uint16_t version;
uint16_t count;
} __attribute__((packed));
struct IndexEntry
{
uint8_t protocol;
uint8_t channel;
uint8_t status;
uint16_t unread;
uint32_t peer;
uint32_t last_msg_id;
uint32_t last_timestamp;
uint32_t last_from;
uint16_t preview_len;
char preview[kPreviewLen];
} __attribute__((packed));
static constexpr uint32_t kFileMagic = 0x474F4C43; // "CLOG"
static constexpr uint32_t kIndexMagic = 0x54414843; // "CHAT"
static constexpr uint16_t kVersion = 2;
bool ensureDir();
bool ensureIndex(std::vector<IndexEntry>& entries);
bool writeIndex(const std::vector<IndexEntry>& entries);
bool readIndex(std::vector<IndexEntry>& entries);
bool findIndexEntry(const ConversationId& conv,
std::vector<IndexEntry>& entries,
size_t* out_idx);
void updateIndexForMessage(const ChatMessage& msg);
void rebuildIndex();
bool loadFileHeader(File& file, FileHeader& header);
void initFileHeader(File& file);
bool readRecord(File& file, uint16_t slot, Record& rec);
bool writeRecord(File& file, uint16_t slot, const Record& rec);
void buildConversationPath(const ConversationId& conv,
char* out,
size_t out_len) const;
const char* channelName(ChannelId channel) const;
};
} // namespace chat
@@ -11,7 +11,6 @@ namespace platform::esp::arduino_common::storage
enum class SdCardBackend : uint8_t
{
None = 0,
ArduinoSd,
SdFat,
};
@@ -35,7 +34,6 @@ bool mount_sd_card(int sd_cs,
void unmount_sd_card();
bool sd_card_ready();
bool sd_card_uses_arduino_sd();
bool sd_card_uses_sdfat();
bool sd_card_is_exfat();
SdCardBackend sd_card_backend();
@@ -46,6 +44,7 @@ const char* sd_card_filesystem_name();
bool sd_exists(const char* path);
bool sd_is_directory(const char* path);
bool sd_mkdir(const char* path);
bool sd_rmdir(const char* path);
bool sd_remove(const char* path);
bool sd_rename(const char* old_path, const char* new_path);
@@ -4,12 +4,12 @@
class LilyGo_Display;
#if LV_USE_FS_POSIX != 1 || LV_FS_POSIX_LETTER != 'A'
#warning "Lvgl fs mismatch, may not be able to use fs function"
#if !defined(TRAIL_MATE_LVGL_SD_FS_LETTER) || TRAIL_MATE_LVGL_SD_FS_LETTER != 'A'
#warning "TrailMate LVGL SD fs mismatch, A: paths may not resolve"
#endif
void beginLvglHelper(LilyGo_Display& display, bool debug = false);
void lv_set_default_group(lv_group_t* group);
lv_indev_t* lv_get_touch_indev();
lv_indev_t* lv_get_keyboard_indev();
lv_indev_t* lv_get_encoder_indev();
lv_indev_t* lv_get_encoder_indev();
@@ -600,7 +600,7 @@ void init_sd_fs_driver()
}
lv_fs_drv_init(&s_sd_fs_drv);
s_sd_fs_drv.letter = LV_FS_POSIX_LETTER;
s_sd_fs_drv.letter = TRAIL_MATE_LVGL_SD_FS_LETTER;
s_sd_fs_drv.ready_cb = sd_fs_ready_cb;
s_sd_fs_drv.open_cb = sd_fs_open;
s_sd_fs_drv.close_cb = sd_fs_close;
@@ -618,8 +618,8 @@ void init_sd_fs_driver()
letters[0] = '\0';
lv_fs_get_letters(letters);
Serial.printf("[LVGL][FS] SD driver registered letter=%c ready=%d letters=%s\n",
LV_FS_POSIX_LETTER,
lv_fs_is_ready(LV_FS_POSIX_LETTER) ? 1 : 0,
TRAIL_MATE_LVGL_SD_FS_LETTER,
lv_fs_is_ready(TRAIL_MATE_LVGL_SD_FS_LETTER) ? 1 : 0,
letters);
}
} // namespace
@@ -2,7 +2,6 @@
#include "platform/esp/arduino_common/app_config_store.h"
#include <Arduino.h>
#include <SD.h>
#include "app/app_facades.h"
#include "board/GpsBoard.h"
@@ -14,11 +13,9 @@
#include "platform/esp/arduino_common/chat/infra/mesh_adapter_router.h"
#include "platform/esp/arduino_common/chat/infra/meshtastic/node_store.h"
#include "platform/esp/arduino_common/chat/infra/protocol_factory.h"
#include "platform/esp/arduino_common/chat/infra/store/log_store.h"
#include "platform/esp/arduino_common/device_identity.h"
#include "platform/esp/arduino_common/gps/gps_service.h"
#include "platform/esp/arduino_common/gps/track_recorder.h"
#include "platform/esp/arduino_common/storage/sd_card_runtime.h"
#include "platform/esp/arduino_common/team/crypto/team_crypto.h"
#include "platform/esp/arduino_common/team/event/team_app_data_event_bus_bridge.h"
#include "platform/esp/arduino_common/team/event/team_event_bus_sink.h"
@@ -113,18 +110,6 @@ void set_team_mode_active(bool active)
std::unique_ptr<chat::IChatStore> create_chat_store()
{
const bool sd_available =
::platform::esp::arduino_common::storage::sd_card_uses_arduino_sd();
if (sd_available)
{
auto log_store = std::unique_ptr<chat::LogStore>(new chat::LogStore());
if (log_store->begin(SD))
{
Serial.printf("[AppContext] chat store=LogStore (SD)\n");
return log_store;
}
}
Serial.printf("[AppContext] chat store=RamStore\n");
return std::unique_ptr<chat::IChatStore>(new chat::RamStore());
}
@@ -8,7 +8,6 @@
#include "ui/widgets/ble_pairing_popup.h"
#include <Arduino.h>
#include <Preferences.h>
#include <SD.h>
#include <algorithm>
#include <cstring>
#include <ctime>
@@ -1,818 +0,0 @@
/**
* @file log_store.cpp
* @brief Per-conversation ring log store (SD-based)
*/
#include "platform/esp/arduino_common/chat/infra/store/log_store.h"
#include "chat/infra/mesh_protocol_utils.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
namespace chat
{
namespace
{
const char* protocolTag(MeshProtocol protocol)
{
return chat::infra::meshProtocolSlug(protocol);
}
} // namespace
bool LogStore::begin(fs::FS& fs)
{
fs_ = &fs;
if (!ensureDir())
{
fs_ = nullptr;
return false;
}
std::vector<IndexEntry> entries;
if (!readIndex(entries))
{
rebuildIndex();
}
return true;
}
void LogStore::append(const ChatMessage& msg)
{
if (!fs_) return;
if (!ensureDir()) return;
ConversationId conv(msg.channel, msg.peer, msg.protocol);
char path[64];
buildConversationPath(conv, path, sizeof(path));
FileHeader header{};
bool have_header = false;
if (fs_->exists(path))
{
File rf = fs_->open(path, FILE_READ);
if (rf)
{
have_header = loadFileHeader(rf, header);
rf.close();
}
}
if (!have_header)
{
File wf = fs_->open(path, FILE_WRITE);
if (!wf)
{
return;
}
initFileHeader(wf);
header.magic = kFileMagic;
header.version = kVersion;
header.head = 0;
header.count = 0;
wf.close();
}
Record rec{};
rec.protocol = static_cast<uint8_t>(msg.protocol);
rec.channel = static_cast<uint8_t>(msg.channel);
rec.status = static_cast<uint8_t>(msg.status);
rec.text_len = static_cast<uint16_t>(std::min<size_t>(msg.text.size(), kMaxTextLen));
rec.from = msg.from;
rec.peer = msg.peer;
rec.msg_id = msg.msg_id;
rec.timestamp = msg.timestamp;
if (rec.text_len > 0)
{
memcpy(rec.text, msg.text.data(), rec.text_len);
}
File wf = fs_->open(path, FILE_WRITE);
if (!wf)
{
return;
}
if (!writeRecord(wf, header.head, rec))
{
wf.close();
return;
}
header.head = static_cast<uint16_t>((header.head + 1) % kMaxMessagesPerConv);
if (header.count < kMaxMessagesPerConv)
{
header.count = static_cast<uint16_t>(header.count + 1);
}
wf.seek(0);
wf.write(reinterpret_cast<const uint8_t*>(&header), sizeof(header));
wf.flush();
wf.close();
updateIndexForMessage(msg);
}
std::vector<ChatMessage> LogStore::loadRecent(const ConversationId& conv, size_t n)
{
std::vector<ChatMessage> out;
if (!fs_ || n == 0) return out;
char path[64];
buildConversationPath(conv, path, sizeof(path));
if (!fs_->exists(path))
{
return out;
}
File rf = fs_->open(path, FILE_READ);
if (!rf)
{
return out;
}
FileHeader header{};
if (!loadFileHeader(rf, header))
{
rf.close();
return out;
}
uint16_t count = header.count;
if (count == 0)
{
rf.close();
return out;
}
size_t to_read = std::min<size_t>(n, count);
uint16_t start = static_cast<uint16_t>(
(header.head + kMaxMessagesPerConv - to_read) % kMaxMessagesPerConv);
out.reserve(to_read);
for (size_t i = 0; i < to_read; ++i)
{
uint16_t slot = static_cast<uint16_t>((start + i) % kMaxMessagesPerConv);
Record rec{};
if (!readRecord(rf, slot, rec))
{
continue;
}
if (rec.text_len == 0)
{
continue;
}
ChatMessage msg;
msg.protocol = static_cast<MeshProtocol>(rec.protocol);
msg.channel = static_cast<ChannelId>(rec.channel);
msg.from = rec.from;
msg.peer = rec.peer;
msg.msg_id = rec.msg_id;
msg.timestamp = rec.timestamp;
msg.text.assign(rec.text, rec.text_len);
msg.status = static_cast<MessageStatus>(rec.status);
out.push_back(msg);
}
rf.close();
return out;
}
std::vector<ConversationMeta> LogStore::loadConversationPage(size_t offset,
size_t limit,
size_t* total)
{
std::vector<IndexEntry> entries;
if (!ensureIndex(entries))
{
if (total)
{
*total = 0;
}
return {};
}
std::sort(entries.begin(), entries.end(),
[](const IndexEntry& a, const IndexEntry& b)
{
return a.last_timestamp > b.last_timestamp;
});
if (total)
{
*total = entries.size();
}
size_t start = offset;
if (start >= entries.size())
{
return {};
}
size_t end = entries.size();
if (limit != 0 && start + limit < end)
{
end = start + limit;
}
std::vector<ConversationMeta> list;
list.reserve(end - start);
for (size_t i = start; i < end; ++i)
{
const IndexEntry& entry = entries[i];
ConversationMeta meta;
meta.id.protocol = static_cast<MeshProtocol>(entry.protocol);
meta.id.channel = static_cast<ChannelId>(entry.channel);
meta.id.peer = entry.peer;
meta.preview.assign(entry.preview, entry.preview_len);
meta.last_timestamp = entry.last_timestamp;
meta.unread = static_cast<int>(entry.unread);
if (entry.peer == 0)
{
meta.name = "Broadcast";
}
else
{
char buf[16];
snprintf(buf, sizeof(buf), "%04lX",
static_cast<unsigned long>(entry.peer & 0xFFFF));
meta.name = buf;
}
list.push_back(meta);
}
return list;
}
void LogStore::setUnread(const ConversationId& conv, int unread)
{
std::vector<IndexEntry> entries;
if (!ensureIndex(entries))
{
return;
}
size_t idx = 0;
if (!findIndexEntry(conv, entries, &idx))
{
return;
}
entries[idx].unread = static_cast<uint16_t>(std::max(0, unread));
writeIndex(entries);
}
int LogStore::getUnread(const ConversationId& conv) const
{
std::vector<IndexEntry> entries;
if (!const_cast<LogStore*>(this)->readIndex(entries))
{
return 0;
}
for (const auto& entry : entries)
{
if (entry.peer == conv.peer &&
entry.channel == static_cast<uint8_t>(conv.channel) &&
entry.protocol == static_cast<uint8_t>(conv.protocol))
{
return entry.unread;
}
}
return 0;
}
void LogStore::clearConversation(const ConversationId& conv)
{
if (!fs_) return;
char path[64];
buildConversationPath(conv, path, sizeof(path));
if (fs_->exists(path))
{
fs_->remove(path);
}
std::vector<IndexEntry> entries;
if (!readIndex(entries))
{
return;
}
entries.erase(std::remove_if(entries.begin(), entries.end(),
[&](const IndexEntry& entry)
{
return entry.peer == conv.peer &&
entry.channel == static_cast<uint8_t>(conv.channel) &&
entry.protocol == static_cast<uint8_t>(conv.protocol);
}),
entries.end());
writeIndex(entries);
}
void LogStore::clearAll()
{
if (!fs_) return;
if (fs_->exists(kIndexFile))
{
fs_->remove(kIndexFile);
}
File dir = fs_->open(kDir);
if (!dir)
{
return;
}
File entry = dir.openNextFile();
while (entry)
{
if (!entry.isDirectory())
{
const char* name = entry.name();
if (name && strstr(name, ".log"))
{
char path[96];
if (name[0] == '/')
{
snprintf(path, sizeof(path), "%s", name);
}
else
{
snprintf(path, sizeof(path), "%s/%s", kDir, name);
}
entry.close();
fs_->remove(path);
entry = dir.openNextFile();
continue;
}
}
entry.close();
entry = dir.openNextFile();
}
dir.close();
}
bool LogStore::updateMessageStatus(MessageId msg_id, MessageStatus status)
{
if (!fs_ || msg_id == 0)
{
return false;
}
std::vector<IndexEntry> entries;
if (!readIndex(entries))
{
return false;
}
bool updated = false;
for (auto& entry : entries)
{
ConversationId conv(static_cast<ChannelId>(entry.channel),
entry.peer,
static_cast<MeshProtocol>(entry.protocol));
char path[64];
buildConversationPath(conv, path, sizeof(path));
if (!fs_->exists(path))
{
continue;
}
File rf = fs_->open(path, FILE_READ);
if (!rf)
{
continue;
}
FileHeader header{};
if (!loadFileHeader(rf, header))
{
rf.close();
continue;
}
bool updated_in_file = false;
for (uint16_t i = 0; i < header.count; ++i)
{
uint16_t slot =
static_cast<uint16_t>((header.head + kMaxMessagesPerConv - header.count + i) %
kMaxMessagesPerConv);
Record rec{};
if (!readRecord(rf, slot, rec))
{
continue;
}
if (rec.msg_id != msg_id)
{
continue;
}
if (rec.from != 0)
{
continue;
}
rec.status = static_cast<uint8_t>(status);
rf.close();
File wf = fs_->open(path, FILE_WRITE);
if (!wf)
{
return updated;
}
writeRecord(wf, slot, rec);
wf.flush();
wf.close();
updated_in_file = true;
break;
}
if (updated_in_file)
{
updated = true;
if (entry.last_msg_id == msg_id)
{
entry.status = static_cast<uint8_t>(status);
}
break;
}
rf.close();
}
if (updated)
{
writeIndex(entries);
}
return updated;
}
bool LogStore::getMessage(MessageId msg_id, ChatMessage* out) const
{
if (!fs_ || msg_id == 0)
{
return false;
}
std::vector<IndexEntry> entries;
if (!const_cast<LogStore*>(this)->readIndex(entries))
{
return false;
}
for (const auto& entry : entries)
{
ConversationId conv(static_cast<ChannelId>(entry.channel),
entry.peer,
static_cast<MeshProtocol>(entry.protocol));
char path[64];
buildConversationPath(conv, path, sizeof(path));
if (!fs_->exists(path))
{
continue;
}
File rf = fs_->open(path, FILE_READ);
if (!rf)
{
continue;
}
FileHeader header{};
if (!const_cast<LogStore*>(this)->loadFileHeader(rf, header))
{
rf.close();
continue;
}
for (uint16_t i = 0; i < header.count; ++i)
{
uint16_t slot =
static_cast<uint16_t>((header.head + kMaxMessagesPerConv - header.count + i) %
kMaxMessagesPerConv);
Record rec{};
if (!const_cast<LogStore*>(this)->readRecord(rf, slot, rec))
{
continue;
}
if (rec.text_len == 0 || rec.msg_id != msg_id)
{
continue;
}
if (out)
{
ChatMessage msg;
msg.protocol = static_cast<MeshProtocol>(rec.protocol);
msg.channel = static_cast<ChannelId>(rec.channel);
msg.from = rec.from;
msg.peer = rec.peer;
msg.msg_id = rec.msg_id;
msg.timestamp = rec.timestamp;
msg.text.assign(rec.text, rec.text_len);
msg.status = static_cast<MessageStatus>(rec.status);
*out = msg;
}
rf.close();
return true;
}
rf.close();
}
return false;
}
bool LogStore::ensureDir()
{
if (!fs_) return false;
if (fs_->exists(kDir))
{
return true;
}
return fs_->mkdir(kDir);
}
bool LogStore::ensureIndex(std::vector<IndexEntry>& entries)
{
if (readIndex(entries))
{
return true;
}
rebuildIndex();
return readIndex(entries);
}
bool LogStore::writeIndex(const std::vector<IndexEntry>& entries)
{
if (!fs_) return false;
if (fs_->exists(kIndexFile))
{
fs_->remove(kIndexFile);
}
File wf = fs_->open(kIndexFile, FILE_WRITE);
if (!wf)
{
return false;
}
IndexHeader header{};
header.magic = kIndexMagic;
header.version = kVersion;
header.count = static_cast<uint16_t>(entries.size());
wf.write(reinterpret_cast<const uint8_t*>(&header), sizeof(header));
for (const auto& entry : entries)
{
wf.write(reinterpret_cast<const uint8_t*>(&entry), sizeof(entry));
}
wf.flush();
wf.close();
return true;
}
bool LogStore::readIndex(std::vector<IndexEntry>& entries)
{
entries.clear();
if (!fs_ || !fs_->exists(kIndexFile))
{
return false;
}
File rf = fs_->open(kIndexFile, FILE_READ);
if (!rf)
{
return false;
}
IndexHeader header{};
size_t read = rf.read(reinterpret_cast<uint8_t*>(&header), sizeof(header));
if (read != sizeof(header) || header.magic != kIndexMagic || header.version != kVersion)
{
rf.close();
return false;
}
entries.resize(header.count);
size_t expected = header.count * sizeof(IndexEntry);
size_t got = rf.read(reinterpret_cast<uint8_t*>(entries.data()), expected);
rf.close();
if (got != expected)
{
entries.clear();
return false;
}
return true;
}
bool LogStore::findIndexEntry(const ConversationId& conv,
std::vector<IndexEntry>& entries,
size_t* out_idx)
{
for (size_t i = 0; i < entries.size(); ++i)
{
if (entries[i].peer == conv.peer &&
entries[i].channel == static_cast<uint8_t>(conv.channel) &&
entries[i].protocol == static_cast<uint8_t>(conv.protocol))
{
if (out_idx)
{
*out_idx = i;
}
return true;
}
}
return false;
}
void LogStore::updateIndexForMessage(const ChatMessage& msg)
{
std::vector<IndexEntry> entries;
if (!ensureIndex(entries))
{
return;
}
ConversationId conv(msg.channel, msg.peer, msg.protocol);
size_t idx = 0;
if (!findIndexEntry(conv, entries, &idx))
{
IndexEntry entry{};
entry.protocol = static_cast<uint8_t>(msg.protocol);
entry.channel = static_cast<uint8_t>(msg.channel);
entry.peer = msg.peer;
entries.push_back(entry);
idx = entries.size() - 1;
}
IndexEntry& entry = entries[idx];
entry.protocol = static_cast<uint8_t>(msg.protocol);
entry.channel = static_cast<uint8_t>(msg.channel);
entry.status = static_cast<uint8_t>(msg.status);
entry.peer = msg.peer;
entry.last_msg_id = msg.msg_id;
entry.last_timestamp = msg.timestamp;
entry.last_from = msg.from;
entry.preview_len = static_cast<uint16_t>(std::min<size_t>(msg.text.size(), kPreviewLen));
memset(entry.preview, 0, sizeof(entry.preview));
if (entry.preview_len > 0)
{
memcpy(entry.preview, msg.text.data(), entry.preview_len);
}
if (msg.status == MessageStatus::Incoming)
{
entry.unread = static_cast<uint16_t>(entry.unread + 1);
}
writeIndex(entries);
}
void LogStore::rebuildIndex()
{
if (!fs_) return;
std::vector<IndexEntry> entries;
File dir = fs_->open(kDir);
if (!dir)
{
return;
}
File entry = dir.openNextFile();
while (entry)
{
if (!entry.isDirectory())
{
const char* name = entry.name();
if (name && strstr(name, ".log"))
{
FileHeader header{};
if (!loadFileHeader(entry, header))
{
entry = dir.openNextFile();
continue;
}
ChatMessage last_msg;
bool have_last = false;
for (uint16_t i = 0; i < header.count; ++i)
{
uint16_t slot =
static_cast<uint16_t>((header.head + kMaxMessagesPerConv - header.count + i) %
kMaxMessagesPerConv);
Record rec{};
if (!readRecord(entry, slot, rec))
{
continue;
}
if (rec.text_len == 0)
{
continue;
}
ChatMessage msg;
msg.protocol = static_cast<MeshProtocol>(rec.protocol);
msg.channel = static_cast<ChannelId>(rec.channel);
msg.from = rec.from;
msg.peer = rec.peer;
msg.msg_id = rec.msg_id;
msg.timestamp = rec.timestamp;
msg.text.assign(rec.text, rec.text_len);
msg.status = static_cast<MessageStatus>(rec.status);
if (!have_last || msg.timestamp >= last_msg.timestamp)
{
last_msg = msg;
have_last = true;
}
}
if (have_last)
{
IndexEntry idx{};
idx.protocol = static_cast<uint8_t>(last_msg.protocol);
idx.channel = static_cast<uint8_t>(last_msg.channel);
idx.status = static_cast<uint8_t>(last_msg.status);
idx.unread = 0;
idx.peer = last_msg.peer;
idx.last_msg_id = last_msg.msg_id;
idx.last_timestamp = last_msg.timestamp;
idx.last_from = last_msg.from;
idx.preview_len =
static_cast<uint16_t>(std::min<size_t>(last_msg.text.size(), kPreviewLen));
if (idx.preview_len > 0)
{
memcpy(idx.preview, last_msg.text.data(), idx.preview_len);
}
entries.push_back(idx);
}
}
}
entry.close();
entry = dir.openNextFile();
}
dir.close();
writeIndex(entries);
}
bool LogStore::loadFileHeader(File& file, FileHeader& header)
{
if (!file)
{
return false;
}
size_t size = file.size();
if (size < sizeof(FileHeader))
{
return false;
}
file.seek(0);
size_t read = file.read(reinterpret_cast<uint8_t*>(&header), sizeof(header));
if (read != sizeof(header))
{
return false;
}
return header.magic == kFileMagic && header.version == kVersion;
}
void LogStore::initFileHeader(File& file)
{
FileHeader header{};
header.magic = kFileMagic;
header.version = kVersion;
header.head = 0;
header.count = 0;
file.seek(0);
file.write(reinterpret_cast<const uint8_t*>(&header), sizeof(header));
file.flush();
}
bool LogStore::readRecord(File& file, uint16_t slot, Record& rec)
{
size_t offset = sizeof(FileHeader) + static_cast<size_t>(slot) * sizeof(Record);
if (file.size() < offset + sizeof(Record))
{
return false;
}
file.seek(offset);
size_t read = file.read(reinterpret_cast<uint8_t*>(&rec), sizeof(rec));
return read == sizeof(rec);
}
bool LogStore::writeRecord(File& file, uint16_t slot, const Record& rec)
{
size_t offset = sizeof(FileHeader) + static_cast<size_t>(slot) * sizeof(Record);
file.seek(offset);
size_t written = file.write(reinterpret_cast<const uint8_t*>(&rec), sizeof(rec));
return written == sizeof(rec);
}
void LogStore::buildConversationPath(const ConversationId& conv,
char* out,
size_t out_len) const
{
if (!out || out_len == 0)
{
return;
}
if (conv.peer == 0)
{
const char* name = channelName(conv.channel);
snprintf(out, out_len, "%s/%s_broadcast_%s.log", kDir, protocolTag(conv.protocol), name);
}
else
{
snprintf(out, out_len, "%s/%s_n_%08lX.log", kDir, protocolTag(conv.protocol),
static_cast<unsigned long>(conv.peer));
}
}
const char* LogStore::channelName(ChannelId channel) const
{
switch (channel)
{
case ChannelId::PRIMARY:
return "LongFast";
case ChannelId::SECONDARY:
return "Squad";
default:
return "Unknown";
}
}
} // namespace chat
@@ -147,7 +147,7 @@ struct StorageFacade
}
};
StorageFacade SD;
StorageFacade s_tab5_storage;
constexpr int FILE_WRITE = 1;
constexpr int CARD_NONE = 0;
constexpr int POWER_SPEAK = 0;
@@ -338,9 +338,9 @@ const char* get_saved_path()
bool ensure_sstv_dir()
{
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
if (!SD.exists("/sstv"))
if (!s_tab5_storage.exists("/sstv"))
{
if (!SD.mkdir("/sstv"))
if (!s_tab5_storage.mkdir("/sstv"))
{
return false;
}
@@ -386,7 +386,7 @@ bool build_save_path(char* out_path, size_t out_len)
static_cast<unsigned long>(millis()), i);
}
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
if (!SD.exists(out_path))
if (!s_tab5_storage.exists(out_path))
#else
if (!::platform::esp::arduino_common::storage::sd_exists(out_path))
#endif
@@ -482,7 +482,7 @@ bool save_frame_to_sd()
return false;
}
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
if (SD.cardType() == CARD_NONE)
if (s_tab5_storage.cardType() == CARD_NONE)
#else
if (!::platform::esp::arduino_common::storage::sd_card_ready())
#endif
@@ -511,7 +511,7 @@ bool save_frame_to_sd()
const uint32_t data_offset = 14 + 40;
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
File f = SD.open(path, FILE_WRITE);
File f = s_tab5_storage.open(path, FILE_WRITE);
if (!f)
#else
::platform::esp::arduino_common::storage::SdRuntimeFile f;
@@ -1,8 +1,7 @@
#include "platform/esp/arduino_common/storage/sd_card_runtime.h"
#include <Arduino.h>
#include <FS.h>
#include <SD.h>
#include <SPI.h>
#ifndef DISABLE_FS_H_WARNING
#define DISABLE_FS_H_WARNING 1
#endif
@@ -20,9 +19,14 @@ namespace
{
constexpr uint8_t kRuntimeCardNone = 0;
constexpr uint8_t kRuntimeCardSd = 2;
constexpr uint8_t kRuntimeCardSdhc = 3;
constexpr uint8_t kRuntimeCardUnknown = 4;
constexpr uint32_t kSdSectorSize = 512;
constexpr uint32_t kDefaultSharedSpiSdHz = 4000000U;
constexpr uint32_t kMaxSharedSpiSdHz = 10000000U;
constexpr uint32_t kSdInitHz = 400000U;
constexpr uint8_t kSdR1IdleState = 0x01U;
#ifndef TRAIL_MATE_SD_IO_LOG_ENABLE
#define TRAIL_MATE_SD_IO_LOG_ENABLE 1
@@ -50,8 +54,6 @@ const char* backend_name_from_info()
{
switch (s_info.backend)
{
case SdCardBackend::ArduinoSd:
return "arduino";
case SdCardBackend::SdFat:
return "sdfat";
case SdCardBackend::None:
@@ -151,17 +153,17 @@ uint8_t card_type_from_sdfat(SdFs& fs)
const uint8_t type = card->type();
if (type == SD_CARD_TYPE_SD1)
{
return CARD_SD;
return kRuntimeCardSd;
}
if (type == SD_CARD_TYPE_SD2)
{
return CARD_SD;
return kRuntimeCardSd;
}
if (type == SD_CARD_TYPE_SDHC)
{
return CARD_SDHC;
return kRuntimeCardSdhc;
}
return CARD_UNKNOWN;
return kRuntimeCardUnknown;
}
bool path_empty(const char* path)
@@ -234,17 +236,87 @@ void reset_info()
s_info = SdCardInfo{};
}
void record_arduino_info()
void sd_clock_bytes(SPIClass& spi, uint8_t count)
{
s_info = SdCardInfo{};
s_info.backend = SdCardBackend::ArduinoSd;
s_info.card_type = SD.cardType();
s_info.fat_type = 0;
s_info.sector_size = SD.sectorSize();
s_info.sector_count = SD.numSectors();
s_info.card_size_bytes = SD.cardSize();
s_info.total_bytes = SD.totalBytes();
s_info.used_bytes = SD.usedBytes();
for (uint8_t i = 0; i < count; ++i)
{
spi.transfer(0xFF);
}
}
bool sd_wait_not_busy(SPIClass& spi, uint32_t timeout_ms)
{
const uint32_t start_ms = millis();
do
{
if (spi.transfer(0xFF) != 0x00)
{
return true;
}
delay(1);
} while (static_cast<uint32_t>(millis() - start_ms) < timeout_ms);
return false;
}
uint8_t sd_send_cmd0(SPIClass& spi)
{
static constexpr uint8_t kCmd0Packet[] = {0x40, 0, 0, 0, 0, 0x95};
for (uint8_t byte : kCmd0Packet)
{
spi.transfer(byte);
}
for (uint8_t i = 0; i < 16; ++i)
{
const uint8_t token = spi.transfer(0xFF);
if ((token & 0x80U) == 0)
{
return token;
}
}
return 0xFF;
}
bool sd_preflight_go_idle(int sd_cs, SPIClass& spi)
{
uint8_t last_token = 0xFF;
bool ok = false;
uint8_t attempt = 0;
uint8_t attempts_used = 0;
spi.beginTransaction(SPISettings(kSdInitHz, MSBFIRST, SPI_MODE0));
digitalWrite(sd_cs, HIGH);
sd_clock_bytes(spi, 20);
for (attempt = 1; attempt <= 4; ++attempt)
{
attempts_used = attempt;
digitalWrite(sd_cs, LOW);
const bool ready = sd_wait_not_busy(spi, 500);
last_token = sd_send_cmd0(spi);
digitalWrite(sd_cs, HIGH);
sd_clock_bytes(spi, 2);
if (last_token == kSdR1IdleState)
{
ok = true;
break;
}
Serial.printf("[SD] SdFat preflight CMD0 retry=%u ready=%d token=0x%02X\n",
static_cast<unsigned>(attempt),
ready ? 1 : 0,
static_cast<unsigned>(last_token));
delay(25);
}
spi.endTransaction();
Serial.printf("[SD] SdFat preflight CMD0 -> %d token=0x%02X attempts=%u\n",
ok ? 1 : 0,
static_cast<unsigned>(last_token),
static_cast<unsigned>(attempts_used));
delay(2);
return ok;
}
void record_sdfat_info()
@@ -282,8 +354,9 @@ bool mount_sd_card(int sd_cs,
const char* mount_point,
uint8_t max_files)
{
(void)mount_point;
(void)max_files;
clear_sdfat();
SD.end();
reset_info();
const uint32_t effective_hz = sanitize_sd_spi_hz(spi_hz);
@@ -294,25 +367,14 @@ bool mount_sd_card(int sd_cs,
static_cast<unsigned long>(effective_hz));
}
const bool arduino_ok = SD.begin(sd_cs, spi, effective_hz, mount_point, max_files, false);
Serial.printf("[SD] Arduino SD.begin hz=%lu -> %d\n",
static_cast<unsigned long>(effective_hz),
arduino_ok ? 1 : 0);
if (arduino_ok && SD.cardType() != CARD_NONE && SD.sectorSize() != 0)
const bool preflight_ok = sd_preflight_go_idle(sd_cs, spi);
if (!preflight_ok)
{
record_arduino_info();
Serial.printf("[SD] backend=arduino fs=fat card=%llu MB total=%llu MB sectors=%lu sector_size=%lu\n",
static_cast<unsigned long long>(s_info.card_size_bytes / (1024ULL * 1024ULL)),
static_cast<unsigned long long>(s_info.total_bytes / (1024ULL * 1024ULL)),
static_cast<unsigned long>(s_info.sector_count),
static_cast<unsigned long>(s_info.sector_size));
return true;
Serial.println("[SD] SdFat preflight failed; continuing to SdFat.begin for detailed error");
}
SD.end();
delay(10);
const bool sdfat_ok = s_sdfat.begin(SdSpiConfig(sd_cs, SHARED_SPI, effective_hz, &spi));
const bool sdfat_ok = s_sdfat.begin(
SdSpiConfig(sd_cs, SHARED_SPI | USER_SPI_BEGIN, effective_hz, &spi));
Serial.printf("[SD] SdFat.begin hz=%lu -> %d\n",
static_cast<unsigned long>(effective_hz),
sdfat_ok ? 1 : 0);
@@ -337,10 +399,6 @@ bool mount_sd_card(int sd_cs,
void unmount_sd_card()
{
if (s_info.backend == SdCardBackend::ArduinoSd)
{
SD.end();
}
clear_sdfat();
reset_info();
}
@@ -351,11 +409,6 @@ bool sd_card_ready()
s_info.card_type != kRuntimeCardNone;
}
bool sd_card_uses_arduino_sd()
{
return s_info.backend == SdCardBackend::ArduinoSd;
}
bool sd_card_uses_sdfat()
{
return s_info.backend == SdCardBackend::SdFat;
@@ -380,8 +433,6 @@ const char* sd_card_backend_name()
{
switch (s_info.backend)
{
case SdCardBackend::ArduinoSd:
return "arduino";
case SdCardBackend::SdFat:
return "sdfat";
case SdCardBackend::None:
@@ -392,10 +443,6 @@ const char* sd_card_backend_name()
const char* sd_card_filesystem_name()
{
if (s_info.backend == SdCardBackend::ArduinoSd)
{
return "fat";
}
if (s_info.backend == SdCardBackend::SdFat)
{
switch (s_info.fat_type)
@@ -420,12 +467,6 @@ bool sd_exists(const char* path)
const char* normalized = normalize_sd_path(path);
const uint32_t start_ms = sd_io_begin("exists", normalized);
bool result = false;
if (s_info.backend == SdCardBackend::ArduinoSd)
{
result = SD.exists(normalized);
sd_io_end("exists", normalized, start_ms, true, 0, result ? 1 : 0);
return result;
}
if (s_info.backend == SdCardBackend::SdFat)
{
result = s_sdfat.exists(normalized);
@@ -441,14 +482,6 @@ bool sd_is_directory(const char* path)
const char* normalized = normalize_sd_path(path);
const uint32_t start_ms = sd_io_begin("is_dir", normalized);
bool result = false;
if (s_info.backend == SdCardBackend::ArduinoSd)
{
File dir = SD.open(normalized, FILE_READ);
result = dir && dir.isDirectory();
dir.close();
sd_io_end("is_dir", normalized, start_ms, true, 0, result ? 1 : 0);
return result;
}
if (s_info.backend == SdCardBackend::SdFat)
{
FsFile dir = s_sdfat.open(normalized, O_RDONLY);
@@ -466,12 +499,6 @@ bool sd_mkdir(const char* path)
const char* normalized = normalize_sd_path(path);
const uint32_t start_ms = sd_io_begin("mkdir", normalized);
bool result = false;
if (s_info.backend == SdCardBackend::ArduinoSd)
{
result = SD.mkdir(normalized);
sd_io_end("mkdir", normalized, start_ms, result);
return result;
}
if (s_info.backend == SdCardBackend::SdFat)
{
result = s_sdfat.mkdir(normalized, true);
@@ -482,17 +509,26 @@ bool sd_mkdir(const char* path)
return false;
}
bool sd_rmdir(const char* path)
{
const char* normalized = normalize_sd_path(path);
const uint32_t start_ms = sd_io_begin("rmdir", normalized);
bool result = false;
if (s_info.backend == SdCardBackend::SdFat)
{
result = s_sdfat.rmdir(normalized);
sd_io_end("rmdir", normalized, start_ms, result);
return result;
}
sd_io_end("rmdir", normalized, start_ms, false, 0, -1);
return false;
}
bool sd_remove(const char* path)
{
const char* normalized = normalize_sd_path(path);
const uint32_t start_ms = sd_io_begin("remove", normalized);
bool result = false;
if (s_info.backend == SdCardBackend::ArduinoSd)
{
result = SD.remove(normalized);
sd_io_end("remove", normalized, start_ms, result);
return result;
}
if (s_info.backend == SdCardBackend::SdFat)
{
result = s_sdfat.remove(normalized);
@@ -509,12 +545,6 @@ bool sd_rename(const char* old_path, const char* new_path)
const char* normalized_new = normalize_sd_path(new_path);
const uint32_t start_ms = sd_io_begin("rename", normalized_old);
bool result = false;
if (s_info.backend == SdCardBackend::ArduinoSd)
{
result = SD.rename(normalized_old, normalized_new);
sd_io_end("rename", normalized_old, start_ms, result, 0, result ? 0 : -1);
return result;
}
if (s_info.backend == SdCardBackend::SdFat)
{
result = s_sdfat.rename(normalized_old, normalized_new);
@@ -528,7 +558,6 @@ bool sd_rename(const char* old_path, const char* new_path)
class SdRuntimeFile::Impl
{
public:
File arduino_file;
FsFile sdfat_file;
SdCardBackend backend = SdCardBackend::None;
char path[128]{};
@@ -558,14 +587,6 @@ bool SdRuntimeFile::open(const char* path, const char* mode)
copy_path(impl_->path, sizeof(impl_->path), normalized);
copy_path(impl_->mode, sizeof(impl_->mode), mode ? mode : "r");
const uint32_t start_ms = sd_io_begin("file_open", impl_->path);
if (s_info.backend == SdCardBackend::ArduinoSd)
{
impl_->arduino_file = SD.open(normalized, mode ? mode : FILE_READ);
impl_->backend = impl_->arduino_file ? SdCardBackend::ArduinoSd : SdCardBackend::None;
sd_io_end("file_open", impl_->path, start_ms, impl_->backend == SdCardBackend::ArduinoSd);
return impl_->backend == SdCardBackend::ArduinoSd;
}
if (s_info.backend == SdCardBackend::SdFat)
{
impl_->sdfat_file = s_sdfat.open(normalized, sdfat_open_flags(mode));
@@ -584,13 +605,7 @@ void SdRuntimeFile::close()
{
return;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
const uint32_t start_ms = sd_io_begin("file_close", impl_->path);
impl_->arduino_file.close();
sd_io_end("file_close", impl_->path, start_ms, true);
}
else if (impl_->backend == SdCardBackend::SdFat)
if (impl_->backend == SdCardBackend::SdFat)
{
const uint32_t start_ms = sd_io_begin("file_close", impl_->path);
impl_->sdfat_file.close();
@@ -612,10 +627,6 @@ int SdRuntimeFile::available() const
{
return 0;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
return impl_->arduino_file.available();
}
if (impl_->backend == SdCardBackend::SdFat)
{
return impl_->sdfat_file.available();
@@ -629,13 +640,6 @@ int SdRuntimeFile::read(void* buffer, std::size_t bytes_to_read)
{
return 0;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
const uint32_t start_ms = sd_io_begin("file_read", impl_->path, bytes_to_read);
const int result = impl_->arduino_file.read(static_cast<uint8_t*>(buffer), bytes_to_read);
sd_io_end("file_read", impl_->path, start_ms, result >= 0, bytes_to_read, result);
return result;
}
if (impl_->backend == SdCardBackend::SdFat)
{
const uint32_t start_ms = sd_io_begin("file_read", impl_->path, bytes_to_read);
@@ -652,10 +656,6 @@ int SdRuntimeFile::read_byte()
{
return -1;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
return impl_->arduino_file.read();
}
if (impl_->backend == SdCardBackend::SdFat)
{
return impl_->sdfat_file.read();
@@ -669,13 +669,6 @@ std::size_t SdRuntimeFile::read_bytes(char* buffer, std::size_t bytes_to_read)
{
return 0;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
const uint32_t start_ms = sd_io_begin("file_read_bytes", impl_->path, bytes_to_read);
const std::size_t result = impl_->arduino_file.readBytes(buffer, bytes_to_read);
sd_io_end("file_read_bytes", impl_->path, start_ms, true, bytes_to_read, result);
return result;
}
if (impl_->backend == SdCardBackend::SdFat)
{
const uint32_t start_ms = sd_io_begin("file_read_bytes", impl_->path, bytes_to_read);
@@ -692,14 +685,6 @@ std::size_t SdRuntimeFile::write(const void* buffer, std::size_t bytes_to_write)
{
return 0;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
const uint32_t start_ms = sd_io_begin("file_write", impl_->path, bytes_to_write);
const std::size_t result =
impl_->arduino_file.write(static_cast<const uint8_t*>(buffer), bytes_to_write);
sd_io_end("file_write", impl_->path, start_ms, result == bytes_to_write, bytes_to_write, result);
return result;
}
if (impl_->backend == SdCardBackend::SdFat)
{
const uint32_t start_ms = sd_io_begin("file_write", impl_->path, bytes_to_write);
@@ -716,10 +701,6 @@ std::size_t SdRuntimeFile::write_byte(uint8_t value)
{
return 0;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
return impl_->arduino_file.write(value);
}
if (impl_->backend == SdCardBackend::SdFat)
{
return impl_->sdfat_file.write(value);
@@ -733,10 +714,6 @@ std::size_t SdRuntimeFile::print(const char* text)
{
return 0;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
return impl_->arduino_file.print(text);
}
if (impl_->backend == SdCardBackend::SdFat)
{
return impl_->sdfat_file.print(text);
@@ -751,10 +728,6 @@ std::size_t SdRuntimeFile::print(double value, int digits)
return 0;
}
const uint8_t precision = digits < 0 ? 0 : static_cast<uint8_t>(digits);
if (impl_->backend == SdCardBackend::ArduinoSd)
{
return impl_->arduino_file.print(value, precision);
}
if (impl_->backend == SdCardBackend::SdFat)
{
return impl_->sdfat_file.print(value, precision);
@@ -793,10 +766,6 @@ bool SdRuntimeFile::seek(uint64_t offset)
{
return false;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
return impl_->arduino_file.seek(offset);
}
if (impl_->backend == SdCardBackend::SdFat)
{
return impl_->sdfat_file.seekSet(offset);
@@ -810,10 +779,6 @@ uint64_t SdRuntimeFile::position() const
{
return 0;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
return impl_->arduino_file.position();
}
if (impl_->backend == SdCardBackend::SdFat)
{
return impl_->sdfat_file.curPosition();
@@ -827,10 +792,6 @@ uint64_t SdRuntimeFile::size() const
{
return 0;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
return impl_->arduino_file.size();
}
if (impl_->backend == SdCardBackend::SdFat)
{
return impl_->sdfat_file.fileSize();
@@ -844,13 +805,6 @@ bool SdRuntimeFile::flush()
{
return false;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
const uint32_t start_ms = sd_io_begin("file_flush", impl_->path);
impl_->arduino_file.flush();
sd_io_end("file_flush", impl_->path, start_ms, true);
return true;
}
if (impl_->backend == SdCardBackend::SdFat)
{
const uint32_t start_ms = sd_io_begin("file_flush", impl_->path);
@@ -864,7 +818,6 @@ bool SdRuntimeFile::flush()
class SdRuntimeDir::Impl
{
public:
File arduino_dir;
FsFile sdfat_dir;
SdCardBackend backend = SdCardBackend::None;
char path[128]{};
@@ -891,15 +844,6 @@ bool SdRuntimeDir::open(const char* path)
const char* normalized = normalize_sd_path(path);
copy_path(impl_->path, sizeof(impl_->path), normalized);
const uint32_t start_ms = sd_io_begin("dir_open", impl_->path);
if (s_info.backend == SdCardBackend::ArduinoSd)
{
impl_->arduino_dir = SD.open(normalized, FILE_READ);
impl_->backend = (impl_->arduino_dir && impl_->arduino_dir.isDirectory())
? SdCardBackend::ArduinoSd
: SdCardBackend::None;
sd_io_end("dir_open", impl_->path, start_ms, impl_->backend == SdCardBackend::ArduinoSd);
return impl_->backend == SdCardBackend::ArduinoSd;
}
if (s_info.backend == SdCardBackend::SdFat)
{
impl_->sdfat_dir = s_sdfat.open(normalized, O_RDONLY);
@@ -919,13 +863,7 @@ void SdRuntimeDir::close()
{
return;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
const uint32_t start_ms = sd_io_begin("dir_close", impl_->path);
impl_->arduino_dir.close();
sd_io_end("dir_close", impl_->path, start_ms, true);
}
else if (impl_->backend == SdCardBackend::SdFat)
if (impl_->backend == SdCardBackend::SdFat)
{
const uint32_t start_ms = sd_io_begin("dir_close", impl_->path);
impl_->sdfat_dir.close();
@@ -952,26 +890,6 @@ bool SdRuntimeDir::read_next(char* name, std::size_t name_size, bool* is_dir)
*is_dir = false;
}
if (impl_->backend == SdCardBackend::ArduinoSd)
{
const uint32_t start_ms = sd_io_begin("dir_read", impl_->path);
File entry = impl_->arduino_dir.openNextFile();
if (!entry)
{
sd_io_end("dir_read", impl_->path, start_ms, true, 0, 0);
return false;
}
const char* raw_name = entry.name();
std::snprintf(name, name_size, "%s", raw_name ? raw_name : "");
if (is_dir != nullptr)
{
*is_dir = entry.isDirectory();
}
entry.close();
sd_io_end("dir_read", impl_->path, start_ms, true, 0, name[0] != '\0' ? 1 : 0);
return name[0] != '\0';
}
if (impl_->backend == SdCardBackend::SdFat)
{
const uint32_t start_ms = sd_io_begin("dir_read", impl_->path);
@@ -1000,12 +918,6 @@ bool sd_read_raw(uint32_t lba, uint8_t* buffer)
std::snprintf(path, sizeof(path), "raw:%lu", static_cast<unsigned long>(lba));
const uint32_t start_ms = sd_io_begin("raw_read", path, kSdSectorSize);
bool result = false;
if (s_info.backend == SdCardBackend::ArduinoSd)
{
result = SD.readRAW(buffer, lba);
sd_io_end("raw_read", path, start_ms, result, kSdSectorSize);
return result;
}
if (s_info.backend == SdCardBackend::SdFat && s_sdfat.card() != nullptr)
{
result = s_sdfat.card()->readSector(lba, buffer);
@@ -1022,12 +934,6 @@ bool sd_write_raw(uint32_t lba, const uint8_t* buffer)
std::snprintf(path, sizeof(path), "raw:%lu", static_cast<unsigned long>(lba));
const uint32_t start_ms = sd_io_begin("raw_write", path, kSdSectorSize);
bool result = false;
if (s_info.backend == SdCardBackend::ArduinoSd)
{
result = SD.writeRAW(const_cast<uint8_t*>(buffer), lba);
sd_io_end("raw_write", path, start_ms, result, kSdSectorSize);
return result;
}
if (s_info.backend == SdCardBackend::SdFat && s_sdfat.card() != nullptr)
{
result = s_sdfat.card()->writeSector(lba, buffer);
@@ -51,7 +51,7 @@ extern "C" esp_err_t esp_crt_bundle_attach(void* conf);
#if defined(ARDUINO)
#include <Arduino.h>
#include <FS.h>
#include <SD.h>
#include "platform/esp/arduino_common/storage/sd_card_runtime.h"
#else
#include "platform/esp/idf_common/bsp_runtime.h"
#endif
@@ -352,6 +352,7 @@ int compare_versions(const std::string& lhs, const std::string& rhs)
struct ArduinoStorageBinding
{
::fs::FS* volume = nullptr;
bool sd_runtime = false;
std::string volume_path;
const char* storage = nullptr;
};
@@ -439,7 +440,7 @@ bool resolve_storage_binding(const std::string& logical_path,
return false;
}
out.volume = &SD;
out.sd_runtime = true;
out.storage = kStorageSd;
out.volume_path = is_explicit_sd_logical_path(logical_path)
? strip_mount_prefix(logical_path, "/sd")
@@ -522,10 +523,10 @@ std::string host_path_from_normalized_lvgl_path(const std::string& normalized_pa
return std::string("/fs") + suffix;
}
#if LV_USE_FS_POSIX
if (letter == LV_FS_POSIX_LETTER)
#if defined(TRAIL_MATE_LVGL_SD_FS_LETTER)
if (letter == TRAIL_MATE_LVGL_SD_FS_LETTER)
{
std::string root = LV_FS_POSIX_PATH;
std::string root = TRAIL_MATE_LVGL_SD_FS_PATH;
if (root.empty() || root == "/")
{
return suffix;
@@ -583,17 +584,30 @@ bool ensure_dir_recursive(const std::string& logical_dir)
current.push_back('/');
current += segment;
File existing = binding.volume->open(current.c_str(), FILE_READ);
const bool exists = static_cast<bool>(existing);
const bool is_dir = exists && existing.isDirectory();
if (existing)
bool exists = false;
bool is_dir = false;
if (binding.sd_runtime)
{
existing.close();
exists = ::platform::esp::arduino_common::storage::sd_exists(current.c_str());
is_dir = exists && ::platform::esp::arduino_common::storage::sd_is_directory(current.c_str());
}
else
{
File existing = binding.volume->open(current.c_str(), FILE_READ);
exists = static_cast<bool>(existing);
is_dir = exists && existing.isDirectory();
if (existing)
{
existing.close();
}
}
if (!exists)
{
if (!binding.volume->mkdir(current.c_str()))
const bool mkdir_ok = binding.sd_runtime
? ::platform::esp::arduino_common::storage::sd_mkdir(current.c_str())
: binding.volume->mkdir(current.c_str());
if (!mkdir_ok)
{
std::printf("[Packs][Storage] mkdir failed logical=%s storage=%s path=%s\n",
logical_dir.c_str(),
@@ -629,6 +643,12 @@ bool logical_file_exists(const std::string& logical_path)
return false;
}
if (binding.sd_runtime)
{
return ::platform::esp::arduino_common::storage::sd_exists(binding.volume_path.c_str()) &&
!::platform::esp::arduino_common::storage::sd_is_directory(binding.volume_path.c_str());
}
File file = binding.volume->open(binding.volume_path.c_str(), FILE_READ);
const bool exists = static_cast<bool>(file) && !file.isDirectory();
if (file)
@@ -646,6 +666,11 @@ bool logical_dir_exists(const std::string& logical_path)
return false;
}
if (binding.sd_runtime)
{
return ::platform::esp::arduino_common::storage::sd_is_directory(binding.volume_path.c_str());
}
File dir = binding.volume->open(binding.volume_path.c_str(), FILE_READ);
const bool exists = static_cast<bool>(dir) && dir.isDirectory();
if (dir)
@@ -718,6 +743,45 @@ bool write_binary_file(const std::string& logical_path, const void* data, std::s
return false;
}
if (binding.sd_runtime)
{
::platform::esp::arduino_common::storage::SdRuntimeFile file;
if (!file.open(binding.volume_path.c_str(), "w"))
{
std::printf("[Packs][Storage] open for write failed logical=%s storage=%s path=%s len=%lu\n",
logical_path.c_str(),
binding.storage ? binding.storage : "<none>",
binding.volume_path.c_str(),
static_cast<unsigned long>(len));
return false;
}
const std::uint8_t* bytes = static_cast<const std::uint8_t*>(data);
std::size_t written = 0;
while (written < len)
{
const std::size_t chunk = std::min(kFileWriteChunkBytes, len - written);
const std::size_t chunk_written = file.write(bytes + written, chunk);
if (chunk_written != chunk)
{
std::printf("[Packs][Storage] write failed logical=%s storage=%s path=%s offset=%lu chunk=%lu wrote=%lu total=%lu\n",
logical_path.c_str(),
binding.storage ? binding.storage : "<none>",
binding.volume_path.c_str(),
static_cast<unsigned long>(written),
static_cast<unsigned long>(chunk),
static_cast<unsigned long>(chunk_written),
static_cast<unsigned long>(len));
file.close();
return false;
}
written += chunk_written;
}
file.flush();
file.close();
return true;
}
File file = binding.volume->open(binding.volume_path.c_str(), FILE_WRITE);
if (!file)
{
@@ -770,6 +834,30 @@ bool read_binary_file(const std::string& logical_path, std::vector<std::uint8_t>
return false;
}
if (binding.sd_runtime)
{
if (::platform::esp::arduino_common::storage::sd_is_directory(binding.volume_path.c_str()))
{
return false;
}
::platform::esp::arduino_common::storage::SdRuntimeFile file;
if (!file.open(binding.volume_path.c_str(), "r"))
{
return false;
}
const std::size_t size = static_cast<std::size_t>(file.size());
out.resize(size);
const int read = file.read(out.data(), size);
file.close();
if (read < 0 || static_cast<std::size_t>(read) != size)
{
out.clear();
return false;
}
return true;
}
File file = binding.volume->open(binding.volume_path.c_str(), FILE_READ);
if (!file || file.isDirectory())
{
@@ -802,6 +890,23 @@ bool logical_file_size(const std::string& logical_path, std::size_t& out_size)
return false;
}
if (binding.sd_runtime)
{
if (::platform::esp::arduino_common::storage::sd_is_directory(binding.volume_path.c_str()))
{
return false;
}
::platform::esp::arduino_common::storage::SdRuntimeFile file;
if (!file.open(binding.volume_path.c_str(), "r"))
{
return false;
}
out_size = static_cast<std::size_t>(file.size());
file.close();
return true;
}
File file = binding.volume->open(binding.volume_path.c_str(), FILE_READ);
if (!file || file.isDirectory())
{
@@ -856,12 +961,22 @@ bool remove_file_if_exists(const std::string& logical_path)
return false;
}
File file = binding.volume->open(binding.volume_path.c_str(), FILE_READ);
const bool exists = static_cast<bool>(file);
const bool is_dir = exists && file.isDirectory();
if (file)
bool exists = false;
bool is_dir = false;
if (binding.sd_runtime)
{
file.close();
exists = ::platform::esp::arduino_common::storage::sd_exists(binding.volume_path.c_str());
is_dir = exists && ::platform::esp::arduino_common::storage::sd_is_directory(binding.volume_path.c_str());
}
else
{
File file = binding.volume->open(binding.volume_path.c_str(), FILE_READ);
exists = static_cast<bool>(file);
is_dir = exists && file.isDirectory();
if (file)
{
file.close();
}
}
if (!exists)
{
@@ -871,7 +986,10 @@ bool remove_file_if_exists(const std::string& logical_path)
{
return false;
}
if (!binding.volume->remove(binding.volume_path.c_str()))
const bool remove_ok = binding.sd_runtime
? ::platform::esp::arduino_common::storage::sd_remove(binding.volume_path.c_str())
: binding.volume->remove(binding.volume_path.c_str());
if (!remove_ok)
{
std::printf("[Packs][Storage] remove file failed logical=%s storage=%s path=%s\n",
logical_path.c_str(),
@@ -895,6 +1013,52 @@ bool remove_dir_recursive_if_exists(const std::string& logical_path)
return false;
}
if (binding.sd_runtime)
{
if (!::platform::esp::arduino_common::storage::sd_exists(binding.volume_path.c_str()))
{
return true;
}
if (!::platform::esp::arduino_common::storage::sd_is_directory(binding.volume_path.c_str()))
{
return ::platform::esp::arduino_common::storage::sd_remove(binding.volume_path.c_str());
}
::platform::esp::arduino_common::storage::SdRuntimeDir dir;
if (!dir.open(binding.volume_path.c_str()))
{
return false;
}
char name_buf[128];
bool child_is_dir = false;
while (dir.read_next(name_buf, sizeof(name_buf), &child_is_dir))
{
const std::string name = file_entry_name(name_buf);
if (!name.empty())
{
const std::string child_logical = join_logical_path(logical_path, name);
const bool ok = child_is_dir ? remove_dir_recursive_if_exists(child_logical)
: remove_file_if_exists(child_logical);
if (!ok)
{
dir.close();
return false;
}
}
}
dir.close();
if (!::platform::esp::arduino_common::storage::sd_rmdir(binding.volume_path.c_str()))
{
std::printf("[Packs][Storage] rmdir failed logical=%s storage=%s path=%s\n",
logical_path.c_str(),
binding.storage ? binding.storage : "<none>",
binding.volume_path.c_str());
return false;
}
return true;
}
File node = binding.volume->open(binding.volume_path.c_str(), FILE_READ);
if (!node)
{
@@ -946,12 +1110,27 @@ class RandomAccessFile
public:
bool open(const std::string& logical_path)
{
close();
ArduinoStorageBinding binding;
if (!resolve_storage_binding(logical_path, false, binding))
{
return false;
}
sd_runtime_ = binding.sd_runtime;
if (sd_runtime_)
{
if (::platform::esp::arduino_common::storage::sd_is_directory(binding.volume_path.c_str()) ||
!sd_file_.open(binding.volume_path.c_str(), "r"))
{
sd_runtime_ = false;
return false;
}
size_ = static_cast<std::size_t>(sd_file_.size());
return true;
}
file_ = binding.volume->open(binding.volume_path.c_str(), FILE_READ);
if (!file_ || file_.isDirectory())
{
@@ -959,6 +1138,7 @@ class RandomAccessFile
{
file_.close();
}
sd_runtime_ = false;
return false;
}
size_ = static_cast<std::size_t>(file_.size());
@@ -967,6 +1147,13 @@ class RandomAccessFile
void close()
{
if (sd_runtime_)
{
sd_file_.close();
sd_runtime_ = false;
size_ = 0;
return;
}
if (file_)
{
file_.close();
@@ -981,6 +1168,15 @@ class RandomAccessFile
bool read_at(std::size_t offset, void* out, std::size_t len)
{
if (sd_runtime_)
{
if (!sd_file_.seek(offset))
{
return false;
}
const int read = sd_file_.read(out, len);
return read >= 0 && static_cast<std::size_t>(read) == len;
}
if (!file_ || !file_.seek(static_cast<uint32_t>(offset)))
{
return false;
@@ -990,6 +1186,8 @@ class RandomAccessFile
private:
File file_{};
::platform::esp::arduino_common::storage::SdRuntimeFile sd_file_{};
bool sd_runtime_ = false;
std::size_t size_ = 0;
};
@@ -998,6 +1196,8 @@ class SequentialWriteFile
public:
bool open(const std::string& logical_path)
{
close();
const std::size_t slash = logical_path.find_last_of('/');
if (slash != std::string::npos &&
!ensure_dir_recursive(logical_path.substr(0, slash)))
@@ -1013,9 +1213,21 @@ class SequentialWriteFile
return false;
}
sd_runtime_ = binding.sd_runtime;
if (sd_runtime_)
{
if (!sd_file_.open(binding.volume_path.c_str(), "w"))
{
sd_runtime_ = false;
return false;
}
return true;
}
file_ = binding.volume->open(binding.volume_path.c_str(), FILE_WRITE);
if (!file_)
{
sd_runtime_ = false;
return false;
}
return true;
@@ -1023,6 +1235,10 @@ class SequentialWriteFile
bool write(const void* data, std::size_t len)
{
if (sd_runtime_)
{
return sd_file_.write(data, len) == len;
}
if (!file_)
{
return false;
@@ -1032,6 +1248,13 @@ class SequentialWriteFile
void close()
{
if (sd_runtime_)
{
sd_file_.flush();
sd_file_.close();
sd_runtime_ = false;
return;
}
if (file_)
{
file_.flush();
@@ -1041,6 +1264,8 @@ class SequentialWriteFile
private:
File file_{};
::platform::esp::arduino_common::storage::SdRuntimeFile sd_file_{};
bool sd_runtime_ = false;
};
#else
@@ -13,7 +13,6 @@
extern "C" lv_draw_buf_t* lv_snapshot_take(lv_obj_t* obj, lv_color_format_t cf);
extern "C" void lv_draw_buf_destroy(lv_draw_buf_t* draw_buf);
#endif
#include <SD.h>
#include <cmath>
#include <cstdio>
#include <ctime>
+30 -61
View File
@@ -1,15 +1,17 @@
#pragma once
#include <Arduino.h>
#include <SD.h>
#include <SPI.h>
#if defined(ARDUINO_ARCH_ESP32)
#include "platform/esp/arduino_common/storage/sd_card_runtime.h"
#if !defined(ARDUINO_ARCH_ESP32)
#error "sd_utils requires the ESP32 SdFat runtime; Arduino SD fallback is intentionally unsupported."
#endif
#include "platform/esp/arduino_common/storage/sd_card_runtime.h"
namespace sdutil
{
constexpr uint8_t kCardNone = 0;
inline void setCsHigh(int pin)
{
if (pin < 0)
@@ -20,6 +22,26 @@ inline void setCsHigh(int pin)
digitalWrite(pin, HIGH);
}
inline void releaseSdBusDevices(int sd_cs, const int* extra_cs, size_t extra_cs_count)
{
for (size_t i = 0; i < extra_cs_count; ++i)
{
setCsHigh(extra_cs[i]);
}
setCsHigh(sd_cs);
}
inline void resetSharedSpiForSd(int sd_cs, const int* extra_cs, size_t extra_cs_count)
{
releaseSdBusDevices(sd_cs, extra_cs, extra_cs_count);
pinMode(MISO, INPUT_PULLUP);
SPI.end();
delay(2);
SPI.begin(SCK, MISO, MOSI);
releaseSdBusDevices(sd_cs, extra_cs, extra_cs_count);
delay(2);
}
template <typename Lockable>
inline bool installSpiSd(Lockable& bus, int sd_cs, uint32_t spi_hz, const char* mount_point,
const int* extra_cs, size_t extra_cs_count,
@@ -31,23 +53,8 @@ inline bool installSpiSd(Lockable& bus, int sd_cs, uint32_t spi_hz, const char*
return false;
}
for (size_t i = 0; i < extra_cs_count; ++i)
{
setCsHigh(extra_cs[i]);
}
setCsHigh(sd_cs);
pinMode(MISO, INPUT_PULLUP);
SPI.end();
delay(2);
SPI.begin(SCK, MISO, MOSI);
resetSharedSpiForSd(sd_cs, extra_cs, extra_cs_count);
SPIClass& sd_bus = SPI;
for (size_t i = 0; i < extra_cs_count; ++i)
{
setCsHigh(extra_cs[i]);
}
setCsHigh(sd_cs);
delay(2);
Serial.printf("[SD] SPI pins sck=%d miso=%d mosi=%d cs=%d hz=%lu\n",
SCK, MISO, MOSI, sd_cs, (unsigned long)spi_hz);
for (size_t i = 0; i < extra_cs_count; ++i)
@@ -57,19 +64,14 @@ inline bool installSpiSd(Lockable& bus, int sd_cs, uint32_t spi_hz, const char*
Serial.printf("[SD] sd CS pin=%d level=%d\n", sd_cs, digitalRead(sd_cs));
bool ok = false;
uint8_t card_type = CARD_NONE;
uint8_t card_type = kCardNone;
uint32_t card_size_mb = 0;
bool locked = true;
#if defined(ARDUINO_ARCH_ESP32)
if (use_lock)
{
locked = bus.lock(portMAX_DELAY);
}
#else
(void)bus;
(void)use_lock;
#endif
if (locked)
{
@@ -97,38 +99,27 @@ inline bool installSpiSd(Lockable& bus, int sd_cs, uint32_t spi_hz, const char*
continue;
}
tried_freqs[tried_count++] = hz_try;
SD.end();
setCsHigh(sd_cs);
resetSharedSpiForSd(sd_cs, extra_cs, extra_cs_count);
delay(10);
Serial.printf("[SD] try hz=%lu\n", (unsigned long)hz_try);
#if defined(ARDUINO_ARCH_ESP32)
ok = ::platform::esp::arduino_common::storage::mount_sd_card(
sd_cs, sd_bus, hz_try, mount_point, max_files);
#else
ok = SD.begin(sd_cs, sd_bus, hz_try, mount_point);
if (!ok)
{
ok = SD.begin(sd_cs, sd_bus, hz_try);
}
#endif
Serial.printf("[SD] SD.begin -> %d\n", ok ? 1 : 0);
Serial.printf("[SD] mount -> %d\n", ok ? 1 : 0);
if (ok)
{
break;
}
SD.end();
delay(25);
}
if (ok)
{
#if defined(ARDUINO_ARCH_ESP32)
const auto info = ::platform::esp::arduino_common::storage::sd_card_info();
card_type = info.card_type;
Serial.printf("[SD] cardType=%u backend=%s fs=%s\n",
(unsigned)card_type,
::platform::esp::arduino_common::storage::sd_card_backend_name(),
::platform::esp::arduino_common::storage::sd_card_filesystem_name());
if (card_type != CARD_NONE)
if (card_type != kCardNone)
{
card_size_mb = static_cast<uint32_t>(info.card_size_bytes / (1024ULL * 1024ULL));
Serial.printf("[SD] card=%llu MB total=%llu MB sectors=%lu sector_size=%lu\n",
@@ -142,33 +133,11 @@ inline bool installSpiSd(Lockable& bus, int sd_cs, uint32_t spi_hz, const char*
ok = false;
::platform::esp::arduino_common::storage::unmount_sd_card();
}
#else
card_type = SD.cardType();
Serial.printf("[SD] cardType=%u\n", (unsigned)card_type);
if (card_type != CARD_NONE)
{
const uint64_t card_size = SD.cardSize();
const uint64_t total_size = SD.totalBytes();
card_size_mb = static_cast<uint32_t>(card_size / (1024ULL * 1024ULL));
Serial.printf("[SD] card=%llu MB total=%llu MB sectors=%lu sector_size=%lu\n",
static_cast<unsigned long long>(card_size / (1024ULL * 1024ULL)),
static_cast<unsigned long long>(total_size / (1024ULL * 1024ULL)),
static_cast<unsigned long>(SD.numSectors()),
static_cast<unsigned long>(SD.sectorSize()));
}
else
{
ok = false;
SD.end();
}
#endif
}
#if defined(ARDUINO_ARCH_ESP32)
if (use_lock)
{
bus.unlock();
}
#endif
}
else
{