Implement device management (#199)

- Added `tt::hal::Device` and functions (de)register devices and search for them.
- Refactored apps: `Power` and `Display` settings apps now use the device API to find devices.
- Implemented the new API for all existing drivers for all devices, including the simulator.
- Updated HAL Configuration to return `std::shared_ptr` instead of raw pointers.
- Added test project for headless tests and implemented tests for the new code.
This commit is contained in:
Ken Van Hoeylandt 2025-02-02 15:16:51 +01:00 committed by GitHub
parent c87200a80d
commit cff0605b0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 655 additions and 109 deletions

View File

@ -19,5 +19,7 @@ jobs:
run: cmake -S ./ -B build run: cmake -S ./ -B build
- name: "Build Tests" - name: "Build Tests"
run: cmake --build build --target build-tests run: cmake --build build --target build-tests
- name: "Run Tests" - name: "Run TactilityCore Tests"
run: build/Tests/TactilityCore/TactilityCoreTests --exit run: build/Tests/TactilityCore/TactilityCoreTests --exit
- name: "Run TactilityHeadless Tests"
run: build/Tests/TactilityHeadless/TactilityHeadlessTests --exit

View File

@ -205,10 +205,10 @@ void YellowDisplay::setGammaCurve(uint8_t index) {
} }
} }
tt::hal::Touch* _Nullable YellowDisplay::createTouch() { std::shared_ptr<tt::hal::Touch> _Nullable YellowDisplay::createTouch() {
return static_cast<tt::hal::Touch*>(new YellowTouch()); return std::make_shared<YellowTouch>();
} }
tt::hal::Display* createDisplay() { std::shared_ptr<tt::hal::Display> createDisplay() {
return static_cast<tt::hal::Display*>(new YellowDisplay()); return std::make_shared<YellowDisplay>();
} }

View File

@ -16,11 +16,14 @@ private:
public: public:
std::string getName() const final { return "ILI9341"; }
std::string getDescription() const final { return "SPI display"; }
bool start() override; bool start() override;
bool stop() override; bool stop() override;
tt::hal::Touch* _Nullable createTouch() override; std::shared_ptr<tt::hal::Touch> _Nullable createTouch() override;
void setBacklightDuty(uint8_t backlightDuty) override; void setBacklightDuty(uint8_t backlightDuty) override;
bool supportsBacklightDuty() const override { return true; } bool supportsBacklightDuty() const override { return true; }
@ -31,4 +34,4 @@ public:
lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; }
}; };
tt::hal::Display* createDisplay(); std::shared_ptr<tt::hal::Display> createDisplay();

View File

@ -5,12 +5,20 @@
#include <esp_lcd_touch.h> #include <esp_lcd_touch.h>
class YellowTouch : public tt::hal::Touch { class YellowTouch : public tt::hal::Touch {
private: private:
esp_lcd_panel_io_handle_t ioHandle = nullptr; esp_lcd_panel_io_handle_t ioHandle = nullptr;
esp_lcd_touch_handle_t touchHandle = nullptr; esp_lcd_touch_handle_t touchHandle = nullptr;
lv_indev_t* _Nullable deviceHandle = nullptr; lv_indev_t* _Nullable deviceHandle = nullptr;
void cleanup(); void cleanup();
public: public:
std::string getName() const final { return "CST816S"; }
std::string getDescription() const final { return "I2C touch driver"; }
bool start(lv_display_t* display) override; bool start(lv_display_t* display) override;
bool stop() override; bool stop() override;
lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; }

View File

@ -191,8 +191,8 @@ void TdeckDisplay::setPowerOn(bool turnOn) {
} }
} }
tt::hal::Touch* _Nullable TdeckDisplay::createTouch() { std::shared_ptr<tt::hal::Touch> _Nullable TdeckDisplay::createTouch() {
return static_cast<tt::hal::Touch*>(new TdeckTouch()); return std::make_shared<TdeckTouch>();
} }
void TdeckDisplay::setBacklightDuty(uint8_t backlightDuty) { void TdeckDisplay::setBacklightDuty(uint8_t backlightDuty) {
@ -233,6 +233,6 @@ void TdeckDisplay::setGammaCurve(uint8_t index) {
} }
} }
tt::hal::Display* createDisplay() { std::shared_ptr<tt::hal::Display> createDisplay() {
return static_cast<tt::hal::Display*>(new TdeckDisplay()); return std::make_shared<TdeckDisplay>();
} }

View File

@ -17,6 +17,9 @@ private:
public: public:
std::string getName() const final { return "ST7780"; }
std::string getDescription() const final { return "SPI display"; }
bool start() override; bool start() override;
bool stop() override; bool stop() override;
@ -25,7 +28,7 @@ public:
bool isPoweredOn() const override { return poweredOn; }; bool isPoweredOn() const override { return poweredOn; };
bool supportsPowerControl() const override { return true; } bool supportsPowerControl() const override { return true; }
tt::hal::Touch* _Nullable createTouch() override; std::shared_ptr<tt::hal::Touch> _Nullable createTouch() override;
void setBacklightDuty(uint8_t backlightDuty) override; void setBacklightDuty(uint8_t backlightDuty) override;
bool supportsBacklightDuty() const override { return true; } bool supportsBacklightDuty() const override { return true; }
@ -40,4 +43,4 @@ private:
static bool startBacklight(); static bool startBacklight();
}; };
tt::hal::Display* createDisplay(); std::shared_ptr<tt::hal::Display> createDisplay();

View File

@ -62,6 +62,6 @@ bool TdeckKeyboard::isAttached() const {
return tt::hal::i2c::masterHasDeviceAtAddress(TDECK_KEYBOARD_I2C_BUS_HANDLE, TDECK_KEYBOARD_SLAVE_ADDRESS, 100); return tt::hal::i2c::masterHasDeviceAtAddress(TDECK_KEYBOARD_I2C_BUS_HANDLE, TDECK_KEYBOARD_SLAVE_ADDRESS, 100);
} }
tt::hal::Keyboard* createKeyboard() { std::shared_ptr<tt::hal::Keyboard> createKeyboard() {
return dynamic_cast<tt::hal::Keyboard*>(new TdeckKeyboard()); return std::make_shared<TdeckKeyboard>();
} }

View File

@ -6,13 +6,20 @@
#include <esp_lcd_touch.h> #include <esp_lcd_touch.h>
class TdeckKeyboard : public tt::hal::Keyboard { class TdeckKeyboard : public tt::hal::Keyboard {
private: private:
lv_indev_t* _Nullable deviceHandle = nullptr; lv_indev_t* _Nullable deviceHandle = nullptr;
public: public:
std::string getName() const final { return "T-Deck Keyboard"; }
std::string getDescription() const final { return "I2C keyboard"; }
bool start(lv_display_t* display) override; bool start(lv_display_t* display) override;
bool stop() override; bool stop() override;
bool isAttached() const override; bool isAttached() const override;
lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; }
}; };
tt::hal::Keyboard* createKeyboard(); std::shared_ptr<tt::hal::Keyboard> createKeyboard();

View File

@ -15,6 +15,9 @@ public:
TdeckPower(); TdeckPower();
~TdeckPower(); ~TdeckPower();
std::string getName() const final { return "ADC Power Measurement"; }
std::string getDescription() const final { return "Power measurement interface via ADC pin"; }
bool supportsMetric(MetricType type) const override; bool supportsMetric(MetricType type) const override;
bool getMetric(Power::MetricType type, Power::MetricData& data) override; bool getMetric(Power::MetricType type, Power::MetricData& data) override;

View File

