예제 #1
0
    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.CLOCK,
                             r'(.*)\r\n',
                             lambda match: match.group(1),
                             lambda string: str(string),
                             type=ParameterDictType.STRING,
                             display_name="clock",
                             expiration=0,
                             visibility=ParameterDictVisibility.READ_ONLY)

        self._param_dict.add(
            Parameter.SAMPLE_INTERVAL,
            r'Not used. This parameter is not parsed from instrument response',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=30,
            value=30,
            startup_param=True,
            display_name="sample_interval",
            visibility=ParameterDictVisibility.IMMUTABLE)
예제 #2
0
    def __init__(self, prompts, newline, driver_event, connections=None):
        """
        Constructor.
        @param prompts Enum class containing possible device prompts used for
        command response logic.
        @param newline The device newline.
        @driver_event The callback for asynchronous driver events.
        """
        if not type(connections) is list:
            raise InstrumentProtocolException(
                'Unable to instantiate multi connection protocol without connection list'
            )
        self._param_dict2 = ProtocolParameterDict()

        # Construct superclass.
        WorkhorseProtocol.__init__(self, prompts, newline, driver_event)

        # Create multiple connection versions of the pieces of protocol involving data to/from the instrument
        self._linebuf = {connection: '' for connection in connections}
        self._promptbuf = {connection: '' for connection in connections}
        self._last_data_timestamp = {
            connection: None
            for connection in connections
        }
        self.connections = {connection: None for connection in connections}
        self.chunkers = {
            connection: StringChunker(self.sieve_function)
            for connection in connections
        }
예제 #3
0
    def _set_params(self, *args, **kwargs):
        if len(args) < 1:
            raise InstrumentParameterException(
                'Set command requires a parameter dict.')
        params = args[0]

        if not isinstance(params, dict):
            raise InstrumentParameterException('Set parameters not a dict.')

        self._param_dict = ProtocolParameterDict()

        for param in params:
            log.info('Creating new parameter: %s', param)
            self._param_dict.add(param, '', None, None)
            self._param_dict.set_value(param, params[param])
예제 #4
0
    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()
        
        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.CLOCK,
                             r'(.*)\r\n', 
                             lambda match : match.group(1),
                             lambda string : str(string),
                             type=ParameterDictType.STRING,
                             display_name="clock",
                             expiration=0,
                             visibility=ParameterDictVisibility.READ_ONLY)

        self._param_dict.add(Parameter.SAMPLE_INTERVAL,
                             r'Not used. This parameter is not parsed from instrument response',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=30,
                             value=30,
                             startup_param=True,
                             display_name="sample_interval",
                             visibility=ParameterDictVisibility.IMMUTABLE)
 def setUp(self):
     self.param_dict = ProtocolParameterDict()
             
     self.param_dict.add("foo", r'.*foo=(\d+).*',
                          lambda match : int(match.group(1)),
                          lambda x : str(x),
                          direct_access=True,
                          startup_param=True,
                          default_value=10,
                          visibility=ParameterDictVisibility.READ_WRITE)
     self.param_dict.add("bar", r'.*bar=(\d+).*',
                          lambda match : int(match.group(1)),
                          lambda x : str(x),
                          direct_access=False,
                          startup_param=True,
                          default_value=15,
                          visibility=ParameterDictVisibility.READ_WRITE)
     self.param_dict.add("baz", r'.*baz=(\d+).*',
                          lambda match : int(match.group(1)),
                          lambda x : str(x),
                          direct_access=True,
                          default_value=20,
                          visibility=ParameterDictVisibility.DIRECT_ACCESS)
     self.param_dict.add("bat", r'.*bat=(\d+).*',
                          lambda match : int(match.group(1)),
                          lambda x : str(x),
                          startup_param=False,
                          default_value=20,
                          visibility=ParameterDictVisibility.READ_ONLY)
     self.param_dict.add("qux", r'.*qux=(\d+).*',
                          lambda match : int(match.group(1)),
                          lambda x : str(x),
                          startup_param=False,
                          visibility=ParameterDictVisibility.READ_ONLY)
    def __init__(self, driver_event):
        """
        Base constructor.
        @param driver_event The callback for asynchronous driver events.
        """
        # Event callback to send asynchronous events to the agent.
        self._driver_event = driver_event

        # The connection used to talk to the device.
        self._connection = None
        
        # The protocol state machine.
        self._protocol_fsm = None
        
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # The spot to stash a configuration before going into direct access
        # mode
        self._pre_direct_access_config = None

        # Driver configuration passed from the user
        self._startup_config = {}

        # scheduler config is a bit redundant now, but if we ever want to
        # re-initialize a scheduler we will need it.
        self._scheduler = None
        self._scheduler_callback = {}
        self._scheduler_config = {}
예제 #7
0
    def __init__(self, config, memento, data_callback, state_callback, exception_callback):
        self._config = config
        self._data_callback = data_callback
        self._state_callback = state_callback
        self._exception_callback = exception_callback
        self._memento = memento
        self._publisher_thread = None

        self._verify_config()
        self._param_dict = ProtocolParameterDict()

        # Updated my set_resource, defaults defined in build_param_dict
        self._polling_interval = None
        self._generate_particle_count = None
        self._particle_count_per_second = None

        self._build_param_dict()
예제 #8
0
    def _set_params(self, *args, **kwargs):
        if len(args) < 1:
            raise InstrumentParameterException("Set command requires a parameter dict.")
        params = args[0]

        if not isinstance(params, dict):
            raise InstrumentParameterException("Set parameters not a dict.")

        self._param_dict = ProtocolParameterDict()

        for param in params:
            log.info("Creating new parameter: %s", param)
            self._param_dict.add(param, "", None, None)
            self._param_dict.set_value(param, params[param])
    def __init__(self, config, memento, data_callback, state_callback, exception_callback):
        self._config = config
        self._data_callback = data_callback
        self._state_callback = state_callback
        self._exception_callback = exception_callback
        self._memento = memento

        self._verify_config()
        self._param_dict = ProtocolParameterDict()

        # Updated my set_resource, defaults defined in build_param_dict
        self._polling_interval = None
        self._generate_particle_count = None
        self._particle_count_per_second = None

        self._build_param_dict()
    def __init__(self, driver_event):
        """
        Base constructor.
        @param driver_event The callback for asynchronous driver events.
        """
        # Event callback to send asynchronous events to the agent.
        self._driver_event = driver_event

        # The connection used to talk to the device.
        self._connection = None
        
        # The protocol state machine.
        self._protocol_fsm = None
        
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()
예제 #11
0
    def __init__(self, prompts, newline, driver_event, connections=None):
        """
        Constructor.
        @param prompts Enum class containing possible device prompts used for
        command response logic.
        @param newline The device newline.
        @driver_event The callback for asynchronous driver events.
        """
        if not type(connections) is list:
            raise InstrumentProtocolException('Unable to instantiate multi connection protocol without connection list')
        self._param_dict2 = ProtocolParameterDict()

        # Construct superclass.
        WorkhorseProtocol.__init__(self, prompts, newline, driver_event)

        # Create multiple connection versions of the pieces of protocol involving data to/from the instrument
        self._linebuf = {connection: '' for connection in connections}
        self._promptbuf = {connection: '' for connection in connections}
        self._last_data_timestamp = {connection: None for connection in connections}
        self.connections = {connection: None for connection in connections}
        self.chunkers = {connection: StringChunker(self.sieve_function) for connection in connections}
    def __init__(self, config, memento, data_callback, state_callback, event_callback, exception_callback):
        self._config = copy.deepcopy(config)
        self._data_callback = data_callback
        self._state_callback = state_callback
        self._event_callback = event_callback
        self._exception_callback = exception_callback
        self._memento = memento
        self._publisher_thread = None

        self._verify_config()

        # Updated my set_resource, defaults defined in build_param_dict
        self._polling_interval = None
        self._generate_particle_count = None
        self._particle_count_per_second = None
        self._resource_id = None

        self._param_dict = ProtocolParameterDict()
        self._cmd_dict = ProtocolCommandDict()
        self._driver_dict = DriverDict()

        self._build_command_dict()
        self._build_driver_dict()
        self._build_param_dict()
예제 #13
0
class Protocol(SamiProtocol):
    """
    Instrument protocol class
    Subclasses SamiProtocol and CommandResponseInstrumentProtocol
    """
    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        SamiProtocol.__init__(self, prompts, newline, driver_event)

        ## Continue building protocol state machine from SamiProtocol
        self._protocol_fsm.add_handler(
            ProtocolState.SCHEDULED_SAMPLE, ProtocolEvent.SUCCESS,
            self._handler_sample_success)
        self._protocol_fsm.add_handler(
            ProtocolState.SCHEDULED_SAMPLE, ProtocolEvent.TIMEOUT,
            self._handler_sample_timeout)

        self._protocol_fsm.add_handler(
            ProtocolState.POLLED_SAMPLE, ProtocolEvent.SUCCESS,
            self._handler_sample_success)
        self._protocol_fsm.add_handler(
            ProtocolState.POLLED_SAMPLE, ProtocolEvent.TIMEOUT,
            self._handler_sample_timeout)

        # State state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)

        # build the chunker bot
        self._chunker = StringChunker(Protocol.sieve_function)

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples
        """

        return_list = []

        sieve_matchers = [REGULAR_STATUS_REGEX_MATCHER,
                          CONTROL_RECORD_REGEX_MATCHER,
                          SAMI_SAMPLE_REGEX_MATCHER,
                          CONFIGURATION_REGEX_MATCHER,
                          ERROR_REGEX_MATCHER]

        for matcher in sieve_matchers:
            for match in matcher.finditer(raw_data):
                return_list.append((match.start(), match.end()))

        return return_list

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        self._extract_sample(SamiRegularStatusDataParticle, REGULAR_STATUS_REGEX_MATCHER, chunk, timestamp)
        self._extract_sample(SamiControlRecordDataParticle, CONTROL_RECORD_REGEX_MATCHER, chunk, timestamp)
        self._extract_sample(PhsenSamiSampleDataParticle, SAMI_SAMPLE_REGEX_MATCHER, chunk, timestamp)
        self._extract_sample(PhsenConfigDataParticle, CONFIGURATION_REGEX_MATCHER, chunk, timestamp)

    #########################################################################
    ## General (for POLLED and SCHEDULED states) Sample handlers.
    #########################################################################

    def _handler_sample_success(self, *args, **kwargs):
        next_state = None
        result = None

        return (next_state, result)

    def _handler_sample_timeout(self, ):
        next_state = None
        result = None

        return (next_state, result)

    ####################################################################
    # Build Parameter dictionary
    ####################################################################

    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.LAUNCH_TIME, CONFIGURATION_REGEX,
                             lambda match: int(match.group(1), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='launch time')

        self._param_dict.add(Parameter.START_TIME_FROM_LAUNCH, CONFIGURATION_REGEX,
                             lambda match: int(match.group(2), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02C7EA00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='start time after launch time')

        self._param_dict.add(Parameter.STOP_TIME_FROM_START, CONFIGURATION_REGEX,
                             lambda match: int(match.group(3), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01E13380,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='stop time after start time')

        self._param_dict.add(Parameter.MODE_BITS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(4), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0A,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='mode bits (set to 00001010)')

        self._param_dict.add(Parameter.SAMI_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(5), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami sample interval')

        self._param_dict.add(Parameter.SAMI_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(6), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x04,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami driver version')

        self._param_dict.add(Parameter.SAMI_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(7), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami parameter pointer')

        self._param_dict.add(Parameter.DEVICE1_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(8), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 sample interval')

        self._param_dict.add(Parameter.DEVICE1_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(9), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 driver version')

        self._param_dict.add(Parameter.DEVICE1_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(10), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0B,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 parameter pointer')

        self._param_dict.add(Parameter.DEVICE2_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(11), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 sample interval')

        self._param_dict.add(Parameter.DEVICE2_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(12), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 driver version')

        self._param_dict.add(Parameter.DEVICE2_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(13), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 parameter pointer')

        self._param_dict.add(Parameter.DEVICE3_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(14), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 sample interval')

        self._param_dict.add(Parameter.DEVICE3_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(15), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 driver version')

        self._param_dict.add(Parameter.DEVICE3_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(16), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 parameter pointer')

        self._param_dict.add(Parameter.PRESTART_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(17), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart sample interval')

        self._param_dict.add(Parameter.PRESTART_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(18), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart driver version')

        self._param_dict.add(Parameter.PRESTART_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(19), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart parameter pointer')

        self._param_dict.add(Parameter.GLOBAL_CONFIGURATION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(20), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='global bits (set to 00000111)')

        self._param_dict.add(Parameter.NUMBER_SAMPLES_AVERAGED, CONFIGURATION_REGEX,
                             lambda match: int(match.group(21), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_FLUSHES, CONFIGURATION_REGEX,
                             lambda match: int(match.group(22), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_ON_FLUSH, CONFIGURATION_REGEX,
                             lambda match: int(match.group(23), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_OFF_FLUSH, CONFIGURATION_REGEX,
                             lambda match: int(match.group(24), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_REAGENT_PUMPS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(25), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.VALVE_DELAY, CONFIGURATION_REGEX,
                             lambda match: int(match.group(26), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_ON_IND, CONFIGURATION_REGEX,
                             lambda match: int(match.group(27), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PV_OFF_IND, CONFIGURATION_REGEX,
                             lambda match: int(match.group(28), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_BLANKS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(29), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_MEASURE_T, CONFIGURATION_REGEX,
                             lambda match: int(match.group(30), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_OFF_TO_MEASURE, CONFIGURATION_REGEX,
                             lambda match: int(match.group(31), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.MEASURE_TO_PUMP_ON, CONFIGURATION_REGEX,
                             lambda match: int(match.group(32), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_MEASUREMENTS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(33), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.SALINITY_DELAY, CONFIGURATION_REGEX,
                             lambda match: int(match.group(34), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')
예제 #14
0
class Protocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    
    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT)

        # Add event handlers for protocol state machine.
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.ENTER, self._handler_unknown_enter)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.EXIT, self._handler_unknown_exit)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.DISCOVER, self._handler_unknown_discover)

        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ENTER, self._handler_command_enter)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.EXIT, self._handler_command_exit)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_DIRECT, self._handler_command_start_direct)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.CLOCK_SYNC, self._handler_sync_clock)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.GET, self._handler_get)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.SET, self._handler_command_set)

        self._protocol_fsm.add_handler(ProtocolState.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.EXECUTE_DIRECT, self._handler_direct_access_execute_direct)
        self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct)

        # Add build handlers for device commands.
        self._add_build_handler(Command.BATTERY, self._build_simple_command)

        # Add response handlers for device commands.
        self._add_response_handler(Command.BATTERY, self._parse_battery_response)
 
        # 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()
        
        self._chunker = StringChunker(Protocol.sieve_function)

        self._add_scheduler_event(ScheduledJob.CLOCK_SYNC, ProtocolEvent.CLOCK_SYNC)

        # Start state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples and status
        """
        matchers = []
        return_list = []

        matchers.append(RASFL_SampleDataParticle.regex_compiled())
                    
        for matcher in matchers:
            for match in matcher.finditer(raw_data):
                return_list.append((match.start(), match.end()))
                    
        """
        if return_list != []:
            log.debug("sieve_function: raw_data=%s, return_list=%s" %(raw_data, return_list))
        """
        return return_list

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        log.debug("_got_chunk: chunk=%s" %chunk)
        self._extract_sample(RASFL_SampleDataParticle, RASFL_SampleDataParticle.regex_compiled(), chunk, timestamp)

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        """
        return [x for x in events if Capability.has(x)]

    ########################################################################
    # implement virtual methods from base class.
    ########################################################################

    def apply_startup_params(self):
        """
        Apply sample_interval startup parameter.  
        """

        config = self.get_startup_config()
        log.debug("apply_startup_params: startup config = %s" %config)
        if config.has_key(Parameter.SAMPLE_INTERVAL):
            log.debug("apply_startup_params: setting sample_interval to %d" %config[Parameter.SAMPLE_INTERVAL])
            self._param_dict.set_value(Parameter.SAMPLE_INTERVAL, config[Parameter.SAMPLE_INTERVAL])

    ########################################################################
    # Unknown handlers.
    ########################################################################

    def _handler_unknown_enter(self, *args, **kwargs):
        """
        Enter unknown state.
        """
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_unknown_exit(self, *args, **kwargs):
        """
        Exit unknown state.
        """
        pass

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state; can only be COMMAND (instrument has no actual AUTOSAMPLE mode).
        @retval (next_state, result), (ProtocolState.COMMAND, None) if successful.
        """

        # force to command mode, this instrument has no autosample mode
        next_state = ProtocolState.COMMAND
        result = ResourceAgentState.COMMAND

        log.debug("_handler_unknown_discover: state = %s", next_state)
        return (next_state, result)


    ########################################################################
    # Command handlers.
    # just implemented to make DA possible, instrument has no actual command mode
    ########################################################################

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        """

        # Command device to update parameters and send a config change event if needed.
        self._update_params()

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_command_exit(self, *args, **kwargs):
        """
        Exit command state.
        """
        pass

    def _handler_command_set(self, *args, **kwargs):
        """
        no writable parameters so does nothing, just implemented to make framework happy
        """

        next_state = None
        result = None
        return (next_state, result)

    def _handler_command_start_direct(self, *args, **kwargs):
        """
        """
        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)
                        
        return (next_state, result)

    def _handler_direct_access_stop_direct(self):
        result = None
        next_state = ProtocolState.COMMAND
        next_agent_state = ResourceAgentState.COMMAND

        return (next_state, (next_agent_state, result))

    ########################################################################
    # general handlers.
    ########################################################################

    def _handler_sync_clock(self, *args, **kwargs):
        """
        sync clock close to a second edge 
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device respond correctly.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """

        next_state = None
        next_agent_state = None
        result = None

        time_format = "%Y/%m/%d %H:%M:%S"
        str_val = get_timestamp_delayed(time_format)
        log.debug("Setting instrument clock to '%s'", str_val)
        self._do_cmd_resp(Command.STOP, expected_prompt=Prompt.STOPPED)
        try:
            self._do_cmd_resp(Command.SET_CLOCK, str_val, expected_prompt=Prompt.CR_NL)
        finally:
            # ensure that we try to start the instrument sampling again
            self._do_cmd_resp(Command.GO, expected_prompt=Prompt.GO)

        return (next_state, (next_agent_state, result))

    ########################################################################
    # Private helpers.
    ########################################################################

    def _wakeup(self, wakeup_timeout=10, response_timeout=3):
        """
        Over-ridden because waking this instrument up is a multi-step process with
        two different requests required
        @param wakeup_timeout The timeout to wake the device.
        @param response_timeout The time to look for response to a wakeup attempt.
        @throw InstrumentTimeoutException if the device could not be woken.
        """
        sleep_time = .1
        command = Command.END_OF_LINE
        
        # Grab start time for overall wakeup timeout.
        starttime = time.time()
        
        while True:
            # Clear the prompt buffer.
            log.debug("_wakeup: clearing promptbuf: %s" % self._promptbuf)
            self._promptbuf = ''
        
            # Send a command and wait delay amount for response.
            log.debug('_wakeup: Sending command %s, delay=%s' %(command.encode("hex"), response_timeout))
            for char in command:
                self._connection.send(char)
                time.sleep(INTER_CHARACTER_DELAY)
            sleep_amount = 0
            while True:
                time.sleep(sleep_time)
                if self._promptbuf.find(Prompt.COMMAND_INPUT) != -1:
                    # instrument is awake
                    log.debug('_wakeup: got command input prompt %s' % Prompt.COMMAND_INPUT)
                    # add inter-character delay which _do_cmd_resp() incorrectly doesn't add to the start of a transmission
                    time.sleep(INTER_CHARACTER_DELAY)
                    return Prompt.COMMAND_INPUT
                if self._promptbuf.find(Prompt.ENTER_CTRL_C) != -1:
                    command = Command.CONTROL_C
                    break
                if self._promptbuf.find(Prompt.PERIOD) == 0:
                    command = Command.CONTROL_C
                    break
                sleep_amount += sleep_time
                if sleep_amount >= response_timeout:
                    log.debug("_wakeup: expected response not received, buffer=%s" % self._promptbuf)
                    break

            if time.time() > starttime + wakeup_timeout:
                raise InstrumentTimeoutException("_wakeup(): instrument failed to wakeup in %d seconds time" %wakeup_timeout)

    
    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False)

    def _build_command_dict(self):
        """
        Populate the command dictionary with command.
        """
        self._cmd_dict.add(Capability.CLOCK_SYNC, display_name="synchronize clock")

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()
        
        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.BATTERY,
                             r'Battery: (.*)V', 
                             lambda match : match.group(1),
                             lambda string : str(string),
                             type=ParameterDictType.STRING,
                             display_name="battery",
                             expiration=0,
                             visibility=ParameterDictVisibility.READ_ONLY)

        self._param_dict.add(Parameter.SAMPLE_INTERVAL,
                             r'Not used. This parameter is not parsed from instrument response',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=30,
                             value=30,
                             startup_param=True,
                             display_name="sample_interval",
                             visibility=ParameterDictVisibility.IMMUTABLE)

    def _do_cmd_resp(self, cmd, *args, **kwargs):
        CommandResponseInstrumentProtocol._do_cmd_resp(self, cmd, args, kwargs, write_delay=INTER_CHARACTER_DELAY)

    
    def _update_params(self, *args, **kwargs):
        """
        Update the parameter dictionary. 
        """
        
        log.debug("_update_params:")
        self._do_cmd_resp(Command.BATTERY)

    def _parse_battery_response(self, response, prompt):
        """
        Parse handler for battery command.
        @param response command response string.
        @param prompt prompt following command response.        
        @throws InstrumentProtocolException if clock command misunderstood.
        """
        log.debug("_parse_battery_response: response=%s, prompt=%s" %(response, prompt))
        if prompt == Prompt.UNRECOGNIZED_COMMAND: 
            raise InstrumentProtocolException('battery command not recognized: %s.' % response)

        if not self._param_dict.update(response):
            raise InstrumentProtocolException('battery command not parsed: %s.' % response)

        return
class InstrumentProtocol(object):
    """
        
    Base instrument protocol class.
    """    
    def __init__(self, driver_event):
        """
        Base constructor.
        @param driver_event The callback for asynchronous driver events.
        """
        # Event callback to send asynchronous events to the agent.
        self._driver_event = driver_event

        # The connection used to talk to the device.
        self._connection = None
        
        # The protocol state machine.
        self._protocol_fsm = None
        
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

    ########################################################################
    # Helper methods
    ########################################################################
    def got_data(self, data):
        """
        Called by the instrument connection when data is available.
         Defined in subclasses.
        """
        pass
    
    def get_current_state(self):
        """
        Return current state of the protocol FSM.
        """
        return self._protocol_fsm.get_current_state()

    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 = self._param_dict.get_keys()
        
        return [res_cmds, res_params]

    def _filter_capabilities(self, events):
        """
        """
        return events

    ########################################################################
    # Command build and response parse handlers.
    ########################################################################            
    def _add_response_handler(self, cmd, func, state=None):
        """
        Insert a handler class responsible for handling the response to a
        command sent to the instrument, optionally available only in a
        specific state.
        
        @param cmd The high level key of the command to respond to.
        @param func The function that handles the response
        @param state The state to pair with the command for which the function
        should be used
        """
        if state == None:
            self._response_handlers[cmd] = func
        else:            
            self._response_handlers[(state, cmd)] = func

    def _add_build_handler(self, cmd, func):
        """
        Add a command building function.
        @param cmd The device command to build.
        @param func The function that constructs the command.
        """
        self._build_handlers[cmd] = func
        
    ########################################################################
    # Helpers to build commands.
    ########################################################################
    def _build_simple_command(self, cmd, *args):
        """
        Builder for simple commands

        @param cmd The command to build
        @param args Unused arguments
        @retval Returns string ready for sending to instrument        
        """
        return "%s%s" % (cmd, self.eoln)
    
    def _build_keypress_command(self, cmd, *args):
        """
        Builder for simple, non-EOLN-terminated commands

        @param cmd The command to build
        @param args Unused arguments
        @retval Returns string ready for sending to instrument        
        """
        return "%s" % (cmd)
    
    def _build_multi_keypress_command(self, cmd, *args):
        """
        Builder for simple, non-EOLN-terminated commands

        @param cmd The command to build
        @param args Unused arguments
        @retval Returns string ready for sending to instrument        
        """
        return "%s%s%s%s%s%s" % (cmd, cmd, cmd, cmd, cmd, cmd)

    ########################################################################
    # Static helpers to format set commands.
    ########################################################################

    @staticmethod
    def _true_false_to_string(v):
        """
        Write a boolean value to string formatted for "generic" set operations.
        Subclasses should overload this as needed for instrument-specific
        formatting.
        
        @param v a boolean value.
        @retval A yes/no string formatted as a Python boolean for set operations.
        @throws InstrumentParameterException if value not a bool.
        """
        
        if not isinstance(v,bool):
            raise InstrumentParameterException('Value %s is not a bool.' % str(v))
        return str(v)

    @staticmethod
    def _int_to_string(v):
        """
        Write an int value to string formatted for "generic" set operations.
        Subclasses should overload this as needed for instrument-specific
        formatting.
        
        @param v An int val.
        @retval an int string formatted for generic set operations.
        @throws InstrumentParameterException if value not an int.
        """
        
        if not isinstance(v,int):
            raise InstrumentParameterException('Value %s is not an int.' % str(v))
        else:
            return '%i' % v

    @staticmethod
    def _float_to_string(v):
        """
        Write a float value to string formatted for "generic" set operations.
        Subclasses should overload this as needed for instrument-specific
        formatting.
        
        @param v A float val.
        @retval a float string formatted for "generic" set operations.
        @throws InstrumentParameterException if value is not a float.
        """

        if not isinstance(v,float):
            raise InstrumentParameterException('Value %s is not a float.' % v)
        else:
            return '%e' % v
예제 #16
0
class Protocol(McLaneProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """

    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        McLaneProtocol.__init__(self, prompts, newline, driver_event)

        # TODO - reset next_port on mechanical refresh of the PPS filters - how is the driver notified?
        # TODO - need to persist state for next_port to save driver restart
        self.next_port = 1  # next available port

        # get the following:
        # - VERSION
        # - CAPACITY (pump flow)
        # - BATTERY
        # - CODES (termination codes)
        # - COPYRIGHT (termination codes)

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        filling = self.get_current_state() == ProtocolState.FILL
        log.debug("_got_chunk:\n%s", chunk)
        sample_dict = self._extract_sample(
            PPSDNSampleDataParticle, PPSDNSampleDataParticle.regex_compiled(), chunk, timestamp, publish=filling
        )

        if sample_dict:
            self._linebuf = ""
            self._promptbuf = ""
            self._protocol_fsm.on_event(
                ProtocolEvent.PUMP_STATUS, PPSDNSampleDataParticle.regex_compiled().search(chunk)
            )

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(
            Parameter.FLUSH_VOLUME,
            r"Flush Volume: (.*)mL",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FLUSH_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Flush Volume",
            description="Amount of sea water to flush prior to taking sample: (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.FLUSH_FLOWRATE,
            r"Flush Flow Rate: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FLUSH_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Flush Flow Rate",
            description="Rate at which to flush: (100 - 250)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.FLUSH_MINFLOW,
            r"Flush Min Flow: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FLUSH_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Flush Minimum Flow",
            description="If the flow rate falls below this value the flush will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.FILL_VOLUME,
            r"Fill Volume: (.*)mL",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Fill Volume",
            description="Amount of seawater to run through the collection filter (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.FILL_FLOWRATE,
            r"Fill Flow Rate: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Fill Flow Rate",
            description="Flow rate during sampling: (100 - 250)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.FILL_MINFLOW,
            r"Fill Min Flow: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Fill Minimum Flow",
            description="If the flow rate falls below this value the fill will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.CLEAR_VOLUME,
            r"Reverse Volume: (.*)mL",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=CLEAR_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Clear Volume",
            description="Amount of sea water to flush the home port after taking sample: (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.CLEAR_FLOWRATE,
            r"Reverse Flow Rate: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=CLEAR_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Clear Flow Rate",
            description="Rate at which to flush: (100 - 250)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.CLEAR_MINFLOW,
            r"Reverse Min Flow: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=CLEAR_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Clear Minimum Flow",
            description="If the flow rate falls below this value the reverse flush will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )

        self._param_dict.set_value(Parameter.FLUSH_VOLUME, FLUSH_VOLUME)
        self._param_dict.set_value(Parameter.FLUSH_FLOWRATE, FLUSH_RATE)
        self._param_dict.set_value(Parameter.FLUSH_MINFLOW, FLUSH_MIN_RATE)
        self._param_dict.set_value(Parameter.FILL_VOLUME, FILL_VOLUME)
        self._param_dict.set_value(Parameter.FILL_FLOWRATE, FILL_RATE)
        self._param_dict.set_value(Parameter.FILL_MINFLOW, FILL_MIN_RATE)
        self._param_dict.set_value(Parameter.CLEAR_VOLUME, CLEAR_VOLUME)
        self._param_dict.set_value(Parameter.CLEAR_FLOWRATE, CLEAR_RATE)
        self._param_dict.set_value(Parameter.CLEAR_MINFLOW, CLEAR_MIN_RATE)
 def test_empty_schema(self):
     self.param_dict = ProtocolParameterDict()
     result = self.param_dict.generate_dict()
     self.assertEqual(result, {})
class TestUnitProtocolParameterDict(TestUnitStringsDict):
    """
    Test cases for instrument driver class. Functions in this class provide
    instrument driver unit tests and provide a tutorial on use of
    the driver interface.
    """

    __test__ = True

    @staticmethod
    def pick_byte2(input_val):
        """ Get the 2nd byte as an example of something tricky and
        arbitrary"""
        val = int(input_val) >> 8
        val &= 255
        return val

    def setUp(self):
        self.param_dict = ProtocolParameterDict()

        self.param_dict.add("foo", r'.*foo=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=True,
                            startup_param=True,
                            default_value=10,
                            visibility=ParameterDictVisibility.READ_WRITE)
        self.param_dict.add("bar", r'.*bar=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=False,
                            startup_param=True,
                            default_value=15,
                            visibility=ParameterDictVisibility.READ_WRITE)
        self.param_dict.add("baz", r'.*baz=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=True,
                            default_value=20,
                            visibility=ParameterDictVisibility.DIRECT_ACCESS,
                            get_timeout=30,
                            set_timeout=40,
                            display_name="Baz",
                            description="The baz parameter",
                            type=ParameterDictType.INT,
                            units="nano-bazers",
                            value_description="Should be an integer between 2 and 2000")
        self.param_dict.add("bat", r'.*bat=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            default_value=20,
                            visibility=ParameterDictVisibility.READ_ONLY,
                            get_timeout=10,
                            set_timeout=20,
                            display_name="Bat",
                            description="The bat parameter",
                            type=ParameterDictType.INT,
                            units="nano-batbit",
                            value_description="Should be an integer between 1 and 1000")
        self.param_dict.add("qux", r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.READ_ONLY)
        self.param_dict.add("pho", r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.IMMUTABLE)
        self.param_dict.add("dil", r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.IMMUTABLE)
        self.param_dict.add("qut", r'.*qut=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=True,
                            default_value=[10, 100],
                            visibility=ParameterDictVisibility.DIRECT_ACCESS,
                            expiration=1,
                            get_timeout=10,
                            set_timeout=20,
                            display_name="Qut",
                            description="The qut list parameter",
                            type=ParameterDictType.LIST,
                            units="nano-qutters",
                            value_description="Should be a 2-10 element list of integers between 2 and 2000")

        self.target_schema = {
            "bar": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": True,
                "value": {
                    "default": 15
                },
                "visibility": "READ_WRITE",
                "range": None,
            },
            "bat": {
                "description": "The bat parameter",
                "direct_access": False,
                "display_name": "Bat",
                "get_timeout": 10,
                "set_timeout": 20,
                "startup": False,
                "value": {
                    "default": 20,
                    "description": "Should be an integer between 1 and 1000",
                    "type": "int",
                    "units": "nano-batbit"
                },
                "visibility": "READ_ONLY",
                "range": None,
            },
            "baz": {
                "description": "The baz parameter",
                "direct_access": True,
                "display_name": "Baz",
                "get_timeout": 30,
                "set_timeout": 40,
                "startup": False,
                "value": {
                    "default": 20,
                    "description": "Should be an integer between 2 and 2000",
                    "type": "int",
                    "units": "nano-bazers"
                },
                "visibility": "DIRECT_ACCESS",
                "range": None,
            },
            "dil": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "IMMUTABLE",
                "range": None,
            },
            "foo": {
                "direct_access": True,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": True,
                "value": {
                    "default": 10
                },
                "visibility": "READ_WRITE",
                "range": None,
            },
            "pho": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "IMMUTABLE",
                "range": None,
            },
            "qut": {
                "description": "The qut list parameter",
                "direct_access": True,
                "display_name": "Qut",
                "get_timeout": 10,
                "set_timeout": 20,
                "startup": False,
                "value": {
                    "default": [
                        10,
                        100
                    ],
                    "description": "Should be a 2-10 element list of integers between 2 and 2000",
                    "type": "list",
                    "units": "nano-qutters"
                },
                "visibility": "DIRECT_ACCESS",
                "range": None,
            },
            "qux": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "READ_ONLY",
                "range": None,
            }
        }

        self.test_yaml = '''
        parameters: {
            qut: {
            description: "QutFileDesc",
            units: "QutFileUnits",
            value_description: "QutFileValueDesc",
            type: "QutFileType",
            display_name: "QutDisplay"
            },
            extra_param: {
            description: "ExtraFileDesc",
            units: "ExtraFileUnits",
            value_description: "ExtraFileValueDesc",
            type: "ExtraFileType"
            }
          }

        commands: {
          dummy: stuff
          }
        '''

    def test_get_direct_access_list(self):
        """
        Test to see we can get a list of direct access parameters
        """
        result = self.param_dict.get_direct_access_list()
        self.assertTrue(isinstance(result, list))
        self.assertEquals(len(result), 3)
        self.assert_("foo" in result)
        self.assert_("baz" in result)
        self.assert_("qut" in result)

    def test_get_startup_list(self):
        """
        Test to see we can get a list of direct access parameters
        """
        result = self.param_dict.get_startup_list()
        self.assertTrue(isinstance(result, list))
        self.assertEquals(len(result), 2)
        self.assert_("foo" in result)
        self.assert_("bar" in result)

    def test_set_default(self):
        """
        Test setting a default value
        """
        result = self.param_dict.get_config()
        self.assertEquals(result["foo"], None)
        self.param_dict.set_default("foo")
        self.assertEquals(self.param_dict.get("foo"), 10)
        self.param_dict.update("foo=1000")
        self.assertEquals(self.param_dict.get("foo"), 1000)
        self.param_dict.set_default("foo")
        self.assertEquals(self.param_dict.get("foo"), 10)

        self.assertRaises(ValueError, self.param_dict.set_default, "qux")

    def test_update_many(self):
        """
        Test updating of multiple variables from the same input
        """
        sample_input = """
foo=100
bar=200, baz=300
"""
        self.assertNotEquals(self.param_dict.get("foo"), 100)
        self.assertNotEquals(self.param_dict.get("bar"), 200)
        self.assertNotEquals(self.param_dict.get("baz"), 300)
        result = self.param_dict.update_many(sample_input)
        log.debug("result: %s", result)
        self.assertEquals(result["foo"], True)
        self.assertEquals(result["bar"], True)
        self.assertEquals(result["baz"], True)
        self.assertEquals(self.param_dict.get("foo"), 100)
        self.assertEquals(self.param_dict.get("bar"), 200)
        self.assertEquals(self.param_dict.get("baz"), 300)

    def test_update_specific_values(self):
        """
        test to verify we can limit update to a specific
        set of parameters
        """
        sample_input = "foo=100, bar=200"

        # First verify we can set both
        self.assertNotEquals(self.param_dict.get("foo"), 100)
        self.assertNotEquals(self.param_dict.get("bar"), 200)
        self.assertTrue(self.param_dict.update(sample_input))
        self.assertEquals(self.param_dict.get("foo"), 100)
        self.assertEquals(self.param_dict.get("bar"), 200)

        # Now let's only have it update 1 parameter with a name
        sample_input = "foo=200, bar=300"
        self.assertTrue(self.param_dict.update(sample_input, target_params="foo"))
        self.assertEquals(self.param_dict.get("foo"), 200)
        self.assertEquals(self.param_dict.get("bar"), 200)

        # Now let's only have it update 1 parameter using a list
        sample_input = "foo=300, bar=400"
        self.assertTrue(self.param_dict.update(sample_input, target_params=["foo"]))
        self.assertEquals(self.param_dict.get("foo"), 300)
        self.assertEquals(self.param_dict.get("bar"), 200)

        # Test our exceptions
        with self.assertRaises(KeyError):
            self.param_dict.update(sample_input, "key_does_not_exist")

        with self.assertRaises(InstrumentParameterException):
            self.param_dict.update(sample_input, {'bad': "key_does_not_exist"})

    def test_visibility_list(self):
        lst = self.param_dict.get_visibility_list(ParameterDictVisibility.READ_WRITE)
        lst.sort()
        self.assertEquals(lst, ["bar", "foo"])
        lst = self.param_dict.get_visibility_list(ParameterDictVisibility.DIRECT_ACCESS)
        lst.sort()
        self.assertEquals(lst, ["baz", "qut"])
        lst = self.param_dict.get_visibility_list(ParameterDictVisibility.READ_ONLY)
        lst.sort()
        self.assertEquals(lst, ["bat", "qux"])
        lst = self.param_dict.get_visibility_list(ParameterDictVisibility.IMMUTABLE)
        lst.sort()
        self.assertEquals(lst, ["dil", "pho"])

    def test_function_values(self):
        """
        Make sure we can add and update values with functions instead of patterns
        """

        self.param_dict.add_parameter(
                FunctionParameter("fn_foo",
                                  self.pick_byte2,
                                  lambda x: str(x),
                                  direct_access=True,
                                  startup_param=True,
                                  value=1,
                                  visibility=ParameterDictVisibility.READ_WRITE)
        )
        self.param_dict.add_parameter(
                FunctionParameter("fn_bar",
                                  lambda x: bool(x & 2),  # bit map example
                                  lambda x: str(x),
                                  direct_access=True,
                                  startup_param=True,
                                  value=False,
                                  visibility=ParameterDictVisibility.READ_WRITE)
        )

        # check defaults just to be safe
        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 1)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, False)

        self.param_dict.update(1005)  # just change first in list
        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 3)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, False)

        # fn_bar does not get updated here
        result = self.param_dict.update_many(1205)
        self.assertEqual(result['fn_foo'], True)
        self.assertEqual(len(result), 1)
        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 4)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, False)

        # both are updated now
        result = self.param_dict.update_many(6)
        self.assertEqual(result['fn_foo'], True)
        self.assertEqual(result['fn_bar'], True)
        self.assertEqual(len(result), 2)

        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 0)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, True)

    def test_mixed_pdv_types(self):
        """ Verify we can add different types of PDVs in one container """
        self.param_dict.add_parameter(
                FunctionParameter("fn_foo",
                                  self.pick_byte2,
                                  lambda x: str(x),
                                  direct_access=True,
                                  startup_param=True,
                                  value=1,
                                  visibility=ParameterDictVisibility.READ_WRITE)
        )
        self.param_dict.add_parameter(
                RegexParameter("foo", r'.*foo=(\d+).*',
                               lambda match: int(match.group(1)),
                               lambda x: str(x),
                               direct_access=True,
                               startup_param=True,
                               value=10,
                               visibility=ParameterDictVisibility.READ_WRITE)
        )
        self.param_dict.add("bar", r'.*bar=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=False,
                            startup_param=True,
                            value=15,
                            visibility=ParameterDictVisibility.READ_WRITE)

        self.assertEqual(self.param_dict.get("fn_foo"), 1)
        self.assertEqual(self.param_dict.get("foo"), 10)
        self.assertEqual(self.param_dict.get("bar"), 15)

    def test_base_update(self):
        pdv = Parameter("foo",
                        lambda x: str(x),
                        value=12)
        self.assertEqual(pdv.get_value(), 12)
        result = pdv.update(1)
        self.assertEqual(result, True)
        self.assertEqual(pdv.get_value(), 1)

        # Its a base class...monkey see, monkey do
        result = pdv.update("foo=1")
        self.assertEqual(result, True)
        self.assertEqual(pdv.get_value(), "foo=1")

    def test_regex_val(self):
        pdv = RegexParameter("foo",
                             r'.*foo=(\d+).*',
                             lambda match: int(match.group(1)),
                             lambda x: str(x),
                             value=12)
        self.assertEqual(pdv.get_value(), 12)
        result = pdv.update(1)
        self.assertEqual(result, False)
        self.assertEqual(pdv.get_value(), 12)
        result = pdv.update("foo=1")
        self.assertEqual(result, True)
        self.assertEqual(pdv.get_value(), 1)

    def test_function_val(self):
        pdv = FunctionParameter("foo",
                                self.pick_byte2,
                                lambda x: str(x),
                                value=12)
        self.assertEqual(pdv.get_value(), 12)
        self.assertRaises(TypeError, pdv.update(1))
        result = pdv.update("1205")
        self.assertEqual(pdv.get_value(), 4)
        self.assertEqual(result, True)

    def test_set_init_value(self):
        result = self.param_dict.get("foo")
        self.assertEqual(result, None)
        self.param_dict.set_init_value("foo", 42)
        result = self.param_dict.get_init_value("foo")
        self.assertEqual(result, 42)

    def test_schema_generation(self):
        self.maxDiff = None
        result = self.param_dict.generate_dict()
        json_result = json.dumps(result, indent=4, sort_keys=True)
        log.debug("Expected: %s", self.target_schema)
        log.debug("Result: %s", json_result)
        self.assertEqual(result, self.target_schema)

    def test_empty_schema(self):
        self.param_dict = ProtocolParameterDict()
        result = self.param_dict.generate_dict()
        self.assertEqual(result, {})

    def test_bad_descriptions(self):
        self.param_dict._param_dict["foo"].description = None
        self.param_dict._param_dict["foo"].value = None
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_init_value, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_default_value, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.set_default, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_init_value, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_menu_path_read, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_submenu_read, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_menu_path_write, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_submenu_write, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.format, "foo", 1)
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_direct_access_list)
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.is_startup_param, "foo")

    def test_set(self):
        """
        Test a simple set of the parameter. Make sure the right values get
        called and the correct exceptions are raised.
        """
        new_param = FunctionParameter("foo",
                                      self.pick_byte2,
                                      lambda x: str(x),
                                      direct_access=True,
                                      startup_param=True,
                                      value=1000,
                                      visibility=ParameterDictVisibility.READ_WRITE)
        self.assertEquals(new_param.get_value(), 1000)
        self.assertEquals(self.param_dict.get("foo"), None)
        # overwrites existing param
        self.param_dict.add_parameter(new_param)
        self.assertEquals(self.param_dict.get("foo"), 1000)
        self.param_dict.set_value("foo", 2000)
        self.assertEquals(self.param_dict.get("foo"), 2000)

    def test_invalid_type(self):
        self.assertRaises(InstrumentParameterException,
                          FunctionParameter,
                          "fn_bar",
                          lambda x: bool(x & 2),  # bit map example
                          lambda x: str(x),
                          direct_access=True,
                          startup_param=True,
                          value=False,
                          type="bad_type",
                          visibility=ParameterDictVisibility.READ_WRITE)

    def test_get(self):
        """
        test getting values with expiration
        """
        # from mi.core.exceptions import InstrumentParameterExpirationException
        pd = ProtocolParameterDict()

        # No expiration, should work just fine
        pd.add('noexp', r'', None, None, expiration=None)
        pd.add('zeroexp', r'', None, None, expiration=0)
        pd.add('lateexp', r'', None, None, expiration=2)

        ###
        # Set and get with no expire
        ###
        pd.set_value('noexp', 1)
        self.assertEqual(pd.get('noexp'), 1)

        ###
        # Set and get with a 0 expire
        ###
        basetime = pd.get_current_timestamp()
        pd.set_value('zeroexp', 2)

        # We should fail because we are calculating exp against current time
        with self.assertRaises(InstrumentParameterExpirationException):
            pd.get('zeroexp')

        # Should succeed because exp is calculated using basetime
        self.assertEqual(pd.get('zeroexp', basetime), 2)

        ###
        # Set and get with a delayed expire
        ###
        basetime = pd.get_current_timestamp()
        futuretime = pd.get_current_timestamp(3)
        self.assertGreater(futuretime - basetime, 3)

        pd.set_value('lateexp', 2)

        # Success because data is not expired
        self.assertEqual(pd.get('lateexp', basetime), 2)

        # Fail because data is expired (simulated three seconds from now)
        with self.assertRaises(InstrumentParameterExpirationException):
            pd.get('lateexp', futuretime)

    def test_regex_flags(self):
        pdv = RegexParameter("foo",
                             r'.+foo=(\d+).+',
                             lambda match: int(match.group(1)),
                             lambda x: str(x),
                             regex_flags=re.DOTALL,
                             value=12)
        # Assert something good with dotall update()
        self.assertTrue(pdv)
        pdv.update("\n\nfoo=1212\n\n")
        self.assertEqual(pdv.get_value(), 1212)

        # negative test with no regex_flags
        pdv = RegexParameter("foo",
                             r'.+foo=(\d+).+',
                             lambda match: int(match.group(1)),
                             lambda x: str(x),
                             value=12)
        # Assert something good with dotall update()
        self.assertTrue(pdv)
        pdv.update("\n\nfoo=1212\n\n")
        self.assertEqual(pdv.get_value(), 12)

        self.assertRaises(TypeError,
                          RegexParameter,
                          "foo",
                          r'.*foo=(\d+).*',
                          lambda match: int(match.group(1)),
                          lambda x: str(x),
                          regex_flags="bad flag",
                          value=12)

    def test_format_current(self):
        self.param_dict.add("test_format", r'.*foo=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: x + 5,
                            value=10)
        self.assertEqual(self.param_dict.format("test_format", 20), 25)
        self.assertEqual(self.param_dict.format("test_format"), 15)
        self.assertRaises(KeyError,
                          self.param_dict.format, "bad_name")

    def _assert_metadata_change(self):
        new_dict = self.param_dict.generate_dict()
        log.debug("Generated dictionary: %s", new_dict)
        self.assertEqual(new_dict["qut"][ParameterDictKey.DESCRIPTION], "QutFileDesc")
        self.assertEqual(new_dict["qut"][ParameterDictKey.DISPLAY_NAME], "QutDisplay")
        self.assertEqual(new_dict["qut"][ParameterDictKey.VALUE][ParameterDictKey.UNITS], "QutFileUnits")
        self.assertEqual(new_dict["qut"][ParameterDictKey.VALUE][ParameterDictKey.DESCRIPTION], "QutFileValueDesc")
        self.assertEqual(new_dict["qut"][ParameterDictKey.VALUE][ParameterDictKey.TYPE], "QutFileType")
        # Should come from hard code
        # self.assertEqual(new_dict["qut"][ParameterDictKey.DISPLAY_NAME], "QutFileName")

        # from base hard code
        new_dict = self.param_dict.generate_dict()
        self.assertEqual(new_dict["baz"][ParameterDictKey.DESCRIPTION],
                         "The baz parameter")
        self.assertEqual(new_dict["baz"][ParameterDictKey.VALUE][ParameterDictKey.UNITS],
                         "nano-bazers")
        self.assertEqual(new_dict["baz"][ParameterDictKey.VALUE][ParameterDictKey.DESCRIPTION],
                         "Should be an integer between 2 and 2000")
        self.assertEqual(new_dict["baz"][ParameterDictKey.VALUE][ParameterDictKey.TYPE],
                         ParameterDictType.INT)
        self.assertEqual(new_dict["baz"][ParameterDictKey.DISPLAY_NAME], "Baz")

        self.assertTrue('extra_param' not in new_dict)
예제 #19
0
class Protocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline,
                                                   driver_event)

        # Build protocol state machine.
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent,
                                           ProtocolEvent.ENTER,
                                           ProtocolEvent.EXIT)

        # Add event handlers for protocol state machine.
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN,
                                       ProtocolEvent.ENTER,
                                       self._handler_unknown_enter)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN,
                                       ProtocolEvent.EXIT,
                                       self._handler_unknown_exit)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN,
                                       ProtocolEvent.DISCOVER,
                                       self._handler_unknown_discover)

        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.ENTER,
                                       self._handler_command_enter)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.EXIT,
                                       self._handler_command_exit)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.START_DIRECT,
                                       self._handler_command_start_direct)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.CLOCK_SYNC,
                                       self._handler_sync_clock)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.GET, self._handler_get)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.SET,
                                       self._handler_command_set)

        self._protocol_fsm.add_handler(ProtocolState.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.EXECUTE_DIRECT,
            self._handler_direct_access_execute_direct)
        self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS,
                                       ProtocolEvent.STOP_DIRECT,
                                       self._handler_direct_access_stop_direct)

        # Add build handlers for device commands.
        self._add_build_handler(Command.BATTERY, self._build_simple_command)

        # Add response handlers for device commands.
        self._add_response_handler(Command.BATTERY,
                                   self._parse_battery_response)

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

        self._chunker = StringChunker(Protocol.sieve_function)

        self._add_scheduler_event(ScheduledJob.CLOCK_SYNC,
                                  ProtocolEvent.CLOCK_SYNC)

        # Start state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples and status
        """
        matchers = []
        return_list = []

        matchers.append(RASFL_SampleDataParticle.regex_compiled())

        for matcher in matchers:
            for match in matcher.finditer(raw_data):
                return_list.append((match.start(), match.end()))
        """
        if return_list != []:
            log.debug("sieve_function: raw_data=%s, return_list=%s" %(raw_data, return_list))
        """
        return return_list

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        log.debug("_got_chunk: chunk=%s" % chunk)
        self._extract_sample(RASFL_SampleDataParticle,
                             RASFL_SampleDataParticle.regex_compiled(), chunk,
                             timestamp)

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        """
        return [x for x in events if Capability.has(x)]

    ########################################################################
    # implement virtual methods from base class.
    ########################################################################

    def apply_startup_params(self):
        """
        Apply sample_interval startup parameter.  
        """

        config = self.get_startup_config()
        log.debug("apply_startup_params: startup config = %s" % config)
        if config.has_key(Parameter.SAMPLE_INTERVAL):
            log.debug("apply_startup_params: setting sample_interval to %d" %
                      config[Parameter.SAMPLE_INTERVAL])
            self._param_dict.set_value(Parameter.SAMPLE_INTERVAL,
                                       config[Parameter.SAMPLE_INTERVAL])

    ########################################################################
    # Unknown handlers.
    ########################################################################

    def _handler_unknown_enter(self, *args, **kwargs):
        """
        Enter unknown state.
        """
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_unknown_exit(self, *args, **kwargs):
        """
        Exit unknown state.
        """
        pass

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state; can only be COMMAND (instrument has no actual AUTOSAMPLE mode).
        @retval (next_state, result), (ProtocolState.COMMAND, None) if successful.
        """

        # force to command mode, this instrument has no autosample mode
        next_state = ProtocolState.COMMAND
        result = ResourceAgentState.COMMAND

        log.debug("_handler_unknown_discover: state = %s", next_state)
        return (next_state, result)

    ########################################################################
    # Command handlers.
    # just implemented to make DA possible, instrument has no actual command mode
    ########################################################################

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        """

        # Command device to update parameters and send a config change event if needed.
        self._update_params()

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_command_exit(self, *args, **kwargs):
        """
        Exit command state.
        """
        pass

    def _handler_command_set(self, *args, **kwargs):
        """
        no writable parameters so does nothing, just implemented to make framework happy
        """

        next_state = None
        result = None
        return (next_state, result)

    def _handler_command_start_direct(self, *args, **kwargs):
        """
        """
        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)

        return (next_state, result)

    def _handler_direct_access_stop_direct(self):
        result = None
        next_state = ProtocolState.COMMAND
        next_agent_state = ResourceAgentState.COMMAND

        return (next_state, (next_agent_state, result))

    ########################################################################
    # general handlers.
    ########################################################################

    def _handler_sync_clock(self, *args, **kwargs):
        """
        sync clock close to a second edge 
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device respond correctly.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """

        next_state = None
        next_agent_state = None
        result = None

        time_format = "%Y/%m/%d %H:%M:%S"
        str_val = get_timestamp_delayed(time_format)
        log.debug("Setting instrument clock to '%s'", str_val)
        self._do_cmd_resp(Command.STOP, expected_prompt=Prompt.STOPPED)
        try:
            self._do_cmd_resp(Command.SET_CLOCK,
                              str_val,
                              expected_prompt=Prompt.CR_NL)
        finally:
            # ensure that we try to start the instrument sampling again
            self._do_cmd_resp(Command.GO, expected_prompt=Prompt.GO)

        return (next_state, (next_agent_state, result))

    ########################################################################
    # Private helpers.
    ########################################################################

    def _wakeup(self, wakeup_timeout=10, response_timeout=3):
        """
        Over-ridden because waking this instrument up is a multi-step process with
        two different requests required
        @param wakeup_timeout The timeout to wake the device.
        @param response_timeout The time to look for response to a wakeup attempt.
        @throw InstrumentTimeoutException if the device could not be woken.
        """
        sleep_time = .1
        command = Command.END_OF_LINE

        # Grab start time for overall wakeup timeout.
        starttime = time.time()

        while True:
            # Clear the prompt buffer.
            log.debug("_wakeup: clearing promptbuf: %s" % self._promptbuf)
            self._promptbuf = ''

            # Send a command and wait delay amount for response.
            log.debug('_wakeup: Sending command %s, delay=%s' %
                      (command.encode("hex"), response_timeout))
            for char in command:
                self._connection.send(char)
                time.sleep(INTER_CHARACTER_DELAY)
            sleep_amount = 0
            while True:
                time.sleep(sleep_time)
                if self._promptbuf.find(Prompt.COMMAND_INPUT) != -1:
                    # instrument is awake
                    log.debug('_wakeup: got command input prompt %s' %
                              Prompt.COMMAND_INPUT)
                    # add inter-character delay which _do_cmd_resp() incorrectly doesn't add to the start of a transmission
                    time.sleep(INTER_CHARACTER_DELAY)
                    return Prompt.COMMAND_INPUT
                if self._promptbuf.find(Prompt.ENTER_CTRL_C) != -1:
                    command = Command.CONTROL_C
                    break
                if self._promptbuf.find(Prompt.PERIOD) == 0:
                    command = Command.CONTROL_C
                    break
                sleep_amount += sleep_time
                if sleep_amount >= response_timeout:
                    log.debug(
                        "_wakeup: expected response not received, buffer=%s" %
                        self._promptbuf)
                    break

            if time.time() > starttime + wakeup_timeout:
                raise InstrumentTimeoutException(
                    "_wakeup(): instrument failed to wakeup in %d seconds time"
                    % wakeup_timeout)

    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False)

    def _build_command_dict(self):
        """
        Populate the command dictionary with command.
        """
        self._cmd_dict.add(Capability.CLOCK_SYNC,
                           display_name="synchronize clock")

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.BATTERY,
                             r'Battery: (.*)V',
                             lambda match: match.group(1),
                             lambda string: str(string),
                             type=ParameterDictType.STRING,
                             display_name="battery",
                             expiration=0,
                             visibility=ParameterDictVisibility.READ_ONLY)

        self._param_dict.add(
            Parameter.SAMPLE_INTERVAL,
            r'Not used. This parameter is not parsed from instrument response',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=30,
            value=30,
            startup_param=True,
            display_name="sample_interval",
            visibility=ParameterDictVisibility.IMMUTABLE)

    def _do_cmd_resp(self, cmd, *args, **kwargs):
        CommandResponseInstrumentProtocol._do_cmd_resp(
            self, cmd, args, kwargs, write_delay=INTER_CHARACTER_DELAY)

    def _update_params(self, *args, **kwargs):
        """
        Update the parameter dictionary. 
        """

        log.debug("_update_params:")
        self._do_cmd_resp(Command.BATTERY)

    def _parse_battery_response(self, response, prompt):
        """
        Parse handler for battery command.
        @param response command response string.
        @param prompt prompt following command response.        
        @throws InstrumentProtocolException if clock command misunderstood.
        """
        log.debug("_parse_battery_response: response=%s, prompt=%s" %
                  (response, prompt))
        if prompt == Prompt.UNRECOGNIZED_COMMAND:
            raise InstrumentProtocolException(
                'battery command not recognized: %s.' % response)

        if not self._param_dict.update(response):
            raise InstrumentProtocolException(
                'battery command not parsed: %s.' % response)

        return
예제 #20
0
class Protocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """

    __metaclass__ = META_LOGGER

    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = ThreadSafeFSM(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.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_autosample_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample),
                (ProtocolEvent.VERY_LONG_COMMAND, self._very_long_command),
            ],
            ProtocolState.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_command_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample),
                (ProtocolEvent.VERY_LONG_COMMAND, self._very_long_command),
            ],
        }

        for state in handlers:
            for event, handler in handlers[state]:
                self._protocol_fsm.add_handler(state, event, handler)

        # Construct the metadata dictionaries
        self._build_command_dict()
        self._build_driver_dict()

        # Start state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)

        # set up scheduled event handling
        self.initialize_scheduler()
        self._schedulers = []

    def _generate_particle(self, stream_name, count=1):
        # we're faking it anyway, send these as fast as we can...
        # the overall rate will be close enough
        particle = VirtualParticle(stream_name, port_timestamp=0)
        for x in range(count):
            particle.contents["port_timestamp"] = ntplib.system_to_ntp_time(time.time())
            self._driver_event(DriverAsyncEvent.SAMPLE, particle.generate())
            time.sleep(0.001)

    def _create_scheduler(self, stream_name, rate):
        job_name = stream_name

        if rate > 1:
            interval = 1
        else:
            interval = 1 / rate

        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.INTERVAL,
                        DriverSchedulerConfigKey.SECONDS: interval,
                    }
                }
            }
        }
        self.set_init_params(config)
        self._schedulers.append(stream_name)

        if rate > 1:
            self._add_scheduler(stream_name, functools.partial(self._generate_particle, stream_name, count=rate))
        else:
            self._add_scheduler(stream_name, functools.partial(self._generate_particle, stream_name))

    def _delete_all_schedulers(self):
        for name in self._schedulers:
            try:
                self._remove_scheduler(name)
            except:
                pass

    def _got_chunk(self, chunk, ts):
        """
        Process chunk output by the chunker.  Generate samples and (possibly) react
        @param chunk: data
        @param ts: ntp timestamp
        @return sample
        @throws InstrumentProtocolException
        """
        return

    def _filter_capabilities(self, events):
        """
        Filter a list of events to only include valid capabilities
        @param events: list of events to be filtered
        @return: list of filtered events
        """
        return [x for x in events if Capability.has(x)]

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.START_AUTOSAMPLE, display_name="Start Autosample")
        self._cmd_dict.add(Capability.STOP_AUTOSAMPLE, display_name="Stop Autosample")

    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False)

    def _update_params(self, *args, **kwargs):
        """
        Update the param dictionary based on instrument response
        """

    def _set_params(self, *args, **kwargs):
        if len(args) < 1:
            raise InstrumentParameterException("Set command requires a parameter dict.")
        params = args[0]

        if not isinstance(params, dict):
            raise InstrumentParameterException("Set parameters not a dict.")

        self._param_dict = ProtocolParameterDict()

        for param in params:
            log.info("Creating new parameter: %s", param)
            self._param_dict.add(param, "", None, None)
            self._param_dict.set_value(param, params[param])

    def set_init_params(self, config):
        if not isinstance(config, dict):
            raise InstrumentParameterException("Invalid init config format")

        self._startup_config = config

        param_config = config.get(DriverConfigKey.PARAMETERS)
        if param_config:
            for name in param_config.keys():
                self._param_dict.add(name, "", None, None)
                log.debug("Setting init value for %s to %s", name, param_config[name])
                self._param_dict.set_init_value(name, param_config[name])

    def _very_long_command(self, *args, **kwargs):
        return None, time.sleep(30)

    ########################################################################
    # Unknown handlers.
    ########################################################################

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Process discover event
        """
        next_state = ProtocolState.COMMAND
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Autosample handlers.
    ########################################################################

    def _handler_autosample_enter(self, *args, **kwargs):
        """
        Enter autosample state.
        """
        self._init_params()

        for stream_name in self._param_dict.get_keys():
            self._create_scheduler(stream_name, self._param_dict.get(stream_name))
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_autosample_stop_autosample(self, *args, **kwargs):
        """
        Stop autosample
        @return next_state, (next_state, result)
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._delete_all_schedulers()
        return next_state, (next_state, result)

    ########################################################################
    # Command handlers.
    ########################################################################

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        """
        self._init_params()
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_command_get(self, *args, **kwargs):
        """
        Process GET event
        """
        return self._handler_get(*args, **kwargs)

    def _handler_command_set(self, *args, **kwargs):
        """
        Perform a set command.
        @param args[0] parameter : value dict.
        @throws InstrumentParameterException
        """
        next_state = None
        result = []
        self._set_params(*args, **kwargs)
        return next_state, (next_state, result)

    def _handler_command_start_autosample(self):
        """
        Start autosample
        @return next_state, (next_state, result)
        """
        next_state = ProtocolState.AUTOSAMPLE
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Generic handlers.
    ########################################################################

    def _handler_generic_enter(self, *args, **kwargs):
        """
        Generic enter state handler
        """
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_generic_exit(self, *args, **kwargs):
        """
예제 #21
0
class Protocol(SamiProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        SamiProtocol.__init__(self, prompts, newline, driver_event)

        # Build protocol state machine.

        ###
        # most of these are defined in the base class with exception of handlers
        # defined below that differ for the two instruments (what defines
        # success and the timeout duration)
        ###

        # this state would be entered whenever an ACQUIRE_SAMPLE event occurred
        # while in the AUTOSAMPLE state and will last anywhere from a few
        # seconds to ~12 minutes depending on instrument and the type of
        # sampling.
        self._protocol_fsm.add_handler(ProtocolState.SCHEDULED_SAMPLE, ProtocolEvent.SUCCESS,
                                       self._handler_sample_success)
        self._protocol_fsm.add_handler(ProtocolState.SCHEDULED_SAMPLE, ProtocolEvent.TIMEOUT,
                                       self._handler_sample_timeout)

        # this state would be entered whenever an ACQUIRE_SAMPLE event occurred
        # while in either the COMMAND state (or via the discover transition
        # from the UNKNOWN state with the instrument unresponsive) and will
        # last anywhere from a few seconds to 3 minutes depending on instrument
        # and sample type.
        self._protocol_fsm.add_handler(ProtocolState.POLLED_SAMPLE, ProtocolEvent.SUCCESS,
                                       self._handler_sample_success)
        self._protocol_fsm.add_handler(ProtocolState.POLLED_SAMPLE, ProtocolEvent.TIMEOUT,
                                       self._handler_sample_timeout)

        # Add build handlers for device commands.
        ### primarily defined in base class
        self._add_build_handler(InstrumentCommand.ACQUIRE_SAMPLE_DEV1, self._build_sample_dev1)

        # Add response handlers for device commands.
        ### primarily defined in base class
        self._add_response_handler(InstrumentCommand.ACQUIRE_SAMPLE_DEV1, self._build_response_sample_dev1)

        # Add sample handlers

        # State state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)

        # build the chunker
        self._chunker = StringChunker(Protocol.sieve_function)

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples
        """
        return_list = []

        sieve_matchers = [REGULAR_STATUS_REGEX_MATCHER,
                          CONTROL_RECORD_REGEX_MATCHER,
                          SAMI_SAMPLE_REGEX_MATCHER,
                          DEV1_SAMPLE_REGEX_MATCHER,
                          CONFIGURATION_REGEX_MATCHER]

        for matcher in sieve_matchers:
            for match in matcher.finditer(raw_data):
                return_list.append((match.start(), match.end()))

        return return_list

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker. Pass it to
        extract_sample with the appropriate particle objects and REGEXes.
        """
        self._extract_sample(SamiRegularStatusDataParticle, REGULAR_STATUS_REGEX_MATCHER, chunk, timestamp)
        self._extract_sample(SamiControlRecordDataParticle, CONTROL_RECORD_REGEX_MATCHER, chunk, timestamp)
        self._extract_sample(Pco2wSamiSampleDataParticle, SAMI_SAMPLE_REGEX_MATCHER, chunk, timestamp)
        self._extract_sample(Pco2wDev1SampleDataParticle, DEV1_SAMPLE_REGEX_MATCHER, chunk, timestamp)
        self._extract_sample(Pco2wConfigurationDataParticle, CONFIGURATION_REGEX_MATCHER, chunk, timestamp)

    ########################################################################
    # Build Command, Driver and Parameter dictionaries
    ########################################################################

    def _build_param_dict(self):
        """
        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()

        ### example configuration string
        # VALID_CONFIG_STRING = 'CEE90B0002C7EA0001E133800A000E100402000E10010B' + \
        #                       '000000000D000000000D000000000D07' + \
        #                       '1020FF54181C01003814' + \
        #                       '000000000000000000000000000000000000000000000000000' + \
        #                       '000000000000000000000000000000000000000000000000000' + \
        #                       '0000000000000000000000000000' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + NEWLINE
        #
        ###

        self._param_dict.add(Parameter.LAUNCH_TIME, CONFIGURATION_REGEX,
                             lambda match: int(match.group(1), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='launch time')

        self._param_dict.add(Parameter.START_TIME_FROM_LAUNCH, CONFIGURATION_REGEX,
                             lambda match: int(match.group(2), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02C7EA00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='start time after launch time')

        self._param_dict.add(Parameter.STOP_TIME_FROM_START, CONFIGURATION_REGEX,
                             lambda match: int(match.group(3), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01E13380,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='stop time after start time')

        self._param_dict.add(Parameter.MODE_BITS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(4), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0A,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='mode bits (set to 00001010)')

        self._param_dict.add(Parameter.SAMI_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(5), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami sample interval')

        self._param_dict.add(Parameter.SAMI_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(6), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x04,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami driver version')

        self._param_dict.add(Parameter.SAMI_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(7), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami parameter pointer')

        self._param_dict.add(Parameter.DEVICE1_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(8), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 sample interval')

        self._param_dict.add(Parameter.DEVICE1_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(9), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 driver version')

        self._param_dict.add(Parameter.DEVICE1_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(10), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0B,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 parameter pointer')

        self._param_dict.add(Parameter.DEVICE2_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(11), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 sample interval')

        self._param_dict.add(Parameter.DEVICE2_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(12), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 driver version')

        self._param_dict.add(Parameter.DEVICE2_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(13), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 parameter pointer')

        self._param_dict.add(Parameter.DEVICE3_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(14), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 sample interval')

        self._param_dict.add(Parameter.DEVICE3_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(15), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 driver version')

        self._param_dict.add(Parameter.DEVICE3_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(16), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 parameter pointer')

        self._param_dict.add(Parameter.PRESTART_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(17), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart sample interval')

        self._param_dict.add(Parameter.PRESTART_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(18), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart driver version')

        self._param_dict.add(Parameter.PRESTART_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(19), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart parameter pointer')

        self._param_dict.add(Parameter.GLOBAL_CONFIGURATION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(20), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='global bits (set to 00000111)')

        self._param_dict.add(Parameter.PUMP_PULSE, CONFIGURATION_REGEX,
                             lambda match: int(match.group(21), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='pump pulse duration')

        self._param_dict.add(Parameter.PUMP_DURATION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(22), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x20,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='pump measurement duration')

        self._param_dict.add(Parameter.SAMPLES_PER_MEASUREMENT, CONFIGURATION_REGEX,
                             lambda match: int(match.group(23), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0xFF,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='samples per measurement')

        self._param_dict.add(Parameter.CYCLES_BETWEEN_BLANKS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(24), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0xA8,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='cycles between blanks')

        self._param_dict.add(Parameter.NUMBER_REAGENT_CYCLES, CONFIGURATION_REGEX,
                             lambda match: int(match.group(25), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x18,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of reagent cycles')

        self._param_dict.add(Parameter.NUMBER_BLANK_CYCLES, CONFIGURATION_REGEX,
                             lambda match: int(match.group(26), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x1C,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of blank cycles')

        self._param_dict.add(Parameter.FLUSH_PUMP_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(27), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='flush pump interval')

        self._param_dict.add(Parameter.BIT_SWITCHES, CONFIGURATION_REGEX,
                             lambda match: int(match.group(28), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='bit switches')

        self._param_dict.add(Parameter.NUMBER_EXTRA_PUMP_CYCLES, CONFIGURATION_REGEX,
                             lambda match: int(match.group(29), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x38,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of extra pump cycles')

        self._param_dict.add(Parameter.EXTERNAL_PUMP_SETTINGS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(30), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x14,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='external pump settings')

    #########################################################################
    ## General (for POLLED and SCHEDULED states) Sample handlers.
    #########################################################################

    def _handler_sample_success(self, *args, **kwargs):
        next_state = None
        result = None

        return (next_state, result)

    def _handler_sample_timeout(self, ):
        next_state = None
        result = None

        return (next_state, result)

    ########################################################################
    # Command handlers.
    ########################################################################
    def _build_sample_dev1(self):
        pass

    ########################################################################
    # Response handlers.
    ########################################################################
    def _build_response_sample_dev1(self):
        pass
예제 #22
0
class Protocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    __metaclass__ = META_LOGGER

    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline,
                                                   driver_event)

        # Build protocol state machine.
        self._protocol_fsm = ThreadSafeFSM(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.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_autosample_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.STOP_AUTOSAMPLE,
                 self._handler_autosample_stop_autosample),
                (ProtocolEvent.VERY_LONG_COMMAND, self._very_long_command),
            ],
            ProtocolState.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_command_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_AUTOSAMPLE,
                 self._handler_command_start_autosample),
                (ProtocolEvent.VERY_LONG_COMMAND, self._very_long_command),
            ],
        }

        for state in handlers:
            for event, handler in handlers[state]:
                self._protocol_fsm.add_handler(state, event, handler)

        # Construct the metadata dictionaries
        self._build_command_dict()
        self._build_driver_dict()

        # Start state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)

        # set up scheduled event handling
        self.initialize_scheduler()
        self._schedulers = []

    def _generate_particle(self, stream_name, count=1):
        # we're faking it anyway, send these as fast as we can...
        # the overall rate will be close enough
        particle = VirtualParticle(stream_name, port_timestamp=0)
        for x in range(count):
            particle.contents['port_timestamp'] = ntplib.system_to_ntp_time(
                time.time())
            self._driver_event(DriverAsyncEvent.SAMPLE, particle.generate())
            time.sleep(.001)

    def _create_scheduler(self, stream_name, rate):
        job_name = stream_name

        if rate > 1:
            interval = 1
        else:
            interval = 1 / rate

        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE:
                        TriggerType.INTERVAL,
                        DriverSchedulerConfigKey.SECONDS: interval
                    }
                }
            }
        }
        self.set_init_params(config)
        self._schedulers.append(stream_name)

        if rate > 1:
            self._add_scheduler(
                stream_name,
                functools.partial(self._generate_particle,
                                  stream_name,
                                  count=rate))
        else:
            self._add_scheduler(
                stream_name,
                functools.partial(self._generate_particle, stream_name))

    def _delete_all_schedulers(self):
        for name in self._schedulers:
            try:
                self._remove_scheduler(name)
            except:
                pass

    def _got_chunk(self, chunk, ts):
        """
        Process chunk output by the chunker.  Generate samples and (possibly) react
        @param chunk: data
        @param ts: ntp timestamp
        @return sample
        @throws InstrumentProtocolException
        """
        return

    def _filter_capabilities(self, events):
        """
        Filter a list of events to only include valid capabilities
        @param events: list of events to be filtered
        @return: list of filtered events
        """
        return [x for x in events if Capability.has(x)]

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.START_AUTOSAMPLE,
                           display_name="Start Autosample")
        self._cmd_dict.add(Capability.STOP_AUTOSAMPLE,
                           display_name="Stop Autosample")

    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False)

    def _update_params(self, *args, **kwargs):
        """
        Update the param dictionary based on instrument response
        """

    def _set_params(self, *args, **kwargs):
        if len(args) < 1:
            raise InstrumentParameterException(
                'Set command requires a parameter dict.')
        params = args[0]

        if not isinstance(params, dict):
            raise InstrumentParameterException('Set parameters not a dict.')

        self._param_dict = ProtocolParameterDict()

        for param in params:
            log.info('Creating new parameter: %s', param)
            self._param_dict.add(param, '', None, None)
            self._param_dict.set_value(param, params[param])

    def set_init_params(self, config):
        if not isinstance(config, dict):
            raise InstrumentParameterException("Invalid init config format")

        self._startup_config = config

        param_config = config.get(DriverConfigKey.PARAMETERS)
        if param_config:
            for name in param_config.keys():
                self._param_dict.add(name, '', None, None)
                log.debug("Setting init value for %s to %s", name,
                          param_config[name])
                self._param_dict.set_init_value(name, param_config[name])

    def _very_long_command(self, *args, **kwargs):
        return None, time.sleep(30)

    ########################################################################
    # Unknown handlers.
    ########################################################################

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Process discover event
        """
        next_state = ProtocolState.COMMAND
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Autosample handlers.
    ########################################################################

    def _handler_autosample_enter(self, *args, **kwargs):
        """
        Enter autosample state.
        """
        self._init_params()

        for stream_name in self._param_dict.get_keys():
            self._create_scheduler(stream_name,
                                   self._param_dict.get(stream_name))
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_autosample_stop_autosample(self, *args, **kwargs):
        """
        Stop autosample
        @return next_state, (next_state, result)
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._delete_all_schedulers()
        return next_state, (next_state, result)

    ########################################################################
    # Command handlers.
    ########################################################################

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        """
        self._init_params()
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_command_get(self, *args, **kwargs):
        """
        Process GET event
        """
        return self._handler_get(*args, **kwargs)

    def _handler_command_set(self, *args, **kwargs):
        """
        Perform a set command.
        @param args[0] parameter : value dict.
        @throws InstrumentParameterException
        """
        next_state = None
        result = []
        self._set_params(*args, **kwargs)
        return next_state, (next_state, result)

    def _handler_command_start_autosample(self):
        """
        Start autosample
        @return next_state, (next_state, result)
        """
        next_state = ProtocolState.AUTOSAMPLE
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Generic handlers.
    ########################################################################

    def _handler_generic_enter(self, *args, **kwargs):
        """
        Generic enter state handler
        """
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_generic_exit(self, *args, **kwargs):
        """
예제 #23
0
class DataSetDriver(object):
    """
    Base class for data set drivers.  Provides:
    - an interface via callback to publish data
    - an interface via callback to persist driver state
    - an interface via callback to handle exceptions
    - an start and stop sampling
    - a client interface for execute resource

    Subclasses need to include harvesters and parsers and
    be specialized to handle the interaction between the two.
    
    Configurations should contain keys from the DataSetDriverConfigKey class
    and should look something like this example (more full documentation in the
    "Dataset Agent Architecture" page on the OOI wiki):
    {
        'harvester':
        {
            'directory': '/tmp/dsatest',
            'storage_directory': '/tmp/stored_dsatest',
            'pattern': '*.txt',
            'frequency': 1,
            'file_mod_wait_time': 30,
        },
        'parser': {}
        'driver': {
            'records_per_second'
            'harvester_polling_interval'
            'batched_particle_count'
        }
    }
    """
    def __init__(self, config, memento, data_callback, state_callback, event_callback, exception_callback):
        self._config = copy.deepcopy(config)
        self._data_callback = data_callback
        self._state_callback = state_callback
        self._event_callback = event_callback
        self._exception_callback = exception_callback
        self._memento = memento
        self._publisher_thread = None

        self._verify_config()

        # Updated my set_resource, defaults defined in build_param_dict
        self._polling_interval = None
        self._generate_particle_count = None
        self._particle_count_per_second = None
        self._resource_id = None

        self._param_dict = ProtocolParameterDict()
        self._cmd_dict = ProtocolCommandDict()
        self._driver_dict = DriverDict()

        self._build_command_dict()
        self._build_driver_dict()
        self._build_param_dict()

    def shutdown(self):
        self.stop_sampling()

    def start_sampling(self):
        """
        Start a new thread to monitor for data
        """
        self._start_sampling()
        self._start_publisher_thread()

    def stop_sampling(self):
        """
        Stop the sampling thread
        """
        log.debug("Stopping sampling and publisher now")

        self._stop_sampling()
        self._stop_publisher_thread()

    def _start_sampling(self):
        raise NotImplementedException('virtual method needs to be specialized')

    def _stop_sampling(self):
        raise NotImplementedException('virtual method needs to be specialized')

    def _is_sampling(self):
        """
        Currently the drivers only have two states, command and streaming and
        all resource commands are common, either start or stop autosample.
        Therefore we didn't implement an enitre state machine to manage states
        and commands.  If it does get more complex than this we should take the
        time to implement a state machine to add some flexibility
        """
        raise NotImplementedException('virtual method needs to be specialized')

    def cmd_dvr(self, cmd, *args, **kwargs):
        log.warn("DRIVER: cmd_dvr %s", cmd)

        if cmd == 'execute_resource':
            resource_cmd = args[0]

            if resource_cmd == DriverEvent.START_AUTOSAMPLE:
                return (ResourceAgentState.STREAMING, None)

            elif resource_cmd == DriverEvent.STOP_AUTOSAMPLE:
                self.stop_sampling()
                return (ResourceAgentState.COMMAND, None)

            else:
                log.error("Unhandled resource command: %s", resource_cmd)
                raise

        elif cmd == 'get_resource_capabilities':
            return self.get_resource_capabilities()

        elif cmd == 'set_resource':
            return self.set_resource(*args, **kwargs)

        elif cmd == 'get_resource':
            return self.get_resource(*args, **kwargs)

        elif cmd == 'get_config_metadata':
            return self.get_config_metadata(*args, **kwargs)

        elif cmd == 'disconnect':
            pass

        elif cmd == 'initialize':
            pass

        else:
            log.error("Unhandled command: %s", cmd)
            raise InstrumentStateException("Unhandled command: %s" % cmd)

    def get_resource_capabilities(self, current_state=True, *args, **kwargs):
        """
        Return driver commands and parameters.
        @param current_state True to retrieve commands available in current
        state, otherwise reutrn all commands.
        @retval list of AgentCapability objects representing the drivers
        capabilities.
        @raises NotImplementedException if not implemented by subclass.
        """
        res_params = self._param_dict.get_keys()
        res_cmds = [DriverEvent.STOP_AUTOSAMPLE, DriverEvent.START_AUTOSAMPLE]

        if current_state and self._is_sampling():
            res_cmds = [DriverEvent.STOP_AUTOSAMPLE]
        elif current_state and not self._is_sampling():
            res_cmds = [DriverEvent.START_AUTOSAMPLE]

        return [res_cmds, res_params]

    def set_resource(self, *args, **kwargs):
        """
        Set the driver parameter
        """
        log.trace("start set_resource")
        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException('Set command requires a parameter dict.')

        log.trace("set_resource: iterate through params: %s", params)
        for (key, val) in params.iteritems():
            if key in [DriverParameter.BATCHED_PARTICLE_COUNT, DriverParameter.RECORDS_PER_SECOND]:
                if not isinstance(val, int): raise InstrumentParameterException("%s must be an integer" % key)
            if key in [DriverParameter.PUBLISHER_POLLING_INTERVAL]:
                if not isinstance(val, (int, float)): raise InstrumentParameterException("%s must be an float" % key)

            if val <= 0:
                raise InstrumentParameterException("%s must be > 0" % key)

            self._param_dict.set_value(key, val)

        # Set the driver parameters
        self._generate_particle_count = self._param_dict.get(DriverParameter.BATCHED_PARTICLE_COUNT)
        self._particle_count_per_second = self._param_dict.get(DriverParameter.RECORDS_PER_SECOND)
        self._polling_interval = self._param_dict.get(DriverParameter.PUBLISHER_POLLING_INTERVAL)
        log.trace("Driver Parameters: %s, %s, %s", self._polling_interval, self._particle_count_per_second,
                  self._generate_particle_count)


    def get_resource(self, *args, **kwargs):
        """
        Get driver parameter
        """
        result = {}

        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException('Set command requires a parameter list.')

        # If all params requested, retrieve config.
        if params == DriverParameter.ALL:
            result = self._param_dict.get_config()

        # If not all params, confirm a list or tuple of params to retrieve.
        # Raise if not a list or tuple.
        # Retrieve each key in the list, raise if any are invalid.
        else:
            if not isinstance(params, (list, tuple)):
                raise InstrumentParameterException('Get argument not a list or tuple.')
            result = {}
            for key in params:
                try:
                    val = self._param_dict.get(key)
                    result[key] = val

                except KeyError:
                    raise InstrumentParameterException(('%s is not a valid parameter.' % key))

        return result

    def get_config_metadata(self):
        """
        Return the configuration metadata object in JSON format
        @retval The description of the parameters, commands, and driver info
        in a JSON string
        @see https://confluence.oceanobservatories.org/display/syseng/CIAD+MI+SV+Instrument+Driver-Agent+parameter+and+command+metadata+exchange
        """
        log.debug("Getting metadata from driver...")
        log.debug("Getting metadata dict from protocol...")
        return_dict = {}
        return_dict[ConfigMetadataKey.DRIVER] = self._driver_dict.generate_dict()
        return_dict[ConfigMetadataKey.COMMANDS] = self._cmd_dict.generate_dict()
        return_dict[ConfigMetadataKey.PARAMETERS] = self._param_dict.generate_dict()

        return return_dict

    def _verify_config(self):
        """
        virtual method to verify the supplied driver configuration is value.  Must
        be overloaded in sub classes.

        raises an ConfigurationException when a configuration error is detected.
        """
        raise NotImplementedException('virtual methond needs to be specialized')

    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        pass

    def _build_command_dict(self):
        """
        Populate the command dictionary with command.
        """
        self._cmd_dict.add(DriverEvent.START_AUTOSAMPLE, display_name="start autosample")
        self._cmd_dict.add(DriverEvent.STOP_AUTOSAMPLE, display_name="stop autosample")

    def _build_param_dict(self):
        """
        Setup three common driver parameters
        """
        self._param_dict.add_parameter(
            Parameter(
                DriverParameter.RECORDS_PER_SECOND,
                int,
                value=60,
                type=ParameterDictType.INT,
                visibility=ParameterDictVisibility.IMMUTABLE,
                display_name="Records Per Second",
                description="Number of records to process per second")
        )

        self._param_dict.add_parameter(
            Parameter(
                DriverParameter.PUBLISHER_POLLING_INTERVAL,
                float,
                value=1,
                type=ParameterDictType.FLOAT,
                visibility=ParameterDictVisibility.IMMUTABLE,
                display_name="Harvester Polling Interval",
                description="Duration in minutes to wait before checking for new files.")
        )

        self._param_dict.add_parameter(
            Parameter(
                DriverParameter.BATCHED_PARTICLE_COUNT,
                int,
                value=1,
                type=ParameterDictType.INT,
                visibility=ParameterDictVisibility.IMMUTABLE,
                display_name="Batched Particle Count",
                description="Number of particles to batch before sending to the agent")
        )

        config = self._config.get(DataSourceConfigKey.DRIVER, {})
        log.debug("set_resource on startup with: %s", config)
        self.set_resource(config)

    def _start_publisher_thread(self):
        self._publisher_thread = gevent.spawn(self._publisher_loop)
        self._publisher_shutdown = False

    def _stop_publisher_thread(self):
        log.debug("Signal shutdown")
        self._publisher_shutdown = True
        if self._publisher_thread:
            self._publisher_thread.kill(block=False)
        log.debug("shutdown complete")

    def _publisher_loop(self):
        """
        Main loop to listen for new files to parse.  Parse them and move on.
        """
        log.info("Starting main publishing loop")

        try:
            while(not self._publisher_shutdown):
                self._poll()
                gevent.sleep(self._polling_interval)
        except Exception as e:
            log.error("Exception in publisher thread (resource id: %s): %s", self._resource_id, traceback.format_exc(e))
            self._exception_callback(e)

        log.debug("publisher thread detected shutdown request")

    def _poll(self):
        raise NotImplementedException('virtual methond needs to be specialized')

    def _new_file_exception(self):
        raise NotImplementedException('virtual methond needs to be specialized')

    def _sample_exception_callback(self, exception):
        """
        Publish an event when a sample exception is detected
        """
        self._event_callback(event_type="ResourceAgentErrorEvent", error_msg = "%s" % exception)


    def _raise_new_file_event(self, name):
        """
        Raise a ResourceAgentIOEvent when a new file is detected.  Add file stats
        to the payload of the event.
        """
        s = os.stat(name)
        checksum = ""
        with open(name, 'rb') as filehandle:
            checksum = hashlib.md5(filehandle.read()).hexdigest()

        stats = {
            'name': name,
            'size': s.st_size,
            'mod': s.st_mtime,
            'md5_checksum': checksum
        }

        self._event_callback(event_type="ResourceAgentIOEvent", source_type="new file", stats=stats)
예제 #24
0
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)
class TestUnitProtocolParameterDict(MiUnitTestCase):
    @staticmethod
    def pick_byte2(input):
        """ Get the 2nd byte as an example of something tricky and
        arbitrary"""
        val = int(input) >> 8
        val = val & 255
        return val
    
    """
    Test cases for instrument driver class. Functions in this class provide
    instrument driver unit tests and provide a tutorial on use of
    the driver interface.
    """ 
    def setUp(self):
        self.param_dict = ProtocolParameterDict()
                
        self.param_dict.add("foo", r'.*foo=(\d+).*',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             direct_access=True,
                             startup_param=True,
                             default_value=10,
                             visibility=ParameterDictVisibility.READ_WRITE)
        self.param_dict.add("bar", r'.*bar=(\d+).*',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             direct_access=False,
                             startup_param=True,
                             default_value=15,
                             visibility=ParameterDictVisibility.READ_WRITE)
        self.param_dict.add("baz", r'.*baz=(\d+).*',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             direct_access=True,
                             default_value=20,
                             visibility=ParameterDictVisibility.DIRECT_ACCESS)
        self.param_dict.add("bat", r'.*bat=(\d+).*',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             startup_param=False,
                             default_value=20,
                             visibility=ParameterDictVisibility.READ_ONLY)
        self.param_dict.add("qux", r'.*qux=(\d+).*',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             startup_param=False,
                             visibility=ParameterDictVisibility.READ_ONLY)
        
    def test_get_direct_access_list(self):
        """
        Test to see we can get a list of direct access parameters
        """
        result = self.param_dict.get_direct_access_list()
        self.assertTrue(isinstance(result, list))
        self.assertEquals(len(result), 2)
        self.assert_("foo" in result)
        self.assert_("baz" in result)
        
    def test_get_startup_list(self):
        """
        Test to see we can get a list of direct access parameters
        """
        result = self.param_dict.get_startup_list()
        self.assertTrue(isinstance(result, list))
        self.assertEquals(len(result), 2)
        self.assert_("foo" in result)
        self.assert_("bar" in result)
        
    def test_set_default(self):
        """
        Test setting a default value
        """
        result = self.param_dict.get_config()
        self.assertEquals(result["foo"], None)
        self.param_dict.set_default("foo")
        self.assertEquals(self.param_dict.get("foo"), 10)
        self.param_dict.update("foo=1000")
        self.assertEquals(self.param_dict.get("foo"), 1000)
        self.param_dict.set_default("foo")
        self.assertEquals(self.param_dict.get("foo"), 10)
        
        self.assertRaises(ValueError, self.param_dict.set_default, "qux")
        
    def test_update_many(self):
        """
        Test updating of multiple variables from the same input
        """
        sample_input = """
foo=100
bar=200, baz=300
"""
        self.assertNotEquals(self.param_dict.get("foo"), 100)
        self.assertNotEquals(self.param_dict.get("bar"), 200)
        self.assertNotEquals(self.param_dict.get("baz"), 300)
        result = self.param_dict.update_many(sample_input)
        log.debug("result: %s", result)
        self.assertEquals(result["foo"], True)
        self.assertEquals(result["bar"], True)
        self.assertEquals(result["baz"], True)
        self.assertEquals(self.param_dict.get("foo"), 100)
        self.assertEquals(self.param_dict.get("bar"), 200)
        self.assertEquals(self.param_dict.get("baz"), 300)
        
    def test_visibility_list(self):
        lst = self.param_dict.get_visibility_list(ParameterDictVisibility.READ_WRITE)
        self.assertEquals(lst, ["foo", "bar"])
        lst = self.param_dict.get_visibility_list(ParameterDictVisibility.DIRECT_ACCESS)
        self.assertEquals(lst, ["baz"])
        lst = self.param_dict.get_visibility_list(ParameterDictVisibility.READ_ONLY)
        self.assertEquals(lst, ["bat", "qux"])
        
    def test_function_values(self):
        """
        Make sure we can add and update values with functions instead of patterns
        """

        self.param_dict.add_paramdictval(
            FunctionParamDictVal(
                "fn_foo",
                self.pick_byte2,
                lambda x : str(x),
                direct_access=True,
                startup_param=True,
                value=1,
                visibility=ParameterDictVisibility.READ_WRITE)
            )
        self.param_dict.add_paramdictval(
            FunctionParamDictVal(
                "fn_bar",
                lambda x : bool(x&2), # bit map example
                lambda x : str(x),
                direct_access=True,
                startup_param=True,
                value=False,
                visibility=ParameterDictVisibility.READ_WRITE)
            )
        
        # check defaults just to be safe
        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 1)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, False)
        
        result = self.param_dict.update(1005) # just change first in list
        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 3)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, False)
        
        # fn_bar does not get updated here
        result = self.param_dict.update_many(1205)
        self.assertEqual(result['fn_foo'], True)
        self.assertEqual(len(result), 1)
        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 4)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, False)
        
        # both are updated now
        result = self.param_dict.update_many(6)
        self.assertEqual(result['fn_foo'], True)
        self.assertEqual(result['fn_bar'], True)
        self.assertEqual(len(result), 2)
        
        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 0)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, True)
        
    def test_mixed_pdv_types(self):
        """ Verify we can add different types of PDVs in one container """
        self.param_dict.add_paramdictval(
            FunctionParamDictVal(
                "fn_foo",
                self.pick_byte2,
                lambda x : str(x),
                direct_access=True,
                startup_param=True,
                value=1,
                visibility=ParameterDictVisibility.READ_WRITE)
            )
        self.param_dict.add_paramdictval(
            RegexParamDictVal("foo", r'.*foo=(\d+).*',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             direct_access=True,
                             startup_param=True,
                             value=10,
                             visibility=ParameterDictVisibility.READ_WRITE)
            )
        self.param_dict.add("bar", r'.*bar=(\d+).*',
                             lambda match : int(match.group(1)),
                             lambda x : str(x),
                             direct_access=False,
                             startup_param=True,
                             value=15,
                             visibility=ParameterDictVisibility.READ_WRITE)
        
        self.assertEqual(self.param_dict.get("fn_foo"), 1)
        self.assertEqual(self.param_dict.get("foo"), 10)
        self.assertEqual(self.param_dict.get("bar"), 15)
        
    def test_base_update(self):
        pdv = ParameterDictVal("foo",
                               lambda x : str(x),
                               value=12)
        self.assertEqual(pdv.value, 12)
        result = pdv.update(1)
        self.assertEqual(result, True)
        self.assertEqual(pdv.value, 1)

        # Its a base class...monkey see, monkey do
        result = pdv.update("foo=1")
        self.assertEqual(result, True)
        self.assertEqual(pdv.value, "foo=1")
        
    def test_regex_val(self):
        pdv = RegexParamDictVal("foo",
                               r'.*foo=(\d+).*',
                               lambda match : int(match.group(1)),
                               lambda x : str(x),
                               value=12)
        self.assertEqual(pdv.value, 12)
        result = pdv.update(1)
        self.assertEqual(result, False)
        self.assertEqual(pdv.value, 12)
        result = pdv.update("foo=1")
        self.assertEqual(result, True)
        self.assertEqual(pdv.value, 1)
        
    def test_function_val(self):
        pdv = FunctionParamDictVal("foo",
                               self.pick_byte2,
                               lambda x : str(x),
                               value=12)
        self.assertEqual(pdv.value, 12)
        self.assertRaises(TypeError, pdv.update(1))
        result = pdv.update("1205")
        self.assertEqual(pdv.value, 4)
        self.assertEqual(result, True)
        
    def test_set_init_value(self):
        result = self.param_dict.get("foo")
        self.assertEqual(result, None)        
        self.param_dict.set_init_value("foo", 42)
        result = self.param_dict.get_init_value("foo")
        self.assertEqual(result, 42)
예제 #26
0
    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.FLUSH_VOLUME,
                             r'Flush Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=150,
                             units='mL',
                             startup_param=True,
                             display_name="flush_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FLUSH_FLOWRATE,
                             r'Flush Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=100,
                             units='mL/min',
                             startup_param=True,
                             display_name="flush_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FLUSH_MINFLOW,
                             r'Flush Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=75,
                             units='mL/min',
                             startup_param=True,
                             display_name="flush_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_VOLUME,
                             r'Fill Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=4000,
                             units='mL',
                             startup_param=True,
                             display_name="fill_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_FLOWRATE,
                             r'Fill Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=100,
                             units='mL/min',
                             startup_param=True,
                             display_name="fill_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_MINFLOW,
                             r'Fill Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=75,
                             units='mL/min',
                             startup_param=True,
                             display_name="fill_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_VOLUME,
                             r'Reverse Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=100,
                             units='mL',
                             startup_param=True,
                             display_name="clear_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_FLOWRATE,
                             r'Reverse Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=100,
                             units='mL/min',
                             startup_param=True,
                             display_name="clear_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_MINFLOW,
                             r'Reverse Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=75,
                             units='mL/min',
                             startup_param=True,
                             display_name="clear_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)
예제 #27
0
class Protocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    last_sample = ''
    
    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT)

        # Add event handlers for protocol state machine.
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.ENTER, self._handler_unknown_enter)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.EXIT, self._handler_unknown_exit)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.DISCOVER, self._handler_unknown_discover)

        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ENTER, self._handler_command_enter)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.EXIT, self._handler_command_exit)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_SAMPLE, self._handler_acquire_sample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_DIRECT, self._handler_command_start_direct)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.CLOCK_SYNC, self._handler_command_sync_clock)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.GET, self._handler_get)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.SET, self._handler_command_set)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.FLASH_STATUS, self._handler_flash_status)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status)

        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.ENTER, self._handler_autosample_enter)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.EXIT, self._handler_autosample_exit)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.ACQUIRE_SAMPLE, self._handler_acquire_sample)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.CLOCK_SYNC, self._handler_autosample_sync_clock)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.GET, self._handler_get)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.FLASH_STATUS, self._handler_flash_status)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status)

        # We setup a new state for clock sync because then we could use the state machine so the autosample scheduler
        # is disabled before we try to sync the clock.  Otherwise there could be a race condition introduced when we
        # are syncing the clock and the scheduler requests a sample.
        self._protocol_fsm.add_handler(ProtocolState.SYNC_CLOCK, ProtocolEvent.ENTER, self._handler_sync_clock_enter)
        self._protocol_fsm.add_handler(ProtocolState.SYNC_CLOCK, ProtocolEvent.CLOCK_SYNC, self._handler_sync_clock_sync)

        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.EXECUTE_DIRECT, self._handler_direct_access_execute_direct)
        self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS, ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct)

        # Add build handlers for device commands.
        self._add_build_handler(Command.GET_CLOCK, self._build_simple_command)
        self._add_build_handler(Command.SET_CLOCK, self._build_set_clock_command)
        self._add_build_handler(Command.D, self._build_simple_command)
        self._add_build_handler(Command.GO, self._build_simple_command)
        self._add_build_handler(Command.STOP, self._build_simple_command)
        self._add_build_handler(Command.FS, self._build_simple_command)
        self._add_build_handler(Command.STAT, self._build_simple_command)

        # Add response handlers for device commands.
        self._add_response_handler(Command.GET_CLOCK, self._parse_clock_response)
        self._add_response_handler(Command.SET_CLOCK, self._parse_clock_response)
        self._add_response_handler(Command.FS, self._parse_fs_response)
        self._add_response_handler(Command.STAT, self._parse_common_response)

        # 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()
        
        self._chunker = StringChunker(Protocol.sieve_function)

        self._add_scheduler_event(ScheduledJob.ACQUIRE_STATUS, ProtocolEvent.ACQUIRE_STATUS)
        self._add_scheduler_event(ScheduledJob.CLOCK_SYNC, ProtocolEvent.CLOCK_SYNC)

        # Start state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples and status
        """
        matchers = []
        return_list = []

        matchers.append(METBK_SampleDataParticle.regex_compiled())
        matchers.append(METBK_StatusDataParticle.regex_compiled())
                    
        for matcher in matchers:
            for match in matcher.finditer(raw_data):
                return_list.append((match.start(), match.end()))
                    
        return return_list

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        log.debug("_got_chunk: chunk=%s" %chunk)
        self._extract_sample(METBK_SampleDataParticle, METBK_SampleDataParticle.regex_compiled(), chunk, timestamp)
        self._extract_sample(METBK_StatusDataParticle, METBK_StatusDataParticle.regex_compiled(), chunk, timestamp)


    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        """
        return [x for x in events if Capability.has(x)]

    ########################################################################
    # override methods from base class.
    ########################################################################

    def _extract_sample(self, particle_class, regex, line, timestamp, publish=True):
        """
        Overridden to add duplicate sample checking.  This duplicate checking should only be performed
        on sample chunks and not other chunk types, therefore the regex is performed before the string checking.
        Extract sample from a response line if present and publish  parsed particle

        @param particle_class The class to instantiate for this specific
            data particle. Parameterizing this allows for simple, standard
            behavior from this routine
        @param regex The regular expression that matches a data sample
        @param line string to match for sample.
        @param timestamp port agent timestamp to include with the particle
        @param publish boolean to publish samples (default True). If True,
               two different events are published: one to notify raw data and
               the other to notify parsed data.

        @retval dict of dicts {'parsed': parsed_sample, 'raw': raw_sample} if
                the line can be parsed for a sample. Otherwise, None.
        @todo Figure out how the agent wants the results for a single poll
            and return them that way from here
        """
        sample = None
        match = regex.match(line)
        if match:
            if particle_class == METBK_SampleDataParticle:
                # check to see if there is a delta from last sample, and don't parse this sample if there isn't
                if match.group(0) == self.last_sample:
                    return
        
                # save this sample as last_sample for next check        
                self.last_sample = match.group(0)
            
            particle = particle_class(line, port_timestamp=timestamp)
            parsed_sample = particle.generate()

            if publish and self._driver_event:
                self._driver_event(DriverAsyncEvent.SAMPLE, parsed_sample)
    
            sample = json.loads(parsed_sample)
            return sample
        return sample

    ########################################################################
    # implement virtual methods from base class.
    ########################################################################

    def apply_startup_params(self):
        """
        Apply sample_interval startup parameter.  
        """
        config = self.get_startup_config()
        log.debug("apply_startup_params: startup config = %s" %config)
        if config.has_key(Parameter.SAMPLE_INTERVAL):
            log.debug("apply_startup_params: setting sample_interval to %d" %config[Parameter.SAMPLE_INTERVAL])
            self._param_dict.set_value(Parameter.SAMPLE_INTERVAL, config[Parameter.SAMPLE_INTERVAL])

    ########################################################################
    # Unknown handlers.
    ########################################################################

    def _handler_unknown_enter(self, *args, **kwargs):
        """
        Enter unknown state.
        """

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_unknown_exit(self, *args, **kwargs):
        """
        Exit unknown state.
        """
        pass

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state; can only be COMMAND (instrument has no actual AUTOSAMPLE mode).
        @retval (next_state, result), (ProtocolState.COMMAND, None) if successful.
        """

        (protocol_state, agent_state) = self._discover()

        # If we are just starting up and we land in command mode then our state should
        # be idle
        if(agent_state == ResourceAgentState.COMMAND):
            agent_state = ResourceAgentState.IDLE

        log.debug("_handler_unknown_discover: state = %s", protocol_state)
        return (protocol_state, agent_state)

    ########################################################################
    # Clock Sync handlers.
    # Not much to do in this state except sync the clock then transition
    # back to autosample.  When in command mode we don't have to worry about
    # stopping the scheduler so we just sync the clock without state
    # transitions
    ########################################################################

    def _handler_sync_clock_enter(self, *args, **kwargs):
        """
        Enter sync clock state.
        """
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

        self._protocol_fsm.on_event(ProtocolEvent.CLOCK_SYNC)

    def _handler_sync_clock_sync(self, *args, **kwargs):
        """
        Sync the clock
        """
        next_state = ProtocolState.AUTOSAMPLE
        next_agent_state = ResourceAgentState.STREAMING
        result = None

        self._sync_clock()

        self._async_agent_state_change(ResourceAgentState.STREAMING)
        return(next_state,(next_agent_state, result))

    ########################################################################
    # Command handlers.
    # just implemented to make DA possible, instrument has no actual command mode
    ########################################################################

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        """
        self._init_params()

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_command_exit(self, *args, **kwargs):
        """
        Exit command state.
        """
        pass

    def _handler_command_set(self, *args, **kwargs):
        """
        no writable parameters so does nothing, just implemented to make framework happy
        """

        next_state = None
        result = None
        return (next_state, result)

    def _handler_command_start_direct(self, *args, **kwargs):
        """
        """
        result = None
        next_state = ProtocolState.DIRECT_ACCESS
        next_agent_state = ResourceAgentState.DIRECT_ACCESS

        return (next_state, (next_agent_state, result))

    def _handler_command_start_autosample(self, *args, **kwargs):
        """
        """
        result = None
        next_state = ProtocolState.AUTOSAMPLE
        next_agent_state = ResourceAgentState.STREAMING

        self._start_logging()

        return (next_state, (next_agent_state, result))

    def _handler_command_sync_clock(self, *args, **kwargs):
        """
        sync clock close to a second edge
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device respond correctly.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """

        next_state = None
        next_agent_state = None
        result = None

        self._sync_clock()

        return(next_state,(next_agent_state, result))

    ########################################################################
    # autosample handlers.
    ########################################################################

    def _handler_autosample_enter(self, *args, **kwargs):
        """
        Enter autosample state  Because this is an instrument that must be
        polled we need to ensure the scheduler is added when we are in an
        autosample state.  This scheduler raises events to poll the
        instrument for data.
        """
        self._init_params()

        self._ensure_autosample_config()
        self._add_scheduler_event(AUTO_SAMPLE_SCHEDULED_JOB, ProtocolEvent.ACQUIRE_SAMPLE)

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_autosample_exit(self, *args, **kwargs):
        """
        exit autosample state.
        """
        self._remove_scheduler(AUTO_SAMPLE_SCHEDULED_JOB)

    def _handler_autosample_stop_autosample(self, *args, **kwargs):
        """
        """
        result = None
        next_state = ProtocolState.COMMAND
        next_agent_state = ResourceAgentState.COMMAND

        self._stop_logging()

        return (next_state, (next_agent_state, result))

    def _handler_autosample_sync_clock(self, *args, **kwargs):
        """
        sync clock close to a second edge
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device respond correctly.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """

        next_state = ProtocolState.SYNC_CLOCK
        next_agent_state = ResourceAgentState.BUSY
        result = None
        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)
                        
        return (next_state, result)

    def _handler_direct_access_stop_direct(self):
        result = None

        (next_state, next_agent_state) = self._discover()

        return (next_state, (next_agent_state, result))

    ########################################################################
    # general handlers.
    ########################################################################

    def _handler_flash_status(self, *args, **kwargs):
        """
        Acquire flash status from instrument.
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device cannot be woken for command.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """
        next_state = None
        next_agent_state = None
        result = None

        result = self._do_cmd_resp(Command.FS, expected_prompt=Prompt.FS)
        log.debug("FLASH RESULT: %s", result)

        return (next_state, (next_agent_state, result))

    def _handler_acquire_sample(self, *args, **kwargs):
        """
        Acquire sample from instrument.
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device cannot be woken for command.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """
        next_state = None
        next_agent_state = None
        result = None

        result = self._do_cmd_resp(Command.D, *args, **kwargs)

        return (next_state, (next_agent_state, result))

    def _handler_acquire_status(self, *args, **kwargs):
        """
        Acquire status from instrument.
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device cannot be woken for command.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """
        next_state = None
        next_agent_state = None
        result = None

        log.debug( "Logging status: %s", self._is_logging())
        result = self._do_cmd_resp(Command.STAT, expected_prompt=[Prompt.STOPPED, Prompt.GO])

        return (next_state, (next_agent_state, result))

    ########################################################################
    # Private helpers.
    ########################################################################

    def _set_params(self, *args, **kwargs):
        """
        Overloaded from the base class, used in apply DA params.  Not needed
        here so just noop it.
        """
        pass

    def _discover(self, *args, **kwargs):
        """
        Discover current state; can only be COMMAND (instrument has no actual AUTOSAMPLE mode).
        @retval (next_state, result), (ProtocolState.COMMAND, None) if successful.
        """
        logging = self._is_logging()

        if(logging == True):
            protocol_state = ProtocolState.AUTOSAMPLE
            agent_state = ResourceAgentState.STREAMING
        elif(logging == False):
            protocol_state = ProtocolState.COMMAND
            agent_state = ResourceAgentState.COMMAND
        else:
            protocol_state = ProtocolState.UNKNOWN
            agent_state = ResourceAgentState.ACTIVE_UNKNOWN

        return (protocol_state, agent_state)

    def _start_logging(self):
        """
        start the instrument logging if is isn't running already.
        """
        if(not self._is_logging()):
            log.debug("Sending start logging command: %s", Command.GO)
            self._do_cmd_resp(Command.GO, expected_prompt=Prompt.GO)

    def _stop_logging(self):
        """
        stop the instrument logging if is is running.  When the instrument
        is in a syncing state we can not stop logging.  We must wait before
        we sent the stop command.
        """
        if(self._is_logging()):
            log.debug("Attempting to stop the instrument logging.")
            result = self._do_cmd_resp(Command.STOP, expected_prompt=[Prompt.STOPPED, Prompt.SYNC, Prompt.GO])
            log.debug("Stop Command Result: %s", result)

            # If we are still logging then let's wait until we are not
            # syncing before resending the command.
            if(self._is_logging()):
                self._wait_for_sync()
                log.debug("Attempting to stop the instrument again.")
                result = self._do_cmd_resp(Command.STOP, expected_prompt=[Prompt.STOPPED, Prompt.SYNC, Prompt.GO])
                log.debug("Stop Command Result: %s", result)

    def _wait_for_sync(self):
        """
        When the instrument is syncing internal parameters we can't stop
        logging.  So we will watch the logging status and when it is not
        synchronizing we will return.  Basically we will just block
        until we are no longer syncing.
        @raise InstrumentProtocolException when we timeout waiting for a
        transition.
        """
        timeout = time.time() + SYNC_TIMEOUT

        while(time.time() < timeout):
            result = self._do_cmd_resp(Command.STAT, expected_prompt=[Prompt.STOPPED, Prompt.SYNC, Prompt.GO])

            match = LOGGING_SYNC_COMPILED.match(result)

            if(match):
                log.debug("We are still in sync mode.  Wait a bit and retry")
                time.sleep(2)
            else:
                log.debug("Transitioned out of sync.")
                return True

        # We timed out
        raise InstrumentProtocolException("failed to transition out of sync mode")

    def _is_logging(self):
        """
        Run the status command to determine if we are in command or autosample
        mode.
        @return: True if sampling, false if not, None if we can't determine
        """
        log.debug("_is_logging: start")
        result = self._do_cmd_resp(Command.STAT, expected_prompt=[Prompt.STOPPED, Prompt.GO])
        log.debug("Checking logging status from %s", result)

        match = LOGGING_STATUS_COMPILED.match(result)

        if not match:
            log.error("Unable to determine logging status from: %s", result)
            return None
        if match.group(1) == 'GO':
            log.debug("Looks like we are logging: %s", match.group(1))
            return True
        else:
            log.debug("Looks like we are NOT logging: %s", match.group(1))
            return False

    def _ensure_autosample_config(self):    
        scheduler_config = self._get_scheduler_config()
        if (scheduler_config == None):
            log.debug("_ensure_autosample_config: adding scheduler element to _startup_config")
            self._startup_config[DriverConfigKey.SCHEDULER] = {}
            scheduler_config = self._get_scheduler_config()
        log.debug("_ensure_autosample_config: adding autosample config to _startup_config")
        config = {DriverSchedulerConfigKey.TRIGGER: {
                     DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.INTERVAL,
                     DriverSchedulerConfigKey.SECONDS: self._param_dict.get(Parameter.SAMPLE_INTERVAL)}}
        self._startup_config[DriverConfigKey.SCHEDULER][AUTO_SAMPLE_SCHEDULED_JOB] = config
        if(not self._scheduler):
            self.initialize_scheduler()

    def _sync_clock(self, *args, **kwargs):
        """
        sync clock close to a second edge
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device respond correctly.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """

        next_state = None
        next_agent_state = None
        result = None

        time_format = "%Y/%m/%d %H:%M:%S"
        str_val = get_timestamp_delayed(time_format)
        log.debug("Setting instrument clock to '%s'", str_val)

        self._do_cmd_resp(Command.SET_CLOCK, str_val, expected_prompt=Prompt.CR_NL)

    def _wakeup(self, timeout):
        """There is no wakeup sequence for this instrument"""
        pass
    
    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False)

    def _build_command_dict(self):
        """
        Populate the command dictionary with command.
        """
        self._cmd_dict.add(Capability.START_AUTOSAMPLE, display_name="start autosample")
        self._cmd_dict.add(Capability.STOP_AUTOSAMPLE, display_name="stop autosample")
        self._cmd_dict.add(Capability.CLOCK_SYNC, display_name="synchronize clock")
        self._cmd_dict.add(Capability.ACQUIRE_STATUS, display_name="acquire status")
        self._cmd_dict.add(Capability.ACQUIRE_SAMPLE, display_name="acquire sample")
        self._cmd_dict.add(Capability.FLASH_STATUS, display_name="flash status")

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()
        
        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.CLOCK,
                             r'(.*)\r\n', 
                             lambda match : match.group(1),
                             lambda string : str(string),
                             type=ParameterDictType.STRING,
                             display_name="clock",
                             expiration=0,
                             visibility=ParameterDictVisibility.READ_ONLY)

        self._param_dict.add(Parameter.SAMPLE_INTERVAL,
                             r'Not used. This parameter is not parsed from instrument response',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=30,
                             value=30,
                             startup_param=True,
                             display_name="sample_interval",
                             visibility=ParameterDictVisibility.IMMUTABLE)

    def _update_params(self, *args, **kwargs):
        """
        Update the parameter dictionary. 
        """
        
        log.debug("_update_params:")
         # Issue clock command and parse results.  
        # This is the only parameter and it is always changing so don't bother with the 'change' event
        self._do_cmd_resp(Command.GET_CLOCK)

    def _build_set_clock_command(self, cmd, val):
        """
        Build handler for set clock command (cmd=val followed by newline).
        @param cmd the string for setting the clock (this should equal #CLOCK=).
        @param val the parameter value to set.
        @ retval The set command to be sent to the device.
        """
        cmd = '%s%s' %(cmd, val) + NEWLINE
        return cmd

    def _parse_clock_response(self, response, prompt):
        """
        Parse handler for clock command.
        @param response command response string.
        @param prompt prompt following command response.        
        @throws InstrumentProtocolException if clock command misunderstood.
        """
        log.debug("_parse_clock_response: response=%s, prompt=%s" %(response, prompt))
        if prompt not in [Prompt.CR_NL]: 
            raise InstrumentProtocolException('CLOCK command not recognized: %s.' % response)

        if not self._param_dict.update(response):
            raise InstrumentProtocolException('CLOCK command not parsed: %s.' % response)

        return

    def _parse_fs_response(self, response, prompt):
        """
        Parse handler for FS command.
        @param response command response string.
        @param prompt prompt following command response.
        @throws InstrumentProtocolException if FS command misunderstood.
        """
        log.debug("_parse_fs_response: response=%s, prompt=%s" %(response, prompt))
        if prompt not in [Prompt.FS]:
            raise InstrumentProtocolException('FS command not recognized: %s.' % response)

        return response

    def _parse_common_response(self, response, prompt):
        """
        Parse handler for common commands.
        @param response command response string.
        @param prompt prompt following command response.
        """
        return response
예제 #28
0
    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(
            Parameter.SAMPLE_INTERVAL,
            '',  # this is a driver only parameter
            None,
            int,
            type=ParameterDictType.INT,
            default_value=DEFAULT_SAMPLE_RATE,
            startup_param=True,
            display_name='D1000 sample periodicity (sec)',
            visibility=ParameterDictVisibility.READ_WRITE)
        self._add_setup_param(Parameter.CHANNEL_ADDRESS,
                              int,
                              display_name='base channel address',
                              type=ParameterDictType.INT,
                              default_value=0x31)
        self._add_setup_param(Parameter.LINEFEED,
                              bool,
                              display_name='line feed flag',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_TYPE,
                              bool,
                              display_name='parity type',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_ENABLE,
                              bool,
                              display_name='parity flag',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.EXTENDED_ADDRESSING,
                              bool,
                              display_name='extended addressing',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.BAUD_RATE,
                              int,
                              display_name='baud rate',
                              type=ParameterDictType.INT,
                              default_value=9600)
        self._add_setup_param(Parameter.ALARM_ENABLE,
                              bool,
                              display_name='enable alarms',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.LOW_ALARM_LATCH,
                              bool,
                              display_name='low alarm latching',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.HIGH_ALARM_LATCH,
                              bool,
                              display_name='high alarm latching',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.RTD_4_WIRE,
                              bool,
                              display_name='4 wire RTD flag',
                              type=ParameterDictType.BOOL,
                              default_value=True)
        self._add_setup_param(Parameter.TEMP_UNITS,
                              bool,
                              display_name='Fahrenheit flag',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.ECHO,
                              bool,
                              display_name='daisy chain',
                              type=ParameterDictType.BOOL,
                              default_value=True)
        self._add_setup_param(Parameter.COMMUNICATION_DELAY,
                              int,
                              display_name='communication delay',
                              type=ParameterDictType.INT,
                              default_value=0)
        self._add_setup_param(Parameter.PRECISION,
                              int,
                              display_name='precision',
                              type=ParameterDictType.INT,
                              default_value=6)
        self._add_setup_param(Parameter.LARGE_SIGNAL_FILTER_C,
                              float,
                              display_name='large signal filter constant',
                              type=ParameterDictType.FLOAT,
                              default_value=0.0)
        self._add_setup_param(Parameter.SMALL_SIGNAL_FILTER_C,
                              float,
                              display_name='small signal filter constant',
                              type=ParameterDictType.FLOAT,
                              default_value=0.50)

        for key in self._param_dict.get_keys():
            self._param_dict.set_default(key)
예제 #29
0
    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.LAUNCH_TIME,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(1), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='launch time')

        self._param_dict.add(Parameter.START_TIME_FROM_LAUNCH,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(2), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02C7EA00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='start time after launch time')

        self._param_dict.add(Parameter.STOP_TIME_FROM_START,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(3), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01E13380,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='stop time after start time')

        self._param_dict.add(Parameter.MODE_BITS,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(4), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0A,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='mode bits (set to 00001010)')

        self._param_dict.add(Parameter.SAMI_SAMPLE_INTERVAL,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(5), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami sample interval')

        self._param_dict.add(Parameter.SAMI_DRIVER_VERSION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(6), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x04,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami driver version')

        self._param_dict.add(Parameter.SAMI_PARAMS_POINTER,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(7), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami parameter pointer')

        self._param_dict.add(Parameter.DEVICE1_SAMPLE_INTERVAL,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(8), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 sample interval')

        self._param_dict.add(Parameter.DEVICE1_DRIVER_VERSION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(9), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 driver version')

        self._param_dict.add(Parameter.DEVICE1_PARAMS_POINTER,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(10), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0B,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 parameter pointer')

        self._param_dict.add(Parameter.DEVICE2_SAMPLE_INTERVAL,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(11), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 sample interval')

        self._param_dict.add(Parameter.DEVICE2_DRIVER_VERSION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(12), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 driver version')

        self._param_dict.add(Parameter.DEVICE2_PARAMS_POINTER,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(13), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 parameter pointer')

        self._param_dict.add(Parameter.DEVICE3_SAMPLE_INTERVAL,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(14), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 sample interval')

        self._param_dict.add(Parameter.DEVICE3_DRIVER_VERSION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(15), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 driver version')

        self._param_dict.add(Parameter.DEVICE3_PARAMS_POINTER,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(16), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 parameter pointer')

        self._param_dict.add(Parameter.PRESTART_SAMPLE_INTERVAL,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(17), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart sample interval')

        self._param_dict.add(Parameter.PRESTART_DRIVER_VERSION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(18), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart driver version')

        self._param_dict.add(Parameter.PRESTART_PARAMS_POINTER,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(19), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart parameter pointer')

        self._param_dict.add(Parameter.GLOBAL_CONFIGURATION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(20), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='global bits (set to 00000111)')

        self._param_dict.add(Parameter.NUMBER_SAMPLES_AVERAGED,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(21), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_FLUSHES,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(22), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_ON_FLUSH,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(23), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_OFF_FLUSH,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(24), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_REAGENT_PUMPS,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(25), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.VALVE_DELAY,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(26), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_ON_IND,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(27), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PV_OFF_IND,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(28), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_BLANKS,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(29), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_MEASURE_T,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(30), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_OFF_TO_MEASURE,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(31), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.MEASURE_TO_PUMP_ON,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(32), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_MEASUREMENTS,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(33), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.SALINITY_DELAY,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(34), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')
예제 #30
0
class Protocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT)

        # Add event handlers for protocol state machine.
        handlers = {
            ProtocolState.UNKNOWN: [
                (ProtocolEvent.ENTER, self._handler_unknown_enter),
                (ProtocolEvent.EXIT, self._handler_unknown_exit),
                (ProtocolEvent.DISCOVER, self._handler_unknown_discover),
            ],
            ProtocolState.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_command_enter),
                (ProtocolEvent.EXIT, self._handler_command_exit),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_sample),
                (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_autosample),
                (ProtocolEvent.GET, self._handler_get),
                (ProtocolEvent.SET, self._handler_command_set),
            ],
            ProtocolState.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_autosample_enter),
                (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_sample),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop),
                (ProtocolEvent.EXIT, self._handler_autosample_exit),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_direct_access_exit),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
            ],
        }

        for state in handlers:
            for event, handler in handlers[state]:
                self._protocol_fsm.add_handler(state, event, handler)

        # Add build handlers for device commands - we are only using simple commands
        for cmd in Command.list():
            self._add_build_handler(cmd, self._build_command)
            self._add_response_handler(cmd, self._check_command)
        self._add_build_handler(Command.SETUP, self._build_setup_command)
        self._add_response_handler(Command.READ_SETUP, self._read_setup_response_handler)

        # Add response handlers for device commands.
        # self._add_response_handler(Command.xyz, self._parse_xyz_response)

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

        self._chunker = StringChunker(Protocol.sieve_function)

        # Start state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)
        self._sent_cmds = None

        self.initialize_scheduler()

        # unit identifiers - must match the setup command (SU31 - '1')
        self._units = ['1', '2', '3']

        self._setup = None  # set by the read setup command handler for comparison to see if the config needs reset

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples and status
        """
        matchers = []
        return_list = []

        matchers.append(D1000TemperatureDataParticle.regex_compiled())

        for matcher in matchers:
            for match in matcher.finditer(raw_data):
                return_list.append((match.start(), match.end()))

        if not return_list:
            log.debug("sieve_function: raw_data=%r, return_list=%s", raw_data, return_list)
        return return_list

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        log.debug("_got_chunk: chunk=%s", chunk)
        self._extract_sample(D1000TemperatureDataParticle, D1000TemperatureDataParticle.regex_compiled(), chunk,
                             timestamp)

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        """
        return [x for x in events if Capability.has(x)]

    ########################################################################
    # implement virtual methods from base class.
    ########################################################################

    def _set_params(self, *args, **kwargs):
        """
        Issue commands to the instrument to set various parameters.  If
        startup is set to true that means we are setting startup values
        and immutable parameters can be set.  Otherwise only READ_WRITE
        parameters can be set.

        must be overloaded in derived classes

        @param params dictionary containing parameter name and value pairs
        @param startup - a flag, true indicates initializing, false otherwise
        """

        params = args[0]

        # check for attempt to set readonly parameters (read-only or immutable set outside startup)
        self._verify_not_readonly(*args, **kwargs)
        old_config = self._param_dict.get_config()

        for (key, val) in params.iteritems():
            log.debug("KEY = " + str(key) + " VALUE = " + str(val))
            self._param_dict.set_value(key, val)

        new_config = self._param_dict.get_config()
        # check for parameter change
        if not dict_equal(old_config, new_config):
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def apply_startup_params(self):
        """
        Apply startup parameters
        """

        config = self.get_startup_config()

        for param in Parameter.list():
            if param in config:
                self._param_dict.set_value(param, config[param])

    ########################################################################
    # Private helpers.
    ########################################################################

    def _wakeup(self, wakeup_timeout=10, response_timeout=3):
        """
        Over-ridden because the D1000 does not go to sleep and requires no special wake-up commands.
        @param wakeup_timeout The timeout to wake the device.
        @param response_timeout The time to look for response to a wakeup attempt.
        @throw InstrumentTimeoutException if the device could not be woken.
        """
        pass

    def _do_command(self, cmd, unit, **kwargs):
        """
        Send command and ensure it matches appropriate response. Simply enforces sending the unit identifier as a
        required argument.
        @param cmd - Command to send to instrument
        @param unit - unit identifier
        @retval - response from instrument
        """
        self._do_cmd_resp(cmd, unit, write_delay=INTER_CHARACTER_DELAY, **kwargs)

    def _build_command(self, cmd, unit):
        """
        @param cmd - Command to process
        @param unit - unit identifier
        """
        return '#' + unit + cmd + NEWLINE

    def _build_setup_command(self, cmd, unit):
        """
        @param cmd - command to send - should be 'SU'
        @param unit - unit identifier - should be '1', '2', or '3', must be a single character
        """
        # use defaults - in the future, may consider making some of these parameters
        # byte 0
        channel_address = unit
        # byte 1
        line_feed = self._param_dict.format(Parameter.LINEFEED)
        parity_type = self._param_dict.format(Parameter.PARITY_TYPE)
        parity_enable = self._param_dict.format(Parameter.PARITY_ENABLE)
        extended_addressing = self._param_dict.format(Parameter.EXTENDED_ADDRESSING)
        baud_rate = self._param_dict.format(Parameter.BAUD_RATE)
        baud_rate = getattr(BaudRate, 'BAUD_%d' % baud_rate, BaudRate.BAUD_9600)
        # byte 2
        alarm_enable = self._param_dict.format(Parameter.ALARM_ENABLE)
        low_alarm_latch = self._param_dict.format(Parameter.LOW_ALARM_LATCH)
        high_alarm_latch = self._param_dict.format(Parameter.HIGH_ALARM_LATCH)
        rtd_wire = self._param_dict.format(Parameter.RTD_4_WIRE)
        temp_units = self._param_dict.format(Parameter.TEMP_UNITS)
        echo = self._param_dict.format(Parameter.ECHO)
        delay_units = self._param_dict.format(Parameter.COMMUNICATION_DELAY)
        # byte 3
        precision = self._param_dict.format(Parameter.PRECISION)
        precision = getattr(UnitPrecision, 'DIGITS_%d' % precision, UnitPrecision.DIGITS_6)
        large_signal_filter_constant = self._param_dict.format(Parameter.LARGE_SIGNAL_FILTER_C)
        large_signal_filter_constant = filter_enum(large_signal_filter_constant)
        small_signal_filter_constant = self._param_dict.format(Parameter.SMALL_SIGNAL_FILTER_C)
        small_signal_filter_constant = filter_enum(small_signal_filter_constant)

        # # Factory default: 0x31070182
        # # Lab default:     0x310214C2

        byte_0 = int(channel_address.encode("hex"), 16)
        log.debug('byte 0: %s', byte_0)
        byte_1 = \
            (line_feed << 7) + \
            (parity_type << 6) + \
            (parity_enable << 5) + \
            (extended_addressing << 4) + \
            baud_rate
        log.debug('byte 1: %s', byte_1)
        byte_2 = \
            (alarm_enable << 7) + \
            (low_alarm_latch << 6) + \
            (high_alarm_latch << 5) + \
            (rtd_wire << 4) + \
            (temp_units << 3) + \
            (echo << 2) + \
            delay_units
        log.debug('byte 2: %s', byte_2)
        byte_3 = \
            (precision << 6) + \
            (large_signal_filter_constant << 3) + \
            small_signal_filter_constant
        log.debug('byte 3: %s', byte_3)

        setup_command = '#%sSU%02x%02x%02x%02x' % (unit[0], byte_0, byte_1, byte_2, byte_3) + NEWLINE
        log.debug('default setup command (%r) for unit %02x (%s)' % (setup_command, byte_0, unit[0]))
        return setup_command

    def _check_command(self, resp, prompt):
        """
        Perform a checksum calculation on provided data. The checksum used for comparison is the last two characters of
        the line.
        @param resp - response from the instrument to the command
        @param prompt - expected prompt (or the joined groups from a regex match)
        @retval
        """
        for line in resp.split(NEWLINE):
            if line.startswith('?'):
                raise InstrumentProtocolException('error processing command (%r)', resp[1:])
            if line.startswith('*'):  # response
                if not valid_response(line):
                    raise InstrumentProtocolException('checksum failed (%r)', line)

    def _read_setup_response_handler(self, resp, prompt):
        """
        Save the setup.
        @param resp - response from the instrument to the command
        @param prompt - expected prompt (or the joined groups from a regex match)
        """
        self._check_command(resp, prompt)
        self._setup = resp

    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False)

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.START_AUTOSAMPLE, display_name="Start Autosample")
        self._cmd_dict.add(Capability.STOP_AUTOSAMPLE, display_name="Stop Autosample")
        self._cmd_dict.add(Capability.ACQUIRE_SAMPLE, display_name="Acquire Sample")

        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

    def _add_setup_param(self, name, fmt, **kwargs):
        """
        Add setup command to the parameter dictionary. All 'SU' parameters are not startup parameter, but should be
        restored upon return from direct access. These parameters are all part of the instrument command 'SU'.
        """
        self._param_dict.add(name, '', None, fmt,
                             startup_param=False,
                             direct_access=True,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             **kwargs)

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.SAMPLE_INTERVAL,
                             '',  # this is a driver only parameter
                             None,
                             int,
                             type=ParameterDictType.INT,
                             startup_param=True,
                             display_name='D1000 Sample Periodicity',
                             description='Periodicity of D1000 temperature sample in autosample mode: (1-3600)',
                             default_value=DEFAULT_SAMPLE_RATE,
                             units=Units.SECOND,
                             visibility=ParameterDictVisibility.READ_WRITE)
        self._add_setup_param(Parameter.CHANNEL_ADDRESS,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Base Channel Address',
                              description='Hex value of ASCII character to ID unit, e.g. 31 is the ASCII code for 1: (30-31, 41-5A, 61-7A)',
                              default_value=0x31)
        self._add_setup_param(Parameter.LINEFEED,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Line Feed Flag',
                              description='Enable D1000 to generate a linefeed before and after each response: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_TYPE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Parity Type',
                              description='Sets the parity: (true:odd | false:even)',
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_ENABLE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Parity Flag',
                              description='Enable use of parity bit, a parity error will be issued if detected: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.EXTENDED_ADDRESSING,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Extended Addressing',
                              description='Enable extended addressing: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.BAUD_RATE,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Baud Rate',
                              description='Using ethernet interface in deployed configuration: (300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600)',
                              default_value=9600,
                              units=Units.BAUD)
        self._add_setup_param(Parameter.ALARM_ENABLE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Enable Alarms',
                              description='Enable alarms to be controlled by the Digital Output (DO) command: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.LOW_ALARM_LATCH,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Low Alarm Latching',
                              description='Enable changing the alarm to latching mode: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.HIGH_ALARM_LATCH,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='High Alarm Latching',
                              description='Enable changing the alarm to latching mode: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.RTD_4_WIRE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='4 Wire RTD Flag',
                              description='Represents a physical configuration of the instrument, disabling may cause data to be misaligned: (true | false)',
                              default_value=True)
        self._add_setup_param(Parameter.TEMP_UNITS,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Fahrenheit Flag',
                              description='Flag to control the temperature format: (true:Fahrenheit | false:Celsius)',
                              default_value=False)
        self._add_setup_param(Parameter.ECHO,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Daisy Chain',
                              description='If not set, only 1 out of 3 D1000s will process commands: (true | false)',
                              default_value=True)
        self._add_setup_param(Parameter.COMMUNICATION_DELAY,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Communication Delay',
                              description='The number of delays to add when processing commands: (0-3)',
                              default_value=0)
        self._add_setup_param(Parameter.PRECISION,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Precision',
                              description='Number of digits the instrument should output for temperature query: (4-7)',
                              default_value=6)
        self._add_setup_param(Parameter.LARGE_SIGNAL_FILTER_C,
                              float,
                              type=ParameterDictType.FLOAT,
                              display_name='Large Signal Filter Constant',
                              description='Time to reach 63% of its final value: (0.0, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0)',
                              default_value=0.0,
                              units=Units.SECOND)
        self._add_setup_param(Parameter.SMALL_SIGNAL_FILTER_C,
                              float,
                              type=ParameterDictType.FLOAT,
                              display_name='Small Signal Filter Constant',
                              description='Smaller filter constant, should be larger than large filter constant: (0.0, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0)',
                              default_value=0.50,
                              units=Units.SECOND)

        for key in self._param_dict.get_keys():
            self._param_dict.set_default(key)

    def _update_params(self):
        """
        Update the parameter dictionary.
        """
        pass

    def _restore_params(self):
        """
        Restore D1000, clearing any alarms and set-point.
        """
        # make sure the alarms are disabled - preferred over doing setup, then clear alarms commands
        self._param_dict.set_value(Parameter.ALARM_ENABLE, False)
        for i in self._units:
            current_setup = None  # set in READ_SETUP response handler
            try:
                self._do_command(Command.READ_SETUP, i, response_regex=Response.READ_SETUP)
                current_setup = self._setup[4:][:-2]  # strip off the leader and checksum
            except InstrumentTimeoutException:
                log.error('D1000 unit %s has been readdressed, unable to restore settings' % i[0])
            new_setup = self._build_setup_command(Command.SETUP, i)[4:]  # strip leader (no checksum)
            if not current_setup == new_setup:
                log.debug('restoring setup to default state (%s) from current state (%s)', new_setup, current_setup)
                self._do_command(Command.ENABLE_WRITE, i)
                self._do_command(Command.SETUP, i)
            self._do_command(Command.ENABLE_WRITE, i)
            self._do_command(Command.CLEAR_ZERO, i)

    ########################################################################
    # Event handlers for UNKNOWN state.
    ########################################################################

    def _handler_unknown_enter(self, *args, **kwargs):
        """
        Enter unknown state.
        """
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_unknown_exit(self, *args, **kwargs):
        """
        Exit unknown state.
        """
        pass

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state
        @retval (next_state, current state), (ProtocolState.COMMAND, None) if successful.
        """

        # force to command mode, this instrument has no autosample mode
        next_state = ProtocolState.COMMAND
        result = ResourceAgentState.COMMAND

        return ProtocolState.COMMAND, ResourceAgentState.IDLE

    ########################################################################
    # Event handlers for COMMAND state.
    ########################################################################

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        """
        # Command device to update parameters and send a config change event if needed.
        self._restore_params()

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_command_exit(self, *args, **kwargs):
        """
        Exit command state.
        """
        pass

    def _handler_command_set(self, *args, **kwargs):
        """
        no writable parameters so does nothing, just implemented to make framework happy
        """
        input_params = args[0]

        for key, value in input_params.items():
            if not Parameter.has(key):
                raise InstrumentParameterException('Invalid parameter supplied to set: %s' % key)

            try:
                value = int(value)
            except TypeError:
                raise InstrumentParameterException('Invalid value [%s] for parameter %s' % (value, key))

            if key == Parameter.SAMPLE_INTERVAL:
                if value < MIN_SAMPLE_RATE or value > MAX_SAMPLE_RATE:
                    raise InstrumentParameterException('Parameter %s value [%d] is out of range [%d %d]' %
                                                       (key, value, MIN_SAMPLE_RATE, MAX_SAMPLE_RATE))
        startup = False
        try:
            startup = args[1]
        except IndexError:
            pass

        self._set_params(input_params, startup)

        return None, None
        # return None, (None, None)

    def _handler_command_autosample(self, *args, **kwargs):
        """
        Begin autosample.
        """
        return ProtocolState.AUTOSAMPLE, (ResourceAgentState.STREAMING, None)

    def _handler_command_start_direct(self, *args, **kwargs):
        """
        """
        return ProtocolState.DIRECT_ACCESS, (ResourceAgentState.DIRECT_ACCESS, None)

    ########################################################################
    # Event handlers for AUTOSAMPLE state.
    ########################################################################

    def _handler_autosample_enter(self, *args, **kwargs):
        """
        Start auto polling the temperature sensors.
        """
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)
        self._protocol_fsm.on_event(ProtocolEvent.ACQUIRE_SAMPLE)

        job_name = ScheduledJob.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.SAMPLE, ProtocolEvent.ACQUIRE_SAMPLE)

    def _handler_autosample_exit(self, *args, **kwargs):
        """
        Stop autosampling - remove the scheduled autosample
        """
        if self._scheduler is not None:
            try:
                self._remove_scheduler(ScheduledJob.SAMPLE)
            except KeyError:
                log.debug('_remove_scheduler count not find: %s', ScheduledJob.SAMPLE)

    def _handler_sample(self, *args, **kwargs):
        """
        Poll the three temperature probes for current temperature readings.
        """
        for i in self._units:
            self._do_command(Command.READ, i)

        return None, (None, None)

    def _handler_autosample_stop(self, *args, **kwargs):
        """
        Terminate autosampling
        """
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    ########################################################################
    # 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.
        """

    def _handler_direct_access_execute_direct(self, data):
        self._do_cmd_direct(data)

        return None, (None, None)

    def _handler_direct_access_stop_direct(self, *args, **kwargs):
        result = None
        next_state = ProtocolState.COMMAND
        next_agent_state = ResourceAgentState.COMMAND

        # return next_state, (next_agent_state, result)
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)
    def setUp(self):
        self.param_dict = ProtocolParameterDict()

        self.param_dict.add("foo", r'.*foo=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=True,
                            startup_param=True,
                            default_value=10,
                            visibility=ParameterDictVisibility.READ_WRITE)
        self.param_dict.add("bar", r'.*bar=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=False,
                            startup_param=True,
                            default_value=15,
                            visibility=ParameterDictVisibility.READ_WRITE)
        self.param_dict.add("baz", r'.*baz=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=True,
                            default_value=20,
                            visibility=ParameterDictVisibility.DIRECT_ACCESS,
                            get_timeout=30,
                            set_timeout=40,
                            display_name="Baz",
                            description="The baz parameter",
                            type=ParameterDictType.INT,
                            units="nano-bazers",
                            value_description="Should be an integer between 2 and 2000")
        self.param_dict.add("bat", r'.*bat=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            default_value=20,
                            visibility=ParameterDictVisibility.READ_ONLY,
                            get_timeout=10,
                            set_timeout=20,
                            display_name="Bat",
                            description="The bat parameter",
                            type=ParameterDictType.INT,
                            units="nano-batbit",
                            value_description="Should be an integer between 1 and 1000")
        self.param_dict.add("qux", r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.READ_ONLY)
        self.param_dict.add("pho", r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.IMMUTABLE)
        self.param_dict.add("dil", r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.IMMUTABLE)
        self.param_dict.add("qut", r'.*qut=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=True,
                            default_value=[10, 100],
                            visibility=ParameterDictVisibility.DIRECT_ACCESS,
                            expiration=1,
                            get_timeout=10,
                            set_timeout=20,
                            display_name="Qut",
                            description="The qut list parameter",
                            type=ParameterDictType.LIST,
                            units="nano-qutters",
                            value_description="Should be a 2-10 element list of integers between 2 and 2000")

        self.target_schema = {
            "bar": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": True,
                "value": {
                    "default": 15
                },
                "visibility": "READ_WRITE",
                "range": None,
            },
            "bat": {
                "description": "The bat parameter",
                "direct_access": False,
                "display_name": "Bat",
                "get_timeout": 10,
                "set_timeout": 20,
                "startup": False,
                "value": {
                    "default": 20,
                    "description": "Should be an integer between 1 and 1000",
                    "type": "int",
                    "units": "nano-batbit"
                },
                "visibility": "READ_ONLY",
                "range": None,
            },
            "baz": {
                "description": "The baz parameter",
                "direct_access": True,
                "display_name": "Baz",
                "get_timeout": 30,
                "set_timeout": 40,
                "startup": False,
                "value": {
                    "default": 20,
                    "description": "Should be an integer between 2 and 2000",
                    "type": "int",
                    "units": "nano-bazers"
                },
                "visibility": "DIRECT_ACCESS",
                "range": None,
            },
            "dil": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "IMMUTABLE",
                "range": None,
            },
            "foo": {
                "direct_access": True,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": True,
                "value": {
                    "default": 10
                },
                "visibility": "READ_WRITE",
                "range": None,
            },
            "pho": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "IMMUTABLE",
                "range": None,
            },
            "qut": {
                "description": "The qut list parameter",
                "direct_access": True,
                "display_name": "Qut",
                "get_timeout": 10,
                "set_timeout": 20,
                "startup": False,
                "value": {
                    "default": [
                        10,
                        100
                    ],
                    "description": "Should be a 2-10 element list of integers between 2 and 2000",
                    "type": "list",
                    "units": "nano-qutters"
                },
                "visibility": "DIRECT_ACCESS",
                "range": None,
            },
            "qux": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "READ_ONLY",
                "range": None,
            }
        }

        self.test_yaml = '''
예제 #32
0
class TestUnitProtocolParameterDict(TestUnitStringsDict):
    """
    Test cases for instrument driver class. Functions in this class provide
    instrument driver unit tests and provide a tutorial on use of
    the driver interface.
    """

    __test__ = True

    @staticmethod
    def pick_byte2(input_val):
        """ Get the 2nd byte as an example of something tricky and
        arbitrary"""
        val = int(input_val) >> 8
        val &= 255
        return val

    def setUp(self):
        self.param_dict = ProtocolParameterDict()

        self.param_dict.add("foo",
                            r'.*foo=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=True,
                            startup_param=True,
                            default_value=10,
                            visibility=ParameterDictVisibility.READ_WRITE)
        self.param_dict.add("bar",
                            r'.*bar=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=False,
                            startup_param=True,
                            default_value=15,
                            visibility=ParameterDictVisibility.READ_WRITE)
        self.param_dict.add(
            "baz",
            r'.*baz=(\d+).*',
            lambda match: int(match.group(1)),
            lambda x: str(x),
            direct_access=True,
            default_value=20,
            visibility=ParameterDictVisibility.DIRECT_ACCESS,
            get_timeout=30,
            set_timeout=40,
            display_name="Baz",
            description="The baz parameter",
            type=ParameterDictType.INT,
            units="nano-bazers",
            value_description="Should be an integer between 2 and 2000")
        self.param_dict.add(
            "bat",
            r'.*bat=(\d+).*',
            lambda match: int(match.group(1)),
            lambda x: str(x),
            startup_param=False,
            default_value=20,
            visibility=ParameterDictVisibility.READ_ONLY,
            get_timeout=10,
            set_timeout=20,
            display_name="Bat",
            description="The bat parameter",
            type=ParameterDictType.INT,
            units="nano-batbit",
            value_description="Should be an integer between 1 and 1000")
        self.param_dict.add("qux",
                            r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.READ_ONLY)
        self.param_dict.add("pho",
                            r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.IMMUTABLE)
        self.param_dict.add("dil",
                            r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.IMMUTABLE)
        self.param_dict.add(
            "qut",
            r'.*qut=(\d+).*',
            lambda match: int(match.group(1)),
            lambda x: str(x),
            direct_access=True,
            default_value=[10, 100],
            visibility=ParameterDictVisibility.DIRECT_ACCESS,
            expiration=1,
            get_timeout=10,
            set_timeout=20,
            display_name="Qut",
            description="The qut list parameter",
            type=ParameterDictType.LIST,
            units="nano-qutters",
            value_description=
            "Should be a 2-10 element list of integers between 2 and 2000")

        self.target_schema = {
            "bar": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": True,
                "value": {
                    "default": 15
                },
                "visibility": "READ_WRITE",
                "range": None,
            },
            "bat": {
                "description": "The bat parameter",
                "direct_access": False,
                "display_name": "Bat",
                "get_timeout": 10,
                "set_timeout": 20,
                "startup": False,
                "value": {
                    "default": 20,
                    "description": "Should be an integer between 1 and 1000",
                    "type": "int",
                    "units": "nano-batbit"
                },
                "visibility": "READ_ONLY",
                "range": None,
            },
            "baz": {
                "description": "The baz parameter",
                "direct_access": True,
                "display_name": "Baz",
                "get_timeout": 30,
                "set_timeout": 40,
                "startup": False,
                "value": {
                    "default": 20,
                    "description": "Should be an integer between 2 and 2000",
                    "type": "int",
                    "units": "nano-bazers"
                },
                "visibility": "DIRECT_ACCESS",
                "range": None,
            },
            "dil": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "IMMUTABLE",
                "range": None,
            },
            "foo": {
                "direct_access": True,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": True,
                "value": {
                    "default": 10
                },
                "visibility": "READ_WRITE",
                "range": None,
            },
            "pho": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "IMMUTABLE",
                "range": None,
            },
            "qut": {
                "description": "The qut list parameter",
                "direct_access": True,
                "display_name": "Qut",
                "get_timeout": 10,
                "set_timeout": 20,
                "startup": False,
                "value": {
                    "default": [10, 100],
                    "description":
                    "Should be a 2-10 element list of integers between 2 and 2000",
                    "type": "list",
                    "units": "nano-qutters"
                },
                "visibility": "DIRECT_ACCESS",
                "range": None,
            },
            "qux": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "READ_ONLY",
                "range": None,
            }
        }

        self.test_yaml = '''
        parameters: {
            qut: {
            description: "QutFileDesc",
            units: "QutFileUnits",
            value_description: "QutFileValueDesc",
            type: "QutFileType",
            display_name: "QutDisplay"
            },
            extra_param: {
            description: "ExtraFileDesc",
            units: "ExtraFileUnits",
            value_description: "ExtraFileValueDesc",
            type: "ExtraFileType"
            }
          }

        commands: {
          dummy: stuff
          }
        '''

    def test_get_direct_access_list(self):
        """
        Test to see we can get a list of direct access parameters
        """
        result = self.param_dict.get_direct_access_list()
        self.assertTrue(isinstance(result, list))
        self.assertEquals(len(result), 3)
        self.assert_("foo" in result)
        self.assert_("baz" in result)
        self.assert_("qut" in result)

    def test_get_startup_list(self):
        """
        Test to see we can get a list of direct access parameters
        """
        result = self.param_dict.get_startup_list()
        self.assertTrue(isinstance(result, list))
        self.assertEquals(len(result), 2)
        self.assert_("foo" in result)
        self.assert_("bar" in result)

    def test_set_default(self):
        """
        Test setting a default value
        """
        result = self.param_dict.get_config()
        self.assertEquals(result["foo"], None)
        self.param_dict.set_default("foo")
        self.assertEquals(self.param_dict.get("foo"), 10)
        self.param_dict.update("foo=1000")
        self.assertEquals(self.param_dict.get("foo"), 1000)
        self.param_dict.set_default("foo")
        self.assertEquals(self.param_dict.get("foo"), 10)

        self.assertRaises(ValueError, self.param_dict.set_default, "qux")

    def test_update_many(self):
        """
        Test updating of multiple variables from the same input
        """
        sample_input = """
foo=100
bar=200, baz=300
"""
        self.assertNotEquals(self.param_dict.get("foo"), 100)
        self.assertNotEquals(self.param_dict.get("bar"), 200)
        self.assertNotEquals(self.param_dict.get("baz"), 300)
        result = self.param_dict.update_many(sample_input)
        log.debug("result: %s", result)
        self.assertEquals(result["foo"], True)
        self.assertEquals(result["bar"], True)
        self.assertEquals(result["baz"], True)
        self.assertEquals(self.param_dict.get("foo"), 100)
        self.assertEquals(self.param_dict.get("bar"), 200)
        self.assertEquals(self.param_dict.get("baz"), 300)

    def test_update_specific_values(self):
        """
        test to verify we can limit update to a specific
        set of parameters
        """
        sample_input = "foo=100, bar=200"

        # First verify we can set both
        self.assertNotEquals(self.param_dict.get("foo"), 100)
        self.assertNotEquals(self.param_dict.get("bar"), 200)
        self.assertTrue(self.param_dict.update(sample_input))
        self.assertEquals(self.param_dict.get("foo"), 100)
        self.assertEquals(self.param_dict.get("bar"), 200)

        # Now let's only have it update 1 parameter with a name
        sample_input = "foo=200, bar=300"
        self.assertTrue(
            self.param_dict.update(sample_input, target_params="foo"))
        self.assertEquals(self.param_dict.get("foo"), 200)
        self.assertEquals(self.param_dict.get("bar"), 200)

        # Now let's only have it update 1 parameter using a list
        sample_input = "foo=300, bar=400"
        self.assertTrue(
            self.param_dict.update(sample_input, target_params=["foo"]))
        self.assertEquals(self.param_dict.get("foo"), 300)
        self.assertEquals(self.param_dict.get("bar"), 200)

        # Test our exceptions
        with self.assertRaises(KeyError):
            self.param_dict.update(sample_input, "key_does_not_exist")

        with self.assertRaises(InstrumentParameterException):
            self.param_dict.update(sample_input, {'bad': "key_does_not_exist"})

    def test_visibility_list(self):
        lst = self.param_dict.get_visibility_list(
            ParameterDictVisibility.READ_WRITE)
        lst.sort()
        self.assertEquals(lst, ["bar", "foo"])
        lst = self.param_dict.get_visibility_list(
            ParameterDictVisibility.DIRECT_ACCESS)
        lst.sort()
        self.assertEquals(lst, ["baz", "qut"])
        lst = self.param_dict.get_visibility_list(
            ParameterDictVisibility.READ_ONLY)
        lst.sort()
        self.assertEquals(lst, ["bat", "qux"])
        lst = self.param_dict.get_visibility_list(
            ParameterDictVisibility.IMMUTABLE)
        lst.sort()
        self.assertEquals(lst, ["dil", "pho"])

    def test_function_values(self):
        """
        Make sure we can add and update values with functions instead of patterns
        """

        self.param_dict.add_parameter(
            FunctionParameter("fn_foo",
                              self.pick_byte2,
                              lambda x: str(x),
                              direct_access=True,
                              startup_param=True,
                              value=1,
                              visibility=ParameterDictVisibility.READ_WRITE))
        self.param_dict.add_parameter(
            FunctionParameter(
                "fn_bar",
                lambda x: bool(x & 2),  # bit map example
                lambda x: str(x),
                direct_access=True,
                startup_param=True,
                value=False,
                visibility=ParameterDictVisibility.READ_WRITE))

        # check defaults just to be safe
        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 1)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, False)

        self.param_dict.update(1005)  # just change first in list
        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 3)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, False)

        # fn_bar does not get updated here
        result = self.param_dict.update_many(1205)
        self.assertEqual(result['fn_foo'], True)
        self.assertEqual(len(result), 1)
        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 4)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, False)

        # both are updated now
        result = self.param_dict.update_many(6)
        self.assertEqual(result['fn_foo'], True)
        self.assertEqual(result['fn_bar'], True)
        self.assertEqual(len(result), 2)

        val = self.param_dict.get("fn_foo")
        self.assertEqual(val, 0)
        val = self.param_dict.get("fn_bar")
        self.assertEqual(val, True)

    def test_mixed_pdv_types(self):
        """ Verify we can add different types of PDVs in one container """
        self.param_dict.add_parameter(
            FunctionParameter("fn_foo",
                              self.pick_byte2,
                              lambda x: str(x),
                              direct_access=True,
                              startup_param=True,
                              value=1,
                              visibility=ParameterDictVisibility.READ_WRITE))
        self.param_dict.add_parameter(
            RegexParameter("foo",
                           r'.*foo=(\d+).*',
                           lambda match: int(match.group(1)),
                           lambda x: str(x),
                           direct_access=True,
                           startup_param=True,
                           value=10,
                           visibility=ParameterDictVisibility.READ_WRITE))
        self.param_dict.add("bar",
                            r'.*bar=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=False,
                            startup_param=True,
                            value=15,
                            visibility=ParameterDictVisibility.READ_WRITE)

        self.assertEqual(self.param_dict.get("fn_foo"), 1)
        self.assertEqual(self.param_dict.get("foo"), 10)
        self.assertEqual(self.param_dict.get("bar"), 15)

    def test_base_update(self):
        pdv = Parameter("foo", lambda x: str(x), value=12)
        self.assertEqual(pdv.get_value(), 12)
        result = pdv.update(1)
        self.assertEqual(result, True)
        self.assertEqual(pdv.get_value(), 1)

        # Its a base class...monkey see, monkey do
        result = pdv.update("foo=1")
        self.assertEqual(result, True)
        self.assertEqual(pdv.get_value(), "foo=1")

    def test_regex_val(self):
        pdv = RegexParameter("foo",
                             r'.*foo=(\d+).*',
                             lambda match: int(match.group(1)),
                             lambda x: str(x),
                             value=12)
        self.assertEqual(pdv.get_value(), 12)
        result = pdv.update(1)
        self.assertEqual(result, False)
        self.assertEqual(pdv.get_value(), 12)
        result = pdv.update("foo=1")
        self.assertEqual(result, True)
        self.assertEqual(pdv.get_value(), 1)

    def test_function_val(self):
        pdv = FunctionParameter("foo",
                                self.pick_byte2,
                                lambda x: str(x),
                                value=12)
        self.assertEqual(pdv.get_value(), 12)
        self.assertRaises(TypeError, pdv.update(1))
        result = pdv.update("1205")
        self.assertEqual(pdv.get_value(), 4)
        self.assertEqual(result, True)

    def test_set_init_value(self):
        result = self.param_dict.get("foo")
        self.assertEqual(result, None)
        self.param_dict.set_init_value("foo", 42)
        result = self.param_dict.get_init_value("foo")
        self.assertEqual(result, 42)

    def test_schema_generation(self):
        self.maxDiff = None
        result = self.param_dict.generate_dict()
        json_result = json.dumps(result, indent=4, sort_keys=True)
        log.debug("Expected: %s", self.target_schema)
        log.debug("Result: %s", json_result)
        self.assertEqual(result, self.target_schema)

    def test_empty_schema(self):
        self.param_dict = ProtocolParameterDict()
        result = self.param_dict.generate_dict()
        self.assertEqual(result, {})

    def test_bad_descriptions(self):
        self.param_dict._param_dict["foo"].description = None
        self.param_dict._param_dict["foo"].value = None
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_init_value, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_default_value, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.set_default, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_init_value, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_menu_path_read, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_submenu_read, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_menu_path_write, "foo")
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_submenu_write, "foo")
        self.assertRaises(InstrumentParameterException, self.param_dict.format,
                          "foo", 1)
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.get_direct_access_list)
        self.assertRaises(InstrumentParameterException,
                          self.param_dict.is_startup_param, "foo")

    def test_set(self):
        """
        Test a simple set of the parameter. Make sure the right values get
        called and the correct exceptions are raised.
        """
        new_param = FunctionParameter(
            "foo",
            self.pick_byte2,
            lambda x: str(x),
            direct_access=True,
            startup_param=True,
            value=1000,
            visibility=ParameterDictVisibility.READ_WRITE)
        self.assertEquals(new_param.get_value(), 1000)
        self.assertEquals(self.param_dict.get("foo"), None)
        # overwrites existing param
        self.param_dict.add_parameter(new_param)
        self.assertEquals(self.param_dict.get("foo"), 1000)
        self.param_dict.set_value("foo", 2000)
        self.assertEquals(self.param_dict.get("foo"), 2000)

    def test_invalid_type(self):
        self.assertRaises(
            InstrumentParameterException,
            FunctionParameter,
            "fn_bar",
            lambda x: bool(x & 2),  # bit map example
            lambda x: str(x),
            direct_access=True,
            startup_param=True,
            value=False,
            type="bad_type",
            visibility=ParameterDictVisibility.READ_WRITE)

    def test_get(self):
        """
        test getting values with expiration
        """
        # from mi.core.exceptions import InstrumentParameterExpirationException
        pd = ProtocolParameterDict()

        # No expiration, should work just fine
        pd.add('noexp', r'', None, None, expiration=None)
        pd.add('zeroexp', r'', None, None, expiration=0)
        pd.add('lateexp', r'', None, None, expiration=2)

        ###
        # Set and get with no expire
        ###
        pd.set_value('noexp', 1)
        self.assertEqual(pd.get('noexp'), 1)

        ###
        # Set and get with a 0 expire
        ###
        basetime = pd.get_current_timestamp()
        pd.set_value('zeroexp', 2)

        # We should fail because we are calculating exp against current time
        with self.assertRaises(InstrumentParameterExpirationException):
            pd.get('zeroexp')

        # Should succeed because exp is calculated using basetime
        self.assertEqual(pd.get('zeroexp', basetime), 2)

        ###
        # Set and get with a delayed expire
        ###
        basetime = pd.get_current_timestamp()
        futuretime = pd.get_current_timestamp(3)
        self.assertGreater(futuretime - basetime, 3)

        pd.set_value('lateexp', 2)

        # Success because data is not expired
        self.assertEqual(pd.get('lateexp', basetime), 2)

        # Fail because data is expired (simulated three seconds from now)
        with self.assertRaises(InstrumentParameterExpirationException):
            pd.get('lateexp', futuretime)

    def test_regex_flags(self):
        pdv = RegexParameter("foo",
                             r'.+foo=(\d+).+',
                             lambda match: int(match.group(1)),
                             lambda x: str(x),
                             regex_flags=re.DOTALL,
                             value=12)
        # Assert something good with dotall update()
        self.assertTrue(pdv)
        pdv.update("\n\nfoo=1212\n\n")
        self.assertEqual(pdv.get_value(), 1212)

        # negative test with no regex_flags
        pdv = RegexParameter("foo",
                             r'.+foo=(\d+).+',
                             lambda match: int(match.group(1)),
                             lambda x: str(x),
                             value=12)
        # Assert something good with dotall update()
        self.assertTrue(pdv)
        pdv.update("\n\nfoo=1212\n\n")
        self.assertEqual(pdv.get_value(), 12)

        self.assertRaises(TypeError,
                          RegexParameter,
                          "foo",
                          r'.*foo=(\d+).*',
                          lambda match: int(match.group(1)),
                          lambda x: str(x),
                          regex_flags="bad flag",
                          value=12)

    def test_format_current(self):
        self.param_dict.add("test_format",
                            r'.*foo=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: x + 5,
                            value=10)
        self.assertEqual(self.param_dict.format("test_format", 20), 25)
        self.assertEqual(self.param_dict.format("test_format"), 15)
        self.assertRaises(KeyError, self.param_dict.format, "bad_name")

    def _assert_metadata_change(self):
        new_dict = self.param_dict.generate_dict()
        log.debug("Generated dictionary: %s", new_dict)
        self.assertEqual(new_dict["qut"][ParameterDictKey.DESCRIPTION],
                         "QutFileDesc")
        self.assertEqual(new_dict["qut"][ParameterDictKey.DISPLAY_NAME],
                         "QutDisplay")
        self.assertEqual(
            new_dict["qut"][ParameterDictKey.VALUE][ParameterDictKey.UNITS],
            "QutFileUnits")
        self.assertEqual(
            new_dict["qut"][ParameterDictKey.VALUE][
                ParameterDictKey.DESCRIPTION], "QutFileValueDesc")
        self.assertEqual(
            new_dict["qut"][ParameterDictKey.VALUE][ParameterDictKey.TYPE],
            "QutFileType")
        # Should come from hard code
        # self.assertEqual(new_dict["qut"][ParameterDictKey.DISPLAY_NAME], "QutFileName")

        # from base hard code
        new_dict = self.param_dict.generate_dict()
        self.assertEqual(new_dict["baz"][ParameterDictKey.DESCRIPTION],
                         "The baz parameter")
        self.assertEqual(
            new_dict["baz"][ParameterDictKey.VALUE][ParameterDictKey.UNITS],
            "nano-bazers")
        self.assertEqual(
            new_dict["baz"][ParameterDictKey.VALUE][
                ParameterDictKey.DESCRIPTION],
            "Should be an integer between 2 and 2000")
        self.assertEqual(
            new_dict["baz"][ParameterDictKey.VALUE][ParameterDictKey.TYPE],
            ParameterDictType.INT)
        self.assertEqual(new_dict["baz"][ParameterDictKey.DISPLAY_NAME], "Baz")

        self.assertTrue('extra_param' not in new_dict)
    def test_get(self):
        """
        test getting values with expiration
        """
        # from mi.core.exceptions import InstrumentParameterExpirationException
        pd = ProtocolParameterDict()

        # No expiration, should work just fine
        pd.add('noexp', r'', None, None, expiration=None)
        pd.add('zeroexp', r'', None, None, expiration=0)
        pd.add('lateexp', r'', None, None, expiration=2)

        ###
        # Set and get with no expire
        ###
        pd.set_value('noexp', 1)
        self.assertEqual(pd.get('noexp'), 1)

        ###
        # Set and get with a 0 expire
        ###
        basetime = pd.get_current_timestamp()
        pd.set_value('zeroexp', 2)

        # We should fail because we are calculating exp against current time
        with self.assertRaises(InstrumentParameterExpirationException):
            pd.get('zeroexp')

        # Should succeed because exp is calculated using basetime
        self.assertEqual(pd.get('zeroexp', basetime), 2)

        ###
        # Set and get with a delayed expire
        ###
        basetime = pd.get_current_timestamp()
        futuretime = pd.get_current_timestamp(3)
        self.assertGreater(futuretime - basetime, 3)

        pd.set_value('lateexp', 2)

        # Success because data is not expired
        self.assertEqual(pd.get('lateexp', basetime), 2)

        # Fail because data is expired (simulated three seconds from now)
        with self.assertRaises(InstrumentParameterExpirationException):
            pd.get('lateexp', futuretime)
예제 #34
0
 def test_empty_schema(self):
     self.param_dict = ProtocolParameterDict()
     result = self.param_dict.generate_dict()
     self.assertEqual(result, {})
예제 #35
0
    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(
            Parameter.FLUSH_VOLUME,
            r"Flush Volume: (.*)mL",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FLUSH_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Flush Volume",
            description="Amount of sea water to flush prior to taking sample: (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.FLUSH_FLOWRATE,
            r"Flush Flow Rate: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FLUSH_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Flush Flow Rate",
            description="Rate at which to flush: (100 - 250)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.FLUSH_MINFLOW,
            r"Flush Min Flow: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FLUSH_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Flush Minimum Flow",
            description="If the flow rate falls below this value the flush will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.FILL_VOLUME,
            r"Fill Volume: (.*)mL",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Fill Volume",
            description="Amount of seawater to run through the collection filter (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.FILL_FLOWRATE,
            r"Fill Flow Rate: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Fill Flow Rate",
            description="Flow rate during sampling: (100 - 250)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.FILL_MINFLOW,
            r"Fill Min Flow: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Fill Minimum Flow",
            description="If the flow rate falls below this value the fill will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.CLEAR_VOLUME,
            r"Reverse Volume: (.*)mL",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=CLEAR_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Clear Volume",
            description="Amount of sea water to flush the home port after taking sample: (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.CLEAR_FLOWRATE,
            r"Reverse Flow Rate: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=CLEAR_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Clear Flow Rate",
            description="Rate at which to flush: (100 - 250)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )
        self._param_dict.add(
            Parameter.CLEAR_MINFLOW,
            r"Reverse Min Flow: (.*)mL/min",
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=CLEAR_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + "/" + Units.MINUTE,
            startup_param=True,
            display_name="Clear Minimum Flow",
            description="If the flow rate falls below this value the reverse flush will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE,
        )

        self._param_dict.set_value(Parameter.FLUSH_VOLUME, FLUSH_VOLUME)
        self._param_dict.set_value(Parameter.FLUSH_FLOWRATE, FLUSH_RATE)
        self._param_dict.set_value(Parameter.FLUSH_MINFLOW, FLUSH_MIN_RATE)
        self._param_dict.set_value(Parameter.FILL_VOLUME, FILL_VOLUME)
        self._param_dict.set_value(Parameter.FILL_FLOWRATE, FILL_RATE)
        self._param_dict.set_value(Parameter.FILL_MINFLOW, FILL_MIN_RATE)
        self._param_dict.set_value(Parameter.CLEAR_VOLUME, CLEAR_VOLUME)
        self._param_dict.set_value(Parameter.CLEAR_FLOWRATE, CLEAR_RATE)
        self._param_dict.set_value(Parameter.CLEAR_MINFLOW, CLEAR_MIN_RATE)
예제 #36
0
class Protocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT)

        # Add event handlers for protocol state machine.
        handlers = {
            ProtocolState.UNKNOWN: [
                (ProtocolEvent.ENTER, self._handler_unknown_enter),
                (ProtocolEvent.EXIT, self._handler_unknown_exit),
                (ProtocolEvent.DISCOVER, self._handler_unknown_discover),
            ],
            ProtocolState.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_command_enter),
                (ProtocolEvent.EXIT, self._handler_command_exit),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_sample),
                (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_autosample),
                (ProtocolEvent.GET, self._handler_get),
                (ProtocolEvent.SET, self._handler_command_set),
            ],
            ProtocolState.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_autosample_enter),
                (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_sample),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop),
                (ProtocolEvent.EXIT, self._handler_autosample_exit),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_direct_access_exit),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
            ],
        }

        for state in handlers:
            for event, handler in handlers[state]:
                self._protocol_fsm.add_handler(state, event, handler)

        # Add build handlers for device commands - we are only using simple commands
        for cmd in Command.list():
            self._add_build_handler(cmd, self._build_command)
            self._add_response_handler(cmd, self._check_command)
        self._add_build_handler(Command.SETUP, self._build_setup_command)
        self._add_response_handler(Command.READ_SETUP, self._read_setup_response_handler)

        # Add response handlers for device commands.
        # self._add_response_handler(Command.xyz, self._parse_xyz_response)

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

        self._chunker = StringChunker(Protocol.sieve_function)

        # Start state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)
        self._sent_cmds = None

        self.initialize_scheduler()

        # unit identifiers - must match the setup command (SU31 - '1')
        self._units = ['1', '2', '3']

        self._setup = None  # set by the read setup command handler for comparison to see if the config needs reset

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples and status
        """
        matchers = []
        return_list = []

        matchers.append(D1000TemperatureDataParticle.regex_compiled())

        for matcher in matchers:
            for match in matcher.finditer(raw_data):
                return_list.append((match.start(), match.end()))

        if not return_list:
            log.debug("sieve_function: raw_data=%r, return_list=%s", raw_data, return_list)
        return return_list

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        log.debug("_got_chunk: chunk=%s", chunk)
        self._extract_sample(D1000TemperatureDataParticle, D1000TemperatureDataParticle.regex_compiled(), chunk,
                             timestamp)

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        """
        return [x for x in events if Capability.has(x)]

    ########################################################################
    # implement virtual methods from base class.
    ########################################################################

    def _set_params(self, *args, **kwargs):
        """
        Issue commands to the instrument to set various parameters.  If
        startup is set to true that means we are setting startup values
        and immutable parameters can be set.  Otherwise only READ_WRITE
        parameters can be set.

        must be overloaded in derived classes

        @param params dictionary containing parameter name and value pairs
        @param startup - a flag, true indicates initializing, false otherwise
        """

        params = args[0]

        # check for attempt to set readonly parameters (read-only or immutable set outside startup)
        self._verify_not_readonly(*args, **kwargs)
        old_config = self._param_dict.get_config()

        for (key, val) in params.iteritems():
            log.debug("KEY = " + str(key) + " VALUE = " + str(val))
            self._param_dict.set_value(key, val)

        new_config = self._param_dict.get_config()
        # check for parameter change
        if not dict_equal(old_config, new_config):
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def apply_startup_params(self):
        """
        Apply startup parameters
        """

        config = self.get_startup_config()

        for param in Parameter.list():
            if param in config:
                self._param_dict.set_value(param, config[param])

    ########################################################################
    # Private helpers.
    ########################################################################

    def _wakeup(self, wakeup_timeout=10, response_timeout=3):
        """
        Over-ridden because the D1000 does not go to sleep and requires no special wake-up commands.
        @param wakeup_timeout The timeout to wake the device.
        @param response_timeout The time to look for response to a wakeup attempt.
        @throw InstrumentTimeoutException if the device could not be woken.
        """
        pass

    def _do_command(self, cmd, unit, **kwargs):
        """
        Send command and ensure it matches appropriate response. Simply enforces sending the unit identifier as a
        required argument.
        @param cmd - Command to send to instrument
        @param unit - unit identifier
        @retval - response from instrument
        """
        self._do_cmd_resp(cmd, unit, write_delay=INTER_CHARACTER_DELAY, **kwargs)

    def _build_command(self, cmd, unit):
        """
        @param cmd - Command to process
        @param unit - unit identifier
        """
        return '#' + unit + cmd + NEWLINE

    def _build_setup_command(self, cmd, unit):
        """
        @param cmd - command to send - should be 'SU'
        @param unit - unit identifier - should be '1', '2', or '3', must be a single character
        """
        # use defaults - in the future, may consider making some of these parameters
        # byte 0
        channel_address = unit
        # byte 1
        line_feed = self._param_dict.format(Parameter.LINEFEED)
        parity_type = self._param_dict.format(Parameter.PARITY_TYPE)
        parity_enable = self._param_dict.format(Parameter.PARITY_ENABLE)
        extended_addressing = self._param_dict.format(Parameter.EXTENDED_ADDRESSING)
        baud_rate = self._param_dict.format(Parameter.BAUD_RATE)
        baud_rate = getattr(BaudRate, 'BAUD_%d' % baud_rate, BaudRate.BAUD_9600)
        # byte 2
        alarm_enable = self._param_dict.format(Parameter.ALARM_ENABLE)
        low_alarm_latch = self._param_dict.format(Parameter.LOW_ALARM_LATCH)
        high_alarm_latch = self._param_dict.format(Parameter.HIGH_ALARM_LATCH)
        rtd_wire = self._param_dict.format(Parameter.RTD_4_WIRE)
        temp_units = self._param_dict.format(Parameter.TEMP_UNITS)
        echo = self._param_dict.format(Parameter.ECHO)
        delay_units = self._param_dict.format(Parameter.COMMUNICATION_DELAY)
        # byte 3
        precision = self._param_dict.format(Parameter.PRECISION)
        precision = getattr(UnitPrecision, 'DIGITS_%d' % precision, UnitPrecision.DIGITS_6)
        large_signal_filter_constant = self._param_dict.format(Parameter.LARGE_SIGNAL_FILTER_C)
        large_signal_filter_constant = filter_enum(large_signal_filter_constant)
        small_signal_filter_constant = self._param_dict.format(Parameter.SMALL_SIGNAL_FILTER_C)
        small_signal_filter_constant = filter_enum(small_signal_filter_constant)

        # # Factory default: 0x31070182
        # # Lab default:     0x310214C2

        byte_0 = int(channel_address.encode("hex"), 16)
        log.debug('byte 0: %s', byte_0)
        byte_1 = \
            (line_feed << 7) + \
            (parity_type << 6) + \
            (parity_enable << 5) + \
            (extended_addressing << 4) + \
            baud_rate
        log.debug('byte 1: %s', byte_1)
        byte_2 = \
            (alarm_enable << 7) + \
            (low_alarm_latch << 6) + \
            (high_alarm_latch << 5) + \
            (rtd_wire << 4) + \
            (temp_units << 3) + \
            (echo << 2) + \
            delay_units
        log.debug('byte 2: %s', byte_2)
        byte_3 = \
            (precision << 6) + \
            (large_signal_filter_constant << 3) + \
            small_signal_filter_constant
        log.debug('byte 3: %s', byte_3)

        setup_command = '#%sSU%02x%02x%02x%02x' % (unit[0], byte_0, byte_1, byte_2, byte_3) + NEWLINE
        log.debug('default setup command (%r) for unit %02x (%s)' % (setup_command, byte_0, unit[0]))
        return setup_command

    def _check_command(self, resp, prompt):
        """
        Perform a checksum calculation on provided data. The checksum used for comparison is the last two characters of
        the line.
        @param resp - response from the instrument to the command
        @param prompt - expected prompt (or the joined groups from a regex match)
        @retval
        """
        for line in resp.split(NEWLINE):
            if line.startswith('?'):
                raise InstrumentProtocolException('error processing command (%r)', resp[1:])
            if line.startswith('*'):  # response
                if not valid_response(line):
                    raise InstrumentProtocolException('checksum failed (%r)', line)

    def _read_setup_response_handler(self, resp, prompt):
        """
        Save the setup.
        @param resp - response from the instrument to the command
        @param prompt - expected prompt (or the joined groups from a regex match)
        """
        self._check_command(resp, prompt)
        self._setup = resp

    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False)

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.START_AUTOSAMPLE, display_name="Start Autosample")
        self._cmd_dict.add(Capability.STOP_AUTOSAMPLE, display_name="Stop Autosample", timeout=40)
        self._cmd_dict.add(Capability.ACQUIRE_SAMPLE, display_name="Acquire Sample")

        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover', timeout=30)

    def _add_setup_param(self, name, fmt, **kwargs):
        """
        Add setup command to the parameter dictionary. All 'SU' parameters are not startup parameter, but should be
        restored upon return from direct access. These parameters are all part of the instrument command 'SU'.
        """
        self._param_dict.add(name, '', None, fmt,
                             startup_param=False,
                             direct_access=True,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             **kwargs)

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.SAMPLE_INTERVAL,
                             '',  # this is a driver only parameter
                             None,
                             int,
                             type=ParameterDictType.INT,
                             startup_param=True,
                             display_name='D1000 Sample Periodicity',
                             range=(1, 3600),
                             description='Periodicity of D1000 temperature sample in autosample mode: (1-3600)',
                             default_value=DEFAULT_SAMPLE_RATE,
                             units=Units.SECOND,
                             visibility=ParameterDictVisibility.READ_WRITE)
        self._add_setup_param(Parameter.CHANNEL_ADDRESS,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Base Channel Address',
                              description='Hex value of ASCII character to ID unit, e.g. 31 is the ASCII code for 1:'
                                          ' (30-31, 41-5A, 61-7A)',
                              range=(0x30, 0x7A),
                              default_value=0x31)
        self._add_setup_param(Parameter.LINEFEED,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Line Feed Flag',
                              range={'True': True, 'False': False},
                              description='Enable D1000 to generate a linefeed before and after each response:'
                                          ' (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_TYPE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Parity Type',
                              range={'Odd': True, 'Even': False},
                              description='Sets the parity: (true:odd | false:even)',
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_ENABLE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Parity Flag',
                              range={'True': True, 'False': False},
                              description='Enable use of parity bit, a parity error will be issued if detected:'
                                          ' (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.EXTENDED_ADDRESSING,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Extended Addressing',
                              range={'True': True, 'False': False},
                              description='Enable extended addressing: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.BAUD_RATE,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Baud Rate',
                              range={'38400': 0, '19200': 1, '9600': 2, '4800': 3, '2400': 4, '1200': 5, '600': 6,
                                     '300': 7, '57600': 8},
                              description='Using ethernet interface in deployed configuration: (300, 600, '
                                          '1200, 2400, 4800, 9600, 19200, 38400, 57600)',
                              default_value=9600,
                              units=Units.BAUD)
        self._add_setup_param(Parameter.ALARM_ENABLE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Enable Alarms',
                              range={'True': True, 'False': False},
                              description='Enable alarms to be controlled by the Digital Output (DO) command:'
                                          ' (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.LOW_ALARM_LATCH,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Low Alarm Latching',
                              range={'True': True, 'False': False},
                              description='Enable changing the alarm to latching mode: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.HIGH_ALARM_LATCH,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='High Alarm Latching',
                              range={'True': True, 'False': False},
                              description='Enable changing the alarm to latching mode: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.RTD_4_WIRE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='4 Wire RTD Flag',
                              range={'True': True, 'False': False},
                              description='Represents a physical configuration of the instrument, '
                                          'disabling may cause data to be misaligned: (true | false)',
                              default_value=True)
        self._add_setup_param(Parameter.TEMP_UNITS,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Fahrenheit Flag',
                              range={'Fahrenheit': True, 'Celsius': False},
                              description='Flag to control the temperature format: (true:Fahrenheit | false:Celsius)',
                              default_value=False)
        self._add_setup_param(Parameter.ECHO,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Daisy Chain',
                              range={'True': True, 'False': False},
                              description='If not set, only 1 out of 3 D1000s will process commands: (true | false)',
                              default_value=True)
        self._add_setup_param(Parameter.COMMUNICATION_DELAY,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Communication Delay',
                              range=(0, 3),
                              description='The number of delays to add when processing commands: (0-3)',
                              default_value=0)
        self._add_setup_param(Parameter.PRECISION,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Precision',
                              range={'4 digits': 0, '5 digits': 1, '6 digits': 2, '7 digits': 3},
                              description='Number of digits the instrument should output for temperature query: '
                                          '(0=4-3=7)',
                              default_value=6)
        self._add_setup_param(Parameter.LARGE_SIGNAL_FILTER_C,
                              float,
                              type=ParameterDictType.FLOAT,
                              display_name='Large Signal Filter Constant',
                              range={'0': 0, '.25': 1, '.5': 2, '1': 3, '2': 4, '4': 5, '8': 6, '16': 7},
                              description='Time to reach 63% of its final value: '
                                          '(0 = 0.0, 1 = 0.25, 2 = 0.5, 3 = 1.0, 4 = 2.0, 5 = 4.0, 6 = 8.0, 7 = 16.0)',
                              default_value=0.0,
                              units=Units.SECOND)
        self._add_setup_param(Parameter.SMALL_SIGNAL_FILTER_C,
                              float,
                              type=ParameterDictType.FLOAT,
                              display_name='Small Signal Filter Constant',
                              range={'0': 0, '.25': 1, '.5': 2, '1': 3, '2': 4, '4': 5, '8': 6, '16': 7},
                              description='Smaller filter constant, should be larger than large filter constant: '
                                          '(0 = 0.0, 1 = 0.25, 2 = 0.5, 3 = 1.0, 4 = 2.0, 5 = 4.0, 6 = 8.0, 7 = 16.0)',
                              default_value=0.50,
                              units=Units.SECOND)

        for key in self._param_dict.get_keys():
            self._param_dict.set_default(key)

    def _update_params(self):
        """
        Update the parameter dictionary.
        """
        pass

    def _restore_params(self):
        """
        Restore D1000, clearing any alarms and set-point.
        """
        # make sure the alarms are disabled - preferred over doing setup, then clear alarms commands
        self._param_dict.set_value(Parameter.ALARM_ENABLE, False)
        for i in self._units:
            current_setup = None  # set in READ_SETUP response handler
            try:
                self._do_command(Command.READ_SETUP, i, response_regex=Response.READ_SETUP)
                current_setup = self._setup[4:][:-2]  # strip off the leader and checksum
            except InstrumentTimeoutException:
                log.error('D1000 unit %s has been readdressed, unable to restore settings' % i[0])
            new_setup = self._build_setup_command(Command.SETUP, i)[4:]  # strip leader (no checksum)
            if not current_setup == new_setup:
                log.debug('restoring setup to default state (%s) from current state (%s)', new_setup, current_setup)
                self._do_command(Command.ENABLE_WRITE, i)
                self._do_command(Command.SETUP, i)
            self._do_command(Command.ENABLE_WRITE, i)
            self._do_command(Command.CLEAR_ZERO, i)

    ########################################################################
    # Event handlers for UNKNOWN state.
    ########################################################################

    def _handler_unknown_enter(self, *args, **kwargs):
        """
        Enter unknown state.
        """
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_unknown_exit(self, *args, **kwargs):
        """
        Exit unknown state.
        """
        pass

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state
        @retval next_state, (next_state, result)
        """

        # force to command mode, this instrument has no autosample mode
        next_state = ProtocolState.COMMAND
        result = []

        return next_state, (next_state, result)

    ########################################################################
    # Event handlers for COMMAND state.
    ########################################################################

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        """
        # Command device to update parameters and send a config change event if needed.
        self._restore_params()

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_command_exit(self, *args, **kwargs):
        """
        Exit command state.
        """
        pass

    def _handler_command_set(self, *args, **kwargs):
        """
        no writable parameters so does nothing, just implemented to make framework happy
        """
        input_params = args[0]

        for key, value in input_params.items():
            if not Parameter.has(key):
                raise InstrumentParameterException('Invalid parameter supplied to set: %s' % key)

            try:
                value = int(value)
            except TypeError:
                raise InstrumentParameterException('Invalid value [%s] for parameter %s' % (value, key))

            if key == Parameter.SAMPLE_INTERVAL:
                if value < MIN_SAMPLE_RATE or value > MAX_SAMPLE_RATE:
                    raise InstrumentParameterException('Parameter %s value [%d] is out of range [%d %d]' %
                                                       (key, value, MIN_SAMPLE_RATE, MAX_SAMPLE_RATE))
        startup = False
        try:
            startup = args[1]
        except IndexError:
            pass

        self._set_params(input_params, startup)

        return None, None

    def _handler_command_autosample(self, *args, **kwargs):
        """
        Begin autosample.
        """
        return ProtocolState.AUTOSAMPLE, (ProtocolState.AUTOSAMPLE, None)

    def _handler_command_start_direct(self, *args, **kwargs):
        next_state = ProtocolState.DIRECT_ACCESS
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Event handlers for AUTOSAMPLE state.
    ########################################################################

    def _handler_autosample_enter(self, *args, **kwargs):
        """
        Start auto polling the temperature sensors.
        """
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)
        self._protocol_fsm.on_event(ProtocolEvent.ACQUIRE_SAMPLE)

        job_name = ScheduledJob.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.SAMPLE, ProtocolEvent.ACQUIRE_SAMPLE)

    def _handler_autosample_exit(self, *args, **kwargs):
        """
        Stop autosampling - remove the scheduled autosample
        """
        if self._scheduler is not None:
            try:
                self._remove_scheduler(ScheduledJob.SAMPLE)
            except KeyError:
                log.debug('_remove_scheduler count not find: %s', ScheduledJob.SAMPLE)

    def _handler_sample(self, *args, **kwargs):
        """
        Poll the three temperature probes for current temperature readings.
        """
        next_state = None
        timeout = time.time() + SAMPLE_TIMEOUT

        for i in self._units:
            self._do_command(Command.READ, i)

        particles = self.wait_for_particles([DataParticleType.D1000_PARSED], timeout)

        return next_state, (next_state, particles)

    def _handler_autosample_stop(self, *args, **kwargs):
        """
        Terminate autosampling
        """
        next_state = ProtocolState.COMMAND
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Direct access handlers.
    ########################################################################

    def _handler_direct_access_enter(self, *args, **kwargs):
        """
        Enter direct access state.
        """
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

        self._sent_cmds = []

    def _handler_direct_access_exit(self, *args, **kwargs):
        """
        Exit direct access state.
        """

    def _handler_direct_access_execute_direct(self, data):
        self._do_cmd_direct(data)

        return None, (None, [])

    def _handler_direct_access_stop_direct(self, *args, **kwargs):
        next_state = ProtocolState.COMMAND
        result = []

        return next_state, (next_state, result)
예제 #37
0
class Protocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    last_sample = ''

    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline,
                                                   driver_event)

        # Build protocol state machine.
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent,
                                           ProtocolEvent.ENTER,
                                           ProtocolEvent.EXIT)

        # Add event handlers for protocol state machine.
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN,
                                       ProtocolEvent.ENTER,
                                       self._handler_unknown_enter)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN,
                                       ProtocolEvent.EXIT,
                                       self._handler_unknown_exit)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN,
                                       ProtocolEvent.DISCOVER,
                                       self._handler_unknown_discover)

        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.ENTER,
                                       self._handler_command_enter)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.EXIT,
                                       self._handler_command_exit)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.ACQUIRE_SAMPLE,
                                       self._handler_acquire_sample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.START_DIRECT,
                                       self._handler_command_start_direct)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.CLOCK_SYNC,
                                       self._handler_command_sync_clock)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.GET, self._handler_get)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.SET,
                                       self._handler_command_set)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.START_AUTOSAMPLE,
                                       self._handler_command_start_autosample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.FLASH_STATUS,
                                       self._handler_flash_status)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.ACQUIRE_STATUS,
                                       self._handler_acquire_status)

        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE,
                                       ProtocolEvent.ENTER,
                                       self._handler_autosample_enter)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE,
                                       ProtocolEvent.EXIT,
                                       self._handler_autosample_exit)
        self._protocol_fsm.add_handler(
            ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE,
            self._handler_autosample_stop_autosample)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE,
                                       ProtocolEvent.ACQUIRE_SAMPLE,
                                       self._handler_acquire_sample)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE,
                                       ProtocolEvent.CLOCK_SYNC,
                                       self._handler_autosample_sync_clock)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE,
                                       ProtocolEvent.GET, self._handler_get)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE,
                                       ProtocolEvent.FLASH_STATUS,
                                       self._handler_flash_status)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE,
                                       ProtocolEvent.ACQUIRE_STATUS,
                                       self._handler_acquire_status)

        # We setup a new state for clock sync because then we could use the state machine so the autosample scheduler
        # is disabled before we try to sync the clock.  Otherwise there could be a race condition introduced when we
        # are syncing the clock and the scheduler requests a sample.
        self._protocol_fsm.add_handler(ProtocolState.SYNC_CLOCK,
                                       ProtocolEvent.ENTER,
                                       self._handler_sync_clock_enter)
        self._protocol_fsm.add_handler(ProtocolState.SYNC_CLOCK,
                                       ProtocolEvent.CLOCK_SYNC,
                                       self._handler_sync_clock_sync)

        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.EXECUTE_DIRECT,
            self._handler_direct_access_execute_direct)
        self._protocol_fsm.add_handler(ProtocolState.DIRECT_ACCESS,
                                       ProtocolEvent.STOP_DIRECT,
                                       self._handler_direct_access_stop_direct)

        # Add build handlers for device commands.
        self._add_build_handler(Command.GET_CLOCK, self._build_simple_command)
        self._add_build_handler(Command.SET_CLOCK,
                                self._build_set_clock_command)
        self._add_build_handler(Command.D, self._build_simple_command)
        self._add_build_handler(Command.GO, self._build_simple_command)
        self._add_build_handler(Command.STOP, self._build_simple_command)
        self._add_build_handler(Command.FS, self._build_simple_command)
        self._add_build_handler(Command.STAT, self._build_simple_command)

        # Add response handlers for device commands.
        self._add_response_handler(Command.GET_CLOCK,
                                   self._parse_clock_response)
        self._add_response_handler(Command.SET_CLOCK,
                                   self._parse_clock_response)
        self._add_response_handler(Command.FS, self._parse_fs_response)
        self._add_response_handler(Command.STAT, self._parse_common_response)

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

        self._chunker = StringChunker(Protocol.sieve_function)

        self._add_scheduler_event(ScheduledJob.ACQUIRE_STATUS,
                                  ProtocolEvent.ACQUIRE_STATUS)
        self._add_scheduler_event(ScheduledJob.CLOCK_SYNC,
                                  ProtocolEvent.CLOCK_SYNC)

        # Start state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples and status
        """
        matchers = []
        return_list = []

        matchers.append(METBK_SampleDataParticle.regex_compiled())
        matchers.append(METBK_StatusDataParticle.regex_compiled())

        for matcher in matchers:
            for match in matcher.finditer(raw_data):
                return_list.append((match.start(), match.end()))

        return return_list

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        log.debug("_got_chunk: chunk=%s" % chunk)
        self._extract_sample(METBK_SampleDataParticle,
                             METBK_SampleDataParticle.regex_compiled(), chunk,
                             timestamp)
        self._extract_sample(METBK_StatusDataParticle,
                             METBK_StatusDataParticle.regex_compiled(), chunk,
                             timestamp)

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        """
        return [x for x in events if Capability.has(x)]

    ########################################################################
    # override methods from base class.
    ########################################################################

    def _extract_sample(self,
                        particle_class,
                        regex,
                        line,
                        timestamp,
                        publish=True):
        """
        Overridden to add duplicate sample checking.  This duplicate checking should only be performed
        on sample chunks and not other chunk types, therefore the regex is performed before the string checking.
        Extract sample from a response line if present and publish  parsed particle

        @param particle_class The class to instantiate for this specific
            data particle. Parameterizing this allows for simple, standard
            behavior from this routine
        @param regex The regular expression that matches a data sample
        @param line string to match for sample.
        @param timestamp port agent timestamp to include with the particle
        @param publish boolean to publish samples (default True). If True,
               two different events are published: one to notify raw data and
               the other to notify parsed data.

        @retval dict of dicts {'parsed': parsed_sample, 'raw': raw_sample} if
                the line can be parsed for a sample. Otherwise, None.
        @todo Figure out how the agent wants the results for a single poll
            and return them that way from here
        """
        sample = None
        match = regex.match(line)
        if match:
            if particle_class == METBK_SampleDataParticle:
                # check to see if there is a delta from last sample, and don't parse this sample if there isn't
                if match.group(0) == self.last_sample:
                    return

                # save this sample as last_sample for next check
                self.last_sample = match.group(0)

            particle = particle_class(line, port_timestamp=timestamp)
            parsed_sample = particle.generate()

            if publish and self._driver_event:
                self._driver_event(DriverAsyncEvent.SAMPLE, parsed_sample)

            sample = json.loads(parsed_sample)
            return sample
        return sample

    ########################################################################
    # implement virtual methods from base class.
    ########################################################################

    def apply_startup_params(self):
        """
        Apply sample_interval startup parameter.  
        """
        config = self.get_startup_config()
        log.debug("apply_startup_params: startup config = %s" % config)
        if config.has_key(Parameter.SAMPLE_INTERVAL):
            log.debug("apply_startup_params: setting sample_interval to %d" %
                      config[Parameter.SAMPLE_INTERVAL])
            self._param_dict.set_value(Parameter.SAMPLE_INTERVAL,
                                       config[Parameter.SAMPLE_INTERVAL])

    ########################################################################
    # Unknown handlers.
    ########################################################################

    def _handler_unknown_enter(self, *args, **kwargs):
        """
        Enter unknown state.
        """

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_unknown_exit(self, *args, **kwargs):
        """
        Exit unknown state.
        """
        pass

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state; can only be COMMAND (instrument has no actual AUTOSAMPLE mode).
        @retval (next_state, result), (ProtocolState.COMMAND, None) if successful.
        """

        (protocol_state, agent_state) = self._discover()

        # If we are just starting up and we land in command mode then our state should
        # be idle
        if (agent_state == ResourceAgentState.COMMAND):
            agent_state = ResourceAgentState.IDLE

        log.debug("_handler_unknown_discover: state = %s", protocol_state)
        return (protocol_state, agent_state)

    ########################################################################
    # Clock Sync handlers.
    # Not much to do in this state except sync the clock then transition
    # back to autosample.  When in command mode we don't have to worry about
    # stopping the scheduler so we just sync the clock without state
    # transitions
    ########################################################################

    def _handler_sync_clock_enter(self, *args, **kwargs):
        """
        Enter sync clock state.
        """
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

        self._protocol_fsm.on_event(ProtocolEvent.CLOCK_SYNC)

    def _handler_sync_clock_sync(self, *args, **kwargs):
        """
        Sync the clock
        """
        next_state = ProtocolState.AUTOSAMPLE
        next_agent_state = ResourceAgentState.STREAMING
        result = None

        self._sync_clock()

        self._async_agent_state_change(ResourceAgentState.STREAMING)
        return (next_state, (next_agent_state, result))

    ########################################################################
    # Command handlers.
    # just implemented to make DA possible, instrument has no actual command mode
    ########################################################################

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        """
        self._init_params()

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_command_exit(self, *args, **kwargs):
        """
        Exit command state.
        """
        pass

    def _handler_command_set(self, *args, **kwargs):
        """
        no writable parameters so does nothing, just implemented to make framework happy
        """

        next_state = None
        result = None
        return (next_state, result)

    def _handler_command_start_direct(self, *args, **kwargs):
        """
        """
        result = None
        next_state = ProtocolState.DIRECT_ACCESS
        next_agent_state = ResourceAgentState.DIRECT_ACCESS

        return (next_state, (next_agent_state, result))

    def _handler_command_start_autosample(self, *args, **kwargs):
        """
        """
        result = None
        next_state = ProtocolState.AUTOSAMPLE
        next_agent_state = ResourceAgentState.STREAMING

        self._start_logging()

        return (next_state, (next_agent_state, result))

    def _handler_command_sync_clock(self, *args, **kwargs):
        """
        sync clock close to a second edge
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device respond correctly.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """

        next_state = None
        next_agent_state = None
        result = None

        self._sync_clock()

        return (next_state, (next_agent_state, result))

    ########################################################################
    # autosample handlers.
    ########################################################################

    def _handler_autosample_enter(self, *args, **kwargs):
        """
        Enter autosample state  Because this is an instrument that must be
        polled we need to ensure the scheduler is added when we are in an
        autosample state.  This scheduler raises events to poll the
        instrument for data.
        """
        self._init_params()

        self._ensure_autosample_config()
        self._add_scheduler_event(AUTO_SAMPLE_SCHEDULED_JOB,
                                  ProtocolEvent.ACQUIRE_SAMPLE)

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_autosample_exit(self, *args, **kwargs):
        """
        exit autosample state.
        """
        self._remove_scheduler(AUTO_SAMPLE_SCHEDULED_JOB)

    def _handler_autosample_stop_autosample(self, *args, **kwargs):
        """
        """
        result = None
        next_state = ProtocolState.COMMAND
        next_agent_state = ResourceAgentState.COMMAND

        self._stop_logging()

        return (next_state, (next_agent_state, result))

    def _handler_autosample_sync_clock(self, *args, **kwargs):
        """
        sync clock close to a second edge
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device respond correctly.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """

        next_state = ProtocolState.SYNC_CLOCK
        next_agent_state = ResourceAgentState.BUSY
        result = None
        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)

        return (next_state, result)

    def _handler_direct_access_stop_direct(self):
        result = None

        (next_state, next_agent_state) = self._discover()

        return (next_state, (next_agent_state, result))

    ########################################################################
    # general handlers.
    ########################################################################

    def _handler_flash_status(self, *args, **kwargs):
        """
        Acquire flash status from instrument.
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device cannot be woken for command.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """
        next_state = None
        next_agent_state = None
        result = None

        result = self._do_cmd_resp(Command.FS, expected_prompt=Prompt.FS)
        log.debug("FLASH RESULT: %s", result)

        return (next_state, (next_agent_state, result))

    def _handler_acquire_sample(self, *args, **kwargs):
        """
        Acquire sample from instrument.
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device cannot be woken for command.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """
        next_state = None
        next_agent_state = None
        result = None

        result = self._do_cmd_resp(Command.D, *args, **kwargs)

        return (next_state, (next_agent_state, result))

    def _handler_acquire_status(self, *args, **kwargs):
        """
        Acquire status from instrument.
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device cannot be woken for command.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """
        next_state = None
        next_agent_state = None
        result = None

        log.debug("Logging status: %s", self._is_logging())
        result = self._do_cmd_resp(Command.STAT,
                                   expected_prompt=[Prompt.STOPPED, Prompt.GO])

        return (next_state, (next_agent_state, result))

    ########################################################################
    # Private helpers.
    ########################################################################

    def _set_params(self, *args, **kwargs):
        """
        Overloaded from the base class, used in apply DA params.  Not needed
        here so just noop it.
        """
        pass

    def _discover(self, *args, **kwargs):
        """
        Discover current state; can only be COMMAND (instrument has no actual AUTOSAMPLE mode).
        @retval (next_state, result), (ProtocolState.COMMAND, None) if successful.
        """
        logging = self._is_logging()

        if (logging == True):
            protocol_state = ProtocolState.AUTOSAMPLE
            agent_state = ResourceAgentState.STREAMING
        elif (logging == False):
            protocol_state = ProtocolState.COMMAND
            agent_state = ResourceAgentState.COMMAND
        else:
            protocol_state = ProtocolState.UNKNOWN
            agent_state = ResourceAgentState.ACTIVE_UNKNOWN

        return (protocol_state, agent_state)

    def _start_logging(self):
        """
        start the instrument logging if is isn't running already.
        """
        if (not self._is_logging()):
            log.debug("Sending start logging command: %s", Command.GO)
            self._do_cmd_resp(Command.GO, expected_prompt=Prompt.GO)

    def _stop_logging(self):
        """
        stop the instrument logging if is is running.  When the instrument
        is in a syncing state we can not stop logging.  We must wait before
        we sent the stop command.
        """
        if (self._is_logging()):
            log.debug("Attempting to stop the instrument logging.")
            result = self._do_cmd_resp(
                Command.STOP,
                expected_prompt=[Prompt.STOPPED, Prompt.SYNC, Prompt.GO])
            log.debug("Stop Command Result: %s", result)

            # If we are still logging then let's wait until we are not
            # syncing before resending the command.
            if (self._is_logging()):
                self._wait_for_sync()
                log.debug("Attempting to stop the instrument again.")
                result = self._do_cmd_resp(
                    Command.STOP,
                    expected_prompt=[Prompt.STOPPED, Prompt.SYNC, Prompt.GO])
                log.debug("Stop Command Result: %s", result)

    def _wait_for_sync(self):
        """
        When the instrument is syncing internal parameters we can't stop
        logging.  So we will watch the logging status and when it is not
        synchronizing we will return.  Basically we will just block
        until we are no longer syncing.
        @raise InstrumentProtocolException when we timeout waiting for a
        transition.
        """
        timeout = time.time() + SYNC_TIMEOUT

        while (time.time() < timeout):
            result = self._do_cmd_resp(
                Command.STAT,
                expected_prompt=[Prompt.STOPPED, Prompt.SYNC, Prompt.GO])

            match = LOGGING_SYNC_COMPILED.match(result)

            if (match):
                log.debug("We are still in sync mode.  Wait a bit and retry")
                time.sleep(2)
            else:
                log.debug("Transitioned out of sync.")
                return True

        # We timed out
        raise InstrumentProtocolException(
            "failed to transition out of sync mode")

    def _is_logging(self):
        """
        Run the status command to determine if we are in command or autosample
        mode.
        @return: True if sampling, false if not, None if we can't determine
        """
        log.debug("_is_logging: start")
        result = self._do_cmd_resp(Command.STAT,
                                   expected_prompt=[Prompt.STOPPED, Prompt.GO])
        log.debug("Checking logging status from %s", result)

        match = LOGGING_STATUS_COMPILED.match(result)

        if not match:
            log.error("Unable to determine logging status from: %s", result)
            return None
        if match.group(1) == 'GO':
            log.debug("Looks like we are logging: %s", match.group(1))
            return True
        else:
            log.debug("Looks like we are NOT logging: %s", match.group(1))
            return False

    def _ensure_autosample_config(self):
        scheduler_config = self._get_scheduler_config()
        if (scheduler_config == None):
            log.debug(
                "_ensure_autosample_config: adding scheduler element to _startup_config"
            )
            self._startup_config[DriverConfigKey.SCHEDULER] = {}
            scheduler_config = self._get_scheduler_config()
        log.debug(
            "_ensure_autosample_config: adding autosample config to _startup_config"
        )
        config = {
            DriverSchedulerConfigKey.TRIGGER: {
                DriverSchedulerConfigKey.TRIGGER_TYPE:
                TriggerType.INTERVAL,
                DriverSchedulerConfigKey.SECONDS:
                self._param_dict.get(Parameter.SAMPLE_INTERVAL)
            }
        }
        self._startup_config[
            DriverConfigKey.SCHEDULER][AUTO_SAMPLE_SCHEDULED_JOB] = config
        if (not self._scheduler):
            self.initialize_scheduler()

    def _sync_clock(self, *args, **kwargs):
        """
        sync clock close to a second edge
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device respond correctly.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """

        next_state = None
        next_agent_state = None
        result = None

        time_format = "%Y/%m/%d %H:%M:%S"
        str_val = get_timestamp_delayed(time_format)
        log.debug("Setting instrument clock to '%s'", str_val)

        self._do_cmd_resp(Command.SET_CLOCK,
                          str_val,
                          expected_prompt=Prompt.CR_NL)

    def _wakeup(self, timeout):
        """There is no wakeup sequence for this instrument"""
        pass

    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False)

    def _build_command_dict(self):
        """
        Populate the command dictionary with command.
        """
        self._cmd_dict.add(Capability.START_AUTOSAMPLE,
                           display_name="start autosample")
        self._cmd_dict.add(Capability.STOP_AUTOSAMPLE,
                           display_name="stop autosample")
        self._cmd_dict.add(Capability.CLOCK_SYNC,
                           display_name="synchronize clock")
        self._cmd_dict.add(Capability.ACQUIRE_STATUS,
                           display_name="acquire status")
        self._cmd_dict.add(Capability.ACQUIRE_SAMPLE,
                           display_name="acquire sample")
        self._cmd_dict.add(Capability.FLASH_STATUS,
                           display_name="flash status")

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.CLOCK,
                             r'(.*)\r\n',
                             lambda match: match.group(1),
                             lambda string: str(string),
                             type=ParameterDictType.STRING,
                             display_name="clock",
                             expiration=0,
                             visibility=ParameterDictVisibility.READ_ONLY)

        self._param_dict.add(
            Parameter.SAMPLE_INTERVAL,
            r'Not used. This parameter is not parsed from instrument response',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=30,
            value=30,
            startup_param=True,
            display_name="sample_interval",
            visibility=ParameterDictVisibility.IMMUTABLE)

    def _update_params(self, *args, **kwargs):
        """
        Update the parameter dictionary. 
        """

        log.debug("_update_params:")
        # Issue clock command and parse results.
        # This is the only parameter and it is always changing so don't bother with the 'change' event
        self._do_cmd_resp(Command.GET_CLOCK)

    def _build_set_clock_command(self, cmd, val):
        """
        Build handler for set clock command (cmd=val followed by newline).
        @param cmd the string for setting the clock (this should equal #CLOCK=).
        @param val the parameter value to set.
        @ retval The set command to be sent to the device.
        """
        cmd = '%s%s' % (cmd, val) + NEWLINE
        return cmd

    def _parse_clock_response(self, response, prompt):
        """
        Parse handler for clock command.
        @param response command response string.
        @param prompt prompt following command response.        
        @throws InstrumentProtocolException if clock command misunderstood.
        """
        log.debug("_parse_clock_response: response=%s, prompt=%s" %
                  (response, prompt))
        if prompt not in [Prompt.CR_NL]:
            raise InstrumentProtocolException(
                'CLOCK command not recognized: %s.' % response)

        if not self._param_dict.update(response):
            raise InstrumentProtocolException('CLOCK command not parsed: %s.' %
                                              response)

        return

    def _parse_fs_response(self, response, prompt):
        """
        Parse handler for FS command.
        @param response command response string.
        @param prompt prompt following command response.
        @throws InstrumentProtocolException if FS command misunderstood.
        """
        log.debug("_parse_fs_response: response=%s, prompt=%s" %
                  (response, prompt))
        if prompt not in [Prompt.FS]:
            raise InstrumentProtocolException(
                'FS command not recognized: %s.' % response)

        return response

    def _parse_common_response(self, response, prompt):
        """
        Parse handler for common commands.
        @param response command response string.
        @param prompt prompt following command response.
        """
        return response
예제 #38
0
    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"]])
예제 #39
0
    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.SAMPLE_INTERVAL,
                             '',  # this is a driver only parameter
                             None,
                             int,
                             type=ParameterDictType.INT,
                             startup_param=True,
                             display_name='D1000 Sample Periodicity',
                             description='Periodicity of D1000 temperature sample in autosample mode: (1-3600)',
                             default_value=DEFAULT_SAMPLE_RATE,
                             units=Units.SECOND,
                             visibility=ParameterDictVisibility.READ_WRITE)
        self._add_setup_param(Parameter.CHANNEL_ADDRESS,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Base Channel Address',
                              description='Hex value of ASCII character to ID unit, e.g. 31 is the ASCII code for 1: (30-31, 41-5A, 61-7A)',
                              default_value=0x31)
        self._add_setup_param(Parameter.LINEFEED,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Line Feed Flag',
                              description='Enable D1000 to generate a linefeed before and after each response: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_TYPE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Parity Type',
                              description='Sets the parity: (true:odd | false:even)',
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_ENABLE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Parity Flag',
                              description='Enable use of parity bit, a parity error will be issued if detected: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.EXTENDED_ADDRESSING,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Extended Addressing',
                              description='Enable extended addressing: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.BAUD_RATE,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Baud Rate',
                              description='Using ethernet interface in deployed configuration: (300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600)',
                              default_value=9600,
                              units=Units.BAUD)
        self._add_setup_param(Parameter.ALARM_ENABLE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Enable Alarms',
                              description='Enable alarms to be controlled by the Digital Output (DO) command: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.LOW_ALARM_LATCH,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Low Alarm Latching',
                              description='Enable changing the alarm to latching mode: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.HIGH_ALARM_LATCH,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='High Alarm Latching',
                              description='Enable changing the alarm to latching mode: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.RTD_4_WIRE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='4 Wire RTD Flag',
                              description='Represents a physical configuration of the instrument, disabling may cause data to be misaligned: (true | false)',
                              default_value=True)
        self._add_setup_param(Parameter.TEMP_UNITS,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Fahrenheit Flag',
                              description='Flag to control the temperature format: (true:Fahrenheit | false:Celsius)',
                              default_value=False)
        self._add_setup_param(Parameter.ECHO,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Daisy Chain',
                              description='If not set, only 1 out of 3 D1000s will process commands: (true | false)',
                              default_value=True)
        self._add_setup_param(Parameter.COMMUNICATION_DELAY,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Communication Delay',
                              description='The number of delays to add when processing commands: (0-3)',
                              default_value=0)
        self._add_setup_param(Parameter.PRECISION,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Precision',
                              description='Number of digits the instrument should output for temperature query: (4-7)',
                              default_value=6)
        self._add_setup_param(Parameter.LARGE_SIGNAL_FILTER_C,
                              float,
                              type=ParameterDictType.FLOAT,
                              display_name='Large Signal Filter Constant',
                              description='Time to reach 63% of its final value: (0.0, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0)',
                              default_value=0.0,
                              units=Units.SECOND)
        self._add_setup_param(Parameter.SMALL_SIGNAL_FILTER_C,
                              float,
                              type=ParameterDictType.FLOAT,
                              display_name='Small Signal Filter Constant',
                              description='Smaller filter constant, should be larger than large filter constant: (0.0, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0)',
                              default_value=0.50,
                              units=Units.SECOND)

        for key in self._param_dict.get_keys():
            self._param_dict.set_default(key)
예제 #40
0
    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.SAMPLE_INTERVAL,
                             '',  # this is a driver only parameter
                             None,
                             int,
                             type=ParameterDictType.INT,
                             default_value=DEFAULT_SAMPLE_RATE,
                             startup_param=True,
                             display_name='D1000 sample periodicity (sec)',
                             visibility=ParameterDictVisibility.READ_WRITE)
        self._add_setup_param(Parameter.CHANNEL_ADDRESS,
                              int,
                              display_name='base channel address',
                              type=ParameterDictType.INT,
                              default_value=0x31)
        self._add_setup_param(Parameter.LINEFEED,
                              bool,
                              display_name='line feed flag',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_TYPE,
                              bool,
                              display_name='parity type',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_ENABLE,
                              bool,
                              display_name='parity flag',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.EXTENDED_ADDRESSING,
                              bool,
                              display_name='extended addressing',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.BAUD_RATE,
                              int,
                              display_name='baud rate',
                              type=ParameterDictType.INT,
                              default_value=9600)
        self._add_setup_param(Parameter.ALARM_ENABLE,
                              bool,
                              display_name='enable alarms',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.LOW_ALARM_LATCH,
                              bool,
                              display_name='low alarm latching',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.HIGH_ALARM_LATCH,
                              bool,
                              display_name='high alarm latching',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.RTD_4_WIRE,
                              bool,
                              display_name='4 wire RTD flag',
                              type=ParameterDictType.BOOL,
                              default_value=True)
        self._add_setup_param(Parameter.TEMP_UNITS,
                              bool,
                              display_name='Fahrenheit flag',
                              type=ParameterDictType.BOOL,
                              default_value=False)
        self._add_setup_param(Parameter.ECHO,
                              bool,
                              display_name='daisy chain',
                              type=ParameterDictType.BOOL,
                              default_value=True)
        self._add_setup_param(Parameter.COMMUNICATION_DELAY,
                              int,
                              display_name='communication delay',
                              type=ParameterDictType.INT,
                              default_value=0)
        self._add_setup_param(Parameter.PRECISION,
                              int,
                              display_name='precision',
                              type=ParameterDictType.INT,
                              default_value=6)
        self._add_setup_param(Parameter.LARGE_SIGNAL_FILTER_C,
                              float,
                              display_name='large signal filter constant',
                              type=ParameterDictType.FLOAT,
                              default_value=0.0)
        self._add_setup_param(Parameter.SMALL_SIGNAL_FILTER_C,
                              float,
                              display_name='small signal filter constant',
                              type=ParameterDictType.FLOAT,
                              default_value=0.50)

        for key in self._param_dict.get_keys():
            self._param_dict.set_default(key)
예제 #41
0
    def setUp(self):
        self.param_dict = ProtocolParameterDict()

        self.param_dict.add("foo",
                            r'.*foo=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=True,
                            startup_param=True,
                            default_value=10,
                            visibility=ParameterDictVisibility.READ_WRITE)
        self.param_dict.add("bar",
                            r'.*bar=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            direct_access=False,
                            startup_param=True,
                            default_value=15,
                            visibility=ParameterDictVisibility.READ_WRITE)
        self.param_dict.add(
            "baz",
            r'.*baz=(\d+).*',
            lambda match: int(match.group(1)),
            lambda x: str(x),
            direct_access=True,
            default_value=20,
            visibility=ParameterDictVisibility.DIRECT_ACCESS,
            get_timeout=30,
            set_timeout=40,
            display_name="Baz",
            description="The baz parameter",
            type=ParameterDictType.INT,
            units="nano-bazers",
            value_description="Should be an integer between 2 and 2000")
        self.param_dict.add(
            "bat",
            r'.*bat=(\d+).*',
            lambda match: int(match.group(1)),
            lambda x: str(x),
            startup_param=False,
            default_value=20,
            visibility=ParameterDictVisibility.READ_ONLY,
            get_timeout=10,
            set_timeout=20,
            display_name="Bat",
            description="The bat parameter",
            type=ParameterDictType.INT,
            units="nano-batbit",
            value_description="Should be an integer between 1 and 1000")
        self.param_dict.add("qux",
                            r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.READ_ONLY)
        self.param_dict.add("pho",
                            r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.IMMUTABLE)
        self.param_dict.add("dil",
                            r'.*qux=(\d+).*',
                            lambda match: int(match.group(1)),
                            lambda x: str(x),
                            startup_param=False,
                            visibility=ParameterDictVisibility.IMMUTABLE)
        self.param_dict.add(
            "qut",
            r'.*qut=(\d+).*',
            lambda match: int(match.group(1)),
            lambda x: str(x),
            direct_access=True,
            default_value=[10, 100],
            visibility=ParameterDictVisibility.DIRECT_ACCESS,
            expiration=1,
            get_timeout=10,
            set_timeout=20,
            display_name="Qut",
            description="The qut list parameter",
            type=ParameterDictType.LIST,
            units="nano-qutters",
            value_description=
            "Should be a 2-10 element list of integers between 2 and 2000")

        self.target_schema = {
            "bar": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": True,
                "value": {
                    "default": 15
                },
                "visibility": "READ_WRITE",
                "range": None,
            },
            "bat": {
                "description": "The bat parameter",
                "direct_access": False,
                "display_name": "Bat",
                "get_timeout": 10,
                "set_timeout": 20,
                "startup": False,
                "value": {
                    "default": 20,
                    "description": "Should be an integer between 1 and 1000",
                    "type": "int",
                    "units": "nano-batbit"
                },
                "visibility": "READ_ONLY",
                "range": None,
            },
            "baz": {
                "description": "The baz parameter",
                "direct_access": True,
                "display_name": "Baz",
                "get_timeout": 30,
                "set_timeout": 40,
                "startup": False,
                "value": {
                    "default": 20,
                    "description": "Should be an integer between 2 and 2000",
                    "type": "int",
                    "units": "nano-bazers"
                },
                "visibility": "DIRECT_ACCESS",
                "range": None,
            },
            "dil": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "IMMUTABLE",
                "range": None,
            },
            "foo": {
                "direct_access": True,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": True,
                "value": {
                    "default": 10
                },
                "visibility": "READ_WRITE",
                "range": None,
            },
            "pho": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "IMMUTABLE",
                "range": None,
            },
            "qut": {
                "description": "The qut list parameter",
                "direct_access": True,
                "display_name": "Qut",
                "get_timeout": 10,
                "set_timeout": 20,
                "startup": False,
                "value": {
                    "default": [10, 100],
                    "description":
                    "Should be a 2-10 element list of integers between 2 and 2000",
                    "type": "list",
                    "units": "nano-qutters"
                },
                "visibility": "DIRECT_ACCESS",
                "range": None,
            },
            "qux": {
                "direct_access": False,
                "get_timeout": 10,
                "set_timeout": 10,
                "startup": False,
                "value": {},
                "visibility": "READ_ONLY",
                "range": None,
            }
        }

        self.test_yaml = '''
class InstrumentProtocol(object):
    """
        
    Base instrument protocol class.
    """    
    def __init__(self, driver_event):
        """
        Base constructor.
        @param driver_event The callback for asynchronous driver events.
        """
        # Event callback to send asynchronous events to the agent.
        self._driver_event = driver_event

        # The connection used to talk to the device.
        self._connection = None
        
        # The protocol state machine.
        self._protocol_fsm = None
        
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # The spot to stash a configuration before going into direct access
        # mode
        self._pre_direct_access_config = None

        # Driver configuration passed from the user
        self._startup_config = {}

        # scheduler config is a bit redundant now, but if we ever want to
        # re-initialize a scheduler we will need it.
        self._scheduler = None
        self._scheduler_callback = {}
        self._scheduler_config = {}

    ########################################################################
    # Helper methods
    ########################################################################
    def got_data(self, port_agent_packet):
        """
        Called by the instrument connection when data is available.
         Defined in subclasses.
        """
        log.error("base got_data.  Who called me?")
        pass

    def _extract_sample(self, particle_class, regex, line, timestamp, publish=True):
        """
        Extract sample from a response line if present and publish
        parsed particle

        @param particle_class The class to instantiate for this specific
            data particle. Parameterizing this allows for simple, standard
            behavior from this routine
        @param regex The regular expression that matches a data sample
        @param line string to match for sample.
        @param timestamp port agent timestamp to include with the particle
        @param publish boolean to publish samples (default True). If True,
               two different events are published: one to notify raw data and
               the other to notify parsed data.

        @retval dict of dicts {'parsed': parsed_sample, 'raw': raw_sample} if
                the line can be parsed for a sample. Otherwise, None.
        @todo Figure out how the agent wants the results for a single poll
            and return them that way from here
        """
        sample = None
        if regex.match(line):
        
            particle = particle_class(line, port_timestamp=timestamp)
            parsed_sample = particle.generate()

            if publish and self._driver_event:
                self._driver_event(DriverAsyncEvent.SAMPLE, parsed_sample)
    
            sample = json.loads(parsed_sample)
            return sample
        return sample

    def get_current_state(self):
        """
        Return current state of the protocol FSM.
        """
        return self._protocol_fsm.get_current_state()

    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 = self._param_dict.get_keys()
        
        return [res_cmds, res_params]

    def _filter_capabilities(self, events):
        """
        """
        return events

    ########################################################################
    # Scheduler interface.
    ########################################################################
    def _add_scheduler(self, name, callback):
        """
        Stage a scheduler in a driver.  The job will actually be configured
        and started by initialize_scheduler

        @param name the name of the job
        @param callback the handler when the job is triggered
        @raise KeyError if we try to add a job twice
        """
        if(self._scheduler_callback.get(name)):
            raise KeyError("duplicate scheduler exists for '%s'" % name)

        log.debug("Add scheduler callback: %s" % name)
        self._scheduler_callback[name] = callback
        self._add_scheduler_job(name)

    def _add_scheduler_event(self, name, event):
        """
        Create a scheduler, but instead of passing a callback we pass in
        an event to raise.  A callback function is dynamically created
        to do this.
        @param name the name of the job
        @param event: event to raise when the scheduler is triggered
        @raise KeyError if we try to add a job twice
        """
        # Create a callback for the scheduler to raise an event
        def event_callback(self, event):
            log.info("driver job triggered, raise event: %s" % event)
            self._protocol_fsm.on_event(event)

        # Dynamically create the method and add it
        method = partial(event_callback, self, event)
        self._add_scheduler(name, method)

    def _add_scheduler_job(self, name):
        """
        Map the driver configuration to a scheduler configuration.  If
        the scheduler has been started then also add the job.
        @param name the name of the job
        @raise KeyError if job name does not exists in the callback config
        @raise KeyError if job is already configured
        """
        # Do nothing if the scheduler isn't initialized
        if(not self._scheduler):
            return

        callback = self._scheduler_callback.get(name)
        if(not callback):
            raise KeyError("callback not defined in driver for '%s'" % name)

        if(self._scheduler_config.get(name)):
            raise KeyError("scheduler job already configured '%s'" % name)

        scheduler_config = self._get_scheduler_config()
        log.debug("Scheduler config: %s" % scheduler_config)

        # No config?  Nothing to do then.
        if(scheduler_config == None):
            return

        job_config = scheduler_config.get(name)

        if(job_config):
            # Store the scheduler configuration
            self._scheduler_config[name] = {
                DriverSchedulerConfigKey.TRIGGER: job_config.get(DriverSchedulerConfigKey.TRIGGER),
                DriverSchedulerConfigKey.CALLBACK: callback
            }
            config = {name: self._scheduler_config[name]}
            log.debug("Scheduler job with config: %s" % config)

            # start the job.  Note, this lazily starts the scheduler too :)
            self._scheduler.add_config(config)

    def _get_scheduler_config(self):
        """
        Get the configuration dictionary to use for initializing jobs

        Returned dictionary structure:
        {
            'job_name': {
                DriverSchedulerConfigKey.TRIGGER: {}
            }
        }

        @return: scheduler configuration dictionary
        """
        # Currently the startup config is in the child class.
        # @TODO should the config code be promoted?
        config = self._startup_config
        return config.get(DriverConfigKey.SCHEDULER)

    def initialize_scheduler(self):
        """
        Activate all configured schedulers added using _add_scheduler.
        Timers start when the job is activated.
        """
        log.debug("Scheduler config: %s" % self._get_scheduler_config())
        log.debug("Scheduler callbacks: %s" % self._scheduler_callback)
        self._scheduler = DriverScheduler()
        for name in self._scheduler_callback.keys():
            log.debug("Add job for callback: %s" % name)
            self._add_scheduler_job(name)

    #############################################################
    # Configuration logic
    #############################################################
    
    def apply_startup_params(self):
        """
        Apply the startup values previously stored in the protocol to
        the running config of the live instrument. The startup values are the
        values that are (1) marked as startup parameters and are (2) the "best"
        value to use at startup. Preference is given to the previously-set init
        value, then the default value, then the currently used value.
        
        This default method assumes a dict of parameter name and value for
        the configuration.
        
        This is the base stub for applying startup parameters at the protocol layer.
        
        @raise InstrumentParameterException If the config cannot be applied
        @raise NotImplementedException In the base class it isnt implemented
        """
        raise NotImplementedException("Base class does not implement apply_startup_params()")
        
    def set_init_params(self, config):
        """
        Set the initialization parameters to the given values in the protocol
        parameter dictionary. 
        @param config The parameter_name/value to set in the initialization
            fields of the parameter dictionary
        @raise InstrumentParameterException If the config cannot be set
        """
        if not isinstance(config, dict):
            raise InstrumentParameterException("Invalid init config format")

        self._startup_config = config
        
        param_config = config.get(DriverConfigKey.PARAMETERS)
        if(param_config):
            for name in param_config.keys():
                log.debug("Setting init value for %s to %s", name, param_config[name])
                self._param_dict.set_init_value(name, param_config[name])
    
    def get_startup_config(self):
        """
        Gets the startup configuration for the instrument. The parameters
        returned are marked as startup, and the values are the best as chosen
        from the initialization, default, and current parameters.
        
        @retval The dict of parameter_name/values (override this method if it
            is more involved for a specific instrument) that should be set at
            a higher level.

        @raise InstrumentProtocolException if a startup parameter doesn't
               have a init or default value
        """
        return_dict = {}
        start_list = self._param_dict.get_keys()
        log.trace("Startup list: %s", start_list)
        assert isinstance(start_list, list)
        
        for param in start_list:
            result = self._param_dict.get_config_value(param)
            if(result != None):
                return_dict[param] = result
            elif(self._param_dict.is_startup_param(param)):
                raise InstrumentProtocolException("Required startup value not specified: %s" % param)

        return return_dict
        
    def get_direct_access_params(self):
        """
        Get the list of direct access parameters, useful for restoring direct
        access configurations up in the driver.
        
        @retval a list of direct access parameter names
        """
        return self._param_dict.get_direct_access_list()
        
    def get_cached_config(self):
        """
        Return the configuration object that shows the instrument's 
        configuration as cached in the parameter dictionary...usually in
        sync with the instrument, but accessible when offline...
        @retval The cached configuration in the instruments config format. By
        default, it is a dictionary of parameter names and values.
        """
        assert self._param_dict != None
        return self._param_dict.get_config()
        
    ########################################################################
    # Command build and response parse handlers.
    ########################################################################            
    def _add_response_handler(self, cmd, func, state=None):
        """
        Insert a handler class responsible for handling the response to a
        command sent to the instrument, optionally available only in a
        specific state.
        
        @param cmd The high level key of the command to respond to.
        @param func The function that handles the response
        @param state The state to pair with the command for which the function
        should be used
        """

        if state == None:
            self._response_handlers[cmd] = func
        else:            
            self._response_handlers[(state, cmd)] = func

    def _add_build_handler(self, cmd, func):
        """
        Add a command building function.
        @param cmd The device command to build.
        @param func The function that constructs the command.
        """

        self._build_handlers[cmd] = func
        
    ########################################################################
    # Helpers to build commands.
    ########################################################################
    def _build_simple_command(self, cmd, *args):
        """
        Builder for simple commands

        @param cmd The command to build
        @param args Unused arguments
        @retval Returns string ready for sending to instrument        
        """

        return "%s%s" % (cmd, self._newline)
    
    def _build_keypress_command(self, cmd, *args):
        """
        Builder for simple, non-EOLN-terminated commands

        @param cmd The command to build
        @param args Unused arguments
        @retval Returns string ready for sending to instrument        
        """


        return "%s" % (cmd)
    
    def _build_multi_keypress_command(self, cmd, *args):
        """
        Builder for simple, non-EOLN-terminated commands

        @param cmd The command to build
        @param args Unused arguments
        @retval Returns string ready for sending to instrument        
        """


        return "%s%s%s%s%s%s" % (cmd, cmd, cmd, cmd, cmd, cmd)

    ########################################################################
    # Static helpers to format set commands.
    ########################################################################

    def _true_false_to_string(v):
        """
        Write a boolean value to string formatted for "generic" set operations.
        Subclasses should overload this as needed for instrument-specific
        formatting.
        
        @param v a boolean value.
        @retval A yes/no string formatted as a Python boolean for set operations.
        @throws InstrumentParameterException if value not a bool.
        """
        
        if not isinstance(v,bool):
            raise InstrumentParameterException('Value %s is not a bool.' % str(v))
        return str(v)

    @staticmethod
    def _int_to_string(v):
        """
        Write an int value to string formatted for "generic" set operations.
        Subclasses should overload this as needed for instrument-specific
        formatting.
        
        @param v An int val.
        @retval an int string formatted for generic set operations.
        @throws InstrumentParameterException if value not an int.
        """
        
        if not isinstance(v,int):
            raise InstrumentParameterException('Value %s is not an int.' % str(v))
        else:
            return '%i' % v

    @staticmethod
    def _float_to_string(v):
        """
        Write a float value to string formatted for "generic" set operations.
        Subclasses should overload this as needed for instrument-specific
        formatting.
        
        @param v A float val.
        @retval a float string formatted for "generic" set operations.
        @throws InstrumentParameterException if value is not a float.
        """

        if not isinstance(v,float):
            raise InstrumentParameterException('Value %s is not a float.' % v)
        else:
            return '%e' % v
예제 #43
0
    def test_get(self):
        """
        test getting values with expiration
        """
        # from mi.core.exceptions import InstrumentParameterExpirationException
        pd = ProtocolParameterDict()

        # No expiration, should work just fine
        pd.add('noexp', r'', None, None, expiration=None)
        pd.add('zeroexp', r'', None, None, expiration=0)
        pd.add('lateexp', r'', None, None, expiration=2)

        ###
        # Set and get with no expire
        ###
        pd.set_value('noexp', 1)
        self.assertEqual(pd.get('noexp'), 1)

        ###
        # Set and get with a 0 expire
        ###
        basetime = pd.get_current_timestamp()
        pd.set_value('zeroexp', 2)

        # We should fail because we are calculating exp against current time
        with self.assertRaises(InstrumentParameterExpirationException):
            pd.get('zeroexp')

        # Should succeed because exp is calculated using basetime
        self.assertEqual(pd.get('zeroexp', basetime), 2)

        ###
        # Set and get with a delayed expire
        ###
        basetime = pd.get_current_timestamp()
        futuretime = pd.get_current_timestamp(3)
        self.assertGreater(futuretime - basetime, 3)

        pd.set_value('lateexp', 2)

        # Success because data is not expired
        self.assertEqual(pd.get('lateexp', basetime), 2)

        # Fail because data is expired (simulated three seconds from now)
        with self.assertRaises(InstrumentParameterExpirationException):
            pd.get('lateexp', futuretime)
예제 #44
0
class DataSetDriver(object):
    """
    Base class for data set drivers.  Provides:
    - an interface via callback to publish data
    - an interface via callback to persist driver state
    - an interface via callback to handle exceptions
    - an start and stop sampling
    - a client interface for execute resource

    Subclasses need to include harvesters and parsers and
    be specialized to handle the interaction between the two.
    
    Configurations should contain keys from the DataSetDriverConfigKey class
    and should look something like this example (more full documentation in the
    "Dataset Agent Architecture" page on the OOI wiki):
    {
        'harvester':
        {
            'directory': '/tmp/dsatest',
            'pattern': '*.txt',
            'frequency': 1,
        },
        'parser': {}
        'driver': {
            'records_per_second'
            'harvester_polling_interval'
            'batched_particle_count'
        }
    }
    """
    def __init__(self, config, memento, data_callback, state_callback, exception_callback):
        self._config = config
        self._data_callback = data_callback
        self._state_callback = state_callback
        self._exception_callback = exception_callback
        self._memento = memento
        self._publisher_thread = None

        self._verify_config()

        # Updated my set_resource, defaults defined in build_param_dict
        self._polling_interval = None
        self._generate_particle_count = None
        self._particle_count_per_second = None

        self._param_dict = ProtocolParameterDict()
        self._cmd_dict = ProtocolCommandDict()
        self._driver_dict = DriverDict()

        self._build_command_dict()
        self._build_driver_dict()
        self._build_param_dict()

    def shutdown(self):
        self.stop_sampling()

    def start_sampling(self):
        """
        Start a new thread to monitor for data
        """
        self._start_sampling()
        self._start_publisher_thread()

    def stop_sampling(self):
        """
        Stop the sampling thread
        """
        log.debug("Stopping driver now")

        self._stop_sampling()
        self._stop_publisher_thread()

    def _start_sampling(self):
        raise NotImplementedException('virtual method needs to be specialized')

    def _stop_sampling(self):
        raise NotImplementedException('virtual method needs to be specialized')

    def _is_sampling(self):
        """
        Currently the drivers only have two states, command and streaming and
        all resource commands are common, either start or stop autosample.
        Therefore we didn't implement an enitre state machine to manage states
        and commands.  If it does get more complex than this we should take the
        time to implement a state machine to add some flexibility
        """
        raise NotImplementedException('virtual method needs to be specialized')

    def cmd_dvr(self, cmd, *args, **kwargs):
        log.warn("DRIVER: cmd_dvr %s", cmd)

        if cmd == 'execute_resource':
            resource_cmd = args[0]

            if resource_cmd == DriverEvent.START_AUTOSAMPLE:
                return (ResourceAgentState.STREAMING, None)

            elif resource_cmd == DriverEvent.STOP_AUTOSAMPLE:
                self.stop_sampling()
                return (ResourceAgentState.COMMAND, None)

            else:
                log.error("Unhandled resource command: %s", resource_cmd)
                raise

        elif cmd == 'get_resource_capabilities':
            return self.get_resource_capabilities()

        elif cmd == 'set_resource':
            return self.set_resource(*args, **kwargs)

        elif cmd == 'get_resource':
            return self.get_resource(*args, **kwargs)

        elif cmd == 'get_config_metadata':
            return self.get_config_metadata(*args, **kwargs)

        elif cmd == 'disconnect':
            pass

        elif cmd == 'initialize':
            pass

        else:
            log.error("Unhandled command: %s", cmd)
            raise InstrumentStateException("Unhandled command: %s" % cmd)

    def get_resource_capabilities(self, current_state=True, *args, **kwargs):
        """
        Return driver commands and parameters.
        @param current_state True to retrieve commands available in current
        state, otherwise reutrn all commands.
        @retval list of AgentCapability objects representing the drivers
        capabilities.
        @raises NotImplementedException if not implemented by subclass.
        """
        res_params = self._param_dict.get_keys()
        res_cmds = [DriverEvent.STOP_AUTOSAMPLE, DriverEvent.START_AUTOSAMPLE]

        if current_state and self._is_sampling():
            res_cmds = [DriverEvent.STOP_AUTOSAMPLE]
        elif current_state and not self._is_sampling():
            res_cmds = [DriverEvent.START_AUTOSAMPLE]

        return [res_cmds, res_params]

    def set_resource(self, *args, **kwargs):
        """
        Set the driver parameter
        """
        log.trace("start set_resource")
        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException('Set command requires a parameter dict.')

        log.trace("set_resource: iterate through params: %s", params)
        for (key, val) in params.iteritems():
            if key in [DriverParameter.BATCHED_PARTICLE_COUNT, DriverParameter.RECORDS_PER_SECOND]:
                if not isinstance(val, int): raise InstrumentParameterException("%s must be an integer" % key)
            if key in [DriverParameter.PUBLISHER_POLLING_INTERVAL]:
                if not isinstance(val, (int, float)): raise InstrumentParameterException("%s must be an float" % key)

            if val <= 0:
                raise InstrumentParameterException("%s must be > 0" % key)

            self._param_dict.set_value(key, val)

        # Set the driver parameters
        self._generate_particle_count = self._param_dict.get(DriverParameter.BATCHED_PARTICLE_COUNT)
        self._particle_count_per_second = self._param_dict.get(DriverParameter.RECORDS_PER_SECOND)
        self._polling_interval = self._param_dict.get(DriverParameter.PUBLISHER_POLLING_INTERVAL)
        log.trace("Driver Parameters: %s, %s, %s", self._polling_interval, self._particle_count_per_second, self._generate_particle_count)

    def get_resource(self, *args, **kwargs):
        """
        Get driver parameter
        """
        result = {}

        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException('Set command requires a parameter list.')

        # If all params requested, retrieve config.
        if params == DriverParameter.ALL:
            result = self._param_dict.get_config()

        # If not all params, confirm a list or tuple of params to retrieve.
        # Raise if not a list or tuple.
        # Retrieve each key in the list, raise if any are invalid.
        else:
            if not isinstance(params, (list, tuple)):
                raise InstrumentParameterException('Get argument not a list or tuple.')
            result = {}
            for key in params:
                try:
                    val = self._param_dict.get(key)
                    result[key] = val

                except KeyError:
                    raise InstrumentParameterException(('%s is not a valid parameter.' % key))

        return result

    def get_config_metadata(self):
        """
        Return the configuration metadata object in JSON format
        @retval The description of the parameters, commands, and driver info
        in a JSON string
        @see https://confluence.oceanobservatories.org/display/syseng/CIAD+MI+SV+Instrument+Driver-Agent+parameter+and+command+metadata+exchange
        """
        log.debug("Getting metadata from driver...")
        log.debug("Getting metadata dict from protocol...")
        return_dict = {}
        return_dict[ConfigMetadataKey.DRIVER] = self._driver_dict.generate_dict()
        return_dict[ConfigMetadataKey.COMMANDS] = self._cmd_dict.generate_dict()
        return_dict[ConfigMetadataKey.PARAMETERS] = self._param_dict.generate_dict()

        return return_dict

    def _verify_config(self):
        """
        virtual method to verify the supplied driver configuration is value.  Must
        be overloaded in sub classes.

        raises an ConfigurationException when a configuration error is detected.
        """
        raise NotImplementedException('virtual methond needs to be specialized')

    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        pass

    def _build_command_dict(self):
        """
        Populate the command dictionary with command.
        """
        self._cmd_dict.add(DriverEvent.START_AUTOSAMPLE, display_name="start autosample")
        self._cmd_dict.add(DriverEvent.STOP_AUTOSAMPLE, display_name="stop autosample")

    def _build_param_dict(self):
        """
        Setup three common driver parameters
        """
        self._param_dict.add_parameter(
            Parameter(
                DriverParameter.RECORDS_PER_SECOND,
                int,
                value=60,
                type=ParameterDictType.INT,
                visibility=ParameterDictVisibility.IMMUTABLE,
                display_name="Records Per Second",
                description="Number of records to process per second")
        )

        self._param_dict.add_parameter(
            Parameter(
                DriverParameter.PUBLISHER_POLLING_INTERVAL,
                float,
                value=1,
                type=ParameterDictType.FLOAT,
                visibility=ParameterDictVisibility.IMMUTABLE,
                display_name="Harvester Polling Interval",
                description="Duration in minutes to wait before checking for new files.")
        )

        self._param_dict.add_parameter(
            Parameter(
                DriverParameter.BATCHED_PARTICLE_COUNT,
                int,
                value=1,
                type=ParameterDictType.INT,
                visibility=ParameterDictVisibility.IMMUTABLE,
                display_name="Batched Particle Count",
                description="Number of particles to batch before sending to the agent")
        )

        config = self._config.get(DataSourceConfigKey.DRIVER, {})
        log.debug("set_resource on startup with: %s", config)
        self.set_resource(config)

    def _start_publisher_thread(self):
        self._publisher_thread = gevent.spawn(self._publisher_loop)
        self._publisher_shutdown = False

    def _stop_publisher_thread(self):
        log.debug("Signal shutdown")
        self._publisher_shutdown = True
        if self._publisher_thread:
            self._publisher_thread.kill(block=False)
        log.debug("shutdown complete")

    def _publisher_loop(self):
        """
        Main loop to listen for new files to parse.  Parse them and move on.
        """
        log.info("Starting main publishing loop")

        try:
            while(not self._publisher_shutdown):
                self._poll()
                gevent.sleep(self._polling_interval)
        except Exception as e:
            log.error("Exception in publisher thread: %s", e)
            self._exception_callback(e)

        log.debug("publisher thread detected shutdown request")

    def _poll(self):
        raise NotImplementedException('virtual methond needs to be specialized')

    def _new_file_exception(self):
        raise NotImplementedException('virtual methond needs to be specialized')
예제 #45
0
    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.SAMPLE_INTERVAL,
                             '',  # this is a driver only parameter
                             None,
                             int,
                             type=ParameterDictType.INT,
                             startup_param=True,
                             display_name='D1000 Sample Periodicity',
                             range=(1, 3600),
                             description='Periodicity of D1000 temperature sample in autosample mode: (1-3600)',
                             default_value=DEFAULT_SAMPLE_RATE,
                             units=Units.SECOND,
                             visibility=ParameterDictVisibility.READ_WRITE)
        self._add_setup_param(Parameter.CHANNEL_ADDRESS,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Base Channel Address',
                              description='Hex value of ASCII character to ID unit, e.g. 31 is the ASCII code for 1:'
                                          ' (30-31, 41-5A, 61-7A)',
                              range=(0x30, 0x7A),
                              default_value=0x31)
        self._add_setup_param(Parameter.LINEFEED,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Line Feed Flag',
                              range={'True': True, 'False': False},
                              description='Enable D1000 to generate a linefeed before and after each response:'
                                          ' (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_TYPE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Parity Type',
                              range={'Odd': True, 'Even': False},
                              description='Sets the parity: (true:odd | false:even)',
                              default_value=False)
        self._add_setup_param(Parameter.PARITY_ENABLE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Parity Flag',
                              range={'True': True, 'False': False},
                              description='Enable use of parity bit, a parity error will be issued if detected:'
                                          ' (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.EXTENDED_ADDRESSING,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Extended Addressing',
                              range={'True': True, 'False': False},
                              description='Enable extended addressing: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.BAUD_RATE,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Baud Rate',
                              range={'38400': 0, '19200': 1, '9600': 2, '4800': 3, '2400': 4, '1200': 5, '600': 6,
                                     '300': 7, '57600': 8},
                              description='Using ethernet interface in deployed configuration: (300, 600, '
                                          '1200, 2400, 4800, 9600, 19200, 38400, 57600)',
                              default_value=9600,
                              units=Units.BAUD)
        self._add_setup_param(Parameter.ALARM_ENABLE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Enable Alarms',
                              range={'True': True, 'False': False},
                              description='Enable alarms to be controlled by the Digital Output (DO) command:'
                                          ' (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.LOW_ALARM_LATCH,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Low Alarm Latching',
                              range={'True': True, 'False': False},
                              description='Enable changing the alarm to latching mode: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.HIGH_ALARM_LATCH,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='High Alarm Latching',
                              range={'True': True, 'False': False},
                              description='Enable changing the alarm to latching mode: (true | false)',
                              default_value=False)
        self._add_setup_param(Parameter.RTD_4_WIRE,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='4 Wire RTD Flag',
                              range={'True': True, 'False': False},
                              description='Represents a physical configuration of the instrument, '
                                          'disabling may cause data to be misaligned: (true | false)',
                              default_value=True)
        self._add_setup_param(Parameter.TEMP_UNITS,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Fahrenheit Flag',
                              range={'Fahrenheit': True, 'Celsius': False},
                              description='Flag to control the temperature format: (true:Fahrenheit | false:Celsius)',
                              default_value=False)
        self._add_setup_param(Parameter.ECHO,
                              bool,
                              type=ParameterDictType.BOOL,
                              display_name='Daisy Chain',
                              range={'True': True, 'False': False},
                              description='If not set, only 1 out of 3 D1000s will process commands: (true | false)',
                              default_value=True)
        self._add_setup_param(Parameter.COMMUNICATION_DELAY,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Communication Delay',
                              range=(0, 3),
                              description='The number of delays to add when processing commands: (0-3)',
                              default_value=0)
        self._add_setup_param(Parameter.PRECISION,
                              int,
                              type=ParameterDictType.INT,
                              display_name='Precision',
                              range={'4 digits': 0, '5 digits': 1, '6 digits': 2, '7 digits': 3},
                              description='Number of digits the instrument should output for temperature query: '
                                          '(0=4-3=7)',
                              default_value=6)
        self._add_setup_param(Parameter.LARGE_SIGNAL_FILTER_C,
                              float,
                              type=ParameterDictType.FLOAT,
                              display_name='Large Signal Filter Constant',
                              range={'0': 0, '.25': 1, '.5': 2, '1': 3, '2': 4, '4': 5, '8': 6, '16': 7},
                              description='Time to reach 63% of its final value: '
                                          '(0 = 0.0, 1 = 0.25, 2 = 0.5, 3 = 1.0, 4 = 2.0, 5 = 4.0, 6 = 8.0, 7 = 16.0)',
                              default_value=0.0,
                              units=Units.SECOND)
        self._add_setup_param(Parameter.SMALL_SIGNAL_FILTER_C,
                              float,
                              type=ParameterDictType.FLOAT,
                              display_name='Small Signal Filter Constant',
                              range={'0': 0, '.25': 1, '.5': 2, '1': 3, '2': 4, '4': 5, '8': 6, '16': 7},
                              description='Smaller filter constant, should be larger than large filter constant: '
                                          '(0 = 0.0, 1 = 0.25, 2 = 0.5, 3 = 1.0, 4 = 2.0, 5 = 4.0, 6 = 8.0, 7 = 16.0)',
                              default_value=0.50,
                              units=Units.SECOND)

        for key in self._param_dict.get_keys():
            self._param_dict.set_default(key)
예제 #46
0
class Protocol(WorkhorseProtocol):
    def __init__(self, prompts, newline, driver_event, connections=None):
        """
        Constructor.
        @param prompts Enum class containing possible device prompts used for
        command response logic.
        @param newline The device newline.
        @driver_event The callback for asynchronous driver events.
        """
        if not type(connections) is list:
            raise InstrumentProtocolException(
                'Unable to instantiate multi connection protocol without connection list'
            )
        self._param_dict2 = ProtocolParameterDict()

        # Construct superclass.
        WorkhorseProtocol.__init__(self, prompts, newline, driver_event)

        # Create multiple connection versions of the pieces of protocol involving data to/from the instrument
        self._linebuf = {connection: '' for connection in connections}
        self._promptbuf = {connection: '' for connection in connections}
        self._last_data_timestamp = {
            connection: None
            for connection in connections
        }
        self.connections = {connection: None for connection in connections}
        self.chunkers = {
            connection: StringChunker(self.sieve_function)
            for connection in connections
        }

    def _get_response(self,
                      timeout=10,
                      expected_prompt=None,
                      response_regex=None,
                      connection=None):
        """
        Overridden to handle multiple port agent connections
        """
        if connection is None:
            raise InstrumentProtocolException(
                '_get_response: no connection supplied!')
        # Grab time for timeout and wait for prompt.
        end_time = time.time() + timeout

        if response_regex and not isinstance(response_regex, RE_PATTERN):
            raise InstrumentProtocolException(
                'Response regex is not a compiled pattern!')

        if expected_prompt and response_regex:
            raise InstrumentProtocolException(
                'Cannot supply both regex and expected prompt!')

        if expected_prompt is None:
            prompt_list = self._get_prompts()
        else:
            if isinstance(expected_prompt, str):
                prompt_list = [expected_prompt]
            else:
                prompt_list = expected_prompt

        if response_regex is None:
            pattern = None
        else:
            pattern = response_regex.pattern

        log.debug(
            '_get_response: timeout=%s, prompt_list=%s, expected_prompt=%r, response_regex=%r, promptbuf=%r',
            timeout, prompt_list, expected_prompt, pattern, self._promptbuf)
        while time.time() < end_time:
            if response_regex:
                # noinspection PyArgumentList
                match = response_regex.search(self._linebuf[connection])
                if match:
                    return match.groups()
            else:
                for item in prompt_list:
                    index = self._promptbuf[connection].find(item)
                    if index >= 0:
                        result = self._promptbuf[connection][0:index +
                                                             len(item)]
                        return item, result

            time.sleep(.1)

        raise InstrumentTimeoutException(
            "in InstrumentProtocol._get_response()")

    def _do_cmd_resp(self, cmd, *args, **kwargs):
        """
        Overridden to handle multiple port agent connections
        """
        connection = kwargs.get('connection')
        if connection is None:
            raise InstrumentProtocolException(
                '_do_cmd_resp: no connection supplied!')

        # Get timeout and initialize response.
        timeout = kwargs.get('timeout', DEFAULT_CMD_TIMEOUT)
        expected_prompt = kwargs.get('expected_prompt', None)
        response_regex = kwargs.get('response_regex', None)

        if response_regex and not isinstance(response_regex, RE_PATTERN):
            raise InstrumentProtocolException(
                'Response regex is not a compiled pattern!')

        if expected_prompt and response_regex:
            raise InstrumentProtocolException(
                'Cannot supply both regex and expected prompt!')

        self._do_cmd_no_resp(cmd, *args, **kwargs)

        # Wait for the prompt, prepare result and return, timeout exception
        if response_regex:
            prompt = ""
            result_tuple = self._get_response(timeout,
                                              connection=connection,
                                              response_regex=response_regex,
                                              expected_prompt=expected_prompt)
            result = "".join(result_tuple)
        else:
            (prompt,
             result) = self._get_response(timeout,
                                          connection=connection,
                                          expected_prompt=expected_prompt)

        resp_handler = self._response_handlers.get(
            (self.get_current_state(), cmd),
            self._response_handlers.get(cmd, None))
        resp_result = None
        if callable(resp_handler):
            resp_result = resp_handler(result, prompt)

        return resp_result

    def _send_data(self, data, write_delay=0, connection=None):
        if connection is None:
            raise InstrumentProtocolException(
                '_send_data: no connection supplied!')

        if write_delay == 0:
            self.connections[connection].send(data)
        else:
            for char in data:
                self.connections[connection].send(char)
                time.sleep(write_delay)

    def _do_cmd_no_resp(self, cmd, *args, **kwargs):
        """
        Overridden to handle multiple port agent connections
        """
        connection = kwargs.get('connection')
        if connection is None:
            raise InstrumentProtocolException(
                '_do_cmd_no_resp: no connection supplied! %r %r %r' %
                (cmd, args, kwargs))

        timeout = kwargs.get('timeout', DEFAULT_CMD_TIMEOUT)
        write_delay = kwargs.get('write_delay', DEFAULT_WRITE_DELAY)

        build_handler = self._build_handlers.get(cmd, None)
        if not callable(build_handler):
            log.error('_do_cmd_no_resp: no handler for command: %s' % cmd)
            raise InstrumentProtocolException(
                error_code=InstErrorCode.BAD_DRIVER_COMMAND)
        cmd_line = build_handler(cmd, *args)

        # Wakeup the device, timeout exception as needed
        self._wakeup(timeout, connection=connection)

        # Clear line and prompt buffers for result, then send command.
        self._linebuf[connection] = ''
        self._promptbuf[connection] = ''
        self._send_data(cmd_line, write_delay, connection=connection)

    def _do_cmd_direct(self, cmd, connection=None):
        """
        Issue an untranslated command to the instrument. No response is handled
        as a result of the command.

        @param cmd The high level command to issue
        """
        # Send command.
        self._send_data(cmd, connection=connection)

    ########################################################################
    # Incoming data (for parsing) callback.
    ########################################################################
    def got_data(self, port_agent_packet, connection=None):
        """
        Called by the instrument connection when data is available.
        Append line and prompt buffers.

        Also add data to the chunker and when received call got_chunk
        to publish results.
        :param connection: connection which produced this packet
        :param port_agent_packet: packet of data
        """
        if connection is None:
            raise InstrumentProtocolException(
                'got_data: no connection supplied!')

        data_length = port_agent_packet.get_data_length()
        data = port_agent_packet.get_data()
        timestamp = port_agent_packet.get_timestamp()

        log.debug("Got Data: %r %r", connection, data)
        log.debug("Add Port Agent Timestamp: %r %s", connection, timestamp)

        if data_length > 0:
            if self.get_current_state() == DriverProtocolState.DIRECT_ACCESS:
                self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, data)

            self.add_to_buffer(data, connection=connection)

            self.chunkers[connection].add_chunk(data, timestamp)
            (timestamp, chunk) = self.chunkers[connection].get_next_data()
            while chunk:
                self._got_chunk(chunk, timestamp, connection=connection)
                (timestamp, chunk) = self.chunkers[connection].get_next_data()

    ########################################################################
    # Incoming raw data callback.
    ########################################################################
    def got_raw(self, port_agent_packet, connection=None):
        """
        Called by the port agent client when raw data is available, such as data
        sent by the driver to the instrument, the instrument responses,etc.
        :param connection: connection which produced this packet
        :param port_agent_packet: packet of data
        """
        self.publish_raw(port_agent_packet, connection)

    def publish_raw(self, port_agent_packet, connection=None):
        pass

    def add_to_buffer(self, data, connection=None):
        """
        Add a chunk of data to the internal data buffers
        buffers implemented as lifo ring buffer
        :param data: bytes to add to the buffer
        :param connection: connection which produced this packet
        """
        # Update the line and prompt buffers.
        self._linebuf[connection] += data
        self._promptbuf[connection] += data
        self._last_data_timestamp[connection] = time.time()

        # If our buffer exceeds the max allowable size then drop the leading
        # characters on the floor.
        if len(self._linebuf[connection]) > self._max_buffer_size():
            self._linebuf[connection] = self._linebuf[connection][
                self._max_buffer_size() * -1:]

        # If our buffer exceeds the max allowable size then drop the leading
        # characters on the floor.
        if len(self._promptbuf[connection]) > self._max_buffer_size():
            self._promptbuf[connection] = self._promptbuf[connection][
                self._max_buffer_size() * -1:]

        log.debug("LINE BUF: %r", self._linebuf[connection][-50:])
        log.debug("PROMPT BUF: %r", self._promptbuf[connection][-50:])

    ########################################################################
    # Wakeup helpers.
    ########################################################################

    def _send_wakeup(self, connection=None):
        """
        Send a wakeup to the device. Overridden by device specific
        subclasses.
        """
        self.connections[connection].send(NEWLINE)

    def _wakeup(self, timeout, delay=1, connection=None):
        """
        Clear buffers and send a wakeup command to the instrument
        @param timeout The timeout to wake the device.
        @param delay The time to wait between consecutive wakeups.
        @throw InstrumentTimeoutException if the device could not be woken.
        """
        if connection is None:
            raise InstrumentProtocolException(
                '_wakeup: no connection supplied!')

        # Clear the prompt buffer.
        log.trace("clearing promptbuf: %r", self._promptbuf)
        self._promptbuf[connection] = ''

        # Grab time for timeout.
        starttime = time.time()

        while True:
            # Send a line return and wait a sec.
            log.trace('Sending wakeup. timeout=%s', timeout)
            self._send_wakeup(connection=connection)
            time.sleep(delay)

            log.trace("Prompts: %s", self._get_prompts())

            for item in self._get_prompts():
                log.trace("buffer: %r", self._promptbuf[connection])
                log.trace("find prompt: %r", item)
                index = self._promptbuf[connection].find(item)
                log.trace("Got prompt (index: %s): %r ", index,
                          self._promptbuf[connection])
                if index >= 0:
                    log.trace('wakeup got prompt: %r', item)
                    return item
            log.trace("Searched for all prompts")

            if time.time() > starttime + timeout:
                raise InstrumentTimeoutException("in _wakeup()")

    def _build_param_dict(self):
        # We're going to build two complete sets of ADCP parameters here
        # one set for the master instrument and one for the slave
        for param in parameter_regexes:
            self._param_dict.add(
                param,
                parameter_regexes.get(param),
                parameter_extractors.get(param),
                parameter_formatters.get(param),
                type=parameter_types.get(param),
                display_name=parameter_names.get(param),
                value_description=parameter_descriptions.get(param),
                startup_param=parameter_startup.get(param, False),
                direct_access=parameter_direct.get(param, False),
                visibility=parameter_visibility.get(
                    param, ParameterDictVisibility.READ_WRITE),
                default_value=master_parameter_defaults.get(param),
                units=parameter_units.get(param))

        for param in parameter_regexes:
            # Scheduled events are handled by the master
            if WorkhorseEngineeringParameter.has(param):
                continue
            self._param_dict.add(
                param + '_5th',
                r'DONTMATCHMEIMNOTREAL!',
                parameter_extractors.get(param),
                parameter_formatters.get(param),
                type=parameter_types.get(param),
                display_name=parameter_names.get(param),
                value_description=parameter_descriptions.get(param),
                startup_param=parameter_startup.get(param, False),
                direct_access=parameter_direct.get(param, False),
                visibility=parameter_visibility.get(
                    param, ParameterDictVisibility.READ_WRITE),
                default_value=slave_parameter_defaults.get(param),
                units=parameter_units.get(param))

        self._param_dict.set_default(WorkhorseParameter.CLOCK_SYNCH_INTERVAL)
        self._param_dict.set_default(WorkhorseParameter.GET_STATUS_INTERVAL)

        # now we're going to build a whole 'nother param dict for the slave parameters
        # that contain regex values so we can fill them in easily...
        for param in parameter_regexes:
            # Scheduled events are handled by the master
            if WorkhorseEngineeringParameter.has(param):
                continue
            self._param_dict2.add(param + '_5th', parameter_regexes.get(param),
                                  parameter_extractors.get(param),
                                  parameter_formatters.get(param))

    # #######################################################################
    # Private helpers.
    # #######################################################################
    def _got_chunk(self, chunk, timestamp, connection=None):
        """
        The base class got_data has gotten a chunk from the chunker.
        Pass it to extract_sample with the appropriate particle
        objects and REGEXes.
        """
        if ADCP_PD0_PARSED_REGEX_MATCHER.match(chunk):
            pd0 = AdcpPd0Record(chunk)
            transform = pd0.coord_transform.coord_transform

            # Only BEAM transform supported for VADCP
            if transform != particles.Pd0CoordinateTransformType.BEAM:
                raise SampleException(
                    'Received unsupported coordinate transform type: %s' %
                    transform)

            if connection == SlaveProtocol.FOURBEAM:
                science = particles.VadcpBeamMasterParticle(
                    pd0, port_timestamp=timestamp).generate()
                config = particles.AdcpPd0ConfigParticle(
                    pd0, port_timestamp=timestamp).generate()
                engineering = particles.AdcpPd0EngineeringParticle(
                    pd0, port_timestamp=timestamp).generate()
            else:
                science = particles.VadcpBeamSlaveParticle(
                    pd0, port_timestamp=timestamp).generate()
                config = particles.VadcpConfigSlaveParticle(
                    pd0, port_timestamp=timestamp).generate()
                engineering = particles.VadcpEngineeringSlaveParticle(
                    pd0, port_timestamp=timestamp).generate()

            out_particles = [science]
            for particle in [config, engineering]:
                if self._changed(particle):
                    out_particles.append(particle)

            for particle in out_particles:
                self._driver_event(DriverAsyncEvent.SAMPLE, particle)

        else:
            if connection == SlaveProtocol.FIFTHBEAM:
                if self._extract_sample(
                        particles.VadcpCompassCalibrationDataParticle,
                        ADCP_COMPASS_CALIBRATION_REGEX_MATCHER, chunk,
                        timestamp):
                    return

                if self._extract_sample(
                        particles.VadcpSystemConfigurationDataParticle,
                        ADCP_SYSTEM_CONFIGURATION_REGEX_MATCHER, chunk,
                        timestamp):
                    return

                if self._extract_sample(
                        particles.VadcpAncillarySystemDataParticle,
                        ADCP_ANCILLARY_SYSTEM_DATA_REGEX_MATCHER, chunk,
                        timestamp):
                    return

                if self._extract_sample(particles.VadcpTransmitPathParticle,
                                        ADCP_TRANSMIT_PATH_REGEX_MATCHER,
                                        chunk, timestamp):
                    return

            elif connection == SlaveProtocol.FOURBEAM:
                if self._extract_sample(
                        particles.AdcpCompassCalibrationDataParticle,
                        ADCP_COMPASS_CALIBRATION_REGEX_MATCHER, chunk,
                        timestamp):
                    return

                if self._extract_sample(
                        particles.AdcpSystemConfigurationDataParticle,
                        ADCP_SYSTEM_CONFIGURATION_REGEX_MATCHER, chunk,
                        timestamp):
                    return

                if self._extract_sample(
                        particles.AdcpAncillarySystemDataParticle,
                        ADCP_ANCILLARY_SYSTEM_DATA_REGEX_MATCHER, chunk,
                        timestamp):
                    return

                if self._extract_sample(particles.AdcpTransmitPathParticle,
                                        ADCP_TRANSMIT_PATH_REGEX_MATCHER,
                                        chunk, timestamp):
                    return

    def _send_break_cmd(self, delay, connection=None):
        """
        Send a BREAK to attempt to wake the device.
        """
        self.connections[connection].send_break(delay)

    def _sync_clock(self,
                    command,
                    date_time_param,
                    timeout=TIMEOUT,
                    delay=1,
                    time_format="%Y/%m/%d,%H:%M:%S"):
        """
        Send the command to the instrument to synchronize the clock
        @param command set command
        @param date_time_param: date time parameter that we want to set
        @param timeout: command timeout
        @param delay: wakeup delay
        @param time_format: time format string for set command
        """
        log.info("SYNCING TIME WITH SENSOR.")
        for connection in self.connections:
            self._do_cmd_resp(command,
                              date_time_param,
                              get_timestamp_delayed("%Y/%m/%d, %H:%M:%S"),
                              timeout=timeout,
                              connection=connection)

    # #######################################################################
    # Startup parameter handlers
    ########################################################################

    def _get_params(self, parameters, connection):
        command = NEWLINE.join(['%s?' % p for p in parameters]) + NEWLINE

        if len(parameters) > 1:
            regex = re.compile(
                r'(%s.*?%s.*?>)' % (parameters[0], parameters[-1]), re.DOTALL)
        else:
            regex = re.compile(r'(%s.*?>)' % parameters[0], re.DOTALL)

        self._linebuf[connection] = ''
        self._promptbuf[connection] = ''
        self._do_cmd_direct(command, connection=connection)
        return self._get_response(response_regex=regex, connection=connection)

    def _update_params(self, *args, **kwargs):
        """
        Update the parameter dictionary.
        """
        # see if we passed in a list of parameters to query
        # if not, use the whole parameter list
        parameters = kwargs.get('params')
        if parameters is None or WorkhorseParameter.ALL in parameters:
            parameters = self._param_dict.get_keys()
        # filter out the engineering parameters and ALL
        parameters = [
            p for p in parameters if not WorkhorseEngineeringParameter.has(p)
            and p != WorkhorseParameter.ALL
        ]

        # Get old param dict config.
        old_config = self._param_dict.get_config()

        if parameters:
            # MASTER
            master_params = [p for p in parameters if '_5th' not in p]
            if master_params:
                resp = self._get_params(master_params, SlaveProtocol.FOURBEAM)
                self._param_dict.update_many(resp)

            # SLAVE
            slave_params = [
                p.replace('_5th', '') for p in parameters if '_5th' in p
            ]
            if slave_params:
                resp = self._get_params(slave_params, SlaveProtocol.FIFTHBEAM)
                self._param_dict2.update_many(resp)
                for key, value in self._param_dict2.get_all().iteritems():
                    self._param_dict.set_value(key, value)

        new_config = self._param_dict.get_config()

        # Check if there is any changes. Ignore TT
        if not dict_equal(new_config, old_config,
                          ['TT']) or kwargs.get('force'):
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def _execute_set_params(self, commands, connection):
        if commands:
            # we are going to send the concatenation of all our set commands
            self._linebuf[connection] = ''
            self._do_cmd_direct(''.join(commands), connection=connection)
            # we'll need to build a regular expression to retrieve all of the responses
            # including any possible errors
            if len(commands) == 1:
                regex = re.compile(r'(%s.*?)\r\n>' % commands[-1].strip(),
                                   re.DOTALL)
            else:
                regex = re.compile(
                    r'(%s.*?%s.*?)\r\n>' %
                    (commands[0].strip(), commands[-1].strip()), re.DOTALL)
            response = self._get_response(response_regex=regex,
                                          connection=connection)
            self._parse_set_response(response[0], None)

    def _set_params(self, *args, **kwargs):
        """
        Issue commands to the instrument to set various parameters
        """
        self._verify_not_readonly(*args, **kwargs)
        params = args[0]
        changed = []

        old_config = self._param_dict.get_config()

        master_commands = []
        slave_commands = []
        for key, val in params.iteritems():
            if WorkhorseEngineeringParameter.has(key):
                continue
            if val != old_config.get(key):
                changed.append(key)
                if '_5th' in key:
                    slave_commands.append(
                        self._build_set_command(WorkhorseInstrumentCmds.SET,
                                                key.replace('_5th', ''), val))
                else:
                    master_commands.append(
                        self._build_set_command(WorkhorseInstrumentCmds.SET,
                                                key, val))

        self._execute_set_params(master_commands,
                                 connection=SlaveProtocol.FOURBEAM)
        self._execute_set_params(slave_commands,
                                 connection=SlaveProtocol.FIFTHBEAM)

        # Handle engineering parameters
        force = False

        if WorkhorseParameter.CLOCK_SYNCH_INTERVAL in params:
            if (params[WorkhorseParameter.CLOCK_SYNCH_INTERVAL] !=
                    self._param_dict.get(
                        WorkhorseParameter.CLOCK_SYNCH_INTERVAL)):
                self._param_dict.set_value(
                    WorkhorseParameter.CLOCK_SYNCH_INTERVAL,
                    params[WorkhorseParameter.CLOCK_SYNCH_INTERVAL])
                self.start_scheduled_job(
                    WorkhorseParameter.CLOCK_SYNCH_INTERVAL,
                    WorkhorseScheduledJob.CLOCK_SYNC,
                    WorkhorseProtocolEvent.SCHEDULED_CLOCK_SYNC)
                force = True

        if WorkhorseParameter.GET_STATUS_INTERVAL in params:
            if (params[WorkhorseParameter.GET_STATUS_INTERVAL] !=
                    self._param_dict.get(
                        WorkhorseParameter.GET_STATUS_INTERVAL)):
                self._param_dict.set_value(
                    WorkhorseParameter.GET_STATUS_INTERVAL,
                    params[WorkhorseParameter.GET_STATUS_INTERVAL])
                self.start_scheduled_job(
                    WorkhorseParameter.GET_STATUS_INTERVAL,
                    WorkhorseScheduledJob.GET_CONFIGURATION,
                    WorkhorseProtocolEvent.SCHEDULED_GET_STATUS)
                force = True

        self._update_params(params=changed, force=force)
        return None

    def _send_break(self, duration=1000, connection=None):
        """
        Send a BREAK to attempt to wake the device.
        """
        self._linebuf[connection] = ''
        self._promptbuf[connection] = ''
        self._send_break_cmd(duration, connection=connection)
        self._get_response(expected_prompt=WorkhorsePrompt.BREAK,
                           connection=connection)

    def _start_logging(self, timeout=TIMEOUT, connection=None):
        """
        Command the instrument to start logging
        @param timeout: how long to wait for a prompt
        @throws: InstrumentProtocolException if failed to start logging
        """
        try:
            start = WorkhorseInstrumentCmds.START_LOGGING
            # start the slave first, it collects on signal from master
            self._do_cmd_resp(start,
                              timeout=timeout,
                              connection=SlaveProtocol.FIFTHBEAM)
            self._do_cmd_resp(start,
                              timeout=timeout,
                              connection=SlaveProtocol.FOURBEAM)
        except InstrumentException:
            self._stop_logging()
            raise

    def _stop_logging(self):
        # stop the master first (slave only collects on signal from master)
        self._send_break(connection=SlaveProtocol.FOURBEAM)
        self._send_break(connection=SlaveProtocol.FIFTHBEAM)

    def _discover(self, connection=None):
        """
        Discover current state; can be COMMAND or AUTOSAMPLE or UNKNOWN.
        @return (next_protocol_state, next_agent_state)
        @throws InstrumentTimeoutException if the device cannot be woken.
        @throws InstrumentStateException if the device response does not correspond to
        an expected state.
        """
        states = set()
        command = (WorkhorseProtocolState.COMMAND, ResourceAgentState.COMMAND)
        auto = (WorkhorseProtocolState.AUTOSAMPLE,
                ResourceAgentState.STREAMING)
        for connection in self.connections:
            try:
                self._wakeup(3, connection=connection)
                states.add(command)
            except InstrumentException:
                states.add(auto)

        if len(states) == 1:
            # states match, return this state
            return states.pop()

        # states don't match
        self._stop_logging()
        return command

    def _run_test(self, *args, **kwargs):
        kwargs['timeout'] = 30
        kwargs['expected_prompt'] = WorkhorsePrompt.COMMAND
        result = []
        for connection in self.connections:
            result.append(connection)
            kwargs['connection'] = connection
            result.append(
                self._do_cmd_resp(WorkhorseInstrumentCmds.RUN_TEST_200, *args,
                                  **kwargs))
        return NEWLINE.join(result)

    @contextmanager
    def _pause_logging(self):
        try:
            self._stop_logging()
            yield
        finally:
            self._start_logging()

    ########################################################################
    # COMMAND handlers.
    ########################################################################

    def _handler_command_acquire_status(self, *args, **kwargs):
        """
        execute a get status
        @return next_state, (next_agent_state, result) if successful.
        @throws InstrumentProtocolException from _do_cmd_resp.
        """
        a = super(Protocol, self)._handler_command_acquire_status(
            connection=SlaveProtocol.FOURBEAM)
        b = super(Protocol, self)._handler_command_acquire_status(
            connection=SlaveProtocol.FIFTHBEAM)
        return None, (None, a[1][1] + b[1][1])

    def _handler_command_recover_autosample(self):
        log.info(
            'PD0 sample detected in COMMAND, not allowed in VADCP. Sending break'
        )
        self._stop_logging()

    ######################################################
    # DIRECT_ACCESS handlers
    ######################################################

    def _handler_direct_access_execute_direct(self, data):
        next_state = None
        result = None
        next_agent_state = None
        self._do_cmd_direct(data)

        # add sent command to list for 'echo' filtering in callback
        self._sent_cmds.append(data)
        return next_state, (next_agent_state, result)
예제 #47
0
    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.LAUNCH_TIME, CONFIGURATION_REGEX,
                             lambda match: int(match.group(1), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='launch time')

        self._param_dict.add(Parameter.START_TIME_FROM_LAUNCH, CONFIGURATION_REGEX,
                             lambda match: int(match.group(2), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02C7EA00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='start time after launch time')

        self._param_dict.add(Parameter.STOP_TIME_FROM_START, CONFIGURATION_REGEX,
                             lambda match: int(match.group(3), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01E13380,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='stop time after start time')

        self._param_dict.add(Parameter.MODE_BITS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(4), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0A,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='mode bits (set to 00001010)')

        self._param_dict.add(Parameter.SAMI_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(5), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami sample interval')

        self._param_dict.add(Parameter.SAMI_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(6), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x04,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami driver version')

        self._param_dict.add(Parameter.SAMI_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(7), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami parameter pointer')

        self._param_dict.add(Parameter.DEVICE1_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(8), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 sample interval')

        self._param_dict.add(Parameter.DEVICE1_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(9), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 driver version')

        self._param_dict.add(Parameter.DEVICE1_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(10), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0B,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 parameter pointer')

        self._param_dict.add(Parameter.DEVICE2_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(11), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 sample interval')

        self._param_dict.add(Parameter.DEVICE2_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(12), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 driver version')

        self._param_dict.add(Parameter.DEVICE2_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(13), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 parameter pointer')

        self._param_dict.add(Parameter.DEVICE3_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(14), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 sample interval')

        self._param_dict.add(Parameter.DEVICE3_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(15), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 driver version')

        self._param_dict.add(Parameter.DEVICE3_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(16), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 parameter pointer')

        self._param_dict.add(Parameter.PRESTART_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(17), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart sample interval')

        self._param_dict.add(Parameter.PRESTART_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(18), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart driver version')

        self._param_dict.add(Parameter.PRESTART_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(19), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart parameter pointer')

        self._param_dict.add(Parameter.GLOBAL_CONFIGURATION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(20), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='global bits (set to 00000111)')

        self._param_dict.add(Parameter.NUMBER_SAMPLES_AVERAGED, CONFIGURATION_REGEX,
                             lambda match: int(match.group(21), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_FLUSHES, CONFIGURATION_REGEX,
                             lambda match: int(match.group(22), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_ON_FLUSH, CONFIGURATION_REGEX,
                             lambda match: int(match.group(23), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_OFF_FLUSH, CONFIGURATION_REGEX,
                             lambda match: int(match.group(24), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_REAGENT_PUMPS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(25), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.VALVE_DELAY, CONFIGURATION_REGEX,
                             lambda match: int(match.group(26), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_ON_IND, CONFIGURATION_REGEX,
                             lambda match: int(match.group(27), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PV_OFF_IND, CONFIGURATION_REGEX,
                             lambda match: int(match.group(28), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_BLANKS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(29), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_MEASURE_T, CONFIGURATION_REGEX,
                             lambda match: int(match.group(30), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_OFF_TO_MEASURE, CONFIGURATION_REGEX,
                             lambda match: int(match.group(31), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.MEASURE_TO_PUMP_ON, CONFIGURATION_REGEX,
                             lambda match: int(match.group(32), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_MEASUREMENTS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(33), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.SALINITY_DELAY, CONFIGURATION_REGEX,
                             lambda match: int(match.group(34), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')
예제 #48
0
class Protocol(McLaneProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        McLaneProtocol.__init__(self, prompts, newline, driver_event)

        # TODO - reset next_port on mechanical refresh of the PPS filters - how is the driver notified?
        # TODO - need to persist state for next_port to save driver restart
        self.next_port = 1  # next available port

        # get the following:
        # - VERSION
        # - CAPACITY (pump flow)
        # - BATTERY
        # - CODES (termination codes)
        # - COPYRIGHT (termination codes)

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        filling = self.get_current_state() in [
            ProtocolState.FILL, ProtocolState.UNKNOWN
        ]
        log.debug("_got_chunk:\n%s", chunk)
        sample_dict = self._extract_sample(
            PPSDNSampleDataParticle,
            PPSDNSampleDataParticle.regex_compiled(),
            chunk,
            timestamp,
            publish=filling)

        if sample_dict and self.get_current_state() != ProtocolState.UNKNOWN:
            self._linebuf = ''
            self._promptbuf = ''
            self._protocol_fsm.on_event(
                ProtocolEvent.PUMP_STATUS,
                PPSDNSampleDataParticle.regex_compiled().search(chunk))

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(
            Parameter.FLUSH_VOLUME,
            r'Flush Volume: (.*)mL',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FLUSH_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Flush Volume",
            description=
            "Amount of sea water to flush prior to taking sample: (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FLUSH_FLOWRATE,
                             r'Flush Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FLUSH_RATE,
                             units=Prefixes.MILLI + Units.LITER + '/' +
                             Units.MINUTE,
                             startup_param=True,
                             display_name="Flush Flow Rate",
                             description="Rate at which to flush: (100 - 250)",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.FLUSH_MINFLOW,
            r'Flush Min Flow: (.*)mL/min',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FLUSH_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + '/' + Units.MINUTE,
            startup_param=True,
            display_name="Flush Minimum Flow",
            description=
            "If the flow rate falls below this value the flush will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.FILL_VOLUME,
            r'Fill Volume: (.*)mL',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Fill Volume",
            description=
            "Amount of seawater to run through the collection filter (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.FILL_FLOWRATE,
            r'Fill Flow Rate: (.*)mL/min',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_RATE,
            units=Prefixes.MILLI + Units.LITER + '/' + Units.MINUTE,
            startup_param=True,
            display_name="Fill Flow Rate",
            description="Flow rate during sampling: (100 - 250)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.FILL_MINFLOW,
            r'Fill Min Flow: (.*)mL/min',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + '/' + Units.MINUTE,
            startup_param=True,
            display_name="Fill Minimum Flow",
            description=
            "If the flow rate falls below this value the fill will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.CLEAR_VOLUME,
            r'Reverse Volume: (.*)mL',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=CLEAR_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Clear Volume",
            description=
            "Amount of sea water to flush the home port after taking sample: (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_FLOWRATE,
                             r'Reverse Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=CLEAR_RATE,
                             units=Prefixes.MILLI + Units.LITER + '/' +
                             Units.MINUTE,
                             startup_param=True,
                             display_name="Clear Flow Rate",
                             description="Rate at which to flush: (100 - 250)",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.CLEAR_MINFLOW,
            r'Reverse Min Flow: (.*)mL/min',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=CLEAR_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + '/' + Units.MINUTE,
            startup_param=True,
            display_name="Clear Minimum Flow",
            description=
            "If the flow rate falls below this value the reverse flush will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE)

        self._param_dict.set_value(Parameter.FLUSH_VOLUME, FLUSH_VOLUME)
        self._param_dict.set_value(Parameter.FLUSH_FLOWRATE, FLUSH_RATE)
        self._param_dict.set_value(Parameter.FLUSH_MINFLOW, FLUSH_MIN_RATE)
        self._param_dict.set_value(Parameter.FILL_VOLUME, FILL_VOLUME)
        self._param_dict.set_value(Parameter.FILL_FLOWRATE, FILL_RATE)
        self._param_dict.set_value(Parameter.FILL_MINFLOW, FILL_MIN_RATE)
        self._param_dict.set_value(Parameter.CLEAR_VOLUME, CLEAR_VOLUME)
        self._param_dict.set_value(Parameter.CLEAR_FLOWRATE, CLEAR_RATE)
        self._param_dict.set_value(Parameter.CLEAR_MINFLOW, CLEAR_MIN_RATE)
예제 #49
0
class McLaneProtocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    # __metaclass__ = get_logging_metaclass(log_level='debug')

    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT)

        # Add event handlers for protocol state machine.
        handlers = {
            ProtocolState.UNKNOWN: [
                (ProtocolEvent.ENTER, self._handler_unknown_enter),
                (ProtocolEvent.DISCOVER, self._handler_unknown_discover),
            ],
            ProtocolState.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_command_enter),
                (ProtocolEvent.INIT_PARAMS, self._handler_command_init_params),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.CLOCK_SYNC, self._handler_sync_clock),
                (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_acquire),
                # (ProtocolEvent.ACQUIRE_STATUS, self._handler_command_status),
                (ProtocolEvent.CLEAR, self._handler_command_clear),
                (ProtocolEvent.GET, self._handler_get),
                (ProtocolEvent.SET, self._handler_command_set),
            ],
            ProtocolState.FLUSH: [
                (ProtocolEvent.ENTER, self._handler_flush_enter),
                (ProtocolEvent.FLUSH, self._handler_flush_flush),
                (ProtocolEvent.PUMP_STATUS, self._handler_flush_pump_status),
                (ProtocolEvent.INSTRUMENT_FAILURE, self._handler_all_failure),
            ],
            ProtocolState.FILL: [
                (ProtocolEvent.ENTER, self._handler_fill_enter),
                (ProtocolEvent.FILL, self._handler_fill_fill),
                (ProtocolEvent.PUMP_STATUS, self._handler_fill_pump_status),
                (ProtocolEvent.INSTRUMENT_FAILURE, self._handler_all_failure),
            ],
            ProtocolState.CLEAR: [
                (ProtocolEvent.ENTER, self._handler_clear_enter),
                (ProtocolEvent.CLEAR, self._handler_clear_clear),
                (ProtocolEvent.PUMP_STATUS, self._handler_clear_pump_status),
                (ProtocolEvent.INSTRUMENT_FAILURE, self._handler_all_failure),
            ],
            ProtocolState.RECOVERY: [
                (ProtocolEvent.ENTER, self._handler_recovery_enter),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
            ],
        }

        for state in handlers:
            for event, handler in handlers[state]:
                self._protocol_fsm.add_handler(state, event, handler)

        # Add build handlers for device commands - we are only using simple commands
        for cmd in McLaneCommand.list():
            self._add_build_handler(cmd, self._build_command)

        # Add response handlers for device commands.
        # self._add_response_handler(McLaneCommand.BATTERY, self._parse_battery_response)
        # self._add_response_handler(McLaneCommand.CLOCK, self._parse_clock_response)
        # self._add_response_handler(McLaneCommand.PORT, self._parse_port_response)

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

        self._chunker = StringChunker(McLaneProtocol.sieve_function)

        self._add_scheduler_event(ScheduledJob.CLOCK_SYNC, ProtocolEvent.CLOCK_SYNC)

        # Start state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)
        self._sent_cmds = None

        # TODO - reset next_port on mechanical refresh of the PPS filters - how is the driver notified?
        # TODO - need to persist state for next_port to save driver restart
        self.next_port = 1  # next available port

        self._second_attempt = False

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples and status
        :param raw_data: raw instrument data
        """
        matchers = []
        return_list = []

        matchers.append(McLaneSampleDataParticle.regex_compiled())

        for matcher in matchers:
            for match in matcher.finditer(raw_data):
                return_list.append((match.start(), match.end()))

        return return_list

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        """
        return [x for x in events if Capability.has(x)]

    ########################################################################
    # implement virtual methods from base class.
    ########################################################################

    def _set_params(self, *args, **kwargs):
        """
        Issue commands to the instrument to set various parameters.  If
        startup is set to true that means we are setting startup values
        and immutable parameters can be set.  Otherwise only READ_WRITE
        parameters can be set.

        must be overloaded in derived classes

        @param params dictionary containing parameter name and value pairs
        @param startup flag - true indicates initializing, false otherwise
        """

        params = args[0]

        # check for attempt to set readonly parameters (read-only or immutable set outside startup)
        self._verify_not_readonly(*args, **kwargs)
        old_config = self._param_dict.get_config()

        for (key, val) in params.iteritems():
            log.debug("KEY = " + str(key) + " VALUE = " + str(val))
            self._param_dict.set_value(key, val)

        new_config = self._param_dict.get_config()
        log.debug('new config: %s\nold config: %s', new_config, old_config)
        # check for parameter change
        if not dict_equal(old_config, new_config):
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def apply_startup_params(self):
        """
        Apply startup parameters
        """

        # fn = "apply_startup_params"
        # config = self.get_startup_config()
        # log.debug("%s: startup config = %s", fn, config)
        #
        # for param in Parameter.list():
        #     if param in config:
        #         self._param_dict.set_value(param, config[param])
        #
        # log.debug("%s: new parameters", fn)
        # for x in config:
        #     log.debug("  parameter %s: %s", x, config[x])
        if self.get_current_state() != DriverProtocolState.COMMAND:
            raise InstrumentProtocolException('cannot set parameters outside command state')

        self._set_params(self.get_startup_config(), True)

    ########################################################################
    # Instrument commands.
    ########################################################################

    def _do_cmd_resp(self, cmd, *args, **kwargs):
        """
        Perform a command-response on the device. Overrides the base class so it will
        return the regular expression groups without concatenating them into a string.
        @param cmd The command to execute.
        @param args positional arguments to pass to the build handler.
        @param write_delay kwarg for the amount of delay in seconds to pause
        between each character. If none supplied, the DEFAULT_WRITE_DELAY
        value will be used.
        @param timeout optional wakeup and command timeout via kwargs.
        @param response_regex kwarg with a compiled regex for the response to
        match. Groups that match will be returned as a tuple.
        @retval response The parsed response result.
        @raises InstrumentTimeoutException if the response did not occur in time.
        @raises InstrumentProtocolException if command could not be built or if response
        was not recognized.
        """

        # Get timeout and initialize response.
        timeout = kwargs.get('timeout', DEFAULT_CMD_TIMEOUT)
        response_regex = kwargs.get('response_regex', None)  # required argument
        write_delay = INTER_CHARACTER_DELAY
        retval = None

        if not response_regex:
            raise InstrumentProtocolException('missing required keyword argument "response_regex"')

        if response_regex and not isinstance(response_regex, RE_PATTERN):
            raise InstrumentProtocolException('Response regex is not a compiled pattern!')

        # Get the build handler.
        build_handler = self._build_handlers.get(cmd, None)
        if not build_handler:
            raise InstrumentProtocolException('Cannot build command: %s' % cmd)

        cmd_line = build_handler(cmd, *args)
        # Wakeup the device, pass up exception if timeout

        prompt = self._wakeup(timeout)

        # Clear line and prompt buffers for result.
        self._linebuf = ''
        self._promptbuf = ''

        # Send command.
        log.debug('_do_cmd_resp: %s, timeout=%s, write_delay=%s, response_regex=%s',
                  repr(cmd_line), timeout, write_delay, response_regex)

        for char in cmd_line:
            self._connection.send(char)
            time.sleep(write_delay)

        # Wait for the prompt, prepare result and return, timeout exception
        return self._get_response(timeout, response_regex=response_regex)

    def _do_cmd_home(self):
        """
        Move valve to the home port
        @retval True if successful, False if unable to return home
        """
        func = '_do_cmd_home'
        port = int(self._do_cmd_resp(McLaneCommand.PORT, response_regex=McLaneResponse.PORT)[0])
        if port != 0:
            self._do_cmd_resp(McLaneCommand.HOME, response_regex=McLaneResponse.HOME, timeout=Timeout.HOME)
            port = int(self._do_cmd_resp(McLaneCommand.PORT, response_regex=McLaneResponse.PORT)[0])
            if port != 0:
                log.error('Unable to return to home port')
                return False
        return True

    def _do_cmd_flush(self, *args, **kwargs):
        """
        Flush the home port in preparation for collecting a sample. This clears the intake port so that
        the sample taken will be new.
        This only starts the flush. The remainder of the flush is monitored by got_chunk.
        """
        flush_volume = self._param_dict.get(Parameter.FLUSH_VOLUME)
        flush_flowrate = self._param_dict.get(Parameter.FLUSH_FLOWRATE)
        flush_minflow = self._param_dict.get(Parameter.FLUSH_MINFLOW)

        if not self._do_cmd_home():
            self._async_raise_fsm_event(ProtocolEvent.INSTRUMENT_FAILURE)
        self._do_cmd_no_resp(McLaneCommand.FORWARD, flush_volume, flush_flowrate, flush_minflow)

    def _do_cmd_fill(self, *args, **kwargs):
        """
        Fill the sample at the next available port
        """
        fill_volume = self._param_dict.get(Parameter.FILL_VOLUME)
        fill_flowrate = self._param_dict.get(Parameter.FILL_FLOWRATE)
        fill_minflow = self._param_dict.get(Parameter.FILL_MINFLOW)

        reply = self._do_cmd_resp(McLaneCommand.PORT, self.next_port, response_regex=McLaneResponse.PORT)

        self.next_port += 1  # succeed or fail, we can't use this port again
        # TODO - commit next_port to the agent for persistent data store
        self._do_cmd_no_resp(McLaneCommand.FORWARD, fill_volume, fill_flowrate, fill_minflow)

    def _do_cmd_clear(self, *args, **kwargs):
        """
        Clear the home port
        """
        self._do_cmd_home()

        clear_volume = self._param_dict.get(Parameter.CLEAR_VOLUME)
        clear_flowrate = self._param_dict.get(Parameter.CLEAR_FLOWRATE)
        clear_minflow = self._param_dict.get(Parameter.CLEAR_MINFLOW)

        self._do_cmd_no_resp(McLaneCommand.REVERSE, clear_volume, clear_flowrate, clear_minflow)

    ########################################################################
    # Generic handlers.
    ########################################################################
    def _handler_pass(self, *args, **kwargs):
        pass

    def _handler_all_failure(self, *args, **kwargs):
        next_state = ProtocolState.RECOVERY
        result = []

        log.error('Instrument failure detected. Entering recovery mode.')
        return next_state, (next_state, result)

    ########################################################################
    # 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)
        # TODO - read persistent data (next port)

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state; can only be COMMAND (instrument has no AUTOSAMPLE mode).
        @retval next_state, next_state
        """
        next_state = ProtocolState.COMMAND
        result = []

        # force to command mode, this instrument has no autosample mode
        return next_state, (next_state, result)

    ########################################################################
    # Flush
    ########################################################################
    def _handler_flush_enter(self, *args, **kwargs):
        """
        Enter the flush state. Trigger FLUSH event.
        """
        self._second_attempt = False
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)
        self._async_raise_fsm_event(ProtocolEvent.FLUSH)

    def _handler_flush_flush(self, *args, **kwargs):
        """
        Begin flushing the home port. Subsequent flushing will be monitored and sent to the flush_pump_status
        handler.
        """
        next_state = None
        result = []

        # 2. Set to home port
        # 3. flush intake (home port)
        # 4. wait 30 seconds
        # 1. Get next available port (if no available port, bail)
        self._do_cmd_flush()

        return next_state, (next_state, result)

    def _handler_flush_pump_status(self, *args, **kwargs):
        """
        Manage pump status update during flush. Status updates indicate continued pumping, Result updates
        indicate completion of command. Check the termination code for success.
        @args match object containing the regular expression match of the status line.
        """
        match = args[0]
        pump_status = match.group('status')
        code = int(match.group('code'))

        next_state = None
        result = []

        if pump_status == 'Result':
            if code == TerminationCodeEnum.SUDDEN_FLOW_OBSTRUCTION:
                log.info('Encountered obstruction during flush, attempting to clear')
                self._async_raise_fsm_event(ProtocolEvent.CLEAR)
            else:
                next_state = ProtocolState.FILL

        return next_state, (next_state, result)

    def _handler_flush_clear(self, *args, **kwargs):
        """
        Attempt to clear home port after stoppage has occurred during flush.
        This is only performed once. On the second stoppage, the driver will enter recovery mode.
        """
        next_state = None
        result = []

        if self._second_attempt:
            next_state = ProtocolState.RECOVERY
        else:
            self._second_attempt = True
            self._do_cmd_clear()

        return next_state, (next_state, result)

    ########################################################################
    # Fill
    ########################################################################
    def _handler_fill_enter(self, *args, **kwargs):
        """
        Enter the fill state. Trigger FILL event.
        """
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)
        self._async_raise_fsm_event(ProtocolEvent.FILL)

    def _handler_fill_fill(self, *args, **kwargs):
        """
        Send the fill command and process the first response
        """
        next_state = None
        result = []

        log.debug('Entering PHIL PHIL')
        # 5. switch to collection port (next available)
        # 6. collect sample (4000 ml)
        # 7. wait 2 minutes
        if self.next_port > NUM_PORTS:
            log.error('Unable to collect RAS sample - %d containers full', NUM_PORTS)
            next_state = ProtocolState.COMMAND
        else:
            self._do_cmd_fill()

        return next_state, (next_state, result)

    def _handler_fill_pump_status(self, *args, **kwargs):
        """
        Process pump status updates during filter collection.
        """
        next_state = None
        result = []

        match = args[0]
        pump_status = match.group('status')
        code = int(match.group('code'))

        if pump_status == 'Result':
            if code != TerminationCodeEnum.VOLUME_REACHED:
                next_state = ProtocolState.RECOVERY
                result = 'unable to fill - possible obstruction - will attempt to clear'
            else:
                next_state = ProtocolState.CLEAR  # all done
                result = 'volume reached'
            # if pump_status == 'Status':
            # TODO - check for bag rupture (> 93% flow rate near end of sample collect- RAS only)

        return next_state, (next_state, result)

    ########################################################################
    # Clear
    ########################################################################
    def _handler_clear_enter(self, *args, **kwargs):
        """
        Enter the clear state. Trigger the CLEAR event.
        """
        self._second_attempt = False
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)
        self._async_raise_fsm_event(ProtocolEvent.CLEAR)

    def _handler_clear_clear(self, *args, **kwargs):
        """
        Send the clear command. If there is an obstruction trigger a FLUSH, otherwise place driver in RECOVERY mode.
        """
        next_state = None
        result = []

        # 8. return to home port
        # 9. reverse flush 75 ml to pump water from exhaust line through intake line
        self._do_cmd_clear()

        return next_state, (next_state, result)

    def _handler_clear_pump_status(self, *args, **kwargs):
        """
        Parse pump status during clear action.
        """
        next_state = None
        result = []

        match = args[0]
        pump_status = match.group('status')
        code = int(match.group('code'))

        if pump_status == 'Result':
            if code != TerminationCodeEnum.VOLUME_REACHED:
                result = 'Encountered obstruction during clear. Attempting flush...'
                log.error(result)
                self._async_raise_fsm_event(ProtocolEvent.FLUSH)
            else:
                next_state = ProtocolState.COMMAND
                result = 'clear successful'
        # if Status, nothing to do
        return next_state, (next_state, result)

    def _handler_clear_flush(self, *args, **kwargs):
        """
        Attempt to recover from failed attempt to clear by flushing home port. Only try once.
        """
        next_state = None
        result = []

        log.info('Attempting to flush main port during clear')
        if self._second_attempt:
            next_state = ProtocolState.RECOVERY
            result = 'unable to flush main port during clear'
        else:
            self._second_attempt = True
            self._do_cmd_flush()
            result = 'attempting to flush main port a second time'

        return next_state, (next_state, result)

    ########################################################################
    # Command handlers.
    # just implemented to make DA possible, instrument has no actual command mode
    ########################################################################

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        """
        # Command device to update parameters and send a config change event if needed.
        self._update_params()
        self._protocol_fsm.on_event(ProtocolEvent.INIT_PARAMS)

        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_command_init_params(self, *args, **kwargs):
        """
        Setup initial parameters.
        """
        next_state = None
        result = self._init_params()

        return next_state, (next_state, result)

    def _handler_command_set(self, *args, **kwargs):
        """
        Set instrument parameters
        """
        next_state = None
        result = []
        startup = False

        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException('set command requires a parameter dictionary.')

        try:
            startup = args[1]
        except IndexError:
            pass

        if not isinstance(params, dict):
            raise InstrumentParameterException('set parameters is not a dictionary')

        self._set_params(params, startup)

        return next_state, (next_state, result)

    def _handler_command_start_direct(self, *args, **kwargs):
        """
        Start direct access.
        """
        next_state = ProtocolState.DIRECT_ACCESS
        result = []

        return next_state, (next_state, result)

    ########################################################################
    # Recovery handlers.
    ########################################################################

    # TODO - not sure how to determine how to exit from this state. Probably requires a driver reset.
    def _handler_recovery_enter(self, *args, **kwargs):
        """
        Error recovery mode. The instrument failed to respond to a command and now requires the user to perform
        diagnostics and correct before proceeding.
        """
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    ########################################################################
    # 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_execute_direct(self, data):
        next_state = None
        result = []
        self._do_cmd_direct(data)

        return next_state, (next_state, result)

    def _handler_direct_access_stop_direct(self, *args, **kwargs):
        next_state = ProtocolState.COMMAND
        result = []

        return next_state, (next_state, result)

    ########################################################################
    # general handlers.
    ########################################################################

    def get_timestamp_delayed(self, fmt, delay=0):
        """
        Return a formatted date string of the current utc time,
        but the string return is delayed until the next second
        transition.

        Formatting:
        http://docs.python.org/library/time.html#time.strftime

        :param fmt: strftime() format string
        :param delay: optional time to wait before getting timestamp
        :return: formatted date string
        :raise ValueError if format is None
        """
        if not fmt:
            raise ValueError

        now = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
        time.sleep((1e6 - now.microsecond) / 1e6)
        now = datetime.datetime.utcnow() + datetime.timedelta(seconds=delay)
        return now.strftime(fmt)

    def _handler_sync_clock(self, *args, **kwargs):
        """
        sync clock close to a second edge
        @retval (next_state, (next_agent_state, result)) tuple, (None, (None, None)).
        @throws InstrumentTimeoutException if device respond correctly.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """

        cmd_len = len('clock 03/20/2014 17:14:55' + NEWLINE)
        delay = cmd_len * INTER_CHARACTER_DELAY

        time_format = "%m/%d/%Y %H:%M:%S"
        str_val = self.get_timestamp_delayed(time_format, delay)
        # str_val = time.strftime(time_format, time.gmtime(time.time() + self._clock_set_offset))
        log.debug("Setting instrument clock to '%s'", str_val)

        ras_time = self._do_cmd_resp(McLaneCommand.CLOCK, str_val, response_regex=McLaneResponse.READY)[0]

        return None, (None, {'time': ras_time})

    def _handler_command_acquire(self, *args, **kwargs):
        next_state = ProtocolState.FLUSH
        result = []
        self._handler_sync_clock()
        return next_state, (next_state, result)

    # def _handler_command_status(self, *args, **kwargs):
    #     # get the following:
    #     # - VERSION
    #     # - CAPACITY (pump flow)
    #     # - BATTERY
    #     # - CODES (termination codes)
    #     # - COPYRIGHT (termination codes)
    #     return None, None

    def _handler_command_clear(self, *args, **kwargs):
        next_state = ProtocolState.CLEAR
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Private helpers.
    ########################################################################

    def _wakeup(self, wakeup_timeout=10, response_timeout=3):
        """
        Over-written because waking this instrument up is a multi-step process with
        two different requests required
        @param wakeup_timeout The timeout to wake the device.
        @param response_timeout The time to look for response to a wakeup attempt.
        @throw InstrumentTimeoutException if the device could not be woken.
        """
        sleep_time = .1
        command = McLaneCommand.GO

        # Grab start time for overall wakeup timeout.
        starttime = time.time()

        while True:
            # Clear the prompt buffer.
            log.debug("_wakeup: clearing promptbuf: %s", self._promptbuf)
            self._promptbuf = ''

            # Send a command and wait delay amount for response.
            log.debug('_wakeup: Sending command %s, delay=%s', command.encode("hex"), response_timeout)
            for char in command:
                self._connection.send(char)
                time.sleep(INTER_CHARACTER_DELAY)
            sleep_amount = 0
            while True:
                time.sleep(sleep_time)
                if self._promptbuf.find(Prompt.COMMAND_INPUT) != -1:
                    # instrument is awake
                    log.debug('_wakeup: got command input prompt %s', Prompt.COMMAND_INPUT)
                    # add inter-character delay which _do_cmd_resp() incorrectly doesn't add to
                    # the start of a transmission
                    time.sleep(INTER_CHARACTER_DELAY)
                    return Prompt.COMMAND_INPUT
                if self._promptbuf.find(Prompt.ENTER_CTRL_C) != -1:
                    command = McLaneCommand.CONTROL_C
                    break
                if self._promptbuf.find(Prompt.PERIOD) == 0:
                    command = McLaneCommand.CONTROL_C
                    break
                sleep_amount += sleep_time
                if sleep_amount >= response_timeout:
                    log.debug("_wakeup: expected response not received, buffer=%s", self._promptbuf)
                    break

            if time.time() > starttime + wakeup_timeout:
                raise InstrumentTimeoutException(
                    "_wakeup(): instrument failed to wakeup in %d seconds time" % wakeup_timeout)

    def _build_command(self, cmd, *args):
        return cmd + ' ' + ' '.join([str(x) for x in args]) + NEWLINE

    def _build_driver_dict(self):
        """
        Populate the driver dictionary with options
        """
        self._driver_dict.add(DriverDictKey.VENDOR_SW_COMPATIBLE, False)

    def _build_command_dict(self):
        """
        Populate the command dictionary with command.
        """
        self._cmd_dict.add(Capability.CLOCK_SYNC, display_name="Synchronize Clock")
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.FLUSH_VOLUME,
                             r'Flush Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=150,
                             units='mL',
                             startup_param=True,
                             display_name="flush_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FLUSH_FLOWRATE,
                             r'Flush Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=100,
                             units='mL/min',
                             startup_param=True,
                             display_name="flush_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FLUSH_MINFLOW,
                             r'Flush Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=75,
                             units='mL/min',
                             startup_param=True,
                             display_name="flush_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_VOLUME,
                             r'Fill Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=4000,
                             units='mL',
                             startup_param=True,
                             display_name="fill_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_FLOWRATE,
                             r'Fill Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=100,
                             units='mL/min',
                             startup_param=True,
                             display_name="fill_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_MINFLOW,
                             r'Fill Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=75,
                             units='mL/min',
                             startup_param=True,
                             display_name="fill_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_VOLUME,
                             r'Reverse Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=100,
                             units='mL',
                             startup_param=True,
                             display_name="clear_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_FLOWRATE,
                             r'Reverse Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=100,
                             units='mL/min',
                             startup_param=True,
                             display_name="clear_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_MINFLOW,
                             r'Reverse Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=75,
                             units='mL/min',
                             startup_param=True,
                             display_name="clear_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)

    def _update_params(self):
        """
예제 #50
0
class Protocol(SamiProtocol):
    """
    Instrument protocol class
    Subclasses SamiProtocol and CommandResponseInstrumentProtocol
    """
    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        SamiProtocol.__init__(self, prompts, newline, driver_event)

        ## Continue building protocol state machine from SamiProtocol
        self._protocol_fsm.add_handler(ProtocolState.SCHEDULED_SAMPLE,
                                       ProtocolEvent.SUCCESS,
                                       self._handler_sample_success)
        self._protocol_fsm.add_handler(ProtocolState.SCHEDULED_SAMPLE,
                                       ProtocolEvent.TIMEOUT,
                                       self._handler_sample_timeout)

        self._protocol_fsm.add_handler(ProtocolState.POLLED_SAMPLE,
                                       ProtocolEvent.SUCCESS,
                                       self._handler_sample_success)
        self._protocol_fsm.add_handler(ProtocolState.POLLED_SAMPLE,
                                       ProtocolEvent.TIMEOUT,
                                       self._handler_sample_timeout)

        # State state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)

        # build the chunker bot
        self._chunker = StringChunker(Protocol.sieve_function)

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples
        """

        return_list = []

        sieve_matchers = [
            REGULAR_STATUS_REGEX_MATCHER, CONTROL_RECORD_REGEX_MATCHER,
            SAMI_SAMPLE_REGEX_MATCHER, CONFIGURATION_REGEX_MATCHER,
            ERROR_REGEX_MATCHER
        ]

        for matcher in sieve_matchers:
            for match in matcher.finditer(raw_data):
                return_list.append((match.start(), match.end()))

        return return_list

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        self._extract_sample(SamiRegularStatusDataParticle,
                             REGULAR_STATUS_REGEX_MATCHER, chunk, timestamp)
        self._extract_sample(SamiControlRecordDataParticle,
                             CONTROL_RECORD_REGEX_MATCHER, chunk, timestamp)
        self._extract_sample(PhsenSamiSampleDataParticle,
                             SAMI_SAMPLE_REGEX_MATCHER, chunk, timestamp)
        self._extract_sample(PhsenConfigDataParticle,
                             CONFIGURATION_REGEX_MATCHER, chunk, timestamp)

    #########################################################################
    ## General (for POLLED and SCHEDULED states) Sample handlers.
    #########################################################################

    def _handler_sample_success(self, *args, **kwargs):
        next_state = None
        result = None

        return (next_state, result)

    def _handler_sample_timeout(self, ):
        next_state = None
        result = None

        return (next_state, result)

    ####################################################################
    # Build Parameter dictionary
    ####################################################################

    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.LAUNCH_TIME,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(1), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='launch time')

        self._param_dict.add(Parameter.START_TIME_FROM_LAUNCH,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(2), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02C7EA00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='start time after launch time')

        self._param_dict.add(Parameter.STOP_TIME_FROM_START,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(3), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01E13380,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='stop time after start time')

        self._param_dict.add(Parameter.MODE_BITS,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(4), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0A,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='mode bits (set to 00001010)')

        self._param_dict.add(Parameter.SAMI_SAMPLE_INTERVAL,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(5), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami sample interval')

        self._param_dict.add(Parameter.SAMI_DRIVER_VERSION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(6), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x04,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami driver version')

        self._param_dict.add(Parameter.SAMI_PARAMS_POINTER,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(7), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami parameter pointer')

        self._param_dict.add(Parameter.DEVICE1_SAMPLE_INTERVAL,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(8), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 sample interval')

        self._param_dict.add(Parameter.DEVICE1_DRIVER_VERSION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(9), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 driver version')

        self._param_dict.add(Parameter.DEVICE1_PARAMS_POINTER,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(10), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0B,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 parameter pointer')

        self._param_dict.add(Parameter.DEVICE2_SAMPLE_INTERVAL,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(11), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 sample interval')

        self._param_dict.add(Parameter.DEVICE2_DRIVER_VERSION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(12), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 driver version')

        self._param_dict.add(Parameter.DEVICE2_PARAMS_POINTER,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(13), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 parameter pointer')

        self._param_dict.add(Parameter.DEVICE3_SAMPLE_INTERVAL,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(14), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 sample interval')

        self._param_dict.add(Parameter.DEVICE3_DRIVER_VERSION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(15), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 driver version')

        self._param_dict.add(Parameter.DEVICE3_PARAMS_POINTER,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(16), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 parameter pointer')

        self._param_dict.add(Parameter.PRESTART_SAMPLE_INTERVAL,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(17), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart sample interval')

        self._param_dict.add(Parameter.PRESTART_DRIVER_VERSION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(18), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart driver version')

        self._param_dict.add(Parameter.PRESTART_PARAMS_POINTER,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(19), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart parameter pointer')

        self._param_dict.add(Parameter.GLOBAL_CONFIGURATION,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(20), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='global bits (set to 00000111)')

        self._param_dict.add(Parameter.NUMBER_SAMPLES_AVERAGED,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(21), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_FLUSHES,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(22), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_ON_FLUSH,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(23), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_OFF_FLUSH,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(24), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_REAGENT_PUMPS,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(25), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.VALVE_DELAY,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(26), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_ON_IND,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(27), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PV_OFF_IND,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(28), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_BLANKS,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(29), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_MEASURE_T,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(30), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.PUMP_OFF_TO_MEASURE,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(31), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.MEASURE_TO_PUMP_ON,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(32), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.NUMBER_MEASUREMENTS,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(33), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')

        self._param_dict.add(Parameter.SALINITY_DELAY,
                             CONFIGURATION_REGEX,
                             lambda match: int(match.group(34), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of samples averaged',
                             description='')
예제 #51
0
class Protocol(McLaneProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """

    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        McLaneProtocol.__init__(self, prompts, newline, driver_event)

        # TODO - reset next_port on mechanical refresh of the RAS bags - how is the driver notified?
        # TODO - need to persist state for next_port to save driver restart
        self.next_port = 1  # next available port

        # TODO - on init, perform the following commands:
        # - VERSION
        # - CAPACITY (pump flow)
        # - BATTERY
        # - CODES (termination codes)
        # - COPYRIGHT (termination codes)

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        filling = self.get_current_state() == ProtocolState.FILL
        log.debug("_got_chunk:\n%s", chunk)
        sample_dict = self._extract_sample(RASFLSampleDataParticle,
                                           RASFLSampleDataParticle.regex_compiled(), chunk, timestamp, publish=filling)

        if sample_dict:
            self._linebuf = ''
            self._promptbuf = ''
            self._protocol_fsm.on_event(ProtocolEvent.PUMP_STATUS,
                                        RASFLSampleDataParticle.regex_compiled().search(chunk))

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.FLUSH_VOLUME,
                             r'Flush Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FLUSH_VOLUME,
                             units='mL',
                             startup_param=True,
                             display_name="flush_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FLUSH_FLOWRATE,
                             r'Flush Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FLUSH_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="flush_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FLUSH_MINFLOW,
                             r'Flush Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FLUSH_MIN_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="flush_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_VOLUME,
                             r'Fill Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FILL_VOLUME,
                             units='mL',
                             startup_param=True,
                             display_name="fill_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_FLOWRATE,
                             r'Fill Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FILL_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="fill_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_MINFLOW,
                             r'Fill Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FILL_MIN_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="fill_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_VOLUME,
                             r'Reverse Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=CLEAR_VOLUME,
                             units='mL',
                             startup_param=True,
                             display_name="clear_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_FLOWRATE,
                             r'Reverse Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=CLEAR_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="clear_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_MINFLOW,
                             r'Reverse Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=CLEAR_MIN_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="clear_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)

        self._param_dict.set_value(Parameter.FLUSH_VOLUME, FLUSH_VOLUME)
        self._param_dict.set_value(Parameter.FLUSH_FLOWRATE, FLUSH_RATE)
        self._param_dict.set_value(Parameter.FLUSH_MINFLOW, FLUSH_MIN_RATE)
        self._param_dict.set_value(Parameter.FILL_VOLUME, FILL_VOLUME)
        self._param_dict.set_value(Parameter.FILL_FLOWRATE, FILL_RATE)
        self._param_dict.set_value(Parameter.FILL_MINFLOW, FILL_MIN_RATE)
        self._param_dict.set_value(Parameter.CLEAR_VOLUME, CLEAR_VOLUME)
        self._param_dict.set_value(Parameter.CLEAR_FLOWRATE, CLEAR_RATE)
        self._param_dict.set_value(Parameter.CLEAR_MINFLOW, CLEAR_MIN_RATE)
예제 #52
0
    def _build_param_dict(self):
        """
        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()

        ### example configuration string
        # VALID_CONFIG_STRING = 'CEE90B0002C7EA0001E133800A000E100402000E10010B' + \
        #                       '000000000D000000000D000000000D07' + \
        #                       '1020FF54181C01003814' + \
        #                       '000000000000000000000000000000000000000000000000000' + \
        #                       '000000000000000000000000000000000000000000000000000' + \
        #                       '0000000000000000000000000000' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + \
        #                       'FFFFFFFFFFFFFFFFFFFFFFFFFFFFF' + NEWLINE
        #
        ###

        self._param_dict.add(Parameter.LAUNCH_TIME, CONFIGURATION_REGEX,
                             lambda match: int(match.group(1), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='launch time')

        self._param_dict.add(Parameter.START_TIME_FROM_LAUNCH, CONFIGURATION_REGEX,
                             lambda match: int(match.group(2), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02C7EA00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='start time after launch time')

        self._param_dict.add(Parameter.STOP_TIME_FROM_START, CONFIGURATION_REGEX,
                             lambda match: int(match.group(3), 16),
                             lambda x: self._int_to_hexstring(x, 8),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01E13380,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='stop time after start time')

        self._param_dict.add(Parameter.MODE_BITS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(4), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0A,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='mode bits (set to 00001010)')

        self._param_dict.add(Parameter.SAMI_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(5), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami sample interval')

        self._param_dict.add(Parameter.SAMI_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(6), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x04,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami driver version')

        self._param_dict.add(Parameter.SAMI_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(7), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x02,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='sami parameter pointer')

        self._param_dict.add(Parameter.DEVICE1_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(8), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 sample interval')

        self._param_dict.add(Parameter.DEVICE1_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(9), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 driver version')

        self._param_dict.add(Parameter.DEVICE1_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(10), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0B,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 1 parameter pointer')

        self._param_dict.add(Parameter.DEVICE2_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(11), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 sample interval')

        self._param_dict.add(Parameter.DEVICE2_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(12), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 driver version')

        self._param_dict.add(Parameter.DEVICE2_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(13), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 2 parameter pointer')

        self._param_dict.add(Parameter.DEVICE3_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(14), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 sample interval')

        self._param_dict.add(Parameter.DEVICE3_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(15), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 driver version')

        self._param_dict.add(Parameter.DEVICE3_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(16), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='device 3 parameter pointer')

        self._param_dict.add(Parameter.PRESTART_SAMPLE_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(17), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x000000,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart sample interval')

        self._param_dict.add(Parameter.PRESTART_DRIVER_VERSION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(18), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart driver version')

        self._param_dict.add(Parameter.PRESTART_PARAMS_POINTER, CONFIGURATION_REGEX,
                             lambda match: int(match.group(19), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x0D,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='prestart parameter pointer')

        self._param_dict.add(Parameter.GLOBAL_CONFIGURATION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(20), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='global bits (set to 00000111)')

        self._param_dict.add(Parameter.PUMP_PULSE, CONFIGURATION_REGEX,
                             lambda match: int(match.group(21), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x10,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='pump pulse duration')

        self._param_dict.add(Parameter.PUMP_DURATION, CONFIGURATION_REGEX,
                             lambda match: int(match.group(22), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x20,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='pump measurement duration')

        self._param_dict.add(Parameter.SAMPLES_PER_MEASUREMENT, CONFIGURATION_REGEX,
                             lambda match: int(match.group(23), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0xFF,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='samples per measurement')

        self._param_dict.add(Parameter.CYCLES_BETWEEN_BLANKS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(24), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0xA8,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='cycles between blanks')

        self._param_dict.add(Parameter.NUMBER_REAGENT_CYCLES, CONFIGURATION_REGEX,
                             lambda match: int(match.group(25), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x18,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of reagent cycles')

        self._param_dict.add(Parameter.NUMBER_BLANK_CYCLES, CONFIGURATION_REGEX,
                             lambda match: int(match.group(26), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x1C,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of blank cycles')

        self._param_dict.add(Parameter.FLUSH_PUMP_INTERVAL, CONFIGURATION_REGEX,
                             lambda match: int(match.group(27), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x01,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='flush pump interval')

        self._param_dict.add(Parameter.BIT_SWITCHES, CONFIGURATION_REGEX,
                             lambda match: int(match.group(28), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x00,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='bit switches')

        self._param_dict.add(Parameter.NUMBER_EXTRA_PUMP_CYCLES, CONFIGURATION_REGEX,
                             lambda match: int(match.group(29), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x38,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='number of extra pump cycles')

        self._param_dict.add(Parameter.EXTERNAL_PUMP_SETTINGS, CONFIGURATION_REGEX,
                             lambda match: int(match.group(30), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=False,
                             direct_access=True,
                             default_value=0x14,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             display_name='external pump settings')
예제 #53
0
class DataSetDriver(object):
    """
    Base class for data set drivers.  Provides:
    - an interface via callback to publish data
    - an interface via callback to persist driver state
    - an interface via callback to handle exceptions
    - an start and stop sampling
    - a client interface for execute resource

    Subclasses need to include harvesters and parsers and
    be specialized to handle the interaction between the two.
    
    Configurations should contain keys from the DataSetDriverConfigKey class
    and should look something like this example (more full documentation in the
    "Dataset Agent Architecture" page on the OOI wiki):
    {
        'harvester':
        {
            'directory': '/tmp/dsatest',
            'pattern': '*.txt',
            'frequency': 1,
        },
        'parser': {}
        'driver': {
            'records_per_second'
            'harvester_polling_interval'
            'batched_particle_count'
        }
    }
    """
    def __init__(self, config, memento, data_callback, state_callback,
                 exception_callback):
        self._config = config
        self._data_callback = data_callback
        self._state_callback = state_callback
        self._exception_callback = exception_callback
        self._memento = memento
        self._publisher_thread = None

        self._verify_config()
        self._param_dict = ProtocolParameterDict()

        # Updated my set_resource, defaults defined in build_param_dict
        self._polling_interval = None
        self._generate_particle_count = None
        self._particle_count_per_second = None

        self._build_param_dict()

    def shutdown(self):
        self.stop_sampling()

    def start_sampling(self):
        """
        Start a new thread to monitor for data
        """
        self._start_sampling()
        self._start_publisher_thread()

    def stop_sampling(self):
        """
        Stop the sampling thread
        """
        log.debug("Stopping driver now")

        self._stop_sampling()
        self._stop_publisher_thread()

    def _start_sampling(self):
        raise NotImplementedException(
            'virtual methond needs to be specialized')

    def _stop_sampling(self):
        raise NotImplementedException(
            'virtual methond needs to be specialized')

    def cmd_dvr(self, cmd, *args, **kwargs):
        log.warn("DRIVER: cmd_dvr %s", cmd)

        if not cmd in [
                'execute_resource', 'get_resource_capabilities',
                'set_resource', 'get_resource'
        ]:
            log.error("Unhandled command: %s", cmd)
            raise InstrumentStateException("Unhandled command: %s" % cmd)

        resource_cmd = args[0]

        if cmd == 'execute_resource':
            if resource_cmd == DriverEvent.START_AUTOSAMPLE:
                return (ResourceAgentState.STREAMING, None)

            elif resource_cmd == DriverEvent.STOP_AUTOSAMPLE:
                self.stop_sampling()
                return (ResourceAgentState.COMMAND, None)

            else:
                log.error("Unhandled resource command: %s", resource_cmd)
                raise

        elif cmd == 'get_resource_capabilities':
            return self.get_resource_capabilities()

        elif cmd == 'set_resource':
            return self.set_resource(*args, **kwargs)

        elif cmd == 'get_resource':
            return self.get_resource(*args, **kwargs)

    def get_resource_capabilities(self, current_state=True, *args, **kwargs):
        """
        Return driver commands and parameters.
        @param current_state True to retrieve commands available in current
        state, otherwise reutrn all commands.
        @retval list of AgentCapability objects representing the drivers
        capabilities.
        @raises NotImplementedException if not implemented by subclass.
        """
        res_params = self._param_dict.get_keys()
        return [[], res_params]

    def set_resource(self, *args, **kwargs):
        """
        Set the driver parameter
        """
        log.trace("start set_resource")
        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException(
                'Set command requires a parameter dict.')

        log.trace("set_resource: iterate through params: %s", params)
        for (key, val) in params.iteritems():
            if key in [
                    DriverParameter.BATCHED_PARTICLE_COUNT,
                    DriverParameter.RECORDS_PER_SECOND
            ]:
                if not isinstance(val, int):
                    raise InstrumentParameterException(
                        "%s must be an integer" % key)
            if key in [DriverParameter.PUBLISHER_POLLING_INTERVAL]:
                if not isinstance(val, (int, float)):
                    raise InstrumentParameterException("%s must be an float" %
                                                       key)

            if val <= 0:
                raise InstrumentParameterException("%s must be > 0" % key)

            self._param_dict.set_value(key, val)

        # Set the driver parameters
        self._generate_particle_count = self._param_dict.get(
            DriverParameter.BATCHED_PARTICLE_COUNT)
        self._particle_count_per_second = self._param_dict.get(
            DriverParameter.RECORDS_PER_SECOND)
        self._polling_interval = self._param_dict.get(
            DriverParameter.PUBLISHER_POLLING_INTERVAL)
        log.trace("Driver Parameters: %s, %s, %s", self._polling_interval,
                  self._particle_count_per_second,
                  self._generate_particle_count)

    def get_resource(self, *args, **kwargs):
        """
        Get driver parameter
        """
        result = {}

        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException(
                'Set command requires a parameter list.')

        # If all params requested, retrieve config.
        if params == DriverParameter.ALL:
            result = self._param_dict.get_config()

        # If not all params, confirm a list or tuple of params to retrieve.
        # Raise if not a list or tuple.
        # Retrieve each key in the list, raise if any are invalid.
        else:
            if not isinstance(params, (list, tuple)):
                raise InstrumentParameterException(
                    'Get argument not a list or tuple.')
            result = {}
            for key in params:
                try:
                    val = self._param_dict.get(key)
                    result[key] = val

                except KeyError:
                    raise InstrumentParameterException(
                        ('%s is not a valid parameter.' % key))

        return result

    def _verify_config(self):
        """
        virtual method to verify the supplied driver configuration is value.  Must
        be overloaded in sub classes.

        raises an ConfigurationException when a configuration error is detected.
        """
        raise NotImplementedException(
            'virtual methond needs to be specialized')

    def _build_param_dict(self):
        """
        Setup three common driver parameters
        """
        self._param_dict.add_parameter(
            Parameter(DriverParameter.RECORDS_PER_SECOND,
                      int,
                      value=60,
                      type=ParameterDictType.INT,
                      display_name="Records Per Second",
                      description="Number of records to process per second"))

        self._param_dict.add_parameter(
            Parameter(
                DriverParameter.PUBLISHER_POLLING_INTERVAL,
                float,
                value=1,
                type=ParameterDictType.FLOAT,
                display_name="Harvester Polling Interval",
                description=
                "Duration in minutes to wait before checking for new files."))

        self._param_dict.add_parameter(
            Parameter(
                DriverParameter.BATCHED_PARTICLE_COUNT,
                int,
                value=1,
                type=ParameterDictType.INT,
                display_name="Batched Particle Count",
                description=
                "Number of particles to batch before sending to the agent"))

        config = self._config.get(DataSourceConfigKey.DRIVER, {})
        log.debug("set_resource on startup with: %s", config)
        self.set_resource(config)

    def _start_publisher_thread(self):
        self._publisher_thread = gevent.spawn(self._publisher_loop)
        self._publisher_shutdown = False

    def _stop_publisher_thread(self):
        log.debug("Signal shutdown")
        self._publisher_shutdown = True
        if self._publisher_thread:
            self._publisher_thread.kill(block=False)
        log.debug("shutdown complete")

    def _publisher_loop(self):
        """
        Main loop to listen for new files to parse.  Parse them and move on.
        """
        log.info("Starting main publishing loop")

        try:
            while (not self._publisher_shutdown):
                self._poll()
                gevent.sleep(self._polling_interval)
        except Exception as e:
            log.error("Exception in publisher thread: %s", e)
            self._exception_callback(e)

        log.debug("publisher thread detected shutdown request")

    def _poll(self):
        raise NotImplementedException(
            'virtual methond needs to be specialized')

    def _new_file_exception(self):
        raise NotImplementedException(
            'virtual methond needs to be specialized')
예제 #54
0
class Protocol(WorkhorseProtocol):
    def __init__(self, prompts, newline, driver_event, connections=None):
        """
        Constructor.
        @param prompts Enum class containing possible device prompts used for
        command response logic.
        @param newline The device newline.
        @driver_event The callback for asynchronous driver events.
        """
        if not type(connections) is list:
            raise InstrumentProtocolException('Unable to instantiate multi connection protocol without connection list')
        self._param_dict2 = ProtocolParameterDict()

        # Construct superclass.
        WorkhorseProtocol.__init__(self, prompts, newline, driver_event)

        # Create multiple connection versions of the pieces of protocol involving data to/from the instrument
        self._linebuf = {connection: '' for connection in connections}
        self._promptbuf = {connection: '' for connection in connections}
        self._last_data_timestamp = {connection: None for connection in connections}
        self.connections = {connection: None for connection in connections}
        self.chunkers = {connection: StringChunker(self.sieve_function) for connection in connections}

    def _get_response(self, timeout=10, expected_prompt=None, response_regex=None, connection=None):
        """
        Overridden to handle multiple port agent connections
        """
        if connection is None:
            raise InstrumentProtocolException('_get_response: no connection supplied!')
        # Grab time for timeout and wait for prompt.
        end_time = time.time() + timeout

        if response_regex and not isinstance(response_regex, RE_PATTERN):
            raise InstrumentProtocolException('Response regex is not a compiled pattern!')

        if expected_prompt and response_regex:
            raise InstrumentProtocolException('Cannot supply both regex and expected prompt!')

        if expected_prompt is None:
            prompt_list = self._get_prompts()
        else:
            if isinstance(expected_prompt, str):
                prompt_list = [expected_prompt]
            else:
                prompt_list = expected_prompt

        if response_regex is None:
            pattern = None
        else:
            pattern = response_regex.pattern

        log.debug('_get_response: timeout=%s, prompt_list=%s, expected_prompt=%r, response_regex=%r, promptbuf=%r',
                  timeout, prompt_list, expected_prompt, pattern, self._promptbuf)
        while time.time() < end_time:
            if response_regex:
                # noinspection PyArgumentList
                match = response_regex.search(self._linebuf[connection])
                if match:
                    return match.groups()
            else:
                for item in prompt_list:
                    index = self._promptbuf[connection].find(item)
                    if index >= 0:
                        result = self._promptbuf[connection][0:index + len(item)]
                        return item, result

            time.sleep(.1)

        raise InstrumentTimeoutException("in InstrumentProtocol._get_response()")

    def _do_cmd_resp(self, cmd, *args, **kwargs):
        """
        Overridden to handle multiple port agent connections
        """
        connection = kwargs.get('connection')
        if connection is None:
            raise InstrumentProtocolException('_do_cmd_resp: no connection supplied!')

        # Get timeout and initialize response.
        timeout = kwargs.get('timeout', DEFAULT_CMD_TIMEOUT)
        expected_prompt = kwargs.get('expected_prompt', None)
        response_regex = kwargs.get('response_regex', None)

        if response_regex and not isinstance(response_regex, RE_PATTERN):
            raise InstrumentProtocolException('Response regex is not a compiled pattern!')

        if expected_prompt and response_regex:
            raise InstrumentProtocolException('Cannot supply both regex and expected prompt!')

        self._do_cmd_no_resp(cmd, *args, **kwargs)

        # Wait for the prompt, prepare result and return, timeout exception
        if response_regex:
            prompt = ""
            result_tuple = self._get_response(timeout,
                                              connection=connection,
                                              response_regex=response_regex,
                                              expected_prompt=expected_prompt)
            result = "".join(result_tuple)
        else:
            (prompt, result) = self._get_response(timeout,
                                                  connection=connection,
                                                  expected_prompt=expected_prompt)

        resp_handler = self._response_handlers.get((self.get_current_state(), cmd),
                                                   self._response_handlers.get(cmd, None))
        resp_result = None
        if callable(resp_handler):
            resp_result = resp_handler(result, prompt)

        return resp_result

    def _send_data(self, data, write_delay=0, connection=None):
        if connection is None:
            raise InstrumentProtocolException('_send_data: no connection supplied!')

        if write_delay == 0:
            self.connections[connection].send(data)
        else:
            for char in data:
                self.connections[connection].send(char)
                time.sleep(write_delay)

    def _do_cmd_no_resp(self, cmd, *args, **kwargs):
        """
        Overridden to handle multiple port agent connections
        """
        connection = kwargs.get('connection')
        if connection is None:
            raise InstrumentProtocolException('_do_cmd_no_resp: no connection supplied! %r %r %r' % (cmd, args, kwargs))

        timeout = kwargs.get('timeout', DEFAULT_CMD_TIMEOUT)
        write_delay = kwargs.get('write_delay', DEFAULT_WRITE_DELAY)

        build_handler = self._build_handlers.get(cmd, None)
        if not callable(build_handler):
            log.error('_do_cmd_no_resp: no handler for command: %s' % cmd)
            raise InstrumentProtocolException(error_code=InstErrorCode.BAD_DRIVER_COMMAND)
        cmd_line = build_handler(cmd, *args)

        # Wakeup the device, timeout exception as needed
        self._wakeup(timeout, connection=connection)

        # Clear line and prompt buffers for result, then send command.
        self._linebuf[connection] = ''
        self._promptbuf[connection] = ''
        self._send_data(cmd_line, write_delay, connection=connection)

    def _do_cmd_direct(self, cmd, connection=None):
        """
        Issue an untranslated command to the instrument. No response is handled
        as a result of the command.

        @param cmd The high level command to issue
        """
        # Send command.
        self._send_data(cmd, connection=connection)

    ########################################################################
    # Incoming data (for parsing) callback.
    ########################################################################
    def got_data(self, port_agent_packet, connection=None):
        """
        Called by the instrument connection when data is available.
        Append line and prompt buffers.

        Also add data to the chunker and when received call got_chunk
        to publish results.
        """
        if connection is None:
            raise InstrumentProtocolException('got_data: no connection supplied!')

        data_length = port_agent_packet.get_data_length()
        data = port_agent_packet.get_data()
        timestamp = port_agent_packet.get_timestamp()

        log.debug("Got Data: %r %r", connection, data)
        log.debug("Add Port Agent Timestamp: %r %s", connection, timestamp)

        if data_length > 0:
            if self.get_current_state() == DriverProtocolState.DIRECT_ACCESS:
                self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, data)

            self.add_to_buffer(data, connection=connection)

            self.chunkers[connection].add_chunk(data, timestamp)
            (timestamp, chunk) = self.chunkers[connection].get_next_data()
            while chunk:
                self._got_chunk(chunk, timestamp, connection=connection)
                (timestamp, chunk) = self.chunkers[connection].get_next_data()

    ########################################################################
    # Incoming raw data callback.
    ########################################################################
    def got_raw(self, port_agent_packet, connection=None):
        """
        Called by the port agent client when raw data is available, such as data
        sent by the driver to the instrument, the instrument responses,etc.
        """
        self.publish_raw(port_agent_packet, connection)

    def publish_raw(self, port_agent_packet, connection=None):
        """
        Publish raw data
        @param: port_agent_packet port agent packet containing raw
        """
        if connection == SlaveProtocol.FOURBEAM:
            particle_class = RawDataParticle
        else:
            particle_class = RawDataParticle5
        particle = particle_class(port_agent_packet.get_as_dict(),
                                  port_timestamp=port_agent_packet.get_timestamp())

        if self._driver_event:
            self._driver_event(DriverAsyncEvent.SAMPLE, particle.generate())

    def add_to_buffer(self, data, connection=None):
        """
        Add a chunk of data to the internal data buffers
        buffers implemented as lifo ring buffer
        @param data: bytes to add to the buffer
        """
        # Update the line and prompt buffers.
        self._linebuf[connection] += data
        self._promptbuf[connection] += data
        self._last_data_timestamp[connection] = time.time()

        # If our buffer exceeds the max allowable size then drop the leading
        # characters on the floor.
        if len(self._linebuf[connection]) > self._max_buffer_size():
            self._linebuf[connection] = self._linebuf[connection][self._max_buffer_size() * -1:]

        # If our buffer exceeds the max allowable size then drop the leading
        # characters on the floor.
        if len(self._promptbuf[connection]) > self._max_buffer_size():
            self._promptbuf[connection] = self._promptbuf[connection][self._max_buffer_size() * -1:]

        log.debug("LINE BUF: %r", self._linebuf[connection][-50:])
        log.debug("PROMPT BUF: %r", self._promptbuf[connection][-50:])

    ########################################################################
    # Wakeup helpers.
    ########################################################################

    def _send_wakeup(self, connection=None):
        """
        Send a wakeup to the device. Overridden by device specific
        subclasses.
        """
        self.connections[connection].send(NEWLINE)

    def _wakeup(self, timeout, delay=1, connection=None):
        """
        Clear buffers and send a wakeup command to the instrument
        @param timeout The timeout to wake the device.
        @param delay The time to wait between consecutive wakeups.
        @throw InstrumentTimeoutException if the device could not be woken.
        """
        if connection is None:
            raise InstrumentProtocolException('_wakeup: no connection supplied!')

        # Clear the prompt buffer.
        log.trace("clearing promptbuf: %r", self._promptbuf)
        self._promptbuf[connection] = ''

        # Grab time for timeout.
        starttime = time.time()

        while True:
            # Send a line return and wait a sec.
            log.trace('Sending wakeup. timeout=%s', timeout)
            self._send_wakeup(connection=connection)
            time.sleep(delay)

            log.trace("Prompts: %s", self._get_prompts())

            for item in self._get_prompts():
                log.trace("buffer: %r", self._promptbuf[connection])
                log.trace("find prompt: %r", item)
                index = self._promptbuf[connection].find(item)
                log.trace("Got prompt (index: %s): %r ", index, self._promptbuf[connection])
                if index >= 0:
                    log.trace('wakeup got prompt: %r', item)
                    return item
            log.trace("Searched for all prompts")

            if time.time() > starttime + timeout:
                raise InstrumentTimeoutException("in _wakeup()")

    def _build_param_dict(self):
        # We're going to build two complete sets of ADCP parameters here
        # one set for the master instrument and one for the slave
        for param in parameter_regexes:
            self._param_dict.add(param,
                                 parameter_regexes.get(param),
                                 parameter_extractors.get(param),
                                 parameter_formatters.get(param),
                                 type=parameter_types.get(param),
                                 display_name=parameter_names.get(param),
                                 value_description=parameter_descriptions.get(param),
                                 startup_param=parameter_startup.get(param, False),
                                 direct_access=parameter_direct.get(param, False),
                                 visibility=parameter_visibility.get(param, ParameterDictVisibility.READ_WRITE),
                                 default_value=master_parameter_defaults.get(param),
                                 units=parameter_units.get(param))

        for param in parameter_regexes:
            # Scheduled events are handled by the master
            if WorkhorseEngineeringParameter.has(param):
                continue
            self._param_dict.add(param + '_5th',
                                 r'DONTMATCHMEIMNOTREAL!',
                                 parameter_extractors.get(param),
                                 parameter_formatters.get(param),
                                 type=parameter_types.get(param),
                                 display_name=parameter_names.get(param),
                                 value_description=parameter_descriptions.get(param),
                                 startup_param=parameter_startup.get(param, False),
                                 direct_access=parameter_direct.get(param, False),
                                 visibility=parameter_visibility.get(param, ParameterDictVisibility.READ_WRITE),
                                 default_value=slave_parameter_defaults.get(param),
                                 units=parameter_units.get(param))

        self._param_dict.set_default(WorkhorseParameter.CLOCK_SYNCH_INTERVAL)
        self._param_dict.set_default(WorkhorseParameter.GET_STATUS_INTERVAL)

        # now we're going to build a whole 'nother param dict for the slave parameters
        # that contain regex values so we can fill them in easily...
        for param in parameter_regexes:
            # Scheduled events are handled by the master
            if WorkhorseEngineeringParameter.has(param):
                continue
            self._param_dict2.add(param + '_5th',
                                  parameter_regexes.get(param),
                                  parameter_extractors.get(param),
                                  parameter_formatters.get(param))

    # #######################################################################
    # Private helpers.
    # #######################################################################
    def _got_chunk(self, chunk, timestamp, connection=None):
        """
        The base class got_data has gotten a chunk from the chunker.
        Pass it to extract_sample with the appropriate particle
        objects and REGEXes.
        """
        if connection == SlaveProtocol.FIFTHBEAM:
            if self._extract_sample(VadcpCompassCalibrationDataParticle,
                                    ADCP_COMPASS_CALIBRATION_REGEX_MATCHER,
                                    chunk,
                                    timestamp):
                # if self.get_current_state() == WorkhorseProtocolState.COMMAND:
                #     self._async_raise_fsm_event(WorkhorseProtocolEvent.RECOVER_AUTOSAMPLE)
                return

            if self._extract_sample(VadcpPd0SlaveDataParticle,
                                    ADCP_PD0_PARSED_REGEX_MATCHER,
                                    chunk,
                                    timestamp):
                return

            if self._extract_sample(VadcpSystemConfigurationDataParticle5,
                                    ADCP_SYSTEM_CONFIGURATION_REGEX_MATCHER,
                                    chunk,
                                    timestamp):
                return

            if self._extract_sample(VadcpAncillarySystemDataParticle,
                                    ADCP_ANCILLARY_SYSTEM_DATA_REGEX_MATCHER,
                                    chunk,
                                    timestamp):
                return

            if self._extract_sample(VadcpTransmitPathParticle,
                                    ADCP_TRANSMIT_PATH_REGEX_MATCHER,
                                    chunk,
                                    timestamp):
                return

        elif connection == SlaveProtocol.FOURBEAM:
            if self._extract_sample(AdcpCompassCalibrationDataParticle,
                                    ADCP_COMPASS_CALIBRATION_REGEX_MATCHER,
                                    chunk,
                                    timestamp):
                # if self.get_current_state() == WorkhorseProtocolState.COMMAND:
                #     self._async_raise_fsm_event(WorkhorseProtocolEvent.RECOVER_AUTOSAMPLE)
                return

            if self._extract_sample(VadcpPd0BeamParsedDataParticle,
                                    ADCP_PD0_PARSED_REGEX_MATCHER,
                                    chunk,
                                    timestamp):
                return

            if self._extract_sample(VadcpSystemConfigurationDataParticle,
                                    ADCP_SYSTEM_CONFIGURATION_REGEX_MATCHER,
                                    chunk,
                                    timestamp):
                return

            if self._extract_sample(AdcpAncillarySystemDataParticle,
                                    ADCP_ANCILLARY_SYSTEM_DATA_REGEX_MATCHER,
                                    chunk,
                                    timestamp):
                return

            if self._extract_sample(AdcpTransmitPathParticle,
                                    ADCP_TRANSMIT_PATH_REGEX_MATCHER,
                                    chunk,
                                    timestamp):
                return

    def _send_break_cmd(self, delay, connection=None):
        """
        Send a BREAK to attempt to wake the device.
        """
        self.connections[connection].send_break(delay)

    def _sync_clock(self, command, date_time_param, timeout=TIMEOUT, delay=1, time_format="%Y/%m/%d,%H:%M:%S"):
        """
        Send the command to the instrument to synchronize the clock
        @param command set command
        @param date_time_param: date time parameter that we want to set
        @param timeout: command timeout
        @param delay: wakeup delay
        @param time_format: time format string for set command
        """
        log.info("SYNCING TIME WITH SENSOR.")
        for connection in self.connections:
            self._do_cmd_resp(command, date_time_param, get_timestamp_delayed("%Y/%m/%d, %H:%M:%S"),
                              timeout=timeout, connection=connection)

    # #######################################################################
    # Startup parameter handlers
    ########################################################################

    def _get_params(self, parameters, connection):
        command = NEWLINE.join(['%s?' % p for p in parameters]) + NEWLINE

        if len(parameters) > 1:
            regex = re.compile(r'(%s.*?%s.*?>)' % (parameters[0], parameters[-1]), re.DOTALL)
        else:
            regex = re.compile(r'(%s.*?>)' % parameters[0], re.DOTALL)

        self._linebuf[connection] = ''
        self._promptbuf[connection] = ''
        self._do_cmd_direct(command, connection=connection)
        return self._get_response(response_regex=regex, connection=connection)

    def _update_params(self, *args, **kwargs):
        """
        Update the parameter dictionary.
        """
        # see if we passed in a list of parameters to query
        # if not, use the whole parameter list
        parameters = kwargs.get('params')
        if parameters is None or WorkhorseParameter.ALL in parameters:
            parameters = self._param_dict.get_keys()
        # filter out the engineering parameters and ALL
        parameters = [p for p in parameters if not WorkhorseEngineeringParameter.has(p) and p != WorkhorseParameter.ALL]

        # Get old param dict config.
        old_config = self._param_dict.get_config()

        if parameters:
            # MASTER
            master_params = [p for p in parameters if '_5th' not in p]
            if master_params:
                resp = self._get_params(master_params, SlaveProtocol.FOURBEAM)
                self._param_dict.update_many(resp)

            # SLAVE
            slave_params = [p.replace('_5th', '') for p in parameters if '_5th' in p]
            if slave_params:
                resp = self._get_params(slave_params, SlaveProtocol.FIFTHBEAM)
                self._param_dict2.update_many(resp)
                for key, value in self._param_dict2.get_all().iteritems():
                    self._param_dict.set_value(key, value)

        new_config = self._param_dict.get_config()

        # Check if there is any changes. Ignore TT
        if not dict_equal(new_config, old_config, ['TT']) or kwargs.get('force'):
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def _execute_set_params(self, commands, connection):
        if commands:
            # we are going to send the concatenation of all our set commands
            self._linebuf[connection] = ''
            self._do_cmd_direct(''.join(commands), connection=connection)
            # we'll need to build a regular expression to retrieve all of the responses
            # including any possible errors
            if len(commands) == 1:
                regex = re.compile(r'(%s.*?)\r\n>' % commands[-1].strip(), re.DOTALL)
            else:
                regex = re.compile(r'(%s.*?%s.*?)\r\n>' % (commands[0].strip(), commands[-1].strip()), re.DOTALL)
            response = self._get_response(response_regex=regex, connection=connection)
            self._parse_set_response(response[0], None)

    def _set_params(self, *args, **kwargs):
        """
        Issue commands to the instrument to set various parameters
        """
        self._verify_not_readonly(*args, **kwargs)
        params = args[0]
        changed = []

        old_config = self._param_dict.get_config()

        master_commands = []
        slave_commands = []
        for key, val in params.iteritems():
            if WorkhorseEngineeringParameter.has(key):
                continue
            if val != old_config.get(key):
                changed.append(key)
                if '_5th' in key:
                    slave_commands.append(self._build_set_command(
                        WorkhorseInstrumentCmds.SET, key.replace('_5th', ''), val))
                else:
                    master_commands.append(self._build_set_command(WorkhorseInstrumentCmds.SET, key, val))

        self._execute_set_params(master_commands, connection=SlaveProtocol.FOURBEAM)
        self._execute_set_params(slave_commands, connection=SlaveProtocol.FIFTHBEAM)

        # Handle engineering parameters
        force = False

        if WorkhorseParameter.CLOCK_SYNCH_INTERVAL in params:
            if (params[WorkhorseParameter.CLOCK_SYNCH_INTERVAL] != self._param_dict.get(
                    WorkhorseParameter.CLOCK_SYNCH_INTERVAL)):
                self._param_dict.set_value(WorkhorseParameter.CLOCK_SYNCH_INTERVAL,
                                           params[WorkhorseParameter.CLOCK_SYNCH_INTERVAL])
                self.start_scheduled_job(WorkhorseParameter.CLOCK_SYNCH_INTERVAL, WorkhorseScheduledJob.CLOCK_SYNC,
                                         WorkhorseProtocolEvent.SCHEDULED_CLOCK_SYNC)
                force = True

        if WorkhorseParameter.GET_STATUS_INTERVAL in params:
            if (params[WorkhorseParameter.GET_STATUS_INTERVAL] != self._param_dict.get(
                    WorkhorseParameter.GET_STATUS_INTERVAL)):
                self._param_dict.set_value(WorkhorseParameter.GET_STATUS_INTERVAL,
                                           params[WorkhorseParameter.GET_STATUS_INTERVAL])
                self.start_scheduled_job(WorkhorseParameter.GET_STATUS_INTERVAL,
                                         WorkhorseScheduledJob.GET_CONFIGURATION,
                                         WorkhorseProtocolEvent.SCHEDULED_GET_STATUS)
                force = True

        self._update_params(params=changed, force=force)
        return None

    def _send_break(self, duration=1000, connection=None):
        """
        Send a BREAK to attempt to wake the device.
        """
        self._linebuf[connection] = ''
        self._promptbuf[connection] = ''
        self._send_break_cmd(duration, connection=connection)
        self._get_response(expected_prompt=WorkhorsePrompt.BREAK, connection=connection)

    def _start_logging(self, timeout=TIMEOUT, connection=None):
        """
        Command the instrument to start logging
        @param timeout: how long to wait for a prompt
        @throws: InstrumentProtocolException if failed to start logging
        """
        try:
            start = WorkhorseInstrumentCmds.START_LOGGING
            # start the slave first, it collects on signal from master
            self._do_cmd_resp(start, timeout=timeout, connection=SlaveProtocol.FIFTHBEAM)
            self._do_cmd_resp(start, timeout=timeout, connection=SlaveProtocol.FOURBEAM)
        except InstrumentException:
            self._stop_logging()
            raise

    def _stop_logging(self):
        # stop the master first (slave only collects on signal from master)
        self._send_break(connection=SlaveProtocol.FOURBEAM)
        self._send_break(connection=SlaveProtocol.FIFTHBEAM)

    def _discover(self, connection=None):
        """
        Discover current state; can be COMMAND or AUTOSAMPLE or UNKNOWN.
        @return (next_protocol_state, next_agent_state)
        @throws InstrumentTimeoutException if the device cannot be woken.
        @throws InstrumentStateException if the device response does not correspond to
        an expected state.
        """
        states = set()
        command = (WorkhorseProtocolState.COMMAND, ResourceAgentState.COMMAND)
        auto = (WorkhorseProtocolState.AUTOSAMPLE, ResourceAgentState.STREAMING)
        for connection in self.connections:
            try:
                self._wakeup(3, connection=connection)
                states.add(command)
            except InstrumentException:
                states.add(auto)

        if len(states) == 1:
            # states match, return this state
            return states.pop()

        # states don't match
        self._stop_logging()
        return command

    def _run_test(self, *args, **kwargs):
        kwargs['timeout'] = 30
        kwargs['expected_prompt'] = WorkhorsePrompt.COMMAND
        result = []
        for connection in self.connections:
            result.append(connection)
            kwargs['connection'] = connection
            result.append(self._do_cmd_resp(WorkhorseInstrumentCmds.RUN_TEST_200, *args, **kwargs))
        return NEWLINE.join(result)

    @contextmanager
    def _pause_logging(self):
        try:
            self._stop_logging()
            yield
        finally:
            self._start_logging()

    ########################################################################
    # COMMAND handlers.
    ########################################################################

    def _handler_command_acquire_status(self, *args, **kwargs):
        """
        execute a get status
        @return next_state, (next_agent_state, result) if successful.
        @throws InstrumentProtocolException from _do_cmd_resp.
        """
        a = super(Protocol, self)._handler_command_acquire_status(connection=SlaveProtocol.FOURBEAM)
        b = super(Protocol, self)._handler_command_acquire_status(connection=SlaveProtocol.FIFTHBEAM)
        return None, (None, a[1][1] + b[1][1])

    def _handler_command_recover_autosample(self):
        log.info('PD0 sample detected in COMMAND, not allowed in VADCP. Sending break')
        self._stop_logging()

    ######################################################
    # DIRECT_ACCESS handlers
    ######################################################

    def _handler_direct_access_execute_direct(self, data):
        next_state = None
        result = None
        next_agent_state = None
        self._do_cmd_direct(data)

        # add sent command to list for 'echo' filtering in callback
        self._sent_cmds.append(data)
        return next_state, (next_agent_state, result)
예제 #55
0
    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(
            Parameter.FLUSH_VOLUME,
            r'Flush Volume: (.*)mL',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FLUSH_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Flush Volume",
            description=
            "Amount of sea water to flush prior to taking sample: (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FLUSH_FLOWRATE,
                             r'Flush Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FLUSH_RATE,
                             units=Prefixes.MILLI + Units.LITER + '/' +
                             Units.MINUTE,
                             startup_param=True,
                             display_name="Flush Flow Rate",
                             description="Rate at which to flush: (100 - 250)",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.FLUSH_MINFLOW,
            r'Flush Min Flow: (.*)mL/min',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FLUSH_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + '/' + Units.MINUTE,
            startup_param=True,
            display_name="Flush Minimum Flow",
            description=
            "If the flow rate falls below this value the flush will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.FILL_VOLUME,
            r'Fill Volume: (.*)mL',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Fill Volume",
            description=
            "Amount of seawater to run through the collection filter (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.FILL_FLOWRATE,
            r'Fill Flow Rate: (.*)mL/min',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_RATE,
            units=Prefixes.MILLI + Units.LITER + '/' + Units.MINUTE,
            startup_param=True,
            display_name="Fill Flow Rate",
            description="Flow rate during sampling: (100 - 250)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.FILL_MINFLOW,
            r'Fill Min Flow: (.*)mL/min',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=FILL_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + '/' + Units.MINUTE,
            startup_param=True,
            display_name="Fill Minimum Flow",
            description=
            "If the flow rate falls below this value the fill will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.CLEAR_VOLUME,
            r'Reverse Volume: (.*)mL',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=CLEAR_VOLUME,
            units=Prefixes.MILLI + Units.LITER,
            startup_param=True,
            display_name="Clear Volume",
            description=
            "Amount of sea water to flush the home port after taking sample: (10 - 20000)",
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_FLOWRATE,
                             r'Reverse Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=CLEAR_RATE,
                             units=Prefixes.MILLI + Units.LITER + '/' +
                             Units.MINUTE,
                             startup_param=True,
                             display_name="Clear Flow Rate",
                             description="Rate at which to flush: (100 - 250)",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.CLEAR_MINFLOW,
            r'Reverse Min Flow: (.*)mL/min',
            None,
            self._int_to_string,
            type=ParameterDictType.INT,
            default_value=CLEAR_MIN_RATE,
            units=Prefixes.MILLI + Units.LITER + '/' + Units.MINUTE,
            startup_param=True,
            display_name="Clear Minimum Flow",
            description=
            "If the flow rate falls below this value the reverse flush will be terminated, "
            "obstruction suspected: (75 - 100)",
            visibility=ParameterDictVisibility.IMMUTABLE)

        self._param_dict.set_value(Parameter.FLUSH_VOLUME, FLUSH_VOLUME)
        self._param_dict.set_value(Parameter.FLUSH_FLOWRATE, FLUSH_RATE)
        self._param_dict.set_value(Parameter.FLUSH_MINFLOW, FLUSH_MIN_RATE)
        self._param_dict.set_value(Parameter.FILL_VOLUME, FILL_VOLUME)
        self._param_dict.set_value(Parameter.FILL_FLOWRATE, FILL_RATE)
        self._param_dict.set_value(Parameter.FILL_MINFLOW, FILL_MIN_RATE)
        self._param_dict.set_value(Parameter.CLEAR_VOLUME, CLEAR_VOLUME)
        self._param_dict.set_value(Parameter.CLEAR_FLOWRATE, CLEAR_RATE)
        self._param_dict.set_value(Parameter.CLEAR_MINFLOW, CLEAR_MIN_RATE)
예제 #56
0
class Protocol(McLaneProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    def __init__(self, prompts, newline, driver_event):
        """
        Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        McLaneProtocol.__init__(self, prompts, newline, driver_event)

        # TODO - reset next_port on mechanical refresh of the RAS bags - how is the driver notified?
        # TODO - need to persist state for next_port to save driver restart
        self.next_port = 1  # next available port

        # TODO - on init, perform the following commands:
        # - VERSION
        # - CAPACITY (pump flow)
        # - BATTERY
        # - CODES (termination codes)
        # - COPYRIGHT (termination codes)

    def _got_chunk(self, chunk, timestamp):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and REGEXes.
        """
        filling = self.get_current_state() == ProtocolState.FILL
        log.debug("_got_chunk:\n%s", chunk)
        sample_dict = self._extract_sample(
            RASFLSampleDataParticle,
            RASFLSampleDataParticle.regex_compiled(),
            chunk,
            timestamp,
            publish=filling)

        if sample_dict:
            self._linebuf = ''
            self._promptbuf = ''
            self._protocol_fsm.on_event(
                ProtocolEvent.PUMP_STATUS,
                RASFLSampleDataParticle.regex_compiled().search(chunk))

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with XR-420 parameters.
        For each parameter key add value formatting function for set commands.
        """
        # The parameter dictionary.
        self._param_dict = ProtocolParameterDict()

        # Add parameter handlers to parameter dictionary for instrument configuration parameters.
        self._param_dict.add(Parameter.FLUSH_VOLUME,
                             r'Flush Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FLUSH_VOLUME,
                             units='mL',
                             startup_param=True,
                             display_name="flush_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FLUSH_FLOWRATE,
                             r'Flush Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FLUSH_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="flush_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FLUSH_MINFLOW,
                             r'Flush Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FLUSH_MIN_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="flush_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_VOLUME,
                             r'Fill Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FILL_VOLUME,
                             units='mL',
                             startup_param=True,
                             display_name="fill_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_FLOWRATE,
                             r'Fill Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FILL_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="fill_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.FILL_MINFLOW,
                             r'Fill Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=FILL_MIN_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="fill_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_VOLUME,
                             r'Reverse Volume: (.*)mL',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=CLEAR_VOLUME,
                             units='mL',
                             startup_param=True,
                             display_name="clear_volume",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_FLOWRATE,
                             r'Reverse Flow Rate: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=CLEAR_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="clear_flow_rate",
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.CLEAR_MINFLOW,
                             r'Reverse Min Flow: (.*)mL/min',
                             None,
                             self._int_to_string,
                             type=ParameterDictType.INT,
                             default_value=CLEAR_MIN_RATE,
                             units='mL/min',
                             startup_param=True,
                             display_name="clear_min_flow",
                             visibility=ParameterDictVisibility.IMMUTABLE)

        self._param_dict.set_value(Parameter.FLUSH_VOLUME, FLUSH_VOLUME)
        self._param_dict.set_value(Parameter.FLUSH_FLOWRATE, FLUSH_RATE)
        self._param_dict.set_value(Parameter.FLUSH_MINFLOW, FLUSH_MIN_RATE)
        self._param_dict.set_value(Parameter.FILL_VOLUME, FILL_VOLUME)
        self._param_dict.set_value(Parameter.FILL_FLOWRATE, FILL_RATE)
        self._param_dict.set_value(Parameter.FILL_MINFLOW, FILL_MIN_RATE)
        self._param_dict.set_value(Parameter.CLEAR_VOLUME, CLEAR_VOLUME)
        self._param_dict.set_value(Parameter.CLEAR_FLOWRATE, CLEAR_RATE)
        self._param_dict.set_value(Parameter.CLEAR_MINFLOW, CLEAR_MIN_RATE)