diff --git a/AGENTS.md b/AGENTS.md index 539a784..7e50fa6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,14 +28,27 @@ Single-file Python application for managing LXST Channel connections via Reticul ## Whisplay GUI Controls + ### Channel list (STATE_READY) + Short press (<1s) Cycle to next channel in list Long press (>=1s) Call the selected channel - Press (while in call) Hang up - RGB LED indicates state: + ### In call (STATE_IN_CALL) — Push-to-Talk mode (default) + + Press & hold Talk (unmute microphone) + Release Stop talking (mute microphone) + Double-press Toggle to always-on mode + + ### In call — Always-on mode (toggled) + + Press Hang up + Double-press Toggle to push-to-talk mode + + ### RGB LED indicates state: Blue = ready, showing channels Orange = calling Green = in call + Yellow = transmitting (PTT mode, mic active) ## Initial Setup Order diff --git a/client.py b/client.py index a9e37bb..62e8a25 100644 --- a/client.py +++ b/client.py @@ -36,6 +36,7 @@ if _WHISPLAY_AVAILABLE: pass LONG_PRESS_THRESHOLD = 1.0 +DOUBLE_PRESS_THRESHOLD = 0.4 class Terminal: @@ -71,6 +72,10 @@ class ChannelClient: self.selected_index = 0 self._press_time = 0.0 self._wm8960_device = None + self._ptt_enabled = True + self._ptt_active = False + self._last_release_time = 0.0 + self._double_pressed = False if use_whisplay and _WHISPLAY_AVAILABLE and _HAS_PIL: try: self._init_whisplay() @@ -304,8 +309,13 @@ class ChannelClient: lines = [f"Channel {self.selected_index + 1}"] if ch: lines.append(ch["hash"][:16] + "..") - lines += ["", "Press to hang up"] - fb = self._render("In Call", lines, accent=(60, 220, 120), footer="Press button to end") + if self._ptt_enabled: + lines += ["", "PTT: hold to talk", "Release to mute"] + footer = "Double-press: toggle" + else: + lines += ["", "Mic always-on", "Press to hang up"] + footer = "Double-press: toggle" + fb = self._render("In Call", lines, accent=(60, 220, 120), footer=footer) self.board.draw_image(0, 0, self.board.LCD_WIDTH, self.board.LCD_HEIGHT, fb) def _show_status(self, title, detail="", accent=(255, 180, 60)): @@ -317,14 +327,41 @@ class ChannelClient: def _on_button_press(self): self._press_time = time.time() + if self.state == self.STATE_IN_CALL: + if time.time() - self._last_release_time < DOUBLE_PRESS_THRESHOLD: + self._double_pressed = True + self._ptt_enabled = not self._ptt_enabled + if self._ptt_enabled: + self.telephone.mute_transmit() + else: + self.telephone.unmute_transmit() + if self.board: + self._show_in_call() + return + if self._ptt_enabled: + self._ptt_active = True + self.telephone.unmute_transmit() + if self.board: + self.board.set_rgb(255, 255, 0) def _on_button_release(self): - elapsed = time.time() - self._press_time + self._last_release_time = time.time() if self.state == self.STATE_IN_CALL: - self.telephone.hangup() - return + if self._double_pressed: + self._double_pressed = False + return + if self._ptt_enabled: + self._ptt_active = False + self.telephone.mute_transmit() + if self.board: + self.board.set_rgb(0, 255, 80) + return + else: + self.telephone.hangup() + return if self.state != self.STATE_READY: return + elapsed = time.time() - self._press_time if elapsed >= LONG_PRESS_THRESHOLD: if self.channels: self.board.set_rgb(255, 180, 0) @@ -457,6 +494,8 @@ class ChannelClient: f"\nCall established with " f"{RNS.prettyhexrep(remote_identity.hash)}" ) + if self._ptt_enabled: + self.telephone.mute_transmit() if self.board: self.board.set_rgb(0, 255, 80) self._show_in_call() @@ -465,6 +504,8 @@ class ChannelClient: was_in_call = self.state == self.STATE_IN_CALL was_calling = self.state == self.STATE_CALLING self.state = self.STATE_READY + self._ptt_active = False + self._double_pressed = False if was_in_call: print(f"\nCall ended") elif was_calling: