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
-1
View File
@@ -1,6 +1,5 @@
#include "Main.h"
#include <Tactility/Thread.h>
#include <Tactility/TactilityCore.h>
#include "FreeRTOS.h"
#include "task.h"
-22
View File
@@ -1,22 +0,0 @@
#include "hal/SdlDisplay.h"
#include "hal/SdlKeyboard.h"
#include "hal/SimulatorPower.h"
#include <Tactility/hal/Configuration.h>
#define TAG "hardware"
using namespace tt::hal;
static std::vector<std::shared_ptr<tt::hal::Device>> createDevices() {
return {
std::make_shared<SdlDisplay>(),
std::make_shared<SdlKeyboard>(),
std::make_shared<SimulatorPower>(),
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = nullptr,
.createDevices = createDevices
};
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: Apache-2.0
#include "sdl_display.h"
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/display.h>
#include <tactility/log.h>
#include <tactility/module.h>
#include <SDL2/SDL.h>
#include <cstdlib>
constexpr auto* TAG = "SdlDisplay";
#define GET_CONFIG(device) (static_cast<const SdlDisplayConfig*>((device)->config))
struct SdlDisplayInternal {
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* texture;
};
// region Driver lifecycle
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<SdlDisplayInternal*>(malloc(sizeof(SdlDisplayInternal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
LOG_E(TAG, "SDL_InitSubSystem failed: %s", SDL_GetError());
free(internal);
return ERROR_RESOURCE;
}
internal->window = SDL_CreateWindow(
"Tactility",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
config->horizontal_resolution, config->vertical_resolution,
SDL_WINDOW_SHOWN
);
internal->renderer = internal->window != nullptr
? SDL_CreateRenderer(internal->window, -1, SDL_RENDERER_ACCELERATED)
: nullptr;
internal->texture = internal->renderer != nullptr
? SDL_CreateTexture(internal->renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING,
config->horizontal_resolution, config->vertical_resolution)
: nullptr;
if (internal->window == nullptr || internal->renderer == nullptr || internal->texture == nullptr) {
LOG_E(TAG, "Failed to create SDL window: %s", SDL_GetError());
if (internal->texture != nullptr) SDL_DestroyTexture(internal->texture);
if (internal->renderer != nullptr) SDL_DestroyRenderer(internal->renderer);
if (internal->window != nullptr) SDL_DestroyWindow(internal->window);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
free(internal);
return ERROR_RESOURCE;
}
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<SdlDisplayInternal*>(device_get_driver_data(device));
SDL_DestroyTexture(internal->texture);
SDL_DestroyRenderer(internal->renderer);
SDL_DestroyWindow(internal->window);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
free(internal);
device_set_driver_data(device, nullptr);
return ERROR_NONE;
}
// endregion
// region DisplayApi
static error_t sdl_display_reset(Device*) { return ERROR_NONE; }
static error_t sdl_display_init(Device*) { return ERROR_NONE; }
static error_t sdl_display_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
auto* internal = static_cast<SdlDisplayInternal*>(device_get_driver_data(device));
SDL_Rect rect = { x_start, y_start, x_end - x_start, y_end - y_start };
// RGB565 = 2 bytes/pixel.
if (SDL_UpdateTexture(internal->texture, &rect, color_data, (x_end - x_start) * 2) != 0) {
return ERROR_RESOURCE;
}
SDL_RenderClear(internal->renderer);
SDL_RenderCopy(internal->renderer, internal->texture, nullptr, nullptr);
SDL_RenderPresent(internal->renderer);
return ERROR_NONE;
}
static enum DisplayColorFormat sdl_display_get_color_format(Device*) {
return DISPLAY_COLOR_FORMAT_RGB565;
}
static uint16_t sdl_display_get_resolution_x(Device* device) {
return GET_CONFIG(device)->horizontal_resolution;
}
static uint16_t sdl_display_get_resolution_y(Device* device) {
return GET_CONFIG(device)->vertical_resolution;
}
static void sdl_display_get_frame_buffer(Device*, uint8_t, void** out_buffer) {
*out_buffer = nullptr;
}
static uint8_t sdl_display_get_frame_buffer_count(Device*) {
return 0;
}
// endregion
static const DisplayApi sdl_display_api = {
.capabilities = 0,
.reset = sdl_display_reset,
.init = sdl_display_init,
.draw_bitmap = sdl_display_draw_bitmap,
.mirror = nullptr,
.swap_xy = nullptr,
.get_swap_xy = nullptr,
.get_mirror_x = nullptr,
.get_mirror_y = nullptr,
.set_gap = nullptr,
.get_gap_x = nullptr,
.get_gap_y = nullptr,
.invert_color = nullptr,
.disp_on_off = nullptr,
.disp_sleep = nullptr,
.get_color_format = sdl_display_get_color_format,
.get_resolution_x = sdl_display_get_resolution_x,
.get_resolution_y = sdl_display_get_resolution_y,
.get_frame_buffer = sdl_display_get_frame_buffer,
.get_frame_buffer_count = sdl_display_get_frame_buffer_count,
.get_backlight = nullptr,
.has_capability = nullptr,
};
extern Module simulator_module;
Driver sdl_display_driver = {
.name = "sdl-display",
.compatible = (const char*[]) { "tactility,sdl-display", nullptr },
.start_device = start,
.stop_device = stop,
.api = &sdl_display_api,
.device_type = &DISPLAY_TYPE,
.owner = &simulator_module,
.internal = nullptr
};
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
struct SdlDisplayConfig {
uint16_t horizontal_resolution;
uint16_t vertical_resolution;
};
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: Apache-2.0
#include "sdl_input.h"
#include <lvgl.h>
#include <SDL2/SDL.h>
#include <cstdlib>
namespace {
constexpr size_t KEY_QUEUE_CAPACITY = 32;
SdlPointerState pointer_state = { 0, 0, false };
uint32_t key_queue[KEY_QUEUE_CAPACITY];
size_t key_queue_head = 0;
size_t key_queue_count = 0;
bool text_input_started = false;
void push_key(uint32_t key) {
if (key == 0 || key_queue_count >= KEY_QUEUE_CAPACITY) {
return;
}
key_queue[(key_queue_head + key_queue_count) % KEY_QUEUE_CAPACITY] = key;
key_queue_count++;
}
// Mirrors LVGL's own lv_sdl_keyboard.c keycode_to_ctrl_key(): maps navigation/control keys to
// LV_KEY_* constants. Printable characters arrive separately via SDL_TEXTINPUT.
uint32_t keycode_to_key(SDL_Keycode sdl_key) {
switch (sdl_key) {
case SDLK_RIGHT: return LV_KEY_RIGHT;
case SDLK_LEFT: return LV_KEY_LEFT;
case SDLK_UP: return LV_KEY_UP;
case SDLK_DOWN: return LV_KEY_DOWN;
case SDLK_ESCAPE: return LV_KEY_ESC;
case SDLK_BACKSPACE: return LV_KEY_BACKSPACE;
case SDLK_DELETE: return LV_KEY_DEL;
case SDLK_RETURN:
case SDLK_KP_ENTER: return LV_KEY_ENTER;
case SDLK_TAB: return LV_KEY_NEXT;
case SDLK_HOME: return LV_KEY_HOME;
case SDLK_END: return LV_KEY_END;
default: return 0;
}
}
}
void sdl_input_pump() {
if (!text_input_started) {
SDL_StartTextInput();
text_input_started = true;
}
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_MOUSEMOTION:
pointer_state.x = event.motion.x;
pointer_state.y = event.motion.y;
break;
case SDL_MOUSEBUTTONDOWN:
if (event.button.button == SDL_BUTTON_LEFT) {
pointer_state.x = event.button.x;
pointer_state.y = event.button.y;
pointer_state.pressed = true;
}
break;
case SDL_MOUSEBUTTONUP:
if (event.button.button == SDL_BUTTON_LEFT) {
pointer_state.pressed = false;
}
break;
case SDL_KEYDOWN:
push_key(keycode_to_key(event.key.keysym.sym));
break;
case SDL_TEXTINPUT:
// ASCII only (first byte of event.text.text) - sufficient for a simulator keyboard.
push_key(static_cast<uint8_t>(event.text.text[0]));
break;
case SDL_QUIT:
exit(0);
default:
break;
}
}
}
void sdl_input_get_pointer_state(SdlPointerState* out_state) {
*out_state = pointer_state;
}
bool sdl_input_pop_key(uint32_t* out_key) {
if (key_queue_count == 0) {
return false;
}
*out_key = key_queue[key_queue_head];
key_queue_head = (key_queue_head + 1) % KEY_QUEUE_CAPACITY;
key_queue_count--;
return true;
}
bool sdl_input_has_queued_key() {
return key_queue_count > 0;
}
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
/**
* @brief Latest pointer (mouse) state as tracked by sdl_input_pump().
*/
struct SdlPointerState {
int32_t x;
int32_t y;
bool pressed;
};
/**
* @brief Drains all pending SDL events exactly once, updating the pointer state and key queue
* below. Safe to call from both the sdl-pointer and sdl-keyboard drivers' polling functions:
* SDL_PollEvent() drains a single global queue, so whichever driver is polled first on a given
* LVGL indev tick pumps events for both.
*/
void sdl_input_pump(void);
/**
* @brief Gets the pointer state as of the most recent sdl_input_pump() call.
*/
void sdl_input_get_pointer_state(struct SdlPointerState* out_state);
/**
* @brief Pops the next queued key event (produced by SDL_KEYDOWN/SDL_TEXTINPUT during
* sdl_input_pump()).
* @retval false when no key event is pending
*/
bool sdl_input_pop_key(uint32_t* out_key);
/**
* @brief Returns true if another key event is queued after the one just popped.
*/
bool sdl_input_has_queued_key(void);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0
#include "sdl_input.h"
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/keyboard.h>
#include <tactility/module.h>
// region Driver lifecycle
static error_t start(Device*) { return ERROR_NONE; }
static error_t stop(Device*) { return ERROR_NONE; }
// endregion
// region KeyboardApi
static error_t sdl_keyboard_read_key(Device*, KeyboardKeyData* data) {
sdl_input_pump();
uint32_t key = 0;
if (sdl_input_pop_key(&key)) {
data->key = key;
data->pressed = true;
data->continue_reading = sdl_input_has_queued_key();
} else {
data->key = 0;
data->pressed = false;
data->continue_reading = false;
}
return ERROR_NONE;
}
// endregion
static const KeyboardApi sdl_keyboard_api = {
.read_key = sdl_keyboard_read_key,
.get_backlight = nullptr,
};
extern Module simulator_module;
Driver sdl_keyboard_driver = {
.name = "sdl-keyboard",
.compatible = (const char*[]) { "tactility,sdl-keyboard", nullptr },
.start_device = start,
.stop_device = stop,
.api = &sdl_keyboard_api,
.device_type = &KEYBOARD_TYPE,
.owner = &simulator_module,
.internal = nullptr
};
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: Apache-2.0
#include "sdl_input.h"
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/pointer.h>
#include <tactility/module.h>
// region Driver lifecycle
static error_t start(Device*) { return ERROR_NONE; }
static error_t stop(Device*) { return ERROR_NONE; }
// endregion
// region PointerApi
static error_t sdl_pointer_read_data(Device*, TickType_t) {
sdl_input_pump();
return ERROR_NONE;
}
static bool sdl_pointer_get_touched_points(Device*, uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* point_count, uint8_t max_point_count) {
SdlPointerState state;
sdl_input_get_pointer_state(&state);
if (!state.pressed || max_point_count == 0) {
*point_count = 0;
return false;
}
x[0] = static_cast<uint16_t>(state.x);
y[0] = static_cast<uint16_t>(state.y);
if (strength != nullptr) {
strength[0] = 0xFFFF;
}
*point_count = 1;
return true;
}
// endregion
static const PointerApi sdl_pointer_api = {
.enter_sleep = nullptr,
.exit_sleep = nullptr,
.read_data = sdl_pointer_read_data,
.get_touched_points = sdl_pointer_get_touched_points,
.set_swap_xy = nullptr,
.get_swap_xy = nullptr,
.set_mirror_x = nullptr,
.get_mirror_x = nullptr,
.set_mirror_y = nullptr,
.get_mirror_y = nullptr,
};
extern Module simulator_module;
Driver sdl_pointer_driver = {
.name = "sdl-pointer",
.compatible = (const char*[]) { "tactility,sdl-pointer", nullptr },
.start_device = start,
.stop_device = stop,
.api = &sdl_pointer_api,
.device_type = &POINTER_TYPE,
.owner = &simulator_module,
.internal = nullptr
};
-42
View File
@@ -1,42 +0,0 @@
#pragma once
#include "SdlTouch.h"
#include <tactility/check.h>
#include <Tactility/hal/display/DisplayDevice.h>
class SdlDisplay final : public tt::hal::display::DisplayDevice {
lv_disp_t* displayHandle = nullptr;
public:
std::string getName() const override { return "SDL Display"; }
std::string getDescription() const override { return ""; }
bool start() override { return true; }
bool stop() override { return true; }
bool supportsLvgl() const override { return true; }
bool startLvgl() override {
if (displayHandle) return true; // already started
displayHandle = lv_sdl_window_create(320, 240);
lv_sdl_window_set_title(displayHandle, "Tactility");
return displayHandle != nullptr;
}
bool stopLvgl() override {
if (!displayHandle) return true;
lv_display_delete(displayHandle);
displayHandle = nullptr;
return true;
}
lv_display_t* getLvglDisplay() const override { return displayHandle; }
std::shared_ptr<tt::hal::touch::TouchDevice> getTouchDevice() override { return std::make_shared<SdlTouch>(); }
bool supportsDisplayDriver() const override { return false; }
std::shared_ptr<tt::hal::display::DisplayDriver> getDisplayDriver() override { return nullptr; }
};
@@ -1,26 +0,0 @@
#pragma once
#include <Tactility/TactilityCore.h>
#include <tactility/check.h>
#include <Tactility/hal/keyboard/KeyboardDevice.h>
class SdlKeyboard final : public tt::hal::keyboard::KeyboardDevice {
lv_indev_t* handle = nullptr;
public:
std::string getName() const override { return "SDL Keyboard"; }
std::string getDescription() const override { return "SDL keyboard device"; }
bool startLvgl(lv_display_t* display) override {
handle = lv_sdl_keyboard_create();
return handle != nullptr;
}
bool stopLvgl() override { check(false, "Not supported"); }
bool isAttached() const override { return true; }
lv_indev_t* getLvglIndev() override { return handle; }
};
-36
View File
@@ -1,36 +0,0 @@
#pragma once
#include "Tactility/hal/touch/TouchDevice.h"
#include <Tactility/TactilityCore.h>
#include <tactility/check.h>
class SdlTouch final : public tt::hal::touch::TouchDevice {
lv_indev_t* handle = nullptr;
public:
std::string getName() const override { return "SDL Mouse"; }
std::string getDescription() const override { return "SDL mouse/touch pointer device"; }
bool start() override { return true; }
bool stop() override { check(false, "Not supported"); }
bool supportsLvgl() const override { return true; }
bool startLvgl(lv_display_t* display) override {
handle = lv_sdl_mouse_create();
return handle != nullptr;
}
bool stopLvgl() override { check(false, "Not supported"); }
lv_indev_t* getLvglIndev() override { return handle; }
bool supportsTouchDriver() override { return false; }
std::shared_ptr<tt::hal::touch::TouchDriver> getTouchDriver() override { return nullptr; };
};
@@ -1,36 +0,0 @@
#include "SimulatorPower.h"
constexpr auto* TAG = "SimulatorPower";
bool SimulatorPower::supportsMetric(MetricType type) const {
switch (type) {
using enum MetricType;
case IsCharging:
case Current:
case BatteryVoltage:
case ChargeLevel:
return true;
}
return false; // Safety guard for when new enum values are introduced
}
bool SimulatorPower::getMetric(MetricType type, MetricData& data) {
switch (type) {
using enum MetricType;
case IsCharging:
data.valueAsBool = true;
return true;
case Current:
data.valueAsInt32 = 42;
return true;
case BatteryVoltage:
data.valueAsUint32 = 4032;
return true;
case ChargeLevel:
data.valueAsUint8 = 100;
return true;
}
return false; // Safety guard for when new enum values are introduced
}
@@ -1,26 +0,0 @@
#pragma once
#include <Tactility/hal/power/PowerDevice.h>
#include <memory>
using tt::hal::power::PowerDevice;
class SimulatorPower final : public PowerDevice {
bool allowedToCharge = false;
public:
SimulatorPower() = default;
~SimulatorPower() override = default;
std::string getName() const override { return "Power Mock"; }
std::string getDescription() const override { return ""; }
bool supportsMetric(MetricType type) const override;
bool getMetric(MetricType type, MetricData& data) override;
bool supportsChargeControl() const override { return true; }
bool isAllowedToCharge() const override { return allowedToCharge; }
void setAllowedToCharge(bool canCharge) override { allowedToCharge = canCharge; }
};
+90 -5
View File
@@ -1,23 +1,108 @@
#include "drivers/sdl_display.h"
#include <tactility/device.h>
#include <tactility/device_listener.h>
#include <tactility/driver.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <tactility/module.h>
#include <cstring>
constexpr auto* TAG = "Simulator";
extern "C" {
extern Driver sdl_display_driver;
extern Driver sdl_pointer_driver;
extern Driver sdl_keyboard_driver;
static Driver* const simulator_drivers[] = {
&sdl_display_driver,
&sdl_pointer_driver,
&sdl_keyboard_driver,
nullptr
};
}
// These devices have no real bus to attach to (SDL has no notion of one), but every non-root
// device is still expected to have a parent (see Device::parent) - they're parented to root once
// it's available below.
static const SdlDisplayConfig sdl_display_config = { 320, 240 };
static Device sdl_display_device {};
static Device sdl_pointer_device {};
static Device sdl_keyboard_device {};
static bool construct_add_start(Device* device, Device* parent, const char* name, const void* config, const char* compatible) {
device->address = 0;
device->name = name;
device->config = config;
device->parent = nullptr;
device->internal = nullptr;
error_t error = device_construct(device);
if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to construct %s: %s", name, error_to_string(error));
return false;
}
device_set_parent(device, parent);
Driver* driver = driver_find_compatible(compatible);
if (driver == nullptr) {
LOG_E(TAG, "No driver registered for %s", compatible);
device_destruct(device);
return false;
}
device_set_driver(device, driver);
if (device_add(device) != ERROR_NONE) {
LOG_E(TAG, "Failed to add %s", name);
device_destruct(device);
return false;
}
if (device_start(device) != ERROR_NONE) {
LOG_E(TAG, "Failed to start %s", name);
device_remove(device);
device_destruct(device);
return false;
}
return true;
}
// Root is only constructed/added/started after all dts_modules (including this one) have already
// started (see kernel_init()), so it can't be looked up by name from this module's own start() -
// wait for its DEVICE_EVENT_STARTED instead, same as e.g. m5stack-tab5's display/keyboard detection.
static void on_root_started(Device* device, DeviceEvent event, void* context) {
if (event != DEVICE_EVENT_STARTED || strcmp(device->name, "/") != 0) {
return;
}
construct_add_start(&sdl_display_device, device, "display0", &sdl_display_config, "tactility,sdl-display");
construct_add_start(&sdl_pointer_device, device, "pointer0", nullptr, "tactility,sdl-pointer");
construct_add_start(&sdl_keyboard_device, device, "keyboard0", nullptr, "tactility,sdl-keyboard");
}
extern "C" {
static error_t start() {
// Empty for now
device_listener_add(on_root_started, nullptr);
return ERROR_NONE;
}
static error_t stop() {
// Empty for now
device_listener_remove(on_root_started);
return ERROR_NONE;
}
struct Module simulator_module = {
Module simulator_module = {
.name = "simulator",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
.drivers = simulator_drivers
};
}