speculative fixes for hardware speech.

This commit is contained in:
Storm Dragon
2026-05-23 17:10:46 -04:00
parent d853e1b24d
commit d4b2fec1db
3 changed files with 57 additions and 4 deletions
+1 -1
View File
@@ -4,5 +4,5 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
version = "2026.05.22"
version = "2026.05.23"
code_name = "testing"
@@ -51,6 +51,8 @@ class hardware_serial_driver(speech_driver):
)
self._open_serial_port()
self._is_initialized = self.serial_port is not None
if not self._is_initialized:
raise RuntimeError("hardware speech device is not available")
if self._is_initialized:
self._stop_worker = False
self.worker_thread = threading.Thread(
@@ -124,6 +126,7 @@ class hardware_serial_driver(speech_driver):
self._debug(
"Hardware speech device not found",
debug.DebugLevel.ERROR,
on_any_level=True,
)
return
try:
@@ -140,10 +143,16 @@ class hardware_serial_driver(speech_driver):
termios.tcsetattr(port, termios.TCSANOW, attrs)
self.serial_port = port
self.device = device
self._debug(
f"Hardware speech device opened: {device}",
debug.DebugLevel.INFO,
on_any_level=True,
)
except OSError as error:
self._debug(
f"Hardware speech device open failed: {device}: {error}",
debug.DebugLevel.ERROR,
on_any_level=True,
)
self.serial_port = None
@@ -173,12 +182,13 @@ class hardware_serial_driver(speech_driver):
self._debug(
f"Hardware speech write failed: {error}",
debug.DebugLevel.ERROR,
on_any_level=True,
)
def _resolve_device(self, device):
if device and device != "auto":
return device
for pattern in ("/dev/ttyACM*", "/dev/ttyUSB*"):
for pattern in ("/dev/ttyACM*", "/dev/ttyUSB*", "/dev/ttyS*"):
matches = sorted(glob.glob(pattern))
if matches:
return matches[0]
@@ -205,10 +215,10 @@ class hardware_serial_driver(speech_driver):
value = max(0.0, min(1.0, value))
return int(round(minimum + value * (maximum - minimum)))
def _debug(self, message, level):
def _debug(self, message, level, on_any_level=False):
try:
self.env["runtime"]["DebugManager"].write_debug_out(
message, level
message, level, on_any_level=on_any_level
)
except Exception:
pass
@@ -1,6 +1,7 @@
import os
import select
import time
from unittest.mock import ANY
from unittest.mock import Mock
import pytest
@@ -103,6 +104,48 @@ def test_litetalk_driver_writes_settings_and_cancel(serial_pair):
speech_driver.shutdown()
def test_auto_device_detection_includes_classic_serial(
monkeypatch, serial_pair
):
master_fd, slave_name = serial_pair
def fake_glob(pattern):
if pattern == "/dev/ttyS*":
return [slave_name]
return []
monkeypatch.setattr(
"fenrirscreenreader.speechDriver.hardwareSerialDriver.glob.glob",
fake_glob,
)
speech_driver = litetalkDriver.driver()
speech_driver.initialize(build_environment("auto"))
try:
assert speech_driver.device == slave_name
speech_driver.speak("Serial")
assert read_available(master_fd, 7) == b"Serial\r"
finally:
speech_driver.shutdown()
def test_auto_device_detection_fails_when_no_serial_device(monkeypatch):
monkeypatch.setattr(
"fenrirscreenreader.speechDriver.hardwareSerialDriver.glob.glob",
lambda pattern: [],
)
speech_driver = litetalkDriver.driver()
with pytest.raises(RuntimeError, match="hardware speech device"):
speech_driver.initialize(build_environment("auto"))
debug_manager = speech_driver.env["runtime"]["DebugManager"]
debug_manager.write_debug_out.assert_called_with(
"Hardware speech device not found",
ANY,
on_any_level=True,
)
@pytest.mark.parametrize("driver_class", [doubletalkDriver, tripletalkDriver])
def test_litetalk_compatible_alias_drivers(driver_class, serial_pair):
speech_driver, master_fd = initialized_driver(driver_class, serial_pair)