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 SBE16Protocol(CommandResponseInstrumentProtocol): """ Instrument protocol class for SBE16 driver. Subclasses CommandResponseInstrumentProtocol """ def __init__(self, prompts, newline, driver_event): """ SBE16Protocol constructor. @param prompts A BaseEnum class containing instrument prompts. @param newline The SBE16 newline. @param driver_event Driver process event callback. """ # Construct protocol superclass. CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event) # Build SBE16 protocol state machine. self._protocol_fsm = InstrumentFSM(SBE16ProtocolState, SBE16ProtocolEvent, SBE16ProtocolEvent.ENTER, SBE16ProtocolEvent.EXIT) # Add event handlers for protocol state machine. self._protocol_fsm.add_handler(SBE16ProtocolState.UNKNOWN, SBE16ProtocolEvent.ENTER, self._handler_unknown_enter) self._protocol_fsm.add_handler(SBE16ProtocolState.UNKNOWN, SBE16ProtocolEvent.EXIT, self._handler_unknown_exit) self._protocol_fsm.add_handler(SBE16ProtocolState.UNKNOWN, SBE16ProtocolEvent.DISCOVER, self._handler_unknown_discover) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, SBE16ProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, SBE16ProtocolEvent.EXIT, self._handler_command_exit) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, SBE16ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_acquire_sample) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, SBE16ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, SBE16ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, SBE16ProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, SBE16ProtocolEvent.TEST, self._handler_command_test) self._protocol_fsm.add_handler(SBE16ProtocolState.AUTOSAMPLE, SBE16ProtocolEvent.ENTER, self._handler_autosample_enter) self._protocol_fsm.add_handler(SBE16ProtocolState.AUTOSAMPLE, SBE16ProtocolEvent.EXIT, self._handler_autosample_exit) self._protocol_fsm.add_handler(SBE16ProtocolState.AUTOSAMPLE, SBE16ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE16ProtocolState.AUTOSAMPLE, SBE16ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample) self._protocol_fsm.add_handler(SBE16ProtocolState.TEST, SBE16ProtocolEvent.ENTER, self._handler_test_enter) self._protocol_fsm.add_handler(SBE16ProtocolState.TEST, SBE16ProtocolEvent.EXIT, self._handler_test_exit) self._protocol_fsm.add_handler(SBE16ProtocolState.TEST, SBE16ProtocolEvent.RUN_TEST, self._handler_test_run_tests) self._protocol_fsm.add_handler(SBE16ProtocolState.TEST, SBE16ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE16ProtocolState.DIRECT_ACCESS, SBE16ProtocolEvent.ENTER, self._handler_direct_access_enter) self._protocol_fsm.add_handler(SBE16ProtocolState.DIRECT_ACCESS, SBE16ProtocolEvent.EXIT, self._handler_direct_access_exit) # 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('ds', self._build_simple_command) self._add_build_handler('dc', self._build_simple_command) self._add_build_handler('ts', self._build_simple_command) self._add_build_handler('startnow', self._build_simple_command) self._add_build_handler('stop', self._build_simple_command) self._add_build_handler('tc', self._build_simple_command) self._add_build_handler('tt', self._build_simple_command) self._add_build_handler('tp', self._build_simple_command) self._add_build_handler('set', self._build_set_command) # Add response handlers for device commands. self._add_response_handler('ds', self._parse_dsdc_response) self._add_response_handler('dc', self._parse_dsdc_response) self._add_response_handler('ts', self._parse_ts_response) self._add_response_handler('set', self._parse_set_response) self._add_response_handler('tc', self._parse_test_response) self._add_response_handler('tt', self._parse_test_response) self._add_response_handler('tp', self._parse_test_response) # Add sample handlers. self._sample_pattern = r'^#? *(-?\d+\.\d+), *(-?\d+\.\d+), *(-?\d+\.\d+)' self._sample_pattern += r'(, *(-?\d+\.\d+))?(, *(-?\d+\.\d+))?' self._sample_pattern += r'(, *(\d+) +([a-zA-Z]+) +(\d+), *(\d+):(\d+):(\d+))?' self._sample_pattern += r'(, *(\d+)-(\d+)-(\d+), *(\d+):(\d+):(\d+))?' self._sample_regex = re.compile(self._sample_pattern) # State state machine in UNKNOWN state. self._protocol_fsm.start(SBE16ProtocolState.UNKNOWN) ######################################################################## # 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_unknown_exit(self, *args, **kwargs): """ Exit unknown state. """ pass def _handler_unknown_discover(self, *args, **kwargs): """ Discover current state; can be COMMAND or AUTOSAMPLE. @retval (next_state, result), (SBE16ProtocolState.COMMAND or SBE16State.AUTOSAMPLE, None) if successful. @throws TimeoutError if the device cannot be woken. @throws ProtocolError if the device response does not correspond to an expected state. """ next_state = None result = None # Wakeup the device with timeout if passed. timeout = kwargs.get('timeout', SBE16_TIMEOUT) prompt = self._wakeup(timeout) prompt = self._wakeup(timeout) # Set the state to change. # Raise if the prompt returned does not match command or autosample. if prompt == SBE16Prompt.COMMAND: next_state = SBE16ProtocolState.COMMAND result = SBE16ProtocolState.COMMAND elif prompt == SBE16Prompt.AUTOSAMPLE: next_state = SBE16ProtocolState.AUTOSAMPLE result = SBE16ProtocolState.AUTOSAMPLE else: raise ProtocolError('Failure to recognzie device state.') return (next_state, result) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throws TimeoutError if the device cannot be woken. @throws ProtocolError if the update commands and not recognized. """ # Command device to update parameters and send a config change event. self._update_params() # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_command_exit(self, *args, **kwargs): """ Exit command state. """ pass def _handler_command_set(self, *args, **kwargs): """ Perform a set command. @param args[0] parameter : value dict. @retval (next_state, result) tuple, (None, None). @throws ParameterError if missing set parameters, if set parameters not ALL and not a dict, or if paramter can't be properly formatted. @throws TimeoutError if device cannot be woken for set command. @throws ProtocolError if set command could not be built or misunderstood. """ next_state = None result = None # Retrieve required parameter. # Raise if no parameter provided, or not a dict. try: params = args[0] except IndexError: raise ParameterError('Set command requires a parameter dict.') if not isinstance(params, dict): raise ParameterError('Set parameters not a dict.') # For each key, val in the dict, issue set command to device. # Raise if the command not understood. else: for (key, val) in params.iteritems(): result = self._do_cmd_resp('set', key, val, **kwargs) self._update_params() return (next_state, result) def _handler_command_acquire_sample(self, *args, **kwargs): """ Acquire sample from SBE16. @retval (next_state, result) tuple, (None, sample dict). @throws TimeoutError if device cannot be woken for command. @throws ProtocolError if command could not be built or misunderstood. @throws SampleError if a sample could not be extracted from result. """ next_state = None result = None result = self._do_cmd_resp('ts', *args, **kwargs) return (next_state, result) def _handler_command_start_autosample(self, *args, **kwargs): """ Switch into autosample mode. @retval (next_state, result) tuple, (SBE16ProtocolState.AUTOSAMPLE, None) if successful. @throws TimeoutError if device cannot be woken for command. @throws ProtocolError if command could not be built or misunderstood. """ next_state = None result = None # Assure the device is transmitting. if not self._param_dict.get(SBE16Parameter.TXREALTIME): self._do_cmd_resp('set', SBE16Parameter.TXREALTIME, True, **kwargs) # Issue start command and switch to autosample if successful. self._do_cmd_no_resp('startnow', *args, **kwargs) next_state = SBE16ProtocolState.AUTOSAMPLE return (next_state, result) def _handler_command_test(self, *args, **kwargs): """ Switch to test state to perform instrument tests. @retval (next_state, result) tuple, (SBE16ProtocolState.TEST, None). """ next_state = None result = None next_state = SBE16ProtocolState.TEST return (next_state, result) ######################################################################## # Autosample handlers. ######################################################################## def _handler_autosample_enter(self, *args, **kwargs): """ Enter autosample state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_autosample_exit(self, *args, **kwargs): """ Exit autosample state. """ pass def _handler_autosample_stop_autosample(self, *args, **kwargs): """ Stop autosample and switch back to command mode. @retval (next_state, result) tuple, (SBE16ProtocolState.COMMAND, None) if successful. @throws TimeoutError if device cannot be woken for command. @throws ProtocolError if command misunderstood or incorrect prompt received. """ next_state = None result = None # Wake up the device, continuing until autosample prompt seen. timeout = kwargs.get('timeout', SBE16_TIMEOUT) self._wakeup_until(timeout, SBE16Prompt.AUTOSAMPLE) # Issue the stop command. self._do_cmd_resp('stop', *args, **kwargs) # Prompt device until command prompt is seen. self._wakeup_until(timeout, SBE16Prompt.COMMAND) next_state = SBE16ProtocolState.COMMAND return (next_state, result) ######################################################################## # Common handlers. ######################################################################## def _handler_command_autosample_test_get(self, *args, **kwargs): """ Get device parameters from the parameter dict. @param args[0] list of parameters to retrieve, or DriverParameter.ALL. @throws ParameterError if missing or invalid parameter. """ next_state = None result = None # Retrieve the required parameter, raise if not present. try: params = args[0] except IndexError: raise ParameterError('Get command requires a parameter list or tuple.') # If all params requested, retrieve config. if params == DriverParameter.ALL: result = self._param_dict.get_config() # If not all params, confirm a list or tuple of params to retrieve. # Raise if not a list or tuple. # Retireve each key in the list, raise if any are invalid. else: if not isinstance(params, (list, tuple)): raise ParameterError('Get argument not a list or tuple.') result = {} for key in params: try: val = self._param_dict.get(key) result[key] = val except KeyError: raise ParameterError(('%s is not a valid parameter.' % key)) return (next_state, result) ######################################################################## # Test handlers. ######################################################################## def _handler_test_enter(self, *args, **kwargs): """ Enter test state. Setup the secondary call to run the tests. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) # Forward the test event again to run the test handler and # switch back to command mode afterward. Timer(1, lambda: self._protocol_fsm.on_event(SBE16ProtocolEvent.RUN_TEST)).start() def _handler_test_exit(self, *args, **kwargs): """ Exit test state. """ pass def _handler_test_run_tests(self, *args, **kwargs): """ Run test routines and validate results. @throws TimeoutError if device cannot be woken for command. @throws ProtocolError if command misunderstood or incorrect prompt received. """ next_state = None result = None tc_pass = False tt_pass = False tp_pass = False tc_result = None tt_result = None tp_result = None test_result = {} try: tc_pass, tc_result = self._do_cmd_resp('tc', timeout=200) tt_pass, tt_result = self._do_cmd_resp('tt', timeout=200) tp_pass, tp_result = self._do_cmd_resp('tp', timeout=200) except Exception as e: test_result['exception'] = e test_result['message'] = 'Error running instrument tests.' finally: test_result['cond_test'] = 'Passed' if tc_pass else 'Failed' test_result['cond_data'] = tc_result test_result['temp_test'] = 'Passed' if tt_pass else 'Failed' test_result['temp_data'] = tt_result test_result['pres_test'] = 'Passed' if tp_pass else 'Failed' test_result['pres_data'] = tp_result test_result['success'] = 'Passed' if (tc_pass and tt_pass and tp_pass) else 'Failed' self._driver_event(DriverAsyncEvent.TEST_RESULT, test_result) next_state = SBE16ProtocolState.COMMAND return (next_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) def _handler_direct_access_exit(self, *args, **kwargs): """ Exit direct access state. """ pass ######################################################################## # Private helpers. ######################################################################## def _send_wakeup(self): """ Send a newline to attempt to wake the SBE16 device. """ self._connection.send(SBE16_NEWLINE) def _update_params(self, *args, **kwargs): """ Update the parameter dictionary. Wake the device then issue display status and display calibration commands. The parameter dict will match line output and udpate itself. @throws TimeoutError if device cannot be timely woken. @throws ProtocolError if ds/dc misunderstood. """ # Get old param dict config. old_config = self._param_dict.get_config() # Issue display commands and parse results. timeout = kwargs.get('timeout', SBE16_TIMEOUT) self._do_cmd_resp('ds',timeout=timeout) self._do_cmd_resp('dc',timeout=timeout) # Get new param dict config. If it differs from the old config, # tell driver superclass to publish a config change event. new_config = self._param_dict.get_config() if new_config != old_config: self._driver_event(DriverAsyncEvent.CONFIG_CHANGE) def _build_simple_command(self, cmd): """ Build handler for basic SBE16 commands. @param cmd the simple sbe16 command to format. @retval The command to be sent to the device. """ return cmd+SBE16_NEWLINE def _build_set_command(self, cmd, param, val): """ Build handler for set commands. param=val followed by newline. String val constructed by param dict formatting function. @param param the parameter key to set. @param val the parameter value to set. @ retval The set command to be sent to the device. @throws ProtocolError if the parameter is not valid or if the formatting function could not accept the value passed. """ try: str_val = self._param_dict.format(param, val) set_cmd = '%s=%s' % (param, str_val) set_cmd = set_cmd + SBE16_NEWLINE except KeyError: raise ParameterError('Unknown driver parameter %s' % param) return set_cmd def _parse_set_response(self, response, prompt): """ Parse handler for set command. @param response command response string. @param prompt prompt following command response. @throws ProtocolError if set command misunderstood. """ if prompt != SBE16Prompt.COMMAND: raise ProtocolError('Set command not recognized: %s' % response) def _parse_dsdc_response(self, response, prompt): """ Parse handler for dsdc commands. @param response command response string. @param prompt prompt following command response. @throws ProtocolError if dsdc command misunderstood. """ if prompt != SBE16Prompt.COMMAND: raise ProtocolError('dsdc command not recognized: %s.' % response) for line in response.split(SBE16_NEWLINE): self._param_dict.update(line) def _parse_ts_response(self, response, prompt): """ Response handler for ts command. @param response command response string. @param prompt prompt following command response. @retval sample dictionary containig c, t, d values. @throws ProtocolError if ts command misunderstood. @throws InstrumentSampleError if response did not contain a sample """ if prompt != SBE16Prompt.COMMAND: raise ProtocolError('ts command not recognized: %s', response) sample = None for line in response.split(SBE16_NEWLINE): sample = self._extract_sample(line, True) if sample: break if not sample: raise SampleError('Response did not contain sample: %s' % repr(response)) return sample def _parse_test_response(self, response, prompt): """ Do minimal checking of test outputs. @param response command response string. @param promnpt prompt following command response. @retval tuple of pass/fail boolean followed by response """ success = False lines = response.split() if len(lines)>2: data = lines[1:-1] bad_count = 0 for item in data: try: float(item) except ValueError: bad_count += 1 if bad_count == 0: success = True return (success, response) def got_data(self, data): """ Callback for receiving new data from the device. """ # Call the superclass to update line and promp buffers. CommandResponseInstrumentProtocol.got_data(self, data) # If in streaming mode, process the buffer for samples to publish. cur_state = self.get_current_state() if cur_state == SBE16ProtocolState.AUTOSAMPLE: if SBE16_NEWLINE in self._linebuf: lines = self._linebuf.split(SBE16_NEWLINE) self._linebuf = lines[-1] for line in lines: self._extract_sample(line) def _extract_sample(self, line, publish=True): """ Extract sample from a response line if present and publish to agent. @param line string to match for sample. @param publsih boolean to publish sample (default True). @retval Sample dictionary if present or None. """ sample = None match = self._sample_regex.match(line) if match: sample = {} sample['t'] = [float(match.group(1))] sample['c'] = [float(match.group(2))] sample['p'] = [float(match.group(3))] # Driver timestamp. sample['time'] = [time.time()] sample['stream_name'] = 'ctd_parsed' if self._driver_event: self._driver_event(DriverAsyncEvent.SAMPLE, sample) return sample def _build_param_dict(self): """ Populate the parameter dictionary with SBE16 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.add(SBE16Parameter.OUTPUTSAL, r'(do not )?output salinity with each sample', lambda match : False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE16Parameter.OUTPUTSV, r'(do not )?output sound velocity with each sample', lambda match : False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE16Parameter.NAVG, r'number of samples to average = (\d+)', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE16Parameter.SAMPLENUM, r'samplenumber = (\d+), free = \d+', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE16Parameter.INTERVAL, r'sample interval = (\d+) seconds', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE16Parameter.STORETIME, r'(do not )?store time with each sample', lambda match : False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE16Parameter.TXREALTIME, r'(do not )?transmit real-time data', lambda match : False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE16Parameter.SYNCMODE, r'serial sync mode (enabled|disabled)', lambda match : False if (match.group(1)=='disabled') else True, self._true_false_to_string) self._param_dict.add(SBE16Parameter.SYNCWAIT, r'wait time after serial sync sampling = (\d+) seconds', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE16Parameter.TCALDATE, r'temperature: +((\d+)-([a-zA-Z]+)-(\d+))', lambda match : self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE16Parameter.TA0, r' +TA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.TA1, r' +TA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.TA2, r' +TA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.TA3, r' +TA3 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.CCALDATE, r'conductivity: +((\d+)-([a-zA-Z]+)-(\d+))', lambda match : self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE16Parameter.CG, r' +G = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.CH, r' +H = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.CI, r' +I = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.CJ, r' +J = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.WBOTC, r' +WBOTC = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.CTCOR, r' +CTCOR = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.CPCOR, r' +CPCOR = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.PCALDATE, r'pressure .+ ((\d+)-([a-zA-Z]+)-(\d+))', lambda match : self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE16Parameter.PA0, r' +PA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.PA1, r' +PA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.PA2, r' +PA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.PTCA0, r' +PTCA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.PTCA1, r' +PTCA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.PTCA2, r' +PTCA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.PTCB0, r' +PTCSB0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.PTCB1, r' +PTCSB1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.PTCB2, r' +PTCSB2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.POFFSET, r' +POFFSET = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.RCALDATE, r'rtc: +((\d+)-([a-zA-Z]+)-(\d+))', lambda match : self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE16Parameter.RTCA0, r' +RTCA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.RTCA1, r' +RTCA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE16Parameter.RTCA2, r' +RTCA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) ######################################################################## # Static helpers to format set commands. ######################################################################## @staticmethod def _true_false_to_string(v): """ Write a boolean value to string formatted for sbe16 set operations. @param v a boolean value. @retval A yes/no string formatted for sbe16 set operations. @throws ParameterError if value not a bool. """ if not isinstance(v,bool): raise ParameterError('Value %s is not a bool.' % str(v)) if v: return 'y' else: return 'n' @staticmethod def _int_to_string(v): """ Write an int value to string formatted for sbe16 set operations. @param v An int val. @retval an int string formatted for sbe16 set operations. @throws ParameterError if value not an int. """ if not isinstance(v,int): raise ParameterError('Value %s is not an int.' % str(v)) else: return '%i' % v @staticmethod def _float_to_string(v): """ Write a float value to string formatted for sbe16 set operations. @param v A float val. @retval a float string formatted for sbe16 set operations. @throws ParameterError if value is not a float. """ if not isinstance(v,float): raise ParameterError('Value %s is not a float.' % v) else: return '%e' % v @staticmethod def _date_to_string(v): """ Write a date tuple to string formatted for sbe16 set operations. @param v a date tuple: (day,month,year). @retval A date string formatted for sbe16 set operations. @throws ParameterError if date tuple is not valid. """ if not isinstance(v,(list,tuple)): raise ParameterError('Value %s is not a list, tuple.' % str(v)) if not len(v)==3: raise ParameterError('Value %s is not length 3.' % str(v)) months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep', 'Oct','Nov','Dec'] day = v[0] month = v[1] year = v[2] if len(str(year)) > 2: year = int(str(year)[-2:]) if not isinstance(day,int) or day < 1 or day > 31: raise ParameterError('Value %s is not a day of month.' % str(day)) if not isinstance(month,int) or month < 1 or month > 12: raise ParameterError('Value %s is not a month.' % str(month)) if not isinstance(year,int) or year < 0 or year > 99: raise ParameterError('Value %s is not a 0-99 year.' % str(year)) return '%02i-%s-%02i' % (day,months[month-1],year) @staticmethod def _string_to_date(datestr,fmt): """ Extract a date tuple from an sbe16 date string. @param str a string containing date information in sbe16 format. @retval a date tuple. @throws ParameterError if datestr cannot be formatted to a date. """ if not isinstance(datestr,str): raise ParameterError('Value %s is not a string.' % str(datestr)) try: date_time = time.strptime(datestr,fmt) date = (date_time[2],date_time[1],date_time[0]) except ValueError: raise ParameterError('Value %s could not be formatted to a date.' % str(datestr)) return date
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)