180 lines
5.8 KiB
Python
180 lines
5.8 KiB
Python
# 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 Timer
|
|
import mycroft.lock
|
|
from mycroft import dialog
|
|
from mycroft.api import is_paired, BackendDown
|
|
from mycroft.enclosure.api import EnclosureAPI
|
|
from mycroft.configuration import Configuration
|
|
from mycroft.messagebus.client.ws import WebsocketClient
|
|
from mycroft.messagebus.message import Message
|
|
from mycroft.util import (
|
|
connected, wait_while_speaking, reset_sigint_handler,
|
|
create_echo_function, create_daemon, wait_for_exit_signal
|
|
)
|
|
|
|
from .skill_manager import SkillManager
|
|
from .core import FallbackSkill
|
|
from .event_scheduler import EventScheduler
|
|
from .intent_service import IntentService
|
|
from .padatious_service import PadatiousService
|
|
|
|
bus = None # Mycroft messagebus reference, see "mycroft.messagebus"
|
|
event_scheduler = None
|
|
skill_manager = None
|
|
|
|
# Remember "now" at startup. Used to detect clock changes.
|
|
start_ticks = time.monotonic()
|
|
start_clock = time.time()
|
|
|
|
|
|
def connect():
|
|
global bus
|
|
bus.run_forever()
|
|
|
|
|
|
def _starting_up():
|
|
"""
|
|
Start loading skills.
|
|
|
|
Starts
|
|
- SkillManager to load/reloading of skills when needed
|
|
- a timer to check for internet connection
|
|
- adapt intent service
|
|
- padatious intent service
|
|
"""
|
|
global bus, skill_manager, event_scheduler
|
|
|
|
bus.on('intent_failure', FallbackSkill.make_intent_failure_handler(bus))
|
|
|
|
# Create the Intent manager, which converts utterances to intents
|
|
# This is the heart of the voice invoked skill system
|
|
|
|
service = IntentService(bus)
|
|
PadatiousService(bus, service)
|
|
event_scheduler = EventScheduler(bus)
|
|
|
|
# Create a thread that monitors the loaded skills, looking for updates
|
|
skill_manager = SkillManager(bus)
|
|
skill_manager.daemon = True
|
|
# Wait until skills have been loaded once before starting to check
|
|
# network connection
|
|
skill_manager.load_priority()
|
|
skill_manager.start()
|
|
check_connection()
|
|
|
|
|
|
def check_connection():
|
|
"""
|
|
Check for network connection. If not paired trigger pairing.
|
|
Runs as a Timer every second until connection is detected.
|
|
"""
|
|
if connected():
|
|
enclosure = EnclosureAPI(bus)
|
|
|
|
if is_paired():
|
|
# Skip the sync message when unpaired because the prompt to go to
|
|
# home.mycrof.ai will be displayed by the pairing skill
|
|
enclosure.mouth_text(dialog.get("message_synching.clock"))
|
|
|
|
# Force a sync of the local clock with the internet
|
|
config = Configuration.get()
|
|
platform = config['enclosure'].get("platform", "unknown")
|
|
if platform in ['mycroft_mark_1', 'picroft']:
|
|
bus.emit(Message("system.ntp.sync"))
|
|
time.sleep(15) # TODO: Generate/listen for a message response...
|
|
|
|
# Check if the time skewed significantly. If so, reboot
|
|
skew = abs((time.monotonic() - start_ticks) -
|
|
(time.time() - start_clock))
|
|
if skew > 60 * 60:
|
|
# Time moved by over an hour in the NTP sync. Force a reboot to
|
|
# prevent weird things from occcurring due to the 'time warp'.
|
|
#
|
|
data = {'utterance': dialog.get("time.changed.reboot")}
|
|
bus.emit(Message("speak", data))
|
|
wait_while_speaking()
|
|
|
|
# provide visual indicators of the reboot
|
|
enclosure.mouth_text(dialog.get("message_rebooting"))
|
|
enclosure.eyes_color(70, 65, 69) # soft gray
|
|
enclosure.eyes_spin()
|
|
|
|
# give the system time to finish processing enclosure messages
|
|
time.sleep(1.0)
|
|
|
|
# reboot
|
|
bus.emit(Message("system.reboot"))
|
|
return
|
|
else:
|
|
bus.emit(Message("enclosure.mouth.reset"))
|
|
time.sleep(0.5)
|
|
|
|
bus.emit(Message('mycroft.internet.connected'))
|
|
# check for pairing, if not automatically start pairing
|
|
try:
|
|
if not is_paired(ignore_errors=False):
|
|
payload = {
|
|
'utterances': ["pair my device"],
|
|
'lang': "en-us"
|
|
}
|
|
bus.emit(Message("recognizer_loop:utterance", payload))
|
|
else:
|
|
from mycroft.api import DeviceApi
|
|
api = DeviceApi()
|
|
api.update_version()
|
|
except BackendDown:
|
|
data = {'utterance': dialog.get("backend.down")}
|
|
bus.emit(Message("speak", data))
|
|
bus.emit(Message("backend.down"))
|
|
|
|
else:
|
|
thread = Timer(1, check_connection)
|
|
thread.daemon = True
|
|
thread.start()
|
|
|
|
|
|
def main():
|
|
global bus
|
|
reset_sigint_handler()
|
|
# Create PID file, prevent multiple instancesof this service
|
|
mycroft.lock.Lock('skills')
|
|
# Connect this Skill management process to the Mycroft Messagebus
|
|
bus = WebsocketClient()
|
|
Configuration.init(bus)
|
|
|
|
bus.on('message', create_echo_function('SKILLS'))
|
|
# Startup will be called after the connection with the Messagebus is done
|
|
bus.once('open', _starting_up)
|
|
|
|
create_daemon(bus.run_forever)
|
|
wait_for_exit_signal()
|
|
shutdown()
|
|
|
|
|
|
def shutdown():
|
|
if event_scheduler:
|
|
event_scheduler.shutdown()
|
|
|
|
# Terminate all running threads that update skills
|
|
if skill_manager:
|
|
skill_manager.stop()
|
|
skill_manager.join()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|