Fixes and new apps (#30)

- M5 Unit Modules library + M5 Unit Test app
- Minor fixes for TodoList, TwoEleven, Snake, Brainfuck and Breakout
- Fixed SerialConsole to use the uart controller as it was broken in one of the many updates
- Fixed keyboard input in TwoEleven, Breakout, Magic8Ball and Snake
- Bluetooth Media Keys app, supports pressing physical keys to trigger the corresponding buttonmatrix button
- Epub Reader app
This commit is contained in:
Shadowtrance
2026-06-07 23:56:32 +10:00
committed by GitHub
parent 1f959c8bbf
commit dbf850c434
127 changed files with 11156 additions and 174 deletions
@@ -0,0 +1,127 @@
#include "TestUnitPaHub.h"
#include "UiScale.h"
#include <tactility/device.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/lvgl_fonts.h>
void TestUnitPaHub::onStart(lv_obj_t* parent, AppHandle handle, M5UnitTest* app) {
app_ = app;
createToolbar(parent, handle, "PaHub");
createBanner(parent, "PaHub", "I2C", COLOR_I2C);
lv_obj_t* cont = lv_obj_create(parent);
lv_obj_set_width(cont, LV_PCT(100));
lv_obj_set_flex_grow(cont, 1);
lv_obj_set_layout(cont, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_all(cont, uiPad(), 0);
lv_obj_set_style_pad_row(cont, uiRowGap(), 0);
lv_obj_set_style_bg_opa(cont, LV_OPA_TRANSP, 0);
lv_obj_set_style_border_width(cont, 0, 0);
const lv_font_t* fnt = lvgl_get_text_font(uiFont());
int pad = uiPad();
int gap = uiRowGap();
// Row of channel buttons — flex-grow so they share space evenly at any width
lv_obj_t* btnRow = lv_obj_create(cont);
lv_obj_set_width(btnRow, LV_PCT(100));
lv_obj_set_height(btnRow, LV_SIZE_CONTENT);
lv_obj_set_layout(btnRow, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(btnRow, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(btnRow, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_column(btnRow, gap, 0);
lv_obj_set_style_bg_opa(btnRow, LV_OPA_TRANSP, 0);
lv_obj_set_style_border_width(btnRow, 0, 0);
lv_obj_set_style_pad_all(btnRow, 0, 0);
for (int i = 0; i < CH_COUNT; i++) {
lv_obj_t* btn = lv_button_create(btnRow);
lv_obj_set_flex_grow(btn, 1);
lv_obj_set_style_pad_hor(btn, pad, 0);
lv_obj_set_style_pad_ver(btn, gap, 0);
lv_obj_set_user_data(btn, (void*)(intptr_t)i);
lv_obj_add_event_cb(btn, onChannelBtn, LV_EVENT_CLICKED, this);
lv_obj_t* lbl = lv_label_create(btn);
lv_label_set_text_fmt(lbl, "CH%d", i);
lv_obj_set_style_text_font(lbl, fnt, 0);
lv_obj_align(lbl, LV_ALIGN_CENTER, 0, 0);
btnCh_[i] = btn;
}
lblStatus_ = lv_label_create(cont);
lv_obj_set_style_text_font(lblStatus_, fnt, 0);
lv_label_set_text(lblStatus_, "Select a channel to probe");
for (int i = 0; i < CH_COUNT; i++) {
lblCh_[i] = lv_label_create(cont);
lv_obj_set_style_text_font(lblCh_[i], fnt, 0);
lv_label_set_text_fmt(lblCh_[i], "CH%d: -", i);
}
Device* i2c = device_find_by_name("i2c1");
if (!i2c || !hub_.begin(i2c)) {
lv_label_set_text(lblStatus_, "PaHub not found");
return;
}
lv_label_set_text(lblStatus_, "PaHub ready - tap channel to probe");
timer_ = lv_timer_create(onTimer, 1000, this);
}
void TestUnitPaHub::onStop() {
if (timer_) { lv_timer_delete(timer_); timer_ = nullptr; }
if (hub_.isPresent()) hub_.deselect();
lblStatus_ = nullptr;
for (int i = 0; i < CH_COUNT; i++) { btnCh_[i] = nullptr; lblCh_[i] = nullptr; }
}
void TestUnitPaHub::onChannelBtn(lv_event_t* e) {
auto* self = static_cast<TestUnitPaHub*>(lv_event_get_user_data(e));
int ch = (int)(intptr_t)lv_obj_get_user_data(lv_event_get_target_obj(e));
self->selChannel_ = ch;
self->probeSelected();
}
void TestUnitPaHub::onTimer(lv_timer_t* t) {
auto* self = static_cast<TestUnitPaHub*>(lv_timer_get_user_data(t));
if (self->selChannel_ >= 0) self->probeSelected();
}
void TestUnitPaHub::probeSelected() {
if (!hub_.isPresent() || selChannel_ < 0 || selChannel_ >= CH_COUNT) return;
hub_.select((uint8_t)selChannel_);
// Scan I2C addresses 0x08-0x77 on this channel
Device* i2c = device_find_by_name("i2c1");
if (!i2c) {
lv_label_set_text_fmt(lblCh_[selChannel_], "CH%d: i2c1 not found", selChannel_);
hub_.deselect();
return;
}
char found[256] = "Found: ";
bool any = false;
for (uint8_t addr = 0x08; addr < 0x78; addr++) {
if (i2c_controller_has_device_at_address(i2c, addr,
pdMS_TO_TICKS(10)) == ERROR_NONE) {
size_t remaining = sizeof(found) - strlen(found) - 1;
if (remaining < 7) {
strncat(found, "...", remaining);
break;
}
char hex[8];
snprintf(hex, sizeof(hex), "0x%02X ", addr);
strncat(found, hex, remaining);
any = true;
}
}
if (!any) strcpy(found, "No devices found");
lv_label_set_text_fmt(lblCh_[selChannel_], "CH%d: %s", selChannel_, found);
lv_label_set_text_fmt(lblStatus_, "Probed CH%d", selChannel_);
hub_.deselect();
}