New kernel drivers, filesystem API, and more (#513)
* **New Features** * BMI270 6-axis IMU driver added; new unified filesystem abstraction for mounted filesystems. * Public Wi‑Fi API surface (no implementation yet) * SDMMC driver added (kernel drive$) * expanded GPIO interrupt/callback support * **Improvements** * M5Stack Tab5: revamped GPIO/power initialization and IMU integration. * LVGL updates including device fontSize configuration. * Updated all code related to SD card device/fs handling * Rename LilyGO T-HMI S3 to LilyGO T-HMI * **Bug Fixes** * Simplified and consolidated SD card handling and mount discovery.
This commit is contained in:
committed by
GitHub
parent
2de35b2d2d
commit
aa7530e515
@@ -1,88 +0,0 @@
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/LogMessages.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/service/ServiceContext.h>
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/Timer.h>
|
||||
|
||||
namespace tt::service::sdcard {
|
||||
|
||||
static const auto LOGGER = Logger("SdcardService");
|
||||
|
||||
extern const ServiceManifest manifest;
|
||||
|
||||
class SdCardService final : public Service {
|
||||
|
||||
Mutex mutex;
|
||||
std::unique_ptr<Timer> updateTimer;
|
||||
hal::sdcard::SdCardDevice::State lastState = hal::sdcard::SdCardDevice::State::Unmounted;
|
||||
|
||||
bool lock(TickType_t timeout) const {
|
||||
return mutex.lock(timeout);
|
||||
}
|
||||
|
||||
void unlock() const {
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
void update() {
|
||||
// TODO: Support multiple SD cards
|
||||
auto sdcard = hal::findFirstDevice<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||
if (sdcard == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lock(50)) {
|
||||
auto new_state = sdcard->getState();
|
||||
|
||||
if (new_state == hal::sdcard::SdCardDevice::State::Error) {
|
||||
LOGGER.error("Sdcard error - unmounting. Did you eject the card in an unsafe manner?");
|
||||
sdcard->unmount();
|
||||
}
|
||||
|
||||
if (new_state != lastState) {
|
||||
lastState = new_state;
|
||||
}
|
||||
|
||||
unlock();
|
||||
} else {
|
||||
LOGGER.warn(LOG_MESSAGE_MUTEX_LOCK_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool onStart(ServiceContext& serviceContext) override {
|
||||
if (hal::findFirstDevice<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard) == nullptr) {
|
||||
LOGGER.warn("No SD card device found - not starting Service");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto service = findServiceById<SdCardService>(manifest.id);
|
||||
updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, 1000, [service] {
|
||||
service->update();
|
||||
});
|
||||
|
||||
// We want to try and scan more often in case of startup or scan lock failure
|
||||
updateTimer->start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onStop(ServiceContext& serviceContext) override {
|
||||
if (updateTimer != nullptr) {
|
||||
// Stop thread
|
||||
updateTimer->stop();
|
||||
updateTimer = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extern const ServiceManifest manifest = {
|
||||
.id = "sdcard",
|
||||
.createService = create<SdCardService>
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/Paths.h>
|
||||
#include <Tactility/Timer.h>
|
||||
#include <tactility/check.h>
|
||||
#include <Tactility/hal/power/PowerDevice.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
#include <Tactility/lvgl/Lvgl.h>
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
#include <Tactility/service/gps/GpsService.h>
|
||||
#include <Tactility/service/wifi/Wifi.h>
|
||||
#include <tactility/check.h>
|
||||
|
||||
#include <tactility/lvgl_icon_statusbar.h>
|
||||
|
||||
@@ -56,18 +57,9 @@ static const char* getWifiStatusIcon(wifi::RadioState state) {
|
||||
}
|
||||
}
|
||||
|
||||
static const char* getSdCardStatusIcon(hal::sdcard::SdCardDevice::State state) {
|
||||
switch (state) {
|
||||
using enum hal::sdcard::SdCardDevice::State;
|
||||
case Mounted:
|
||||
return LVGL_ICON_STATUSBAR_SD_CARD;
|
||||
case Error:
|
||||
case Unmounted:
|
||||
case Timeout:
|
||||
return LVGL_ICON_STATUSBAR_SD_CARD_ALERT;
|
||||
default:
|
||||
check(false, "Unhandled SdCard state");
|
||||
}
|
||||
static const char* getSdCardStatusIcon(bool mounted) {
|
||||
if (mounted) return LVGL_ICON_STATUSBAR_SD_CARD;
|
||||
return LVGL_ICON_STATUSBAR_SD_CARD_ALERT;
|
||||
}
|
||||
|
||||
static const char* getPowerStatusIcon() {
|
||||
@@ -172,20 +164,21 @@ class StatusbarService final : public Service {
|
||||
}
|
||||
|
||||
void updateSdCardIcon() {
|
||||
auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||
auto* sdcard_fs = findSdcardFileSystem(false);
|
||||
// TODO: Support multiple SD cards
|
||||
auto sdcard = sdcards.empty() ? nullptr : sdcards[0];
|
||||
if (sdcard != nullptr) {
|
||||
auto state = sdcard->getState(50 / portTICK_PERIOD_MS);
|
||||
if (state != hal::sdcard::SdCardDevice::State::Timeout) {
|
||||
auto* desired_icon = getSdCardStatusIcon(state);
|
||||
if (sdcard_last_icon != desired_icon) {
|
||||
lvgl::statusbar_icon_set_image(sdcard_icon_id, desired_icon);
|
||||
lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true);
|
||||
sdcard_last_icon = desired_icon;
|
||||
}
|
||||
if (sdcard_fs != nullptr) {
|
||||
auto mounted = file_system_is_mounted(sdcard_fs);
|
||||
auto* desired_icon = getSdCardStatusIcon(mounted);
|
||||
if (sdcard_last_icon != desired_icon) {
|
||||
lvgl::statusbar_icon_set_image(sdcard_icon_id, desired_icon);
|
||||
lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true);
|
||||
sdcard_last_icon = desired_icon;
|
||||
}
|
||||
} else {
|
||||
if (sdcard_last_icon != nullptr) {
|
||||
lvgl::statusbar_icon_set_visibility(sdcard_icon_id, false);
|
||||
sdcard_last_icon = nullptr;
|
||||
}
|
||||
// TODO: Consider tracking how long the SD card has been in unknown status and then show error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <Tactility/StringUtils.h>
|
||||
|
||||
#include <ranges>
|
||||
#include <tactility/filesystem/file_system.h>
|
||||
|
||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||
#include <lv_screenshot.h>
|
||||
@@ -764,20 +765,24 @@ esp_err_t WebServerService::handleFsList(httpd_req_t* request) {
|
||||
|
||||
std::ostringstream json;
|
||||
json << "{\"path\":\"" << norm << "\",\"entries\":[";
|
||||
|
||||
struct FsIterContext {
|
||||
std::ostringstream& json;
|
||||
uint16_t count = 0;
|
||||
};
|
||||
FsIterContext fs_iter_context { json };
|
||||
// Special handling for root: show available mount points
|
||||
if (norm == "/") {
|
||||
// Always show /data
|
||||
json << "{\"name\":\"data\",\"type\":\"dir\",\"size\":0}";
|
||||
|
||||
// Show /sdcard if mounted
|
||||
const auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||
for (const auto& sdcard : sdcard_devices) {
|
||||
if (sdcard->isMounted()) {
|
||||
json << ",{\"name\":\"sdcard\",\"type\":\"dir\",\"size\":0}";
|
||||
break;
|
||||
file_system_for_each(&fs_iter_context, [] (auto* fs, void* context) {
|
||||
auto* fs_iter_context = static_cast<FsIterContext*>(context);
|
||||
char path[128];
|
||||
if (file_system_is_mounted(fs) && file_system_get_path(fs, path, sizeof(path)) == ERROR_NONE && strcmp(path, "/system") != 0) {
|
||||
fs_iter_context->count++;
|
||||
if (fs_iter_context->count != 1) fs_iter_context->json << ","; // add separator between json array entries
|
||||
fs_iter_context->json << "{\"name\":\"" << path << "\",\"type\":\"dir\",\"size\":0}";
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
json << "]}";
|
||||
} else {
|
||||
std::vector<dirent> entries;
|
||||
@@ -1160,34 +1165,38 @@ esp_err_t WebServerService::handleApiSysinfo(httpd_req_t* request) {
|
||||
json << "\"storage\":{";
|
||||
uint64_t storage_total = 0, storage_free = 0;
|
||||
|
||||
// Data partition
|
||||
json << "\"data\":{";
|
||||
if (esp_vfs_fat_info(file::MOUNT_POINT_DATA, &storage_total, &storage_free) == ESP_OK) {
|
||||
json << "\"free\":" << storage_free << ",";
|
||||
json << "\"total\":" << storage_total << ",";
|
||||
json << "\"mounted\":true";
|
||||
} else {
|
||||
json << "\"mounted\":false";
|
||||
}
|
||||
json << "},";
|
||||
struct FsIterContext {
|
||||
std::ostringstream& json;
|
||||
uint16_t count = 0;
|
||||
};
|
||||
FsIterContext fs_iter_context { json };
|
||||
file_system_for_each(&fs_iter_context, [] (auto* fs, void* context) {
|
||||
char mount_path[128] = "";
|
||||
if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) return true;
|
||||
if (strcmp(mount_path, "/system") == 0) return true; // Hide system partition
|
||||
|
||||
// SD card - check all sdcard devices
|
||||
json << "\"sdcard\":{";
|
||||
bool sdcard_found = false;
|
||||
const auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||
for (const auto& sdcard : sdcard_devices) {
|
||||
if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) {
|
||||
json << "\"free\":" << storage_free << ",";
|
||||
json << "\"total\":" << storage_total << ",";
|
||||
json << "\"mounted\":true";
|
||||
sdcard_found = true;
|
||||
break;
|
||||
bool mounted = file_system_is_mounted(fs);
|
||||
auto* fs_iter_context = static_cast<FsIterContext*>(context);
|
||||
auto& json_context = fs_iter_context->json;
|
||||
std::string mount_path_cpp = mount_path;
|
||||
|
||||
fs_iter_context->count++;
|
||||
if (fs_iter_context->count != 1) json_context << ","; // add separator between json array entries
|
||||
json_context << "\"" << mount_path_cpp.substr(1) << "\":{";
|
||||
|
||||
uint64_t storage_total = 0, storage_free = 0;
|
||||
if (esp_vfs_fat_info(mount_path, &storage_total, &storage_free) == ESP_OK) {
|
||||
json_context << "\"free\":" << storage_free << ",";
|
||||
json_context << "\"total\":" << storage_total << ",";
|
||||
} else {
|
||||
json_context << "\"free\":0,";
|
||||
json_context << "\"total\":0,";
|
||||
}
|
||||
}
|
||||
if (!sdcard_found) {
|
||||
json << "\"mounted\":false";
|
||||
}
|
||||
json << "}";
|
||||
|
||||
json_context << "\"mounted\":" << (mounted ? "true" : "false") << "";
|
||||
json_context << "}";
|
||||
return true;
|
||||
});
|
||||
|
||||
json << "},"; // end storage
|
||||
|
||||
@@ -1459,14 +1468,7 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) {
|
||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||
// Determine save location: prefer SD card root if mounted, otherwise /data
|
||||
std::string save_path;
|
||||
auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||
for (const auto& sdcard : sdcard_devices) {
|
||||
if (sdcard->isMounted()) {
|
||||
save_path = sdcard->getMountPath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (save_path.empty()) {
|
||||
if (!findFirstMountedSdCardPath(save_path)) {
|
||||
save_path = file::MOUNT_POINT_DATA;
|
||||
}
|
||||
|
||||
@@ -1543,7 +1545,7 @@ esp_err_t WebServerService::handleFsTree(httpd_req_t* request) {
|
||||
std::ostringstream json;
|
||||
json << "{";
|
||||
// Gather mount points
|
||||
auto mounts = file::getMountPoints();
|
||||
auto mounts = file::getFileSystemDirents();
|
||||
json << "\"mounts\": [";
|
||||
bool firstMount = true;
|
||||
for (auto& m : mounts) {
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/service/wifi/WifiApSettings.h>
|
||||
|
||||
#include <Tactility/Paths.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
#include <dirent.h>
|
||||
#include <format>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
|
||||
namespace tt::service::wifi {
|
||||
|
||||
@@ -118,18 +119,14 @@ static void importWifiApSettingsFromDir(const std::string& path) {
|
||||
void bootSplashInit() {
|
||||
getMainDispatcher().dispatch([] {
|
||||
// First import any provisioning files placed on the system data partition.
|
||||
const std::string settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings");
|
||||
importWifiApSettingsFromDir(settings_path);
|
||||
const std::string data_settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings");
|
||||
importWifiApSettingsFromDir(data_settings_path);
|
||||
|
||||
// Then scan attached SD cards as before.
|
||||
const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||
for (auto& sdcard : sdcards) {
|
||||
if (sdcard->isMounted()) {
|
||||
const std::string settings_path = file::getChildPath(sdcard->getMountPath(), "settings");
|
||||
importWifiApSettingsFromDir(settings_path);
|
||||
} else {
|
||||
LOGGER.warn("Skipping unmounted SD card {}", sdcard->getMountPath());
|
||||
}
|
||||
std::string sdcard_path;
|
||||
if (findFirstMountedSdCardPath((sdcard_path))) {
|
||||
const std::string sd_settings_path = file::getChildPath(sdcard_path, "settings");
|
||||
importWifiApSettingsFromDir(sd_settings_path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user