Implement device management (#199)

- Added `tt::hal::Device` and functions (de)register devices and search for them.
- Refactored apps: `Power` and `Display` settings apps now use the device API to find devices.
- Implemented the new API for all existing drivers for all devices, including the simulator.
- Updated HAL Configuration to return `std::shared_ptr` instead of raw pointers.
- Added test project for headless tests and implemented tests for the new code.
This commit is contained in:
Ken Van Hoeylandt
2025-02-02 15:16:51 +01:00
committed by GitHub
parent c87200a80d
commit cff0605b0a
54 changed files with 655 additions and 109 deletions
@@ -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 {
@@ -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
View File
@@ -0,0 +1,97 @@
#include "Tactility/hal/Device.h"
#include <ranges>
#include <Tactility/Mutex.h>
namespace tt::hal {
std::vector<std::shared_ptr<Device>> devices;
Mutex mutex = Mutex(Mutex::Type::Recursive);
static Device::Id nextId = 0;
#define TAG "devices"
Device::Device() : id(nextId++) {}
template <std::ranges::range RangeType>
auto toVector(RangeType&& range) {
auto view = range | std::views::common;
return std::vector(view.begin(), view.end());
}
void registerDevice(const std::shared_ptr<Device>& device) {
auto scoped_mutex = mutex.scoped();
if (scoped_mutex->lock()) {
if (findDevice(device->getId()) == nullptr) {
devices.push_back(device);
TT_LOG_I(TAG, "Registered %s with id %lu", device->getName().c_str(), device->getId());
} else {
TT_LOG_W(TAG, "Device %s with id %lu was already registered", device->getName().c_str(), device->getId());
}
}
}
void deregisterDevice(const std::shared_ptr<Device>& device) {
auto scoped_mutex = mutex.scoped();
auto id_to_remove = device->getId();
if (scoped_mutex->lock()) {
auto remove_iterator = std::remove_if(devices.begin(), devices.end(), [id_to_remove](const auto& device) {
return device->getId() == id_to_remove;
});
if (remove_iterator != devices.end()) {
TT_LOG_I(TAG, "Deregistering %s with id %lu", device->getName().c_str(), device->getId());
devices.erase(remove_iterator);
} else {
TT_LOG_W(TAG, "Deregistering %s with id %lu failed: not found", device->getName().c_str(), device->getId());
}
}
}
std::vector<std::shared_ptr<Device>> findDevices(const std::function<bool(const std::shared_ptr<Device>&)>& filterFunction) {
auto scoped_mutex = mutex.scoped();
if (scoped_mutex->lock()) {
auto devices_view = devices | std::views::filter([&filterFunction](auto& device) {
return filterFunction(device);
});
return toVector(devices_view);
}
return {};
}
static std::shared_ptr<Device> _Nullable findDevice(const std::function<bool(const std::shared_ptr<Device>&)>& filterFunction) {
auto scoped_mutex = mutex.scoped();
if (scoped_mutex->lock()) {
auto result_set = devices | std::views::filter([&filterFunction](auto& device) {
return filterFunction(device);
});
if (!result_set.empty()) {
return result_set.front();
}
}
return nullptr;
}
std::shared_ptr<Device> _Nullable findDevice(std::string name) {
return findDevice([&name](auto& device){
return device->getName() == name;
});
}
std::shared_ptr<Device> _Nullable findDevice(Device::Id id) {
return findDevice([id](auto& device){
return device->getId() == id;
});
}
std::vector<std::shared_ptr<Device>> findDevices(Device::Type type) {
return findDevices([type](auto& device) {
return device->getType() == type;
});
}
std::vector<std::shared_ptr<Device>> getDevices() {
return devices;
}
}
+7
View File
@@ -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