1313# limitations under the License.
1414#
1515"""Mycroft's intent service, providing intent parsing since forever!"""
16- from copy import copy
1716import time
17+ from threading import Event
1818
1919from mycroft .configuration import Configuration , setup_locale
20- from mycroft .util .log import LOG
21- from mycroft .util .parse import normalize
20+ from mycroft .messagebus .message import Message
2221from mycroft .metrics import report_timing , Stopwatch
22+ from mycroft .skills .intent_service_interface import open_intent_envelope
2323from mycroft .skills .intent_services import (
24- AdaptService , AdaptIntent ,
25- FallbackService ,
24+ AdaptService , FallbackService ,
2625 PadatiousService , PadatiousMatcher ,
27- IntentMatch
26+ ConverseService , IntentMatch
2827)
29- from mycroft .skills .intent_service_interface import open_intent_envelope
3028from mycroft .skills .permissions import ConverseMode , ConverseActivationMode
31- from mycroft .messagebus .message import Message
29+ from mycroft .util .log import LOG
30+ from mycroft .util .parse import normalize
3231
3332
3433def _get_message_lang (message ):
@@ -88,14 +87,13 @@ def __init__(self, bus):
8887
8988 # Dictionary for translating a skill id to a name
9089 self .skill_names = {}
91-
9290 self .adapt_service = AdaptService (config .get ('context' , {}))
9391 try :
9492 self .padatious_service = PadatiousService (bus , config ['padatious' ])
9593 except Exception as err :
96- LOG .exception ('Failed to create padatious handlers '
97- '({})' .format (repr (err )))
94+ LOG .exception (f'Failed to create padatious handlers ({ err } )' )
9895 self .fallback = FallbackService (bus )
96+ self .converse = ConverseService (bus )
9997
10098 self .bus .on ('register_vocab' , self .handle_register_vocab )
10199 self .bus .on ('register_intent' , self .handle_register_intent )
@@ -108,7 +106,6 @@ def __init__(self, bus):
108106 self .bus .on ('clear_context' , self .handle_clear_context )
109107
110108 # Converse method
111- self .converse_config = config ["skills" ].get ("converse" ) or {}
112109 self .bus .on ('mycroft.speech.recognition.unknown' , self .reset_converse )
113110 self .bus .on ('mycroft.skills.loaded' , self .update_skill_name_dict )
114111
@@ -119,9 +116,6 @@ def __init__(self, bus):
119116 # TODO backwards compat, deprecate
120117 self .bus .on ('active_skill_request' , self .handle_activate_skill_request )
121118
122- self .active_skills = [] # [skill_id , timestamp]
123- self ._consecutive_activations = {}
124-
125119 # Intents API
126120 self .registered_vocab = []
127121 self .bus .on ('intent.service.intent.get' , self .handle_get_intent )
@@ -161,67 +155,18 @@ def get_skill_name(self, skill_id):
161155 return self .skill_names .get (skill_id , skill_id )
162156
163157 # converse handling
164- def handle_activate_skill_request (self , message ):
165-
166- skill_id = message .data ['skill_id' ]
167-
168- # cross activation control if skills can activate each other
169- if not self .converse_config .get ("cross_activation" ):
170- # TODO imperfect solution - only a skill can activate itself
171- # someone can forge this message and emit it raw, but in OpenVoiceOS all
172- # skill messages should have skill_id in context, so let's make sure
173- # this doesnt happen accidentally at very least
174- source_skill = message .context .get ("skill_id" ) or skill_id
175- if skill_id != source_skill :
176- # different skill is trying to activate this skill
177- return
178-
179- # mode of activation dictates under what conditions a skill is
180- # allowed to activate itself
181- acmode = self .converse_config .get ("converse_activation" ) or \
182- ConverseActivationMode .ACCEPT_ALL
183-
184- if acmode == ConverseActivationMode .PRIORITY :
185- prio = self .converse_config .get ("converse_priorities" ) or {}
186- # only allowed to activate if no skill with higher priority is
187- # active, currently there is no api for skills to
188- # define their default priority, this is a user/developer setting
189- priority = prio .get (skill_id , 50 )
190- if any (p > priority for p in
191- [prio .get (s [0 ], 50 ) for s in self .active_skills ]):
192- return
193-
194- if acmode == ConverseActivationMode .BLACKLIST :
195- if skill_id in self .converse_config .get ("converse_blacklist" , []):
196- return
197-
198- if acmode == ConverseActivationMode .WHITELIST :
199- if skill_id not in self .converse_config .get ("converse_whitelist" , []):
200- return
201-
202- # limit of consecutive activations
203- default_max = self .converse_config .get ("max_activations" , - 1 )
204- # per skill override limit of consecutive activations
205- skill_max = self .converse_config .get ("skill_activations" , {}).get (skill_id )
206- max_activations = skill_max or default_max
207- if skill_id not in self ._consecutive_activations :
208- self ._consecutive_activations [skill_id ] = 0
209- if max_activations < 0 :
210- pass # no limit (mycroft-core default)
211- elif max_activations == 0 :
212- return # skill activation disabled
213- elif self ._consecutive_activations .get (skill_id , 0 ) > max_activations :
214- return # skill exceeded authorized consecutive number of
215- # activations
216-
217- # activate skill
218- self .add_active_skill (skill_id )
219- self ._consecutive_activations [skill_id ] += 1
220-
221- # converse handling
158+ @property
159+ def active_skills (self ):
160+ return self .converse .active_skills # [skill_id , timestamp]
222161
223162 def handle_activate_skill_request (self , message ):
224- self .add_active_skill (message .data ['skill_id' ])
163+ # TODO imperfect solution - only a skill can activate itself
164+ # someone can forge this message and emit it raw, but in OpenVoiceOS all
165+ # skill messages should have skill_id in context, so let's make sure
166+ # this doesnt happen accidentally at very least
167+ skill_id = message .data ['skill_id' ]
168+ source_skill = message .context .get ("skill_id" )
169+ self .converse .activate_skill (skill_id , source_skill )
225170
226171 def handle_deactivate_skill_request (self , message ):
227172 # TODO imperfect solution - only a skill can deactivate itself
@@ -230,15 +175,13 @@ def handle_deactivate_skill_request(self, message):
230175 # this doesnt happen accidentally
231176 skill_id = message .data ['skill_id' ]
232177 source_skill = message .context .get ("skill_id" ) or skill_id
233- if skill_id == source_skill :
234- self .remove_active_skill (message .data ['skill_id' ])
178+ self .converse .deactivate_skill (skill_id , source_skill )
235179
236180 def reset_converse (self , message ):
237181 """Let skills know there was a problem with speech recognition"""
238182 lang = _get_message_lang (message )
239183 setup_locale (lang ) # restore default lang
240- for skill in copy (self .active_skills ):
241- self .do_converse (None , skill [0 ], lang , message )
184+ self .converse .converse_with_skills (None , lang , message )
242185
243186 def do_converse (self , utterances , skill_id , lang , message ):
244187 """Call skill and ask if they want to process the utterance.
@@ -250,56 +193,29 @@ def do_converse(self, utterances, skill_id, lang, message):
250193 lang (str): current language
251194 message (Message): message containing interaction info.
252195 """
253- opmode = self .converse_config .get ("converse_mode" ,
254- ConverseMode .ACCEPT_ALL )
255-
256- if opmode == ConverseMode .BLACKLIST and skill_id in \
257- self .converse_config .get ("converse_blacklist" , []):
258- return False
259-
260- elif opmode == ConverseMode .WHITELIST and skill_id not in \
261- self .converse_config .get ("converse_whitelist" , []):
262- return False
263-
264- converse_msg = (message .reply ("skill.converse.request" , {
265- "skill_id" : skill_id , "utterances" : utterances , "lang" : lang }))
266- result = self .bus .wait_for_response (converse_msg ,
267- 'skill.converse.response' )
268- if result and 'error' in result .data :
269- self .handle_converse_error (result )
270- ret = False
271- elif result is not None :
272- ret = result .data .get ('result' , False )
273- else :
274- ret = False
275- return ret
196+ # NOTE: can not delete method for backwards compat with upstream
197+ LOG .warning ("self.do_converse has been deprecated!\n "
198+ "use self.converse.converse instead" )
199+ return self .converse .converse (utterances , skill_id , lang , message )
276200
277201 def handle_converse_error (self , message ):
278202 """Handle error in converse system.
279-
280203 Args:
281204 message (Message): info about the error.
282205 """
283- skill_id = message .data ["skill_id" ]
284- error_msg = message .data ['error' ]
285- LOG .error ("{}: {}" .format (skill_id , error_msg ))
286- if message .data ["error" ] == "skill id does not exist" :
287- self .remove_active_skill (skill_id )
206+ # NOTE: can not delete method for backwards compat with upstream
207+ LOG .warning ("handle_converse_error has been deprecated!" )
288208
289209 def remove_active_skill (self , skill_id ):
290210 """Remove a skill from being targetable by converse.
291211
292212 Args:
293213 skill_id (str): skill to remove
294214 """
295- for skill in self .active_skills :
296- if skill [0 ] == skill_id :
297- self .active_skills .remove (skill )
298- self .bus .emit (
299- Message ("intent.service.skills.deactivated" ,
300- {"skill_id" : skill_id }))
301- if skill_id in self ._consecutive_activations :
302- self ._consecutive_activations [skill_id ] = 0
215+ # NOTE: can not delete method for backwards compat with upstream
216+ LOG .warning ("self.remove_active_skill has been deprecated!\n "
217+ "use self.converse.deactivate_skill instead" )
218+ self .converse .deactivate_skill (skill_id )
303219
304220 def add_active_skill (self , skill_id ):
305221 """Add a skill or update the position of an active skill.
@@ -310,21 +226,10 @@ def add_active_skill(self, skill_id):
310226 Args:
311227 skill_id (str): identifier of skill to be added.
312228 """
313- # search the list for an existing entry that already contains it
314- # and remove that reference
315- if skill_id != '' :
316- # do not call remove method to not send deactivate bus event!
317- for skill in self .active_skills :
318- if skill [0 ] == skill_id :
319- self .active_skills .remove (skill )
320- # add skill with timestamp to start of skill_list
321- self .active_skills .insert (0 , [skill_id , time .time ()])
322- self .bus .emit (
323- Message ("intent.service.skills.activated" ,
324- {"skill_id" : skill_id }))
325- else :
326- LOG .warning ('Skill ID was empty, won\' t add to list of '
327- 'active skills.' )
229+ # NOTE: can not delete method for backwards compat with upstream
230+ LOG .warning ("self.add_active_skill has been deprecated!\n "
231+ "use self.converse.activate_skill instead" )
232+ self .converse .activate_skill (skill_id )
328233
329234 def send_metrics (self , intent , context , stopwatch ):
330235 """Send timing metrics to the backend.
@@ -392,7 +297,7 @@ def handle_utterance(self, message):
392297 # List of functions to use to match the utterance with intent.
393298 # These are listed in priority order.
394299 match_funcs = [
395- self ._converse , padatious_matcher .match_high ,
300+ self .converse . converse_with_skills , padatious_matcher .match_high ,
396301 self .adapt_service .match_intent , self .fallback .high_prio ,
397302 padatious_matcher .match_medium , self .fallback .medium_prio ,
398303 padatious_matcher .match_low , self .fallback .low_prio
@@ -407,7 +312,7 @@ def handle_utterance(self, message):
407312 break
408313 if match :
409314 if match .skill_id :
410- self .add_active_skill (match .skill_id )
315+ self .converse . activate_skill (match .skill_id )
411316 # If the service didn't report back the skill_id it
412317 # takes on the responsibility of making the skill "active"
413318
@@ -428,35 +333,6 @@ def handle_utterance(self, message):
428333 except Exception as err :
429334 LOG .exception (err )
430335
431- def _converse (self , utterances , lang , message ):
432- """Give active skills a chance at the utterance
433-
434- Args:
435- utterances (list): list of utterances
436- lang (string): 4 letter ISO language code
437- message (Message): message to use to generate reply
438-
439- Returns:
440- IntentMatch if handled otherwise None.
441- """
442- utterances = [item for tup in utterances for item in tup ]
443-
444- # check for conversation time-out
445- skill_timeouts = self .converse_config .get ("skill_timeouts" ) or {}
446- default_timeout = self .converse_config .get ("timeout" , 300 )
447- self .active_skills = [
448- skill for skill in self .active_skills
449- if time .time () - skill [1 ] <= skill_timeouts .get (skill [0 ],
450- default_timeout )]
451-
452- # check if any skill wants to handle utterance
453- for skill in copy (self .active_skills ):
454- if self .do_converse (utterances , skill [0 ], lang , message ):
455- # update timestamp, or there will be a timeout where
456- # intent stops conversing whether its being used or not
457- return IntentMatch ('Converse' , None , None , skill [0 ])
458- return None
459-
460336 def send_complete_intent_failure (self , message ):
461337 """Send a message that no skill could handle the utterance.
462338
@@ -608,7 +484,7 @@ def handle_get_active_skills(self, message):
608484 message: query message to reply to.
609485 """
610486 self .bus .emit (message .reply ("intent.service.active_skills.reply" ,
611- {"skills" : self .active_skills }))
487+ {"skills" : self .converse . active_skills }))
612488
613489 def handle_get_adapt (self , message ):
614490 """handler getting the adapt response for an utterance.
0 commit comments