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
11 changes: 8 additions & 3 deletions carwatch/listen.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,9 +430,14 @@ def _speak(text: str) -> bool:
# Bond can exist while the A2DP link is down; connect is cheap
# when already connected (car-speak.sh does the same).
if _bt_pcm_mac("a2dpsrc/sink") != car:
subprocess.run(["bluetoothctl", "connect", car],
capture_output=True, timeout=10)
time.sleep(2)
# An absent car makes connect hang to the timeout; that is
# a normal answer here, not a reason to drop the reply.
try:
subprocess.run(["bluetoothctl", "connect", car],
capture_output=True, timeout=10)
time.sleep(2)
except subprocess.TimeoutExpired:
pass
# Only a LIVE link counts. The saved MAC outlives the car: on the
# kitchen table (26 Sep, Jabra) every answer went to the absent
# car's A2DP and the speakerphone on the desk stayed silent.
Expand Down
5 changes: 5 additions & 0 deletions scripts/restart-when-quiet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ sleep 1
systemctl daemon-reload
systemctl enable --now carwatch-chat carwatch-presence
systemctl restart $UNITS
# The voice listener is optional (USB mic installs only), so it rides along
# as try-restart: restarted when running, skipped when absent. Left out, it
# kept executing pre-update listen.py (26 Sep: #71 merged and pulled, the
# Jabra still silent until a hand restart).
systemctl try-restart carwatch-listen 2>/dev/null || true
echo "restart-when-quiet: restarted $UNITS after $(( $(date +%s) - start ))s"
23 changes: 23 additions & 0 deletions tests/test_speak_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,29 @@ def run(cmd, **kw):
def test_absent_car_falls_back_to_usb(self):
self.assertEqual(self._route(None), ["plughw:0,0"])

def test_connect_timeout_still_falls_back_to_usb(self):
# 26 Sep on vadelma: connect to the absent car hung past 10 s and the
# TimeoutExpired aborted the whole reply.
played = []

def run(cmd, **kw):
if cmd[0] == "bluetoothctl":
raise self.L.subprocess.TimeoutExpired(cmd, 10)
if cmd[0] == "aplay":
played.append(cmd[cmd.index("-D") + 1])
return mock.Mock(returncode=0)

from carwatch import voiceroom
with mock.patch.object(voiceroom, "tts_wav", return_value=self.wav), \
mock.patch.object(self.L, "_car_a2dp_mac", return_value=CAR), \
mock.patch.object(self.L, "_bt_pcm_mac", return_value=None), \
mock.patch.object(self.L, "_usb_audio_device", return_value="plughw:0,0"), \
mock.patch.object(self.L, "_echo_tail_sec", return_value=0.0), \
mock.patch.object(self.L.subprocess, "run", side_effect=run), \
mock.patch.object(self.L.time, "sleep"):
self.assertTrue(self.L._speak("hello"))
self.assertEqual(played, ["plughw:0,0"])

def test_connected_car_still_wins(self):
self.assertEqual(self._route(CAR), [f"bluealsa:DEV={CAR},PROFILE=a2dp"])

Expand Down
Loading