class Protocol(InstrumentProtocol): """ Instrument protocol class Subclasses CommandResponseInstrumentProtocol """ __metaclass__ = META_LOGGER def __init__(self, driver_event): """ Protocol constructor. @param driver_event Driver process event callback. """ # Construct protocol superclass. InstrumentProtocol.__init__(self, driver_event) # Build protocol state machine. self._protocol_fsm = InstrumentFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT) # Add event handlers for protocol state machine. handlers = { ProtocolState.UNKNOWN: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.DISCOVER, self._handler_unknown_discover), ], ProtocolState.COMMAND: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.START_DIRECT, self._handler_command_start_direct), (ProtocolEvent.GET, self._handler_command_get), (ProtocolEvent.SET, self._handler_command_set), (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample), (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_start_poll), (ProtocolEvent.CALIBRATE, self._handler_command_start_calibrate), (ProtocolEvent.START_NAFION, self._handler_command_start_nafion_regen), (ProtocolEvent.START_ION, self._handler_command_start_ion_regen), (ProtocolEvent.ERROR, self._handler_error), (ProtocolEvent.POWEROFF, self._handler_command_poweroff), (ProtocolEvent.START_MANUAL, self._handler_command_start_manual), ], ProtocolState.AUTOSAMPLE: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_autosample_acquire_sample), (ProtocolEvent.STOP, self._handler_stop_generic), (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_stop_generic), (ProtocolEvent.ERROR, self._handler_error), ], ProtocolState.POLL: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.STOP, self._handler_stop_generic), (ProtocolEvent.ERROR, self._handler_error), ], ProtocolState.ERROR: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.CLEAR, self._handler_error_clear), ], ProtocolState.CALIBRATE: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.STOP, self._handler_stop_generic), (ProtocolEvent.ERROR, self._handler_error), ], ProtocolState.REGEN: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.STOP_REGEN, self._handler_stop_regen), (ProtocolEvent.REGEN_COMPLETE, self._handler_regen_complete), (ProtocolEvent.ERROR, self._handler_error), ], ProtocolState.DIRECT_ACCESS: [ (ProtocolEvent.ENTER, self._handler_direct_access_enter), (ProtocolEvent.EXIT, self._handler_direct_access_exit), (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct), (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct), ], ProtocolState.MANUAL_OVERRIDE: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.STOP_MANUAL, self._handler_manual_override_stop), (ProtocolEvent.GET_SLAVE_STATES, self._handler_manual_get_slave_states), ], } for state in handlers: for event, handler in handlers[state]: self._protocol_fsm.add_handler(state, event, handler) # 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() # 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._slave_protocols = {} self.initialize_scheduler() def _add_manual_override_handlers(self): for slave in self._slave_protocols: for event in self._slave_protocols[slave]._cmd_dict._cmd_dict: self._protocol_fsm.add_handler( ProtocolState.MANUAL_OVERRIDE, event, self._build_override_handler(slave, event)) def _build_override_handler(self, slave, event): log.debug('Building event handler for protocol: %s event: %s', slave, event) def inner(): return None, self._slave_protocols[slave]._protocol_fsm.on_event( event) return inner def register_slave_protocol(self, name, protocol): """ @param name: slave protocol name @param protocol: slave protocol instance @return: None """ self._slave_protocols[name] = protocol def _slave_protocol_event(self, event, *args, **kwargs): """ Handle an event from a slave protocol. @param event: event to be processed """ name = kwargs.get('name') if name is not None and name in self._slave_protocols: # only react to slave protocol events once we have transitioned out of unknown if self.get_current_state( ) != ProtocolState.UNKNOWN or event == DriverAsyncEvent.ERROR: if event == DriverAsyncEvent.STATE_CHANGE: self._react() elif event == DriverAsyncEvent.CONFIG_CHANGE: # do nothing, we handle this ourselves in set_param pass else: # pass the event up to the instrument agent log.debug( 'Passing event up to the Instrument agent: %r %r %r', event, args, kwargs) self._driver_event(event, *args) def _build_param_dict(self): """ Populate the parameter dictionary with parameters. For each parameter key, add match string, match lambda function, and value formatting function for set commands. """ self._param_dict.add( Parameter.SAMPLE_INTERVAL, '', None, int, type=ParameterDictType.INT, display_name='Autosample Interval', description= 'Interval between sample starts during autosample state', units=Units.SECOND) def _build_command_dict(self): """ Populate the command dictionary with commands. """ self._cmd_dict.add(Capability.ACQUIRE_SAMPLE, display_name="Acquire Sample") self._cmd_dict.add(Capability.START_AUTOSAMPLE, display_name="Start Autosample") self._cmd_dict.add(Capability.CALIBRATE, display_name="Acquire Calibration Samples") self._cmd_dict.add(Capability.START_ION, display_name="Start Ion Chamber Regeneration") self._cmd_dict.add(Capability.START_NAFION, display_name="Start Nafion Regeneration") self._cmd_dict.add(Capability.STOP_REGEN, display_name="Stop Current Regeneration") self._cmd_dict.add(Capability.STOP_AUTOSAMPLE, display_name="Stop Autosample") self._cmd_dict.add(Capability.POWEROFF, display_name='Low Power State') self._cmd_dict.add(Capability.GET_SLAVE_STATES, display_name='Get Slave States') self._cmd_dict.add(Capability.DISCOVER, display_name='Discover') def _build_driver_dict(self): """ Populate the driver dictionary with options """ self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False) def _react(self): """ Determine if an action is necessary based on the states of the slave protocols. (MCU STATE, TURBO STATE, RGA STATE) : (TARGET, EVENT) The specified event will be sent to the specified target. """ state = self.get_current_state() slave_states = self._get_slave_states() if MASSP_STATE_ERROR in slave_states: return self._error() if state == ProtocolState.REGEN and slave_states[ 0] == ProtocolState.COMMAND: self._async_raise_fsm_event(ProtocolEvent.REGEN_COMPLETE) # these actions are only applicable in POLL, AUTOSAMPLE or CALIBRATE states if state not in [ ProtocolState.POLL, ProtocolState.AUTOSAMPLE, ProtocolState.CALIBRATE ]: return mps = mcu.ProtocolState tps = turbo.ProtocolState rps = rga.ProtocolState action_map = { # Waiting Turbo (RGA is off) (mps.WAITING_TURBO, tps.COMMAND, rps.COMMAND): (TURBO, turbo.Capability.START_TURBO), (mps.WAITING_TURBO, tps.AT_SPEED, rps.COMMAND): (MCU, mcu.Capability.START2), # Waiting RGA (mps.WAITING_RGA, tps.AT_SPEED, rps.SCAN): (MCU, mcu.Capability.SAMPLE), (mps.WAITING_RGA, tps.AT_SPEED, rps.COMMAND): (RGA, rga.Capability.START_SCAN), (mps.WAITING_RGA, tps.COMMAND, rps.SCAN): (RGA, rga.Capability.STOP_SCAN), # this should never happen! (mps.WAITING_RGA, tps.COMMAND, rps.COMMAND): (MCU, mcu.Capability.STANDBY), # this should never happen! # Stopping (mps.STOPPING, tps.AT_SPEED, rps.SCAN): (RGA, rga.Capability.STOP_SCAN), (mps.STOPPING, tps.AT_SPEED, rps.COMMAND): (TURBO, turbo.Capability.STOP_TURBO), (mps.STOPPING, tps.COMMAND, rps.SCAN): (RGA, rga.Capability.STOP_SCAN), # this should never happen! (mps.STOPPING, tps.COMMAND, rps.COMMAND): (MCU, mcu.Capability.STANDBY), } action = action_map.get(self._get_slave_states()) if action is not None: if not isinstance(action, list): action = [action] # iterate through the action list, sending the events to the targets # if we are in POLL or CALIBRATE and we see a STANDBY event, return this driver to COMMAND. for target, command in action: if command == mcu.Capability.SAMPLE and state == ProtocolState.CALIBRATE: command = mcu.Capability.CALIBRATE if command == mcu.Capability.STANDBY and state in [ ProtocolState.CALIBRATE, ProtocolState.POLL ]: self._send_event_to_slave(target, command) self._async_raise_fsm_event(ProtocolEvent.STOP) else: self._send_event_to_slave(target, command) return action def _error(self): """ Handle error state in slave protocol """ state = self.get_current_state() slave_states = self._get_slave_states() # if we are not currently in the error state, make the transition if state != ProtocolState.ERROR: self._async_raise_fsm_event(ProtocolEvent.ERROR) mcu_state, turbo_state, rga_state = slave_states # before we do anything else, the RGA must be stopped. if rga_state not in [ rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR ]: self._send_event_to_slave(RGA, rga.ProtocolEvent.STOP_SCAN) # RGA must be in COMMAND or ERROR, the TURBO must be stopped. elif turbo_state not in [ turbo.ProtocolState.COMMAND, turbo.ProtocolState.SPINNING_DOWN ]: self._send_event_to_slave(TURBO, turbo.ProtocolEvent.STOP_TURBO) # Turbo and RGA must be in COMMAND or ERROR, stop the MCU elif mcu_state != mcu.ProtocolState.COMMAND: self._send_event_to_slave(MCU, mcu.ProtocolEvent.STANDBY) def _got_chunk(self, chunk): """ This driver has no chunker... """ def _filter_capabilities(self, events): """ Return a list of currently available capabilities. @param events: Events to be filtered @return: list of events which are also capabilities """ return [ x for x in events if Capability.has(x) or mcu.Capability.has(x) or turbo.Capability.has(x) or rga.Capability.has(x) ] def _get_slave_states(self): """ Retrieve the current protocol state from each of the slave protocols and return them as a tuple. """ return ( self._slave_protocols[MCU].get_current_state(), self._slave_protocols[TURBO].get_current_state(), self._slave_protocols[RGA].get_current_state(), ) def _send_event_to_all(self, event): """ Send the same event to all slave protocols. @return: List of (name, result) for a slave protocols """ return [(name, slave._protocol_fsm.on_event(event)) for name, slave in self._slave_protocols.items()] def _send_event_to_slave(self, name, event): """ Send an event to a specific protocol @param name: Name of slave protocol @param event: Event to be sent """ slave_protocol = self._slave_protocols.get(name) if slave_protocol is None: raise InstrumentProtocolException( 'Attempted to send event to non-existent protocol: %s' % name) slave_protocol._async_raise_fsm_event(event) def _send_massp_direct_access(self, command): """ Handle a direct access command. Driver expects direct access commands to specify the target using the following format: target:command It then routes the command to the appropriate slave protocol. @param command: Direct access command received """ err_string = 'Invalid command. Command must be in the following format: "target:command' + NEWLINE + \ 'Valid targets are: %r' % self._slave_protocols.keys() try: target, command = command.split(DA_COMMAND_DELIMITER, 1) target = target.lower() except ValueError: target = None log.debug('_do_cmd_direct - target: %s command: %r', target, command) if target not in self._slave_protocols: self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, err_string) else: self._slave_protocols[target]._protocol_fsm.on_event( ProtocolEvent.EXECUTE_DIRECT, command) def _set_params(self, *args, **kwargs): """ Set one or more parameters. This method will determine where the parameter actually resides and forward it to the appropriate parameter dictionary based on name. @param args: arglist which must contain a parameter dictionary @throws InstrumentParameterException """ params = args[0] if not isinstance(params, dict): raise InstrumentParameterException( 'Attempted to set parameters with a non-dictionary argument') _, old_config = self._handler_command_get([Parameter.ALL]) temp_dict = {} for key in params: split_key = key.split('_', 1) if len(split_key) == 1: raise InstrumentParameterException( 'Missing target in MASSP parameter: %s' % key) target = split_key[0] if not target in self._slave_protocols: # this is a master driver parameter, set it here if key in self._param_dict.get_keys(): log.debug("Setting value for %s to %s", key, params[key]) self._param_dict.set_value(key, params[key]) else: raise InstrumentParameterException( 'Invalid key in SET action: %s' % key) else: temp_dict.setdefault(target, {})[key] = params[key] # set parameters for slave protocols for name in temp_dict: if name in self._slave_protocols: self._slave_protocols[name]._set_params(temp_dict[name]) else: # how did we get here? This should never happen, but raise an exception if it does. raise InstrumentParameterException( 'Invalid key(s) in SET action: %r' % temp_dict[name]) _, new_config = self._handler_command_get([Parameter.ALL]) if not new_config == old_config: self._driver_event(DriverAsyncEvent.CONFIG_CHANGE) def set_init_params(self, config): """ Set initial parameters. Parameters are forwarded to the appropriate parameter dictionary based on name. @param config: Init param config to be handled """ temp_dict = {} self._startup_config = config config = config.get(DriverConfigKey.PARAMETERS, {}) for key in config: target, _ = key.split('_', 1) if not target in self._slave_protocols: # master driver parameter log.debug("Setting init value for %s to %s", key, config[key]) self._param_dict.set_init_value(key, config[key]) else: temp_dict.setdefault(target, {})[key] = config[key] for name in temp_dict: if name in self._slave_protocols: self._slave_protocols[name].set_init_params( {DriverConfigKey.PARAMETERS: temp_dict[name]}) else: # how did we get here? This should never happen, but raise an exception if it does. raise InstrumentParameterException( 'Invalid key(s) in INIT PARAMS action: %r' % temp_dict[name]) def get_config_metadata_dict(self): """ See base class for full description. This method is overridden to retrieve the parameter dictionary from each slave protocol and merge them. @return: dictionary containing driver metadata """ log.debug("Getting metadata dict from protocol...") return_dict = { ConfigMetadataKey.DRIVER: self._driver_dict.generate_dict(), ConfigMetadataKey.COMMANDS: self._cmd_dict.generate_dict(), ConfigMetadataKey.PARAMETERS: self._param_dict.generate_dict() } for protocol in self._slave_protocols.values(): return_dict[ConfigMetadataKey.PARAMETERS].update( protocol._param_dict.generate_dict()) return_dict[ConfigMetadataKey.COMMANDS].update( protocol._cmd_dict.generate_dict()) return return_dict def get_resource_capabilities(self, current_state=True): """ Overrides base class to include slave protocol parameters @param current_state: Boolean indicating whether we should return only the current state events @return: (resource_commands, resource_parameters) """ res_cmds = self._protocol_fsm.get_events(current_state) res_cmds = self._filter_capabilities(res_cmds) res_params = self._param_dict.get_keys() for protocol in self._slave_protocols.values(): res_params.extend(protocol._param_dict.get_keys()) return res_cmds, res_params def _build_scheduler(self): """ Build a scheduler for periodic status updates """ job_name = ScheduledJob.ACQUIRE_SAMPLE config = { DriverConfigKey.SCHEDULER: { job_name: { DriverSchedulerConfigKey.TRIGGER: { DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.INTERVAL, DriverSchedulerConfigKey.SECONDS: self._param_dict.get(Parameter.SAMPLE_INTERVAL) }, } } } self.set_init_params(config) self._add_scheduler_event(ScheduledJob.ACQUIRE_SAMPLE, ProtocolEvent.ACQUIRE_SAMPLE) def _delete_scheduler(self): """ Remove the autosample schedule. """ try: self._remove_scheduler(ScheduledJob.ACQUIRE_SAMPLE) except KeyError: log.info('Failed to remove scheduled job for ACQUIRE_SAMPLE') ######################################################################## # Generic handlers. ######################################################################## def _handler_generic_enter(self, *args, **kwargs): """ Generic enter handler, raise STATE CHANGE """ if self.get_current_state() != ProtocolState.UNKNOWN: self._init_params() self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_generic_exit(self, *args, **kwargs): """ Generic exit handler, do nothing. """ def _handler_stop_generic(self, *args, **kwargs): """ Generic stop method to return to COMMAND (via POLL if appropriate) @return next_state, (next_agent_state, None) """ self._delete_scheduler() next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND # check if we are in autosample AND currently taking a sample, if so, move to POLL # otherwise go back to COMMAND. if self.get_current_state() == ProtocolState.AUTOSAMPLE: if self._get_slave_states() != (ProtocolState.COMMAND, ProtocolState.COMMAND, ProtocolState.COMMAND): next_state = ProtocolState.POLL next_agent_state = ResourceAgentState.BUSY # notify the agent we have changed states self._async_agent_state_change(next_agent_state) return next_state, (next_agent_state, None) ######################################################################## # Unknown handlers. ######################################################################## def _handler_unknown_discover(self, *args, **kwargs): """ Discover current state @return (next_state, next_agent_state) """ result = self._send_event_to_all(ProtocolEvent.DISCOVER) log.debug('_handler_unknown_discover -- send DISCOVER to all: %r', result) target_state = (ProtocolState.COMMAND, ProtocolState.COMMAND, ProtocolState.COMMAND) success = False # wait for the slave protocols to discover for attempt in xrange(5): slave_states = self._get_slave_states() if slave_states == target_state: success = True break time.sleep(1) if not success: return ProtocolState.ERROR, ResourceAgentState.IDLE return ProtocolState.COMMAND, ResourceAgentState.IDLE ######################################################################## # Command handlers. ######################################################################## def _handler_command_get(self, *args, **kwargs): """ Get parameter. Query this protocol plus all slave protocols. @param args: arglist which should contain a list of parameters to get @return None, results """ params = args[0] if not isinstance(params, list): params = [params] temp_dict = {} result_dict = {} # request is for all parameters, send get(ALL) to each protocol then combine the results. if Parameter.ALL in params: params = [Parameter.ALL] _, result = self._handler_get(params, **kwargs) result_dict.update(result) for protocol in self._slave_protocols.values(): _, result = protocol._handler_get(params, **kwargs) result_dict.update(result) # request is for specific parameters. Determine which protocol should service each, # call the appropriate _handler_get and combine the results else: for key in params: log.debug('about to split: %s', key) target, _ = key.split('_', 1) temp_dict.setdefault(target, []).append(key) for key in temp_dict: if key == MASTER: _, result = self._handler_get(params, **kwargs) else: if key in self._slave_protocols: _, result = self._slave_protocols[key]._handler_get( params, **kwargs) else: raise InstrumentParameterException( 'Invalid key(s) in GET action: %r' % temp_dict[key]) result_dict.update(result) return None, result_dict def _handler_command_set(self, *args, **kwargs): """ Set parameter, just pass through to _set_params, which knows how to set the params in the slave protocols. """ self._set_params(*args, **kwargs) return None, None def _handler_command_start_direct(self): """ Start direct access @return next_state, (next_agent_state, result) """ return ProtocolState.DIRECT_ACCESS, (ResourceAgentState.DIRECT_ACCESS, None) def _handler_command_start_autosample(self): """ Move my FSM to autosample and start the sample sequence by sending START1 to the MCU. Create the scheduler to automatically start the next sample sequence @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.START1) self._build_scheduler() return ProtocolState.AUTOSAMPLE, (ResourceAgentState.STREAMING, None) def _handler_command_start_poll(self): """ Move my FSM to poll and start the sample sequence by sending START1 to the MCU @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.START1) return ProtocolState.POLL, (ResourceAgentState.BUSY, None) def _handler_command_start_calibrate(self): """ Move my FSM to calibrate and start the calibrate sequence by sending START1 to the MCU @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.START1) return ProtocolState.CALIBRATE, (ResourceAgentState.BUSY, None) def _handler_command_start_nafion_regen(self): """ Move my FSM to NAFION_REGEN and send NAFION_REGEN to the MCU @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.NAFREG) return ProtocolState.REGEN, (ResourceAgentState.BUSY, None) def _handler_command_start_ion_regen(self): """ Move my FSM to ION_REGEN and send ION_REGEN to the MCU @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.IONREG) return ProtocolState.REGEN, (ResourceAgentState.BUSY, None) def _handler_command_poweroff(self): """ Send POWEROFF to the MCU @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.POWEROFF) return None, (None, None) def _handler_command_start_manual(self): """ Move FSM to MANUAL OVERRIDE state @return next_state, (next_agent_state, result) """ return ProtocolState.MANUAL_OVERRIDE, (ResourceAgentState.COMMAND, None) ######################################################################## # Error handlers. ######################################################################## def _handler_error(self): """ @return next_state, next_agent_state """ return ProtocolState.ERROR, ResourceAgentState.BUSY def _handler_error_clear(self): """ Send the CLEAR event to any slave protocol in the error state and return this driver to COMMAND @return next_state, (next_agent_state, result) """ for protocol in self._slave_protocols: state = protocol.get_current_state() if state == MASSP_STATE_ERROR: # do this synchronously, to allow each slave protocol to complete the CLEAR action # before transitioning states. protocol._protocol_fsm.on_event(ProtocolEvent.CLEAR) return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None) ######################################################################## # Autosample handlers. ######################################################################## def _handler_autosample_acquire_sample(self): """ Fire off a sample sequence while in the autosample state. @return None, None @throws InstrumentProtocolException """ slave_states = self._get_slave_states() # verify the MCU is not already in a sequence if slave_states[0] == ProtocolState.COMMAND: result = self._send_event_to_slave(MCU, mcu.Capability.START1) else: raise InstrumentProtocolException( "Attempted to acquire sample while sampling") return None, None ######################################################################## # Direct access handlers. ######################################################################## def _handler_direct_access_enter(self, *args, **kwargs): """ Enter direct access state. Forward to all slave protocols. """ self._send_event_to_all(ProtocolEvent.START_DIRECT) # 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. Check slave protocol states and verify they all return to COMMAND, otherwise raise InstrumentProtocolException. @throws InstrumentProtocolException """ for attempt in range(DA_EXIT_MAX_RETRIES): slave_states = self._get_slave_states() if ProtocolState.DIRECT_ACCESS in slave_states: log.error( 'Slave protocol failed to return to command, attempt %d', attempt) time.sleep(1) else: return raise InstrumentProtocolException( 'Slave protocol never returned to command from DA.') def _handler_direct_access_execute_direct(self, data): """ Execute a direct access command. For MASSP, this means passing the actual command to the correct slave protocol. This is handled by _send_massp_direct_access. @return next_state, (next_agent_state, result) """ self._send_massp_direct_access(data) # add sent command to list for 'echo' filtering in callback self._sent_cmds.append(data) return None, (None, None) def _handler_direct_access_stop_direct(self): """ @return next_state, (next_agent_state, result) """ self._send_event_to_all(ProtocolEvent.STOP_DIRECT) return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None) ######################################################################## # Regen handlers. ######################################################################## def _handler_stop_regen(self): """ Abort the current regeneration sequence, return to COMMAND @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.STANDBY) return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None) def _handler_regen_complete(self): """ Regeneration sequence is complete, return to COMMAND @return next_state, (next_agent_state, result) """ self._async_agent_state_change(ResourceAgentState.COMMAND) return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None) def _handler_manual_override_stop(self): """ Exit manual override. Attempt to bring the slave drivers back to COMMAND. """ mcu_state, turbo_state, rga_state = self._get_slave_states() if rga_state == rga.ProtocolState.SCAN: self._slave_protocols[RGA]._protocol_fsm.on_event( rga.Capability.STOP_SCAN) if turbo_state == turbo.ProtocolState.AT_SPEED: self._slave_protocols[TURBO]._protocol_fsm.on_event( turbo.Capability.STOP_TURBO) while rga_state not in [rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR] or \ turbo_state not in [turbo.ProtocolState.COMMAND, turbo.ProtocolState.ERROR]: time.sleep(.1) mcu_state, turbo_state, rga_state = self._get_slave_states() if mcu_state != mcu.ProtocolState.COMMAND: self._slave_protocols[MCU]._protocol_fsm.on_event( mcu.Capability.STANDBY) return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None) def _handler_manual_get_slave_states(self): """ Get the slave states and return them to the user @return: next_state, (next_agent_state, result) """ mcu_state, turbo_state, rga_state = self._get_slave_states() return None, (None, { MCU: mcu_state, RGA: rga_state, TURBO: turbo_state })
class Protocol(InstrumentProtocol): """ Instrument protocol class Subclasses CommandResponseInstrumentProtocol """ __metaclass__ = META_LOGGER def __init__(self, driver_event): """ Protocol constructor. @param driver_event Driver process event callback. """ # Construct protocol superclass. InstrumentProtocol.__init__(self, driver_event) # Build protocol state machine. self._protocol_fsm = InstrumentFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT) # Add event handlers for protocol state machine. handlers = { ProtocolState.UNKNOWN: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.DISCOVER, self._handler_unknown_discover), ], ProtocolState.COMMAND: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.START_DIRECT, self._handler_command_start_direct), (ProtocolEvent.GET, self._handler_command_get), (ProtocolEvent.SET, self._handler_command_set), (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample), (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_start_poll), (ProtocolEvent.CALIBRATE, self._handler_command_start_calibrate), (ProtocolEvent.START_NAFION, self._handler_command_start_nafion_regen), (ProtocolEvent.START_ION, self._handler_command_start_ion_regen), (ProtocolEvent.ERROR, self._handler_error), (ProtocolEvent.POWEROFF, self._handler_command_poweroff), (ProtocolEvent.START_MANUAL, self._handler_command_start_manual), ], ProtocolState.AUTOSAMPLE: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_autosample_acquire_sample), (ProtocolEvent.STOP, self._handler_stop_generic), (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_stop_generic), (ProtocolEvent.ERROR, self._handler_error), ], ProtocolState.POLL: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.STOP, self._handler_stop_generic), (ProtocolEvent.ERROR, self._handler_error), ], ProtocolState.ERROR: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.CLEAR, self._handler_error_clear), ], ProtocolState.CALIBRATE: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.STOP, self._handler_stop_generic), (ProtocolEvent.ERROR, self._handler_error), ], ProtocolState.REGEN: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.STOP_REGEN, self._handler_stop_regen), (ProtocolEvent.REGEN_COMPLETE, self._handler_regen_complete), (ProtocolEvent.ERROR, self._handler_error), ], ProtocolState.DIRECT_ACCESS: [ (ProtocolEvent.ENTER, self._handler_direct_access_enter), (ProtocolEvent.EXIT, self._handler_direct_access_exit), (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct), (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct), ], ProtocolState.MANUAL_OVERRIDE: [ (ProtocolEvent.ENTER, self._handler_generic_enter), (ProtocolEvent.EXIT, self._handler_generic_exit), (ProtocolEvent.STOP_MANUAL, self._handler_manual_override_stop), (ProtocolEvent.GET_SLAVE_STATES, self._handler_manual_get_slave_states), ], } for state in handlers: for event, handler in handlers[state]: self._protocol_fsm.add_handler(state, event, handler) # 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() # 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._slave_protocols = {} self.initialize_scheduler() def _add_manual_override_handlers(self): for slave in self._slave_protocols: for event in self._slave_protocols[slave]._cmd_dict._cmd_dict: self._protocol_fsm.add_handler(ProtocolState.MANUAL_OVERRIDE, event, self._build_override_handler(slave, event)) def _build_override_handler(self, slave, event): log.debug('Building event handler for protocol: %s event: %s', slave, event) def inner(): return None, self._slave_protocols[slave]._protocol_fsm.on_event(event) return inner def register_slave_protocol(self, name, protocol): """ @param name: slave protocol name @param protocol: slave protocol instance @return: None """ self._slave_protocols[name] = protocol def _slave_protocol_event(self, event, *args, **kwargs): """ Handle an event from a slave protocol. @param event: event to be processed """ name = kwargs.get('name') if name is not None and name in self._slave_protocols: # only react to slave protocol events once we have transitioned out of unknown if self.get_current_state() != ProtocolState.UNKNOWN or event == DriverAsyncEvent.ERROR: if event == DriverAsyncEvent.STATE_CHANGE: self._react() elif event == DriverAsyncEvent.CONFIG_CHANGE: # do nothing, we handle this ourselves in set_param pass else: # pass the event up to the instrument agent log.debug('Passing event up to the Instrument agent: %r %r %r', event, args, kwargs) self._driver_event(event, *args) def _build_param_dict(self): """ Populate the parameter dictionary with parameters. For each parameter key, add match string, match lambda function, and value formatting function for set commands. """ self._param_dict.add(Parameter.SAMPLE_INTERVAL, '', None, int, type=ParameterDictType.INT, display_name='Autosample Interval', description='Interval between sample starts during autosample state', units=Units.SECOND) def _build_command_dict(self): """ Populate the command dictionary with commands. """ self._cmd_dict.add(Capability.ACQUIRE_SAMPLE, display_name="Acquire one sample") self._cmd_dict.add(Capability.START_AUTOSAMPLE, display_name="Start autosample") self._cmd_dict.add(Capability.CALIBRATE, display_name="Acquire calibration samples") self._cmd_dict.add(Capability.START_ION, display_name="Start the ion chamber regeneration sequence") self._cmd_dict.add(Capability.START_NAFION, display_name="Start the nafion regeneration sequence") self._cmd_dict.add(Capability.STOP_REGEN, display_name="Stop the current regeneration sequence") self._cmd_dict.add(Capability.STOP_AUTOSAMPLE, display_name="Stop autosample") self._cmd_dict.add(Capability.POWEROFF, display_name='Issue the "Power Off" command to the instrument') self._cmd_dict.add(Capability.GET_SLAVE_STATES, display_name='Report the states of the underlying slave protocols') def _build_driver_dict(self): """ Populate the driver dictionary with options """ self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False) def _react(self): """ Determine if an action is necessary based on the states of the slave protocols. (MCU STATE, TURBO STATE, RGA STATE) : (TARGET, EVENT) The specified event will be sent to the specified target. """ state = self.get_current_state() slave_states = self._get_slave_states() if MASSP_STATE_ERROR in slave_states: return self._error() if state == ProtocolState.REGEN and slave_states[0] == ProtocolState.COMMAND: self._async_raise_fsm_event(ProtocolEvent.REGEN_COMPLETE) # these actions are only applicable in POLL, AUTOSAMPLE or CALIBRATE states if state not in [ProtocolState.POLL, ProtocolState.AUTOSAMPLE, ProtocolState.CALIBRATE]: return mps = mcu.ProtocolState tps = turbo.ProtocolState rps = rga.ProtocolState action_map = { # Waiting Turbo (RGA is off) (mps.WAITING_TURBO, tps.COMMAND, rps.COMMAND): (TURBO, turbo.Capability.START_TURBO), (mps.WAITING_TURBO, tps.AT_SPEED, rps.COMMAND): (MCU, mcu.Capability.START2), # Waiting RGA (mps.WAITING_RGA, tps.AT_SPEED, rps.SCAN): (MCU, mcu.Capability.SAMPLE), (mps.WAITING_RGA, tps.AT_SPEED, rps.COMMAND): (RGA, rga.Capability.START_SCAN), (mps.WAITING_RGA, tps.COMMAND, rps.SCAN): (RGA, rga.Capability.STOP_SCAN), # this should never happen! (mps.WAITING_RGA, tps.COMMAND, rps.COMMAND): (MCU, mcu.Capability.STANDBY), # this should never happen! # Stopping (mps.STOPPING, tps.AT_SPEED, rps.SCAN): (RGA, rga.Capability.STOP_SCAN), (mps.STOPPING, tps.AT_SPEED, rps.COMMAND): (TURBO, turbo.Capability.STOP_TURBO), (mps.STOPPING, tps.COMMAND, rps.SCAN): (RGA, rga.Capability.STOP_SCAN), # this should never happen! (mps.STOPPING, tps.COMMAND, rps.COMMAND): (MCU, mcu.Capability.STANDBY), } action = action_map.get(self._get_slave_states()) if action is not None: if not isinstance(action, list): action = [action] # iterate through the action list, sending the events to the targets # if we are in POLL or CALIBRATE and we see a STANDBY event, return this driver to COMMAND. for target, command in action: if command == mcu.Capability.SAMPLE and state == ProtocolState.CALIBRATE: command = mcu.Capability.CALIBRATE if command == mcu.Capability.STANDBY and state in [ProtocolState.CALIBRATE, ProtocolState.POLL]: self._send_event_to_slave(target, command) self._async_raise_fsm_event(ProtocolEvent.STOP) else: self._send_event_to_slave(target, command) return action def _error(self): """ Handle error state in slave protocol """ state = self.get_current_state() slave_states = self._get_slave_states() # if we are not currently in the error state, make the transition if state != ProtocolState.ERROR: self._async_raise_fsm_event(ProtocolEvent.ERROR) mcu_state, turbo_state, rga_state = slave_states # before we do anything else, the RGA must be stopped. if rga_state not in [rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR]: self._send_event_to_slave(RGA, rga.ProtocolEvent.STOP_SCAN) # RGA must be in COMMAND or ERROR, the TURBO must be stopped. elif turbo_state not in [turbo.ProtocolState.COMMAND, turbo.ProtocolState.SPINNING_DOWN]: self._send_event_to_slave(TURBO, turbo.ProtocolEvent.STOP_TURBO) # Turbo and RGA must be in COMMAND or ERROR, stop the MCU elif mcu_state != mcu.ProtocolState.COMMAND: self._send_event_to_slave(MCU, mcu.ProtocolEvent.STANDBY) def _got_chunk(self, chunk): """ This driver has no chunker... """ def _filter_capabilities(self, events): """ Return a list of currently available capabilities. @param events: Events to be filtered @return: list of events which are also capabilities """ return [x for x in events if Capability.has(x) or mcu.Capability.has(x) or turbo.Capability.has(x) or rga.Capability.has(x)] def _get_slave_states(self): """ Retrieve the current protocol state from each of the slave protocols and return them as a tuple. """ return ( self._slave_protocols[MCU].get_current_state(), self._slave_protocols[TURBO].get_current_state(), self._slave_protocols[RGA].get_current_state(), ) def _send_event_to_all(self, event): """ Send the same event to all slave protocols. @return: List of (name, result) for a slave protocols """ return [(name, slave._protocol_fsm.on_event(event)) for name, slave in self._slave_protocols.items()] def _send_event_to_slave(self, name, event): """ Send an event to a specific protocol @param name: Name of slave protocol @param event: Event to be sent """ slave_protocol = self._slave_protocols.get(name) if slave_protocol is None: raise InstrumentProtocolException('Attempted to send event to non-existent protocol: %s' % name) slave_protocol._async_raise_fsm_event(event) def _send_massp_direct_access(self, command): """ Handle a direct access command. Driver expects direct access commands to specify the target using the following format: target:command It then routes the command to the appropriate slave protocol. @param command: Direct access command received """ err_string = 'Invalid command. Command must be in the following format: "target:command' + NEWLINE + \ 'Valid targets are: %r' % self._slave_protocols.keys() try: target, command = command.split(DA_COMMAND_DELIMITER, 1) target = target.lower() except ValueError: target = None log.debug('_do_cmd_direct - target: %s command: %r', target, command) if target not in self._slave_protocols: self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, err_string) else: self._slave_protocols[target]._protocol_fsm.on_event(ProtocolEvent.EXECUTE_DIRECT, command) def _set_params(self, *args, **kwargs): """ Set one or more parameters. This method will determine where the parameter actually resides and forward it to the appropriate parameter dictionary based on name. @param args: arglist which must contain a parameter dictionary @throws InstrumentParameterException """ params = args[0] if not isinstance(params, dict): raise InstrumentParameterException('Attempted to set parameters with a non-dictionary argument') _, old_config = self._handler_command_get([Parameter.ALL]) temp_dict = {} for key in params: split_key = key.split('_', 1) if len(split_key) == 1: raise InstrumentParameterException('Missing target in MASSP parameter: %s' % key) target = split_key[0] if not target in self._slave_protocols: # this is a master driver parameter, set it here if key in self._param_dict.get_keys(): log.debug("Setting value for %s to %s", key, params[key]) self._param_dict.set_value(key, params[key]) else: raise InstrumentParameterException('Invalid key in SET action: %s' % key) else: temp_dict.setdefault(target, {})[key] = params[key] # set parameters for slave protocols for name in temp_dict: if name in self._slave_protocols: self._slave_protocols[name]._set_params(temp_dict[name]) else: # how did we get here? This should never happen, but raise an exception if it does. raise InstrumentParameterException('Invalid key(s) in SET action: %r' % temp_dict[name]) _, new_config = self._handler_command_get([Parameter.ALL]) if not new_config == old_config: self._driver_event(DriverAsyncEvent.CONFIG_CHANGE) def set_init_params(self, config): """ Set initial parameters. Parameters are forwarded to the appropriate parameter dictionary based on name. @param config: Init param config to be handled """ temp_dict = {} self._startup_config = config config = config.get(DriverConfigKey.PARAMETERS, {}) for key in config: target, _ = key.split('_', 1) if not target in self._slave_protocols: # master driver parameter log.debug("Setting init value for %s to %s", key, config[key]) self._param_dict.set_init_value(key, config[key]) else: temp_dict.setdefault(target, {})[key] = config[key] for name in temp_dict: if name in self._slave_protocols: self._slave_protocols[name].set_init_params({DriverConfigKey.PARAMETERS: temp_dict[name]}) else: # how did we get here? This should never happen, but raise an exception if it does. raise InstrumentParameterException('Invalid key(s) in INIT PARAMS action: %r' % temp_dict[name]) def get_config_metadata_dict(self): """ See base class for full description. This method is overridden to retrieve the parameter dictionary from each slave protocol and merge them. @return: dictionary containing driver metadata """ log.debug("Getting metadata dict from protocol...") return_dict = {ConfigMetadataKey.DRIVER: self._driver_dict.generate_dict(), ConfigMetadataKey.COMMANDS: self._cmd_dict.generate_dict(), ConfigMetadataKey.PARAMETERS: self._param_dict.generate_dict()} for protocol in self._slave_protocols.values(): return_dict[ConfigMetadataKey.PARAMETERS].update(protocol._param_dict.generate_dict()) return_dict[ConfigMetadataKey.COMMANDS].update(protocol._cmd_dict.generate_dict()) return return_dict def get_resource_capabilities(self, current_state=True): """ Overrides base class to include slave protocol parameters @param current_state: Boolean indicating whether we should return only the current state events @return: (resource_commands, resource_parameters) """ res_cmds = self._protocol_fsm.get_events(current_state) res_cmds = self._filter_capabilities(res_cmds) res_params = self._param_dict.get_keys() for protocol in self._slave_protocols.values(): res_params.extend(protocol._param_dict.get_keys()) return res_cmds, res_params def _build_scheduler(self): """ Build a scheduler for periodic status updates """ job_name = ScheduledJob.ACQUIRE_SAMPLE config = { DriverConfigKey.SCHEDULER: { job_name: { DriverSchedulerConfigKey.TRIGGER: { DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.INTERVAL, DriverSchedulerConfigKey.SECONDS: self._param_dict.get(Parameter.SAMPLE_INTERVAL) }, } } } self.set_init_params(config) self._add_scheduler_event(ScheduledJob.ACQUIRE_SAMPLE, ProtocolEvent.ACQUIRE_SAMPLE) def _delete_scheduler(self): """ Remove the autosample schedule. """ try: self._remove_scheduler(ScheduledJob.ACQUIRE_SAMPLE) except KeyError: log.info('Failed to remove scheduled job for ACQUIRE_SAMPLE') ######################################################################## # Generic handlers. ######################################################################## def _handler_generic_enter(self, *args, **kwargs): """ Generic enter handler, raise STATE CHANGE """ if self.get_current_state() != ProtocolState.UNKNOWN: self._init_params() self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_generic_exit(self, *args, **kwargs): """ Generic exit handler, do nothing. """ def _handler_stop_generic(self, *args, **kwargs): """ Generic stop method to return to COMMAND (via POLL if appropriate) @return next_state, (next_agent_state, None) """ self._delete_scheduler() next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND # check if we are in autosample AND currently taking a sample, if so, move to POLL # otherwise go back to COMMAND. if self.get_current_state() == ProtocolState.AUTOSAMPLE: if self._get_slave_states() != (ProtocolState.COMMAND, ProtocolState.COMMAND, ProtocolState.COMMAND): next_state = ProtocolState.POLL next_agent_state = ResourceAgentState.BUSY # notify the agent we have changed states self._async_agent_state_change(next_agent_state) return next_state, (next_agent_state, None) ######################################################################## # Unknown handlers. ######################################################################## def _handler_unknown_discover(self, *args, **kwargs): """ Discover current state @return (next_state, next_agent_state) """ result = self._send_event_to_all(ProtocolEvent.DISCOVER) log.debug('_handler_unknown_discover -- send DISCOVER to all: %r', result) target_state = (ProtocolState.COMMAND, ProtocolState.COMMAND, ProtocolState.COMMAND) success = False # wait for the slave protocols to discover for attempt in xrange(5): slave_states = self._get_slave_states() if slave_states == target_state: success = True break time.sleep(1) if not success: return ProtocolState.ERROR, ResourceAgentState.IDLE return ProtocolState.COMMAND, ResourceAgentState.IDLE ######################################################################## # Command handlers. ######################################################################## def _handler_command_get(self, *args, **kwargs): """ Get parameter. Query this protocol plus all slave protocols. @param args: arglist which should contain a list of parameters to get @return None, results """ params = args[0] if not isinstance(params, list): params = [params] temp_dict = {} result_dict = {} # request is for all parameters, send get(ALL) to each protocol then combine the results. if Parameter.ALL in params: params = [Parameter.ALL] _, result = self._handler_get(params, **kwargs) result_dict.update(result) for protocol in self._slave_protocols.values(): _, result = protocol._handler_get(params, **kwargs) result_dict.update(result) # request is for specific parameters. Determine which protocol should service each, # call the appropriate _handler_get and combine the results else: for key in params: log.debug('about to split: %s', key) target, _ = key.split('_', 1) temp_dict.setdefault(target, []).append(key) for key in temp_dict: if key == MASTER: _, result = self._handler_get(params, **kwargs) else: if key in self._slave_protocols: _, result = self._slave_protocols[key]._handler_get(params, **kwargs) else: raise InstrumentParameterException('Invalid key(s) in GET action: %r' % temp_dict[key]) result_dict.update(result) return None, result_dict def _handler_command_set(self, *args, **kwargs): """ Set parameter, just pass through to _set_params, which knows how to set the params in the slave protocols. """ self._set_params(*args, **kwargs) return None, None def _handler_command_start_direct(self): """ Start direct access @return next_state, (next_agent_state, result) """ return ProtocolState.DIRECT_ACCESS, (ResourceAgentState.DIRECT_ACCESS, None) def _handler_command_start_autosample(self): """ Move my FSM to autosample and start the sample sequence by sending START1 to the MCU. Create the scheduler to automatically start the next sample sequence @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.START1) self._build_scheduler() return ProtocolState.AUTOSAMPLE, (ResourceAgentState.STREAMING, None) def _handler_command_start_poll(self): """ Move my FSM to poll and start the sample sequence by sending START1 to the MCU @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.START1) return ProtocolState.POLL, (ResourceAgentState.BUSY, None) def _handler_command_start_calibrate(self): """ Move my FSM to calibrate and start the calibrate sequence by sending START1 to the MCU @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.START1) return ProtocolState.CALIBRATE, (ResourceAgentState.BUSY, None) def _handler_command_start_nafion_regen(self): """ Move my FSM to NAFION_REGEN and send NAFION_REGEN to the MCU @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.NAFREG) return ProtocolState.REGEN, (ResourceAgentState.BUSY, None) def _handler_command_start_ion_regen(self): """ Move my FSM to ION_REGEN and send ION_REGEN to the MCU @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.IONREG) return ProtocolState.REGEN, (ResourceAgentState.BUSY, None) def _handler_command_poweroff(self): """ Send POWEROFF to the MCU @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.POWEROFF) return None, (None, None) def _handler_command_start_manual(self): """ Move FSM to MANUAL OVERRIDE state @return next_state, (next_agent_state, result) """ return ProtocolState.MANUAL_OVERRIDE, (ResourceAgentState.COMMAND, None) ######################################################################## # Error handlers. ######################################################################## def _handler_error(self): """ @return next_state, next_agent_state """ return ProtocolState.ERROR, ResourceAgentState.BUSY def _handler_error_clear(self): """ Send the CLEAR event to any slave protocol in the error state and return this driver to COMMAND @return next_state, (next_agent_state, result) """ for protocol in self._slave_protocols: state = protocol.get_current_state() if state == MASSP_STATE_ERROR: # do this synchronously, to allow each slave protocol to complete the CLEAR action # before transitioning states. protocol._protocol_fsm.on_event(ProtocolEvent.CLEAR) return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None) ######################################################################## # Autosample handlers. ######################################################################## def _handler_autosample_acquire_sample(self): """ Fire off a sample sequence while in the autosample state. @return None, None @throws InstrumentProtocolException """ slave_states = self._get_slave_states() # verify the MCU is not already in a sequence if slave_states[0] == ProtocolState.COMMAND: result = self._send_event_to_slave(MCU, mcu.Capability.START1) else: raise InstrumentProtocolException("Attempted to acquire sample while sampling") return None, None ######################################################################## # Direct access handlers. ######################################################################## def _handler_direct_access_enter(self, *args, **kwargs): """ Enter direct access state. Forward to all slave protocols. """ self._send_event_to_all(ProtocolEvent.START_DIRECT) # 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. Check slave protocol states and verify they all return to COMMAND, otherwise raise InstrumentProtocolException. @throws InstrumentProtocolException """ for attempt in range(DA_EXIT_MAX_RETRIES): slave_states = self._get_slave_states() if ProtocolState.DIRECT_ACCESS in slave_states: log.error('Slave protocol failed to return to command, attempt %d', attempt) time.sleep(1) else: return raise InstrumentProtocolException('Slave protocol never returned to command from DA.') def _handler_direct_access_execute_direct(self, data): """ Execute a direct access command. For MASSP, this means passing the actual command to the correct slave protocol. This is handled by _send_massp_direct_access. @return next_state, (next_agent_state, result) """ self._send_massp_direct_access(data) # add sent command to list for 'echo' filtering in callback self._sent_cmds.append(data) return None, (None, None) def _handler_direct_access_stop_direct(self): """ @return next_state, (next_agent_state, result) """ self._send_event_to_all(ProtocolEvent.STOP_DIRECT) return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None) ######################################################################## # Regen handlers. ######################################################################## def _handler_stop_regen(self): """ Abort the current regeneration sequence, return to COMMAND @return next_state, (next_agent_state, result) """ self._send_event_to_slave(MCU, mcu.Capability.STANDBY) return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None) def _handler_regen_complete(self): """ Regeneration sequence is complete, return to COMMAND @return next_state, (next_agent_state, result) """ self._async_agent_state_change(ResourceAgentState.COMMAND) return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None) def _handler_manual_override_stop(self): """ Exit manual override. Attempt to bring the slave drivers back to COMMAND. """ mcu_state, turbo_state, rga_state = self._get_slave_states() if rga_state == rga.ProtocolState.SCAN: self._slave_protocols[RGA]._protocol_fsm.on_event(rga.Capability.STOP_SCAN) if turbo_state == turbo.ProtocolState.AT_SPEED: self._slave_protocols[TURBO]._protocol_fsm.on_event(turbo.Capability.STOP_TURBO) while rga_state not in [rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR] or \ turbo_state not in [turbo.ProtocolState.COMMAND, turbo.ProtocolState.ERROR]: time.sleep(.1) mcu_state, turbo_state, rga_state = self._get_slave_states() if mcu_state != mcu.ProtocolState.COMMAND: self._slave_protocols[MCU]._protocol_fsm.on_event(mcu.Capability.STANDBY) return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None) def _handler_manual_get_slave_states(self): """ Get the slave states and return them to the user @return: next_state, (next_agent_state, result) """ mcu_state, turbo_state, rga_state = self._get_slave_states() return None, (None, {MCU: mcu_state, RGA: rga_state, TURBO: turbo_state})
class Protocol(MenuInstrumentProtocol): """ Instrument protocol class Subclasses MenuInstrumentProtocol """ def __init__(self, menu, 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. MenuInstrumentProtocol.__init__(self, menu, 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.DISCOVER, self._handler_discover) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.DISCOVER, self._handler_discover) 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_AUTOSAMPLE, self._handler_command_autosample) self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_DIRECT, self._handler_command_start_direct) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop) self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.DISCOVER, self._handler_discover) 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. self._add_build_handler(Command.BACK_MENU, self._build_menu_command) self._add_build_handler(Command.BLANK, self._build_solo_command) self._add_build_handler(Command.START_AUTOSAMPLE, self._build_menu_command) self._add_build_handler(Command.CHANGE_PARAM, self._build_menu_command) self._add_build_handler(Command.SHOW_PARAM, self._build_menu_command) self._add_build_handler(Command.SENSOR_POWER, self._build_menu_command) self._add_build_handler(Command.DIRECT_SET, self._build_direct_command) self._add_build_handler(Command.CHANGE_CYCLE_TIME, self._build_menu_command) self._add_build_handler(Command.CHANGE_VERBOSE, self._build_menu_command) self._add_build_handler(Command.CHANGE_METADATA_RESTART, self._build_menu_command) self._add_build_handler(Command.CHANGE_METADATA_POWERUP, self._build_menu_command) self._add_build_handler(Command.CHANGE_RES_SENSOR_POWER, self._build_menu_command) self._add_build_handler(Command.CHANGE_INST_AMP_POWER, self._build_menu_command) self._add_build_handler(Command.CHANGE_EH_ISOLATION_AMP_POWER, self._build_menu_command) self._add_build_handler(Command.CHANGE_HYDROGEN_POWER, self._build_menu_command) self._add_build_handler(Command.CHANGE_REFERENCE_TEMP_POWER, self._build_menu_command) # Add response handlers for device commands. #self._add_response_handler(Command.GET, self._parse_get_response) #self._add_response_handler(Command.SET, self._parse_get_response) self._add_response_handler(Command.BACK_MENU, self._parse_menu_change_response) self._add_response_handler(Command.BLANK, self._parse_menu_change_response) self._add_response_handler(Command.SHOW_PARAM, self._parse_show_param_response) self._add_response_handler(Command.CHANGE_CYCLE_TIME, self._parse_menu_change_response) self._add_response_handler(Command.CHANGE_VERBOSE, self._parse_menu_change_response) self._add_response_handler(Command.CHANGE_METADATA_RESTART, self._parse_menu_change_response) self._add_response_handler(Command.CHANGE_METADATA_POWERUP, self._parse_menu_change_response) self._add_response_handler(Command.CHANGE_RES_SENSOR_POWER, self._parse_menu_change_response) self._add_response_handler(Command.CHANGE_INST_AMP_POWER, self._parse_menu_change_response) self._add_response_handler(Command.CHANGE_EH_ISOLATION_AMP_POWER, self._parse_menu_change_response) self._add_response_handler(Command.CHANGE_HYDROGEN_POWER, self._parse_menu_change_response) self._add_response_handler(Command.CHANGE_REFERENCE_TEMP_POWER, self._parse_menu_change_response) self._add_response_handler(Command.DIRECT_SET, self._parse_menu_change_response) # 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(self.sieve_function) @staticmethod def sieve_function(raw_data): """ The method that splits samples """ return_list = [] for match in SAMPLE_REGEX.finditer(raw_data): return_list.append((match.start(), match.end())) return return_list def _go_to_root_menu(self): """ Get back to the root menu, assuming we are in COMMAND mode. Getting to command mode should be done before this method is called. A discover will get there. """ log.debug("Returning to root menu...") # Issue an enter or two off the bat to get out of any display screens # and confirm command mode try: response = self._do_cmd_resp(Command.BLANK, expected_prompt=Prompt.CMD_PROMPT) while not str(response).lstrip().endswith(Prompt.CMD_PROMPT): response = self._do_cmd_resp(Command.BLANK, expected_prompt=Prompt.CMD_PROMPT) time.sleep(1) except InstrumentTimeoutException: raise InstrumentProtocolException("Not able to get valid command prompt. Is instrument in command mode?") # When you get a --> prompt, do 9's until you get back to the root response = self._do_cmd_resp(Command.BACK_MENU, expected_prompt=MENU_PROMPTS) while not str(response).lstrip().endswith(Prompt.MAIN_MENU): response = self._do_cmd_resp(Command.BACK_MENU, expected_prompt=MENU_PROMPTS) def _filter_capabilities(self, events): """ Define a small filter of the capabilities @param A list of events to consider as capabilities @retval A list of events that are actually capabilities """ events_out = [x for x in events if Capability.has(x)] return events_out def get_resource_capabilities(self, current_state=True): """ """ res_cmds = self._protocol_fsm.get_events(current_state) res_cmds = self._filter_capabilities(res_cmds) res_params = VisibleParameters.list() return [res_cmds, res_params] ######################################################################## # 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_discover(self, *args, **kwargs): """ Discover current state by going to the root menu @retval (next_state, result) """ next_state = None next_agent_state = None # Try to break in case we are in auto sample self._send_break() next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.IDLE self._go_to_root_menu() return (next_state, next_agent_state) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throw InstrumentTimeoutException if the device cannot be woken. @throw InstrumentProtocolException if the update commands and not recognized. """ # Command device to update parameters and send a config change event. # 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): """ Get parameters while in the command state. @param params List of the parameters to pass to the state @retval returns (next_state, result) where result is a dict {}. No agent state changes happening with Get, so no next_agent_state @throw InstrumentParameterException for invalid parameter """ next_state = None result = None result_vals = {} if (params == None): raise InstrumentParameterException("GET parameter list empty!") if (params == Parameter.ALL): params = [Parameter.CYCLE_TIME, Parameter.EH_ISOLATION_AMP_POWER, Parameter.HYDROGEN_POWER, Parameter.INST_AMP_POWER, Parameter.METADATA_POWERUP, Parameter.METADATA_RESTART, Parameter.REFERENCE_TEMP_POWER, Parameter.RES_SENSOR_POWER, Parameter.VERBOSE] if not isinstance(params, list): raise InstrumentParameterException("GET parameter list not a list!") # Do a bulk update from the instrument since they are all on one page self._update_params() # fill the return values from the update for param in params: if not Parameter.has(param): raise InstrumentParameterException("Invalid parameter!") result_vals[param] = self._param_dict.get(param) 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() # restrict operations to just the read/write parameters if (key == Parameter.CYCLE_TIME): self._navigate(SubMenu.CYCLE_TIME) (unit, value) = self._from_seconds(name_values[key]) try: self._do_cmd_resp(Command.DIRECT_SET, unit, expected_prompt=[Prompt.CYCLE_TIME_SEC_VALUE_PROMPT, Prompt.CYCLE_TIME_MIN_VALUE_PROMPT]) self._do_cmd_resp(Command.DIRECT_SET, value, expected_prompt=Prompt.CHANGE_PARAM_MENU) except InstrumentProtocolException: self._go_to_root_menu() raise InstrumentProtocolException("Could not set cycle time") # Populate with actual value set result_vals[key] = name_values[key] # re-sync with param dict? self._go_to_root_menu() self._update_params() result = result_vals log.debug("next: %s, result: %s", next_state, result) return (next_state, result) def _handler_command_autosample(self, *args, **kwargs): """ Start autosample mode """ next_state = None next_agent_state = None result = None self._navigate(SubMenu.MAIN) self._do_cmd_no_resp(Command.START_AUTOSAMPLE) next_state = ProtocolState.AUTOSAMPLE next_agent_state = ResourceAgentState.STREAMING return (next_state, (next_agent_state, result)) def _handler_command_start_direct(self): """ """ next_state = None next_agent_state = None result = None next_state = ProtocolState.DIRECT_ACCESS next_agent_state = ResourceAgentState.DIRECT_ACCESS 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 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 next_agent_state = None result = None next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND return (next_state, (next_agent_state, result)) ######################################################################## # Autosample handlers ######################################################################## def _handler_autosample_enter(self, *args, **kwargs): """ Enter autosample mode """ self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_autosample_stop(self): """ Stop autosample mode """ next_state = None next_agent_state = None result = None if (self._send_break()): next_state = ProtocolState.COMMAND next_agent_state = ResourceAgentState.COMMAND return (next_state, (next_agent_state, result)) ######################################################################## # Command builders ######################################################################## def _build_solo_command(self, cmd): """ Issue a simple command that does NOT require a newline at the end to execute. Likly used for control characters or special characters """ return COMMAND_CHAR[cmd] def _build_menu_command(self, cmd): """ Pick the right character and add a newline """ if COMMAND_CHAR[cmd]: return COMMAND_CHAR[cmd]+self._newline else: raise InstrumentProtocolException("Unknown command character for %s" % cmd) def _build_direct_command(self, cmd, arg): """ Build a command where we just send the argument to the instrument. Ignore the command part, we dont need it here as we are already in a submenu. """ return "%s%s" % (arg, self._newline) ######################################################################## # Command parsers ######################################################################## def _parse_menu_change_response(self, response, prompt): """ Parse a response to a menu change @param response What was sent back from the command that was sent @param prompt The prompt that was returned from the device @retval The prompt that was encountered after the change """ log.trace("Parsing menu change response with prompt: %s", prompt) return prompt def _parse_show_param_response(self, response, prompt): """ Parse the show parameter response screen """ log.trace("Parsing show parameter screen") self._param_dict.update_many(response) ######################################################################## # Utilities ######################################################################## def _wakeup(self, timeout): # Always awake for this instrument! pass def _got_chunk(self, chunk): ''' extract samples from a chunk of data @param chunk: bytes to parse into a sample. ''' self._extract_sample(BarsDataParticle, SAMPLE_REGEX, chunk) def _update_params(self): """Fetch the parameters from the device, and update the param dict. @param args Unused @param kwargs Takes timeout value @throw InstrumentProtocolException @throw 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 _get_config(self, *args, **kwargs): """ Get the entire configuration for the instrument @param params The parameters and values to set Should be a dict of parameters and values @throw InstrumentProtocolException On a deeper issue """ # Just need to show the parameter screen...the parser for the command # does the update_many() self._go_to_root_menu() self._navigate(SubMenu.SHOW_PARAM) self._go_to_root_menu() def _send_break(self, timeout=4): """ Execute an attempts to break out of auto sample (a few if things get garbled). For this instrument, it is done with a ^S, a wait for a \r\n, then another ^S within 1/2 a second @param timeout @retval True if 2 ^S chars were sent with a prompt in the middle, False if not. """ log.debug("Sending break sequence to instrument...") # Timing is an issue, so keep it simple, work directly with the # couple chars instead of command/respose. Could be done that way # though. Just more steps, logic, and delay for such a simple # exchange for count in range(0, 3): self._promptbuf = "" try: self._connection.send("%c" % COMMAND_CHAR[Command.BREAK]) (prompt, result) = self._get_raw_response(timeout, expected_prompt=[Prompt.BREAK_ACK, Prompt.CMD_PROMPT]) if (prompt == Prompt.BREAK_ACK): self._connection.send("%c" % COMMAND_CHAR[Command.BREAK]) (prompt, result) = self._get_response(timeout, expected_prompt=Prompt.CMD_PROMPT) return True elif(prompt == Prompt.CMD_PROMPT): return True except InstrumentTimeoutException: continue log.trace("_send_break failing after several attempts") return False def set_readonly_values(self, *args, **kwargs): """Set read-only values to the instrument. This is usually (only?) done at initialization. @throw InstrumentProtocolException When in the wrong state or something really bad prevents the setting of all values. """ # Let's give it a try in unknown state if (self.get_current_state() != ProtocolState.COMMAND): raise InstrumentProtocolException("Not in command state. Unable to set read-only params") self._go_to_root_menu() self._update_params() for param in self._param_dict.get_visibility_list(ParameterDictVisibility.READ_ONLY): if not Parameter.has(param): raise InstrumentParameterException() self._go_to_root_menu() # Only try to change them if they arent set right as it is log.trace("Setting read-only parameter: %s, current paramdict value: %s, init val: %s", param, self._param_dict.get(param), self._param_dict.get_init_value(param)) if (self._param_dict.get(param) != self._param_dict.get_init_value(param)): if (param == Parameter.METADATA_POWERUP): self._navigate(SubMenu.METADATA_POWERUP) result = self._do_cmd_resp(Command.DIRECT_SET, (1+ int(self._param_dict.get_init_value(param))), expected_prompt=Prompt.CHANGE_PARAM_MENU) if not result: raise InstrumentParameterException("Could not set param %s" % param) self._go_to_root_menu() elif (param == Parameter.METADATA_RESTART): self._navigate(SubMenu.METADATA_RESTART) result = self._do_cmd_resp(Command.DIRECT_SET, (1 + int(self._param_dict.get_init_value(param))), expected_prompt=Prompt.CHANGE_PARAM_MENU) if not result: raise InstrumentParameterException("Could not set param %s" % param) self._go_to_root_menu() elif (param == Parameter.VERBOSE): self._navigate(SubMenu.VERBOSE) result = self._do_cmd_resp(Command.DIRECT_SET, self._param_dict.get_init_value(param), expected_prompt=Prompt.CHANGE_PARAM_MENU) if not result: raise InstrumentParameterException("Could not set param %s" % param) self._go_to_root_menu() elif (param == Parameter.EH_ISOLATION_AMP_POWER): result = self._navigate(SubMenu.EH_ISOLATION_AMP_POWER) while not result: result = self._navigate(SubMenu.EH_ISOLATION_AMP_POWER) elif (param == Parameter.HYDROGEN_POWER): result = self._navigate(SubMenu.HYDROGEN_POWER) while not result: result = self._navigate(SubMenu.HYDROGEN_POWER) elif (param == Parameter.INST_AMP_POWER): result = self._navigate(SubMenu.INST_AMP_POWER) while not result: result = self._navigate(SubMenu.INST_AMP_POWER) elif (param == Parameter.REFERENCE_TEMP_POWER): result = self._navigate(SubMenu.REFERENCE_TEMP_POWER) while not result: result = self._navigate(SubMenu.REFERENCE_TEMP_POWER) elif (param == Parameter.RES_SENSOR_POWER): result = self._navigate(SubMenu.RES_SENSOR_POWER) while not result: result = self._navigate(SubMenu.RES_SENSOR_POWER) # re-sync with param dict? self._go_to_root_menu() self._update_params() # Should be good by now, but let's double check just to be safe for param in self._param_dict.get_visibility_list(ParameterDictVisibility.READ_ONLY): if (param == Parameter.VERBOSE): continue if (self._param_dict.get(param) != self._param_dict.get_init_value(param)): raise InstrumentProtocolException("Could not set default values!") 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 = ProtocolParameterDict() self._param_dict.add(Parameter.CYCLE_TIME, r'(\d+)\s+= Cycle Time \(.*\)\r\n(0|1)\s+= Minutes or Seconds Cycle Time', lambda match : self._to_seconds(int(match.group(1)), int(match.group(2))), self._int_to_string, visibility=ParameterDictVisibility.READ_WRITE, startup_param=True, direct_access=False, default_value=20, menu_path_read=SubMenu.SHOW_PARAM, submenu_read=[], menu_path_write=SubMenu.CHANGE_PARAM, submenu_write=[["1", Prompt.CYCLE_TIME_PROMPT]]) self._param_dict.add(Parameter.VERBOSE, r'', # Write-only, so does it really matter? lambda match : None, self._int_to_string, visibility=ParameterDictVisibility.READ_ONLY, startup_param=True, direct_access=True, init_value=1, menu_path_write=SubMenu.CHANGE_PARAM, submenu_write=[["2", Prompt.VERBOSE_PROMPT]]) self._param_dict.add(Parameter.METADATA_POWERUP, r'(0|1)\s+= Metadata Print Status on Power up', lambda match : int(match.group(1)), self._int_to_string, visibility=ParameterDictVisibility.READ_ONLY, startup_param=True, direct_access=True, init_value=0, menu_path_write=SubMenu.CHANGE_PARAM, submenu_write=[["3", Prompt.METADATA_PROMPT]]) self._param_dict.add(Parameter.METADATA_RESTART, r'(0|1)\s+= Metadata Print Status on Restart Data Collection', lambda match : int(match.group(1)), self._int_to_string, visibility=ParameterDictVisibility.READ_ONLY, startup_param=True, direct_access=True, init_value=0, menu_path_write=SubMenu.CHANGE_PARAM, submenu_write=[["4", Prompt.METADATA_PROMPT]]) self._param_dict.add(Parameter.RES_SENSOR_POWER, r'(0|1)\s+= Res Power Status', lambda match : int(match.group(1)), self._int_to_string, visibility=ParameterDictVisibility.READ_ONLY, startup_param=True, direct_access=False, init_value=1, menu_path_read=SubMenu.SHOW_PARAM, submenu_read=[], menu_path_write=SubMenu.SENSOR_POWER, submenu_write=[["1"]]) self._param_dict.add(Parameter.INST_AMP_POWER, r'(0|1)\s+= Thermocouple & Hydrogen Amp Power Status', lambda match : int(match.group(1)), self._int_to_string, visibility=ParameterDictVisibility.READ_ONLY, startup_param=True, direct_access=False, init_value=1, menu_path_read=SubMenu.SHOW_PARAM, submenu_read=[], menu_path_write=SubMenu.SENSOR_POWER, submenu_write=[["2"]]) self._param_dict.add(Parameter.EH_ISOLATION_AMP_POWER, r'(0|1)\s+= eh Amp Power Status', lambda match : int(match.group(1)), self._int_to_string, visibility=ParameterDictVisibility.READ_ONLY, startup_param=True, direct_access=False, init_value=1, menu_path_read=SubMenu.SHOW_PARAM, submenu_read=[], menu_path_write=SubMenu.SENSOR_POWER, submenu_write=[["3"]]) self._param_dict.add(Parameter.HYDROGEN_POWER, r'(0|1)\s+= Hydrogen Sensor Power Status', lambda match : int(match.group(1)), self._int_to_string, visibility=ParameterDictVisibility.READ_ONLY, startup_param=True, direct_access=False, init_value=1, menu_path_read=SubMenu.SHOW_PARAM, submenu_read=[], menu_path_write=SubMenu.SENSOR_POWER, submenu_write=[["4"]]) self._param_dict.add(Parameter.REFERENCE_TEMP_POWER, r'(0|1)\s+= Reference Temperature Power Status', lambda match : int(match.group(1)), self._int_to_string, visibility=ParameterDictVisibility.READ_ONLY, startup_param=True, direct_access=False, init_value=1, menu_path_read=SubMenu.SHOW_PARAM, submenu_read=[], menu_path_write=SubMenu.SENSOR_POWER, submenu_write=[["5"]]) @staticmethod def _to_seconds(value, unit): """ Converts a number and a unit into seconds. Ie if "4" and "1" comes in, it spits out 240 @param value The int value for some number of minutes or seconds @param unit int of 0 or 1 where 0 is seconds, 1 is minutes @return Number of seconds. """ if (not isinstance(value, int)) or (not isinstance(unit, int)): raise InstrumentProtocolException("Invalid second arguments!") if unit == 1: return value * 60 elif unit == 0: return value else: raise InstrumentProtocolException("Invalid Units!") @staticmethod def _from_seconds(value): """ Converts a number of seconds into a (unit, value) tuple. @param value The number of seconds to convert @retval A tuple of unit and value where the unit is 1 for seconds and 2 for minutes. If the value is 15-59, units should be returned in seconds. If the value is over 59, the units will be returned in a number of minutes where the seconds are rounded down to the nearest minute. """ if (value < 15) or (value > 3600): raise InstrumentParameterException("Invalid seconds value: %s" % value) if (value < 60): return (1, value) else: return (2, value // 60)