@ -6,12 +6,19 @@
#include <esp_lcd_touch.h> #include <esp_lcd_touch.h>
class TdeckTouch : public tt::hal::Touch { class TdeckTouch : public tt::hal::Touch {
private: private:
std::string getName() const final { return "GT911"; }
std::string getDescription() const final { return "I2C Touch Driver"; }
esp_lcd_panel_io_handle_t _Nullable ioHandle = nullptr; esp_lcd_panel_io_handle_t _Nullable ioHandle = nullptr;
esp_lcd_touch_handle_t _Nullable touchHandle = nullptr; esp_lcd_touch_handle_t _Nullable touchHandle = nullptr;
lv_indev_t* _Nullable deviceHandle = nullptr; lv_indev_t* _Nullable deviceHandle = nullptr;
void cleanup(); void cleanup();
public: public:
bool start(lv_display_t* display) override; bool start(lv_display_t* display) override;
bool stop() override; bool stop() override;
lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; }

View File

@ -153,10 +153,10 @@ void Core2Display::setGammaCurve(uint8_t index) {
} }
} }
tt::hal::Touch* _Nullable Core2Display::createTouch() { std::shared_ptr<tt::hal::Touch> _Nullable Core2Display::createTouch() {
return static_cast<tt::hal::Touch*>(new Core2Touch()); return std::make_shared<Core2Touch>();
} }
tt::hal::Display* createDisplay() { std::shared_ptr<tt::hal::Display> createDisplay() {
return static_cast<tt::hal::Display*>(new Core2Display()); return std::make_shared<Core2Display>();
} }

View File

@ -17,11 +17,14 @@ private:
public: public:
std::string getName() const final { return "ILI9342C"; }
std::string getDescription() const final { return "Display (ILI9342C with an ILI9341 driver)"; }
bool start() override; bool start() override;
bool stop() override; bool stop() override;
tt::hal::Touch* _Nullable createTouch() override; std::shared_ptr<tt::hal::Touch> _Nullable createTouch() override;
bool supportsBacklightDuty() const override { return false; } bool supportsBacklightDuty() const override { return false; }
@ -31,4 +34,4 @@ public:
lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; }
}; };
tt::hal::Display* createDisplay(); std::shared_ptr<tt::hal::Display> createDisplay();

View File

@ -12,6 +12,9 @@ public:
Core2Power() = default; Core2Power() = default;
~Core2Power() override = default; ~Core2Power() override = default;
std::string getName() const final { return "AXP192 Power"; }
std::string getDescription() const final { return "Power management via I2C"; }
bool supportsMetric(MetricType type) const override; bool supportsMetric(MetricType type) const override;
bool getMetric(Power::MetricType type, Power::MetricData& data) override; bool getMetric(Power::MetricType type, Power::MetricData& data) override;

View File

@ -23,6 +23,9 @@ public:
Core2Touch(); Core2Touch();
std::string getName() const final { return "FT6336U"; }
std::string getDescription() const final { return "I2C touch driver"; }
bool start(lv_display_t* display) override; bool start(lv_display_t* display) override;
bool stop() override; bool stop() override;

View File

@ -4,12 +4,15 @@
#define AW9523_ADDRESS 0x58 #define AW9523_ADDRESS 0x58
class Aw9523 : I2cDevice { class Aw9523 : public tt::hal::i2c::I2cDevice {
public: public:
explicit Aw9523(i2c_port_t port) : I2cDevice(port, AW9523_ADDRESS) {} explicit Aw9523(i2c_port_t port) : I2cDevice(port, AW9523_ADDRESS) {}
std::string getName() const final { return "AW9523"; }
std::string getDescription() const final { return "GPIO expander with LED driver and I2C interface."; }
bool readP0(uint8_t& output) const; bool readP0(uint8_t& output) const;
bool readP1(uint8_t& output) const; bool readP1(uint8_t& output) const;

View File

@ -9,7 +9,7 @@
* - https://github.com/m5stack/M5Unified/blob/master/src/utility/AXP2101_Class.cpp * - https://github.com/m5stack/M5Unified/blob/master/src/utility/AXP2101_Class.cpp
* - http://file.whycan.com/files/members/6736/AXP2101_Datasheet_V1.0_en_3832.pdf * - http://file.whycan.com/files/members/6736/AXP2101_Datasheet_V1.0_en_3832.pdf
*/ */
class Axp2101 : I2cDevice { class Axp2101 final : public tt::hal::i2c::I2cDevice {
public: public:
@ -21,6 +21,9 @@ public:
explicit Axp2101(i2c_port_t port) : I2cDevice(port, AXP2101_ADDRESS) {} explicit Axp2101(i2c_port_t port) : I2cDevice(port, AXP2101_ADDRESS) {}
std::string getName() const final { return "AXP2101"; }
std::string getDescription() const final { return "Power management with I2C interface."; }
bool setRegisters(uint8_t* bytePairs, size_t bytePairsSize) const; bool setRegisters(uint8_t* bytePairs, size_t bytePairsSize) const;
bool getBatteryVoltage(float& vbatMillis) const; bool getBatteryVoltage(float& vbatMillis) const;

View File

@ -181,10 +181,10 @@ void CoreS3Display::setBacklightDuty(uint8_t backlightDuty) {
} }
} }
tt::hal::Touch* _Nullable CoreS3Display::createTouch() { std::shared_ptr<tt::hal::Touch> _Nullable CoreS3Display::createTouch() {
return static_cast<tt::hal::Touch*>(new CoreS3Touch()); return std::make_shared<CoreS3Touch>();
} }
tt::hal::Display* createDisplay() { std::shared_ptr<tt::hal::Display> createDisplay() {
return static_cast<tt::hal::Display*>(new CoreS3Display()); return std::make_shared<CoreS3Display>();
} }

View File

@ -16,11 +16,14 @@ private:
public: public:
std::string getName() const final { return "ILI9342C"; }
std::string getDescription() const final { return "Display (ILI9342C with an ILI9341 driver)"; }
bool start() override; bool start() override;
bool stop() override; bool stop() override;
tt::hal::Touch* _Nullable createTouch() override; std::shared_ptr<tt::hal::Touch> _Nullable createTouch() override;
void setBacklightDuty(uint8_t backlightDuty) override; void setBacklightDuty(uint8_t backlightDuty) override;
bool supportsBacklightDuty() const override { return true; } bool supportsBacklightDuty() const override { return true; }
@ -31,4 +34,4 @@ public:
lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; }
}; };
tt::hal::Display* createDisplay(); std::shared_ptr<tt::hal::Display> createDisplay();

View File

@ -1,5 +1,4 @@
#include "CoreS3Power.h" #include "CoreS3Power.h"
#include <Tactility/TactilityCore.h>
#define TAG "core2_power" #define TAG "core2_power"

View File

@ -16,6 +16,9 @@ public:
CoreS3Power() = default; CoreS3Power() = default;
~CoreS3Power() override = default; ~CoreS3Power() override = default;
std::string getName() const final { return "AXP2101 Power"; }
std::string getDescription() const final { return "Power management via I2C"; }
bool supportsMetric(MetricType type) const override; bool supportsMetric(MetricType type) const override;
bool getMetric(Power::MetricType type, Power::MetricData& data) override; bool getMetric(Power::MetricType type, Power::MetricData& data) override;

View File

