Beispiel #1
0
class Protocol(InstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    __metaclass__ = META_LOGGER

    def __init__(self, driver_event):
        """
        Protocol constructor.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        InstrumentProtocol.__init__(self, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent,
                                           ProtocolEvent.ENTER, ProtocolEvent.EXIT)

        # Add event handlers for protocol state machine.
        handlers = {
            ProtocolState.UNKNOWN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.DISCOVER, self._handler_unknown_discover),
            ],
            ProtocolState.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample),
                (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_start_poll),
                (ProtocolEvent.CALIBRATE, self._handler_command_start_calibrate),
                (ProtocolEvent.START_NAFION, self._handler_command_start_nafion_regen),
                (ProtocolEvent.START_ION, self._handler_command_start_ion_regen),
                (ProtocolEvent.ERROR, self._handler_error),
                (ProtocolEvent.POWEROFF, self._handler_command_poweroff),
                (ProtocolEvent.START_MANUAL, self._handler_command_start_manual),
            ],
            ProtocolState.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_autosample_acquire_sample),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.POLL: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.CLEAR, self._handler_error_clear),
            ],
            ProtocolState.CALIBRATE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.REGEN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_REGEN, self._handler_stop_regen),
                (ProtocolEvent.REGEN_COMPLETE, self._handler_regen_complete),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_direct_access_exit),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
            ],
            ProtocolState.MANUAL_OVERRIDE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_MANUAL, self._handler_manual_override_stop),
                (ProtocolEvent.GET_SLAVE_STATES, self._handler_manual_get_slave_states),
            ],
        }

        for state in handlers:
            for event, handler in handlers[state]:
                self._protocol_fsm.add_handler(state, event, handler)

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # State state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._slave_protocols = {}
        self.initialize_scheduler()

    def _add_manual_override_handlers(self):
        for slave in self._slave_protocols:
            for event in self._slave_protocols[slave]._cmd_dict._cmd_dict:
                self._protocol_fsm.add_handler(ProtocolState.MANUAL_OVERRIDE,
                                               event, self._build_override_handler(slave, event))

    def _build_override_handler(self, slave, event):
        log.debug('Building event handler for protocol: %s event: %s', slave, event)

        def inner():
            return None, self._slave_protocols[slave]._protocol_fsm.on_event(event)
        return inner

    def register_slave_protocol(self, name, protocol):
        """
        @param name: slave protocol name
        @param protocol: slave protocol instance
        @return: None
        """
        self._slave_protocols[name] = protocol

    def _slave_protocol_event(self, event, *args, **kwargs):
        """
        Handle an event from a slave protocol.
        @param event: event to be processed
        """
        name = kwargs.get('name')
        if name is not None and name in self._slave_protocols:
            # only react to slave protocol events once we have transitioned out of unknown
            if self.get_current_state() != ProtocolState.UNKNOWN or event == DriverAsyncEvent.ERROR:
                if event == DriverAsyncEvent.STATE_CHANGE:
                    self._react()
                elif event == DriverAsyncEvent.CONFIG_CHANGE:
                    # do nothing, we handle this ourselves in set_param
                    pass
                else:
                    # pass the event up to the instrument agent
                    log.debug('Passing event up to the Instrument agent: %r %r %r', event, args, kwargs)
                    self._driver_event(event, *args)

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with parameters.
        For each parameter key, add match string, match lambda function,
        and value formatting function for set commands.
        """
        self._param_dict.add(Parameter.SAMPLE_INTERVAL, '', None, int,
                             type=ParameterDictType.INT,
                             display_name='Autosample Interval',
                             description='Interval between sample starts during autosample state',
                             range=(7200, 86400),
                             units=Units.SECOND)

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.ACQUIRE_SAMPLE, display_name="Acquire Sample")
        self._cmd_dict.add(Capability.START_AUTOSAMPLE, display_name="Start Autosample")
        self._cmd_dict.add(Capability.CALIBRATE, display_name="Acquire Calibration Samples")
        self._cmd_dict.add(Capability.START_ION, display_name="Start Ion Chamber Regeneration")
        self._cmd_dict.add(Capability.START_NAFION, display_name="Start Nafion Regeneration")
        self._cmd_dict.add(Capability.STOP_REGEN, display_name="Stop Current Regeneration")
        self._cmd_dict.add(Capability.STOP_AUTOSAMPLE, display_name="Stop Autosample")
        self._cmd_dict.add(Capability.POWEROFF, display_name='Low Power State')
        self._cmd_dict.add(Capability.GET_SLAVE_STATES,
                           display_name='Get Slave States')
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False)

    def _react(self):
        """
        Determine if an action is necessary based on the states of the slave protocols.

            (MCU STATE, TURBO STATE, RGA STATE) : (TARGET, EVENT)

        The specified event will be sent to the specified target.
        """
        state = self.get_current_state()
        slave_states = self._get_slave_states()

        if MASSP_STATE_ERROR in slave_states:
            return self._error()

        if state == ProtocolState.REGEN and slave_states[0] == ProtocolState.COMMAND:
            self._async_raise_fsm_event(ProtocolEvent.REGEN_COMPLETE)

        # these actions are only applicable in POLL, AUTOSAMPLE or CALIBRATE states
        if state not in [ProtocolState.POLL, ProtocolState.AUTOSAMPLE, ProtocolState.CALIBRATE]:
            return

        mps = mcu.ProtocolState
        tps = turbo.ProtocolState
        rps = rga.ProtocolState
        action_map = {
            # Waiting Turbo (RGA is off)
            (mps.WAITING_TURBO, tps.COMMAND, rps.COMMAND): (TURBO, turbo.Capability.START_TURBO),
            (mps.WAITING_TURBO, tps.AT_SPEED, rps.COMMAND): (MCU, mcu.Capability.START2),

            # Waiting RGA
            (mps.WAITING_RGA, tps.AT_SPEED, rps.SCAN): (MCU, mcu.Capability.SAMPLE),
            (mps.WAITING_RGA, tps.AT_SPEED, rps.COMMAND): (RGA, rga.Capability.START_SCAN),
            (mps.WAITING_RGA, tps.COMMAND, rps.SCAN): (RGA, rga.Capability.STOP_SCAN),  # this should never happen!
            (mps.WAITING_RGA, tps.COMMAND, rps.COMMAND): (MCU, mcu.Capability.STANDBY),  # this should never happen!

            # Stopping
            (mps.STOPPING, tps.AT_SPEED, rps.SCAN): (RGA, rga.Capability.STOP_SCAN),
            (mps.STOPPING, tps.AT_SPEED, rps.COMMAND): (TURBO, turbo.Capability.STOP_TURBO),
            (mps.STOPPING, tps.COMMAND, rps.SCAN): (RGA, rga.Capability.STOP_SCAN),  # this should never happen!
            (mps.STOPPING, tps.COMMAND, rps.COMMAND): (MCU, mcu.Capability.STANDBY),
        }

        action = action_map.get(self._get_slave_states())

        if action is not None:
            if not isinstance(action, list):
                action = [action]

            # iterate through the action list, sending the events to the targets
            # if we are in POLL or CALIBRATE and we see a STANDBY event, return this driver to COMMAND.
            for target, command in action:
                if command == mcu.Capability.SAMPLE and state == ProtocolState.CALIBRATE:
                    command = mcu.Capability.CALIBRATE
                if command == mcu.Capability.STANDBY and state in [ProtocolState.CALIBRATE, ProtocolState.POLL]:
                    self._send_event_to_slave(target, command)
                    self._async_raise_fsm_event(ProtocolEvent.STOP)
                else:
                    self._send_event_to_slave(target, command)
        return action

    def _error(self):
        """
        Handle error state in slave protocol
        """
        state = self.get_current_state()
        slave_states = self._get_slave_states()

        # if we are not currently in the error state, make the transition
        if state != ProtocolState.ERROR:
            self._async_raise_fsm_event(ProtocolEvent.ERROR)
        mcu_state, turbo_state, rga_state = slave_states

        # before we do anything else, the RGA must be stopped.
        if rga_state not in [rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR]:
            self._send_event_to_slave(RGA, rga.ProtocolEvent.STOP_SCAN)
        # RGA must be in COMMAND or ERROR, the TURBO must be stopped.
        elif turbo_state not in [turbo.ProtocolState.COMMAND, turbo.ProtocolState.SPINNING_DOWN]:
            self._send_event_to_slave(TURBO, turbo.ProtocolEvent.STOP_TURBO)
        # Turbo and RGA must be in COMMAND or ERROR, stop the MCU
        elif mcu_state != mcu.ProtocolState.COMMAND:
            self._send_event_to_slave(MCU, mcu.ProtocolEvent.STANDBY)

    def _got_chunk(self, chunk):
        """
        This driver has no chunker...
        """

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        @param events: Events to be filtered
        @return: list of events which are also capabilities
        """
        return [x for x in events if Capability.has(x) or mcu.Capability.has(x) or turbo.Capability.has(x) or
                rga.Capability.has(x)]

    def _get_slave_states(self):
        """
        Retrieve the current protocol state from each of the slave protocols and return them as a tuple.
        """
        return (
            self._slave_protocols[MCU].get_current_state(),
            self._slave_protocols[TURBO].get_current_state(),
            self._slave_protocols[RGA].get_current_state(),
        )

    def _send_event_to_all(self, event):
        """
        Send the same event to all slave protocols.
        @return: List of (name, result) for a slave protocols
        """
        return [(name, slave._protocol_fsm.on_event(event)) for name, slave in self._slave_protocols.items()]

    def _send_event_to_slave(self, name, event):
        """
        Send an event to a specific protocol
        @param name: Name of slave protocol
        @param event: Event to be sent
        """
        slave_protocol = self._slave_protocols.get(name)
        if slave_protocol is None:
            raise InstrumentProtocolException('Attempted to send event to non-existent protocol: %s' % name)
        slave_protocol._async_raise_fsm_event(event)

    def _send_massp_direct_access(self, command):
        """
        Handle a direct access command.  Driver expects direct access commands to specify the target
        using the following format:

        target:command

        It then routes the command to the appropriate slave protocol.
        @param command: Direct access command received
        """
        err_string = 'Invalid command.  Command must be in the following format: "target:command' + NEWLINE + \
                     'Valid targets are: %r' % self._slave_protocols.keys()
        try:
            target, command = command.split(DA_COMMAND_DELIMITER, 1)
            target = target.lower()
        except ValueError:
            target = None

        log.debug('_do_cmd_direct - target: %s command: %r', target, command)

        if target not in self._slave_protocols:
            self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, err_string)
        else:
            self._slave_protocols[target]._protocol_fsm.on_event(ProtocolEvent.EXECUTE_DIRECT, command)

    def _set_params(self, *args, **kwargs):
        """
        Set one or more parameters.  This method will determine where the parameter actually resides and
        forward it to the appropriate parameter dictionary based on name.
        @param args: arglist which must contain a parameter dictionary
        @throws InstrumentParameterException
        """
        params = args[0]

        if not isinstance(params, dict):
            raise InstrumentParameterException('Attempted to set parameters with a non-dictionary argument')

        _, old_config = self._handler_command_get([Parameter.ALL])

        temp_dict = {}
        for key in params:
            split_key = key.split('_', 1)
            if len(split_key) == 1:
                raise InstrumentParameterException('Missing target in MASSP parameter: %s' % key)
            target = split_key[0]
            if target not in self._slave_protocols:
                # this is a master driver parameter, set it here
                if key in self._param_dict.get_keys():
                    log.debug("Setting value for %s to %s", key, params[key])
                    self._param_dict.set_value(key, params[key])
                else:
                    raise InstrumentParameterException('Invalid key in SET action: %s' % key)
            else:
                temp_dict.setdefault(target, {})[key] = params[key]

        # set parameters for slave protocols
        for name in temp_dict:
            if name in self._slave_protocols:
                self._slave_protocols[name]._set_params(temp_dict[name])
            else:
                # how did we get here?  This should never happen, but raise an exception if it does.
                raise InstrumentParameterException('Invalid key(s) in SET action: %r' % temp_dict[name])

        _, new_config = self._handler_command_get([Parameter.ALL])

        if not new_config == old_config:
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def set_init_params(self, config):
        """
        Set initial parameters.  Parameters are forwarded to the appropriate parameter dictionary based on name.
        @param config: Init param config to be handled
        """
        temp_dict = {}
        self._startup_config = config
        config = config.get(DriverConfigKey.PARAMETERS, {})
        for key in config:
            target, _ = key.split('_', 1)
            if target not in self._slave_protocols:
                # master driver parameter
                log.debug("Setting init value for %s to %s", key, config[key])
                self._param_dict.set_init_value(key, config[key])
            else:
                temp_dict.setdefault(target, {})[key] = config[key]

        for name in temp_dict:
            if name in self._slave_protocols:
                self._slave_protocols[name].set_init_params({DriverConfigKey.PARAMETERS: temp_dict[name]})
            else:
                # how did we get here?  This should never happen, but raise an exception if it does.
                raise InstrumentParameterException('Invalid key(s) in INIT PARAMS action: %r' % temp_dict[name])

    def get_config_metadata_dict(self):
        """
        See base class for full description.  This method is overridden to retrieve the parameter
        dictionary from each slave protocol and merge them.
        @return: dictionary containing driver metadata
        """
        log.debug("Getting metadata dict from protocol...")
        return_dict = {ConfigMetadataKey.DRIVER: self._driver_dict.generate_dict(),
                       ConfigMetadataKey.COMMANDS: self._cmd_dict.generate_dict(),
                       ConfigMetadataKey.PARAMETERS: self._param_dict.generate_dict()}

        for protocol in self._slave_protocols.values():
            return_dict[ConfigMetadataKey.PARAMETERS].update(protocol._param_dict.generate_dict())
            return_dict[ConfigMetadataKey.COMMANDS].update(protocol._cmd_dict.generate_dict())

        return return_dict

    def get_resource_capabilities(self, current_state=True):
        """
        Overrides base class to include slave protocol parameters
        @param current_state: Boolean indicating whether we should return only the current state events
        @return: (resource_commands, resource_parameters)
        """
        res_cmds = self._protocol_fsm.get_events(current_state)
        res_cmds = self._filter_capabilities(res_cmds)
        res_params = self._param_dict.get_keys()

        for protocol in self._slave_protocols.values():
            res_params.extend(protocol._param_dict.get_keys())

        return res_cmds, res_params

    def _build_scheduler(self):
        """
        Build a scheduler for periodic status updates
        """
        job_name = ScheduledJob.ACQUIRE_SAMPLE
        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.INTERVAL,
                        DriverSchedulerConfigKey.SECONDS: self._param_dict.get(Parameter.SAMPLE_INTERVAL)
                    },
                }
            }
        }

        self.set_init_params(config)
        self._add_scheduler_event(ScheduledJob.ACQUIRE_SAMPLE, ProtocolEvent.ACQUIRE_SAMPLE)

    def _delete_scheduler(self):
        """
        Remove the autosample schedule.
        """
        try:
            self._remove_scheduler(ScheduledJob.ACQUIRE_SAMPLE)
        except KeyError:
            log.info('Failed to remove scheduled job for ACQUIRE_SAMPLE')

    ########################################################################
    # Generic handlers.
    ########################################################################
    def _handler_generic_enter(self, *args, **kwargs):
        """
        Generic enter handler, raise STATE CHANGE
        """
        if self.get_current_state() != ProtocolState.UNKNOWN:
            self._init_params()
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_generic_exit(self, *args, **kwargs):
        """
        Generic exit handler, do nothing.
        """

    def _handler_stop_generic(self, *args, **kwargs):
        """
        Generic stop method to return to COMMAND (via POLL if appropriate)
        @return next_state, (next_state, None)
        """
        next_state = ProtocolState.COMMAND
        result = []

        self._delete_scheduler()

        # check if we are in autosample AND currently taking a sample, if so, move to POLL
        # otherwise go back to COMMAND.
        if self.get_current_state() == ProtocolState.AUTOSAMPLE:
            if self._get_slave_states() != (ProtocolState.COMMAND, ProtocolState.COMMAND, ProtocolState.COMMAND):
                next_state = ProtocolState.POLL

        return next_state, (next_state, result)

    ########################################################################
    # Unknown handlers.
    ########################################################################

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state
        @return next_state, (next_state, result)
        """
        next_state = ProtocolState.COMMAND
        result = self._send_event_to_all(ProtocolEvent.DISCOVER)
        log.debug('_handler_unknown_discover -- send DISCOVER to all: %r', result)
        target_state = (ProtocolState.COMMAND, ProtocolState.COMMAND, ProtocolState.COMMAND)
        success = False
        # wait for the slave protocols to discover
        for attempt in xrange(5):
            slave_states = self._get_slave_states()
            if slave_states == target_state:
                success = True
                break
            time.sleep(1)
        if not success:
            next_state = ProtocolState.ERROR
        return next_state, (next_state, result)

    ########################################################################
    # Command handlers.
    ########################################################################

    def _handler_command_get(self, *args, **kwargs):
        """
        Get parameter.  Query this protocol plus all slave protocols.
        @param args: arglist which should contain a list of parameters to get
        @return None, results
        """
        params = args[0]

        if not isinstance(params, list):
            params = [params]

        temp_dict = {}
        result_dict = {}

        # request is for all parameters, send get(ALL) to each protocol then combine the results.
        if Parameter.ALL in params:
            params = [Parameter.ALL]
            _, result = self._handler_get(params, **kwargs)
            result_dict.update(result)
            for protocol in self._slave_protocols.values():
                _, result = protocol._handler_get(params, **kwargs)
                result_dict.update(result)

        # request is for specific parameters.  Determine which protocol should service each,
        # call the appropriate _handler_get and combine the results
        else:
            for key in params:
                log.debug('about to split: %s', key)
                target, _ = key.split('_', 1)
                temp_dict.setdefault(target, []).append(key)
            for key in temp_dict:
                if key == MASTER:
                    _, result = self._handler_get(params, **kwargs)
                else:
                    if key in self._slave_protocols:
                        _, result = self._slave_protocols[key]._handler_get(params, **kwargs)
                    else:
                        raise InstrumentParameterException('Invalid key(s) in GET action: %r' % temp_dict[key])
                result_dict.update(result)

        return None, result_dict

    def _handler_command_set(self, *args, **kwargs):
        """
        Set parameter, just pass through to _set_params, which knows how to set the params
        in the slave protocols.
        """
        next_state = None
        result = []
        self._set_params(*args, **kwargs)
        return next_state, (next_state, result)

    def _handler_command_start_direct(self):
        """
        Start direct access
        """
        next_state = ProtocolState.DIRECT_ACCESS
        result = []
        return next_state, (next_state, result)

    def _handler_command_start_autosample(self):
        """
        Move my FSM to autosample and start the sample sequence by sending START1 to the MCU.
        Create the scheduler to automatically start the next sample sequence
        """
        next_state = ProtocolState.AUTOSAMPLE
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        self._build_scheduler()
        return next_state, (next_state, result)

    def _handler_command_start_poll(self):
        """
        Move my FSM to poll and start the sample sequence by sending START1 to the MCU
        """
        next_state = ProtocolState.POLL
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return next_state, (next_state, result)

    def _handler_command_start_calibrate(self):
        """
        Move my FSM to calibrate and start the calibrate sequence by sending START1 to the MCU
        """
        next_state = ProtocolState.CALIBRATE
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return next_state, (next_state, result)

    def _handler_command_start_nafion_regen(self):
        """
        Move my FSM to NAFION_REGEN and send NAFION_REGEN to the MCU
        """
        next_state = ProtocolState.REGEN
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.NAFREG)
        return next_state, (next_state, result)

    def _handler_command_start_ion_regen(self):
        """
        Move my FSM to ION_REGEN and send ION_REGEN to the MCU
        """
        next_state = ProtocolState.REGEN
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.IONREG)
        return next_state, (next_state, result)

    def _handler_command_poweroff(self):
        """
        Send POWEROFF to the MCU
        """
        next_state = None
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.POWEROFF)
        return next_state, (next_state, result)

    def _handler_command_start_manual(self):
        """
        Move FSM to MANUAL OVERRIDE state
        """
        next_state = ProtocolState.MANUAL_OVERRIDE
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Error handlers.
    ########################################################################

    def _handler_error(self):
        next_state = ProtocolState.ERROR
        result = []
        return next_state, (next_state, result)

    def _handler_error_clear(self):
        """
        Send the CLEAR event to any slave protocol in the error state and return this driver to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        for protocol in self._slave_protocols:
            state = protocol.get_current_state()
            if state == MASSP_STATE_ERROR:
                # do this synchronously, to allow each slave protocol to complete the CLEAR action
                # before transitioning states.
                protocol._protocol_fsm.on_event(ProtocolEvent.CLEAR)
        return next_state, (next_state, result)

    ########################################################################
    # Autosample handlers.
    ########################################################################

    def _handler_autosample_acquire_sample(self):
        """
        Fire off a sample sequence while in the autosample state.
        @throws InstrumentProtocolException
        """
        next_state = None
        result = []
        slave_states = self._get_slave_states()
        # verify the MCU is not already in a sequence
        if slave_states[0] == ProtocolState.COMMAND:
            result = self._send_event_to_slave(MCU, mcu.Capability.START1)
        else:
            raise InstrumentProtocolException("Attempted to acquire sample while sampling")
        return next_state, (next_state, result)

    ########################################################################
    # Direct access handlers.
    ########################################################################

    def _handler_direct_access_enter(self, *args, **kwargs):
        """
        Enter direct access state.  Forward to all slave protocols.
        """
        self._send_event_to_all(ProtocolEvent.START_DIRECT)

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)
        self._sent_cmds = []

    def _handler_direct_access_exit(self, *args, **kwargs):
        """
        Exit direct access state.  Check slave protocol states and verify they all
        return to COMMAND, otherwise raise InstrumentProtocolException.
        @throws InstrumentProtocolException
        """
        for attempt in range(DA_EXIT_MAX_RETRIES):
            slave_states = self._get_slave_states()
            if ProtocolState.DIRECT_ACCESS in slave_states:
                log.error('Slave protocol failed to return to command, attempt %d', attempt)
                time.sleep(1)
            else:
                return

        raise InstrumentProtocolException('Slave protocol never returned to command from DA.')

    def _handler_direct_access_execute_direct(self, data):
        """
        Execute a direct access command.  For MASSP, this means passing the actual command to the
        correct slave protocol.  This is handled by _send_massp_direct_access.
        """
        self._send_massp_direct_access(data)

        # add sent command to list for 'echo' filtering in callback
        self._sent_cmds.append(data)

        return None, (None, [])

    def _handler_direct_access_stop_direct(self):
        next_state = ProtocolState.COMMAND
        result = []
        self._send_event_to_all(ProtocolEvent.STOP_DIRECT)
        return next_state, (next_state, result)

    ########################################################################
    # Regen handlers.
    ########################################################################

    def _handler_stop_regen(self):
        """
        Abort the current regeneration sequence, return to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.STANDBY)
        return next_state, (next_state, result)

    def _handler_regen_complete(self):
        """
        Regeneration sequence is complete, return to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._async_agent_state_change(ResourceAgentState.COMMAND)
        return next_state, (next_state, result)

    def _handler_manual_override_stop(self):
        """
        Exit manual override.  Attempt to bring the slave drivers back to COMMAND.
        """
        next_state = ProtocolState.COMMAND
        result = []
        mcu_state, turbo_state, rga_state = self._get_slave_states()
        if rga_state == rga.ProtocolState.SCAN:
            self._slave_protocols[RGA]._protocol_fsm.on_event(rga.Capability.STOP_SCAN)
        if turbo_state == turbo.ProtocolState.AT_SPEED:
            self._slave_protocols[TURBO]._protocol_fsm.on_event(turbo.Capability.STOP_TURBO)
        while rga_state not in [rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR] or \
                turbo_state not in [turbo.ProtocolState.COMMAND, turbo.ProtocolState.ERROR]:
            time.sleep(.1)
            mcu_state, turbo_state, rga_state = self._get_slave_states()
        if mcu_state != mcu.ProtocolState.COMMAND:
            self._slave_protocols[MCU]._protocol_fsm.on_event(mcu.Capability.STANDBY)

        return next_state, (next_state, result)

    def _handler_manual_get_slave_states(self):
        """
        Get the slave states and return them to the user
        @return: next_state, (next_state, result)
        """
        mcu_state, turbo_state, rga_state = self._get_slave_states()
        return None, (None, {MCU: mcu_state, RGA: rga_state, TURBO: turbo_state})
Beispiel #2
0
class Protocol(InstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    __metaclass__ = META_LOGGER

    def __init__(self, driver_event):
        """
        Protocol constructor.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        InstrumentProtocol.__init__(self, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent,
                                           ProtocolEvent.ENTER,
                                           ProtocolEvent.EXIT)

        # Add event handlers for protocol state machine.
        handlers = {
            ProtocolState.UNKNOWN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.DISCOVER, self._handler_unknown_discover),
            ],
            ProtocolState.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT,
                 self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_AUTOSAMPLE,
                 self._handler_command_start_autosample),
                (ProtocolEvent.ACQUIRE_SAMPLE,
                 self._handler_command_start_poll),
                (ProtocolEvent.CALIBRATE,
                 self._handler_command_start_calibrate),
                (ProtocolEvent.START_NAFION,
                 self._handler_command_start_nafion_regen),
                (ProtocolEvent.START_ION,
                 self._handler_command_start_ion_regen),
                (ProtocolEvent.ERROR, self._handler_error),
                (ProtocolEvent.POWEROFF, self._handler_command_poweroff),
                (ProtocolEvent.START_MANUAL,
                 self._handler_command_start_manual),
            ],
            ProtocolState.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_SAMPLE,
                 self._handler_autosample_acquire_sample),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.POLL: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.CLEAR, self._handler_error_clear),
            ],
            ProtocolState.CALIBRATE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.REGEN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_REGEN, self._handler_stop_regen),
                (ProtocolEvent.REGEN_COMPLETE, self._handler_regen_complete),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_direct_access_exit),
                (ProtocolEvent.STOP_DIRECT,
                 self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT,
                 self._handler_direct_access_execute_direct),
            ],
            ProtocolState.MANUAL_OVERRIDE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_MANUAL,
                 self._handler_manual_override_stop),
                (ProtocolEvent.GET_SLAVE_STATES,
                 self._handler_manual_get_slave_states),
            ],
        }

        for state in handlers:
            for event, handler in handlers[state]:
                self._protocol_fsm.add_handler(state, event, handler)

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # State state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._slave_protocols = {}
        self.initialize_scheduler()

    def _add_manual_override_handlers(self):
        for slave in self._slave_protocols:
            for event in self._slave_protocols[slave]._cmd_dict._cmd_dict:
                self._protocol_fsm.add_handler(
                    ProtocolState.MANUAL_OVERRIDE, event,
                    self._build_override_handler(slave, event))

    def _build_override_handler(self, slave, event):
        log.debug('Building event handler for protocol: %s event: %s', slave,
                  event)

        def inner():
            return None, self._slave_protocols[slave]._protocol_fsm.on_event(
                event)

        return inner

    def register_slave_protocol(self, name, protocol):
        """
        @param name: slave protocol name
        @param protocol: slave protocol instance
        @return: None
        """
        self._slave_protocols[name] = protocol

    def _slave_protocol_event(self, event, *args, **kwargs):
        """
        Handle an event from a slave protocol.
        @param event: event to be processed
        """
        name = kwargs.get('name')
        if name is not None and name in self._slave_protocols:
            # only react to slave protocol events once we have transitioned out of unknown
            if self.get_current_state(
            ) != ProtocolState.UNKNOWN or event == DriverAsyncEvent.ERROR:
                if event == DriverAsyncEvent.STATE_CHANGE:
                    self._react()
                elif event == DriverAsyncEvent.CONFIG_CHANGE:
                    # do nothing, we handle this ourselves in set_param
                    pass
                else:
                    # pass the event up to the instrument agent
                    log.debug(
                        'Passing event up to the Instrument agent: %r %r %r',
                        event, args, kwargs)
                    self._driver_event(event, *args)

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with parameters.
        For each parameter key, add match string, match lambda function,
        and value formatting function for set commands.
        """
        self._param_dict.add(
            Parameter.SAMPLE_INTERVAL,
            '',
            None,
            int,
            type=ParameterDictType.INT,
            display_name='Autosample Interval',
            description=
            'Interval between sample starts during autosample state',
            range=(7200, 86400),
            units=Units.SECOND)

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.ACQUIRE_SAMPLE,
                           display_name="Acquire Sample")
        self._cmd_dict.add(Capability.START_AUTOSAMPLE,
                           display_name="Start Autosample")
        self._cmd_dict.add(Capability.CALIBRATE,
                           display_name="Acquire Calibration Samples")
        self._cmd_dict.add(Capability.START_ION,
                           display_name="Start Ion Chamber Regeneration")
        self._cmd_dict.add(Capability.START_NAFION,
                           display_name="Start Nafion Regeneration")
        self._cmd_dict.add(Capability.STOP_REGEN,
                           display_name="Stop Current Regeneration")
        self._cmd_dict.add(Capability.STOP_AUTOSAMPLE,
                           display_name="Stop Autosample")
        self._cmd_dict.add(Capability.POWEROFF, display_name='Low Power State')
        self._cmd_dict.add(Capability.GET_SLAVE_STATES,
                           display_name='Get Slave States')
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False)

    def _react(self):
        """
        Determine if an action is necessary based on the states of the slave protocols.

            (MCU STATE, TURBO STATE, RGA STATE) : (TARGET, EVENT)

        The specified event will be sent to the specified target.
        """
        state = self.get_current_state()
        slave_states = self._get_slave_states()

        if MASSP_STATE_ERROR in slave_states:
            return self._error()

        if state == ProtocolState.REGEN and slave_states[
                0] == ProtocolState.COMMAND:
            self._async_raise_fsm_event(ProtocolEvent.REGEN_COMPLETE)

        # these actions are only applicable in POLL, AUTOSAMPLE or CALIBRATE states
        if state not in [
                ProtocolState.POLL, ProtocolState.AUTOSAMPLE,
                ProtocolState.CALIBRATE
        ]:
            return

        mps = mcu.ProtocolState
        tps = turbo.ProtocolState
        rps = rga.ProtocolState
        action_map = {
            # Waiting Turbo (RGA is off)
            (mps.WAITING_TURBO, tps.COMMAND, rps.COMMAND):
            (TURBO, turbo.Capability.START_TURBO),
            (mps.WAITING_TURBO, tps.AT_SPEED, rps.COMMAND):
            (MCU, mcu.Capability.START2),

            # Waiting RGA
            (mps.WAITING_RGA, tps.AT_SPEED, rps.SCAN): (MCU,
                                                        mcu.Capability.SAMPLE),
            (mps.WAITING_RGA, tps.AT_SPEED, rps.COMMAND):
            (RGA, rga.Capability.START_SCAN),
            (mps.WAITING_RGA, tps.COMMAND, rps.SCAN):
            (RGA, rga.Capability.STOP_SCAN),  # this should never happen!
            (mps.WAITING_RGA, tps.COMMAND, rps.COMMAND):
            (MCU, mcu.Capability.STANDBY),  # this should never happen!

            # Stopping
            (mps.STOPPING, tps.AT_SPEED, rps.SCAN): (RGA,
                                                     rga.Capability.STOP_SCAN),
            (mps.STOPPING, tps.AT_SPEED, rps.COMMAND):
            (TURBO, turbo.Capability.STOP_TURBO),
            (mps.STOPPING, tps.COMMAND, rps.SCAN):
            (RGA, rga.Capability.STOP_SCAN),  # this should never happen!
            (mps.STOPPING, tps.COMMAND, rps.COMMAND): (MCU,
                                                       mcu.Capability.STANDBY),
        }

        action = action_map.get(self._get_slave_states())

        if action is not None:
            if not isinstance(action, list):
                action = [action]

            # iterate through the action list, sending the events to the targets
            # if we are in POLL or CALIBRATE and we see a STANDBY event, return this driver to COMMAND.
            for target, command in action:
                if command == mcu.Capability.SAMPLE and state == ProtocolState.CALIBRATE:
                    command = mcu.Capability.CALIBRATE
                if command == mcu.Capability.STANDBY and state in [
                        ProtocolState.CALIBRATE, ProtocolState.POLL
                ]:
                    self._send_event_to_slave(target, command)
                    self._async_raise_fsm_event(ProtocolEvent.STOP)
                else:
                    self._send_event_to_slave(target, command)
        return action

    def _error(self):
        """
        Handle error state in slave protocol
        """
        state = self.get_current_state()
        slave_states = self._get_slave_states()

        # if we are not currently in the error state, make the transition
        if state != ProtocolState.ERROR:
            self._async_raise_fsm_event(ProtocolEvent.ERROR)
        mcu_state, turbo_state, rga_state = slave_states

        # before we do anything else, the RGA must be stopped.
        if rga_state not in [
                rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR
        ]:
            self._send_event_to_slave(RGA, rga.ProtocolEvent.STOP_SCAN)
        # RGA must be in COMMAND or ERROR, the TURBO must be stopped.
        elif turbo_state not in [
                turbo.ProtocolState.COMMAND, turbo.ProtocolState.SPINNING_DOWN
        ]:
            self._send_event_to_slave(TURBO, turbo.ProtocolEvent.STOP_TURBO)
        # Turbo and RGA must be in COMMAND or ERROR, stop the MCU
        elif mcu_state != mcu.ProtocolState.COMMAND:
            self._send_event_to_slave(MCU, mcu.ProtocolEvent.STANDBY)

    def _got_chunk(self, chunk):
        """
        This driver has no chunker...
        """

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        @param events: Events to be filtered
        @return: list of events which are also capabilities
        """
        return [
            x for x in events if Capability.has(x) or mcu.Capability.has(x)
            or turbo.Capability.has(x) or rga.Capability.has(x)
        ]

    def _get_slave_states(self):
        """
        Retrieve the current protocol state from each of the slave protocols and return them as a tuple.
        """
        return (
            self._slave_protocols[MCU].get_current_state(),
            self._slave_protocols[TURBO].get_current_state(),
            self._slave_protocols[RGA].get_current_state(),
        )

    def _send_event_to_all(self, event):
        """
        Send the same event to all slave protocols.
        @return: List of (name, result) for a slave protocols
        """
        return [(name, slave._protocol_fsm.on_event(event))
                for name, slave in self._slave_protocols.items()]

    def _send_event_to_slave(self, name, event):
        """
        Send an event to a specific protocol
        @param name: Name of slave protocol
        @param event: Event to be sent
        """
        slave_protocol = self._slave_protocols.get(name)
        if slave_protocol is None:
            raise InstrumentProtocolException(
                'Attempted to send event to non-existent protocol: %s' % name)
        slave_protocol._async_raise_fsm_event(event)

    def _send_massp_direct_access(self, command):
        """
        Handle a direct access command.  Driver expects direct access commands to specify the target
        using the following format:

        target:command

        It then routes the command to the appropriate slave protocol.
        @param command: Direct access command received
        """
        err_string = 'Invalid command.  Command must be in the following format: "target:command' + NEWLINE + \
                     'Valid targets are: %r' % self._slave_protocols.keys()
        try:
            target, command = command.split(DA_COMMAND_DELIMITER, 1)
            target = target.lower()
        except ValueError:
            target = None

        log.debug('_do_cmd_direct - target: %s command: %r', target, command)

        if target not in self._slave_protocols:
            self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, err_string)
        else:
            self._slave_protocols[target]._protocol_fsm.on_event(
                ProtocolEvent.EXECUTE_DIRECT, command)

    def _set_params(self, *args, **kwargs):
        """
        Set one or more parameters.  This method will determine where the parameter actually resides and
        forward it to the appropriate parameter dictionary based on name.
        @param args: arglist which must contain a parameter dictionary
        @throws InstrumentParameterException
        """
        params = args[0]

        if not isinstance(params, dict):
            raise InstrumentParameterException(
                'Attempted to set parameters with a non-dictionary argument')

        _, old_config = self._handler_command_get([Parameter.ALL])

        temp_dict = {}
        for key in params:
            split_key = key.split('_', 1)
            if len(split_key) == 1:
                raise InstrumentParameterException(
                    'Missing target in MASSP parameter: %s' % key)
            target = split_key[0]
            if target not in self._slave_protocols:
                # this is a master driver parameter, set it here
                if key in self._param_dict.get_keys():
                    log.debug("Setting value for %s to %s", key, params[key])
                    self._param_dict.set_value(key, params[key])
                else:
                    raise InstrumentParameterException(
                        'Invalid key in SET action: %s' % key)
            else:
                temp_dict.setdefault(target, {})[key] = params[key]

        # set parameters for slave protocols
        for name in temp_dict:
            if name in self._slave_protocols:
                self._slave_protocols[name]._set_params(temp_dict[name])
            else:
                # how did we get here?  This should never happen, but raise an exception if it does.
                raise InstrumentParameterException(
                    'Invalid key(s) in SET action: %r' % temp_dict[name])

        _, new_config = self._handler_command_get([Parameter.ALL])

        if not new_config == old_config:
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def set_init_params(self, config):
        """
        Set initial parameters.  Parameters are forwarded to the appropriate parameter dictionary based on name.
        @param config: Init param config to be handled
        """
        temp_dict = {}
        self._startup_config = config
        config = config.get(DriverConfigKey.PARAMETERS, {})
        for key in config:
            target, _ = key.split('_', 1)
            if target not in self._slave_protocols:
                # master driver parameter
                log.debug("Setting init value for %s to %s", key, config[key])
                self._param_dict.set_init_value(key, config[key])
            else:
                temp_dict.setdefault(target, {})[key] = config[key]

        for name in temp_dict:
            if name in self._slave_protocols:
                self._slave_protocols[name].set_init_params(
                    {DriverConfigKey.PARAMETERS: temp_dict[name]})
            else:
                # how did we get here?  This should never happen, but raise an exception if it does.
                raise InstrumentParameterException(
                    'Invalid key(s) in INIT PARAMS action: %r' %
                    temp_dict[name])

    def get_config_metadata_dict(self):
        """
        See base class for full description.  This method is overridden to retrieve the parameter
        dictionary from each slave protocol and merge them.
        @return: dictionary containing driver metadata
        """
        log.debug("Getting metadata dict from protocol...")
        return_dict = {
            ConfigMetadataKey.DRIVER: self._driver_dict.generate_dict(),
            ConfigMetadataKey.COMMANDS: self._cmd_dict.generate_dict(),
            ConfigMetadataKey.PARAMETERS: self._param_dict.generate_dict()
        }

        for protocol in self._slave_protocols.values():
            return_dict[ConfigMetadataKey.PARAMETERS].update(
                protocol._param_dict.generate_dict())
            return_dict[ConfigMetadataKey.COMMANDS].update(
                protocol._cmd_dict.generate_dict())

        return return_dict

    def get_resource_capabilities(self, current_state=True):
        """
        Overrides base class to include slave protocol parameters
        @param current_state: Boolean indicating whether we should return only the current state events
        @return: (resource_commands, resource_parameters)
        """
        res_cmds = self._protocol_fsm.get_events(current_state)
        res_cmds = self._filter_capabilities(res_cmds)
        res_params = self._param_dict.get_keys()

        for protocol in self._slave_protocols.values():
            res_params.extend(protocol._param_dict.get_keys())

        return res_cmds, res_params

    def _build_scheduler(self):
        """
        Build a scheduler for periodic status updates
        """
        job_name = ScheduledJob.ACQUIRE_SAMPLE
        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE:
                        TriggerType.INTERVAL,
                        DriverSchedulerConfigKey.SECONDS:
                        self._param_dict.get(Parameter.SAMPLE_INTERVAL)
                    },
                }
            }
        }

        self.set_init_params(config)
        self._add_scheduler_event(ScheduledJob.ACQUIRE_SAMPLE,
                                  ProtocolEvent.ACQUIRE_SAMPLE)

    def _delete_scheduler(self):
        """
        Remove the autosample schedule.
        """
        try:
            self._remove_scheduler(ScheduledJob.ACQUIRE_SAMPLE)
        except KeyError:
            log.info('Failed to remove scheduled job for ACQUIRE_SAMPLE')

    ########################################################################
    # Generic handlers.
    ########################################################################
    def _handler_generic_enter(self, *args, **kwargs):
        """
        Generic enter handler, raise STATE CHANGE
        """
        if self.get_current_state() != ProtocolState.UNKNOWN:
            self._init_params()
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_generic_exit(self, *args, **kwargs):
        """
        Generic exit handler, do nothing.
        """

    def _handler_stop_generic(self, *args, **kwargs):
        """
        Generic stop method to return to COMMAND (via POLL if appropriate)
        @return next_state, (next_state, None)
        """
        next_state = ProtocolState.COMMAND
        result = []

        self._delete_scheduler()

        # check if we are in autosample AND currently taking a sample, if so, move to POLL
        # otherwise go back to COMMAND.
        if self.get_current_state() == ProtocolState.AUTOSAMPLE:
            if self._get_slave_states() != (ProtocolState.COMMAND,
                                            ProtocolState.COMMAND,
                                            ProtocolState.COMMAND):
                next_state = ProtocolState.POLL

        return next_state, (next_state, result)

    ########################################################################
    # Unknown handlers.
    ########################################################################

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state
        @return next_state, (next_state, result)
        """
        next_state = ProtocolState.COMMAND
        result = self._send_event_to_all(ProtocolEvent.DISCOVER)
        log.debug('_handler_unknown_discover -- send DISCOVER to all: %r',
                  result)
        target_state = (ProtocolState.COMMAND, ProtocolState.COMMAND,
                        ProtocolState.COMMAND)
        success = False
        # wait for the slave protocols to discover
        for attempt in xrange(5):
            slave_states = self._get_slave_states()
            if slave_states == target_state:
                success = True
                break
            time.sleep(1)
        if not success:
            next_state = ProtocolState.ERROR
        return next_state, (next_state, result)

    ########################################################################
    # Command handlers.
    ########################################################################

    def _handler_command_get(self, *args, **kwargs):
        """
        Get parameter.  Query this protocol plus all slave protocols.
        @param args: arglist which should contain a list of parameters to get
        @return None, results
        """
        params = args[0]

        if not isinstance(params, list):
            params = [params]

        temp_dict = {}
        result_dict = {}

        # request is for all parameters, send get(ALL) to each protocol then combine the results.
        if Parameter.ALL in params:
            params = [Parameter.ALL]
            _, result = self._handler_get(params, **kwargs)
            result_dict.update(result)
            for protocol in self._slave_protocols.values():
                _, result = protocol._handler_get(params, **kwargs)
                result_dict.update(result)

        # request is for specific parameters.  Determine which protocol should service each,
        # call the appropriate _handler_get and combine the results
        else:
            for key in params:
                log.debug('about to split: %s', key)
                target, _ = key.split('_', 1)
                temp_dict.setdefault(target, []).append(key)
            for key in temp_dict:
                if key == MASTER:
                    _, result = self._handler_get(params, **kwargs)
                else:
                    if key in self._slave_protocols:
                        _, result = self._slave_protocols[key]._handler_get(
                            params, **kwargs)
                    else:
                        raise InstrumentParameterException(
                            'Invalid key(s) in GET action: %r' %
                            temp_dict[key])
                result_dict.update(result)

        return None, result_dict

    def _handler_command_set(self, *args, **kwargs):
        """
        Set parameter, just pass through to _set_params, which knows how to set the params
        in the slave protocols.
        """
        next_state = None
        result = []
        self._set_params(*args, **kwargs)
        return next_state, (next_state, result)

    def _handler_command_start_direct(self):
        """
        Start direct access
        """
        next_state = ProtocolState.DIRECT_ACCESS
        result = []
        return next_state, (next_state, result)

    def _handler_command_start_autosample(self):
        """
        Move my FSM to autosample and start the sample sequence by sending START1 to the MCU.
        Create the scheduler to automatically start the next sample sequence
        """
        next_state = ProtocolState.AUTOSAMPLE
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        self._build_scheduler()
        return next_state, (next_state, result)

    def _handler_command_start_poll(self):
        """
        Move my FSM to poll and start the sample sequence by sending START1 to the MCU
        """
        next_state = ProtocolState.POLL
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return next_state, (next_state, result)

    def _handler_command_start_calibrate(self):
        """
        Move my FSM to calibrate and start the calibrate sequence by sending START1 to the MCU
        """
        next_state = ProtocolState.CALIBRATE
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return next_state, (next_state, result)

    def _handler_command_start_nafion_regen(self):
        """
        Move my FSM to NAFION_REGEN and send NAFION_REGEN to the MCU
        """
        next_state = ProtocolState.REGEN
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.NAFREG)
        return next_state, (next_state, result)

    def _handler_command_start_ion_regen(self):
        """
        Move my FSM to ION_REGEN and send ION_REGEN to the MCU
        """
        next_state = ProtocolState.REGEN
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.IONREG)
        return next_state, (next_state, result)

    def _handler_command_poweroff(self):
        """
        Send POWEROFF to the MCU
        """
        next_state = None
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.POWEROFF)
        return next_state, (next_state, result)

    def _handler_command_start_manual(self):
        """
        Move FSM to MANUAL OVERRIDE state
        """
        next_state = ProtocolState.MANUAL_OVERRIDE
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Error handlers.
    ########################################################################

    def _handler_error(self):
        next_state = ProtocolState.ERROR
        result = []
        return next_state, (next_state, result)

    def _handler_error_clear(self):
        """
        Send the CLEAR event to any slave protocol in the error state and return this driver to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        for protocol in self._slave_protocols:
            state = protocol.get_current_state()
            if state == MASSP_STATE_ERROR:
                # do this synchronously, to allow each slave protocol to complete the CLEAR action
                # before transitioning states.
                protocol._protocol_fsm.on_event(ProtocolEvent.CLEAR)
        return next_state, (next_state, result)

    ########################################################################
    # Autosample handlers.
    ########################################################################

    def _handler_autosample_acquire_sample(self):
        """
        Fire off a sample sequence while in the autosample state.
        @throws InstrumentProtocolException
        """
        next_state = None
        result = []
        slave_states = self._get_slave_states()
        # verify the MCU is not already in a sequence
        if slave_states[0] == ProtocolState.COMMAND:
            result = self._send_event_to_slave(MCU, mcu.Capability.START1)
        else:
            raise InstrumentProtocolException(
                "Attempted to acquire sample while sampling")
        return next_state, (next_state, result)

    ########################################################################
    # Direct access handlers.
    ########################################################################

    def _handler_direct_access_enter(self, *args, **kwargs):
        """
        Enter direct access state.  Forward to all slave protocols.
        """
        self._send_event_to_all(ProtocolEvent.START_DIRECT)

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)
        self._sent_cmds = []

    def _handler_direct_access_exit(self, *args, **kwargs):
        """
        Exit direct access state.  Check slave protocol states and verify they all
        return to COMMAND, otherwise raise InstrumentProtocolException.
        @throws InstrumentProtocolException
        """
        for attempt in range(DA_EXIT_MAX_RETRIES):
            slave_states = self._get_slave_states()
            if ProtocolState.DIRECT_ACCESS in slave_states:
                log.error(
                    'Slave protocol failed to return to command, attempt %d',
                    attempt)
                time.sleep(1)
            else:
                return

        raise InstrumentProtocolException(
            'Slave protocol never returned to command from DA.')

    def _handler_direct_access_execute_direct(self, data):
        """
        Execute a direct access command.  For MASSP, this means passing the actual command to the
        correct slave protocol.  This is handled by _send_massp_direct_access.
        """
        self._send_massp_direct_access(data)

        # add sent command to list for 'echo' filtering in callback
        self._sent_cmds.append(data)

        return None, (None, [])

    def _handler_direct_access_stop_direct(self):
        next_state = ProtocolState.COMMAND
        result = []
        self._send_event_to_all(ProtocolEvent.STOP_DIRECT)
        return next_state, (next_state, result)

    ########################################################################
    # Regen handlers.
    ########################################################################

    def _handler_stop_regen(self):
        """
        Abort the current regeneration sequence, return to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.STANDBY)
        return next_state, (next_state, result)

    def _handler_regen_complete(self):
        """
        Regeneration sequence is complete, return to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._async_agent_state_change(ResourceAgentState.COMMAND)
        return next_state, (next_state, result)

    def _handler_manual_override_stop(self):
        """
        Exit manual override.  Attempt to bring the slave drivers back to COMMAND.
        """
        next_state = ProtocolState.COMMAND
        result = []
        mcu_state, turbo_state, rga_state = self._get_slave_states()
        if rga_state == rga.ProtocolState.SCAN:
            self._slave_protocols[RGA]._protocol_fsm.on_event(
                rga.Capability.STOP_SCAN)
        if turbo_state == turbo.ProtocolState.AT_SPEED:
            self._slave_protocols[TURBO]._protocol_fsm.on_event(
                turbo.Capability.STOP_TURBO)
        while rga_state not in [rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR] or \
                turbo_state not in [turbo.ProtocolState.COMMAND, turbo.ProtocolState.ERROR]:
            time.sleep(.1)
            mcu_state, turbo_state, rga_state = self._get_slave_states()
        if mcu_state != mcu.ProtocolState.COMMAND:
            self._slave_protocols[MCU]._protocol_fsm.on_event(
                mcu.Capability.STANDBY)

        return next_state, (next_state, result)

    def _handler_manual_get_slave_states(self):
        """
        Get the slave states and return them to the user
        @return: next_state, (next_state, result)
        """
        mcu_state, turbo_state, rga_state = self._get_slave_states()
        return None, (None, {
            MCU: mcu_state,
            RGA: rga_state,
            TURBO: turbo_state
        })