From 20e45056c8d0a4fd5bdcd7b931d3c9e4c2d69d22 Mon Sep 17 00:00:00 2001 From: chexit Date: Fri, 13 Nov 2026 12:04:19 +0300 Subject: [PATCH] Use sensor timestamps for Mini 2 volume taps --- station/panel.go | 34 +++++++++++++++++++++++++--------- station/panel_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/station/panel.go b/station/panel.go index 867dcab..9b2e4b9 100644 --- a/station/panel.go +++ b/station/panel.go @@ -157,6 +157,7 @@ func (g *panelGesture) key(now time.Time, code uint16, value uint32) { } type panelEvent struct { + at time.Time kind, code uint16 value uint32 } @@ -170,6 +171,7 @@ func readPanel(device io.Reader, events chan<- panelEvent) error { return err } events <- panelEvent{ + at: time.Unix(int64(binary.LittleEndian.Uint64(raw[:8])), int64(binary.LittleEndian.Uint64(raw[8:16]))*1000), kind: binary.LittleEndian.Uint16(raw[16:18]), code: binary.LittleEndian.Uint16(raw[18:20]), value: binary.LittleEndian.Uint32(raw[20:24]), @@ -177,6 +179,27 @@ func readPanel(device io.Reader, events chan<- panelEvent) error { } } +func (g *panelGesture) applyEvent(event panelEvent) panelAction { + switch { + case event.kind == 0 && event.code == 3: // SYN_DROPPED + *g = panelGesture{ignoreToSync: true} + case event.kind == 0 && event.code == 0: + g.ignoreToSync = false + case event.kind == 1 && !g.ignoreToSync: + var action panelAction + // Separate repeated volume taps before waiting for another swipe zone. + if event.value == 1 && event.code != panelCenter && + g.down == 0 && g.count == 1 && g.path[0] == event.code { + action = g.finish(event.at) + } else { + action = g.tick(event.at) + } + g.key(event.at, event.code, event.value) + return action + } + return "" +} + func listenPanel(playback string, dialog *conversation) { names, _ := filepath.Glob("/sys/class/input/event*/device/name") for _, path := range names { @@ -221,6 +244,7 @@ func runPanel(events <-chan panelEvent, playback string, dialog *conversation) { continue } lights.showVolume(level) + log.Printf("panel volume: %d%%", level) go playVolumeTone(playback, level) } }() @@ -279,15 +303,7 @@ func runPanel(events <-chan panelEvent, playback string, dialog *conversation) { if !ok { return } - now := time.Now() - if event.kind == 0 && event.code == 3 { // SYN_DROPPED - gesture = panelGesture{ignoreToSync: true} - } else if event.kind == 0 && event.code == 0 { - gesture.ignoreToSync = false - } else if event.kind == 1 { - emit(gesture.tick(now)) - gesture.key(now, event.code, event.value) - } + emit(gesture.applyEvent(event)) case now := <-ticker.C: emit(gesture.tick(now)) } diff --git a/station/panel_test.go b/station/panel_test.go index 4fe42ee..1f87845 100644 --- a/station/panel_test.go +++ b/station/panel_test.go @@ -1,10 +1,42 @@ package main import ( + "bytes" + "encoding/binary" "testing" "time" ) +func TestPanelBatchedVolumeTapsUseEventTime(t *testing.T) { + for _, tc := range []struct { + code uint16 + want panelAction + }{{panelMinus, panelQuieter}, {panelPlus, panelLouder}} { + var raw bytes.Buffer + for i, micros := range []uint64{1000, 52000, 125000, 151000} { + var event [24]byte + binary.LittleEndian.PutUint64(event[:8], 100) + binary.LittleEndian.PutUint64(event[8:16], micros) + binary.LittleEndian.PutUint16(event[16:18], 1) + binary.LittleEndian.PutUint16(event[18:20], tc.code) + binary.LittleEndian.PutUint32(event[20:24], uint32(1-i%2)) + raw.Write(event[:]) + } + events := make(chan panelEvent, 4) + _ = readPanel(&raw, events) + var gesture panelGesture + var actions []panelAction + for event := range events { + if action := gesture.applyEvent(event); action != "" { + actions = append(actions, action) + } + } + if len(actions) != 1 || actions[0] != tc.want || gesture.tick(time.Unix(101, 0)) != tc.want { + t.Fatalf("zone %d: expected two steps in the same direction, first actions %v", tc.code, actions) + } + } +} + func TestPanelSwipeAndPartialSwipeAreExclusive(t *testing.T) { for _, tc := range []struct { zones []uint16