Grove driver, I2C migrations, bugfixes and docs (#532)

This commit is contained in:
Ken Van Hoeylandt
2026-06-19 00:52:31 +02:00
committed by GitHub
parent 8dabda2b5b
commit a35c88c8fd
74 changed files with 937 additions and 483 deletions
+7 -2
View File
@@ -13,7 +13,6 @@
#include <Tactility/LogMessages.h>
#include <Tactility/Logger.h>
#include <Tactility/MountPoints.h>
#include <Tactility/Paths.h>
#include <Tactility/app/AppManifestParsing.h>
#include <Tactility/app/AppRegistration.h>
#include <Tactility/file/File.h>
@@ -27,6 +26,7 @@
#include <Tactility/settings/TimePrivate.h>
#include <tactility/concurrent/thread.h>
#include <tactility/drivers/grove.h>
#include <tactility/drivers/uart_controller.h>
#include <tactility/filesystem/file_system.h>
#include <tactility/hal_device_module.h>
@@ -94,6 +94,7 @@ namespace app {
namespace files { extern const AppManifest manifest; }
namespace fileselection { extern const AppManifest manifest; }
namespace gpssettings { extern const AppManifest manifest; }
namespace grovesettings { extern const AppManifest manifest; }
namespace i2cscanner { extern const AppManifest manifest; }
namespace imageviewer { extern const AppManifest manifest; }
namespace inputdialog { extern const AppManifest manifest; }
@@ -187,7 +188,11 @@ static void registerInternalApps() {
addAppManifest(app::chat::manifest);
#endif
if (device_exists_of_type(&UART_CONTROLLER_TYPE)) {
if (device_exists_of_type(&GROVE_TYPE)) {
addAppManifest(app::grovesettings::manifest);
}
if (device_exists_of_type(&UART_CONTROLLER_TYPE) || device_exists_of_type(&GROVE_TYPE)) {
addAppManifest(app::addgps::manifest);
addAppManifest(app::gpssettings::manifest);
}
@@ -0,0 +1,79 @@
#include <vector>
#include <lvgl.h>
#include <tactility/device.h>
#include <tactility/drivers/grove.h>
#include <tactility/lvgl_icon_shared.h>
#include <Tactility/Tactility.h>
#include <Tactility/lvgl/Toolbar.h>
namespace tt::app::grovesettings {
class GroveSettingsApp final : public App {
std::vector<::Device*> devices;
void collectDevices() {
devices.clear();
device_for_each_of_type(&GROVE_TYPE, &devices, [](auto* device, auto* context) {
auto* vec = static_cast<std::vector<::Device*>*>(context);
vec->push_back(device);
return true;
});
}
static void onModeChanged(lv_event_t* e) {
auto* device = static_cast<::Device*>(lv_event_get_user_data(e));
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(e));
auto mode = static_cast<GroveMode>(lv_dropdown_get_selected(dropdown));
grove_set_mode(device, mode);
}
public:
void onShow(AppContext& app, lv_obj_t* parent) override {
collectDevices();
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
lvgl::toolbar_create(parent, app);
auto* main_wrapper = lv_obj_create(parent);
lv_obj_set_flex_flow(main_wrapper, LV_FLEX_FLOW_COLUMN);
lv_obj_set_width(main_wrapper, LV_PCT(100));
lv_obj_set_flex_grow(main_wrapper, 1);
for (auto* device : devices) {
auto* row = lv_obj_create(main_wrapper);
lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(row, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(row, 0, LV_STATE_DEFAULT);
auto* label = lv_label_create(row);
lv_label_set_text(label, device->name);
lv_obj_align(label, LV_ALIGN_LEFT_MID, 0, 0);
auto* dropdown = lv_dropdown_create(row);
lv_dropdown_set_options(dropdown, "Disabled\nUART\nI2C");
lv_obj_align(dropdown, LV_ALIGN_RIGHT_MID, 0, 0);
GroveMode current = GROVE_MODE_DISABLED;
grove_get_mode(device, &current);
lv_dropdown_set_selected(dropdown, static_cast<uint32_t>(current));
lv_obj_add_event_cb(dropdown, onModeChanged, LV_EVENT_VALUE_CHANGED, device);
}
}
};
extern const AppManifest manifest = {
.appId = "GroveSettings",
.appName = "Grove",
.appIcon = LVGL_ICON_SHARED_CABLE,
.appCategory = Category::Settings,
.createApp = create<GroveSettingsApp>
};
}
+26 -20
View File
@@ -1,7 +1,8 @@
#include "Tactility/app/i2cscanner/I2cHelpers.h"
#include <Tactility/StringUtils.h>
#include <Tactility/hal/i2c/I2c.h>
#include <tactility/drivers/i2c_controller.h>
#include <iomanip>
#include <vector>
@@ -19,31 +20,36 @@ std::string getAddressText(uint8_t address) {
std::string getPortNamesForDropdown() {
std::vector<std::string> config_names;
for (int port = 0; port < I2C_NUM_MAX; ++port) {
auto native_port = static_cast<i2c_port_t>(port);
if (hal::i2c::isStarted(native_port)) {
auto* name = hal::i2c::getName(native_port);
if (name != nullptr) {
config_names.push_back(name);
}
device_for_each_of_type(&I2C_CONTROLLER_TYPE, &config_names, [](auto* device, auto* context) {
auto* names = static_cast<std::vector<std::string>*>(context);
if (device_is_ready(device)) {
names->push_back(device->name);
}
}
return true;
});
return string::join(config_names, "\n");
}
bool getActivePortAtIndex(int32_t index, int32_t& out) {
int current_index = -1;
for (int port = 0; port < I2C_NUM_MAX; ++port) {
auto native_port = static_cast<i2c_port_t>(port);
if (hal::i2c::isStarted(native_port)) {
current_index++;
if (current_index == index) {
out = port;
return true;
bool getActivePortAtIndex(int32_t index, struct Device** out) {
struct Context {
int32_t targetIndex;
int32_t currentIndex;
struct Device* foundDevice;
} context = { index, 0, nullptr };
device_for_each_of_type(&I2C_CONTROLLER_TYPE, &context, [](auto* device, auto* ctx) {
auto* c = static_cast<Context*>(ctx);
if (device_is_ready(device)) {
if (c->currentIndex == c->targetIndex) {
c->foundDevice = device;
return false;
}
c->currentIndex++;
}
}
return false;
return true;
});
*out = context.foundDevice;
return context.foundDevice != nullptr;
}
}
+16 -34
View File
@@ -1,9 +1,8 @@
#include <Tactility/app/i2cscanner/I2cScannerPrivate.h>
#include <Tactility/app/i2cscanner/I2cHelpers.h>
#include <Tactility/Assets.h>
#include <Tactility/app/AppContext.h>
#include <Tactility/hal/i2c/I2cDevice.h>
#include <tactility/drivers/i2c_controller.h>
#include <Tactility/Logger.h>
#include <Tactility/LogMessages.h>
#include <Tactility/lvgl/LvglSync.h>
@@ -33,7 +32,7 @@ class I2cScannerApp final : public App {
std::unique_ptr<Timer> scanTimer = nullptr;
// State
ScanState scanState = ScanStateInitial;
i2c_port_t port = I2C_NUM_0;
struct Device* portDevice = nullptr;
std::vector<uint8_t> scannedAddresses;
// Widgets
lv_obj_t* scanButtonLabelWidget = nullptr;
@@ -47,14 +46,13 @@ class I2cScannerApp final : public App {
static void onSelectBusCallback(lv_event_t* event);
static void onPressScanCallback(lv_event_t* event);
static void onScanTimerCallback();
void onSelectBus(lv_event_t* event);
void onPressScan(lv_event_t* event);
void onScanTimer();
bool shouldStopScanTimer();
bool getPort(i2c_port_t* outPort);
bool getPort(struct Device** outPort);
bool addAddressToList(uint8_t address);
bool hasScanThread();
void startScanning();
@@ -140,8 +138,10 @@ void I2cScannerApp::onShow(AppContext& app, lv_obj_t* parent) {
lv_obj_add_flag(scan_list, LV_OBJ_FLAG_HIDDEN);
scanListWidget = scan_list;
int32_t first_port;
if (getActivePortAtIndex(0, first_port)) {
struct Device* dummy;
if (getActivePortAtIndex(selected_bus, &dummy)) {
selectBus(selected_bus);
} else if (getActivePortAtIndex(0, &dummy)) {
lv_dropdown_set_selected(port_dropdown, 0);
selectBus(0);
}
@@ -184,9 +184,9 @@ void I2cScannerApp::onPressScanCallback(lv_event_t* event) {
// endregion Callbacks
bool I2cScannerApp::getPort(i2c_port_t* outPort) {
bool I2cScannerApp::getPort(struct Device** outPort) {
if (mutex.lock(100 / portTICK_PERIOD_MS)) {
*outPort = this->port;
*outPort = this->portDevice;
mutex.unlock();
return true;
} else {
@@ -219,21 +219,21 @@ bool I2cScannerApp::shouldStopScanTimer() {
void I2cScannerApp::onScanTimer() {
logger.info("Scan thread started");
i2c_port_t safe_port;
Device* safe_port;
if (!getPort(&safe_port)) {
logger.error("Failed to get I2C port");
onScanTimerFinished();
return;
}
if (!hal::i2c::isStarted(safe_port)) {
if (!device_is_ready(safe_port)) {
logger.error("I2C port not started");
onScanTimerFinished();
return;
}
for (uint8_t address = 0; address < 128; ++address) {
if (hal::i2c::masterHasDeviceAtAddress(safe_port, address, 10 / portTICK_PERIOD_MS)) {
if (i2c_controller_has_device_at_address(safe_port, address, 10 / portTICK_PERIOD_MS) == ERROR_NONE) {
logger.info("Found device at address 0x{:02X}", address);
if (!shouldStopScanTimer()) {
addAddressToList(address);
@@ -305,14 +305,14 @@ void I2cScannerApp::onSelectBus(lv_event_t* event) {
}
void I2cScannerApp::selectBus(int32_t selected) {
int32_t found_port;
if (!getActivePortAtIndex(selected, found_port)) {
struct Device* found_device;
if (!getActivePortAtIndex(selected, &found_device)) {
return;
}
if (mutex.lock(100 / portTICK_PERIOD_MS)) {
scannedAddresses.clear();
port = static_cast<i2c_port_t>(found_port);
portDevice = found_device;
scanState = ScanStateInitial;
mutex.unlock();
}
@@ -334,16 +334,6 @@ void I2cScannerApp::onPressScan(lv_event_t* event) {
updateViews();
}
static bool findDeviceName(const std::vector<std::shared_ptr<hal::i2c::I2cDevice>>& devices, i2c_port_t port, uint8_t address, std::string& outName) {
for (auto& device : devices) {
if (device->getPort() == port && device->getAddress() == address) {
outName = device->getName();
return true;
}
}
return false;
}
void I2cScannerApp::updateViews() {
if (mutex.lock(100 / portTICK_PERIOD_MS)) {
if (scanState == ScanStateScanning) {
@@ -358,18 +348,10 @@ void I2cScannerApp::updateViews() {
if (scanState == ScanStateStopped) {
lv_obj_remove_flag(scanListWidget, LV_OBJ_FLAG_HIDDEN);
auto devices = hal::findDevices<hal::i2c::I2cDevice>(hal::Device::Type::I2c);
if (!scannedAddresses.empty()) {
for (auto address: scannedAddresses) {
std::string address_text = getAddressText(address);
std::string device_name;
if (findDeviceName(devices, port, address, device_name)) {
auto text = std::format("{} - {}", address_text, device_name);
lv_list_add_text(scanListWidget, text.c_str());
} else {
lv_list_add_text(scanListWidget, address_text.c_str());
}
lv_list_add_text(scanListWidget, address_text.c_str());
}
} else {
lv_list_add_text(scanListWidget, "No devices found");
-111
View File
@@ -1,111 +0,0 @@
#include <Tactility/hal/i2c/I2c.h>
#include <Tactility/Logger.h>
#include <Tactility/Mutex.h>
#include <tactility/check.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/time.h>
#ifdef ESP_PLATFORM
#include <tactility/drivers/esp32_i2c.h>
#endif
namespace tt::hal::i2c {
class NoLock final : public tt::Lock {
bool lock(TickType_t timeout) const override { return true; }
void unlock() const override { /* NO-OP */ }
};
static NoLock NO_LOCK;
Device* findDevice(i2c_port_t port) {
#ifdef ESP_PLATFORM
struct Params {
i2c_port_t port;
Device* device;
};
Params params = {
.port = port,
.device = nullptr
};
device_for_each_of_type(&I2C_CONTROLLER_TYPE, &params, [](auto* device, auto* context) {
auto* params_ptr = (Params*)context;
auto* driver = device_get_driver(device);
if (driver == nullptr) return true;
if (!driver_is_compatible(driver, "espressif,esp32-i2c")) return true;
auto* config = static_cast<const Esp32I2cConfig*>(device->config);
if (config->port != params_ptr->port) return true;
// Found it, stop iterating
params_ptr->device = device;
return false;
});
return params.device;
#else
return nullptr;
#endif
}
bool isStarted(i2c_port_t port) {
auto* device = findDevice(port);
if (device == nullptr) return false;
return device_is_ready(device);
}
const char* getName(i2c_port_t port) {
auto* device = findDevice(port);
if (device == nullptr) return nullptr;
return device->name;
}
bool masterRead(i2c_port_t port, uint8_t address, uint8_t* data, size_t dataSize, TickType_t timeout) {
auto* device = findDevice(port);
if (device == nullptr) return false;
return i2c_controller_read(device, address, data, dataSize, timeout) == ERROR_NONE;
}
bool masterReadRegister(i2c_port_t port, uint8_t address, uint8_t reg, uint8_t* data, size_t dataSize, TickType_t timeout) {
auto* device = findDevice(port);
if (device == nullptr) return false;
return i2c_controller_read_register(device, address, reg, data, dataSize, timeout) == ERROR_NONE;
}
bool masterWrite(i2c_port_t port, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
auto* device = findDevice(port);
if (device == nullptr) return false;
return i2c_controller_write(device, address, data, dataSize, timeout) == ERROR_NONE;
}
bool masterWriteRegister(i2c_port_t port, uint8_t address, uint8_t reg, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
auto* device = findDevice(port);
if (device == nullptr) return false;
return i2c_controller_write_register(device, address, reg, data, dataSize, timeout) == ERROR_NONE;
}
bool masterWriteRegisterArray(i2c_port_t port, uint8_t address, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
auto* device = findDevice(port);
if (device == nullptr) return false;
return i2c_controller_write_register_array(device, address, data, dataSize, timeout) == ERROR_NONE;
}
bool masterWriteRead(i2c_port_t port, uint8_t address, const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout) {
auto* device = findDevice(port);
if (device == nullptr) return false;
return i2c_controller_write_read(device, address, writeData, writeDataSize, readData, readDataSize, timeout) == ERROR_NONE;
}
bool masterHasDeviceAtAddress(i2c_port_t port, uint8_t address, TickType_t timeout) {
auto* device = findDevice(port);
if (device == nullptr) return false;
return i2c_controller_has_device_at_address(device, address, timeout) == ERROR_NONE;
}
Lock& getLock(i2c_port_t port) {
return NO_LOCK;
}
} // namespace
+9 -9
View File
@@ -5,24 +5,24 @@
namespace tt::hal::i2c {
bool I2cDevice::read(uint8_t* data, size_t dataSize, TickType_t timeout) {
return masterRead(port, address, data, dataSize, timeout);
return i2c_controller_read(controller, address, data, dataSize, timeout) == ERROR_NONE;
}
bool I2cDevice::write(const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
return masterWrite(port, address, data, dataSize, timeout);
return i2c_controller_write(controller, address, data, dataSize, timeout) == ERROR_NONE;
}
bool I2cDevice::writeRead(const uint8_t* writeData, size_t writeDataSize, uint8_t* readData, size_t readDataSize, TickType_t timeout) {
return masterWriteRead(port, address, writeData, writeDataSize, readData, readDataSize, timeout);
return i2c_controller_write_read(controller, address, writeData, writeDataSize, readData, readDataSize, timeout) == ERROR_NONE;
}
bool I2cDevice::writeRegister(uint8_t reg, const uint8_t* data, uint16_t dataSize, TickType_t timeout) {
return masterWriteRegister(port, address, reg, data, dataSize, timeout);
return i2c_controller_write_register(controller, address, reg, data, dataSize, timeout) == ERROR_NONE;
}
bool I2cDevice::readRegister12(uint8_t reg, float& out) const {
std::uint8_t data[2] = {0};
if (masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) {
if (i2c_controller_read_register(controller, address, reg, data, 2, DEFAULT_TIMEOUT) == ERROR_NONE) {
out = (data[0] & 0x0F) << 8 | data[1];
return true;
} else {
@@ -32,7 +32,7 @@ bool I2cDevice::readRegister12(uint8_t reg, float& out) const {
bool I2cDevice::readRegister14(uint8_t reg, float& out) const {
std::uint8_t data[2] = {0};
if (masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) {
if (i2c_controller_read_register(controller, address, reg, data, 2, DEFAULT_TIMEOUT) == ERROR_NONE) {
out = (data[0] & 0x3F) << 8 | data[1];
return true;
} else {
@@ -42,7 +42,7 @@ bool I2cDevice::readRegister14(uint8_t reg, float& out) const {
bool I2cDevice::readRegister16(uint8_t reg, uint16_t& out) const {
std::uint8_t data[2] = {0};
if (masterReadRegister(port, address, reg, data, 2, DEFAULT_TIMEOUT)) {
if (i2c_controller_read_register(controller, address, reg, data, 2, DEFAULT_TIMEOUT) == ERROR_NONE) {
out = data[0] << 8 | data[1];
return true;
} else {
@@ -51,11 +51,11 @@ bool I2cDevice::readRegister16(uint8_t reg, uint16_t& out) const {
}
bool I2cDevice::readRegister8(uint8_t reg, uint8_t& result) const {
return masterWriteRead(port, address, &reg, 1, &result, 1, DEFAULT_TIMEOUT);
return i2c_controller_write_read(controller, address, &reg, 1, &result, 1, DEFAULT_TIMEOUT) == ERROR_NONE;
}
bool I2cDevice::writeRegister8(uint8_t reg, uint8_t value) const {
return masterWriteRegister(port, address, reg, &value, 1, DEFAULT_TIMEOUT);
return i2c_controller_write_register(controller, address, reg, &value, 1, DEFAULT_TIMEOUT) == ERROR_NONE;
}
bool I2cDevice::bitOn(uint8_t reg, uint8_t bitmask) const {