transition WhisplayInterface to pure state machine
This commit is contained in:
+64
-91
@@ -1,38 +1,28 @@
|
||||
import time
|
||||
import threading
|
||||
import runtime.whisplay_client
|
||||
|
||||
|
||||
class WhisplayInterface:
|
||||
def __init__(self, board=None, long_press_threshold=0.5, double_press_threshold=0.3):
|
||||
self._board = board
|
||||
self._long_press_threshold = long_press_threshold
|
||||
self._double_press_threshold = double_press_threshold
|
||||
self._button_is_double_pressed_set = False
|
||||
|
||||
LONG_PRESS_THRESHOLD = 0.5
|
||||
DOUBLE_PRESS_THRESHOLD = 0.3
|
||||
|
||||
def __init__(self):
|
||||
self.board = None
|
||||
self._lock = threading.Lock()
|
||||
self._button_press_start = None
|
||||
self._button_press_end = None
|
||||
self._button_is_pressed = False
|
||||
self._button_was_pressed = False
|
||||
self._button_is_long_pressed = False
|
||||
self._button_was_long_pressed = False
|
||||
self._button_is_double_pressed = False
|
||||
self._button_was_double_pressed = False
|
||||
|
||||
self._press_callbacks = []
|
||||
self._release_callbacks = []
|
||||
self._running = False
|
||||
self._gesture_thread = None
|
||||
# Edge-detection state
|
||||
self._prev_is_pressed = False
|
||||
self._prev_press_end = None
|
||||
|
||||
if board is not None:
|
||||
self.attach(board)
|
||||
|
||||
@property
|
||||
def long_press_threshold(self):
|
||||
return self._long_press_threshold
|
||||
|
||||
@property
|
||||
def double_press_threshold(self):
|
||||
return self._double_press_threshold
|
||||
# -- Properties --
|
||||
|
||||
@property
|
||||
def is_pressed(self):
|
||||
@@ -50,10 +40,6 @@ class WhisplayInterface:
|
||||
def was_long_pressed(self):
|
||||
return self._button_was_long_pressed
|
||||
|
||||
@property
|
||||
def is_double_pressed(self):
|
||||
return self._button_is_double_pressed
|
||||
|
||||
@property
|
||||
def was_double_pressed(self):
|
||||
return self._button_was_double_pressed
|
||||
@@ -65,76 +51,63 @@ class WhisplayInterface:
|
||||
end = self._button_press_end if self._button_press_end is not None else time.time()
|
||||
return end - self._button_press_start
|
||||
|
||||
def attach(self, board):
|
||||
self._board = board
|
||||
self._board.on_button_press(self._on_button_press)
|
||||
self._board.on_button_release(self._on_button_release)
|
||||
self.start()
|
||||
# -- Attachment --
|
||||
|
||||
def start(self):
|
||||
if self._gesture_thread is not None:
|
||||
return
|
||||
self._running = True
|
||||
self._gesture_thread = threading.Thread(daemon=True, target=self._gesture_loop)
|
||||
self._gesture_thread.start()
|
||||
def create_board(self,
|
||||
app_id,
|
||||
display_name,
|
||||
icon,
|
||||
use_daemon_default_log=True):
|
||||
self.board = runtime.whisplay_client.create_whisplay_hardware(app_id=app_id, display_name=display_name, icon=icon, use_daemon_default_log=use_daemon_default_log)
|
||||
self.board.on_button_press(self._on_button_press)
|
||||
self.board.on_button_release(self._on_button_release)
|
||||
|
||||
def stop(self):
|
||||
self._running = False
|
||||
|
||||
def on_press(self, callback):
|
||||
self._press_callbacks.append(callback)
|
||||
|
||||
def on_release(self, callback):
|
||||
self._release_callbacks.append(callback)
|
||||
|
||||
def acknowledge_press(self):
|
||||
self._button_was_pressed = False
|
||||
|
||||
def acknowledge_long_press(self):
|
||||
self._button_was_long_pressed = False
|
||||
|
||||
def acknowledge_double_press(self):
|
||||
self._button_was_double_pressed = False
|
||||
# -- Internal press/release (called from GPIO thread) --
|
||||
|
||||
def _on_button_press(self):
|
||||
self._button_is_pressed = True
|
||||
self._button_press_start = time.time()
|
||||
if self._button_press_end is not None:
|
||||
gap = self._button_press_start - self._button_press_end
|
||||
if gap < self._double_press_threshold:
|
||||
self._button_is_double_pressed = True
|
||||
self._button_is_double_pressed_set = True
|
||||
for cb in self._press_callbacks:
|
||||
cb()
|
||||
with self._lock:
|
||||
self._button_is_pressed = True
|
||||
self._button_press_start = time.time()
|
||||
|
||||
def _on_button_release(self):
|
||||
self._button_was_pressed = self._button_is_pressed
|
||||
self._button_press_end = time.time()
|
||||
self._button_was_long_pressed = self._button_is_long_pressed
|
||||
self._button_was_double_pressed = self._button_is_double_pressed
|
||||
self._button_is_pressed = False
|
||||
self._button_is_double_pressed = False
|
||||
self._button_is_long_pressed = False
|
||||
self._button_is_double_pressed_set = False
|
||||
for cb in self._release_callbacks:
|
||||
cb()
|
||||
with self._lock:
|
||||
self._button_is_pressed = False
|
||||
self._button_press_end = time.time()
|
||||
|
||||
def _gesture_loop(self):
|
||||
while self._running:
|
||||
try:
|
||||
current_time = time.time()
|
||||
if self._button_is_pressed:
|
||||
if not self._button_is_double_pressed_set:
|
||||
self._button_is_double_pressed = (
|
||||
self._button_was_pressed
|
||||
and self._button_press_end is not None
|
||||
and current_time - self._button_press_end < self._double_press_threshold
|
||||
)
|
||||
if self._button_is_double_pressed:
|
||||
self._button_is_double_pressed_set = True
|
||||
self._button_is_long_pressed = (
|
||||
current_time - self._button_press_start > self._long_press_threshold
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(0.05)
|
||||
# -- Polled update (called from gesture thread) --
|
||||
|
||||
def update_gestures(self):
|
||||
self._button_was_pressed = False
|
||||
self._button_was_long_pressed = False
|
||||
self._button_was_double_pressed = False
|
||||
|
||||
with self._lock:
|
||||
is_pressed = self._button_is_pressed
|
||||
press_start = self._button_press_start
|
||||
press_end = self._button_press_end
|
||||
|
||||
now = time.time()
|
||||
|
||||
# Press edge: transitioned from not-pressed to pressed
|
||||
if not self._prev_is_pressed and is_pressed:
|
||||
if self._prev_press_end is not None and press_start is not None:
|
||||
gap = press_start - self._prev_press_end
|
||||
if gap < WhisplayInterface.DOUBLE_PRESS_THRESHOLD:
|
||||
self._button_was_double_pressed = True
|
||||
|
||||
# Release edge: transitioned from pressed to not-pressed
|
||||
if self._prev_is_pressed and not is_pressed:
|
||||
self._button_was_pressed = True
|
||||
if press_start is not None and press_end is not None:
|
||||
duration = press_end - press_start
|
||||
if duration >= WhisplayInterface.LONG_PRESS_THRESHOLD:
|
||||
self._button_was_long_pressed = True
|
||||
self._prev_press_end = press_end
|
||||
|
||||
# Level flag: currently held past threshold
|
||||
if is_pressed and press_start is not None:
|
||||
self._button_is_long_pressed = (now - press_start) >= WhisplayInterface.LONG_PRESS_THRESHOLD
|
||||
else:
|
||||
self._button_is_long_pressed = False
|
||||
|
||||
self._prev_is_pressed = is_pressed
|
||||
|
||||
Reference in New Issue
Block a user