Browse Source

Port Kaylee and shell plugin to new plugin API

The new shell plugin is a lot cleaner, only determining how the command
matches once and parsing numbers zero or one times.

Kaylee uses the new API, but there are still some problems.  Most
notably, Handler objects have no sorting methods yet, so behavior will
be unpredictable when multiple plugins are loaded.
Clara Hobbs 6 years ago
parent
commit
fd6dbad6b6
2 changed files with 49 additions and 51 deletions
  1. 22
    19
      kayleevc/kaylee.py
  2. 27
    32
      kayleevc/plugins/shell.py

+ 22
- 19
kayleevc/kaylee.py View File

38
         for plugin in self.config.plugins.keys():
38
         for plugin in self.config.plugins.keys():
39
             pmod = importlib.import_module(plugin, 'kayleevc.plugins')
39
             pmod = importlib.import_module(plugin, 'kayleevc.plugins')
40
             pobj = pmod.Plugin(self.config, plugin)
40
             pobj = pmod.Plugin(self.config, plugin)
41
-            pobj.connect('tts', self.plugin_tts)
42
             self.plugins.append(pobj)
41
             self.plugins.append(pobj)
43
 
42
 
44
         # Create a hasher
43
         # Create a hasher
139
     def recognizer_finished(self, recognizer, text):
138
     def recognizer_finished(self, recognizer, text):
140
         confidence_heap = queue.PriorityQueue()
139
         confidence_heap = queue.PriorityQueue()
141
         min_confidence = self.options['minimum_confidence']
140
         min_confidence = self.options['minimum_confidence']
142
-        # Add plugins to the heap
141
+        # Add handlers to the heap
143
         for index, plugin in enumerate(self.plugins):
142
         for index, plugin in enumerate(self.plugins):
144
-            # Get plugin confidence
145
-            confidence = plugin.confidence(text)
146
-            # Clamp confidence to [0, 1]
147
-            confidence = min(max(confidence, 0), 1)
148
-            # If the plugin meets minimum confidence
149
-            if ((min_confidence is not None and confidence >= min_confidence)
150
-                or (min_confidence is None and confidence > 0)):
151
-                # Add a triple to the heap, so plugins are sorted first by
152
-                # confidence, then by index to break ties
153
-                confidence_heap.put_nowait((1 - confidence, index, plugin))
143
+            # Get a handler
144
+            handler = plugin.get_handler(text)
145
+            # If the handler meets minimum confidence
146
+            if ((handler is not None)
147
+                and ((min_confidence is None)
148
+                     or (min_confidence is not None
149
+                         and handler.confidence >= min_confidence))):
150
+                # Add a triple to the heap, so handlers are sorted first by
151
+                # their confidence, then by index to break ties
152
+                confidence_heap.put_nowait((handler, index, plugin))
154
 
153
 
155
         # Run valid or invalid sentence command
154
         # Run valid or invalid sentence command
156
         if confidence_heap.empty():
155
         if confidence_heap.empty():
158
         else:
157
         else:
159
             self._valid_cmd()
158
             self._valid_cmd()
160
 
159
 
161
-        # Give the command to the plugins that want it
160
+        # Give the command to the handlers that want it
162
         while True:
161
         while True:
163
             try:
162
             try:
164
-                plugin = confidence_heap.get_nowait()[2]
165
-            except queue.Empty:
166
-                break
167
-            # If the plugin successfully handled the command
168
-            if plugin.handle(text):
163
+                # Get information about the handler
164
+                info = confidence_heap.get_nowait()
165
+                handler = info[0]
166
+                plugin = info[2]
167
+                # Handle the command
168
+                handler(self.plugin_tts)
169
                 self._log_history(plugin, text)
169
                 self._log_history(plugin, text)
170
+            except queue.Empty:
170
                 break
171
                 break
172
+                # If an unknown error occurs, ignore it and keep trying
173
+                # FIXME not a good API decision
171
 
174
 
172
         self._stop_ui(text)
175
         self._stop_ui(text)
173
 
176
 
174
-    def plugin_tts(self, plugin, text):
177
+    def plugin_tts(self, text):
175
         # Stop listening
178
         # Stop listening
176
         self.recognizer.pause()
179
         self.recognizer.pause()
177
         # Speak
180
         # Speak

+ 27
- 32
kayleevc/plugins/shell.py View File

28
 
28
 
29
 import subprocess
29
 import subprocess
30
 
30
 
31
-from .pluginbase import PluginBase
31
+from . import PluginBase, Handler
32
 from ..numbers import NumberParser
32
 from ..numbers import NumberParser
33
 
33
 
34
 
34
 
44
         for voice_cmd in self.commands.keys():
44
         for voice_cmd in self.commands.keys():
45
             self.corpus_strings.add(voice_cmd.strip().replace('%d', ''))
45
             self.corpus_strings.add(voice_cmd.strip().replace('%d', ''))
46
 
46
 
47
-    def _run_command(self, cmd):
48
-        """Print the command, then run it"""
49
-        print(cmd)
50
-        subprocess.call(cmd, shell=True)
51
-
52
-    def confidence(self, text):
47
+    def get_handler(self, text):
53
         """Return whether or not the command can be handled"""
48
         """Return whether or not the command can be handled"""
54
-        numt, nums = self.number_parser.parse_all_numbers(text)
55
         if text in self.commands:
49
         if text in self.commands:
56
-            return 1
57
-        elif numt in self.commands:
58
-            return 1
50
+            # If there's an exact match, return a handler for it
51
+            return ShellHandler(1, self.commands[text])
59
         else:
52
         else:
60
-            return 0
61
-
62
-    def handle(self, text):
63
-        """Run the shell command corresponding to a recognized voice command
64
-
65
-        Spoken number(s) may be substituted in the shell command.  The shell
66
-        commands are printed immediately before they are executed.
67
-        """
68
-        numt, nums = self.number_parser.parse_all_numbers(text)
69
-        # Is there a matching command?
70
-        if text in self.commands:
71
-            cmd = self.commands[text]
72
-            self._run_command(cmd)
73
-            return True
74
-        # Is there a matching command with numbers substituted?
75
-        elif numt in self.commands:
76
-            cmd = self.commands[numt]
77
-            cmd = cmd.format(*nums)
78
-            self._run_command(cmd)
79
-            return True
53
+            # Otherwise, try substituting numbers
54
+            numt, nums = self.number_parser.parse_all_numbers(text)
55
+            if numt in self.commands:
56
+                # Return a handler if we have a match
57
+                return ShellHandler(1, self.commands[numt], nums)
58
+            # Return None if that fails too
59
+            return None
60
+
61
+
62
+class ShellHandler(Handler):
63
+    """Handle a voice command that corresponds to a shell command"""
64
+
65
+    def __init__(self, confidence, command, nums=None):
66
+        """Determine the exact command that will be run"""
67
+        super().__init__(confidence)
68
+        if nums is None:
69
+            self.command = command
80
         else:
70
         else:
81
-            return False
71
+            self.command = command.format(*nums)
72
+
73
+    def __call__(self, tts):
74
+        """Print the command, then run it"""
75
+        print(self.command)
76
+        subprocess.call(self.command, shell=True)

Loading…
Cancel
Save