@ -5,12 +5,20 @@
#include <esp_lcd_touch.h> #include <esp_lcd_touch.h>
class CoreS3Touch : public tt::hal::Touch { class CoreS3Touch : public tt::hal::Touch {
private: private:
esp_lcd_panel_io_handle_t ioHandle = nullptr; esp_lcd_panel_io_handle_t ioHandle = nullptr;
esp_lcd_touch_handle_t touchHandle = nullptr; esp_lcd_touch_handle_t touchHandle = nullptr;
lv_indev_t* _Nullable deviceHandle = nullptr; lv_indev_t* _Nullable deviceHandle = nullptr;
void cleanup(); void cleanup();
public: public:
std::string getName() const final { return "FT6336U"; }
std::string getDescription() const final { return "I2C touch driver"; }
bool start(lv_display_t* display) override; bool start(lv_display_t* display) override;
bool stop() override; bool stop() override;
lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; }

View File

@ -2,23 +2,28 @@
#include "SdlTouch.h" #include "SdlTouch.h"
#include <Tactility/hal/Display.h> #include <Tactility/hal/Display.h>
#include <memory>
extern lv_disp_t* displayHandle; extern lv_disp_t* displayHandle;
class SdlDisplay : public tt::hal::Display { class SdlDisplay final : public tt::hal::Display {
public: public:
std::string getName() const final { return "SDL Display"; }
std::string getDescription() const final { return ""; }
bool start() override { bool start() override {
return displayHandle != nullptr; return displayHandle != nullptr;
} }
bool stop() override { tt_crash("Not supported"); } bool stop() override { tt_crash("Not supported"); }
tt::hal::Touch* _Nullable createTouch() override { return dynamic_cast<tt::hal::Touch*>(new SdlTouch()); } std::shared_ptr<tt::hal::Touch> _Nullable createTouch() override { return std::make_shared<SdlTouch>(); }
lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; }
}; };
tt::hal::Display* createDisplay() { std::shared_ptr<tt::hal::Display> createDisplay() {
return static_cast<tt::hal::Display*>(new SdlDisplay()); return std::make_shared<SdlDisplay>();
} }

View File

