Remove TactilityCore (#550)

This commit is contained in:
Ken Van Hoeylandt
2026-07-05 12:47:37 +02:00
committed by GitHub
parent dfaeb9a2d9
commit 66a4eb66d6
39 changed files with 10 additions and 807 deletions
-1
View File
@@ -6,7 +6,6 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
list(APPEND REQUIRES_LIST
TactilityKernel
TactilityCore
TactilityFreeRtos
hal-device-module
lvgl-module
+68
View File
@@ -0,0 +1,68 @@
/**
* @brief key-value storage for general purpose.
* Maps strings on a fixed set of data types.
*/
#pragma once
#include <cstdint>
#include <string>
#include <unordered_map>
namespace tt {
/**
* A dictionary that maps keys (strings) onto several atomary types.
*/
class Bundle final {
typedef uint32_t Hash;
enum class Type {
Bool,
Int32,
Int64,
String,
};
typedef struct {
Type type;
union {
bool value_bool;
int32_t value_int32;
int64_t value_int64;
};
std::string value_string;
} Value;
std::unordered_map<std::string, Value> entries;
public:
Bundle() = default;
Bundle(const Bundle& bundle) {
this->entries = bundle.entries;
}
bool getBool(const std::string& key) const;
int32_t getInt32(const std::string& key) const;
int64_t getInt64(const std::string& key) const;
std::string getString(const std::string& key) const;
bool hasBool(const std::string& key) const;
bool hasInt32(const std::string& key) const;
bool hasInt64(const std::string& key) const;
bool hasString(const std::string& key) const;
bool optBool(const std::string& key, bool& out) const;
bool optInt32(const std::string& key, int32_t& out) const;
bool optInt64(const std::string& key, int64_t& out) const;
bool optString(const std::string& key, std::string& out) const;
void putBool(const std::string& key, bool value);
void putInt32(const std::string& key, int32_t value);
void putInt64(const std::string& key, int64_t value);
void putString(const std::string& key, const std::string& value);
};
} // namespace
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#define TT_STRINGIFY(x) #x
// region Variable arguments support
// Adapted from https://stackoverflow.com/a/78848701/3848666
#define TT_ARG_IGNORE(X)
#define TT_ARG_CAT(X,Y) _TT_ARG_CAT(X,Y)
#define _TT_ARG_CAT(X,Y) X ## Y
#define TT_ARGCOUNT(...) _TT_ARGCOUNT ## __VA_OPT__(1(__VA_ARGS__) TT_ARG_IGNORE) (0)
#define _TT_ARGCOUNT1(X,...) _TT_ARGCOUNT ## __VA_OPT__(2(__VA_ARGS__) TT_ARG_IGNORE) (1)
#define _TT_ARGCOUNT2(X,...) _TT_ARGCOUNT ## __VA_OPT__(3(__VA_ARGS__) TT_ARG_IGNORE) (2)
#define _TT_ARGCOUNT3(X,...) _TT_ARGCOUNT ## __VA_OPT__(4(__VA_ARGS__) TT_ARG_IGNORE) (3)
#define _TT_ARGCOUNT4(X,...) _TT_ARGCOUNT ## __VA_OPT__(5(__VA_ARGS__) TT_ARG_IGNORE) (4)
#define _TT_ARGCOUNT5(X,...) 5
#define _TT_ARGCOUNT(X) X
#if defined(__GNUC__) || defined(__clang__)
#define TT_DEPRECATED __attribute__((deprecated))
#elif defined(_MSC_VER)
#define TT_DEPRECATED __declspec(deprecated)
#else
#pragma message("WARNING: TT_DEPRECATED is not implemented for this compiler")
#define TT_DEPRECATED
#endif
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <Tactility/freertoscompat/RTOS.h>
namespace tt {
typedef portBASE_TYPE CpuAffinity;
constexpr static CpuAffinity None = -1;
/**
* Determines the preferred affinity for certain (sub)systems.
*/
struct CpuAffinityConfiguration {
CpuAffinity system;
CpuAffinity graphics; // Display, LVGL
CpuAffinity wifi;
CpuAffinity mainDispatcher;
CpuAffinity apps;
CpuAffinity timer; // Tactility Timer (based on FreeRTOS)
};
void setCpuAffinityConfiguration(const CpuAffinityConfiguration& config);
const CpuAffinityConfiguration& getCpuAffinityConfiguration();
}
+96
View File
@@ -0,0 +1,96 @@
#pragma once
#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
#include <functional>
namespace tt::string {
/**
* Given a filesystem path as input, try and get the parent path.
* @param[in] path input path
* @param[out] output an output buffer that is allocated to at least the size of "current"
* @return true when successful
*/
bool getPathParent(const std::string& path, std::string& output);
/**
* Given a filesystem path as input, get the last segment of a path
* @param[in] path input path
*/
std::string getLastPathSegment(const std::string& path);
/**
* Splits the provided input into separate pieces with delimiter as separator text.
* When the input string is empty, the output list will be empty too.
*
* @param input the input to split up
* @param delimiter a non-empty string to recognize as separator
*/
std::vector<std::string> split(const std::string& input, const std::string& delimiter);
/**
* Splits the provided input into separate pieces with delimiter as separator text.
* When the input string is empty, the output list will be empty too.
*
* @param input the input to split up
* @param delimiter a non-empty string to recognize as separator
* @param callback the callback function that receives the split parts
*/
void split(const std::string& input, const std::string& delimiter, std::function<void(const std::string&)> callback);
/**
* Join a set of tokens into a single string, given a delimiter (separator).
* If the input is an empty list, the result will be an empty string.
* The delimeter is only placed inbetween tokens and not appended at the end of the resulting string.
*
* @param input the tokens to join together
* @param delimiter the separator to join with
*/
std::string join(const std::vector<std::string>& input, const std::string& delimiter);
/**
* Join a set of tokens into a single string, given a delimiter (separator).
* If the input is an empty list, the result will be an empty string.
* The delimeter is only placed inbetween tokens and not appended at the end of the resulting string.
*
* @param input the tokens to join together
* @param delimiter the separator to join with
*/
std::string join(const std::vector<const char*>& input, const std::string& delimiter);
/**
* Returns the lowercase value of a string.
* @warning This only works for strings with 1 byte per character
* @param[in] the string with lower and/or uppercase characters
* @return a string with only lowercase characters
*/
template <typename T>
std::basic_string<T> lowercase(const std::basic_string<T>& input) {
std::basic_string<T> output = input;
std::transform(
output.begin(),
output.end(),
output.begin(),
[](const T character) { return static_cast<T>(std::tolower(character)); }
);
return std::move(output);
}
/** @return true when input only has hex characters: [a-z], [A-Z], [0-9] */
bool isAsciiHexString(const std::string& input);
/** @return the first part of a file name right up (and excluding) the first period character. */
std::string removeFileExtension(const std::string& input);
/**
* Remove the given characters from the start and end of the specified string.
* @param[in] input the text to trim
* @param[in] characters the characters to remove from the input
* @return the input where the specified characters are removed from the start and end of the input string
*/
std::string trim(const std::string& input, const std::string& characters);
} // namespace
@@ -0,0 +1,5 @@
#pragma once
#include <cstdio>
#include "CoreDefines.h"
+167
View File
@@ -0,0 +1,167 @@
/**
* All functions in this file can be safely called without manually applying file locks.
* For calls to C stdlib APIs such as fopen(), always call file::getLock(path) first!
*/
#pragma once
#include <Tactility/TactilityCore.h>
#include <Tactility/Lock.h>
#include <cstdio>
#include <dirent.h>
#include <sys/stat.h>
#include <vector>
/**
* @warning SD card access requires a locking mechanism:
* @warning When using this in the Tactility main project, use `file::getLock()` or `file::withLock()`
*/
namespace tt::file {
/** File types for `dirent`'s `d_type`. */
enum {
TT_DT_UNKNOWN = 0, // Unknown type
TT_DT_FIFO = 1, // Named pipe or FIFO
TT_DT_CHR = 2, // Character device
TT_DT_DIR = 4, // Directory
TT_DT_BLK = 6, // Block device
TT_DT_REG = 8, // Regular file
TT_DT_LNK = 10, // Symbolic link
TT_DT_SOCK = 12, // Local-domain socket
TT_DT_WHT = 14 // Whiteout inodes
};
#ifdef _WIN32
constexpr char SEPARATOR = '\\';
#else
constexpr char SEPARATOR = '/';
#endif
struct FileCloser {
void operator()(FILE* file) {
if (file != nullptr) {
fclose(file);
}
}
};
typedef std::function<std::shared_ptr<Lock>(const std::string&)> FindLockFunction;
/**
* @param[in] path the path to get a lock for
* @return a lock instance (never null)
*/
std::shared_ptr<Lock> getLock(const std::string& path);
void setFindLockFunction(const FindLockFunction& function);
long getSize(FILE* file);
/** Read a file and return its data.
* @param[in] filepath the path of the file
* @param[out] size the amount of bytes that were read
* @return null on error, or an array of bytes in case of success
*/
std::unique_ptr<uint8_t[]> readBinary(const std::string& filepath, size_t& outSize);
/** Read a file and return a null-terminated string that represents its content.
* @param[in] filepath the path of the file
* @return null on error, or an array of bytes in case of success. Empty string returns as a single 0 character.
*/
std::unique_ptr<uint8_t[]> readString(const std::string& filepath);
/** Write text to a file
* @param[in] path file path to write to
* @param[in] content file content to write
* @return true when operation is successful
*/
bool writeString(const std::string& filepath, const std::string& content);
/** Ensure a directory path exists.
* @param[in] path the directory path to find, or to create recursively
* @param[in] mode the mode to use when creating directories
* @return true when the specified path was found, or otherwise creates the directories recursively with the specified mode.
*/
bool findOrCreateDirectory(const std::string& path, mode_t mode);
bool findOrCreateParentDirectory(const std::string& path, mode_t mode);
bool deleteRecursively(const std::string& path);
bool deleteFile(const std::string& path);
bool deleteDirectory(const std::string& path);
/**
* Concatenate a child path with a parent path, ensuring proper slash inbetween
* @param basePath an absolute path with or without trailing "/"
* @param childPath the name of the child path (e.g. subfolder or file)
* @return the concatenated path
*/
std::string getChildPath(const std::string& basePath, const std::string& childPath);
/**
* Find the last part of the path. This can either be the filename or the deepest folder name.
* @param path an absolute or relative path
* @return the last segment of the specified path
*/
std::string getLastPathSegment(const std::string& path);
/**
* Find the first part of the path. This is either the root folder, or a file if the latter has no parent folder.
* @param path an absolute or relative path
* @return the first segment of the specified path
*/
std::string getFirstPathSegment(const std::string& path);
typedef int (*ScandirFilter)(const dirent*);
typedef bool (*ScandirSort)(const dirent&, const dirent&);
/** Used for sorting by alphanumeric value and file type */
bool direntSortAlphaAndType(const dirent& left, const dirent& right);
/** A filter for filtering out "." and ".." */
int direntFilterDotEntries(const dirent* entry);
bool isFile(const std::string& path);
bool isDirectory(const std::string& path);
/**
* A scandir()-like implementation that works on ESP32.
* It does not return "." and ".." items but otherwise functions the same.
* It returns an allocated output array with allocated dirent instances.
* The caller is responsible for free-ing the memory of these.
*
* @param[in] path path the scan for files and directories
* @param[out] onEntry a pointer to a function that accepts an entry
* @return true if the directory exists and listing its contents was successful
*/
bool listDirectory(
const std::string& path,
std::function<void(const dirent&)> onEntry
);
/**
* A scandir()-like implementation that works on ESP32.
* It does not return "." and ".." items but otherwise functions the same.
* It returns an allocated output array with allocated dirent instances.
* The caller is responsible for free-ing the memory of these.
*
* @param[in] path path the scan for files and directories
* @param[out] outList a pointer to vector of dirent
* @param[in] filter an optional filter to filter out specific items (nullable)
* @param[in] sort an optional sorting function (nullable)
* @return the amount of items that were stored in "output" or -1 when an error occurred
*/
int scandir(
const std::string& path,
std::vector<dirent>& outList,
ScandirFilter filter = nullptr,
ScandirSort sort = nullptr
);
bool readLines(const std::string& filePath, bool stripNewLine, std::function<void(const char* line)> callback);
}
@@ -0,0 +1,28 @@
#ifdef ESP_PLATFORM
#pragma once
#include <cstdio>
#define CRASH_DATA_CALLSTACK_LIMIT 64
#define CRASH_DATA_INCLUDES_SP false
/** Represents a single frame on the callstack. */
struct CallstackFrame {
uint32_t pc = 0;
#if CRASH_DATA_INCLUDES_SP
uint32_t sp = 0;
#endif
};
/** Callstack-related crash data. */
struct CrashData {
bool callstackCorrupted = false;
uint8_t callstackLength = 0;
CallstackFrame callstack[CRASH_DATA_CALLSTACK_LIMIT];
};
/** @return the crash data */
const CrashData& getRtcCrashData();
#endif
@@ -0,0 +1,14 @@
#pragma once
namespace tt::kernel {
/** Recognized platform types */
typedef enum {
PlatformEsp,
PlatformSimulator
} Platform;
/** @return the platform that Tactility currently is running on. */
Platform getPlatform();
} // namespace
@@ -0,0 +1,24 @@
#pragma once
#include <tactility/drivers/spi_controller.h>
#include <tactility/device.h>
#include <Tactility/Lock.h>
namespace tt {
class SpiDeviceLock : public Lock {
::Device* device;
public:
explicit SpiDeviceLock(::Device* device) : device(device) { }
bool lock(TickType_t timeout) const override {
return spi_controller_try_lock(device, timeout) == ERROR_NONE;
}
void unlock() const override {
spi_controller_unlock(device);
}
};
}
@@ -0,0 +1,24 @@
#pragma once
#include <cstdint>
namespace tt::kernel::critical {
struct CriticalInfo {
uint32_t isrm;
bool fromIsr;
bool kernelRunning;
};
/** Enter a critical section
* @return info on the status
*/
CriticalInfo enter();
/**
* Exit a critical section
* @param[in] info the info from when the critical section was started
*/
void exit(CriticalInfo info);
} // namespace
+113
View File
@@ -0,0 +1,113 @@
#include "Tactility/Bundle.h"
namespace tt {
bool Bundle::getBool(const std::string& key) const {
return this->entries.find(key)->second.value_bool;
}
int32_t Bundle::getInt32(const std::string& key) const {
return this->entries.find(key)->second.value_int32;
}
int64_t Bundle::getInt64(const std::string& key) const {
return this->entries.find(key)->second.value_int64;
}
std::string Bundle::getString(const std::string& key) const {
return this->entries.find(key)->second.value_string;
}
bool Bundle::hasBool(const std::string& key) const {
auto entry = this->entries.find(key);
return entry != std::end(this->entries) && entry->second.type == Type::Bool;
}
bool Bundle::hasInt32(const std::string& key) const {
auto entry = this->entries.find(key);
return entry != std::end(this->entries) && entry->second.type == Type::Int32;
}
bool Bundle::hasInt64(const std::string& key) const {
auto entry = this->entries.find(key);
return entry != std::end(this->entries) && entry->second.type == Type::Int64;
}
bool Bundle::hasString(const std::string& key) const {
auto entry = this->entries.find(key);
return entry != std::end(this->entries) && entry->second.type == Type::String;
}
bool Bundle::optBool(const std::string& key, bool& out) const {
auto entry = this->entries.find(key);
if (entry != std::end(this->entries) && entry->second.type == Type::Bool) {
out = entry->second.value_bool;
return true;
} else {
return false;
}
}
bool Bundle::optInt32(const std::string& key, int32_t& out) const {
auto entry = this->entries.find(key);
if (entry != std::end(this->entries) && entry->second.type == Type::Int32) {
out = entry->second.value_int32;
return true;
} else {
return false;
}
}
bool Bundle::optInt64(const std::string& key, int64_t& out) const {
auto entry = this->entries.find(key);
if (entry != std::end(this->entries) && entry->second.type == Type::Int64) {
out = entry->second.value_int64;
return true;
} else {
return false;
}
}
bool Bundle::optString(const std::string& key, std::string& out) const {
auto entry = this->entries.find(key);
if (entry != std::end(this->entries) && entry->second.type == Type::String) {
out = entry->second.value_string;
return true;
} else {
return false;
}
}
void Bundle::putBool(const std::string& key, bool value) {
this->entries[key] = {
.type = Type::Bool,
.value_bool = value,
.value_string = ""
};
}
void Bundle::putInt32(const std::string& key, int32_t value) {
this->entries[key] = {
.type = Type::Int32,
.value_int32 = value,
.value_string = ""
};
}
void Bundle::putInt64(const std::string& key, int64_t value) {
this->entries[key] = {
.type = Type::Int64,
.value_int64 = value,
.value_string = ""
};
}
void Bundle::putString(const std::string& key, const std::string& value) {
this->entries[key] = {
.type = Type::String,
.value_bool = false,
.value_string = value
};
}
} // namespace
+108
View File
@@ -0,0 +1,108 @@
#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#endif
#include "Tactility/CpuAffinity.h"
#include <tactility/check.h>
namespace tt {
#ifdef ESP_PLATFORM
#ifdef CONFIG_ESP_WIFI_ENABLED
static CpuAffinity getEspWifiAffinity() {
#if CONFIG_FREERTOS_NUMBER_OF_CORES == 1
return 0;
#elif defined(CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0)
return 0;
#elif defined(CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1)
return 1;
#else // Assume core 0 (risky, but safer than "None")
return 0;
#endif
}
#endif
// Warning: Must watch ESP WiFi, as this task is used by WiFi
static CpuAffinity getEspMainSchedulerAffinity() {
#if CONFIG_FREERTOS_NUMBER_OF_CORES == 1
return 0;
#elif defined(CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0)
return 0;
#elif defined(CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1)
return 1;
#else
// Default to core 0 when no explicit WiFi pinning is configured
return 0;
#endif
}
static CpuAffinity getFreeRtosTimerAffinity() {
#if CONFIG_FREERTOS_NUMBER_OF_CORES == 1
return 0;
#elif defined(CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY)
return None;
#elif defined(CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0)
return 0;
#elif defined(CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1)
return 1;
#else
static_assert(false);
#endif
}
#if CONFIG_FREERTOS_NUMBER_OF_CORES == 1
static const CpuAffinityConfiguration esp = {
.system = 0,
.graphics = 0,
.wifi = 0,
.mainDispatcher = 0,
.apps = 0,
.timer = getFreeRtosTimerAffinity()
};
#elif CONFIG_FREERTOS_NUMBER_OF_CORES == 2
static const CpuAffinityConfiguration esp = {
.system = 0,
.graphics = 1,
#ifdef CONFIG_ESP_WIFI_ENABLED
.wifi = getEspWifiAffinity(),
#else
.wifi = 0,
#endif
.mainDispatcher = getEspMainSchedulerAffinity(),
.apps = 1,
.timer = getFreeRtosTimerAffinity()
};
#endif
#else
static const CpuAffinityConfiguration simulator = {
.system = None,
.graphics = None,
.wifi = None,
.mainDispatcher = 0,
.apps = None,
.timer = None
};
#endif
const CpuAffinityConfiguration& getCpuAffinityConfiguration() {
#ifdef ESP_PLATFORM
#if CONFIG_FREERTOS_NUMBER_OF_CORES == 2
// WiFi uses the main dispatcher to defer operations in the background
assert(esp.wifi == esp.mainDispatcher);
#endif // CORES
return esp;
#else
return simulator;
#endif
}
}
+135
View File
@@ -0,0 +1,135 @@
#include "Tactility/StringUtils.h"
#include <cstring>
#include <ranges>
#include <sstream>
#include <string>
namespace tt::string {
bool getPathParent(const std::string& path, std::string& output) {
auto index = path.find_last_of('/');
if (index == std::string::npos) {
return false;
} else if (index == 0) {
output = "/";
return true;
} else {
output = path.substr(0, index);
return true;
}
}
std::string getLastPathSegment(const std::string& path) {
auto index = path.find_last_of('/');
if (index != std::string::npos) {
return path.substr(index + 1);
} else {
return "";
}
}
void split(const std::string& input, const std::string& delimiter, std::function<void(const std::string&)> callback) {
size_t token_index = 0;
size_t delimiter_index;
const size_t delimiter_length = delimiter.length();
while ((delimiter_index = input.find(delimiter, token_index)) != std::string::npos) {
std::string token = input.substr(token_index, delimiter_index - token_index);
token_index = delimiter_index + delimiter_length;
callback(token);
}
auto end_token = input.substr(token_index);
if (!end_token.empty()) {
callback(end_token);
}
}
std::vector<std::string> split(const std::string&input, const std::string&delimiter) {
std::vector<std::string> result;
split(input, delimiter, [&result](const std::string& token) {
result.push_back(token);
});
return result;
}
std::string join(const std::vector<const char*>& input, const std::string& delimiter) {
std::stringstream stream;
size_t size = input.size();
if (size == 0) {
return "";
} else if (size == 1) {
return input.front();
} else {
auto iterator = input.begin();
while (iterator != input.end()) {
stream << *iterator;
iterator++;
if (iterator != input.end()) {
stream << delimiter;
}
}
}
return stream.str();
}
std::string join(const std::vector<std::string>& input, const std::string& delimiter) {
std::stringstream stream;
size_t size = input.size();
if (size == 0) {
return "";
} else if (size == 1) {
return input.front();
} else {
auto iterator = input.begin();
while (iterator != input.end()) {
stream << *iterator;
iterator++;
if (iterator != input.end()) {
stream << delimiter;
}
}
}
return stream.str();
}
std::string removeFileExtension(const std::string& input) {
auto index = input.find('.');
if (index != std::string::npos) {
return input.substr(0, index);
} else {
return input;
}
}
bool isAsciiHexString(const std::string& input) {
// Find invalid characters
return std::ranges::views::filter(input, [](char character){
if (
(('0' <= character) && ('9' >= character)) ||
(('a' <= character) && ('f' >= character)) ||
(('A' <= character) && ('F' >= character))
) {
return false;
} else {
return true;
}
}).empty();
}
std::string trim(const std::string& input, const std::string& characters) {
auto index = input.find_first_not_of(characters);
if (index == std::string::npos) {
return "";
} else {
auto end_index = input.find_last_not_of(characters);
return input.substr(index, end_index - index + 1);
}
}
} // namespace
+391
View File
@@ -0,0 +1,391 @@
#include <Tactility/file/File.h>
#include <cstring>
#include <fstream>
#include <unistd.h>
#include <tactility/log.h>
#include <Tactility/StringUtils.h>
namespace tt::hal::sdcard {
class SdCardDevice;
}
namespace tt::file {
constexpr auto* TAG = "file";
class NoLock final : public Lock {
bool lock(TickType_t timeout) const override { return true; }
void unlock() const override { /* NO-OP */ }
};
static std::shared_ptr<Lock> noLock = std::make_shared<NoLock>();
static std::function<std::shared_ptr<Lock>(const std::string&)> findLockFunction = nullptr;
std::shared_ptr<Lock> getLock(const std::string& path) {
if (findLockFunction == nullptr) {
LOG_W(TAG, "File lock function not set!");
return noLock;
}
auto lock = findLockFunction(path);
if (lock == nullptr) {
return noLock;
}
return lock;
}
void setFindLockFunction(const FindLockFunction& function) {
findLockFunction = function;
}
std::string getChildPath(const std::string& basePath, const std::string& childPath) {
// Postfix with "/" when the current path isn't "/"
if (basePath.length() != 1) {
return basePath + "/" + childPath;
} else {
return "/" + childPath;
}
}
int direntFilterDotEntries(const dirent* entry) {
return (strcmp(entry->d_name, "..") == 0 || strcmp(entry->d_name, ".") == 0) ? -1 : 0;
}
bool direntSortAlphaAndType(const dirent& left, const dirent& right) {
bool left_is_dir = left.d_type == TT_DT_DIR || left.d_type == TT_DT_CHR;
bool right_is_dir = right.d_type == TT_DT_DIR || right.d_type == TT_DT_CHR;
if (left_is_dir == right_is_dir) {
return strcmp(left.d_name, right.d_name) < 0;
} else {
return left_is_dir > right_is_dir;
}
}
bool listDirectory(
const std::string& path,
std::function<void(const dirent&)> onEntry
) {
auto lock = getLock(path)->asScopedLock();
lock.lock();
LOG_I(TAG, "listDir start %s", path.c_str());
DIR* dir = opendir(path.c_str());
if (dir == nullptr) {
LOG_E(TAG, "Failed to open dir %s", path.c_str());
return false;
}
dirent* current_entry;
while ((current_entry = readdir(dir)) != nullptr) {
onEntry(*current_entry);
}
closedir(dir);
LOG_I(TAG, "listDir stop %s", path.c_str());
return true;
}
int scandir(
const std::string& path,
std::vector<dirent>& outList,
ScandirFilter filterMethod,
ScandirSort sortMethod
) {
auto lock = getLock(path)->asScopedLock();
lock.lock();
LOG_I(TAG, "scandir start");
DIR* dir = opendir(path.c_str());
if (dir == nullptr) {
LOG_E(TAG, "Failed to open dir %s", path.c_str());
return -1;
}
dirent* current_entry;
while ((current_entry = readdir(dir)) != nullptr) {
if (filterMethod == nullptr || filterMethod(current_entry) == 0) {
outList.push_back(*current_entry);
}
}
closedir(dir);
if (sortMethod != nullptr) {
std::ranges::sort(outList, sortMethod);
}
LOG_I(TAG, "scandir finish");
return outList.size();
}
long getSize(FILE* file) {
long original_offset = ftell(file);
if (fseek(file, 0, SEEK_END) != 0) {
LOG_E(TAG, "fseek failed");
return -1;
}
long file_size = ftell(file);
if (file_size == -1) {
LOG_E(TAG, "Could not get file length");
return -1;
}
if (fseek(file, original_offset, SEEK_SET) != 0) {
LOG_E(TAG, "fseek Failed");
return -1;
}
return file_size;
}
/** Read a file.
* @param[in] filepath
* @param[out] outSize the amount of bytes that were read, excluding the sizePadding
* @param[in] sizePadding optional padding to add at the end of the output data (the values are not set)
*/
static std::unique_ptr<uint8_t[]> readBinaryInternal(const std::string& filepath, size_t& outSize, size_t sizePadding = 0) {
FILE* file = fopen(filepath.c_str(), "rb");
if (file == nullptr) {
LOG_E(TAG, "Failed to open %s", filepath.c_str());
return nullptr;
}
long content_length = getSize(file);
if (content_length == -1) {
LOG_E(TAG, "Failed to determine content length for %s", filepath.c_str());
return nullptr;
}
auto data = std::make_unique<uint8_t[]>(content_length + sizePadding);
if (data == nullptr) {
LOG_E(TAG, "Insufficient memory. Failed to allocate %ld bytes.", content_length);
return nullptr;
}
size_t buffer_offset = 0;
while (buffer_offset < content_length) {
size_t bytes_read = fread(&data.get()[buffer_offset], 1, content_length - buffer_offset, file);
LOG_D(TAG, "Read %u bytes", (unsigned)bytes_read);
if (bytes_read > 0) {
buffer_offset += bytes_read;
} else { // Something went wrong?
break;
}
}
fclose(file);
if (buffer_offset == content_length) {
outSize = buffer_offset;
return data;
} else {
outSize = 0;
return nullptr;
}
}
std::unique_ptr<uint8_t[]> readBinary(const std::string& filepath, size_t& outSize) {
return readBinaryInternal(filepath, outSize);
}
std::unique_ptr<uint8_t[]> readString(const std::string& filepath) {
size_t size = 0;
auto data = readBinaryInternal(filepath, size, 1);
if (data == nullptr) {
return nullptr;
}
data.get()[size] = 0; // Append null terminator
return data;
}
bool writeString(const std::string& filepath, const std::string& content) {
std::ofstream fileStream(filepath);
if (!fileStream.is_open()) {
return false;
}
fileStream << content;
fileStream.close();
return true;
}
static bool findOrCreateDirectoryInternal(std::string path, mode_t mode) {
auto lock = getLock(path)->asScopedLock();
lock.lock();
struct stat dir_stat;
if (mkdir(path.c_str(), mode) == 0) {
return true;
}
if (errno != EEXIST) {
return false;
}
if (stat(path.c_str(), &dir_stat) != 0) {
return false;
}
if (!S_ISDIR(dir_stat.st_mode)) {
return false;
}
return true;
}
std::string getLastPathSegment(const std::string& path) {
auto index = path.find_last_of('/');
if (index != std::string::npos) {
return path.substr(index + 1);
} else {
return path;
}
}
std::string getFirstPathSegment(const std::string& path) {
if (path.empty()) {
return path;
}
auto start_index = path[0] == SEPARATOR ? 1 : 0;
auto index = path.find_first_of(SEPARATOR, start_index);
if (index != std::string::npos) {
return path.substr(0, index);
} else {
return path;
}
}
bool findOrCreateDirectory(const std::string& path, mode_t mode) {
if (path.empty()) {
return true;
}
LOG_D(TAG, "findOrCreate: %s %u", path.c_str(), (unsigned)mode);
const char separator_to_find[] = {SEPARATOR, 0x00};
auto first_index = path[0] == SEPARATOR ? 1 : 0;
auto separator_index = path.find(separator_to_find, first_index);
bool should_break = false;
while (!should_break) {
bool is_last_segment = (separator_index == std::string::npos);
auto to_create = is_last_segment ? path : path.substr(0, separator_index);
should_break = is_last_segment;
if (!findOrCreateDirectoryInternal(to_create, mode)) {
LOG_E(TAG, "Failed to create %s", to_create.c_str());
return false;
} else {
LOG_D(TAG, " - got: %s", to_create.c_str());
}
// Find next file separator index
separator_index = path.find(separator_to_find, separator_index + 1);
}
return true;
}
bool findOrCreateParentDirectory(const std::string& path, mode_t mode) {
std::string parent;
if (!string::getPathParent(path, parent)) {
return false;
}
return findOrCreateDirectory(parent, mode);
}
bool deleteRecursively(const std::string& path) {
if (path.empty()) {
return true;
}
if (isDirectory(path)) {
std::vector<dirent> entries;
if (scandir(path, entries) < 0) {
LOG_E(TAG, "Failed to scan directory %s", path.c_str());
return false;
}
for (const auto& entry : entries) {
auto child_path = path + "/" + entry.d_name;
if (!deleteRecursively(child_path)) {
return false;
}
}
LOG_I(TAG, "Deleting %s", path.c_str());
return deleteDirectory(path);
} else if (isFile(path)) {
LOG_I(TAG, "Deleting %s", path.c_str());
return deleteFile(path);
} else if (path == "/" || path == "." || path == "..") {
// No-op
return true;
} else {
LOG_E(TAG, "Failed to delete \"%s\": unknown type", path.c_str());
return false;
}
}
bool deleteFile(const std::string& path) {
auto lock = getLock(path)->asScopedLock();
lock.lock();
return remove(path.c_str()) == 0;
}
bool deleteDirectory(const std::string& path) {
auto lock = getLock(path)->asScopedLock();
lock.lock();
return rmdir(path.c_str()) == 0;
}
bool isFile(const std::string& path) {
auto lock = getLock(path)->asScopedLock();
lock.lock();
return access(path.c_str(), F_OK) == 0;
}
bool isDirectory(const std::string& path) {
auto lock = getLock(path)->asScopedLock();
lock.lock();
struct stat stat_result;
return stat(path.c_str(), &stat_result) == 0 && S_ISDIR(stat_result.st_mode);
}
bool readLines(const std::string& filePath, bool stripNewLine, std::function<void(const char* line)> callback) {
auto lock = getLock(filePath)->asScopedLock();
lock.lock();
auto* file = fopen(filePath.c_str(), "r");
if (file == nullptr) {
return false;
}
char line[1024];
while (fgets(line, sizeof(line), file) != nullptr) {
// Strip newline
if (stripNewLine) {
size_t line_length = strlen(line);
if (line_length > 0 && line[line_length - 1] == '\n') {
line[line_length - 1] = '\0';
}
}
// Publish
callback(line);
}
bool success = feof(file);
fclose(file);
return success;
}
}
+95
View File
@@ -0,0 +1,95 @@
#if defined(ESP_PLATFORM) && defined(CONFIG_IDF_TARGET_ARCH_XTENSA)
#include "Tactility/kernel/PanicHandler.h"
#include <esp_debug_helpers.h>
#include <esp_attr.h>
#include <esp_memory_utils.h>
#include <esp_cpu.h>
#include <esp_cpu_utils.h>
#include <xtensa/xtruntime.h>
extern "C" {
/**
* This static variable survives a crash reboot.
* It is reset by the Boot app.
*/
static RTC_NOINIT_ATTR CrashData crashData;
void __real_esp_panic_handler(void* info);
void __wrap_esp_panic_handler(void* info) {
esp_backtrace_frame_t frame = {
.pc = 0,
.sp = 0,
.next_pc = 0,
.exc_frame = nullptr
};
crashData.callstackLength = 0;
esp_backtrace_get_start(&frame.pc, &frame.sp, &frame.next_pc);
crashData.callstack[0].pc = frame.pc;
#if CRASH_DATA_INCLUDES_SP
crashData.callstack[0].sp = frame.sp;
#endif
crashData.callstackLength++;
uint32_t processed_pc = esp_cpu_process_stack_pc(frame.pc);
bool pc_is_valid = esp_ptr_executable((void *)processed_pc);
/* Ignore the first corrupted PC in case of InstrFetchProhibited on Xtensa */
if (frame.exc_frame && ((XtExcFrame *)frame.exc_frame)->exccause == EXCCAUSE_INSTR_PROHIBITED) {
pc_is_valid = true;
}
crashData.callstackCorrupted = !(esp_stack_ptr_is_sane(frame.sp) && pc_is_valid);
while (
frame.next_pc != 0 &&
!crashData.callstackCorrupted
&& crashData.callstackLength < CRASH_DATA_CALLSTACK_LIMIT
) {
if (esp_backtrace_get_next_frame(&frame)) {
// Validate the current frame
uint32_t processed_frame_pc = esp_cpu_process_stack_pc(frame.pc);
bool frame_pc_is_valid = esp_ptr_executable((void *)processed_frame_pc);
if (!esp_stack_ptr_is_sane(frame.sp) || !frame_pc_is_valid) {
crashData.callstackCorrupted = true;
break;
}
crashData.callstack[crashData.callstackLength].pc = frame.pc;
#if CRASH_DATA_INCLUDES_SP
crashData.callstack[crashData.callstackLength].sp = frame.sp;
#endif
crashData.callstackLength++;
} else {
crashData.callstackCorrupted = true;
break;
}
}
// TODO: Handle corrupted logic
__real_esp_panic_handler(info);
}
}
const CrashData& getRtcCrashData() { return crashData; }
#elif defined(ESP_PLATFORM)
// Stub implementation for RISC-V and other architectures
// TODO: Implement crash data collection for RISC-V using frame pointer or EH frame
#include <Tactility/kernel/PanicHandler.h>
static CrashData emptyCrashData = {};
const CrashData& getRtcCrashData() { return emptyCrashData; }
#endif
+13
View File
@@ -0,0 +1,13 @@
#include "Tactility/kernel/Platform.h"
namespace tt::kernel {
Platform getPlatform() {
#ifdef ESP_PLATFORM
return PlatformEsp;
#else
return PlatformSimulator;
#endif
}
} // namespace
@@ -0,0 +1,45 @@
#include <Tactility/kernel/critical/Critical.h>
#include <Tactility/freertoscompat/Task.h>
#include <Tactility/kernel/Kernel.h>
#ifdef ESP_PLATFORM
static portMUX_TYPE critical_mutex;
#define TT_ENTER_CRITICAL() taskENTER_CRITICAL(&critical_mutex)
#define TT_EXIT_CRITICAL() taskEXIT_CRITICAL(&critical_mutex)
#else
#define TT_ENTER_CRITICAL() taskENTER_CRITICAL()
#define TT_EXIT_CRITICAL() taskEXIT_CRITICAL()
#endif
namespace tt::kernel::critical {
CriticalInfo enter() {
CriticalInfo info = {
.isrm = 0,
.fromIsr = (xPortInIsrContext() == pdTRUE),
.kernelRunning = (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING)
};
if (info.fromIsr) {
info.isrm = taskENTER_CRITICAL_FROM_ISR();
} else if (info.kernelRunning) {
TT_ENTER_CRITICAL();
} else {
portDISABLE_INTERRUPTS();
}
return info;
}
void exit(CriticalInfo info) {
if (info.fromIsr) {
taskEXIT_CRITICAL_FROM_ISR(info.isrm);
} else if (info.kernelRunning) {
TT_EXIT_CRITICAL();
} else {
portENABLE_INTERRUPTS();
}
}
}