From 23e373028978ba8e3fcea46719dda724c53eef4a Mon Sep 17 00:00:00 2001 From: Raphael Coeffic Date: Sun, 27 Jul 2025 02:41:52 +0200 Subject: [PATCH] refactor: convert simufatfs to C++17 (#6454) --- radio/src/lib_file.cpp | 2 +- radio/src/targets/simu/simpgmspace.h | 13 +- radio/src/targets/simu/simufatfs.cpp | 897 +++++++++++++-------------- radio/src/tests/gtests.cpp | 6 +- radio/src/tests/lcd.cpp | 32 +- radio/src/tests/lcd_480x272.cpp | 12 +- 6 files changed, 468 insertions(+), 494 deletions(-) diff --git a/radio/src/lib_file.cpp b/radio/src/lib_file.cpp index 098eae1fd..5996b7847 100644 --- a/radio/src/lib_file.cpp +++ b/radio/src/lib_file.cpp @@ -109,7 +109,7 @@ FRESULT sdReadDir(DIR * dir, FILINFO * fno, bool & firstTime) return res; } -#if !defined(BOOT) && (!defined(SIMU) || defined(SIMU_DISKIO)) +#if !defined(BOOT) // Replace FatFS implementation of f_puts and f_printf diff --git a/radio/src/targets/simu/simpgmspace.h b/radio/src/targets/simu/simpgmspace.h index 09dfa7b5e..cd3c9bf3d 100644 --- a/radio/src/targets/simu/simpgmspace.h +++ b/radio/src/targets/simu/simpgmspace.h @@ -30,6 +30,8 @@ #include #include +#include + #define __disable_irq() #define __enable_irq() @@ -52,15 +54,10 @@ void stopEepromThread(); void simuMain(); -#if !defined(SKIP_FATFS_DECLARATION) && !defined(SIMU_DISKIO) - #define SIMU_USE_SDCARD -#endif +void simuFatfsSetPaths(const char * sdPath, const char * settingsPath); -#if defined(SIMU_USE_SDCARD) - void simuFatfsSetPaths(const char * sdPath, const char * settingsPath); -#else - #define simuFatfsSetPaths(...) -#endif +std::string simuFatfsGetCurrentPath(); +std::string simuFatfsGetRealPath(const std::string &p); #if defined(TRACE_SIMPGMSPACE) #undef TRACE_SIMPGMSPACE diff --git a/radio/src/targets/simu/simufatfs.cpp b/radio/src/targets/simu/simufatfs.cpp index 0dd92eeac..04782c5e3 100644 --- a/radio/src/targets/simu/simufatfs.cpp +++ b/radio/src/targets/simu/simufatfs.cpp @@ -19,360 +19,343 @@ * GNU General Public License for more details. */ -#include -#include -#include -#include -#include "edgetx.h" +#include "simpgmspace.h" -#if defined(SIMU_USE_SDCARD) // rest of file is excluded otherwise - -#if defined(_MSC_VER) || !defined (__GNUC__) - #define MSVC_BUILD 1 -#else - #define MSVC_BUILD 0 -#endif - -// NOTE: the #include order is important here, sensitive on different platforms. -#include -#include -#include -#include -#include -#include - -#if MSVC_BUILD - #include - #include - #include - #define mkdir(s, f) _mkdir(s) -#else - #include - #include -#endif +#if !defined(SIMU_DISKIO) #include "ff.h" +#include "sdcard.h" -namespace simu { -#include -#if !defined(_MSC_VER) - #include -#endif +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +using sysclock = std::chrono::system_clock; +using ftime_type = fs::file_time_type; + +// path to the root of the SD card image +static fs::path simuSdDirectory; + +// path to the root of the models and settings +static fs::path simuSettingsDirectory; + +// current simulater path +static fs::path simuCurrentPath; + +static bool lower_case_equal(unsigned char c1, unsigned char c2) +{ + return std::tolower(c1) == std::tolower(c2); } -std::string simuSdDirectory; // path to the root of the SD card image -std::string simuSettingsDirectory; // path to the root of the models and settings (only for the radios that use SD for model storage) - -bool isPathDelimiter(char delimiter) +static bool starts_with(const std::string& str, const std::string& prefix) { - return delimiter == '/'; + if (str.length() < prefix.length()) return false; + return std::equal(prefix.begin(), prefix.end(), str.begin(), lower_case_equal); } -std::string removeTrailingPathDelimiter(const std::string & path) +static std::string to_lower(const std::string& s) { - std::string result = path; - while (!result.empty() && isPathDelimiter(result.back())) { - result.pop_back(); - } - return result; + std::string result(s.size(), '\0'); + std::transform(s.begin(), s.end(), result.begin(), + [](unsigned char c) { return std::tolower(c); }); + return result; } -std::string fixPathDelimiters(const char * path) +static sysclock::time_point ftime_to_systime(ftime_type ftime) { - // replace all '\' characters with '/' - std::string result(path); - std::replace(result.begin(), result.end(), '\\', '/'); - // TRACE_SIMPGMSPACE("fixPathDelimiters(): %s -> %s", path, result.c_str()); - return result; + return std::chrono::time_point_cast( + ftime - ftime_type::clock::now() + sysclock::now()); } -void simuFatfsSetPaths(const char * sdPath, const char * settingsPath) +static ftime_type systime_to_ftime(sysclock::time_point systime) { - if (sdPath && strlen(sdPath) > 0) { - simuSdDirectory = removeTrailingPathDelimiter(fixPathDelimiters(sdPath)); - } else if (simuSdDirectory.empty()) { - char buff[1024]; - f_getcwd(buff, sizeof(buff) - 1); - simuSdDirectory = removeTrailingPathDelimiter(fixPathDelimiters(buff)); - } - if (settingsPath && strlen(settingsPath) > 0) { - simuSettingsDirectory = removeTrailingPathDelimiter(fixPathDelimiters(settingsPath)); - } - TRACE_SIMPGMSPACE("simuFatfsSetPaths(): simuSdDirectory: \"%s\"", simuSdDirectory.c_str()); - TRACE_SIMPGMSPACE("simuFatfsSetPaths(): simuSettingsDirectory: \"%s\"", simuSettingsDirectory.c_str()); + return std::chrono::time_point_cast( + systime - sysclock::now() + ftime_type::clock::now()); } -bool startsWith(const std::string & str, const std::string & prefix) +static bool redirectToSettingsDirectory(const fs::path& p) { - return str.length() >= prefix.length() && - str.compare(0, prefix.length(), prefix) == 0; + if (simuSettingsDirectory.empty()) return false; + return starts_with(p.generic_string(), MODELS_PATH) || starts_with(p.generic_string(), RADIO_PATH); } -bool endsWith(const std::string & str, const std::string & suffix) +static fs::path resolveCaseInsensitivePath( + const fs::path& base, + const fs::path& relativePath) { - return str.length() >= suffix.length() && - str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0; -} + fs::path current = base; + std::error_code ec; -bool redirectToSettingsDirectory(const std::string & path) -{ - /* - Decide if we use special simuSettingsDirectory path or normal path + for (const auto& component : relativePath) { + std::string componentStr = component.string(); - We use special path for: - * radio settings and models list in /RADIO directory - * model (*.bin) files in /MODELS directory - */ - if (!simuSettingsDirectory.empty()) { - if (path == MODELS_PATH || path == RADIO_PATH) return true; - if (startsWith(path, MODELS_PATH) && endsWith(path, MODELS_EXT)) - return true; - if (path == MODELSLIST_YAML_PATH || path == RADIO_SETTINGS_YAML_PATH || path == RADIO_SETTINGS_TMPFILE_YAML_PATH || path == RADIO_SETTINGS_ERRORFILE_YAML_PATH) - return true; - if (startsWith(path, MODELS_PATH) && endsWith(path, YAML_EXT)) return true; - } - return false; -} - -std::string convertToSimuPath(const char * path) -{ - std::string result; - if (isPathDelimiter(path[0])) { - if (redirectToSettingsDirectory(path)) { - // TRACE("REDIRECT ********************************************"); - result = simuSettingsDirectory + std::string(path); + // Skip current directory and parent directory references + if (componentStr == "." || componentStr == "..") { + current /= component; + continue; } - else { - result = simuSdDirectory + std::string(path); + + // Try exact match first + fs::path exactPath = current / component; + if (fs::exists(exactPath, ec) && !ec) { + current = exactPath; + continue; } - } - else { - result = std::string(path); - } - // TRACE_SIMPGMSPACE("convertToSimuPath(): %s -> %s", path, result.c_str()); - return result; -} -std::string convertFromSimuPath(const char * path) -{ - std::string result; - if (startsWith(path, simuSdDirectory)) { - result = std::string(path).substr(simuSdDirectory.length(), std::string::npos); - if (result.empty()) { - result = "/"; - } - } - else { - result = std::string(path); - if (!result.empty() && !isPathDelimiter(result[0])) { - result = "/" + result; - } - } - // TRACE_SIMPGMSPACE("convertFromSimuPath(): %s -> %s", path, result.c_str()); - return result; -} + // Look for case-insensitive match + bool found = false; + if (fs::is_directory(current, ec) && !ec) { + std::string componentLower = to_lower(componentStr); -typedef std::map filemap_t; + for (const auto& entry : fs::directory_iterator(current, ec)) { + if (ec) break; -filemap_t fileMap; - -void splitPath(const std::string & path, std::string & dir, std::string & name) -{ -#if MSVC_BUILD - char drive[_MAX_DRIVE]; - char directory[_MAX_DIR]; - char fname[_MAX_FNAME]; - char ext[_MAX_EXT]; - _splitpath(path.c_str(), drive, directory, fname, ext); - name = std::string(fname) + std::string(ext); - dir = std::string(drive) + std::string(directory); -#else - char * buff = new char[path.length()+1]; - strcpy(buff, path.c_str()); - name = simu::basename(buff); - strcpy(buff, path.c_str()); - dir = simu::dirname(buff); - delete[] buff; -#endif -} - - -#if !MSVC_BUILD -bool isFile(const std::string & fullName) -{ -//#if defined(WIN32) || defined(__APPLE__) || defined(__FreeBSD__) -// #define REGULAR_FILE DT_REG -// #define SYMBOLIC_LINK DT_LNK -//#else -// #define REGULAR_FILE simu::DT_REG -// #define SYMBOLIC_LINK simu::DT_LNK -//#endif - - //if (d_type == REGULAR_FILE) return true; - //if (d_type == SYMBOLIC_LINK) { - struct stat tmp; - if (stat(fullName.c_str(), &tmp) == 0) { - // TRACE_SIMPGMSPACE("\tsymlik: %s is %s", fullName.c_str(), (tmp.st_mode & S_IFREG) ? "file" : "other"); - if (tmp.st_mode & S_IFREG) return true; - } - //} - return false; -} -#endif - -std::vector listDirectoryFiles(const std::string & dirName) -{ - std::vector result; - -#if MSVC_BUILD - std::string searchName = dirName + "*"; - // TRACE_SIMPGMSPACE("\tsearching for: %s", fileName.c_str()); - WIN32_FIND_DATA ffd; - HANDLE hFind = FindFirstFile(searchName.c_str(), &ffd); - if (INVALID_HANDLE_VALUE != hFind) { - do { - if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { - std::string fullName = dirName + std::string(ffd.cFileName); - // TRACE_SIMPGMSPACE("listDirectoryFiles(): %s", fullName.c_str()); - result.push_back(fullName); + std::string entryName = entry.path().filename().string(); + if (to_lower(entryName) == componentLower) { + current = entry.path(); + found = true; + break; } } - while (FindNextFile(hFind, &ffd) != 0); } -#else - simu::DIR * dir = simu::opendir(dirName.c_str()); - if (dir) { - struct simu::dirent * res; - while ((res = simu::readdir(dir)) != 0) { - std::string fullName = dirName + "/" + std::string(res->d_name); - if (isFile(fullName)) { - // TRACE_SIMPGMSPACE("listDirectoryFiles(): %s", fullName.c_str()); - result.push_back(fullName); - } - } - simu::closedir(dir); + + // If not found, use original component name (file doesn't exist) + if (!found) current /= component; } -#endif - return result; + + return current; } -std::string findTrueFileName(const std::string & path) +static std::string convertToSimuPath(const fs::path& path) { - // TRACE_SIMPGMSPACE("findTrueFileName(%s)", path.c_str()); - std::string result; - filemap_t::iterator i = fileMap.find(path); - if (i != fileMap.end()) { - result = i->second; - // TRACE_SIMPGMSPACE("\tfound in map: %s", result.c_str()); - return result; + fs::path p{path}; + fs::path basePath; + + if (p.is_relative()) { + p = simuCurrentPath / p; } - else { - //find file and add to map - std::string dirName; - std::string fileName; - splitPath(path, dirName, fileName); - std::vector files = listDirectoryFiles(dirName); - for(unsigned int i=0; i 0) { + simuSdDirectory = fs::path{sdPath}.lexically_normal(); + } else if (simuSdDirectory.empty()) { + simuSdDirectory = fs::current_path(); + } + if (settingsPath && strlen(settingsPath) > 0) { + simuSettingsDirectory = + fs::path{settingsPath}.lexically_normal(); + } +} + +std::string simuFatfsGetCurrentPath() { return simuCurrentPath.string(); } + +std::string simuFatfsGetRealPath(const std::string &p) +{ + return convertToSimuPath(p); +} + +FRESULT file_stat(const std::string& realPath, FILINFO* fno) +{ + std::error_code ec; + fs::path fsPath(realPath); + + // Check if file/directory exists + auto status = fs::status(fsPath, ec); + if (ec || status.type() == fs::file_type::not_found) { return FR_INVALID_NAME; } - else { - TRACE_SIMPGMSPACE("f_stat(%s) = OK", path.c_str()); - if (fno) { - fno->fattrib = (tmp.st_mode & S_IFDIR) ? AM_DIR : 0; - // convert to FatFs fdate/ftime - struct tm *ltime = localtime(&tmp.st_mtime); - fno->fdate = ((ltime->tm_year - 80) << 9) | ((ltime->tm_mon + 1) << 5) | ltime->tm_mday; - fno->ftime = (ltime->tm_hour << 11) | (ltime->tm_min << 5) | (ltime->tm_sec / 2); - fno->fsize = (DWORD)tmp.st_size; + + if (fno) { + // Set attributes + fno->fattrib = + (status.type() == fs::file_type::directory) ? AM_DIR : 0; + + // Get file size + if (status.type() == fs::file_type::regular) { + auto size = fs::file_size(fsPath, ec); + fno->fsize = ec ? 0 : static_cast(size); + } else { + fno->fsize = 0; + } + + // Get last write time + auto ftime = fs::last_write_time(fsPath, ec); + if (!ec) { + try { + auto systime = ftime_to_systime(ftime); + auto time_t_val = std::chrono::system_clock::to_time_t(systime); + struct tm* ltime = localtime(&time_t_val); + + if (ltime) { + // Convert to FatFs format + fno->fdate = ((ltime->tm_year - 80) << 9) | + ((ltime->tm_mon + 1) << 5) | ltime->tm_mday; + fno->ftime = (ltime->tm_hour << 11) | (ltime->tm_min << 5) | + (ltime->tm_sec / 2); + } else { + fno->fdate = 0; + fno->ftime = 0; + } + } catch (...) { + fno->fdate = 0; + fno->ftime = 0; + } + } else { + fno->fdate = 0; + fno->ftime = 0; } - return FR_OK; } + + return FR_OK; } -FRESULT f_mount (FATFS* ,const TCHAR*, BYTE opt) +FRESULT f_stat(const TCHAR* name, FILINFO* fno) +{ + std::string realPath = convertToSimuPath(name); + return file_stat(realPath, fno); +} + +FRESULT f_mount(FATFS*, const TCHAR*, BYTE opt) { return FR_OK; } -FRESULT f_open (FIL * fil, const TCHAR *name, BYTE flag) +struct _simu_FIL { + std::unique_ptr stream; + std::string filepath; + std::ios::openmode mode; + + _simu_FIL(const std::string& path, std::ios::openmode m) : + filepath(path), mode(m) + { + stream = std::make_unique(path, m); + } +}; + +FRESULT f_open(FIL* fil, const TCHAR* name, BYTE flag) { - std::string path = convertToSimuPath(name); - std::string realPath = findTrueFileName(path); - fil->obj.fs = 0; - if (!(flag & FA_WRITE)) { - struct stat tmp; - if (stat(realPath.c_str(), &tmp)) { - TRACE_SIMPGMSPACE("f_open(%s) = INVALID_NAME (FIL %p)", path.c_str(), fil); + std::string realPath = convertToSimuPath(name); + fil->obj.fs = nullptr; + fil->fptr = 0; + + std::ios::openmode mode = std::ios::binary; + + if (flag & FA_WRITE) { + mode |= std::ios::out | std::ios::in; + if (flag & FA_CREATE_ALWAYS) { + mode |= std::ios::trunc; + } else { + // For append mode, we need to check if file exists + std::error_code ec; + if (fs::exists(realPath, ec) && !ec) { + mode |= std::ios::ate; // Open at end + } else { + mode |= std::ios::trunc; // Create new file + } + } + } else { + mode |= std::ios::in; + + std::error_code ec; + if (!fs::exists(realPath, ec) || ec) { return FR_INVALID_NAME; } - fil->obj.objsize = tmp.st_size; - fil->fptr = 0; - } - fil->obj.fs = (FATFS*)fopen(realPath.c_str(), (flag & FA_WRITE) ? ((flag & FA_CREATE_ALWAYS) ? "wb+" : "ab+") : "rb"); - fil->fptr = 0; - if (fil->obj.fs) { - TRACE_SIMPGMSPACE("f_open(%s, %x) = %p (FIL %p)", path.c_str(), flag, fil->obj.fs, fil); - return FR_OK; - } - TRACE_SIMPGMSPACE("f_open(%s) = error %d (%s) (FIL %p)", path.c_str(), errno, strerror(errno), fil); - return FR_INVALID_NAME; -} -FRESULT f_read (FIL* fil, void* data, UINT size, UINT* read) -{ - if (fil && fil->obj.fs) { - *read = fread(data, 1, size, (FILE*)fil->obj.fs); - fil->fptr += *read; - // TRACE_SIMPGMSPACE("fread(%p) %u, %u", fil->obj.fs, size, *read); - } - return FR_OK; -} - -FRESULT f_write (FIL* fil, const void* data, UINT size, UINT* written) -{ - if (fil && fil->obj.fs) { - *written = fwrite(data, 1, size, (FILE*)fil->obj.fs); - fil->fptr += size; - // TRACE_SIMPGMSPACE("fwrite(%p) %u, %u", fil->obj.fs, size, *written); - } - return FR_OK; -} - -TCHAR * f_gets (TCHAR* buff, int len, FIL* fil) -{ - if (fil && fil->obj.fs) { - buff = fgets(buff, len, (FILE*)fil->obj.fs); - if (buff != nullptr) { - fil->fptr = *buff; + auto size = fs::file_size(realPath, ec); + if (!ec) { + fil->obj.objsize = static_cast(size); } - // TRACE_SIMPGMSPACE("fgets(%p) %u, %s", fil->obj.fs, len, buff); } - return buff; + + try { + auto simuFil = new _simu_FIL(realPath, mode); + if (simuFil->stream->is_open()) { + fil->obj.fs = reinterpret_cast(simuFil); + return FR_OK; + } else { + delete simuFil; + return FR_INVALID_NAME; + } + } catch (...) { + return FR_INVALID_NAME; + } } -FRESULT f_lseek (FIL* fil, DWORD offset) +FRESULT f_close(FIL* fil) { if (fil && fil->obj.fs) { - fseek((FILE*)fil->obj.fs, offset, SEEK_SET); - fil->fptr = offset; + delete reinterpret_cast<_simu_FIL*>(fil->obj.fs); + fil->obj.fs = nullptr; + } + return FR_OK; +} + +FRESULT f_read(FIL* fil, void* data, UINT size, UINT* read) +{ + *read = 0; + if (fil && fil->obj.fs) { + _simu_FIL* sf = reinterpret_cast<_simu_FIL*>(fil->obj.fs); + if (sf->stream && sf->stream->is_open()) { + sf->stream->read(static_cast(data), size); + *read = static_cast(sf->stream->gcount()); + fil->fptr += *read; + } + } + return FR_OK; +} + +FRESULT f_write(FIL* fil, const void* data, UINT size, UINT* written) +{ + *written = 0; + if (fil && fil->obj.fs) { + _simu_FIL* sf = reinterpret_cast<_simu_FIL*>(fil->obj.fs); + if (sf->stream && sf->stream->is_open()) { + auto pos_before = sf->stream->tellp(); + sf->stream->write(static_cast(data), size); + if (sf->stream->good()) { + *written = size; + fil->fptr += size; + } else { + auto pos_after = sf->stream->tellp(); + if (pos_after != std::streampos(-1) && + pos_before != std::streampos(-1)) { + *written = static_cast(pos_after - pos_before); + fil->fptr += *written; + } + } + } + } + return FR_OK; +} + +FRESULT f_lseek(FIL* fil, DWORD offset) +{ + if (fil && fil->obj.fs) { + _simu_FIL* sf = reinterpret_cast<_simu_FIL*>(fil->obj.fs); + if (sf->stream && sf->stream->is_open()) { + sf->stream->seekg(offset, std::ios::beg); + sf->stream->seekp(offset, std::ios::beg); + fil->fptr = offset; + } } return FR_OK; } @@ -380,240 +363,238 @@ FRESULT f_lseek (FIL* fil, DWORD offset) UINT f_size(FIL* fil) { if (fil && fil->obj.fs) { - long curr = ftell((FILE*)fil->obj.fs); - fseek((FILE*)fil->obj.fs, 0, SEEK_END); - long size = ftell((FILE*)fil->obj.fs); - fseek((FILE*)fil->obj.fs, curr, SEEK_SET); - TRACE_SIMPGMSPACE("f_size(%p) %u", fil->obj.fs, size); - return size; + _simu_FIL* sf = reinterpret_cast<_simu_FIL*>(fil->obj.fs); + + // Use filesystem to get size (more efficient) + std::error_code ec; + auto size = fs::file_size(sf->filepath, ec); + if (!ec) { + return static_cast(size); + } } return 0; } -FRESULT f_close (FIL * fil) +FRESULT f_chdir(const TCHAR *name) { - TRACE_SIMPGMSPACE("f_close(%p) (FIL:%p)", fil->obj.fs, fil); - if (fil->obj.fs) { - fclose((FILE*)fil->obj.fs); - fil->obj.fs = nullptr; + fs::path p = {name}; + // TRACE("f_chdir(%s)", name); + + if (p.is_relative()) { + p = simuCurrentPath / p; } - return FR_OK; -} -FRESULT f_chdir (const TCHAR *name) -{ - std::string path = convertToSimuPath(name); - if (chdir(path.c_str())) { - TRACE_SIMPGMSPACE("f_chdir(%s) = error %d (%s)", path.c_str(), errno, strerror(errno)); - return FR_NO_PATH; - } - TRACE_SIMPGMSPACE("f_chdir(%s)", path.c_str()); - return FR_OK; -} + std::error_code ec; + auto realPath = convertToSimuPath(p); -struct _simu_DIR -{ - std::string name; - simu::DIR* dir; - - _simu_DIR(simu::DIR* dir, const TCHAR* name) : - name(name), dir(dir) - {} -}; - -FRESULT f_opendir (DIR * rep, const TCHAR * name) -{ - std::string path = convertToSimuPath(name); - simu::DIR* dp = simu::opendir(path.c_str()); - if (dp) { - rep->obj.fs = reinterpret_cast(new _simu_DIR(dp, name)); - TRACE_SIMPGMSPACE("f_opendir(%s) = OK", path.c_str()); + if (fs::exists(realPath, ec) && !ec) { + simuCurrentPath = p; return FR_OK; } - rep->obj.fs = nullptr; - TRACE_SIMPGMSPACE("f_opendir(%s) = error %d (%s)", path.c_str(), errno, strerror(errno)); return FR_NO_PATH; } -FRESULT f_closedir (DIR * rep) +struct _simu_DIR { + std::string name; + fs::directory_iterator iter; + fs::directory_iterator end_iter; + + _simu_DIR(const std::string& dirName) : name(dirName) + { + std::error_code ec; + iter = fs::directory_iterator(dirName, ec); + if (ec) { + iter = end_iter; // Set to end if error + } + skipDotEntries(); // TODO: check if necessary + } + + private: + void skipDotEntries() + { + while (iter != end_iter) { + std::string filename = iter->path().filename().string(); + if (filename != "." && filename != "..") { + break; + } + std::error_code ec; + ++iter; + } + } + + public: + bool hasNext() const { return iter != end_iter; } + + fs::directory_entry getNext() + { + if (iter == end_iter) { + throw std::runtime_error("No more entries"); + } + + auto current = *iter; + std::error_code ec; + ++iter; + skipDotEntries(); + + return current; + } +}; + +FRESULT f_opendir(DIR* rep, const TCHAR* name) +{ + std::string path = convertToSimuPath(name); + + std::error_code ec; + if (!fs::is_directory(path, ec) || ec) { + rep->obj.fs = nullptr; + return FR_NO_PATH; + } + + try { + rep->obj.fs = reinterpret_cast(new _simu_DIR(path)); + return FR_OK; + } catch (...) { + rep->obj.fs = nullptr; + return FR_NO_PATH; + } +} + +FRESULT f_closedir(DIR* rep) { - TRACE_SIMPGMSPACE("f_closedir(%p)", rep); if (rep->obj.fs) { _simu_DIR* sd = reinterpret_cast<_simu_DIR*>(rep->obj.fs); rep->obj.fs = nullptr; - simu::closedir(sd->dir); delete sd; } return FR_OK; } -FRESULT f_readdir (DIR * rep, FILINFO * fil) +FRESULT f_readdir(DIR* rep, FILINFO* fil) { - simu::dirent * ent; _simu_DIR* sd = reinterpret_cast<_simu_DIR*>(rep->obj.fs); - if (!sd) return FR_NO_FILE; - - for(;;) { - ent = simu::readdir(sd->dir); - if (!ent) return FR_NO_FILE; - if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..") ) break; + if (!sd || !sd->hasNext()) { + return FR_NO_FILE; } - if (fil != nullptr) { + try { + auto entry = sd->getNext(); + + if (fil != nullptr) { memset(fil->fname, 0, FF_MAX_LFN); - strcpy(fil->fname, ent->d_name); - // TRACE_SIMPGMSPACE("f_readdir(): %s", fil->fname); - std::string fullName = sd->name + std::string("/") + std::string(ent->d_name); - return f_stat(fullName.c_str(), fil); + + std::string filename = entry.path().filename().string(); + size_t copyLen = std::min(filename.length(), size_t(FF_MAX_LFN - 1)); + strncpy(fil->fname, filename.c_str(), copyLen); + + std::string fullPath = entry.path().string(); + return file_stat(fullPath, fil); + } + + return FR_OK; + } catch (...) { + return FR_NO_FILE; } - - return FR_OK; } -FRESULT f_mkfs (const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len) +FRESULT f_mkfs(const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len) { - TRACE_SIMPGMSPACE("Format SD..."); return FR_OK; } -FRESULT f_mkdir (const TCHAR * name) +FRESULT f_mkdir(const TCHAR* name) { std::string path = convertToSimuPath(name); - if (f_stat(name, nullptr) != FR_OK) { -#if defined(WIN32) && defined(__GNUC__) - if (mkdir(path.c_str())) { -#else - if (mkdir(path.c_str(), 0777)) { -#endif - TRACE_SIMPGMSPACE("mkdir(%s) = error %d (%s)", path.c_str(), errno, strerror(errno)); - return FR_INVALID_NAME; - } - else { - TRACE_SIMPGMSPACE("mkdir(%s) = OK", path.c_str()); - return FR_OK; - } - } else { - return FR_EXIST; - } - return FR_OK; + std::error_code ec; + + bool created = fs::create_directory(path, ec); + if (ec) return FR_INVALID_NAME; + + return created ? FR_OK : FR_EXIST; } -FRESULT f_unlink (const TCHAR * name) +FRESULT f_unlink(const TCHAR * name) { std::string path = convertToSimuPath(name); - if (isFile(path)) { - if (unlink(path.c_str())) { - TRACE_SIMPGMSPACE("f_unlink(%s) = error %d (%s)", path.c_str(), errno, strerror(errno)); - return FR_INVALID_NAME; - } - else { - TRACE_SIMPGMSPACE("f_unlink(%s) = OK", path.c_str()); - return FR_OK; - } - } else { - if (rmdir(path.c_str())) { - TRACE_SIMPGMSPACE("f_unlink(%s) = error %d (%s)", path.c_str(), errno, strerror(errno)); - return FR_INVALID_NAME; - } - else { - TRACE_SIMPGMSPACE("f_unlink(%s) = OK", path.c_str()); - return FR_OK; - } - } + std::error_code ec; + + bool removed = fs::remove(path, ec); + if (ec) return FR_INVALID_NAME; + + return removed ? FR_OK : FR_INVALID_NAME; } FRESULT f_rename(const TCHAR *oldname, const TCHAR *newname) { std::string old = convertToSimuPath(oldname); std::string path = convertToSimuPath(newname); + std::error_code ec; + + fs::rename(old.c_str(), path.c_str(), ec); + if (ec) return FR_INVALID_NAME; - if (rename(old.c_str(), path.c_str()) < 0) { - TRACE_SIMPGMSPACE("f_rename(%s, %s) = error %d (%s)", old.c_str(), path.c_str(), errno, strerror(errno)); - return FR_INVALID_NAME; - } - TRACE_SIMPGMSPACE("f_rename(%s, %s) = OK", old.c_str(), path.c_str()); return FR_OK; } FRESULT f_utime(const TCHAR* path, const FILINFO* fno) { - if (fno == nullptr) - return FR_INVALID_PARAMETER; + if (fno == nullptr) { + return FR_INVALID_PARAMETER; + } - std::string simpath = convertToSimuPath(path); - std::string realPath = findTrueFileName(simpath); - struct utimbuf newTimes; - struct tm ltime; + std::string realPath = convertToSimuPath(path); + + // Convert FatFs time to tm structure + struct tm ltime = {}; + ltime.tm_year = ((fno->fdate >> 9) & 0x7F) + 80; + ltime.tm_mon = ((fno->fdate >> 5) & 0xF) - 1; + ltime.tm_mday = (fno->fdate & 0x1F); + ltime.tm_hour = ((fno->ftime >> 11) & 0x1F); + ltime.tm_min = ((fno->ftime >> 5) & 0x3F); + ltime.tm_sec = (fno->ftime & 0x1F) * 2; + ltime.tm_isdst = -1; - // convert from FatFs fdate/ftime - ltime.tm_year = ((fno->fdate >> 9) & 0x7F) + 80; - ltime.tm_mon = ((fno->fdate >> 5) & 0xF) - 1; - ltime.tm_mday = (fno->fdate & 0x1F); - ltime.tm_hour = ((fno->ftime >> 11) & 0x1F); - ltime.tm_min = ((fno->ftime >> 5) & 0x3F); - ltime.tm_sec = (fno->ftime & 0x1F) * 2; - ltime.tm_isdst = -1; // force mktime() to check dst + time_t timestamp = mktime(<ime); + if (timestamp == -1) { + return FR_INVALID_PARAMETER; + } - newTimes.modtime = mktime(<ime); - newTimes.actime = newTimes.modtime; + // Convert to file time and set it + auto systime = std::chrono::system_clock::from_time_t(timestamp); + auto ftime = systime_to_ftime(systime); - if (utime(realPath.c_str(), &newTimes)) { - TRACE_SIMPGMSPACE("f_utime(%s) = error %d (%s)", simpath.c_str(), errno, strerror(errno)); - return FR_DENIED; - } - else { - TRACE_SIMPGMSPACE("f_utime(%s) set mtime = %s", simpath.c_str(), ctime(&newTimes.modtime)); - return FR_OK; - } + std::error_code ec; + fs::last_write_time(realPath, ftime, ec); + + return ec ? FR_DENIED : FR_OK; } -int f_putc (TCHAR c, FIL * fil) +int f_putc(TCHAR c, FIL* fil) { - if (fil && fil->obj.fs) fwrite(&c, 1, 1, (FILE*)fil->obj.fs); - return FR_OK; -} - -int f_puts (const TCHAR * str, FIL * fil) -{ - int n; - for (n = 0; *str; str++, n++) { - if (f_putc(*str, fil) == EOF) return EOF; - } - return n; -} - -int f_printf (FIL *fil, const TCHAR * format, ...) -{ - va_list arglist; - va_start(arglist, format); - if (fil && fil->obj.fs) vfprintf((FILE*)fil->obj.fs, format, arglist); - va_end(arglist); - return 0; -} - -FRESULT f_getcwd (TCHAR *path, UINT sz_path) -{ - char cwd[1024]; - if (!getcwd(cwd, 1024)) { - TRACE_SIMPGMSPACE("f_getcwd() = getcwd() error %d (%s)", errno, strerror(errno)); - strcpy(path, "."); - return FR_NO_PATH; + if (fil && fil->obj.fs) { + _simu_FIL* sf = reinterpret_cast<_simu_FIL*>(fil->obj.fs); + if (sf->stream && sf->stream->is_open()) { + sf->stream->put(c); + fil->fptr++; + } } - std::string result = convertFromSimuPath(fixPathDelimiters(cwd).c_str()); - if (result.length() > sz_path) { - //TRACE_SIMPGMSPACE("f_getcwd(): buffer too short"); + return EOF; +} + +FRESULT f_getcwd(TCHAR *path, UINT sz_path) +{ + auto p_str = simuCurrentPath.generic_string(); + if (p_str.length() > sz_path) { return FR_NOT_ENOUGH_CORE; } - strcpy(path, result.c_str()); - TRACE_SIMPGMSPACE("f_getcwd() = %s", path); + strncpy(path, p_str.c_str(), sz_path + 1); return FR_OK; } -FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs) +FRESULT f_getfree(const TCHAR* path, DWORD* nclst, FATFS** fatfs) { // just fake that we always have some clusters free *nclst = 10; diff --git a/radio/src/tests/gtests.cpp b/radio/src/tests/gtests.cpp index 79d5cba3f..e822c934a 100644 --- a/radio/src/tests/gtests.cpp +++ b/radio/src/tests/gtests.cpp @@ -24,7 +24,7 @@ #include #include #include "gtests.h" -#include "hal/adc_driver.h" +#include "location.h" using ::testing::TestEventListener; using ::testing::EmptyTestEventListener; @@ -141,12 +141,10 @@ const char * nchar2string(const char * string, int size) return _stringResult; } -extern const etx_hal_adc_driver_t simu_adc_driver; - int main(int argc, char **argv) { simuInit(); - adcInit(&simu_adc_driver); + simuFatfsSetPaths(TESTS_PATH, nullptr); #if !defined(COLORLCD) menuLevel = 0; diff --git a/radio/src/tests/lcd.cpp b/radio/src/tests/lcd.cpp index 63d7404b6..03a96092c 100644 --- a/radio/src/tests/lcd.cpp +++ b/radio/src/tests/lcd.cpp @@ -24,11 +24,13 @@ #include #include -#include "edgetx.h" -#include "location.h" -#include "targets/simu/simulcd.h" - #if !defined(COLORLCD) +#include "simpgmspace.h" +#include "simulcd.h" + +#include "common/stdlcd/draw_functions.h" +#include "dataconstants.h" +#include "debug.h" #define STB_IMAGE_IMPLEMENTATION #include "stb/stb_image.h" @@ -48,7 +50,7 @@ static uint8_t _get_pixel(int x, int y) if (BPP == 1) { if (simuLcdBuf[x + (y / 8) * LCD_W] & (1 << (y % 8))) { - return 0; // TODO + return 0; } return 161; } @@ -56,7 +58,7 @@ static uint8_t _get_pixel(int x, int y) if (BPP == 4) { pixel_t p = simuLcdBuf[y / 2 * LCD_W + x]; uint8_t z = (y & 1) ? (p >> 4) : (p & 0x0F); - return 161 - ((uint16_t)z * 161) / 15; // TODO + return 161 - ((uint16_t)z * 161) / 15; } } @@ -67,7 +69,7 @@ static uint8_t _get_ref_pixel(const uint8_t* data, int x, int y, int bpp) static void dumpImage(const std::string& filename) { - std::string fullpath = TESTS_PATH "/images/color/failed_" + filename; + std::string fullpath = simuFatfsGetRealPath("images/bw/failed_" + filename); TRACE("dumping image '%s'", fullpath.c_str()); @@ -99,7 +101,7 @@ bool checkScreenshot(const char* test) filename += 'x' + std::to_string(LCD_H); filename += ".png"; - std::string fullpath = TESTS_PATH "/images/bw/" + filename; + std::string fullpath = simuFatfsGetRealPath("images/bw/" + filename); // Read data int32_t w, h, bpp; @@ -350,7 +352,7 @@ TEST(Lcd, BMPWrapping) { lcdClear(); uint8_t bitmap[2+40*40/2]; - lcdLoadBitmap(bitmap, TESTS_PATH "/images/bw/plane.bmp", 40, 40); + lcdLoadBitmap(bitmap, "images/bw/plane.bmp", 40, 40); lcdDrawBitmap(200, 0, bitmap); lcdDrawBitmap(200, 60, bitmap); lcdDrawBitmap(240, 60, bitmap); // x too big @@ -419,31 +421,31 @@ TEST(Lcd, lcdDrawBitmapLoadAndDisplay) // Test proper BMP files, they should display correctly { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(7, 32)); - EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/images/bw/4b_7x32.bmp", 7, 32) != NULL); + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), "images/bw/4b_7x32.bmp", 7, 32) != NULL); bitmap.leakCheck(); lcdDrawBitmap(10, 2, bitmap.buffer()); } { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(6, 32)); - EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/images/bw/1b_6x32.bmp", 6, 32) != NULL); + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), "images/bw/1b_6x32.bmp", 6, 32) != NULL); bitmap.leakCheck(); lcdDrawBitmap(20, 2, bitmap.buffer()); } { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(31, 31)); - EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/images/bw/4b_31x31.bmp", 31, 31) != NULL); + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), "images/bw/4b_31x31.bmp", 31, 31) != NULL); bitmap.leakCheck(); lcdDrawBitmap(30, 2, bitmap.buffer()); } { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(39, 32)); - EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/images/bw/1b_39x32.bmp", 39, 32) != NULL); + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), "images/bw/1b_39x32.bmp", 39, 32) != NULL); bitmap.leakCheck(); lcdDrawBitmap(70, 2, bitmap.buffer()); } { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(20, 20)); - EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/images/bw/4b_20x20.bmp", 20, 20) != NULL); + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), "images/bw/4b_20x20.bmp", 20, 20) != NULL); bitmap.leakCheck(); lcdDrawBitmap(120, 2, bitmap.buffer()); } @@ -457,7 +459,7 @@ TEST(Lcd, lcdDrawBitmapLoadAndDisplay) } { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(10, 10)); - EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/images/bw/1b_39x32.bmp", 10, 10) == NULL) << "to small buffer"; + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), "images/bw/1b_39x32.bmp", 10, 10) == NULL) << "to small buffer"; bitmap.leakCheck(); } } diff --git a/radio/src/tests/lcd_480x272.cpp b/radio/src/tests/lcd_480x272.cpp index af3216cba..880699bc3 100644 --- a/radio/src/tests/lcd_480x272.cpp +++ b/radio/src/tests/lcd_480x272.cpp @@ -22,11 +22,9 @@ #include #include -#include "location.h" -#include "edgetx.h" - #if defined(COLORLCD) +#include "simpgmspace.h" #include "bitmaps.h" #define STB_IMAGE_WRITE_IMPLEMENTATION @@ -47,8 +45,7 @@ void convert_RGB565_to_RGB888(uint8_t* dst, const BitmapBuffer* src, coord_t w, void dumpImage(const std::string& filename, const BitmapBuffer* dc) { - std::string fullpath = TESTS_PATH "/images/color/failed_" + filename; - + std::string fullpath = simuFatfsGetRealPath("images/color/failed_" + filename); TRACE("dumping image '%s'", fullpath.c_str()); // allocate enough for 3 channels @@ -72,7 +69,7 @@ bool checkScreenshot_colorlcd(const BitmapBuffer* dc, const char* test) filename += 'x' + std::to_string(LCD_H); filename += ".png"; - std::string fullpath = TESTS_PATH "/images/color/" + filename; + std::string fullpath = "images/color/" + filename; std::unique_ptr testPict( BitmapBuffer::loadBitmap(fullpath.c_str())); @@ -265,8 +262,7 @@ TEST(Lcd_colorlcd, bitmap) dc.clear(COLOR_THEME_SECONDARY3); dc.setClippingRect(100, 400, 50, 200); - std::unique_ptr bmp( - BitmapBuffer::loadBitmap(TESTS_PATH "/images/color/edgetx.png")); + std::unique_ptr bmp(BitmapBuffer::loadBitmap("images/color/edgetx.png")); dc.drawBitmap(0, 0, bmp.get()); dc.drawBitmap(320, 0, bmp.get()); dc.drawBitmap(0, 150, bmp.get());