Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Documentation/ideas.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@

- Make tt.py work without an app project. Ping the device for info.
- LVGL spinner not working on cl32 v04 (possibly anything monochrome)
- Export platform-esp32 with TactilitySDK
- Find out why the first build of an app with a clean build cache folder always fails
- Improve posix support: use user home path and system /tmp folder.
- Drivers/audio-codec-module is not a module. Move it somewhere else. Or make it an actual module.
- Improve SPI kernel driver (implement read, write, transactions)
- External app loading: Check the version of Tactility and check ESP target hardware to check for compatibility
Check during installation process, but also when starting (SD card might have old app install from before Tactility OS update)
- minitar/untarFile(): "entry->metadata.path" can escape its confined path (e.g. "../something")
Expand Down
18 changes: 9 additions & 9 deletions Documentation/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@
2. Merge the branch and wait for a build.
3. Push a tag e.g. `v1.2.3` - the `v` prefix is crucial for the pipelines.
4. Wait for the SDK and firmwares to be published on the CDN.
Only tags publish SDKs of a release version: `main` only publishes SDKs of a `-dev` version.
5. Test the firmwares on all devices with the web installer
(Only tags publish SDKs of a release version: `main` only publishes SDKs of a `-dev` version.)
5. Test the firmwares on several different devices with the web installer.
Try different architectures, input methods, screen sizes.
6. Run `Buildscripts/gh-download-artifacts.py [actionId] --token [token]` and let it run in the background.
Get a token from [here](https://github.com/settings/personal-access-tokens)
7. Update the [TactilityApps](https://github.com/TactilityProject/TactilityApps) with the new SDK version and bump the app versions.
8. After merging the apps, manually upload to CDN, then test them.
9. Make a new version of the docs available at [TactilityDocs](https://github.com/TactilityProject/TactilityDocs)
8. Make a new version of the docs available at [TactilityDocs](https://github.com/TactilityProject/TactilityDocs)
1. Copy `docs/versions/main/` into new version folder
2. Edit `index.html` to include the new version
3. Push code directly to `main` branch
10. Make a new [GitHub release](https://github.com/TactilityProject/Tactility/releases/new)
9. Make a new [GitHub release](https://github.com/TactilityProject/Tactility/releases/new)
Run `Buildscripts/release-summary.py` to generate a release, then review it
11. Double-check that all CDN/TactilityApps/Tactility repository changes are merged.
12. Add the `-dev` postfix from `version.txt` and bump the version. Merge it to `main`.
13. Ensure that the CDN is not in development mode anymore.
10. Double-check that all CDN/TactilityApps/Tactility repository changes are merged.
11. Add the `-dev` postfix from `version.txt` and bump the version. Merge it to `main`.
12. Ensure that the CDN is not in development mode anymore.

### Post-release

1. Mention on Discord
2. Consider making a video with one of the devices, showcasing new features
2. Publish on social media
3. Consider notifying vendors/stakeholders

11 changes: 10 additions & 1 deletion Drivers/ssd16xx-module/source/ssd16xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,14 +241,23 @@ static bool full_refresh(Ssd16xxInternal* internal) {
}

// Drives the pixels that differ from the displayed image
// Without a known displayed image, every pixel is driven by describing the old image as the inverse of the new one.
// Returns false when a transfer failed, which leaves the base image invalid.
static bool partial_update(Ssd16xxInternal* internal) {
const size_t size = snapshot_frame(internal);
const bool displayed_unknown = !internal->base_valid;
internal->update_failed = false;

spi_controller_lock(internal->spi_controller);
set_full_window(internal);
write_command_with_buffer(internal, 0x24, internal->snapshot, size);
if (displayed_unknown) {
for (size_t i = 0; i < size; i++) {
internal->snapshot[i] = ~internal->snapshot[i];
}
set_full_window(internal);
write_command_with_buffer(internal, 0x26, internal->snapshot, size);
}
if (internal->update_failed) {
spi_controller_unlock(internal->spi_controller);
internal->base_valid = false;
Expand Down Expand Up @@ -355,7 +364,7 @@ static void update_task(void* arg) {
portEXIT_CRITICAL(&internal->lock);

if (dirty) {
const bool ok = internal->base_valid ? partial_update(internal) : full_refresh(internal);
const bool ok = partial_update(internal);
if (!ok) {
retry_update(internal);
}
Expand Down
43 changes: 39 additions & 4 deletions Modules/lvgl-module/source/devices/devices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,15 @@ struct LvglDeviceList {
*/
constexpr uint32_t IDLE_REPAINT_MS = 5000;

constexpr uint32_t ACTIVITY_MARGIN_MS = 20;
constexpr uint32_t MAX_ACTIVITY_READ_SKEW_MS = 5;

struct IdleRepaintContext {
lv_display_t* lvgl_display;
struct Device* kernel_display_device;
lv_timer_t* timer;
// Tick of the last input that the flags below already account for
uint32_t handled_activity_at;
// Set once the invalidate+lv_refr_now() repaint has run for the current idle stretch, so it
// isn't redone every tick while still idle.
bool repaint_done_since_activity;
Expand Down Expand Up @@ -161,11 +166,33 @@ static void lvgl_device_listener(struct Device* device, enum DeviceEvent event,
lvgl_unlock();
}

// Reads the tick of the last input, which the display only exposes as time since then.
// Fails when the task was preempted during the read, which would skew the result beyond ACTIVITY_MARGIN_MS.
static bool read_activity(lv_display_t* display, uint32_t* inactive_ms, uint32_t* activity_at) {
const uint32_t start = lv_tick_get();
*inactive_ms = lv_display_get_inactive_time(display);
if (lv_tick_get() - start > MAX_ACTIVITY_READ_SKEW_MS) {
return false;
}
*activity_at = start - *inactive_ms;
return true;
}

static void idle_repaint_timer_cb(lv_timer_t* timer) {
auto* ctx = static_cast<IdleRepaintContext*>(lv_timer_get_user_data(timer));
if (lv_display_get_inactive_time(ctx->lvgl_display) < IDLE_REPAINT_MS) {
uint32_t inactive_ms;
uint32_t activity_at;
if (!read_activity(ctx->lvgl_display, &inactive_ms, &activity_at)) {
return;
}
// Any difference beyond the margin is new input, also when the tick counter wrapped in between
const uint32_t activity_delta = activity_at - ctx->handled_activity_at;
if (activity_delta > ACTIVITY_MARGIN_MS && activity_delta < 0U - ACTIVITY_MARGIN_MS) {
ctx->handled_activity_at = activity_at;
ctx->repaint_done_since_activity = false;
ctx->refresh_done_since_activity = false;
}
if (inactive_ms < IDLE_REPAINT_MS) {
return;
}
if (!ctx->repaint_done_since_activity) {
Expand Down Expand Up @@ -237,13 +264,21 @@ void lvgl_devices_attach() {
// boot and whenever an app that stopped LVGL closes, so clearing here covers both
// cases without extra plumbing at app-close time specifically.
if (display_has_capability(kernel_display_device, DISPLAY_CAPABILITY_SLOW_REFRESH)) {
display_clear(kernel_display_device);
// The first attach follows the boot logo, which the first LVGL render overdraws completely
static bool first_attach = true;
if (!first_attach) {
display_clear(kernel_display_device);
}
first_attach = false;
IdleRepaintContext* ctx = &idle_repaint_contexts()[i];
if (ctx->timer == nullptr) {
ctx->lvgl_display = added_display;
ctx->kernel_display_device = kernel_display_device;
ctx->repaint_done_since_activity = false;
ctx->refresh_done_since_activity = false;
// The panel was just cleared or fully drawn, so the first idle stretch needs no repaint
uint32_t inactive_ms;
while (!read_activity(added_display, &inactive_ms, &ctx->handled_activity_at)) {}
Comment thread
KenVanHoeylandt marked this conversation as resolved.
Resolved
ctx->repaint_done_since_activity = true;
ctx->refresh_done_since_activity = true;
Comment thread
KenVanHoeylandt marked this conversation as resolved.
Resolved
Comment thread
KenVanHoeylandt marked this conversation as resolved.
Resolved
ctx->timer = lv_timer_create(idle_repaint_timer_cb, 1000, ctx);
}
}
Expand Down
20 changes: 11 additions & 9 deletions Tactility/Source/app/launcher/Launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,22 +193,22 @@ int32_t appMain(int argc, char* argv[]) {
check(file_ops_subscribe(&file_ops_sub, &event_group) == ERROR_NONE);

Context ctx;
WindowId window = window_manager_create_ext(appInstanceId, createWidgets, destroyWidgets, &ctx);
WindowId window = 0;
bool windowCreated = false;

// The file operations run in order: the favourites are available when auto-start runs
// The window is created once the favourites are loaded, so its first render has the final order
check(file_ops_queue(loadFavourites, nullptr, &file_ops_sub) == ERROR_NONE);
check(file_ops_queue(runAutoStart, nullptr, nullptr) == ERROR_NONE);

// The launcher is meant to stay resident (it's the home screen) - it only gives up its
// thread when app-module's scheduler asks it to (e.g. another new-model app is started).
while (true) {
task_event_group_wait_any(&event_group, nullptr, portMAX_DELAY);

if (file_ops_poll(&file_ops_sub) == ERROR_NONE) {
// The favourites were loaded
lvgl_lock();
ctx.grid.requestRepopulate(false);
lvgl_unlock();
if (!windowCreated && file_ops_poll(&file_ops_sub) == ERROR_NONE) {
window = window_manager_create_ext(appInstanceId, createWidgets, destroyWidgets, &ctx);
Comment thread
KenVanHoeylandt marked this conversation as resolved.
Resolved
windowCreated = true;
// The file operations run in order: the favourites are available when auto-start runs
check(file_ops_queue(runAutoStart, nullptr, nullptr) == ERROR_NONE);
}

bool shouldClose = false;
Expand All @@ -222,7 +222,9 @@ int32_t appMain(int argc, char* argv[]) {
if (shouldClose) break;
}

window_manager_remove(window);
if (windowCreated) {
window_manager_remove(window);
}
file_ops_unsubscribe(&file_ops_sub);
check(app_event_unsubscribe(&sub) == ERROR_NONE);
task_event_group_destruct(&event_group);
Expand Down
Loading