Skip to content

Commit

Permalink
Implemented microphone modes (#853)
Browse files Browse the repository at this point in the history
  • Loading branch information
LexiconCode authored Oct 10, 2020
1 parent d4bbd89 commit 8b0dd35
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 84 deletions.
Binary file modified CasterQuickReference.pdf
Binary file not shown.
2 changes: 1 addition & 1 deletion castervoice/lib/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class CCRType(object):
# Original Caster CCR "core" set:
"Alphabet", "Navigation", "NavigationNon", "Numbers", "Punctuation",
# Rules which were split out of _caster.py:
"CasterRule", "HardwareRule", "MouseAlternativesRule", "WindowManagementRule",
"CasterRule", "CasterMicRule", "HardwareRule", "MouseAlternativesRule", "WindowManagementRule",
# Alternate mouse grid controls:
"DouglasGridRule", "RainbowGridRule", "SudokuGridRule",
# HMC GUI control rules:
Expand Down
27 changes: 12 additions & 15 deletions castervoice/lib/ctrl/configure_engine.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 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():
class EngineConfigEarly:
"""
Initializes engine specific customizations before Nexus initializes.
Grammars are not loaded
Expand All @@ -23,43 +23,40 @@ def set_cancel_word(self):
SymbolSpecs.set_cancel_word("escape")


class EngineConfigLate():
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
from castervoice.lib.ctrl.mgr.engine_manager import EngineModesManager
self.EngineModesManager = EngineModesManager.initialize()
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)

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.EngineModesManager.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`
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)
default_mode = "command"
self.EngineModesManager.set_engine_mode(mode=default_mode, state=True)
196 changes: 135 additions & 61 deletions castervoice/lib/ctrl/mgr/engine_manager.py
Original file line number Diff line number Diff line change
@@ -1,104 +1,178 @@
from dragonfly import get_engine, get_current_engine
from castervoice.lib import settings, printer
from dragonfly import get_engine, get_current_engine, FuncContext, Function, MappingRule, Grammar, Choice, Dictation
from castervoice.lib import printer

engine = get_current_engine().name
if engine == 'natlink':
import natlink

# 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_modes = {"normal": 0, "command": 2,
"dictation": 1, "numbers": 3, "spell": 4}
mic_modes = {"on": 5, "sleeping": 6, "off": 7}
engine_state = None
previous_engine_state = None
mic_state = None
timer_callback = None

def initialize(self):
@classmethod
def initialize(cls):
# 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")
if engine != 'natlink':
cls.engine_modes.pop("normal", 0)
cls.mic_modes.pop("off", 7)
#if self.engine == 'natlink':
# 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()))

cls.engine_state = cls.previous_engine_state = next(
iter(cls.engine_modes.keys()))
# Timer to synchronize natlink.getMicState/SetRecognitionMode with mode_state in case of changed by end-user via DNS GUI.
if engine == 'natlink' and cls.timer_callback is None:
cls.timer_callback = get_current_engine().create_timer(callback=cls._sync_mode, interval=1)
cls.timer_callback.start()

def set_mic_mode(self, mode):
@classmethod
def set_mic_mode(cls, 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)
:param mode: str
'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. (DPI 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))
if mode in cls.mic_modes:
cls.mic_state = mode
if engine == 'natlink':
natlink.setMicState(mode)
# Overrides DNS/DPI is built in sleep grammar
ExclusiveManager(mode, modetype="mic_mode")
else:
printer.out("Caster: '{}' is not a valid. set_mic_state modes are: 'off' - DNS Only, 'on', 'sleeping'".format(mode))
printer.out(
"Caster: '{}' is not valid. set_mic_state modes are: 'off' - DPI Only, 'on', 'sleeping'".format(mode))


def get_mic_mode(self):
@classmethod
def get_mic_mode(cls):
"""
Returns mic state.
mode: string
mode: str
"""
return self.mic_state

return cls.mic_state

def set_engine_mode(self, mode=None, state=True):
@classmethod
def set_engine_mode(cls, mode=None, state=True):
"""
Sets the engine mode so that only certain types of commands/dictation are recognized.
'state': Bool - enable/disable mode.
:param 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
:param mode: str
'normal': dictation and command (Default: DPI 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
cls.previous_engine_state = cls.engine_state
else:
if not state:
# Restore previous mode
mode = self.previous_engine_state
mode = cls.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':
printer.out(
"Caster: set_engine_mode: 'State' cannot be 'True' with a undefined a 'mode'")

if mode in cls.engine_modes:
if engine == 'natlink':
try:
self.natlink.execScript("SetRecognitionMode {}".format(self.engine_modes[mode])) # engine_modes[mode] is an integer
self.engine_state = mode
natlink.execScript("SetRecognitionMode {}".format(
cls.engine_modes[mode])) # engine_modes[mode] is an integer
cls.engine_state = mode
ExclusiveManager(mode, modetype="engine_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
# TODO: Implement set_engine_mode exclusivity. This should override DPI is built mode but kept in sync automatically.
if engine == 'text':
cls.engine_state = mode
else:
printer.out("Caster: 'set_engine_mode' is not implemented for '{}'".format(self.engine))
printer.out(
"Caster: 'set_engine_mode' is not implemented for '{}'".format(engine))
else:
printer.out("Caster: '{}' mode is not a valid. set_engine_mode: Modes: 'normal'- DNS Only, 'dictation', 'command', 'numbers', 'spell'".format(mode))

printer.out(
"Caster: '{}' mode is not valid. set_engine_mode: Modes: 'normal'- DPI Only, 'dictation', 'command', 'numbers', 'spell'".format(mode))

def get_engine_mode(self):
@classmethod
def get_engine_mode(cls):
"""
Returns engine mode.
mode: str
mode: str
"""
return cls.engine_state

@classmethod
def _sync_mode(cls):
"""
Synchronizes Caster exclusivity modes an with DNS/DPI GUI built-in modes state.
"""
return self.engine_state
# TODO: Implement set_engine_mode logic with modes not just mic_state.
mic_state = cls.get_mic_mode()
if mic_state is None:
cls.mic_state = natlink.getMicState()
else:
if natlink.getMicState() != mic_state:
cls.set_mic_mode(natlink.getMicState())


class ExclusiveManager:
"""
Loads and switches exclusivity for caster modes
:param mode: str
:param modetype: 'mic_mode' or 'engine_mode' str
"""
# TODO: Implement set_engine_mode exclusivity with mode rules.
# TODO: Implement timer for sleep mode.
# TODO: Implement hotkey for microphone on-off
sleep_grammar = None
sleeping = False

sleep_rule = MappingRule(
name="sleep_rule",
mapping={
"caster <mic_mode>": Function(lambda mic_mode: EngineModesManager.set_mic_mode(mode=mic_mode)),
"<text>": Function(lambda text: False)
},
extras=[Choice("mic_mode", {
"off": "off",
"on": "on",
"sleep": "sleeping",
}),
Dictation("text")],
context=FuncContext(lambda: ExclusiveManager.sleeping),
)

def __init__(self, mode, modetype):
if modetype == "mic_mode":
if not isinstance(ExclusiveManager.sleep_grammar, Grammar):
grammar = ExclusiveManager.sleep_grammar = Grammar("sleeping")
grammar.add_rule(self.sleep_rule)
grammar.load()
if mode == "sleeping":
self.set_exclusive(state=True)
printer.out("Caster: Microphone is sleeping")
if mode == "on":
self.set_exclusive(state=False)
printer.out("Caster: Microphone is on")
if mode == "off":
printer.out("Caster: Microphone is off")
else:
printer.out("{}, {} not implemented".format(mode, modetype))

def set_exclusive(self, state):
grammar = ExclusiveManager.sleep_grammar
ExclusiveManager.sleeping = state
grammar.set_exclusive(state)
get_engine().process_grammars_context()
5 changes: 0 additions & 5 deletions castervoice/lib/ctrl/nexus.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
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,13 +78,9 @@ 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
2 changes: 0 additions & 2 deletions castervoice/rules/apps/speech_engine/dragon_rules/dragon.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

class DragonRule(MappingRule):
mapping = {
'(lock Dragon | deactivate)':
R(Playback([(["go", "to", "sleep"], 0.0)])),
'(number|numbers) mode':
R(Playback([(["numbers", "mode", "on"], 0.0)])),
'spell mode':
Expand Down
24 changes: 24 additions & 0 deletions castervoice/rules/core/engine_manager_rules/mic_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from dragonfly import Dictation, MappingRule, Choice, Function
from castervoice.lib.actions import Text

from castervoice.lib.ctrl.mgr.rule_details import RuleDetails
from castervoice.lib.merge.state.short import R

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

class CasterMicRule(MappingRule):
mapping = {
"caster <mic_mode>": Function(lambda mic_mode: EngineModesManager.set_mic_mode(mode=mic_mode)),
}
extras = [
Choice(
"mic_mode", {
"off": "off",
"on": "on",
"sleep": "sleeping",
}),
]
defaults = {}

def get_rule():
return CasterMicRule, RuleDetails(name="caster mic mode", transformer_exclusion=True)
Binary file modified docs/CasterQuickReference.pdf
Binary file not shown.
2 changes: 2 additions & 0 deletions docs/CasterQuickReference.tex
Original file line number Diff line number Diff line change
Expand Up @@ -437,11 +437,13 @@ \section*{Caster Quick Reference} % Title

\sectiontitle{Update and Caster Management}

\command{caster <mode> \footnotemark[2]}{microphone on, off, sleep}
\command{clear caster log}{Clears Log Window}
\command{update dragonfly}{Updates Dragonfly}
\command{reboot caster}{Restarts Caster}
\command{caster settings editor}{Caster settings editor}

\footnotetext[2]{'Off' Implemented for DNS/DPI only}
%----------------------------------------------------------------------------------------
\end{minipage}
}
Expand Down

0 comments on commit 8b0dd35

Please sign in to comment.