@ -3,11 +3,15 @@
#include <Tactility/hal/Keyboard.h> #include <Tactility/hal/Keyboard.h>
#include <Tactility/TactilityCore.h> #include <Tactility/TactilityCore.h>
class SdlKeyboard : public tt::hal::Keyboard { class SdlKeyboard final : public tt::hal::Keyboard {
private: private:
lv_indev_t* _Nullable handle = nullptr; lv_indev_t* _Nullable handle = nullptr;
public: public:
std::string getName() const final { return "SDL Keyboard"; }
std::string getDescription() const final { return "SDL keyboard device"; }
bool start(lv_display_t* display) override { bool start(lv_display_t* display) override {
handle = lv_sdl_keyboard_create(); handle = lv_sdl_keyboard_create();
return handle != nullptr; return handle != nullptr;
@ -20,6 +24,6 @@ public:
lv_indev_t* _Nullable getLvglIndev() override { return handle; } lv_indev_t* _Nullable getLvglIndev() override { return handle; }
}; };
tt::hal::Keyboard* createKeyboard() { std::shared_ptr<tt::hal::Keyboard> createKeyboard() {
return static_cast<tt::hal::Keyboard*>(new SdlKeyboard()); return std::make_shared<SdlKeyboard>();
} }

View File

@ -3,11 +3,15 @@
#include <Tactility/hal/Touch.h> #include <Tactility/hal/Touch.h>
#include <Tactility/TactilityCore.h> #include <Tactility/TactilityCore.h>
class SdlTouch : public tt::hal::Touch { class SdlTouch final : public tt::hal::Touch {
private: private:
lv_indev_t* _Nullable handle = nullptr; lv_indev_t* _Nullable handle = nullptr;
public: public:
std::string getName() const final { return "SDL Pointer"; }
std::string getDescription() const final { return "SDL mouse/touch pointer device"; }
bool start(lv_display_t* display) override { bool start(lv_display_t* display) override {
handle = lv_sdl_mouse_create(); handle = lv_sdl_mouse_create();
return handle != nullptr; return handle != nullptr;

View File

@ -5,7 +5,7 @@
using namespace tt::hal; using namespace tt::hal;
class SimulatorPower : public Power { class SimulatorPower final : public Power {
bool allowedToCharge = false; bool allowedToCharge = false;
@ -14,6 +14,9 @@ public:
SimulatorPower() = default; SimulatorPower() = default;
~SimulatorPower() override = default; ~SimulatorPower() override = default;
std::string getName() const final { return "Power Mock"; }
std::string getDescription() const final { return ""; }
bool supportsMetric(MetricType type) const override; bool supportsMetric(MetricType type) const override;
bool getMetric(Power::MetricType type, Power::MetricData& data) override; bool getMetric(Power::MetricType type, Power::MetricData& data) override;

View File

@ -4,16 +4,24 @@
using namespace tt::hal; using namespace tt::hal;
class SimulatorSdCard : public SdCard { class SimulatorSdCard final : public SdCard {
private: private:
State state; State state;
public: public:
SimulatorSdCard() : SdCard(MountBehaviour::AtBoot), state(State::Unmounted) {} SimulatorSdCard() : SdCard(MountBehaviour::AtBoot), state(State::Unmounted) {}
std::string getName() const final { return "Mock SD Card"; }
std::string getDescription() const final { return ""; }
bool mount(const char* mountPath) override { bool mount(const char* mountPath) override {
state = State::Mounted; state = State::Mounted;
return true; return true;
} }
bool unmount() override { bool unmount() override {
state = State::Unmounted; state = State::Unmounted;
return true; return true;

View File

@ -4,7 +4,7 @@
#define BQ24295_ADDRESS 0x6BU #define BQ24295_ADDRESS 0x6BU
class Bq24295 : I2cDevice { class Bq24295 final : public tt::hal::i2c::I2cDevice {
private: private:
@ -12,6 +12,10 @@ private:
public: public:
std::string getName() const final { return "BQ24295"; }
std::string getDescription() const final { return "I2C-controlled single cell USB charger"; }
enum class WatchDogTimer { enum class WatchDogTimer {
Disabled = 0b000000, Disabled = 0b000000,
Enabled40s = 0b010000, Enabled40s = 0b010000,

View File

@ -30,8 +30,8 @@ bool UnPhoneDisplay::start() {
lv_display_set_color_format(displayHandle, LV_COLOR_FORMAT_NATIVE); lv_display_set_color_format(displayHandle, LV_COLOR_FORMAT_NATIVE);
// TODO malloc to use SPIRAM // TODO malloc to use SPIRAM
static auto* buffer1 = (uint8_t*)heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_SPIRAM); buffer1 = (uint8_t*)heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_SPIRAM);
static auto* buffer2 = (uint8_t*)heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_SPIRAM); buffer2 = (uint8_t*)heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_SPIRAM);
assert(buffer1 != nullptr); assert(buffer1 != nullptr);
assert(buffer2 != nullptr); assert(buffer2 != nullptr);
@ -61,13 +61,18 @@ bool UnPhoneDisplay::stop() {
lv_display_delete(displayHandle); lv_display_delete(displayHandle);
displayHandle = nullptr; displayHandle = nullptr;
heap_caps_free(buffer1);
heap_caps_free(buffer2);
buffer1 = nullptr;
buffer2 = nullptr;
return true; return true;
} }
tt::hal::Touch* _Nullable UnPhoneDisplay::createTouch() { std::shared_ptr<tt::hal::Touch> _Nullable UnPhoneDisplay::createTouch() {
return static_cast<tt::hal::Touch*>(new UnPhoneTouch()); return std::make_shared<UnPhoneTouch>();
} }
tt::hal::Display* createDisplay() { std::shared_ptr<tt::hal::Display> createDisplay() {
return static_cast<tt::hal::Display*>(new UnPhoneDisplay()); return std::make_shared<UnPhoneDisplay>();
} }

View File

@ -11,16 +11,21 @@ class UnPhoneDisplay : public tt::hal::Display {
private: private:
lv_display_t* displayHandle = nullptr; lv_display_t* displayHandle = nullptr;
uint8_t* buffer1 = nullptr;
uint8_t* buffer2 = nullptr;
public: public:
std::string getName() const final { return "HX8357"; }
std::string getDescription() const final { return "SPI display"; }
bool start() override; bool start() override;
bool stop() override; bool stop() override;
tt::hal::Touch* _Nullable createTouch() override; std::shared_ptr<tt::hal::Touch> _Nullable createTouch() override;
lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; }
}; };
tt::hal::Display* createDisplay(); std::shared_ptr<tt::hal::Display> createDisplay();

View File

@ -12,6 +12,9 @@ public:
UnPhonePower() = default; UnPhonePower() = default;
~UnPhonePower() = default; ~UnPhonePower() = default;
std::string getName() const final { return "XPT2046 Power Measurement"; }
std::string getDescription() const final { return "Power interface via XPT2046 voltage measurement"; }
bool supportsMetric(MetricType type) const override; bool supportsMetric(MetricType type) const override;
bool getMetric(Power::MetricType type, Power::MetricData& data) override; bool getMetric(Power::MetricType type, Power::MetricData& data) override;

View File

@ -18,6 +18,9 @@ private:
public: public:
std::string getName() const final { return "XPT2046"; }
std::string getDescription() const final { return "I2C touch driver"; }
bool start(lv_display_t* display) override; bool start(lv_display_t* display) override;
bool stop() override; bool stop() override;
lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; }

View File

@ -3,4 +3,5 @@
cmake -S ./ -B build-sim cmake -S ./ -B build-sim
cmake --build build-sim --target build-tests -j 14 cmake --build build-sim --target build-tests -j 14
build-sim/Tests/TactilityCore/TactilityCoreTests --exit build-sim/Tests/TactilityCore/TactilityCoreTests --exit
build-sim/Tests/TactilityHeadless/TactilityHeadlessTests --exit

View File

@ -1,5 +1,5 @@
# TODOs # TODOs
- Create a base `Driver` object for drives, and a `DriverManager` to find devices - Fix system time to not be 1980 (use build year as minimum)
- Use std::span or string_view in StringUtils https://youtu.be/FRkJCvHWdwQ?t=2754 - Use std::span or string_view in StringUtils https://youtu.be/FRkJCvHWdwQ?t=2754
- Fix bug in T-Deck/etc: esp_lvgl_port settings has a large stack size (~9kB) to fix an issue where the T-Deck would get a stackoverflow. This sometimes happens when WiFi is auto-enabled and you open the app while it is still connecting. - Fix bug in T-Deck/etc: esp_lvgl_port settings has a large stack size (~9kB) to fix an issue where the T-Deck would get a stackoverflow. This sometimes happens when WiFi is auto-enabled and you open the app while it is still connecting.
- Clean up static_cast when casting to base class. - Clean up static_cast when casting to base class.

View File

@ -22,8 +22,10 @@ static uint8_t gamma = 255;
#define ROTATION_270 2 #define ROTATION_270 2
#define ROTATION_90 3 #define ROTATION_90 3
static void onBacklightSliderEvent(lv_event_t* event) { static void onBacklightSliderEvent(lv_event_t* event) {
auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event)); auto* slider = static_cast<lv_obj_t*>(lv_event_get_target(event));
auto* lvgl_display = lv_display_get_default(); auto* lvgl_display = lv_display_get_default();
assert(lvgl_display != nullptr); assert(lvgl_display != nullptr);
auto* hal_display = (tt::hal::Display*)lv_display_get_user_data(lvgl_display); auto* hal_display = (tt::hal::Display*)lv_display_get_user_data(lvgl_display);
@ -55,12 +57,8 @@ static void onGammaSliderEvent(lv_event_t* event) {
} }
} }
static tt::hal::Display* getHalDisplay(lv_obj_t* widget) { static std::shared_ptr<tt::hal::Display> getHalDisplay() {
auto* lvgl_display = lv_obj_get_display(widget); return hal::findFirstDevice<hal::Display>(hal::Device::Type::Display);
assert(lvgl_display != nullptr);
auto* hal_display = (tt::hal::Display*)lv_display_get_user_data(lvgl_display);
assert(hal_display != nullptr);
return hal_display;
} }
static lv_display_rotation_t orientationSettingToDisplayRotation(uint32_t setting) { static lv_display_rotation_t orientationSettingToDisplayRotation(uint32_t setting) {
@ -103,6 +101,9 @@ class DisplayApp : public App {
void onShow(AppContext& app, lv_obj_t* parent) override { void onShow(AppContext& app, lv_obj_t* parent) override {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
auto hal_display = getHalDisplay();
assert(hal_display != nullptr);
lvgl::toolbar_create(parent, app); lvgl::toolbar_create(parent, app);
lv_obj_t* main_wrapper = lv_obj_create(parent); lv_obj_t* main_wrapper = lv_obj_create(parent);
@ -131,12 +132,9 @@ class DisplayApp : public App {
lv_obj_t* gamma_slider = lv_slider_create(wrapper); lv_obj_t* gamma_slider = lv_slider_create(wrapper);
lv_obj_set_width(gamma_slider, LV_PCT(50)); lv_obj_set_width(gamma_slider, LV_PCT(50));
lv_obj_align(gamma_slider, LV_ALIGN_TOP_RIGHT, -8, 40); lv_obj_align(gamma_slider, LV_ALIGN_TOP_RIGHT, -8, 40);
lv_slider_set_range(gamma_slider, 0, getHalDisplay(parent)->getGammaCurveCount()); lv_slider_set_range(gamma_slider, 0, hal_display->getGammaCurveCount());
lv_obj_add_event_cb(gamma_slider, onGammaSliderEvent, LV_EVENT_VALUE_CHANGED, nullptr); lv_obj_add_event_cb(gamma_slider, onGammaSliderEvent, LV_EVENT_VALUE_CHANGED, nullptr);
auto* hal_display = getHalDisplay(parent);
assert(hal_display != nullptr);
if (!hal_display->supportsBacklightDuty()) { if (!hal_display->supportsBacklightDuty()) {
lv_slider_set_value(brightness_slider, 255, LV_ANIM_OFF); lv_slider_set_value(brightness_slider, 255, LV_ANIM_OFF);
lv_obj_add_state(brightness_slider, LV_STATE_DISABLED); lv_obj_add_state(brightness_slider, LV_STATE_DISABLED);

View File

@ -4,6 +4,7 @@
#include "Tactility/lvgl/Toolbar.h" #include "Tactility/lvgl/Toolbar.h"
#include "Tactility/service/loader/Loader.h" #include "Tactility/service/loader/Loader.h"
#include <Tactility/hal/Device.h>
#include <Tactility/Assets.h> #include <Tactility/Assets.h>
#include <Tactility/Tactility.h> #include <Tactility/Tactility.h>
#include <Tactility/Timer.h> #include <Tactility/Timer.h>
@ -33,7 +34,9 @@ class PowerApp : public App {
private: private:
Timer update_timer = Timer(Timer::Type::Periodic, &onTimer, nullptr); Timer update_timer = Timer(Timer::Type::Periodic, &onTimer, nullptr);
std::shared_ptr<tt::hal::Power> power = getConfiguration()->hardware->power();
std::shared_ptr<hal::Power> power;
lv_obj_t* enableLabel = nullptr; lv_obj_t* enableLabel = nullptr;
lv_obj_t* enableSwitch = nullptr; lv_obj_t* enableSwitch = nullptr;
lv_obj_t* batteryVoltageLabel = nullptr; lv_obj_t* batteryVoltageLabel = nullptr;
@ -135,11 +138,19 @@ private:
public: public:
void onCreate(AppContext& app) override {
power = hal::findFirstDevice<hal::Power>(hal::Device::Type::Power);
}
void onShow(AppContext& app, lv_obj_t* parent) override { void onShow(AppContext& app, lv_obj_t* parent) override {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app); lvgl::toolbar_create(parent, app);
if (power == nullptr) {
return;
}
lv_obj_t* wrapper = lv_obj_create(parent); lv_obj_t* wrapper = lv_obj_create(parent);
lv_obj_set_width(wrapper, LV_PCT(100)); lv_obj_set_width(wrapper, LV_PCT(100));
lv_obj_set_style_border_width(wrapper, 0, 0); lv_obj_set_style_border_width(wrapper, 0, 0);

View File

@ -1,6 +1,7 @@
#include "Tactility/lvgl/Toolbar.h" #include "Tactility/lvgl/Toolbar.h"
#include <Tactility/Assets.h> #include <Tactility/Assets.h>
#include <Tactility/hal/Device.h>
#include <Tactility/Tactility.h> #include <Tactility/Tactility.h>
#include <lvgl.h> #include <lvgl.h>
@ -42,17 +43,17 @@ static size_t getSpiTotal() {
} }
static void addMemoryBar(lv_obj_t* parent, const char* label, size_t used, size_t total) { static void addMemoryBar(lv_obj_t* parent, const char* label, size_t used, size_t total) {
lv_obj_t* container = lv_obj_create(parent); auto* container = lv_obj_create(parent);
lv_obj_set_size(container, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_size(container, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(container, 0, 0); lv_obj_set_style_pad_all(container, 0, 0);
lv_obj_set_style_border_width(container, 0, 0); lv_obj_set_style_border_width(container, 0, 0);
lv_obj_set_flex_flow(container, LV_FLEX_FLOW_ROW); lv_obj_set_flex_flow(container, LV_FLEX_FLOW_ROW);
lv_obj_t* left_label = lv_label_create(container); auto* left_label = lv_label_create(container);
lv_label_set_text(left_label, label); lv_label_set_text(left_label, label);
lv_obj_set_width(left_label, 60); lv_obj_set_width(left_label, 60);
lv_obj_t* bar = lv_bar_create(container); auto* bar = lv_bar_create(container);
lv_obj_set_flex_grow(bar, 1); lv_obj_set_flex_grow(bar, 1);
if (total > 0) { if (total > 0) {
@ -63,7 +64,7 @@ static void addMemoryBar(lv_obj_t* parent, const char* label, size_t used, size_
lv_bar_set_value(bar, (int32_t)used, LV_ANIM_OFF); lv_bar_set_value(bar, (int32_t)used, LV_ANIM_OFF);
lv_obj_t* bottom_label = lv_label_create(parent); auto* bottom_label = lv_label_create(parent);
lv_label_set_text_fmt(bottom_label, "%u / %u kB", (used / 1024), (total / 1024)); lv_label_set_text_fmt(bottom_label, "%u / %u kB", (used / 1024), (total / 1024));
lv_obj_set_width(bottom_label, LV_PCT(100)); lv_obj_set_width(bottom_label, LV_PCT(100));
lv_obj_set_style_text_align(bottom_label, LV_TEXT_ALIGN_RIGHT, 0); lv_obj_set_style_text_align(bottom_label, LV_TEXT_ALIGN_RIGHT, 0);
@ -90,19 +91,18 @@ static const char* getTaskState(const TaskStatus_t& task) {
} }
static void addRtosTask(lv_obj_t* parent, const TaskStatus_t& task) { static void addRtosTask(lv_obj_t* parent, const TaskStatus_t& task) {
lv_obj_t* label = lv_label_create(parent); auto* label = lv_label_create(parent);
const char* name = (task.pcTaskName == nullptr || task.pcTaskName[0] == 0) ? "(unnamed)" : task.pcTaskName; const char* name = (task.pcTaskName == nullptr || task.pcTaskName[0] == 0) ? "(unnamed)" : task.pcTaskName;
lv_label_set_text_fmt(label, "%s (%s)", name, getTaskState(task)); lv_label_set_text_fmt(label, "%s (%s)", name, getTaskState(task));
} }
static void addRtosTasks(lv_obj_t* parent) { static void addRtosTasks(lv_obj_t* parent) {
UBaseType_t count = uxTaskGetNumberOfTasks(); UBaseType_t count = uxTaskGetNumberOfTasks();
TaskStatus_t* tasks = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * count); auto* tasks = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * count);
uint32_t totalRuntime = 0; uint32_t totalRuntime = 0;
UBaseType_t actual = uxTaskGetSystemState(tasks, count, &totalRuntime); UBaseType_t actual = uxTaskGetSystemState(tasks, count, &totalRuntime);
for (int i = 0; i < actual; ++i) { for (int i = 0; i < actual; ++i) {
const TaskStatus_t& task = tasks[i]; const TaskStatus_t& task = tasks[i];
TT_LOG_I(TAG, "Task: %s", task.pcTaskName);
addRtosTask(parent, task); addRtosTask(parent, task);
} }
free(tasks); free(tasks);
@ -110,6 +110,18 @@ static void addRtosTasks(lv_obj_t* parent) {
#endif #endif
static void addDevice(lv_obj_t* parent, const std::shared_ptr<hal::Device>& device) {
auto* label = lv_label_create(parent);
lv_label_set_text(label, device->getName().c_str());
}
static void addDevices(lv_obj_t* parent) {
auto devices = hal::getDevices();
for (const auto& device: devices) {
addDevice(parent, device);
}
}
class SystemInfoApp : public App { class SystemInfoApp : public App {
void onShow(AppContext& app, lv_obj_t* parent) override { void onShow(AppContext& app, lv_obj_t* parent) override {
@ -117,16 +129,16 @@ class SystemInfoApp : public App {
lvgl::toolbar_create(parent, app); lvgl::toolbar_create(parent, app);
// This wrapper automatically has its children added vertically underneath eachother // This wrapper automatically has its children added vertically underneath eachother
lv_obj_t* wrapper = lv_obj_create(parent); auto* wrapper = lv_obj_create(parent);
lv_obj_set_style_border_width(wrapper, 0, 0); lv_obj_set_style_border_width(wrapper, 0, 0);
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);
lv_obj_set_width(wrapper, LV_PCT(100)); lv_obj_set_width(wrapper, LV_PCT(100));
lv_obj_set_flex_grow(wrapper, 1); lv_obj_set_flex_grow(wrapper, 1);
// Wrapper for the memory usage bars // Wrapper for the memory usage bars
lv_obj_t* memory_label = lv_label_create(wrapper); auto* memory_label = lv_label_create(wrapper);
lv_label_set_text(memory_label, "Memory usage"); lv_label_set_text(memory_label, "Memory usage");
lv_obj_t* memory_wrapper = lv_obj_create(wrapper); auto* memory_wrapper = lv_obj_create(wrapper);
lv_obj_set_flex_flow(memory_wrapper, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(memory_wrapper, LV_FLEX_FLOW_COLUMN);
lv_obj_set_size(memory_wrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_size(memory_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
@ -134,23 +146,29 @@ class SystemInfoApp : public App {
addMemoryBar(memory_wrapper, "SPI", getSpiTotal() - getSpiFree(), getSpiTotal()); addMemoryBar(memory_wrapper, "SPI", getSpiTotal() - getSpiFree(), getSpiTotal());
#if configUSE_TRACE_FACILITY #if configUSE_TRACE_FACILITY
lv_obj_t* tasks_label = lv_label_create(wrapper); auto* tasks_label = lv_label_create(wrapper);
lv_label_set_text(tasks_label, "Tasks"); lv_label_set_text(tasks_label, "Tasks");
lv_obj_t* tasks_wrapper = lv_obj_create(wrapper); auto* tasks_wrapper = lv_obj_create(wrapper);
lv_obj_set_flex_flow(tasks_wrapper, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(tasks_wrapper, LV_FLEX_FLOW_COLUMN);
lv_obj_set_size(tasks_wrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_size(tasks_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
addRtosTasks(tasks_wrapper); addRtosTasks(tasks_wrapper);
#endif #endif
auto* devices_label = lv_label_create(wrapper);
lv_label_set_text(devices_label, "Devices");
auto* devices_wrapper = lv_obj_create(wrapper);
lv_obj_set_flex_flow(devices_wrapper, LV_FLEX_FLOW_COLUMN);
lv_obj_set_size(devices_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
addDevices(devices_wrapper);
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
// Build info // Build info
lv_obj_t* build_info_label = lv_label_create(wrapper); auto* build_info_label = lv_label_create(wrapper);
lv_label_set_text(build_info_label, "Build info"); lv_label_set_text(build_info_label, "Build info");
lv_obj_t* build_info_wrapper = lv_obj_create(wrapper); auto* build_info_wrapper = lv_obj_create(wrapper);
lv_obj_set_flex_flow(build_info_wrapper, LV_FLEX_FLOW_COLUMN); lv_obj_set_flex_flow(build_info_wrapper, LV_FLEX_FLOW_COLUMN);
lv_obj_set_size(build_info_wrapper, LV_PCT(100), LV_SIZE_CONTENT); lv_obj_set_size(build_info_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_t* esp_idf_version = lv_label_create(build_info_wrapper); auto* esp_idf_version = lv_label_create(build_info_wrapper);
lv_label_set_text_fmt(esp_idf_version, "IDF version: %d.%d.%d", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR, ESP_IDF_VERSION_PATCH); lv_label_set_text_fmt(esp_idf_version, "IDF version: %d.%d.%d", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR, ESP_IDF_VERSION_PATCH);
#endif #endif
} }

View File

@ -11,14 +11,16 @@
namespace tt::lvgl { namespace tt::lvgl {
#define TAG "lvglinit" #define TAG "lvgl_init"
bool initDisplay(const hal::Configuration& config) { static std::shared_ptr<tt::hal::Display> initDisplay(const hal::Configuration& config) {
assert(config.createDisplay); assert(config.createDisplay);
auto* display = config.createDisplay(); auto display = config.createDisplay();
assert(display != nullptr);
if (!display->start()) { if (!display->start()) {
TT_LOG_E(TAG, "Display start failed"); TT_LOG_E(TAG, "Display start failed");
return false; return nullptr;
} }
lv_display_t* lvgl_display = display->getLvglDisplay(); lv_display_t* lvgl_display = display->getLvglDisplay();
@ -32,17 +34,17 @@ bool initDisplay(const hal::Configuration& config) {
// esp_lvgl_port users user_data by default, so we have to modify the source // esp_lvgl_port users user_data by default, so we have to modify the source
// this is a check for when we upgrade esp_lvgl_port and forget to modify it again // this is a check for when we upgrade esp_lvgl_port and forget to modify it again
assert(existing_display_user_data == nullptr); assert(existing_display_user_data == nullptr);
lv_display_set_user_data(lvgl_display, display); lv_display_set_user_data(lvgl_display, display.get());
lv_display_rotation_t rotation = app::display::getRotation(); lv_display_rotation_t rotation = app::display::getRotation();
if (rotation != lv_disp_get_rotation(lv_disp_get_default())) { if (rotation != lv_disp_get_rotation(lv_disp_get_default())) {
lv_disp_set_rotation(lv_disp_get_default(), static_cast<lv_display_rotation_t>(rotation)); lv_disp_set_rotation(lv_disp_get_default(), static_cast<lv_display_rotation_t>(rotation));
} }
return true; return display;
} }
bool initTouch(hal::Display* display, hal::Touch* touch) { static bool initTouch(const std::shared_ptr<hal::Display>& display, const std::shared_ptr<hal::Touch>& touch) {
TT_LOG_I(TAG, "Touch init"); TT_LOG_I(TAG, "Touch init");
assert(display); assert(display);
assert(touch); assert(touch);
@ -54,14 +56,14 @@ bool initTouch(hal::Display* display, hal::Touch* touch) {
} }
} }
bool initKeyboard(hal::Display* display, hal::Keyboard* keyboard) { static bool initKeyboard(const std::shared_ptr<hal::Display>& display, const std::shared_ptr<hal::Keyboard>& keyboard) {
TT_LOG_I(TAG, "Keyboard init"); TT_LOG_I(TAG, "Keyboard init");
assert(display); assert(display);
assert(keyboard); assert(keyboard);
if (keyboard->isAttached()) { if (keyboard->isAttached()) {
if (keyboard->start(display->getLvglDisplay())) { if (keyboard->start(display->getLvglDisplay())) {
lv_indev_t* keyboard_indev = keyboard->getLvglIndev(); lv_indev_t* keyboard_indev = keyboard->getLvglIndev();
lv_indev_set_user_data(keyboard_indev, keyboard); lv_indev_set_user_data(keyboard_indev, keyboard.get());
tt::lvgl::keypad_set_indev(keyboard_indev); tt::lvgl::keypad_set_indev(keyboard_indev);
TT_LOG_I(TAG, "Keyboard started"); TT_LOG_I(TAG, "Keyboard started");
return true; return true;
@ -85,20 +87,24 @@ void init(const hal::Configuration& config) {
return; return;
} }
if (!initDisplay(config)) { auto display = initDisplay(config);
if (display == nullptr) {
return; return;
} }
hal::registerDevice(display);
hal::Display* display = getDisplay(); auto touch = display->createTouch();
hal::Touch* touch = display->createTouch();
if (touch != nullptr) { if (touch != nullptr) {
hal::registerDevice(touch);
initTouch(display, touch); initTouch(display, touch);
} }
if (config.createKeyboard) { if (config.createKeyboard) {
hal::Keyboard* keyboard = config.createKeyboard(); auto keyboard = config.createKeyboard();
initKeyboard(display, keyboard); if (keyboard != nullptr) {
hal::registerDevice(keyboard);
initKeyboard(display, keyboard);
}
} }
TT_LOG_I(TAG, "Finished"); TT_LOG_I(TAG, "Finished");

View File

@ -3,8 +3,7 @@
namespace tt { namespace tt {
std::unique_ptr<ScopedLockableUsage> Lockable::scoped() const { std::unique_ptr<ScopedLockableUsage> Lockable::scoped() const {
auto* scoped = new ScopedLockableUsage(*this); return std::make_unique<ScopedLockableUsage>(*this);
return std::unique_ptr<ScopedLockableUsage>(scoped);
} }
} }

View File

@ -12,8 +12,8 @@ typedef bool (*InitLvgl)();
class Display; class Display;
class Keyboard; class Keyboard;
typedef Display* (*CreateDisplay)(); typedef std::shared_ptr<Display> (*CreateDisplay)();
typedef Keyboard* (*CreateKeyboard)(); typedef std::shared_ptr<Keyboard> (*CreateKeyboard)();
typedef std::shared_ptr<Power> (*CreatePower)(); typedef std::shared_ptr<Power> (*CreatePower)();
struct Configuration { struct Configuration {

View File

@ -0,0 +1,83 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
namespace tt::hal {
/**
* Base class for HAL-related devices.
*/
class Device {
public:
enum class Type {
I2c,
Display,
Touch,
SdCard,
Keyboard,
Power
};
typedef uint32_t Id;
private:
Id id;
public:
Device();
virtual ~Device() = default;
Id getId() const { return id; }
/** The type of device. */
virtual Type getType() const = 0;
/** The part number or hardware name e.g. TdeckTouch, TdeckDisplay, BQ24295, etc. */
virtual std::string getName() const = 0;
/** A short description of what this device does.
* e.g. "USB charging controller with I2C interface."
*/
virtual std::string getDescription() const = 0;
};
/**
* Adds a device to the registry.
* @warning This will leak memory if you want to destroy a device and don't call deregisterDevice()!
*/
void registerDevice(const std::shared_ptr<Device>& device);
/** Remove a device from the registry. */
void deregisterDevice(const std::shared_ptr<Device>& device);
/** Find a device in the registry by its name. */
std::shared_ptr<Device> _Nullable findDevice(std::string name);
/** Find a device in the registry by its identifier. */
std::shared_ptr<Device> _Nullable findDevice(Device::Id id);
/** Find 0, 1 or more devices in the registry by type. */
std::vector<std::shared_ptr<Device>> findDevices(Device::Type type);
/** Get a copy of the entire device registry in its current state. */
std::vector<std::shared_ptr<Device>> getDevices();
template<class DeviceType>
std::shared_ptr<DeviceType> findFirstDevice(Device::Type type) {
auto devices = findDevices(type);
if (devices.empty()) {
return {};
} else {
auto& first = devices[0];
return std::static_pointer_cast<DeviceType>(first);
}
}
}

View File

@ -1,13 +1,19 @@
#pragma once #pragma once
#include "lvgl.h" #include "Device.h"
#include <lvgl.h>
namespace tt::hal { namespace tt::hal {
class Touch; class Touch;
class Display { class Display : public Device {
public: public:
Type getType() const override { return Type::Display; }
virtual bool start() = 0; virtual bool start() = 0;
virtual bool stop() = 0; virtual bool stop() = 0;
@ -15,7 +21,7 @@ public:
virtual bool isPoweredOn() const { return true; } virtual bool isPoweredOn() const { return true; }
virtual bool supportsPowerControl() const { return false; } virtual bool supportsPowerControl() const { return false; }
virtual Touch* _Nullable createTouch() = 0; virtual std::shared_ptr<Touch> _Nullable createTouch() = 0;
/** Set a value in the range [0, 255] */ /** Set a value in the range [0, 255] */
virtual void setBacklightDuty(uint8_t backlightDuty) { /* NO-OP */ } virtual void setBacklightDuty(uint8_t backlightDuty) { /* NO-OP */ }

View File

@ -1,13 +1,19 @@
#pragma once #pragma once
#include "lvgl.h" #include "Device.h"
#include <lvgl.h>
namespace tt::hal { namespace tt::hal {
class Display; class Display;
class Keyboard { class Keyboard : public Device {
public: public:
Type getType() const override { return Type::Keyboard; }
virtual bool start(lv_display_t* display) = 0; virtual bool start(lv_display_t* display) = 0;
virtual bool stop() = 0; virtual bool stop() = 0;
virtual bool isAttached() const = 0; virtual bool isAttached() const = 0;

View File

@ -1,15 +1,18 @@
#pragma once #pragma once
#include "Device.h"
#include <cstdint> #include <cstdint>
namespace tt::hal { namespace tt::hal {
class Power{ class Power : public Device {
public: public:
Power() = default; Power() = default;
virtual ~Power() = default; ~Power() override = default;
Type getType() const override { return Type::Power; }
enum class MetricType { enum class MetricType {
IsCharging, // bool IsCharging, // bool

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "Device.h"
#include <Tactility/TactilityCore.h> #include <Tactility/TactilityCore.h>
namespace tt::hal { namespace tt::hal {
@ -7,8 +9,10 @@ namespace tt::hal {
#define TT_SDCARD_MOUNT_NAME "sdcard" #define TT_SDCARD_MOUNT_NAME "sdcard"
#define TT_SDCARD_MOUNT_POINT "/sdcard" #define TT_SDCARD_MOUNT_POINT "/sdcard"
class SdCard { class SdCard : public Device {
public: public:
enum class State { enum class State {
Mounted, Mounted,
Unmounted, Unmounted,
@ -22,11 +26,15 @@ public:
}; };
private: private:
MountBehaviour mountBehaviour; MountBehaviour mountBehaviour;
public: public:
explicit SdCard(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {} explicit SdCard(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {}
virtual ~SdCard() = default; virtual ~SdCard() override = default;
Type getType() const final { return Type::SdCard; };
virtual bool mount(const char* mountPath) = 0; virtual bool mount(const char* mountPath) = 0;
virtual bool unmount() = 0; virtual bool unmount() = 0;

View File

@ -70,6 +70,9 @@ public:
config(std::move(config)) config(std::move(config))
{} {}
std::string getName() const final { return "SD Card"; }
std::string getDescription() const final { return "SD card via SPI interface"; }
bool mount(const char* mountPath) override; bool mount(const char* mountPath) override;
bool unmount() override; bool unmount() override;
State getState() const override; State getState() const override;

View File

@ -1,14 +1,19 @@
#pragma once #pragma once
#include "lvgl.h" #include "Device.h"
#include <lvgl.h>
namespace tt::hal { namespace tt::hal {
class Display; class Display;
class Touch { class Touch : public Device {
public: public:
Type getType() const override { return Type::SdCard; }
virtual bool start(lv_display_t* display) = 0; virtual bool start(lv_display_t* display) = 0;
virtual bool stop() = 0; virtual bool stop() = 0;

View File

@ -1,6 +1,9 @@
#pragma once #pragma once
#include "./I2c.h" #include "I2c.h"
#include "../Device.h"
namespace tt::hal::i2c {
/** /**
* Represents an I2C peripheral at a specific port and address. * Represents an I2C peripheral at a specific port and address.
@ -8,7 +11,7 @@
* *
* All read and write calls are thread-safe. * All read and write calls are thread-safe.
*/ */
class I2cDevice { class I2cDevice : public Device {
protected: protected:
@ -17,6 +20,8 @@ protected:
static constexpr TickType_t DEFAULT_TIMEOUT = 1000 / portTICK_PERIOD_MS; static constexpr TickType_t DEFAULT_TIMEOUT = 1000 / portTICK_PERIOD_MS;
Type getType() const override { return Type::I2c; }
bool readRegister8(uint8_t reg, uint8_t& result) const; bool readRegister8(uint8_t reg, uint8_t& result) const;
bool writeRegister8(uint8_t reg, uint8_t value) const; bool writeRegister8(uint8_t reg, uint8_t value) const;
bool readRegister12(uint8_t reg, float& out) const; bool readRegister12(uint8_t reg, float& out) const;
@ -26,7 +31,10 @@ protected:
bool bitOff(uint8_t reg, uint8_t bitmask) const; bool bitOff(uint8_t reg, uint8_t bitmask) const;
bool bitOnByIndex(uint8_t reg, uint8_t index) const { return bitOn(reg, 1 << index); } bool bitOnByIndex(uint8_t reg, uint8_t index) const { return bitOn(reg, 1 << index); }
bool bitOffByIndex(uint8_t reg, uint8_t index) const { return bitOff(reg, 1 << index); } bool bitOffByIndex(uint8_t reg, uint8_t index) const { return bitOff(reg, 1 << index); }
public: public:
explicit I2cDevice(i2c_port_t port, uint32_t address) : port(port), address(address) {} explicit I2cDevice(i2c_port_t port, uint32_t address) : port(port), address(address) {}
}; };
}

View File

@ -0,0 +1,97 @@
#include "Tactility/hal/Device.h"
#include <ranges>
#include <Tactility/Mutex.h>
namespace tt::hal {
std::vector<std::shared_ptr<Device>> devices;
Mutex mutex = Mutex(Mutex::Type::Recursive);
static Device::Id nextId = 0;
#define TAG "devices"
Device::Device() : id(nextId++) {}
template <std::ranges::range RangeType>
auto toVector(RangeType&& range) {
auto view = range | std::views::common;
return std::vector(view.begin(), view.end());
}
void registerDevice(const std::shared_ptr<Device>& device) {
auto scoped_mutex = mutex.scoped();
if (scoped_mutex->lock()) {
if (findDevice(device->getId()) == nullptr) {
devices.push_back(device);
TT_LOG_I(TAG, "Registered %s with id %lu", device->getName().c_str(), device->getId());
} else {
TT_LOG_W(TAG, "Device %s with id %lu was already registered", device->getName().c_str(), device->getId());
}
}
}
void deregisterDevice(const std::shared_ptr<Device>& device) {
auto scoped_mutex = mutex.scoped();
auto id_to_remove = device->getId();
if (scoped_mutex->lock()) {
auto remove_iterator = std::remove_if(devices.begin(), devices.end(), [id_to_remove](const auto& device) {
return device->getId() == id_to_remove;
});
if (remove_iterator != devices.end()) {
TT_LOG_I(TAG, "Deregistering %s with id %lu", device->getName().c_str(), device->getId());
devices.erase(remove_iterator);
} else {
TT_LOG_W(TAG, "Deregistering %s with id %lu failed: not found", device->getName().c_str(), device->getId());
}
}
}
std::vector<std::shared_ptr<Device>> findDevices(const std::function<bool(const std::shared_ptr<Device>&)>& filterFunction) {
auto scoped_mutex = mutex.scoped();
if (scoped_mutex->lock()) {
auto devices_view = devices | std::views::filter([&filterFunction](auto& device) {
return filterFunction(device);
});
return toVector(devices_view);
}
return {};
}
static std::shared_ptr<Device> _Nullable findDevice(const std::function<bool(const std::shared_ptr<Device>&)>& filterFunction) {
auto scoped_mutex = mutex.scoped();
if (scoped_mutex->lock()) {
auto result_set = devices | std::views::filter([&filterFunction](auto& device) {
return filterFunction(device);
});
if (!result_set.empty()) {
return result_set.front();
}
}
return nullptr;
}
std::shared_ptr<Device> _Nullable findDevice(std::string name) {
return findDevice([&name](auto& device){
return device->getName() == name;
});
}
std::shared_ptr<Device> _Nullable findDevice(Device::Id id) {
return findDevice([id](auto& device){
return device->getId() == id;
});
}
std::vector<std::shared_ptr<Device>> findDevices(Device::Type type) {
return findDevices([type](auto& device) {
return device->getType() == type;
});
}
std::vector<std::shared_ptr<Device>> getDevices() {
return devices;
}
}

View File

@ -1,3 +1,4 @@
#include "Tactility/hal/Device.h"
#include "Tactility/hal/Hal_i.h" #include "Tactility/hal/Hal_i.h"
#include "Tactility/hal/i2c/I2c.h" #include "Tactility/hal/i2c/I2c.h"
@ -28,6 +29,12 @@ void init(const Configuration& configuration) {
if (!configuration.sdcard->mount(TT_SDCARD_MOUNT_POINT)) { if (!configuration.sdcard->mount(TT_SDCARD_MOUNT_POINT)) {
TT_LOG_W(TAG, "SD card mount failed (init can continue)"); TT_LOG_W(TAG, "SD card mount failed (init can continue)");
} }
hal::registerDevice(configuration.sdcard);
}
if (configuration.power != nullptr) {
std::shared_ptr<tt::hal::Power> power = configuration.power();
hal::registerDevice(power);
} }
kernel::systemEventPublish(kernel::SystemEvent::BootInitHalEnd); kernel::systemEventPublish(kernel::SystemEvent::BootInitHalEnd);

View File

@ -2,6 +2,8 @@
#include <cstdint> #include <cstdint>
namespace tt::hal::i2c {
bool I2cDevice::readRegister12(uint8_t reg, float& out) const { bool I2cDevice::readRegister12(uint8_t reg, float& out) const {
std::uint8_t data[2] = {0}; std::uint8_t data[2] = {0};
if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) { if (tt::hal::i2c::masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) {
@ -59,3 +61,5 @@ bool I2cDevice::bitOff(uint8_t reg, uint8_t bitmask) const {
return false; return false;
} }
} }
} // namespace

View File

@ -4,6 +4,8 @@ set(DOCTESTINC ${PROJECT_SOURCE_DIR}/Include)
enable_testing() enable_testing()
add_subdirectory(TactilityCore) add_subdirectory(TactilityCore)
add_subdirectory(TactilityHeadless)
add_custom_target(build-tests) add_custom_target(build-tests)
add_dependencies(build-tests TactilityCoreTests) add_dependencies(build-tests TactilityCoreTests)
add_dependencies(build-tests TactilityHeadlessTests)

View File

@ -0,0 +1,27 @@
project(TactilityCoreTests)
enable_language(C CXX ASM)
set(CMAKE_CXX_COMPILER g++)
file(GLOB_RECURSE TEST_SOURCES ${PROJECT_SOURCE_DIR}/*.cpp)
add_executable(TactilityHeadlessTests EXCLUDE_FROM_ALL ${TEST_SOURCES})
add_definitions(-D_Nullable=)
add_definitions(-D_Nonnull=)
target_include_directories(TactilityHeadlessTests PRIVATE
${DOCTESTINC}
)
add_test(NAME TactilityHeadlessTests
COMMAND TactilityHeadlessTests
)
target_link_libraries(TactilityHeadlessTests PRIVATE
Tactility
TactilityCore
TactilityHeadless
Simulator
SDL2::SDL2-static SDL2-static
)

View File

@ -0,0 +1,98 @@
#include "doctest.h"
#include <Tactility/hal/Device.h>
#include <utility>
using namespace tt;
class TestDevice final : public hal::Device {
private:
hal::Device::Type type;
std::string name;
std::string description;
public:
TestDevice(hal::Device::Type type, std::string name, std::string description) :
type(type),
name(std::move(name)),
description(std::move(description))
{}
TestDevice() : TestDevice(hal::Device::Type::Power, "PowerMock", "PowerMock description") {}
~TestDevice() final = default;
Type getType() const final { return type; }
std::string getName() const final { return name; }
std::string getDescription() const final { return description; }
};
class DeviceAutoRegistration {
std::shared_ptr<hal::Device> device;
public:
explicit DeviceAutoRegistration(std::shared_ptr<hal::Device> inDevice) : device(std::move(inDevice)) {
hal::registerDevice(device);
}
~DeviceAutoRegistration() {
hal::deregisterDevice(device);
}
};
/** We add 3 tests into 1 to ensure cleanup happens */
TEST_CASE("registering and deregistering a device works") {
auto device = std::make_shared<TestDevice>();
// Pre-registration
CHECK_EQ(hal::findDevice(device->getId()), nullptr);
// Registration
hal::registerDevice(device);
auto found_device = hal::findDevice(device->getId());
CHECK_NE(found_device, nullptr);
CHECK_EQ(found_device->getId(), device->getId());
// Deregistration
hal::deregisterDevice(device);
CHECK_EQ(hal::findDevice(device->getId()), nullptr);
found_device = nullptr; // to decrease use count
CHECK_EQ(device.use_count(), 1);
}
TEST_CASE("find device by id") {
auto device = std::make_shared<TestDevice>();
DeviceAutoRegistration auto_registration(device);
auto found_device = hal::findDevice(device->getId());
CHECK_NE(found_device, nullptr);
CHECK_EQ(found_device->getId(), device->getId());
}
TEST_CASE("find device by name") {
auto device = std::make_shared<TestDevice>();
DeviceAutoRegistration auto_registration(device);
auto found_device = hal::findDevice(device->getName());
CHECK_NE(found_device, nullptr);
CHECK_EQ(found_device->getId(), device->getId());
}
TEST_CASE("find device by type") {
// Headless mode shouldn't have a display, so we want to create one to find only our own display as unique device
// We first verify the initial assumption that there is no display:
auto unexpected_display = hal::findFirstDevice<TestDevice>(hal::Device::Type::Display);
CHECK_EQ(unexpected_display, nullptr);
auto device = std::make_shared<TestDevice>(hal::Device::Type::Display, "DisplayMock", "");
DeviceAutoRegistration auto_registration(device);
auto found_device = hal::findFirstDevice<TestDevice>(hal::Device::Type::Display);
CHECK_NE(found_device, nullptr);
CHECK_EQ(found_device->getId(), device->getId());
}

View File

@ -0,0 +1,51 @@
#define DOCTEST_CONFIG_IMPLEMENT
#include "doctest.h"
#include <cassert>
#include "FreeRTOS.h"
#include "task.h"
typedef struct {
int argc;
char** argv;
int result;
} TestTaskData;
void test_task(void* parameter) {
auto* data = (TestTaskData*)parameter;
doctest::Context context;
context.applyCommandLine(data->argc, data->argv);
// overrides
context.setOption("no-breaks", true); // don't break in the debugger when assertions fail
data->result = context.run();
if (context.shouldExit()) { // important - query flags (and --exit) rely on the user doing this
vTaskEndScheduler();
}
vTaskDelete(nullptr);
}
int main(int argc, char** argv) {
TestTaskData data = {
.argc = argc,
.argv = argv,
.result = 0
};
BaseType_t task_result = xTaskCreate(
test_task,
"test_task",
8192,
&data,
1,
nullptr
);
assert(task_result == pdPASS);
vTaskStartScheduler();
}