OctoPrint-Tentacles

import enum

import octoprint.plugin

from . import actions, keypad, menu



class Tentacles(octoprint.plugin.StartupPlugin,
        octoprint.plugin.SettingsPlugin,
        octoprint.plugin.EventHandlerPlugin,
        octoprint.plugin.TemplatePlugin):

    def __init__(self):
        self._tentacles = {}
        self._mode = menu.Mode.UNINITIALIZED

    def on_after_startup(self, *args, **kwargs):

        # Load available actions for tentacles to use (just print them out now)
        self._logger.debug('Loading actions...')
        for action in actions.ACTIONS.keys():
            self._logger.debug(f'Action: {action}')

        # This should be dynamic 
        action_init_args = dict(printer=self._printer, tentacles=self)

        # Load saved tentacle settings from octoprint instance
        # and map actions to keycodes
        self._logger.info('Loading tentacles...')
        tentacles_settings = self._settings.get(['tentacles'])
        menu_start_key = None
        for tentacle_code, tentacle_config in tentacles_settings.items():
            self._logger.debug(f"Loading tentacle: {tentacle_code}")
            self._tentacles[tentacle_code] = {}
            for mode, mode_action in tentacle_config.items():
                self._logger.debug(f"Loading action: {mode_action['action']} for mode: {mode}")
                if ('action' in mode_action) and (mode_action['action'] in actions.ACTIONS) and (menu.Mode[mode.upper()] in menu.Mode):
                    
                    # Find the menu_start key to make it easy to reference later
                    if mode_action['action'] == 'menu_start':
                        menu_start_key = tentacle_code

                    # Init action passing it all arguments for now
                    key_action = actions.ACTIONS[mode_action['action']](**action_init_args)

                    # Configure action with defined args, or use defaults if none exist
                    if 'args' in mode_action:
                        key_action.configure(**mode_action['args'])
                    else:
                        key_action.configure()

                    # Configure name if defined
                    if 'name' in mode_action:
                        action_name = mode_action['name']
                    else:
                        action_name = mode_action['action'].title()

                    self._tentacles[tentacle_code][menu.Mode[mode.upper()]] = {
                        'action': key_action,
                        'name': action_name
                    }
                else:
                    self._logger.warn(f"Invalid tentacle config: {mode} - {mode_action}")

        if menu_start_key is not None:
            # Setup our menu to switch modes
            menu_start = menu.Mode(menu.Mode.MENU + 1)
            menu_end = menu.Mode.MAX()

            # Setup menu button for every mode
            for mode in range(menu_start, menu_end+1):
                self._tentacles[menu_start_key][menu.Mode(mode)] = \
                    self._tentacles[menu_start_key][menu.Mode.MENU]
            
            # Clear menu_start button while in MENU mode
            del self._tentacles[menu_start_key][menu.Mode.MENU]

            # Init Menu object
            self._menu = menu.Menu(self._printer, menu_start, menu_end)
        else:
            self._logger.warn('No menu key defined! You will not be able to change modes!')
        # Configure serial port for our tentacle device
        serial_conf = self._settings.get(['serial'], merged=True)
        self._logger.info('Starting keypad listening thread...')
        self._keypad_listener = keypad.KeypadListener(serial_conf['port'], serial_conf['baud'], self._logger, self._event_bus).start()


    def on_event(self, event, payload, *args, **kwargs):
        
        #Listen for key press events that get emitted from our KeypadListener
        # and execute the action mapped to the keycode
        if event == 'plugin_tentacle_key_press':
            keycode = payload['keycode'][0]
            self._logger.debug(f'Got keypress: {keycode}')
            try:
                tentacle_action = self._tentacles[keycode][self._mode]['action']
            except KeyError:
                self._logger.debug(f'Keycode {keycode} is not attached to an action!')
                return
            if isinstance(tentacle_action, actions.BaseAction):
                tentacle_action._run()
            else:
                self._logger.err(f"Unknown action: {tentacle_action}")
        
        if event == 'plugin_tentacle_key_release':
            keycode = payload['keycode'][0] - 128
            try:
                tentacle_action = self._tentacles[keycode][self._mode]['action']
            except KeyError:
                self._logger.debug(f'Keycode {keycode} is not attached to an action!')
                return
            if isinstance(tentacle_action, actions.BaseAction):
                if tentacle_action._running:
                    tentacle_action.stop()
            else:
                self._logger.err(f"Unknown action: {tentacle_action}")


        # Change modes on printer state change
        if event == 'PrinterStateChanged':

            if payload['state_id'] == 'PRINTING':
                self._mode = menu.Mode.PRINTING
            elif payload['state_id'] == 'OPERATIONAL':
                self._mode = menu.Mode.CONTROL

    def get_template_vars(self):
        tentacle_vars = {}
        for tentacle_code, tentacle_config in self._tentacles.items():
            tentacle_vars[tentacle_code] = {}
            for mode, action in tentacle_config.items():
                if isinstance(action['action'], actions.BaseAction):
                    action_name = action['action'].name
                    action_desc = action['name']
                else:
                    action_name = action['action']
                    action_desc = action_name
                tentacle_vars[tentacle_code][mode.name] =  {
                    'action': action_name,
                    'name': action_desc
                }
        return {
            'tentacles': tentacle_vars
        }

    # Default setting are no tentacles, and serial0 (default raspi UART)
    def get_settings_defaults(self):
        return {
            'serial':
                {
                    'port': '/dev/serial0',
                    'baud': 9600
                }
        }

def register_tentacle_events(*args, **kwargs):
    return ['key_press', 'key_release']