Skip to content

Commit

Permalink
Added change engine mic state and mode on caster startup for DNS (#815)
Browse files Browse the repository at this point in the history
  • Loading branch information
LexiconCode authored May 30, 2020
1 parent f795116 commit bd1f07c
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 15 deletions.
18 changes: 7 additions & 11 deletions _caster.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,29 @@
from castervoice.lib.ctrl.dependencies import DependencyMan # requires nothing
DependencyMan().initialize()

from castervoice.lib import settings
from castervoice.lib import settings # requires DependencyMan to be initialized
settings.initialize()

from castervoice.lib.ctrl.updatecheck import UpdateChecker # requires settings/dependencies
UpdateChecker().initialize()

from dragonfly import get_engine
from castervoice.lib.ctrl.configure_engine import EngineConfigEarly, EngineConfigLate
EngineConfigEarly() # requires settings/dependencies

_NEXUS = None

# get_engine() is used here as a workaround for running Natlink inprocess
if get_engine().name in ["sapi5shared", "sapi5", "sapi5inproc"]:
settings.WSR = True
from castervoice.rules.ccr.standard import SymbolSpecs
SymbolSpecs.set_cancel_word("escape")

from castervoice.lib import control

if control.nexus() is None:
if control.nexus() is None: # Initialize Caster State
from castervoice.lib.ctrl.mgr.loading.load.content_loader import ContentLoader
from castervoice.lib.ctrl.mgr.loading.load.content_request_generator import ContentRequestGenerator
_crg = ContentRequestGenerator()
_content_loader = ContentLoader(_crg)
control.init_nexus(_content_loader)

EngineConfigLate() # Requires grammars to be loaded and nexus

if settings.SETTINGS["sikuli"]["enabled"]:
from castervoice.asynch.sikuli import sikuli_controller
sikuli_controller.get_instance().bootstrap_start_server_proxy()

print("\n*- Starting " + settings.SOFTWARE_NAME + " -*")
print("\n*- Starting " + settings.SOFTWARE_NAME + " -*")
65 changes: 65 additions & 0 deletions castervoice/lib/ctrl/configure_engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# TODO: Create and utilize base class. These classes should be initialized only once.
# TODO: Add a function for end-user user to overload in EngineConfigEarly and EngineConfigLate

class EngineConfigEarly():
"""
Initializes engine specific customizations before Nexus initializes.
Grammars are not loaded
"""
from castervoice.lib import settings
from dragonfly import get_engine
engine = get_engine().name # get_engine used as a workaround for running Natlink inprocess

def __init__(self):
self.set_cancel_word()

def set_cancel_word(self):
"""
Defines SymbolSpecs cancel word as "escape" for windows speech recognition (WSR)
"""
if self.engine in ["sapi5shared", "sapi5", "sapi5inproc"]:
self.settings.WSR = True
from castervoice.rules.ccr.standard import SymbolSpecs
SymbolSpecs.set_cancel_word("escape")


class EngineConfigLate():
"""
Initializes engine specific customizations after Nexus has initialized.
Grammars are loaded into engine.
"""
from castervoice.lib import settings
from castervoice.lib import printer
from dragonfly import get_current_engine
engine = get_current_engine().name

def __init__(self):
from castervoice.lib import control # Access to Nexus instance
self.instannce = control.nexus()._engine_modes_manager
if self.engine != "text":
self.set_default_mic_mode()
self.set_engine_default_mode()


def set_default_mic_mode(self):
"""
Sets the microphone state on Caster startup.
"""
# Only DNS supports mic_state 'off'. Substituts `sleep` mode on other engines"
if self.settings.SETTINGS["engine"]["default_mic"]: # Default is `False`
default_mic_state = self.settings.SETTINGS["engine"]["mic_mode"] # Default is `on`
if self.engine != "natlink" and default_mic_state == "off":
default_mic_state == "sleep"
self.instannce.set_mic_mode(default_mic_state)


def set_engine_default_mode(self):
"""
Sets the engine mode on Caster startup.
"""
# Only DNS supports 'normal'. Substituts `command` mode on other engines"
if self.settings.SETTINGS["engine"]["default_engine_mode"]: # Default is `False`
default_mode = self.settings.SETTINGS["engine"]["engine_mode"] # Default is `normal`
if self.engine != "natlink" and default_mode == "normal":
default_mode == "command"
self.instannce.set_engine_mode(mode=default_mode, state=True)
104 changes: 104 additions & 0 deletions castervoice/lib/ctrl/mgr/engine_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from dragonfly import get_engine, get_current_engine
from castervoice.lib import settings, printer

# TODO: Implement a grammar exclusivity for non-DNS engines in a separate class

class EngineModesManager(object):
"""
Manages engine modes and microphone states using backend engine API and through dragonfly grammar exclusivity.
"""
engine = get_current_engine().name
if engine == 'natlink':
import natlink

engine_modes = {"normal":0, "command":2, "dictation":1,"numbers":3, "spell":4}
mic_modes = ["on", "sleeping", "off"]
engine_state = None
previous_engine_state = None
mic_state = None

def initialize(self):
# Remove "normal" and "off" from 'states' for non-DNS based engines.
if self.engine != 'natlink':
self.engine_modes.pop("normal", 0)
self.mic_modes.remove("off")
# Sets 1st index key ("normal" or "command") depending on engine type as default mode
self.engine_state = self.previous_engine_state = next(iter(self.engine_modes.keys()))


def set_mic_mode(self, mode):
"""
Changes the engine microphone mode
'on': mic is on
'sleeping': mic from the sleeping and can be woken up by command
'off': mic off and cannot be turned back on by voice. (DNS Only)
"""
if mode in self.mic_modes:
self.mic_state = mode
if self.engine == 'natlink':
self.natlink.setMicState(mode)
# From here other engines use grammar exclusivity to re-create the sleep mode
#if mode != "off": # off does not need grammar exclusivity
#pass
# TODO: Implement mic mode sleep mode using grammar exclusivity. This should override DNS is built in sleep grammar but kept in sync automatically with natlink.setmic_state
else:
printer.out("Caster: 'set_mic_mode' is not implemented for '{}'".format(self.engine))
else:
printer.out("Caster: '{}' is not a valid. set_mic_state modes are: 'off' - DNS Only, 'on', 'sleeping'".format(mode))


def get_mic_mode(self):
"""
Returns mic state.
mode: string
"""
return self.mic_state


def set_engine_mode(self, mode=None, state=True):
"""
Sets the engine mode so that only certain types of commands/dictation are recognized.
'state': Bool - enable/disable mode.
'True': replaces current mode (Default)
'False': restores previous mode
'normal': dictation and command (Default: DNS only)
'dictation': Dictation only
'command': Commands only (Default: Other engines)
'numbers': Numbers only
'spell': Spelling only
"""
if state and mode is not None:
# Track previous engine state
# TODO: Timer to synchronize natlink.getMicState() with mengine_state in case of changed by end-user via DNS GUI.
self.previous_engine_state = self.engine_state
else:
if not state:
# Restore previous mode
mode = self.previous_engine_state
else:
printer.out("Caster: set_engine_mode: 'State' cannot be 'True' with a undefined a 'mode'")

if mode in self.engine_modes:
if self.engine == 'natlink':
try:
self.natlink.execScript("SetRecognitionMode {}".format(self.engine_modes[mode])) # engine_modes[mode] is an integer
self.engine_state = mode
except Exception as e:
printer.out("natlink.execScript failed \n {}".format(e))
else:
# TODO: Implement mode exclusivity. This should override DNS is built in sleep grammar but kept in sync automatically with natlinks SetRecognitionMode
# Once DNS enters its native mode exclusivity will override the any native DNS mode except for normal/command mode.
if self.engine == 'text':
self.engine_state = mode
else:
printer.out("Caster: 'set_engine_mode' is not implemented for '{}'".format(self.engine))
else:
printer.out("Caster: '{}' mode is not a valid. set_engine_mode: Modes: 'normal'- DNS Only, 'dictation', 'command', 'numbers', 'spell'".format(mode))


def get_engine_mode(self):
"""
Returns engine mode.
mode: str
"""
return self.engine_state
6 changes: 5 additions & 1 deletion castervoice/lib/ctrl/nexus.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from castervoice.lib.ctrl.mgr.validation.rules.rule_validation_delegator import CCRRuleValidationDelegator
from castervoice.lib.merge.ccrmerging2.ccrmerger2 import CCRMerger2
from castervoice.lib.merge.ccrmerging2.merging.classic_merging_strategy import ClassicMergingStrategy

from castervoice.lib.ctrl.mgr.engine_manager import EngineModesManager

class Nexus:
def __init__(self, content_loader):
Expand Down Expand Up @@ -79,9 +79,13 @@ def __init__(self, content_loader):
self._content_loader, hooks_runner, rules_config, smrc, mapping_rule_maker,
transformers_runner)

