mirror of
https://github.com/meshtastic/firmware.git
synced 2026-09-16 16:41:24 -04:00
* logging: gate LOG_TRACE behind MESHTASTIC_TRACE_LOGGING, drop redundant reclock logs LOG_TRACE now compiles out by default so trace-level diagnostics cost no flash; enable with -DMESHTASTIC_TRACE_LOGGING. Portduino keeps it on for the traceFilename packet-trace feature. Remove the 66 caller-side I2C reclock/restore log lines in the telemetry sensors: ReClockI2C::setClock/restoreClock already log both frequencies internally (now at trace level, since they fire every sensor read). Also unify near-duplicate literals (colon/case/punctuation variants) so linker string dedup applies, and drop an information-free bare 'done'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * logging: demote chatty per-packet/per-poll DEBUG lines to trace level With LOG_TRACE compiled out by default, per-iteration chatter (packet bookkeeping, sensor poll values, e-ink refresh reasons, GPS pin states, UI runState traces) now costs no flash on device builds while remaining one -DMESHTASTIC_TRACE_LOGGING away. 108 lines demoted, 4 information- free lines removed; failure paths, drop reasons, and one-time init logs all stay at debug level. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * logging: address CodeRabbit review on trace-gate PR - GPS: pass serial-derived buffers as %s args, never as format strings (untrusted bytes could contain % directives) - 0x%08x for packet id / NodeNum per convention (Router, CannedMessage, NeighborInfo); unsigned casts for size_t args; %u for uint32_t delta - EInk: async full-refresh begin/complete back to DEBUG (rare state transitions); per-frame SKIPPED lines stay trace Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * logging: gate trace on the flag's value, not its presence -DMESHTASTIC_TRACE_LOGGING=0 previously *enabled* trace logging because the gate tested definedness. The flag now defaults per-platform (portduino 1, else 0) and both backends test the value, so =0 disables, =1 or a bare -D enables. Also cast tx_after-millis() to uint32_t for %u (millis() is unsigned long on native). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * logging: clang-format rewrap after specifier widening Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * Even fewer bytes! * logging: keep compile-gated debug lines at debug level; fix native-suite-count Lines already inside default-off #ifdef blocks (GPS_DEBUG, DEBUG_LOOP_TIMING) cost no flash and should stay visible at debug level when their gate is enabled, rather than also requiring MESHTASTIC_TRACE_LOGGING. test/native-suite-count lags the two test_event_channel_* suites added by #11045 (develop's Native Suite Count check has the same mismatch); bump 46 -> 47. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 * gps: route GPS_DEBUG diagnostics through a LOG_DEBUG_GPS() macro (#11414) Replaces 27 log-only #ifdef GPS_DEBUG blocks across GPS.cpp, PositionModule, MeshService, and GPSStatus.h with a single-line LOG_DEBUG_GPS() call (src/gps/GPSLog.h, modeled on LOG_MIGRATION: value-gated, ((void)0) when off). Blocks containing declarations, control flow, hexDump, or nested conditionals keep an explicit '#if GPS_DEBUG' guard. RTC.cpp's per-reading raw time dumps and per-candidate rejection chatter fold under the same gate; quality transitions and boot-time seeding stay at debug. Also fixes the '// define GPS_DEBUG' missing-# typo in two variant headers and updates all seven commented examples to the value form ('#define GPS_DEBUG 1') required by the value-based gate. Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 Co-authored-by: Claude <noreply@anthropic.com> * gps: declare RTC gmtime result as pointer to const (cppcheck) With the setTime debug dump gated behind GPS_DEBUG, all remaining uses of t are reads; cppcheck (constVariablePointer) now flags it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LBiZc9sfPrH1MZ2L3Fxgt1 --------- Co-authored-by: Claude <noreply@anthropic.com>
436 lines
12 KiB
C++
436 lines
12 KiB
C++
/**
|
|
* @file FSCommon.cpp
|
|
* @brief This file contains functions for common filesystem operations such as copying, renaming, listing and deleting files and
|
|
* directories.
|
|
*
|
|
* The functions in this file are used to perform common filesystem operations such as copying, renaming, listing and deleting
|
|
* files and directories. These functions are used in the Meshtastic-device project to manage files and directories on the
|
|
* device's filesystem.
|
|
*
|
|
*/
|
|
#include "FSCommon.h"
|
|
#include "SPILock.h"
|
|
#include "configuration.h"
|
|
|
|
#if defined(ARCH_PORTDUINO)
|
|
#include <filesystem>
|
|
#endif
|
|
|
|
#if defined(ARCH_NRF52) || defined(ARCH_STM32)
|
|
// Adafruit_LittleFS (nRF52) and STM32_LittleFS both wrap littlefs v1, which predates lfs_fs_size().
|
|
// Count the blocks currently in use with lfs_traverse() instead.
|
|
static int fsCountBlockCb(void *ctx, lfs_block_t)
|
|
{
|
|
*static_cast<size_t *>(ctx) += 1;
|
|
return 0;
|
|
}
|
|
|
|
size_t fsTotalBytes()
|
|
{
|
|
lfs_t *fs = FSCom._getFS();
|
|
if (!fs || !fs->cfg)
|
|
return 0;
|
|
return (size_t)fs->cfg->block_count * (size_t)fs->cfg->block_size;
|
|
}
|
|
|
|
size_t fsUsedBytes()
|
|
{
|
|
lfs_t *fs = FSCom._getFS();
|
|
if (!fs || !fs->cfg)
|
|
return 0;
|
|
size_t blocks = 0;
|
|
FSCom._lockFS();
|
|
int err = lfs_traverse(fs, fsCountBlockCb, &blocks);
|
|
FSCom._unlockFS();
|
|
if (err < 0)
|
|
return fsTotalBytes(); // report "full" so capacity checks fail safe
|
|
return blocks * (size_t)fs->cfg->block_size;
|
|
}
|
|
#elif defined(ARCH_RP2040)
|
|
// arduino-pico reports capacity through FSInfo rather than as methods.
|
|
size_t fsTotalBytes()
|
|
{
|
|
FSInfo info;
|
|
return FSCom.info(info) ? (size_t)info.totalBytes : 0;
|
|
}
|
|
|
|
size_t fsUsedBytes()
|
|
{
|
|
FSInfo info;
|
|
return FSCom.info(info) ? (size_t)info.usedBytes : 0;
|
|
}
|
|
#elif defined(ARCH_PORTDUINO)
|
|
// Portduino is backed by the host filesystem; ask the OS about the volume holding the working
|
|
// directory. std::filesystem keeps this working on native-windows too, where statvfs() does not
|
|
// exist. On error we report "full" so capacity checks fail safe.
|
|
size_t fsTotalBytes()
|
|
{
|
|
std::error_code ec;
|
|
const auto info = std::filesystem::space(".", ec);
|
|
return ec ? 0 : (size_t)info.capacity;
|
|
}
|
|
|
|
size_t fsUsedBytes()
|
|
{
|
|
std::error_code ec;
|
|
const auto info = std::filesystem::space(".", ec);
|
|
if (ec || info.capacity < info.available)
|
|
return fsTotalBytes();
|
|
return (size_t)(info.capacity - info.available);
|
|
}
|
|
#else
|
|
// ESP32 LittleFS and the nRF54L15 wrapper expose these directly.
|
|
size_t fsTotalBytes()
|
|
{
|
|
return FSCom.totalBytes();
|
|
}
|
|
|
|
size_t fsUsedBytes()
|
|
{
|
|
return FSCom.usedBytes();
|
|
}
|
|
#endif
|
|
|
|
// Software SPI is used by MUI so disable SD card here until it's also implemented
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
|
#include <SD.h>
|
|
#include <SPI.h>
|
|
|
|
#ifdef SDCARD_USE_SPI1
|
|
SPIClass SPI_HSPI(HSPI);
|
|
#define SDHandler SPI_HSPI
|
|
#else
|
|
#define SDHandler SPI
|
|
#endif
|
|
|
|
#ifndef SD_SPI_FREQUENCY
|
|
#define SD_SPI_FREQUENCY 4000000U
|
|
#endif
|
|
|
|
#endif // HAS_SDCARD
|
|
|
|
/**
|
|
* Renames a file from pathFrom to pathTo.
|
|
*
|
|
* @param pathFrom The original path of the file.
|
|
* @param pathTo The new path of the file.
|
|
*
|
|
* @return True if the file was successfully renamed, false otherwise.
|
|
*/
|
|
bool renameFile(const char *pathFrom, const char *pathTo)
|
|
{
|
|
#ifdef FSCom
|
|
spiLock->lock();
|
|
bool result = FSCom.rename(pathFrom, pathTo);
|
|
spiLock->unlock();
|
|
return result;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#include <cstring>
|
|
#include <new>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
|
|
/**
|
|
* @brief Platform-agnostic filesystem format / wipe.
|
|
*
|
|
* On embedded targets (ESP32, NRF52, STM32WL, RP2040) this calls the
|
|
* native FSCom.format() which erases and reinitialises the LittleFS
|
|
* partition.
|
|
*
|
|
* On Portduino the fs::FS backend has no format() method. We instead
|
|
* delete /prefs (the only meshtastic data directory written at runtime)
|
|
* and return. rmDir("/prefs") is already called unconditionally by
|
|
* factoryReset() so this is a proven primitive on Portduino.
|
|
* FSBegin() is a no-op (#define FSBegin() true) on Portduino.
|
|
*
|
|
* @return true on success, false on failure or if no filesystem is configured.
|
|
*/
|
|
bool fsFormat()
|
|
{
|
|
#ifdef FSCom
|
|
#if defined(ARCH_PORTDUINO)
|
|
rmDir("/prefs");
|
|
return FSBegin();
|
|
#else
|
|
return FSCom.format();
|
|
#endif
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
#ifdef FSCom
|
|
namespace
|
|
{
|
|
bool pathEndsWithDot(const char *path)
|
|
{
|
|
if (!path)
|
|
return false;
|
|
|
|
size_t length = strlen(path);
|
|
return length > 0 && path[length - 1] == '.';
|
|
}
|
|
|
|
bool copyFilePath(char *dest, size_t destSize, const char *path, bool *wasLimited)
|
|
{
|
|
if (!path || destSize == 0) {
|
|
if (wasLimited)
|
|
*wasLimited = true;
|
|
return false;
|
|
}
|
|
|
|
if (strlcpy(dest, path, destSize) >= destSize) {
|
|
if (wasLimited)
|
|
*wasLimited = true;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void collectFiles(const char *dirname, uint8_t levels, size_t maxCount, std::vector<meshtastic_FileInfo> &filenames,
|
|
bool *wasLimited)
|
|
{
|
|
if (!dirname)
|
|
return;
|
|
|
|
File root = FSCom.open(dirname, FILE_O_READ);
|
|
if (!root)
|
|
return;
|
|
if (!root.isDirectory()) {
|
|
root.close();
|
|
return;
|
|
}
|
|
|
|
File file = root.openNextFile();
|
|
// file.name()[0] check is a workaround for a bug in the Adafruit LittleFS nrf52 glue (see issue 4395)
|
|
while (file && file.name()[0]) {
|
|
if (filenames.size() >= maxCount) {
|
|
if (wasLimited)
|
|
*wasLimited = true;
|
|
file.close();
|
|
break;
|
|
}
|
|
const char *fileName = file.name();
|
|
if (file.isDirectory() && !pathEndsWithDot(fileName)) {
|
|
char pathBuffer[sizeof(((meshtastic_FileInfo *)nullptr)->file_name)] = {};
|
|
#ifdef ARCH_ESP32
|
|
const char *subDirPath = file.path();
|
|
#else
|
|
const char *subDirPath = fileName;
|
|
#endif
|
|
bool hasSubDirPath = copyFilePath(pathBuffer, sizeof(pathBuffer), subDirPath, wasLimited);
|
|
file.close();
|
|
|
|
if (levels && hasSubDirPath) {
|
|
collectFiles(pathBuffer, levels - 1, maxCount, filenames, wasLimited);
|
|
} else if (wasLimited) {
|
|
*wasLimited = true;
|
|
}
|
|
} else {
|
|
meshtastic_FileInfo fileInfo = {"", static_cast<uint32_t>(file.size())};
|
|
#ifdef ARCH_ESP32
|
|
bool hasFilePath = copyFilePath(fileInfo.file_name, sizeof(fileInfo.file_name), file.path(), wasLimited);
|
|
#else
|
|
bool hasFilePath = copyFilePath(fileInfo.file_name, sizeof(fileInfo.file_name), file.name(), wasLimited);
|
|
#endif
|
|
if (hasFilePath && !pathEndsWithDot(fileInfo.file_name)) {
|
|
filenames.push_back(fileInfo);
|
|
}
|
|
file.close();
|
|
}
|
|
file = root.openNextFile();
|
|
}
|
|
root.close();
|
|
}
|
|
} // namespace
|
|
#endif
|
|
|
|
/**
|
|
* @brief Get the list of files in a directory.
|
|
*
|
|
* This function returns a list of files in a directory. The list includes the full path of each file.
|
|
* We can't use SPILOCK here because of recursion. Callers of this function should use SPILOCK.
|
|
*
|
|
* @param dirname The name of the directory.
|
|
* @param levels The number of levels of subdirectories to list.
|
|
* @param maxCount The maximum number of files to collect before truncating the walk.
|
|
* @param wasLimited Optional out-param, set to true if the listing was truncated (by maxCount or low memory).
|
|
* @return A vector of meshtastic_FileInfo for each file in the directory.
|
|
*/
|
|
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels, size_t maxCount, bool *wasLimited)
|
|
{
|
|
std::vector<meshtastic_FileInfo> filenames = {};
|
|
if (wasLimited)
|
|
*wasLimited = false;
|
|
#ifdef FSCom
|
|
#if defined(__cpp_exceptions) || defined(__EXCEPTIONS)
|
|
size_t reservedCount = maxCount;
|
|
while (reservedCount > 0) {
|
|
try {
|
|
filenames.reserve(reservedCount);
|
|
break;
|
|
} catch (const std::bad_alloc &) {
|
|
reservedCount /= 2;
|
|
} catch (const std::length_error &) {
|
|
reservedCount /= 2;
|
|
}
|
|
}
|
|
if (reservedCount == 0) {
|
|
if (wasLimited)
|
|
*wasLimited = true;
|
|
return filenames;
|
|
}
|
|
if (reservedCount < maxCount) {
|
|
if (wasLimited)
|
|
*wasLimited = true;
|
|
maxCount = reservedCount;
|
|
}
|
|
#endif
|
|
collectFiles(dirname, levels, maxCount, filenames, wasLimited);
|
|
#endif
|
|
return filenames;
|
|
}
|
|
|
|
/**
|
|
* Lists the contents of a directory.
|
|
* We can't use SPILOCK here because of recursion. Callers of this function should use SPILOCK.
|
|
*
|
|
* @param dirname The name of the directory to list.
|
|
* @param levels The number of levels of subdirectories to list.
|
|
* @param del Whether or not to delete the contents of the directory after listing.
|
|
*/
|
|
void listDir(const char *dirname, uint8_t levels, bool del)
|
|
{
|
|
#ifdef FSCom
|
|
char buffer[255];
|
|
File root = FSCom.open(dirname, FILE_O_READ);
|
|
if (!root || !root.isDirectory())
|
|
return;
|
|
|
|
File file = root.openNextFile();
|
|
while (file && file.name()[0]) { // file.name()[0] check: workaround for Adafruit LittleFS nRF52 bug #4395
|
|
#ifdef ARCH_ESP32
|
|
const char *filepath = file.path();
|
|
#else
|
|
const char *filepath = file.name();
|
|
#endif
|
|
if (file.isDirectory() && !String(file.name()).endsWith(".")) {
|
|
if (levels) {
|
|
listDir(filepath, levels - 1, del);
|
|
if (del) {
|
|
LOG_DEBUG("Remove %s", filepath);
|
|
strncpy(buffer, filepath, sizeof(buffer) - 1);
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
file.close();
|
|
FSCom.rmdir(buffer);
|
|
} else {
|
|
file.close();
|
|
}
|
|
}
|
|
} else {
|
|
if (del) {
|
|
LOG_DEBUG("Delete %s", filepath);
|
|
strncpy(buffer, filepath, sizeof(buffer) - 1);
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
file.close();
|
|
FSCom.remove(buffer);
|
|
} else {
|
|
LOG_TRACE(" %s (%i Bytes)", filepath, file.size());
|
|
file.close();
|
|
}
|
|
}
|
|
file = root.openNextFile();
|
|
}
|
|
#ifdef ARCH_ESP32
|
|
const char *rootpath = root.path();
|
|
#else
|
|
const char *rootpath = root.name();
|
|
#endif
|
|
if (del) {
|
|
LOG_DEBUG("Remove %s", rootpath);
|
|
strncpy(buffer, rootpath, sizeof(buffer) - 1);
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
root.close();
|
|
FSCom.rmdir(buffer);
|
|
} else {
|
|
root.close();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @brief Removes a directory and all its contents.
|
|
*
|
|
* This function recursively removes a directory and all its contents, including subdirectories and files.
|
|
*
|
|
* @param dirname The name of the directory to remove.
|
|
*/
|
|
void rmDir(const char *dirname)
|
|
{
|
|
#ifdef FSCom
|
|
listDir(dirname, 10, true);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Some platforms (nrf52) might need to do an extra step before FSBegin().
|
|
*/
|
|
__attribute__((weak, noinline)) void preFSBegin() {}
|
|
|
|
void fsInit()
|
|
{
|
|
#ifdef FSCom
|
|
concurrency::LockGuard g(spiLock);
|
|
preFSBegin();
|
|
if (!FSBegin()) {
|
|
LOG_ERROR("Filesystem mount failed");
|
|
// assert(0); This auto-formats the partition, so no need to fail here.
|
|
}
|
|
#if defined(ARCH_ESP32)
|
|
LOG_DEBUG("Filesystem files (%d/%d Bytes):", FSCom.usedBytes(), FSCom.totalBytes());
|
|
#else
|
|
LOG_TRACE("Filesystem files:");
|
|
#endif
|
|
listDir("/", 10);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Initializes the SD card and mounts the file system.
|
|
*/
|
|
void setupSDCard()
|
|
{
|
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI) && !defined(HAS_SD_MMC)
|
|
concurrency::LockGuard g(spiLock);
|
|
SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
|
|
if (!SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY)) {
|
|
LOG_DEBUG("No SD_MMC card detected");
|
|
return;
|
|
}
|
|
uint8_t cardType = SD.cardType();
|
|
if (cardType == CARD_NONE) {
|
|
LOG_DEBUG("No SD_MMC card attached");
|
|
return;
|
|
}
|
|
LOG_DEBUG("SD_MMC Card Type: ");
|
|
if (cardType == CARD_MMC) {
|
|
LOG_DEBUG("MMC");
|
|
} else if (cardType == CARD_SD) {
|
|
LOG_DEBUG("SDSC");
|
|
} else if (cardType == CARD_SDHC) {
|
|
LOG_DEBUG("SDHC");
|
|
} else {
|
|
LOG_DEBUG("UNKNOWN");
|
|
}
|
|
|
|
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
|
|
LOG_DEBUG("SD Card Size: %lu MB", (uint32_t)cardSize);
|
|
LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024)));
|
|
LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024)));
|
|
#endif
|
|
} |