files: # 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.
  #
  """Daemon launched at startup to handle skill activities.
  
  In this repo, you will not find an entry called mycroft-skills in the bin
  directory.  The executable gets added to the bin directory when installed
  (see setup.py)
  """
  import time
  from threading import Event
  
  import mycroft.lock
  from msm.exceptions import MsmException
  
  from mycroft import dialog
  from mycroft.api import is_paired, BackendDown, DeviceApi
  from mycroft.audio import wait_while_speaking
  from mycroft.enclosure.api import EnclosureAPI
  from mycroft.configuration import Configuration
  from mycroft.messagebus.message import Message
  from mycroft.util import (
      connected,
      reset_sigint_handler,
      start_message_bus_client,
      wait_for_exit_signal
  )
  from mycroft.util.lang import set_active_lang
  from mycroft.util.log import LOG
  from mycroft.util.process_utils import ProcessStatus, StatusCallbackMap
  from .core import FallbackSkill
  from .event_scheduler import EventScheduler
  from .intent_service import IntentService
  from .skill_manager import SkillManager
  
  RASPBERRY_PI_PLATFORMS = ('mycroft_mark_1', 'picroft', 'mycroft_mark_2pi')
  
  
  class DevicePrimer(object):
      """Container handling the device preparation.
  
      Arguments:
          message_bus_client: Bus client used to interact with the system
          config (dict): Mycroft configuration
      """
      def __init__(self, message_bus_client, config):
          self.bus = message_bus_client
          self.platform = config['enclosure'].get("platform", "unknown")
          self.enclosure = EnclosureAPI(self.bus)
          self.is_paired = False
          self.backend_down = False
          # Remember "now" at startup.  Used to detect clock changes.
  
      def prepare_device(self):
          """Internet dependent updates of various aspects of the device."""
          self._get_pairing_status()
          self._update_system_clock()
          self._update_system()
          # Above will block during update process and kill this instance if
          # new software is installed
  
          if self.backend_down:
              self._notify_backend_down()
          else:
              self._display_skill_loading_notification()
              self.bus.emit(Message('mycroft.internet.connected'))
              self._ensure_device_is_paired()
              self._update_device_attributes_on_backend()
  
      def _get_pairing_status(self):
          """Set an instance attribute indicating the device's pairing status"""
          try:
              self.is_paired = is_paired(ignore_errors=False)
          except BackendDown:
              LOG.error('Cannot complete device updates due to backend issues.')
              self.backend_down = True
  
          if self.is_paired:
              LOG.info('Device is paired')
  
      def _update_system_clock(self):
          """Force a sync of the local clock with the Network Time Protocol.
  
          The NTP sync is only forced on Raspberry Pi based devices.  The
          assumption being that these devices are only running Mycroft services.
          We don't want to sync the time on a Linux desktop device, for example,
          because it could have a negative impact on other software running on
          that device.
          """
          if self.platform in RASPBERRY_PI_PLATFORMS:
              LOG.info('Updating the system clock via NTP...')
              if self.is_paired:
                  # Only display time sync message when paired because the prompt
                  # to go to home.mycroft.ai will be displayed by the pairing
                  # skill when pairing
                  self.enclosure.mouth_text(dialog.get("message_synching.clock"))
              self.bus.wait_for_response(
                  Message('system.ntp.sync'),
                  'system.ntp.sync.complete',
                  15
              )
  
      def _notify_backend_down(self):
          """Notify user of inability to communicate with the backend."""
          self._speak_dialog(dialog_id="backend.down")
          self.bus.emit(Message("backend.down"))
  
      def _display_skill_loading_notification(self):
          """Indicate to the user that skills are being loaded."""
          self.enclosure.eyes_color(189, 183, 107)  # dark khaki
          self.enclosure.mouth_text(dialog.get("message_loading.skills"))
  
      def _ensure_device_is_paired(self):
          """Determine if device is paired, if not automatically start pairing.
  
          Pairing cannot be performed if there is no connection to the back end.
          So skip pairing if the backend is down.
          """
          if not self.is_paired and not self.backend_down:
              LOG.info('Device not paired, invoking the pairing skill')
              payload = dict(utterances=["pair my device"], lang="en-us")
              self.bus.emit(Message("recognizer_loop:utterance", payload))
  
      def _update_device_attributes_on_backend(self):
          """Communicate version information to the backend.
  
          The backend tracks core version, enclosure version, platform build
          and platform name for each device, if it is known.
          """
          if self.is_paired:
              LOG.info('Sending updated device attributes to the backend...')
              try:
                  api = DeviceApi()
                  api.update_version()
              except Exception:
                  self._notify_backend_down()
  
      def _update_system(self):
          """Emit an update event that will be handled by the admin service."""
          if not self.is_paired:
              LOG.info('Attempting system update
  
  [... the rest is omitted ...]
files: mycroft/skills/__main__.py
files: # 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.
  #
  """Daemon launched at startup to handle skill activities.
  
  In this repo, you will not find an entry called mycroft-skills in the bin
  directory.  The executable gets added to the bin directory when installed
  (see setup.py)
  """
  import time
  from threading import Event
  
  import mycroft.lock
  from msm.exceptions import MsmException
  
  from mycroft import dialog
  from mycroft.api import is_paired, BackendDown, DeviceApi
  from mycroft.audio import wait_while_speaking
  from mycroft.enclosure.api import EnclosureAPI
  from mycroft.configuration import Configuration
  from mycroft.messagebus.message import Message
  from mycroft.util import (
      connected,
      reset_sigint_handler,
      start_message_bus_client,
      wait_for_exit_signal
  )
  from mycroft.util.lang import set_active_lang
  from mycroft.util.log import LOG
  from .core import FallbackSkill
  from .event_scheduler import EventScheduler
  from .intent_service import IntentService
  from .skill_manager import SkillManager
  
  RASPBERRY_PI_PLATFORMS = ('mycroft_mark_1', 'picroft', 'mycroft_mark_2pi')
  
  
  class DevicePrimer(object):
      """Container handling the device preparation.
  
      Arguments:
          message_bus_client: Bus client used to interact with the system
          config (dict): Mycroft configuration
      """
      def __init__(self, message_bus_client, config):
          self.bus = message_bus_client
          self.platform = config['enclosure'].get("platform", "unknown")
          self.enclosure = EnclosureAPI(self.bus)
          self.is_paired = False
          self.backend_down = False
          # Remember "now" at startup.  Used to detect clock changes.
  
      def prepare_device(self):
          """Internet dependent updates of various aspects of the device."""
          self._get_pairing_status()
          self._update_system_clock()
          self._update_system()
          # Above will block during update process and kill this instance if
          # new software is installed
  
          if self.backend_down:
              self._notify_backend_down()
          else:
              self._display_skill_loading_notification()
              self.bus.emit(Message('mycroft.internet.connected'))
              self._ensure_device_is_paired()
              self._update_device_attributes_on_backend()
  
      def _get_pairing_status(self):
          """Set an instance attribute indicating the device's pairing status"""
          try:
              self.is_paired = is_paired(ignore_errors=False)
          except BackendDown:
              LOG.error('Cannot complete device updates due to backend issues.')
              self.backend_down = True
  
          if self.is_paired:
              LOG.info('Device is paired')
  
      def _update_system_clock(self):
          """Force a sync of the local clock with the Network Time Protocol.
  
          The NTP sync is only forced on Raspberry Pi based devices.  The
          assumption being that these devices are only running Mycroft services.
          We don't want to sync the time on a Linux desktop device, for example,
          because it could have a negative impact on other software running on
          that device.
          """
          if self.platform in RASPBERRY_PI_PLATFORMS:
              LOG.info('Updating the system clock via NTP...')
              if self.is_paired:
                  # Only display time sync message when paired because the prompt
                  # to go to home.mycroft.ai will be displayed by the pairing
                  # skill when pairing
                  self.enclosure.mouth_text(dialog.get("message_synching.clock"))
              self.bus.wait_for_response(
                  Message('system.ntp.sync'),
                  'system.ntp.sync.complete',
                  15
              )
  
      def _notify_backend_down(self):
          """Notify user of inability to communicate with the backend."""
          self._speak_dialog(dialog_id="backend.down")
          self.bus.emit(Message("backend.down"))
  
      def _display_skill_loading_notification(self):
          """Indicate to the user that skills are being loaded."""
          self.enclosure.eyes_color(189, 183, 107)  # dark khaki
          self.enclosure.mouth_text(dialog.get("message_loading.skills"))
  
      def _ensure_device_is_paired(self):
          """Determine if device is paired, if not automatically start pairing.
  
          Pairing cannot be performed if there is no connection to the back end.
          So skip pairing if the backend is down.
          """
          if not self.is_paired and not self.backend_down:
              LOG.info('Device not paired, invoking the pairing skill')
              payload = dict(utterances=["pair my device"], lang="en-us")
              self.bus.emit(Message("recognizer_loop:utterance", payload))
  
      def _update_device_attributes_on_backend(self):
          """Communicate version information to the backend.
  
          The backend tracks core version, enclosure version, platform build
          and platform name for each device, if it is known.
          """
          if self.is_paired:
              LOG.info('Sending updated device attributes to the backend...')
              try:
                  api = DeviceApi()
                  api.update_version()
              except Exception:
                  self._notify_backend_down()
  
      def _update_system(self):
          """Emit an update event that will be handled by the admin service."""
          if not self.is_paired:
              LOG.info('Attempting system update...')
              self.bus.emit(Message('system.update'))
             
  
  [... the rest is omitted ...]
files: mycroft/skills/__main__.py
files: # 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.
  #
  """Load, update and manage skills on this device."""
  import os
  from glob import glob
  from threading import Thread, Event, Lock
  from time import sleep, time, monotonic
  
  from mycroft.api import is_paired
  from mycroft.enclosure.api import EnclosureAPI
  from mycroft.configuration import Configuration
  from mycroft.messagebus.message import Message
  from mycroft.util.log import LOG
  from .msm_wrapper import create_msm as msm_creator, build_msm_config
  from .settings import SkillSettingsDownloader
  from .skill_loader import SkillLoader
  from .skill_updater import SkillUpdater
  
  SKILL_MAIN_MODULE = '__init__.py'
  
  
  class UploadQueue:
      """Queue for holding loaders with data that still needs to be uploaded.
  
      This queue can be used during startup to capture all loaders
      and then processing can be triggered at a later stage when the system is
      connected to the backend.
  
      After all queued settingsmeta has been processed and the queue is empty
      the queue will set the self.started flag.
      """
      def __init__(self):
          self._queue = []
          self.started = False
          self.lock = Lock()
  
      def start(self):
          """Start processing of the queue."""
          self.started = True
          self.send()
  
      def stop(self):
          """Stop the queue, and hinder any further transmissions."""
          self.started = False
  
      def send(self):
          """Loop through all stored loaders triggering settingsmeta upload."""
          with self.lock:
              queue = self._queue
              self._queue = []
          if queue:
              LOG.info('New Settings meta to upload.')
              for loader in queue:
                  if self.started:
                      loader.instance.settings_meta.upload()
                  else:
                      break
  
      def __len__(self):
          return len(self._queue)
  
      def put(self, loader):
          """Append a skill loader to the queue.
  
          If a loader is already present it's removed in favor of the new entry.
          """
          if self.started:
              LOG.info('Updating settings meta during runtime...')
          with self.lock:
              # Remove existing loader
              self._queue == [e for e in self._queue if e != loader]
              self._queue.append(loader)
  
  
  def _shutdown_skill(instance):
      """Shutdown a skill.
  
      Call the default_shutdown method of the skill, will produce a warning if
      the shutdown process takes longer than 1 second.
  
      Arguments:
          instance (MycroftSkill): Skill instance to shutdown
      """
      try:
          ref_time = monotonic()
          # Perform the shutdown
          instance.default_shutdown()
  
          shutdown_time = monotonic() - ref_time
          if shutdown_time > 1:
              LOG.warning('{} shutdown took {} seconds'.format(instance.skill_id,
                                                               shutdown_time))
      except Exception:
          LOG.exception('Failed to shut down skill: '
                        '{}'.format(instance.skill_id))
  
  
  class SkillManager(Thread):
      _msm = None
  
      def __init__(self, bus, watchdog=None):
          """Constructor
  
          Arguments:
              bus (event emitter): Mycroft messagebus connection
              watchdog (callable): optional watchdog function
          """
          super(SkillManager, self).__init__()
          self.bus = bus
          # Set watchdog to argument or function returning None
          self._watchdog = watchdog or (lambda: None)
          self._stop_event = Event()
          self._connected_event = Event()
          self.config = Configuration.get()
          self.upload_queue = UploadQueue()
  
          self.skill_loaders = {}
          self.enclosure = EnclosureAPI(bus)
          self.initial_load_complete = False
          self.num_install_retries = 0
          self.settings_downloader = SkillSettingsDownloader(self.bus)
  
          # Statuses
          self._alive_status = False  # True after priority skills has loaded
          self._loaded_status = False  # True after all skills has loaded
  
          self.skill_updater = SkillUpdater()
          self._define_message_bus_events()
          self.daemon = True
  
      def _define_message_bus_events(self):
          """Define message bus events with handlers defined in this class."""
          # Conversation management
          self.bus.on('skill.converse.request', self.handle_converse_request)
  
          # Update on initial connection
          self.bus.on(
              'mycroft.internet.connected',
              lambda x: self._connected_event.set()
          )
  
          # Update upon request
          self.bus.on('skillmanager.update', self.schedule_now)
          self.bus.on('skillmanager.list', self.send_skill_list)
          self.bus.on('skillmanager.deactivate', self.deactivate_skill)
          self.bus.on('skillmanager.keep', self.deactivate_except)
          self.bus.on('skillmanager.activate', self.activate_skill)
          self.bus.on('mycroft.paired', self.handle_paired)
          self.bus.on(
              'mycroft.skills.settings.update',
              self.settings_downloader.download
          )
  
      @property
      def skills_config(self):
          return self.config['skills']
  
      @property
      def msm(self):
          if self._msm is None:
              msm_config = build_msm_config(self.config)
              self._msm = msm_creator(msm_config)
  
          return self._msm
  
      @staticmethod
      def create_msm():
          LOG.debug('instantiating msm via
  
  [... the rest is omitted ...]
files: mycroft/skills/skill_manager.py
files: # 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.
  #
  """Load, update and manage skills on this device."""
  import os
  from glob import glob
  from threading import Thread, Event, Lock
  from time import sleep, time, monotonic
  
  from mycroft.api import is_paired
  from mycroft.enclosure.api import EnclosureAPI
  from mycroft.configuration import Configuration
  from mycroft.messagebus.message import Message
  from mycroft.util.log import LOG
  from .msm_wrapper import create_msm as msm_creator, build_msm_config
  from .settings import SkillSettingsDownloader
  from .skill_loader import SkillLoader
  from .skill_updater import SkillUpdater
  
  SKILL_MAIN_MODULE = '__init__.py'
  
  
  class UploadQueue:
      """Queue for holding loaders with data that still needs to be uploaded.
  
      This queue can be used during startup to capture all loaders
      and then processing can be triggered at a later stage when the system is
      connected to the backend.
  
      After all queued settingsmeta has been processed and the queue is empty
      the queue will set the self.started flag.
      """
      def __init__(self):
          self._queue = []
          self.started = False
          self.lock = Lock()
  
      def start(self):
          """Start processing of the queue."""
          self.started = True
          self.send()
  
      def stop(self):
          """Stop the queue, and hinder any further transmissions."""
          self.started = False
  
      def send(self):
          """Loop through all stored loaders triggering settingsmeta upload."""
          with self.lock:
              queue = self._queue
              self._queue = []
          if queue:
              LOG.info('New Settings meta to upload.')
              for loader in queue:
                  if self.started:
                      loader.instance.settings_meta.upload()
                  else:
                      break
  
      def __len__(self):
          return len(self._queue)
  
      def put(self, loader):
          """Append a skill loader to the queue.
  
          If a loader is already present it's removed in favor of the new entry.
          """
          if self.started:
              LOG.info('Updating settings meta during runtime...')
          with self.lock:
              # Remove existing loader
              self._queue == [e for e in self._queue if e != loader]
              self._queue.append(loader)
  
  
  def _shutdown_skill(instance):
      """Shutdown a skill.
  
      Call the default_shutdown method of the skill, will produce a warning if
      the shutdown process takes longer than 1 second.
  
      Arguments:
          instance (MycroftSkill): Skill instance to shutdown
      """
      try:
          ref_time = monotonic()
          # Perform the shutdown
          instance.default_shutdown()
  
          shutdown_time = monotonic() - ref_time
          if shutdown_time > 1:
              LOG.warning('{} shutdown took {} seconds'.format(instance.skill_id,
                                                               shutdown_time))
      except Exception:
          LOG.exception('Failed to shut down skill: '
                        '{}'.format(instance.skill_id))
  
  
  class SkillManager(Thread):
      _msm = None
  
      def __init__(self, bus, watchdog=None):
          """Constructor
  
          Arguments:
              bus (event emitter): Mycroft messagebus connection
              watchdog (callable): optional watchdog function
          """
          super(SkillManager, self).__init__()
          self.bus = bus
          # Set watchdog to argument or function returning None
          self._watchdog = watchdog or (lambda: None)
          self._stop_event = Event()
          self._connected_event = Event()
          self.config = Configuration.get()
          self.upload_queue = UploadQueue()
  
          self.skill_loaders = {}
          self.enclosure = EnclosureAPI(bus)
          self.initial_load_complete = False
          self.num_install_retries = 0
          self.settings_downloader = SkillSettingsDownloader(self.bus)
  
          # Statuses
          self._alive_status = False  # True after priority skills has loaded
          self._loaded_status = False  # True after all skills has loaded
  
          self.skill_updater = SkillUpdater()
          self._define_message_bus_events()
          self.daemon = True
  
      def _define_message_bus_events(self):
          """Define message bus events with handlers defined in this class."""
          # Conversation management
          self.bus.on('skill.converse.request', self.handle_converse_request)
  
          # Update on initial connection
          self.bus.on(
              'mycroft.internet.connected',
              lambda x: self._connected_event.set()
          )
  
          # Update upon request
          self.bus.on('skillmanager.update', self.schedule_now)
          self.bus.on('skillmanager.list', self.send_skill_list)
          self.bus.on('skillmanager.deactivate', self.deactivate_skill)
          self.bus.on('skillmanager.keep', self.deactivate_except)
          self.bus.on('skillmanager.activate', self.activate_skill)
          self.bus.on('mycroft.paired', self.handle_paired)
          self.bus.on('mycroft.skills.is_alive', self.is_alive)
          self.bus.on('mycroft.skills.all_loaded', self.is_all_loaded)
          self.bus.on(
              'mycroft.skills.settings.update',
              self.settings_downloader.download
          )
  
      @property
      def skills_config(self):
          return self.config['skills']
  
      @property
      def msm(self):
          if self._msm is None:
              msm_config = build_msm_config(self.config)
             
  
  [... the rest is omitted ...]
files: mycroft/skills/skill_manager.py
files: from collections import namedtuple
  from enum import IntEnum
  import json
  import logging
  import signal as sig
  import sys
  from threading import Event, Thread
  from time import sleep
  
  from .log import LOG
  
  
  def reset_sigint_handler():
      """Reset the sigint handler to the default.
  
      This fixes KeyboardInterrupt not getting raised when started via
      start-mycroft.sh
      """
      sig.signal(sig.SIGINT, sig.default_int_handler)
  
  
  def create_daemon(target, args=(), kwargs=None):
      """Helper to quickly create and start a thread with daemon = True"""
      t = Thread(target=target, args=args, kwargs=kwargs)
      t.daemon = True
      t.start()
      return t
  
  
  def wait_for_exit_signal():
      """Blocks until KeyboardInterrupt is received."""
      try:
          while True:
              sleep(100)
      except KeyboardInterrupt:
          pass
  
  
  _log_all_bus_messages = False
  
  
  def bus_logging_status():
      global _log_all_bus_messages
      return _log_all_bus_messages
  
  
  def _update_log_level(msg, name):
      """Update log level for process.
  
      Arguments:
          msg (Message): Message sent to trigger the log level change
          name (str): Name of the current process
      """
      global _log_all_bus_messages
  
      # Respond to requests to adjust the logger settings
      lvl = msg["data"].get("level", "").upper()
      if lvl in ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]:
          LOG.level = lvl
          LOG(name).info("Changing log level to: {}".format(lvl))
          try:
              logging.getLogger().setLevel(lvl)
              logging.getLogger('urllib3').setLevel(lvl)
          except Exception:
              pass  # We don't really care about if this fails...
      else:
          LOG(name).info("Invalid level provided: {}".format(lvl))
  
      # Allow enable/disable of messagebus traffic
      log_bus = msg["data"].get("bus", None)
      if log_bus is not None:
          LOG(name).info("Bus logging: {}".format(log_bus))
          _log_all_bus_messages = log_bus
  
  
  def create_echo_function(name, whitelist=None):
      """Standard logging mechanism for Mycroft processes.
  
      This handles the setup of the basic logging for all Mycroft
      messagebus-based processes.
      TODO 20.08: extract log level setting thing completely from this function
  
      Arguments:
          name (str): Reference name of the process
          whitelist (list, optional): List of "type" strings. If defined, only
                                      messages in this list will be logged.
  
      Returns:
          func: The echo function
      """
  
      from mycroft.configuration import Configuration
      blacklist = Configuration.get().get("ignore_logs")
  
      # Make sure whitelisting doesn't remove the log level setting command
      if whitelist:
          whitelist.append('mycroft.debug.log')
  
      def echo(message):
          global _log_all_bus_messages
          try:
              msg = json.loads(message)
              msg_type = msg.get("type", "")
              # Whitelist match beginning of message
              # i.e 'mycroft.audio.service' will allow the message
              # 'mycroft.audio.service.play' for example
              if whitelist and not any([msg_type.startswith(e)
                                       for e in whitelist]):
                  return
  
              if blacklist and msg_type in blacklist:
                  return
  
              if msg_type == "mycroft.debug.log":
                  _update_log_level(msg, name)
              elif msg_type == "registration":
                  # do not log tokens from registration messages
                  msg["data"]["token"] = None
                  message = json.dumps(msg)
          except Exception as e:
              LOG.info("Error: {}".format(repr(e)), exc_info=True)
  
          if _log_all_bus_messages:
              # Listen for messages and echo them for logging
              LOG(name).info("BUS: {}".format(message))
      return echo
  
  
  def start_message_bus_client(service, bus=None, whitelist=None):
      """Start the bus client daemon and wait for connection.
  
      Arguments:
          service (str): name of the service starting the connection
          bus (MessageBusClient): an instance of the Mycroft MessageBusClient
          whitelist (list, optional): List of "type" strings. If defined, only
                                      messages in this list will be logged.
      Returns:
          A connected instance of the MessageBusClient
      """
      # Local imports to avoid circular importing
      from mycroft.messagebus.client import MessageBusClient
      from mycroft.configuration import Configuration
      # Create a client if one was not provided
      if bus is None:
          bus = MessageBusClient()
      Configuration.set_config_update_handlers(bus)
      bus_connected = Event()
      bus.on('message', create_echo_function(service, whitelist))
      # Set the bus connected event when connection is established
      bus.once('open', bus_connected.set)
      create_daemon(bus.run_forever)
  
      # Wait for connection
      bus_connected.wait()
      LOG.info('Connected to messagebus')
  
      return bus
  
  
  class ProcessState(IntEnum):
  
      """Oredered enum to make state checks easy.
  
      For example Alive can be determined using >= ProcessState.ALIVE,
      which will return True if the state is READY as well as ALIVE.
      """
      NOT_STARTED = 0
      STARTED = 1
      ERROR = 2
      STOPPING = 3
      ALIVE = 4
      READY = 5
  
  
  # Process state change callback mappings.
  _STATUS_CALLBACKS = [
      'on_started',
      'on_alive',
      'on_complete',
      'on_error',
      'on_stopping',
  ]
  # namedtuple defaults only available on 3.7 and later python versions
  if sys.version_info < (3, 7):
      StatusCallbackMap = namedtuple('CallbackMap', _STATUS_CALLBACK
  
  [... the rest is omitted ...]
files: mycroft/util/process_utils.py
files: import json
  import logging
  import signal as sig
  from threading import Event, Thread
  from time import sleep
  
  from .log import LOG
  
  
  def reset_sigint_handler():
      """Reset the sigint handler to the default.
  
      This fixes KeyboardInterrupt not getting raised when started via
      start-mycroft.sh
      """
      sig.signal(sig.SIGINT, sig.default_int_handler)
  
  
  def create_daemon(target, args=(), kwargs=None):
      """Helper to quickly create and start a thread with daemon = True"""
      t = Thread(target=target, args=args, kwargs=kwargs)
      t.daemon = True
      t.start()
      return t
  
  
  def wait_for_exit_signal():
      """Blocks until KeyboardInterrupt is received."""
      try:
          while True:
              sleep(100)
      except KeyboardInterrupt:
          pass
  
  
  _log_all_bus_messages = False
  
  
  def bus_logging_status():
      global _log_all_bus_messages
      return _log_all_bus_messages
  
  
  def _update_log_level(msg, name):
      """Update log level for process.
  
      Arguments:
          msg (Message): Message sent to trigger the log level change
          name (str): Name of the current process
      """
      global _log_all_bus_messages
  
      # Respond to requests to adjust the logger settings
      lvl = msg["data"].get("level", "").upper()
      if lvl in ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]:
          LOG.level = lvl
          LOG(name).info("Changing log level to: {}".format(lvl))
          try:
              logging.getLogger().setLevel(lvl)
              logging.getLogger('urllib3').setLevel(lvl)
          except Exception:
              pass  # We don't really care about if this fails...
      else:
          LOG(name).info("Invalid level provided: {}".format(lvl))
  
      # Allow enable/disable of messagebus traffic
      log_bus = msg["data"].get("bus", None)
      if log_bus is not None:
          LOG(name).info("Bus logging: {}".format(log_bus))
          _log_all_bus_messages = log_bus
  
  
  def create_echo_function(name, whitelist=None):
      """Standard logging mechanism for Mycroft processes.
  
      This handles the setup of the basic logging for all Mycroft
      messagebus-based processes.
      TODO 20.08: extract log level setting thing completely from this function
  
      Arguments:
          name (str): Reference name of the process
          whitelist (list, optional): List of "type" strings. If defined, only
                                      messages in this list will be logged.
  
      Returns:
          func: The echo function
      """
  
      from mycroft.configuration import Configuration
      blacklist = Configuration.get().get("ignore_logs")
  
      # Make sure whitelisting doesn't remove the log level setting command
      if whitelist:
          whitelist.append('mycroft.debug.log')
  
      def echo(message):
          global _log_all_bus_messages
          try:
              msg = json.loads(message)
              msg_type = msg.get("type", "")
              # Whitelist match beginning of message
              # i.e 'mycroft.audio.service' will allow the message
              # 'mycroft.audio.service.play' for example
              if whitelist and not any([msg_type.startswith(e)
                                       for e in whitelist]):
                  return
  
              if blacklist and msg_type in blacklist:
                  return
  
              if msg_type == "mycroft.debug.log":
                  _update_log_level(msg, name)
              elif msg_type == "registration":
                  # do not log tokens from registration messages
                  msg["data"]["token"] = None
                  message = json.dumps(msg)
          except Exception as e:
              LOG.info("Error: {}".format(repr(e)), exc_info=True)
  
          if _log_all_bus_messages:
              # Listen for messages and echo them for logging
              LOG(name).info("BUS: {}".format(message))
      return echo
  
  
  def start_message_bus_client(service, bus=None, whitelist=None):
      """Start the bus client daemon and wait for connection.
  
      Arguments:
          service (str): name of the service starting the connection
          bus (MessageBusClient): an instance of the Mycroft MessageBusClient
          whitelist (list, optional): List of "type" strings. If defined, only
                                      messages in this list will be logged.
      Returns:
          A connected instance of the MessageBusClient
      """
      # Local imports to avoid circular importing
      from mycroft.messagebus.client import MessageBusClient
      from mycroft.configuration import Configuration
      # Create a client if one was not provided
      if bus is None:
          bus = MessageBusClient()
      Configuration.set_config_update_handlers(bus)
      bus_connected = Event()
      bus.on('message', create_echo_function(service, whitelist))
      # Set the bus connected event when connection is established
      bus.once('open', bus_connected.set)
      create_daemon(bus.run_forever)
  
      # Wait for connection
      bus_connected.wait()
      LOG.info('Connected to messagebus')
  
      return bus
  
  
  [... the rest is omitted ...]
files: mycroft/util/process_utils.py