New hardware synth support added. Untested, so consider this experimental.
This commit is contained in:
@@ -79,7 +79,15 @@ Fenrir is a Linux screen reader. Linux is the only officially supported platform
|
||||
- python-speechd
|
||||
2. **genericDriver** - Generic subprocess speech driver
|
||||
- espeak or espeak-ng (or any TTS command)
|
||||
3. **debugDriver** - Debug speech driver for testing
|
||||
3. **dectalkDriver** - Serial DECtalk-compatible hardware speech driver
|
||||
- RPITalk gadget mode or a DECtalk-compatible serial device
|
||||
4. **litetalkDriver** - Serial LiteTalk-compatible hardware speech driver
|
||||
- RPITalk gadget mode or a LiteTalk-compatible serial device
|
||||
5. **doubletalkDriver** - Serial DoubleTalk LT-compatible hardware speech driver
|
||||
- DoubleTalk LT; does not support the internal DoubleTalk PC card
|
||||
6. **tripletalkDriver** - Serial TripleTalk-compatible hardware speech driver
|
||||
- External DB9 serial TripleTalk devices, or USB models that expose a tty serial device
|
||||
7. **debugDriver** - Debug speech driver for testing
|
||||
- No dependencies
|
||||
|
||||
|
||||
@@ -447,7 +455,13 @@ setting <action> [parameters]
|
||||
- `speech#volume=0.1-1.0` - Speech volume
|
||||
- `speech#voice=voice_name` - Voice selection (e.g., "en-us+f3")
|
||||
- `speech#module=module_name` - TTS module (e.g., "espeak-ng")
|
||||
- `speech#driver=driver_name` - Speech driver (speechdDriver/genericDriver)
|
||||
- `speech#driver=driver_name` - Speech driver (speechdDriver/genericDriver/dectalkDriver/litetalkDriver/doubletalkDriver/tripletalkDriver)
|
||||
- `speech#hardware_device=auto` - Hardware synth serial device for dectalkDriver/litetalkDriver
|
||||
- `speech#hardware_baud_rate=9600` - Hardware synth serial baud rate
|
||||
|
||||
USB hardware synths are supported only when Linux exposes them as a serial tty
|
||||
such as `/dev/ttyACM0` or `/dev/ttyUSB0`. A USB-only TripleTalk with no tty
|
||||
device would require a separate USB protocol driver.
|
||||
- `speech#auto_read_incoming=True/False` - Auto-read new text
|
||||
|
||||
*Sound Settings:*
|
||||
|
||||
@@ -35,9 +35,14 @@ progress_monitoring=True
|
||||
# Turn speech on or off:
|
||||
enabled=True
|
||||
|
||||
# Select speech driver, options are speechdDriver or genericDriver:
|
||||
# Select speech driver, options are speechdDriver, genericDriver,
|
||||
# dectalkDriver, litetalkDriver, doubletalkDriver, or tripletalkDriver:
|
||||
driver=speechdDriver
|
||||
#driver=genericDriver
|
||||
#driver=dectalkDriver
|
||||
#driver=litetalkDriver
|
||||
#driver=doubletalkDriver
|
||||
#driver=tripletalkDriver
|
||||
|
||||
# The rate selects how fast Fenrir will speak. Options range from 0, slowest, to 1.0, fastest.
|
||||
rate=0.5
|
||||
@@ -70,6 +75,20 @@ volume=1.0
|
||||
# Select the language you want Fenrir to use.
|
||||
#language=en
|
||||
|
||||
# Hardware speech synthesizer serial device.
|
||||
# Used by dectalkDriver, litetalkDriver, doubletalkDriver, and tripletalkDriver.
|
||||
# USB serial devices are supported if Linux exposes them as /dev/ttyACM*
|
||||
# or /dev/ttyUSB*. USB-only synths with no tty device need a separate driver.
|
||||
# auto checks /dev/ttyACM* first, then /dev/ttyUSB*.
|
||||
# Examples:
|
||||
# hardware_device=/dev/ttyACM0 # RPITalk USB gadget mode
|
||||
# hardware_device=/dev/ttyUSB0 # USB serial adapter
|
||||
# hardware_device=/dev/ttyS0 # built-in serial port
|
||||
hardware_device=auto
|
||||
|
||||
# Serial baud rate for hardware speech synthesizers.
|
||||
hardware_baud_rate=9600
|
||||
|
||||
# Read new text as it happens?
|
||||
auto_read_incoming=True
|
||||
|
||||
|
||||
@@ -238,6 +238,14 @@ speechdDriver - Speech-dispatcher (recommended)
|
||||
.IP \[bu] 4
|
||||
genericDriver - Command-line TTS (espeak, etc.)
|
||||
.IP \[bu] 4
|
||||
dectalkDriver - DECtalk-compatible serial hardware speech
|
||||
.IP \[bu] 4
|
||||
litetalkDriver - LiteTalk-compatible serial hardware speech
|
||||
.IP \[bu] 4
|
||||
doubletalkDriver - DoubleTalk LT-compatible serial hardware speech
|
||||
.IP \[bu] 4
|
||||
tripletalkDriver - TripleTalk-compatible serial hardware speech
|
||||
.IP \[bu] 4
|
||||
debugDriver - Debug/testing
|
||||
|
||||
.TP
|
||||
|
||||
+28
-1
@@ -1548,8 +1548,12 @@ enabled=True
|
||||
Values: on=`+True+`, off=`+False+`
|
||||
|
||||
# Select speech driver, options are speechdDriver (default),
|
||||
genericDriver or espeakDriver: driver=speechdDriver #driver=espeakDriver
|
||||
genericDriver, dectalkDriver, litetalkDriver, doubletalkDriver or tripletalkDriver: driver=speechdDriver
|
||||
#driver=genericDriver
|
||||
#driver=dectalkDriver
|
||||
#driver=litetalkDriver
|
||||
#driver=doubletalkDriver
|
||||
#driver=tripletalkDriver
|
||||
|
||||
This Selects the driver used to generate speech output.
|
||||
|
||||
@@ -1677,6 +1681,29 @@ the pico module:
|
||||
language=de-DE
|
||||
....
|
||||
|
||||
Hardware speech drivers use a serial device. The default `+auto+` checks
|
||||
`+/dev/ttyACM*+` first, then `+/dev/ttyUSB*+`. Set an explicit path for
|
||||
stable systems.
|
||||
|
||||
....
|
||||
hardware_device=auto
|
||||
hardware_device=/dev/ttyACM0
|
||||
hardware_device=/dev/ttyUSB0
|
||||
hardware_device=/dev/ttyS0
|
||||
....
|
||||
|
||||
Hardware speech drivers use 9600 baud by default.
|
||||
|
||||
....
|
||||
hardware_baud_rate=9600
|
||||
....
|
||||
|
||||
The `+doubletalkDriver+` targets DoubleTalk LT-style serial devices. It does
|
||||
not support the internal DoubleTalk PC ISA card.
|
||||
USB hardware speech synthesizers are supported only when Linux exposes them as
|
||||
a serial tty such as `+/dev/ttyACM0+` or `+/dev/ttyUSB0+`. USB-only TripleTalk
|
||||
models with no tty device need a separate driver.
|
||||
|
||||
Read new text as it occurs auto_read_incoming=True Values: on=`+True+`,
|
||||
off=`+False+`
|
||||
|
||||
|
||||
@@ -100,6 +100,8 @@ driver=speechdDriver
|
||||
rate=0.5
|
||||
pitch=0.5
|
||||
volume=1.0
|
||||
hardware_device=auto
|
||||
hardware_baud_rate=9600
|
||||
|
||||
[sound]
|
||||
enabled=True
|
||||
@@ -330,6 +332,19 @@ Fenrir automatically detects and provides audio feedback for progress indicators
|
||||
### Speech Drivers
|
||||
- **speechdDriver** - Speech-dispatcher (recommended)
|
||||
- **genericDriver** - Command-line TTS (espeak, etc.)
|
||||
- **dectalkDriver** - Serial DECtalk-compatible hardware speech
|
||||
- **litetalkDriver** - Serial LiteTalk-compatible hardware speech
|
||||
- **doubletalkDriver** - Serial DoubleTalk LT-compatible hardware speech
|
||||
- **tripletalkDriver** - Serial TripleTalk-compatible hardware speech
|
||||
|
||||
For hardware speech, set `speech#hardware_device` to `auto` or an explicit
|
||||
serial path. RPITalk gadget mode usually appears as `/dev/ttyACM0`; USB serial
|
||||
adapters usually appear as `/dev/ttyUSB0`; built-in serial ports may be
|
||||
`/dev/ttyS0`. The default baud rate is `9600`. `doubletalkDriver` targets
|
||||
DoubleTalk LT-style serial devices, not the internal DoubleTalk PC ISA card.
|
||||
USB TripleTalk devices work only if Linux exposes them as a serial tty such as
|
||||
`/dev/ttyACM0` or `/dev/ttyUSB0`; USB-only models with no tty device need a
|
||||
separate driver.
|
||||
|
||||
### Sound Drivers
|
||||
- **genericDriver** - Sox-based (default)
|
||||
|
||||
+21
-3
@@ -878,10 +878,13 @@ Turn speech on or off:
|
||||
enabled=True
|
||||
Values: on=''True'', off=''False''
|
||||
|
||||
# Select speech driver, options are speechdDriver (default), genericDriver or espeakDriver:
|
||||
# Select speech driver, options are speechdDriver (default), genericDriver, dectalkDriver, litetalkDriver, doubletalkDriver or tripletalkDriver:
|
||||
driver=speechdDriver
|
||||
#driver=espeakDriver
|
||||
#driver=genericDriver
|
||||
#driver=dectalkDriver
|
||||
#driver=litetalkDriver
|
||||
#driver=doubletalkDriver
|
||||
#driver=tripletalkDriver
|
||||
|
||||
Select the driver used to generate speech output.
|
||||
|
||||
@@ -890,7 +893,10 @@ Select the driver used to generate speech output.
|
||||
Available Drivers:
|
||||
* ''genericDriver'' using the generic driver, for Fenrir <1.5 this is not available
|
||||
* ''speechdDriver'' using speech-dispatcher, for Fenrir <1.5 just use ''speechd''
|
||||
* ''espeakDriver'' using the espeak directly, for Fenrir <1.5 just use ''espeak''
|
||||
* ''dectalkDriver'' using DECtalk-compatible serial hardware or RPITalk
|
||||
* ''litetalkDriver'' using LiteTalk-compatible serial hardware or RPITalk
|
||||
* ''doubletalkDriver'' using DoubleTalk LT-compatible serial hardware
|
||||
* ''tripletalkDriver'' using TripleTalk-compatible serial hardware
|
||||
|
||||
The rate selects how fast Fenrir will speak.
|
||||
rate=0.65
|
||||
@@ -921,6 +927,18 @@ Select the language you want Fenrir to use.
|
||||
language=english-us
|
||||
Values: Text, see your TTS synths documentation what is available.
|
||||
|
||||
Hardware speech drivers use a serial device. The default ''auto'' checks /dev/ttyACM* first, then /dev/ttyUSB*. Set an explicit path for stable systems.
|
||||
hardware_device=auto
|
||||
hardware_device=/dev/ttyACM0
|
||||
hardware_device=/dev/ttyUSB0
|
||||
hardware_device=/dev/ttyS0
|
||||
|
||||
Hardware speech drivers use 9600 baud by default.
|
||||
hardware_baud_rate=9600
|
||||
|
||||
The doubletalkDriver targets DoubleTalk LT-style serial devices. It does not support the internal DoubleTalk PC ISA card.
|
||||
USB hardware speech synthesizers are supported only when Linux exposes them as a serial tty such as /dev/ttyACM0 or /dev/ttyUSB0. USB-only TripleTalk models with no tty device need a separate driver.
|
||||
|
||||
Read new text as it occurs
|
||||
auto_read_incoming=True
|
||||
Values: on=''True'', off=''False''
|
||||
|
||||
@@ -127,6 +127,8 @@ class config_command:
|
||||
self.config.set("speech", "rate", "0.75")
|
||||
self.config.set("speech", "pitch", "0.5")
|
||||
self.config.set("speech", "volume", "1.0")
|
||||
self.config.set("speech", "hardware_device", "auto")
|
||||
self.config.set("speech", "hardware_baud_rate", "9600")
|
||||
|
||||
self.config.add_section("sound")
|
||||
self.config.set("sound", "driver", "genericDriver")
|
||||
|
||||
@@ -108,6 +108,8 @@ class command(config_command):
|
||||
"rate": "0.5",
|
||||
"pitch": "0.5",
|
||||
"volume": "1.0",
|
||||
"hardware_device": "auto",
|
||||
"hardware_baud_rate": "9600",
|
||||
"auto_read_incoming": "True",
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ settings_data = {
|
||||
"module": "",
|
||||
"voice": "en-us",
|
||||
"language": "",
|
||||
"hardware_device": "auto",
|
||||
"hardware_baud_rate": 9600,
|
||||
"auto_read_incoming": True,
|
||||
"read_numbers_as_digits": False,
|
||||
"rapid_update_threshold": 5,
|
||||
|
||||
@@ -508,6 +508,10 @@ class SettingsManager:
|
||||
valid_drivers = [
|
||||
"speechdDriver",
|
||||
"genericDriver",
|
||||
"dectalkDriver",
|
||||
"doubletalkDriver",
|
||||
"litetalkDriver",
|
||||
"tripletalkDriver",
|
||||
"dummyDriver",
|
||||
]
|
||||
if value not in valid_drivers:
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributors.
|
||||
|
||||
version = "2026.05.19"
|
||||
version = "2026.05.20"
|
||||
code_name = "testing"
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributors.
|
||||
|
||||
from fenrirscreenreader.speechDriver.hardwareSerialDriver import (
|
||||
hardware_serial_driver,
|
||||
)
|
||||
|
||||
|
||||
class driver(hardware_serial_driver):
|
||||
cancel_command = b"\x18"
|
||||
|
||||
def _speak_bytes(self, text):
|
||||
return self._clean_text(text).encode("ascii", errors="replace") + b"\x01"
|
||||
|
||||
def _rate_command(self, rate):
|
||||
return self._setting_command("ra", self._scale(rate, 75, 650))
|
||||
|
||||
def _pitch_command(self, pitch):
|
||||
return self._setting_command("dv ap", self._scale(pitch, 50, 180))
|
||||
|
||||
def _volume_command(self, volume):
|
||||
return self._setting_command("vo", self._scale(volume, 0, 100))
|
||||
|
||||
def _setting_command(self, command, value):
|
||||
return f"[:{command} {value}]".encode("ascii")
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributors.
|
||||
|
||||
from fenrirscreenreader.speechDriver.litetalkDriver import driver
|
||||
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributors.
|
||||
|
||||
import glob
|
||||
import os
|
||||
import termios
|
||||
import threading
|
||||
import tty
|
||||
from queue import Empty
|
||||
from queue import Queue
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
from fenrirscreenreader.core.speechDriver import speech_driver
|
||||
|
||||
|
||||
class SpeakQueue(Queue):
|
||||
def clear(self):
|
||||
try:
|
||||
while True:
|
||||
self.get_nowait()
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
|
||||
class hardware_serial_driver(speech_driver):
|
||||
cancel_command = b""
|
||||
default_baud_rate = 9600
|
||||
|
||||
def __init__(self):
|
||||
speech_driver.__init__(self)
|
||||
self.device = ""
|
||||
self.baud_rate = self.default_baud_rate
|
||||
self.serial_port = None
|
||||
self.text_queue = SpeakQueue()
|
||||
self.lock = threading.Lock()
|
||||
self.worker_thread = None
|
||||
self._stop_worker = False
|
||||
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self._is_initialized = False
|
||||
settings_manager = self.env["runtime"]["SettingsManager"]
|
||||
self.device = settings_manager.get_setting(
|
||||
"speech", "hardware_device"
|
||||
)
|
||||
self.baud_rate = settings_manager.get_setting_as_int(
|
||||
"speech", "hardware_baud_rate"
|
||||
)
|
||||
self._open_serial_port()
|
||||
self._is_initialized = self.serial_port is not None
|
||||
if self._is_initialized:
|
||||
self._stop_worker = False
|
||||
self.worker_thread = threading.Thread(
|
||||
target=self._worker, daemon=True
|
||||
)
|
||||
self.worker_thread.start()
|
||||
|
||||
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)
|
||||
self._close_serial_port()
|
||||
self._is_initialized = False
|
||||
|
||||
def speak(self, text, queueable=True, ignore_punctuation=False):
|
||||
if not self._is_initialized:
|
||||
return
|
||||
if not queueable:
|
||||
self.cancel()
|
||||
if not isinstance(text, str) or text == "":
|
||||
return
|
||||
self.text_queue.put(text)
|
||||
|
||||
def cancel(self):
|
||||
if not self._is_initialized:
|
||||
return
|
||||
self.clear_buffer()
|
||||
if self.cancel_command:
|
||||
self._write_bytes(self.cancel_command)
|
||||
|
||||
def clear_buffer(self):
|
||||
if not self._is_initialized:
|
||||
return
|
||||
self.text_queue.clear()
|
||||
|
||||
def set_rate(self, rate):
|
||||
if not self._is_initialized:
|
||||
return
|
||||
if not isinstance(rate, float):
|
||||
return
|
||||
self._write_bytes(self._rate_command(rate))
|
||||
|
||||
def set_pitch(self, pitch):
|
||||
if not self._is_initialized:
|
||||
return
|
||||
if not isinstance(pitch, float):
|
||||
return
|
||||
self._write_bytes(self._pitch_command(pitch))
|
||||
|
||||
def set_volume(self, volume):
|
||||
if not self._is_initialized:
|
||||
return
|
||||
if not isinstance(volume, float):
|
||||
return
|
||||
self._write_bytes(self._volume_command(volume))
|
||||
|
||||
def _worker(self):
|
||||
while not self._stop_worker:
|
||||
text = self.text_queue.get()
|
||||
if text is None:
|
||||
return
|
||||
self._write_bytes(self._speak_bytes(text))
|
||||
|
||||
def _open_serial_port(self):
|
||||
device = self._resolve_device(self.device)
|
||||
if not device:
|
||||
self._debug(
|
||||
"Hardware speech device not found",
|
||||
debug.DebugLevel.ERROR,
|
||||
)
|
||||
return
|
||||
try:
|
||||
port = os.open(device, os.O_RDWR | os.O_NOCTTY)
|
||||
tty.setraw(port)
|
||||
attrs = termios.tcgetattr(port)
|
||||
attrs[2] |= termios.CLOCAL | termios.CREAD
|
||||
baud_rate = self._termios_baud_rate(self.baud_rate)
|
||||
attrs[4] = baud_rate
|
||||
attrs[5] = baud_rate
|
||||
attrs[6][termios.VMIN] = 0
|
||||
attrs[6][termios.VTIME] = 0
|
||||
attrs[0] &= ~(termios.IXON | termios.IXOFF | termios.IXANY)
|
||||
termios.tcsetattr(port, termios.TCSANOW, attrs)
|
||||
self.serial_port = port
|
||||
self.device = device
|
||||
except OSError as error:
|
||||
self._debug(
|
||||
f"Hardware speech device open failed: {device}: {error}",
|
||||
debug.DebugLevel.ERROR,
|
||||
)
|
||||
self.serial_port = None
|
||||
|
||||
def _close_serial_port(self):
|
||||
with self.lock:
|
||||
if self.serial_port is None:
|
||||
return
|
||||
try:
|
||||
os.close(self.serial_port)
|
||||
except OSError as error:
|
||||
self._debug(
|
||||
f"Hardware speech device close failed: {error}",
|
||||
debug.DebugLevel.WARNING,
|
||||
)
|
||||
finally:
|
||||
self.serial_port = None
|
||||
|
||||
def _write_bytes(self, data):
|
||||
if not data:
|
||||
return
|
||||
with self.lock:
|
||||
if self.serial_port is None:
|
||||
return
|
||||
try:
|
||||
os.write(self.serial_port, data)
|
||||
except OSError as error:
|
||||
self._debug(
|
||||
f"Hardware speech write failed: {error}",
|
||||
debug.DebugLevel.ERROR,
|
||||
)
|
||||
|
||||
def _resolve_device(self, device):
|
||||
if device and device != "auto":
|
||||
return device
|
||||
for pattern in ("/dev/ttyACM*", "/dev/ttyUSB*"):
|
||||
matches = sorted(glob.glob(pattern))
|
||||
if matches:
|
||||
return matches[0]
|
||||
return ""
|
||||
|
||||
def _termios_baud_rate(self, baud_rate):
|
||||
baud_name = f"B{baud_rate}"
|
||||
if hasattr(termios, baud_name):
|
||||
return getattr(termios, baud_name)
|
||||
self._debug(
|
||||
f"Unsupported hardware speech baud rate {baud_rate}; using 9600",
|
||||
debug.DebugLevel.WARNING,
|
||||
)
|
||||
return termios.B9600
|
||||
|
||||
def _clean_text(self, text):
|
||||
text = text.replace("\r", " ").replace("\n", " ")
|
||||
return "".join(
|
||||
char if 0x20 <= ord(char) <= 0x7E else " "
|
||||
for char in text
|
||||
)
|
||||
|
||||
def _scale(self, value, minimum, maximum):
|
||||
value = max(0.0, min(1.0, value))
|
||||
return int(round(minimum + value * (maximum - minimum)))
|
||||
|
||||
def _debug(self, message, level):
|
||||
try:
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
message, level
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _speak_bytes(self, text):
|
||||
raise NotImplementedError
|
||||
|
||||
def _rate_command(self, rate):
|
||||
return b""
|
||||
|
||||
def _pitch_command(self, pitch):
|
||||
return b""
|
||||
|
||||
def _volume_command(self, volume):
|
||||
return b""
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributors.
|
||||
|
||||
from fenrirscreenreader.speechDriver.hardwareSerialDriver import (
|
||||
hardware_serial_driver,
|
||||
)
|
||||
|
||||
|
||||
class driver(hardware_serial_driver):
|
||||
cancel_command = b"\x18"
|
||||
|
||||
def _speak_bytes(self, text):
|
||||
return self._clean_text(text).encode("ascii", errors="replace") + b"\r"
|
||||
|
||||
def _rate_command(self, rate):
|
||||
return self._setting_command(self._scale(rate, 0, 9), b"S")
|
||||
|
||||
def _pitch_command(self, pitch):
|
||||
return self._setting_command(self._scale(pitch, 0, 99), b"P")
|
||||
|
||||
def _volume_command(self, volume):
|
||||
return self._setting_command(self._scale(volume, 0, 9), b"V")
|
||||
|
||||
def _setting_command(self, value, command):
|
||||
return b"\x01" + str(value).encode("ascii") + command
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributors.
|
||||
|
||||
from fenrirscreenreader.speechDriver.litetalkDriver import driver
|
||||
@@ -0,0 +1,124 @@
|
||||
import os
|
||||
import select
|
||||
import time
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from fenrirscreenreader.speechDriver import dectalkDriver
|
||||
from fenrirscreenreader.speechDriver import doubletalkDriver
|
||||
from fenrirscreenreader.speechDriver import litetalkDriver
|
||||
from fenrirscreenreader.speechDriver import tripletalkDriver
|
||||
|
||||
|
||||
def build_environment(device):
|
||||
settings_manager = Mock()
|
||||
settings_manager.get_setting.side_effect = (
|
||||
lambda section, setting: device
|
||||
if (section, setting) == ("speech", "hardware_device")
|
||||
else ""
|
||||
)
|
||||
settings_manager.get_setting_as_int.side_effect = (
|
||||
lambda section, setting: 9600
|
||||
if (section, setting) == ("speech", "hardware_baud_rate")
|
||||
else 0
|
||||
)
|
||||
return {
|
||||
"runtime": {
|
||||
"SettingsManager": settings_manager,
|
||||
"DebugManager": Mock(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def read_available(fd, expected_length, timeout=1.0):
|
||||
deadline = time.monotonic() + timeout
|
||||
data = b""
|
||||
while len(data) < expected_length and time.monotonic() < deadline:
|
||||
readable, _, _ = select.select([fd], [], [], 0.05)
|
||||
if readable:
|
||||
data += os.read(fd, 1024)
|
||||
return data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def serial_pair():
|
||||
master_fd, slave_fd = os.openpty()
|
||||
try:
|
||||
yield master_fd, os.ttyname(slave_fd)
|
||||
finally:
|
||||
os.close(master_fd)
|
||||
os.close(slave_fd)
|
||||
|
||||
|
||||
def initialized_driver(driver_class, serial_pair):
|
||||
master_fd, slave_name = serial_pair
|
||||
speech_driver = driver_class.driver()
|
||||
speech_driver.initialize(build_environment(slave_name))
|
||||
assert speech_driver._is_initialized
|
||||
return speech_driver, master_fd
|
||||
|
||||
|
||||
def test_dectalk_driver_speaks_printable_text(serial_pair):
|
||||
speech_driver, master_fd = initialized_driver(dectalkDriver, serial_pair)
|
||||
try:
|
||||
speech_driver.speak("Hello\nworld ☃")
|
||||
assert read_available(master_fd, 13) == b"Hello world \x01"
|
||||
finally:
|
||||
speech_driver.shutdown()
|
||||
|
||||
|
||||
def test_dectalk_driver_writes_settings_and_cancel(serial_pair):
|
||||
speech_driver, master_fd = initialized_driver(dectalkDriver, serial_pair)
|
||||
try:
|
||||
speech_driver.set_rate(1.0)
|
||||
speech_driver.set_pitch(0.0)
|
||||
speech_driver.set_volume(0.5)
|
||||
speech_driver.cancel()
|
||||
assert read_available(master_fd, 33) == (
|
||||
b"[:ra 650][:dv ap 50][:vo 50]\x18"
|
||||
)
|
||||
finally:
|
||||
speech_driver.shutdown()
|
||||
|
||||
|
||||
def test_litetalk_driver_speaks_printable_text(serial_pair):
|
||||
speech_driver, master_fd = initialized_driver(litetalkDriver, serial_pair)
|
||||
try:
|
||||
speech_driver.speak("Ready")
|
||||
assert read_available(master_fd, 6) == b"Ready\r"
|
||||
finally:
|
||||
speech_driver.shutdown()
|
||||
|
||||
|
||||
def test_litetalk_driver_writes_settings_and_cancel(serial_pair):
|
||||
speech_driver, master_fd = initialized_driver(litetalkDriver, serial_pair)
|
||||
try:
|
||||
speech_driver.set_rate(1.0)
|
||||
speech_driver.set_pitch(0.0)
|
||||
speech_driver.set_volume(0.5)
|
||||
speech_driver.cancel()
|
||||
assert read_available(master_fd, 9) == b"\x019S\x010P\x014V\x18"
|
||||
finally:
|
||||
speech_driver.shutdown()
|
||||
|
||||
|
||||
@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)
|
||||
try:
|
||||
speech_driver.speak("Alias")
|
||||
speech_driver.set_rate(1.0)
|
||||
assert read_available(master_fd, 10) == b"\x019SAlias\r"
|
||||
finally:
|
||||
speech_driver.shutdown()
|
||||
|
||||
|
||||
def test_hardware_driver_ignores_empty_and_non_string_text(serial_pair):
|
||||
speech_driver, master_fd = initialized_driver(dectalkDriver, serial_pair)
|
||||
try:
|
||||
speech_driver.speak("")
|
||||
speech_driver.speak(None)
|
||||
assert read_available(master_fd, 1, timeout=0.2) == b""
|
||||
finally:
|
||||
speech_driver.shutdown()
|
||||
@@ -71,6 +71,10 @@ class TestSpeechSettingsValidation:
|
||||
# Valid drivers
|
||||
self.manager._validate_setting_value("speech", "driver", "speechdDriver")
|
||||
self.manager._validate_setting_value("speech", "driver", "genericDriver")
|
||||
self.manager._validate_setting_value("speech", "driver", "dectalkDriver")
|
||||
self.manager._validate_setting_value("speech", "driver", "doubletalkDriver")
|
||||
self.manager._validate_setting_value("speech", "driver", "litetalkDriver")
|
||||
self.manager._validate_setting_value("speech", "driver", "tripletalkDriver")
|
||||
self.manager._validate_setting_value("speech", "driver", "dummyDriver")
|
||||
|
||||
# Invalid driver
|
||||
|
||||
Reference in New Issue
Block a user