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. Note protocol state machine must be called "self._protocol_fsm" @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, Prompt, EOLN, callback) self.write_delay = WRITE_DELAY self._last_data_timestamp = None self.eoln = EOLN self._protocol_fsm = InstrumentFSM(PARProtocolState, PARProtocolEvent, PARProtocolEvent.ENTER_STATE, PARProtocolEvent.EXIT_STATE) self._protocol_fsm.add_handler(PARProtocolState.COMMAND_MODE, PARProtocolEvent.AUTOSAMPLE, self._handler_command_autosample) self._protocol_fsm.add_handler(PARProtocolState.COMMAND_MODE, PARProtocolEvent.COMMAND, self._handler_command_command) self._protocol_fsm.add_handler(PARProtocolState.COMMAND_MODE, PARProtocolEvent.ENTER_STATE, self._handler_command_enter_state) self._protocol_fsm.add_handler(PARProtocolState.COMMAND_MODE, PARProtocolEvent.GET, self._handler_command_get) self._protocol_fsm.add_handler(PARProtocolState.COMMAND_MODE, PARProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler(PARProtocolState.COMMAND_MODE, PARProtocolEvent.POLL, self._handler_command_poll) #self._protocol_fsm.add_handler(PARProtocolState.COMMAND_MODE, PARProtocolEvent.SAMPLE, # self._handler_command_sample) self._protocol_fsm.add_handler(PARProtocolState.COMMAND_MODE, PARProtocolEvent.BREAK, self._handler_noop) self._protocol_fsm.add_handler(PARProtocolState.AUTOSAMPLE_MODE, PARProtocolEvent.BREAK, self._handler_autosample_break) self._protocol_fsm.add_handler(PARProtocolState.AUTOSAMPLE_MODE, PARProtocolEvent.STOP, self._handler_autosample_stop) self._protocol_fsm.add_handler(PARProtocolState.AUTOSAMPLE_MODE, PARProtocolEvent.RESET, self._handler_reset) self._protocol_fsm.add_handler(PARProtocolState.AUTOSAMPLE_MODE, PARProtocolEvent.COMMAND, self._handler_autosample_command) self._protocol_fsm.add_handler(PARProtocolState.AUTOSAMPLE_MODE, PARProtocolEvent.ENTER_STATE, self._handler_autosample_enter_state) self._protocol_fsm.add_handler(PARProtocolState.POLL_MODE, PARProtocolEvent.AUTOSAMPLE, self._handler_poll_autosample) self._protocol_fsm.add_handler(PARProtocolState.POLL_MODE, PARProtocolEvent.RESET, self._handler_reset) self._protocol_fsm.add_handler(PARProtocolState.POLL_MODE, PARProtocolEvent.BREAK, self._handler_poll_break) self._protocol_fsm.add_handler(PARProtocolState.POLL_MODE, PARProtocolEvent.SAMPLE, self._handler_poll_sample) self._protocol_fsm.add_handler(PARProtocolState.POLL_MODE, PARProtocolEvent.COMMAND, self._handler_poll_command) self._protocol_fsm.add_handler(PARProtocolState.POLL_MODE, PARProtocolEvent.ENTER_STATE, self._handler_poll_enter_state) self._protocol_fsm.add_handler(PARProtocolState.UNKNOWN, PARProtocolEvent.INITIALIZE, self._handler_initialize) self._protocol_fsm.start(PARProtocolState.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_multi_control_command) self._add_build_handler(Command.RESET, self._build_control_command) self._add_build_handler(Command.BREAK, self._build_multi_control_command) self._add_build_handler(Command.SAMPLE, self._build_control_command) self._add_build_handler(Command.STOP, self._build_multi_control_command) self._add_response_handler(Command.GET, self._parse_get_response) self._add_response_handler(Command.SET, self._parse_set_response) self._add_response_handler(Command.STOP, self._parse_silent_response) self._add_response_handler(Command.SAMPLE, self._parse_sample_poll_response, PARProtocolState.POLL_MODE) self._add_response_handler(Command.SAMPLE, self._parse_cmd_prompt_response, PARProtocolState.COMMAND_MODE) self._add_response_handler(Command.BREAK, self._parse_silent_response, PARProtocolState.COMMAND_MODE) self._add_response_handler(Command.BREAK, self._parse_header_response, PARProtocolState.POLL_MODE) self._add_response_handler(Command.BREAK, self._parse_header_response, PARProtocolState.AUTOSAMPLE_MODE) self._add_response_handler(Command.RESET, self._parse_silent_response, PARProtocolState.COMMAND_MODE) self._add_response_handler(Command.RESET, self._parse_reset_response, PARProtocolState.POLL_MODE) self._add_response_handler(Command.RESET, self._parse_reset_response, PARProtocolState.AUTOSAMPLE_MODE) self._param_dict.add(Parameter.TELBAUD, r'Telemetry Baud Rate:\s+(\d+) bps', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(Parameter.MAXRATE, r'Maximum Frame Rate:\s+(\d+) Hz', lambda match : int(match.group(1)), self._int_to_string) 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._protocol_fsm.on_event(PARProtocolEvent.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._protocol_fsm.on_event(PARProtocolEvent.COMMAND, *args, **kwargs) def execute_poll(self, *args, **kwargs): """ Enter manual poll mode @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing @todo fix this to handle change to poll mode from different states """ return self._protocol_fsm.on_event(PARProtocolEvent.POLL, *args, **kwargs) def execute_sample(self, *args, **kwargs): """ Try to get a sample. Should only be successful once in poll mode, will leave the instrument in poll mode, so an exit may be required to get back to command mode. @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing @todo fix this to handle change to poll mode from different states """ return self._protocol_fsm.on_event(PARProtocolEvent.SAMPLE, *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._protocol_fsm.on_event(PARProtocolEvent.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._protocol_fsm.on_event(PARProtocolEvent.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._protocol_fsm.on_event(PARProtocolEvent.STOP, *args, **kwargs) def execute_stop_autosample(self, *args, **kwargs): """ Leave autosample mode, back to command mode @param timeout=timeout Optional command timeout. @throws InstrumentTimeoutException if could not wake device or no response. @throws InstrumentProtocolException if stop command not recognized. @throws InstrumentStateException if command not allowed in current state. """ # Forward event and argument to the protocol FSM. return self._protocol_fsm.on_event(PARProtocolEvent.BREAK, *args, **kwargs) def execute_init_device(self, *args, **kwargs): """ Transition the device to a know, ready-to-respond, command prompt and bring the state machine from unknown state to a known one (hopefully COMMAND mode) @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing """ return self._protocol_fsm.on_event(PARProtocolEvent.INITIALIZE, *args, **kwargs) def get_config(self, *args, **kwargs): """ 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 """ config = self._protocol_fsm.on_event(PARProtocolEvent.GET, [Parameter.TELBAUD, Parameter.MAXRATE], **kwargs) assert (isinstance(config, dict)) assert (config.has_key(Parameter.TELBAUD)) assert (config.has_key(Parameter.MAXRATE)) # Make sure we get these while config[Parameter.TELBAUD] == InstErrorCode.HARDWARE_ERROR: config[Parameter.TELBAUD] = self._protocol_fsm.on_event(PARProtocolEvent.GET, [Parameter.TELBAUD]) while config[Parameter.MAXRATE] == InstErrorCode.HARDWARE_ERROR: config[Parameter.MAXRATE] = self._protocol_fsm.on_event(PARProtocolEvent.GET, [Parameter.MAXRATE]) return config def restore_config(self, config=None, *args, **kwargs): """ 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 InstrumentParameterException() if ((config.has_key(Parameter.TELBAUD)) and (config.has_key(Parameter.MAXRATE))): assert (isinstance(config, dict)) assert (len(config) == 2) return self._protocol_fsm.on_event(PARProtocolEvent.SET, config, **kwargs) else: raise InstrumentParameterException() ################ # 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) @todo fix this to only do break when connected """ next_state = None result = None # Break to command mode, then set next state to command mode # If we are doing this, we must be connected self._send_break() self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.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 self._send_reset() self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.AUTOSAMPLE_MODE return (next_state, None) def _handler_autosample_enter_state(self, *args, **kwargs): """ Handle PARProtocolState.AUTOSAMPLE_MODE PARProtocolEvent.ENTER @param params Parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For hardware error """ next_state = None result = None if not self._confirm_autosample_mode: raise InstrumentProtocolException(error_code=InstErrorCode.HARDWARE_ERROR, msg="Not in the correct mode!") return (next_state, result) def _handler_autosample_break(self, *args, **kwargs): """Handle PARProtocolState.AUTOSAMPLE_MODE PARProtocolEvent.BREAK @param params Parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For hardware error """ next_state = None result = None try: self._send_break() self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.COMMAND_MODE except InstrumentException: raise InstrumentProtocolException(error_code=InstErrorCode.HARDWARE_ERROR, msg="Could not break from autosample!") return (next_state, result) def _handler_autosample_stop(self, *args, **kwargs): """Handle PARProtocolState.AUTOSAMPLE_MODE PARProtocolEvent.STOP @param params Parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For hardware error """ next_state = None try: self._send_stop() # Give the instrument a bit to keep up. 1 sec is not enough! time.sleep(5) self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.POLL_MODE except InstrumentException: raise InstrumentProtocolException(error_code=InstErrorCode.HARDWARE_ERROR, msg="Could not stop autosample!") return (next_state, None) def _handler_autosample_command(self, *args, **kwargs): """Handle PARProtocolState.AUTOSAMPLE_MODE PARProtocolEvent.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._protocol_fsm.on_event(PARProtocolEvent.BREAK, *args, **kwargs) elif (cmd == Command.STOP): result = self._protocol_fsm.on_event(PARProtocolEvent.STOP, *args, **kwargs) elif (cmd == Command.RESET): result = self._protocol_fsm.on_event(PARProtocolEvent.RESET, *args, **kwargs) else: raise InstrumentProtocolException(error_code=InstErrorCode.INVALID_COMMAND) mi_logger.debug("next: %s, result: %s", next_state, result) return (next_state, result) def _handler_command_enter_state(self, *args, **kwargs): """Handle PARProtocolState.COMMAND_MODE PARProtocolEvent.ENTER_STATE 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' @todo Make this only active when we are connected. """ # Just update parameters, no state change try: # dont do anything for now # pass self._update_params(timeout=3) except InstrumentTimeoutException: #squelch the error if we timeout...best effort update pass return (None, None) def _handler_command_command(self, *args, **kwargs): """Handle PARProtocolState.COMMAND_MODE PARProtocolEvent.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' @todo Fix this funky on_event logic...should just feed one on_event call """ next_state = None result = None cmd = kwargs.get(KwargsKey.COMMAND, None) mi_logger.info("Handling command event [%s] in command mode...", cmd) if cmd == Command.EXIT: result = self._do_cmd_resp(Command.EXIT, None, write_delay=self.write_delay) if result: self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.POLL_MODE elif cmd == Command.EXIT_AND_RESET: self._do_cmd_no_resp(Command.EXIT_AND_RESET, None, write_delay=self.write_delay) time.sleep(RESET_DELAY) self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.AUTOSAMPLE_MODE elif cmd == Command.SAVE: # Sadly, instrument never gives confirmation of a save in any way self._do_cmd_no_resp(Command.SAVE, None, write_delay=self.write_delay) else: raise InstrumentProtocolException(error_code=InstErrorCode.INVALID_COMMAND) mi_logger.debug("next: %s, result: %s", next_state, result) return (next_state, result) def _handler_command_poll(self, *args, **kwargs): """Handle getting a POLL event when in command mode. This should move the state machine into poll mode via autosample mode @retval return (next state, result) @throw InstrumentProtocolException For invalid parameter """ next_state = None result = None try: # get into auto-sample mode guaranteed, then stop and sample kwargs.update({KwargsKey.COMMAND:Command.EXIT_AND_RESET}) result = self._protocol_fsm.on_event(PARProtocolEvent.COMMAND, *args, **kwargs) result = self._protocol_fsm.on_event(PARProtocolEvent.STOP, *args, **kwargs) next_state = PARProtocolState.POLL_MODE self._driver_event(DriverAsyncEvent.STATE_CHANGE) except (InstrumentTimeoutException, InstrumentProtocolException) as e: mi_logger.debug("Caught exception while moving to manual poll mode: %s", e) if self._protocol_fsm.current_state == PARProtocolState.AUTOSAMPLE_MODE: result = self._protocol_fsm.on_event(PARProtocolEvent.BREAK, *args, **kwargs) raise e elif (self._protocol_fsm.current_state == PARProtocolState.POLL_MODE): result = self._protocol_fsm.on_event(PARProtocolEvent.AUTOSAMPLE, *args, **kwargs) result = self._protocol_fsm.on_event(PARProtocolEvent.BREAK, *args, **kwargs) raise e return (next_state, result) def _handler_command_autosample(self, params=None, *args, **kwargs): """Handle getting an autosample event when in 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 = self.execute_exit_and_reset(*args, **kwargs) 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 == DriverParameter.ALL): params = [Parameter.TELBAUD, Parameter.MAXRATE] if ((params == None) or (not isinstance(params, list))): raise InstrumentParameterException() for param in params: if not Parameter.has(param): raise InstrumentParameterException() break result_vals[param] = self._do_cmd_resp(Command.GET, param, expected_prompt=Prompt.COMMAND, write_delay=self.write_delay) result = result_vals mi_logger.debug("Get finished, next: %s, result: %s", next_state, result) return (next_state, result) def _handler_command_set(self, params, *args, **kwargs): """Handle setting data from command mode @param params Dict of the parameters and values to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For invalid parameter """ next_state = None result = None result_vals = {} if ((params == None) or (not isinstance(params, dict))): raise InstrumentParameterException() name_values = params for key in name_values.keys(): if not Parameter.has(key): raise InstrumentParameterException() break result_vals[key] = self._do_cmd_resp(Command.SET, key, name_values[key], expected_prompt=Prompt.COMMAND, write_delay=self.write_delay) # Populate with actual value instead of success flag if result_vals[key]: result_vals[key] = name_values[key] self._update_params() result = self._do_cmd_resp(Command.SAVE, None, None, expected_prompt=Prompt.COMMAND, write_delay=self.write_delay) """@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_enter_state(self, *args, **kwargs): """ Handle PARProtocolState.POLL_MODE PARProtocolEvent.ENTER @param params Parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For hardware error """ next_state = None result = None if not self._confirm_poll_mode: raise InstrumentProtocolException(error_code=InstErrorCode.HARDWARE_ERROR, msg="Not in the correct mode!") return (next_state, result) def _handler_poll_sample(self, *args, **kwargs): """Handle PARProtocolState.POLL_MODE PARProtocolEvent.SAMPLE @retval return (next state, result) @throw InstrumentProtocolException For invalid command """ next_state = None result = None # This sometimes takes a few seconds, so stall after our sample cmd # and before the read/parse delay = self.write_delay + 2 result = self._do_cmd_resp(Command.SAMPLE, None, expected_prompt=Prompt.NULL, write_delay=delay) mi_logger.debug("Polled sample: %s", result) if (result): self._driver_event(DriverAsyncEvent.SAMPLE, result) return (next_state, result) def _handler_poll_break(self, *args, **kwargs): """Handle PARProtocolState.POLL_MODE, PARProtocolEvent.BREAK @retval return (next state, result) @throw InstrumentProtocolException For invalid command """ next_state = None result = None mi_logger.debug("Breaking from poll mode...") try: self._send_break() next_state = PARProtocolState.COMMAND_MODE except InstrumentException: raise InstrumentProtocolException(error_code=InstErrorCode.HARDWARE_ERROR, msg="Could not interrupt hardware!") return (next_state, result) def _handler_poll_autosample(self, *args, **kwargs): """Handle PARProtocolState.POLL_MODE PARProtocolEvent.AUTOSAMPLE @retval return (success/fail code, next state, result) """ next_state = None self._do_cmd_no_resp(Command.AUTOSAMPLE, None) self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.AUTOSAMPLE_MODE return (next_state, None) def _handler_poll_command(self, *args, **kwargs): """Handle PARProtocolState.POLL_MODE PARProtocolEvent.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 cmd = kwargs.get(KwargsKey.COMMAND, None) if (cmd == Command.AUTOSAMPLE): result = self._protocol_fsm.on_event(PARProtocolEvent.AUTOSAMPLE, *args, **kwargs) elif (cmd == Command.RESET): result = self._protocol_fsm.on_event(PARProtocolEvent.RESET, *args, **kwargs) elif (cmd == Command.SAMPLE): result = self._protocol_fsm.on_event(PARProtocolEvent.SAMPLE, *args, **kwargs) elif (cmd == Command.BREAK): result = self._protocol_fsm.on_event(PARProtocolEvent.BREAK, *args, **kwargs) else: raise InstrumentProtocolException(error_code=InstErrorCode.INVALID_COMMAND) mi_logger.debug("next: %s, result: %s", next_state, result) return (next_state, result) def _handler_noop(self, *args, **kwargs): """ Do nothing as a handler...for when an event is acceptable, but not worth acting on. """ return (None, None) ################################################################### # 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, self._param_dict.format(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, *args): """ Builder for simple commands @param cmd The command being used (Command.GET in this case) @param args Unused arguments @retval Returns string ready for sending to instrument """ return "%s%s" % (cmd, self.eoln) def _build_control_command(self, cmd, *args): """ Send a single control char command @param cmd The control character to send @param args Unused arguments @retval The string with the complete command """ return "%c" % (cmd) def _build_multi_control_command(self, cmd, *args): """ Send a quick series of control char command @param cmd The control character to send @param args Unused arguments @retval The string with the complete command """ return "%c%c%c%c%c%c%c" % (cmd, cmd, cmd, cmd, cmd, cmd, 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 """ if prompt == Prompt.COMMAND: return True elif response == PARProtocolError.INVALID_COMMAND: return InstErrorCode.SET_DEVICE_ERR else: return InstErrorCode.HARDWARE_ERROR 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 @todo Fill this in """ # should end with the response, an eoln, and a prompt split_response = response.split(self.eoln) if (len(split_response) < 2) or (split_response[-1] != Prompt.COMMAND): return InstErrorCode.HARDWARE_ERROR name = self._param_dict.update(split_response[-2]) return self._param_dict.get(name) def _parse_silent_response(self, response, prompt): """Parse a silent response @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device @retval return An InstErrorCode value """ mi_logger.debug("Parsing silent response of [%s] with prompt [%s]", response, prompt) if ((response == "") or (response == prompt)) and \ ((prompt == Prompt.NULL) or (prompt == Prompt.COMMAND)): return InstErrorCode.OK else: return InstErrorCode.HARDWARE_ERROR def _parse_header_response(self, response, prompt): """ Parse what the header looks like to make sure if came up. @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device @retval return An InstErrorCode value """ mi_logger.debug("Parsing header response of [%s] with prompt [%s]", response, prompt) if header_regex.search(response): return InstErrorCode.OK else: return InstErrorCode.HARDWARE_ERROR def _parse_reset_response(self, response, prompt): """ Parse the results of a reset This is basically a header followed by some initialization lines @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device @retval return An InstErrorCode value """ mi_logger.debug("Parsing reset response of [%s] with prompt [%s]", response, prompt) lines = response.split(self.eoln) for line in lines: if init_regex.search(line): return InstErrorCode.OK # else return InstErrorCode.HARDWARE_ERROR def _parse_cmd_prompt_response(self, response, prompt): """Parse a command prompt response @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device @retval return An InstErrorCode value """ mi_logger.debug("Parsing command prompt response of [%s] with prompt [%s]", response, prompt) if (response == Prompt.COMMAND): # yank out the command we sent, split at the self.eoln split_result = response.split(self.eoln, 1) if len(split_result) > 1: response = split_result[1] return InstErrorCode.OK else: return InstErrorCode.HARDWARE_ERROR def _parse_sample_poll_response(self, response, prompt): """Parse a sample poll response @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device @retval return The sample string """ mi_logger.debug("Parsing sample poll response of [%s] with prompt [%s]", response, prompt) if (prompt == ""): # strip the eoln, check for regex, report data, # and leave it in the buffer for return via execute_poll if self.eoln in response: lines = response.split(self.eoln) for line in lines: if sample_regex.match(line): # In poll mode, we only care about the first response, right? return line else: return "" elif sample_regex.match(response): return response else: return "" else: return InstErrorCode.HARDWARE_ERROR ################################################################### # Helpers ################################################################### def _wakeup(self, timeout): """There is no wakeup sequence for this instrument""" pass def _update_params(self, *args, **kwargs): """Fetch the parameters from the device, and update the param dict. @param args Unused @param kwargs Takes timeout value @throws InstrumentProtocolException @throws InstrumentTimeoutException """ mi_logger.debug("Updating parameter dict") old_config = self._param_dict.get_config() self.get_config() new_config = self._param_dict.get_config() if (new_config != old_config) and (None not in old_config.values()): self._driver_event(DriverAsyncEvent.CONFIG_CHANGE) def _send_reset(self, timeout=10): """Send a reset command out to the device @throw InstrumentTimeoutException @throw InstrumentProtocolException @todo handle errors correctly here, deal with repeats at high sample rate """ write_delay = 0.2 mi_logger.debug("Sending reset chars") if self._protocol_fsm.get_current_state() == PARProtocolState.COMMAND_MODE: return InstErrorCode.OK while True: self._do_cmd_no_resp(Command.RESET, timeout=timeout, write_delay=write_delay) time.sleep(RESET_DELAY) if self._confirm_autosample_mode(): break def _send_stop(self, timeout=10): """Send a stop command out to the device @retval return InstErrorCode.OK for success or no-op, error code on failure @throw InstrumentTimeoutException @throw InstrumentProtocolException @todo handle errors correctly here, deal with repeats at high sample rate """ write_delay = 0.2 mi_logger.debug("Sending stop chars") if self._protocol_fsm.get_current_state() == PARProtocolState.COMMAND_MODE: return InstErrorCode.OK while True: self._do_cmd_no_resp(Command.STOP, timeout=timeout, write_delay=write_delay) if self._confirm_poll_mode(): return def _send_break(self, timeout=10): """Send a blind break command to the device, confirm command mode after @throw InstrumentTimeoutException @throw InstrumentProtocolException @todo handle errors correctly here, deal with repeats at high sample rate """ write_delay = 0.2 mi_logger.debug("Sending break char") # do the magic sequence of sending lots of characters really fast... # but not too fast if self._protocol_fsm.get_current_state() == PARProtocolState.COMMAND_MODE: return while True: self._do_cmd_no_resp(Command.BREAK, timeout=timeout, expected_prompt=Prompt.COMMAND, write_delay=write_delay) if self._confirm_command_mode(): break def _got_data(self, data): """ The comms object fires this when data is received @param data The chunk of data that was received """ CommandResponseInstrumentProtocol._got_data(self, data) # If we are streaming, process the line buffer for samples, but it # could have header stuff come out if you just got a break! if self._protocol_fsm.get_current_state() == PARProtocolState.AUTOSAMPLE_MODE: if self.eoln in self._linebuf: lines = self._linebuf.split(self.eoln) for line in lines: if sample_regex.match(line): self._last_data_timestamp = time.time() self._driver_event(DriverAsyncEvent.SAMPLE, line) self._linebuf = self._linebuf.replace(line+self.eoln, "") # been processed def _confirm_autosample_mode(self): """Confirm we are in autosample mode This is done by waiting for a sample to come in, and confirming that it does or does not. @retval True if in autosample mode, False if not """ mi_logger.debug("Confirming autosample mode...") # timestamp now, start_time = self._last_data_timestamp # wait a sample period, # @todo get this working when _update_params is happening right (only when connected) #time_between_samples = (1/self._param_dict.get_config()[Parameter.MAXRATE])+1 time_between_samples = 2 time.sleep(time_between_samples) end_time = self._last_data_timestamp return not (end_time == start_time) def _confirm_poll_mode(self): """Confirm we are in poll mode by waiting for things not to happen. Time depends on max data rate @retval True if in poll mode, False if not """ mi_logger.debug("Confirming poll mode...") autosample_mode = self._confirm_autosample_mode() cmd_mode = self._confirm_command_mode() if (not autosample_mode) and (not cmd_mode): mi_logger.debug("Confirmed in poll mode") return True else: mi_logger.debug("Confirmed NOT in poll mode") return False def _confirm_command_mode(self): """Confirm we are in command mode This is done by issuing a bogus command and getting a prompt @retval True if in command mode, False if not """ mi_logger.debug("Confirming command mode...") try: # suspend our belief that we are in another state, and behave # as if we are in command mode long enough to confirm or deny it self._do_cmd_no_resp(Command.SAMPLE, timeout=2, expected_prompt=Prompt.COMMAND) (prompt, result) = self._get_response(timeout=2, expected_prompt=Prompt.COMMAND) except InstrumentTimeoutException: # If we timed out, its because we never got our $ prompt and must # not be in command mode (probably got a data value in POLL mode) mi_logger.debug("Confirmed NOT in command mode via timeout") return False except InstrumentProtocolException: mi_logger.debug("Confirmed NOT in command mode via protocol exception") return False # made it this far mi_logger.debug("Confirmed in command mode") time.sleep(0.5) return True
class SBE37Protocol(CommandResponseInstrumentProtocol): """ Instrument protocol class for SBE37 driver. Subclasses CommandResponseInstrumentProtocol """ def __init__(self, prompts, newline, driver_event): """ SBE37Protocol constructor. @param prompts A BaseEnum class containing instrument prompts. @param newline The SBE37 newline. @param driver_event Driver process event callback. """ # Construct protocol superclass. CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event) # Build SBE37 protocol state machine. self._protocol_fsm = InstrumentFSM(SBE37ProtocolState, SBE37ProtocolEvent, SBE37ProtocolEvent.ENTER, SBE37ProtocolEvent.EXIT) # Add event handlers for protocol state machine. self._protocol_fsm.add_handler(SBE37ProtocolState.UNKNOWN, SBE37ProtocolEvent.ENTER, self._handler_unknown_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.UNKNOWN, SBE37ProtocolEvent.EXIT, self._handler_unknown_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.UNKNOWN, SBE37ProtocolEvent.DISCOVER, self._handler_unknown_discover) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.EXIT, self._handler_command_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_acquire_sample) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.TEST, self._handler_command_test) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.START_DIRECT, self._handler_command_start_direct) self._protocol_fsm.add_handler(SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.ENTER, self._handler_autosample_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.EXIT, self._handler_autosample_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample) self._protocol_fsm.add_handler(SBE37ProtocolState.TEST, SBE37ProtocolEvent.ENTER, self._handler_test_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.TEST, SBE37ProtocolEvent.EXIT, self._handler_test_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.TEST, SBE37ProtocolEvent.RUN_TEST, self._handler_test_run_tests) self._protocol_fsm.add_handler(SBE37ProtocolState.TEST, SBE37ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.ENTER, self._handler_direct_access_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.EXIT, self._handler_direct_access_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct) self._protocol_fsm.add_handler(SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct) # Construct the parameter dictionary containing device parameters, # current parameter values, and set formatting functions. self._build_param_dict() # Add build handlers for device commands. self._add_build_handler('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(SBE37ProtocolState.UNKNOWN) # commands sent sent to device to be filtered in responses for telnet DA self._sent_cmds = [] ######################################################################## # 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), (SBE37ProtocolState.COMMAND or SBE37State.AUTOSAMPLE, None) if successful. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentStateException 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', SBE37_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 == SBE37Prompt.COMMAND: next_state = SBE37ProtocolState.COMMAND result = SBE37ProtocolState.COMMAND elif prompt == SBE37Prompt.AUTOSAMPLE: next_state = SBE37ProtocolState.AUTOSAMPLE result = SBE37ProtocolState.AUTOSAMPLE else: raise InstrumentStateException('Unknown state.') return (next_state, result) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentProtocolException 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 InstrumentParameterException if missing set parameters, if set parameters not ALL and not a dict, or if paramter can't be properly formatted. @throws InstrumentTimeoutException if device cannot be woken for set command. @throws InstrumentProtocolException 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 InstrumentParameterException('Set command requires a parameter dict.') if not isinstance(params, dict): raise InstrumentParameterException('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 SBE37. @retval (next_state, result) tuple, (None, sample dict). @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command could not be built or misunderstood. @throws SampleException 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, (SBE37ProtocolState.AUTOSAMPLE, None) if successful. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command could not be built or misunderstood. """ next_state = None result = None # Assure the device is transmitting. if not self._param_dict.get(SBE37Parameter.TXREALTIME): self._do_cmd_resp('set', SBE37Parameter.TXREALTIME, True, **kwargs) # Issue start command and switch to autosample if successful. self._do_cmd_no_resp('startnow', *args, **kwargs) next_state = SBE37ProtocolState.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, (SBE37ProtocolState.TEST, None). """ next_state = None result = None next_state = SBE37ProtocolState.TEST return (next_state, result) def _handler_command_start_direct(self): """ """ next_state = None result = None next_state = SBE37ProtocolState.DIRECT_ACCESS 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, (SBE37ProtocolState.COMMAND, None) if successful. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException 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', SBE37_TIMEOUT) self._wakeup_until(timeout, SBE37Prompt.AUTOSAMPLE) # Issue the stop command. self._do_cmd_resp('stop', *args, **kwargs) # Prompt device until command prompt is seen. self._wakeup_until(timeout, SBE37Prompt.COMMAND) next_state = SBE37ProtocolState.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 InstrumentParameterException 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 InstrumentParameterException('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 InstrumentParameterException('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 InstrumentParameterException(('%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(SBE37ProtocolEvent.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 InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException 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 = SBE37ProtocolState.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) self._sent_cmds = [] def _handler_direct_access_exit(self, *args, **kwargs): """ Exit direct access state. """ pass def _handler_direct_access_execute_direct(self, data): """ """ next_state = None result = None self._do_cmd_direct(data) # add sent command to list for 'echo' filtering in callback self._sent_cmds.append(data) return (next_state, result) def _handler_direct_access_stop_direct(self): """ @throw InstrumentProtocolException on invalid command """ next_state = None result = None next_state = SBE37ProtocolState.COMMAND return (next_state, result) ######################################################################## # Private helpers. ######################################################################## def _send_wakeup(self): """ Send a newline to attempt to wake the SBE37 device. """ self._connection.send(SBE37_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 InstrumentTimeoutException if device cannot be timely woken. @throws InstrumentProtocolException 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', SBE37_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 SBE37 commands. @param cmd the simple sbe37 command to format. @retval The command to be sent to the device. """ return cmd+SBE37_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 InstrumentProtocolException 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 + SBE37_NEWLINE except KeyError: raise InstrumentParameterException('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 InstrumentProtocolException if set command misunderstood. """ if prompt != SBE37Prompt.COMMAND: raise InstrumentProtocolException('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 InstrumentProtocolException if dsdc command misunderstood. """ if prompt != SBE37Prompt.COMMAND: raise InstrumentProtocolException('dsdc command not recognized: %s.' % response) for line in response.split(SBE37_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 InstrumentProtocolException if ts command misunderstood. @throws InstrumentSampleException if response did not contain a sample """ if prompt != SBE37Prompt.COMMAND: raise InstrumentProtocolException('ts command not recognized: %s', response) sample = None for line in response.split(SBE37_NEWLINE): sample = self._extract_sample(line, True) if sample: break if not sample: raise SampleException('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. """ if self.get_current_state() == SBE37ProtocolState.DIRECT_ACCESS: # direct access mode if len(data) > 0: #mi_logger.debug("SBE37Protocol._got_data(): <" + data + ">") # check for echoed commands from instrument (TODO: this should only be done for telnet?) if len(self._sent_cmds) > 0: # there are sent commands that need to have there echoes filtered out oldest_sent_cmd = self._sent_cmds[0] if string.count(data, oldest_sent_cmd) > 0: # found a command echo, so remove it from data and delete the command form list data = string.replace(data, oldest_sent_cmd, "", 1) self._sent_cmds.pop(0) if len(data) > 0 and self._driver_event: self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, data) # TODO: what about logging this as an event? return if len(data)>0: # Call the superclass to update line and prompt 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 == SBE37ProtocolState.AUTOSAMPLE: if SBE37_NEWLINE in self._linebuf: lines = self._linebuf.split(SBE37_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 SBE37 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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.NAVG, r'number of samples to average = (\d+)', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE37Parameter.SAMPLENUM, r'samplenumber = (\d+), free = \d+', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE37Parameter.INTERVAL, r'sample interval = (\d+) seconds', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.SYNCWAIT, r'wait time after serial sync sampling = (\d+) seconds', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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(SBE37Parameter.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 sbe37 set operations. @param v a boolean value. @retval A yes/no string formatted for sbe37 set operations. @throws InstrumentParameterException if value not a bool. """ if not isinstance(v,bool): raise InstrumentParameterException('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 sbe37 set operations. @param v An int val. @retval an int string formatted for sbe37 set operations. @throws InstrumentParameterException if value not an int. """ if not isinstance(v,int): raise InstrumentParameterException('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 sbe37 set operations. @param v A float val. @retval a float string formatted for sbe37 set operations. @throws InstrumentParameterException if value is not a float. """ if not isinstance(v,float): raise InstrumentParameterException('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 sbe37 set operations. @param v a date tuple: (day,month,year). @retval A date string formatted for sbe37 set operations. @throws InstrumentParameterException if date tuple is not valid. """ if not isinstance(v,(list,tuple)): raise InstrumentParameterException('Value %s is not a list, tuple.' % str(v)) if not len(v)==3: raise InstrumentParameterException('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 InstrumentParameterException('Value %s is not a day of month.' % str(day)) if not isinstance(month,int) or month < 1 or month > 12: raise InstrumentParameterException('Value %s is not a month.' % str(month)) if not isinstance(year,int) or year < 0 or year > 99: raise InstrumentParameterException('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 sbe37 date string. @param str a string containing date information in sbe37 format. @retval a date tuple. @throws InstrumentParameterException if datestr cannot be formatted to a date. """ if not isinstance(datestr,str): raise InstrumentParameterException('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 InstrumentParameterException('Value %s could not be formatted to a date.' % str(datestr)) return date
class VadcpProtocol(CommandResponseInstrumentProtocol): """ """ def __init__(self, callback=None): CommandResponseInstrumentProtocol.__init__(self, Prompt, EOLN, callback) # TODO probably promote this convenience to super-class? # _timeout: Default timeout value for operations accepting an # optional timeout argument self._timeout = 30 self._last_data_timestamp = None self.eoln = EOLN self._protocol_fsm = InstrumentFSM(ProtocolState, ProtocolEvent, None, None) # UNKNOWN self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.INITIALIZE, self._handler_initialize) # COMMAND_MODE self._protocol_fsm.add_handler(ProtocolState.COMMAND_MODE, ProtocolEvent.GET_LAST_ENSEMBLE, self._handler_command_get_latest_sample) self._protocol_fsm.add_handler(ProtocolState.COMMAND_MODE, ProtocolEvent.GET_METADATA, self._handler_command_get_metadata) self._protocol_fsm.add_handler( ProtocolState.COMMAND_MODE, ProtocolEvent.RUN_RECORDER_TESTS, self._handler_command_run_recorder_tests) self._protocol_fsm.add_handler(ProtocolState.COMMAND_MODE, ProtocolEvent.RUN_ALL_TESTS, self._handler_command_run_all_tests) self._protocol_fsm.add_handler(ProtocolState.COMMAND_MODE, ProtocolEvent.START_AUTOSAMPLE, self._handler_command_autosample) # AUTOSAMPLE_MODE self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE_MODE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop) self._protocol_fsm.start(ProtocolState.UNKNOWN) def execute_init_protocol(self, *args, **kwargs): """ """ return self._protocol_fsm.on_event(ProtocolEvent.INITIALIZE, *args, **kwargs) def execute_get_latest_sample(self, *args, **kwargs): """ """ return self._protocol_fsm.on_event(ProtocolEvent.GET_LAST_ENSEMBLE, *args, **kwargs) def execute_get_metadata(self, *args, **kwargs): """ """ return self._protocol_fsm.on_event(ProtocolEvent.GET_METADATA, *args, **kwargs) def execute_run_recorder_tests(self, *args, **kwargs): """ """ return self._protocol_fsm.on_event(ProtocolEvent.RUN_RECORDER_TESTS, *args, **kwargs) def execute_run_all_tests(self, *args, **kwargs): """ """ return self._protocol_fsm.on_event(ProtocolEvent.RUN_ALL_TESTS, *args, **kwargs) ################ # State handlers ################ def _handler_initialize(self, *args, **kwargs): """ Determines initial protocol state according to instrument's state """ next_state = None result = None # TODO determine the state. For now, assume command mode self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = ProtocolState.COMMAND_MODE return (next_state, result) def _handler_command_get_latest_sample(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.DEBUG): log.debug("args=%s kwargs=%s" % (str(args), str(kwargs))) next_state = self._protocol_fsm.get_current_state() result = None timeout = kwargs.get('timeout', self._timeout) try: result = self._connection.get_latest_sample(timeout) except TimeoutException, e: raise InstrumentTimeoutException(msg=str(e)) except ClientException, e: log.warn("ClientException while get_latest_sample: %s" % str(e)) raise InstrumentException('ClientException: %s' % str(e))
class VadcpProtocol(CommandResponseInstrumentProtocol): """ """ def __init__(self, callback=None): CommandResponseInstrumentProtocol.__init__(self, Prompt, EOLN, callback) # TODO probably promote this convenience to super-class? # _timeout: Default timeout value for operations accepting an # optional timeout argument self._timeout = 30 self._last_data_timestamp = None self.eoln = EOLN self._protocol_fsm = InstrumentFSM(ProtocolState, ProtocolEvent, None, None) # UNKNOWN self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.INITIALIZE, self._handler_initialize) # COMMAND_MODE self._protocol_fsm.add_handler(ProtocolState.COMMAND_MODE, ProtocolEvent.GET_LAST_ENSEMBLE, self._handler_command_get_latest_sample) self._protocol_fsm.add_handler(ProtocolState.COMMAND_MODE, ProtocolEvent.GET_METADATA, self._handler_command_get_metadata) self._protocol_fsm.add_handler(ProtocolState.COMMAND_MODE, ProtocolEvent.RUN_RECORDER_TESTS, self._handler_command_run_recorder_tests) self._protocol_fsm.add_handler(ProtocolState.COMMAND_MODE, ProtocolEvent.RUN_ALL_TESTS, self._handler_command_run_all_tests) self._protocol_fsm.add_handler(ProtocolState.COMMAND_MODE, ProtocolEvent.START_AUTOSAMPLE, self._handler_command_autosample) # AUTOSAMPLE_MODE self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE_MODE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop) self._protocol_fsm.start(ProtocolState.UNKNOWN) def execute_init_protocol(self, *args, **kwargs): """ """ return self._protocol_fsm.on_event(ProtocolEvent.INITIALIZE, *args, **kwargs) def execute_get_latest_sample(self, *args, **kwargs): """ """ return self._protocol_fsm.on_event(ProtocolEvent.GET_LAST_ENSEMBLE, *args, **kwargs) def execute_get_metadata(self, *args, **kwargs): """ """ return self._protocol_fsm.on_event(ProtocolEvent.GET_METADATA, *args, **kwargs) def execute_run_recorder_tests(self, *args, **kwargs): """ """ return self._protocol_fsm.on_event(ProtocolEvent.RUN_RECORDER_TESTS, *args, **kwargs) def execute_run_all_tests(self, *args, **kwargs): """ """ return self._protocol_fsm.on_event(ProtocolEvent.RUN_ALL_TESTS, *args, **kwargs) ################ # State handlers ################ def _handler_initialize(self, *args, **kwargs): """ Determines initial protocol state according to instrument's state """ next_state = None result = None # TODO determine the state. For now, assume command mode self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = ProtocolState.COMMAND_MODE return (next_state, result) def _handler_command_get_latest_sample(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.DEBUG): log.debug("args=%s kwargs=%s" % (str(args), str(kwargs))) next_state = self._protocol_fsm.get_current_state() result = None timeout = kwargs.get('timeout', self._timeout) try: result = self._connection.get_latest_sample(timeout) except TimeoutException, e: raise InstrumentTimeoutException(msg=str(e)) except ClientException, e: log.warn("ClientException while get_latest_sample: %s" % str(e)) raise InstrumentException('ClientException: %s' % str(e))
class SingleConnectionInstrumentDriver(InstrumentDriver): """ Base class for instrument drivers with a single device connection. Provides connenction state logic for single connection drivers. This is the base class for the majority of driver implementation classes. """ 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.EXECUTE, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.FORCE_STATE, 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 InstrumentStateException 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 InstrumentStateException if command not allowed in current state @throws InstrumentParameterException 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 InstrumentStateException if command not allowed in current state @throws InstrumentConnectionException 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 InstrumentStateException 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_state(self, *args, **kwargs): """ Determine initial state upon establishing communications. @param timeout=timeout Optional command timeout. @retval Current device state. @raises InstrumentTimeoutException if could not wake device. @raises InstrumentStateException if command not allowed in current state or if device state not recognized. @raises NotImplementedException 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_resource_capabilities(self, current_state=True, *args, **kwargs): """ Return driver commands and parameters. @param current_state True to retrieve commands available in current state, otherwise reutrn all commands. @retval list of AgentCapability objects representing the drivers capabilities. @raises NotImplementedException if not implemented by subclass. """ if self._protocol: return self._protocol.get_resource_capabilities(current_state) else: return [[], []] def get_resource_state(self, *args, **kwargs): """ Return the current state of the driver. @retval str current driver state. @raises NotImplementedException if not implemented by subclass. """ connection_state = self._connection_fsm.get_current_state() if connection_state == DriverConnectionState.CONNECTED: return self._protocol.get_current_state() else: return connection_state def get_resource(self, *args, **kwargs): """ Retrieve device parameters. @param args[0] DriverParameter.ALL or a list of parameters to retrive. @retval parameter : value dict. @raises InstrumentParameterException if missing or invalid get parameters. @raises InstrumentStateException if command not allowed in current state @raises NotImplementedException 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_resource(self, *args, **kwargs): """ Set device parameters. @param args[0] parameter : value dict of parameters to set. @param timeout=timeout Optional command timeout. @raises InstrumentParameterException if missing or invalid set parameters. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if set command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException 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_resource(self, resource_cmd, *args, **kwargs): """ Poll for a sample. @param timeout=timeout Optional command timeout. @ retval Device sample dict. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if acquire command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.EXECUTE, resource_cmd, *args, **kwargs) def test_force_state(self, *args, **kwargs): """ Force driver into a given state for the purposes of unit testing @param state=desired_state Required desired state to change to. @raises InstrumentParameterException if no state parameter. @raises TestModeException if not in test mode """ if(not self._test_mode): raise TestModeException(); # Get the required param state = kwargs.get('state', None) # via kwargs if state is None: raise InstrumentParameterException('Missing state parameter.') # We are mucking with internal FSM parameters which may be bad. # The alternative was to raise an event to change the state. Dont # know which is better. self._protocol._protocol_fsm.current_state = 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 InstrumentParameterException if missing or invalid param dict. """ next_state = None result = None # Get the required param dict. config = kwargs.get('config', None) # via kwargs # TODO use kwargs as the only mechanism if config is None: try: config = args[0] # via first argument except IndexError: pass if config is None: raise InstrumentParameterException('Missing comms config parameter.') # Verify dict and construct connection client. self._connection = self._build_connection(config) next_state = DriverConnectionState.DISCONNECTED 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 InstrumentParameterException if missing or invalid param dict. """ next_state = None result = None # Get required config param dict. config = kwargs.get('config', None) # via kwargs # TODO use kwargs as the only mechanism if config is None: try: config = args[0] # via first argument except IndexError: pass if config is None: raise InstrumentParameterException('Missing comms config parameter.') # Verify configuration dict, and update connection if possible. self._connection = self._build_connection(config) 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 InstrumentConnectionException 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_connection(self, config): """ Constructs and returns a Connection object according to the given configuration. The connection object is a LoggerClient instance in this base class. Subclasses can overwrite this operation as needed. The value returned by this operation is assigned to self._connection and also to self._protocol._connection upon entering in the DriverConnectionState.CONNECTED state. @param config configuration dict @retval a Connection instance, which will be assigned to self._connection @throws InstrumentParameterException Invalid configuration. """ if 'mock_port_agent' in config: mock_port_agent = config['mock_port_agent'] # check for validity here... if (mock_port_agent is not None): return mock_port_agent try: addr = config['addr'] port = config['port'] if isinstance(addr, str) and isinstance(port, int) and len(addr)>0: #return LoggerClient(addr, port) return PortAgentClient(addr, port) else: raise InstrumentParameterException('Invalid comms config dict.') except (TypeError, KeyError): raise InstrumentParameterException('Invalid comms config dict.') def _build_protocol(self): """ Construct device specific single connection protocol FSM. Overridden in device specific subclasses. """ pass
class SingleConnectionInstrumentDriver(InstrumentDriver): """ Base class for instrument drivers with a single device connection. Provides connenction state logic for single connection drivers. This is the base class for the majority of driver implementation classes. """ 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.EXECUTE, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.FORCE_STATE, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.START_DIRECT, self._handler_connected_start_direct_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.STOP_DIRECT, self._handler_connected_stop_direct_event) # Start state machine. self._connection_fsm.start(DriverConnectionState.UNCONFIGURED) self._pre_da_config = {} self._startup_config = {} ############################################################# # Device connection interface. ############################################################# def initialize(self, *args, **kwargs): """ Initialize driver connection, bringing communications parameters into unconfigured state (no connection object). @raises InstrumentStateException 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 InstrumentStateException if command not allowed in current state @throws InstrumentParameterException 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 InstrumentStateException if command not allowed in current state @throws InstrumentConnectionException if the connection failed. """ # Forward event and argument to the connection FSM. result = self._connection_fsm.on_event(DriverEvent.CONNECT, *args, **kwargs) init_config = {} if len(args) > 0 and isinstance(args[0], dict): init_config = args[0] self.set_init_params(init_config) return result def disconnect(self, *args, **kwargs): """ Disconnect from device via port agent / logger. @raises InstrumentStateException 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) ############################################################# # Configuration logic ############################################################# def get_init_params(self): """ get the driver initialization parameters @return driver configuration dictionary """ return self._startup_config def set_init_params(self, config): """ Set the initialization parameters down in the protocol and store the driver configuration in the driver. If the protocol hasn't been setup yet cache the config. Next time this method is called, if you call it with an empty config it will read from the cache. @param config This default configuration assumes a structure driver configuration dict with keys named in DriverConfigKey. Stranger parameters can be adjusted by over riding this method. @raise InstrumentParameterException If the config cannot be applied """ if not isinstance(config, dict): raise InstrumentParameterException("Incompatible initialization parameters") if(self._protocol): param_config = None if(len(config)): param_config = config elif(len(self._startup_config)): param_config = self._startup_config if(param_config): self._protocol.set_init_params(param_config) self._protocol.initialize_scheduler() self._startup_config = config def apply_startup_params(self): """ Apply the startup values previously stored in the protocol to the running config of the live instrument. The startup values are the values that are (1) marked as startup parameters and are (2) the "best" value to use at startup. Preference is given to the previously-set init value, then the default value, then the currently used value. This default implementation simply pushes the logic down into the protocol for processing should the action be better accomplished down there. The driver writer can decide to overload this method in the derived driver class and apply startup parameters in the driver (likely calling some get and set methods for the resource). If the driver does not implement an apply_startup_params() method in the driver, this method will call into the protocol. Deriving protocol classes are expected to implement an apply_startup_params() method lest they get the exception from the base InstrumentProtocol implementation. """ log.debug("Base driver applying startup params...") self._protocol.apply_startup_params() def get_cached_config(self): """ Return the configuration object that shows the instrument's configuration as cached in the protocol parameter dictionary. @retval The running configuration in the instruments config format. By default, it is a dictionary of parameter names and values. """ if self._protocol: return self._protocol.get_cached_config() def restore_direct_access_params(self, config): """ Restore the correct values out of the full config that is given when returning from direct access. By default, this takes a simple dict of param name and value. Override this class as needed as it makes some simple assumptions about how your instrument sets things. @param config The configuration that was previously saved (presumably to disk somewhere by the driver that is working with this protocol) """ vals = {} # for each parameter that is read only, restore da_params = self._protocol.get_direct_access_params() for param in da_params: vals[param] = config[param] self.set_resource(vals) ############################################################# # Commande and control interface. ############################################################# def discover_state(self, *args, **kwargs): """ Determine initial state upon establishing communications. @param timeout=timeout Optional command timeout. @retval Current device state. @raises InstrumentTimeoutException if could not wake device. @raises InstrumentStateException if command not allowed in current state or if device state not recognized. @raises NotImplementedException 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_resource_capabilities(self, current_state=True, *args, **kwargs): """ Return driver commands and parameters. @param current_state True to retrieve commands available in current state, otherwise reutrn all commands. @retval list of AgentCapability objects representing the drivers capabilities. @raises NotImplementedException if not implemented by subclass. """ if self._protocol: return self._protocol.get_resource_capabilities(current_state) else: return [[], []] def get_resource_state(self, *args, **kwargs): """ Return the current state of the driver. @retval str current driver state. @raises NotImplementedException if not implemented by subclass. """ connection_state = self._connection_fsm.get_current_state() if connection_state == DriverConnectionState.CONNECTED: return self._protocol.get_current_state() else: return connection_state def get_resource(self, *args, **kwargs): """ Retrieve device parameters. @param args[0] DriverParameter.ALL or a list of parameters to retrive. @retval parameter : value dict. @raises InstrumentParameterException if missing or invalid get parameters. @raises InstrumentStateException if command not allowed in current state @raises NotImplementedException 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_resource(self, *args, **kwargs): """ Set device parameters. @param args[0] parameter : value dict of parameters to set. @param timeout=timeout Optional command timeout. @raises InstrumentParameterException if missing or invalid set parameters. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if set command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException 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_resource(self, resource_cmd, *args, **kwargs): """ Poll for a sample. @param timeout=timeout Optional command timeout. @ retval Device sample dict. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if acquire command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. if resource_cmd == DriverEvent.START_DIRECT: return self._connection_fsm.on_event(DriverEvent.START_DIRECT, resource_cmd, *args, **kwargs) elif resource_cmd == DriverEvent.STOP_DIRECT: return self._connection_fsm.on_event(DriverEvent.STOP_DIRECT, resource_cmd, *args, **kwargs) else: return self._connection_fsm.on_event(DriverEvent.EXECUTE, resource_cmd, *args, **kwargs) def test_force_state(self, *args, **kwargs): """ Force driver into a given state for the purposes of unit testing @param state=desired_state Required desired state to change to. @raises InstrumentParameterException if no state parameter. @raises TestModeException if not in test mode """ if(not self._test_mode): raise TestModeException(); # Get the required param state = kwargs.get('state', None) # via kwargs if state is None: raise InstrumentParameterException('Missing state parameter.') # We are mucking with internal FSM parameters which may be bad. # The alternative was to raise an event to change the state. Dont # know which is better. self._protocol._protocol_fsm.current_state = 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 InstrumentParameterException if missing or invalid param dict. """ next_state = None result = None # Get the required param dict. config = kwargs.get('config', None) # via kwargs # TODO use kwargs as the only mechanism if config is None: try: config = args[0] # via first argument except IndexError: pass if config is None: raise InstrumentParameterException('Missing comms config parameter.') # Verify dict and construct connection client. self._connection = self._build_connection(config) next_state = DriverConnectionState.DISCONNECTED 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 InstrumentParameterException if missing or invalid param dict. """ next_state = None result = None # Get required config param dict. config = kwargs.get('config', None) # via kwargs # TODO use kwargs as the only mechanism if config is None: try: config = args[0] # via first argument except IndexError: pass if config is None: raise InstrumentParameterException('Missing comms config parameter.') # Verify configuration dict, and update connection if possible. self._connection = self._build_connection(config) 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 InstrumentConnectionException 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) def _handler_connected_start_direct_event(self, event, *args, **kwargs): """ Stash the current config first, then 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 self._pre_da_config = self.get_resource(DriverParameter.ALL) result = self._protocol._protocol_fsm.on_event(event, *args, **kwargs) return (next_state, result) def _handler_connected_stop_direct_event(self, event, *args, **kwargs): """ Restore previous config first, then 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) self.restore_direct_access_params(self._pre_da_config) return (next_state, result) ######################################################################## # Helpers. ######################################################################## def _build_connection(self, config): """ Constructs and returns a Connection object according to the given configuration. The connection object is a LoggerClient instance in this base class. Subclasses can overwrite this operation as needed. The value returned by this operation is assigned to self._connection and also to self._protocol._connection upon entering in the DriverConnectionState.CONNECTED state. @param config configuration dict @retval a Connection instance, which will be assigned to self._connection @throws InstrumentParameterException Invalid configuration. """ if 'mock_port_agent' in config: mock_port_agent = config['mock_port_agent'] # check for validity here... if (mock_port_agent is not None): return mock_port_agent try: addr = config['addr'] port = config['port'] cmd_port = config.get('cmd_port') if isinstance(addr, str) and isinstance(port, int) and len(addr)>0: return PortAgentClient(addr, port, cmd_port) else: raise InstrumentParameterException('Invalid comms config dict.') except (TypeError, KeyError): raise InstrumentParameterException('Invalid comms config dict.') def _build_protocol(self): """ Construct device specific single connection protocol FSM. Overridden in device specific subclasses. """ pass
class Protocol(CommandResponseInstrumentProtocol): """ Instrument protocol class Subclasses CommandResponseInstrumentProtocol """ def __init__(self, prompts, newline, driver_event): """ Protocol constructor. @param prompts A BaseEnum class containing instrument prompts. @param newline The newline. @param driver_event Driver process event callback. """ # Construct protocol superclass. CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event) # Build protocol state machine. self._protocol_fsm = InstrumentFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT) # Add event handlers for protocol state machine. self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.ENTER, self._handler_unknown_enter) self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.EXIT, self._handler_unknown_exit) self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.DISCOVER, self._handler_unknown_discover) self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.START_DIRECT, self._handler_command_start_direct) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.EXIT, self._handler_command_exit) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.GET, self._handler_get) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_AUTOSAMPLE, self._handler_command_autosample_start) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.INIT_PARAMS, self._handler_command_init_params) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.ENTER, self._handler_autosample_enter) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.EXIT, self._handler_autosample_exit) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop) self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.ENTER, self._handler_direct_access_enter) self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.EXIT, self._handler_direct_access_exit) self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct) self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct) # Construct the parameter dictionary containing device parameters, # current parameter values, and set formatting functions. self._build_param_dict() # Add build handlers for device commands. # Add response handlers for device commands. # Add sample handlers. # State state machine in UNKNOWN state. self._protocol_fsm.start(ProtocolState.UNKNOWN) # commands sent sent to device to be filtered in responses for telnet DA self._sent_cmds = [] # self._chunker = StringChunker(Protocol.sieve_function) self._payload_cache = {} @staticmethod def sieve_function(raw_data): """ The method that splits samples """ return_list = [] return return_list def _build_param_dict(self): """ Populate the parameter dictionary with parameters. For each parameter key, add match stirng, match lambda function, and value formatting function for set commands. """ # Add parameter handlers to parameter dict. self._param_dict.add_parameter( Parameter(ParameterName.PAYLOAD_SIZE, int, type=ParameterDictType.INT, display_name="Payload Size", startup_param = True, direct_access = True, default_value = 1024) ) self._param_dict.add_parameter( Parameter(ParameterName.SAMPLE_INTERVAL, int, type=ParameterDictType.INT, display_name="Sample Interval (sec)", startup_param = True, direct_access = True, default_value = 1) ) def _got_chunk(self, chunk): """ The base class got_data has gotten a chunk from the chunker. Pass it to extract_sample with the appropriate particle objects and REGEXes. """ def _filter_capabilities(self, events): """ Return a list of currently available capabilities. """ return [x for x in events if Capability.has(x)] ######################################################################## # 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 @retval (next_state, result) """ return (ProtocolState.COMMAND, ResourceAgentState.IDLE) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentProtocolException if the update commands and not recognized. """ self._protocol_fsm.on_event(DriverEvent.INIT_PARAMS) # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_command_set(self, *args, **kwargs): """ Set parameter """ next_state = None result = None self._set_params(*args, **kwargs) log.debug("_handler_command_set: result: %s", result) return (next_state, result) def _handler_command_exit(self, *args, **kwargs): """ Exit command state. """ pass def _handler_command_start_direct(self): """ Start direct access """ next_state = ProtocolState.DIRECT_ACCESS next_agent_state = ResourceAgentState.DIRECT_ACCESS result = None log.debug("_handler_command_start_direct: entering DA mode") return (next_state, (next_agent_state, result)) def _handler_command_autosample_start(self, *args, **kwargs): next_state = ProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.STREAMING result = None return (next_state, (next_agent_state, result)) def _handler_command_init_params(self, *args, **kwargs): """ initialize parameters """ next_state = None result = None self._init_params() 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) self._start_packet_generator() def _handler_autosample_exit(self, *args, **kwargs): """ Exit autosample state. """ self._stop_packet_generator() def _handler_autosample_stop(self, *args, **kwargs): """ Stop autosample and switch back to command mode. @retval (next_state, result) tuple, (ProtocolState.COMMAND, (next_agent_state, None) if successful. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command misunderstood or incorrect prompt received. """ next_state = None result = None next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND return (next_state, (next_agent_state, result)) ######################################################################## # Direct access handlers. ######################################################################## def _handler_direct_access_enter(self, *args, **kwargs): """ Enter direct access state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) self._sent_cmds = [] def _handler_direct_access_exit(self, *args, **kwargs): """ Exit direct access state. """ pass def _handler_direct_access_execute_direct(self, data): """ """ next_state = None result = None next_agent_state = None self._do_cmd_direct(data) # add sent command to list for 'echo' filtering in callback self._sent_cmds.append(data) return (next_state, (next_agent_state, result)) def _handler_direct_access_stop_direct(self): """ @throw InstrumentProtocolException on invalid command """ next_state = None result = None next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND return (next_state, (next_agent_state, result)) ######################################################################## # Helpers ######################################################################## def _start_packet_generator(self): packet_size = self._param_dict.get(ParameterName.PAYLOAD_SIZE) sample_interval = self._param_dict.get(ParameterName.SAMPLE_INTERVAL) self._generate_payload_value(packet_size) self._stop_generator_thread = False self._generator_thread = Thread( target=self._generate_packets, args=(packet_size, sample_interval, self._publish_packet )) self._generator_thread.start() def _generate_packets(self, *args, **kwargs): packet_size = args[0] sample_interval = args[1] publish_callback = args[2] log.debug("_generate_packets, starting packet generator. packet_size: %s, sample_interval: %s", packet_size, sample_interval) while(self._stop_generator_thread != True): publish_callback(packet_size) time.sleep(sample_interval) log.debug("_generate_packets, stopping packet generator") def _publish_packet(self, packet_size): buf = self._get_payload_value(packet_size) particle = TestDataParticle(buf, port_timestamp=time_to_ntp_date_time()) log.debug("_publish_packet, packet size: %d", len(buf)) self._driver_event(DriverAsyncEvent.SAMPLE, particle.generate()) def _get_payload_value(self, packet_size): if self._payload_cache.get(packet_size): return self._payload_cache[packet_size] return self._generate_payload_value(packet_size) def _generate_payload_value(self, packet_size): log.debug("generating new value, packet size: %s", packet_size) charlist = [random.choice(string.letters) for _ in range(packet_size)] buf = struct.pack('%sc' % len(charlist), *charlist) self._payload_cache[packet_size] = buf return buf def _stop_packet_generator(self): log.debug("_stop_packet_generator: Signal the packet generator to stop") self._stop_generator_thread = True self._generator_thread.join(60) def _set_params(self, *args, **kwargs): """ Issue commands to the instrument to set various parameters """ startup = False config_change = False result = {} try: params = args[0] except IndexError: raise InstrumentParameterException('Set command requires a parameter dict.') try: startup = args[1] except IndexError: pass for (key, val) in params.iteritems(): log.debug("KEY = " + str(key) + " VALUE = " + str(val)) if self._param_dict.get(key) != val: config_change = True self._param_dict.set_value(key, val) result[key] = val if config_change: self._driver_event(DriverAsyncEvent.CONFIG_CHANGE) return result
class Protocol(CommandResponseInstrumentProtocol): """ Instrument protocol class Subclasses CommandResponseInstrumentProtocol """ def __init__(self, prompts, newline, driver_event): """ Protocol constructor. @param prompts A BaseEnum class containing instrument prompts. @param newline The newline. @param driver_event Driver process event callback. """ # Construct protocol superclass. CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event) # Build protocol state machine. self._protocol_fsm = InstrumentFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT) # Add event handlers for protocol state machine. self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.ENTER, self._handler_unknown_enter) self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.EXIT, self._handler_unknown_exit) self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.DISCOVER, self._handler_unknown_discover) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.ENTER, self._handler_autosample_enter) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.EXIT, self._handler_autosample_exit) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.DUMP_01, self._handler_command_autosample_dump01) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.DUMP_02, self._handler_command_autosample_dump02) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.START_LEVELING, self._handler_command_autosample_start_leveling) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.EXIT, self._handler_command_exit) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.GET, self._handler_command_get) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.DUMP_01, self._handler_command_autosample_dump01) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.DUMP_02, self._handler_command_autosample_dump02) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_LEVELING, self._handler_command_autosample_start_leveling) self._protocol_fsm.add_handler(ProtocolState.LEVELING, ProtocolEvent.STOP_LEVELING, self._handler_leveling_stop_leveling) self._protocol_fsm.add_handler(ProtocolState.LEVELING, ProtocolEvent.LEVELING_COMPLETE, self._handler_leveling_complete) # 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(InstrumentCommand.DATA_ON, self._build_command) self._add_build_handler(InstrumentCommand.DATA_OFF, self._build_command) self._add_build_handler(InstrumentCommand.DUMP_SETTINGS_01, self._build_command) self._add_build_handler(InstrumentCommand.DUMP_SETTINGS_02, self._build_command) self._add_build_handler(InstrumentCommand.START_LEVELING, self._build_command) self._add_build_handler(InstrumentCommand.STOP_LEVELING, self._build_command) # Add response handlers for device commands. self._add_response_handler(InstrumentCommand.DATA_ON, self._parse_data_on_off_resp) self._add_response_handler(InstrumentCommand.DATA_OFF, self._parse_data_on_off_resp) self._add_response_handler(InstrumentCommand.DUMP_SETTINGS_01, self._parse_status_01_resp) self._add_response_handler(InstrumentCommand.DUMP_SETTINGS_02, self._parse_status_02_resp) # Add sample handlers. # State state machine in UNKNOWN state. self._protocol_fsm.start(ProtocolState.UNKNOWN) # commands sent sent to device to be filtered in responses for telnet DA self._sent_cmds = [] # self._chunker = StringChunker(Protocol.sieve_function) # set up the regexes now so we don't have to do it repeatedly self.data_regex = LILYDataParticle.regex_compiled() self.cmd_rsp_regex = LILYCommandResponse.regex_compiled() self.signon_regex = LILYStatusSignOnParticle.regex_compiled() self.status_01_regex = LILYStatus_01_Particle.regex_compiled() self.status_02_regex = LILYStatus_02_Particle.regex_compiled() self.leveling_regex = LILYLevelingParticle.regex_compiled() @staticmethod def sieve_function(raw_data): """ The method that splits samples """ matchers = [] return_list = [] """ would be nice to be able to do this. matchers.append(self.data_regex) matchers.append(self.signon_regex) matchers.append(self.status_01_regex) matchers.append(self.status_02_regex) matchers.append(self.cmd_rsp_regex) """ """ Not a good idea to be compiling these for every invocation of this method; they don't change. """ matchers.append(LILYDataParticle.regex_compiled()) #matchers.append(LILYStatusSignOnParticle.regex_compiled()) matchers.append(LILYStatus_01_Particle.regex_compiled()) matchers.append(LILYStatus_02_Particle.regex_compiled()) matchers.append(LILYCommandResponse.regex_compiled()) matchers.append(LILYLevelingParticle.regex_compiled()) for matcher in matchers: for match in matcher.finditer(raw_data): return_list.append((match.start(), match.end())) return return_list def _filter_capabilities(self, events): """ Return a list of currently available capabilities. """ events_out = [x for x in events if Capability.has(x)] return events_out def _build_cmd_dict(self): """ Populate the command dictionary with NOAA LILY Driver metadata information. Currently LILY only supports DATA_ON and DATA_OFF. """ self._cmd_dict = ProtocolCommandDict() def _build_param_dict(self): """ Populate the parameter dictionary with parameters. For each parameter key, add match stirng, match lambda function, and value formatting function for set commands. """ # Add parameter handlers to parameter dict. pass def add_to_buffer(self, data): ''' Overridden because most of the data coming to this driver isn't meant for it. I'm only adding to the buffer when a chunk arrives (see my_add_to_buffer, below), so this method does nothing. @param data: bytes to add to the buffer ''' pass def _my_add_to_buffer(self, data): """ Replaces add_to_buffer. Most data coming to this driver isn't meant for it. I'm only adding to the buffer when data meant for this driver arrives. That is accomplished using the chunker mechanism. This method would normally collet any data fragments that are then search by the get_response method in the context of a synchronous command sent from the observatory. However, because so much data arrives here that is not applicable, the add_to_buffer method has been overridden to do nothing. @param data: bytes to add to the buffer """ # Update the line and prompt buffers; first acquire mutex. promptbuf_mutex.acquire() self._linebuf += data self._promptbuf += data promptbuf_mutex.release() self._last_data_timestamp = time.time() def _got_chunk(self, chunk, timestamp): """ The base class got_data has gotten a chunk from the chunker. Invoke this driver's _my_add_to_buffer, or pass it to extract_sample with the appropriate particle objects and REGEXes. We need to invoke _my_add_to_buffer, because we've overridden the base class add_to_buffer that is called from got_data(). The reason is explained in comments in _my_add_to_buffer. """ log.debug("_got_chunk_: %s", chunk) """ If we're in leveling mode, use a different got_chunk """ if (self._protocol_fsm.get_current_state() == ProtocolState.LEVELING): log.error("~~~~~~~~~~~~~~~~~~~~~~~~~ YAHOOO!!!! LEVELING CHUNK: %s", chunk) if (self.leveling_regex.match(chunk)): self._protocol_fsm.on_event(ProtocolEvent.LEVELING_COMPLETE) return else: log.error("!!!!!!!!!!!!!!!!!!!! NOOOO!!") if (self.cmd_rsp_regex.match(chunk) \ #or self.signon_regex.match(chunk) \ # currently not using the signon chunk or self.status_01_regex.match(chunk) \ or self.status_02_regex.match(chunk)): self._my_add_to_buffer(chunk) log.error("++++++++++++++++++++++++ Adding CHUNK: %s to buffer", chunk) else: if not self._extract_sample(LILYDataParticle, self.data_regex, chunk, timestamp): raise InstrumentProtocolException("Unhandled chunk") def _build_command(self, cmd, *args, **kwargs): command = cmd + NEWLINE log.debug("_build_command: command is: %s", command) return command def _parse_data_on_off_resp(self, response, prompt): log.debug("_parse_data_on_off_resp: response: %r; prompt: %s", response, prompt) return response.lily_command_response def _parse_status_01_resp(self, response, prompt): log.debug("_parse_status_01_resp: response: %r; prompt: %s", response, prompt) return response.lily_status_response def _parse_status_02_resp(self, response, prompt): log.debug("_parse_status_02_resp: response: %r; prompt: %s", response, prompt) return response.lily_status_response def _wakeup(self, timeout, delay=1): """ Overriding _wakeup; does not apply to this instrument """ pass """ Overriding this because it clears the promptbuf with no coordination with another thread of execution that uses the same variable. """ def _do_cmd_resp(self, cmd, *args, **kwargs): """ Perform a command-response on the device. @param cmd The command to execute. @param args positional arguments to pass to the build handler. @param timeout=timeout optional wakeup and command timeout. @retval resp_result The (possibly parsed) response result. @raises InstrumentTimeoutException if the response did not occur in time. @raises InstrumentProtocolException if command could not be built or if response was not recognized. """ # Get timeout and initialize response. timeout = kwargs.get('timeout', DEFAULT_CMD_TIMEOUT) expected_prompt = kwargs.get('expected_prompt', None) write_delay = kwargs.get('write_delay', DEFAULT_WRITE_DELAY) retval = None # Get the build handler. build_handler = self._build_handlers.get(cmd, None) if not build_handler: raise InstrumentProtocolException('Cannot build command: %s' % cmd) cmd_line = build_handler(cmd, *args) # Wakeup the device, pass up exception if timeout prompt = self._wakeup(timeout) # Clear line and prompt buffers for result. self._linebuf = '' #self._promptbuf = '' # Send command. log.debug('_do_cmd_resp: %s, timeout=%s, write_delay=%s, expected_prompt=%s,' % (repr(cmd_line), timeout, write_delay, expected_prompt)) if (write_delay == 0): self._connection.send(cmd_line) else: for char in cmd_line: self._connection.send(char) time.sleep(write_delay) # Wait for the prompt, prepare result and return, timeout exception (prompt, result) = self._get_response(timeout, expected_prompt=expected_prompt) resp_handler = self._response_handlers.get((self.get_current_state(), cmd), None) or \ self._response_handlers.get(cmd, None) resp_result = None if resp_handler: resp_result = resp_handler(result, prompt) return resp_result def _get_response(self, timeout=30, expected_prompt=None): """ Overriding _get_response: this one uses regex on chunks that have already been filtered by the chunker. An improvement to the chunker could be metadata labeling the chunk so that we don't have to do another match, although I don't think it is that expensive once the chunk has been pulled out to match again @param timeout The timeout in seconds @param expected_prompt Only consider the specific expected prompt as presented by this string @throw InstrumentProtocolExecption on timeout """ # Grab time for timeout and wait for response starttime = time.time() response = None log.error("!!!!!!!! DHE Timeout is: %d", timeout) """ Spin around for <timeout> looking for the response to arrive """ continuing = True response = "no response" while continuing: if self.cmd_rsp_regex.match(self._promptbuf): response = LILYCommandResponse(self._promptbuf) log.debug("_get_response() matched CommandResponse. _promptbuf: %s", self._promptbuf) response.check_command_response(expected_prompt) continuing = False #elif self.signon_regex.match(self._promptbuf): #response = LILYStatusSignOnParticle(self._promptbuf) #log.debug("~~~~~~~~~ SignonResponse") """ Currently not using the signon particle. """ elif self.status_01_regex.match(self._promptbuf): response = LILYStatus_01_Particle(self._promptbuf) log.debug("_get_response() matched Status_01_Response") response.build_response() continuing = False elif self.status_02_regex.match(self._promptbuf): response = LILYStatus_02_Particle(self._promptbuf) log.debug("_get_response() matched Status_02_Response") response.build_response() continuing = False else: log.error("DHE TEMPTEMP no match: promptbuf: %s", self._promptbuf) """ TODO: moved clearing of promptbuf to after everything. """ #self._promptbuf = '' #time.sleep(.1) time.sleep(.5) if timeout and time.time() > starttime + timeout: log.error("TIMEOUT IN GET RESPONSE! LOOKING FOR %r in %s", expected_prompt, self._promptbuf) raise InstrumentTimeoutException("in BOTPT LILY driver._get_response()") """ Clear the promptbuf here; first acquire mutex """ promptbuf_mutex.acquire() self._promptbuf = '' promptbuf_mutex.release() return ('LILY_RESPONSE', response) ######################################################################## # 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 @retval (next_state, result) """ return (ProtocolState.COMMAND, ResourceAgentState.IDLE) ######################################################################## # 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 command state. """ pass def _handler_autosample_stop_autosample(self): """ Turn the lily data off """ next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND result = self._do_cmd_resp(InstrumentCommand.DATA_OFF, expected_prompt = LILY_DATA_OFF) return (next_state, (next_agent_state, result)) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentProtocolException 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_get(self, *args, **kwargs): """ Get parameter """ next_state = None result = {} return (next_state, result) def _handler_command_set(self, *args, **kwargs): """ Set parameter """ next_state = None result = None params = args[0] return (next_state, result) def _handler_command_start_autosample(self, *args, **kwargs): """ Turn the lily data on """ next_state = ProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.STREAMING """ call _do_cmd_resp, passing our LILY_DATA_ON as the expected_prompt """ result = self._do_cmd_resp(InstrumentCommand.DATA_ON, expected_prompt = LILY_DATA_ON) return (next_state, (next_agent_state, result)) def _handler_command_exit(self, *args, **kwargs): """ Exit command state. """ pass ######################################################################## # Leveling Handlers ######################################################################## def _handler_leveling_stop_leveling(self, *args, **kwargs): """ Take instrument out of leveling mode; according to the LILY Commands document, we need to turn data on automatically (corresponds to AUTOSAMPLE). """ next_state = ProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.STREAMING result = None log.debug("_handler_leveling_stop_leveling") result = self._do_cmd_resp(InstrumentCommand.STOP_LEVELING, expected_prompt = LILY_LEVEL_OFF) log.debug("STOP_LEVELING response: %s", result) log.debug("Turning LILY data on and transitioning to AUTOSAMPLE.") result = self._do_cmd_resp(InstrumentCommand.DATA_ON, expected_prompt = LILY_DATA_ON) log.debug("DATA_ON response: %s", result) return (next_state, (next_agent_state, result)) def _handler_leveling_complete(self, *args, **kwargs): """ Leveling is complete. According to LILY Commands document, we need to turn data on automatically (enter AUTOSAMPLE) @retval (next_state, result) tuple, (None, sample dict). """ next_state = ProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.STREAMING result = None log.debug("LILY reports leveling complete: sending STOP LEVELING.") result = self._do_cmd_resp(InstrumentCommand.STOP_LEVELING, expected_prompt = LILY_LEVEL_OFF) log.debug("Turning LILY data on, transitioning to AUTOSAMPLE.") result = self._do_cmd_resp(InstrumentCommand.DATA_ON, expected_prompt = LILY_DATA_ON) #result = self._do_cmd_no_resp(InstrumentCommand.DATA_ON) #log.debug("DATA_ON response: %s", result) self._async_agent_state_change(ResourceAgentState.STREAMING) return (next_state, next_agent_state) ######################################################################## # Handlers common to Command and Autosample States. ######################################################################## def _handler_command_autosample_dump01(self, *args, **kwargs): """ Get device status """ next_state = None next_agent_state = None result = None log.debug("_handler_command_autosample_dump01") timeout = kwargs.get('timeout') if timeout is not None: result = self._do_cmd_resp(InstrumentCommand.DUMP_SETTINGS_01, timeout = timeout) else: result = self._do_cmd_resp(InstrumentCommand.DUMP_SETTINGS_01) log.debug("DUMP_SETTINGS_01 response: %s", result) return (next_state, (next_agent_state, result)) def _handler_command_autosample_dump02(self, *args, **kwargs): """ Get device status """ next_state = None next_agent_state = None result = None log.debug("_handler_command_autosample_dump02") result = self._do_cmd_resp(InstrumentCommand.DUMP_SETTINGS_02) log.debug("DUMP_SETTINGS_02 response: %s", result) return (next_state, (next_agent_state, result)) def _handler_command_autosample_start_leveling(self, *args, **kwargs): """ Put instrument into leveling mode """ next_state = ProtocolState.LEVELING next_agent_state = ResourceAgentState.CALIBRATE result = None log.debug("_handler_command_autosample_start_leveling") result = self._do_cmd_resp(InstrumentCommand.START_LEVELING, expected_prompt = LILY_LEVEL_ON) log.debug("START_LEVELING response: %s", result) return (next_state, (next_agent_state, result))
class Protocol(CommandResponseInstrumentProtocol): """ Instrument protocol class Subclasses CommandResponseInstrumentProtocol """ def __init__(self, prompts, newline, driver_event): """ Protocol constructor. @param prompts A BaseEnum class containing instrument prompts. @param newline The newline. @param driver_event Driver process event callback. """ # Construct protocol superclass. CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event) # Build protocol state machine. self._protocol_fsm = InstrumentFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT) # Add event handlers for protocol state machine. self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.ENTER, self._handler_unknown_enter) self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.EXIT, self._handler_unknown_exit) self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.DISCOVER, self._handler_unknown_discover) self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.START_DIRECT, self._handler_command_start_direct) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.EXIT, self._handler_command_exit) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.GET, self._handler_get) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_AUTOSAMPLE, self._handler_command_autosample_start) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.INIT_PARAMS, self._handler_command_init_params) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.ENTER, self._handler_autosample_enter) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.EXIT, self._handler_autosample_exit) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop) self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.ENTER, self._handler_direct_access_enter) self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.EXIT, self._handler_direct_access_exit) self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct) self._protocol_fsm.add_handler( ProtocolState.DIRECT_ACCESS, ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct) # Construct the parameter dictionary containing device parameters, # current parameter values, and set formatting functions. self._build_param_dict() # Add build handlers for device commands. # Add response handlers for device commands. # Add sample handlers. # State state machine in UNKNOWN state. self._protocol_fsm.start(ProtocolState.UNKNOWN) # commands sent sent to device to be filtered in responses for telnet DA self._sent_cmds = [] # self._chunker = StringChunker(Protocol.sieve_function) self._payload_cache = {} @staticmethod def sieve_function(raw_data): """ The method that splits samples """ return_list = [] return return_list def _build_param_dict(self): """ Populate the parameter dictionary with parameters. For each parameter key, add match stirng, match lambda function, and value formatting function for set commands. """ # Add parameter handlers to parameter dict. self._param_dict.add_parameter( Parameter(ParameterName.PAYLOAD_SIZE, int, type=ParameterDictType.INT, display_name="Payload Size", startup_param=True, direct_access=True, default_value=1024)) self._param_dict.add_parameter( Parameter(ParameterName.SAMPLE_INTERVAL, int, type=ParameterDictType.INT, display_name="Sample Interval (sec)", startup_param=True, direct_access=True, default_value=1)) def _got_chunk(self, chunk): """ The base class got_data has gotten a chunk from the chunker. Pass it to extract_sample with the appropriate particle objects and REGEXes. """ def _filter_capabilities(self, events): """ Return a list of currently available capabilities. """ return [x for x in events if Capability.has(x)] ######################################################################## # 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 @retval (next_state, result) """ return (ProtocolState.COMMAND, ResourceAgentState.IDLE) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentProtocolException if the update commands and not recognized. """ self._protocol_fsm.on_event(DriverEvent.INIT_PARAMS) # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_command_set(self, *args, **kwargs): """ Set parameter """ next_state = None result = None self._set_params(*args, **kwargs) log.debug("_handler_command_set: result: %s", result) return (next_state, result) def _handler_command_exit(self, *args, **kwargs): """ Exit command state. """ pass def _handler_command_start_direct(self): """ Start direct access """ next_state = ProtocolState.DIRECT_ACCESS next_agent_state = ResourceAgentState.DIRECT_ACCESS result = None log.debug("_handler_command_start_direct: entering DA mode") return (next_state, (next_agent_state, result)) def _handler_command_autosample_start(self, *args, **kwargs): next_state = ProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.STREAMING result = None return (next_state, (next_agent_state, result)) def _handler_command_init_params(self, *args, **kwargs): """ initialize parameters """ next_state = None result = None self._init_params() 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) self._start_packet_generator() def _handler_autosample_exit(self, *args, **kwargs): """ Exit autosample state. """ self._stop_packet_generator() def _handler_autosample_stop(self, *args, **kwargs): """ Stop autosample and switch back to command mode. @retval (next_state, result) tuple, (ProtocolState.COMMAND, (next_agent_state, None) if successful. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command misunderstood or incorrect prompt received. """ next_state = None result = None next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND return (next_state, (next_agent_state, result)) ######################################################################## # Direct access handlers. ######################################################################## def _handler_direct_access_enter(self, *args, **kwargs): """ Enter direct access state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) self._sent_cmds = [] def _handler_direct_access_exit(self, *args, **kwargs): """ Exit direct access state. """ pass def _handler_direct_access_execute_direct(self, data): """ """ next_state = None result = None next_agent_state = None self._do_cmd_direct(data) # add sent command to list for 'echo' filtering in callback self._sent_cmds.append(data) return (next_state, (next_agent_state, result)) def _handler_direct_access_stop_direct(self): """ @throw InstrumentProtocolException on invalid command """ next_state = None result = None next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND return (next_state, (next_agent_state, result)) ######################################################################## # Helpers ######################################################################## def _start_packet_generator(self): packet_size = self._param_dict.get(ParameterName.PAYLOAD_SIZE) sample_interval = self._param_dict.get(ParameterName.SAMPLE_INTERVAL) self._generate_payload_value(packet_size) self._stop_generator_thread = False self._generator_thread = Thread(target=self._generate_packets, args=(packet_size, sample_interval, self._publish_packet)) self._generator_thread.start() def _generate_packets(self, *args, **kwargs): packet_size = args[0] sample_interval = args[1] publish_callback = args[2] log.debug( "_generate_packets, starting packet generator. packet_size: %s, sample_interval: %s", packet_size, sample_interval) while (self._stop_generator_thread != True): publish_callback(packet_size) time.sleep(sample_interval) log.debug("_generate_packets, stopping packet generator") def _publish_packet(self, packet_size): buf = self._get_payload_value(packet_size) particle = TestDataParticle( buf, port_timestamp=mi.core.time.time_to_ntp_date_time()) log.debug("_publish_packet, packet size: %d", len(buf)) self._driver_event(DriverAsyncEvent.SAMPLE, particle.generate()) def _get_payload_value(self, packet_size): if self._payload_cache.get(packet_size): return self._payload_cache[packet_size] return self._generate_payload_value(packet_size) def _generate_payload_value(self, packet_size): log.debug("generating new value, packet size: %s", packet_size) charlist = [random.choice(string.letters) for _ in range(packet_size)] buf = struct.pack('%sc' % len(charlist), *charlist) self._payload_cache[packet_size] = buf return buf def _stop_packet_generator(self): log.debug( "_stop_packet_generator: Signal the packet generator to stop") self._stop_generator_thread = True self._generator_thread.join(60) def _set_params(self, *args, **kwargs): """ Issue commands to the instrument to set various parameters """ startup = False config_change = False result = {} try: params = args[0] except IndexError: raise InstrumentParameterException( 'Set command requires a parameter dict.') try: startup = args[1] except IndexError: pass for (key, val) in params.iteritems(): log.debug("KEY = " + str(key) + " VALUE = " + str(val)) if self._param_dict.get(key) != val: config_change = True self._param_dict.set_value(key, val) result[key] = val if config_change: self._driver_event(DriverAsyncEvent.CONFIG_CHANGE) return result
class SingleConnectionInstrumentDriver(InstrumentDriver): """ Base class for instrument drivers with a single device connection. Provides connenction state logic for single connection drivers. This is the base class for the majority of driver implementation classes. """ 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 InstrumentStateException 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 InstrumentStateException if command not allowed in current state @throws InstrumentParameterException 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 InstrumentStateException if command not allowed in current state @throws InstrumentConnectionException 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 InstrumentStateException 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 InstrumentTimeoutException if could not wake device. @raises InstrumentStateException if command not allowed in current state or if device state not recognized. @raises NotImplementedException 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 InstrumentParameterException if missing or invalid get parameters. @raises InstrumentStateException if command not allowed in current state @raises NotImplementedException 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 InstrumentParameterException if missing or invalid set parameters. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if set command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException 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. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if acquire command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException 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. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException 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. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if stop command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException 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). @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if test commands not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException 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). @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if test commands not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException 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 InstrumentParameterException if missing or invalid param dict. """ next_state = None result = None # Get the required param dict. config = kwargs.get("config", None) # via kwargs # TODO use kwargs as the only mechanism if config is None: try: config = args[0] # via first argument except IndexError: pass if config is None: raise InstrumentParameterException("Missing comms config parameter.") # Verify dict and construct connection client. self._connection = self._build_connection(config) next_state = DriverConnectionState.DISCONNECTED 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 InstrumentParameterException if missing or invalid param dict. """ next_state = None result = None # Get required config param dict. config = kwargs.get("config", None) # via kwargs # TODO use kwargs as the only mechanism if config is None: try: config = args[0] # via first argument except IndexError: pass if config is None: raise InstrumentParameterException("Missing comms config parameter.") # Verify configuration dict, and update connection if possible. self._connection = self._build_connection(config) 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 InstrumentConnectionException 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_connection(self, config): """ Constructs and returns a Connection object according to the given configuration. The connection object is a LoggerClient instance in this base class. Subclasses can overwrite this operation as needed. The value returned by this operation is assigned to self._connection and also to self._protocol._connection upon entering in the DriverConnectionState.CONNECTED state. @param config configuration dict @retval a Connection instance, which will be assigned to self._connection @throws InstrumentParameterException Invalid configuration. """ try: addr = config["addr"] port = config["port"] if isinstance(addr, str) and isinstance(port, int) and len(addr) > 0: return LoggerClient(addr, port) else: raise InstrumentParameterException("Invalid comms config dict.") except (TypeError, KeyError): raise InstrumentParameterException("Invalid comms config dict.") 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. Note protocol state machine must be called "self._protocol_fsm" @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, Prompt, EOLN, callback) self.write_delay = WRITE_DELAY self._last_data_timestamp = None self.eoln = EOLN self._protocol_fsm = InstrumentFSM( PARProtocolState, PARProtocolEvent, PARProtocolEvent.ENTER, PARProtocolEvent.EXIT ) self._protocol_fsm.add_handler(PARProtocolState.UNKNOWN, PARProtocolEvent.ENTER, self._handler_unknown_enter) self._protocol_fsm.add_handler( PARProtocolState.UNKNOWN, PARProtocolEvent.DISCOVER, self._handler_unknown_discover ) self._protocol_fsm.add_handler(PARProtocolState.COMMAND, PARProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler(PARProtocolState.COMMAND, PARProtocolEvent.GET, self._handler_command_get) self._protocol_fsm.add_handler(PARProtocolState.COMMAND, PARProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler( PARProtocolState.COMMAND, PARProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample ) self._protocol_fsm.add_handler( PARProtocolState.COMMAND, PARProtocolEvent.START_POLL, self._handler_command_start_poll ) self._protocol_fsm.add_handler( PARProtocolState.COMMAND, PARProtocolEvent.START_DIRECT, self._handler_command_start_direct ) self._protocol_fsm.add_handler( PARProtocolState.AUTOSAMPLE, PARProtocolEvent.ENTER, self._handler_autosample_enter ) self._protocol_fsm.add_handler( PARProtocolState.AUTOSAMPLE, PARProtocolEvent.START_POLL, self._handler_autosample_start_poll ) self._protocol_fsm.add_handler( PARProtocolState.AUTOSAMPLE, PARProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample ) self._protocol_fsm.add_handler( PARProtocolState.AUTOSAMPLE, PARProtocolEvent.RESET, self._handler_autosample_reset ) self._protocol_fsm.add_handler(PARProtocolState.POLL, PARProtocolEvent.ENTER, self._handler_poll_enter) self._protocol_fsm.add_handler( PARProtocolState.POLL, PARProtocolEvent.START_AUTOSAMPLE, self._handler_poll_start_autosample ) self._protocol_fsm.add_handler(PARProtocolState.POLL, PARProtocolEvent.STOP_POLL, self._handler_poll_stop_poll) self._protocol_fsm.add_handler( PARProtocolState.POLL, PARProtocolEvent.ACQUIRE_SAMPLE, self._handler_poll_acquire_sample ) self._protocol_fsm.add_handler(PARProtocolState.POLL, PARProtocolEvent.RESET, self._handler_poll_reset) self._protocol_fsm.add_handler( PARProtocolState.DIRECT_ACCESS, PARProtocolEvent.ENTER, self._handler_direct_access_enter ) self._protocol_fsm.add_handler( PARProtocolState.DIRECT_ACCESS, PARProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct ) self._protocol_fsm.add_handler( PARProtocolState.DIRECT_ACCESS, PARProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct ) self._protocol_fsm.start(PARProtocolState.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.SWITCH_TO_AUTOSAMPLE, self._build_control_command) self._add_build_handler(Command.RESET, self._build_control_command) self._add_build_handler(Command.BREAK, self._build_multi_control_command) self._add_build_handler(Command.SAMPLE, self._build_control_command) self._add_build_handler(Command.SWITCH_TO_POLL, self._build_control_command) self._add_response_handler(Command.GET, self._parse_get_response) self._add_response_handler(Command.SET, self._parse_set_response) self._add_response_handler(Command.SWITCH_TO_POLL, self._parse_silent_response) self._add_response_handler(Command.SAMPLE, self._parse_sample_poll_response, PARProtocolState.POLL) self._add_response_handler(Command.SAMPLE, self._parse_cmd_prompt_response, PARProtocolState.COMMAND) self._add_response_handler(Command.BREAK, self._parse_silent_response, PARProtocolState.COMMAND) self._add_response_handler(Command.BREAK, self._parse_header_response, PARProtocolState.POLL) self._add_response_handler(Command.BREAK, self._parse_header_response, PARProtocolState.AUTOSAMPLE) self._add_response_handler(Command.RESET, self._parse_silent_response, PARProtocolState.COMMAND) self._add_response_handler(Command.RESET, self._parse_reset_response, PARProtocolState.POLL) self._add_response_handler(Command.RESET, self._parse_reset_response, PARProtocolState.AUTOSAMPLE) self._param_dict.add( Parameter.MAXRATE, r"Maximum Frame Rate:\s+(\d+) Hz", lambda match: int(match.group(1)), self._int_to_string ) def _filter_capabilities(self, events): """ """ events_out = [x for x in events if PARCapability.has(x)] return events_out def get_config(self, *args, **kwargs): """ 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 """ config = self._protocol_fsm.on_event(PARProtocolEvent.GET, [Parameter.MAXRATE], **kwargs) assert isinstance(config, dict) assert config.has_key(Parameter.MAXRATE) # Make sure we get these # TODO: endless loops seem like really bad idea while config[Parameter.MAXRATE] == InstErrorCode.HARDWARE_ERROR: config[Parameter.MAXRATE] = self._protocol_fsm.on_event(PARProtocolEvent.GET, [Parameter.MAXRATE]) return config def _do_cmd_no_resp(self, cmd, *args, **kwargs): """ Issue a command to the instrument after clearing of buffers. No response is handled as a result of the command. @param cmd The command to execute. @param args positional arguments to pass to the build handler. @param timeout=timeout optional wakeup timeout. @raises InstrumentTimeoutException if the response did not occur in time. @raises InstrumentProtocolException if command could not be built. """ timeout = kwargs.get("timeout", 10) write_delay = kwargs.get("write_delay", 0) build_handler = self._build_handlers.get(cmd, None) if not build_handler: raise InstrumentProtocolException(error_code=InstErrorCode.BAD_DRIVER_COMMAND) cmd_line = build_handler(cmd, *args) # Clear line and prompt buffers for result. self._linebuf = "" self._promptbuf = "" # Send command. log.debug("_do_cmd_no_resp: %s, timeout=%s" % (repr(cmd_line), timeout)) if write_delay == 0: self._connection.send(cmd_line) else: for char in cmd_line: self._connection.send(char) time.sleep(write_delay) ######################################################################## # 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_discover(self, *args, **kwargs): """ Discover current state; can be COMMAND or AUTOSAMPLE. @retval (next_state, result), (SBE37ProtocolState.COMMAND or SBE37State.AUTOSAMPLE, None) if successful. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentStateException if the device response does not correspond to an expected state. """ next_state = None result = None # Break to command mode, then set next state to command mode # If we are doing this, we must be connected self._send_break() self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.COMMAND result = ResourceAgentState.IDLE return (next_state, result) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentProtocolException if the update commands and not recognized. """ # Command device to update parameters and send a config change event. self._update_params(timeout=3) # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_command_get(self, params=None, *args, **kwargs): """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 == DriverParameter.ALL: params = [Parameter.MAXRATE] if (params == None) or (not isinstance(params, list)): raise InstrumentParameterException() for param in params: if not Parameter.has(param): raise InstrumentParameterException() for attempt in range(RETRY): # retry up to RETRY times try: result_vals[param] = self._do_cmd_resp( Command.GET, param, expected_prompt=Prompt.COMMAND, write_delay=self.write_delay ) break # GET worked, so exit inner for loop except InstrumentProtocolException as ex: pass # GET failed, so retry again else: # retries exhausted, so raise exception raise ex result = result_vals log.debug("Get finished, next: %s, result: %s", next_state, result) return (next_state, result) def _handler_command_set(self, params, *args, **kwargs): """Handle setting data from command mode @param params Dict of the parameters and values to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For invalid parameter """ next_state = None result = None result_vals = {} if (params == None) or (not isinstance(params, dict)): raise InstrumentParameterException() name_values = params for key in name_values.keys(): if not Parameter.has(key): raise InstrumentParameterException() try: str_val = self._param_dict.format(key, name_values[key]) except KeyError: raise InstrumentParameterException() result_vals[key] = self._do_cmd_resp( Command.SET, key, str_val, expected_prompt=Prompt.COMMAND, write_delay=self.write_delay ) # Populate with actual value instead of success flag if result_vals[key]: result_vals[key] = name_values[key] self._update_params() result = self._do_cmd_resp( Command.SAVE, None, None, expected_prompt=Prompt.COMMAND, write_delay=self.write_delay ) """@todo raise a parameter error if there was a bad value""" result = result_vals log.debug("next: %s, result: %s", next_state, result) return (next_state, result) def _handler_command_start_autosample(self, params=None, *args, **kwargs): """ Handle getting an start autosample event when in 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 self._do_cmd_no_resp(Command.EXIT_AND_RESET, None, write_delay=self.write_delay) time.sleep(RESET_DELAY) self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.STREAMING return (next_state, (next_agent_state, result)) def _handler_command_start_poll(self, *args, **kwargs): """Handle getting a POLL event when in command mode. This should move the state machine into poll mode via autosample mode @retval return (next state, result) @throw InstrumentProtocolException For invalid parameter """ next_state = None next_agent_state = None result = None try: # get into auto-sample mode guaranteed, then switch to poll mode self._do_cmd_no_resp(Command.EXIT_AND_RESET, None, write_delay=self.write_delay) time.sleep(RESET_DELAY) if not self._switch_to_poll(): next_state = PARProtocolState.COMMAND else: next_state = PARProtocolState.POLL except (InstrumentTimeoutException, InstrumentProtocolException) as e: log.debug("Caught exception while switching to poll mode: %s", e) return (next_state, (next_agent_state, result)) def _handler_command_start_direct(self): """ """ next_state = None result = None next_state = PARProtocolState.DIRECT_ACCESS next_agent_state = ResourceAgentState.DIRECT_ACCESS log.debug("_handler_command_start_direct: entering DA mode") return (next_state, (next_agent_state, result)) ######################################################################## # Autosample handlers. ######################################################################## def _handler_autosample_enter(self, *args, **kwargs): """ Handle PARProtocolState.AUTOSAMPLE PARProtocolEvent.ENTER @param params Parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For hardware error """ next_state = None result = None if not self._confirm_autosample_mode: # TODO: seems like some kind of recovery should occur here raise InstrumentProtocolException(error_code=InstErrorCode.HARDWARE_ERROR, msg="Not in the correct mode!") self._driver_event(DriverAsyncEvent.STATE_CHANGE) return (next_state, result) def _handler_autosample_stop_autosample(self, *args, **kwargs): """Handle PARProtocolState.AUTOSAMPLE stop @param params Parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For hardware error """ next_state = None result = None try: self._send_break() self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND except InstrumentException: raise InstrumentProtocolException( error_code=InstErrorCode.HARDWARE_ERROR, msg="Could not break from autosample!" ) return (next_state, (next_agent_state, result)) def _handler_autosample_start_poll(self, *args, **kwargs): """Handle PARProtocolState.AUTOSAMPLE start poll @param params Parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For hardware error """ next_state = None result = None try: self._switch_to_poll() # Give the instrument a bit to keep up. 1 sec is not enough! time.sleep(5) self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.POLL next_agent_state = ResourceAgentState.COMMAND except InstrumentException: raise InstrumentProtocolException(error_code=InstErrorCode.HARDWARE_ERROR, msg="Could not stop autosample!") return (next_state, (next_agent_state, result)) def _handler_autosample_reset(self, *args, **kwargs): """Handle PARProtocolState.AUTOSAMPLE reset @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 try: self._send_reset() time.sleep(RESET_DELAY) self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.COMMAND except InstrumentException: raise InstrumentProtocolException( error_code=InstErrorCode.HARDWARE_ERROR, msg="Could not reset autosample!" ) log.debug("next: %s, result: %s", next_state, result) return (next_state, (next_agent_state, result)) ######################################################################## # Poll handlers. ######################################################################## def _handler_poll_enter(self, *args, **kwargs): """ Handle PARProtocolState.POLL PARProtocolEvent.ENTER @param params Parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For hardware error """ next_state = None result = None if not self._confirm_poll_mode: # TODO: seems like some kind of recovery should occur here raise InstrumentProtocolException(error_code=InstErrorCode.HARDWARE_ERROR, msg="Not in the correct mode!") self._driver_event(DriverAsyncEvent.STATE_CHANGE) return (next_state, result) def _handler_poll_acquire_sample(self, *args, **kwargs): """Handle PARProtocolState.POLL PARProtocolEvent.ACQUIRE_SAMPLE @retval return (next state, result) @throw InstrumentProtocolException For invalid command """ next_state = None next_agent_state = None result = None # This sometimes takes a few seconds, so stall after our sample cmd # and before the read/parse delay = self.write_delay + 2 self._do_cmd_no_resp(Command.SAMPLE, write_delay=delay) return (next_state, (next_agent_state, result)) def _handler_poll_stop_poll(self, *args, **kwargs): """Handle PARProtocolState.POLL, PARProtocolEvent.STOP_POLL @retval return (next state, result) @throw InstrumentProtocolException For invalid command """ next_state = None result = None log.debug("Breaking from poll mode...") try: self._send_break() next_state = PARProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND except InstrumentException: raise InstrumentProtocolException( error_code=InstErrorCode.HARDWARE_ERROR, msg="Could not interrupt hardware!" ) return (next_state, (next_agent_state, result)) def _handler_poll_start_autosample(self, *args, **kwargs): """Handle PARProtocolState.POLL PARProtocolEvent.START_AUTOSAMPLE @retval return (success/fail code, next state, result) """ next_state = None result = None self._do_cmd_no_resp(Command.SWITCH_TO_AUTOSAMPLE, None) self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.STREAMING return (next_state, (next_agent_state, result)) def _handler_poll_reset(self, *args, **kwargs): """Handle PARProtocolState.POLL PARProtocolEvent.reset @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 next_state = None result = None try: self._send_reset() time.sleep(RESET_DELAY) self._driver_event(DriverAsyncEvent.STATE_CHANGE) next_state = PARProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.COMMAND except InstrumentException: raise InstrumentProtocolException(error_code=InstErrorCode.HARDWARE_ERROR, msg="Could not reset poll!") log.debug("next: %s, result: %s", next_state, result) return (next_state, (next_agent_state, result)) ######################################################################## # Direct access handlers. ######################################################################## def _handler_direct_access_enter(self, *args, **kwargs): """ Enter direct access state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) self._sent_cmds = [] def _handler_direct_access_exit(self, *args, **kwargs): """ Exit direct access state. """ pass def _handler_direct_access_execute_direct(self, data): """ """ next_state = None result = None next_agent_state = None self._do_cmd_direct(data) # add sent command to list for 'echo' filtering in callback self._sent_cmds.append(data) return (next_state, (next_agent_state, result)) def _handler_direct_access_stop_direct(self): """ @throw InstrumentProtocolException on invalid command """ next_state = None result = None next_state = PARProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND return (next_state, (next_agent_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, *args): """ Builder for simple commands @param cmd The command being used (Command.GET in this case) @param args Unused arguments @retval Returns string ready for sending to instrument """ return "%s%s" % (cmd, self.eoln) def _build_control_command(self, cmd, *args): """ Send a single control char command @param cmd The control character to send @param args Unused arguments @retval The string with the complete command """ return "%c" % (cmd) def _build_multi_control_command(self, cmd, *args): """ Send a quick series of control char command @param cmd The control character to send @param args Unused arguments @retval The string with the complete command """ return "%c%c%c%c%c%c%c" % (cmd, cmd, cmd, cmd, cmd, cmd, 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 """ if prompt == Prompt.COMMAND: return True elif response == PARProtocolError.INVALID_COMMAND: return InstErrorCode.SET_DEVICE_ERR else: return InstErrorCode.HARDWARE_ERROR 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 @raise InstrumentProtocolException When a bad response is encountered """ # should end with the response, an eoln, and a prompt split_response = response.split(self.eoln) if (len(split_response) < 2) or (split_response[-1] != Prompt.COMMAND): return InstErrorCode.HARDWARE_ERROR name = self._param_dict.update(split_response[-2]) if not name: log.warn("Bad response from instrument") raise InstrumentProtocolException("Invalid response. Bad command?") log.debug("Parameter %s set to %s" % (name, self._param_dict.get(name))) return self._param_dict.get(name) def _parse_silent_response(self, response, prompt): """Parse a silent response @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device @retval return An InstErrorCode value """ log.debug("Parsing silent response of [%s] with prompt [%s]", response, prompt) if ((response == "") or (response == prompt)) and ((prompt == Prompt.NULL) or (prompt == Prompt.COMMAND)): return InstErrorCode.OK else: return InstErrorCode.HARDWARE_ERROR def _parse_header_response(self, response, prompt): """ Parse what the header looks like to make sure if came up. @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device @retval return An InstErrorCode value """ log.debug("Parsing header response of [%s] with prompt [%s]", response, prompt) if HEADER_REGEX.search(response): return InstErrorCode.OK else: return InstErrorCode.HARDWARE_ERROR def _parse_reset_response(self, response, prompt): """ Parse the results of a reset This is basically a header followed by some initialization lines @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device @retval return An InstErrorCode value """ log.debug("Parsing reset response of [%s] with prompt [%s]", response, prompt) lines = response.split(self.eoln) for line in lines: if init_regex.search(line): return InstErrorCode.OK # else return InstErrorCode.HARDWARE_ERROR def _parse_cmd_prompt_response(self, response, prompt): """Parse a command prompt response @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device @retval return An InstErrorCode value """ log.debug("Parsing command prompt response of [%s] with prompt [%s]", response, prompt) if response == Prompt.COMMAND: # yank out the command we sent, split at the self.eoln split_result = response.split(self.eoln, 1) if len(split_result) > 1: response = split_result[1] return InstErrorCode.OK else: return InstErrorCode.HARDWARE_ERROR def _parse_sample_poll_response(self, response, prompt): """Parse a sample poll response @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device @retval return The sample string """ log.debug("Parsing sample poll response of [%s] with prompt [%s]", response, prompt) if prompt == "": # strip the eoln, check for regex, report data, # and leave it in the buffer for return via execute_poll if self.eoln in response: lines = response.split(self.eoln) for line in lines: if SAMPLE_REGEX.match(line): # In poll mode, we only care about the first response, right? return line else: return "" elif SAMPLE_REGEX.match(response): return response else: return "" else: return InstErrorCode.HARDWARE_ERROR ################################################################### # Helpers ################################################################### def _wakeup(self, timeout): """There is no wakeup sequence for this instrument""" pass def _update_params(self, *args, **kwargs): """Fetch the parameters from the device, and update the param dict. @param args Unused @param kwargs Takes timeout value @throws InstrumentProtocolException @throws InstrumentTimeoutException """ log.debug("Updating parameter dict") old_config = self._param_dict.get_config() self.get_config() new_config = self._param_dict.get_config() if new_config != old_config: self._driver_event(DriverAsyncEvent.CONFIG_CHANGE) def _send_reset(self, timeout=10): """Send a reset command out to the device @throw InstrumentTimeoutException @throw InstrumentProtocolException @todo handle errors correctly here, deal with repeats at high sample rate """ write_delay = 0.2 log.debug("Sending reset chars") if self._protocol_fsm.get_current_state() == PARProtocolState.COMMAND: return InstErrorCode.OK # TODO: infinite loop bad idea while True: self._do_cmd_no_resp(Command.RESET, timeout=timeout, write_delay=write_delay) time.sleep(RESET_DELAY) if self._confirm_autosample_mode(): break def _switch_to_poll(self, timeout=10): """Send a switch_to_poll command out to the device @retval return InstErrorCode.OK for success or no-op, error code on failure @throw InstrumentTimeoutException @throw InstrumentProtocolException @todo handle errors correctly here, deal with repeats at high sample rate """ write_delay = 0.2 log.debug("Sending switch_to_poll char") counter = 0 while counter < 10: self._do_cmd_no_resp(Command.SWITCH_TO_POLL, timeout=0, write_delay=write_delay) time.sleep(0.5) # wait a little for samples to stop coming out if self._confirm_poll_mode(): return True counter = counter + 1 return False def _send_break(self, timeout=10): """Send a blind break command to the device, confirm command mode after @throw InstrumentTimeoutException @throw InstrumentProtocolException @todo handle errors correctly here, deal with repeats at high sample rate """ write_delay = 0.2 log.debug("Sending break char") # do the magic sequence of sending lots of characters really fast... # but not too fast if self._protocol_fsm.get_current_state() == PARProtocolState.COMMAND: return # TODO: infinite loop bad idea while True: self._do_cmd_no_resp( Command.BREAK, timeout=timeout, expected_prompt=Prompt.COMMAND, write_delay=write_delay ) if self._confirm_command_mode(): break def got_data(self, paPacket): """ Callback for receiving new data from the device. The port agent object fires this when data is received @param paPacket The packet of data that was received """ paLength = paPacket.get_data_size() paData = paPacket.get_data() if self.get_current_state() == PARProtocolState.DIRECT_ACCESS: # direct access mode if paLength > 0: log.debug("SatlanticPARInstrumentProtocol.got_data(): <" + paData + ">") if self._driver_event: self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, paData) # TODO: what about logging this as an event? return if paLength > 0: CommandResponseInstrumentProtocol.got_data(self, paData) # If we are streaming, process the line buffer for samples, but it # could have header stuff come out if you just got a break! if ( self._protocol_fsm.get_current_state() == PARProtocolState.AUTOSAMPLE or self._protocol_fsm.get_current_state() == PARProtocolState.POLL ): if self.eoln in self._linebuf: lines = self._linebuf.split(self.eoln) for line in lines: if SAMPLE_REGEX.match(line): self._last_data_timestamp = time.time() # construct and publish data particles self._extract_sample(SatlanticPARDataParticle, SAMPLE_REGEX, line) self._linebuf = self._linebuf.replace(line + self.eoln, "") # been processed def _confirm_autosample_mode(self): """Confirm we are in autosample mode This is done by waiting for a sample to come in, and confirming that it does or does not. @retval True if in autosample mode, False if not """ log.debug("Confirming autosample mode...") # timestamp now, start_time = self._last_data_timestamp # wait a sample period, time_between_samples = (1 / self._param_dict.get_config()[Parameter.MAXRATE]) + 1 time.sleep(time_between_samples) end_time = self._last_data_timestamp log.debug("_confirm_autosample_mode: end_time=%s, start_time=%s" % (end_time, start_time)) if end_time != start_time: log.debug("Confirmed in autosample mode") return True else: log.debug("Confirmed NOT in autosample mode") return False def _confirm_command_mode(self): """Confirm we are in command mode This is done by issuing a bogus command and getting a prompt @retval True if in command mode, False if not """ log.debug("Confirming command mode...") try: # suspend our belief that we are in another state, and behave # as if we are in command mode long enough to confirm or deny it self._do_cmd_no_resp(Command.SAMPLE, timeout=2, expected_prompt=Prompt.COMMAND) (prompt, result) = self._get_response(timeout=2, expected_prompt=Prompt.COMMAND) except InstrumentTimeoutException: # If we timed out, its because we never got our $ prompt and must # not be in command mode (probably got a data value in POLL mode) log.debug("Confirmed NOT in command mode via timeout") return False except InstrumentProtocolException: log.debug("Confirmed NOT in command mode via protocol exception") return False # made it this far log.debug("Confirmed in command mode") time.sleep(0.5) return True def _confirm_poll_mode(self): """Confirm we are in poll mode by waiting for things not to happen. Time depends on max data rate @retval True if in poll mode, False if not """ log.debug("Confirming poll mode...") auto_sample_mode = self._confirm_autosample_mode() cmd_mode = self._confirm_command_mode() if (not auto_sample_mode) and (not cmd_mode): log.debug("Confirmed in poll mode") return True else: log.debug("Confirmed NOT in poll mode") return False
class SamiProtocol(CommandResponseInstrumentProtocol): """ SAMI Instrument protocol class Subclasses CommandResponseInstrumentProtocol Should be sub-classed in specific driver. """ def __init__(self, prompts, newline, driver_event): """ Protocol constructor. @param prompts A BaseEnum class containing instrument prompts. @param newline The newline. @param driver_event Driver process event callback. """ # Construct protocol superclass. CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event) # Build protocol state machine. self._protocol_fsm = InstrumentFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT) # Add event handlers for protocol state machine self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.ENTER, self._handler_unknown_enter) self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.EXIT, self._handler_unknown_exit) self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.DISCOVER, self._handler_unknown_discover) self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.START_DIRECT, self._handler_command_start_direct) self._protocol_fsm.add_handler(ProtocolState.WAITING, ProtocolEvent.ENTER, self._handler_waiting_enter) self._protocol_fsm.add_handler(ProtocolState.WAITING, ProtocolEvent.EXIT, self._handler_waiting_exit) self._protocol_fsm.add_handler(ProtocolState.WAITING, ProtocolEvent.DISCOVER, self._handler_waiting_discover) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.EXIT, self._handler_command_exit) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.GET, self._handler_command_get) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_DIRECT, self._handler_command_start_direct) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_STATUS, self._handler_command_acquire_status) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_acquire_sample) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample) # the ACQUIRE_CONFIGURATION event may not be necessary #self._protocol_fsm.add_handler( # ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_CONFIGURATION, # self._handler_command_acquire_configuration) self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.ENTER, self._handler_direct_access_enter) self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.EXIT, self._handler_direct_access_exit) self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct) self._protocol_fsm.add_handler( ProtocolState.DIRECT_ACCESS, ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.ENTER, self._handler_autosample_enter) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.EXIT, self._handler_autosample_exit) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.ACQUIRE_SAMPLE, self._handler_autosample_acquire_sample) # this state would be entered whenever an ACQUIRE_SAMPLE event # occurred while in the AUTOSAMPLE state and will last anywhere # from 10 seconds to 3 minutes depending on instrument and the # type of sampling. self._protocol_fsm.add_handler(ProtocolState.SCHEDULED_SAMPLE, ProtocolEvent.ENTER, self._handler_scheduled_sample_enter) self._protocol_fsm.add_handler(ProtocolState.SCHEDULED_SAMPLE, ProtocolEvent.EXIT, self._handler_scheduled_sample_exit) # this state would be entered whenever an ACQUIRE_SAMPLE event # occurred while in either the COMMAND state (or via the # discover transition from the UNKNOWN state with the instrument # unresponsive) and will last anywhere from a few seconds to 3 # minutes depending on instrument and sample type. self._protocol_fsm.add_handler(ProtocolState.POLLED_SAMPLE, ProtocolEvent.ENTER, self._handler_polled_sample_enter) self._protocol_fsm.add_handler(ProtocolState.POLLED_SAMPLE, ProtocolEvent.EXIT, self._handler_polled_sample_exit) # Construct the parameter dictionary containing device # parameters, current parameter values, and set formatting # functions. self._build_param_dict() self._build_command_dict() self._build_driver_dict() # Add build handlers for device commands. self._add_build_handler(SamiInstrumentCommand.GET_STATUS, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.START_STATUS, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.STOP_STATUS, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.GET_CONFIG, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.SET_CONFIG, self._build_set_config) self._add_build_handler(SamiInstrumentCommand.ERASE_ALL, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.START, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.STOP, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.ACQUIRE_SAMPLE_SAMI, self._build_sample_sami) self._add_build_handler(SamiInstrumentCommand.ESCAPE_BOOT, self._build_escape_boot) # Add response handlers for device commands. self._add_response_handler(SamiInstrumentCommand.GET_STATUS, self._build_response_get_status) self._add_response_handler(SamiInstrumentCommand.GET_CONFIG, self._build_response_get_config) self._add_response_handler(SamiInstrumentCommand.SET_CONFIG, self._build_response_set_config) self._add_response_handler(SamiInstrumentCommand.ERASE_ALL, self._build_response_erase_all) self._add_response_handler(SamiInstrumentCommand.ACQUIRE_SAMPLE_SAMI, self._build_response_sample_sami) # Add sample handlers. # commands sent sent to device to be filtered in responses for telnet DA self._sent_cmds = [] # continue __init__ in the sub-class in the specific driver def _filter_capabilities(self, events): """ Return a list of currently available capabilities. """ return [x for x in events if Capability.has(x)] ######################################################################## # Unknown handlers. ######################################################################## def _handler_unknown_enter(self, *args, **kwargs): """ Enter unknown state. """ # Turn on debugging log.debug("_handler_unknown_enter") # 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 UNKNOWN, COMMAND or POLLED_SAMPLE @retval (next_state, result) """ next_state = None result = None log.debug("_handler_unknown_discover: starting discover") (next_state, next_agent_state) = self._discover() log.debug("_handler_unknown_discover: next agent state: %s", next_agent_state) return (next_state, (next_agent_state, result)) ######################################################################## # Waiting handlers. ######################################################################## def _handler_waiting_enter(self, *args, **kwargs): """ Enter discover state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) # Test to determine what state we truly are in, command or unknown. self._protocol_fsm.on_event(ProtocolEvent.DISCOVER) def _handler_waiting_exit(self, *args, **kwargs): """ Exit discover state. """ pass def _handler_waiting_discover(self, *args, **kwargs): """ Discover current state; can be UNKNOWN or COMMAND @retval (next_state, result) """ # Exit states can be either COMMAND or back to UNKNOWN. next_state = None next_agent_state = None result = None # try to discover our state count = 1 while count <= 6: log.debug("_handler_waiting_discover: starting discover") (next_state, next_agent_state) = self._discover() if next_state is ProtocolState.COMMAND: log.debug("_handler_waiting_discover: discover succeeded") log.debug("_handler_waiting_discover: next agent state: %s", next_agent_state) return (next_state, (next_agent_state, result)) else: log.debug( "_handler_waiting_discover: discover failed, attempt %d of 3", count) count += 1 time.sleep(20) log.debug("_handler_waiting_discover: discover failed") log.debug("_handler_waiting_discover: next agent state: %s", ResourceAgentState.ACTIVE_UNKNOWN) return (ProtocolState.UNKNOWN, (ResourceAgentState.ACTIVE_UNKNOWN, result)) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentProtocolException if the update commands and not recognized. """ # Command device to update parameters and send a config change event. #self._update_params() # Command device to initialize parameters and send a config change event. self._protocol_fsm.on_event(DriverEvent.INIT_PARAMS) # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_command_init_params(self, *args, **kwargs): """ initialize parameters """ next_state = None result = None self._init_params() return (next_state, result) def _handler_command_exit(self, *args, **kwargs): """ Exit command state. """ pass def _handler_command_get(self, *args, **kwargs): """ Get parameter """ next_state = None result = None return (next_state, result) def _handler_command_set(self, *args, **kwargs): """ Set parameter """ next_state = None result = None return (next_state, result) def _handler_command_start_direct(self): """ Start direct access """ next_state = ProtocolState.DIRECT_ACCESS next_agent_state = ResourceAgentState.DIRECT_ACCESS result = None log.debug("_handler_command_start_direct: entering DA mode") return (next_state, (next_agent_state, result)) # Not used currently. Maybe added later #def _handler_command_acquire_configuration(self, *args, **kwargs): # """ # Acquire the instrument's configuration # """ # next_state = None # result = None # # return (next_state, result) def _handler_command_acquire_status(self, *args, **kwargs): """ Acquire the instrument's status """ next_state = None result = None return (next_state, result) def _handler_command_acquire_sample(self, *args, **kwargs): """ Acquire a sample """ next_state = None result = None return (next_state, result) def _handler_command_start_autosample(self): """ Start autosample mode (spoofed via use of scheduler) """ next_state = ProtocolState.START_AUTOSAMPLE next_agent_state = ResourceAgentState.START_AUTOSAMPLE result = None log.debug( "_handler_command_start_autosample: entering Autosample mode") return (next_state, (next_agent_state, result)) 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) self._sent_cmds = [] def _handler_direct_access_exit(self, *args, **kwargs): """ Exit direct access state. """ pass def _handler_direct_access_execute_direct(self, data): """ """ next_state = None result = None next_agent_state = None self._do_cmd_direct(data) # add sent command to list for 'echo' filtering in callback self._sent_cmds.append(data) return (next_state, (next_agent_state, result)) def _handler_direct_access_stop_direct(self): """ @throw InstrumentProtocolException on invalid command """ next_state = None result = None log.debug("_handler_direct_access_stop_direct: starting discover") (next_state, next_agent_state) = self._discover() log.debug("_handler_direct_access_stop_direct: next agent state: %s", next_agent_state) return (next_state, (next_agent_state, result)) ######################################################################## # Autosample handlers. ######################################################################## def _handler_autosample_enter(self, ): """ Enter Autosample state """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) self._sent_cmds = [] def _handler_autosample_exit(self, *args, **kwargs): """ Exit autosample state """ pass def _handler_autosample_stop(self, *args, **kwargs): """ Stop autosample """ next_state = None result = None return (next_state, result) def _handler_autosample_acquire_sample(self, *args, **kwargs): """ While in autosample mode, poll for samples using the scheduler """ next_state = None result = None return (next_state, result) ######################################################################## # Scheduled Sample handlers. ######################################################################## def _handler_scheduled_sample_enter(self, *args, **kwargs): """ Enter busy state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) self._sent_cmds = [] def _handler_scheduled_sample_exit(self, *args, **kwargs): """ Exit busy state. """ pass ######################################################################## # Polled Sample handlers. ######################################################################## def _handler_polled_sample_enter(self, *args, **kwargs): """ Enter busy state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) self._sent_cmds = [] def _handler_polled_sample_exit(self, *args, **kwargs): """ Exit busy state. """ pass # handlers for the SUCCESS and TIMEOUT events associated with the # Polled Sample and Scheduled Sample states are defined in the sub # class. #################################################################### # Build Command & Parameter dictionary #################################################################### def _build_command_dict(self): """ Populate the command dictionary with command. """ self._cmd_dict.add(Capability.ACQUIRE_STATUS, display_name="acquire status") self._cmd_dict.add(Capability.START_AUTOSAMPLE, display_name="start autosample") self._cmd_dict.add(Capability.STOP_AUTOSAMPLE, display_name="stop autosample") self._cmd_dict.add(Capability.START_DIRECT, display_name="start direct access") self._cmd_dict.add(Capability.STOP_DIRECT, display_name="stop direct access") def _build_driver_dict(self): """ Populate the driver dictionary with options """ self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, True) ######################################################################## # Command handlers. ######################################################################## def _build_simple_command(self, cmd): """ Build handler for basic SAMI commands. @param cmd the simple SAMI command to format. @retval The command to be sent to the device. """ return cmd + NEWLINE def _build_set_config(self): pass def _build_sample_sami(self): pass def _build_escape_boot(self): pass ######################################################################## # Response handlers. ######################################################################## def _build_response_get_status(self): pass def _build_response_get_config(self): pass def _build_response_set_config(self): pass def _build_response_erase_all(self): pass def _build_response_sample_sami(self): pass ######################################################################## # Private helpers. ######################################################################## @staticmethod def _discover(): """ Discover current state; can be UNKNOWN, COMMAND or DISCOVER @retval (next_state, result) """ # Exit states can be either COMMAND, DISCOVER or back to UNKNOWN. next_state = None next_agent_state = None log.debug("_discover") # Set response timeout to 10 seconds. Should be immediate if # communications are enabled and the instrument is not sampling. # Otherwise, sampling can take up to ~3 minutes to complete. Partial # strings are output during that time period. kwargs['timeout'] = 10 # Make sure automatic-status updates are off. This will stop the # broadcast of information while we are trying to get data. cmd = self._build_simple_command(InstrumentCommand.STOP_STATUS) self._do_cmd_direct(cmd) # Acquire the current instrument status status = self._do_cmd_resp(InstrumentCommand.GET_STATUS, *args, **kwargs) status_match = REGULAR_STATUS_REGEX_MATCHER.match(status) if status is None or not status_match: # No response received in the timeout period, or response that does # not match the status string format is received. In either case, # we assume the unit is sampling and transition to a new state, # WAITING, to confirm or deny. next_state = ProtocolState.WAITING next_agent_state = ResourceAgentState.BUSY else: # Unit is powered on and ready to accept commands, etc. next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.IDLE log.debug("_discover. result start: %s" % next_state) return (next_state, next_agent_state) @staticmethod def _int_to_hexstring(val, slen): """ Write an integer value to an ASCIIHEX string formatted for SAMI configuration set operations. @param val the integer value to convert. @param slen the required length of the returned string. @retval an integer string formatted in ASCIIHEX for SAMI configuration set operations. @throws InstrumentParameterException if the integer and string length values are not an integers. """ if not isinstance(val, int): raise InstrumentParameterException('Value %s is not an integer.' % str(val)) elif not isinstance(slen, int): raise InstrumentParameterException('Value %s is not an integer.' % str(slen)) else: hexstr = format(val, 'X') return hexstr.zfill(slen) @staticmethod def _epoch_to_sami(): """ Create a timestamp in seconds using January 1, 1904 as the Epoch @retval an integer value representing the number of seconds since January 1, 1904. """ return int(time.time()) + SAMI_TO_UNIX @staticmethod def calc_crc(s, num_points): """ Compute a checksum for a Sami data record or control record string. The '*' (character 1) and unique identifying byte (byte 1, characters 2 & 3) at the beginning should be excluded from the checksum calculation as well as the checksum value itself (last byte, last 2 characters). It should include the record length (byte 2, characters 4 & 5). Note that this method does NOT calculate the checksum on the configuration string that is returned during instrument configuration. @author Chris Center @param s: string for check-sum analysis. @param num_points: number of bytes (each byte is 2-chars). """ cs = 0 k = 0 for i in range(num_points): value = int(s[k:k + 2], 16) # 2-chars per data point cs = cs + value k = k + 2 cs = cs & 0xFF return (cs)
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, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT) # Add event handlers for protocol state machine. self._protocol_fsm.add_handler(SBE16ProtocolState.UNKNOWN, ProtocolEvent.ENTER, self._handler_unknown_enter) self._protocol_fsm.add_handler(SBE16ProtocolState.UNKNOWN, ProtocolEvent.EXIT, self._handler_unknown_exit) self._protocol_fsm.add_handler(SBE16ProtocolState.UNKNOWN, ProtocolEvent.DISCOVER, self._handler_unknown_discover) self._protocol_fsm.add_handler(SBE16ProtocolState.UNKNOWN, ProtocolEvent.FORCE_STATE, self._handler_unknown_force_state) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, ProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, ProtocolEvent.EXIT, self._handler_command_exit) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_acquire_sample) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, ProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler(SBE16ProtocolState.COMMAND, ProtocolEvent.TEST, self._handler_command_test) self._protocol_fsm.add_handler(SBE16ProtocolState.AUTOSAMPLE, ProtocolEvent.ENTER, self._handler_autosample_enter) self._protocol_fsm.add_handler(SBE16ProtocolState.AUTOSAMPLE, ProtocolEvent.EXIT, self._handler_autosample_exit) self._protocol_fsm.add_handler(SBE16ProtocolState.AUTOSAMPLE, ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE16ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample) self._protocol_fsm.add_handler(SBE16ProtocolState.TEST, ProtocolEvent.ENTER, self._handler_test_enter) self._protocol_fsm.add_handler(SBE16ProtocolState.TEST, ProtocolEvent.EXIT, self._handler_test_exit) self._protocol_fsm.add_handler(SBE16ProtocolState.TEST, ProtocolEvent.RUN_TEST, self._handler_test_run_tests) self._protocol_fsm.add_handler(SBE16ProtocolState.TEST, ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE16ProtocolState.DIRECT_ACCESS, ProtocolEvent.ENTER, self._handler_direct_access_enter) self._protocol_fsm.add_handler(SBE16ProtocolState.DIRECT_ACCESS, ProtocolEvent.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(SBE16Command.DS, self._build_simple_command) # DHE dcal replaces dc self._add_build_handler(SBE16Command.DCAL, self._build_simple_command) self._add_build_handler(SBE16Command.TS, self._build_simple_command) self._add_build_handler(SBE16Command.STARTNOW, self._build_simple_command) self._add_build_handler(SBE16Command.STOP, self._build_simple_command) self._add_build_handler(SBE16Command.TC, self._build_simple_command) self._add_build_handler(SBE16Command.TT, self._build_simple_command) self._add_build_handler(SBE16Command.TP, self._build_simple_command) self._add_build_handler(SBE16Command.SET, self._build_set_command) # Add response handlers for device commands. self._add_response_handler(SBE16Command.DS, self._parse_dsdc_response) # DHE dcal replaces dc self._add_response_handler(SBE16Command.DCAL, self._parse_dcal_response) self._add_response_handler(SBE16Command.TS, self._parse_ts_response) self._add_response_handler(SBE16Command.SET, self._parse_set_response) self._add_response_handler(SBE16Command.TC, self._parse_test_response) self._add_response_handler(SBE16Command.TT, self._parse_test_response) self._add_response_handler(SBE16Command.TP, self._parse_test_response) # Add sample handlers. # DHE: replaced the pattern because our borrowed SBE16 doesn't have a pressure sensor #self._sample_pattern = r'^#? *(-?\d+\.\d+), *(-?\d+\.\d+), *(-?\d+\.\d+)' self._sample_pattern = r'^#? *(-?\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) def _filter_capabilities(self, events): """ """ events_out = [x for x in events if Capability.has(x)] return events_out ######################################################################## # 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, next_agent_state), (SBE16ProtocolState.COMMAND or SBE16State.AUTOSAMPLE, next_agent_state) if successful. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentProtocolException if the device response does not correspond to an expected state. """ next_state = None next_agent_state = None current_state = self._protocol_fsm.get_current_state() # Driver can only be started in streaming, command or unknown. if current_state == SBE16ProtocolState.AUTOSAMPLE: next_agent_state = ResourceAgentState.STREAMING elif current_state == SBE16ProtocolState.COMMAND: next_agent_state = ResourceAgentState.IDLE elif current_state == SBE16ProtocolState.UNKNOWN: # 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 in [SBE16Prompt.COMMAND, SBE16Prompt.EXECUTED]: next_state = SBE16ProtocolState.COMMAND next_agent_state = ResourceAgentState.IDLE elif prompt == SBE16Prompt.AUTOSAMPLE: next_state = SBE16ProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.STREAMING else: errorString = 'Failure to recognize device prompt: ' + prompt raise InstrumentProtocolException(errorString) return (next_state, next_agent_state) def _handler_unknown_force_state(self, *args, **kwargs): """ Force driver into a given state for the purposes of unit testing @param state=desired_state Required desired state to transition to. @raises InstrumentParameterException if no state parameter. """ state = kwargs.get('state', None) # via kwargs if state is None: raise InstrumentParameterException('Missing state parameter.') next_state = state result = state return (next_state, result) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentProtocolException 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 InstrumentParameterException if missing set parameters, if set parameters not ALL and not a dict, or if paramter can't be properly formatted. @throws InstrumentTimeoutException if device cannot be woken for set command. @throws InstrumentProtocolException 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 InstrumentParameterException('Set command requires a parameter dict.') if not isinstance(params, dict): raise InstrumentParameterException('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(SBE16Command.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, (next_agent_state, result)) tuple, (None, sample dict). @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command could not be built or misunderstood. @throws SampleException if a sample could not be extracted from result. """ next_state = None next_agent_state = None result = None result = self._do_cmd_resp(SBE16Command.TS, *args, **kwargs) return (next_state, (next_agent_state, result)) def _handler_command_start_autosample(self, *args, **kwargs): """ Switch into autosample mode. @retval (next_state, result) tuple, (SBE16ProtocolState.AUTOSAMPLE, (next_agent_state, None) if successful. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command could not be built or misunderstood. """ next_state = None next_agent_state = None result = None # Assure the device is transmitting. if not self._param_dict.get(SBE16Parameter.TXREALTIME): self._do_cmd_resp(SBE16Command.SET, SBE16Parameter.TXREALTIME, True, **kwargs) # Issue start command and switch to autosample if successful. self._do_cmd_no_resp(SBE16Command.STARTNOW, *args, **kwargs) next_state = SBE16ProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.STREAMING return (next_state, (next_agent_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_agent_state = ResourceAgentState.TEST return (next_state, (next_agent_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, (next_agent_state, None) if successful. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException 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) tries = kwargs.get('tries',5) notries = 0 try: # DHE: there should really be a tuple of expected prompts #self._wakeup_until(timeout, SBE16Prompt.AUTOSAMPLE) self._wakeup_until(timeout, SBE16Prompt.EXECUTED) except InstrumentTimeoutException: notries = notries + 1 if notries >= tries: raise # Issue the stop command. self._do_cmd_resp(SBE16Command.STOP, *args, **kwargs) # Prompt device until command prompt is seen. # DHE: there should really be a tuple of expected prompts #self._wakeup_until(timeout, SBE16Prompt.COMMAND) self._wakeup_until(timeout, SBE16Prompt.EXECUTED) next_state = SBE16ProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND return (next_state, (next_agent_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 InstrumentParameterException 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 InstrumentParameterException('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 InstrumentParameterException('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 InstrumentParameterException(('%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(ProtocolEvent.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 InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException 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(SBE16Command.TC, timeout=200) tt_pass, tt_result = self._do_cmd_resp(SBE16Command.TT, timeout=200) # DHE: our SBE16 has no pressure sensor #tp_pass, tp_result = self._do_cmd_resp(SBE16Command.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 # DHE: our SBE16 has no pressure sensor #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' test_result['success'] = 'Passed' if (tc_pass and tt_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 InstrumentTimeoutException if device cannot be timely woken. @throws InstrumentProtocolException 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(SBE16Command.DS, timeout=timeout) self._do_cmd_resp(SBE16Command.DCAL, 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 InstrumentProtocolException 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) if param == 'INTERVAL': param = 'sampleinterval' set_cmd = '%s=%s' % (param, str_val) set_cmd = set_cmd + SBE16_NEWLINE except KeyError: raise InstrumentParameterException('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 InstrumentProtocolException if set command misunderstood. """ if prompt not in [SBE16Prompt.EXECUTED, SBE16Prompt.COMMAND]: log.error("Set command encountered error; instrument returned: %s", response) raise InstrumentProtocolException('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 InstrumentProtocolException if dsdc command misunderstood. """ if prompt not in [SBE16Prompt.COMMAND, SBE16Prompt.EXECUTED]: raise InstrumentProtocolException('dsdc command not recognized: %s.' % response) for line in response.split(SBE16_NEWLINE): if 'sample interval' in line: for sline in line.split(','): #print 'DHE: split this: ' + sline.lstrip() self._param_dict.update(sline.lstrip()) elif 'output salinity' in line: for sline in line.split(','): print ' ==========> DHE TEMP: split this: ' + sline.lstrip() self._param_dict.update(sline.lstrip()) else: self._param_dict.update(line) def _parse_dcal_response(self, response, prompt): """ Parse handler for dsdc commands. @param response command response string. @param prompt prompt following command response. @throws InstrumentProtocolException if dsdc command misunderstood. """ if prompt not in [SBE16Prompt.COMMAND, SBE16Prompt.EXECUTED]: raise InstrumentProtocolException('dcal 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 InstrumentProtocolException if ts command misunderstood. @throws InstrumentSampleException if response did not contain a sample """ if prompt not in [SBE16Prompt.COMMAND, SBE16Prompt.EXECUTED]: raise InstrumentProtocolException('ts command not recognized: %s', response) sample = None for line in response.split(SBE16_NEWLINE): sample = self._extract_sample(SBE16DataParticle, SAMPLE_REGEX, line, True) if sample: break if not sample: raise SampleException('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, paPacket): """ Callback for receiving new data from the device. """ length = paPacket.get_data_size() data = paPacket.get_data() tempLength = len(data) if length > 0: # Call the superclass to update line and prompt 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: sample = self._extract_sample(SBE16DataParticle, SAMPLE_REGEX, line) 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'output salinity = (no)?', lambda match : False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE16Parameter.OUTPUTSV, r'output sound velocity = (no)?', lambda match : False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE16Parameter.NAVG, r'number of measurements per sample = (\d+)', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE16Parameter.SAMPLENUM, r'samples = (\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) # DHE: 16plus does not do this #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', r'transmit real-time = (yes|no)', #lambda match : False if match.group(1) else True, lambda match : True if match.group(1)=='yes' else False, 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) # DHE This doesn't show up in status when SYNCMODE # is disabled, so the tests fail. Commenting out for # now. #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)', 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) # # DHE SBE16 doesn't have this parameter # #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) # # DHE SBE16 different than SBE16 # 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) # # DHE SBE16 different than SBE16 # #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 InstrumentParameterException if value not a bool. """ if not isinstance(v,bool): raise InstrumentParameterException('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 InstrumentParameterException if value not an int. """ if not isinstance(v,int): raise InstrumentParameterException('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 InstrumentParameterException if value is not a float. """ if not isinstance(v,float): raise InstrumentParameterException('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 InstrumentParameterException if date tuple is not valid. """ if not isinstance(v,(list,tuple)): raise InstrumentParameterException('Value %s is not a list, tuple.' % str(v)) if not len(v)==3: raise InstrumentParameterException('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 InstrumentParameterException('Value %s is not a day of month.' % str(day)) if not isinstance(month,int) or month < 1 or month > 12: raise InstrumentParameterException('Value %s is not a month.' % str(month)) if not isinstance(year,int) or year < 0 or year > 99: raise InstrumentParameterException('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 InstrumentParameterException if datestr cannot be formatted to a date. """ if not isinstance(datestr,str): raise InstrumentParameterException('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 InstrumentParameterException('Value %s could not be formatted to a date.' % str(datestr)) return date
class SamiProtocol(CommandResponseInstrumentProtocol): """ SAMI Instrument protocol class Subclasses CommandResponseInstrumentProtocol Should be sub-classed in specific driver. """ def __init__(self, prompts, newline, driver_event): """ Protocol constructor. @param prompts A BaseEnum class containing instrument prompts. @param newline The newline. @param driver_event Driver process event callback. """ # Construct protocol superclass. CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event) # Build protocol state machine. self._protocol_fsm = InstrumentFSM( ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT) # Add event handlers for protocol state machine self._protocol_fsm.add_handler( ProtocolState.UNKNOWN, ProtocolEvent.ENTER, self._handler_unknown_enter) self._protocol_fsm.add_handler( ProtocolState.UNKNOWN, ProtocolEvent.EXIT, self._handler_unknown_exit) self._protocol_fsm.add_handler( ProtocolState.UNKNOWN, ProtocolEvent.DISCOVER, self._handler_unknown_discover) self._protocol_fsm.add_handler( ProtocolState.UNKNOWN, ProtocolEvent.START_DIRECT, self._handler_command_start_direct) self._protocol_fsm.add_handler( ProtocolState.WAITING, ProtocolEvent.ENTER, self._handler_waiting_enter) self._protocol_fsm.add_handler( ProtocolState.WAITING, ProtocolEvent.EXIT, self._handler_waiting_exit) self._protocol_fsm.add_handler( ProtocolState.WAITING, ProtocolEvent.DISCOVER, self._handler_waiting_discover) self._protocol_fsm.add_handler( ProtocolState.COMMAND, ProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler( ProtocolState.COMMAND, ProtocolEvent.EXIT, self._handler_command_exit) self._protocol_fsm.add_handler( ProtocolState.COMMAND, ProtocolEvent.GET, self._handler_command_get) self._protocol_fsm.add_handler( ProtocolState.COMMAND, ProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler( ProtocolState.COMMAND, ProtocolEvent.START_DIRECT, self._handler_command_start_direct) self._protocol_fsm.add_handler( ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_STATUS, self._handler_command_acquire_status) self._protocol_fsm.add_handler( ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_acquire_sample) self._protocol_fsm.add_handler( ProtocolState.COMMAND, ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample) # the ACQUIRE_CONFIGURATION event may not be necessary #self._protocol_fsm.add_handler( # ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_CONFIGURATION, # self._handler_command_acquire_configuration) self._protocol_fsm.add_handler( ProtocolState.DIRECT_ACCESS, ProtocolEvent.ENTER, self._handler_direct_access_enter) self._protocol_fsm.add_handler( ProtocolState.DIRECT_ACCESS, ProtocolEvent.EXIT, self._handler_direct_access_exit) self._protocol_fsm.add_handler( ProtocolState.DIRECT_ACCESS, ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct) self._protocol_fsm.add_handler( ProtocolState.DIRECT_ACCESS, ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct) self._protocol_fsm.add_handler( ProtocolState.AUTOSAMPLE, ProtocolEvent.ENTER, self._handler_autosample_enter) self._protocol_fsm.add_handler( ProtocolState.AUTOSAMPLE, ProtocolEvent.EXIT, self._handler_autosample_exit) self._protocol_fsm.add_handler( ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop) self._protocol_fsm.add_handler( ProtocolState.AUTOSAMPLE, ProtocolEvent.ACQUIRE_SAMPLE, self._handler_autosample_acquire_sample) # this state would be entered whenever an ACQUIRE_SAMPLE event # occurred while in the AUTOSAMPLE state and will last anywhere # from 10 seconds to 3 minutes depending on instrument and the # type of sampling. self._protocol_fsm.add_handler( ProtocolState.SCHEDULED_SAMPLE, ProtocolEvent.ENTER, self._handler_scheduled_sample_enter) self._protocol_fsm.add_handler( ProtocolState.SCHEDULED_SAMPLE, ProtocolEvent.EXIT, self._handler_scheduled_sample_exit) # this state would be entered whenever an ACQUIRE_SAMPLE event # occurred while in either the COMMAND state (or via the # discover transition from the UNKNOWN state with the instrument # unresponsive) and will last anywhere from a few seconds to 3 # minutes depending on instrument and sample type. self._protocol_fsm.add_handler( ProtocolState.POLLED_SAMPLE, ProtocolEvent.ENTER, self._handler_polled_sample_enter) self._protocol_fsm.add_handler( ProtocolState.POLLED_SAMPLE, ProtocolEvent.EXIT, self._handler_polled_sample_exit) # Construct the parameter dictionary containing device # parameters, current parameter values, and set formatting # functions. self._build_param_dict() self._build_command_dict() self._build_driver_dict() # Add build handlers for device commands. self._add_build_handler(SamiInstrumentCommand.GET_STATUS, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.START_STATUS, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.STOP_STATUS, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.GET_CONFIG, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.SET_CONFIG, self._build_set_config) self._add_build_handler(SamiInstrumentCommand.ERASE_ALL, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.START, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.STOP, self._build_simple_command) self._add_build_handler(SamiInstrumentCommand.ACQUIRE_SAMPLE_SAMI, self._build_sample_sami) self._add_build_handler(SamiInstrumentCommand.ESCAPE_BOOT, self._build_escape_boot) # Add response handlers for device commands. self._add_response_handler(SamiInstrumentCommand.GET_STATUS, self._build_response_get_status) self._add_response_handler(SamiInstrumentCommand.GET_CONFIG, self._build_response_get_config) self._add_response_handler(SamiInstrumentCommand.SET_CONFIG, self._build_response_set_config) self._add_response_handler(SamiInstrumentCommand.ERASE_ALL, self._build_response_erase_all) self._add_response_handler(SamiInstrumentCommand.ACQUIRE_SAMPLE_SAMI, self._build_response_sample_sami) # Add sample handlers. # commands sent sent to device to be filtered in responses for telnet DA self._sent_cmds = [] # continue __init__ in the sub-class in the specific driver def _filter_capabilities(self, events): """ Return a list of currently available capabilities. """ return [x for x in events if Capability.has(x)] ######################################################################## # Unknown handlers. ######################################################################## def _handler_unknown_enter(self, *args, **kwargs): """ Enter unknown state. """ # Turn on debugging log.debug("_handler_unknown_enter") # 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 UNKNOWN, COMMAND or POLLED_SAMPLE @retval (next_state, result) """ next_state = None result = None log.debug("_handler_unknown_discover: starting discover") (next_state, next_agent_state) = self._discover() log.debug("_handler_unknown_discover: next agent state: %s", next_agent_state) return (next_state, (next_agent_state, result)) ######################################################################## # Waiting handlers. ######################################################################## def _handler_waiting_enter(self, *args, **kwargs): """ Enter discover state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) # Test to determine what state we truly are in, command or unknown. self._protocol_fsm.on_event(ProtocolEvent.DISCOVER) def _handler_waiting_exit(self, *args, **kwargs): """ Exit discover state. """ pass def _handler_waiting_discover(self, *args, **kwargs): """ Discover current state; can be UNKNOWN or COMMAND @retval (next_state, result) """ # Exit states can be either COMMAND or back to UNKNOWN. next_state = None next_agent_state = None result = None # try to discover our state count = 1 while count <= 6: log.debug("_handler_waiting_discover: starting discover") (next_state, next_agent_state) = self._discover() if next_state is ProtocolState.COMMAND: log.debug("_handler_waiting_discover: discover succeeded") log.debug("_handler_waiting_discover: next agent state: %s", next_agent_state) return (next_state, (next_agent_state, result)) else: log.debug("_handler_waiting_discover: discover failed, attempt %d of 3", count) count += 1 time.sleep(20) log.debug("_handler_waiting_discover: discover failed") log.debug("_handler_waiting_discover: next agent state: %s", ResourceAgentState.ACTIVE_UNKNOWN) return (ProtocolState.UNKNOWN, (ResourceAgentState.ACTIVE_UNKNOWN, result)) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentProtocolException if the update commands and not recognized. """ # Command device to update parameters and send a config change event. #self._update_params() # Command device to initialize parameters and send a config change event. self._protocol_fsm.on_event(DriverEvent.INIT_PARAMS) # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_command_init_params(self, *args, **kwargs): """ initialize parameters """ next_state = None result = None self._init_params() return (next_state, result) def _handler_command_exit(self, *args, **kwargs): """ Exit command state. """ pass def _handler_command_get(self, *args, **kwargs): """ Get parameter """ next_state = None result = None return (next_state, result) def _handler_command_set(self, *args, **kwargs): """ Set parameter """ next_state = None result = None return (next_state, result) def _handler_command_start_direct(self): """ Start direct access """ next_state = ProtocolState.DIRECT_ACCESS next_agent_state = ResourceAgentState.DIRECT_ACCESS result = None log.debug("_handler_command_start_direct: entering DA mode") return (next_state, (next_agent_state, result)) # Not used currently. Maybe added later #def _handler_command_acquire_configuration(self, *args, **kwargs): # """ # Acquire the instrument's configuration # """ # next_state = None # result = None # # return (next_state, result) def _handler_command_acquire_status(self, *args, **kwargs): """ Acquire the instrument's status """ next_state = None result = None return (next_state, result) def _handler_command_acquire_sample(self, *args, **kwargs): """ Acquire a sample """ next_state = None result = None return (next_state, result) def _handler_command_start_autosample(self): """ Start autosample mode (spoofed via use of scheduler) """ next_state = ProtocolState.START_AUTOSAMPLE next_agent_state = ResourceAgentState.START_AUTOSAMPLE result = None log.debug("_handler_command_start_autosample: entering Autosample mode") return (next_state, (next_agent_state, result)) 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) self._sent_cmds = [] def _handler_direct_access_exit(self, *args, **kwargs): """ Exit direct access state. """ pass def _handler_direct_access_execute_direct(self, data): """ """ next_state = None result = None next_agent_state = None self._do_cmd_direct(data) # add sent command to list for 'echo' filtering in callback self._sent_cmds.append(data) return (next_state, (next_agent_state, result)) def _handler_direct_access_stop_direct(self): """ @throw InstrumentProtocolException on invalid command """ next_state = None result = None log.debug("_handler_direct_access_stop_direct: starting discover") (next_state, next_agent_state) = self._discover() log.debug("_handler_direct_access_stop_direct: next agent state: %s", next_agent_state) return (next_state, (next_agent_state, result)) ######################################################################## # Autosample handlers. ######################################################################## def _handler_autosample_enter(self, ): """ Enter Autosample state """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) self._sent_cmds = [] def _handler_autosample_exit(self, *args, **kwargs): """ Exit autosample state """ pass def _handler_autosample_stop(self, *args, **kwargs): """ Stop autosample """ next_state = None result = None return (next_state, result) def _handler_autosample_acquire_sample(self, *args, **kwargs): """ While in autosample mode, poll for samples using the scheduler """ next_state = None result = None return (next_state, result) ######################################################################## # Scheduled Sample handlers. ######################################################################## def _handler_scheduled_sample_enter(self, *args, **kwargs): """ Enter busy state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) self._sent_cmds = [] def _handler_scheduled_sample_exit(self, *args, **kwargs): """ Exit busy state. """ pass ######################################################################## # Polled Sample handlers. ######################################################################## def _handler_polled_sample_enter(self, *args, **kwargs): """ Enter busy state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) self._sent_cmds = [] def _handler_polled_sample_exit(self, *args, **kwargs): """ Exit busy state. """ pass # handlers for the SUCCESS and TIMEOUT events associated with the # Polled Sample and Scheduled Sample states are defined in the sub # class. #################################################################### # Build Command & Parameter dictionary #################################################################### def _build_command_dict(self): """ Populate the command dictionary with command. """ self._cmd_dict.add(Capability.ACQUIRE_STATUS, display_name="acquire status") self._cmd_dict.add(Capability.START_AUTOSAMPLE, display_name="start autosample") self._cmd_dict.add(Capability.STOP_AUTOSAMPLE, display_name="stop autosample") self._cmd_dict.add(Capability.START_DIRECT, display_name="start direct access") self._cmd_dict.add(Capability.STOP_DIRECT, display_name="stop direct access") def _build_driver_dict(self): """ Populate the driver dictionary with options """ self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, True) ######################################################################## # Command handlers. ######################################################################## def _build_simple_command(self, cmd): """ Build handler for basic SAMI commands. @param cmd the simple SAMI command to format. @retval The command to be sent to the device. """ return cmd + NEWLINE def _build_set_config(self): pass def _build_sample_sami(self): pass def _build_escape_boot(self): pass ######################################################################## # Response handlers. ######################################################################## def _build_response_get_status(self): pass def _build_response_get_config(self): pass def _build_response_set_config(self): pass def _build_response_erase_all(self): pass def _build_response_sample_sami(self): pass ######################################################################## # Private helpers. ######################################################################## @staticmethod def _discover(): """ Discover current state; can be UNKNOWN, COMMAND or DISCOVER @retval (next_state, result) """ # Exit states can be either COMMAND, DISCOVER or back to UNKNOWN. next_state = None next_agent_state = None log.debug("_discover") # Set response timeout to 10 seconds. Should be immediate if # communications are enabled and the instrument is not sampling. # Otherwise, sampling can take up to ~3 minutes to complete. Partial # strings are output during that time period. kwargs['timeout'] = 10 # Make sure automatic-status updates are off. This will stop the # broadcast of information while we are trying to get data. cmd = self._build_simple_command(InstrumentCommand.STOP_STATUS) self._do_cmd_direct(cmd) # Acquire the current instrument status status = self._do_cmd_resp(InstrumentCommand.GET_STATUS, *args, **kwargs) status_match = REGULAR_STATUS_REGEX_MATCHER.match(status) if status is None or not status_match: # No response received in the timeout period, or response that does # not match the status string format is received. In either case, # we assume the unit is sampling and transition to a new state, # WAITING, to confirm or deny. next_state = ProtocolState.WAITING next_agent_state = ResourceAgentState.BUSY else: # Unit is powered on and ready to accept commands, etc. next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.IDLE log.debug("_discover. result start: %s" % next_state) return (next_state, next_agent_state) @staticmethod def _int_to_hexstring(val, slen): """ Write an integer value to an ASCIIHEX string formatted for SAMI configuration set operations. @param val the integer value to convert. @param slen the required length of the returned string. @retval an integer string formatted in ASCIIHEX for SAMI configuration set operations. @throws InstrumentParameterException if the integer and string length values are not an integers. """ if not isinstance(val, int): raise InstrumentParameterException('Value %s is not an integer.' % str(val)) elif not isinstance(slen, int): raise InstrumentParameterException('Value %s is not an integer.' % str(slen)) else: hexstr = format(val, 'X') return hexstr.zfill(slen) @staticmethod def _epoch_to_sami(): """ Create a timestamp in seconds using January 1, 1904 as the Epoch @retval an integer value representing the number of seconds since January 1, 1904. """ return int(time.time()) + SAMI_TO_UNIX @staticmethod def calc_crc(s, num_points): """ Compute a checksum for a Sami data record or control record string. The '*' (character 1) and unique identifying byte (byte 1, characters 2 & 3) at the beginning should be excluded from the checksum calculation as well as the checksum value itself (last byte, last 2 characters). It should include the record length (byte 2, characters 4 & 5). Note that this method does NOT calculate the checksum on the configuration string that is returned during instrument configuration. @author Chris Center @param s: string for check-sum analysis. @param num_points: number of bytes (each byte is 2-chars). """ cs = 0 k = 0 for i in range(num_points): value = int(s[k:k+2], 16) # 2-chars per data point cs = cs + value k = k + 2 cs = cs & 0xFF return(cs)