'''tracks engine grammar exclusivity and mic states -- TODO Grammar exclusivity should be managed through grammar manager'''
self._engine_modes_manager = EngineModesManager()

'''ACTION TIME:'''
self._load_and_register_all_content(rules_config, hooks_runner, transformers_runner)
self._grammar_manager.initialize()
self._engine_modes_manager.initialize()

def _load_and_register_all_content(self, rules_config, hooks_runner, transformers_runner):
"""
Expand Down
8 changes: 8 additions & 0 deletions castervoice/lib/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,14 @@ def _get_defaults():
SYSTEM_INFORMATION["hidden console binary"],
},

# Speech recognition engine settings
"engine": {
"default_engine_mode": False,
"engine_mode": "normal",
"default_mic": False,
"mic_mode": "on"
},

# python settings
"python": {
"automatic_settings":
Expand Down
Empty file.
31 changes: 28 additions & 3 deletions docs/readthedocs/Caster_Settings/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,39 @@ Explanation of `settings.toml`. Caster settings can be edited in the following w

- The settings file can be summoned manually by saying `bring me caster settings file` to your default editor for `.toml` files


The following is an `example.toml` settings file with comments explaining the various settings. Some of the settings fields have been truncated for brevity as noted in the comments.

## Settings

Explanation of `settings.toml`. Caster settings can be edited in the following ways:

- Edited through a GUI. Say `launch caster settings`. Once done, say `complete` to save the file

- The settings file can be summoned manually by saying `bring me caster settings file` to your default editor for `.toml` files

The following is an `example.toml` settings file with comments explaining the various settings. Some of the settings fields have been truncated for brevity as noted in the comments.



```toml
[Tree_Node_Path] # Paths for Node Tree Rules
SM_CSS_TREE_PATH = "C:\\Users\\Main\\AppData\\Local\\caster\\data\\sm_css_tree.toml"

[engine] # controls configuration of engine. Currently limited only for DNS
# 'on': mic is on # default
# 'sleeping': mic from the sleeping and can be woken up by command
# 'off': mic off and cannot be turned back on by voice. (DNS Only)
default_engine_mode = true
engine_mode = "normal"

# Valid mic_mode options
# 'normal': dictation and command (Default: DNS only)
# 'dictation': Dictation only
# 'command': Commands only (Default: Other engines)
# 'numbers': Numbers only
# 'spell': Spelling only
default_mic = true
mic_mode = "on"

[formats] # Truncated - Control setting dictation formatting per programming language.
# Legend - Represents text formatting (capitalization and spacing) rules.
Expand Down Expand Up @@ -42,7 +67,6 @@ text_format = [3, 1]
# 3 snake - words_with_underscores
# 4 pebble - words.with.fullstops
# 5 incline - words/with/slashes
...

[hooks]
default_hooks = ["PrinterHook"] # Default hooks. Do not edit.
Expand Down Expand Up @@ -89,6 +113,7 @@ version = "python"
[sikuli]
enabled = false # Toggle sikuli third-party integration
version = "" # Sikuli Version

```


```
35 changes: 35 additions & 0 deletions tests/lib/ctrl/test_EngineModesManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from unittest import TestCase

from castervoice.lib.ctrl.mgr.engine_manager import EngineModesManager

class TestEngineModesManager(TestCase):
_Manager = EngineModesManager()
EngineModesManager().initialize()

def test_set_engine_mode(self):
self._Manager.set_engine_mode(mode="numbers", state=True)
self.assertEqual("numbers", self._Manager.get_engine_mode())

def test_get_previous_engine_state(self):
self._Manager.set_engine_mode(mode="spell", state=True)
self._Manager.set_engine_mode(mode="dictation", state=True)
self.assertEqual("spell", self._Manager.previous_engine_state)

def test_restore_previous_engine_mode(self):
self._Manager.set_engine_mode(mode="spell", state=True)
self._Manager.set_engine_mode(mode="dictation", state=True)
self._Manager.set_engine_mode(state=False)
self.assertEqual("spell", self._Manager.get_engine_mode())

def test_fail_engine_mode_change(self):
self._Manager.set_engine_mode(mode="numbers", state=True)
self._Manager.set_engine_mode(state=True)
self.assertEqual("numbers", self._Manager.get_engine_mode())

def test_fail_invalid_mode(self):
self._Manager.set_engine_mode(mode="invalid", state=True)
self.assertNotEqual("invalid", self._Manager.get_engine_mode())

def test_set_mic_mode(self):
self._Manager.set_mic_mode("sleeping")
self.assertEqual("sleeping", self._Manager.get_mic_mode())

0 comments on commit bd1f07c

Please sign in to comment.