Hopefully fixed a weird speech bug where some games could make it suddenly stop speaking.
This commit is contained in:
@@ -201,16 +201,38 @@ class SpeechServer(speechserver.SpeechServer):
|
|||||||
mode = self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle]
|
mode = self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle]
|
||||||
self._client.set_punctuation(mode)
|
self._client.set_punctuation(mode)
|
||||||
|
|
||||||
def _send_command(self, command, *args, **kwargs):
|
def _log_command_failure(self, command_name, error, will_retry=False):
|
||||||
try:
|
error_type = type(error).__name__
|
||||||
return command(*args, **kwargs)
|
action = " Resetting backend and retrying." if will_retry else ""
|
||||||
except speechd.SSIPCommunicationError:
|
msg = f"SPEECH DISPATCHER: {command_name} failed with {error_type}: {error!s}.{action}"
|
||||||
msg = "SPEECH DISPATCHER: Connection lost. Trying to reconnect."
|
debug.printMessage(debug.LEVEL_WARNING, msg, True)
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self.reset()
|
def _send_command(self, command, *args, treat_none_as_error=False, **kwargs):
|
||||||
return command(*args, **kwargs)
|
command_name = getattr(command, "__name__", repr(command))
|
||||||
except Exception:
|
|
||||||
pass
|
for attempt in range(2):
|
||||||
|
try:
|
||||||
|
result = command(*args, **kwargs)
|
||||||
|
if treat_none_as_error and result is None:
|
||||||
|
raise RuntimeError(f"{command_name} returned None")
|
||||||
|
return result
|
||||||
|
except speechd.SSIPCommunicationError as error:
|
||||||
|
self._log_command_failure(command_name, error, will_retry=attempt == 0)
|
||||||
|
except speechd.SSIPCommandError as error:
|
||||||
|
self._log_command_failure(command_name, error, will_retry=attempt == 0)
|
||||||
|
except RuntimeError as error:
|
||||||
|
if not treat_none_as_error:
|
||||||
|
raise
|
||||||
|
self._log_command_failure(command_name, error, will_retry=attempt == 0)
|
||||||
|
except Exception as error:
|
||||||
|
self._log_command_failure(command_name, error, will_retry=False)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if attempt == 0:
|
||||||
|
self.reset()
|
||||||
|
continue
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def _set_rate(self, acss_rate):
|
def _set_rate(self, acss_rate):
|
||||||
rate = int(2 * max(0, min(99, acss_rate)) - 98)
|
rate = int(2 * max(0, min(99, acss_rate)) - 98)
|
||||||
@@ -267,10 +289,10 @@ class SpeechServer(speechserver.SpeechServer):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sd_rate = self._send_command(self._client.get_rate)
|
sd_rate = self._send_command(self._client.get_rate, treat_none_as_error=True)
|
||||||
sd_pitch = self._send_command(self._client.get_pitch)
|
sd_pitch = self._send_command(self._client.get_pitch, treat_none_as_error=True)
|
||||||
sd_volume = self._send_command(self._client.get_volume)
|
sd_volume = self._send_command(self._client.get_volume, treat_none_as_error=True)
|
||||||
sd_language = self._send_command(self._client.get_language)
|
sd_language = self._send_command(self._client.get_language, treat_none_as_error=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
sd_rate = sd_pitch = sd_volume = sd_language = "(exception occurred)"
|
sd_rate = sd_pitch = sd_volume = sd_language = "(exception occurred)"
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from unittest import mock
|
|||||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||||
|
|
||||||
from cthulhu import speechdispatcherfactory
|
from cthulhu import speechdispatcherfactory
|
||||||
|
import speechd
|
||||||
|
|
||||||
|
|
||||||
class SpeechDispatcherInterruptRegressionTests(unittest.TestCase):
|
class SpeechDispatcherInterruptRegressionTests(unittest.TestCase):
|
||||||
@@ -38,6 +39,56 @@ class SpeechDispatcherInterruptRegressionTests(unittest.TestCase):
|
|||||||
server._cancel.assert_not_called()
|
server._cancel.assert_not_called()
|
||||||
server._speak.assert_called_once_with("next", None)
|
server._speak.assert_called_once_with("next", None)
|
||||||
|
|
||||||
|
def test_send_command_logs_and_recovers_from_command_errors(self):
|
||||||
|
server = self._make_server()
|
||||||
|
server.reset = mock.Mock()
|
||||||
|
command = mock.Mock(
|
||||||
|
side_effect=[
|
||||||
|
speechd.SSIPCommandError(500, "bad ssml", "bad ssml"),
|
||||||
|
"recovered",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
with mock.patch.object(speechdispatcherfactory.debug, "printMessage") as print_message:
|
||||||
|
result = speechdispatcherfactory.SpeechServer._send_command(server, command, "payload")
|
||||||
|
|
||||||
|
self.assertEqual("recovered", result)
|
||||||
|
server.reset.assert_called_once_with()
|
||||||
|
self.assertEqual(2, command.call_count)
|
||||||
|
logged_messages = [call.args[1] for call in print_message.call_args_list]
|
||||||
|
self.assertTrue(any("SSIPCommandError" in message for message in logged_messages))
|
||||||
|
|
||||||
|
def test_debug_sd_values_logs_when_backend_state_queries_return_none(self):
|
||||||
|
server = self._make_server()
|
||||||
|
server._current_voice_properties = {}
|
||||||
|
server._id = "default"
|
||||||
|
server.reset = mock.Mock()
|
||||||
|
server._send_command = speechdispatcherfactory.SpeechServer._send_command.__get__(
|
||||||
|
server, speechdispatcherfactory.SpeechServer
|
||||||
|
)
|
||||||
|
|
||||||
|
fake_app = mock.Mock()
|
||||||
|
fake_app.settingsManager.getSetting.return_value = speechdispatcherfactory.settings.PUNCTUATION_STYLE_MOST
|
||||||
|
fake_script = mock.Mock()
|
||||||
|
fake_script.utilities.adjustForDigits.side_effect = lambda text: text
|
||||||
|
fake_state = mock.Mock(activeScript=fake_script)
|
||||||
|
|
||||||
|
server._client.get_rate.return_value = None
|
||||||
|
server._client.get_pitch.return_value = None
|
||||||
|
server._client.get_volume.return_value = None
|
||||||
|
server._client.get_language.return_value = None
|
||||||
|
|
||||||
|
with mock.patch.object(speechdispatcherfactory, "cthulhu") as fake_cthulhu, \
|
||||||
|
mock.patch.object(speechdispatcherfactory, "cthulhu_state", fake_state), \
|
||||||
|
mock.patch.object(speechdispatcherfactory.debug, "debugLevel", speechdispatcherfactory.debug.LEVEL_INFO), \
|
||||||
|
mock.patch.object(speechdispatcherfactory.debug, "printMessage") as print_message:
|
||||||
|
fake_cthulhu.cthulhuApp = fake_app
|
||||||
|
speechdispatcherfactory.SpeechServer._debug_sd_values(server, "prefix")
|
||||||
|
|
||||||
|
logged_messages = [str(call.args[1]) for call in print_message.call_args_list]
|
||||||
|
self.assertTrue(any("returned None" in message for message in logged_messages))
|
||||||
|
self.assertEqual(4, server.reset.call_count)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user