More attempts at getting this usb dectalk working.

This commit is contained in:
Storm Dragon
2026-07-26 20:18:17 -04:00
parent 45bd33f756
commit c863245c24
11 changed files with 531 additions and 21 deletions
@@ -521,6 +521,7 @@ class SettingsManager:
"speechdDriver",
"genericDriver",
"dectalkDriver",
"dectalkUsbDriver",
"doubletalkDriver",
"litetalkDriver",
"tripletalkDriver",
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import threading
from fenrirscreenreader.core import debug
from fenrirscreenreader.speechDriver import dectalkDriver
class driver(dectalkDriver.driver):
vendor_id = 0x0DD0
product_id = 0x2001
interface = 0
speech_endpoint = 0x02
immediate_endpoint = 0x04
packet_size = 64
write_timeout = 1000
def initialize(self, environment):
self.env = environment
self._is_initialized = False
self.usb_device = None
self.usb_core = None
self.usb_util = None
self.immediate_lock = threading.Lock()
self._debug(
"DECtalk USB initialize: requested_device=0dd0:2001",
debug.DebugLevel.INFO,
on_any_level=True,
)
try:
self.usb_core, self.usb_util = self._load_usb_modules()
except ImportError as error:
self._debug(
f"DECtalk USB requires PyUSB: {error}",
debug.DebugLevel.ERROR,
on_any_level=True,
)
raise RuntimeError("DECtalk USB requires PyUSB") from error
try:
usb_device = self.usb_core.find(
idVendor=self.vendor_id,
idProduct=self.product_id,
)
except (
self.usb_core.NoBackendError,
self.usb_core.USBError,
OSError,
) as error:
self._debug(
f"DECtalk USB discovery failed: {error}",
debug.DebugLevel.ERROR,
on_any_level=True,
)
raise RuntimeError(
f"DECtalk USB discovery failed: {error}"
) from error
if usb_device is None:
self._debug(
"DECtalk USB device 0dd0:2001 was not found",
debug.DebugLevel.ERROR,
on_any_level=True,
)
raise RuntimeError("DECtalk USB device 0dd0:2001 was not found")
try:
try:
usb_device.get_active_configuration()
except self.usb_core.USBError:
usb_device.set_configuration()
self.usb_util.claim_interface(usb_device, self.interface)
except (self.usb_core.USBError, OSError) as error:
self._dispose_usb_device(usb_device)
self._debug(
f"DECtalk USB open failed: {error}",
debug.DebugLevel.ERROR,
on_any_level=True,
)
raise RuntimeError(f"DECtalk USB open failed: {error}") from error
self.usb_device = usb_device
self._is_initialized = True
self._stop_worker = False
self.worker_thread = threading.Thread(
target=self._worker,
daemon=True,
)
self.worker_thread.start()
self._debug(
"DECtalk USB device opened: 0dd0:2001",
debug.DebugLevel.INFO,
on_any_level=True,
)
def shutdown(self):
if not self._is_initialized:
return
self._stop_worker = True
self.clear_buffer()
self.text_queue.put(None)
if self.worker_thread:
self.worker_thread.join(timeout=0.5)
try:
with self.lock, self.immediate_lock:
usb_device = self.usb_device
self.usb_device = None
if usb_device is None:
return
try:
self.usb_util.release_interface(
usb_device,
self.interface,
)
except (self.usb_core.USBError, OSError) as error:
self._debug(
f"DECtalk USB interface release failed: {error}",
debug.DebugLevel.WARNING,
)
self._dispose_usb_device(usb_device)
finally:
self._is_initialized = False
def cancel(self):
if not self._is_initialized:
return
self.clear_buffer()
self._write_endpoint(
self.immediate_endpoint,
b"\x06",
"cancel",
write_lock=self.immediate_lock,
)
def _write_bytes(self, data, description="data"):
self._write_endpoint(self.speech_endpoint, data, description)
def _write_endpoint(
self,
endpoint,
data,
description,
write_lock=None,
):
if not data:
return
if write_lock is None:
write_lock = self.lock
with write_lock:
if self.usb_device is None:
return
try:
total_written = 0
while total_written < len(data):
chunk = data[
total_written : total_written + self.packet_size
]
bytes_written = self.usb_device.write(
endpoint,
chunk,
timeout=self.write_timeout,
)
if bytes_written != len(chunk):
raise OSError(
"USB write returned "
f"{bytes_written} of {len(chunk)} bytes"
)
total_written += bytes_written
preview = self._format_bytes_preview(data)
self._debug(
"DECtalk USB wrote "
f"{total_written} {description} bytes "
f"to endpoint 0x{endpoint:02x}: {preview}",
debug.DebugLevel.INFO,
on_any_level=True,
)
except (self.usb_core.USBError, OSError) as error:
self._debug(
f"DECtalk USB write failed: {error}",
debug.DebugLevel.ERROR,
on_any_level=True,
)
def _dispose_usb_device(self, usb_device):
try:
self.usb_util.dispose_resources(usb_device)
except Exception as error:
self._debug(
f"DECtalk USB resource disposal failed: {error}",
debug.DebugLevel.WARNING,
)
def _load_usb_modules(self):
import usb.core
import usb.util
return usb.core, usb.util