Let a tap survive a centimetre of finger drift and log every refused tap (0.2.28)

The touch guard dropped a tap as soon as the finger drifted more than
18 px (under 3 mm) from its very first contact point, on both boards and
regardless of the page-swipe setting; LVGL itself never cancelled these
clicks (no scrollable ancestors, gestures do not suppress clicks). A finger
that flattens or rolls while pressing was enough, and the tap vanished
without a trace in the log.

The limit is now about one centimetre per board (TOUCH_MOVE_LIMIT_PX:
Guition 67 px, CYD 56 px), measured from a reference that settles over the
first four samples; only the contact that started the touch is followed,
so a second finger no longer counts as movement; the minimum contact time
is per board too (TOUCH_MIN_PRESS_MS: 60 ms resistive, 20 ms capacitive).
Every refused tap is logged on INFO with its reason (moved, too short,
already used, bounce). Firmware 0.2.23 for CYD and Guition.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-14 11:30:23 +02:00
parent 2327d31261
commit ea0b423fab
13 changed files with 236 additions and 40 deletions
+6 -2
View File
@@ -18,8 +18,12 @@ De wifi-, API- en OTA-geheimen blijven in `secrets.yaml`.
bestaande licht-, climate- of vacuumkaart, of de ingestelde lange actie.
- Standby begint na tien minuten zonder aanraking (`AUTO_DIM_TIMEOUT: "600"`).
- De eerste tik op het gedimde scherm maakt het scherm wakker.
- Contacten korter dan 60 ms worden genegeerd. Eén aanraking kan maximaal
één tegelactie geven; een verplaatsing groter dan 18 pixels annuleert de tik; dezelfde tegel wordt 600 ms tegen contactdender beschermd.
- Contacten korter dan `TOUCH_MIN_PRESS_MS` (60 ms op de CYD, 20 ms op de
Guition) worden genegeerd. Eén aanraking kan maximaal één tegelactie geven;
een verplaatsing groter dan `TOUCH_MOVE_LIMIT_PX` (circa 1 cm: 56 px CYD,
67 px Guition) ten opzichte van het gesettelde eerste contactpunt annuleert de
tik; alleen het eerste contact telt; dezelfde tegel wordt 600 ms tegen
contactdender beschermd. Een geweigerde tik staat met reden in het log (tag `touch`).
- De thermostaat verstuurt de afgeronde, begrensde temperatuur die hij toont.
Een lokale temperatuur- of moduskeuze krijgt drie seconden om met HA te
synchroniseren. Een ingedrukte bediening voorkomt automatisch dimmen.
+74 -19
View File
@@ -27,49 +27,104 @@ inline int quantize_temperature(int value, int minimum, int maximum, int step) {
class TouchGuard {
public:
void begin(uint32_t now, int x = 0, int y = 0) {
enum Reject { NONE, MOVED, TOO_SHORT, USED, BOUNCE };
// Board tuning, set from the profile on boot: how far the finger may drift (pixels, about
// one centimetre) before a tap is dropped, and the shortest contact that counts. A resistive
// panel (CYD) bounces on landing and lift-off; a capacitive one (Guition) does not.
void configure(int move_limit_px, uint32_t min_press_ms) {
move_limit_ = move_limit_px;
min_press_ = min_press_ms;
}
int move_limit() const { return move_limit_; }
uint32_t min_press() const { return min_press_; }
void begin(uint32_t now, int x = 0, int y = 0, int contact = 0) {
started_ = now;
accepted_ = false;
moved_ = false;
start_x_ = x;
start_y_ = y;
contact_ = contact;
samples_ = 1;
sum_x_ = anchor_x_ = x;
sum_y_ = anchor_y_ = y;
distance_ = 0;
reject_ = NONE;
}
void update(int x, int y) {
// A contact that jumps to another target must never become a tile tap.
if (std::abs(x - start_x_) > 18 || std::abs(y - start_y_) > 18) moved_ = true;
int contact() const { return contact_; }
// Follows only the contact that started this touch: a second finger elsewhere is no movement.
// The reference point settles over the first samples, because a finger flattens as it lands;
// a tap is dropped once the finger is farther than the limit from that point.
void update(int x, int y, int contact = 0) {
if (contact != contact_) return;
if (samples_ < SETTLE_SAMPLES) {
++samples_;
sum_x_ += x;
sum_y_ += y;
anchor_x_ = sum_x_ / samples_;
anchor_y_ = sum_y_ / samples_;
}
const long dx = x - anchor_x_, dy = y - anchor_y_;
const int distance = static_cast<int>(std::lround(std::sqrt(static_cast<double>(dx * dx + dy * dy))));
distance_ = std::max(distance_, distance);
if (distance > move_limit_) moved_ = true;
}
int distance() const { return distance_; }
void consume() { accepted_ = true; moved_ = true; }
bool accept(uint32_t now, int tile) {
return !moved_ && accept_slider(now, tile);
if (moved_) { reject_ = MOVED; return false; }
return accept_slider(now, tile);
}
// For -/+ keys: every clean tap counts, even the third within a second, so a
// setpoint moves several steps in one go. Only bounce (same key within `gap`) is dropped.
bool accept_repeat(uint32_t now, int tile, uint32_t gap = 150) {
if (moved_ || accepted_ || now - started_ < 40) return false;
if (has_previous_ && tile == previous_tile_ && now - previous_ < gap) return false;
accepted_ = true;
has_previous_ = true;
previous_tile_ = tile;
previous_ = now;
if (moved_) { reject_ = MOVED; return false; }
if (accepted_) { reject_ = USED; return false; }
if (!long_enough(now, std::min<uint32_t>(min_press_, 40))) return false;
if (has_previous_ && tile == previous_tile_ && now - previous_ < gap) { reject_ = BOUNCE; return false; }
remember(now, tile);
return true;
}
// Only for a slider which captured this contact and did not lose the press.
// Consume the gesture so its parent can never also turn it into a tile tap.
bool accept_slider(uint32_t now, int tile) {
if (accepted_ || now - started_ < 60) return false;
if (has_previous_ && tile == previous_tile_ && now - previous_ < 600) return false;
if (accepted_) { reject_ = USED; return false; }
if (!long_enough(now, min_press_)) return false;
if (has_previous_ && tile == previous_tile_ && now - previous_ < 600) { reject_ = BOUNCE; return false; }
remember(now, tile);
return true;
}
// Why the last accept() refused, for the touch log; empty after a success.
std::string reason() const {
switch (reject_) {
case MOVED: return "verplaatst " + std::to_string(distance_) + " px (grens " + std::to_string(move_limit_) + ")";
case TOO_SHORT: return "te kort (" + std::to_string(duration_) + " ms, minimaal " + std::to_string(min_press_) + ")";
case USED: return "al verwerkt in dit contact";
case BOUNCE: return "dezelfde knop binnen de dendertijd";
default: return "";
}
}
private:
static constexpr int SETTLE_SAMPLES = 4;
bool long_enough(uint32_t now, uint32_t minimum) {
duration_ = now - started_;
if (duration_ < minimum) { reject_ = TOO_SHORT; return false; }
return true;
}
void remember(uint32_t now, int tile) {
accepted_ = true;
has_previous_ = true;
previous_tile_ = tile;
previous_ = now;
return true;
reject_ = NONE;
}
private:
uint32_t started_{0}, previous_{0};
int move_limit_{18};
uint32_t min_press_{60};
uint32_t started_{0}, previous_{0}, duration_{0};
int previous_tile_{-1};
int start_x_{0}, start_y_{0};
int contact_{0}, samples_{1};
long sum_x_{0}, sum_y_{0};
int anchor_x_{0}, anchor_y_{0}, distance_{0};
bool moved_{false};
bool accepted_{false}, has_previous_{false};
Reject reject_{NONE};
};
inline TouchGuard touch_guard;
} // namespace cyd
+11 -4
View File
@@ -109,6 +109,13 @@ inline bool ha_connected() { return esphome::api_is_connected(); }
// loop slack and the sending itself are tolerated before the feed counts as gone.
inline bool feed_alive() { return esphome::millis() - last_received < keepalive_seconds * 2000 + 60000; }
inline bool fresh() { return model.ready() && ha_connected() && feed_alive(); }
// A dropped tap is logged with its reason, so a missed touch can be read from the ESPHome log
// instead of guessed: moved too far, too short, already used by this contact, or bounce.
inline bool allowed(uint32_t now, int tile, const std::string &what) {
if (cyd::touch_guard.accept(now, tile)) return true;
ESP_LOGI("touch", "tik op %s genegeerd: %s", what.c_str(), cyd::touch_guard.reason().c_str());
return false;
}
inline float number(JsonVariant value, float fallback = NAN) {
if (!value.is<float>() && !value.is<int>()) return fallback;
float n = value.as<float>();
@@ -388,7 +395,7 @@ inline lv_obj_t *detail_button(const char *text,int x,int y,int width,int height
auto *label=detail_label(button,text,6,0,width-12);lv_obj_center(label);lv_obj_set_style_text_align(label,LV_TEXT_ALIGN_CENTER,0);if(command==0)lv_obj_set_style_text_color(label,lv_color_hex(0xFFFFFF),0);lv_obj_remove_flag(label,LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(button,[](lv_event_t *e){
int cmd=(intptr_t)lv_event_get_user_data(e);if(cmd==-1){hide_detail();return;}
if(!fresh()||detail_index>=model.count || !cyd::touch_guard.accept(esphome::millis(),300+cmd))return;
if(!fresh()||detail_index>=model.count || !allowed(esphome::millis(),300+cmd,"kaartknop "+model.tiles[detail_index].entity))return;
auto &t=model.tiles[detail_index];if(!t.available()||t.loading(esphome::millis()))return;
if(cmd<4){const char *services[]={"vacuum.start","vacuum.pause","vacuum.return_to_base","vacuum.locate"};action(services[cmd],t.entity);}
if(cmd>=10 && cmd<14 && cmd-10<(int)t.fan_speed_count)action("vacuum.set_fan_speed",t.entity,"fan_speed",t.fan_speeds[cmd-10]);
@@ -723,7 +730,7 @@ inline void event(lv_event_t *event) {
if (!enabled || !fresh() || w.index >= model.count) return;
auto code = lv_event_get_code(event);
if (code != LV_EVENT_SHORT_CLICKED && code != LV_EVENT_LONG_PRESSED) return;
if (!cyd::touch_guard.accept(esphome::millis(), 100 + w.index)) return;
if (!allowed(esphome::millis(), 100 + w.index, model.tiles[w.index].entity)) return;
auto &tile = model.tiles[w.index];
auto d = tile.domain();
// Scenes/scripts often have timestamps or 'off'; unavailable devices never act.
@@ -1227,8 +1234,8 @@ inline void control_event(lv_event_t *e) {
bool step=command==tile_controls::STEP_DOWN || command==tile_controls::STEP_UP;
bool held=lv_event_get_code(e)==LV_EVENT_LONG_PRESSED_REPEAT;
if(held){ if(!step || now-t.edit_since<300)return; } // three steps a second while holding
else if(step){ if(!cyd::touch_guard.accept_repeat(now,400+slot*16+n))return; }
else if(!cyd::touch_guard.accept(now,400+slot*16+n))return;
else if(step){ if(!cyd::touch_guard.accept_repeat(now,400+slot*16+n)){ESP_LOGI("touch","tik op bediening %u genegeerd: %s",(unsigned)slot,cyd::touch_guard.reason().c_str());return;} }
else if(!allowed(now,400+slot*16+n,"bediening "+std::to_string(slot)))return;
if(!t.available())return;
if(step){
// Local at once, tap after tap; tick() sends the last value after a short pause.
+10
View File
@@ -177,6 +177,16 @@ staan iconen in de browser uit het midden); draai daarna `generate_packages.py`.
De substituties `MDI_GLYPH_*` zijn vervallen: `TILEn_ICON` in handmatige
profielen moet uit de set komen. Geen gewijzigde preferences of sleutels.
### Compatibiliteit 0.2.28 / firmware 0.2.23
Alleen firmware. `cyd::TouchGuard` krijgt `configure(move_limit_px, min_press_ms)`
(aangeroepen in `on_boot` uit de substituties `TOUCH_MOVE_LIMIT_PX` en
`TOUCH_MIN_PRESS_MS`; zonder aanroep gelden de oude 18 px en 60 ms), volgt alleen
het contact-id waarmee de aanraking begon, meet de verplaatsing als afstand tot een
over vier metingen gesetteld referentiepunt en meldt via `reason()` waarom een tik
is geweigerd; `runtime_tiles::allowed()` logt dat op INFO met tag `touch`.
Protocol, opslag, preferences en sleutels ongewijzigd.
### Compatibiliteit 0.2.27 (firmware 0.2.22 blijft)
Alleen de app. `POST /api/install` (YAML-download) is verwijderd; `POST
+48
View File
@@ -0,0 +1,48 @@
# Testresultaten app 0.2.28 / firmware 0.2.23 (2026-09-14)
Alleen firmware: tikken die verloren gingen doordat de vinger tijdens het drukken
iets verschoof. De app verandert alleen de doelversie.
## Diagnose vooraf (waarom tikken wegvielen)
- `cyd::TouchGuard::update()` markeerde een aanraking als "verplaatst" zodra x of y
meer dan 18 px van het allereerste contactpunt afweek (Guition ≈ 2,7 mm, CYD ≈ 3,2 mm);
`accept()` weigerde daarna stil, zonder logregel. LVGL 9.5 zelf annuleert een klik
alleen bij scrollen (alle containers staan op `scrollable: false`) of bij een ander
object onder de vinger (press lost); een gesture (50 px, snelheid 3 px/meting)
onderdrukt geen klik. De blokkade zat dus in de eigen guard, ver onder de veegdrempel
en ook met "Vegen tussen pagina's" uit.
- Bijzaken: elk contactpunt (ook een tweede vinger) voedde de guard; de 60 ms
minimumduur voor contactdender gold ook op de capacitieve Guition.
## Wat veranderde
- Per bord `TOUCH_MOVE_LIMIT_PX` (Guition 67, CYD 56: circa 1 cm) en
`TOUCH_MIN_PRESS_MS` (CYD 60, Guition 20), via `configure()` in `on_boot`.
- Verplaatsing is nu de afstand tot een referentiepunt dat over de eerste vier
metingen (≈ 60 ms bij 20 ms polling) settelt; een snelle veeg wordt daardoor pas
op de vijfde meting herkend, een langzame drift onder 1 cm blijft een tik.
- Alleen het contact-id waarmee de aanraking begon telt (`touch.id`).
- `reason()` en `runtime_tiles::allowed()`: elke geweigerde tik op tegel, kaartknop of
bediening staat op INFO in het log met tag `touch`.
## Geautomatiseerd
- C++ (clang++ -std=c++17 -Wall -Wextra -Werror): 11/11 PASS. `test_cyd_ui` dekt de
nieuwe grenzen: 55 px drift geaccepteerd, veeg van 180 px geweigerd met reden
`verplaatst`, tweede vinger genegeerd, 10 ms te kort op de Guition, `al verwerkt`,
dendertijd, CYD-sprong van 30 px geaccepteerd en 70 px geweigerd; de oude
standaardwaarden (18 px, 60 ms) gelden nog zonder `configure()`.
- Python: 84 tests OK (`FIRMWARE_VERSION` 0.2.23 gelijk aan beide bordprofielen en
pakketten). `tools/generate_packages.py --check` groen; de bench-kopieën in
`.esphome/easy-*-package.yaml` opnieuw gegenereerd.
- ESPHome 2026.6.2 compile: `easy-guition-device.yaml` SUCCESS in 43 s (RAM 31,9%,
flash 18,3%), `easy-cyd-device.yaml` SUCCESS in 41 s (RAM 30,9%, flash 76,0%);
geen nieuwe waarschuwingen in `cyd_ui.h` of `runtime_tiles.h`.
## Nog niet geverifieerd
- Fysiek tikken met een bewegende vinger op beide borden; de bench-borden hingen
tijdens deze ronde niet aan USB en reageerden niet op de API, dus geen flash en
geen logcontrole van de nieuwe `touch`-regels. Max werkt de schermen bij via
**Bijwerken** en kijkt in het ESPHome-log of geweigerde tikken nu een reden geven.
+9 -3
View File
@@ -1,7 +1,11 @@
# Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911.
# Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile.
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.22"
SCREEN_FIRMWARE_VERSION: "0.2.23"
# A tap survives a finger that drifts up to this far (480 px over 72 mm: 1 cm); further is a swipe.
TOUCH_MOVE_LIMIT_PX: "67"
# Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not.
TOUCH_MIN_PRESS_MS: "20"
DYNAMIC_TILES: "false"
DASHBOARD_INBOX_INTERNAL: "true"
DEVICE_NAME: "guition-wallbox"
@@ -587,6 +591,7 @@ esphome:
priority: -100
then:
- lambda: |-
cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS});
id(last_touch_ms) = millis();
id(display_dimmed) = false;
lv_obj_set_style_pad_row(id(vacuum_actions), 4, 0);
@@ -1870,13 +1875,14 @@ touchscreen:
- lambda: |-
id(last_touch_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y);
cyd::touch_guard.begin(millis(), touch.x, touch.y, touch.id);
ESP_LOGI("touch", "GT911 press x=%u y=%u test=%d", touch.x, touch.y, id(calibration_active));
on_update:
- lambda: |-
id(last_touch_ms) = millis();
// Only the contact that started the touch counts; the guard ignores other ids.
for (const auto &point : touches) {
cyd::touch_guard.update(point.x, point.y);
cyd::touch_guard.update(point.x, point.y, point.id);
}
on_release:
- lambda: |-
+9 -3
View File
@@ -58,7 +58,11 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.22"
SCREEN_FIRMWARE_VERSION: "0.2.23"
# A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe.
TOUCH_MOVE_LIMIT_PX: "56"
# Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not.
TOUCH_MIN_PRESS_MS: "60"
DYNAMIC_TILES: "false"
DASHBOARD_INBOX_INTERNAL: "true"
DEVICE_NAME: "cyd-new"
@@ -781,6 +785,7 @@ esphome:
- lambda: |-
// Per-panel affine correction; shared defaults are neutral.
id(ts_touch).set_raw_correction(${TOUCH_AFFINE_XX}f, ${TOUCH_AFFINE_XY}f, ${TOUCH_AFFINE_XC}f, ${TOUCH_AFFINE_YX}f, ${TOUCH_AFFINE_YY}f, ${TOUCH_AFFINE_YC}f);
cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS});
id(last_touch_ms) = millis();
id(display_dimmed) = false;
lv_obj_set_style_pad_row(id(vacuum_actions), 4, 0);
@@ -2095,14 +2100,15 @@ touchscreen:
- lambda: |-
id(last_touch_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y);
cyd::touch_guard.begin(millis(), touch.x, touch.y, touch.id);
screen_calibration::press();
ESP_LOGI("touch", "press native=%u,%u raw=%d,%d pressure=%d calibration=%d", touch.x, touch.y, id(ts_touch).filtered_raw_x(), id(ts_touch).filtered_raw_y(), touch.z_raw, id(calibration_active));
on_update:
- lambda: |-
id(last_touch_ms) = millis();
// Only the contact that started the touch counts; the guard ignores other ids.
for (const auto &point : touches) {
cyd::touch_guard.update(point.x, point.y);
cyd::touch_guard.update(point.x, point.y, point.id);
}
on_release:
- lambda: |-
+9 -3
View File
@@ -59,7 +59,11 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.22"
SCREEN_FIRMWARE_VERSION: "0.2.23"
# A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe.
TOUCH_MOVE_LIMIT_PX: "56"
# Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not.
TOUCH_MIN_PRESS_MS: "60"
DYNAMIC_TILES: "true"
DASHBOARD_INBOX_INTERNAL: "false"
DEVICE_NAME: "cyd-new"
@@ -785,6 +789,7 @@ esphome:
- lambda: |-
// Per-panel affine correction; shared defaults are neutral.
id(ts_touch).set_raw_correction(${TOUCH_AFFINE_XX}f, ${TOUCH_AFFINE_XY}f, ${TOUCH_AFFINE_XC}f, ${TOUCH_AFFINE_YX}f, ${TOUCH_AFFINE_YY}f, ${TOUCH_AFFINE_YC}f);
cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS});
id(last_touch_ms) = millis();
id(display_dimmed) = false;
lv_obj_set_style_pad_row(id(vacuum_actions), 4, 0);
@@ -2026,14 +2031,15 @@ touchscreen:
- lambda: |-
id(last_touch_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y);
cyd::touch_guard.begin(millis(), touch.x, touch.y, touch.id);
screen_calibration::press();
ESP_LOGI("touch", "press native=%u,%u raw=%d,%d pressure=%d calibration=%d", touch.x, touch.y, id(ts_touch).filtered_raw_x(), id(ts_touch).filtered_raw_y(), touch.z_raw, id(calibration_active));
on_update:
- lambda: |-
id(last_touch_ms) = millis();
// Only the contact that started the touch counts; the guard ignores other ids.
for (const auto &point : touches) {
cyd::touch_guard.update(point.x, point.y);
cyd::touch_guard.update(point.x, point.y, point.id);
}
on_release:
- lambda: |-
+9 -3
View File
@@ -2,7 +2,11 @@
# Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911.
# Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile.
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.22"
SCREEN_FIRMWARE_VERSION: "0.2.23"
# A tap survives a finger that drifts up to this far (480 px over 72 mm: 1 cm); further is a swipe.
TOUCH_MOVE_LIMIT_PX: "67"
# Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not.
TOUCH_MIN_PRESS_MS: "20"
DYNAMIC_TILES: "true"
DASHBOARD_INBOX_INTERNAL: "false"
DEVICE_NAME: "guition-new"
@@ -591,6 +595,7 @@ esphome:
priority: -100
then:
- lambda: |-
cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS});
id(last_touch_ms) = millis();
id(display_dimmed) = false;
lv_obj_set_style_pad_row(id(vacuum_actions), 4, 0);
@@ -1801,13 +1806,14 @@ touchscreen:
- lambda: |-
id(last_touch_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y);
cyd::touch_guard.begin(millis(), touch.x, touch.y, touch.id);
ESP_LOGI("touch", "GT911 press x=%u y=%u test=%d", touch.x, touch.y, id(calibration_active));
on_update:
- lambda: |-
id(last_touch_ms) = millis();
// Only the contact that started the touch counts; the guard ignores other ids.
for (const auto &point : touches) {
cyd::touch_guard.update(point.x, point.y);
cyd::touch_guard.update(point.x, point.y, point.id);
}
on_release:
- lambda: |-
+8
View File
@@ -1,3 +1,11 @@
## 0.2.28 (firmware 0.2.23)
- **Tikken die niet doorkwamen**: de firmware gooide een tik weg zodra de vinger tijdens het drukken meer dan 18 px (nog geen 3 mm) van het eerste contactpunt afweek, ver onder LVGL's eigen veegdrempel en ook met "Vegen tussen pagina's" uit. Een vinger die platter wordt of iets rolt haalde dat al. Nu geldt per bord een grens van ongeveer één centimeter (`TOUCH_MOVE_LIMIT_PX`: Guition 67 px, CYD 56 px), gemeten als afstand tot een referentiepunt dat over de eerste vier metingen (circa 60 ms) settelt in plaats van tot het allereerste punt. Eindigt de vinger op een andere tegel, dan vangt LVGL dat nog steeds op (press lost).
- Alleen het contact dat de aanraking begon telt: een tweede vinger of een duim aan de rand geldt niet meer als verplaatsing (`touch.id` van de touchscreen-component).
- De minimale contactduur is per bord (`TOUCH_MIN_PRESS_MS`): 60 ms op de resistieve CYD (contactdender), 20 ms op de capacitieve Guition, waar een lichte snelle tik anders verloren ging.
- Elke geweigerde tik staat nu in het ESPHome-log (tag `touch`, niveau INFO) met de reden: `verplaatst 73 px (grens 67)`, `te kort (12 ms, minimaal 20)`, `al verwerkt in dit contact` of `dezelfde knop binnen de dendertijd`, zodat een gemiste aanraking uit het log te lezen is.
- Firmware 0.2.23 voor beide borden; de app biedt de update aan. Alleen firmware verandert; de app-kant is ongewijzigd behalve de doelversie.
## 0.2.27 (firmware 0.2.22 blijft actueel)
- **Nieuw scherm in één venster**: bord, naam, USB-poort, **Installeren**. Het venster maakt het profiel met unieke API- en OTA-sleutels in de ESPHome-map, bouwt de firmware en schrijft die via USB, met het ESPHome-log en de fase (bouwen, schrijven) in hetzelfde venster. Na afloop staat de API-sleutel klaar met een kopieerknop plus de koppelstappen voor Home Assistant; mislukt de build, dan staat het log open en is er **Opnieuw proberen**. De apparaatnaam volgt uit de naam (`Keuken` → `keuken`) en is aan te passen. Zonder aangesloten scherm kun je alleen het profiel bewaren; het staat dan in dezelfde map als ESPHome Device Builder. Elk scherm krijgt zijn eigen profiel; het venster zegt dat nu ook.
+1 -1
View File
@@ -16,7 +16,7 @@ WEEKDAYS = ['ma', 'di', 'wo', 'do', 'vr', 'za', 'zo']
REPO = 'https://github.com/MaxGramser/homeassistant_espscreen'
REFS = {'cyd': 'main', 'guition': 'main'}
# Firmware shipped with this app release; screens below it get an update offer.
FIRMWARE_VERSION = '0.2.22'
FIRMWARE_VERSION = '0.2.23'
ATTRS = frozenset('brightness percentage current_position current_temperature temperature current_humidity min_temp max_temp target_temp_step supported_color_modes hvac_modes hvac_action hs_color color_temp_kelvin min_color_temp_kelvin max_color_temp_kelvin fan_speed_list unit_of_measurement battery_level fan_speed volume_level is_volume_muted media_title options min max step temperature_unit supported_features device_class next_rising next_setting finishes_at duration remaining humidity wind_speed wind_speed_unit apparent_temperature fan_modes swing_modes fan_mode swing_mode'.split())
# Attributes whose boolean value the screen needs; every other bool stays behind.
BOOL_ATTRS = frozenset(['is_volume_muted'])
+1 -1
View File
@@ -1,5 +1,5 @@
name: ESP Screen Manager
version: "0.2.27"
version: "0.2.28"
slug: esp_screen_manager
description: Kies tegels voor je CYD en Guition touchscreen, zonder opnieuw flashen.
url: https://github.com/MaxGramser/homeassistant_espscreen
+41 -1
View File
@@ -37,7 +37,8 @@ int main() {
moving.update(52, 48);
assert(moving.accept(180, 1)); // small resistive jitter remains usable
moving.begin(1000, 50, 50);
moving.update(50, 80);
moving.update(51, 50); moving.update(50, 51); moving.update(50, 50); // reference settled
moving.update(50, 80); // 30 px beyond the default 18 px
moving.update(50, 50);
assert(!moving.accept(1200, 2)); // excursion stays cancelled, even on return
assert(moving.accept_slider(1200, 202)); // captured drag is a valid slider gesture
@@ -46,6 +47,45 @@ int main() {
moving.begin(1250);
assert(!moving.accept_slider(1260, 202)); // noise remains rejected
assert(!moving.accept_slider(1350, 202)); // same-control bounce remains rejected
// Per-board limits (0.2.23): a centimetre of drift on the Guition is still a tap, and the
// reference settles over the first samples, so the landing wobble does not count.
cyd::TouchGuard wide;
wide.configure(67, 20);
assert(wide.move_limit() == 67 && wide.min_press() == 20);
wide.begin(100, 200, 200, 3);
wide.update(206, 203, 3); wide.update(210, 205, 3); wide.update(212, 206, 3); // finger flattens
wide.update(262, 206, 3); // 55 px from the settled point
assert(wide.distance() > 45 && wide.distance() <= 67);
assert(wide.accept(180, 1)); // under one centimetre: still a tap
wide.begin(1000, 200, 200, 3);
wide.update(275, 200, 3); wide.update(300, 200, 3); wide.update(340, 200, 3); // a real swipe: the reference
wide.update(380, 200, 3); // settles, the finger keeps going
assert(!wide.accept(1100, 1));
assert(wide.reason().rfind("verplaatst", 0) == 0);
wide.begin(2000, 200, 200, 3);
wide.update(400, 400, 5); // a second finger elsewhere
assert(wide.accept(2050, 1)); // ignored: only contact 3 is followed; 50 ms is long enough here
wide.begin(3000, 200, 200, 3);
assert(!wide.accept(3010, 2)); // 10 ms is below the capacitive minimum
assert(wide.reason().rfind("te kort", 0) == 0);
wide.begin(3100, 200, 200, 3);
assert(wide.accept(3160, 2));
assert(wide.reason().empty());
assert(!wide.accept(3170, 2));
assert(wide.reason() == "al verwerkt in dit contact");
wide.begin(3200, 200, 200, 3);
assert(!wide.accept(3260, 2)); // same tile within 600 ms
assert(wide.reason() == "dezelfde knop binnen de dendertijd");
cyd::TouchGuard resistive;
resistive.configure(56, 60);
resistive.begin(100, 50, 50);
resistive.update(80, 50); resistive.update(50, 50); // the CYD's jump of 30 px is fine now
assert(resistive.accept(180, 1));
resistive.begin(1000, 50, 50);
resistive.update(50, 120); resistive.update(50, 130); resistive.update(50, 140);
resistive.update(50, 180); // 70 px past the settled point: a swipe
assert(!resistive.accept(1100, 1));
assert(!resistive.accept_repeat(1100, 1));
cyd::TouchGuard rollover;
rollover.begin(std::numeric_limits<uint32_t>::max() - 30);
assert(rollover.accept(50, 1)); // millis wraps after 49 days