__init__.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. #!/usr/bin/env python
  2. # coding: utf8
  3. import argparse
  4. import logging
  5. import time
  6. from kalliope.core import ShellGui
  7. from kalliope.core import Utils
  8. from kalliope.core.ConfigurationManager import SettingLoader
  9. from kalliope.core.ConfigurationManager.BrainLoader import BrainLoader
  10. from kalliope.core.SignalLauncher import SignalLauncher
  11. from kalliope.core.Utils.RpiUtils import RpiUtils
  12. from flask import Flask
  13. from kalliope.core.RestAPI.FlaskAPI import FlaskAPI
  14. from ._version import version_str
  15. import signal
  16. import sys
  17. from kalliope.core.ResourcesManager import ResourcesManager
  18. from kalliope.core.SynapseLauncher import SynapseLauncher
  19. from kalliope.core.OrderAnalyser import OrderAnalyser
  20. logging.basicConfig()
  21. logger = logging.getLogger("kalliope")
  22. def signal_handler(signal, frame):
  23. """
  24. Used to catch a keyboard signal like Ctrl+C in order to kill the kalliope program.
  25. :param signal: signal handler
  26. :param frame: execution frame
  27. """
  28. print("\n")
  29. Utils.print_info("Ctrl+C pressed. Killing Kalliope")
  30. sys.exit(0)
  31. # actions available
  32. ACTION_LIST = ["start", "gui", "install", "uninstall"]
  33. def parse_args(args):
  34. """
  35. Parsing function
  36. :param args: arguments passed from the command line
  37. :return: return parser
  38. """
  39. # create arguments
  40. parser = argparse.ArgumentParser(description='Kalliope')
  41. parser.add_argument("action", help="[start|gui|install|uninstall]")
  42. parser.add_argument("--run-synapse",
  43. help="Name of a synapse to load surrounded by quote")
  44. parser.add_argument("--run-order", help="order surrounded by a quote")
  45. parser.add_argument("--brain-file", help="Full path of a brain file")
  46. parser.add_argument("--debug", action='store_true',
  47. help="Show debug output")
  48. parser.add_argument("--git-url", help="Git URL of the neuron to install")
  49. parser.add_argument("--neuron-name", help="Neuron name to uninstall")
  50. parser.add_argument("--stt-name", help="STT name to uninstall")
  51. parser.add_argument("--tts-name", help="TTS name to uninstall")
  52. parser.add_argument("--trigger-name", help="Trigger name to uninstall")
  53. parser.add_argument("--signal-name", help="Signal name to uninstall")
  54. parser.add_argument("--muted", action='store_true', help="Starts Kalliope muted")
  55. parser.add_argument('-v', '--version', action='version',
  56. version='Kalliope ' + version_str)
  57. # parse arguments from script parameters
  58. return parser.parse_args(args)
  59. def main():
  60. """Entry point of Kalliope program."""
  61. # parse argument. the script name is removed
  62. try:
  63. parser = parse_args(sys.argv[1:])
  64. except SystemExit:
  65. sys.exit(1)
  66. # check if we want debug
  67. configure_logging(debug=parser.debug)
  68. logger.debug("kalliope args: %s" % parser)
  69. # by default, no brain file is set.
  70. # Use the default one: brain.yml in the root path
  71. brain_file = None
  72. # check if user set a brain.yml file
  73. if parser.brain_file:
  74. brain_file = parser.brain_file
  75. # check the user provide a valid action
  76. if parser.action not in ACTION_LIST:
  77. Utils.print_warning("%s is not a recognised action\n" % parser.action)
  78. sys.exit(1)
  79. # install modules
  80. if parser.action == "install":
  81. if not parser.git_url:
  82. Utils.print_danger("You must specify the git url")
  83. sys.exit(1)
  84. else:
  85. parameters = {
  86. "git_url": parser.git_url
  87. }
  88. res_manager = ResourcesManager(**parameters)
  89. res_manager.install()
  90. return
  91. # uninstall modules
  92. if parser.action == "uninstall":
  93. if not parser.neuron_name \
  94. and not parser.stt_name \
  95. and not parser.tts_name \
  96. and not parser.trigger_name\
  97. and not parser.signal_name:
  98. Utils.print_danger("You must specify a module name with "
  99. "--neuron-name "
  100. "or --stt-name "
  101. "or --tts-name "
  102. "or --trigger-name "
  103. "or --signal-name")
  104. sys.exit(1)
  105. else:
  106. res_manager = ResourcesManager()
  107. res_manager.uninstall(neuron_name=parser.neuron_name,
  108. stt_name=parser.stt_name,
  109. tts_name=parser.tts_name,
  110. trigger_name=parser.trigger_name,
  111. signal_name=parser.signal_name)
  112. return
  113. # load the brain once
  114. brain_loader = BrainLoader(file_path=brain_file)
  115. brain = brain_loader.brain
  116. # load settings
  117. # get global configuration once
  118. settings_loader = SettingLoader()
  119. settings = settings_loader.settings
  120. if parser.action == "start":
  121. # user set a synapse to start
  122. if parser.run_synapse is not None:
  123. SynapseLauncher.start_synapse_by_name(parser.run_synapse,
  124. brain=brain)
  125. if parser.run_order is not None:
  126. SynapseLauncher.run_matching_synapse_from_order(parser.run_order,
  127. brain=brain,
  128. settings=settings,
  129. is_api_call=False)
  130. if (parser.run_synapse is None) and (parser.run_order is None):
  131. # if --muted
  132. if parser.muted:
  133. settings.start_options['muted'] = True
  134. # start rest api
  135. start_rest_api(settings, brain)
  136. start_kalliope(settings, brain)
  137. if parser.action == "gui":
  138. try:
  139. ShellGui(brain=brain)
  140. except (KeyboardInterrupt, SystemExit):
  141. Utils.print_info("Ctrl+C pressed. Killing Kalliope")
  142. sys.exit(0)
  143. class AppFilter(logging.Filter):
  144. """
  145. Class used to add a custom entry into the logger
  146. """
  147. def filter(self, record):
  148. record.app_version = "kalliope-%s" % version_str
  149. return True
  150. def configure_logging(debug=None):
  151. """
  152. Prepare log folder in current home directory.
  153. :param debug: If true, set the lof level to debug
  154. """
  155. logger = logging.getLogger("kalliope")
  156. logger.addFilter(AppFilter())
  157. logger.propagate = False
  158. syslog = logging.StreamHandler()
  159. syslog .setLevel(logging.DEBUG)
  160. formatter = logging.Formatter('%(asctime)s :: %(app_version)s :: %(message)s', "%Y-%m-%d %H:%M:%S")
  161. syslog .setFormatter(formatter)
  162. if debug:
  163. logger.setLevel(logging.DEBUG)
  164. else:
  165. logger.setLevel(logging.INFO)
  166. # add the handlers to logger
  167. logger.addHandler(syslog)
  168. logger.debug("Logger ready")
  169. def get_list_signal_class_to_load(brain):
  170. """
  171. Return a list of signal class name
  172. For all synapse, each signal type is added to a list only if the signal is not yet present in the list
  173. :param brain: Brain object
  174. :type brain: Brain
  175. :return: set of signal class
  176. """
  177. list_signal_class_name = set()
  178. for synapse in brain.synapses:
  179. for signal_object in synapse.signals:
  180. list_signal_class_name.add(signal_object.name)
  181. logger.debug("[Kalliope entrypoint] List of signal class to load: %s" % list_signal_class_name)
  182. return list_signal_class_name
  183. def start_rest_api(settings, brain):
  184. """
  185. Start the Rest API if asked in the user settings
  186. """
  187. # run the api if the user want it
  188. if settings.rest_api.active:
  189. Utils.print_info("Starting REST API Listening port: %s" % settings.rest_api.port)
  190. app = Flask(__name__)
  191. flask_api = FlaskAPI(app=app,
  192. port=settings.rest_api.port,
  193. brain=brain,
  194. allowed_cors_origin=settings.rest_api.allowed_cors_origin)
  195. flask_api.daemon = True
  196. flask_api.start()
  197. def start_kalliope(settings, brain):
  198. """
  199. Start all signals declared in the brain
  200. """
  201. # start kalliope
  202. Utils.print_success("Starting Kalliope")
  203. Utils.print_info("Press Ctrl+C for stopping")
  204. # catch signal for killing on Ctrl+C pressed
  205. signal.signal(signal.SIGINT, signal_handler)
  206. # get a list of signal class to load from declared synapse in the brain
  207. # this list will contain string of signal class type.
  208. # For example, if the brain contains multiple time the signal type "order", the list will be ["order"]
  209. # If the brain contains some synapse with "order" and "event", the list will be ["order", "event"]
  210. list_signals_class_to_load = get_list_signal_class_to_load(brain)
  211. # start each class name
  212. try:
  213. for signal_class_name in list_signals_class_to_load:
  214. signal_instance = SignalLauncher.launch_signal_class_by_name(signal_name=signal_class_name,
  215. settings=settings)
  216. if signal_instance is not None:
  217. signal_instance.daemon = True
  218. signal_instance.start()
  219. while True: # keep main thread alive
  220. time.sleep(0.1)
  221. except (KeyboardInterrupt, SystemExit):
  222. # we need to switch GPIO pin to default status if we are using a Rpi
  223. if settings.rpi_settings:
  224. Utils.print_info("GPIO cleaned")
  225. logger.debug("Clean GPIO")
  226. import RPi.GPIO as GPIO
  227. GPIO.cleanup()