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
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)