class SingleConnectionInstrumentDriver(InstrumentDriver):
    """
    Base class for instrument drivers with a single device connection.
    Provides connenction state logic for single connection drivers.
    """

    def __init__(self, event_callback):
        """
        Constructor for singly connected instrument drivers.
        @param event_callback Callback to the driver process to send asynchronous
        driver events back to the agent.
        """
        InstrumentDriver.__init__(self, event_callback)

        # The only and only instrument connection.
        # Exists in the connected state.
        self._connection = None

        # The one and only instrument protocol.
        self._protocol = None

        # Build connection state machine.
        self._connection_fsm = InstrumentFSM(DriverConnectionState, DriverEvent, DriverEvent.ENTER, DriverEvent.EXIT)

        # Add handlers for all events.
        self._connection_fsm.add_handler(
            DriverConnectionState.UNCONFIGURED, DriverEvent.ENTER, self._handler_unconfigured_enter
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.UNCONFIGURED, DriverEvent.EXIT, self._handler_unconfigured_exit
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.UNCONFIGURED, DriverEvent.INITIALIZE, self._handler_unconfigured_initialize
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.UNCONFIGURED, DriverEvent.CONFIGURE, self._handler_unconfigured_configure
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.DISCONNECTED, DriverEvent.ENTER, self._handler_disconnected_enter
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.DISCONNECTED, DriverEvent.EXIT, self._handler_disconnected_exit
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.DISCONNECTED, DriverEvent.INITIALIZE, self._handler_disconnected_initialize
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.DISCONNECTED, DriverEvent.CONFIGURE, self._handler_disconnected_configure
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.DISCONNECTED, DriverEvent.CONNECT, self._handler_disconnected_connect
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.ENTER, self._handler_connected_enter
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.EXIT, self._handler_connected_exit
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.DISCONNECT, self._handler_connected_disconnect
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.DISCOVER, self._handler_connected_protocol_event
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.GET, self._handler_connected_protocol_event
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.SET, self._handler_connected_protocol_event
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.ACQUIRE_SAMPLE, self._handler_connected_protocol_event
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.START_AUTOSAMPLE, self._handler_connected_protocol_event
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.STOP_AUTOSAMPLE, self._handler_connected_protocol_event
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.TEST, self._handler_connected_protocol_event
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.CALIBRATE, self._handler_connected_protocol_event
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.EXECUTE_DIRECT, self._handler_connected_protocol_event
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.START_DIRECT, self._handler_connected_protocol_event
        )
        self._connection_fsm.add_handler(
            DriverConnectionState.CONNECTED, DriverEvent.STOP_DIRECT, self._handler_connected_protocol_event
        )

        # Start state machine.
        self._connection_fsm.start(DriverConnectionState.UNCONFIGURED)

    #############################################################
    # Device connection interface.
    #############################################################

    def initialize(self, *args, **kwargs):
        """
        Initialize driver connection, bringing communications parameters
        into unconfigured state (no connection object).
        @raises StateError if command not allowed in current state        
        """
        # Forward event and argument to the connection FSM.
        return self._connection_fsm.on_event(DriverEvent.INITIALIZE, *args, **kwargs)

    def configure(self, *args, **kwargs):
        """
        Configure the driver for communications with the device via
        port agent / logger (valid but unconnected connection object).
        @param arg[0] comms config dict.
        @raises StateError if command not allowed in current state        
        @throws ParameterError if missing comms or invalid config dict.
        """
        # Forward event and argument to the connection FSM.
        return self._connection_fsm.on_event(DriverEvent.CONFIGURE, *args, **kwargs)

    def connect(self, *args, **kwargs):
        """
        Establish communications with the device via port agent / logger
        (connected connection object).
        @raises StateError if command not allowed in current state
        @throws ConnectionError if the connection failed.
        """
        # Forward event and argument to the connection FSM.
        return self._connection_fsm.on_event(DriverEvent.CONNECT, *args, **kwargs)

    def disconnect(self, *args, **kwargs):
        """
        Disconnect from device via port agent / logger.
        @raises StateError if command not allowed in current state
        """
        # Forward event and argument to the connection FSM.
        return self._connection_fsm.on_event(DriverEvent.DISCONNECT, *args, **kwargs)

    #############################################################
    # Commande and control interface.
    #############################################################

    def discover(self, *args, **kwargs):
        """
        Determine initial state upon establishing communications.
        @param timeout=timeout Optional command timeout.        
        @retval Current device state.
        @raises TimeoutError if could not wake device.
        @raises StateError if command not allowed in current state or if
        device state not recognized.
        @raises NotImplementedError if not implemented by subclass.
        """
        # Forward event and argument to the protocol FSM.
        return self._connection_fsm.on_event(DriverEvent.DISCOVER, DriverEvent.DISCOVER, *args, **kwargs)

    def get(self, *args, **kwargs):
        """
        Retrieve device parameters.
        @param args[0] DriverParameter.ALL or a list of parameters to retrive.
        @retval parameter : value dict.
        @raises ParameterError if missing or invalid get parameters.
        @raises StateError if command not allowed in current state
        @raises NotImplementedError if not implemented by subclass.                        
        """
        # Forward event and argument to the protocol FSM.
        return self._connection_fsm.on_event(DriverEvent.GET, DriverEvent.GET, *args, **kwargs)

    def set(self, *args, **kwargs):
        """
        Set device parameters.
        @param args[0] parameter : value dict of parameters to set.
        @param timeout=timeout Optional command timeout.
        @raises ParameterError if missing or invalid set parameters.
        @riases TimeoutError if could not wake device or no response.
        @raises ProtocolError if set command not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                        
        """
        # Forward event and argument to the protocol FSM.
        return self._connection_fsm.on_event(DriverEvent.SET, DriverEvent.SET, *args, **kwargs)

    def execute_acquire_sample(self, *args, **kwargs):
        """
        Poll for a sample.
        @param timeout=timeout Optional command timeout.        
        @ retval Device sample dict.
        @riases TimeoutError if could not wake device or no response.
        @raises ProtocolError if acquire command not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                        
        """
        # Forward event and argument to the protocol FSM.
        return self._connection_fsm.on_event(DriverEvent.ACQUIRE_SAMPLE, DriverEvent.ACQUIRE_SAMPLE, *args, **kwargs)

    def execute_start_autosample(self, *args, **kwargs):
        """
        Switch to autosample mode.
        @param timeout=timeout Optional command timeout.        
        @riases TimeoutError if could not wake device or no response.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                        
        """
        # Forward event and argument to the protocol FSM.
        return self._connection_fsm.on_event(
            DriverEvent.START_AUTOSAMPLE, DriverEvent.START_AUTOSAMPLE, *args, **kwargs
        )

    def execute_stop_autosample(self, *args, **kwargs):
        """
        Leave autosample mode.
        @param timeout=timeout Optional command timeout.        
        @riases TimeoutError if could not wake device or no response.
        @raises ProtocolError if stop command not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                        
         """
        # Forward event and argument to the protocol FSM.
        return self._connection_fsm.on_event(DriverEvent.STOP_AUTOSAMPLE, DriverEvent.STOP_AUTOSAMPLE, *args, **kwargs)

    def execute_test(self, *args, **kwargs):
        """
        Execute device tests.
        @param timeout=timeout Optional command timeout (for wakeup only --
        device specific timeouts for internal test commands).
        @riases TimeoutError if could not wake device or no response.
        @raises ProtocolError if test commands not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                        
        """
        # Forward event and argument to the protocol FSM.
        return self._connection_fsm.on_event(DriverEvent.TEST, DriverEvent.TEST, *args, **kwargs)

    def execute_calibrate(self, *args, **kwargs):
        """
        Execute device calibration.
        @param timeout=timeout Optional command timeout (for wakeup only --
        device specific timeouts for internal calibration commands).
        @riases TimeoutError if could not wake device or no response.
        @raises ProtocolError if test commands not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                        
        """
        # Forward event and argument to the protocol FSM.
        return self._connection_fsm.on_event(DriverEvent.CALIBRATE, DriverEvent.CALIBRATE, *args, **kwargs)

    def execute_start_direct_access(self, *args, **kwargs):
        """
        Switch to direct access mode.
        @raises TimeoutError if could not wake device or no response.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                
        """
        return self._connection_fsm.on_event(DriverEvent.START_DIRECT, DriverEvent.START_DIRECT, *args, **kwargs)

    def execute_direct_access(self, *args, **kwargs):
        """
        output direct access data to device.
        @raises TimeoutError if could not wake device or no response.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                
        """
        return self._connection_fsm.on_event(DriverEvent.EXECUTE_DIRECT, DriverEvent.EXECUTE_DIRECT, *args, **kwargs)

    def execute_stop_direct_access(self, *args, **kwargs):
        """
        Leave direct access mode.
        @raises TimeoutError if could not wake device or no response.
        @raises ProtocolError if stop command not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                
        """
        return self._connection_fsm.on_event(DriverEvent.STOP_DIRECT, DriverEvent.STOP_DIRECT, *args, **kwargs)

    ########################################################################
    # Resource query interface.
    ########################################################################

    def get_current_state(self):
        """
        Return current device state. For single connection devices, return
        a single connection state if not connected, and protocol state if connected.
        """
        connection_state = self._connection_fsm.get_current_state()
        if connection_state == DriverConnectionState.CONNECTED:
            return self._protocol.get_current_state()
        else:
            return connection_state

    ########################################################################
    # Unconfigured handlers.
    ########################################################################

    def _handler_unconfigured_enter(self, *args, **kwargs):
        """
        Enter unconfigured state.
        """
        # Send state change event to agent.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_unconfigured_exit(self, *args, **kwargs):
        """
        Exit unconfigured state.
        """
        pass

    def _handler_unconfigured_initialize(self, *args, **kwargs):
        """
        Initialize handler. We are already in unconfigured state, do nothing.
        @retval (next_state, result) tuple, (None, None).
        """
        next_state = None
        result = None

        return (next_state, result)

    def _handler_unconfigured_configure(self, *args, **kwargs):
        """
        Configure driver for device comms.
        @param args[0] Communiations config dictionary.
        @retval (next_state, result) tuple, (DriverConnectionState.DISCONNECTED,
        None) if successful, (None, None) otherwise.
        @raises Parameter error if missing or invalid param dict.        
        """
        next_state = None
        result = None

        # Get the required param dict.
        try:
            config = args[0]

        except IndexError:
            raise ParameterError("Missing comms config parameter.")

        # Verify dict and construct connection client.
        try:
            addr = config["addr"]
            port = config["port"]

            if isinstance(addr, str) and isinstance(port, int) and len(addr) > 0:
                self._connection = LoggerClient(addr, port)
                next_state = DriverConnectionState.DISCONNECTED

            else:
                raise ParameterError("Invalid comms config dict.")

        except (TypeError, KeyError):
            raise ParameterError("Invalid comms config dict.")

        return (next_state, result)

    ########################################################################
    # Disconnected handlers.
    ########################################################################

    def _handler_disconnected_enter(self, *args, **kwargs):
        """
        Enter disconnected state.
        """
        # Send state change event to agent.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_disconnected_exit(self, *args, **kwargs):
        """
        Exit disconnected state.
        """
        pass

    def _handler_disconnected_initialize(self, *args, **kwargs):
        """
        Initialize device communications. Causes the connection parameters to
        be reset.
        @retval (next_state, result) tuple, (DriverConnectionState.UNCONFIGURED,
        None).
        """
        next_state = None
        result = None

        self._connection = None
        next_state = DriverConnectionState.UNCONFIGURED

        return (next_state, result)

    def _handler_disconnected_configure(self, *args, **kwargs):
        """
        Configure driver for device comms.
        @param args[0] Communiations config dictionary.
        @retval (next_state, result) tuple, (None, None).
        @raises Parameter error if missing or invalid param dict.
        """
        next_state = None
        result = None

        # Get required config param dict.
        try:
            config = args[0]

        except IndexError:
            raise ParameterError("Missing comms config parameter.")

        # Verify configuration dict, and update connection if possible.
        try:
            addr = config["addr"]
            port = config["port"]

            if isinstance(addr, str) and isinstance(port, int) and len(addr) > 0:
                self._connection = LoggerClient(addr, port)

            else:
                raise ParameterError("Invalid comms config dict.")

        except (TypeError, KeyError):
            raise ParameterError("Invalid comms config dict.")

        return (next_state, result)

    def _handler_disconnected_connect(self, *args, **kwargs):
        """
        Establish communications with the device via port agent / logger and
        construct and intialize a protocol FSM for device interaction.
        @retval (next_state, result) tuple, (DriverConnectionState.CONNECTED,
        None) if successful.
        @raises ConnectionError if the attempt to connect failed.
        """
        next_state = None
        result = None

        self._build_protocol()
        self._connection.init_comms(self._protocol.got_data)
        self._protocol._connection = self._connection
        next_state = DriverConnectionState.CONNECTED

        return (next_state, result)

    ########################################################################
    # Connected handlers.
    ########################################################################

    def _handler_connected_enter(self, *args, **kwargs):
        """
        Enter connected state.
        """
        # Send state change event to agent.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_connected_exit(self, *args, **kwargs):
        """
        Exit connected state.
        """
        pass

    def _handler_connected_disconnect(self, *args, **kwargs):
        """
        Disconnect to the device via port agent / logger and destroy the
        protocol FSM.
        @retval (next_state, result) tuple, (DriverConnectionState.DISCONNECTED,
        None) if successful.
        """
        next_state = None
        result = None

        self._connection.stop_comms()
        self._protocol = None
        next_state = DriverConnectionState.DISCONNECTED

        return (next_state, result)

    def _handler_connected_connection_lost(self, *args, **kwargs):
        """
        The device connection was lost. Stop comms, destroy protocol FSM and
        revert to disconnected state.
        @retval (next_state, result) tuple, (DriverConnectionState.DISCONNECTED,
        None).
        """
        next_state = None
        result = None

        self._connection.stop_comms()
        self._protocol = None
        next_state = DriverConnectionState.DISCONNECTED

        return (next_state, result)

    def _handler_connected_protocol_event(self, event, *args, **kwargs):
        """
        Forward a driver command event to the protocol FSM.
        @param args positional arguments to pass on.
        @param kwargs keyword arguments to pass on.
        @retval (next_state, result) tuple, (None, protocol result).
        """
        next_state = None
        result = self._protocol._protocol_fsm.on_event(event, *args, **kwargs)

        return (next_state, result)

    ########################################################################
    # Helpers.
    ########################################################################

    def _build_protocol(self):
        """
        Construct device specific single connection protocol FSM.
        Overridden in device specific subclasses.
        """
        pass
