mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-04-18 09:25:06 +00:00
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:
parent
c87200a80d
commit
cff0605b0a
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@ -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
|
||||
|
||||
@ -205,10 +205,10 @@ void YellowDisplay::setGammaCurve(uint8_t index) {
|
||||
}
|
||||
}
|
||||
|
||||
tt::hal::Touch* _Nullable YellowDisplay::createTouch() {
|
||||
return static_cast<tt::hal::Touch*>(new YellowTouch());
|
||||
std::shared_ptr<tt::hal::Touch> _Nullable YellowDisplay::createTouch() {
|
||||
return std::make_shared<YellowTouch>();
|
||||
}
|
||||
|
||||
tt::hal::Display* createDisplay() {
|
||||
return static_cast<tt::hal::Display*>(new YellowDisplay());
|
||||
std::shared_ptr<tt::hal::Display> createDisplay() {
|
||||
return std::make_shared<YellowDisplay>();
|
||||
}
|
||||
|
||||
@ -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<tt::hal::Touch> _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<tt::hal::Display> createDisplay();
|
||||
|
||||
@ -5,12 +5,20 @@
|
||||
#include <esp_lcd_touch.h>
|
||||
|
||||
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; }
|
||||
|
||||
@ -191,8 +191,8 @@ void TdeckDisplay::setPowerOn(bool turnOn) {
|
||||
}
|
||||
}
|
||||
|
||||
tt::hal::Touch* _Nullable TdeckDisplay::createTouch() {
|
||||
return static_cast<tt::hal::Touch*>(new TdeckTouch());
|
||||
std::shared_ptr<tt::hal::Touch> _Nullable TdeckDisplay::createTouch() {
|
||||
return std::make_shared<TdeckTouch>();
|
||||
}
|
||||
|
||||
void TdeckDisplay::setBacklightDuty(uint8_t backlightDuty) {
|
||||
@ -233,6 +233,6 @@ void TdeckDisplay::setGammaCurve(uint8_t index) {
|
||||
}
|
||||
}
|
||||
|
||||
tt::hal::Display* createDisplay() {
|
||||
return static_cast<tt::hal::Display*>(new TdeckDisplay());
|
||||
std::shared_ptr<tt::hal::Display> createDisplay() {
|
||||
return std::make_shared<TdeckDisplay>();
|
||||
}
|
||||
|
||||
@ -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<tt::hal::Touch> _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<tt::hal::Display> createDisplay();
|
||||
|
||||
@ -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<tt::hal::Keyboard*>(new TdeckKeyboard());
|
||||
std::shared_ptr<tt::hal::Keyboard> createKeyboard() {
|
||||
return std::make_shared<TdeckKeyboard>();
|
||||
}
|
||||
|
||||
@ -6,13 +6,20 @@
|
||||
#include <esp_lcd_touch.h>
|
||||
|
||||
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<tt::hal::Keyboard> createKeyboard();
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -6,12 +6,19 @@
|
||||
#include <esp_lcd_touch.h>
|
||||
|
||||
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; }
|
||||
|
||||
@ -153,10 +153,10 @@ void Core2Display::setGammaCurve(uint8_t index) {
|
||||
}
|
||||
}
|
||||
|
||||
tt::hal::Touch* _Nullable Core2Display::createTouch() {
|
||||
return static_cast<tt::hal::Touch*>(new Core2Touch());
|
||||
std::shared_ptr<tt::hal::Touch> _Nullable Core2Display::createTouch() {
|
||||
return std::make_shared<Core2Touch>();
|
||||
}
|
||||
|
||||
tt::hal::Display* createDisplay() {
|
||||
return static_cast<tt::hal::Display*>(new Core2Display());
|
||||
std::shared_ptr<tt::hal::Display> createDisplay() {
|
||||
return std::make_shared<Core2Display>();
|
||||
}
|
||||
|
||||
@ -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<tt::hal::Touch> _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<tt::hal::Display> createDisplay();
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -181,10 +181,10 @@ void CoreS3Display::setBacklightDuty(uint8_t backlightDuty) {
|
||||
}
|
||||
}
|
||||
|
||||
tt::hal::Touch* _Nullable CoreS3Display::createTouch() {
|
||||
return static_cast<tt::hal::Touch*>(new CoreS3Touch());
|
||||
std::shared_ptr<tt::hal::Touch> _Nullable CoreS3Display::createTouch() {
|
||||
return std::make_shared<CoreS3Touch>();
|
||||
}
|
||||
|
||||
tt::hal::Display* createDisplay() {
|
||||
return static_cast<tt::hal::Display*>(new CoreS3Display());
|
||||
std::shared_ptr<tt::hal::Display> createDisplay() {
|
||||
return std::make_shared<CoreS3Display>();
|
||||
}
|
||||
|
||||
@ -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<tt::hal::Touch> _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<tt::hal::Display> createDisplay();
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
#include "CoreS3Power.h"
|
||||
#include <Tactility/TactilityCore.h>
|
||||
|
||||
#define TAG "core2_power"
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -5,12 +5,20 @@
|
||||
#include <esp_lcd_touch.h>
|
||||
|
||||
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; }
|
||||
|
||||
@ -2,23 +2,28 @@
|
||||
|
||||
#include "SdlTouch.h"
|
||||
#include <Tactility/hal/Display.h>
|
||||
#include <memory>
|
||||
|
||||
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<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; }
|
||||
};
|
||||
|
||||
tt::hal::Display* createDisplay() {
|
||||
return static_cast<tt::hal::Display*>(new SdlDisplay());
|
||||
std::shared_ptr<tt::hal::Display> createDisplay() {
|
||||
return std::make_shared<SdlDisplay>();
|
||||
}
|
||||
|
||||
|
||||
@ -3,11 +3,15 @@
|
||||
#include <Tactility/hal/Keyboard.h>
|
||||
#include <Tactility/TactilityCore.h>
|
||||
|
||||
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<tt::hal::Keyboard*>(new SdlKeyboard());
|
||||
std::shared_ptr<tt::hal::Keyboard> createKeyboard() {
|
||||
return std::make_shared<SdlKeyboard>();
|
||||
}
|
||||
|
||||
@ -3,11 +3,15 @@
|
||||
#include <Tactility/hal/Touch.h>
|
||||
#include <Tactility/TactilityCore.h>
|
||||
|
||||
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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<tt::hal::Touch*>(new UnPhoneTouch());
|
||||
std::shared_ptr<tt::hal::Touch> _Nullable UnPhoneDisplay::createTouch() {
|
||||
return std::make_shared<UnPhoneTouch>();
|
||||
}
|
||||
|
||||
tt::hal::Display* createDisplay() {
|
||||
return static_cast<tt::hal::Display*>(new UnPhoneDisplay());
|
||||
std::shared_ptr<tt::hal::Display> createDisplay() {
|
||||
return std::make_shared<UnPhoneDisplay>();
|
||||
}
|
||||
|
||||
@ -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<tt::hal::Touch> _Nullable createTouch() override;
|
||||
|
||||
lv_display_t* _Nullable getLvglDisplay() const override { return displayHandle; }
|
||||
};
|
||||
|
||||
tt::hal::Display* createDisplay();
|
||||
std::shared_ptr<tt::hal::Display> createDisplay();
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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_obj_t*>(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<tt::hal::Display> getHalDisplay() {
|
||||
return hal::findFirstDevice<hal::Display>(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);
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include "Tactility/lvgl/Toolbar.h"
|
||||
#include "Tactility/service/loader/Loader.h"
|
||||
|
||||
#include <Tactility/hal/Device.h>
|
||||
#include <Tactility/Assets.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/Timer.h>
|
||||
@ -33,7 +34,9 @@ class PowerApp : public App {
|
||||
private:
|
||||
|
||||
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* enableSwitch = nullptr;
|
||||
lv_obj_t* batteryVoltageLabel = nullptr;
|
||||
@ -135,11 +138,19 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
void onCreate(AppContext& app) override {
|
||||
power = hal::findFirstDevice<hal::Power>(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);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "Tactility/lvgl/Toolbar.h"
|
||||
|
||||
#include <Tactility/Assets.h>
|
||||
#include <Tactility/hal/Device.h>
|
||||
#include <Tactility/Tactility.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) {
|
||||
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<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 {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -11,14 +11,16 @@
|
||||
|
||||
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);
|
||||
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<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");
|
||||
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<hal::Display>& display, const std::shared_ptr<hal::Keyboard>& 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");
|
||||
|
||||
@ -3,8 +3,7 @@
|
||||
namespace tt {
|
||||
|
||||
std::unique_ptr<ScopedLockableUsage> Lockable::scoped() const {
|
||||
auto* scoped = new ScopedLockableUsage(*this);
|
||||
return std::unique_ptr<ScopedLockableUsage>(scoped);
|
||||
return std::make_unique<ScopedLockableUsage>(*this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -12,8 +12,8 @@ typedef bool (*InitLvgl)();
|
||||
|
||||
class Display;
|
||||
class Keyboard;
|
||||
typedef Display* (*CreateDisplay)();
|
||||
typedef Keyboard* (*CreateKeyboard)();
|
||||
typedef std::shared_ptr<Display> (*CreateDisplay)();
|
||||
typedef std::shared_ptr<Keyboard> (*CreateKeyboard)();
|
||||
typedef std::shared_ptr<Power> (*CreatePower)();
|
||||
|
||||
struct Configuration {
|
||||
|
||||
83
TactilityHeadless/Include/Tactility/hal/Device.h
Normal file
83
TactilityHeadless/Include/Tactility/hal/Device.h
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,13 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "lvgl.h"
|
||||
#include "Device.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
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<Touch> _Nullable createTouch() = 0;
|
||||
|
||||
/** Set a value in the range [0, 255] */
|
||||
virtual void setBacklightDuty(uint8_t backlightDuty) { /* NO-OP */ }
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "lvgl.h"
|
||||
#include "Device.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
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;
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "Device.h"
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Device.h"
|
||||
|
||||
#include <Tactility/TactilityCore.h>
|
||||
|
||||
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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "lvgl.h"
|
||||
#include "Device.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@ -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) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
97
TactilityHeadless/Source/hal/Device.cpp
Normal file
97
TactilityHeadless/Source/hal/Device.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<tt::hal::Power> power = configuration.power();
|
||||
hal::registerDevice(power);
|
||||
}
|
||||
|
||||
kernel::systemEventPublish(kernel::SystemEvent::BootInitHalEnd);
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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
|
||||
|
||||
@ -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)
|
||||
|
||||
27
Tests/TactilityHeadless/CMakeLists.txt
Normal file
27
Tests/TactilityHeadless/CMakeLists.txt
Normal 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
|
||||
)
|
||||
98
Tests/TactilityHeadless/HalDeviceTest.cpp
Normal file
98
Tests/TactilityHeadless/HalDeviceTest.cpp
Normal 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());
|
||||
}
|
||||
51
Tests/TactilityHeadless/Main.cpp
Normal file
51
Tests/TactilityHeadless/Main.cpp
Normal 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();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user