コード例 #1
0
ファイル: driver.py プロジェクト: r-swilderd/mi-instrument
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 = InstrumentFSM(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',
            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 not target 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 not target 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_agent_state, None)
        """
        self._delete_scheduler()

        next_state = ProtocolState.COMMAND
        next_agent_state = ResourceAgentState.COMMAND

        # 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
                next_agent_state = ResourceAgentState.BUSY

        # notify the agent we have changed states
        self._async_agent_state_change(next_agent_state)
        return next_state, (next_agent_state, None)

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

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state
        @return (next_state, next_agent_state)
        """
        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:
            return ProtocolState.ERROR, ResourceAgentState.IDLE
        return ProtocolState.COMMAND, ResourceAgentState.IDLE

    ########################################################################
    # 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.
        """
        self._set_params(*args, **kwargs)
        return None, None

    def _handler_command_start_direct(self):
        """
        Start direct access
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.DIRECT_ACCESS, (ResourceAgentState.DIRECT_ACCESS,
                                             None)

    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
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        self._build_scheduler()
        return ProtocolState.AUTOSAMPLE, (ResourceAgentState.STREAMING, None)

    def _handler_command_start_poll(self):
        """
        Move my FSM to poll and start the sample sequence by sending START1 to the MCU
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return ProtocolState.POLL, (ResourceAgentState.BUSY, None)

    def _handler_command_start_calibrate(self):
        """
        Move my FSM to calibrate and start the calibrate sequence by sending START1 to the MCU
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return ProtocolState.CALIBRATE, (ResourceAgentState.BUSY, None)

    def _handler_command_start_nafion_regen(self):
        """
        Move my FSM to NAFION_REGEN and send NAFION_REGEN to the MCU
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.NAFREG)
        return ProtocolState.REGEN, (ResourceAgentState.BUSY, None)

    def _handler_command_start_ion_regen(self):
        """
        Move my FSM to ION_REGEN and send ION_REGEN to the MCU
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.IONREG)
        return ProtocolState.REGEN, (ResourceAgentState.BUSY, None)

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

    def _handler_command_start_manual(self):
        """
        Move FSM to MANUAL OVERRIDE state
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.MANUAL_OVERRIDE, (ResourceAgentState.COMMAND,
                                               None)

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

    def _handler_error(self):
        """
        @return next_state, next_agent_state
        """
        return ProtocolState.ERROR, ResourceAgentState.BUSY

    def _handler_error_clear(self):
        """
        Send the CLEAR event to any slave protocol in the error state and return this driver to COMMAND
        @return next_state, (next_agent_state, 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 ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

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

    def _handler_autosample_acquire_sample(self):
        """
        Fire off a sample sequence while in the autosample state.
        @return None, None
        @throws InstrumentProtocolException
        """
        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 None, None

    ########################################################################
    # 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.
        @return next_state, (next_agent_state, result)
        """
        self._send_massp_direct_access(data)

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

        return None, (None, None)

    def _handler_direct_access_stop_direct(self):
        """
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_all(ProtocolEvent.STOP_DIRECT)
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

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

    def _handler_stop_regen(self):
        """
        Abort the current regeneration sequence, return to COMMAND
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.STANDBY)
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    def _handler_regen_complete(self):
        """
        Regeneration sequence is complete, return to COMMAND
        @return next_state, (next_agent_state, result)
        """
        self._async_agent_state_change(ResourceAgentState.COMMAND)
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    def _handler_manual_override_stop(self):
        """
        Exit manual override.  Attempt to bring the slave drivers back to COMMAND.
        """
        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 ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    def _handler_manual_get_slave_states(self):
        """
        Get the slave states and return them to the user
        @return: next_state, (next_agent_state, result)
        """
        mcu_state, turbo_state, rga_state = self._get_slave_states()
        return None, (None, {
            MCU: mcu_state,
            RGA: rga_state,
            TURBO: turbo_state
        })
コード例 #2
0
ファイル: driver.py プロジェクト: JeffRoy/marine-integrations
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 = InstrumentFSM(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',
                             units=Units.SECOND)

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.ACQUIRE_SAMPLE, display_name="Acquire one 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 the ion chamber regeneration sequence")
        self._cmd_dict.add(Capability.START_NAFION, display_name="Start the nafion regeneration sequence")
        self._cmd_dict.add(Capability.STOP_REGEN, display_name="Stop the current regeneration sequence")
        self._cmd_dict.add(Capability.STOP_AUTOSAMPLE, display_name="Stop autosample")
        self._cmd_dict.add(Capability.POWEROFF, display_name='Issue the "Power Off" command to the instrument')
        self._cmd_dict.add(Capability.GET_SLAVE_STATES,
                           display_name='Report the states of the underlying slave protocols')

    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 not target 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 not target 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_agent_state, None)
        """
        self._delete_scheduler()

        next_state = ProtocolState.COMMAND
        next_agent_state = ResourceAgentState.COMMAND

        # 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
                next_agent_state = ResourceAgentState.BUSY

        # notify the agent we have changed states
        self._async_agent_state_change(next_agent_state)
        return next_state, (next_agent_state, None)

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

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state
        @return (next_state, next_agent_state)
        """
        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:
            return ProtocolState.ERROR, ResourceAgentState.IDLE
        return ProtocolState.COMMAND, ResourceAgentState.IDLE

    ########################################################################
    # 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.
        """
        self._set_params(*args, **kwargs)
        return None, None

    def _handler_command_start_direct(self):
        """
        Start direct access
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.DIRECT_ACCESS, (ResourceAgentState.DIRECT_ACCESS, None)

    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
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        self._build_scheduler()
        return ProtocolState.AUTOSAMPLE, (ResourceAgentState.STREAMING, None)

    def _handler_command_start_poll(self):
        """
        Move my FSM to poll and start the sample sequence by sending START1 to the MCU
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return ProtocolState.POLL, (ResourceAgentState.BUSY, None)

    def _handler_command_start_calibrate(self):
        """
        Move my FSM to calibrate and start the calibrate sequence by sending START1 to the MCU
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return ProtocolState.CALIBRATE, (ResourceAgentState.BUSY, None)

    def _handler_command_start_nafion_regen(self):
        """
        Move my FSM to NAFION_REGEN and send NAFION_REGEN to the MCU
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.NAFREG)
        return ProtocolState.REGEN, (ResourceAgentState.BUSY, None)

    def _handler_command_start_ion_regen(self):
        """
        Move my FSM to ION_REGEN and send ION_REGEN to the MCU
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.IONREG)
        return ProtocolState.REGEN, (ResourceAgentState.BUSY, None)

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

    def _handler_command_start_manual(self):
        """
        Move FSM to MANUAL OVERRIDE state
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.MANUAL_OVERRIDE, (ResourceAgentState.COMMAND, None)

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

    def _handler_error(self):
        """
        @return next_state, next_agent_state
        """
        return ProtocolState.ERROR, ResourceAgentState.BUSY

    def _handler_error_clear(self):
        """
        Send the CLEAR event to any slave protocol in the error state and return this driver to COMMAND
        @return next_state, (next_agent_state, 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 ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

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

    def _handler_autosample_acquire_sample(self):
        """
        Fire off a sample sequence while in the autosample state.
        @return None, None
        @throws InstrumentProtocolException
        """
        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 None, None

    ########################################################################
    # 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.
        @return next_state, (next_agent_state, result)
        """
        self._send_massp_direct_access(data)

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

        return None, (None, None)

    def _handler_direct_access_stop_direct(self):
        """
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_all(ProtocolEvent.STOP_DIRECT)
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

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

    def _handler_stop_regen(self):
        """
        Abort the current regeneration sequence, return to COMMAND
        @return next_state, (next_agent_state, result)
        """
        self._send_event_to_slave(MCU, mcu.Capability.STANDBY)
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    def _handler_regen_complete(self):
        """
        Regeneration sequence is complete, return to COMMAND
        @return next_state, (next_agent_state, result)
        """
        self._async_agent_state_change(ResourceAgentState.COMMAND)
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    def _handler_manual_override_stop(self):
        """
        Exit manual override.  Attempt to bring the slave drivers back to COMMAND.
        """
        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 ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    def _handler_manual_get_slave_states(self):
        """
        Get the slave states and return them to the user
        @return: next_state, (next_agent_state, result)
        """
        mcu_state, turbo_state, rga_state = self._get_slave_states()
        return None, (None, {MCU: mcu_state, RGA: rga_state, TURBO: turbo_state})
コード例 #3
0
class Protocol(MenuInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses MenuInstrumentProtocol
    """
    def __init__(self, menu, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        MenuInstrumentProtocol.__init__(self, menu, prompts, newline, driver_event)

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

        # Add event handlers for protocol state machine.
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.ENTER, self._handler_unknown_enter)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.DISCOVER, self._handler_discover)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ENTER, self._handler_command_enter)

        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.DISCOVER, self._handler_discover)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.GET, self._handler_command_get)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.SET, self._handler_command_set)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_AUTOSAMPLE, self._handler_command_autosample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_DIRECT, self._handler_command_start_direct)
        
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.DISCOVER, self._handler_discover)
        self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.ENTER, self._handler_direct_access_enter)
        self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.EXIT, self._handler_direct_access_exit)
        self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct)
        self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct)

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

        # Add build handlers for device commands.
        self._add_build_handler(Command.BACK_MENU, self._build_menu_command)
        self._add_build_handler(Command.BLANK, self._build_solo_command)
        self._add_build_handler(Command.START_AUTOSAMPLE, self._build_menu_command)
        self._add_build_handler(Command.CHANGE_PARAM, self._build_menu_command)
        self._add_build_handler(Command.SHOW_PARAM, self._build_menu_command)
        self._add_build_handler(Command.SENSOR_POWER, self._build_menu_command)
        self._add_build_handler(Command.DIRECT_SET, self._build_direct_command)
        self._add_build_handler(Command.CHANGE_CYCLE_TIME, self._build_menu_command)
        self._add_build_handler(Command.CHANGE_VERBOSE, self._build_menu_command)
        self._add_build_handler(Command.CHANGE_METADATA_RESTART, self._build_menu_command)
        self._add_build_handler(Command.CHANGE_METADATA_POWERUP, self._build_menu_command)
        self._add_build_handler(Command.CHANGE_RES_SENSOR_POWER, self._build_menu_command)
        self._add_build_handler(Command.CHANGE_INST_AMP_POWER, self._build_menu_command)
        self._add_build_handler(Command.CHANGE_EH_ISOLATION_AMP_POWER, self._build_menu_command)
        self._add_build_handler(Command.CHANGE_HYDROGEN_POWER, self._build_menu_command)
        self._add_build_handler(Command.CHANGE_REFERENCE_TEMP_POWER, self._build_menu_command)

        # Add response handlers for device commands.
        #self._add_response_handler(Command.GET, self._parse_get_response)
        #self._add_response_handler(Command.SET, self._parse_get_response)
        self._add_response_handler(Command.BACK_MENU, self._parse_menu_change_response)
        self._add_response_handler(Command.BLANK, self._parse_menu_change_response)
        self._add_response_handler(Command.SHOW_PARAM, self._parse_show_param_response)
        self._add_response_handler(Command.CHANGE_CYCLE_TIME, self._parse_menu_change_response)
        self._add_response_handler(Command.CHANGE_VERBOSE, self._parse_menu_change_response)
        self._add_response_handler(Command.CHANGE_METADATA_RESTART, self._parse_menu_change_response)
        self._add_response_handler(Command.CHANGE_METADATA_POWERUP, self._parse_menu_change_response)
        self._add_response_handler(Command.CHANGE_RES_SENSOR_POWER, self._parse_menu_change_response)
        self._add_response_handler(Command.CHANGE_INST_AMP_POWER, self._parse_menu_change_response)
        self._add_response_handler(Command.CHANGE_EH_ISOLATION_AMP_POWER, self._parse_menu_change_response)
        self._add_response_handler(Command.CHANGE_HYDROGEN_POWER, self._parse_menu_change_response)
        self._add_response_handler(Command.CHANGE_REFERENCE_TEMP_POWER, self._parse_menu_change_response)
        self._add_response_handler(Command.DIRECT_SET, self._parse_menu_change_response)
        
        # Add sample handlers.

        # 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._chunker = StringChunker(self.sieve_function)

    @staticmethod
    def sieve_function(raw_data):
        """ The method that splits samples
        """
        return_list = []
        
        for match in SAMPLE_REGEX.finditer(raw_data):
            return_list.append((match.start(), match.end()))

        return return_list

    def _go_to_root_menu(self):
        """ Get back to the root menu, assuming we are in COMMAND mode.
        Getting to command mode should be done before this method is called.
        A discover will get there.
        """
        log.debug("Returning to root menu...")
        # Issue an enter or two off the bat to get out of any display screens
        # and confirm command mode
        try:
            response = self._do_cmd_resp(Command.BLANK, expected_prompt=Prompt.CMD_PROMPT)
            while not str(response).lstrip().endswith(Prompt.CMD_PROMPT):
                response = self._do_cmd_resp(Command.BLANK,
                                             expected_prompt=Prompt.CMD_PROMPT)
                time.sleep(1)
        except InstrumentTimeoutException:
            raise InstrumentProtocolException("Not able to get valid command prompt. Is instrument in command mode?")
        
        # When you get a --> prompt, do 9's until you get back to the root
        response = self._do_cmd_resp(Command.BACK_MENU,
                                     expected_prompt=MENU_PROMPTS)
        while not str(response).lstrip().endswith(Prompt.MAIN_MENU):
            response = self._do_cmd_resp(Command.BACK_MENU,
                                         expected_prompt=MENU_PROMPTS)

            
    def _filter_capabilities(self, events):
        """ Define a small filter of the capabilities
        
        @param A list of events to consider as capabilities
        @retval A list of events that are actually capabilities
        """ 
        events_out = [x for x in events if Capability.has(x)]
        return events_out

    def get_resource_capabilities(self, current_state=True):
        """
        """

        res_cmds = self._protocol_fsm.get_events(current_state)
        res_cmds = self._filter_capabilities(res_cmds)        
        res_params = VisibleParameters.list()
        
        return [res_cmds, res_params]
        
    ########################################################################
    # Unknown handlers.
    ########################################################################

    def _handler_unknown_enter(self, *args, **kwargs):
        """
        Enter unknown state.
        """
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_discover(self, *args, **kwargs):
        """
        Discover current state by going to the root menu 
        @retval (next_state, result)
        """
        next_state = None
        next_agent_state = None
        
        # Try to break in case we are in auto sample
        self._send_break() 

        next_state = ProtocolState.COMMAND
        next_agent_state = ResourceAgentState.IDLE

        self._go_to_root_menu()
      
        return (next_state, next_agent_state)
                
    ########################################################################
    # Command handlers.
    ########################################################################

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        @throw InstrumentTimeoutException if the device cannot be woken.
        @throw InstrumentProtocolException if the update commands and not recognized.
        """
        # Command device to update parameters and send a config change event.
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_command_get(self, params=None, *args, **kwargs):
        """
        Get parameters while in the command state.
        @param params List of the parameters to pass to the state
        @retval returns (next_state, result) where result is a dict {}. No
            agent state changes happening with Get, so no next_agent_state
        @throw InstrumentParameterException for invalid parameter
        """
        next_state = None
        result = None
        result_vals = {}
        
        if (params == None):
            raise InstrumentParameterException("GET parameter list empty!")
            
        if (params == Parameter.ALL):
            params = [Parameter.CYCLE_TIME, Parameter.EH_ISOLATION_AMP_POWER,
                      Parameter.HYDROGEN_POWER, Parameter.INST_AMP_POWER,
                      Parameter.METADATA_POWERUP, Parameter.METADATA_RESTART,
                      Parameter.REFERENCE_TEMP_POWER, Parameter.RES_SENSOR_POWER,
                      Parameter.VERBOSE]
            
        if not isinstance(params, list):
            raise InstrumentParameterException("GET parameter list not a list!")

        # Do a bulk update from the instrument since they are all on one page
        self._update_params()
        
        # fill the return values from the update
        for param in params:
            if not Parameter.has(param):
                raise InstrumentParameterException("Invalid parameter!")
            result_vals[param] = self._param_dict.get(param) 
        result = result_vals

        log.debug("Get finished, next: %s, result: %s", next_state, result) 
        return (next_state, result)

    def _handler_command_set(self, params, *args, **kwargs):
        """Handle setting data from command mode
         
        @param params Dict of the parameters and values to pass to the state
        @retval return (next state, result)
        @throw InstrumentProtocolException For invalid parameter
        """
        next_state = None
        result = None
        result_vals = {}    

        if ((params == None) or (not isinstance(params, dict))):
            raise InstrumentParameterException()
        name_values = params
        for key in name_values.keys():
            if not Parameter.has(key):
                raise InstrumentParameterException()
            
            # restrict operations to just the read/write parameters
            if (key == Parameter.CYCLE_TIME):
                self._navigate(SubMenu.CYCLE_TIME)
                (unit, value) = self._from_seconds(name_values[key])
                
                try:                
                    self._do_cmd_resp(Command.DIRECT_SET, unit,
                                      expected_prompt=[Prompt.CYCLE_TIME_SEC_VALUE_PROMPT,
                                                      Prompt.CYCLE_TIME_MIN_VALUE_PROMPT])
                    self._do_cmd_resp(Command.DIRECT_SET, value,
                                      expected_prompt=Prompt.CHANGE_PARAM_MENU)
                except InstrumentProtocolException:
                    self._go_to_root_menu()
                    raise InstrumentProtocolException("Could not set cycle time")
                
                # Populate with actual value set
                result_vals[key] = name_values[key]
                
        # re-sync with param dict?
        self._go_to_root_menu()
        self._update_params()
        
        result = result_vals
            
        log.debug("next: %s, result: %s", next_state, result) 
        return (next_state, result)

    def _handler_command_autosample(self, *args, **kwargs):
        """ Start autosample mode """
        next_state = None
        next_agent_state = None
        result = None
        
        self._navigate(SubMenu.MAIN)
        self._do_cmd_no_resp(Command.START_AUTOSAMPLE)
        
        next_state = ProtocolState.AUTOSAMPLE
        next_agent_state = ResourceAgentState.STREAMING
        
        return (next_state, (next_agent_state, result))

    def _handler_command_start_direct(self):
        """
        """
        next_state = None
        next_agent_state = None
        result = None

        next_state = ProtocolState.DIRECT_ACCESS
        next_agent_state = ResourceAgentState.DIRECT_ACCESS

        return (next_state, (next_agent_state, result))

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

    def _handler_direct_access_enter(self, *args, **kwargs):
        """
        Enter direct access state.
        """
        # 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.
        """
        pass

    def _handler_direct_access_execute_direct(self, data):
        """
        """
        next_state = None
        result = None

        self._do_cmd_direct(data)

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

        return (next_state, result)

    def _handler_direct_access_stop_direct(self):
        """
        @throw InstrumentProtocolException on invalid command
        """
        next_state = None
        next_agent_state = None
        result = None

        next_state = ProtocolState.COMMAND
        next_agent_state = ResourceAgentState.COMMAND

        return (next_state, (next_agent_state, result))

    ########################################################################
    # Autosample handlers
    ########################################################################
    def _handler_autosample_enter(self, *args, **kwargs):
        """
        Enter autosample mode
        """
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_autosample_stop(self):
        """
        Stop autosample mode
        """
        next_state = None
        next_agent_state = None
        result = None

        if (self._send_break()):        
            next_state = ProtocolState.COMMAND
            next_agent_state = ResourceAgentState.COMMAND
        
        return (next_state, (next_agent_state, result))

    ########################################################################
    # Command builders
    ########################################################################    
    def _build_solo_command(self, cmd):
        """ Issue a simple command that does NOT require a newline at the end to
        execute. Likly used for control characters or special characters """
        return COMMAND_CHAR[cmd]
    
    def _build_menu_command(self, cmd):
        """ Pick the right character and add a newline """
        if COMMAND_CHAR[cmd]:
            return COMMAND_CHAR[cmd]+self._newline
        else:
            raise InstrumentProtocolException("Unknown command character for %s" % cmd)
            
    def _build_direct_command(self, cmd, arg):
        """ Build a command where we just send the argument to the instrument.
        Ignore the command part, we dont need it here as we are already in
        a submenu.
        """
        return "%s%s" % (arg, self._newline)
    
    ########################################################################
    # Command parsers
    ########################################################################
    def _parse_menu_change_response(self, response, prompt):
        """ Parse a response to a menu change
        
        @param response What was sent back from the command that was sent
        @param prompt The prompt that was returned from the device
        @retval The prompt that was encountered after the change
        """
        log.trace("Parsing menu change response with prompt: %s", prompt)
        return prompt

    def _parse_show_param_response(self, response, prompt):
        """ Parse the show parameter response screen """
        log.trace("Parsing show parameter screen")
        self._param_dict.update_many(response)
        
    ########################################################################
    # Utilities
    ########################################################################

    def _wakeup(self, timeout):
        # Always awake for this instrument!
        pass
    
    def _got_chunk(self, chunk):
        '''
        extract samples from a chunk of data
        @param chunk: bytes to parse into a sample.
        '''
        self._extract_sample(BarsDataParticle, SAMPLE_REGEX, chunk)
        
    def _update_params(self):
        """Fetch the parameters from the device, and update the param dict.
        
        @param args Unused
        @param kwargs Takes timeout value
        @throw InstrumentProtocolException
        @throw InstrumentTimeoutException
        """
        log.debug("Updating parameter dict")
        old_config = self._param_dict.get_config()
        self._get_config()
        new_config = self._param_dict.get_config()            
        if (new_config != old_config):
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)  
    
    def _get_config(self, *args, **kwargs):
        """ Get the entire configuration for the instrument
        
        @param params The parameters and values to set
        Should be a dict of parameters and values
        @throw InstrumentProtocolException On a deeper issue
        """
        # Just need to show the parameter screen...the parser for the command
        # does the update_many()
        self._go_to_root_menu()
        self._navigate(SubMenu.SHOW_PARAM)
        self._go_to_root_menu()
            
    def _send_break(self, timeout=4):
        """
        Execute an attempts to break out of auto sample (a few if things get garbled).
        For this instrument, it is done with a ^S, a wait for a \r\n, then
        another ^S within 1/2 a second
        @param timeout
        @retval True if 2 ^S chars were sent with a prompt in the middle, False
            if not.
        """
        log.debug("Sending break sequence to instrument...")
        # Timing is an issue, so keep it simple, work directly with the
        # couple chars instead of command/respose. Could be done that way
        # though. Just more steps, logic, and delay for such a simple
        # exchange
        
        for count in range(0, 3):
            self._promptbuf = ""
            try:
                self._connection.send("%c" % COMMAND_CHAR[Command.BREAK])
                (prompt, result) = self._get_raw_response(timeout, expected_prompt=[Prompt.BREAK_ACK,
                                                                              Prompt.CMD_PROMPT])
                if (prompt == Prompt.BREAK_ACK):
                    self._connection.send("%c" % COMMAND_CHAR[Command.BREAK])
                    (prompt, result) = self._get_response(timeout, expected_prompt=Prompt.CMD_PROMPT)
                    return True
                elif(prompt == Prompt.CMD_PROMPT):
                    return True
                
            except InstrumentTimeoutException:
                continue

        log.trace("_send_break failing after several attempts")
        return False   
 
    def set_readonly_values(self, *args, **kwargs):
        """Set read-only values to the instrument. This is usually (only?)
        done at initialization.
        
        @throw InstrumentProtocolException When in the wrong state or something
        really bad prevents the setting of all values.
        """
        # Let's give it a try in unknown state
        if (self.get_current_state() != ProtocolState.COMMAND):
            raise InstrumentProtocolException("Not in command state. Unable to set read-only params")

        self._go_to_root_menu()
        self._update_params()

        for param in self._param_dict.get_visibility_list(ParameterDictVisibility.READ_ONLY):
            if not Parameter.has(param):
                raise InstrumentParameterException()

            self._go_to_root_menu()
            # Only try to change them if they arent set right as it is
            log.trace("Setting read-only parameter: %s, current paramdict value: %s, init val: %s",
                      param, self._param_dict.get(param),
                      self._param_dict.get_init_value(param))
            if (self._param_dict.get(param) != self._param_dict.get_init_value(param)):
                if (param == Parameter.METADATA_POWERUP):
                    self._navigate(SubMenu.METADATA_POWERUP)
                    result = self._do_cmd_resp(Command.DIRECT_SET, (1+ int(self._param_dict.get_init_value(param))),
                                               expected_prompt=Prompt.CHANGE_PARAM_MENU)
                    if not result:
                        raise InstrumentParameterException("Could not set param %s" % param)
                    
                    self._go_to_root_menu()                
                
                elif (param == Parameter.METADATA_RESTART):
                    self._navigate(SubMenu.METADATA_RESTART)
                    result = self._do_cmd_resp(Command.DIRECT_SET, (1 + int(self._param_dict.get_init_value(param))),
                                               expected_prompt=Prompt.CHANGE_PARAM_MENU)
                    if not result:
                        raise InstrumentParameterException("Could not set param %s" % param)
                    
                    self._go_to_root_menu()
                    
                elif (param == Parameter.VERBOSE):
                    self._navigate(SubMenu.VERBOSE)
                    result = self._do_cmd_resp(Command.DIRECT_SET, self._param_dict.get_init_value(param),
                                               expected_prompt=Prompt.CHANGE_PARAM_MENU)
                    if not result:
                        raise InstrumentParameterException("Could not set param %s" % param)
                    
                    self._go_to_root_menu()    
                    
                elif (param == Parameter.EH_ISOLATION_AMP_POWER):
                    result = self._navigate(SubMenu.EH_ISOLATION_AMP_POWER)
                    while not result:
                        result = self._navigate(SubMenu.EH_ISOLATION_AMP_POWER)
                        
                elif (param == Parameter.HYDROGEN_POWER):
                    result = self._navigate(SubMenu.HYDROGEN_POWER)
                    while not result:
                        result = self._navigate(SubMenu.HYDROGEN_POWER)
        
                elif (param == Parameter.INST_AMP_POWER):
                    result = self._navigate(SubMenu.INST_AMP_POWER)
                    while not result:
                        result = self._navigate(SubMenu.INST_AMP_POWER)
                    
                elif (param == Parameter.REFERENCE_TEMP_POWER):
                    result = self._navigate(SubMenu.REFERENCE_TEMP_POWER)
                    while not result:
                        result = self._navigate(SubMenu.REFERENCE_TEMP_POWER)
                    
                elif (param == Parameter.RES_SENSOR_POWER):
                    result = self._navigate(SubMenu.RES_SENSOR_POWER)
                    while not result:
                        result = self._navigate(SubMenu.RES_SENSOR_POWER)
                
        # re-sync with param dict?
        self._go_to_root_menu()
        self._update_params()
        
        # Should be good by now, but let's double check just to be safe
        for param in self._param_dict.get_visibility_list(ParameterDictVisibility.READ_ONLY):
            if (param == Parameter.VERBOSE):
                continue
            if (self._param_dict.get(param) != self._param_dict.get_init_value(param)):
                raise InstrumentProtocolException("Could not set default values!")
                
        
    def _build_param_dict(self):
        """
        Populate the parameter dictionary with parameters.
        For each parameter key, add match stirng, match lambda function,
        and value formatting function for set commands.
        """
        # Add parameter handlers to parameter dict.
        self._param_dict = ProtocolParameterDict()
        
        self._param_dict.add(Parameter.CYCLE_TIME,
                             r'(\d+)\s+= Cycle Time \(.*\)\r\n(0|1)\s+= Minutes or Seconds Cycle Time',
                             lambda match : self._to_seconds(int(match.group(1)),
                                                             int(match.group(2))),
                             self._int_to_string,
                             visibility=ParameterDictVisibility.READ_WRITE,
                             startup_param=True,
                             direct_access=False,
                             default_value=20,
                             menu_path_read=SubMenu.SHOW_PARAM,
                             submenu_read=[],
                             menu_path_write=SubMenu.CHANGE_PARAM,
                             submenu_write=[["1", Prompt.CYCLE_TIME_PROMPT]])
        
        self._param_dict.add(Parameter.VERBOSE,
                             r'', # Write-only, so does it really matter?
                             lambda match : None,
                             self._int_to_string,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             startup_param=True,
                             direct_access=True,
                             init_value=1,
                             menu_path_write=SubMenu.CHANGE_PARAM,
                             submenu_write=[["2", Prompt.VERBOSE_PROMPT]])
 
        self._param_dict.add(Parameter.METADATA_POWERUP,
                             r'(0|1)\s+= Metadata Print Status on Power up',
                             lambda match : int(match.group(1)),
                             self._int_to_string,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             startup_param=True,
                             direct_access=True,
                             init_value=0,
                             menu_path_write=SubMenu.CHANGE_PARAM,
                             submenu_write=[["3", Prompt.METADATA_PROMPT]])

        self._param_dict.add(Parameter.METADATA_RESTART,
                             r'(0|1)\s+= Metadata Print Status on Restart Data Collection',
                             lambda match : int(match.group(1)),
                             self._int_to_string,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             startup_param=True,
                             direct_access=True,
                             init_value=0,
                             menu_path_write=SubMenu.CHANGE_PARAM,
                             submenu_write=[["4", Prompt.METADATA_PROMPT]])
        
        self._param_dict.add(Parameter.RES_SENSOR_POWER,
                             r'(0|1)\s+= Res Power Status',
                             lambda match : int(match.group(1)),
                             self._int_to_string,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             startup_param=True,
                             direct_access=False,
                             init_value=1,
                             menu_path_read=SubMenu.SHOW_PARAM,
                             submenu_read=[],
                             menu_path_write=SubMenu.SENSOR_POWER,
                             submenu_write=[["1"]])

        self._param_dict.add(Parameter.INST_AMP_POWER,
                             r'(0|1)\s+= Thermocouple & Hydrogen Amp Power Status',
                             lambda match : int(match.group(1)),
                             self._int_to_string,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             startup_param=True,
                             direct_access=False,
                             init_value=1,
                             menu_path_read=SubMenu.SHOW_PARAM,
                             submenu_read=[],
                             menu_path_write=SubMenu.SENSOR_POWER,
                             submenu_write=[["2"]])

        self._param_dict.add(Parameter.EH_ISOLATION_AMP_POWER,
                             r'(0|1)\s+= eh Amp Power Status',
                             lambda match : int(match.group(1)),
                             self._int_to_string,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             startup_param=True,
                             direct_access=False,
                             init_value=1,
                             menu_path_read=SubMenu.SHOW_PARAM,
                             submenu_read=[],
                             menu_path_write=SubMenu.SENSOR_POWER,
                             submenu_write=[["3"]])
        
        self._param_dict.add(Parameter.HYDROGEN_POWER,
                             r'(0|1)\s+= Hydrogen Sensor Power Status',
                             lambda match : int(match.group(1)),
                             self._int_to_string,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             startup_param=True,
                             direct_access=False,
                             init_value=1,
                             menu_path_read=SubMenu.SHOW_PARAM,
                             submenu_read=[],
                             menu_path_write=SubMenu.SENSOR_POWER,
                             submenu_write=[["4"]])
        
        self._param_dict.add(Parameter.REFERENCE_TEMP_POWER,
                             r'(0|1)\s+= Reference Temperature Power Status',
                             lambda match : int(match.group(1)),
                             self._int_to_string,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             startup_param=True,
                             direct_access=False,
                             init_value=1,
                             menu_path_read=SubMenu.SHOW_PARAM,
                             submenu_read=[],
                             menu_path_write=SubMenu.SENSOR_POWER,
                             submenu_write=[["5"]])
    
    @staticmethod
    def _to_seconds(value, unit):
        """
        Converts a number and a unit into seconds. Ie if "4" and "1"
        comes in, it spits out 240
        @param value The int value for some number of minutes or seconds
        @param unit int of 0 or 1 where 0 is seconds, 1 is minutes
        @return Number of seconds.
        """
        if (not isinstance(value, int)) or (not isinstance(unit, int)):
            raise InstrumentProtocolException("Invalid second arguments!")
        
        if unit == 1:
            return value * 60
        elif unit == 0:
            return value
        else:
            raise InstrumentProtocolException("Invalid Units!")
            
    @staticmethod
    def _from_seconds(value):
        """
        Converts a number of seconds into a (unit, value) tuple.
        
        @param value The number of seconds to convert
        @retval A tuple of unit and value where the unit is 1 for seconds and 2
            for minutes. If the value is 15-59, units should be returned in
            seconds. If the value is over 59, the units will be returned in
            a number of minutes where the seconds are rounded down to the
            nearest minute.
        """
        if (value < 15) or (value > 3600):
            raise InstrumentParameterException("Invalid seconds value: %s" % value)
        
        if (value < 60):
            return (1, value)
        else:
            return (2, value // 60)