__init__.py 9.2 KB

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