Remove old HAL components and refactored GPS-related code (#583)

- Added generic GPS/GNSS support with device detection, configuration, and persistent settings.
- Improved device and module lifecycle management.
- Added flexible filesystem locking support for displays and storage.
- Improved display-idle and keyboard backlight handling.
- Updated architecture, driver, module, testing, and licensing documentation.
- Removed old HAL device and related code.
This commit is contained in:
Ken Van Hoeylandt
2026-07-25 17:20:17 +02:00
committed by GitHub
parent 29e80cfd65
commit 2a2558b29a
173 changed files with 3855 additions and 5445 deletions
@@ -2,32 +2,28 @@
#include <Tactility/service/displayidle/DisplayIdleService.h>
#include "Screensaver.h"
#include "BouncingBallsScreensaver.h"
#include "MatrixRainScreensaver.h"
#include "MystifyScreensaver.h"
#include "Screensaver.h"
#include "StackChanScreensaver.h"
#include <tactility/log.h>
#include <Tactility/CoreDefines.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h>
#include <cstdlib>
#include <ctime>
#include <tactility/log.h>
#include <tactility/drivers/display.h>
#include <tactility/drivers/backlight.h>
#include <tactility/lvgl_module.h>
namespace tt::service::displayidle {
constexpr auto* TAG = "DisplayIdle";
constexpr uint32_t kWakeActivityThresholdMs = 100;
static std::shared_ptr<hal::display::DisplayDevice> getDisplay() {
return hal::findFirstDevice<hal::display::DisplayDevice>(hal::Device::Type::Display);
}
void DisplayIdleService::stopScreensaverCb(lv_event_t* e) {
auto* self = static_cast<DisplayIdleService*>(lv_event_get_user_data(e));
lv_event_stop_bubbling(e);
@@ -35,8 +31,34 @@ void DisplayIdleService::stopScreensaverCb(lv_event_t* e) {
lv_display_trigger_activity(nullptr);
}
static void setBacklightBrightness(uint8_t brightness) {
::Device* display;
if (device_get_first_active_by_type(&DISPLAY_TYPE, &display) == ERROR_NONE) {
::Device* backlight;
if (display_get_backlight(display, &backlight) == ERROR_NONE) {
device_get(backlight);
backlight_set_brightness(backlight, brightness);
device_put(backlight);
}
device_put(display);
}
}
static bool hasDisplayWithBacklight() {
::Device* display;
bool result = false;
if (device_get_first_active_by_type(&DISPLAY_TYPE, &display) == ERROR_NONE) {
::Device* backlight;
if (display_get_backlight(display, &backlight) == ERROR_NONE) {
result = true;
}
device_put(display);
}
return result;
}
void DisplayIdleService::stopScreensaver() {
if (!lvgl::lock(100)) {
if (!lvgl_try_lock(100)) {
// Lock failed - keep flag set to retry on next tick
return;
}
@@ -52,7 +74,7 @@ void DisplayIdleService::stopScreensaver() {
lv_obj_delete(screensaverOverlay);
screensaverOverlay = nullptr;
}
lvgl::unlock();
lvgl_unlock();
stopScreensaverRequested.store(false, std::memory_order_relaxed);
// Reset auto-off state
@@ -60,10 +82,10 @@ void DisplayIdleService::stopScreensaver() {
backlightOff = false;
// Restore backlight if display was dimmed
auto display = getDisplay();
if (display && wasDimmed) {
display->setBacklightDuty(restoreDuty);
if (wasDimmed) {
setBacklightBrightness(restoreDuty);
}
displayDimmed = wasDimmed ? false : displayDimmed;
}
@@ -124,11 +146,11 @@ void DisplayIdleService::updateScreensaver() {
}
void DisplayIdleService::tick() {
if (!lvgl::lock(100)) {
if (!lvgl_try_lock(100)) {
return;
}
if (lv_display_get_default() == nullptr) {
lvgl::unlock();
lvgl_unlock();
return;
}
@@ -152,10 +174,7 @@ void DisplayIdleService::tick() {
screensaver->stop();
screensaver.reset();
}
auto display = getDisplay();
if (display) {
display->setBacklightDuty(0);
}
setBacklightBrightness(0);
backlightOff = true;
} else {
updateScreensaver();
@@ -163,7 +182,7 @@ void DisplayIdleService::tick() {
}
}
lvgl::unlock();
lvgl_unlock();
// Check stop request early for faster response
if (stopScreensaverRequested.load(std::memory_order_acquire)) {
@@ -171,23 +190,22 @@ void DisplayIdleService::tick() {
return;
}
auto display = getDisplay();
if (display != nullptr && display->supportsBacklightDuty()) {
if (hasDisplayWithBacklight()) {
if (!cachedDisplaySettings.backlightTimeoutEnabled || cachedDisplaySettings.backlightTimeoutMs == 0) {
if (displayDimmed) {
display->setBacklightDuty(cachedDisplaySettings.backlightDuty);
setBacklightBrightness(cachedDisplaySettings.backlightDuty);
displayDimmed = false;
}
} else {
if (!displayDimmed && inactive_ms >= cachedDisplaySettings.backlightTimeoutMs) {
if (!lvgl::lock(100)) {
if (!lvgl_try_lock(100)) {
return; // Retry on next tick
}
activateScreensaver();
lvgl::unlock();
lvgl_unlock();
// Turn off backlight for "None" screensaver (just black screen)
if (cachedDisplaySettings.screensaverType == settings::display::ScreensaverType::None) {
display->setBacklightDuty(0);
setBacklightBrightness(0);
}
displayDimmed = true;
} else if (displayDimmed && (inactive_ms < kWakeActivityThresholdMs)) {
@@ -231,7 +249,7 @@ void DisplayIdleService::onStop(ServiceContext& service) {
}
void DisplayIdleService::startScreensaver() {
if (!lvgl::lock(100)) {
if (!lvgl_try_lock(100)) {
return;
}
@@ -240,12 +258,11 @@ void DisplayIdleService::startScreensaver() {
cachedDisplaySettings = settings::display::loadOrGetDefault();
activateScreensaver();
lvgl::unlock();
lvgl_unlock();
// Turn off backlight for "None" screensaver
auto display = getDisplay();
if (display && cachedDisplaySettings.screensaverType == settings::display::ScreensaverType::None) {
display->setBacklightDuty(0);
if (hasDisplayWithBacklight() && cachedDisplaySettings.screensaverType == settings::display::ScreensaverType::None) {
setBacklightBrightness(0);
}
displayDimmed = true;
}
@@ -1,119 +0,0 @@
#include <Tactility/file/ObjectFile.h>
#include <Tactility/service/gps/GpsService.h>
#include <Tactility/service/ServicePaths.h>
#include <cstring>
#include <unistd.h>
#include <tactility/log.h>
using tt::hal::gps::GpsDevice;
namespace tt::service::gps {
constexpr auto* TAG = "GpsService";
bool GpsService::getConfigurationFilePath(std::string& output) const {
if (paths == nullptr) {
LOG_E(TAG, "Can't add configuration: service not started");
return false;
}
if (!file::findOrCreateDirectory(paths->getUserDataDirectory(), 0777)) {
LOG_E(TAG, "Failed to find or create path %s", paths->getUserDataDirectory().c_str());
return false;
}
output = paths->getUserDataPath("config.bin");
return true;
}
bool GpsService::getGpsConfigurations(std::vector<hal::gps::GpsConfiguration>& configurations) const {
std::string path;
if (!getConfigurationFilePath(path)) {
return false;
}
// If file does not exist, return empty list
if (access(path.c_str(), F_OK) != 0) {
LOG_W(TAG, "No configurations (file not found: %s)", path.c_str());
return true;
}
LOG_I(TAG, "Reading configuration file %s", path.c_str());
auto reader = file::ObjectFileReader(path, sizeof(hal::gps::GpsConfiguration));
if (!reader.open()) {
LOG_E(TAG, "Failed to open configuration file");
return false;
}
hal::gps::GpsConfiguration configuration;
while (reader.hasNext()) {
if (!reader.readNext(&configuration)) {
LOG_E(TAG, "Failed to read configuration");
reader.close();
return false;
} else {
configurations.push_back(configuration);
}
}
return true;
}
bool GpsService::addGpsConfiguration(hal::gps::GpsConfiguration configuration) {
std::string path;
if (!getConfigurationFilePath(path)) {
return false;
}
auto appender = file::ObjectFileWriter(path, sizeof(hal::gps::GpsConfiguration), 1, true);
if (!appender.open()) {
LOG_E(TAG, "Failed to open/create configuration file");
return false;
}
if (!appender.write(&configuration)) {
LOG_E(TAG, "Failed to add configuration");
appender.close();
return false;
}
appender.close();
return true;
}
bool GpsService::removeGpsConfiguration(hal::gps::GpsConfiguration configuration) {
std::string path;
if (!getConfigurationFilePath(path)) {
return false;
}
std::vector<hal::gps::GpsConfiguration> configurations;
if (!getGpsConfigurations(configurations)) {
LOG_E(TAG, "Failed to get gps configurations");
return false;
}
auto count = std::erase_if(configurations, [&configuration](auto& item) {
return strcmp(item.uartName, configuration.uartName) == 0 &&
item.baudRate == configuration.baudRate &&
item.model == configuration.model;
});
auto writer = file::ObjectFileWriter(path, sizeof(hal::gps::GpsConfiguration), 1, false);
if (!writer.open()) {
LOG_E(TAG, "Failed to open configuration file");
return false;
}
for (auto& configuration : configurations) {
writer.write(&configuration);
}
writer.close();
return count > 0;
}
} // namespace tt::service::gps
-259
View File
@@ -1,259 +0,0 @@
#include <Tactility/service/gps/GpsService.h>
#include <Tactility/file/File.h>
#include <Tactility/service/ServicePaths.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h>
#include <tactility/log.h>
using tt::hal::gps::GpsDevice;
namespace tt::service::gps {
constexpr auto* TAG = "GpsService";
extern const ServiceManifest manifest;
constexpr bool hasTimeElapsed(TickType_t now, TickType_t timeInThePast, TickType_t expireTimeInTicks) {
return (now - timeInThePast) >= expireTimeInTicks;
}
GpsService::GpsDeviceRecord* GpsService::findGpsRecord(const std::shared_ptr<GpsDevice>& device) {
auto lock = mutex.asScopedLock();
lock.lock();
auto result = std::views::filter(deviceRecords, [&device](auto& record) {
return record.device.get() == device.get();
});
if (!result.empty()) {
return &result.front();
} else {
return nullptr;
}
}
void GpsService::addGpsDevice(const std::shared_ptr<GpsDevice>& device) {
auto lock = mutex.asScopedLock();
lock.lock();
GpsDeviceRecord record = {.device = device};
if (getState() == State::On) { // Ignore during OnPending due to risk of data corruption
startGpsDevice(record);
}
deviceRecords.push_back(record);
}
void GpsService::removeGpsDevice(const std::shared_ptr<GpsDevice>& device) {
auto lock = mutex.asScopedLock();
lock.lock();
GpsDeviceRecord* record = findGpsRecord(device);
if (getState() == State::On) { // Ignore during OnPending due to risk of data corruption
stopGpsDevice(*record);
}
std::erase_if(deviceRecords, [&device](auto& reference) {
return reference.device.get() == device.get();
});
}
bool GpsService::onStart(ServiceContext& serviceContext) {
auto lock = mutex.asScopedLock();
lock.lock();
paths = serviceContext.getPaths();
return true;
}
void GpsService::onStop(ServiceContext& serviceContext) {
if (getState() == State::On) {
stopReceiving();
}
}
bool GpsService::startGpsDevice(GpsDeviceRecord& record) {
LOG_I(TAG, "[device %u] starting", (unsigned)record.device->getId());
auto lock = mutex.asScopedLock();
lock.lock();
auto device = record.device;
if (!device->start()) {
LOG_E(TAG, "[device %u] starting failed", (unsigned)record.device->getId());
return false;
}
record.satelliteSubscriptionId = device->subscribeGga([this](hal::Device::Id deviceId, auto& record) {
mutex.lock();
if (record.fix_quality > 0) {
ggaRecord = record;
ggaTime = kernel::getTicks();
}
onGgaSentence(deviceId, record);
mutex.unlock();
});
record.rmcSubscriptionId = device->subscribeRmc([this](hal::Device::Id deviceId, auto& record) {
mutex.lock();
if (record.longitude.value != 0 && record.longitude.scale != 0) {
rmcRecord = record;
rmcTime = kernel::getTicks();
}
onRmcSentence(deviceId, record);
mutex.unlock();
});
return true;
}
bool GpsService::stopGpsDevice(GpsDeviceRecord& record) {
LOG_I(TAG, "[device %u] stopping", (unsigned)record.device->getId());
auto device = record.device;
device->unsubscribeGga(record.satelliteSubscriptionId);
device->unsubscribeRmc(record.rmcSubscriptionId);
record.satelliteSubscriptionId = -1;
record.rmcSubscriptionId = -1;
if (!device->stop()) {
LOG_E(TAG, "[device %u] stopping failed", (unsigned)record.device->getId());
return false;
}
return true;
}
bool GpsService::startReceiving() {
LOG_I(TAG, "Start receiving");
if (getState() != State::Off) {
LOG_E(TAG, "Already receiving");
return false;
}
setState(State::OnPending);
auto lock = mutex.asScopedLock();
lock.lock();
deviceRecords.clear();
std::vector<hal::gps::GpsConfiguration> configurations;
if (!getGpsConfigurations(configurations)) {
LOG_E(TAG, "Failed to get GPS configurations");
setState(State::Off);
return false;
}
if (configurations.empty()) {
LOG_E(TAG, "No GPS configurations");
setState(State::Off);
return false;
}
for (const auto& configuration: configurations) {
auto device = std::make_shared<GpsDevice>(configuration);
addGpsDevice(device);
}
// Reset times before starting devices to avoid race with incoming data
rmcTime = 0;
ggaTime = 0;
bool started_one_or_more = false;
for (auto& record: deviceRecords) {
started_one_or_more |= startGpsDevice(record);
}
if (started_one_or_more) {
setState(State::On);
return true;
} else {
setState(State::Off);
return false;
}
}
void GpsService::stopReceiving() {
LOG_I(TAG, "Stop receiving");
setState(State::OffPending);
auto lock = mutex.asScopedLock();
lock.lock();
for (auto& record: deviceRecords) {
stopGpsDevice(record);
}
rmcTime = 0;
ggaTime = 0;
setState(State::Off);
}
void GpsService::onGgaSentence(hal::Device::Id deviceId, const minmea_sentence_gga& gga) {
LOG_D(TAG, "[device %u] LAT %f LON %f, satellites: %d", (unsigned)deviceId, minmea_tocoord(&gga.latitude), minmea_tocoord(&gga.longitude), gga.satellites_tracked);
}
void GpsService::onRmcSentence(hal::Device::Id deviceId, const minmea_sentence_rmc& rmc) {
LOG_D(TAG, "[device %u] LAT %f LON %f, speed: %f", (unsigned)deviceId, minmea_tocoord(&rmc.latitude), minmea_tocoord(&rmc.longitude), minmea_tofloat(&rmc.speed));
}
State GpsService::getState() const {
auto lock = stateMutex.asScopedLock();
lock.lock();
return state;
}
void GpsService::setState(State newState) {
auto lock = stateMutex.asScopedLock();
lock.lock();
state = newState;
lock.unlock();
statePubSub->publish(state);
}
bool GpsService::hasCoordinates() const {
auto lock = mutex.asScopedLock();
lock.lock();
return getState() == State::On && rmcTime != 0 && !hasTimeElapsed(kernel::getTicks(), rmcTime, kernel::secondsToTicks(10));
}
bool GpsService::getCoordinates(minmea_sentence_rmc& rmc) const {
if (hasCoordinates()) {
rmc = rmcRecord;
return true;
} else {
return false;
}
}
bool GpsService::getGga(minmea_sentence_gga& gga) const {
auto lock = mutex.asScopedLock();
lock.lock();
if (getState() == State::On && ggaTime != 0 && !hasTimeElapsed(kernel::getTicks(), ggaTime, kernel::secondsToTicks(10))) {
gga = ggaRecord;
return true;
}
return false;
}
std::shared_ptr<GpsService> findGpsService() {
auto service = findServiceById(manifest.id);
assert(service != nullptr);
return std::static_pointer_cast<GpsService>(service);
}
extern const ServiceManifest manifest = {
.id = "Gps",
.createService = create<GpsService>
};
} // namespace tt::service::gps
@@ -1,16 +1,18 @@
#ifdef ESP_PLATFORM
#include <Tactility/CoreDefines.h>
#include <Tactility/hal/keyboard/KeyboardDevice.h>
#include <Tactility/lvgl/LvglSync.h>
#include <display/lv_display.h>
#include <Tactility/Timer.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/settings/KeyboardSettings.h>
#include <Tactility/Timer.h>
#include <tactility/device.h>
#include <tactility/drivers/backlight.h>
#include <tactility/drivers/keyboard.h>
#include <tactility/lvgl_module.h>
namespace tt::service::keyboardidle {
@@ -20,18 +22,29 @@ class KeyboardIdleService final : public Service {
bool keyboardDimmed = false;
settings::keyboard::KeyboardSettings cachedKeyboardSettings;
static std::shared_ptr<hal::keyboard::KeyboardDevice> getKeyboard() {
return hal::findFirstDevice<hal::keyboard::KeyboardDevice>(hal::Device::Type::Keyboard);
}
// TODO: This only works for the fist active keyboard. Update it so it works for all keyboards with a backlight.
static Device* getKeyboardBacklight() {
return device_find_by_name("keyboard_backlight");
::Device* keyboard;
if (device_get_first_active_by_type(&KEYBOARD_TYPE, &keyboard) == ERROR_NONE) {
::Device* backlight = nullptr;
keyboard_get_backlight(keyboard, &backlight); // disregard result
device_put(keyboard);
return backlight; // WARNING: did not increase refcount
}
// TODO: Remove after all drivers are migrated
::Device* backlight;
if (device_get_by_name("keyboard_backlight", &backlight) != ERROR_NONE) {
return nullptr;
}
return backlight;
}
void setKeyboardBacklightBrightness(uint8_t brightness) {
Device* backlight = getKeyboardBacklight();
if (backlight != nullptr) {
backlight_set_brightness(backlight, brightness);
device_put(backlight);
}
}
@@ -41,14 +54,16 @@ class KeyboardIdleService final : public Service {
// Query LVGL inactivity once for both checks
uint32_t inactive_ms = 0;
if (lvgl::lock(100)) {
if (lvgl_try_lock(100)) {
inactive_ms = lv_display_get_inactive_time(nullptr);
lvgl::unlock();
lvgl_unlock();
} else {
// Assume it's not used
inactive_ms = 100;
}
// Handle keyboard backlight
auto keyboard = getKeyboard();
if (keyboard != nullptr && keyboard->isAttached()) {
if (device_has_active_by_type(&KEYBOARD_TYPE)) {
// If timeout disabled, ensure backlight restored if we had dimmed it
if (!cachedKeyboardSettings.backlightTimeoutEnabled || cachedKeyboardSettings.backlightTimeoutMs == 0) {
if (keyboardDimmed) {
@@ -89,8 +104,7 @@ public:
timer = nullptr;
}
// Ensure keyboard restored on stop
auto keyboard = getKeyboard();
if (keyboard && keyboardDimmed) {
if (device_has_active_by_type(&KEYBOARD_TYPE) && keyboardDimmed) {
setKeyboardBacklightBrightness(cachedKeyboardSettings.backlightEnabled ? cachedKeyboardSettings.backlightBrightness : 0);
keyboardDimmed = false;
}
@@ -2,30 +2,32 @@
#include <Tactility/Mutex.h>
#include <Tactility/Timer.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/filesystem/file_system.h>
#include <Tactility/lvgl/Lvgl.h>
#include <Tactility/bluetooth/Bluetooth.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServicePaths.h>
#include <Tactility/service/ServiceRegistration.h>
#include <Tactility/bluetooth/Bluetooth.h>
#include <tactility/drivers/bluetooth.h>
#include <tactility/drivers/bluetooth_serial.h>
#include <tactility/drivers/bluetooth_midi.h>
#include <Tactility/service/wifi/Wifi.h>
#include <tactility/check.h>
#include <tactility/device.h>
#include <tactility/drivers/bluetooth.h>
#include <tactility/drivers/bluetooth_midi.h>
#include <tactility/drivers/bluetooth_serial.h>
#include <tactility/drivers/power_supply.h>
#include <tactility/drivers/usb_host_hid.h>
#include <tactility/drivers/usb_host_midi.h>
#include <tactility/drivers/usb_host_msc.h>
#include <Tactility/service/gps/GpsService.h>
#include <Tactility/service/wifi/Wifi.h>
#include <tactility/check.h>
#include <tactility/filesystem/file_system.h>
#include <tactility/log.h>
#include <tactility/module.h>
#include <tactility/lvgl_module.h>
#include <tactility/lvgl_icon_statusbar.h>
#include <cstring>
#include <tactility/log.h>
#include <gps/gps.h>
namespace tt::service::statusbar {
@@ -152,8 +154,7 @@ class StatusbarService final : public Service {
}
void updateGpsIcon() {
auto gps_state = gps::findGpsService()->getState();
bool show_icon = (gps_state == gps::State::OnPending) || (gps_state == gps::State::On);
bool show_icon = device_has_active_by_type(&GPS_TYPE);
if (gps_last_state != show_icon) {
if (show_icon) {
lvgl::statusbar_icon_set_image(gps_icon_id, LVGL_ICON_STATUSBAR_LOCATION_ON);
@@ -267,15 +268,15 @@ class StatusbarService final : public Service {
}
void update() {
if (lvgl::isStarted()) {
if (lvgl::lock(100)) {
if (module_is_started(&lvgl_module)) {
if (lvgl_try_lock(100)) {
updateGpsIcon();
updateBluetoothIcon();
updateWifiIcon();
updateSdCardIcon();
updatePowerStatusIcon();
updateUsbIcon();
lvgl::unlock();
lvgl_unlock();
}
}
}
@@ -11,7 +11,6 @@
#include <Tactility/Mutex.h>
#include <Tactility/TactilityConfig.h>
#include <tactility/hal/Device.h>
#include <Tactility/app/AppRegistration.h>
#include <Tactility/app/AppManifest.h>
#include <Tactility/app/App.h>