From cff0605b0a273e670bda413a588c738b33a97903 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sun, 2 Feb 2025 15:16:51 +0100 Subject: [PATCH] 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. --- .github/workflows/tests.yml | 4 +- .../Source/hal/YellowDisplay.cpp | 8 +- .../CYD-2432S024C/Source/hal/YellowDisplay.h | 7 +- Boards/CYD-2432S024C/Source/hal/YellowTouch.h | 8 ++ .../LilygoTdeck/Source/hal/TdeckDisplay.cpp | 8 +- Boards/LilygoTdeck/Source/hal/TdeckDisplay.h | 7 +- .../LilygoTdeck/Source/hal/TdeckKeyboard.cpp | 4 +- Boards/LilygoTdeck/Source/hal/TdeckKeyboard.h | 9 +- Boards/LilygoTdeck/Source/hal/TdeckPower.h | 3 + Boards/LilygoTdeck/Source/hal/TdeckTouch.h | 7 ++ .../M5stackCore2/Source/hal/Core2Display.cpp | 8 +- Boards/M5stackCore2/Source/hal/Core2Display.h | 7 +- Boards/M5stackCore2/Source/hal/Core2Power.h | 3 + Boards/M5stackCore2/Source/hal/Core2Touch.h | 3 + Boards/M5stackCoreS3/Source/Aw9523/Aw9523.h | 5 +- Boards/M5stackCoreS3/Source/Axp2101/Axp2101.h | 5 +- .../Source/hal/CoreS3Display.cpp | 8 +- .../M5stackCoreS3/Source/hal/CoreS3Display.h | 7 +- .../M5stackCoreS3/Source/hal/CoreS3Power.cpp | 1 - Boards/M5stackCoreS3/Source/hal/CoreS3Power.h | 3 + Boards/M5stackCoreS3/Source/hal/CoreS3Touch.h | 8 ++ Boards/Simulator/Source/hal/SdlDisplay.h | 13 ++- Boards/Simulator/Source/hal/SdlKeyboard.h | 10 +- Boards/Simulator/Source/hal/SdlTouch.h | 6 +- Boards/Simulator/Source/hal/SimulatorPower.h | 5 +- Boards/Simulator/Source/hal/SimulatorSdCard.h | 10 +- Boards/UnPhone/Source/bq24295/Bq24295.h | 6 +- Boards/UnPhone/Source/hal/UnPhoneDisplay.cpp | 17 ++-- Boards/UnPhone/Source/hal/UnPhoneDisplay.h | 9 +- Boards/UnPhone/Source/hal/UnPhonePower.h | 3 + Boards/UnPhone/Source/hal/UnPhoneTouch.h | 3 + Buildscripts/runtests.sh | 1 + Documentation/ideas.md | 2 +- Tactility/Source/app/display/Display.cpp | 18 ++-- Tactility/Source/app/power/Power.cpp | 13 ++- .../Source/app/systeminfo/SystemInfo.cpp | 48 ++++++--- Tactility/Source/lvgl/Init.cpp | 36 ++++--- TactilityCore/Source/Lockable.cpp | 3 +- .../Include/Tactility/hal/Configuration.h | 4 +- .../Include/Tactility/hal/Device.h | 83 ++++++++++++++++ .../Include/Tactility/hal/Display.h | 12 ++- .../Include/Tactility/hal/Keyboard.h | 10 +- .../Include/Tactility/hal/Power.h | 7 +- .../Include/Tactility/hal/SdCard.h | 12 ++- .../Include/Tactility/hal/SpiSdCard.h | 3 + .../Include/Tactility/hal/Touch.h | 9 +- .../Include/Tactility/hal/i2c/I2cDevice.h | 12 ++- TactilityHeadless/Source/hal/Device.cpp | 97 ++++++++++++++++++ TactilityHeadless/Source/hal/Hal.cpp | 7 ++ .../Source/hal/i2c/I2cDevice.cpp | 4 + Tests/CMakeLists.txt | 2 + Tests/TactilityHeadless/CMakeLists.txt | 27 +++++ Tests/TactilityHeadless/HalDeviceTest.cpp | 98 +++++++++++++++++++ Tests/TactilityHeadless/Main.cpp | 51 ++++++++++ 54 files changed, 655 insertions(+), 109 deletions(-) create mode 100644 TactilityHeadless/Include/Tactility/hal/Device.h create mode 100644 TactilityHeadless/Source/hal/Device.cpp create mode 100644 Tests/TactilityHeadless/CMakeLists.txt create mode 100644 Tests/TactilityHeadless/HalDeviceTest.cpp create mode 100644 Tests/TactilityHeadless/Main.cpp diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dc54967d..eff7ff55 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,5 +19,7 @@ jobs: run: cmake -S ./ -B build - name: "Build Tests" run: cmake --build build --target build-tests - - name: "Run Tests" + - name: "Run TactilityCore Tests" run: build/Tests/TactilityCore/TactilityCoreTests --exit + - name: "Run TactilityHeadless Tests" + run: build/Tests/TactilityHeadless/TactilityHeadlessTests --exit diff --git a/Boards/CYD-2432S024C/Source/hal/YellowDisplay.cpp b/Boards/CYD-2432S024C/Source/hal/YellowDisplay.cpp index 57de49c7..93893f99 100644 --- a/Boards/CYD-2432S024C/Source/hal/YellowDisplay.cpp +++ b/Boards/CYD-2432S024C/Source/hal/YellowDisplay.cpp @@ -205,10 +205,10 @@ void YellowDisplay::setGammaCurve(uint8_t index) { } } -tt::hal::Touch* _Nullable YellowDisplay::createTouch() { - return static_cast(new YellowTouch()); +std::shared_ptr _Nullable YellowDisplay::createTouch() { + return std::make_shared(); } -tt::hal::Display* createDisplay() { - return static_cast(new YellowDisplay()); +std::shared_ptr createDisplay() { + return std::make_shared(); } diff --git a/Boards/CYD-2432S024C/Source/hal/YellowDisplay.h b/Boards/CYD-2432S024C/Source/hal/YellowDisplay.h index 7db767c1..26ff2834 100644 --- a/Boards/CYD-2432S024C/Source/hal/YellowDisplay.h +++ b/Boards/CYD-2432S024C/Source/hal/YellowDisplay.h @@ -16,11 +16,14 @@ private: public: + std::string getName() const final { return "ILI9341"; } + std::string getDescription() const final { return "SPI display"; } + bool start() override; bool stop() override; - tt::hal::Touch* _Nullable createTouch() override; + std::shared_ptr _Nullable createTouch() override; void setBacklightDuty(uint8_t backlightDuty) override; bool supportsBacklightDuty() const override { return true; } @@ -31,4 +34,4 @@ public: lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } }; -tt::hal::Display* createDisplay(); +std::shared_ptr createDisplay(); diff --git a/Boards/CYD-2432S024C/Source/hal/YellowTouch.h b/Boards/CYD-2432S024C/Source/hal/YellowTouch.h index 306a290a..57dbcef5 100644 --- a/Boards/CYD-2432S024C/Source/hal/YellowTouch.h +++ b/Boards/CYD-2432S024C/Source/hal/YellowTouch.h @@ -5,12 +5,20 @@ #include class YellowTouch : public tt::hal::Touch { + private: + esp_lcd_panel_io_handle_t ioHandle = nullptr; esp_lcd_touch_handle_t touchHandle = nullptr; lv_indev_t* _Nullable deviceHandle = nullptr; + void cleanup(); + 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 stop() override; lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } diff --git a/Boards/LilygoTdeck/Source/hal/TdeckDisplay.cpp b/Boards/LilygoTdeck/Source/hal/TdeckDisplay.cpp index bc8c1c4f..00caaa99 100644 --- a/Boards/LilygoTdeck/Source/hal/TdeckDisplay.cpp +++ b/Boards/LilygoTdeck/Source/hal/TdeckDisplay.cpp @@ -191,8 +191,8 @@ void TdeckDisplay::setPowerOn(bool turnOn) { } } -tt::hal::Touch* _Nullable TdeckDisplay::createTouch() { - return static_cast(new TdeckTouch()); +std::shared_ptr _Nullable TdeckDisplay::createTouch() { + return std::make_shared(); } void TdeckDisplay::setBacklightDuty(uint8_t backlightDuty) { @@ -233,6 +233,6 @@ void TdeckDisplay::setGammaCurve(uint8_t index) { } } -tt::hal::Display* createDisplay() { - return static_cast(new TdeckDisplay()); +std::shared_ptr createDisplay() { + return std::make_shared(); } diff --git a/Boards/LilygoTdeck/Source/hal/TdeckDisplay.h b/Boards/LilygoTdeck/Source/hal/TdeckDisplay.h index 5c437d84..8e4f8808 100644 --- a/Boards/LilygoTdeck/Source/hal/TdeckDisplay.h +++ b/Boards/LilygoTdeck/Source/hal/TdeckDisplay.h @@ -17,6 +17,9 @@ private: public: + std::string getName() const final { return "ST7780"; } + std::string getDescription() const final { return "SPI display"; } + bool start() override; bool stop() override; @@ -25,7 +28,7 @@ public: bool isPoweredOn() const override { return poweredOn; }; bool supportsPowerControl() const override { return true; } - tt::hal::Touch* _Nullable createTouch() override; + std::shared_ptr _Nullable createTouch() override; void setBacklightDuty(uint8_t backlightDuty) override; bool supportsBacklightDuty() const override { return true; } @@ -40,4 +43,4 @@ private: static bool startBacklight(); }; -tt::hal::Display* createDisplay(); +std::shared_ptr createDisplay(); diff --git a/Boards/LilygoTdeck/Source/hal/TdeckKeyboard.cpp b/Boards/LilygoTdeck/Source/hal/TdeckKeyboard.cpp index ad7407b0..1f257cc2 100644 --- a/Boards/LilygoTdeck/Source/hal/TdeckKeyboard.cpp +++ b/Boards/LilygoTdeck/Source/hal/TdeckKeyboard.cpp @@ -62,6 +62,6 @@ bool TdeckKeyboard::isAttached() const { return tt::hal::i2c::masterHasDeviceAtAddress(TDECK_KEYBOARD_I2C_BUS_HANDLE, TDECK_KEYBOARD_SLAVE_ADDRESS, 100); } -tt::hal::Keyboard* createKeyboard() { - return dynamic_cast(new TdeckKeyboard()); +std::shared_ptr createKeyboard() { + return std::make_shared(); } diff --git a/Boards/LilygoTdeck/Source/hal/TdeckKeyboard.h b/Boards/LilygoTdeck/Source/hal/TdeckKeyboard.h index e2d453f1..566d2b2f 100644 --- a/Boards/LilygoTdeck/Source/hal/TdeckKeyboard.h +++ b/Boards/LilygoTdeck/Source/hal/TdeckKeyboard.h @@ -6,13 +6,20 @@ #include class TdeckKeyboard : public tt::hal::Keyboard { + private: + lv_indev_t* _Nullable deviceHandle = nullptr; + 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 stop() override; bool isAttached() const override; lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } }; -tt::hal::Keyboard* createKeyboard(); +std::shared_ptr createKeyboard(); diff --git a/Boards/LilygoTdeck/Source/hal/TdeckPower.h b/Boards/LilygoTdeck/Source/hal/TdeckPower.h index df4de389..fc6467ef 100644 --- a/Boards/LilygoTdeck/Source/hal/TdeckPower.h +++ b/Boards/LilygoTdeck/Source/hal/TdeckPower.h @@ -15,6 +15,9 @@ public: 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 getMetric(Power::MetricType type, Power::MetricData& data) override; diff --git a/Boards/LilygoTdeck/Source/hal/TdeckTouch.h b/Boards/LilygoTdeck/Source/hal/TdeckTouch.h index e282adc4..73ce708b 100644 --- a/Boards/LilygoTdeck/Source/hal/TdeckTouch.h +++ b/Boards/LilygoTdeck/Source/hal/TdeckTouch.h @@ -6,12 +6,19 @@ #include class TdeckTouch : public tt::hal::Touch { + 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_touch_handle_t _Nullable touchHandle = nullptr; lv_indev_t* _Nullable deviceHandle = nullptr; void cleanup(); + public: + bool start(lv_display_t* display) override; bool stop() override; lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } diff --git a/Boards/M5stackCore2/Source/hal/Core2Display.cpp b/Boards/M5stackCore2/Source/hal/Core2Display.cpp index 9151c0cc..501360fe 100644 --- a/Boards/M5stackCore2/Source/hal/Core2Display.cpp +++ b/Boards/M5stackCore2/Source/hal/Core2Display.cpp @@ -153,10 +153,10 @@ void Core2Display::setGammaCurve(uint8_t index) { } } -tt::hal::Touch* _Nullable Core2Display::createTouch() { - return static_cast(new Core2Touch()); +std::shared_ptr _Nullable Core2Display::createTouch() { + return std::make_shared(); } -tt::hal::Display* createDisplay() { - return static_cast(new Core2Display()); +std::shared_ptr createDisplay() { + return std::make_shared(); } diff --git a/Boards/M5stackCore2/Source/hal/Core2Display.h b/Boards/M5stackCore2/Source/hal/Core2Display.h index 9530c2ff..b0f18b6e 100644 --- a/Boards/M5stackCore2/Source/hal/Core2Display.h +++ b/Boards/M5stackCore2/Source/hal/Core2Display.h @@ -17,11 +17,14 @@ private: public: + std::string getName() const final { return "ILI9342C"; } + std::string getDescription() const final { return "Display (ILI9342C with an ILI9341 driver)"; } + bool start() override; bool stop() override; - tt::hal::Touch* _Nullable createTouch() override; + std::shared_ptr _Nullable createTouch() override; bool supportsBacklightDuty() const override { return false; } @@ -31,4 +34,4 @@ public: lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } }; -tt::hal::Display* createDisplay(); +std::shared_ptr createDisplay(); diff --git a/Boards/M5stackCore2/Source/hal/Core2Power.h b/Boards/M5stackCore2/Source/hal/Core2Power.h index 63c2d5bb..44583c70 100644 --- a/Boards/M5stackCore2/Source/hal/Core2Power.h +++ b/Boards/M5stackCore2/Source/hal/Core2Power.h @@ -12,6 +12,9 @@ public: Core2Power() = 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 getMetric(Power::MetricType type, Power::MetricData& data) override; diff --git a/Boards/M5stackCore2/Source/hal/Core2Touch.h b/Boards/M5stackCore2/Source/hal/Core2Touch.h index e9de26da..5aaf8349 100644 --- a/Boards/M5stackCore2/Source/hal/Core2Touch.h +++ b/Boards/M5stackCore2/Source/hal/Core2Touch.h @@ -23,6 +23,9 @@ public: 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 stop() override; diff --git a/Boards/M5stackCoreS3/Source/Aw9523/Aw9523.h b/Boards/M5stackCoreS3/Source/Aw9523/Aw9523.h index 36b32b57..3325c09c 100644 --- a/Boards/M5stackCoreS3/Source/Aw9523/Aw9523.h +++ b/Boards/M5stackCoreS3/Source/Aw9523/Aw9523.h @@ -4,12 +4,15 @@ #define AW9523_ADDRESS 0x58 -class Aw9523 : I2cDevice { +class Aw9523 : public tt::hal::i2c::I2cDevice { public: 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 readP1(uint8_t& output) const; diff --git a/Boards/M5stackCoreS3/Source/Axp2101/Axp2101.h b/Boards/M5stackCoreS3/Source/Axp2101/Axp2101.h index 327ffd23..71b789e3 100644 --- a/Boards/M5stackCoreS3/Source/Axp2101/Axp2101.h +++ b/Boards/M5stackCoreS3/Source/Axp2101/Axp2101.h @@ -9,7 +9,7 @@ * - 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 */ -class Axp2101 : I2cDevice { +class Axp2101 final : public tt::hal::i2c::I2cDevice { public: @@ -21,6 +21,9 @@ public: 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 getBatteryVoltage(float& vbatMillis) const; diff --git a/Boards/M5stackCoreS3/Source/hal/CoreS3Display.cpp b/Boards/M5stackCoreS3/Source/hal/CoreS3Display.cpp index d8c4308e..cff6f5bc 100644 --- a/Boards/M5stackCoreS3/Source/hal/CoreS3Display.cpp +++ b/Boards/M5stackCoreS3/Source/hal/CoreS3Display.cpp @@ -181,10 +181,10 @@ void CoreS3Display::setBacklightDuty(uint8_t backlightDuty) { } } -tt::hal::Touch* _Nullable CoreS3Display::createTouch() { - return static_cast(new CoreS3Touch()); +std::shared_ptr _Nullable CoreS3Display::createTouch() { + return std::make_shared(); } -tt::hal::Display* createDisplay() { - return static_cast(new CoreS3Display()); +std::shared_ptr createDisplay() { + return std::make_shared(); } diff --git a/Boards/M5stackCoreS3/Source/hal/CoreS3Display.h b/Boards/M5stackCoreS3/Source/hal/CoreS3Display.h index 45b7851b..00cd9815 100644 --- a/Boards/M5stackCoreS3/Source/hal/CoreS3Display.h +++ b/Boards/M5stackCoreS3/Source/hal/CoreS3Display.h @@ -16,11 +16,14 @@ private: public: + std::string getName() const final { return "ILI9342C"; } + std::string getDescription() const final { return "Display (ILI9342C with an ILI9341 driver)"; } + bool start() override; bool stop() override; - tt::hal::Touch* _Nullable createTouch() override; + std::shared_ptr _Nullable createTouch() override; void setBacklightDuty(uint8_t backlightDuty) override; bool supportsBacklightDuty() const override { return true; } @@ -31,4 +34,4 @@ public: lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } }; -tt::hal::Display* createDisplay(); +std::shared_ptr createDisplay(); diff --git a/Boards/M5stackCoreS3/Source/hal/CoreS3Power.cpp b/Boards/M5stackCoreS3/Source/hal/CoreS3Power.cpp index 36a983a2..3cda8174 100644 --- a/Boards/M5stackCoreS3/Source/hal/CoreS3Power.cpp +++ b/Boards/M5stackCoreS3/Source/hal/CoreS3Power.cpp @@ -1,5 +1,4 @@ #include "CoreS3Power.h" -#include #define TAG "core2_power" diff --git a/Boards/M5stackCoreS3/Source/hal/CoreS3Power.h b/Boards/M5stackCoreS3/Source/hal/CoreS3Power.h index a2988900..f6238497 100644 --- a/Boards/M5stackCoreS3/Source/hal/CoreS3Power.h +++ b/Boards/M5stackCoreS3/Source/hal/CoreS3Power.h @@ -16,6 +16,9 @@ public: CoreS3Power() = 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 getMetric(Power::MetricType type, Power::MetricData& data) override; diff --git a/Boards/M5stackCoreS3/Source/hal/CoreS3Touch.h b/Boards/M5stackCoreS3/Source/hal/CoreS3Touch.h index fed1d5de..91f8d05b 100644 --- a/Boards/M5stackCoreS3/Source/hal/CoreS3Touch.h +++ b/Boards/M5stackCoreS3/Source/hal/CoreS3Touch.h @@ -5,12 +5,20 @@ #include class CoreS3Touch : public tt::hal::Touch { + private: + esp_lcd_panel_io_handle_t ioHandle = nullptr; esp_lcd_touch_handle_t touchHandle = nullptr; lv_indev_t* _Nullable deviceHandle = nullptr; + void cleanup(); + 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 stop() override; lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } diff --git a/Boards/Simulator/Source/hal/SdlDisplay.h b/Boards/Simulator/Source/hal/SdlDisplay.h index dd2c600c..13f64882 100644 --- a/Boards/Simulator/Source/hal/SdlDisplay.h +++ b/Boards/Simulator/Source/hal/SdlDisplay.h @@ -2,23 +2,28 @@ #include "SdlTouch.h" #include +#include extern lv_disp_t* displayHandle; -class SdlDisplay : public tt::hal::Display { +class SdlDisplay final : public tt::hal::Display { public: + + std::string getName() const final { return "SDL Display"; } + std::string getDescription() const final { return ""; } + bool start() override { return displayHandle != nullptr; } bool stop() override { tt_crash("Not supported"); } - tt::hal::Touch* _Nullable createTouch() override { return dynamic_cast(new SdlTouch()); } + std::shared_ptr _Nullable createTouch() override { return std::make_shared(); } lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } }; -tt::hal::Display* createDisplay() { - return static_cast(new SdlDisplay()); +std::shared_ptr createDisplay() { + return std::make_shared(); } diff --git a/Boards/Simulator/Source/hal/SdlKeyboard.h b/Boards/Simulator/Source/hal/SdlKeyboard.h index b1c572c3..640a8c2d 100644 --- a/Boards/Simulator/Source/hal/SdlKeyboard.h +++ b/Boards/Simulator/Source/hal/SdlKeyboard.h @@ -3,11 +3,15 @@ #include #include -class SdlKeyboard : public tt::hal::Keyboard { +class SdlKeyboard final : public tt::hal::Keyboard { private: lv_indev_t* _Nullable handle = nullptr; 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 { handle = lv_sdl_keyboard_create(); return handle != nullptr; @@ -20,6 +24,6 @@ public: lv_indev_t* _Nullable getLvglIndev() override { return handle; } }; -tt::hal::Keyboard* createKeyboard() { - return static_cast(new SdlKeyboard()); +std::shared_ptr createKeyboard() { + return std::make_shared(); } diff --git a/Boards/Simulator/Source/hal/SdlTouch.h b/Boards/Simulator/Source/hal/SdlTouch.h index a6e3cc46..265cac69 100644 --- a/Boards/Simulator/Source/hal/SdlTouch.h +++ b/Boards/Simulator/Source/hal/SdlTouch.h @@ -3,11 +3,15 @@ #include #include -class SdlTouch : public tt::hal::Touch { +class SdlTouch final : public tt::hal::Touch { private: lv_indev_t* _Nullable handle = nullptr; 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 { handle = lv_sdl_mouse_create(); return handle != nullptr; diff --git a/Boards/Simulator/Source/hal/SimulatorPower.h b/Boards/Simulator/Source/hal/SimulatorPower.h index 15931960..5d7ca8fe 100644 --- a/Boards/Simulator/Source/hal/SimulatorPower.h +++ b/Boards/Simulator/Source/hal/SimulatorPower.h @@ -5,7 +5,7 @@ using namespace tt::hal; -class SimulatorPower : public Power { +class SimulatorPower final : public Power { bool allowedToCharge = false; @@ -14,6 +14,9 @@ public: SimulatorPower() = 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 getMetric(Power::MetricType type, Power::MetricData& data) override; diff --git a/Boards/Simulator/Source/hal/SimulatorSdCard.h b/Boards/Simulator/Source/hal/SimulatorSdCard.h index 43cc459c..f7ab367a 100644 --- a/Boards/Simulator/Source/hal/SimulatorSdCard.h +++ b/Boards/Simulator/Source/hal/SimulatorSdCard.h @@ -4,16 +4,24 @@ using namespace tt::hal; -class SimulatorSdCard : public SdCard { +class SimulatorSdCard final : public SdCard { + private: + State state; + public: + 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 { state = State::Mounted; return true; } + bool unmount() override { state = State::Unmounted; return true; diff --git a/Boards/UnPhone/Source/bq24295/Bq24295.h b/Boards/UnPhone/Source/bq24295/Bq24295.h index 822ee45a..0ed4ce7f 100644 --- a/Boards/UnPhone/Source/bq24295/Bq24295.h +++ b/Boards/UnPhone/Source/bq24295/Bq24295.h @@ -4,7 +4,7 @@ #define BQ24295_ADDRESS 0x6BU -class Bq24295 : I2cDevice { +class Bq24295 final : public tt::hal::i2c::I2cDevice { private: @@ -12,6 +12,10 @@ private: public: + std::string getName() const final { return "BQ24295"; } + + std::string getDescription() const final { return "I2C-controlled single cell USB charger"; } + enum class WatchDogTimer { Disabled = 0b000000, Enabled40s = 0b010000, diff --git a/Boards/UnPhone/Source/hal/UnPhoneDisplay.cpp b/Boards/UnPhone/Source/hal/UnPhoneDisplay.cpp index 9142318a..1cd1d552 100644 --- a/Boards/UnPhone/Source/hal/UnPhoneDisplay.cpp +++ b/Boards/UnPhone/Source/hal/UnPhoneDisplay.cpp @@ -30,8 +30,8 @@ bool UnPhoneDisplay::start() { lv_display_set_color_format(displayHandle, LV_COLOR_FORMAT_NATIVE); // TODO malloc to use SPIRAM - static auto* buffer1 = (uint8_t*)heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_SPIRAM); - static auto* buffer2 = (uint8_t*)heap_caps_malloc(BUFFER_SIZE, MALLOC_CAP_SPIRAM); + buffer1 = (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(buffer2 != nullptr); @@ -61,13 +61,18 @@ bool UnPhoneDisplay::stop() { lv_display_delete(displayHandle); displayHandle = nullptr; + heap_caps_free(buffer1); + heap_caps_free(buffer2); + buffer1 = nullptr; + buffer2 = nullptr; + return true; } -tt::hal::Touch* _Nullable UnPhoneDisplay::createTouch() { - return static_cast(new UnPhoneTouch()); +std::shared_ptr _Nullable UnPhoneDisplay::createTouch() { + return std::make_shared(); } -tt::hal::Display* createDisplay() { - return static_cast(new UnPhoneDisplay()); +std::shared_ptr createDisplay() { + return std::make_shared(); } diff --git a/Boards/UnPhone/Source/hal/UnPhoneDisplay.h b/Boards/UnPhone/Source/hal/UnPhoneDisplay.h index 1f28fca5..2810530c 100644 --- a/Boards/UnPhone/Source/hal/UnPhoneDisplay.h +++ b/Boards/UnPhone/Source/hal/UnPhoneDisplay.h @@ -11,16 +11,21 @@ class UnPhoneDisplay : public tt::hal::Display { private: lv_display_t* displayHandle = nullptr; + uint8_t* buffer1 = nullptr; + uint8_t* buffer2 = nullptr; public: + std::string getName() const final { return "HX8357"; } + std::string getDescription() const final { return "SPI display"; } + bool start() override; bool stop() override; - tt::hal::Touch* _Nullable createTouch() override; + std::shared_ptr _Nullable createTouch() override; lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; } }; -tt::hal::Display* createDisplay(); +std::shared_ptr createDisplay(); diff --git a/Boards/UnPhone/Source/hal/UnPhonePower.h b/Boards/UnPhone/Source/hal/UnPhonePower.h index c70dce01..84ac56cc 100644 --- a/Boards/UnPhone/Source/hal/UnPhonePower.h +++ b/Boards/UnPhone/Source/hal/UnPhonePower.h @@ -12,6 +12,9 @@ public: 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 getMetric(Power::MetricType type, Power::MetricData& data) override; diff --git a/Boards/UnPhone/Source/hal/UnPhoneTouch.h b/Boards/UnPhone/Source/hal/UnPhoneTouch.h index d275d44d..358af212 100644 --- a/Boards/UnPhone/Source/hal/UnPhoneTouch.h +++ b/Boards/UnPhone/Source/hal/UnPhoneTouch.h @@ -18,6 +18,9 @@ private: 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 stop() override; lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } diff --git a/Buildscripts/runtests.sh b/Buildscripts/runtests.sh index fe1c03b4..2f6592c5 100755 --- a/Buildscripts/runtests.sh +++ b/Buildscripts/runtests.sh @@ -3,4 +3,5 @@ cmake -S ./ -B build-sim cmake --build build-sim --target build-tests -j 14 build-sim/Tests/TactilityCore/TactilityCoreTests --exit +build-sim/Tests/TactilityHeadless/TactilityHeadlessTests --exit diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 5ff12e87..7fec59c6 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -1,5 +1,5 @@ # 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 - 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. diff --git a/Tactility/Source/app/display/Display.cpp b/Tactility/Source/app/display/Display.cpp index ff84f267..340f290f 100644 --- a/Tactility/Source/app/display/Display.cpp +++ b/Tactility/Source/app/display/Display.cpp @@ -22,8 +22,10 @@ static uint8_t gamma = 255; #define ROTATION_270 2 #define ROTATION_90 3 + static void onBacklightSliderEvent(lv_event_t* event) { auto* slider = static_cast(lv_event_get_target(event)); + auto* lvgl_display = lv_display_get_default(); assert(lvgl_display != nullptr); 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) { - auto* lvgl_display = lv_obj_get_display(widget); - 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 std::shared_ptr getHalDisplay() { + return hal::findFirstDevice(hal::Device::Type::Display); } 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 { lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + auto hal_display = getHalDisplay(); + assert(hal_display != nullptr); + lvgl::toolbar_create(parent, app); 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_set_width(gamma_slider, LV_PCT(50)); 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); - auto* hal_display = getHalDisplay(parent); - assert(hal_display != nullptr); - if (!hal_display->supportsBacklightDuty()) { lv_slider_set_value(brightness_slider, 255, LV_ANIM_OFF); lv_obj_add_state(brightness_slider, LV_STATE_DISABLED); diff --git a/Tactility/Source/app/power/Power.cpp b/Tactility/Source/app/power/Power.cpp index a7deec24..1a87201b 100644 --- a/Tactility/Source/app/power/Power.cpp +++ b/Tactility/Source/app/power/Power.cpp @@ -4,6 +4,7 @@ #include "Tactility/lvgl/Toolbar.h" #include "Tactility/service/loader/Loader.h" +#include #include #include #include @@ -33,7 +34,9 @@ class PowerApp : public App { private: Timer update_timer = Timer(Timer::Type::Periodic, &onTimer, nullptr); - std::shared_ptr power = getConfiguration()->hardware->power(); + + std::shared_ptr power; + lv_obj_t* enableLabel = nullptr; lv_obj_t* enableSwitch = nullptr; lv_obj_t* batteryVoltageLabel = nullptr; @@ -135,11 +138,19 @@ private: public: + void onCreate(AppContext& app) override { + power = hal::findFirstDevice(hal::Device::Type::Power); + } + void onShow(AppContext& app, lv_obj_t* parent) override { lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lvgl::toolbar_create(parent, app); + if (power == nullptr) { + return; + } + lv_obj_t* wrapper = lv_obj_create(parent); lv_obj_set_width(wrapper, LV_PCT(100)); lv_obj_set_style_border_width(wrapper, 0, 0); diff --git a/Tactility/Source/app/systeminfo/SystemInfo.cpp b/Tactility/Source/app/systeminfo/SystemInfo.cpp index 7f157347..f95b0916 100644 --- a/Tactility/Source/app/systeminfo/SystemInfo.cpp +++ b/Tactility/Source/app/systeminfo/SystemInfo.cpp @@ -1,6 +1,7 @@ #include "Tactility/lvgl/Toolbar.h" #include +#include #include #include @@ -42,17 +43,17 @@ static size_t getSpiTotal() { } 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_style_pad_all(container, 0, 0); lv_obj_set_style_border_width(container, 0, 0); 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_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); 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_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_obj_set_width(bottom_label, LV_PCT(100)); 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) { - 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; lv_label_set_text_fmt(label, "%s (%s)", name, getTaskState(task)); } static void addRtosTasks(lv_obj_t* parent) { 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; UBaseType_t actual = uxTaskGetSystemState(tasks, count, &totalRuntime); for (int i = 0; i < actual; ++i) { const TaskStatus_t& task = tasks[i]; - TT_LOG_I(TAG, "Task: %s", task.pcTaskName); addRtosTask(parent, task); } free(tasks); @@ -110,6 +110,18 @@ static void addRtosTasks(lv_obj_t* parent) { #endif +static void addDevice(lv_obj_t* parent, const std::shared_ptr& 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 { void onShow(AppContext& app, lv_obj_t* parent) override { @@ -117,16 +129,16 @@ class SystemInfoApp : public App { lvgl::toolbar_create(parent, app); // 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_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN); lv_obj_set_width(wrapper, LV_PCT(100)); lv_obj_set_flex_grow(wrapper, 1); // 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_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_size(memory_wrapper, LV_PCT(100), LV_SIZE_CONTENT); @@ -134,23 +146,29 @@ class SystemInfoApp : public App { addMemoryBar(memory_wrapper, "SPI", getSpiTotal() - getSpiFree(), getSpiTotal()); #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_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_size(tasks_wrapper, LV_PCT(100), LV_SIZE_CONTENT); addRtosTasks(tasks_wrapper); #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 // 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_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_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); #endif } diff --git a/Tactility/Source/lvgl/Init.cpp b/Tactility/Source/lvgl/Init.cpp index 18f89328..4525878e 100644 --- a/Tactility/Source/lvgl/Init.cpp +++ b/Tactility/Source/lvgl/Init.cpp @@ -11,14 +11,16 @@ namespace tt::lvgl { -#define TAG "lvglinit" +#define TAG "lvgl_init" -bool initDisplay(const hal::Configuration& config) { +static std::shared_ptr initDisplay(const hal::Configuration& config) { assert(config.createDisplay); - auto* display = config.createDisplay(); + auto display = config.createDisplay(); + assert(display != nullptr); + if (!display->start()) { TT_LOG_E(TAG, "Display start failed"); - return false; + return nullptr; } 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 // this is a check for when we upgrade esp_lvgl_port and forget to modify it again 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(); if (rotation != lv_disp_get_rotation(lv_disp_get_default())) { lv_disp_set_rotation(lv_disp_get_default(), static_cast(rotation)); } - return true; + return display; } -bool initTouch(hal::Display* display, hal::Touch* touch) { +static bool initTouch(const std::shared_ptr& display, const std::shared_ptr& touch) { TT_LOG_I(TAG, "Touch init"); assert(display); 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& display, const std::shared_ptr& keyboard) { TT_LOG_I(TAG, "Keyboard init"); assert(display); assert(keyboard); if (keyboard->isAttached()) { if (keyboard->start(display->getLvglDisplay())) { 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_LOG_I(TAG, "Keyboard started"); return true; @@ -85,20 +87,24 @@ void init(const hal::Configuration& config) { return; } - if (!initDisplay(config)) { + auto display = initDisplay(config); + if (display == nullptr) { return; } + hal::registerDevice(display); - hal::Display* display = getDisplay(); - - hal::Touch* touch = display->createTouch(); + auto touch = display->createTouch(); if (touch != nullptr) { + hal::registerDevice(touch); initTouch(display, touch); } if (config.createKeyboard) { - hal::Keyboard* keyboard = config.createKeyboard(); - initKeyboard(display, keyboard); + auto keyboard = config.createKeyboard(); + if (keyboard != nullptr) { + hal::registerDevice(keyboard); + initKeyboard(display, keyboard); + } } TT_LOG_I(TAG, "Finished"); diff --git a/TactilityCore/Source/Lockable.cpp b/TactilityCore/Source/Lockable.cpp index edcaedc9..643e369b 100644 --- a/TactilityCore/Source/Lockable.cpp +++ b/TactilityCore/Source/Lockable.cpp @@ -3,8 +3,7 @@ namespace tt { std::unique_ptr Lockable::scoped() const { - auto* scoped = new ScopedLockableUsage(*this); - return std::unique_ptr(scoped); + return std::make_unique(*this); } } diff --git a/TactilityHeadless/Include/Tactility/hal/Configuration.h b/TactilityHeadless/Include/Tactility/hal/Configuration.h index ec6387df..1a58dfab 100644 --- a/TactilityHeadless/Include/Tactility/hal/Configuration.h +++ b/TactilityHeadless/Include/Tactility/hal/Configuration.h @@ -12,8 +12,8 @@ typedef bool (*InitLvgl)(); class Display; class Keyboard; -typedef Display* (*CreateDisplay)(); -typedef Keyboard* (*CreateKeyboard)(); +typedef std::shared_ptr (*CreateDisplay)(); +typedef std::shared_ptr (*CreateKeyboard)(); typedef std::shared_ptr (*CreatePower)(); struct Configuration { diff --git a/TactilityHeadless/Include/Tactility/hal/Device.h b/TactilityHeadless/Include/Tactility/hal/Device.h new file mode 100644 index 00000000..aa0beec6 --- /dev/null +++ b/TactilityHeadless/Include/Tactility/hal/Device.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include + +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); + +/** Remove a device from the registry. */ +void deregisterDevice(const std::shared_ptr& device); + +/** Find a device in the registry by its name. */ +std::shared_ptr _Nullable findDevice(std::string name); + +/** Find a device in the registry by its identifier. */ +std::shared_ptr _Nullable findDevice(Device::Id id); + +/** Find 0, 1 or more devices in the registry by type. */ +std::vector> findDevices(Device::Type type); + +/** Get a copy of the entire device registry in its current state. */ +std::vector> getDevices(); + +template +std::shared_ptr findFirstDevice(Device::Type type) { + auto devices = findDevices(type); + if (devices.empty()) { + return {}; + } else { + auto& first = devices[0]; + return std::static_pointer_cast(first); + } +} + +} diff --git a/TactilityHeadless/Include/Tactility/hal/Display.h b/TactilityHeadless/Include/Tactility/hal/Display.h index 6bdac615..bf12b69c 100644 --- a/TactilityHeadless/Include/Tactility/hal/Display.h +++ b/TactilityHeadless/Include/Tactility/hal/Display.h @@ -1,13 +1,19 @@ #pragma once -#include "lvgl.h" +#include "Device.h" + +#include namespace tt::hal { class Touch; -class Display { +class Display : public Device { + public: + + Type getType() const override { return Type::Display; } + virtual bool start() = 0; virtual bool stop() = 0; @@ -15,7 +21,7 @@ public: virtual bool isPoweredOn() const { return true; } virtual bool supportsPowerControl() const { return false; } - virtual Touch* _Nullable createTouch() = 0; + virtual std::shared_ptr _Nullable createTouch() = 0; /** Set a value in the range [0, 255] */ virtual void setBacklightDuty(uint8_t backlightDuty) { /* NO-OP */ } diff --git a/TactilityHeadless/Include/Tactility/hal/Keyboard.h b/TactilityHeadless/Include/Tactility/hal/Keyboard.h index 8cab2478..372a14d4 100644 --- a/TactilityHeadless/Include/Tactility/hal/Keyboard.h +++ b/TactilityHeadless/Include/Tactility/hal/Keyboard.h @@ -1,13 +1,19 @@ #pragma once -#include "lvgl.h" +#include "Device.h" + +#include namespace tt::hal { class Display; -class Keyboard { +class Keyboard : public Device { + public: + + Type getType() const override { return Type::Keyboard; } + virtual bool start(lv_display_t* display) = 0; virtual bool stop() = 0; virtual bool isAttached() const = 0; diff --git a/TactilityHeadless/Include/Tactility/hal/Power.h b/TactilityHeadless/Include/Tactility/hal/Power.h index fa048847..c0ab590b 100644 --- a/TactilityHeadless/Include/Tactility/hal/Power.h +++ b/TactilityHeadless/Include/Tactility/hal/Power.h @@ -1,15 +1,18 @@ #pragma once +#include "Device.h" #include namespace tt::hal { -class Power{ +class Power : public Device { public: Power() = default; - virtual ~Power() = default; + ~Power() override = default; + + Type getType() const override { return Type::Power; } enum class MetricType { IsCharging, // bool diff --git a/TactilityHeadless/Include/Tactility/hal/SdCard.h b/TactilityHeadless/Include/Tactility/hal/SdCard.h index 80375833..cfbb44e3 100644 --- a/TactilityHeadless/Include/Tactility/hal/SdCard.h +++ b/TactilityHeadless/Include/Tactility/hal/SdCard.h @@ -1,5 +1,7 @@ #pragma once +#include "Device.h" + #include namespace tt::hal { @@ -7,8 +9,10 @@ namespace tt::hal { #define TT_SDCARD_MOUNT_NAME "sdcard" #define TT_SDCARD_MOUNT_POINT "/sdcard" -class SdCard { +class SdCard : public Device { + public: + enum class State { Mounted, Unmounted, @@ -22,11 +26,15 @@ public: }; private: + MountBehaviour mountBehaviour; public: + 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 unmount() = 0; diff --git a/TactilityHeadless/Include/Tactility/hal/SpiSdCard.h b/TactilityHeadless/Include/Tactility/hal/SpiSdCard.h index 5e7dca54..780b1ca4 100644 --- a/TactilityHeadless/Include/Tactility/hal/SpiSdCard.h +++ b/TactilityHeadless/Include/Tactility/hal/SpiSdCard.h @@ -70,6 +70,9 @@ public: 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 unmount() override; State getState() const override; diff --git a/TactilityHeadless/Include/Tactility/hal/Touch.h b/TactilityHeadless/Include/Tactility/hal/Touch.h index 9ce0decb..2677f36b 100644 --- a/TactilityHeadless/Include/Tactility/hal/Touch.h +++ b/TactilityHeadless/Include/Tactility/hal/Touch.h @@ -1,14 +1,19 @@ #pragma once -#include "lvgl.h" +#include "Device.h" + +#include namespace tt::hal { class Display; -class Touch { +class Touch : public Device { public: + + Type getType() const override { return Type::SdCard; } + virtual bool start(lv_display_t* display) = 0; virtual bool stop() = 0; diff --git a/TactilityHeadless/Include/Tactility/hal/i2c/I2cDevice.h b/TactilityHeadless/Include/Tactility/hal/i2c/I2cDevice.h index 6ca94064..0c1d42a6 100644 --- a/TactilityHeadless/Include/Tactility/hal/i2c/I2cDevice.h +++ b/TactilityHeadless/Include/Tactility/hal/i2c/I2cDevice.h @@ -1,6 +1,9 @@ #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. @@ -8,7 +11,7 @@ * * All read and write calls are thread-safe. */ -class I2cDevice { +class I2cDevice : public Device { protected: @@ -17,6 +20,8 @@ protected: 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 writeRegister8(uint8_t reg, uint8_t value) const; bool readRegister12(uint8_t reg, float& out) const; @@ -26,7 +31,10 @@ protected: bool bitOff(uint8_t reg, uint8_t bitmask) const; 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); } + public: explicit I2cDevice(i2c_port_t port, uint32_t address) : port(port), address(address) {} }; + +} diff --git a/TactilityHeadless/Source/hal/Device.cpp b/TactilityHeadless/Source/hal/Device.cpp new file mode 100644 index 00000000..77897f31 --- /dev/null +++ b/TactilityHeadless/Source/hal/Device.cpp @@ -0,0 +1,97 @@ +#include "Tactility/hal/Device.h" + +#include +#include + +namespace tt::hal { + +std::vector> devices; +Mutex mutex = Mutex(Mutex::Type::Recursive); +static Device::Id nextId = 0; + +#define TAG "devices" + +Device::Device() : id(nextId++) {} + +template +auto toVector(RangeType&& range) { + auto view = range | std::views::common; + return std::vector(view.begin(), view.end()); +} + +void registerDevice(const std::shared_ptr& 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) { + 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> findDevices(const std::function&)>& 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 _Nullable findDevice(const std::function&)>& 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 _Nullable findDevice(std::string name) { + return findDevice([&name](auto& device){ + return device->getName() == name; + }); +} + +std::shared_ptr _Nullable findDevice(Device::Id id) { + return findDevice([id](auto& device){ + return device->getId() == id; + }); +} + +std::vector> findDevices(Device::Type type) { + return findDevices([type](auto& device) { + return device->getType() == type; + }); +} + +std::vector> getDevices() { + return devices; +} + +} diff --git a/TactilityHeadless/Source/hal/Hal.cpp b/TactilityHeadless/Source/hal/Hal.cpp index 6c853e69..401d11bb 100644 --- a/TactilityHeadless/Source/hal/Hal.cpp +++ b/TactilityHeadless/Source/hal/Hal.cpp @@ -1,3 +1,4 @@ +#include "Tactility/hal/Device.h" #include "Tactility/hal/Hal_i.h" #include "Tactility/hal/i2c/I2c.h" @@ -28,6 +29,12 @@ void init(const Configuration& configuration) { if (!configuration.sdcard->mount(TT_SDCARD_MOUNT_POINT)) { TT_LOG_W(TAG, "SD card mount failed (init can continue)"); } + hal::registerDevice(configuration.sdcard); + } + + if (configuration.power != nullptr) { + std::shared_ptr power = configuration.power(); + hal::registerDevice(power); } kernel::systemEventPublish(kernel::SystemEvent::BootInitHalEnd); diff --git a/TactilityHeadless/Source/hal/i2c/I2cDevice.cpp b/TactilityHeadless/Source/hal/i2c/I2cDevice.cpp index bd1be457..4891e471 100644 --- a/TactilityHeadless/Source/hal/i2c/I2cDevice.cpp +++ b/TactilityHeadless/Source/hal/i2c/I2cDevice.cpp @@ -2,6 +2,8 @@ #include +namespace tt::hal::i2c { + bool I2cDevice::readRegister12(uint8_t reg, float& out) const { std::uint8_t data[2] = {0}; 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; } } + +} // namespace diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index d53c5025..db15e22e 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -4,6 +4,8 @@ set(DOCTESTINC ${PROJECT_SOURCE_DIR}/Include) enable_testing() add_subdirectory(TactilityCore) +add_subdirectory(TactilityHeadless) add_custom_target(build-tests) add_dependencies(build-tests TactilityCoreTests) +add_dependencies(build-tests TactilityHeadlessTests) diff --git a/Tests/TactilityHeadless/CMakeLists.txt b/Tests/TactilityHeadless/CMakeLists.txt new file mode 100644 index 00000000..5116706a --- /dev/null +++ b/Tests/TactilityHeadless/CMakeLists.txt @@ -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 +) diff --git a/Tests/TactilityHeadless/HalDeviceTest.cpp b/Tests/TactilityHeadless/HalDeviceTest.cpp new file mode 100644 index 00000000..70243947 --- /dev/null +++ b/Tests/TactilityHeadless/HalDeviceTest.cpp @@ -0,0 +1,98 @@ +#include "doctest.h" +#include + +#include + +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 device; + +public: + + explicit DeviceAutoRegistration(std::shared_ptr 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(); + + // 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(); + 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(); + 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(hal::Device::Type::Display); + CHECK_EQ(unexpected_display, nullptr); + + auto device = std::make_shared(hal::Device::Type::Display, "DisplayMock", ""); + DeviceAutoRegistration auto_registration(device); + + auto found_device = hal::findFirstDevice(hal::Device::Type::Display); + CHECK_NE(found_device, nullptr); + CHECK_EQ(found_device->getId(), device->getId()); +} diff --git a/Tests/TactilityHeadless/Main.cpp b/Tests/TactilityHeadless/Main.cpp new file mode 100644 index 00000000..cfa29e91 --- /dev/null +++ b/Tests/TactilityHeadless/Main.cpp @@ -0,0 +1,51 @@ +#define DOCTEST_CONFIG_IMPLEMENT +#include "doctest.h" +#include + +#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(); +}