class BarsInstrumentProtocol(CommandResponseInstrumentProtocol): """The instrument protocol classes to deal with a BARS sensor. """ def __init__(self, callback=None): """ Creates an instance of this protocol. This basically sets up the state machine, which is initialized in the INIT state. """ CommandResponseInstrumentProtocol.__init__(self, callback, BarsPrompt, NEWLINE) self._outfile = sys.stdout self._outfile = file("protoc_output.txt", "w") self._fsm = InstrumentFSM(BarsProtocolState, BarsProtocolEvent, None, None, InstErrorCode.UNHANDLED_EVENT) # PRE_INIT self._fsm.add_handler(BarsProtocolState.PRE_INIT, BarsProtocolEvent.INITIALIZE, self._handler_pre_init_initialize) # COLLECTING_DATA self._fsm.add_handler(BarsProtocolState.COLLECTING_DATA, BarsProtocolEvent.ENTER_MAIN_MENU, self._handler_collecting_data_enter_main_menu) # MAIN_MENU self._fsm.add_handler(BarsProtocolState.MAIN_MENU, BarsProtocolEvent.RESTART_DATA_COLLECTION, self._handler_main_menu_restart_data_collection) self._fsm.add_handler(BarsProtocolState.MAIN_MENU, BarsProtocolEvent.ENTER_CHANGE_PARAMS, self._handler_main_menu_enter_change_params) self._fsm.add_handler(BarsProtocolState.MAIN_MENU, BarsProtocolEvent.SHOW_SYSTEM_DIAGNOSTICS, self._handler_main_menu_show_system_diagnostics) self._fsm.add_handler(BarsProtocolState.MAIN_MENU, BarsProtocolEvent.ENTER_SET_SYSTEM_CLOCK, self._handler_main_menu_enter_set_system_clock) # CHANGE_PARAMS_MENU self._fsm.add_handler(BarsProtocolState.CHANGE_PARAMS_MENU, BarsProtocolEvent.EXIT_CHANGE_PARAMS, self._handler_change_params_menu_exit) # we start in the PRE_INIT state self._fsm.start(BarsProtocolState.PRE_INIT) # add build command handlers self._add_build_handler(CONTROL_S, self._build_simple_cmd) self._add_build_handler(CONTROL_M, self._build_simple_cmd) for c in range(8): char = '%d' % c self._add_build_handler(char, self._build_simple_cmd) # add response handlers self._add_response_handler(CONTROL_S, self._control_s_response_handler) def _assert_state(self, state): cs = self.get_current_state() res = cs == state if not res: raise AssertionError("current state=%s, expected=%s" % (cs, state)) def _build_simple_cmd(self, cmd): """ """ return cmd def _control_s_response_handler(self, response, prompt): """ """ log.debug("response='%s' prompt='%s'" % (str(response), str(prompt))) # TODO return response def _got_data(self, data): """ """ #super(BarsInstrumentProtocol, self)._got_data(data) if self._outfile: os.write(self._outfile.fileno(), data) self._outfile.flush() if re.match(DATA_LINE_PATTERN, data): # clear prompt buffer self._promptbuf = '' self._process_streaming_data(data) else: self._promptbuf += data #self._show_buffer('_promptbuf', data) def _show_buffer(self, title, buffer, prefix='\n\t| '): """ """ msg = prefix + buffer.replace('\n', prefix) log.debug("%s:%s" % (title, msg)) def _process_streaming_data(self, data): """ """ # TODO just printing data out for now self._show_buffer('_process_streaming_data', data, '\n\t! ') def get_current_state(self): """Gets the current state of the protocol.""" return self._fsm.get_current_state() def get(self, params, *args, **kwargs): # TODO it only handles BarsParameter.TIME_BETWEEN_BURSTS if log.isEnabledFor(logging.DEBUG): log.debug("params=%s args=%s kwargs=%s" % (str(params), str(args), str(kwargs))) #self._assert_state(DriverState.AUTOSAMPLE) assert isinstance(params, list) params = list(set(params)) # remove any duplicates result = {} for param in params: if param == BarsParameter.TIME_BETWEEN_BURSTS: value = self._get_cycle_time(params) else: value = InstErrorCode.INVALID_PARAMETER result[param] = value return result def _get_cycle_time(self, params): # # enter main menu # self._fsm.on_event(BarsProtocolEvent.ENTER_MAIN_MENU) self._assert_state(BarsProtocolState.MAIN_MENU) # # enter change param menu # self._fsm.on_event(BarsProtocolEvent.ENTER_CHANGE_PARAMS, params) self._assert_state(BarsProtocolState.CHANGE_PARAMS_MENU) # # save the menu to retrieve info below # menu = self._promptbuf # # exit change param menu # self._fsm.on_event(BarsProtocolEvent.EXIT_CHANGE_PARAMS) self._assert_state(BarsProtocolState.MAIN_MENU) # # return to COLLECTING_DATA # self._fsm.on_event(BarsProtocolEvent.RESTART_DATA_COLLECTION) self._assert_state(BarsProtocolState.COLLECTING_DATA) # # scan menu for requested value # seconds = None mo = re.search(CYCLE_TIME_PATTERN, menu) if mo is not None: cycle_time_str = mo.group(1) log.debug("scanned cycle_time_str='%s'" % str(cycle_time_str)) seconds = bars.get_cycle_time_seconds(cycle_time_str) if seconds is None: raise InstrumentProtocolException( msg="Unexpected: string could not be matched: %s" % menu) return seconds def set(self, params, *args, **kwargs): """ """ # TODO it only handles BarsParameter.TIME_BETWEEN_BURSTS if log.isEnabledFor(logging.DEBUG): log.debug("params=%s args=%s kwargs=%s" % (str(params), str(args), str(kwargs))) assert isinstance(params, dict) updated_params = 0 result = {} for (param, value) in params.items(): if param == BarsParameter.TIME_BETWEEN_BURSTS: result[param] = self._set_cycle_time(value) if InstErrorCode.is_ok(result[param]): updated_params += 1 else: result[param] = InstErrorCode.INVALID_PARAMETER msg = "%s parameter(s) successfully set." % updated_params log.debug("announcing to driver: %s" % msg) self.announce_to_driver(DriverAnnouncement.CONFIG_CHANGE, msg=msg) return result def _set_cycle_time(self, seconds): if not isinstance(seconds, int): return InstErrorCode.INVALID_PARAM_VALUE if seconds < 15: return InstErrorCode.INVALID_PARAM_VALUE # TODO implement me! log.debug("_set_cycle_time NOT IMPLEMENTED YET!") return InstErrorCode.OK ######################################################################## def connect(self, channels=None, *args, **kwargs): """ """ if log.isEnabledFor(logging.DEBUG): log.debug("channels=%s args=%s kwargs=%s" % (str(channels), str(args), str(kwargs))) channels = channels or [BarsChannel.INSTRUMENT] self._assert_state(BarsProtocolState.PRE_INIT) super(BarsInstrumentProtocol, self).connect(channels, *args, **kwargs) log.info("connected.") self._fsm.on_event(BarsProtocolEvent.INITIALIZE) def disconnect(self, channels=None, *args, **kwargs): """ """ if log.isEnabledFor(logging.DEBUG): log.debug("channels=%s args=%s kwargs=%s" % (str(channels), str(args), str(kwargs))) channels = channels or [BarsChannel.INSTRUMENT] #self._assert_state(BarsProtocolState.PRE_INIT) super(BarsInstrumentProtocol, self).disconnect(channels, *args, **kwargs) log.info("disconnected.") #self._fsm.on_event(BarsProtocolEvent.INITIALIZE) ######################################################################## # State handlers ######################################################################## def _handler_pre_init_initialize(self, *args, **kwargs): """ Handler to transition from PRE_INIT to appropriate state in the actual instrument, typically COLLECTING_DATA. """ if log.isEnabledFor(logging.DEBUG): log.debug("args=%s kwargs=%s" % (str(args), str(kwargs))) next_state = None result = None #TODO read from the instrument to determine current state #... # assume collecting data for the moment next_state = BarsProtocolState.COLLECTING_DATA return (next_state, result) def _handler_collecting_data_enter_main_menu(self, *args, **kwargs): """ handler to enter main menu from collecting data """ if log.isEnabledFor(logging.DEBUG): log.debug("args=%s kwargs=%s" % (str(args), str(kwargs))) time_limit = time.time() + 60 log.debug("### automatic ^S") got_prompt = False while not got_prompt and time.time() <= time_limit: log.debug("### sending ^S") result = None try: result = self._do_cmd_resp(CONTROL_S, timeout=2) except InstrumentTimeoutException: pass # ignore # TODO remove the following except case when # InstrumentTimeoutException is used by the core class # consistently. except InstrumentProtocolException as e: if e.error_code != InstErrorCode.TIMEOUT: raise time.sleep(1) log.debug("### ******** result = '%s'" % str(result)) log.debug("### ******** linebuff = '%s'" % self._linebuf) log.debug("### ******** _promptbuf = '%s'" % self._promptbuf) string = self._promptbuf got_prompt = GENERIC_PROMPT_PATTERN.search(string) is not None if not got_prompt: raise InstrumentTimeoutException() log.debug("### got prompt. Sending one ^m to clean up any ^S leftover") result = self._do_cmd_resp(CONTROL_M, timeout=10) time.sleep(1) log.debug("### ******** result = '%s'" % str(result)) log.debug("### ******** linebuff = '%s'" % repr(self._linebuf)) log.debug("### ******** _promptbuf = '%s'" % repr(self._promptbuf)) string = self._promptbuf got_prompt = GENERIC_PROMPT_PATTERN.search(string) is not None if not got_prompt: raise InstrumentProtocolException( InstErrorCode.UNKNOWN_ERROR, msg="Unexpected, should have gotten prompt after enter.") next_state = BarsProtocolState.MAIN_MENU return (next_state, result) def _wakeup(self, timeout=10): """overwritten: no need to send anything""" return None def _handler_main_menu_restart_data_collection(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.DEBUG): log.debug("args=%s kwargs=%s" % (str(args), str(kwargs))) next_state = None result = None # send '1' not expecting response: result = self._do_cmd_no_resp('1', timeout=60) log.debug("restart_data_coll result='%s'" % str(result)) # TODO confirm that data is streaming again? # ... next_state = BarsProtocolState.COLLECTING_DATA return (next_state, result) def _handler_main_menu_enter_change_params(self, *args, **kwargs): """ handler to enter in the change params menu. This menu shows the current value of some parameters so it's used to scan for respective values if that's the case. """ if log.isEnabledFor(logging.DEBUG): log.debug("args=%s kwargs=%s" % (str(args), str(kwargs))) next_state = None result = None result = self._do_cmd_resp('2', timeout=10) log.debug("result='%s'" % str(result)) next_state = BarsProtocolState.CHANGE_PARAMS_MENU return (next_state, result) def _handler_main_menu_show_system_diagnostics(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.DEBUG): log.debug("args=%s kwargs=%s" % (str(args), str(kwargs))) next_state = None result = None #TODO send '3' to instrument # ... # TODO wait for "How Many Scans do you want? --> " # ... # TODO what number of scans? -> presumably included in the params # ... num_scans = 3 # TODO send num_scans # ... # TODO read response until "Press Enter to return to Main Menu." and # notify somebody about it # then, back to main menu: next_state = BarsProtocolState.MAIN_MENU return (next_state, result) def _handler_main_menu_enter_set_system_clock(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.DEBUG): log.debug("args=%s kwargs=%s" % (str(args), str(kwargs))) next_state = None result = None # TODO validate params, it should contain a datatime spec # ... #TODO send '4' to instrument # ... # TODO wait "Do you want to Change the Current Time? (0 = No, 1 = Yes) --> " # TODO enter 1 # TODO complete interaction based on the given datetime: # Enter the Month (1-12) : 1 # Enter the Day (1-31) : 26 # Enter the Year (Two Digits): 12 # Enter the Hour (0-23) : 10 # Enter the Minute (0-59) : 51 # Enter the Second (0-59) : 14 # TODO wat "Do you want to Change the Current Time? (0 = No, 1 = Yes) --> " # TODO enter 0 # then, back to main menu: next_state = BarsProtocolState.MAIN_MENU return (next_state, result) def _handler_change_params_menu_exit(self, *args, **kwargs): """ handler to exit change params menu """ if log.isEnabledFor(logging.DEBUG): log.debug("args=%s kwargs=%s" % (str(args), str(kwargs))) # # Send '3' # result = self._do_cmd_resp(BarsProtocolEvent.ENTER_CHANGE_PARAMS) next_state = BarsProtocolState.MAIN_MENU return (next_state, result) def _state_handler_waiting_for_system_info(self, *args, **kwargs): """ Handler for BarsState.WAITING_FOR_SYSTEM_INFO """ if log.isEnabledFor(logging.DEBUG): log.debug("args=%s kwargs=%s" % (str(args), str(kwargs))) next_state = None result = None # #TODO wait for some time, then read input stream from instrument # until we are sure the complete message has been # generated and captured. Then, send the message to somebody. # while not message generated: # continue waiting while gathering any input from instrument # # etc. # TODO: what if we don't get the expected response? # ... next_state = BarsProtocolState.MAIN_MENU return (next_state, result)
class SBE37Protocol(CommandResponseInstrumentProtocol): """ """ def __init__(self, prompts, newline, evt_callback): """ """ CommandResponseInstrumentProtocol.__init__(self, evt_callback, prompts, newline) # Build protocol state machine. self._fsm = InstrumentFSM(SBE37State, SBE37Event, SBE37Event.ENTER, SBE37Event.EXIT, InstErrorCode.UNHANDLED_EVENT) # Add handlers for all events. self._fsm.add_handler(SBE37State.UNCONFIGURED, SBE37Event.ENTER, self._handler_unconfigured_enter) self._fsm.add_handler(SBE37State.UNCONFIGURED, SBE37Event.EXIT, self._handler_unconfigured_exit) self._fsm.add_handler(SBE37State.UNCONFIGURED, SBE37Event.INITIALIZE, self._handler_unconfigured_initialize) self._fsm.add_handler(SBE37State.UNCONFIGURED, SBE37Event.CONFIGURE, self._handler_unconfigured_configure) self._fsm.add_handler(SBE37State.DISCONNECTED, SBE37Event.ENTER, self._handler_disconnected_enter) self._fsm.add_handler(SBE37State.DISCONNECTED, SBE37Event.EXIT, self._handler_disconnected_exit) self._fsm.add_handler(SBE37State.DISCONNECTED, SBE37Event.INITIALIZE, self._handler_disconnected_initialize) self._fsm.add_handler(SBE37State.DISCONNECTED, SBE37Event.CONFIGURE, self._handler_disconnected_configure) self._fsm.add_handler(SBE37State.DISCONNECTED, SBE37Event.CONNECT, self._handler_disconnected_connect) self._fsm.add_handler(SBE37State.COMMAND, SBE37Event.ENTER, self._handler_command_enter) self._fsm.add_handler(SBE37State.COMMAND, SBE37Event.EXIT, self._handler_command_exit) self._fsm.add_handler(SBE37State.COMMAND, SBE37Event.DISCONNECT, self._handler_command_disconnect) self._fsm.add_handler(SBE37State.COMMAND, SBE37Event.GET, self._handler_command_autosample_get) self._fsm.add_handler(SBE37State.COMMAND, SBE37Event.SET, self._handler_command_set) self._fsm.add_handler(SBE37State.COMMAND, SBE37Event.ACQUIRE_SAMPLE, self._handler_command_acquire_sample) self._fsm.add_handler(SBE37State.COMMAND, SBE37Event.START_AUTOSAMPLE, self._handler_command_start_autosample) self._fsm.add_handler(SBE37State.COMMAND, SBE37Event.TEST, self._handler_command_test) self._fsm.add_handler(SBE37State.COMMAND, SBE37Event.UPDATE_PARAMS, self._handler_command_update_params) self._fsm.add_handler(SBE37State.AUTOSAMPLE, SBE37Event.ENTER, self._handler_autosample_enter) self._fsm.add_handler(SBE37State.AUTOSAMPLE, SBE37Event.EXIT, self._handler_autosample_exit) self._fsm.add_handler(SBE37State.AUTOSAMPLE, SBE37Event.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample) self._fsm.add_handler(SBE37State.AUTOSAMPLE, SBE37Event.GET, self._handler_command_autosample_get) # Start state machine. self._fsm.start(SBE37State.UNCONFIGURED) # Add build command handlers. 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('set', self._build_set_command) # Add parse response handlers. 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) # 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) # Add parameter handlers to parameter dict. self._add_param_dict(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._add_param_dict(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._add_param_dict(SBE37Parameter.NAVG, r'number of samples to average = (\d+)', lambda match : int(match.group(1)), self._int_to_string) self._add_param_dict(SBE37Parameter.SAMPLENUM, r'samplenumber = (\d+), free = \d+', lambda match : int(match.group(1)), self._int_to_string) self._add_param_dict(SBE37Parameter.INTERVAL, r'sample interval = (\d+) seconds', lambda match : int(match.group(1)), self._int_to_string) self._add_param_dict(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._add_param_dict(SBE37Parameter.TXREALTIME, r'(do not )?transmit real-time data', lambda match : False if match.group(1) else True, self._true_false_to_string) self._add_param_dict(SBE37Parameter.SYNCMODE, r'serial sync mode (enabled|disabled)', lambda match : False if (match.group(1)=='disabled') else True, self._true_false_to_string) self._add_param_dict(SBE37Parameter.SYNCWAIT, r'wait time after serial sync sampling = (\d+) seconds', lambda match : int(match.group(1)), self._int_to_string) self._add_param_dict(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._add_param_dict(SBE37Parameter.TA0, r' +TA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.TA1, r' +TA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.TA2, r' +TA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.TA3, r' +TA3 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(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._add_param_dict(SBE37Parameter.CG, r' +G = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.CH, r' +H = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.CI, r' +I = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.CJ, r' +J = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.WBOTC, r' +WBOTC = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.CTCOR, r' +CTCOR = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.CPCOR, r' +CPCOR = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(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._add_param_dict(SBE37Parameter.PA0, r' +PA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.PA1, r' +PA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.PA2, r' +PA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.PTCA0, r' +PTCA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.PTCA1, r' +PTCA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.PTCA2, r' +PTCA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.PTCB0, r' +PTCSB0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.PTCB1, r' +PTCSB1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.PTCB2, r' +PTCSB2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.POFFSET, r' +POFFSET = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(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._add_param_dict(SBE37Parameter.RTCA0, r' +RTCA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.RTCA1, r' +RTCA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._add_param_dict(SBE37Parameter.RTCA2, r' +RTCA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) ######################################################################## # Protocol connection interface. ######################################################################## def initialize(self, *args, **kwargs): """ """ # Construct state machine params and fire event. return self._fsm.on_event(SBE37Event.INITIALIZE, *args, **kwargs) def configure(self, *args, **kwargs): """ """ # Construct state machine params and fire event. return self._fsm.on_event(SBE37Event.CONFIGURE, *args, **kwargs) def connect(self, *args, **kwargs): """ """ # Construct state machine params and fire event. return self._fsm.on_event(SBE37Event.CONNECT, *args, **kwargs) def disconnect(self, *args, **kwargs): """ """ # Construct state machine params and fire event. return self._fsm.on_event(SBE37Event.DISCONNECT, *args, **kwargs) def detach(self, *args, **kwargs): """ """ # Construct state machine params and fire event. return self._fsm.on_event(SBE37Event.DETACH, *args, **kwargs) ######################################################################## # Protocol command interface. ######################################################################## def get(self, *args, **kwargs): """ """ return self._fsm.on_event(SBE37Event.GET, *args, **kwargs) def set(self, *args, **kwargs): """ """ return self._fsm.on_event(SBE37Event.SET, *args, **kwargs) def execute_direct(self, *args, **kwargs): """ """ return self._fsm.on_event(SBE37Event.EXECUTE, *args, **kwargs) def execute_acquire_sample(self, *args, **kwargs): """ """ return self._fsm.on_event(SBE37Event.ACQUIRE_SAMPLE, *args, **kwargs) def execute_start_autosample(self, *args, **kwargs): """ """ return self._fsm.on_event(SBE37Event.START_AUTOSAMPLE, *args, **kwargs) def execute_stop_autosample(self, *args, **kwargs): """ """ return self._fsm.on_event(SBE37Event.STOP_AUTOSAMPLE, *args, **kwargs) def execute_test(self, *args, **kwargs): """ """ return self._fsm.on_event(SBE37Event.TEST, *args, **kwargs) def update_params(self, *args, **kwargs): """ """ return self._fsm.on_event(SBE37Event.UPDATE_PARAMS, *args, **kwargs) ######################################################################## # Protocol query interface. ######################################################################## def get_resource_commands(self): """ """ return [cmd for cmd in dir(self) if cmd.startswith('execute_')] def get_resource_params(self): """ """ return self._get_param_dict_names() def get_current_state(self): """ """ return self._fsm.get_current_state() ######################################################################## # State handlers ######################################################################## ######################################################################## # SBE37State.UNCONFIGURED ######################################################################## def _handler_unconfigured_enter(self, *args, **kwargs): """ """ mi_logger.info('channel %s entered state %s', SBE37Channel.CTD, SBE37State.UNCONFIGURED) self._publish_state_change(SBE37State.UNCONFIGURED) # Initialize throws no exceptions. InstrumentProtocol.initialize(self, *args, **kwargs) def _handler_unconfigured_exit(self, *args, **kwargs): """ """ pass def _handler_unconfigured_initialize(self, *args, **kwargs): """ """ next_state = None result = None # Reenter initialize. next_state = SBE37State.UNCONFIGURED return (next_state, result) def _handler_unconfigured_configure(self, *args, **kwargs): """ """ next_state = None result = None try: InstrumentProtocol.configure(self, *args, **kwargs) except (TypeError, KeyError, InstrumentConnectionException, IndexError): result = InstErrorCode.INVALID_PARAMETER next_state = None # Everything worked, set next state. else: next_state = SBE37State.DISCONNECTED return (next_state, result) ######################################################################## # SBE37State.DISCONNECTED ######################################################################## def _handler_disconnected_enter(self, *args, **kwargs): """ """ mi_logger.info('channel %s entered state %s',SBE37Channel.CTD, SBE37State.DISCONNECTED) self._publish_state_change(SBE37State.DISCONNECTED) def _handler_disconnected_exit(self, *args, **kwargs): """ """ pass def _handler_disconnected_initialize(self, *args, **kwargs): """ """ next_state = None result = None # Switch to unconfigured to initialize comms. next_state = SBE37State.UNCONFIGURED return (next_state, result) def _handler_disconnected_configure(self, *args, **kwargs): """ """ next_state = None result = None try: InstrumentProtocol.configure(self, *args, **kwargs) except (TypeError, KeyError, InstrumentConnectionException, IndexError): result = InstErrorCode.INVALID_PARAMETER next_state = SBE37State.UNCONFIGURED return (next_state, result) def _handler_disconnected_connect(self, *args, **kwargs): """ @throw InstrumentTimeoutException on timeout """ next_state = None result = None try: InstrumentProtocol.connect(self, *args, **kwargs) timeout = kwargs.get('timeout', 10) prompt = self._wakeup(timeout) if prompt == SBE37Prompt.COMMAND: next_state = SBE37State.COMMAND elif prompt == SBE37Prompt.AUTOSAMPLE: next_state = SBE37State.AUTOSAMPLE except InstrumentConnectionException: # Connection failed, fail and stay here. next_state = None result = InstErrorCode.DRIVER_CONNECT_FAILED except InstrumentTimeoutException: # Timeout connecting or waking device. Stay disconnected. InstrumentProtocol.disconnect(self, *args, **kwargs) next_state = None result = InstErrorCode.DRIVER_CONNECT_FAILED return (next_state, result) ######################################################################## # SBE37State.COMMAND ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ """ mi_logger.info('channel %s entered state %s',SBE37Channel.CTD, SBE37State.COMMAND) self._publish_state_change(SBE37State.COMMAND) self._update_params(*args, **kwargs) def _handler_command_exit(self, *args, **kwargs): """ """ pass def _handler_command_disconnect(self, *args, **kwargs): """ """ next_state = None result = None try: mi_logger.info('DISCONNECTING') InstrumentProtocol.disconnect(self, *args, **kwargs) mi_logger.info('DONE DISCONNECTING') next_state = SBE37State.DISCONNECTED except InstrumentConnectionException: # Disconnect failed. Fail and stay here. next_state = None result = InstErrorCode.DISCONNECT_FAILED else: next_state = SBE37State.DISCONNECTED result = InstErrorCode.OK return (next_state, result) def _handler_command_set(self, *args, **kwargs): """ """ next_state = None result = None try: result = self._do_cmd_resp('set', *args, **kwargs) next_state = None except InstrumentTimeoutException: next_state = None result = InstErrorCode.TIMEOUT except IndexError: next_state = None result = InstErrorCode.REQUIRED_PARAMETER return (next_state, result) def _handler_command_acquire_sample(self, *args, **kwargs): """ """ next_state = None result = None try: result = self._do_cmd_resp('ts', *args, **kwargs) except InstrumentTimeoutException: result = InstErrorCode.TIMEOUT return (next_state, result) def _handler_command_start_autosample(self, *args, **kwargs): """ """ next_state = None result = None try: self._do_cmd_no_resp('startnow', *args, **kwargs) next_state = SBE37State.AUTOSAMPLE except InstrumentTimeoutException: result = InstErrorCode.TIMEOUT return (next_state, result) def _handler_command_test(self, *args, **kwargs): """ """ next_state = None result = None return (next_state, result) def _handler_command_update_params(self, *args, **kwargs): """ """ next_state = None result = None try: self._update_params(*args, **kwargs) except InstrumentTimeoutError: result = InstErrorCode.TIMEOUT return (next_state, result) ######################################################################## # SBE37State.AUTOSAMPLE ######################################################################## def _handler_autosample_enter(self, *args, **kwargs): """ """ mi_logger.info('channel %s entered state %s',SBE37Channel.CTD, SBE37State.AUTOSAMPLE) self._publish_state_change(SBE37State.AUTOSAMPLE) def _handler_autosample_exit(self, *args, **kwargs): """ """ pass def _handler_autosample_stop_autosample(self, *args, **kwargs): """ @throw InstrumentProtocolException on invalid command """ next_state = None result = None try: prompt = None timeout = kwargs.get('timeout', 10) while prompt != SBE37Prompt.AUTOSAMPLE: prompt = self._wakeup(timeout) self._do_cmd_resp('stop', *args, **kwargs) prompt = None while prompt != SBE37Prompt.COMMAND: prompt = self._wakeup(timeout) next_state = SBE37State.COMMAND except InstrumentTimeoutException: result = InstErrorCode.TIMEOUT return (next_state, result) ######################################################################## # SBE37State.COMMAND and SBE37State.AUTOSAMPLE common handlers. ######################################################################## def _handler_command_autosample_get(self, *args, **kwargs): """ """ next_state = None result = None try: parameter = args[0] except IndexError: result = InstErrorCode.REQUIRED_PARAMETER else: try: result = self._get_param_dict(parameter) except KeyError: result = InstErrorCode.INVALID_PARAMETER return (next_state, result) ######################################################################## # Private helpers ######################################################################## def _got_data(self, data): """ """ CommandResponseInstrumentProtocol._got_data(self, data) # Only keep the latest characters in the prompt buffer. if len(self._promptbuf)>7: self._promptbuf = self._promptbuf[-7:] # If we are streaming, process the line buffer for samples. if self._fsm.get_current_state() == SBE37State.AUTOSAMPLE: self._process_streaming_data() def _process_streaming_data(self): """ """ if self.eoln in self._linebuf: lines = self._linebuf.split(SBE37_NEWLINE) self._linebuf = lines[-1] for line in lines: sample = self._extract_sample(line, True) def _send_wakeup(self): """ """ self._logger_client.send(SBE37_NEWLINE) def _update_params(self, *args, **kwargs): """ """ timeout = kwargs.get('timeout', 10) old_config = self._get_config_param_dict() self._do_cmd_resp('ds',timeout=timeout) self._do_cmd_resp('dc',timeout=timeout) new_config = self._get_config_param_dict() if new_config != old_config: if self.send_event: event = { 'type' : 'config_change', 'value' : new_config } self.send_event(event) def _build_simple_command(self, cmd): """ """ return cmd+SBE37_NEWLINE def _build_set_command(self, cmd, param, val): """ """ str_val = self._format_param_dict(param, val) set_cmd = '%s=%s' % (param, str_val) set_cmd = set_cmd + SBE37_NEWLINE return set_cmd def _parse_dsdc_response(self, response, prompt): """ """ for line in response.split(SBE37_NEWLINE): self._update_param_dict(line) def _parse_ts_response(self, response, prompt): """ """ sample = None for line in response.split(SBE37_NEWLINE): sample = self._extract_sample(line, True) if sample: break return sample def _extract_sample(self, line, publish=True): """ """ 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))] # Extract sound velocity and salinity if present. #if match.group(5) and match.group(7): # sample['salinity'] = float(match.group(5)) # sample['sound_velocity'] = float(match.group(7)) #elif match.group(5): # if self._get_param_dict(SBE37Parameter.OUTPUTSAL): # sample['salinity'] = float(match.group(5)) # elif self._get_param_dict(SBE37Parameter.OUTPUTSV): # sample['sound_velocity'] = match.group(5) # Extract date and time if present. # sample_time = None #if match.group(8): # sample_time = time.strptime(match.group(8),', %d %b %Y, %H:%M:%S') # #elif match.group(15): # sample_time = time.strptime(match.group(15),', %m-%d-%Y, %H:%M:%S') # #if sample_time: # sample['time'] = \ # '%4i-%02i-%02iT:%02i:%02i:%02i' % sample_time[:6] # Add UTC time from driver in iso 8601 format. #sample['driver_time'] = datetime.datetime.utcnow().isoformat() # Driver timestamp. sample['time'] = [time.time()] if publish and self.send_event: event = { 'type':'sample', 'name':'ctd_parsed', 'value':sample } self.send_event(event) return sample def _parse_set_response(self, response, prompt): """ """ if prompt == SBE37Prompt.COMMAND: return InstErrorCode.OK else: return InstErrorCode.BAD_DRIVER_COMMAND def _publish_state_change(self, state): """ """ if self.send_event: event = { 'type': 'state_change', 'value': state } self.send_event(event) ######################################################################## # 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, or None if the input is not a valid bool. """ if not isinstance(v,bool): return None 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, or None if the input is not a valid int value. """ if not isinstance(v,int): return None 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, or None if the input is not a valid float value. """ if not isinstance(v,float): return None 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, or None if the input is not a valid date tuple. """ if not isinstance(v,(list,tuple)): return None if not len(v)==3: return None 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: return None if not isinstance(month,int) or month < 1 or month > 12: return None if not isinstance(year,int) or year < 0 or year > 99: return None 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, or None if the input string is not valid. """ if not isinstance(datestr,str): return None try: date_time = time.strptime(datestr,fmt) date = (date_time[2],date_time[1],date_time[0]) except ValueError: return None return date
class InstrumentAgent(ResourceAgent): """ ResourceAgent derived class for the instrument agent. This class logically abstracts instruments as taskable resources in the ION system. It directly provides common functionality (common state model, common resource interface, point of publication) and creates a driver process to specialize for particular hardware. """ def __init__(self, initial_state=InstrumentAgentState.UNINITIALIZED): """ Initialize instrument agent prior to pyon process initialization. Define state machine, initialize member variables. """ ResourceAgent.__init__(self) # Instrument agent state machine. self._fsm = InstrumentFSM(InstrumentAgentState, InstrumentAgentEvent, InstrumentAgentEvent.ENTER, InstrumentAgentEvent.EXIT, InstErrorCode.UNHANDLED_EVENT) # Populate state machine for all state-events. self._fsm.add_handler(InstrumentAgentState.POWERED_DOWN, InstrumentAgentEvent.ENTER, self._handler_powered_down_enter) self._fsm.add_handler(InstrumentAgentState.POWERED_DOWN, InstrumentAgentEvent.EXIT, self._handler_powered_down_exit) self._fsm.add_handler(InstrumentAgentState.UNINITIALIZED, InstrumentAgentEvent.ENTER, self._handler_uninitialized_enter) self._fsm.add_handler(InstrumentAgentState.UNINITIALIZED, InstrumentAgentEvent.EXIT, self._handler_uninitialized_exit) self._fsm.add_handler(InstrumentAgentState.UNINITIALIZED, InstrumentAgentEvent.POWER_DOWN, self._handler_uninitialized_power_down) self._fsm.add_handler(InstrumentAgentState.UNINITIALIZED, InstrumentAgentEvent.INITIALIZE, self._handler_uninitialized_initialize) self._fsm.add_handler(InstrumentAgentState.UNINITIALIZED, InstrumentAgentEvent.RESET, self._handler_uninitialized_reset) self._fsm.add_handler(InstrumentAgentState.INACTIVE, InstrumentAgentEvent.ENTER, self._handler_inactive_enter) self._fsm.add_handler(InstrumentAgentState.INACTIVE, InstrumentAgentEvent.EXIT, self._handler_inactive_exit) self._fsm.add_handler(InstrumentAgentState.INACTIVE, InstrumentAgentEvent.INITIALIZE, self._handler_inactive_initialize) self._fsm.add_handler(InstrumentAgentState.INACTIVE, InstrumentAgentEvent.RESET, self._handler_inactive_reset) self._fsm.add_handler(InstrumentAgentState.INACTIVE, InstrumentAgentEvent.GO_ACTIVE, self._handler_inactive_go_active) self._fsm.add_handler(InstrumentAgentState.INACTIVE, InstrumentAgentEvent.GET_RESOURCE_COMMANDS, self._handler_get_resource_commands) self._fsm.add_handler(InstrumentAgentState.INACTIVE, InstrumentAgentEvent.GET_RESOURCE_PARAMS, self._handler_get_resource_params) self._fsm.add_handler(InstrumentAgentState.IDLE, InstrumentAgentEvent.ENTER, self._handler_idle_enter) self._fsm.add_handler(InstrumentAgentState.IDLE, InstrumentAgentEvent.EXIT, self._handler_idle_exit) self._fsm.add_handler(InstrumentAgentState.IDLE, InstrumentAgentEvent.GO_INACTIVE, self._handler_idle_go_inactive) self._fsm.add_handler(InstrumentAgentState.IDLE, InstrumentAgentEvent.RESET, self._handler_idle_reset) self._fsm.add_handler(InstrumentAgentState.IDLE, InstrumentAgentEvent.RUN, self._handler_idle_run) self._fsm.add_handler(InstrumentAgentState.IDLE, InstrumentAgentEvent.GET_RESOURCE_COMMANDS, self._handler_get_resource_commands) self._fsm.add_handler(InstrumentAgentState.IDLE, InstrumentAgentEvent.GET_RESOURCE_PARAMS, self._handler_get_resource_params) self._fsm.add_handler(InstrumentAgentState.STOPPED, InstrumentAgentEvent.ENTER, self._handler_stopped_enter) self._fsm.add_handler(InstrumentAgentState.STOPPED, InstrumentAgentEvent.EXIT, self._handler_stopped_exit) self._fsm.add_handler(InstrumentAgentState.STOPPED, InstrumentAgentEvent.GO_INACTIVE, self._handler_stopped_go_inactive) self._fsm.add_handler(InstrumentAgentState.STOPPED, InstrumentAgentEvent.RESET, self._handler_stopped_reset) self._fsm.add_handler(InstrumentAgentState.STOPPED, InstrumentAgentEvent.CLEAR, self._handler_stopped_clear) self._fsm.add_handler(InstrumentAgentState.STOPPED, InstrumentAgentEvent.RESUME, self._handler_stopped_resume) self._fsm.add_handler(InstrumentAgentState.STOPPED, InstrumentAgentEvent.GET_RESOURCE_COMMANDS, self._handler_get_resource_commands) self._fsm.add_handler(InstrumentAgentState.STOPPED, InstrumentAgentEvent.GET_RESOURCE_PARAMS, self._handler_get_resource_params) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.ENTER, self._handler_observatory_enter) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.EXIT, self._handler_observatory_exit) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.GO_INACTIVE, self._handler_observatory_go_inactive) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.RESET, self._handler_observatory_reset) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.CLEAR, self._handler_observatory_clear) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.PAUSE, self._handler_observatory_pause) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.GO_STREAMING, self._handler_observatory_go_streaming) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.GO_DIRECT_ACCESS, self._handler_observatory_go_direct_access) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.GET_RESOURCE_COMMANDS, self._handler_get_resource_commands) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.GET_RESOURCE_PARAMS, self._handler_get_resource_params) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.GET_PARAMS, self._handler_get_params) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.SET_PARAMS, self._handler_observatory_set_params) self._fsm.add_handler(InstrumentAgentState.OBSERVATORY, InstrumentAgentEvent.EXECUTE_RESOURCE, self._handler_observatory_execute_resource) self._fsm.add_handler(InstrumentAgentState.STREAMING, InstrumentAgentEvent.ENTER, self._handler_streaming_enter) self._fsm.add_handler(InstrumentAgentState.STREAMING, InstrumentAgentEvent.EXIT, self._handler_streaming_exit) self._fsm.add_handler(InstrumentAgentState.STREAMING, InstrumentAgentEvent.GO_INACTIVE, self._handler_streaming_go_inactive) self._fsm.add_handler(InstrumentAgentState.STREAMING, InstrumentAgentEvent.RESET, self._handler_streaming_reset) self._fsm.add_handler(InstrumentAgentState.STREAMING, InstrumentAgentEvent.GO_OBSERVATORY, self._handler_streaming_go_observatory) self._fsm.add_handler(InstrumentAgentState.STREAMING, InstrumentAgentEvent.GET_RESOURCE_COMMANDS, self._handler_get_resource_commands) self._fsm.add_handler(InstrumentAgentState.STREAMING, InstrumentAgentEvent.GET_RESOURCE_PARAMS, self._handler_get_resource_params) self._fsm.add_handler(InstrumentAgentState.STREAMING, InstrumentAgentEvent.GET_PARAMS, self._handler_get_params) self._fsm.add_handler(InstrumentAgentState.DIRECT_ACCESS, InstrumentAgentEvent.ENTER, self._handler_direct_access_enter) self._fsm.add_handler(InstrumentAgentState.DIRECT_ACCESS, InstrumentAgentEvent.EXIT, self._handler_direct_access_exit) self._fsm.add_handler(InstrumentAgentState.DIRECT_ACCESS, InstrumentAgentEvent.GO_OBSERVATORY, self._handler_direct_access_go_observatory) self._fsm.add_handler(InstrumentAgentState.DIRECT_ACCESS, InstrumentAgentEvent.GET_RESOURCE_COMMANDS, self._handler_get_resource_commands) self._fsm.add_handler(InstrumentAgentState.DIRECT_ACCESS, InstrumentAgentEvent.GET_RESOURCE_PARAMS, self._handler_get_resource_params) ############################################################################### # Instrument agent internal parameters. ############################################################################### # State machine start state, defaults to unconfigured. self._initial_state = initial_state # Driver configuration. Passed as part of the spawn configuration # or with an initialize command. Sets driver specific # context. self._dvr_config = None # Process ID of the driver process. Useful to identify and signal # the process if necessary. Set by transition to inactive. self._dvr_pid = None # The driver process popen object. To terminate, signal, wait on, # or otherwise interact with the driver process via subprocess. # Set by transition to inactive. self._dvr_proc = None # The driver client for communicating to the driver process in # request-response or event publication. Set by transition to # inactive. self._dvr_client = None # UUID of the current transaction. self.transaction_id = None # List of pending transactions. self._pending_transactions = [] # Dictionary of data stream IDs for data publishing. Constructed # by stream_config agent config member during process on_init. self._data_streams = {} # Dictionary of data stream publishers. Constructed by # stream_config agent config member during process on_init. self._data_publishers = {} # Factories for stream packets. Constructed by driver # configuration information on transition to inactive. self._packet_factories = {} # Stream registrar to create publishers. Used to create # stream publishers, set during process on_init. self._stream_registrar = None # Latitude value. Set by subscription to platform. Used to # append data packets prior to publication. self._lat = 0 # Longitude value. Set by subscription to platform. Used to # append data packets prior to publication. self._lon = 0 ############################################################################### # Instrument agent parameter capabilities. ############################################################################### self.aparam_ia_param = None def on_init(self): """ Instrument agent pyon process initialization. Init objects that depend on the container services and start state machine. """ # The registrar to create publishers. self._stream_registrar = StreamPublisherRegistrar(process=self, node=self.container.node) # Set the driver config from the agent config if present. self._dvr_config = self.CFG.get('driver_config', None) # Construct stream publishers. self._construct_data_publishers() # Start state machine. self._fsm.start(self._initial_state) ############################################################################### # Event callback and handling. ############################################################################### def evt_recv(self, evt): """ Callback to receive asynchronous driver events. @param evt The driver event received. """ log.info('Instrument agent %s received driver event %s', self._proc_name, str(evt)) try: if evt['type'] == 'sample': name = evt['name'] value = evt['value'] value['lat'] = [self._lat] value['lon'] = [self._lon] value['stream_id'] = self._data_streams[name] if isinstance(value, dict): packet = self._packet_factories[name](**value) self._data_publishers[name].publish(packet) log.info('Instrument agent %s published data packet.', self._proc_name) except (KeyError, TypeError) as e: pass except Exception as e: log.info('Instrument agent %s error %s', self._proc_name, str(e)) ############################################################################### # Instrument agent state transition interface. # All the following commands are forwarded as a eponymous event to # the agent state machine and return the state handler result. ############################################################################### def acmd_power_up(self, *args, **kwargs): """ Agent power_up command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.POWER_UP, *args, **kwargs) def acmd_power_down(self, *args, **kwargs): """ Agent power_down command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.POWER_DOWN, *args, **kwargs) def acmd_initialize(self, *args, **kwargs): """ Agent initialize command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.INITIALIZE, *args, **kwargs) def acmd_reset(self, *args, **kwargs): """ Agent reset command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.RESET, *args, **kwargs) def acmd_go_active(self, *args, **kwargs): """ Agent go_active command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.GO_ACTIVE, *args, **kwargs) def acmd_go_inactive(self, *args, **kwargs): """ Agent go_inactive command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.GO_INACTIVE, *args, **kwargs) def acmd_run(self, *args, **kwargs): """ Agent run command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.RUN, *args, **kwargs) def acmd_clear(self, *args, **kwargs): """ Agent clear command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.CLEAR, *args, **kwargs) def acmd_pause(self, *args, **kwargs): """ Agent pause command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.PAUSE, *args, **kwargs) def acmd_resume(self, *args, **kwargs): """ Agent resume command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.RESUME, *args, **kwargs) def acmd_go_streaming(self, *args, **kwargs): """ Agent go_streaming command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.GO_STREAMING, *args, **kwargs) def acmd_go_direct_access(self, *args, **kwargs): """ Agent go_direct_access command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.GO_DIRECT_ACCESS, *args, **kwargs) def acmd_go_observatory(self, *args, **kwargs): """ Agent go_observatory command. Forward with args to state machine. """ return self._fsm.on_event(InstrumentAgentEvent.GO_OBSERVATORY, *args, **kwargs) ############################################################################### # Misc instrument agent command interface. ############################################################################### def acmd_get_current_state(self, *args, **kwargs): """ Query the agent current state. """ return self._fsm.get_current_state() ############################################################################### # Instrument agent capabilities interface. These functions override base # class helper functinos for specialized instrument agent behavior. ############################################################################### def _get_resource_commands(self): """ Get driver resource commands. Send event to state machine and return response or empty list if none. """ return self._fsm.on_event(InstrumentAgentEvent.GET_RESOURCE_COMMANDS) or [] def _get_resource_params(self): """ Get driver resource parameters. Send event to state machine and return response or empty list if none. """ return self._fsm.on_event(InstrumentAgentEvent.GET_RESOURCE_PARAMS) or [] ############################################################################### # Instrument agent resource interface. These functions override ResourceAgent # base class functions to specialize behavior for instrument driver resources. ############################################################################### def get_param(self, resource_id="", name=''): """ Get driver resource parameters. Send get_params event and args to agent state machine to handle request. NOTE: Need to adjust the ResourceAgent class and client for instrument interface needs. @param resource_id @param name A list of (channel, name) tuples of driver parameter to retrieve @retval Dict of (channel, name) : value parameter values if handled. """ params = name return self._fsm.on_event(InstrumentAgentEvent.GET_PARAMS, params) or {} def set_param(self, resource_id="", name='', value=''): """ Set driver resource parameters. Send set_params event and args to agent state machine to handle set driver resource parameters request. NOTE: Need to adjust the ResourceAgent class and client for instrument interface needs. @param resource_id @param name a Dict of (channel, name) : value for driver parameters to be set. @retval Dict of (channel, name) : None or Error if handled. """ params = name return self._fsm.on_event(InstrumentAgentEvent.SET_PARAMS, params) or {} def execute(self, resource_id="", command=None): """ Execute driver resource command. Send execute_resource event and args to agent state machine to handle resource execute request. @param resource_id @param command agent command object containing the driver command to execute @retval Resrouce agent command response object if handled. """ return self._fsm.on_event(InstrumentAgentEvent.EXECUTE_RESOURCE, command) ############################################################################### # Instrument agent transaction interface. ############################################################################### def acmd_start_transaction(self): """ """ pass def acmd_end_transaction(self): """ """ pass ############################################################################### # Powered down state handlers. # TBD. This state requires clarification of use. ############################################################################### def _handler_powered_down_enter(self, *args, **kwargs): """ Handler upon entry to powered_down state. """ log.info('Instrument agent entered state %s', self._fsm.get_current_state()) def _handler_powered_down_exit(self, *args, **kwargs): """ Handler upon exit from powered_down state. """ log.info('Instrument agent left state %s', self._fsm.get_current_state()) ############################################################################### # Uninitialized state handlers. # Default start state. The driver has not been configured or started. ############################################################################### def _handler_uninitialized_enter(self, *args, **kwargs): """ Handler upon entry to uninitialized state. """ log.info('Instrument agent entered state %s', self._fsm.get_current_state()) def _handler_uninitialized_exit(self, *args, **kwargs): """ Handler upon exit from uninitialized state. """ log.info('Instrument agent left state %s', self._fsm.get_current_state()) def _handler_uninitialized_power_down(self, *args, **kwargs): """ Handler for power_down agent command in uninitialized state. """ result = InstErrorCode.NOT_IMPLEMENTED next_state = None return (next_state, result) def _handler_uninitialized_initialize(self, dvr_config=None, *args, **kwargs): """ Handler for initialize agent command in uninitialized state. Attempt to start driver process with driver config supplied as argument or in agent configuration. Switch to inactive state if successful. """ result = None next_state = None self._dvr_config = dvr_config or self._dvr_config result = self._start_driver(self._dvr_config) if not result: next_state = InstrumentAgentState.INACTIVE return (next_state, result) def _handler_uninitialized_reset(self, *args, **kwargs): """ Handler for reset agent command in uninitialized state. Exit and reenter uninitializeds state. """ result = None next_state = InstrumentAgentState.UNINITIALIZED return (next_state, result) ############################################################################### # Inactive state handlers. # The driver is configured and started, but not connected. ############################################################################### def _handler_inactive_enter(self, *args, **kwargs): """ Handler upon entry to inactive state. """ log.info('Instrument agent entered state %s', self._fsm.get_current_state()) def _handler_inactive_exit(self, *args, **kwargs): """ Handler upon exit from inactive state. """ log.info('Instrument agent left state %s', self._fsm.get_current_state()) def _handler_inactive_initialize(self, dvr_config=None, *args, **kwargs): """ Handler for initialize command in inactive state. Stop and restart driver process using new driver config if supplied. """ result = None next_state = None result = self._stop_driver() if result: return (next_state, result) self._dvr_config = dvr_config or self._dvr_config result = self._start_driver(self._dvr_config) if not result: next_state = InstrumentAgentState.INACTIVE return (next_state, result) def _handler_inactive_reset(self, *args, **kwargs): """ Handler for reset agent command in inactive state. Stop the driver process and switch to unitinitalized state if successful. """ result = None next_state = None result = self._stop_driver() if not result: next_state = InstrumentAgentState.UNINITIALIZED return (next_state, result) def _handler_inactive_go_active(self, dvr_comms=None, *args, **kwargs): """ Handler for go_active agent command in inactive state. Attempt to establsih communications with all device channels. Switch to active state if any channels activated. """ result = None next_state = None if not dvr_comms: dvr_comms = self._dvr_config.get('comms_config', None) cfg_result = self._dvr_client.cmd_dvr('configure', dvr_comms) channels = [key for (key, val) in cfg_result.iteritems() if not InstErrorCode.is_error(val)] con_result = self._dvr_client.cmd_dvr('connect', channels) result = cfg_result.copy() for (key, val) in con_result.iteritems(): result[key] = val self._active_channels = self._dvr_client.cmd_dvr('get_active_channels') if len(self._active_channels)>0: next_state = InstrumentAgentState.IDLE return (next_state, result) ############################################################################### # Idle state handlers. ############################################################################### def _handler_idle_enter(self, *args, **kwargs): """ Handler upon entry to idle state. """ log.info('Instrument agent entered state %s', self._fsm.get_current_state()) def _handler_idle_exit(self, *args, **kwargs): """ Handler upon exit from idle state. """ log.info('Instrument agent left state %s', self._fsm.get_current_state()) def _handler_idle_go_inactive(self, *args, **kwargs): """ Handler for go_inactive agent command in idle state. Attempt to disconnect and initialize all active driver channels. Swtich to inactive state if successful. """ result = None next_state = None channels = self._dvr_client.cmd_dvr('get_active_channels') dis_result = self._dvr_client.cmd_dvr('disconnect', channels) [key for (key, val) in dis_result.iteritems() if not InstErrorCode.is_error(val)] init_result = self._dvr_client.cmd_dvr('initialize', channels) result = dis_result.copy() for (key, val) in init_result.iteritems(): result[key] = val self._active_channels = self._dvr_client.cmd_dvr('get_active_channels') if len(self._active_channels)==0: next_state = InstrumentAgentState.INACTIVE return (next_state, result) def _handler_idle_reset(self, *args, **kwargs): """ Handler for reset agent command in idle state. """ result = None next_state = None return (next_state, result) def _handler_idle_run(self, *args, **kwargs): """ Handler for run agent command in idle state. Switch to observatory state. """ result = None next_state = InstrumentAgentState.OBSERVATORY return (next_state, result) ############################################################################### # Stopped state handlers. ############################################################################### def _handler_stopped_enter(self, *args, **kwargs): """ Handler for entry into stopped state. """ log.info('Instrument agent entered state %s', self._fsm.get_current_state()) def _handler_stopped_exit(self, *args, **kwargs): """ Handler for exit from stopped state. """ log.info('Instrument agent left state %s', self._fsm.get_current_state()) def _handler_stopped_go_inactive(self, *args, **kwargs): """ Handler for go_inactive agent command in stopped state. """ result = None next_state = None return (next_state, result) def _handler_stopped_reset(self, *args, **kwargs): """ Handler for reset agent command in stopped state. """ result = None next_state = None return (next_state, result) def _handler_stopped_clear(self, *args, **kwargs): """ Handler for clear agent command in stopped state. """ result = None next_state = None return (next_state, result) def _handler_stopped_resume(self, *args, **kwargs): """ Handler for resume agent command in stopped state. """ result = None next_state = None return (next_state, result) ############################################################################### # Observatory state handlers. ############################################################################### def _handler_observatory_enter(self, *args, **kwargs): """ Handler upon entry to observatory state. """ log.info('Instrument agent entered state %s', self._fsm.get_current_state()) def _handler_observatory_exit(self, *args, **kwargs): """ Handler upon exit from observatory state. """ log.info('Instrument agent left state %s', self._fsm.get_current_state()) def _handler_observatory_go_inactive(self, *args, **kwargs): """ Handler for go_inactive agent command in observatory state. Attempt to disconnect and initialize all active driver channels. Switch to inactive state if successful. """ result = None next_state = None channels = self._dvr_client.cmd_dvr('get_active_channels') dis_result = self._dvr_client.cmd_dvr('disconnect', channels) [key for (key, val) in dis_result.iteritems() if not InstErrorCode.is_error(val)] init_result = self._dvr_client.cmd_dvr('initialize', channels) result = dis_result.copy() for (key, val) in init_result.iteritems(): result[key] = val self._active_channels = self._dvr_client.cmd_dvr('get_active_channels') if len(self._active_channels)==0: next_state = InstrumentAgentState.INACTIVE return (next_state, result) def _handler_observatory_reset(self, *args, **kwargs): """ Handler for reset agent command in observatory state. """ result = None next_state = None return (next_state, result) def _handler_observatory_clear(self, *args, **kwargs): """ Handler for clear agent command in observatory state. """ result = None next_state = None return (next_state, result) def _handler_observatory_pause(self, *args, **kwargs): """ Handler for pause agent command in observatory state. """ result = None next_state = None return (next_state, result) def _handler_observatory_go_streaming(self, *args, **kwargs): """ Handler for go_streaming agent command in observatory state. Send start autosample command to driver and switch to streaming state if successful. """ result = None next_state = None result = self._dvr_client.cmd_dvr('start_autosample', *args, **kwargs) if isinstance(result, dict): if any([val == None for val in result.values()]): next_state = InstrumentAgentState.STREAMING return (next_state, result) def _handler_observatory_go_direct_access(self, *args, **kwargs): """ Handler for go_direct_access agent ommand in observatory state. """ result = None next_state = None return (next_state, result) def _handler_get_params(self, params, *args, **kwargs): """ Handler for get_params resource command in observatory state. Send get command to driver and return result. """ result = self._dvr_client.cmd_dvr('get', params) next_state = None return (next_state, result) def _handler_observatory_set_params(self, params, *args, **kwargs): """ Handler for set_params resource command in observatory state. Send the set command to the driver and return result. """ result = self._dvr_client.cmd_dvr('set', params) next_state = None return (next_state, result) def _handler_observatory_execute_resource(self, command, *args, **kwargs): """ Handler for execute_resource command in observatory state. Issue driver command and return the result. """ result = None next_state = None if not command: raise iex.BadRequest("execute argument 'command' not present") if not command.command: raise iex.BadRequest("command not set") cmd_res = IonObject("AgentCommandResult", command_id=command.command_id, command=command.command) cmd_res.ts_execute = get_ion_ts() command.command = 'execute_' + command.command res = self._dvr_client.cmd_dvr(command.command, *command.args, **command.kwargs) cmd_res.status = 0 cmd_res.result = res result = cmd_res return (next_state, result) ############################################################################### # Streaming state handlers. ############################################################################### def _handler_streaming_enter(self, *args, **kwargs): """ Handler for entry to streaming state. """ log.info('Instrument agent entered state %s', self._fsm.get_current_state()) def _handler_streaming_exit(self, *args, **kwargs): """ Handler upon exit from streaming state. """ log.info('Instrument agent left state %s', self._fsm.get_current_state()) def _handler_streaming_go_inactive(self, *args, **kwargs): """ Handler for go_inactive agent command within streaming state. """ result = None next_state = None return (next_state, result) def _handler_streaming_reset(self, *args, **kwargs): """ Handler for reset agent command within streaming state. """ result = None next_state = None return (next_state, result) def _handler_streaming_go_observatory(self, *args, **kwargs): """ Handler for go_observatory agent command within streaming state. Command driver to stop autosampling, and switch to observatory mode if successful. """ result = None next_state = None result = self._dvr_client.cmd_dvr('stop_autosample', *args, **kwargs) if isinstance(result, dict): if all([val == None for val in result.values()]): next_state = InstrumentAgentState.OBSERVATORY return (next_state, result) ############################################################################### # Direct access state handlers. ############################################################################### def _handler_direct_access_enter(self, *args, **kwargs): """ Handler upon direct access entry. """ log.info('Instrument agent entered state %s', self._fsm.get_current_state()) def _handler_direct_access_exit(self, *args, **kwargs): """ Handler upon direct access exit. """ log.info('Instrument agent left state %s', self._fsm.get_current_state()) def _handler_direct_access_go_observatory(self, *args, **kwargs): """ Handler for go_observatory agent command within direct access state. """ result = None next_state = None return (next_state, result) ############################################################################### # Get resource state handlers. # Available for all states with a valid driver process. ############################################################################### def _handler_get_resource_params(self, *args, **kwargs): """ Handler for get_resource_params resource command. Send get_resource_params and args to driver and return result. """ result = self._dvr_client.cmd_dvr('get_resource_params') next_state = None return (next_state, result) def _handler_get_resource_commands(self, *args, **kwargs): """ Handler for get_resource_commands resource command. Send get_resource_commands and args to driver and return result. """ result = self._dvr_client.cmd_dvr('get_resource_commands') next_state = None return (next_state, result) ############################################################################### # Private helpers. ############################################################################### def _start_driver(self, dvr_config): """ Start the driver process and driver client. @param dvr_config The driver configuration. @param comms_config The driver communications configuration. @retval None or error. """ try: cmd_port = dvr_config['cmd_port'] evt_port = dvr_config['evt_port'] dvr_mod = dvr_config['dvr_mod'] dvr_cls = dvr_config['dvr_cls'] svr_addr = dvr_config['svr_addr'] except (TypeError, KeyError): # Not a dict. or missing required parameter. log.error('Insturment agent %s missing required parameter in start_driver.', self._proc_name) return InstErrorCode.REQUIRED_PARAMETER # Launch driver process. self._dvr_proc = ZmqDriverProcess.launch_process(cmd_port, evt_port, dvr_mod, dvr_cls) self._dvr_proc.poll() if self._dvr_proc.returncode: # Error proc didn't start. log.error('Insturment agent %s driver process did not launch.', self._proc_name) return InstErrorCode.AGENT_INIT_FAILED log.info('Insturment agent %s launched driver process.', self._proc_name) # Create client and start messaging. self._dvr_client = ZmqDriverClient(svr_addr, cmd_port, evt_port) self._dvr_client.start_messaging(self.evt_recv) log.info('Insturment agent %s driver process client started.', self._proc_name) time.sleep(1) try: retval = self._dvr_client.cmd_dvr('process_echo', 'Test.') log.info('Insturment agent %s driver process echo test: %s.', self._proc_name, str(retval)) except Exception: self._dvr_proc.terminate() self._dvr_proc.wait() self._dvr_proc = None self._dvr_client = None log.error('Insturment agent %s error commanding driver process.', self._proc_name) return InstErrorCode.AGENT_INIT_FAILED else: log.info('Insturment agent %s started its driver.', self._proc_name) self._construct_packet_factories() def _stop_driver(self): """ Stop the driver process and driver client. @retval None. """ if self._dvr_client: self._dvr_client.done() self._dvr_proc.wait() self._dvr_proc = None self._dvr_client = None self._clear_packet_factories() log.info('Insturment agent %s stopped its driver.', self._proc_name) time.sleep(1) def _construct_data_publishers(self): """ Construct the stream publishers from the stream_config agent config variable. @retval None """ stream_config = self.CFG.stream_config for (name, stream_id) in stream_config.iteritems(): self._data_streams[name] = stream_id publisher = self._stream_registrar.create_publisher(stream_id=stream_id) self._data_publishers[name] = publisher log.info('Instrumen agent %s created publisher for stream %s', self._proc_name, name) def _construct_packet_factories(self): """ Construct packet factories from packet_config member of the driver_config. @retval None """ packet_config = self._dvr_config['packet_config'] for (name, val) in packet_config.iteritems(): if val: mod = val[0] cls = val[1] import_str = 'from %s import %s' % (mod, cls) ctor_str = 'ctor = %s' % cls try: exec import_str exec ctor_str except Exception: log.error('Instrument agent %s had error creating packet factories from %s.%s', self._proc_name, mod, cls) else: self._packet_factories[name] = ctor log.info('Instrument agent %s created packet factory for stream %s', self._proc_name, name) def _clear_packet_factories(self): """ Delete packet factories. @retval None """ self._packet_factories.clear() log.info('Instrument agent %s deleted packet factories.', self._proc_name) ############################################################################### # Misc and test. ############################################################################### def test_ia(self): log.info('Hello from the instrument agent!')
class SatlanticPARInstrumentProtocol(CommandResponseInstrumentProtocol): """The instrument protocol classes to deal with a Satlantic PAR sensor. The protocol is a very simple command/response protocol with a few show commands and a few set commands. @todo Check for valid state transitions and handle requests appropriately possibly using better exceptions from the fsm.on_event() method """ def __init__(self, callback=None): CommandResponseInstrumentProtocol.__init__(self, callback, Prompt, "\n") self._fsm = InstrumentFSM(State, Event, Event.ENTER_STATE, Event.EXIT_STATE, InstErrorCode.UNHANDLED_EVENT) self._fsm.add_handler(State.COMMAND_MODE, Event.COMMAND, self._handler_command_command) self._fsm.add_handler(State.COMMAND_MODE, Event.GET, self._handler_command_get) self._fsm.add_handler(State.COMMAND_MODE, Event.SET, self._handler_command_set) self._fsm.add_handler(State.AUTOSAMPLE_MODE, Event.BREAK, self._handler_autosample_break) self._fsm.add_handler(State.AUTOSAMPLE_MODE, Event.STOP, self._handler_autosample_stop) self._fsm.add_handler(State.AUTOSAMPLE_MODE, Event.RESET, self._handler_reset) self._fsm.add_handler(State.AUTOSAMPLE_MODE, Event.COMMAND, self._handler_autosample_command) self._fsm.add_handler(State.POLL_MODE, Event.AUTOSAMPLE, self._handler_poll_autosample) self._fsm.add_handler(State.POLL_MODE, Event.RESET, self._handler_reset) self._fsm.add_handler(State.POLL_MODE, Event.SAMPLE, self._handler_poll_sample) self._fsm.add_handler(State.POLL_MODE, Event.COMMAND, self._handler_poll_command) self._fsm.add_handler(State.UNKNOWN, Event.INITIALIZE, self._handler_initialize) self._fsm.start(State.UNKNOWN) self._add_build_handler(Command.SET, self._build_set_command) self._add_build_handler(Command.GET, self._build_param_fetch_command) self._add_build_handler(Command.SAVE, self._build_exec_command) self._add_build_handler(Command.EXIT, self._build_exec_command) self._add_build_handler(Command.EXIT_AND_RESET, self._build_exec_command) self._add_build_handler(Command.AUTOSAMPLE, self._build_control_command) self._add_build_handler(Command.RESET, self._build_control_command) self._add_build_handler(Command.BREAK, self._build_control_command) self._add_build_handler(Command.SAMPLE, self._build_control_command) self._add_build_handler(Command.STOP, self._build_control_command) self._add_response_handler(Command.SET, self._parse_set_response) # self._add_response_handler(Command.GET, self._parse_get_response) self._add_param_dict( Parameter.TELBAUD, r"Telemetry Baud Rate:\s+(\d+) bps", lambda match: int(match.group(1)), self._int_to_string, ) self._add_param_dict( Parameter.MAXRATE, r"Maximum Frame Rate:\s+(\d+) Hz", lambda match: int(match.group(1)), self._int_to_string ) # The normal interface for a protocol. These should drive the FSM # transitions as they get things done. def get(self, *args, **kwargs): """ Get the given parameters from the instrument @param params The parameter values to get @retval Result of FSM event handle, hould be a dict of parameters and values @throws InstrumentProtocolException On invalid parameter """ # Parameters checked in Handler result = self._fsm.on_event(Event.GET, *args, **kwargs) if result == None: raise InstrumentProtocolException(InstErrorCode.INCORRECT_STATE) assert isinstance(result, dict) return result def set(self, *args, **kwargs): """ Set the given parameters on the instrument @param params The dict of parameters and values to set @retval result of FSM event handle @throws InstrumentProtocolException On invalid parameter """ # Parameters checked in handler result = self._fsm.on_event(Event.SET, *args, **kwargs) if result == None: raise InstrumentProtocolException(InstErrorCode.INCORRECT_STATE) assert isinstance(result, dict) return result def execute_save(self, *args, **kwargs): """ Execute the save command @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing """ kwargs.update({KwargsKey.COMMAND: Command.SAVE}) return self._fsm.on_event(Event.COMMAND, *args, **kwargs) def execute_exit(self, *args, **kwargs): """ Execute the exit command @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing """ kwargs.update({KwargsKey.COMMAND: Command.EXIT}) return self._fsm.on_event(Event.COMMAND, *args, **kwargs) def execute_exit_and_reset(self, *args, **kwargs): """ Execute the exit and reset command @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing """ kwargs.update({KwargsKey.COMMAND: Command.EXIT_AND_RESET}) return self._fsm.on_event(Event.COMMAND, *args, **kwargs) def execute_poll(self, *args, **kwargs): """ Execute the poll command @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing """ kwargs.update({KwargsKey.COMMAND: Command.POLL}) return self._fsm.on_event(Event.COMMAND, *args, **kwargs) def execute_reset(self, *args, **kwargs): """ Execute the reset command @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing """ return self._fsm.on_event(Event.RESET, *args, **kwargs) def execute_break(self, *args, **kwargs): """ Execute the break command @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing """ return self._fsm.on_event(Event.BREAK, *args, **kwargs) def execute_stop(self, *args, **kwargs): """ Execute the stop command @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing """ return self._fsm.on_event(Event.STOP, *args, **kwargs) def execute_autosample(self, *args, **kwargs): """ Execute the autosample command @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing """ return self._fsm.on_event(Event.AUTOSAMPLE, *args, **kwargs) def execute_sample(self, *args, **kwargs): """ Execute the sample command @retval None if nothing was done, otherwise result of FSM event handle @throws InstrumentProtocolException On invalid command or missing """ return self._fsm.on_event(Event.SAMPLE, *args, **kwargs) def get_config(self): """ Get the entire configuration for the instrument @param params The parameters and values to set @retval None if nothing was done, otherwise result of FSM event handle Should be a dict of parameters and values @throws InstrumentProtocolException On invalid parameter """ result = self.get([Parameter.TELBAUD, Parameter.MAXRATE]) assert isinstance(result, dict) assert result.has_key(Parameter.TELBAUD) assert result.has_key(Parameter.MAXRATE) return result def restore_config(self, config=None): """ Apply a complete configuration. In this instrument, it is simply a compound set that must contain all of the parameters. @throws InstrumentProtocolException on missing or bad config """ if config == None: raise InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER) if (config.has_key(Parameter.TELBAUD)) and (config.has_key(Parameter.MAXRATE)): assert isinstance(config, dict) assert len(config) == 2 self.set(config) else: raise InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER) def get_status(self): """ Get the current state of the state machine as the instrument doesnt maintain a status beyond its configuration and its active mode @retval Something from the State enum """ return self._fsm.current_state() def initialize(self, *args, **kwargs): mi_logger.info("Initializing PAR sensor") self._fsm.on_event(Event.INITIALIZE, *args, **kwargs) ################ # State handlers ################ def _handler_initialize(self, *args, **kwargs): """Handle transition from UNKNOWN state to a known one. This method determines what state the device is in or gets it to a known state so that the instrument and protocol are in sync. @param params Parameters to pass to the state @retval return (next state, result) """ next_state = None result = None # Break to command mode, then set next state to command mode if self._send_break(Command.BREAK): self.announce_to_driver(DriverAnnouncement.STATE_CHANGE, msg="Initialized, in command mode") next_state = State.COMMAND_MODE return (next_state, result) def _handler_reset(self, *args, **kwargs): """Handle reset condition for all states. @param params Parameters to pass to the state @retval return (next state, result) """ next_state = None result = None if self._send_break(Command.RESET): self.announce_to_driver(DriverAnnouncement.STATE_CHANGE, msg="Reset!") next_state = State.AUTOSAMPLE_MODE return (next_state, result) def _handler_autosample_break(self, *args, **kwargs): """Handle State.AUTOSAMPLE_MODE Event.BREAK @param params Parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For hardware error """ next_state = None result = None if self._send_break(Command.BREAK): self.announce_to_driver(DriverAnnouncement.STATE_CHANGE, msg="Leaving auto sample!") next_state = State.COMMAND_MODE else: self.announce_to_driver( DriverAnnouncement.ERROR, error_code=InstErrorCode.HARDWARE_ERROR, msg="Could not break from autosample!", ) raise InstrumentProtocolException(InstErrorCode.HARDWARE_ERROR) return (next_state, result) def _handler_autosample_stop(self, *args, **kwargs): """Handle State.AUTOSAMPLE_MODE Event.STOP @param params Parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For hardware error """ next_state = None result = None if self._send_break(Command.STOP): self.announce_to_driver(DriverAnnouncement.STATE_CHANGE, msg="Leaving auto sample!") next_state = State.POLL_MODE else: self.announce_to_driver( DriverAnnouncement.ERROR, error_code=InstErrorCode.HARDWARE_ERROR, msg="Could not stop autosample!" ) raise InstrumentProtocolException(InstErrorCode.HARDWARE_ERROR) return (next_state, result) def _handler_autosample_command(self, *args, **kwargs): """Handle State.AUTOSAMPLE_MODE Event.COMMAND transition @param params Dict with "command" enum and "params" of the parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For invalid parameter """ next_state = None result = None cmd = kwargs.get(KwargsKey.COMMAND, None) if cmd == Command.BREAK: result = self._fsm.on_event(Event.BREAK, *args, **kwargs) elif cmd == Command.STOP: result = self._fsm.on_event(Event.STOP, *args, **kwargs) elif cmd == Command.RESET: result = self._fsm.on_event(Event.RESET, *args, **kwargs) else: raise InstrumentProtocolException(InstErrorCode.INVALID_COMMAND) mi_logger.debug("next: %s, result: %s", next_state, result) return (next_state, result) def _handler_command_command(self, *args, **kwargs): """Handle State.COMMAND_MODE Event.COMMAND transition @param params Dict with "command" enum and "params" of the parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For invalid parameter """ next_state = None result = None cmd = kwargs.get(KwargsKey.COMMAND, None) if cmd == Command.EXIT: result = self._do_cmd_no_resp(Command.EXIT, None) if result: self.announce_to_driver(DriverAnnouncement.STATE_CHANGE, msg="Starting auto sample") next_state = State.AUTOSAMPLE_MODE elif cmd == Command.EXIT_AND_RESET: result = self._do_cmd_no_resp(Command.EXIT_AND_RESET, None) if result: self.announce_to_driver(DriverAnnouncement.STATE_CHANGE, msg="Starting auto sample") next_state = State.AUTOSAMPLE_MODE elif cmd == Command.SAVE: result = self._do_cmd_no_resp(Command.SAVE, None) elif cmd == Command.POLL: try: kwargs.update({KwargsKey.COMMAND: Command.EXIT}) result = self._fsm.on_event(Event.COMMAND, *args, **kwargs) result = self._fsm.on_event(Event.STOP, *args, **kwargs) result = self._fsm.on_event(Event.SAMPLE, *args, **kwargs) # result should have data, right? mi_logger.debug("Polled sample: %s", result) result = self._fsm.on_event(Event.AUTOSAMPLE, *args, **kwargs) result = self._fsm.on_event(Event.BREAK, *args, **kwargs) except (InstrumentTimeoutException, InstrumentProtocolException) as e: if self._fsm.current_state == State.AUTOSAMPLE_MODE: result = self._fsm.on_event(Event.BREAK, *args, **kwargs) elif self._fsm.current_state == State.POLL_MODE: result = self._fsm.on_event(Event.AUTOSAMPLE, *args, **kwargs) result = self._fsm.on_event(Event.BREAK, *args, **kwargs) else: raise InstrumentProtocolException(InstErrorCode.INVALID_COMMAND) mi_logger.debug("next: %s, result: %s", next_state, result) return (next_state, result) def _handler_command_get(self, params=None, *args, **kwargs): """Handle getting data from command mode @param params List of the parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For invalid parameter """ next_state = None result = None result_vals = {} if (params == None) or (not isinstance(params, list)): raise InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER) for param in params: if not Parameter.has(param): raise InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER) break result_vals[param] = self._do_cmd_resp(Command.GET, param) result = result_vals mi_logger.debug("next: %s, result: %s", next_state, result) return (next_state, result) def _handler_command_set(self, params, *args, **kwargs): """Handle setting data from command mode @param params Dict of the parameters and values to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For invalid parameter """ next_state = None result = None result_vals = {} if (params == None) or (not isinstance(params, dict)): raise InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER) name_values = params for key in name_values.keys(): if not Parameter.has(key): raise InstrumentProtocolException(InstErrorCode.INVALID_PARAMETER) break result_vals[key] = self._do_cmd_resp(Command.SET, key, name_values[key]) """@todo raise a parameter error if there was a bad value""" result = result_vals mi_logger.debug("next: %s, result: %s", next_state, result) return (next_state, result) def _handler_poll_sample(self, *args, **kwargs): """Handle State.POLL_MODE Event.SAMPLE @retval return (next state, result) @throw InstrumentProtocolException For invalid command """ next_state = None result = None result = self._do_cmd_resp(Command.SAMPLE, None) self.announce_to_driver(DriverAnnouncement.DATA_RECEIVED, msg=result) return (next_state, result) def _handler_poll_autosample(self, *args, **kwargs): """Handle State.POLL_MODE Event.AUTOSAMPLE @retval return (success/fail code, next state, result) """ next_state = None result = None if self._do_cmd_no_resp(Command.AUTOSAMPLE, None): self.announce_to_driver(DriverAnnouncement.STATE_CHANGE, msg="Starting auto sample") next_state = State.AUTOSAMPLE_MODE return (next_state, result) def _handler_poll_command(self, *args, **kwargs): """Handle State.POLL_MODE Event.COMMAND transition @param params Dict with "command" enum and "params" of the parameters to pass to the state @retval return (next state, result) @throw InstrumentProtocolException For invalid command """ next_state = None result = None result_vals = {} cmd = kwargs.get(KwargsKey.COMMAND, None) if cmd == Command.AUTOSAMPLE: result = self._fsm.on_event(Event.AUTOSAMPLE, *args, **kwargs) elif cmd == Command.RESET: result = self._fsm.on_event(Event.RESET, *args, **kwargs) elif cmd == Command.POLL: result = self._fsm.on_event(Event.SAMPLE, *args, **kwargs) else: raise InstrumentProtocolException(InstErrorCode.INVALID_COMMAND) mi_logger.debug("next: %s, result: %s", next_state, result) return (next_state, result) ################################################################### # Builders ################################################################### def _build_set_command(self, cmd, param, value): """ Build a command that is ready to send out to the instrument. Checks for valid parameter name, only handles one value at a time. @param cmd The command...in this case, Command.SET @param param The name of the parameter to set. From Parameter enum @param value The value to set for that parameter @retval Returns string ready for sending to instrument """ # Check to make sure all parameters are valid up front assert Parameter.has(param) assert cmd == Command.SET return "%s %s %s%s" % (Command.SET, param, value, self.eoln) def _build_param_fetch_command(self, cmd, param): """ Build a command to fetch the desired argument. @param cmd The command being used (Command.GET in this case) @param param The name of the parameter to fetch @retval Returns string ready for sending to instrument """ assert Parameter.has(param) return "%s %s%s" % (Command.GET, param, self.eoln) def _build_exec_command(self, cmd, param): """ Builder for simple commands @param cmd The command being used (Command.GET in this case) @param param The name of the parameter to fetch @retval Returns string ready for sending to instrument """ assert param == None return "%s%s" % (cmd, self.eoln) def _build_control_command(self, cmd, param): """ Send a quick control char command @param cmd The control character to send @param param Unused parameters @retval The string wit the complete command (1 char) """ return cmd ################################################################## # Response parsers ################################################################## def _parse_set_response(self, response, prompt): """Determine if a set was successful or not @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device """ mi_logger.debug("Parsing SET response of %s with prompt %s", response, prompt) if (prompt != Prompt.COMMAND) or (response == Error.INVALID_COMMAND): return InstErrorCode.SET_DEVICE_ERR def _parse_get_response(self, response, prompt): """ Parse the response from the instrument for a couple of different query responses. @param response The response string from the instrument @param prompt The prompt received from the instrument @retval return The numerical value of the parameter in the known units """ pass ################################################################### # Helpers ################################################################### def _wakeup(self, timeout): """There is no wakeup sequence for this instrument""" pass def _send_break(self, break_char, timeout=30): """Break out of autosample mode. Issue the proper sequence of stuff to get the device out of autosample mode. The character used will result in a different end state. Ctrl-S goes to poll mode, Ctrl-C goes to command mode. Ctrl-R resets. @param break_char The character to send to get out of autosample. Should be Event.STOP, Event.BREAK, or Event.RESET. @retval return True for success, Error for failure @throw InstrumentTimeoutException @throw InstrumentProtocolException """ if not ((break_char == Command.BREAK) or (break_char == Command.STOP) or (break_char == Command.RESET)): return False mi_logger.debug("Sending break char %s", break_char) # do the magic sequence of sending lots of characters really fast starttime = time.time() while True: self._do_cmd_no_resp(break_char, None) (prompt, result) = self._get_response(timeout) mi_logger.debug("Got prompt %s when trying to break", prompt) if prompt: return True else: if time.time() > starttime + timeout: raise InstrumentTimeoutException(InstErrorCode.TIMEOUT) # catch all return False def _got_data(self, data): """ The comms object fires this when data is received @param data The chunk of data that was received """ mi_logger.debug("*** Data received: %s, promptbuf: %s", data, self._promptbuf) CommandResponseInstrumentProtocol._got_data(self, data) # Only keep the latest characters in the prompt buffer. # if len(self._promptbuf)>7: # self._promptbuf = self._promptbuf[-7:] # If we are streaming, process the line buffer for samples. if self._fsm.get_current_state() == State.AUTOSAMPLE_MODE: if self.eoln in self._linebuf: lines = self._linebuf.split(self.eoln) self._linebuf = lines[-1] for line in lines: self.announce_to_driver(DriverAnnouncement.DATA_RECEIVED, msg=line)