Skip to content

Commit

Permalink
refactor/get_response_v2_converse_session
Browse files Browse the repository at this point in the history
make get_response event based

companion PR to OpenVoiceOS/ovos-core#160
  • Loading branch information
JarbasAl committed Sep 22, 2023
1 parent f19fb3f commit 970db67
Showing 1 changed file with 99 additions and 0 deletions.
99 changes: 99 additions & 0 deletions ovos_workshop/skills/ovos.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,105 @@ def send_stop_signal(self, stop_event: Optional[str] = None):
self.bus.emit(msg.forward("mycroft.audio.speech.stop"))


# get response V2
def __get_response(self):
"""Helper to get a response from the user
NOTE: There is a race condition here. There is a small amount of
time between the end of the device speaking and the converse method
being overridden in this method. If an utterance is injected during
this time, the wrong converse method is executed. The condition is
hidden during normal use due to the amount of time it takes a user
to speak a response. The condition is revealed when an automated
process injects an utterance quicker than this method can flip the
converse methods.
Returns:
str: user's response or None on a timeout
"""

# in ovos-core < 0.0.8 we reassign the converse method itself
is_old = False
try:
from mycroft.version import OVOS_VERSION_MAJOR, OVOS_VERSION_MINOR, OVOS_VERSION_BUILD, OVOS_VERSION_ALPHA
if OVOS_VERSION_MAJOR == 0 and OVOS_VERSION_MINOR == 0 and OVOS_VERSION_BUILD <= 8 and OVOS_VERSION_ALPHA < 5:
is_old = True
except ImportError:
# standalone usage without core
pass

self.bus.emit(Message("skill.converse.get_response.enable",
{"skill_id": self.skill_id}))
self._activate()

if is_old: # NOT SAFE for multiple users at same time

def converse(utterances, lang=None):
converse.response = utterances[0] if utterances else None
converse.finished = True
return True

converse.finished = False
converse.response = None

# replace converse method to capture utterance by hijacking the pipeline
self.converse = converse

# 10 for listener, 5 for SST, then timeout
# NOTE: a threading.Event is not used otherwise we can't raise the
# AbortEvent exception to kill the thread
start = time.time()
while time.time() - start <= 15 and not converse.finished:
time.sleep(0.1)
if self.__response is not False:
if self.__response is None:
# aborted externally (if None)
self.log.debug("get_response aborted")
converse.finished = True
converse.response = self.__response # external override

# restore converse method
self.converse = self.__original_converse

return converse.response

utterances = []
lang = self.lang # unused, but we get this info together with utterance if needed
# user could switch lang midway, maybe ignore response in this case (?)

def _handle_get_response(message):
nonlocal utterances, lang

skill_id = message.data["skill_id"]
if skill_id != self.skill_id:
return # not for us!

utterances = message.data["utterances"]
lang = message.data["lang"]
# received get_response

self.bus.on("skill.converse.get_response", _handle_get_response)

# NOTE: a threading.Event is not used otherwise we can't raise the
# AbortEvent exception to kill the thread
start = time.time()
while time.time() - start <= 15 and not len(utterances):
time.sleep(0.1)
if self.__response is not False:
if self.__response is None:
# aborted externally (if None)
self.log.debug("get_response aborted")
else:
utterances = [self.__response] # external override

self.bus.emit(Message("skill.converse.get_response.disable",
{"skill_id": self.skill_id}))

if utterances:
return utterances[0]
return None


# backwards compat alias, no functional difference
class OVOSFallbackSkill(OVOSSkill):
def __new__(cls, *args, **kwargs):
Expand Down

0 comments on commit 970db67

Please sign in to comment.