diff --git a/mycroft/audio/service.py b/mycroft/audio/service.py index a9c43c574cd0..1d2011d12f24 100644 --- a/mycroft/audio/service.py +++ b/mycroft/audio/service.py @@ -1,7 +1,7 @@ import time from threading import Thread, Lock from os.path import exists, expanduser -from ovos_bus_client import Message +from ovos_bus_client.message import Message from mycroft.audio.tts import TTSFactory, TTS from ovos_config.config import Configuration diff --git a/mycroft/configuration/mycroft.conf b/mycroft/configuration/mycroft.conf index db6f331354df..c5da85379ba6 100644 --- a/mycroft/configuration/mycroft.conf +++ b/mycroft/configuration/mycroft.conf @@ -556,7 +556,7 @@ // Overrride: none "session": { // Time To Live, in seconds - "ttl": 180 + "ttl": -1 }, // Speech to Text parameters diff --git a/mycroft/listener/__init__.py b/mycroft/listener/__init__.py index 892a99ad4f2f..c1c0c3a38294 100644 --- a/mycroft/listener/__init__.py +++ b/mycroft/listener/__init__.py @@ -24,7 +24,7 @@ from mycroft.listener.mic import MutableMicrophone, ResponsiveRecognizer, ListenerState from ovos_config.config import Configuration from mycroft.metrics import Stopwatch, report_timing -from mycroft.session import SessionManager +from ovos_bus_client.session import SessionManager from mycroft.listener.stt import STTFactory from mycroft.util import find_input_device from ovos_utils.log import LOG diff --git a/mycroft/listener/mic.py b/mycroft/listener/mic.py index 705874231513..1cd57fe2851b 100644 --- a/mycroft/listener/mic.py +++ b/mycroft/listener/mic.py @@ -39,7 +39,7 @@ from mycroft.deprecated.speech_client import NoiseTracker from mycroft.listener.data_structures import RollingMean, CyclicAudioBuffer from mycroft.listener.silence import SilenceDetector, SilenceResultType, SilenceMethod -from mycroft.session import SessionManager +from ovos_bus_client.session import SessionManager from mycroft.util import ( resolve_resource_file, play_wav, play_ogg, play_mp3 @@ -50,7 +50,7 @@ from ovos_config.locations import get_xdg_data_save_path from mycroft.util.audio_utils import play_audio_file from ovos_plugin_manager.vad import OVOSVADFactory -from ovos_utils.messagebus import get_message_lang +from ovos_bus_client.util import get_message_lang WakeWordData = namedtuple('WakeWordData', ['audio', 'found', 'stopped', 'end_audio']) diff --git a/mycroft/metrics/__init__.py b/mycroft/metrics/__init__.py index fd54a0709279..edc07a7da76d 100644 --- a/mycroft/metrics/__init__.py +++ b/mycroft/metrics/__init__.py @@ -21,7 +21,7 @@ from ovos_backend_client.api import MetricsApi from ovos_config.config import Configuration -from mycroft.session import SessionManager +from ovos_bus_client.session import SessionManager from ovos_utils.log import LOG from mycroft.version import CORE_VERSION_STR from copy import copy diff --git a/mycroft/session/__init__.py b/mycroft/session/__init__.py index 19786361105e..0c4fcf7cd003 100644 --- a/mycroft/session/__init__.py +++ b/mycroft/session/__init__.py @@ -1,84 +1 @@ -# Copyright 2017 Mycroft AI Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -import time -from threading import Lock -from uuid import uuid4 - -from ovos_config.config import Configuration -from ovos_utils.log import LOG - - -class Session: - """ - An class representing a Mycroft Session Identifier - """ - - def __init__(self, session_id, expiration_seconds=180): - self.session_id = session_id - self.touch_time = int(time.time()) - self.expiration_seconds = expiration_seconds - - def touch(self): - """ - update the touch_time on the session - - :return: - """ - self.touch_time = int(time.time()) - - def expired(self): - """ - determine if the session has expired - - :return: - """ - return int(time.time()) - self.touch_time > self.expiration_seconds - - def __str__(self): - return "{%s,%d}" % (str(self.session_id), self.touch_time) - - -class SessionManager: - """ Keeps track of the current active session. """ - __current_session = None - __lock = Lock() - - @staticmethod - def get(): - """ - get the active session. - - :return: An active session - """ - config = Configuration().get('session') - - with SessionManager.__lock: - if (not SessionManager.__current_session or - SessionManager.__current_session.expired()): - SessionManager.__current_session = Session( - str(uuid4()), expiration_seconds=config.get('ttl', 180)) - LOG.info( - "New Session Start: " + - SessionManager.__current_session.session_id) - return SessionManager.__current_session - - @staticmethod - def touch(): - """ - Update the last_touch timestamp on the current session - - :return: None - """ - SessionManager.get().touch() +from ovos_bus_client.session import Session, SessionManager diff --git a/mycroft/skills/common_query_skill.py b/mycroft/skills/common_query_skill.py index 388151919ae9..2e44e0d6bcb0 100644 --- a/mycroft/skills/common_query_skill.py +++ b/mycroft/skills/common_query_skill.py @@ -215,4 +215,4 @@ def CQS_action(self, phrase, data): """ # Derived classes may implement this if they use additional media # or wish to set context after being called. - pass + return None diff --git a/mycroft/skills/intent_service.py b/mycroft/skills/intent_service.py index 7ebbe2c1ee32..1e03f11dd704 100644 --- a/mycroft/skills/intent_service.py +++ b/mycroft/skills/intent_service.py @@ -28,7 +28,8 @@ from ovos_utils.log import LOG from mycroft.util.audio_utils import play_error_sound from mycroft.util.parse import normalize -from ovos_utils.messagebus import get_message_lang +from ovos_bus_client.util import get_message_lang +from ovos_bus_client.session import SessionManager def _normalize_all_utterances(utterances): @@ -102,6 +103,9 @@ def __init__(self, bus): self.handle_activate_skill_request) self.bus.on('intent.service.skills.deactivate', self.handle_deactivate_skill_request) + self.bus.on("skill.converse.get_response.enable", self.handle_get_response_enable) + self.bus.on("skill.converse.get_response.disable", self.handle_get_response_disable) + # TODO backwards compat, deprecate self.bus.on('active_skill_request', self.handle_activate_skill_request) @@ -156,7 +160,7 @@ def handle_activate_skill_request(self, message): # this doesnt happen accidentally at very least skill_id = message.data['skill_id'] source_skill = message.context.get("skill_id") - self.converse.activate_skill(skill_id, source_skill) + self.converse.activate_skill(skill_id, source_skill, message) def handle_deactivate_skill_request(self, message): # TODO imperfect solution - only a skill can deactivate itself @@ -165,7 +169,7 @@ def handle_deactivate_skill_request(self, message): # this doesnt happen accidentally skill_id = message.data['skill_id'] source_skill = message.context.get("skill_id") or skill_id - self.converse.deactivate_skill(skill_id, source_skill) + self.converse.deactivate_skill(skill_id, source_skill, message) def reset_converse(self, message): """Let skills know there was a problem with speech recognition""" @@ -282,6 +286,8 @@ def handle_utterance(self, message): """ try: lang = get_message_lang(message) + sess = SessionManager.get(message) + sess.lang = lang # update session object try: setup_locale(lang) except Exception as e: @@ -314,7 +320,7 @@ def handle_utterance(self, message): break if match: if match.skill_id: - self.converse.activate_skill(match.skill_id) + self.converse.activate_skill(match.skill_id, message=message) # If the service didn't report back the skill_id it # takes on the responsibility of making the skill "active" @@ -335,6 +341,16 @@ def handle_utterance(self, message): except Exception as err: LOG.exception(err) + def handle_get_response_enable(self, message): + skill_id = message.data["skill_id"] + session = SessionManager.get(message) + session.enable_response_mode(skill_id) + + def handle_get_response_disable(self, message): + skill_id = message.data["skill_id"] + session = SessionManager.get(message) + session.disable_response_mode(skill_id) + def send_complete_intent_failure(self, message): """Send a message that no skill could handle the utterance. diff --git a/mycroft/skills/intent_services/commonqa_service.py b/mycroft/skills/intent_services/commonqa_service.py index 7bd87d788eb7..ca6c6923e3e8 100644 --- a/mycroft/skills/intent_services/commonqa_service.py +++ b/mycroft/skills/intent_services/commonqa_service.py @@ -9,7 +9,7 @@ from mycroft.skills.skill_data import CoreResources from ovos_utils.enclosure.api import EnclosureAPI from ovos_utils.log import LOG -from ovos_utils.messagebus import get_message_lang +from ovos_bus_client.util import get_message_lang EXTENSION_TIME = 10 diff --git a/mycroft/skills/intent_services/converse_service.py b/mycroft/skills/intent_services/converse_service.py index 133738ccca95..be3621a45d3c 100644 --- a/mycroft/skills/intent_services/converse_service.py +++ b/mycroft/skills/intent_services/converse_service.py @@ -1,12 +1,13 @@ import time -from ovos_config.config import Configuration from ovos_bus_client.message import Message +from ovos_bus_client.session import SessionManager, UtteranceState from mycroft.skills.intent_services import ( IntentMatch ) from mycroft.skills.permissions import ConverseMode, ConverseActivationMode from ovos_utils.log import LOG +from ovos_config.config import Configuration class ConverseService: @@ -15,7 +16,20 @@ class ConverseService: def __init__(self, bus): self.bus = bus self._consecutive_activations = {} - self.active_skills = [] # [skill_id , timestamp] + + @property + def active_skills(self): + LOG.warning("self.active_skills is deprecated! use Session instead") + session = SessionManager.get() + return session.active_skills + + @active_skills.setter + def active_skills(self, val): + LOG.warning("self.active_skills is deprecated! use Session instead") + session = SessionManager.get() + session.active_skills = [] + for skill_id, ts in val: + session.activate_skill(skill_id) @property def config(self): @@ -25,16 +39,17 @@ def config(self): """ return Configuration().get("skills", {}).get("converse") or {} - def get_active_skills(self): + def get_active_skills(self, message=None): """Active skill ids ordered by converse priority this represents the order in which converse will be called Returns: active_skills (list): ordered list of skill_ids """ - return [skill[0] for skill in self.active_skills] + session = SessionManager.get(message) + return [skill[0] for skill in session.active_skills] - def deactivate_skill(self, skill_id, source_skill=None): + def deactivate_skill(self, skill_id, source_skill=None, message=None): """Remove a skill from being targetable by converse. Args: @@ -43,17 +58,24 @@ def deactivate_skill(self, skill_id, source_skill=None): """ source_skill = source_skill or skill_id if self._deactivate_allowed(skill_id, source_skill): - active_skills = self.get_active_skills() - if skill_id in active_skills: - idx = active_skills.index(skill_id) - self.active_skills.pop(idx) + session = SessionManager.get(message) + if session.is_active(skill_id): + # update converse session + if message: + session.update_history(message) + session.deactivate_skill(skill_id) + # also update default session + if session.session_id != SessionManager.default_session.session_id: + SessionManager.default_session.deactivate_skill(skill_id) + # send bus event self.bus.emit( Message("intent.service.skills.deactivated", {"skill_id": skill_id})) + # reset activation counter if skill_id in self._consecutive_activations: self._consecutive_activations[skill_id] = 0 - def activate_skill(self, skill_id, source_skill=None): + def activate_skill(self, skill_id, source_skill=None, message=None): """Add a skill or update the position of an active skill. The skill is added to the front of the list, if it's already in the @@ -65,19 +87,24 @@ def activate_skill(self, skill_id, source_skill=None): """ source_skill = source_skill or skill_id if self._activate_allowed(skill_id, source_skill): - # NOTE: do not call self.remove_active_skill - # do not want to send the deactivation bus event! - active_skills = self.get_active_skills() - if skill_id in active_skills: - idx = active_skills.index(skill_id) - self.active_skills.pop(idx) - - # add skill with timestamp to start of skill_list - self.active_skills.insert(0, [skill_id, time.time()]) - self.bus.emit( - Message("intent.service.skills.activated", - {"skill_id": skill_id})) - + # update converse session + session = SessionManager.get(message) + if message: + session.update_history(message) + session.activate_skill(skill_id) + # also update default session + if session.session_id != SessionManager.default_session.session_id: + SessionManager.default_session.activate_skill(skill_id) + + if message: + msg = message.forward("intent.service.skills.activated", + {"skill_id": skill_id}) + else: + msg = Message("intent.service.skills.activated", + {"skill_id": skill_id}) + # send bus event + self.bus.emit(msg) + # update activation counter self._consecutive_activations[skill_id] += 1 def _activate_allowed(self, skill_id, source_skill=None): @@ -180,16 +207,16 @@ def _converse_allowed(self, skill_id): return False return True - def _collect_converse_skills(self): + def _collect_converse_skills(self, message): """use the messagebus api to determine which skills want to converse This includes all skills and external applications""" skill_ids = [] want_converse = [] - active_skills = self.get_active_skills() + active_skills = self.get_active_skills(message) - def handle_ack(message): - skill_id = message.data["skill_id"] - if message.data.get("can_handle", True): + def handle_ack(msg): + skill_id = msg.data["skill_id"] + if msg.data.get("can_handle", True): if skill_id in active_skills: want_converse.append(skill_id) skill_ids.append(skill_id) @@ -227,7 +254,20 @@ def converse(self, utterances, skill_id, lang, message): Returns: handled (bool): True if handled otherwise False. """ + session = SessionManager.get(message) + session.lang = lang + state = session.utterance_states.get(skill_id, UtteranceState.INTENT) + if state == UtteranceState.RESPONSE: + session.update_history(message) + converse_msg = message.reply("skill.converse.get_response", + {"skill_id": skill_id, + "utterances": utterances, + "lang": lang}) + self.bus.emit(converse_msg) + return True + if self._converse_allowed(skill_id): + session.update_history(message) converse_msg = message.reply("skill.converse.request", {"skill_id": skill_id, "utterances": utterances, @@ -257,8 +297,7 @@ def converse_with_skills(self, utterances, lang, message): # filter allowed skills self._check_converse_timeout() # check if any skill wants to handle utterance - for skill_id in self._collect_converse_skills(): + for skill_id in self._collect_converse_skills(message): if self.converse(utterances, skill_id, lang, message): return IntentMatch('Converse', None, None, skill_id) return None -