Esempio n. 2
0
class SatlanticPARInstrumentProtocol(CommandResponseInstrumentProtocol):
    """The instrument protocol classes to deal with a Satlantic PAR sensor.
    
    The protocol is a very simple command/response protocol with a few show
    commands and a few set commands.
    @todo Check for valid state transitions and handle requests appropriately
    possibly using better exceptions from the fsm.on_event() method
    """
    
    
    def __init__(self, callback=None):
        CommandResponseInstrumentProtocol.__init__(self, callback, Prompt, "\n")
        
        self._fsm = InstrumentFSM(State, Event, Event.ENTER_STATE,
                                  Event.EXIT_STATE,
                                  InstErrorCode.UNHANDLED_EVENT)
        self._fsm.add_handler(State.COMMAND_MODE, Event.COMMAND,
                              self._handler_command_command)
        self._fsm.add_handler(State.COMMAND_MODE, Event.GET,
                              self._handler_command_get)    
        self._fsm.add_handler(State.COMMAND_MODE, Event.SET,
                              self._handler_command_set)
        self._fsm.add_handler(State.AUTOSAMPLE_MODE, Event.BREAK,
                              self._handler_autosample_break)
        self._fsm.add_handler(State.AUTOSAMPLE_MODE, Event.STOP,
                              self._handler_autosample_stop)
        self._fsm.add_handler(State.AUTOSAMPLE_MODE, Event.RESET,
                              self._handler_reset)
        self._fsm.add_handler(State.AUTOSAMPLE_MODE, Event.COMMAND,
                              self._handler_autosample_command)
        self._fsm.add_handler(State.POLL_MODE, Event.AUTOSAMPLE,
                              self._handler_poll_autosample)
        self._fsm.add_handler(State.POLL_MODE, Event.RESET,
                              self._handler_reset)
        self._fsm.add_handler(State.POLL_MODE, Event.SAMPLE,
                              self._handler_poll_sample)
        self._fsm.add_handler(State.POLL_MODE, Event.COMMAND,
                              self._handler_poll_command)
        self._fsm.add_handler(State.UNKNOWN, Event.INITIALIZE,
                              self._handler_initialize)
        self._fsm.start(State.UNKNOWN)

        self._add_build_handler(Command.SET, self._build_set_command)
        self._add_build_handler(Command.GET, self._build_param_fetch_command)
        self._add_build_handler(Command.SAVE, self._build_exec_command)
        self._add_build_handler(Command.EXIT, self._build_exec_command)
        self._add_build_handler(Command.EXIT_AND_RESET, self._build_exec_command)
        self._add_build_handler(Command.AUTOSAMPLE, self._build_control_command)
        self._add_build_handler(Command.RESET, self._build_control_command)
        self._add_build_handler(Command.BREAK, self._build_control_command)
        self._add_build_handler(Command.SAMPLE, self._build_control_command)
        self._add_build_handler(Command.STOP, self._build_control_command)

        
        self._add_response_handler(Command.SET, self._parse_set_response)
        # self._add_response_handler(Command.GET, self._parse_get_response)

        self._add_param_dict(Parameter.TELBAUD,
                             r'Telemetry Baud Rate:\s+(\d+) bps',
                             lambda match : int(match.group(1)),
                             self._int_to_string)
        
        self._add_param_dict(Parameter.MAXRATE,
                             r'Maximum Frame Rate:\s+(\d+) Hz',
                             lambda match : int(match.group(1)),
                             self._int_to_string)
                
    # The normal interface for a protocol. These should drive the FSM
    # transitions as they get things done.
    def get(self, *args, **kwargs):
        """ Get the given parameters from the instrument
        
        @param params The parameter values to get
        @retval Result of FSM event handle, hould be a dict of parameters and values
        @throws InstrumentProtocolException On invalid parameter
        """
        # Parameters checked in Handler
        result = self._fsm.on_event(Event.GET, *args, **kwargs)
        if result == None:
            raise InstrumentProtocolException(InstErrorCode.INCORRECT_STATE)
        assert (isinstance(result, dict))
        return result
   
    def set(self, *args, **kwargs):
        """ Set the given parameters on the instrument
        
        @param params The dict of parameters and values to set
        @retval result of FSM event handle
        @throws InstrumentProtocolException On invalid parameter
        """
        # Parameters checked in handler
        result = self._fsm.on_event(Event.SET, *args, **kwargs)
        if result == None:
            raise InstrumentProtocolException(InstErrorCode.INCORRECT_STATE)
        assert(isinstance(result, dict))
        return result
    
    def execute_save(self, *args, **kwargs):
        """ Execute the save command

        @retval None if nothing was done, otherwise result of FSM event handle
        @throws InstrumentProtocolException On invalid command or missing
        """
        kwargs.update({KwargsKey.COMMAND:Command.SAVE})
        return self._fsm.on_event(Event.COMMAND, *args, **kwargs)
        
    def execute_exit(self, *args, **kwargs):
        """ Execute the exit command

        @retval None if nothing was done, otherwise result of FSM event handle
        @throws InstrumentProtocolException On invalid command or missing
        """
        kwargs.update({KwargsKey.COMMAND:Command.EXIT})
        return self._fsm.on_event(Event.COMMAND, *args, **kwargs)
        
    def execute_exit_and_reset(self, *args, **kwargs):
        """ Execute the exit and reset command

        @retval None if nothing was done, otherwise result of FSM event handle
        @throws InstrumentProtocolException On invalid command or missing
        """
        kwargs.update({KwargsKey.COMMAND:Command.EXIT_AND_RESET})
        return self._fsm.on_event(Event.COMMAND, *args, **kwargs)
    
    def execute_poll(self, *args, **kwargs):
        """ Execute the poll command

        @retval None if nothing was done, otherwise result of FSM event handle
        @throws InstrumentProtocolException On invalid command or missing
        """
        kwargs.update({KwargsKey.COMMAND:Command.POLL})
        return self._fsm.on_event(Event.COMMAND, *args, **kwargs)
    
    def execute_reset(self, *args, **kwargs):
        """ Execute the reset command

        @retval None if nothing was done, otherwise result of FSM event handle
        @throws InstrumentProtocolException On invalid command or missing
        """
        return self._fsm.on_event(Event.RESET, *args, **kwargs)
    
    def execute_break(self, *args, **kwargs):
        """ Execute the break command

        @retval None if nothing was done, otherwise result of FSM event handle
        @throws InstrumentProtocolException On invalid command or missing
        """
        return self._fsm.on_event(Event.BREAK, *args, **kwargs)
    
    def execute_stop(self, *args, **kwargs):
        """ Execute the stop command

        @retval None if nothing was done, otherwise result of FSM event handle
        @throws InstrumentProtocolException On invalid command or missing
        """
        return self._fsm.on_event(Event.STOP, *args, **kwargs)
    
    def execute_autosample(self, *args, **kwargs):
        """ Execute the autosample command

        @retval None if nothing was done, otherwise result of FSM event handle
        @throws InstrumentProtocolException On invalid command or missing
        """
        return self._fsm.on_event(Event.AUTOSAMPLE, *args, **kwargs) 
    
    def execute_sample(self, *args, **kwargs):
        """ Execute the sample command

        @retval None if nothing was done, otherwise result of FSM event handle
        @throws InstrumentProtocolException On invalid command or missing
        """
        return self._fsm.on_event(Event.SAMPLE, *args, **kwargs)
        
    def get_config(self):
        """ Get the entire configuration for the instrument
        
        @param params The parameters and values to set
        @retval None if nothing was done, otherwise result of FSM event handle
        Should be a dict of parameters and values
        @throws InstrumentProtocolException On invalid parameter
        """
        result = self.get([Parameter.TELBAUD, Parameter.MAXRATE])
        assert (isinstance(result, dict))
        assert (result.has_key(Parameter.TELBAUD))
        assert (result.has_key(Parameter.MAXRATE))
        return result
        
    def restore_config(self, config=None):
        """ Apply a complete configuration.
        
        In this instrument, it is simply a compound set that must contain all
        of the parameters.
        @throws InstrumentProtocolException on missing or bad config
        """
        if (config == None):
            raise InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER)
        
        if ((config.has_key(Parameter.TELBAUD))
            and (config.has_key(Parameter.MAXRATE))):  
            assert (isinstance(config, dict))
            assert (len(config) == 2)
            self.set(config)
        else:
            raise InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER)
        
    def get_status(self):
        """
        Get the current state of the state machine as the instrument
        doesnt maintain a status beyond its configuration and its active mode
        
        @retval Something from the State enum
        """
        return self._fsm.current_state()
    
    def initialize(self, *args, **kwargs):
        mi_logger.info('Initializing PAR sensor')
        self._fsm.on_event(Event.INITIALIZE, *args, **kwargs)

    ################
    # State handlers
    ################
    def _handler_initialize(self, *args, **kwargs):
        """Handle transition from UNKNOWN state to a known one.
        
        This method determines what state the device is in or gets it to a
        known state so that the instrument and protocol are in sync.
        @param params Parameters to pass to the state
        @retval return (next state, result)
        """
        next_state = None
        result = None
                
        # Break to command mode, then set next state to command mode
        if self._send_break(Command.BREAK):
            self.announce_to_driver(DriverAnnouncement.STATE_CHANGE,
                                    msg="Initialized, in command mode")            
            next_state = State.COMMAND_MODE
            
        return (next_state, result)
        
        
    def _handler_reset(self, *args, **kwargs):
        """Handle reset condition for all states.
        
        @param params Parameters to pass to the state
        @retval return (next state, result)
        """
        next_state = None
        result = None
        if (self._send_break(Command.RESET)):
            self.announce_to_driver(DriverAnnouncement.STATE_CHANGE,
                                    msg="Reset!")
            next_state = State.AUTOSAMPLE_MODE
            
        return (next_state, result)
        
    def _handler_autosample_break(self, *args, **kwargs):
        """Handle State.AUTOSAMPLE_MODE Event.BREAK
        
        @param params Parameters to pass to the state
        @retval return (next state, result)
        @throw InstrumentProtocolException For hardware error
        """
        next_state = None
        result = None
        
        if (self._send_break(Command.BREAK)):
            self.announce_to_driver(DriverAnnouncement.STATE_CHANGE,
                                    msg="Leaving auto sample!")
            next_state = State.COMMAND_MODE
        else:
            self.announce_to_driver(DriverAnnouncement.ERROR,
                                    error_code=InstErrorCode.HARDWARE_ERROR,
                                    msg="Could not break from autosample!")
            raise InstrumentProtocolException(InstErrorCode.HARDWARE_ERROR)
            
        return (next_state, result)
        
    def _handler_autosample_stop(self, *args, **kwargs):
        """Handle State.AUTOSAMPLE_MODE Event.STOP
        
        @param params Parameters to pass to the state
        @retval return (next state, result)
        @throw InstrumentProtocolException For hardware error
        """
        next_state = None
        result = None
        
        if (self._send_break(Command.STOP)):
            self.announce_to_driver(DriverAnnouncement.STATE_CHANGE,
                                    msg="Leaving auto sample!")
            next_state = State.POLL_MODE
        else:
            self.announce_to_driver(DriverAnnouncement.ERROR,
                                    error_code=InstErrorCode.HARDWARE_ERROR,
                                    msg="Could not stop autosample!")
            raise InstrumentProtocolException(InstErrorCode.HARDWARE_ERROR)
                
        return (next_state, result)

    def _handler_autosample_command(self, *args, **kwargs):
        """Handle State.AUTOSAMPLE_MODE Event.COMMAND transition
        
        @param params Dict with "command" enum and "params" of the parameters to
        pass to the state
        @retval return (next state, result)
        @throw InstrumentProtocolException For invalid parameter
        """
        next_state = None
        result = None
                    
        cmd = kwargs.get(KwargsKey.COMMAND, None)

        if (cmd == Command.BREAK):
            result = self._fsm.on_event(Event.BREAK, *args, **kwargs)
        elif (cmd == Command.STOP):
            result = self._fsm.on_event(Event.STOP, *args, **kwargs)
        elif (cmd == Command.RESET):
            result = self._fsm.on_event(Event.RESET, *args, **kwargs)
        else:
            raise InstrumentProtocolException(InstErrorCode.INVALID_COMMAND)
        
        mi_logger.debug("next: %s, result: %s", next_state, result) 
        return (next_state, result)

    def _handler_command_command(self, *args, **kwargs):
        """Handle State.COMMAND_MODE Event.COMMAND transition
        
        @param params Dict with "command" enum and "params" of the parameters to
        pass to the state
        @retval return (next state, result)
        @throw InstrumentProtocolException For invalid parameter
        """
        next_state = None
        result = None
        cmd = kwargs.get(KwargsKey.COMMAND, None)

        if cmd == Command.EXIT:
            result = self._do_cmd_no_resp(Command.EXIT, None)
            if result:
                self.announce_to_driver(DriverAnnouncement.STATE_CHANGE,
                                         msg="Starting auto sample")
                next_state = State.AUTOSAMPLE_MODE
            
        elif cmd == Command.EXIT_AND_RESET:
            result = self._do_cmd_no_resp(Command.EXIT_AND_RESET, None)
            if result:
                self.announce_to_driver(DriverAnnouncement.STATE_CHANGE,
                                         msg="Starting auto sample")
                next_state = State.AUTOSAMPLE_MODE
            
        elif cmd == Command.SAVE:
            result = self._do_cmd_no_resp(Command.SAVE, None)
        
        elif cmd == Command.POLL:
            try:
                kwargs.update({KwargsKey.COMMAND:Command.EXIT})
                result = self._fsm.on_event(Event.COMMAND, *args, **kwargs)
                result = self._fsm.on_event(Event.STOP, *args, **kwargs)
                result = self._fsm.on_event(Event.SAMPLE, *args, **kwargs)
                # result should have data, right?
                mi_logger.debug("Polled sample: %s", result)
                result = self._fsm.on_event(Event.AUTOSAMPLE, *args, **kwargs)
                result = self._fsm.on_event(Event.BREAK, *args, **kwargs)   
            except (InstrumentTimeoutException, InstrumentProtocolException) as e:
                if self._fsm.current_state == State.AUTOSAMPLE_MODE:
                    result = self._fsm.on_event(Event.BREAK, *args, **kwargs)
                elif (self._fsm.current_state == State.POLL_MODE):
                    result = self._fsm.on_event(Event.AUTOSAMPLE, *args, **kwargs)
                    result = self._fsm.on_event(Event.BREAK, *args, **kwargs)
        
        else:
            raise InstrumentProtocolException(InstErrorCode.INVALID_COMMAND)

        mi_logger.debug("next: %s, result: %s", next_state, result) 
        return (next_state, result)

    def _handler_command_get(self, params=None, *args, **kwargs):
        """Handle getting data from command mode
         
        @param params List of the parameters 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, list))):
                raise InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER)
                
        for param in params:
            if not Parameter.has(param):
                raise InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER)
                break
            result_vals[param] = self._do_cmd_resp(Command.GET, param)
        result = result_vals
            
        mi_logger.debug("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 InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER)
        name_values = params
        for key in name_values.keys():
            if not Parameter.has(key):
                raise InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER)
                break
            result_vals[key] = self._do_cmd_resp(Command.SET, key, name_values[key])
        """@todo raise a parameter error if there was a bad value"""
        result = result_vals
            
        mi_logger.debug("next: %s, result: %s", next_state, result) 
        return (next_state, result)

    def _handler_poll_sample(self, *args, **kwargs):
        """Handle State.POLL_MODE Event.SAMPLE
        
        @retval return (next state, result)
        @throw InstrumentProtocolException For invalid command
        """
        next_state = None
        result = None
        
        result = self._do_cmd_resp(Command.SAMPLE, None)

        self.announce_to_driver(DriverAnnouncement.DATA_RECEIVED,
                                msg=result)          
        return (next_state, result)

    def _handler_poll_autosample(self, *args, **kwargs):
        """Handle State.POLL_MODE Event.AUTOSAMPLE
        
        @retval return (success/fail code, next state, result)
        """
        next_state = None
        result = None
                
        if (self._do_cmd_no_resp(Command.AUTOSAMPLE, None)):
            self.announce_to_driver(DriverAnnouncement.STATE_CHANGE,
                                    msg="Starting auto sample")
            next_state = State.AUTOSAMPLE_MODE
                        
        return (next_state, result)
        
    def _handler_poll_command(self, *args, **kwargs):
        """Handle State.POLL_MODE Event.COMMAND transition
        
        @param params Dict with "command" enum and "params" of the parameters to
        pass to the state
        @retval return (next state, result)
        @throw InstrumentProtocolException For invalid command
        """
        next_state = None
        result = None
        result_vals = {} 
        
        cmd = kwargs.get(KwargsKey.COMMAND, None)
        
        if (cmd == Command.AUTOSAMPLE):
            result = self._fsm.on_event(Event.AUTOSAMPLE, *args, **kwargs)
        elif (cmd == Command.RESET):
            result = self._fsm.on_event(Event.RESET, *args, **kwargs)
        elif (cmd == Command.POLL):
            result = self._fsm.on_event(Event.SAMPLE, *args, **kwargs)
        else:
            raise InstrumentProtocolException(InstErrorCode.INVALID_COMMAND)
        
        mi_logger.debug("next: %s, result: %s", next_state, result) 
        return (next_state, result)

    ###################################################################
    # Builders
    ###################################################################
    def _build_set_command(self, cmd, param, value):
        """
        Build a command that is ready to send out to the instrument. Checks for
        valid parameter name, only handles one value at a time.
        
        @param cmd The command...in this case, Command.SET
        @param param The name of the parameter to set. From Parameter enum
        @param value The value to set for that parameter
        @retval Returns string ready for sending to instrument
        """
        # Check to make sure all parameters are valid up front
        assert Parameter.has(param)
        assert cmd == Command.SET
        return "%s %s %s%s" % (Command.SET, param, value, self.eoln)
        
    def _build_param_fetch_command(self, cmd, param):
        """
        Build a command to fetch the desired argument.
        
        @param cmd The command being used (Command.GET in this case)
        @param param The name of the parameter to fetch
        @retval Returns string ready for sending to instrument
        """
        assert Parameter.has(param)
        return "%s %s%s" % (Command.GET, param, self.eoln)
    
    def _build_exec_command(self, cmd, param):
        """
        Builder for simple commands

        @param cmd The command being used (Command.GET in this case)
        @param param The name of the parameter to fetch
        @retval Returns string ready for sending to instrument        
        """
        assert param == None
        return "%s%s" % (cmd, self.eoln)
    
    def _build_control_command(self, cmd, param):
        """ Send a quick control char command
        
        @param cmd The control character to send
        @param param Unused parameters
        @retval The string wit the complete command (1 char)
        """
        return cmd
    
    ##################################################################
    # Response parsers
    ##################################################################
    def _parse_set_response(self, response, prompt):
        """Determine if a set was successful or not
        
        @param response What was sent back from the command that was sent
        @param prompt The prompt that was returned from the device
        """
        mi_logger.debug("Parsing SET response of %s with prompt %s",
                        response, prompt)
        if ((prompt != Prompt.COMMAND) or (response == Error.INVALID_COMMAND)):
            return InstErrorCode.SET_DEVICE_ERR
        
    def _parse_get_response(self, response, prompt):
        """ Parse the response from the instrument for a couple of different
        query responses.
        
        @param response The response string from the instrument
        @param prompt The prompt received from the instrument
        @retval return The numerical value of the parameter in the known units
        """
        pass
    
    ###################################################################
    # Helpers
    ###################################################################
    def _wakeup(self, timeout):
        """There is no wakeup sequence for this instrument"""
        pass
            
    def _send_break(self, break_char, timeout=30):
        """Break out of autosample mode.
        
        Issue the proper sequence of stuff to get the device out of autosample
        mode. The character used will result in a different end state. Ctrl-S
        goes to poll mode, Ctrl-C goes to command mode. Ctrl-R resets. 
        @param break_char The character to send to get out of autosample.
        Should be Event.STOP, Event.BREAK, or Event.RESET.
        @retval return True for success, Error for failure
        @throw InstrumentTimeoutException
        @throw InstrumentProtocolException
        """
        if not ((break_char == Command.BREAK)
            or (break_char == Command.STOP)
            or (break_char == Command.RESET)):
            return False

        mi_logger.debug("Sending break char %s", break_char)        
        # do the magic sequence of sending lots of characters really fast
        starttime = time.time()
        while True:
            self._do_cmd_no_resp(break_char, None)
            (prompt, result) = self._get_response(timeout)
            mi_logger.debug("Got prompt %s when trying to break",
                            prompt)
            if (prompt):                
                return True
            else:
                if time.time() > starttime + timeout:
                    raise InstrumentTimeoutException(InstErrorCode.TIMEOUT)
                    
        # catch all
        return False
    
    def _got_data(self, data):
        """ The comms object fires this when data is received
        
        @param data The chunk of data that was received
        """
        mi_logger.debug("*** Data received: %s, promptbuf: %s", data, self._promptbuf)
        CommandResponseInstrumentProtocol._got_data(self, data)
        
        # Only keep the latest characters in the prompt buffer.
        #if len(self._promptbuf)>7:
        #    self._promptbuf = self._promptbuf[-7:]
            
        # If we are streaming, process the line buffer for samples.
        if self._fsm.get_current_state() == State.AUTOSAMPLE_MODE:
            if self.eoln in self._linebuf:
                lines = self._linebuf.split(self.eoln)
                self._linebuf = lines[-1]
                for line in lines:
                    self.announce_to_driver(DriverAnnouncement.DATA_RECEIVED,
                                            msg=line)