def setUp(self):
        """
        """
        self.prompts = [">"]
        self.newline = "\n"
        self.callback_result = None
        self._trigger_count = 0
        self._events = []

        self.protocol = CommandResponseInstrumentProtocol(
            self.prompts, self.newline, self.event_callback)

        self.protocol_fsm = ThreadSafeFSM(self.TestState, self.TestEvent,
                                          self.TestEvent.ENTER,
                                          self.TestEvent.EXIT)

        self.protocol_fsm.add_handler(self.TestState.TEST, self.TestEvent.TEST,
                                      lambda x: x)

        self.protocol._add_build_handler(self.TestEvent.TEST,
                                         self._build_simple_command)
        self.protocol._add_response_handler(self.TestEvent.TEST,
                                            self._parse_test_response)
        self.protocol._connection = Mock()
        self.protocol._connection.send = lambda x: self.protocol.add_to_buffer(
            "%s >->" % x)
        self.protocol.get_current_state = Mock(
            return_value=self.TestState.TEST)
        self.protocol._send_wakeup = lambda: self.protocol.add_to_buffer(
            "wakeup response >->")
        self.protocol._wakeup = functools.partial(self.protocol._wakeup,
                                                  delay=0)
Exemplo n.º 2
0
    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.
        """

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

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

        # Build protocol state machine.

        self._protocol_fsm.add_handler(
            ProtocolState.COMMAND, ProtocolEvent.RUN_EXTERNAL_PUMP,
            self._handler_command_run_external_pump)

        # this state would be entered whenever a RUN_EXTERNAL_PUMP event
        # occurred while in the COMMAND state
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.ENTER,
            self._execution_state_enter)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.EXIT,
            self._execution_state_exit)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.EXECUTE,
            self._handler_run_external_pump_execute)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.SUCCESS,
            self._execution_success_to_command_state)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.TIMEOUT,
            self._execution_timeout_to_command_state)
        ## Events to queue - intended for schedulable events occurring when a sample is being taken
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.ACQUIRE_STATUS,
            self._handler_queue_acquire_status)

        # Add build handlers for device commands.
        ### primarily defined in base class
        self._add_build_handler(InstrumentCommand.PCO2WB_ACQUIRE_SAMPLE_DEV1, self._build_simple_command)
        # Add response handlers for device commands.
        ### primarily defined in base class
        self._add_response_handler(InstrumentCommand.PCO2WB_ACQUIRE_SAMPLE_DEV1, self._parse_response_sample_dev1)

        # Add sample handlers

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

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

        self._engineering_parameters.append(Parameter.EXTERNAL_PUMP_DELAY)
Exemplo n.º 3
0
    def _construct_fsm(self, states=PlatformDriverState,
                       events=PlatformDriverEvent,
                       enter_event=PlatformDriverEvent.ENTER,
                       exit_event=PlatformDriverEvent.EXIT):
        """
        Constructs the FSM for the driver. The preparations here are mostly
        related with the UNCONFIGURED, DISCONNECTED, and CONNECTED state
        transitions, with some common handlers for the CONNECTED state.
        Subclasses can override to indicate specific parameters and add new
        handlers (typically for the CONNECTED state).
        """
        log.debug("constructing base platform driver FSM")

        self._fsm = ThreadSafeFSM(states, events, enter_event, exit_event)

        for state in PlatformDriverState.list():
            self._fsm.add_handler(state, enter_event, self._common_state_enter)
            self._fsm.add_handler(state, exit_event, self._common_state_exit)

        # UNCONFIGURED state event handlers:
        self._fsm.add_handler(PlatformDriverState.UNCONFIGURED, PlatformDriverEvent.CONFIGURE, self._handler_unconfigured_configure)

        # DISCONNECTED state event handlers:
        self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.CONNECT, self._handler_disconnected_connect)
        self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_disconnected_disconnect)

        # CONNECTED state event handlers:
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_connected_disconnect)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost)

        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.PING, self._handler_connected_ping)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET, self._handler_connected_get)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.SET, self._handler_connected_set)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.EXECUTE, self._handler_connected_execute)
Exemplo n.º 4
0
    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.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.START_AUTOSAMPLE,
                                       self._handler_command_autosample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.ACQUIRE_STATUS,
                                       self._handler_command_acquire_status)
        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.AUTOSAMPLE,
                                       ProtocolEvent.STOP_AUTOSAMPLE,
                                       self._handler_autosample_stop)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE,
                                       ProtocolEvent.GET,
                                       self._handler_command_get)

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_driver_dict()
        self._build_command_dict()
        self._build_param_dict()

        # 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 = []
Exemplo n.º 5
0
    def __init__(self, driver_event):
        super(Protocol, self).__init__(driver_event)
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent,
                                           ProtocolEvent.ENTER, ProtocolEvent.EXIT)

        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.GET, self._handler_get),
                (ProtocolEvent.SET, self._handler_set),
                (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample),
            ),
            ProtocolState.AUTOSAMPLE: (
                (ProtocolEvent.ENTER, self._handler_autosample_enter),
                (ProtocolEvent.EXIT, self._handler_autosample_exit),
                (ProtocolEvent.GET, self._handler_get),
                (ProtocolEvent.FLUSH, self._flush),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample),
            ),
            ProtocolState.STOPPING: (
                (ProtocolEvent.ENTER, self._handler_stopping_enter),
                (ProtocolEvent.EXIT, self._handler_stopping_exit),
                (ProtocolEvent.FLUSH, self._flush),
            ),
            ProtocolState.WRITE_ERROR: (
                (ProtocolEvent.ENTER, self._handler_write_error_enter),
                (ProtocolEvent.EXIT, self._handler_write_error_exit),
                (ProtocolEvent.CLEAR_WRITE_ERROR, self._handler_clear_write_error),
            )}

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

        # Build dictionaries for driver schema
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # State state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)
        self._logs = {}
        self._filled_logs = []
        self._pickle_cache = []

        # persistent store, cannot initialize until startup config has been applied
        # since we need the address for postgres
        self._persistent_store = None

        # lock for flush actions to prevent writing or altering the data files
        # during flush
        self._lock = Lock()
        self._pktid = 0
Exemplo n.º 6
0
    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 __init__(self, event_callback):
        """
        Constructor for singly connected instrument drivers.
        @param event_callback Callback to the driver process to send asynchronous
        driver events back to the agent.
        """
        InstrumentDriver.__init__(self, event_callback)
        
        # The only and only instrument connection.
        # Exists in the connected state.
        self._connection = None

        # The one and only instrument protocol.
        self._protocol = None
        
        # Build connection state machine.
        self._connection_fsm = ThreadSafeFSM(DriverConnectionState,
                                                DriverEvent,
                                                DriverEvent.ENTER,
                                                DriverEvent.EXIT)
        
        # Add handlers for all events.
        self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.ENTER, self._handler_unconfigured_enter)
        self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.EXIT, self._handler_unconfigured_exit)
        self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.INITIALIZE, self._handler_unconfigured_initialize)
        self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.CONFIGURE, self._handler_unconfigured_configure)
        self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.ENTER, self._handler_disconnected_enter)
        self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.EXIT, self._handler_disconnected_exit)
        self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.INITIALIZE, self._handler_disconnected_initialize)
        self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.CONFIGURE, self._handler_disconnected_configure)
        self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.CONNECT, self._handler_disconnected_connect)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.ENTER, self._handler_connected_enter)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.EXIT, self._handler_connected_exit)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.DISCONNECT, self._handler_connected_disconnect)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.DISCOVER, self._handler_connected_protocol_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.GET, self._handler_connected_protocol_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.SET, self._handler_connected_protocol_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.EXECUTE, self._handler_connected_protocol_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.FORCE_STATE, self._handler_connected_protocol_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.START_DIRECT, self._handler_connected_start_direct_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.STOP_DIRECT, self._handler_connected_stop_direct_event)
        
            
        # Start state machine.
        self._connection_fsm.start(DriverConnectionState.UNCONFIGURED)
        
        self._pre_da_config = {}
        self._startup_config = {}
        
        # Idempotency flag for lost connections.
        # This set to false when a connection is established to
        # allow for lost callback to become activated.
        self._connection_lost = True
Exemplo n.º 8
0
    def __init__(self, driver_event):
        super(Protocol, self).__init__(driver_event)
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent,
                                           ProtocolEvent.ENTER,
                                           ProtocolEvent.EXIT)

        handlers = {
            ProtocolState.UNKNOWN: (
                (ProtocolEvent.ENTER, self._handler_unknown_enter),
                (ProtocolEvent.EXIT, self._handler_unknown_exit),
                (ProtocolEvent.DISCOVER, self._handler_unknown_discover),
            ),
            ProtocolState.AUTOSAMPLE: (
                (ProtocolEvent.ENTER, self._handler_autosample_enter),
                (ProtocolEvent.EXIT, self._handler_autosample_exit),
                (ProtocolEvent.GET, self._handler_get),
                (ProtocolEvent.FLUSH, self._flush),
                (ProtocolEvent.CONFIG_ERROR, self._handler_config_error),
            ),
            ProtocolState.WRITE_ERROR: (
                (ProtocolEvent.ENTER, self._handler_error_enter),
                (ProtocolEvent.EXIT, self._handler_error_exit),
            ),
            ProtocolState.CONFIG_ERROR: (
                (ProtocolEvent.ENTER, self._handler_error_enter),
                (ProtocolEvent.EXIT, self._handler_error_exit),
            )
        }

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

        # Build dictionaries for driver schema
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # State state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)
        self._logs = {}
        self._filled_logs = []
        self._pickle_cache = []

        self._persistent_store = None

        # lock for flush actions to prevent writing or altering the data files
        # during flush
        self._lock = Lock()
        self._pktid = None
Exemplo n.º 9
0
    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.
        """

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

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

        # Build protocol state machine.

        self._protocol_fsm.add_handler(
            ProtocolState.COMMAND, ProtocolEvent.RUN_EXTERNAL_PUMP,
            self._handler_command_run_external_pump)

        # this state would be entered whenever a RUN_EXTERNAL_PUMP event
        # occurred while in the COMMAND state
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.ENTER,
            self._execution_state_enter)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.EXIT,
            self._execution_state_exit)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.EXECUTE,
            self._handler_run_external_pump_execute)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.SUCCESS,
            self._execution_success_to_command_state)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.TIMEOUT,
            self._execution_timeout_to_command_state)
        ## Events to queue - intended for schedulable events occurring when a sample is being taken
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.ACQUIRE_STATUS,
            self._handler_queue_acquire_status)

        # Add build handlers for device commands.
        ### primarily defined in base class
        self._add_build_handler(InstrumentCommand.PCO2WB_ACQUIRE_SAMPLE_DEV1, self._build_simple_command)
        # Add response handlers for device commands.
        ### primarily defined in base class
        self._add_response_handler(InstrumentCommand.PCO2WB_ACQUIRE_SAMPLE_DEV1, self._parse_response_sample_dev1)

        # Add sample handlers

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

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

        self._engineering_parameters.append(Parameter.EXTERNAL_PUMP_DELAY)
    def setUp(self):
        """
        """
        self.prompts = [">"]
        self.newline = "\n"
        self.callback_result = None
        self._trigger_count = 0
        self._events = []

        self.protocol = CommandResponseInstrumentProtocol(self.prompts,
                                                          self.newline,
                                                          self.event_callback)
                
        self.protocol_fsm = ThreadSafeFSM(self.TestState, self.TestEvent,
                            self.TestEvent.ENTER, self.TestEvent.EXIT)

        self.protocol_fsm.add_handler(self.TestState.TEST, self.TestEvent.TEST, lambda x : x)

        self.protocol._add_build_handler(self.TestEvent.TEST, self._build_simple_command)
        self.protocol._add_response_handler(self.TestEvent.TEST, self._parse_test_response)
        self.protocol._connection = Mock()
        self.protocol._connection.send = lambda x : self.protocol.add_to_buffer("%s >->" % x)
        self.protocol.get_current_state = Mock(return_value=self.TestState.TEST)
        self.protocol._send_wakeup = lambda: self.protocol.add_to_buffer("wakeup response >->")
        self.protocol._wakeup = functools.partial(self.protocol._wakeup, delay=0)
Exemplo n.º 11
0
    def _construct_fsm(self, states=PlatformDriverState,
                       events=PlatformDriverEvent,
                       enter_event=PlatformDriverEvent.ENTER,
                       exit_event=PlatformDriverEvent.EXIT):
        """
        Constructs the FSM for the driver. The preparations here are mostly
        related with the UNCONFIGURED, DISCONNECTED, and CONNECTED state
        transitions, with some common handlers for the CONNECTED state.
        Subclasses can override to indicate specific parameters and add new
        handlers (typically for the CONNECTED state).
        """
        log.debug("constructing base platform driver FSM")

        self._fsm = ThreadSafeFSM(states, events, enter_event, exit_event)

        for state in PlatformDriverState.list():
            self._fsm.add_handler(state, enter_event, self._common_state_enter)
            self._fsm.add_handler(state, exit_event, self._common_state_exit)

        # UNCONFIGURED state event handlers:
        self._fsm.add_handler(PlatformDriverState.UNCONFIGURED, PlatformDriverEvent.CONFIGURE, self._handler_unconfigured_configure)

        # DISCONNECTED state event handlers:
        self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.CONNECT, self._handler_disconnected_connect)
        self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_disconnected_disconnect)

        # CONNECTED state event handlers:
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_connected_disconnect)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost)

        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.PING, self._handler_connected_ping)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET, self._handler_connected_get)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.SET, self._handler_connected_set)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.EXECUTE, self._handler_connected_execute)
Exemplo n.º 12
0
    def __init__(self, driver_event):
        super(Protocol, self).__init__(driver_event)
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT)

        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.GET, self._handler_get),
                (ProtocolEvent.SET, self._handler_set),
                (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample),
            ),
            ProtocolState.AUTOSAMPLE: (
                (ProtocolEvent.ENTER, self._handler_autosample_enter),
                (ProtocolEvent.EXIT, self._handler_autosample_exit),
                (ProtocolEvent.GET, self._handler_get),
                (ProtocolEvent.FLUSH, self._flush),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample),
            ),
            ProtocolState.STOPPING: (
                (ProtocolEvent.ENTER, self._handler_stopping_enter),
                (ProtocolEvent.EXIT, self._handler_stopping_exit),
                (ProtocolEvent.FLUSH, self._flush),
            ),
            ProtocolState.WRITE_ERROR: (
                (ProtocolEvent.ENTER, self._handler_write_error_enter),
                (ProtocolEvent.EXIT, self._handler_write_error_exit),
                (ProtocolEvent.CLEAR_WRITE_ERROR, self._handler_clear_write_error),
            ),
        }

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

        # Build dictionaries for driver schema
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # State state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)
        self._logs = {}
        self._filled_logs = []
        self._pickle_cache = []

        # persistent store, cannot initialize until startup config has been applied
        # since we need the address for postgres
        self._persistent_store = None

        # lock for flush actions to prevent writing or altering the data files
        # during flush
        self._lock = Lock()
        self._pktid = 0
Exemplo n.º 13
0
    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.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.START_AUTOSAMPLE, self._handler_command_autosample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.ACQUIRE_STATUS, self._handler_command_acquire_status)
        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.AUTOSAMPLE,
                                       ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.GET, self._handler_command_get)

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_driver_dict()
        self._build_command_dict()
        self._build_param_dict()

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

        log.info('processing particles with %d workers', POOL_SIZE)
        self._process_particles = True
        self._pending_particles = deque()
        self._processing_pool = multiprocessing.Pool(POOL_SIZE)

        self._particles_thread = Thread(target=self.particles_thread)
        self._particles_thread.setDaemon(True)
        self._particles_thread.start()
    def __init__(self, event_callback):
        """
        Constructor for singly connected instrument drivers.
        @param event_callback Callback to the driver process to send asynchronous
        driver events back to the agent.
        """
        InstrumentDriver.__init__(self, event_callback)
        
        # The only and only instrument connection.
        # Exists in the connected state.
        self._connection = None

        # The one and only instrument protocol.
        self._protocol = None
        
        # Build connection state machine.
        self._connection_fsm = ThreadSafeFSM(DriverConnectionState,
                                                DriverEvent,
                                                DriverEvent.ENTER,
                                                DriverEvent.EXIT)
        
        # Add handlers for all events.
        self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.ENTER, self._handler_unconfigured_enter)
        self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.EXIT, self._handler_unconfigured_exit)
        self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.INITIALIZE, self._handler_unconfigured_initialize)
        self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.CONFIGURE, self._handler_unconfigured_configure)
        self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.ENTER, self._handler_disconnected_enter)
        self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.EXIT, self._handler_disconnected_exit)
        self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.INITIALIZE, self._handler_disconnected_initialize)
        self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.CONFIGURE, self._handler_disconnected_configure)
        self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.CONNECT, self._handler_disconnected_connect)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.ENTER, self._handler_connected_enter)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.EXIT, self._handler_connected_exit)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.DISCONNECT, self._handler_connected_disconnect)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.DISCOVER, self._handler_connected_protocol_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.GET, self._handler_connected_protocol_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.SET, self._handler_connected_protocol_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.EXECUTE, self._handler_connected_protocol_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.FORCE_STATE, self._handler_connected_protocol_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.START_DIRECT, self._handler_connected_start_direct_event)
        self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.STOP_DIRECT, self._handler_connected_stop_direct_event)
        
            
        # Start state machine.
        self._connection_fsm.start(DriverConnectionState.UNCONFIGURED)
        
        self._pre_da_config = {}
        self._startup_config = {}
        
        # Idempotency flag for lost connections.
        # This set to false when a connection is established to
        # allow for lost callback to become activated.
        self._connection_lost = True
Exemplo n.º 15
0
    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 = []
Exemplo n.º 16
0
    def __init__(self, prompts, newline, driver_event):
        """
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The SBE16 newline.
        @param driver_event Driver process event callback.
        """
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline,
                                                   driver_event)

        # This driver does not process commands, the finite state machine and handlers are stubs
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent,
                                           ProtocolEvent.ENTER,
                                           ProtocolEvent.EXIT)

        handlers = {
            ProtocolState.UNKNOWN: {
                (ProtocolEvent.ENTER, self._handler_state_change()),
                (ProtocolEvent.EXIT, self._handler_pass_through()),
                (ProtocolEvent.DISCOVER, self._handler_unknown_discover()),
            },
            ProtocolState.COMMAND: {
                (ProtocolEvent.ENTER, self._handler_state_change()),
                (ProtocolEvent.EXIT, self._handler_pass_through()),
                (ProtocolEvent.GET, self._handler_pass_through()),
            },
        }

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

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

        self._protocol_fsm.start(ProtocolState.UNKNOWN)

        self._chunker = StringChunker(self.sieve_function)
Exemplo n.º 17
0
    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.
        """

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

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

        # Build protocol state machine.

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

        # build the chunker
        self._chunker = StringChunker(Protocol.sieve_function)
Exemplo n.º 18
0
    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.
        """
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline,
                                                   driver_event)

        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent,
                                           ProtocolEvent.ENTER,
                                           ProtocolEvent.EXIT)

        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN,
                                       ProtocolEvent.ENTER,
                                       self._handler_unknown_enter)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN,
                                       ProtocolEvent.EXIT,
                                       self._handler_unknown_exit)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN,
                                       ProtocolEvent.DISCOVER,
                                       self._handler_unknown_discover)

        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE,
                                       ProtocolEvent.ENTER,
                                       self._handler_autosample_enter)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE,
                                       ProtocolEvent.EXIT,
                                       self._handler_autosample_exit)

        self._protocol_fsm.start(ProtocolState.UNKNOWN)

        self._chunker = StringChunker(Protocol.sieve_function)

        self._build_driver_dict()
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')
Exemplo n.º 19
0
    def __init__(self, driver_event):
        super(Protocol, self).__init__(driver_event)
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent,
                                           ProtocolEvent.ENTER, ProtocolEvent.EXIT)

        handlers = {
            ProtocolState.UNKNOWN: (
                (ProtocolEvent.ENTER, self._handler_unknown_enter),
                (ProtocolEvent.EXIT, self._handler_unknown_exit),
                (ProtocolEvent.DISCOVER, self._handler_unknown_discover),
            ),
            ProtocolState.AUTOSAMPLE: (
                (ProtocolEvent.ENTER, self._handler_autosample_enter),
                (ProtocolEvent.EXIT, self._handler_autosample_exit),
                (ProtocolEvent.GET, self._handler_get),
                (ProtocolEvent.FLUSH, self._flush),
                (ProtocolEvent.CONFIG_ERROR, self._handler_config_error),
            ),
            ProtocolState.WRITE_ERROR: (
                (ProtocolEvent.ENTER, self._handler_error_enter),
                (ProtocolEvent.EXIT, self._handler_error_exit),
            ),
            ProtocolState.CONFIG_ERROR: (
                (ProtocolEvent.ENTER, self._handler_error_enter),
                (ProtocolEvent.EXIT, self._handler_error_exit),
            )
        }

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

        # Build dictionaries for driver schema
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # State state machine in UNKNOWN state.
        self._protocol_fsm.start(ProtocolState.UNKNOWN)
        self._logs = {}
        self._filled_logs = []
        self._pickle_cache = []

        self._persistent_store = None

        # lock for flush actions to prevent writing or altering the data files
        # during flush
        self._lock = Lock()
        self._pktid = None
Exemplo n.º 20
0
    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)
Exemplo n.º 21
0
    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.
        """

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

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

        # Build protocol state machine.

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

        # build the chunker
        self._chunker = StringChunker(Protocol.sieve_function)
Exemplo n.º 22
0
    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.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.START_AUTOSAMPLE, self._handler_command_autosample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_STATUS, self._handler_command_acquire_status)
        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.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop)

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_driver_dict()
        self._build_command_dict()
        self._build_param_dict()

        # 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 = []
Exemplo n.º 23
0
    def __init__(self, prompts, newline, driver_event):
        """
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The SBE16 newline.
        @param driver_event Driver process event callback.
        """
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event)

        # This driver does not process commands, the finite state machine and handlers are stubs
        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent,
                                           ProtocolEvent.ENTER, ProtocolEvent.EXIT)

        handlers = {
            ProtocolState.UNKNOWN: {
                (ProtocolEvent.ENTER, self._handler_state_change()),
                (ProtocolEvent.EXIT, self._handler_pass_through()),
                (ProtocolEvent.DISCOVER, self._handler_unknown_discover()),
            },
            ProtocolState.COMMAND: {
                (ProtocolEvent.ENTER, self._handler_state_change()),
                (ProtocolEvent.EXIT, self._handler_pass_through()),
                (ProtocolEvent.GET, self._handler_pass_through()),
            },
        }

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

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

        self._protocol_fsm.start(ProtocolState.UNKNOWN)

        self._chunker = StringChunker(self.sieve_function)
Exemplo n.º 24
0
    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.
        """
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event)

        self._protocol_fsm = ThreadSafeFSM(ProtocolState, ProtocolEvent, ProtocolEvent.ENTER, ProtocolEvent.EXIT)

        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.ENTER, self._handler_unknown_enter)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.EXIT, self._handler_unknown_exit)
        self._protocol_fsm.add_handler(ProtocolState.UNKNOWN, ProtocolEvent.DISCOVER, self._handler_unknown_discover)

        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.ENTER, self._handler_autosample_enter)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.EXIT, self._handler_autosample_exit)

        self._protocol_fsm.start(ProtocolState.UNKNOWN)

        self._chunker = StringChunker(Protocol.sieve_function)

        self._build_driver_dict()
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')
Exemplo n.º 25
0
    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
Exemplo n.º 26
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)
Exemplo n.º 27
0
    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.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_command_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_TURBO, self._handler_command_start_turbo),
            ],
            ProtocolState.SPINNING_UP: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.AT_SPEED, self._handler_spinning_up_at_speed),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.AT_SPEED: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.CLEAR, self._handler_clear),
                (ProtocolEvent.GET, self._handler_command_get),
            ],
            ProtocolState.SPINNING_DOWN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOPPED, self._handler_spinning_down_stopped),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
            ],
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # Add build and response handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_build_handler(command, self._generic_build_handler)
            self._add_response_handler(command, self._generic_response_handler)

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._chunker = StringChunker(Protocol.sieve_function)
        self._max_current_count = 0
        self.initialize_scheduler()
Exemplo n.º 28
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):
        """
Exemplo n.º 29
0
class SBE43Protocol(SBE16Protocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    def __init__(self, prompts, newline, driver_event):
        """
        SBE43Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The SBE43 newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline,
                                                   driver_event)

        # Build SBE19 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_generic_exit),
                (ProtocolEvent.DISCOVER, self._handler_unknown_discover),
            ],
            ProtocolState.COMMAND:
            [(ProtocolEvent.ENTER, self._handler_command_enter),
             (ProtocolEvent.EXIT, self._handler_generic_exit),
             (ProtocolEvent.ACQUIRE_SAMPLE,
              self._handler_command_acquire_sample),
             (ProtocolEvent.START_AUTOSAMPLE,
              self._handler_command_start_autosample),
             (ProtocolEvent.GET, self._handler_get),
             (ProtocolEvent.SET, self._handler_command_set),
             (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
             (ProtocolEvent.CLOCK_SYNC,
              self._handler_command_clock_sync_clock),
             (ProtocolEvent.ACQUIRE_STATUS,
              self._handler_command_acquire_status)],
            ProtocolState.ACQUIRING_SAMPLE: [
                (ProtocolEvent.ENTER, self._handler_acquiring_sample_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_SAMPLE_ASYNC,
                 self._handler_acquire_sample_async),
            ],
            ProtocolState.DIRECT_ACCESS:
            [(ProtocolEvent.ENTER, self._handler_direct_access_enter),
             (ProtocolEvent.EXIT, self._handler_generic_exit),
             (ProtocolEvent.EXECUTE_DIRECT,
              self._handler_direct_access_execute_direct),
             (ProtocolEvent.STOP_DIRECT,
              self._handler_direct_access_stop_direct)],
            ProtocolState.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_autosample_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.GET, self._handler_get),
                (ProtocolEvent.STOP_AUTOSAMPLE,
                 self._handler_autosample_stop_autosample),
                (ProtocolEvent.SCHEDULED_ACQUIRED_STATUS,
                 self._handler_autosample_acquire_status),
            ]
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_driver_dict()
        self._build_command_dict()
        self._build_param_dict()

        # Add build handlers for device commands, only using simple command handler.
        for cmd in Command.list():
            if cmd == Command.SET:
                self._add_build_handler(Command.SET, self._build_set_command)
            else:
                self._add_build_handler(cmd, self._build_simple_command)

        # Add response handlers for device commands.
        # these are here to ensure that correct responses to the commands are received before the next command is sent
        self._add_response_handler(Command.SET, self._parse_set_response)
        self._add_response_handler(Command.GET_SD,
                                   self._validate_GetSD_response)
        self._add_response_handler(Command.GET_HD,
                                   self._validate_GetHD_response)
        self._add_response_handler(Command.GET_CD,
                                   self._validate_GetCD_response)
        self._add_response_handler(Command.GET_CC,
                                   self._validate_GetCC_response)
        self._add_response_handler(Command.GET_EC,
                                   self._validate_GetEC_response)

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

        self._chunker = StringChunker(self.sieve_function)

    def _build_command_dict(self):
        """
        Populate the command dictionary with command. Overridden to specify timeouts.
        """
        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,
                           timeout=ACQUIRE_STATUS_TIMEOUT,
                           display_name="Acquire Status")
        self._cmd_dict.add(Capability.ACQUIRE_SAMPLE,
                           display_name="Acquire Sample")
        self._cmd_dict.add(Capability.DISCOVER,
                           timeout=DISCOVER_TIMEOUT,
                           display_name='Discover')

    def _filter_capabilities(self, events):
        return [x for x in events if Capability.has(x)]

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples
        Over-ride sieve function to handle additional particles.
        """
        matchers = []
        return_list = []

        matchers.append(SBE43DataParticle.regex_compiled())
        matchers.append(SBE43HardwareParticle.regex_compiled())
        matchers.append(SBE43CalibrationParticle.regex_compiled())
        matchers.append(SBE43StatusParticle.regex_compiled())
        matchers.append(SBE43ConfigurationParticle.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):
        """
        Over-ride sieve function to handle additional particles.
        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 self._extract_sample(SBE43DataParticle,
                                SBE43DataParticle.regex_compiled(), chunk,
                                timestamp):
            self._sampling = True
            return

        for particle_class in SBE43HardwareParticle, \
                              SBE43CalibrationParticle, \
                              SBE43ConfigurationParticle, \
                              SBE43StatusParticle:
            if self._extract_sample(particle_class,
                                    particle_class.regex_compiled(), chunk,
                                    timestamp):
                return

    def _set_params(self, *args, **kwargs):
        """
        Issue commands to the instrument to set various parameters
        """
        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException(
                'Set command requires a parameter dict.')

        self._verify_not_readonly(*args, **kwargs)
        update_params = False

        # check values that the instrument doesn't validate
        # handle special cases for driver specific parameters
        for (key, val) in params.iteritems():
            if key == Parameter.PUMP_DELAY and (val < MIN_PUMP_DELAY
                                                or val > MAX_PUMP_DELAY):
                raise InstrumentParameterException("pump delay out of range")
            elif key == Parameter.NUM_AVG_SAMPLES and (val < MIN_AVG_SAMPLES or
                                                       val > MAX_AVG_SAMPLES):
                raise InstrumentParameterException(
                    "num average samples out of range")

        for (key, val) in params.iteritems():

            old_val = self._param_dict.format(key)
            new_val = self._param_dict.format(key, val)
            log.debug("KEY = %r OLD VALUE = %r NEW VALUE = %r", key, old_val,
                      new_val)

            if old_val != new_val:
                update_params = True
                if ConfirmedParameter.has(key):
                    # We add a write delay here because this command has to be sent
                    # twice, the write delay allows it to process the first command
                    # before it receives the beginning of the second.
                    self._do_cmd_resp(Command.SET, key, val, write_delay=0.2)
                else:
                    self._do_cmd_resp(Command.SET, key, val, **kwargs)

        log.debug("set complete, update params")
        if update_params:
            self._update_params()

    ########################################################################
    # Command handlers.
    ########################################################################
    def _handler_command_acquire_status(self, *args, **kwargs):
        """
        Get device status
        """
        next_state = None

        response = self._do_cmd_resp(
            Command.GET_SD,
            response_regex=SBE43StatusParticle.regex_compiled(),
            timeout=TIMEOUT)
        log.debug("_handler_command_acquire_status: GetSD Response: %s",
                  response)
        response = self._do_cmd_resp(
            Command.GET_HD,
            response_regex=SBE43HardwareParticle.regex_compiled(),
            timeout=TIMEOUT)
        log.debug("_handler_command_acquire_status: GetHD Response: %s",
                  response)
        response = self._do_cmd_resp(
            Command.GET_CD,
            response_regex=SBE43ConfigurationParticle.regex_compiled(),
            timeout=TIMEOUT)
        log.debug("_handler_command_acquire_status: GetCD Response: %s",
                  response)
        response = self._do_cmd_resp(
            Command.GET_CC,
            response_regex=SBE43CalibrationParticle.regex_compiled(),
            timeout=TIMEOUT)
        log.debug("_handler_command_acquire_status: GetCC Response: %s",
                  response)
        response = self._do_cmd_resp(Command.GET_EC, timeout=TIMEOUT)
        log.debug("_handler_command_acquire_status: GetEC Response: %s",
                  response)

        # Reset the event counter right after getEC
        self._do_cmd_resp(Command.RESET_EC, timeout=TIMEOUT)

        result = self.wait_for_particles([
            DataParticleType.DEVICE_STATUS,
            DataParticleType.DEVICE_CALIBRATION,
            DataParticleType.DEVICE_HARDWARE,
            DataParticleType.DEVICE_CONFIGURATION
        ])

        return next_state, (next_state, result)

    def _handler_command_acquire_sample(self, *args, **kwargs):
        """
        Acquire Sample is implemented asynchronously. Transition to ACQUIRING_SAMPLE state.
        """
        next_state = ProtocolState.ACQUIRING_SAMPLE
        result = []

        return next_state, (next_state, result)

    def _handler_acquiring_sample_enter(self):
        """
        Trigger the ACQUIRE_SAMPLE_ASYNC event
        """
        self._async_raise_fsm_event(ProtocolEvent.ACQUIRE_SAMPLE_ASYNC)

    def _handler_acquire_sample_async(self, *args, **kwargs):
        """
        Acquire sample from SBE16.
        @retval next_state, (next_state, result) tuple
        """
        next_state = ProtocolState.COMMAND
        result = []

        self._do_cmd_resp(Command.TS, *args, timeout=ASYNC_TIMEOUT, **kwargs)

        return next_state, (next_state, result)

    def _handler_autosample_acquire_status(self, *args, **kwargs):
        """
        Get device status in autosample mode
        """
        next_state = None
        result = []

        # When in autosample this command requires two wake-ups to get to the right prompt
        self._wakeup(timeout=WAKEUP_TIMEOUT, delay=0.3)
        self._wakeup(timeout=WAKEUP_TIMEOUT, delay=0.3)

        response = self._do_cmd_resp(
            Command.GET_SD,
            response_regex=SBE43StatusParticle.regex_compiled(),
            timeout=TIMEOUT)
        log.debug("_handler_autosample_acquire_status: GetSD Response: %s",
                  response)
        response = self._do_cmd_resp(
            Command.GET_HD,
            response_regex=SBE43HardwareParticle.regex_compiled(),
            timeout=TIMEOUT)
        log.debug("_handler_autosample_acquire_status: GetHD Response: %s",
                  response)
        response = self._do_cmd_resp(
            Command.GET_CD,
            response_regex=SBE43ConfigurationParticle.regex_compiled(),
            timeout=TIMEOUT)
        log.debug("_handler_autosample_acquire_status: GetCD Response: %s",
                  response)
        response = self._do_cmd_resp(
            Command.GET_CC,
            response_regex=SBE43CalibrationParticle.regex_compiled(),
            timeout=TIMEOUT)
        log.debug("_handler_autosample_acquire_status: GetCC Response: %s",
                  response)
        response = self._do_cmd_resp(Command.GET_EC, timeout=TIMEOUT)
        log.debug("_handler_autosample_acquire_status: GetEC Response: %s",
                  response)

        # Reset the event counter right after getEC
        self._do_cmd_no_resp(Command.RESET_EC)

        return next_state, (next_state, result)

    ########################################################################
    # response handlers.
    ########################################################################
    def _validate_GetSD_response(self, response, prompt):
        """
        validation handler for GetSD command
        @param response command response string.
        @param prompt prompt following command response.
        @throws InstrumentProtocolException if command misunderstood.
        """
        error = self._find_error(response)

        if error:
            log.error(
                "_validate_GetSD_response: GetSD command encountered error; type='%s' msg='%s'",
                error[0], error[1])
            raise InstrumentProtocolException(
                'GetSD command failure: type="%s" msg="%s"' %
                (error[0], error[1]))

        if not SBE43StatusParticle.resp_regex_compiled().search(response):
            log.error(
                '_validate_GetSD_response: GetSD command not recognized: %s.' %
                response)
            raise InstrumentProtocolException(
                'GetSD command not recognized: %s.' % response)

        self._param_dict.update_many(response)

        return response

    def _validate_GetHD_response(self, response, prompt):
        """
        validation handler for GetHD command
        @param response command response string.
        @param prompt prompt following command response.
        @throws InstrumentProtocolException if command misunderstood.
        """
        error = self._find_error(response)

        if error:
            log.error("GetHD command encountered error; type='%s' msg='%s'",
                      error[0], error[1])
            raise InstrumentProtocolException(
                'GetHD command failure: type="%s" msg="%s"' %
                (error[0], error[1]))

        if not SBE43HardwareParticle.resp_regex_compiled().search(response):
            log.error(
                '_validate_GetHD_response: GetHD command not recognized: %s.' %
                response)
            raise InstrumentProtocolException(
                'GetHD command not recognized: %s.' % response)

        self._param_dict.update_many(response)

        return response

    def _validate_GetCD_response(self, response, prompt):
        """
        validation handler for GetCD command
        @param response command response string.
        @param prompt prompt following command response.
        @throws InstrumentProtocolException if command misunderstood.
        """
        error = self._find_error(response)

        if error:
            log.error("GetCD command encountered error; type='%s' msg='%s'",
                      error[0], error[1])
            raise InstrumentProtocolException(
                'GetCD command failure: type="%s" msg="%s"' %
                (error[0], error[1]))

        if not SBE43ConfigurationParticle.resp_regex_compiled().search(
                response):
            log.error(
                '_validate_GetCD_response: GetCD command not recognized: %s.' %
                response)
            raise InstrumentProtocolException(
                'GetCD command not recognized: %s.' % response)

        self._param_dict.update_many(response)

        return response

    def _validate_GetCC_response(self, response, prompt):
        """
        validation handler for GetCC command
        @param response command response string.
        @param prompt prompt following command response.
        @throws InstrumentProtocolException if command misunderstood.
        """
        error = self._find_error(response)

        if error:
            log.error("GetCC command encountered error; type='%s' msg='%s'",
                      error[0], error[1])
            raise InstrumentProtocolException(
                'GetCC command failure: type="%s" msg="%s"' %
                (error[0], error[1]))

        if not SBE43CalibrationParticle.resp_regex_compiled().search(response):
            log.error(
                '_validate_GetCC_response: GetCC command not recognized: %s.' %
                response)
            raise InstrumentProtocolException(
                'GetCC command not recognized: %s.' % response)

        return response

    def _validate_GetEC_response(self, response, prompt):
        """
        validation handler for GetEC command
        @param response command response string.
        @throws InstrumentProtocolException if command misunderstood.
        """
        error = self._find_error(response)

        if error:
            log.error("GetEC command encountered error; type='%s' msg='%s'",
                      error[0], error[1])
            raise InstrumentProtocolException(
                'GetEC command failure: type="%s" msg="%s"' %
                (error[0], error[1]))

        return response

    ########################################################################
    # Private helpers.
    ########################################################################
    def _build_param_dict(self):
        """
        Populate the parameter dictionary with SBE19 parameters.
        For each parameter key, add match string, match lambda function,
        and value formatting function for set commands.
        """
        self._build_common_param_dict()

        self._param_dict.add(Parameter.SBE63,
                             r'SBE63>(.*)</SBE63',
                             lambda match: True
                             if match.group(1) == 'yes' else False,
                             self._true_false_to_string,
                             type=ParameterDictType.BOOL,
                             display_name="SBE63 Attached",
                             range={
                                 'True': True,
                                 'False': False
                             },
                             description="Enable SBE63: (true | false)",
                             startup_param=True,
                             direct_access=True,
                             default_value=False,
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(Parameter.NUM_AVG_SAMPLES,
                             r'ScansToAverage>([\d]+)</ScansToAverage>',
                             lambda match: int(match.group(1)),
                             str,
                             type=ParameterDictType.INT,
                             display_name="Scans to Average",
                             range=INT16,
                             description="Number of samples to average",
                             startup_param=True,
                             direct_access=False,
                             default_value=4,
                             visibility=ParameterDictVisibility.READ_WRITE)
        self._param_dict.add(
            Parameter.MIN_COND_FREQ,
            r'MinimumCondFreq>([\d]+)</MinimumCondFreq',
            lambda match: int(match.group(1)),
            str,
            type=ParameterDictType.INT,
            display_name="Minimum Conductivity Frequency",
            range=INT16,
            description=
            "Minimum conductivity frequency to enable pump turn-on.",
            startup_param=True,
            direct_access=False,
            default_value=500,
            units=Units.HERTZ,
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.PUMP_DELAY,
            r'PumpDelay>([\d]+)</PumpDelay',
            lambda match: int(match.group(1)),
            str,
            type=ParameterDictType.INT,
            display_name="Pump Delay",
            range=INT16,
            description=
            "Time to wait after minimum conductivity frequency is reached before turning pump on.",
            startup_param=True,
            direct_access=False,
            default_value=60,
            units=Units.SECOND,
            visibility=ParameterDictVisibility.READ_WRITE)
        self._param_dict.add(
            Parameter.AUTO_RUN,
            r'AutoRun>(.*)</AutoRun',
            lambda match: True if match.group(1) == 'yes' else False,
            self._true_false_to_string,
            type=ParameterDictType.BOOL,
            display_name="Auto Run",
            range={
                'True': True,
                'False': False
            },
            description=
            "Enable automatic logging when power is applied: (true | false).",
            startup_param=True,
            direct_access=True,
            default_value=False,
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.IGNORE_SWITCH,
            r'IgnoreSwitch>(.*)</IgnoreSwitch',
            lambda match: True if match.group(1) == 'yes' else False,
            self._true_false_to_string,
            type=ParameterDictType.BOOL,
            display_name="Ignore Switch",
            range={
                'True': True,
                'False': False
            },
            description=
            "Disable magnetic switch position for starting or stopping logging: (true | false)",
            startup_param=True,
            direct_access=True,
            default_value=True,
            visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.PTYPE,
            r"<Sensor id = 'Main Pressure'>.*?<type>(.*?)</type>.*?</Sensor>",
            self._pressure_sensor_to_int,
            str,
            type=ParameterDictType.INT,
            display_name="Pressure Sensor Type",
            range={
                'Strain Gauge': 1,
                'Quartz with Temp Comp': 3
            },
            startup_param=True,
            direct_access=True,
            default_value=1,
            description=
            "Sensor type: (1:strain gauge | 3:quartz with temp comp)",
            visibility=ParameterDictVisibility.IMMUTABLE,
            regex_flags=re.DOTALL)
        self._param_dict.add(Parameter.OPTODE,
                             r'OPTODE>(.*)</OPTODE',
                             lambda match: True
                             if match.group(1) == 'yes' else False,
                             self._true_false_to_string,
                             type=ParameterDictType.BOOL,
                             display_name="Optode Attached",
                             range={
                                 'True': True,
                                 'False': False
                             },
                             description="Enable optode: (true | false)",
                             startup_param=True,
                             direct_access=True,
                             default_value=False,
                             visibility=ParameterDictVisibility.IMMUTABLE)
        self._param_dict.add(
            Parameter.VOLT1,
            r'ExtVolt1>(.*)</ExtVolt1',
            lambda match: True if match.group(1) == 'yes' else False,
            self._true_false_to_string,
            type=ParameterDictType.BOOL,
            display_name="Volt 1",
            range={
                'True': True,
                'False': False
            },
            description="Enable external voltage 1: (true | false)",
            startup_param=True,
            direct_access=True,
            default_value=False,
            visibility=ParameterDictVisibility.IMMUTABLE)
Exemplo n.º 30
0
class Protocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """

    __metaclass__ = get_logging_metaclass(log_level='trace')

    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.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.START_AUTOSAMPLE,
                                       self._handler_command_autosample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND,
                                       ProtocolEvent.ACQUIRE_STATUS,
                                       self._handler_command_acquire_status)
        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.AUTOSAMPLE,
                                       ProtocolEvent.STOP_AUTOSAMPLE,
                                       self._handler_autosample_stop)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE,
                                       ProtocolEvent.GET,
                                       self._handler_command_get)

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_driver_dict()
        self._build_command_dict()
        self._build_param_dict()

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

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with parameters.
        For each parameter key, add match string, match lambda function,
        and value formatting function for set commands.
        """

        self._param_dict.add(
            Parameter.SCHEDULE,
            r'schedule:\s+(.*)',
            lambda match: match.group(1),
            str,
            type=ParameterDictType.STRING,
            display_name="Schedule",
            description=
            "Large block of text used to create the .yaml file defining the sampling schedule.",
            startup_param=True,
            default_value=yaml.dump(DEFAULT_CONFIG, default_flow_style=False))

        self._param_dict.add(
            Parameter.FTP_IP_ADDRESS,
            r'ftp address:\s+(\d\d\d\d\.\d\d\d\d\.\d\d\d\d\.\d\d\d)',
            lambda match: match.group(1),
            str,
            type=ParameterDictType.STRING,
            display_name="FTP IP Address",
            description=
            "IP address the driver uses to connect to the instrument FTP server.",
            startup_param=True,
            default_value=DEFAULT_HOST)

        self._param_dict.add(
            Parameter.FTP_USERNAME,
            r'username:(.*)',
            lambda match: match.group(1),
            str,
            type=ParameterDictType.STRING,
            display_name="FTP User Name",
            description="Username used to connect to the FTP server.",
            startup_param=True,
            default_value=USER_NAME)

        self._param_dict.add(
            Parameter.FTP_PASSWORD,
            r'password:(.*)',
            lambda match: match.group(1),
            str,
            type=ParameterDictType.STRING,
            display_name="FTP Password",
            description="Password used to connect to the FTP server.",
            startup_param=True,
            default_value=PASSWORD)

        self._param_dict.add(
            Parameter.FTP_PORT,
            r'port:(.*)',
            lambda match: match.group(1),
            str,
            type=ParameterDictType.STRING,
            display_name="FTP Port",
            description=
            "Location on the OOI infrastructure where .raw files and echogram images will be stored.",
            startup_param=True,
            default_value=DEFAULT_PORT)

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

    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.ACQUIRE_STATUS,
                           display_name="Acquire Status")
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

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

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

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

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

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state
        @retval next_state, (next_state, result)
        """
        next_state = ProtocolState.COMMAND
        result = []

        # Try to get the status to check if the instrument is alive
        host = self._param_dict.get_config_value(Parameter.FTP_IP_ADDRESS)
        port = self._param_dict.get_config_value(Parameter.FTP_PORT)
        response = self._url_request(host, port, '/status.json')

        if response is None:
            error_msg = "_handler_unknown_discover: Unable to connect to host: %s" % host
            log.error(error_msg)
            raise InstrumentConnectionException(error_msg)

        return next_state, (next_state, result)

    ########################################################################
    # Command handlers.
    ########################################################################
    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        @throws InstrumentTimeoutException if the device cannot be woken.
        @throws InstrumentProtocolException if the update commands and not recognized.
        """
        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_get(self, *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
        """
        result_vals = {}

        # Retrieve required parameter.
        # Raise if no parameter provided, or not a dict.
        try:
            params = args[0]

        except IndexError:
            raise InstrumentParameterException(
                '_handler_command_get requires a parameter dict.')

        if Parameter.ALL in params:
            log.debug("Parameter ALL in params")
            params = Parameter.list()
            params.remove(Parameter.ALL)

        log.debug("_handler_command_get: params = %s", params)

        if params is None or not isinstance(params, list):
            raise InstrumentParameterException(
                "GET parameter list not a list!")

        # 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)
            self._param_dict.get_config_value(param)
        result = result_vals

        log.debug("Get finished, next_state: %s, result: %s", None, result)
        return None, result

    def _handler_command_set(self, *args, **kwargs):
        """
        Set parameter
        @retval next state, result
        """
        startup = False

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

        try:
            startup = args[1]
        except IndexError:
            pass

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

        # For each key, val in the params, set the param dictionary.
        old_config = self._param_dict.get_config()
        self._set_params(params, startup)

        new_config = self._param_dict.get_config()
        if old_config != new_config:
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

        return None, None

    def _set_params(self, *args, **kwargs):
        """
        Issue commands to the instrument to set various parameters
        """
        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException(
                'Set command requires a parameter dict.')

        # verify param is not readonly param
        self._verify_not_readonly(*args, **kwargs)

        for key, val in params.iteritems():
            log.debug("KEY = %s VALUE = %s", key, val)
            self._param_dict.set_value(key, val)
            if key == Parameter.SCHEDULE:
                self._ftp_schedule_file()

                # Load the schedule file
                host = self._param_dict.get(Parameter.FTP_IP_ADDRESS)
                port = self._param_dict.get_config_value(Parameter.FTP_PORT)
                log.debug("_set_params: stop the current schedule file")
                self._url_request(host, port, '/stop_schedule', data={})
                log.debug("_set_params: upload driver YAML file to host %s",
                          host)
                res = self._url_request(host,
                                        port,
                                        '/load_schedule',
                                        data=json.dumps(
                                            {'filename': YAML_FILE_NAME}))
                log.debug("_set_params: result from load = %s", res)

        log.debug("set complete, update params")

    def _ftp_schedule_file(self):
        """
        Construct a YAML schedule file and
        ftp the file to the Instrument server
        """
        # Create a temporary file and write the schedule YAML information to the file
        try:
            config_file = tempfile.TemporaryFile()
            log.debug("temporary file created")

            if config_file is None or not isinstance(config_file, file):
                raise InstrumentException("config_file is not a temp file!")

            config_file.write(self._param_dict.get(Parameter.SCHEDULE))
            config_file.seek(0)
            log.debug("finished writing config file:\n%r",
                      self._param_dict.get(Parameter.SCHEDULE))

        except Exception as e:
            log.error("Create schedule YAML file exception: %s", e)
            raise e

        #  FTP the schedule file to the ZPLSC server
        host = ''

        try:
            log.debug("Create a ftp session")
            host = self._param_dict.get_config_value(Parameter.FTP_IP_ADDRESS)
            log.debug("Got host ip address %s", host)

            ftp_session = ftplib.FTP()
            ftp_session.connect(host)
            ftp_session.login(USER_NAME, PASSWORD)
            log.debug("ftp session was created...")

            ftp_session.set_pasv(False)
            ftp_session.cwd("config")

            ftp_session.storlines('STOR ' + YAML_FILE_NAME, config_file)
            files = ftp_session.dir()

            log.debug("*** Config yaml file sent: %s", files)

            ftp_session.quit()
            config_file.close()

        except (ftplib.socket.error, ftplib.socket.gaierror), e:
            log.error("ERROR: cannot reach FTP Host %s: %s ", host, e)
            raise InstrumentException("ERROR: cannot reach FTP Host %s " %
                                      host)

        log.debug("*** FTP %s to ftp host %s successfully", YAML_FILE_NAME,
                  host)
Exemplo n.º 31
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.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_command_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_TURBO, self._handler_command_start_turbo),
            ],
            ProtocolState.SPINNING_UP: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.AT_SPEED, self._handler_spinning_up_at_speed),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.AT_SPEED: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.CLEAR, self._handler_clear),
                (ProtocolEvent.GET, self._handler_command_get),
            ],
            ProtocolState.SPINNING_DOWN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOPPED, self._handler_spinning_down_stopped),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
            ],
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # Add build and response handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_build_handler(command, self._generic_build_handler)
            self._add_response_handler(command, self._generic_response_handler)

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._chunker = StringChunker(Protocol.sieve_function)
        self._max_current_count = 0
        self.initialize_scheduler()

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples
        @param raw_data: data to be searched
        @return: list of (start,stop) indexes of matches
        """
        return [(m.start(), m.end()) for m in TurboStatusParticle.regex_compiled().finditer(raw_data)]

    def _build_param_dict(self):
        """
        All turbo parameters have the same signature, add them in a loop...
        """
        parameters = {
            Parameter.UPDATE_INTERVAL: {
                'display_name': 'Acquire Status Interval',
                'description': 'Interval between automatic acquire status calls: (5 - 60)',
                'units': Units.SECOND,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.MAX_DRIVE_CURRENT: {
                'display_name': 'Maximum Allowable Drive Current',
                'description': 'Maximum allowable drive current at speed: (100 - 200)',
                'units': Prefixes.CENTI + Units.AMPERE,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.MAX_TEMP_MOTOR: {
                'display_name': 'Maximum Allowable Motor Temperature',
                'description': 'Maximum allowable motor temperature: (5 - 100)',
                'units': Units.DEGREE_CELSIUS,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.MAX_TEMP_BEARING: {
                'display_name': 'Maximum Allowable Bearing Temperature',
                'description': 'Maximum allowable bearing temperature: (5 - 100)',
                'units': Units.DEGREE_CELSIUS,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.MIN_SPEED: {
                'display_name': 'Minimum Allowable Turbo Speed',
                'description': 'Minimum allowable turbo speed before RGA is shutdown: (70000 - 90000)',
                'units': Units.REVOLUTION_PER_MINUTE,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.TARGET_SPEED: {
                'display_name': 'Target Turbo Speed',
                'description': 'Target turbo speed before RGA is initialized: (70000 - 90000)',
                'units': Units.REVOLUTION_PER_MINUTE,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.ERROR_REASON: {
                'display_name': 'Turbo Error Reason',
                'description': 'Reason for turbo error state.',
                'visibility': ParameterDictVisibility.READ_ONLY,
                'type': ParameterDictType.STRING,
            }
        }

        reverse_param = Parameter.reverse_dict()
        constraints = ParameterConstraints.dict()

        for name in parameters:
            kwargs = parameters[name]
            if name in constraints:
                _type, minimum, maximum = constraints[name]
                kwargs['val_description'] = '%s value from %d - %d' % (_type, minimum, maximum)
            self._param_dict.add(name, '', None, None, **kwargs)

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.ACQUIRE_STATUS, display_name="Acquire Status")
        self._cmd_dict.add(Capability.START_TURBO, display_name="Start Turbo")
        self._cmd_dict.add(Capability.STOP_TURBO, display_name="Stop Turbo")
        self._cmd_dict.add(Capability.CLEAR, display_name="Clear Error State")
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

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

    def _got_chunk(self, chunk, ts):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and regexes.
        @param chunk: data to be processed
        @param ts: timestamp
        """
        self._extract_sample(TurboStatusParticle, TurboStatusParticle.regex_compiled(), chunk, ts)

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        @param events: events to be filtered
        @return: list of events that are in Capability
        """
        return [x for x in events if Capability.has(x)]

    @staticmethod
    def _checksum(s):
        """
        Calculate the turbopump checksum for the given string.
        @param s: string to be checked
        @return: checksum string
        """
        return '%03d' % (sum([ord(x) for x in s]) % 256)

    def _build_turbo_command(self, address, c_type, c, data):
        """
        Build a command for the turbopump
        @param address: target address
        @param c_type: command type (QUERY/SET)
        @param c: command
        @param data: command_data
        @return: command string
        """
        command = '%03d%02d%03d%02d%s' % (address, c_type, c, len(data), data)
        checksum = self._checksum(command)
        return command + checksum

    def _generic_build_handler(self, command, *args, **kwargs):
        """
        Determine if this is a query or set action based on the
        input args.  Dispatch the builder with the appropriate arguments.
        @param command: command to be sent
        @param args: arglist which may contain a value
        @return: command string
        """
        if len(args) == 1:
            # this is a set action
            value = args[0]
            return self._build_turbo_command(ADDRESS, CommandType.SET, command, value) + NEWLINE
        # this is a query
        return self._build_turbo_command(ADDRESS, CommandType.QUERY, command, QUERY) + NEWLINE

    def _generic_response_handler(self, resp, prompt):
        """
        Parse the response from the turbopump.
        @param resp: response
        @param prompt: unused, require to match signature
        @returns: integer value extracted from response
        @throws InstrumentDataException
        """
        my_checksum = self._checksum(resp[:-3])
        if resp[-3:] != my_checksum:
            err_str = 'bad checksum: %r calculated: %r' % (resp, my_checksum)
            raise exceptions.InstrumentDataException(err_str)
        command = int(resp[5:8])
        data_length = int(resp[8:10])
        data = resp[10:-3]
        log.trace('command: %s data: %s', command, data)
        if len(data) != data_length:
            raise exceptions.InstrumentDataException('invalid data length: %r' % resp)
        if command not in InstrumentCommand.list():
            raise exceptions.InstrumentDataException('command not found: %r' % resp)
        return int(data)

    def _wakeup(self, timeout, delay=1):
        """
        Not valid for this instrument
        """

    def _build_scheduler(self):
        """
        Build a scheduler for periodic status updates
        """
        job_name = ScheduledJob.ACQUIRE_STATUS
        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.INTERVAL,
                        DriverSchedulerConfigKey.SECONDS: self._param_dict.get(Parameter.UPDATE_INTERVAL)
                    },
                }
            }
        }

        self.set_init_params(config)
        self._add_scheduler_event(ScheduledJob.ACQUIRE_STATUS, ProtocolEvent.ACQUIRE_STATUS)

    def _update_params(self, *args, **kwargs):
        """
        Parameters are NOT set in the instrument by this method, as all parameters are driver only.
        """

    def _set_params(self, *args, **kwargs):
        """
        Set parameters, raise a CONFIG_CHANGE event if necessary.
        @throws InstrumentParameterException
        """
        self._verify_not_readonly(*args, **kwargs)
        params_to_set = args[0]
        old_config = self._param_dict.get_all()

        # check if in range
        constraints = ParameterConstraints.dict()
        parameters = Parameter.reverse_dict()

        # step through the list of parameters
        for key, val in params_to_set.iteritems():
            # if constraint exists, verify we have not violated it
            constraint_key = parameters.get(key)
            if constraint_key in constraints:
                var_type, minimum, maximum = constraints[constraint_key]
                try:
                    value = var_type(val)
                except ValueError:
                    raise exceptions.InstrumentParameterException(
                        'Unable to verify type - parameter: %s value: %s' % (key, val))
                if val < minimum or val > maximum:
                    raise exceptions.InstrumentParameterException(
                        'Value out of range - parameter: %s value: %s min: %s max: %s' %
                        (key, val, minimum, maximum))

        # all constraints met or no constraints exist, set the values
        for key, val in params_to_set.iteritems():
            if key in old_config:
                self._param_dict.set_value(key, val)
            else:
                raise exceptions.InstrumentParameterException(
                    'Attempted to set unknown parameter: %s value: %s' % (key, val))
        new_config = self._param_dict.get_all()

        # If we changed anything, raise a CONFIG_CHANGE event
        if old_config != new_config:
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def _send_command_with_retry(self, command, value=None, sleep_time=1, max_retries=MAX_RETRIES):
        """
        Attempt to send a command up to max_retries times.  Protocol state will move to ERROR if we fail to
        receive a response after max_retries attempts.
        @throws InstrumentTimeoutException
        """
        for attempt in xrange(1, max_retries + 1):
            try:
                if value is None:
                    result = self._do_cmd_resp(command, response_regex=TURBO_RESPONSE, timeout=TIMEOUT)
                else:
                    result = self._do_cmd_resp(command, value, response_regex=TURBO_RESPONSE, timeout=TIMEOUT)
                return result
            except exceptions.InstrumentTimeoutException:
                log.error('Error sending command: %s, attempt %d', command, attempt)
                time.sleep(sleep_time)

        # set the error reason
        self._param_dict.set_value(Parameter.ERROR_REASON, 'Unable to command the turbo')
        self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

        self._async_raise_fsm_event(ProtocolEvent.ERROR)
        raise exceptions.InstrumentTimeoutException('Failed to command the turbo: %s' % command)

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

    def _handler_generic_enter(self, *args, **kwargs):
        """
        Generic enter handler when no specific action is needed.
        """
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_generic_exit(self, *args, **kwargs):
        """
        Generic exit handler when no specific action is needed.
        """

    def _handler_acquire_status(self, *args, **kwargs):
        """
        Query the instrument for the following status items:
            drive current
            drive voltage
            bearing temp
            motor temp
            rotation speed

        Verify no values exceed the limits specified in the parameter dictionary.
        @returns: next_state, (next_agent_state, result)
        @throws InstrumentStateException
        """
        responses = {}

        # query the turbo for the speed/temp/current values
        for command in [InstrumentCommand.DRIVE_CURRENT, InstrumentCommand.DRIVE_VOLTAGE,
                        InstrumentCommand.TEMP_BEARING, InstrumentCommand.TEMP_MOTOR,
                        InstrumentCommand.ROTATION_SPEED_ACTUAL]:
            responses[command] = self._send_command_with_retry(command)

        # check the current driver state
        current_state = self.get_current_state()
        error = None

        # Check for over temperature conditions
        if responses[InstrumentCommand.TEMP_MOTOR] > self._param_dict.get(Parameter.MAX_TEMP_MOTOR) or \
                responses[InstrumentCommand.TEMP_BEARING] > self._param_dict.get(Parameter.MAX_TEMP_BEARING):
            error = 'Over temp error - Motor: %d Bearing: %d' % (responses[InstrumentCommand.TEMP_MOTOR],
                                                                 responses[InstrumentCommand.TEMP_BEARING])

        # Check if we were up to speed but have dipped below MIN_SPEED
        elif current_state == ProtocolState.AT_SPEED:
            if responses[InstrumentCommand.ROTATION_SPEED_ACTUAL] < self._param_dict.get(Parameter.MIN_SPEED):
                error = 'Fell below min speed: %d' % responses[InstrumentCommand.ROTATION_SPEED_ACTUAL]

            # or if we're up to speed and we have exceeded MAX_DRIVE_CURRENT more than 3 subsequent intervals
            if responses[InstrumentCommand.DRIVE_CURRENT] > self._param_dict.get(Parameter.MAX_DRIVE_CURRENT):
                self._max_current_count += 1
                if self._max_current_count > CURRENT_STABILIZE_RETRIES:
                    error = 'Turbo current draw to high: %d' % responses[InstrumentCommand.DRIVE_CURRENT]
            else:
                self._max_current_count = 0

        if error:
            self._param_dict.set_value(Parameter.ERROR_REASON, error)
            self._async_raise_fsm_event(ProtocolEvent.ERROR)
            self._driver_event(DriverAsyncEvent.ERROR, error)

        # now check if up to speed when spinning up
        elif current_state == ProtocolState.SPINNING_UP:
            if responses[InstrumentCommand.ROTATION_SPEED_ACTUAL] >= self._param_dict.get(Parameter.TARGET_SPEED):
                self._async_raise_fsm_event(ProtocolEvent.AT_SPEED)

        # or maybe we've stopped while spinning down (we'll consider < MIN_SPEED as stopped...)
        elif current_state == ProtocolState.SPINNING_DOWN:
            if responses[InstrumentCommand.ROTATION_SPEED_ACTUAL] <= self._param_dict.get(Parameter.MIN_SPEED):
                self._async_raise_fsm_event(ProtocolEvent.STOPPED)

        return None, (None, responses)

    def _handler_stop_turbo(self):
        """
        Stop the turbo
        @returns: next_state, (next_agent_state, result)
        """
        for command in [InstrumentCommand.PUMP_STATION, InstrumentCommand.MOTOR_PUMP]:
            self._send_command_with_retry(command, value=FALSE)

        return ProtocolState.SPINNING_DOWN, (ResourceAgentState.BUSY, None)

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

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state.  This instrument always discovers to COMMAND
        @returns: next_state, next_agent_state
        """
        return ProtocolState.COMMAND, ResourceAgentState.IDLE

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

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

        # delete the scheduled acquire status job, if it exists.
        # This portion of the MASSP is powered OFF the majority of the time
        # so acquire_status should not be running
        try:
            self._remove_scheduler(ScheduledJob.ACQUIRE_STATUS)
        except KeyError:
            pass

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

    def _handler_command_get(self, *args, **kwargs):
        """
        Get parameter
        @returns: next_state, result
        """
        return self._handler_get(*args, **kwargs)

    def _handler_command_set(self, *args, **kwargs):
        """
        Set parameter
        @returns: next_state, result
        """
        next_state = None
        result = None
        self._set_params(*args, **kwargs)

        return next_state, result

    def _handler_command_start_direct(self):
        """
        Start direct access
        @returns: next_state, (next_agent_state, result)
        """
        return ProtocolState.DIRECT_ACCESS, (ResourceAgentState.DIRECT_ACCESS, None)

    def _handler_command_start_turbo(self):
        """
        Start the turbo, periodic status scheduler
        @returns: next_state, (next_agent_state, result)
        """
        for command in [InstrumentCommand.PUMP_STATION, InstrumentCommand.MOTOR_PUMP]:
            self._send_command_with_retry(command, value=TRUE)
        # start the acquire_status scheduler
        self._build_scheduler()
        return ProtocolState.SPINNING_UP, (ResourceAgentState.BUSY, 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_execute_direct(self, data):
        """
        Forward a direct access command to the instrument
        @returns: next_state, (next_agent_state, result)
        """
        self._do_cmd_direct(data)

        # add sent command to list for 'echo' filtering in callback
        self._sent_cmds.append(data)

        return None, (None, None)

    def _handler_direct_access_stop_direct(self):
        """
        Stop direct access, return to COMMAND
        @returns: next_state, (next_agent_state, result)
        """
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    ########################################################################
    # Spinning up/down handlers.
    ########################################################################

    def _handler_spinning_up_at_speed(self):
        """
        Instrument has reached operating speed, transition states.
        @returns: next_state, next_agent_state
        """
        return ProtocolState.AT_SPEED, ResourceAgentState.BUSY

    def _handler_spinning_down_stopped(self):
        """
        Instrument has spun down, transition states.
        @returns: next_state, next_agent_state
        """
        self._async_agent_state_change(ResourceAgentState.COMMAND)
        return ProtocolState.COMMAND, ResourceAgentState.COMMAND

    ########################################################################
    # Error handlers.
    ########################################################################

    def _handler_error(self, *args, **kwargs):
        """
        Error detected, go to the ERROR state.
        @returns: next_state, (next_agent_state, result)
        """
        return ProtocolState.ERROR, (ResourceAgentState.COMMAND, None)

    def _handler_clear(self, *args, **kwargs):
        """
        User requests error state be cleared, go to COMMAND.
        @returns: next_state, (next_agent_state, result)
        """
        self._param_dict.set_value(Parameter.ERROR_REASON, '')
        self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)
Exemplo n.º 32
0
class THSPHProtocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    SERIES_A = 'A'
    SERIES_B = 'B'
    SERIES_C = 'C'
    GET_SAMPLE_SERIES_A = 'aH*'  # Gets data sample from ADC for series A
    GET_SAMPLE_SERIES_B = 'bH*'  # Gets data sample from ADC for series B
    GET_SAMPLE_SERIES_C = 'cH*'  # Gets data sample from ADC for series C

    # THSPH commands for instrument series A, B and C
    THSPH_COMMANDS = {
        SERIES_A: {Command.GET_SAMPLE: GET_SAMPLE_SERIES_A},
        SERIES_B: {Command.GET_SAMPLE: GET_SAMPLE_SERIES_B},
        SERIES_C: {Command.GET_SAMPLE: GET_SAMPLE_SERIES_C},
    }

    __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.
        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_AUTOSAMPLE,
                                       self._handler_command_start_autosample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_SAMPLE,
                                       self._handler_command_acquire_sample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.GET, self._handler_command_get)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.SET, self._handler_command_set)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_DIRECT,
                                       self._handler_command_start_direct)

        self._protocol_fsm.add_handler(ProtocolState.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.SCHEDULE_ACQUIRE_SAMPLE,
                                       self._handler_command_acquire_sample)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE,
                                       self._handler_autosample_stop_autosample)

        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)

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_driver_dict()
        self._build_command_dict()
        self._build_param_dict()

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

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

        # commands sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._chunker = StringChunker(THSPHProtocol.sieve_function)

        # Set Get Sample Command and Communication Test Command for Series A as default
        self._get_sample_cmd = self.GET_SAMPLE_SERIES_A

        self._direct_commands['Newline'] = self._newline
        self._direct_commands['Test A'] = 'aP*' + self._newline
        self._direct_commands['Test B'] = 'bP*' + self._newline
        self._direct_commands['Test C'] = 'cP*' + self._newline
        self._direct_commands['Sample A'] = self.GET_SAMPLE_SERIES_A + self._newline
        self._direct_commands['Sample B'] = self.GET_SAMPLE_SERIES_B + self._newline
        self._direct_commands['Sample C'] = self.GET_SAMPLE_SERIES_C + self._newline

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

        matchers.append(THSPHParticle.regex_compiled())

        for matcher in matchers:
            log.trace('matcher: %r raw_data: %r', matcher.pattern, raw_data)
            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.
        """
        if not self._extract_sample(THSPHParticle, THSPHParticle.regex_compiled(), chunk, timestamp):
            raise InstrumentProtocolException("Unhandled chunk")

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

    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.ACQUIRE_SAMPLE, display_name="Acquire Sample")
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with THSPH parameters.
        For each parameter key, add match string, match lambda function,
        and value formatting function for set commands.
        """

        # Add parameter handlers to parameter dict.
        self._param_dict.add(Parameter.INTERVAL,
                             r'Auto Polled Interval = (\d+)',
                             lambda match: int(match.group(1)),
                             str,
                             type=ParameterDictType.INT,
                             units=Units.SECOND,
                             display_name="Polled Interval",
                             range=(1, 600),
                             description="Polling interval, internal to driver (1-600).",
                             visibility=ParameterDictVisibility.READ_WRITE,
                             startup_param=True,
                             direct_access=False,
                             default_value=5)

        self._param_dict.add(Parameter.INSTRUMENT_SERIES,
                             r'Instrument Series = ([A-C])',
                             lambda match: int(match.group(1)),
                             str,
                             type=ParameterDictType.STRING,
                             display_name="Instrument Series",
                             range={'A': 'A', 'B': 'B', 'C': 'C'},
                             description='Defines instance of instrument series [A, B, C].',
                             visibility=ParameterDictVisibility.IMMUTABLE,
                             startup_param=True,
                             direct_access=False,
                             default_value='A')

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

    ########################################################################
    # Unknown State handlers.
    ########################################################################
    # noinspection PyUnusedLocal
    def _handler_unknown_enter(self, *args, **kwargs):

        # 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

    # noinspection PyUnusedLocal, PyMethodMayBeStatic
    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state; Change next state to be COMMAND state.
        """
        next_state = ProtocolState.COMMAND
        result = []

        return next_state, (next_state, result)

    ########################################################################
    # Command State handlers.
    ########################################################################
    # noinspection PyUnusedLocal
    def _handler_command_acquire_sample(self, *args, **kwargs):
        """
        Get device status
        """
        timeout = time.time() + TIMEOUT

        next_state = None

        self._do_cmd_no_resp(Command.GET_SAMPLE, timeout=TIMEOUT)

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

        return next_state, (next_state, particles)

    # noinspection PyUnusedLocal
    def _handler_command_enter(self, *args, **kwargs):
        # Tell driver superclass to send a state change event.
        # Superclass will query the state.
        self._init_params()
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_command_exit(self, *args, **kwargs):
        pass

    def _handler_command_get(self, *args, **kwargs):
        """
        Get device parameters from the parameter dict.  First we set a baseline timestamp
        that all data expirations will be calculated against.  Then we try to get parameter
        value.  If we catch an expired parameter then we will update all parameters and get
        values using the original baseline time that we set at the beginning of this method.
        Assuming our _update_params is updating all parameter values properly then we can
        ensure that all data will be fresh.  Nobody likes stale data!
        @param args[0] list of parameters to retrieve, or DriverParameter.ALL.
        """
        next_state, result = self._handler_get(*args, **kwargs)
        # TODO - update return signature to match other handlers - next_state, (next_state, result)
        return next_state, result

    # noinspection PyUnusedLocal
    def _handler_command_set(self, *args, **kwargs):
        """
        Perform a set command.
        @param args[0] parameter : value dict.
        @retval (next_state, result) tuple, (None, None).
        @throws InstrumentParameterException if missing set parameters, if set parameters not ALL and
        not a dict, or if parameter can't be properly formatted.
        """
        next_state = None
        result = []
        startup = False

        # Retrieve required parameter.
        # Raise if no parameter provided, or not a dict.
        try:
            params = args[0]

        except IndexError:
            raise InstrumentParameterException('Set command requires a parameter dict.')

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

        try:
            startup = args[1]
        except IndexError:
            pass

        old_config = self._param_dict.get_config()
        self._set_params(params, startup)

        new_config = self._param_dict.get_config()
        if old_config != new_config:
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

        return next_state, result

    def _set_params(self, *args, **kwargs):
        """
        Set various parameters internally to the driver. No issuing commands to the
        instrument needed for this driver.
        """
        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException('Set command requires a parameter dict.')

        # list can be null, like in the case of direct access params, in this case do nothing
        if not params:
            return

        # Do a range check before we start all sets
        for (key, val) in params.iteritems():

            if key == Parameter.INTERVAL and not (0 < val < 601):
                log.debug("Auto Sample Interval not in 1 to 600 range ")
                raise InstrumentParameterException("sample interval out of range [1, 600]")

            if key == Parameter.INSTRUMENT_SERIES:
                if val not in 'ABC':
                    log.debug("Instrument Series is not A, B or C ")
                    raise InstrumentParameterException("Instrument Series is not invalid ")
                else:
                    self._get_sample_cmd = self.THSPH_COMMANDS[val][Command.GET_SAMPLE]

            log.debug('key = (%s), value = (%s)' % (key, val))

            self._param_dict.set_value(key, val)

    # noinspection PyUnusedLocal, PyMethodMayBeStatic
    def _handler_command_start_autosample(self, *args, **kwargs):
        """
        Switch into autosample mode.
        @retval next_state, (next_state, result)
        @throws InstrumentTimeoutException if device cannot be woken for command.
        @throws InstrumentProtocolException if command could not be built or misunderstood.
        """
        next_state = ProtocolState.AUTOSAMPLE
        result = []
        return next_state, (next_state, result)

    # noinspection PyMethodMayBeStatic
    def _handler_command_start_direct(self):
        """
        Start direct access
        """
        next_state = ProtocolState.DIRECT_ACCESS
        result = []
        return next_state, (next_state, result)

    #######################################################################
    # Autosample State handlers.
    ########################################################################
    # noinspection PyUnusedLocal
    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.
        @retval next_state, (next_state, result)
        """
        next_state = None
        result = []

        self._init_params()

        self._setup_autosample_config()

        # Schedule auto sample task
        self._add_scheduler_event(ScheduledJob.AUTO_SAMPLE, ProtocolEvent.SCHEDULE_ACQUIRE_SAMPLE)

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

        return next_state, (next_state, result)

    def _setup_autosample_config(self):
        """
        Set up auto sample configuration and add it to the scheduler.
        """
        # Start the scheduler to poll the instrument for
        # data every sample interval seconds

        job_name = ScheduledJob.AUTO_SAMPLE
        polled_interval = self._param_dict.get(Parameter.INTERVAL)
        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.INTERVAL,
                        DriverSchedulerConfigKey.SECONDS: polled_interval
                    }
                }
            }
        }
        self.set_init_params(config)

        # Start the scheduler if it is not running
        if not self._scheduler:
            self.initialize_scheduler()

    # noinspection PyUnusedLocal, PyMethodMayBeStatic
    def _handler_autosample_exit(self, *args, **kwargs):
        """
        Exit auto sample state. Remove the auto sample task
        """
        next_state = None
        result = []

        return next_state, (next_state, result)

    # noinspection PyUnusedLocal
    def _handler_autosample_stop_autosample(self, *args, **kwargs):
        """
        Remove the auto sample task. Exit Auto sample state
        """
        next_state = ProtocolState.COMMAND
        result = []

        # Stop the Auto Poll scheduling
        self._remove_scheduler(ScheduledJob.AUTO_SAMPLE)

        return next_state, (next_state, result)

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

    # noinspection PyUnusedLocal
    def _handler_direct_access_enter(self, *args, **kwargs):
        """
        Enter direct access 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):
        """
        Execute direct command
        """
        next_state = None
        result = []

        self._do_cmd_direct(data)

        # add sent command to list for 'echo' filtering in callback
        self._sent_cmds.append(data)

        return next_state, (next_state, result)

    # noinspection PyMethodMayBeStatic
    def _handler_direct_access_stop_direct(self):
        next_state = ProtocolState.COMMAND
        result = []

        return next_state, (next_state, result)

    def _build_simple_command(self, cmd, *args):
        """
        Build handler for basic THSPH commands.
        @param cmd the simple ooicore command to format.
        @retval The command to be sent to the device.
        """
        instrument_series = self._param_dict.get(Parameter.INSTRUMENT_SERIES)

        if cmd == Command.GET_SAMPLE:
            instrument_cmd = self.THSPH_COMMANDS[instrument_series][Command.GET_SAMPLE]
        else:
            raise InstrumentException('Unknown THSPH driver command  %s' % cmd)

        return "%s%s" % (instrument_cmd, NEWLINE)

    def _wakeup(self, wakeup_timeout=0, response_timeout=0):
        """
        There is no wakeup for this instrument.  Do nothing.
        @param wakeup_timeout The timeout to wake the device.
        @param response_timeout The time to look for response to a wakeup attempt.
        """
        pass
Exemplo n.º 33
0
    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.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_SCAN, self._handler_command_start_scan),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
            ],
            ProtocolState.SCAN: [
                (ProtocolEvent.ENTER, self._handler_scan_enter),
                (ProtocolEvent.EXIT, self._handler_scan_exit),
                (ProtocolEvent.STOP_SCAN, self._handler_scan_stop_scan),
                (ProtocolEvent.TAKE_SCAN, self._handler_scan_take_scan),
                (ProtocolEvent.TIMEOUT, self._handler_scan_timeout),
                (ProtocolEvent.ERROR, self._handler_scan_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.CLEAR, self._handler_error_clear),
                (ProtocolEvent.GET, self._handler_command_get),
            ]
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # Add build handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_build_handler(command, self._generic_build_handler)

        # Add response handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_response_handler(command, functools.partial(self._generic_response_handler, command=command))

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._chunker = StringChunker(Protocol.sieve_function)
        self.initialize_scheduler()

        # all calls to do_cmd_resp should expect RESPONSE_REGEX and use TIMEOUT.  Freeze these arguments...
        self._do_cmd_resp = functools.partial(self._do_cmd_resp, response_regex=RESPONSE_REGEX, timeout=TIMEOUT)

        # these variables are used to track scan time and completion status
        # for development and performance data
        self.scan_start_time = 0
        self.in_scan = False
Exemplo n.º 34
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.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_SCAN, self._handler_command_start_scan),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
            ],
            ProtocolState.SCAN: [
                (ProtocolEvent.ENTER, self._handler_scan_enter),
                (ProtocolEvent.EXIT, self._handler_scan_exit),
                (ProtocolEvent.STOP_SCAN, self._handler_scan_stop_scan),
                (ProtocolEvent.TAKE_SCAN, self._handler_scan_take_scan),
                (ProtocolEvent.TIMEOUT, self._handler_scan_timeout),
                (ProtocolEvent.ERROR, self._handler_scan_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.CLEAR, self._handler_error_clear),
                (ProtocolEvent.GET, self._handler_command_get),
            ]
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # Add build handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_build_handler(command, self._generic_build_handler)

        # Add response handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_response_handler(command, functools.partial(self._generic_response_handler, command=command))

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._chunker = StringChunker(Protocol.sieve_function)
        self.initialize_scheduler()

        # all calls to do_cmd_resp should expect RESPONSE_REGEX and use TIMEOUT.  Freeze these arguments...
        self._do_cmd_resp = functools.partial(self._do_cmd_resp, response_regex=RESPONSE_REGEX, timeout=TIMEOUT)

        # these variables are used to track scan time and completion status
        # for development and performance data
        self.scan_start_time = 0
        self.in_scan = False

    @staticmethod
    def sieve_function(raw_data):
        """
        This is a placeholder.  The sieve function for the RGA is built dynamically when a scan is started.
        This function must return a list.
        see self._build_sieve_function()
        """
        return []

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with parameters.
        For each parameter key, add match string, match lambda function,
        and value formatting function for set commands.
        """
        name = 'display_name'
        desc = 'description'
        units = 'units'
        val_desc = 'value_description'

        parameters = {
            Parameter.ID: {
                name: 'RGA ID String',
                desc: '',
            },
            Parameter.EE: {
                name: 'Electron Energy',
                desc: 'The desired electron ionization energy: (25 - 105)',
                units: DriverUnits.ELECTRONVOLT,
                val_desc: 'The desired electron ionization energy in units of eV'
            },
            Parameter.IE: {
                name: 'Ion Energy',
                desc: 'The ion energy: (0:8eV | 1:12eV)',
                val_desc: 'Ion energy level: 0 for Low and 1 for High',
            },
            Parameter.VF: {
                name: 'Focus Plate Voltage',
                desc: 'The focus plate voltage in the ionizer: (0 - 150)',
                val_desc: 'The parameter represents the magnitude of the biasing voltage (negative) in units of volts.',
                units: DriverUnits.VOLT,
            },
            Parameter.NF: {
                name: 'Noise Floor',
                desc: 'Rate and detection limit for ion current measurements: (0 - 7)',
                val_desc: 'The parameter represents the noise-floor level desired. Lower parameter values ' +
                          'correspond to lower baseline noise, better detection limits and increased measurement ' +
                          'times. Please refer to the Electrometer section of the RGA Electronics Control Unit ' +
                          'chapter to obtain detailed information about detection limits and bandwidth values' +
                          'as a function of NF settings.',
            },
            Parameter.SA: {
                name: 'Steps per AMU',
                desc: 'Number of steps executed per amu of analog scan: (10 - 25)',
                val_desc: 'The parameter specifies the number of steps-per-amu.',
                units: DriverUnits.COUNTS,
            },
            Parameter.MI: {
                name: 'Initial Mass',
                desc: 'The initial scan mass: (1 - 200)',
                units: DriverUnits.AMU,
            },
            Parameter.MF: {
                name: 'Final Mass',
                desc: 'The final scan mass: (1 - 200)',
                units: DriverUnits.AMU,
            },
            Parameter.FL: {
                name: 'Electron Emission Current',
                desc: 'Electron emission current level in the ionizer: (0 - 3.5)',
                val_desc: 'The parameter represents the desired electron emission current.',
                units: Prefixes.MILLI + Units.AMPERE
            },
            Parameter.FL_ACTUAL: {
                name: 'Actual Electron Emission Current',
                desc: 'The actual electron emission current level in the ionizer.',
                val_desc: 'The parameter represents the actual electron emission current.',
                units: Prefixes.MILLI + Units.AMPERE
            },
            Parameter.AP: {
                name: 'Analog Scan Points',
                desc: 'The total number of ion currents that will be measured and transmitted ' +
                      'during an analog scan under the current scan conditions.',
                val_desc: 'Total number of ion currents to be transmitted.  Does not include the four extra' +
                          'bytes for total pressure included when performing an analog scan.',
                units: DriverUnits.COUNTS
            },
            Parameter.HV: {
                name: 'High Voltage CDEM',
                desc: 'Electron multiplier high voltage bias setting: (0:disables CDEM, 10 - 2490)',
                val_desc: '0 disables the CDEM, values from 10-2490 enable the CDEM and specify the CDEM bias voltage',
                units: Units.VOLT
            },
            Parameter.ER: {
                name: 'Status Byte',
                desc: 'Bit-mapped value representing any errors detected by the RGA.',
                val_desc: '0 indicates no errors detected.  See the RGA manual if this value is non-zero.',
            },
            Parameter.ERROR_REASON: {
                name: 'RGA Error Reason',
                desc: 'Reason for RGA error state.'
            }
        }

        constraints = ParameterConstraints.dict()
        read_only = [Parameter.ID, Parameter.AP, Parameter.ER, Parameter.FL_ACTUAL, Parameter.ERROR_REASON]
        floats = [Parameter.FL, Parameter.FL_ACTUAL]
        strings = [Parameter.ID, Parameter.ERROR_REASON]

        for param in parameters:
            visibility = ParameterDictVisibility.READ_WRITE
            value_type = ParameterDictType.INT
            formatter = int
            startup = True
            if param in read_only:
                visibility = ParameterDictVisibility.READ_ONLY
                startup = False
            if param in floats:
                value_type = ParameterDictType.FLOAT
                formatter = float
            elif param in strings:
                value_type = ParameterDictType.STRING
                formatter = str

            if param in constraints:
                _type, minimum, maximum = constraints[param]
                parameters[param][val_desc] = '%s %s value from %d - %d' % (parameters[param].get(val_desc, ''),
                                                                            _type, minimum, maximum)

            self._param_dict.add(param, '', None, formatter, type=value_type,
                                 visibility=visibility, startup_param=startup, **parameters[param])

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.START_SCAN, display_name="Start Scan")
        self._cmd_dict.add(Capability.STOP_SCAN, display_name="Stop Scan")
        self._cmd_dict.add(Capability.CLEAR, display_name="Clear Error State")
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

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

    def _got_chunk(self, chunk, ts):
        """
        The base class got_data has gotten a chunk from the chunker.
        We only generate sample particles and they cannot be verified (beyond size, which is done in the chunker).
        Just create a particle, reset the scheduler and start the next scan.
        @param chunk: data to process
        @param ts: timestamp
        """
        elapsed = time.time() - self.scan_start_time
        self.in_scan = False
        log.debug('_got_chunk: Received complete scan.  AP: %d NF: %d SIZE: %d ET: %d secs',
                  self._param_dict.get(Parameter.AP),
                  self._param_dict.get(Parameter.NF),
                  len(chunk),
                  elapsed)
        self._driver_event(DriverAsyncEvent.SAMPLE, RGASampleParticle(chunk, port_timestamp=ts).generate())
        # Reset the scheduler and initiate the next scan if we are in the scan state
        if self.get_current_state() == ProtocolState.SCAN:
            self._build_scheduler()
            self._async_raise_fsm_event(ProtocolEvent.TAKE_SCAN)

    def _generic_response_handler(self, resp, prompt, command=None):
        """
        Generic response handler.  Shove the results into the param dict.
        The associated command should be frozen when the response handler is registered using functools.partial
        @param resp: command response
        @param prompt: not used, required to match signature
        @param command: command which generated response
        @return: response
        """
        parameter = getattr(Parameter, command, None)
        log.debug('_generic_response_handler: command: %s parameter: %s resp: %s', command, parameter, resp)
        if parameter in self._param_dict.get_keys():
            if parameter == Parameter.FL:
                parameter = Parameter.FL_ACTUAL
            try:
                self._param_dict.set_value(parameter, self._param_dict.format(parameter, resp))
            except ValueError:
                # bad data?  Don't set the value, but keep the driver moving forward
                # verify the data if necessary downstream.
                pass
        return resp

    def _generic_build_handler(self, command, *args, **kwargs):
        """
        Generic build handler.  If a value is passed, then this is a set, otherwise it's a query...
        @param command: command to build
        @param args: arglist which may contain a value
        @return: command string
        """
        if len(args) == 1:
            # this is a set action
            value = args[0]
            return self._build_rga_set(command, value) + NEWLINE
        # this is a query
        return self._build_rga_query(command) + NEWLINE

    def _build_rga_set(self, command, value):
        """
        Build a set command
        @param command: command to build
        @param value: value to set
        @return: command string
        """
        return command + str(value)

    def _build_rga_query(self, command):
        """
        Build a query command
        @param command: command to build
        @return: command string
        """
        return command + '?'

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        @param events: list of events to be filtered
        @return: list of events which are in capability
        """
        return [x for x in events if Capability.has(x)]

    def _wakeup(self, timeout, delay=1):
        """
        Wakeup not required for this instrument
        """

    def _build_scheduler(self):
        """
        Remove any previously scheduled event, then generate an absolute trigger to schedule the next
        scan in case we lose some data and the next scan isn't triggered by got_chunk.
        """
        try:
            self._remove_scheduler(ScheduledJob.TAKE_SCAN)
            log.debug('Successfully removed existing scheduled event TAKE_SCAN.')
        except KeyError as ke:
            log.debug('KeyError: %s', ke)

        # this formula was derived from testing, should yield a slightly higher time than the actual
        # time required to collect a single scan.
        delay = self._param_dict.get(Parameter.AP) / 9 / self._param_dict.get(Parameter.NF) + 5

        if delay > 0:
            dt = datetime.datetime.now() + datetime.timedelta(seconds=delay)

            job_name = ScheduledJob.TAKE_SCAN
            config = {
                DriverConfigKey.SCHEDULER: {
                    job_name: {
                        DriverSchedulerConfigKey.TRIGGER: {
                            DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.ABSOLUTE,
                            DriverSchedulerConfigKey.DATE: dt
                        },
                    }
                }
            }

            self.set_init_params(config)
            self._add_scheduler_event(ScheduledJob.TAKE_SCAN, ProtocolEvent.TIMEOUT)

    def _update_params(self, *args, **kwargs):
        """
        Parameters are NOT set in the instrument by this method, as the RGA is configured anytime
        a scan is started, as it may have been powered off since the last time we saw it.
        """

    def _set_params(self, *args, **kwargs):
        """
        Set parameters, raise a CONFIG_CHANGE event if necessary.
        @throws InstrumentParameterException
        """
        self._verify_not_readonly(*args, **kwargs)
        params_to_set = args[0]
        old_config = self._param_dict.get_all()

        # check if in range
        constraints = ParameterConstraints.dict()
        parameters = Parameter.reverse_dict()

        # step through the list of parameters
        for key, val in params_to_set.iteritems():
            # if constraint exists, verify we have not violated it
            constraint_key = parameters.get(key)
            if constraint_key in constraints:
                var_type, minimum, maximum = constraints[constraint_key]
                try:
                    value = var_type(val)
                except ValueError:
                    raise exceptions.InstrumentParameterException(
                        'Unable to verify type - parameter: %s value: %s' % (key, val))
                if val < minimum or val > maximum:
                    raise exceptions.InstrumentParameterException(
                        'Value out of range - parameter: %s value: %s min: %s max: %s' %
                        (key, val, minimum, maximum))

        # all constraints met or no constraints exist, set the values
        for key, val in params_to_set.iteritems():
            if key in old_config:
                self._param_dict.set_value(key, val)
            else:
                raise exceptions.InstrumentParameterException(
                    'Attempted to set unknown parameter: %s value: %s' % (key, val))
        new_config = self._param_dict.get_all()

        # If we changed anything, raise a CONFIG_CHANGE event
        if old_config != new_config:
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def _check_error_byte(self, error_string):
        """
        Check the error byte as returned by some commands
        @param error_string: byte to be checked for errors
        @throws InstrumentStateException
        """
        # trim, just in case we received some garbage with our response...
        if len(error_string) > 1:
            error_string = error_string[-1]
        if int(error_string):
            self._async_raise_fsm_event(ProtocolEvent.ERROR)
            error = 'RGA Error byte set: %s' % error_string
            self._param_dict.set_value(Parameter.ERROR_REASON, error)
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
            raise exceptions.InstrumentStateException(error)

    def _set_instrument_parameter(self, command):
        """
        Set a parameter on the instrument.
        We will attempt up to MAX_SET_RETRIES to set the value correctly, according to the following sequence:
        1. send set command
        2. verify error byte, if returned (per Responses)
        3. send query command
        4. verify returned value equals the set value (within tolerance)
        @throws InstrumentParameterException
        """
        response_type = getattr(Responses, command)
        parameter = getattr(Parameter, command)

        # store the configured setting
        old_value = self._param_dict.format(parameter)

        if old_value is None:
            raise exceptions.InstrumentParameterException('Missing required instrument parameter: %s' % parameter)

        log.debug('response_type: %s parameter: %s command: %s', response_type, getattr(Parameter, command), command)
        # attempt to set the value up to MAX_SET_RETRIES times
        for x in xrange(MAX_RETRIES):
            if response_type == STATUS:
                resp = self._do_cmd_resp(command, old_value)
                self._check_error_byte(resp)
            else:
                self._do_cmd_no_resp(command, old_value)

            # query the value from the instrument to load the parameter dictionary
            self._do_cmd_resp(command)

            # if values match, we were successful, return.
            difference = abs(self._param_dict.format(parameter) - old_value)
            if difference < CLOSE_ENOUGH:
                return
            log.error('Set attempt failed. Parameter: %s Set value: %s Returned value: %s difference: %.2f',
                      parameter, old_value, self._param_dict.get(parameter), difference)

        # configuring the RGA failed, restore the setting from our configuration and raise an exception
        self._param_dict.set_value(parameter, old_value)
        raise exceptions.InstrumentParameterException('Unable to set instrument parameter: %s, attempted %d times' %
                                                      (parameter, MAX_RETRIES))

    def _build_sieve_function(self):
        """
        Build a sieve function based on the expected data size.  Replace the previous sieve function in
        the chunker.  This should happen during the configuration phase.
        """
        num_points = int(self._param_dict.get(Parameter.AP))
        match_string = r'(?<=%s)(.{%d})' % (SCAN_START_SENTINEL, (num_points + 1) * 4)
        matcher = re.compile(match_string, re.DOTALL)

        def my_sieve(raw_data):
            return_list = []

            log.debug('SIEVE: pattern=%r, raw_data_len=%d', matcher.pattern, len(raw_data))
            # do not descend into this loop unless we are at log level trace...
            if log.isEnabledFor('trace'):
                temp = raw_data[:]
                while temp:
                    log.trace('SIEVE: raw_data: %s', temp[:32].encode('hex'))
                    if len(temp) > 32:
                        temp = temp[32:]
                    else:
                        temp = ''

            for match in matcher.finditer(raw_data):
                # if sentinel value present in this slice it is invalid
                if not SCAN_START_SENTINEL in raw_data[match.start():match.end()]:
                    return_list.append((match.start(), match.end()))

            return return_list

        self._chunker.sieve = my_sieve

    def _verify_filament(self):
        """
        Ensure the filament is on and the current is within tolerance
        @throws InstrumentProtocolException
        """
        self._do_cmd_resp(InstrumentCommand.FILAMENT_EMISSION)
        filament_difference = abs(1 - self._param_dict.get(Parameter.FL_ACTUAL))
        if filament_difference > CLOSE_ENOUGH:
            self._async_raise_fsm_event(ProtocolEvent.ERROR)
            error = 'Filament power not withing tolerance (%.2f): %.2f' % (CLOSE_ENOUGH, filament_difference)
            self._param_dict.set_value(Parameter.ERROR_REASON, error)
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
            raise exceptions.InstrumentProtocolException(error)

    def _stop_instrument(self):
        """
        Stop any running scan, flush the output buffer, turn off the filament and CDEM.
        Update the parameter dictionary for FL.
        """
        try:
            self._remove_scheduler(ScheduledJob.TAKE_SCAN)
            log.debug('Successfully removed existing scheduled event TAKE_SCAN.')
        except KeyError as ke:
            log.debug('KeyError: %s', ke)

        self._do_cmd_resp(InstrumentCommand.INITIALIZE, 0)
        self._do_cmd_resp(InstrumentCommand.INITIALIZE, 2)
        self._do_cmd_resp(InstrumentCommand.FILAMENT_EMISSION)
        self.in_scan = False

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

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state
        @return (next_state, result)
        """
        return ProtocolState.COMMAND, ResourceAgentState.IDLE

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

    def _handler_command_get(self, *args, **kwargs):
        """
        Get parameter
        """
        return self._handler_get(*args, **kwargs)

    def _handler_command_set(self, *args, **kwargs):
        """
        Set parameter
        """
        self._set_params(*args, **kwargs)
        return None, None

    def _handler_command_start_direct(self):
        """
        Start direct access
        @return next_state, (next_agent_state, None)
        """
        return ProtocolState.DIRECT_ACCESS, (ResourceAgentState.DIRECT_ACCESS, None)

    def _handler_command_start_scan(self):
        """
        Start a scan
        @return next_state, (next_agent_state, None)
        """
        return ProtocolState.SCAN, (ResourceAgentState.STREAMING, 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_execute_direct(self, data):
        """
        Forward direct access commands to the instrument.
        @return next_state, (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 None, (None, None)

    def _handler_direct_access_stop_direct(self):
        """
        Stop direct access, return to COMMAND.
        @return next_state, (next_agent_state, None)
        """
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    ########################################################################
    # Scan handlers
    ########################################################################

    def _handler_scan_enter(self, *args, **kwargs):
        """
        Enter the scan state.  Configure the RGA, start the first scan and the scheduler.
        @throws InstrumentTimeoutException
        """
        for attempt in range(1, MAX_RETRIES+1):
            try:
                self._handler_scan_configure_rga()
                self._async_raise_fsm_event(ProtocolEvent.TAKE_SCAN)
                self._build_scheduler()
                self._driver_event(DriverAsyncEvent.STATE_CHANGE)
                return
            except exceptions.InstrumentTimeoutException:
                log.error('Failed to configure the RGA - attempt %d', attempt)
        self._async_raise_fsm_event(ProtocolEvent.ERROR)
        error = 'Failed to configure RGA and start scanning.'
        self._param_dict.set_value(Parameter.ERROR_REASON, error)
        self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        raise exceptions.InstrumentTimeoutException(error)

    def _handler_scan_exit(self, *args, **kwargs):
        """
        Exit scan.  Delete the scheduler.
        """
        try:
            self._remove_scheduler(ScheduledJob.TAKE_SCAN)
        except KeyError:
            log.error("_remove_scheduler could not find: %s", ScheduledJob.TAKE_SCAN)

    def _handler_scan_configure_rga(self):
        """
        Send the appropriate configuration to the RGA and update the chunker sieve function for the
        correct data length.
        """
        # initialize the connection
        self._do_cmd_resp(InstrumentCommand.INITIALIZE, 0)

        # set these
        set_commands = [
            (InstrumentCommand.ELECTRON_ENERGY, Parameter.EE),
            (InstrumentCommand.ION_ENERGY, Parameter.IE),
            (InstrumentCommand.FOCUS_VOLTAGE, Parameter.VF),
            (InstrumentCommand.NOISE_FLOOR, Parameter.NF),
            (InstrumentCommand.STEPS_PER_AMU, Parameter.SA),
            (InstrumentCommand.INITIAL_MASS, Parameter.MI),
            (InstrumentCommand.FINAL_MASS, Parameter.MF),
        ]

        for command, parameter in set_commands:
            self._set_instrument_parameter(command)

        # turn on the filament
        self._set_instrument_parameter(InstrumentCommand.FILAMENT_EMISSION)

        # query the read only items
        for command in [InstrumentCommand.READINGS_PER_SCAN, InstrumentCommand.FILAMENT_EMISSION,
                        InstrumentCommand.ID, InstrumentCommand.CHECK_ERRORS]:
            self._do_cmd_resp(command)

        # publish the config as a status particle
        pd = self._param_dict.get_all()
        log.debug('parameter dictionary: %r', pd)
        ts = ntplib.system_to_ntp_time(time.time())
        self._driver_event(DriverAsyncEvent.SAMPLE, RGAStatusParticle(pd, port_timestamp=ts).generate())

        # replace the sieve function
        self._build_sieve_function()

    def _handler_scan_take_scan(self, *args, **kwargs):
        """
        place a sentinel value in the chunker, then perform one analog scan from the RGA
        @return next_state, (next_agent_state, None)
        """
        # empty the chunker
        self._chunker.clean()
        # place sentinel value in chunker
        self._chunker.add_chunk(SCAN_START_SENTINEL, ntplib.system_to_ntp_time(time.time()))
        self.scan_start_time = time.time()
        if self.in_scan:
            log.error('FAILED scan detected, in_scan sentinel set to TRUE')
        self.in_scan = True
        self._do_cmd_no_resp(InstrumentCommand.ANALOG_SCAN, 1)
        return None, (None, None)

    def _handler_scan_timeout(self, *args, **kwargs):
        """
        Handle scan timeout
        @return next_state, (next_agent_state, None)
        """
        # timeout, clear the instrument buffers
        self._do_cmd_resp(InstrumentCommand.INITIALIZE, 0)
        # verify the filament is still on
        self._verify_filament()
        return self._handler_scan_take_scan()

    def _handler_scan_stop_scan(self, *args, **kwargs):
        """
        Stop scanning, go to COMMAND.
        @return next_state, (next_agent_state, None)
        """
        self._stop_instrument()
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    def _handler_scan_error(self, *args, **kwargs):
        """
        Stop scanning, go to ERROR.
        @return next_state, (next_agent_state, None)
        """
        self._stop_instrument()
        return ProtocolState.ERROR, (ResourceAgentState.COMMAND, None)

    ########################################################################
    # Error handlers
    ########################################################################

    def _handler_error_clear(self):
        """
        Leave the error state, return to COMMAND.
        @return next_state, (next_agent_state, None)
        """
        self._param_dict.set_value(Parameter.ERROR_REASON, '')
        self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    ########################################################################
    # Generic handlers
    ########################################################################

    def _handler_generic_enter(self):
        """
        Generic method to handle entering state.
        """
        if self.get_current_state() != ProtocolState.UNKNOWN:
            self._init_params()
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_generic_exit(self):
        """
Exemplo n.º 35
0
class Protocol(CommandResponseInstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    __metaclass__ = METALOGGER

    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),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_command_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT,
                 self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START1, self._handler_command_start1),
                (ProtocolEvent.NAFREG, self._handler_command_nafreg),
                (ProtocolEvent.IONREG, self._handler_command_ionreg),
                (ProtocolEvent.POWEROFF, self._handler_command_poweroff),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.START1: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START1_COMPLETE, self._handler_start1_complete),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.WAITING_TURBO: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.START2, self._handler_waiting_turbo_start2),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.START2: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START2_COMPLETE, self._handler_start2_complete),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.WAITING_RGA: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.SAMPLE, self._handler_waiting_rga_sample),
                (ProtocolEvent.CALIBRATE, self._handler_waiting_rga_cal),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.SAMPLE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.SAMPLE_COMPLETE, self._handler_sample_complete),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.CALIBRATE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.CALIBRATE_COMPLETE, self._handler_cal_complete),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.STOPPING: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.REGEN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_DIRECT, self._handler_stop),
                (ProtocolEvent.EXECUTE_DIRECT,
                 self._handler_direct_access_execute_direct),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STANDBY, self._handler_error_standby),
                (ProtocolEvent.CLEAR, self._handler_stop),
            ],
        }

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

        # response handlers
        for command in InstrumentCommand.list():
            self._add_response_handler(
                command,
                functools.partial(self._generic_response_handler,
                                  command=command))

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # Add build handlers for device commands.
        for command in InstrumentCommand.list():
            if command == InstrumentCommand.SET_TELEGRAM_INTERVAL:
                self._add_build_handler(command,
                                        self._build_telegram_interval_command)
            elif command == InstrumentCommand.SAMPLE:
                self._add_build_handler(command, self._build_sample_command)
            elif command == InstrumentCommand.SET_MINUTE:
                self._add_build_handler(command,
                                        self._build_set_minute_command)
            else:
                self._add_build_handler(command, self._build_simple_command)

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._chunker = StringChunker(Protocol.sieve_function)

        self.resetting = False

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples
        @param raw_data - data to be searched
        """
        matchers = []
        return_list = []

        matchers.append(McuDataParticle.regex_compiled())
        matchers.append(re.compile(r'(M .*?)(?=\r)'))
        matchers.append(re.compile(r'(E\d{3}.*?)(?=\r)'))

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

        return return_list

    def _build_param_dict(self):
        """
        Build the parameter dictionary
        """
        self._param_dict.add(
            Parameter.TELEGRAM_INTERVAL,
            '',
            None,
            None,
            type=ParameterDictType.INT,
            startup_param=True,
            display_name='Data Telegram Interval in Sample',
            units=Prefixes.MILLI + Units.SECOND,
            description='The interval between successive MCU data telegrams' +
            ' while in the SAMPLE/CAL state: (1 - 30000)')
        self._param_dict.add(
            Parameter.SAMPLE_TIME,
            '',
            None,
            None,
            type=ParameterDictType.INT,
            startup_param=True,
            display_name='Sample Cycle Time',
            units=Units.MINUTE,
            description=
            'The length of each portion of the sample cycle: (1 - 99)')
        self._param_dict.add(
            Parameter.ONE_MINUTE,
            '',
            None,
            None,
            visibility=ParameterDictVisibility.IMMUTABLE,
            type=ParameterDictType.INT,
            startup_param=True,
            default_value=60000,
            display_name='Length of One Minute',
            units=Prefixes.MILLI + Units.SECOND,
            description=
            'MCU timing constant representing the number of seconds per minute: (1 - 99999)'
        )
        self._param_dict.add(Parameter.ERROR_REASON,
                             '',
                             None,
                             None,
                             visibility=ParameterDictVisibility.READ_ONLY,
                             type=ParameterDictType.STRING,
                             value='',
                             display_name='Reason for Error State',
                             description='MCU reason for error state.')

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.START1, display_name="Execute ASTART1")
        self._cmd_dict.add(Capability.START2, display_name="Execute ASTART2")
        self._cmd_dict.add(Capability.SAMPLE, display_name="Execute ASAMPLEXX")
        self._cmd_dict.add(Capability.CALIBRATE, display_name="Execute ACAL9")
        self._cmd_dict.add(Capability.NAFREG,
                           display_name="Execute U ANAFREG3")
        self._cmd_dict.add(Capability.IONREG,
                           display_name="Execute U AIONREG3")
        self._cmd_dict.add(Capability.STANDBY,
                           display_name="Execute U ASTANDBY")
        self._cmd_dict.add(Capability.CLEAR, display_name="Clear Error State")
        self._cmd_dict.add(Capability.POWEROFF,
                           display_name="Execute U APOWEROFF")
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

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

    def _build_telegram_interval_command(self, *args, **kwargs):
        """
        Build the telegram interval command using the TELEGRAM_INTERVAL parameter
        """
        return '%s%08d%s' % (
            InstrumentCommand.SET_TELEGRAM_INTERVAL,
            int(self._param_dict.get(Parameter.TELEGRAM_INTERVAL)), NEWLINE)

    def _build_set_minute_command(self, *args, **kwargs):
        """
        Build the SETMINUTE command
        """
        return '%s%05d%s' % (InstrumentCommand.SET_MINUTE,
                             int(self._param_dict.get(
                                 Parameter.ONE_MINUTE)), NEWLINE)

    def _build_sample_command(self, *args, **kwargs):
        """
        Build the SAMPLE command
        """
        return '%s%02d%s' % (InstrumentCommand.SAMPLE,
                             int(self._param_dict.get(
                                 Parameter.SAMPLE_TIME)), NEWLINE)

    def _got_chunk(self, chunk, ts):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and regexes.

        Raise specific events on receipt of chunks.  This allows the driver to react asynchronously.

        @param chunk - data to be converted to a particle
        @param ts - timestamp
        """
        event = None
        exception = None
        sample = self._extract_sample(McuDataParticle,
                                      McuDataParticle.regex_compiled(), chunk,
                                      ts)
        if sample:
            return

        # we don't want to act on any responses in direct access or command mode
        # so just return here if that's the case...
        current_state = self.get_current_state()
        if current_state in [
                ProtocolState.DIRECT_ACCESS, ProtocolState.COMMAND
        ]:
            return

        # These responses (may) come from the instrument asynchronously, so they are handled
        # here rather than in a response handler.
        ignored = [Prompt.OK, Prompt.BEAT, Prompt.STANDBY]
        if chunk in ignored:
            pass
        elif chunk == Prompt.START1:
            event = ProtocolEvent.START1_COMPLETE
        elif chunk == Prompt.START2:
            event = ProtocolEvent.START2_COMPLETE
        elif chunk == Prompt.SAMPLE_FINISHED:
            event = ProtocolEvent.SAMPLE_COMPLETE
        elif chunk == Prompt.CAL_FINISHED:
            event = ProtocolEvent.CALIBRATE_COMPLETE
        elif chunk == Prompt.IONREG_FINISHED:
            event = ProtocolEvent.STANDBY
        elif chunk == Prompt.NAFREG_FINISHED:
            event = ProtocolEvent.STANDBY
        elif chunk == Prompt.ERROR:
            event = ProtocolEvent.ERROR
            self._param_dict.set_value(
                Parameter.ERROR_REASON,
                'Error prompt received from instrument.')
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        elif chunk == Prompt.ONLINE:
            if not self.resetting:
                # This is an unexpected reset, ignore if we are in command or error
                if current_state == ProtocolState.ERROR:
                    event = ProtocolEvent.ERROR
                    self._param_dict.set_value(Parameter.ERROR_REASON,
                                               'MCU reset during sequence.')
                    self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        elif chunk in [
                Prompt.NAFTEMP_NOT_ACHIEVED, Prompt.IONTEMP_NOT_ACHIEVED
        ]:
            # regeneration temperature not achieved, move to COMMAND and raise an exception
            event = ProtocolEvent.STANDBY
            exception = InstrumentProtocolException(
                'Failed to achieve regen temperature')
        else:
            log.error('Unhandled chunk: %r in state: %s', chunk, current_state)
            exception = InstrumentProtocolException(
                'Unhandled chunk: %r in state: %s' % (chunk, current_state))

        if event is not None:
            self._async_raise_fsm_event(event)
        if exception:
            self._driver_event(DriverAsyncEvent.ERROR, exception)

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        @param events - events to be filtered
        @return list of events which are also capabilities
        """
        return [x for x in events if Capability.has(x)]

    def _wakeup(self, *args, **kwargs):
        """
        Not needed, the MCU never sleeps...
        """

    def _generic_response_handler(self, result, prompt, command=None):
        """
        Generic response handler to pass results through unmodified.
        @param result - result
        @param prompt - prompt
        @command - Command which generated the result
        @return result
        """
        return result

    def _set_params(self, *args, **kwargs):
        """
        This instrument has no params
        @throws InstrumentParameterException
        """
        self._verify_not_readonly(*args, **kwargs)
        params_to_set = args[0]
        startup = False
        if len(args) > 1:
            startup = args[1]
        old_config = self._param_dict.get_all()

        # check if in range
        constraints = ParameterConstraint.dict()
        parameters = Parameter.reverse_dict()

        # step through the list of parameters
        for key, val in params_to_set.iteritems():
            # if constraint exists, verify we have not violated it
            constraint_key = parameters.get(key)
            if constraint_key in constraints:
                var_type, minimum, maximum = constraints[constraint_key]
                try:
                    value = var_type(val)
                except ValueError:
                    raise InstrumentParameterException(
                        'Unable to verify type - parameter: %s value: %s' %
                        (key, val))
                if val < minimum or val > maximum:
                    raise InstrumentParameterException(
                        'Value out of range - parameter: %s value: %s min: %s max: %s'
                        % (key, val, minimum, maximum))

        # all constraints met or no constraints exist, set the values
        for key, val in params_to_set.iteritems():
            if key in old_config:
                self._param_dict.set_value(key, val)
            else:
                raise InstrumentParameterException(
                    'Attempted to set unknown parameter: %s value: %s' %
                    (key, val))
        new_config = self._param_dict.get_all()

        # If we changed anything, raise a CONFIG_CHANGE event
        if old_config != new_config:
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def _reset_mcu(self):
        """
        Reset the MCU via the watchdog timer
        """
        try:
            self.resetting = True
            # set the watchdog timer
            self._do_cmd_resp(InstrumentCommand.SET_WATCHDOG,
                              expected_prompt=Prompt.OK,
                              timeout=60)
            # try to put the MCU in standby, if successful watchdog will reset MCU
            result = self._do_cmd_resp(
                InstrumentCommand.STANDBY,
                expected_prompt=[Prompt.ONLINE, Prompt.IN_SEQUENCE],
                timeout=60)
            # MCU was in sequence, abort it and then go standby to reset MCU
            if result == Prompt.IN_SEQUENCE:
                self._do_cmd_resp(InstrumentCommand.ABORT,
                                  expected_prompt=Prompt.ABORTED,
                                  timeout=60)
                self._do_cmd_resp(InstrumentCommand.STANDBY,
                                  expected_prompt=Prompt.ONLINE,
                                  timeout=60)
            # MCU expects a BEAT after reset, send it
            self._do_cmd_resp(InstrumentCommand.BEAT,
                              expected_prompt=Prompt.BEAT)
            # set the MINUTE value
            self._do_cmd_resp(InstrumentCommand.SET_MINUTE,
                              expected_prompt=Prompt.SET_MINUTE)
            # This should actually put us in standby
            self._do_cmd_resp(InstrumentCommand.STANDBY,
                              expected_prompt=Prompt.STANDBY,
                              timeout=60)
        finally:
            self.resetting = False

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

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state
        @return_value (next_state, result)
        """
        return ProtocolState.COMMAND, ResourceAgentState.IDLE

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

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.  Break out of any currently running sequence and return the MCU to STANDBY
        """
        self._init_params()

        try:
            self._reset_mcu()
        except InstrumentTimeoutException:
            # something else is wrong, pass the buck to the operator
            self._param_dict.set_value(
                Parameter.ERROR_REASON,
                'Timeout communicating with instrument.')
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
            self._async_raise_fsm_event(ProtocolEvent.ERROR)

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

    def _handler_command_get(self, *args, **kwargs):
        """
        This driver has no parameters, return an empty dict.
        """
        return self._handler_get(*args, **kwargs)

    def _handler_command_set(self, *args, **kwargs):
        """
        Set parameter
        """
        self._set_params(*args, **kwargs)
        return None, None

    def _handler_command_start_direct(self):
        """
        Start direct access
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.DIRECT_ACCESS, (ResourceAgentState.DIRECT_ACCESS,
                                             None)

    def _handler_command_start1(self):
        """
        Send the start1 command and move to the start1 state
        @return next_state, (next_agent_state, result)
        """
        self._reset_mcu()
        return ProtocolState.START1, (ResourceAgentState.BUSY,
                                      self._do_cmd_resp(
                                          InstrumentCommand.START1))

    def _handler_command_nafreg(self):
        """
        Send the nafreg command and move to the nafreg state
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.REGEN, (ResourceAgentState.BUSY,
                                     self._do_cmd_resp(
                                         InstrumentCommand.NAFREG))

    def _handler_command_ionreg(self):
        """
        Send the ionreg command and move to the ionreg state
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.REGEN, (ResourceAgentState.BUSY,
                                     self._do_cmd_resp(
                                         InstrumentCommand.IONREG))

    def _handler_command_poweroff(self):
        """
        Send the ionreg command and move to the ionreg state
        @return next_state, (next_agent_state, result)
        """
        return None, (None, self._do_cmd_resp(InstrumentCommand.POWEROFF))

    ########################################################################
    # START1 handlers.
    ########################################################################

    def _handler_start1_complete(self):
        """
        Start1 sequence complete, move to waiting_turbo
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.WAITING_TURBO, (ResourceAgentState.IDLE, None)

    ########################################################################
    # WAITING_TURBO handlers.
    ########################################################################

    def _handler_waiting_turbo_start2(self):
        """
        Turbo is at speed, send start2 and move to start2 state
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.START2, (ResourceAgentState.BUSY,
                                      self._do_cmd_resp(
                                          InstrumentCommand.START2))

    ########################################################################
    # START2 handlers.
    ########################################################################

    def _handler_start2_complete(self):
        """
        Start2 complete, move to waiting_rga state
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.WAITING_RGA, (ResourceAgentState.BUSY, None)

    ########################################################################
    # WAITING_RGA handlers.
    ########################################################################

    def _handler_waiting_rga_sample(self):
        """
        RGA configuration/startup complete, send start sample and move to sample state
        @return next_state, (next_agent_state, result)
        """
        result = self._do_cmd_resp(InstrumentCommand.SAMPLE)
        self._do_cmd_resp(InstrumentCommand.SET_TELEGRAM_INTERVAL)
        return ProtocolState.SAMPLE, (ResourceAgentState.BUSY, result)

    def _handler_waiting_rga_cal(self):
        """
        RGA configuration/startup complete, send start cal and move to cal state
        @return next_state, (next_agent_state, result)
        """
        result = self._do_cmd_resp(InstrumentCommand.CAL)
        self._do_cmd_resp(InstrumentCommand.SET_TELEGRAM_INTERVAL)
        return ProtocolState.CALIBRATE, (ResourceAgentState.BUSY, result)

    ########################################################################
    # SAMPLE handlers.
    ########################################################################

    def _handler_sample_complete(self):
        """
        Sample complete, move to the stopping state.
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.STOPPING, (ResourceAgentState.BUSY, None)

    ########################################################################
    # CALIBRATE handlers.
    ########################################################################

    def _handler_cal_complete(self):
        """
        Cal complete, move to the stopping state.
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.STOPPING, (ResourceAgentState.BUSY, None)

    ########################################################################
    # ERROR handler. Handle in all states.
    ########################################################################

    def _handler_error(self):
        """
        Error detected, move to error state.
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.ERROR, (ResourceAgentState.BUSY, None)

    def _handler_stop(self):
        """
        Return to COMMAND
        """
        if self._param_dict.get(Parameter.ERROR_REASON):
            self._param_dict.set_value(Parameter.ERROR_REASON, '')
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

    def _handler_error_standby(self):
        """
        Move instrument to STANDBY, stay in error state
        """
        self._reset_mcu()

    ########################################################################
    # GENERIC handlers.
    ########################################################################

    def _handler_generic_enter(self, *args, **kwargs):
        """
        Generic enter handler
        """
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_generic_exit(self, *args, **kwargs):
        """
        Generic exit handler
        """

    ########################################################################
    # 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):
        """
        Pass direct access commands through to the instrument
        @return next_state, (next_agent_state, result)
        """
        self._do_cmd_direct(data)

        # add sent command to list for 'echo' filtering in callback
        self._sent_cmds.append(data)
        return None, (None, None)
Exemplo n.º 36
0
    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),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_command_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT,
                 self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START1, self._handler_command_start1),
                (ProtocolEvent.NAFREG, self._handler_command_nafreg),
                (ProtocolEvent.IONREG, self._handler_command_ionreg),
                (ProtocolEvent.POWEROFF, self._handler_command_poweroff),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.START1: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START1_COMPLETE, self._handler_start1_complete),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.WAITING_TURBO: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.START2, self._handler_waiting_turbo_start2),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.START2: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START2_COMPLETE, self._handler_start2_complete),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.WAITING_RGA: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.SAMPLE, self._handler_waiting_rga_sample),
                (ProtocolEvent.CALIBRATE, self._handler_waiting_rga_cal),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.SAMPLE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.SAMPLE_COMPLETE, self._handler_sample_complete),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.CALIBRATE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.CALIBRATE_COMPLETE, self._handler_cal_complete),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.STOPPING: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.REGEN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STANDBY, self._handler_stop),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_DIRECT, self._handler_stop),
                (ProtocolEvent.EXECUTE_DIRECT,
                 self._handler_direct_access_execute_direct),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STANDBY, self._handler_error_standby),
                (ProtocolEvent.CLEAR, self._handler_stop),
            ],
        }

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

        # response handlers
        for command in InstrumentCommand.list():
            self._add_response_handler(
                command,
                functools.partial(self._generic_response_handler,
                                  command=command))

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # Add build handlers for device commands.
        for command in InstrumentCommand.list():
            if command == InstrumentCommand.SET_TELEGRAM_INTERVAL:
                self._add_build_handler(command,
                                        self._build_telegram_interval_command)
            elif command == InstrumentCommand.SAMPLE:
                self._add_build_handler(command, self._build_sample_command)
            elif command == InstrumentCommand.SET_MINUTE:
                self._add_build_handler(command,
                                        self._build_set_minute_command)
            else:
                self._add_build_handler(command, self._build_simple_command)

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._chunker = StringChunker(Protocol.sieve_function)

        self.resetting = False
Exemplo n.º 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
Exemplo n.º 38
0
    def __init__(self, prompts, newline, driver_event):
        """
        SBE43Protocol constructor.
        @param prompts A BaseEnum class containing instrument prompts.
        @param newline The SBE43 newline.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        CommandResponseInstrumentProtocol.__init__(self, prompts, newline,
                                                   driver_event)

        # Build SBE19 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_generic_exit),
                (ProtocolEvent.DISCOVER, self._handler_unknown_discover),
            ],
            ProtocolState.COMMAND:
            [(ProtocolEvent.ENTER, self._handler_command_enter),
             (ProtocolEvent.EXIT, self._handler_generic_exit),
             (ProtocolEvent.ACQUIRE_SAMPLE,
              self._handler_command_acquire_sample),
             (ProtocolEvent.START_AUTOSAMPLE,
              self._handler_command_start_autosample),
             (ProtocolEvent.GET, self._handler_get),
             (ProtocolEvent.SET, self._handler_command_set),
             (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
             (ProtocolEvent.CLOCK_SYNC,
              self._handler_command_clock_sync_clock),
             (ProtocolEvent.ACQUIRE_STATUS,
              self._handler_command_acquire_status)],
            ProtocolState.ACQUIRING_SAMPLE: [
                (ProtocolEvent.ENTER, self._handler_acquiring_sample_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_SAMPLE_ASYNC,
                 self._handler_acquire_sample_async),
            ],
            ProtocolState.DIRECT_ACCESS:
            [(ProtocolEvent.ENTER, self._handler_direct_access_enter),
             (ProtocolEvent.EXIT, self._handler_generic_exit),
             (ProtocolEvent.EXECUTE_DIRECT,
              self._handler_direct_access_execute_direct),
             (ProtocolEvent.STOP_DIRECT,
              self._handler_direct_access_stop_direct)],
            ProtocolState.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_autosample_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.GET, self._handler_get),
                (ProtocolEvent.STOP_AUTOSAMPLE,
                 self._handler_autosample_stop_autosample),
                (ProtocolEvent.SCHEDULED_ACQUIRED_STATUS,
                 self._handler_autosample_acquire_status),
            ]
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_driver_dict()
        self._build_command_dict()
        self._build_param_dict()

        # Add build handlers for device commands, only using simple command handler.
        for cmd in Command.list():
            if cmd == Command.SET:
                self._add_build_handler(Command.SET, self._build_set_command)
            else:
                self._add_build_handler(cmd, self._build_simple_command)

        # Add response handlers for device commands.
        # these are here to ensure that correct responses to the commands are received before the next command is sent
        self._add_response_handler(Command.SET, self._parse_set_response)
        self._add_response_handler(Command.GET_SD,
                                   self._validate_GetSD_response)
        self._add_response_handler(Command.GET_HD,
                                   self._validate_GetHD_response)
        self._add_response_handler(Command.GET_CD,
                                   self._validate_GetCD_response)
        self._add_response_handler(Command.GET_CC,
                                   self._validate_GetCC_response)
        self._add_response_handler(Command.GET_EC,
                                   self._validate_GetEC_response)

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

        self._chunker = StringChunker(self.sieve_function)
Exemplo n.º 39
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.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_AUTOSAMPLE,
                 self._handler_autosample_stop_autosample),
                (ProtocolEvent.START_LEVELING, self._handler_start_leveling),
                (ProtocolEvent.STOP_LEVELING, self._handler_stop_leveling),
                (ProtocolEvent.NANO_TIME_SYNC, self._handler_time_sync),
                (ProtocolEvent.START_HEATER, self._handler_start_heater),
                (ProtocolEvent.STOP_HEATER, self._handler_stop_heater),
                (ProtocolEvent.LEVELING_TIMEOUT,
                 self._handler_leveling_timeout),
                (ProtocolEvent.HEATER_TIMEOUT, self._handler_heater_timeout),
            ],
            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.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.START_AUTOSAMPLE,
                 self._handler_command_start_autosample),
                (ProtocolEvent.START_LEVELING, self._handler_start_leveling),
                (ProtocolEvent.STOP_LEVELING, self._handler_stop_leveling),
                (ProtocolEvent.START_DIRECT,
                 self._handler_command_start_direct),
                (ProtocolEvent.NANO_TIME_SYNC, self._handler_time_sync),
                (ProtocolEvent.START_HEATER, self._handler_start_heater),
                (ProtocolEvent.STOP_HEATER, self._handler_stop_heater),
                (ProtocolEvent.LEVELING_TIMEOUT,
                 self._handler_leveling_timeout),
                (ProtocolEvent.HEATER_TIMEOUT, self._handler_heater_timeout),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_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)

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

        # Add build handlers for device commands.
        for command in InstrumentCommand.list():
            if command in [
                    InstrumentCommand.NANO_SET_RATE, InstrumentCommand.HEAT
            ]:
                self._add_build_handler(command,
                                        self._build_command_with_value)
            else:
                self._add_build_handler(command, self._build_simple_command)

        # # Add response handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_response_handler(command, self._generic_response_handler)

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

        # commands sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        # create chunker
        self._chunker = StringChunker(Protocol.sieve_function)

        self._last_data_timestamp = 0
        self.has_pps = True

        # set up scheduled event handling
        self.initialize_scheduler()
        self._add_scheduler_event(ScheduledJob.ACQUIRE_STATUS,
                                  ProtocolEvent.ACQUIRE_STATUS)
        self._add_scheduler_event(ScheduledJob.NANO_TIME_SYNC,
                                  ProtocolEvent.NANO_TIME_SYNC)

    @staticmethod
    def sieve_function(raw_data):
        """
        Sort data in the chunker...
        @param raw_data: Data to be searched for samples
        @return: list of (start,end) tuples
        """
        matchers = []
        return_list = []

        matchers.append(particles.HeatSampleParticle.regex_compiled())
        matchers.append(particles.IrisSampleParticle.regex_compiled())
        matchers.append(particles.NanoSampleParticle.regex_compiled())
        matchers.append(particles.LilySampleParticle.regex_compiled())
        matchers.append(particles.LilyLevelingParticle.regex_compiled())

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

        return return_list

    def _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
        """
        possible_particles = [
            (particles.LilySampleParticle, self._check_for_autolevel),
            (particles.LilyLevelingParticle, self._check_completed_leveling),
            (particles.HeatSampleParticle, None),
            (particles.IrisSampleParticle, None),
            (particles.NanoSampleParticle, self._check_pps_sync),
        ]

        for particle_type, func in possible_particles:
            sample = self._extract_sample(particle_type,
                                          particle_type.regex_compiled(),
                                          chunk, ts)
            if sample:
                if func:
                    func(sample)
                return sample

        raise InstrumentProtocolException(
            u'unhandled chunk received by _got_chunk: [{0!r:s}]'.format(chunk))

    def _extract_sample(self,
                        particle_class,
                        regex,
                        line,
                        timestamp,
                        publish=True):
        """
        Overridden to set the quality flag for LILY particles that are out of range.
        @param particle_class: Class type for particle
        @param regex: regular expression to verify data
        @param line: data
        @param timestamp: ntp timestamp
        @param publish: boolean to indicate if sample should be published
        @return: extracted sample
        """
        if regex.match(line):
            if particle_class == particles.LilySampleParticle and self._param_dict.get(
                    Parameter.LEVELING_FAILED):
                particle = particle_class(
                    line,
                    port_timestamp=timestamp,
                    quality_flag=DataParticleValue.OUT_OF_RANGE)
            else:
                particle = particle_class(line, port_timestamp=timestamp)
            parsed_sample = particle.generate()

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

            return parsed_sample

    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")
        self._cmd_dict.add(Capability.ACQUIRE_STATUS,
                           display_name="Acquire instrument status")
        self._cmd_dict.add(Capability.START_LEVELING,
                           display_name="Start the LILY leveling sequence")
        self._cmd_dict.add(Capability.STOP_LEVELING,
                           display_name="Stop the LILY leveling sequence")
        self._cmd_dict.add(Capability.START_HEATER,
                           display_name="Start the heater")
        self._cmd_dict.add(Capability.STOP_HEATER,
                           display_name="Stop the heater")

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with parameters.
        For each parameter key, add match string, match lambda function,
        and value formatting function for set commands.
        """
        my_regex = 'Not used'
        ro, rw = ParameterDictVisibility.READ_ONLY, ParameterDictVisibility.READ_WRITE
        _bool, _float, _int = ParameterDictType.BOOL, ParameterDictType.FLOAT, ParameterDictType.INT

        parameters = {
            Parameter.AUTO_RELEVEL: {
                'type': _bool,
                'display_name': 'Automatic Releveling Enabled',
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.XTILT_TRIGGER: {
                'type': _float,
                'display_name': 'X-tilt Releveling Trigger',
                'units': Prefixes.MICRO + Units.RADIAN,
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.YTILT_TRIGGER: {
                'type': _float,
                'display_name': 'Y-tilt Releveling Trigger',
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.LEVELING_TIMEOUT: {
                'type': _int,
                'display_name': 'LILY Leveling Timeout',
                'units': Units.SECOND,
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.HEAT_DURATION: {
                'type': _int,
                'display_name': 'Heater Run Time Duration',
                'units': Units.HOUR,
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.OUTPUT_RATE: {
                'type': _int,
                'display_name': 'NANO Output Rate',
                'units': Units.HERTZ,
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.HEATER_ON: {
                'type': _bool,
                'display_name': 'Heater Running',
                'value': False,
                'visibility': ro,
            },
            Parameter.LILY_LEVELING: {
                'type': _bool,
                'display_name': 'Lily Leveling',
                'value': False,
                'visibility': ro,
            },
            Parameter.LEVELING_FAILED: {
                'type': _bool,
                'display_name': 'LILY Leveling Failed',
                'value': False,
                'visibility': ro,
            },
        }
        for param in parameters:
            self._param_dict.add(param, my_regex, None, None,
                                 **parameters[param])

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

    def _build_command_with_value(self, cmd, value):
        """
        Build a simple command with one value specified
        @param cmd: instrument command
        @param value: value to be sent
        @return: command string
        """
        return '%s%d%s' % (cmd, value, NEWLINE)

    def _verify_set_values(self, params):
        """
        Verify supplied values are in range, if applicable
        @param params: Dictionary of Parameter:value pairs to be verified
        @throws InstrumentParameterException
        """
        constraints = ParameterConstraint.dict()
        parameters = Parameter.reverse_dict()

        # step through the list of parameters
        for key, val in params.iteritems():
            # verify this parameter exists
            if not Parameter.has(key):
                raise InstrumentParameterException(
                    'Received invalid parameter in SET: %s' % key)
            # if constraint exists, verify we have not violated it
            constraint_key = parameters.get(key)
            if constraint_key in constraints:
                var_type, minimum, maximum = constraints[constraint_key]
                constraint_string = 'Parameter: %s Value: %s Type: %s Minimum: %s Maximum: %s' % \
                                    (key, val, var_type, minimum, maximum)
                log.debug('SET CONSTRAINT: %s', constraint_string)
                # check bool values are actual booleans
                if var_type == bool:
                    if val not in [True, False]:
                        raise InstrumentParameterException(
                            'Non-boolean value!: %s' % constraint_string)
                # else, check if we can cast to the correct type
                else:
                    try:
                        var_type(val)
                    except ValueError:
                        raise InstrumentParameterException(
                            'Type mismatch: %s' % constraint_string)
                    # now, verify we are within min/max
                    if val < minimum or val > maximum:
                        raise InstrumentParameterException('Out of range: %s' %
                                                           constraint_string)

    def _set_params(self, *args, **kwargs):
        """
        Issue commands to the instrument to set various parameters
        @param args: arglist, should contain a dictionary of parameters/values to be set
        """
        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException(
                'Set command requires a parameter dict.')

        self._verify_set_values(params)
        self._verify_not_readonly(*args, **kwargs)

        # if setting the output rate, get the current rate from the instrument first...
        if Parameter.OUTPUT_RATE in params:
            self._update_params()

        old_config = self._param_dict.get_config()

        # all constraints met or no constraints exist, set the values
        for key, value in params.iteritems():
            self._param_dict.set_value(key, value)

        new_config = self._param_dict.get_config()

        if not old_config == new_config:
            log.debug('Config change: %r %r', old_config, new_config)
            if old_config[Parameter.OUTPUT_RATE] is not None:
                if int(old_config[Parameter.OUTPUT_RATE]) != int(
                        new_config[Parameter.OUTPUT_RATE]):
                    self._do_cmd_no_resp(
                        InstrumentCommand.NANO_SET_RATE,
                        int(new_config[Parameter.OUTPUT_RATE]))
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def _update_params(self, *args, **kwargs):
        """
        Update the param dictionary based on instrument response
        """
        result, _ = self._do_cmd_resp(
            InstrumentCommand.NANO_DUMP1,
            response_regex=particles.NanoStatusParticle.regex_compiled())
        rate = int(re.search(r'NANO,\*TH:(\d+)', result).group(1))
        self._param_dict.set_value(Parameter.OUTPUT_RATE, rate)

    def _wakeup(self, timeout, delay=1):
        """
        Overriding _wakeup; does not apply to this instrument
        """

    def add_to_buffer(self, data):
        """
        Overriding base class to reduce logging due to NANO high data rate
        @param data: data to be added to buffers
        """
        # Update the line and prompt buffers.
        self._linebuf += data
        self._promptbuf += data
        self._last_data_timestamp = time.time()

        # If our buffer exceeds the max allowable size then drop the leading
        # characters on the floor.
        max_size = self._max_buffer_size()
        if len(self._linebuf) > max_size:
            self._linebuf = self._linebuf[max_size * -1:]

        # If our buffer exceeds the max allowable size then drop the leading
        # characters on the floor.
        if len(self._promptbuf) > max_size:
            self._promptbuf = self._linebuf[max_size * -1:]

    def _max_buffer_size(self):
        """
        Overriding base class to increase max buffer size
        @return int max_buffer_size
        """
        return MAX_BUFFER_SIZE

    def _remove_leveling_timeout(self):
        """
        Clean up the leveling timer
        """
        try:
            self._remove_scheduler(ScheduledJob.LEVELING_TIMEOUT)
        except KeyError:
            log.debug(
                'Unable to remove LEVELING_TIMEOUT scheduled job, job does not exist.'
            )

    def _schedule_leveling_timeout(self):
        """
        Set up a leveling timer to make sure we don't stay in leveling state forever if something goes wrong
        """
        self._remove_leveling_timeout()
        dt = datetime.datetime.now() + datetime.timedelta(
            seconds=self._param_dict.get(Parameter.LEVELING_TIMEOUT))
        job_name = ScheduledJob.LEVELING_TIMEOUT
        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE:
                        TriggerType.ABSOLUTE,
                        DriverSchedulerConfigKey.DATE: dt
                    },
                }
            }
        }

        self.set_init_params(config)
        self._add_scheduler_event(ScheduledJob.LEVELING_TIMEOUT,
                                  ProtocolEvent.LEVELING_TIMEOUT)

    def _remove_heater_timeout(self):
        """
        Clean up the heater timer
        """
        try:
            self._remove_scheduler(ScheduledJob.HEATER_TIMEOUT)
        except KeyError:
            log.debug(
                'Unable to remove HEATER_TIMEOUT scheduled job, job does not exist.'
            )

    def _schedule_heater_timeout(self):
        """
        Set up a timer to set HEATER_ON to false around the time the heater shuts off
        """
        self._remove_heater_timeout()
        dt = datetime.datetime.now() + datetime.timedelta(
            hours=self._param_dict.get(Parameter.HEAT_DURATION))
        job_name = ScheduledJob.HEATER_TIMEOUT
        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE:
                        TriggerType.ABSOLUTE,
                        DriverSchedulerConfigKey.DATE: dt
                    },
                }
            }
        }

        self.set_init_params(config)
        self._add_scheduler_event(ScheduledJob.HEATER_TIMEOUT,
                                  ProtocolEvent.HEATER_TIMEOUT)

    def _stop_autosample(self):
        """
        Stop autosample, leveling if in progress.
        """
        self.leveling = False
        self._do_cmd_no_resp(InstrumentCommand.NANO_OFF)
        self._do_cmd_resp(InstrumentCommand.LILY_STOP_LEVELING,
                          expected_prompt=Prompt.LILY_STOP_LEVELING)
        self._do_cmd_resp(InstrumentCommand.LILY_OFF,
                          expected_prompt=Prompt.LILY_OFF)
        self._do_cmd_resp(InstrumentCommand.IRIS_OFF,
                          expected_prompt=Prompt.IRIS_OFF)

    def _generic_response_handler(self, resp, prompt):
        """
        Pass through response handler
        @param resp: response
        @param prompt: prompt
        @return: (response, prompt)
        """
        return resp, prompt

    def _particle_to_dict(self, sample):
        """
        Convert a particle to a dictionary of value_id:value
        @param sample: particle to be parsed
        @return: dictionary representing the particle
        """
        sample_dict = {}
        values = sample.get(DataParticleKey.VALUES, [])
        for each in values:
            sample_dict[each[DataParticleKey.VALUE_ID]] = each[
                DataParticleKey.VALUE]
        return sample_dict

    def _check_for_autolevel(self, sample):
        """
        Check this sample, kick off a leveling event if out of range
        @param sample: sample to be checked
        """
        if self._param_dict.get(
                Parameter.AUTO_RELEVEL) and self.get_current_state(
                ) == ProtocolState.AUTOSAMPLE:
            # Find the current X and Y tilt values
            # If they exceed the trigger parameters, begin autolevel
            relevel = False
            sample = self._particle_to_dict(sample)
            x_tilt = abs(sample[particles.LilySampleParticleKey.X_TILT])
            y_tilt = abs(sample[particles.LilySampleParticleKey.Y_TILT])
            x_trig = int(self._param_dict.get(Parameter.XTILT_TRIGGER))
            y_trig = int(self._param_dict.get(Parameter.YTILT_TRIGGER))
            if x_tilt > x_trig or y_tilt > y_trig:
                self._async_raise_fsm_event(ProtocolEvent.START_LEVELING)

    def _failed_leveling(self, axis):
        """
        Handle a failed leveling event.  Set the failed flag, disable auto relevel and notify the operator
        @param axis: Axis which failed leveling
        """
        log.error('Detected leveling error in %s axis!', axis)
        # Read only parameter, must be set outside of handler
        self._param_dict.set_value(Parameter.LEVELING_FAILED, True)
        # Use the handler to disable auto relevel to raise a config change event if needed.
        self._handler_command_set({Parameter.AUTO_RELEVEL: False})
        raise InstrumentDataException(
            'LILY Leveling (%s) Failed.  Disabling auto relevel' % axis)

    def _check_completed_leveling(self, sample):
        """
        Check this sample if leveling is complete or failed
        @param sample: Sample to be checked
        """
        sample = self._particle_to_dict(sample)
        status = sample[particles.LilyLevelingParticleKey.STATUS]
        if status is not None:
            # Leveling status update received
            # If leveling complete, send STOP_LEVELING, set the _leveling_failed flag to False
            if 'Leveled' in status:
                if self._param_dict.get(Parameter.LEVELING_FAILED):
                    self._handler_command_set(
                        {Parameter.LEVELING_FAILED: False})
                self._async_raise_fsm_event(ProtocolEvent.STOP_LEVELING)
            # Leveling X failed!  Set the flag and raise an exception to notify the operator
            # and disable auto leveling. Let the instrument attempt to level
            # in the Y axis.
            elif 'X Axis out of range' in status:
                self._failed_leveling('X')
            # Leveling X failed!  Set the flag and raise an exception to notify the operator
            # and disable auto leveling. Send STOP_LEVELING
            elif 'Y Axis out of range' in status:
                self._async_raise_fsm_event(ProtocolEvent.STOP_LEVELING)
                self._failed_leveling('Y')

    def _check_pps_sync(self, sample):
        """
        Check if PPS sync status has changed.  Update driver flag and, if appropriate, trigger a time sync
        @param sample: sample to be checked
        """
        sample = self._particle_to_dict(sample)
        pps_sync = sample[particles.NanoSampleParticleKey.PPS_SYNC] == 'P'
        if pps_sync and not self.has_pps:
            # pps sync regained, sync the time
            self.has_pps = True
            self._async_raise_fsm_event(ProtocolEvent.NANO_TIME_SYNC)
        elif self.has_pps:
            self.has_pps = False

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

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Process discover event
        @return next_state, next_agent_state
        """
        return ProtocolState.COMMAND, ResourceAgentState.IDLE

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

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

    def _handler_autosample_stop_autosample(self, *args, **kwargs):
        """
        Stop autosample
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

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

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        """
        # key off the initialization flag to determine if we should sync the time
        if self._init_type == InitializationType.STARTUP:
            self._handler_time_sync()

        self._init_params()
        self._stop_autosample()
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

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

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

        if len(args) < 1:
            raise InstrumentParameterException(
                'Set command requires a parameter dict.')
        params = args[0]
        if len(args) > 1:
            startup = args[1]

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

        self._set_params(params, startup)
        return next_state, result

    def _handler_command_start_direct(self):
        """
        Start direct access
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.DIRECT_ACCESS, (ResourceAgentState.DIRECT_ACCESS,
                                             None)

    def _handler_command_start_autosample(self):
        """
        Start autosample
        @return next_state, (next_agent_state, result)
        """
        self._do_cmd_resp(InstrumentCommand.LILY_ON,
                          expected_prompt=Prompt.LILY_ON)
        self._do_cmd_resp(InstrumentCommand.NANO_ON,
                          expected_prompt=NANO_STRING)
        self._do_cmd_resp(InstrumentCommand.IRIS_ON,
                          expected_prompt=Prompt.IRIS_ON)
        return ProtocolState.AUTOSAMPLE, (ResourceAgentState.STREAMING, 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_execute_direct(self, data):
        """
        Execute direct access command
        @return next_state, (next_agent_state, result)
        """
        self._do_cmd_direct(data)
        self._sent_cmds.append(data)
        return None, (None, None)

    def _handler_direct_access_stop_direct(self):
        """
        Stop direct access
        @return next_state, (next_agent_state, result)
        """
        next_state, next_agent_state = self._handler_unknown_discover()
        if next_state == DriverProtocolState.COMMAND:
            next_agent_state = ResourceAgentState.COMMAND

        return next_state, (next_agent_state, None)

    ########################################################################
    # 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):
        """
        Generic exit state handler
        """

    def _handler_acquire_status(self, *args, **kwargs):
        """
        We generate these particles here to avoid the chunker.  This allows us to process status
        messages with embedded messages from the other parts of the instrument.
        @return next_state, (next_agent_state, result)
        """
        ts = ntplib.system_to_ntp_time(time.time())
        parts = []

        for command, particle_class in [
            (InstrumentCommand.SYST_DUMP1, particles.SystStatusParticle),
            (InstrumentCommand.LILY_DUMP1, particles.LilyStatusParticle1),
            (InstrumentCommand.LILY_DUMP2, particles.LilyStatusParticle2),
            (InstrumentCommand.IRIS_DUMP1, particles.IrisStatusParticle1),
            (InstrumentCommand.IRIS_DUMP2, particles.IrisStatusParticle2),
            (InstrumentCommand.NANO_DUMP1, particles.NanoStatusParticle),
        ]:
            result, _ = self._do_cmd_resp(
                command, response_regex=particle_class.regex_compiled())
            parts.append(result)
        sample = self._extract_sample(
            particles.BotptStatusParticle,
            particles.BotptStatusParticle.regex_compiled(),
            NEWLINE.join(parts), ts)

        if self.get_current_state() == ProtocolState.AUTOSAMPLE:
            # acquiring status stops NANO output, restart it
            self._do_cmd_resp(InstrumentCommand.NANO_ON,
                              expected_prompt=NANO_STRING)

        if not sample:
            raise InstrumentProtocolException(
                'Failed to generate status particle')
        return None, (None, sample)

    def _handler_time_sync(self, *args, **kwargs):
        """
        Syncing time starts autosample...
        @return next_state, (next_agent_state, result)
        """
        self._do_cmd_resp(InstrumentCommand.NANO_SET_TIME,
                          expected_prompt=NANO_STRING)
        if self.get_current_state() == ProtocolState.COMMAND:
            self._do_cmd_no_resp(InstrumentCommand.NANO_OFF)
        return None, (None, None)

    def _handler_start_leveling(self):
        """
        Send the start leveling command
        @return next_state, (next_agent_state, result)
        """
        if not self._param_dict.get(Parameter.LILY_LEVELING):
            self._schedule_leveling_timeout()
            self._do_cmd_resp(InstrumentCommand.LILY_START_LEVELING,
                              expected_prompt=Prompt.LILY_START_LEVELING)
            self._param_dict.set_value(Parameter.LILY_LEVELING, True)
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return None, (None, None)

    def _handler_stop_leveling(self):
        """
        Send the stop leveling command
        @return next_state, (next_agent_state, result)
        """
        if self._param_dict.get(Parameter.LILY_LEVELING):
            self._remove_leveling_timeout()

            self._do_cmd_resp(InstrumentCommand.LILY_STOP_LEVELING,
                              expected_prompt=Prompt.LILY_STOP_LEVELING)
            self._param_dict.set_value(Parameter.LILY_LEVELING, False)

            if self.get_current_state() == ProtocolState.AUTOSAMPLE:
                self._do_cmd_resp(InstrumentCommand.LILY_ON,
                                  expected_prompt=Prompt.LILY_ON)

            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

        return None, (None, None)

    def _handler_leveling_timeout(self):
        """
        Leveling has timed out, disable auto-relevel and mark leveling as failed.
        handler_stop_leveling will raise the config change event.
        @throws InstrumentProtocolException
        """
        self._param_dict.set_value(Parameter.AUTO_RELEVEL, False)
        self._param_dict.set_value(Parameter.LEVELING_FAILED, True)
        self._handler_stop_leveling()
        raise InstrumentProtocolException(
            'Leveling failed to complete within timeout, disabling auto-relevel'
        )

    def _handler_start_heater(self, *args, **kwargs):
        """
        Turn the heater on for Parameter.HEAT_DURATION hours
        @return next_state, (next_agent_state, result)
        """
        if not self._param_dict.get(Parameter.HEATER_ON):
            self._do_cmd_resp(InstrumentCommand.HEAT,
                              self._param_dict.get(Parameter.HEAT_DURATION),
                              response_regex=RegexResponse.HEAT)
            self._param_dict.set_value(Parameter.HEATER_ON, True)
            self._schedule_heater_timeout()
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return None, (None, None)

    def _handler_stop_heater(self, *args, **kwargs):
        """
        Turn the heater on for Parameter.HEAT_DURATION hours
        @return next_state, (next_agent_state, result)
        """
        if self._param_dict.get(Parameter.HEATER_ON):
            self._do_cmd_resp(InstrumentCommand.HEAT,
                              0,
                              response_regex=RegexResponse.HEAT)
            self._param_dict.set_value(Parameter.HEATER_ON, False)
            self._remove_heater_timeout()
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return None, (None, None)

    def _handler_heater_timeout(self):
        """
        Heater should be finished.  Set HEATER_ON to false.
        """
        self._param_dict.set_value(Parameter.HEATER_ON, False)
        self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return None, None
Exemplo n.º 40
0
    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
Exemplo n.º 41
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.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_command_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_TURBO, self._handler_command_start_turbo),
            ],
            ProtocolState.SPINNING_UP: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.AT_SPEED, self._handler_spinning_up_at_speed),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.AT_SPEED: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.CLEAR, self._handler_clear),
                (ProtocolEvent.GET, self._handler_command_get),
            ],
            ProtocolState.SPINNING_DOWN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOPPED, self._handler_spinning_down_stopped),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
            ],
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # Add build and response handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_build_handler(command, self._generic_build_handler)
            self._add_response_handler(command, self._generic_response_handler)

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._chunker = StringChunker(Protocol.sieve_function)
        self._max_current_count = 0
        self.initialize_scheduler()

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples
        @param raw_data: data to be searched
        @return: list of (start,stop) indexes of matches
        """
        return [(m.start(), m.end()) for m in TurboStatusParticle.regex_compiled().finditer(raw_data)]

    def _build_param_dict(self):
        """
        All turbo parameters have the same signature, add them in a loop...
        """
        parameters = {
            Parameter.UPDATE_INTERVAL: {
                'display_name': 'Acquire Status Interval',
                'description': 'Interval between automatic acquire status calls: (5 - 60)',
                'units': Units.SECOND,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.MAX_DRIVE_CURRENT: {
                'display_name': 'Maximum Allowable Drive Current',
                'description': 'Maximum allowable drive current at speed: (100 - 200)',
                'units': Prefixes.CENTI + Units.AMPERE,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.MAX_TEMP_MOTOR: {
                'display_name': 'Maximum Allowable Motor Temperature',
                'description': 'Maximum allowable motor temperature: (5 - 100)',
                'units': Units.DEGREE_CELSIUS,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.MAX_TEMP_BEARING: {
                'display_name': 'Maximum Allowable Bearing Temperature',
                'description': 'Maximum allowable bearing temperature: (5 - 100)',
                'units': Units.DEGREE_CELSIUS,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.MIN_SPEED: {
                'display_name': 'Minimum Allowable Turbo Speed',
                'description': 'Minimum allowable turbo speed before RGA is shutdown: (70000 - 90000)',
                'units': Units.REVOLUTION_PER_MINUTE,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.TARGET_SPEED: {
                'display_name': 'Target Turbo Speed',
                'description': 'Target turbo speed before RGA is initialized: (70000 - 90000)',
                'units': Units.REVOLUTION_PER_MINUTE,
                'type': ParameterDictType.INT,
                'startup_param': True,
            },
            Parameter.ERROR_REASON: {
                'display_name': 'Turbo Error Reason',
                'description': 'Reason for turbo error state.',
                'visibility': ParameterDictVisibility.READ_ONLY,
                'type': ParameterDictType.STRING,
            }
        }

        reverse_param = Parameter.reverse_dict()
        constraints = ParameterConstraints.dict()

        for name in parameters:
            kwargs = parameters[name]
            if name in constraints:
                _type, minimum, maximum = constraints[name]
                kwargs['val_description'] = '%s value from %d - %d' % (_type, minimum, maximum)
            self._param_dict.add(name, '', None, None, **kwargs)

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.ACQUIRE_STATUS, display_name="Acquire Status")
        self._cmd_dict.add(Capability.START_TURBO, display_name="Start Turbo")
        self._cmd_dict.add(Capability.STOP_TURBO, display_name="Stop Turbo")
        self._cmd_dict.add(Capability.CLEAR, display_name="Clear Error State")
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

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

    def _got_chunk(self, chunk, ts):
        """
        The base class got_data has gotten a chunk from the chunker.  Pass it to extract_sample
        with the appropriate particle objects and regexes.
        @param chunk: data to be processed
        @param ts: timestamp
        """
        self._extract_sample(TurboStatusParticle, TurboStatusParticle.regex_compiled(), chunk, ts)

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        @param events: events to be filtered
        @return: list of events that are in Capability
        """
        return [x for x in events if Capability.has(x)]

    @staticmethod
    def _checksum(s):
        """
        Calculate the turbopump checksum for the given string.
        @param s: string to be checked
        @return: checksum string
        """
        return '%03d' % (sum([ord(x) for x in s]) % 256)

    def _build_turbo_command(self, address, c_type, c, data):
        """
        Build a command for the turbopump
        @param address: target address
        @param c_type: command type (QUERY/SET)
        @param c: command
        @param data: command_data
        @return: command string
        """
        command = '%03d%02d%03d%02d%s' % (address, c_type, c, len(data), data)
        checksum = self._checksum(command)
        return command + checksum

    def _generic_build_handler(self, command, *args, **kwargs):
        """
        Determine if this is a query or set action based on the
        input args.  Dispatch the builder with the appropriate arguments.
        @param command: command to be sent
        @param args: arglist which may contain a value
        @return: command string
        """
        if len(args) == 1:
            # this is a set action
            value = args[0]
            return self._build_turbo_command(ADDRESS, CommandType.SET, command, value) + NEWLINE
        # this is a query
        return self._build_turbo_command(ADDRESS, CommandType.QUERY, command, QUERY) + NEWLINE

    def _generic_response_handler(self, resp, prompt):
        """
        Parse the response from the turbopump.
        @param resp: response
        @param prompt: unused, require to match signature
        @returns: integer value extracted from response
        @throws InstrumentDataException
        """
        my_checksum = self._checksum(resp[:-3])
        if resp[-3:] != my_checksum:
            err_str = 'bad checksum: %r calculated: %r' % (resp, my_checksum)
            raise exceptions.InstrumentDataException(err_str)
        command = int(resp[5:8])
        data_length = int(resp[8:10])
        data = resp[10:-3]
        log.trace('command: %s data: %s', command, data)
        if len(data) != data_length:
            raise exceptions.InstrumentDataException('invalid data length: %r' % resp)
        if command not in InstrumentCommand.list():
            raise exceptions.InstrumentDataException('command not found: %r' % resp)
        return int(data)

    def _wakeup(self, timeout, delay=1):
        """
        Not valid for this instrument
        """

    def _build_scheduler(self):
        """
        Build a scheduler for periodic status updates
        """
        job_name = ScheduledJob.ACQUIRE_STATUS
        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.INTERVAL,
                        DriverSchedulerConfigKey.SECONDS: self._param_dict.get(Parameter.UPDATE_INTERVAL)
                    },
                }
            }
        }

        self.set_init_params(config)
        self._add_scheduler_event(ScheduledJob.ACQUIRE_STATUS, ProtocolEvent.ACQUIRE_STATUS)

    def _update_params(self, *args, **kwargs):
        """
        Parameters are NOT set in the instrument by this method, as all parameters are driver only.
        """

    def _set_params(self, *args, **kwargs):
        """
        Set parameters, raise a CONFIG_CHANGE event if necessary.
        @throws InstrumentParameterException
        """
        self._verify_not_readonly(*args, **kwargs)
        params_to_set = args[0]
        old_config = self._param_dict.get_all()

        # check if in range
        constraints = ParameterConstraints.dict()
        parameters = Parameter.reverse_dict()

        # step through the list of parameters
        for key, val in params_to_set.iteritems():
            # if constraint exists, verify we have not violated it
            constraint_key = parameters.get(key)
            if constraint_key in constraints:
                var_type, minimum, maximum = constraints[constraint_key]
                try:
                    value = var_type(val)
                except ValueError:
                    raise exceptions.InstrumentParameterException(
                        'Unable to verify type - parameter: %s value: %s' % (key, val))
                if val < minimum or val > maximum:
                    raise exceptions.InstrumentParameterException(
                        'Value out of range - parameter: %s value: %s min: %s max: %s' %
                        (key, val, minimum, maximum))

        # all constraints met or no constraints exist, set the values
        for key, val in params_to_set.iteritems():
            if key in old_config:
                self._param_dict.set_value(key, val)
            else:
                raise exceptions.InstrumentParameterException(
                    'Attempted to set unknown parameter: %s value: %s' % (key, val))
        new_config = self._param_dict.get_all()

        # If we changed anything, raise a CONFIG_CHANGE event
        if old_config != new_config:
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def _send_command_with_retry(self, command, value=None, sleep_time=1, max_retries=MAX_RETRIES):
        """
        Attempt to send a command up to max_retries times.  Protocol state will move to ERROR if we fail to
        receive a response after max_retries attempts.
        @throws InstrumentTimeoutException
        """
        for attempt in xrange(1, max_retries + 1):
            try:
                if value is None:
                    result = self._do_cmd_resp(command, response_regex=TURBO_RESPONSE, timeout=TIMEOUT)
                else:
                    result = self._do_cmd_resp(command, value, response_regex=TURBO_RESPONSE, timeout=TIMEOUT)
                return result
            except exceptions.InstrumentTimeoutException:
                log.error('Error sending command: %s, attempt %d', command, attempt)
                time.sleep(sleep_time)

        # set the error reason
        self._param_dict.set_value(Parameter.ERROR_REASON, 'Unable to command the turbo')
        self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

        self._async_raise_fsm_event(ProtocolEvent.ERROR)
        raise exceptions.InstrumentTimeoutException('Failed to command the turbo: %s' % command)

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

    def _handler_generic_enter(self, *args, **kwargs):
        """
        Generic enter handler when no specific action is needed.
        """
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_generic_exit(self, *args, **kwargs):
        """
        Generic exit handler when no specific action is needed.
        """

    def _handler_acquire_status(self, *args, **kwargs):
        """
        Query the instrument for the following status items:
            drive current
            drive voltage
            bearing temp
            motor temp
            rotation speed

        Verify no values exceed the limits specified in the parameter dictionary.
        @returns: next_state, (next_agent_state, result)
        @throws InstrumentStateException
        """
        next_state = None
        responses = {}

        # query the turbo for the speed/temp/current values
        for command in [InstrumentCommand.DRIVE_CURRENT, InstrumentCommand.DRIVE_VOLTAGE,
                        InstrumentCommand.TEMP_BEARING, InstrumentCommand.TEMP_MOTOR,
                        InstrumentCommand.ROTATION_SPEED_ACTUAL]:
            responses[command] = self._send_command_with_retry(command)

        # check the current driver state
        current_state = self.get_current_state()
        error = None

        # Check for over temperature conditions
        if responses[InstrumentCommand.TEMP_MOTOR] > self._param_dict.get(Parameter.MAX_TEMP_MOTOR) or \
                responses[InstrumentCommand.TEMP_BEARING] > self._param_dict.get(Parameter.MAX_TEMP_BEARING):
            error = 'Over temp error - Motor: %d Bearing: %d' % (responses[InstrumentCommand.TEMP_MOTOR],
                                                                 responses[InstrumentCommand.TEMP_BEARING])

        # Check if we were up to speed but have dipped below MIN_SPEED
        elif current_state == ProtocolState.AT_SPEED:
            if responses[InstrumentCommand.ROTATION_SPEED_ACTUAL] < self._param_dict.get(Parameter.MIN_SPEED):
                error = 'Fell below min speed: %d' % responses[InstrumentCommand.ROTATION_SPEED_ACTUAL]

            # or if we're up to speed and we have exceeded MAX_DRIVE_CURRENT more than 3 subsequent intervals
            if responses[InstrumentCommand.DRIVE_CURRENT] > self._param_dict.get(Parameter.MAX_DRIVE_CURRENT):
                self._max_current_count += 1
                if self._max_current_count > CURRENT_STABILIZE_RETRIES:
                    error = 'Turbo current draw to high: %d' % responses[InstrumentCommand.DRIVE_CURRENT]
            else:
                self._max_current_count = 0

        if error:
            self._param_dict.set_value(Parameter.ERROR_REASON, error)
            self._async_raise_fsm_event(ProtocolEvent.ERROR)
            self._driver_event(DriverAsyncEvent.ERROR, error)

        # now check if up to speed when spinning up
        elif current_state == ProtocolState.SPINNING_UP:
            if responses[InstrumentCommand.ROTATION_SPEED_ACTUAL] >= self._param_dict.get(Parameter.TARGET_SPEED):
                self._async_raise_fsm_event(ProtocolEvent.AT_SPEED)

        # or maybe we've stopped while spinning down (we'll consider < MIN_SPEED as stopped...)
        elif current_state == ProtocolState.SPINNING_DOWN:
            if responses[InstrumentCommand.ROTATION_SPEED_ACTUAL] <= self._param_dict.get(Parameter.MIN_SPEED):
                self._async_raise_fsm_event(ProtocolEvent.STOPPED)

        return next_state, (next_state, responses)

    def _handler_stop_turbo(self):
        """
        Stop the turbo
        """
        next_state = ProtocolState.SPINNING_DOWN
        result = []
        for command in [InstrumentCommand.PUMP_STATION, InstrumentCommand.MOTOR_PUMP]:
            self._send_command_with_retry(command, value=FALSE)

        return next_state, (next_state, result)

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

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state.  This instrument always discovers to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        return next_state, (next_state, result)

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

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

        # delete the scheduled acquire status job, if it exists.
        # This portion of the MASSP is powered OFF the majority of the time
        # so acquire_status should not be running
        try:
            self._remove_scheduler(ScheduledJob.ACQUIRE_STATUS)
        except KeyError:
            pass

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

    def _handler_command_get(self, *args, **kwargs):
        """
        Get parameter
        """
        return self._handler_get(*args, **kwargs)

    def _handler_command_set(self, *args, **kwargs):
        """
        Set parameter
        """
        next_state = None
        result = None
        self._set_params(*args, **kwargs)

        return next_state, result

    def _handler_command_start_direct(self):
        """
        Start direct access
        """
        next_state = ProtocolState.DIRECT_ACCESS
        result = []
        return next_state, (next_state, result)

    def _handler_command_start_turbo(self):
        """
        Start the turbo, periodic status scheduler
        """
        next_state = ProtocolState.SPINNING_UP
        result = []
        for command in [InstrumentCommand.PUMP_STATION, InstrumentCommand.MOTOR_PUMP]:
            self._send_command_with_retry(command, value=TRUE)
        # start the acquire_status scheduler
        self._build_scheduler()
        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_execute_direct(self, data):
        """
        Forward a direct access command to the instrument
        """
        next_state = None
        result = []
        self._do_cmd_direct(data)

        # add sent command to list for 'echo' filtering in callback
        self._sent_cmds.append(data)

        return next_state, (next_state, result)

    def _handler_direct_access_stop_direct(self):
        """
        Stop direct access, return to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Spinning up/down handlers.
    ########################################################################

    def _handler_spinning_up_at_speed(self):
        """
        Instrument has reached operating speed, transition states.
        """
        next_state = ProtocolState.AT_SPEED
        result = []
        return next_state, (next_state, result)

    def _handler_spinning_down_stopped(self):
        """
        Instrument has spun down, transition states.
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._async_agent_state_change(ResourceAgentState.COMMAND)
        return next_state, (next_state, result)

    ########################################################################
    # Error handlers.
    ########################################################################

    def _handler_error(self, *args, **kwargs):
        """
        Error detected, go to the ERROR state.
        """
        next_state = ProtocolState.ERROR
        result = []
        return next_state, (next_state, result)

    def _handler_clear(self, *args, **kwargs):
        """
        User requests error state be cleared, go to COMMAND.
        @returns: next_state, (next_state, result)
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._param_dict.set_value(Parameter.ERROR_REASON, '')
        self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return next_state, (next_state, result)
Exemplo n.º 42
0
class PlatformDriver(object):
    """
    A platform driver handles a particular platform in a platform network.
    This base class provides a common interface and supporting functionality.
    """

    def __init__(self, pnode, event_callback,
                 create_event_subscriber, destroy_event_subscriber):
        """
        Creates a PlatformDriver instance.

        @param pnode           Root PlatformNode defining the platform network
                               rooted at this platform.
        @param event_callback  Callback to notify platform agent about events
                               generated by this driver.
                               This is captured in self._send_event for this
                               class and subclasses to call as needed.

        @param create_event_subscriber
        @param destroy_event_subscriber
                 functions to create/destroy any needed EventSubscriber's,
                 in particular regarding the Managed Endpoint API.
        """

        #
        # NOTE the "pnode" parameter may be not very "standard" but it is the
        # current convenient mechanism that captures the overall definition
        # of the corresponding platform (most of which coming from configuration)
        #

        self._pnode = pnode
        self._send_event = event_callback

        self._create_event_subscriber = create_event_subscriber
        self._destroy_event_subscriber = destroy_event_subscriber

        self._platform_id = self._pnode.platform_id
        if self._pnode.parent:
            self._parent_platform_id = self._pnode.parent.platform_id
        else:
            self._parent_platform_id = None

        self._platform_attributes = \
            dict((a.attr_id, a.defn) for a in self._pnode.attrs.itervalues())

        if log.isEnabledFor(logging.DEBUG):
            log.debug("%r: PlatformDriver constructor called: pnode:\n%s\n"
                      "_platform_attributes=%s",
                      self._platform_id,
                      NetworkUtil._dump_pnode(self._pnode, include_subplatforms=False),
                      self._platform_attributes)

        self._driver_config = None
        self._resource_schema = {}
        
        # The parameter dictionary.
        self._param_dict = {}

        # construct FSM and start it with initial state UNCONFIGURED:
        self._construct_fsm()
        self._fsm.start(PlatformDriverState.UNCONFIGURED)

    def get_platform_driver_event_class(self):
        """
        Returns PlatformDriverEvent in this base class, but this is typically
        overwritten.
        """
        return PlatformDriverEvent

    def get_platform_driver_capability_class(self):
        """
        Returns PlatformDriverCapability in this base class, but this is typically
        overwritten.
        """
        return PlatformDriverCapability

    def get_resource_capabilities(self, current_state=True, cmd_attrs=False):
        """
        @param current_state
        @param cmd_attrs   If true, the returned commands will be the actual
                           attributes of the associated capability class (or
                           subclass) instead of the associated values.
        """
        res_cmds = self._fsm.get_events(current_state)
        res_cmds = self._filter_capabilities(res_cmds, cmd_attrs=cmd_attrs)
        res_params = self._param_dict.keys()

        return [res_cmds, res_params]

    def _filter_capabilities(self, events, cmd_attrs=False):
        """
        @param events      the events to filter
        @param cmd_attrs   If true, then the actual attributes of the
                           PlatformDriverCapability class (or subclass) are
                           returned instead of the associated values.
        """
        capability_class = self.get_platform_driver_capability_class()
        event_values = [x for x in events if capability_class.has(x)]

        if not cmd_attrs:
            return event_values

        # map event_values to the actual enum attributes:
        event_attrs = []
        for attr in dir(capability_class):
            # first two checks below similar to BaseEnum.list()
            if attr.startswith('__'):
                continue
            val = getattr(capability_class, attr)
            if callable(val):
                continue

            if val in event_values:
                event_attrs.append(attr)

        return event_attrs

    def get_resource_state(self, *args, **kwargs):
        """
        Return the current state of the driver.
        @retval str current driver state.
        """
        return self._fsm.get_current_state()

    def get_resource(self, *args, **kwargs):
        """
        """
        return self._fsm.on_event(PlatformDriverEvent.GET, *args, **kwargs)

    def set_resource(self, *args, **kwargs):
        """
        """
        return self._fsm.on_event(PlatformDriverEvent.SET, *args, **kwargs)

    def execute_resource(self, resource_cmd, *args, **kwargs):
        """
        Platform agent calls this directly to trigger the execution of a
        resource command. The actual action occurs in execute.
        """
        return self._fsm.on_event(PlatformDriverEvent.EXECUTE, resource_cmd, *args, **kwargs)

    def _get_platform_attributes(self):
        """
        Gets a dict of the attribute definitions in this platform as given at
        construction time (from pnode parameter).
        """
        return self._platform_attributes

    def validate_driver_configuration(self, driver_config):
        """
        Called by configure so a subclass can perform any needed additional
        validation of the provided configuration.
        Nothing is done in this base class. Note that basic validation is
        done by PlatformAgent prior to creating/configuring the driver.

        @param driver_config Driver configuration.

        @raise PlatformDriverException Error in driver configuration.
        """
        pass

    def configure(self, driver_config):
        """
        Configures this driver. In this base class it basically
        calls validate_driver_configuration and then assigns the given
        config to self._driver_config.

        @param driver_config Driver configuration.
        """
        if log.isEnabledFor(logging.DEBUG):
            log.debug("%r: configure: %s" % (self._platform_id, str(driver_config)))

        self.validate_driver_configuration(driver_config)
        self._driver_config = driver_config
        #self._param_dict = deepcopy(self._driver_config.get('attributes',{}))
        
    def get_config_metadata(self):
        """
        """
        return deepcopy(self._resource_schema)

    def connect(self, recursion=None):
        """
        To be implemented by subclass.
        Establishes communication with the platform device.

        @raise PlatformConnectionException
        """
        raise NotImplementedError()  #pragma: no cover

    def disconnect(self, recursion=None):
        """
        To be implemented by subclass.
        Ends communication with the platform device.

        @raise PlatformConnectionException
        """
        raise NotImplementedError()  #pragma: no cover

    def ping(self):
        """
        To be implemented by subclass.
        Verifies communication with external platform returning "PONG" if
        this verification completes OK.

        @retval "PONG"

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        raise NotImplementedError()  #pragma: no cover

    def get_attributes(self):
        """
        To be implemented by subclass.
        Returns the attributes of this platform. This is used by the agent
        for attribute monitoring purposes.

        @retval {attr_id: dict, ...}
                dict indexed by attribute ID with associated properties.

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        raise NotImplementedError()  #pragma: no cover

    def get_attribute_values(self, attrs):
        """
        To be implemented by subclass.
        Returns the values for specific attributes since a given time for
        each attribute.

        @param attrs     [(attrName, from_time), ...] desired attributes.
                         from_time Assummed to be in the format basically described by
                         pyon's get_ion_ts function, "a str representing an
                         integer number, the millis in UNIX epoch."

        @retval {attrName : [(attrValue, timestamp), ...], ...}
                dict indexed by attribute name with list of (value, timestamp)
                pairs. Timestamps in same format as from_time.

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        raise NotImplementedError()  #pragma: no cover

    def supports_set_operation(self):
        """
        @return True only if the SET operation is supported by this driver.
        """
        return False

    def set_attribute_values(self, attrs):
        """
        Sets values for writable attributes in this platform.
        Only called by SET handler when supports_set_operation() returns True.

        @param attrs 	[(attrName, attrValue), ...] 	List of attribute values

        @retval {attrName : [(attrValue, timestamp), ...], ...}
                dict with a list of (value,timestamp) pairs for each attribute
                indicated in the input. Returned timestamps indicate the time when the
                value was set. Each timestamp is "a str representing an
                integer number, the millis in UNIX epoch" to
                align with description of pyon's get_ion_ts function.

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        #
        # TODO Any needed alignment with the instrument case?
        #
        raise NotImplementedError()  #pragma: no cover

    def execute(self, cmd, *args, **kwargs):
        """
        Executes the given command.
        Subclasses can override to execute particular commands or delegate to
        its super class. However, note that this base class raises
        NotImplementedError.

        @param cmd     command
        @param args    command's args
        @param kwargs  command's kwargs

        @return  result of the execution

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        raise NotImplementedError()  # pragma: no cover

    def get(self, *args, **kwargs):
        """
        Gets the values of the requested attributes.
        Subclasses can override to get particular attributes and
        delegate to this base implementation to handle common attributes.

        @param args    get's args
        @param kwargs  get's kwargs

        @return  result of the retrieval.

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        raise NotImplementedError()  # pragma: no cover

    def destroy(self):
        """
        Stops all activity done by the driver. Nothing done in this class.
        """
        pass

    def get_driver_state(self):
        """
        Returns the current FSM state.
        """
        return self._fsm.get_current_state()

    #####################################################################
    # Supporting method for handling connection lost in CONNECT handlers
    #####################################################################

    def _connection_lost(self, cmd, args, kwargs, exc=None):
        """
        Supporting method to be called by any CONNECTED handler right after
        detecting that the connection with the external platform device has
        been lost. It does a regular disconnect() and notifies the agent about
        the lost connection. Note that the call to disconnect() itself may
        throw some additional exception very likely caused by the fact that
        the connection is lost--this exception is just logged out but ignored.

        All parameters are for logging purposes.

        @param cmd     string indicating the command that was attempted
        @param args    args of the command that was attempted
        @param kwargs  kwargs of the command that was attempted
        @param exc     associated exception (if any),

        @return (next_state, result) suitable as the return of the FSM
                handler where the connection lost was detected. The
                next_state will always be PlatformDriverState.DISCONNECTED.
        """
        log.debug("%r: (LC) _connection_lost: cmd=%s, args=%s, kwargs=%s, exc=%s",
                  self._platform_id, cmd, args, kwargs, exc)

        result = None
        try:
            result = self.disconnect()

        except Exception as e:
            # just log a message
            log.debug("%r: (LC) ignoring exception while calling disconnect upon"
                      " lost connection: %s", self._platform_id, e)

        # in any case, notify the agent about the lost connection and
        # transition to DISCONNECTED:
        self._send_event(AsyncAgentEvent(PlatformAgentEvent.LOST_CONNECTION))

        next_state = PlatformDriverState.DISCONNECTED

        return next_state, result

    ##############################################################
    # FSM event handlers.
    ##############################################################

    def _common_state_enter(self, *args, **kwargs):
        """
        Common work upon every state entry.
        """
        state = self.get_driver_state()
        log.debug('%r: driver entering state: %s', self._platform_id, state)

        self._send_event(StateChangeDriverEvent(state))

    def _common_state_exit(self, *args, **kwargs):
        """
        Common work upon every state exit.
        Nothing done in this base class.
        """

    ##############################################################
    # UNCONFIGURED event handlers.
    ##############################################################

    def _handler_unconfigured_configure(self, *args, **kwargs):
        """
        """
        if log.isEnabledFor(logging.TRACE):  # pragma: no cover
            log.trace("%r/%s args=%s kwargs=%s" % (
                      self._platform_id, self.get_driver_state(),
                      str(args), str(kwargs)))

        driver_config = kwargs.get('driver_config', None)
        if driver_config is None:
            raise FSMError('configure: missing driver_config argument')

        try:
            result = self.configure(driver_config)
            next_state = PlatformDriverState.DISCONNECTED
        except PlatformDriverException as e:
            result = None
            next_state = None
            log.error("Error in platform driver configuration", e)

        return next_state, result

    ##############################################################
    # DISCONNECTED event handlers.
    ##############################################################

    def _handler_disconnected_connect(self, *args, **kwargs):
        """
        """
        if log.isEnabledFor(logging.TRACE):  # pragma: no cover
            log.trace("%r/%s args=%s kwargs=%s" % (
                      self._platform_id, self.get_driver_state(),
                      str(args), str(kwargs)))

        recursion = kwargs.get('recursion', None)

        self.connect(recursion=recursion)
        result = next_state = PlatformDriverState.CONNECTED

        return next_state, result

    def _handler_disconnected_disconnect(self, *args, **kwargs):
        """
        We allow the DISCONNECT event in DISCONNECTED state for convenience,
        in particular it facilitates the overall handling of the connection_lost
        event, which is processed by a subsequent call to disconnect from the
        platform agent. The handler here does nothing.
        """
        if log.isEnabledFor(logging.TRACE):  # pragma: no cover
            log.trace("%r/%s args=%s kwargs=%s" % (
                      self._platform_id, self.get_driver_state(),
                      str(args), str(kwargs)))

        return None, None

    ###########################################################################
    # CONNECTED event handlers.
    # Except for the explicit disconnect and connection_lost handlers, the
    # CONNECTED handlers (here and in subclasses) should directly catch any
    # PlatformConnectionException to call _connection_lost.
    ###########################################################################

    def _handler_connected_disconnect(self, *args, **kwargs):
        """
        """
        if log.isEnabledFor(logging.TRACE):  # pragma: no cover
            log.trace("%r/%s args=%s kwargs=%s" % (
                      self._platform_id, self.get_driver_state(),
                      str(args), str(kwargs)))

        recursion = kwargs.get('recursion', None)

        result = self.disconnect(recursion=recursion)
        next_state = PlatformDriverState.DISCONNECTED

        return next_state, result

    def _handler_connected_connection_lost(self, *args, **kwargs):
        """
        The connection was lost (as opposed to a normal disconnect request).
        Here we do the regular disconnect but also notify the platform agent
        about the lost connection.

        NOTE: this handler in the FSM is provided in case there is a need to
        directly trigger the associated transition along with the associated
        notification to the agent. However, the typical case is that a CONNECTED
        handler dealing with commands will catch any PlatformConnectionException
        to call _connection_lost directly.
        """
        if log.isEnabledFor(logging.TRACE):  # pragma: no cover
            log.trace("%r/%s args=%s kwargs=%s" % (
                      self._platform_id, self.get_driver_state(),
                      str(args), str(kwargs)))

        # just use our supporting method:
        return self._connection_lost(PlatformDriverEvent.CONNECTION_LOST, args, kwargs)

    def _handler_connected_ping(self, *args, **kwargs):
        """
        """
        if log.isEnabledFor(logging.TRACE):  # pragma: no cover
            log.trace("%r/%s args=%s kwargs=%s" % (
                      self._platform_id, self.get_driver_state(),
                      str(args), str(kwargs)))

        try:
            result = self.ping()
            return None, result

        except PlatformConnectionException as e:
            return self._connection_lost(PlatformDriverEvent.PING, args, kwargs, e)

    def _handler_connected_get(self, *args, **kwargs):
        """
        """
        if log.isEnabledFor(logging.TRACE):  # pragma: no cover
            log.trace("%r/%s args=%s kwargs=%s" % (
                      self._platform_id, self.get_driver_state(),
                      str(args), str(kwargs)))

        try:
            result = self.get(*args, **kwargs)
            return None, result

        except PlatformConnectionException as e:
            return self._connection_lost(PlatformDriverEvent.GET, args, kwargs, e)

    def _handler_connected_set(self, *args, **kwargs):
        """
        """
        if log.isEnabledFor(logging.TRACE):  # pragma: no cover
            log.trace("%r/%s args=%s kwargs=%s" % (
                      self._platform_id, self.get_driver_state(),
                      str(args), str(kwargs)))

        if not self.supports_set_operation():
            raise FSMError('Unsupported operation: %s' % PlatformDriverEvent.SET)

        attrs = kwargs.get('attrs', None)
        if attrs is None:
            raise FSMError('set_attribute_values: missing attrs argument')

        try:
            result = self.set_attribute_values(attrs)
            return None, result

        except PlatformConnectionException as e:
            return self._connection_lost(PlatformDriverEvent.SET, args, kwargs, e)

    def _handler_connected_execute(self, *args, **kwargs):
        """
        """
        if log.isEnabledFor(logging.TRACE):  # pragma: no cover
            log.trace("%r/%s args=%s kwargs=%s" % (
                      self._platform_id, self.get_driver_state(),
                      str(args), str(kwargs)))

        if len(args) == 0:
            raise FSMError('execute_resource: missing resource_cmd argument')

        try:
            result = self.execute(*args, **kwargs)
            return None, result

        except PlatformConnectionException as e:
            return self._connection_lost(PlatformDriverEvent.EXECUTE, args, kwargs, e)

    ##############################################################
    # Platform driver FSM setup
    ##############################################################

    def _construct_fsm(self, states=PlatformDriverState,
                       events=PlatformDriverEvent,
                       enter_event=PlatformDriverEvent.ENTER,
                       exit_event=PlatformDriverEvent.EXIT):
        """
        Constructs the FSM for the driver. The preparations here are mostly
        related with the UNCONFIGURED, DISCONNECTED, and CONNECTED state
        transitions, with some common handlers for the CONNECTED state.
        Subclasses can override to indicate specific parameters and add new
        handlers (typically for the CONNECTED state).
        """
        log.debug("constructing base platform driver FSM")

        self._fsm = ThreadSafeFSM(states, events, enter_event, exit_event)

        for state in PlatformDriverState.list():
            self._fsm.add_handler(state, enter_event, self._common_state_enter)
            self._fsm.add_handler(state, exit_event, self._common_state_exit)

        # UNCONFIGURED state event handlers:
        self._fsm.add_handler(PlatformDriverState.UNCONFIGURED, PlatformDriverEvent.CONFIGURE, self._handler_unconfigured_configure)

        # DISCONNECTED state event handlers:
        self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.CONNECT, self._handler_disconnected_connect)
        self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_disconnected_disconnect)

        # CONNECTED state event handlers:
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_connected_disconnect)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost)

        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.PING, self._handler_connected_ping)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET, self._handler_connected_get)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.SET, self._handler_connected_set)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.EXECUTE, self._handler_connected_execute)
Exemplo n.º 43
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.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample),
                (ProtocolEvent.START_LEVELING, self._handler_start_leveling),
                (ProtocolEvent.STOP_LEVELING, self._handler_stop_leveling),
                (ProtocolEvent.NANO_TIME_SYNC, self._handler_time_sync),
                (ProtocolEvent.START_HEATER, self._handler_start_heater),
                (ProtocolEvent.STOP_HEATER, self._handler_stop_heater),
                (ProtocolEvent.LEVELING_TIMEOUT, self._handler_leveling_timeout),
                (ProtocolEvent.HEATER_TIMEOUT, self._handler_heater_timeout),
            ],
            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.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample),
                (ProtocolEvent.START_LEVELING, self._handler_start_leveling),
                (ProtocolEvent.STOP_LEVELING, self._handler_stop_leveling),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.NANO_TIME_SYNC, self._handler_time_sync),
                (ProtocolEvent.START_HEATER, self._handler_start_heater),
                (ProtocolEvent.STOP_HEATER, self._handler_stop_heater),
                (ProtocolEvent.LEVELING_TIMEOUT, self._handler_leveling_timeout),
                (ProtocolEvent.HEATER_TIMEOUT, self._handler_heater_timeout),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_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)

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

        # Add build handlers for device commands.
        for command in InstrumentCommand.list():
            if command in [InstrumentCommand.NANO_SET_RATE, InstrumentCommand.HEAT]:
                self._add_build_handler(command, self._build_command_with_value)
            else:
                self._add_build_handler(command, self._build_simple_command)

        # # Add response handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_response_handler(command, self._generic_response_handler)

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

        # commands sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        # create chunker
        self._chunker = StringChunker(Protocol.sieve_function)

        self._last_data_timestamp = 0
        self.has_pps = True

        # set up scheduled event handling
        self.initialize_scheduler()
        self._add_scheduler_event(ScheduledJob.ACQUIRE_STATUS, ProtocolEvent.ACQUIRE_STATUS)
        self._add_scheduler_event(ScheduledJob.NANO_TIME_SYNC, ProtocolEvent.NANO_TIME_SYNC)

    @staticmethod
    def sieve_function(raw_data):
        """
        Sort data in the chunker...
        @param raw_data: Data to be searched for samples
        @return: list of (start,end) tuples
        """
        matchers = []
        return_list = []

        matchers.append(particles.HeatSampleParticle.regex_compiled())
        matchers.append(particles.IrisSampleParticle.regex_compiled())
        matchers.append(particles.NanoSampleParticle.regex_compiled())
        matchers.append(particles.LilySampleParticle.regex_compiled())
        matchers.append(particles.LilyLevelingParticle.regex_compiled())

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

        return return_list

    def _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
        """
        possible_particles = [
            (particles.LilySampleParticle, self._check_for_autolevel),
            (particles.LilyLevelingParticle, self._check_completed_leveling),
            (particles.HeatSampleParticle, None),
            (particles.IrisSampleParticle, None),
            (particles.NanoSampleParticle, self._check_pps_sync),
        ]

        for particle_type, func in possible_particles:
            sample = self._extract_sample(particle_type, particle_type.regex_compiled(), chunk, ts)
            if sample:
                if func:
                    func(sample)
                return sample

        raise InstrumentProtocolException(u'unhandled chunk received by _got_chunk: [{0!r:s}]'.format(chunk))

    def _extract_sample(self, particle_class, regex, line, timestamp, publish=True):
        """
        Overridden to set the quality flag for LILY particles that are out of range.
        @param particle_class: Class type for particle
        @param regex: regular expression to verify data
        @param line: data
        @param timestamp: ntp timestamp
        @param publish: boolean to indicate if sample should be published
        @return: extracted sample
        """
        if regex.match(line):
            if particle_class == particles.LilySampleParticle and self._param_dict.get(Parameter.LEVELING_FAILED):
                particle = particle_class(line, port_timestamp=timestamp, quality_flag=DataParticleValue.OUT_OF_RANGE)
            else:
                particle = particle_class(line, port_timestamp=timestamp)
            parsed_sample = particle.generate()

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

            return parsed_sample

    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")
        self._cmd_dict.add(Capability.ACQUIRE_STATUS, display_name="Acquire Status")
        self._cmd_dict.add(Capability.START_LEVELING, display_name="Start LILY Leveling")
        self._cmd_dict.add(Capability.STOP_LEVELING, display_name="Stop LILY Leveling")
        self._cmd_dict.add(Capability.START_HEATER, display_name="Start Heater")
        self._cmd_dict.add(Capability.STOP_HEATER, display_name="Stop Heater")

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with parameters.
        For each parameter key, add match string, match lambda function,
        and value formatting function for set commands.
        """
        my_regex = 'Not used'
        ro, rw = ParameterDictVisibility.READ_ONLY, ParameterDictVisibility.READ_WRITE
        _bool, _float, _int = ParameterDictType.BOOL, ParameterDictType.FLOAT, ParameterDictType.INT

        parameters = {
            Parameter.AUTO_RELEVEL: {
                'type': _bool,
                'display_name': 'Automatic Releveling Enabled',
                'description': 'Enable LILY re-leveling automatically: (true | off)',
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.XTILT_TRIGGER: {
                'type': _float,
                'display_name': 'X-tilt Releveling Trigger',
                'description': 'The X-tilt value that must be exceeded before LILY auto releveling occurs.',
                'units': Prefixes.MICRO + Units.RADIAN,
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.YTILT_TRIGGER: {
                'type': _float,
                'display_name': 'Y-tilt Releveling Trigger',
                'description': 'The Y-tilt value that must be exceeded before LILY auto releveling occurs.',
                'units': Prefixes.MICRO + Units.RADIAN,
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.LEVELING_TIMEOUT: {
                'type': _int,
                'display_name': 'LILY Leveling Timeout',
                'description': 'Leveling timeout',
                'units': Units.SECOND,
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.HEAT_DURATION: {
                'type': _int,
                'display_name': 'Heater Run Time Duration',
                'description': 'The number of hours the heater will run when it is given the command to turn on.',
                'units': Units.HOUR,
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.OUTPUT_RATE: {
                'type': _int,
                'display_name': 'NANO Output Rate',
                'description': 'Sample rate',
                'units': Units.HERTZ,
                'visibility': rw,
                'startup_param': True,
            },
            Parameter.HEATER_ON: {
                'type': _bool,
                'display_name': 'Heater Running',
                'description': 'Indicates if the heater is running: (true | false)',
                'value': False,
                'visibility': ro,
            },
            Parameter.LILY_LEVELING: {
                'type': _bool,
                'display_name': 'Lily Leveling',
                'description': 'Indicates if LILY leveling is occurring: (true | false)',
                'value': False,
                'visibility': ro,
            },
            Parameter.LEVELING_FAILED: {
                'type': _bool,
                'display_name': 'LILY Leveling Failed',
                'description': 'Indicates if LILY leveling failed: (true | false)',
                'value': False,
                'visibility': ro,
            },
        }
        for param in parameters:
            self._param_dict.add(param, my_regex, None, None, **parameters[param])

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

    def _build_command_with_value(self, cmd, value):
        """
        Build a simple command with one value specified
        @param cmd: instrument command
        @param value: value to be sent
        @return: command string
        """
        return '%s%d%s' % (cmd, value, NEWLINE)

    def _verify_set_values(self, params):
        """
        Verify supplied values are in range, if applicable
        @param params: Dictionary of Parameter:value pairs to be verified
        @throws InstrumentParameterException
        """
        constraints = ParameterConstraint.dict()
        parameters = Parameter.reverse_dict()

        # step through the list of parameters
        for key, val in params.iteritems():
            # verify this parameter exists
            if not Parameter.has(key):
                raise InstrumentParameterException('Received invalid parameter in SET: %s' % key)
            # if constraint exists, verify we have not violated it
            constraint_key = parameters.get(key)
            if constraint_key in constraints:
                var_type, minimum, maximum = constraints[constraint_key]
                constraint_string = 'Parameter: %s Value: %s Type: %s Minimum: %s Maximum: %s' % \
                                    (key, val, var_type, minimum, maximum)
                log.debug('SET CONSTRAINT: %s', constraint_string)
                # check bool values are actual booleans
                if var_type == bool:
                    if val not in [True, False]:
                        raise InstrumentParameterException('Non-boolean value!: %s' % constraint_string)
                # else, check if we can cast to the correct type
                else:
                    try:
                        var_type(val)
                    except ValueError:
                        raise InstrumentParameterException('Type mismatch: %s' % constraint_string)
                    # now, verify we are within min/max
                    if val < minimum or val > maximum:
                        raise InstrumentParameterException('Out of range: %s' % constraint_string)

    def _set_params(self, *args, **kwargs):
        """
        Issue commands to the instrument to set various parameters
        @param args: arglist, should contain a dictionary of parameters/values to be set
        """
        try:
            params = args[0]
        except IndexError:
            raise InstrumentParameterException('Set command requires a parameter dict.')

        self._verify_set_values(params)
        self._verify_not_readonly(*args, **kwargs)

        # if setting the output rate, get the current rate from the instrument first...
        if Parameter.OUTPUT_RATE in params:
            self._update_params()

        old_config = self._param_dict.get_config()

        # all constraints met or no constraints exist, set the values
        for key, value in params.iteritems():
            self._param_dict.set_value(key, value)

        new_config = self._param_dict.get_config()

        if not old_config == new_config:
            log.debug('Config change: %r %r', old_config, new_config)
            if old_config[Parameter.OUTPUT_RATE] is not None:
                if int(old_config[Parameter.OUTPUT_RATE]) != int(new_config[Parameter.OUTPUT_RATE]):
                    self._do_cmd_no_resp(InstrumentCommand.NANO_SET_RATE, int(new_config[Parameter.OUTPUT_RATE]))
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def _update_params(self, *args, **kwargs):
        """
        Update the param dictionary based on instrument response
        """
        result, _ = self._do_cmd_resp(InstrumentCommand.NANO_DUMP1,
                                      response_regex=particles.NanoStatusParticle.regex_compiled())
        rate = int(re.search(r'NANO,\*TH:(\d+)', result).group(1))
        self._param_dict.set_value(Parameter.OUTPUT_RATE, rate)

    def _wakeup(self, timeout, delay=1):
        """
        Overriding _wakeup; does not apply to this instrument
        """

    def add_to_buffer(self, data):
        """
        Overriding base class to reduce logging due to NANO high data rate
        @param data: data to be added to buffers
        """
        # Update the line and prompt buffers.
        self._linebuf += data
        self._promptbuf += data
        self._last_data_timestamp = time.time()

        # If our buffer exceeds the max allowable size then drop the leading
        # characters on the floor.
        max_size = self._max_buffer_size()
        if len(self._linebuf) > max_size:
            self._linebuf = self._linebuf[max_size * -1:]

        # If our buffer exceeds the max allowable size then drop the leading
        # characters on the floor.
        if len(self._promptbuf) > max_size:
            self._promptbuf = self._linebuf[max_size * -1:]

    def _max_buffer_size(self):
        """
        Overriding base class to increase max buffer size
        @return int max_buffer_size
        """
        return MAX_BUFFER_SIZE

    def _remove_leveling_timeout(self):
        """
        Clean up the leveling timer
        """
        try:
            self._remove_scheduler(ScheduledJob.LEVELING_TIMEOUT)
        except KeyError:
            log.debug('Unable to remove LEVELING_TIMEOUT scheduled job, job does not exist.')

    def _schedule_leveling_timeout(self):
        """
        Set up a leveling timer to make sure we don't stay in leveling state forever if something goes wrong
        """
        self._remove_leveling_timeout()
        dt = datetime.datetime.now() + datetime.timedelta(seconds=self._param_dict.get(Parameter.LEVELING_TIMEOUT))
        job_name = ScheduledJob.LEVELING_TIMEOUT
        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.ABSOLUTE,
                        DriverSchedulerConfigKey.DATE: dt
                    },
                }
            }
        }

        self.set_init_params(config)
        self._add_scheduler_event(ScheduledJob.LEVELING_TIMEOUT, ProtocolEvent.LEVELING_TIMEOUT)

    def _remove_heater_timeout(self):
        """
        Clean up the heater timer
        """
        try:
            self._remove_scheduler(ScheduledJob.HEATER_TIMEOUT)
        except KeyError:
            log.debug('Unable to remove HEATER_TIMEOUT scheduled job, job does not exist.')

    def _schedule_heater_timeout(self):
        """
        Set up a timer to set HEATER_ON to false around the time the heater shuts off
        """
        self._remove_heater_timeout()
        dt = datetime.datetime.now() + datetime.timedelta(hours=self._param_dict.get(Parameter.HEAT_DURATION))
        job_name = ScheduledJob.HEATER_TIMEOUT
        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.ABSOLUTE,
                        DriverSchedulerConfigKey.DATE: dt
                    },
                }
            }
        }

        self.set_init_params(config)
        self._add_scheduler_event(ScheduledJob.HEATER_TIMEOUT, ProtocolEvent.HEATER_TIMEOUT)

    def _stop_autosample(self):
        """
        Stop autosample, leveling if in progress.
        """
        self.leveling = False
        self._do_cmd_no_resp(InstrumentCommand.NANO_OFF)
        self._do_cmd_resp(InstrumentCommand.LILY_STOP_LEVELING, expected_prompt=Prompt.LILY_STOP_LEVELING)
        self._do_cmd_resp(InstrumentCommand.LILY_OFF, expected_prompt=Prompt.LILY_OFF)
        self._do_cmd_resp(InstrumentCommand.IRIS_OFF, expected_prompt=Prompt.IRIS_OFF)

    def _generic_response_handler(self, resp, prompt):
        """
        Pass through response handler
        @param resp: response
        @param prompt: prompt
        @return: (response, prompt)
        """
        return resp, prompt

    def _particle_to_dict(self, sample):
        """
        Convert a particle to a dictionary of value_id:value
        @param sample: particle to be parsed
        @return: dictionary representing the particle
        """
        sample_dict = {}
        values = sample.get(DataParticleKey.VALUES, [])
        for each in values:
            sample_dict[each[DataParticleKey.VALUE_ID]] = each[DataParticleKey.VALUE]
        return sample_dict

    def _check_for_autolevel(self, sample):
        """
        Check this sample, kick off a leveling event if out of range
        @param sample: sample to be checked
        """
        if self._param_dict.get(Parameter.AUTO_RELEVEL) and self.get_current_state() == ProtocolState.AUTOSAMPLE:
            # Find the current X and Y tilt values
            # If they exceed the trigger parameters, begin autolevel
            relevel = False
            sample = self._particle_to_dict(sample)
            x_tilt = abs(sample[particles.LilySampleParticleKey.X_TILT])
            y_tilt = abs(sample[particles.LilySampleParticleKey.Y_TILT])
            x_trig = int(self._param_dict.get(Parameter.XTILT_TRIGGER))
            y_trig = int(self._param_dict.get(Parameter.YTILT_TRIGGER))
            if x_tilt > x_trig or y_tilt > y_trig:
                self._async_raise_fsm_event(ProtocolEvent.START_LEVELING)

    def _failed_leveling(self, axis):
        """
        Handle a failed leveling event.  Set the failed flag, disable auto relevel and notify the operator
        @param axis: Axis which failed leveling
        """
        log.error('Detected leveling error in %s axis!', axis)
        # Read only parameter, must be set outside of handler
        self._param_dict.set_value(Parameter.LEVELING_FAILED, True)
        # Use the handler to disable auto relevel to raise a config change event if needed.
        self._handler_command_set({Parameter.AUTO_RELEVEL: False})
        raise InstrumentDataException('LILY Leveling (%s) Failed.  Disabling auto relevel' % axis)

    def _check_completed_leveling(self, sample):
        """
        Check this sample if leveling is complete or failed
        @param sample: Sample to be checked
        """
        sample = self._particle_to_dict(sample)
        status = sample[particles.LilyLevelingParticleKey.STATUS]
        if status is not None:
            # Leveling status update received
            # If leveling complete, send STOP_LEVELING, set the _leveling_failed flag to False
            if 'Leveled' in status:
                if self._param_dict.get(Parameter.LEVELING_FAILED):
                    self._handler_command_set({Parameter.LEVELING_FAILED: False})
                self._async_raise_fsm_event(ProtocolEvent.STOP_LEVELING)
            # Leveling X failed!  Set the flag and raise an exception to notify the operator
            # and disable auto leveling. Let the instrument attempt to level
            # in the Y axis.
            elif 'X Axis out of range' in status:
                self._failed_leveling('X')
            # Leveling X failed!  Set the flag and raise an exception to notify the operator
            # and disable auto leveling. Send STOP_LEVELING
            elif 'Y Axis out of range' in status:
                self._async_raise_fsm_event(ProtocolEvent.STOP_LEVELING)
                self._failed_leveling('Y')

    def _check_pps_sync(self, sample):
        """
        Check if PPS sync status has changed.  Update driver flag and, if appropriate, trigger a time sync
        @param sample: sample to be checked
        """
        sample = self._particle_to_dict(sample)
        pps_sync = sample[particles.NanoSampleParticleKey.PPS_SYNC] == 'P'
        if pps_sync:
            if not self.has_pps:
                # pps sync regained, sync the time
                self.has_pps = True
                if self.get_current_state() in [ProtocolState.COMMAND, ProtocolState.AUTOSAMPLE]:
                    self._async_raise_fsm_event(ProtocolEvent.NANO_TIME_SYNC)
        else:
            self.has_pps = False

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

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Process discover event
        @return next_state, next_agent_state
        """
        return ProtocolState.COMMAND, ResourceAgentState.IDLE

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

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

    def _handler_autosample_stop_autosample(self, *args, **kwargs):
        """
        Stop autosample
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.COMMAND, (ResourceAgentState.COMMAND, None)

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

    def _handler_command_enter(self, *args, **kwargs):
        """
        Enter command state.
        """
        # key off the initialization flag to determine if we should sync the time
        if self._init_type == InitializationType.STARTUP:
            self._handler_time_sync()

        self._init_params()
        self._stop_autosample()
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

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

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

        if len(args) < 1:
            raise InstrumentParameterException('Set command requires a parameter dict.')
        params = args[0]
        if len(args) > 1:
            startup = args[1]

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

        self._set_params(params, startup)
        return next_state, result

    def _handler_command_start_direct(self):
        """
        Start direct access
        @return next_state, (next_agent_state, result)
        """
        return ProtocolState.DIRECT_ACCESS, (ResourceAgentState.DIRECT_ACCESS, None)

    def _handler_command_start_autosample(self):
        """
        Start autosample
        @return next_state, (next_agent_state, result)
        """
        self._do_cmd_resp(InstrumentCommand.LILY_ON, expected_prompt=Prompt.LILY_ON)
        self._do_cmd_resp(InstrumentCommand.NANO_ON, expected_prompt=NANO_STRING)
        self._do_cmd_resp(InstrumentCommand.IRIS_ON, expected_prompt=Prompt.IRIS_ON)
        return ProtocolState.AUTOSAMPLE, (ResourceAgentState.STREAMING, 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_execute_direct(self, data):
        """
        Execute direct access command
        @return next_state, (next_agent_state, result)
        """
        self._do_cmd_direct(data)
        self._sent_cmds.append(data)
        return None, (None, None)

    def _handler_direct_access_stop_direct(self):
        """
        Stop direct access
        @return next_state, (next_agent_state, result)
        """
        next_state, next_agent_state = self._handler_unknown_discover()
        if next_state == DriverProtocolState.COMMAND:
            next_agent_state = ResourceAgentState.COMMAND

        return next_state, (next_agent_state, None)

    ########################################################################
    # 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):
        """
        Generic exit state handler
        """

    def _handler_acquire_status(self, *args, **kwargs):
        """
        We generate these particles here to avoid the chunker.  This allows us to process status
        messages with embedded messages from the other parts of the instrument.
        @return next_state, (next_agent_state, result)
        """
        ts = ntplib.system_to_ntp_time(time.time())
        parts = []

        for command, particle_class in [
            (InstrumentCommand.SYST_DUMP1, particles.SystStatusParticle),
            (InstrumentCommand.LILY_DUMP1, particles.LilyStatusParticle1),
            (InstrumentCommand.LILY_DUMP2, particles.LilyStatusParticle2),
            (InstrumentCommand.IRIS_DUMP1, particles.IrisStatusParticle1),
            (InstrumentCommand.IRIS_DUMP2, particles.IrisStatusParticle2),
            (InstrumentCommand.NANO_DUMP1, particles.NanoStatusParticle),
        ]:
            result, _ = self._do_cmd_resp(command, response_regex=particle_class.regex_compiled())
            parts.append(result)
        sample = self._extract_sample(particles.BotptStatusParticle,
                                      particles.BotptStatusParticle.regex_compiled(),
                                      NEWLINE.join(parts), ts)

        if self.get_current_state() == ProtocolState.AUTOSAMPLE:
            # acquiring status stops NANO output, restart it
            self._do_cmd_resp(InstrumentCommand.NANO_ON, expected_prompt=NANO_STRING)

        if not sample:
            raise InstrumentProtocolException('Failed to generate status particle')
        return None, (None, sample)

    def _handler_time_sync(self, *args, **kwargs):
        """
        Syncing time starts autosample...
        @return next_state, (next_agent_state, result)
        """
        self._do_cmd_resp(InstrumentCommand.NANO_SET_TIME, expected_prompt=NANO_STRING)
        if self.get_current_state() == ProtocolState.COMMAND:
            self._do_cmd_no_resp(InstrumentCommand.NANO_OFF)
        return None, (None, None)

    def _handler_start_leveling(self):
        """
        Send the start leveling command
        @return next_state, (next_agent_state, result)
        """
        if not self._param_dict.get(Parameter.LILY_LEVELING):
            self._schedule_leveling_timeout()
            self._do_cmd_resp(InstrumentCommand.LILY_START_LEVELING, expected_prompt=Prompt.LILY_START_LEVELING)
            self._param_dict.set_value(Parameter.LILY_LEVELING, True)
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return None, (None, None)

    def _handler_stop_leveling(self):
        """
        Send the stop leveling command
        @return next_state, (next_agent_state, result)
        """
        if self._param_dict.get(Parameter.LILY_LEVELING):
            self._remove_leveling_timeout()

            self._do_cmd_resp(InstrumentCommand.LILY_STOP_LEVELING, expected_prompt=Prompt.LILY_STOP_LEVELING)
            self._param_dict.set_value(Parameter.LILY_LEVELING, False)

            if self.get_current_state() == ProtocolState.AUTOSAMPLE:
                self._do_cmd_resp(InstrumentCommand.LILY_ON, expected_prompt=Prompt.LILY_ON)

            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

        return None, (None, None)

    def _handler_leveling_timeout(self):
        """
        Leveling has timed out, disable auto-relevel and mark leveling as failed.
        handler_stop_leveling will raise the config change event.
        @throws InstrumentProtocolException
        """
        self._param_dict.set_value(Parameter.AUTO_RELEVEL, False)
        self._param_dict.set_value(Parameter.LEVELING_FAILED, True)
        self._handler_stop_leveling()
        raise InstrumentProtocolException('Leveling failed to complete within timeout, disabling auto-relevel')

    def _handler_start_heater(self, *args, **kwargs):
        """
        Turn the heater on for Parameter.HEAT_DURATION hours
        @return next_state, (next_agent_state, result)
        """
        if not self._param_dict.get(Parameter.HEATER_ON):
            self._do_cmd_resp(InstrumentCommand.HEAT,
                              self._param_dict.get(Parameter.HEAT_DURATION),
                              response_regex=RegexResponse.HEAT)
            self._param_dict.set_value(Parameter.HEATER_ON, True)

            # Want to disable auto leveling when the heater is on
            self._param_dict.set_value(Parameter.AUTO_RELEVEL, False)

            self._schedule_heater_timeout()
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return None, (None, None)

    def _handler_stop_heater(self, *args, **kwargs):
        """
        Turn the heater on for Parameter.HEAT_DURATION hours
        @return next_state, (next_agent_state, result)
        """
        if self._param_dict.get(Parameter.HEATER_ON):
            self._do_cmd_resp(InstrumentCommand.HEAT,
                              0,
                              response_regex=RegexResponse.HEAT)
            self._param_dict.set_value(Parameter.HEATER_ON, False)
            self._remove_heater_timeout()
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return None, (None, None)

    def _handler_heater_timeout(self):
        """
        Heater should be finished.  Set HEATER_ON to false.
        """
        self._param_dict.set_value(Parameter.HEATER_ON, False)
        self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)
        return None, None
Exemplo n.º 44
0
    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)
Exemplo n.º 45
0
    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.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample),
                (ProtocolEvent.START_LEVELING, self._handler_start_leveling),
                (ProtocolEvent.STOP_LEVELING, self._handler_stop_leveling),
                (ProtocolEvent.NANO_TIME_SYNC, self._handler_time_sync),
                (ProtocolEvent.START_HEATER, self._handler_start_heater),
                (ProtocolEvent.STOP_HEATER, self._handler_stop_heater),
                (ProtocolEvent.LEVELING_TIMEOUT, self._handler_leveling_timeout),
                (ProtocolEvent.HEATER_TIMEOUT, self._handler_heater_timeout),
            ],
            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.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample),
                (ProtocolEvent.START_LEVELING, self._handler_start_leveling),
                (ProtocolEvent.STOP_LEVELING, self._handler_stop_leveling),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.NANO_TIME_SYNC, self._handler_time_sync),
                (ProtocolEvent.START_HEATER, self._handler_start_heater),
                (ProtocolEvent.STOP_HEATER, self._handler_stop_heater),
                (ProtocolEvent.LEVELING_TIMEOUT, self._handler_leveling_timeout),
                (ProtocolEvent.HEATER_TIMEOUT, self._handler_heater_timeout),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_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)

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

        # Add build handlers for device commands.
        for command in InstrumentCommand.list():
            if command in [InstrumentCommand.NANO_SET_RATE, InstrumentCommand.HEAT]:
                self._add_build_handler(command, self._build_command_with_value)
            else:
                self._add_build_handler(command, self._build_simple_command)

        # # Add response handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_response_handler(command, self._generic_response_handler)

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

        # commands sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        # create chunker
        self._chunker = StringChunker(Protocol.sieve_function)

        self._last_data_timestamp = 0
        self.has_pps = True

        # set up scheduled event handling
        self.initialize_scheduler()
        self._add_scheduler_event(ScheduledJob.ACQUIRE_STATUS, ProtocolEvent.ACQUIRE_STATUS)
        self._add_scheduler_event(ScheduledJob.NANO_TIME_SYNC, ProtocolEvent.NANO_TIME_SYNC)
Exemplo n.º 46
0
    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.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_AUTOSAMPLE,
                 self._handler_autosample_stop_autosample),
                (ProtocolEvent.START_LEVELING, self._handler_start_leveling),
                (ProtocolEvent.STOP_LEVELING, self._handler_stop_leveling),
                (ProtocolEvent.NANO_TIME_SYNC, self._handler_time_sync),
                (ProtocolEvent.START_HEATER, self._handler_start_heater),
                (ProtocolEvent.STOP_HEATER, self._handler_stop_heater),
                (ProtocolEvent.LEVELING_TIMEOUT,
                 self._handler_leveling_timeout),
                (ProtocolEvent.HEATER_TIMEOUT, self._handler_heater_timeout),
            ],
            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.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.START_AUTOSAMPLE,
                 self._handler_command_start_autosample),
                (ProtocolEvent.START_LEVELING, self._handler_start_leveling),
                (ProtocolEvent.STOP_LEVELING, self._handler_stop_leveling),
                (ProtocolEvent.START_DIRECT,
                 self._handler_command_start_direct),
                (ProtocolEvent.NANO_TIME_SYNC, self._handler_time_sync),
                (ProtocolEvent.START_HEATER, self._handler_start_heater),
                (ProtocolEvent.STOP_HEATER, self._handler_stop_heater),
                (ProtocolEvent.LEVELING_TIMEOUT,
                 self._handler_leveling_timeout),
                (ProtocolEvent.HEATER_TIMEOUT, self._handler_heater_timeout),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_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)

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

        # Add build handlers for device commands.
        for command in InstrumentCommand.list():
            if command in [
                    InstrumentCommand.NANO_SET_RATE, InstrumentCommand.HEAT
            ]:
                self._add_build_handler(command,
                                        self._build_command_with_value)
            else:
                self._add_build_handler(command, self._build_simple_command)

        # # Add response handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_response_handler(command, self._generic_response_handler)

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

        # commands sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        # create chunker
        self._chunker = StringChunker(Protocol.sieve_function)

        self._last_data_timestamp = 0
        self.has_pps = True

        # set up scheduled event handling
        self.initialize_scheduler()
        self._add_scheduler_event(ScheduledJob.ACQUIRE_STATUS,
                                  ProtocolEvent.ACQUIRE_STATUS)
        self._add_scheduler_event(ScheduledJob.NANO_TIME_SYNC,
                                  ProtocolEvent.NANO_TIME_SYNC)
Exemplo n.º 47
0
class Protocol(InstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    __metaclass__ = META_LOGGER

    def __init__(self, driver_event):
        """
        Protocol constructor.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        InstrumentProtocol.__init__(self, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = 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.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample),
                (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_start_poll),
                (ProtocolEvent.CALIBRATE, self._handler_command_start_calibrate),
                (ProtocolEvent.START_NAFION, self._handler_command_start_nafion_regen),
                (ProtocolEvent.START_ION, self._handler_command_start_ion_regen),
                (ProtocolEvent.ERROR, self._handler_error),
                (ProtocolEvent.POWEROFF, self._handler_command_poweroff),
                (ProtocolEvent.START_MANUAL, self._handler_command_start_manual),
            ],
            ProtocolState.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_autosample_acquire_sample),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.POLL: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.CLEAR, self._handler_error_clear),
            ],
            ProtocolState.CALIBRATE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.REGEN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_REGEN, self._handler_stop_regen),
                (ProtocolEvent.REGEN_COMPLETE, self._handler_regen_complete),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_direct_access_exit),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
            ],
            ProtocolState.MANUAL_OVERRIDE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_MANUAL, self._handler_manual_override_stop),
                (ProtocolEvent.GET_SLAVE_STATES, self._handler_manual_get_slave_states),
            ],
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._slave_protocols = {}
        self.initialize_scheduler()

    def _add_manual_override_handlers(self):
        for slave in self._slave_protocols:
            for event in self._slave_protocols[slave]._cmd_dict._cmd_dict:
                self._protocol_fsm.add_handler(ProtocolState.MANUAL_OVERRIDE,
                                               event, self._build_override_handler(slave, event))

    def _build_override_handler(self, slave, event):
        log.debug('Building event handler for protocol: %s event: %s', slave, event)

        def inner():
            return None, self._slave_protocols[slave]._protocol_fsm.on_event(event)
        return inner

    def register_slave_protocol(self, name, protocol):
        """
        @param name: slave protocol name
        @param protocol: slave protocol instance
        @return: None
        """
        self._slave_protocols[name] = protocol

    def _slave_protocol_event(self, event, *args, **kwargs):
        """
        Handle an event from a slave protocol.
        @param event: event to be processed
        """
        name = kwargs.get('name')
        if name is not None and name in self._slave_protocols:
            # only react to slave protocol events once we have transitioned out of unknown
            if self.get_current_state() != ProtocolState.UNKNOWN or event == DriverAsyncEvent.ERROR:
                if event == DriverAsyncEvent.STATE_CHANGE:
                    self._react()
                elif event == DriverAsyncEvent.CONFIG_CHANGE:
                    # do nothing, we handle this ourselves in set_param
                    pass
                else:
                    # pass the event up to the instrument agent
                    log.debug('Passing event up to the Instrument agent: %r %r %r', event, args, kwargs)
                    self._driver_event(event, *args)

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with parameters.
        For each parameter key, add match string, match lambda function,
        and value formatting function for set commands.
        """
        self._param_dict.add(Parameter.SAMPLE_INTERVAL, '', None, int,
                             type=ParameterDictType.INT,
                             display_name='Autosample Interval',
                             description='Interval between sample starts during autosample state',
                             range=(7200, 86400),
                             units=Units.SECOND)

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.ACQUIRE_SAMPLE, display_name="Acquire Sample")
        self._cmd_dict.add(Capability.START_AUTOSAMPLE, display_name="Start Autosample")
        self._cmd_dict.add(Capability.CALIBRATE, display_name="Acquire Calibration Samples")
        self._cmd_dict.add(Capability.START_ION, display_name="Start Ion Chamber Regeneration")
        self._cmd_dict.add(Capability.START_NAFION, display_name="Start Nafion Regeneration")
        self._cmd_dict.add(Capability.STOP_REGEN, display_name="Stop Current Regeneration")
        self._cmd_dict.add(Capability.STOP_AUTOSAMPLE, display_name="Stop Autosample")
        self._cmd_dict.add(Capability.POWEROFF, display_name='Low Power State')
        self._cmd_dict.add(Capability.GET_SLAVE_STATES,
                           display_name='Get Slave States')
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

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

    def _react(self):
        """
        Determine if an action is necessary based on the states of the slave protocols.

            (MCU STATE, TURBO STATE, RGA STATE) : (TARGET, EVENT)

        The specified event will be sent to the specified target.
        """
        state = self.get_current_state()
        slave_states = self._get_slave_states()

        if MASSP_STATE_ERROR in slave_states:
            return self._error()

        if state == ProtocolState.REGEN and slave_states[0] == ProtocolState.COMMAND:
            self._async_raise_fsm_event(ProtocolEvent.REGEN_COMPLETE)

        # these actions are only applicable in POLL, AUTOSAMPLE or CALIBRATE states
        if state not in [ProtocolState.POLL, ProtocolState.AUTOSAMPLE, ProtocolState.CALIBRATE]:
            return

        mps = mcu.ProtocolState
        tps = turbo.ProtocolState
        rps = rga.ProtocolState
        action_map = {
            # Waiting Turbo (RGA is off)
            (mps.WAITING_TURBO, tps.COMMAND, rps.COMMAND): (TURBO, turbo.Capability.START_TURBO),
            (mps.WAITING_TURBO, tps.AT_SPEED, rps.COMMAND): (MCU, mcu.Capability.START2),

            # Waiting RGA
            (mps.WAITING_RGA, tps.AT_SPEED, rps.SCAN): (MCU, mcu.Capability.SAMPLE),
            (mps.WAITING_RGA, tps.AT_SPEED, rps.COMMAND): (RGA, rga.Capability.START_SCAN),
            (mps.WAITING_RGA, tps.COMMAND, rps.SCAN): (RGA, rga.Capability.STOP_SCAN),  # this should never happen!
            (mps.WAITING_RGA, tps.COMMAND, rps.COMMAND): (MCU, mcu.Capability.STANDBY),  # this should never happen!

            # Stopping
            (mps.STOPPING, tps.AT_SPEED, rps.SCAN): (RGA, rga.Capability.STOP_SCAN),
            (mps.STOPPING, tps.AT_SPEED, rps.COMMAND): (TURBO, turbo.Capability.STOP_TURBO),
            (mps.STOPPING, tps.COMMAND, rps.SCAN): (RGA, rga.Capability.STOP_SCAN),  # this should never happen!
            (mps.STOPPING, tps.COMMAND, rps.COMMAND): (MCU, mcu.Capability.STANDBY),
        }

        action = action_map.get(self._get_slave_states())

        if action is not None:
            if not isinstance(action, list):
                action = [action]

            # iterate through the action list, sending the events to the targets
            # if we are in POLL or CALIBRATE and we see a STANDBY event, return this driver to COMMAND.
            for target, command in action:
                if command == mcu.Capability.SAMPLE and state == ProtocolState.CALIBRATE:
                    command = mcu.Capability.CALIBRATE
                if command == mcu.Capability.STANDBY and state in [ProtocolState.CALIBRATE, ProtocolState.POLL]:
                    self._send_event_to_slave(target, command)
                    self._async_raise_fsm_event(ProtocolEvent.STOP)
                else:
                    self._send_event_to_slave(target, command)
        return action

    def _error(self):
        """
        Handle error state in slave protocol
        """
        state = self.get_current_state()
        slave_states = self._get_slave_states()

        # if we are not currently in the error state, make the transition
        if state != ProtocolState.ERROR:
            self._async_raise_fsm_event(ProtocolEvent.ERROR)
        mcu_state, turbo_state, rga_state = slave_states

        # before we do anything else, the RGA must be stopped.
        if rga_state not in [rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR]:
            self._send_event_to_slave(RGA, rga.ProtocolEvent.STOP_SCAN)
        # RGA must be in COMMAND or ERROR, the TURBO must be stopped.
        elif turbo_state not in [turbo.ProtocolState.COMMAND, turbo.ProtocolState.SPINNING_DOWN]:
            self._send_event_to_slave(TURBO, turbo.ProtocolEvent.STOP_TURBO)
        # Turbo and RGA must be in COMMAND or ERROR, stop the MCU
        elif mcu_state != mcu.ProtocolState.COMMAND:
            self._send_event_to_slave(MCU, mcu.ProtocolEvent.STANDBY)

    def _got_chunk(self, chunk):
        """
        This driver has no chunker...
        """

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        @param events: Events to be filtered
        @return: list of events which are also capabilities
        """
        return [x for x in events if Capability.has(x) or mcu.Capability.has(x) or turbo.Capability.has(x) or
                rga.Capability.has(x)]

    def _get_slave_states(self):
        """
        Retrieve the current protocol state from each of the slave protocols and return them as a tuple.
        """
        return (
            self._slave_protocols[MCU].get_current_state(),
            self._slave_protocols[TURBO].get_current_state(),
            self._slave_protocols[RGA].get_current_state(),
        )

    def _send_event_to_all(self, event):
        """
        Send the same event to all slave protocols.
        @return: List of (name, result) for a slave protocols
        """
        return [(name, slave._protocol_fsm.on_event(event)) for name, slave in self._slave_protocols.items()]

    def _send_event_to_slave(self, name, event):
        """
        Send an event to a specific protocol
        @param name: Name of slave protocol
        @param event: Event to be sent
        """
        slave_protocol = self._slave_protocols.get(name)
        if slave_protocol is None:
            raise InstrumentProtocolException('Attempted to send event to non-existent protocol: %s' % name)
        slave_protocol._async_raise_fsm_event(event)

    def _send_massp_direct_access(self, command):
        """
        Handle a direct access command.  Driver expects direct access commands to specify the target
        using the following format:

        target:command

        It then routes the command to the appropriate slave protocol.
        @param command: Direct access command received
        """
        err_string = 'Invalid command.  Command must be in the following format: "target:command' + NEWLINE + \
                     'Valid targets are: %r' % self._slave_protocols.keys()
        try:
            target, command = command.split(DA_COMMAND_DELIMITER, 1)
            target = target.lower()
        except ValueError:
            target = None

        log.debug('_do_cmd_direct - target: %s command: %r', target, command)

        if target not in self._slave_protocols:
            self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, err_string)
        else:
            self._slave_protocols[target]._protocol_fsm.on_event(ProtocolEvent.EXECUTE_DIRECT, command)

    def _set_params(self, *args, **kwargs):
        """
        Set one or more parameters.  This method will determine where the parameter actually resides and
        forward it to the appropriate parameter dictionary based on name.
        @param args: arglist which must contain a parameter dictionary
        @throws InstrumentParameterException
        """
        params = args[0]

        if not isinstance(params, dict):
            raise InstrumentParameterException('Attempted to set parameters with a non-dictionary argument')

        _, old_config = self._handler_command_get([Parameter.ALL])

        temp_dict = {}
        for key in params:
            split_key = key.split('_', 1)
            if len(split_key) == 1:
                raise InstrumentParameterException('Missing target in MASSP parameter: %s' % key)
            target = split_key[0]
            if target not in self._slave_protocols:
                # this is a master driver parameter, set it here
                if key in self._param_dict.get_keys():
                    log.debug("Setting value for %s to %s", key, params[key])
                    self._param_dict.set_value(key, params[key])
                else:
                    raise InstrumentParameterException('Invalid key in SET action: %s' % key)
            else:
                temp_dict.setdefault(target, {})[key] = params[key]

        # set parameters for slave protocols
        for name in temp_dict:
            if name in self._slave_protocols:
                self._slave_protocols[name]._set_params(temp_dict[name])
            else:
                # how did we get here?  This should never happen, but raise an exception if it does.
                raise InstrumentParameterException('Invalid key(s) in SET action: %r' % temp_dict[name])

        _, new_config = self._handler_command_get([Parameter.ALL])

        if not new_config == old_config:
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def set_init_params(self, config):
        """
        Set initial parameters.  Parameters are forwarded to the appropriate parameter dictionary based on name.
        @param config: Init param config to be handled
        """
        temp_dict = {}
        self._startup_config = config
        config = config.get(DriverConfigKey.PARAMETERS, {})
        for key in config:
            target, _ = key.split('_', 1)
            if target not in self._slave_protocols:
                # master driver parameter
                log.debug("Setting init value for %s to %s", key, config[key])
                self._param_dict.set_init_value(key, config[key])
            else:
                temp_dict.setdefault(target, {})[key] = config[key]

        for name in temp_dict:
            if name in self._slave_protocols:
                self._slave_protocols[name].set_init_params({DriverConfigKey.PARAMETERS: temp_dict[name]})
            else:
                # how did we get here?  This should never happen, but raise an exception if it does.
                raise InstrumentParameterException('Invalid key(s) in INIT PARAMS action: %r' % temp_dict[name])

    def get_config_metadata_dict(self):
        """
        See base class for full description.  This method is overridden to retrieve the parameter
        dictionary from each slave protocol and merge them.
        @return: dictionary containing driver metadata
        """
        log.debug("Getting metadata dict from protocol...")
        return_dict = {ConfigMetadataKey.DRIVER: self._driver_dict.generate_dict(),
                       ConfigMetadataKey.COMMANDS: self._cmd_dict.generate_dict(),
                       ConfigMetadataKey.PARAMETERS: self._param_dict.generate_dict()}

        for protocol in self._slave_protocols.values():
            return_dict[ConfigMetadataKey.PARAMETERS].update(protocol._param_dict.generate_dict())
            return_dict[ConfigMetadataKey.COMMANDS].update(protocol._cmd_dict.generate_dict())

        return return_dict

    def get_resource_capabilities(self, current_state=True):
        """
        Overrides base class to include slave protocol parameters
        @param current_state: Boolean indicating whether we should return only the current state events
        @return: (resource_commands, resource_parameters)
        """
        res_cmds = self._protocol_fsm.get_events(current_state)
        res_cmds = self._filter_capabilities(res_cmds)
        res_params = self._param_dict.get_keys()

        for protocol in self._slave_protocols.values():
            res_params.extend(protocol._param_dict.get_keys())

        return res_cmds, res_params

    def _build_scheduler(self):
        """
        Build a scheduler for periodic status updates
        """
        job_name = ScheduledJob.ACQUIRE_SAMPLE
        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE: TriggerType.INTERVAL,
                        DriverSchedulerConfigKey.SECONDS: self._param_dict.get(Parameter.SAMPLE_INTERVAL)
                    },
                }
            }
        }

        self.set_init_params(config)
        self._add_scheduler_event(ScheduledJob.ACQUIRE_SAMPLE, ProtocolEvent.ACQUIRE_SAMPLE)

    def _delete_scheduler(self):
        """
        Remove the autosample schedule.
        """
        try:
            self._remove_scheduler(ScheduledJob.ACQUIRE_SAMPLE)
        except KeyError:
            log.info('Failed to remove scheduled job for ACQUIRE_SAMPLE')

    ########################################################################
    # Generic handlers.
    ########################################################################
    def _handler_generic_enter(self, *args, **kwargs):
        """
        Generic enter handler, raise STATE CHANGE
        """
        if self.get_current_state() != ProtocolState.UNKNOWN:
            self._init_params()
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_generic_exit(self, *args, **kwargs):
        """
        Generic exit handler, do nothing.
        """

    def _handler_stop_generic(self, *args, **kwargs):
        """
        Generic stop method to return to COMMAND (via POLL if appropriate)
        @return next_state, (next_state, None)
        """
        next_state = ProtocolState.COMMAND
        result = []

        self._delete_scheduler()

        # check if we are in autosample AND currently taking a sample, if so, move to POLL
        # otherwise go back to COMMAND.
        if self.get_current_state() == ProtocolState.AUTOSAMPLE:
            if self._get_slave_states() != (ProtocolState.COMMAND, ProtocolState.COMMAND, ProtocolState.COMMAND):
                next_state = ProtocolState.POLL

        return next_state, (next_state, result)

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

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state
        @return next_state, (next_state, result)
        """
        next_state = ProtocolState.COMMAND
        result = self._send_event_to_all(ProtocolEvent.DISCOVER)
        log.debug('_handler_unknown_discover -- send DISCOVER to all: %r', result)
        target_state = (ProtocolState.COMMAND, ProtocolState.COMMAND, ProtocolState.COMMAND)
        success = False
        # wait for the slave protocols to discover
        for attempt in xrange(5):
            slave_states = self._get_slave_states()
            if slave_states == target_state:
                success = True
                break
            time.sleep(1)
        if not success:
            next_state = ProtocolState.ERROR
        return next_state, (next_state, result)

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

    def _handler_command_get(self, *args, **kwargs):
        """
        Get parameter.  Query this protocol plus all slave protocols.
        @param args: arglist which should contain a list of parameters to get
        @return None, results
        """
        params = args[0]

        if not isinstance(params, list):
            params = [params]

        temp_dict = {}
        result_dict = {}

        # request is for all parameters, send get(ALL) to each protocol then combine the results.
        if Parameter.ALL in params:
            params = [Parameter.ALL]
            _, result = self._handler_get(params, **kwargs)
            result_dict.update(result)
            for protocol in self._slave_protocols.values():
                _, result = protocol._handler_get(params, **kwargs)
                result_dict.update(result)

        # request is for specific parameters.  Determine which protocol should service each,
        # call the appropriate _handler_get and combine the results
        else:
            for key in params:
                log.debug('about to split: %s', key)
                target, _ = key.split('_', 1)
                temp_dict.setdefault(target, []).append(key)
            for key in temp_dict:
                if key == MASTER:
                    _, result = self._handler_get(params, **kwargs)
                else:
                    if key in self._slave_protocols:
                        _, result = self._slave_protocols[key]._handler_get(params, **kwargs)
                    else:
                        raise InstrumentParameterException('Invalid key(s) in GET action: %r' % temp_dict[key])
                result_dict.update(result)

        return None, result_dict

    def _handler_command_set(self, *args, **kwargs):
        """
        Set parameter, just pass through to _set_params, which knows how to set the params
        in the slave protocols.
        """
        next_state = None
        result = []
        self._set_params(*args, **kwargs)
        return next_state, (next_state, result)

    def _handler_command_start_direct(self):
        """
        Start direct access
        """
        next_state = ProtocolState.DIRECT_ACCESS
        result = []
        return next_state, (next_state, result)

    def _handler_command_start_autosample(self):
        """
        Move my FSM to autosample and start the sample sequence by sending START1 to the MCU.
        Create the scheduler to automatically start the next sample sequence
        """
        next_state = ProtocolState.AUTOSAMPLE
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        self._build_scheduler()
        return next_state, (next_state, result)

    def _handler_command_start_poll(self):
        """
        Move my FSM to poll and start the sample sequence by sending START1 to the MCU
        """
        next_state = ProtocolState.POLL
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return next_state, (next_state, result)

    def _handler_command_start_calibrate(self):
        """
        Move my FSM to calibrate and start the calibrate sequence by sending START1 to the MCU
        """
        next_state = ProtocolState.CALIBRATE
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return next_state, (next_state, result)

    def _handler_command_start_nafion_regen(self):
        """
        Move my FSM to NAFION_REGEN and send NAFION_REGEN to the MCU
        """
        next_state = ProtocolState.REGEN
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.NAFREG)
        return next_state, (next_state, result)

    def _handler_command_start_ion_regen(self):
        """
        Move my FSM to ION_REGEN and send ION_REGEN to the MCU
        """
        next_state = ProtocolState.REGEN
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.IONREG)
        return next_state, (next_state, result)

    def _handler_command_poweroff(self):
        """
        Send POWEROFF to the MCU
        """
        next_state = None
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.POWEROFF)
        return next_state, (next_state, result)

    def _handler_command_start_manual(self):
        """
        Move FSM to MANUAL OVERRIDE state
        """
        next_state = ProtocolState.MANUAL_OVERRIDE
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Error handlers.
    ########################################################################

    def _handler_error(self):
        next_state = ProtocolState.ERROR
        result = []
        return next_state, (next_state, result)

    def _handler_error_clear(self):
        """
        Send the CLEAR event to any slave protocol in the error state and return this driver to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        for protocol in self._slave_protocols:
            state = protocol.get_current_state()
            if state == MASSP_STATE_ERROR:
                # do this synchronously, to allow each slave protocol to complete the CLEAR action
                # before transitioning states.
                protocol._protocol_fsm.on_event(ProtocolEvent.CLEAR)
        return next_state, (next_state, result)

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

    def _handler_autosample_acquire_sample(self):
        """
        Fire off a sample sequence while in the autosample state.
        @throws InstrumentProtocolException
        """
        next_state = None
        result = []
        slave_states = self._get_slave_states()
        # verify the MCU is not already in a sequence
        if slave_states[0] == ProtocolState.COMMAND:
            result = self._send_event_to_slave(MCU, mcu.Capability.START1)
        else:
            raise InstrumentProtocolException("Attempted to acquire sample while sampling")
        return next_state, (next_state, result)

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

    def _handler_direct_access_enter(self, *args, **kwargs):
        """
        Enter direct access state.  Forward to all slave protocols.
        """
        self._send_event_to_all(ProtocolEvent.START_DIRECT)

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

    def _handler_direct_access_exit(self, *args, **kwargs):
        """
        Exit direct access state.  Check slave protocol states and verify they all
        return to COMMAND, otherwise raise InstrumentProtocolException.
        @throws InstrumentProtocolException
        """
        for attempt in range(DA_EXIT_MAX_RETRIES):
            slave_states = self._get_slave_states()
            if ProtocolState.DIRECT_ACCESS in slave_states:
                log.error('Slave protocol failed to return to command, attempt %d', attempt)
                time.sleep(1)
            else:
                return

        raise InstrumentProtocolException('Slave protocol never returned to command from DA.')

    def _handler_direct_access_execute_direct(self, data):
        """
        Execute a direct access command.  For MASSP, this means passing the actual command to the
        correct slave protocol.  This is handled by _send_massp_direct_access.
        """
        self._send_massp_direct_access(data)

        # add sent command to list for 'echo' filtering in callback
        self._sent_cmds.append(data)

        return None, (None, [])

    def _handler_direct_access_stop_direct(self):
        next_state = ProtocolState.COMMAND
        result = []
        self._send_event_to_all(ProtocolEvent.STOP_DIRECT)
        return next_state, (next_state, result)

    ########################################################################
    # Regen handlers.
    ########################################################################

    def _handler_stop_regen(self):
        """
        Abort the current regeneration sequence, return to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.STANDBY)
        return next_state, (next_state, result)

    def _handler_regen_complete(self):
        """
        Regeneration sequence is complete, return to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._async_agent_state_change(ResourceAgentState.COMMAND)
        return next_state, (next_state, result)

    def _handler_manual_override_stop(self):
        """
        Exit manual override.  Attempt to bring the slave drivers back to COMMAND.
        """
        next_state = ProtocolState.COMMAND
        result = []
        mcu_state, turbo_state, rga_state = self._get_slave_states()
        if rga_state == rga.ProtocolState.SCAN:
            self._slave_protocols[RGA]._protocol_fsm.on_event(rga.Capability.STOP_SCAN)
        if turbo_state == turbo.ProtocolState.AT_SPEED:
            self._slave_protocols[TURBO]._protocol_fsm.on_event(turbo.Capability.STOP_TURBO)
        while rga_state not in [rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR] or \
                turbo_state not in [turbo.ProtocolState.COMMAND, turbo.ProtocolState.ERROR]:
            time.sleep(.1)
            mcu_state, turbo_state, rga_state = self._get_slave_states()
        if mcu_state != mcu.ProtocolState.COMMAND:
            self._slave_protocols[MCU]._protocol_fsm.on_event(mcu.Capability.STANDBY)

        return next_state, (next_state, result)

    def _handler_manual_get_slave_states(self):
        """
        Get the slave states and return them to the user
        @return: next_state, (next_state, result)
        """
        mcu_state, turbo_state, rga_state = self._get_slave_states()
        return None, (None, {MCU: mcu_state, RGA: rga_state, TURBO: turbo_state})
Exemplo n.º 48
0
class PlatformDriver(object):
    """
    A platform driver handles a particular platform in a platform network.
    This base class provides a common interface and supporting functionality.
    """
    __metaclass__ = META_LOGGER

    def __init__(self, event_callback):
        """
        Creates a PlatformDriver instance.

        @param event_callback  Listener of events generated by this driver
        """
        self._platform_id = None
        self._send_event = event_callback

        self._driver_config = None
        self._resource_schema = {}
        
        # The parameter dictionary.
        self._param_dict = {}

        # construct FSM and start it with initial state UNCONFIGURED:
        self._construct_fsm()
        self._fsm.start(PlatformDriverState.UNCONFIGURED)

    def get_resource_capabilities(self, current_state=True):
        """
        """
        res_cmds = self._fsm.get_events(current_state)
        res_cmds = self._filter_capabilities(res_cmds)
        res_params = self._param_dict.keys()

        return [res_cmds, res_params]

    def _filter_capabilities(self, events):
        """
        Typically overwritten in subclass.
        """
        events_out = [x for x in events if PlatformDriverCapability.has(x)]
        return events_out

    def get_resource_state(self, *args, **kwargs):
        """
        Return the current state of the driver.
        @retval str current driver state.
        """
        return self._fsm.get_current_state()

    def get_resource(self, *args, **kwargs):
        """
        """
        return self._fsm.on_event(PlatformDriverEvent.GET, *args, **kwargs)

    def set_resource(self, *args, **kwargs):
        """
        """
        return self._fsm.on_event(PlatformDriverEvent.SET, *args, **kwargs)

    def configure(self, *args, **kwargs):
        """
        Configure the driver for communications with the device via
        port agent / logger (valid but unconnected connection object).
        @param arg[0] comms config dict.
        @raises InstrumentStateException if command not allowed in current state
        @throws InstrumentParameterException if missing comms or invalid config dict.
        """
        # Forward event and argument to the connection FSM.
        return self._fsm.on_event(PlatformDriverEvent.CONFIGURE, *args, **kwargs)

    def connect(self, *args, **kwargs):
        """
        Configure the driver for communications with the device via
        port agent / logger (valid but unconnected connection object).
        @param arg[0] comms config dict.
        @raises InstrumentStateException if command not allowed in current state
        @throws InstrumentParameterException if missing comms or invalid config dict.
        """
        # Forward event and argument to the connection FSM.
        return self._fsm.on_event(PlatformDriverEvent.CONNECT, *args, **kwargs)

    def disconnect(self, *args, **kwargs):
        """
        @raises InstrumentStateException if command not allowed in current state
        """
        # Forward event and argument to the connection FSM.
        return self._fsm.on_event(PlatformDriverEvent.DISCONNECT, *args, **kwargs)

    def ping(self, *args, **kwargs):
        """
        @raises InstrumentStateException if command not allowed in current state
        """
        # Forward event and argument to the connection FSM.
        return self._fsm.on_event(PlatformDriverEvent.PING, *args, **kwargs)

    def execute_resource(self, resource_cmd, *args, **kwargs):
        """
        Platform agent calls this directly to trigger the execution of a
        resource command. The actual action occurs in execute.
        """
        return self._fsm.on_event(PlatformDriverEvent.EXECUTE, resource_cmd, *args, **kwargs)

    def _get_platform_attributes(self):
        """
        Gets a dict of the attribute definitions in this platform as given at
        construction time (from pnode parameter).
        """
        return self._platform_attributes

    def validate_driver_configuration(self, driver_config):
        """
        Called by configure so a subclass can perform any needed additional
        validation of the provided configuration.
        Nothing is done in this base class. Note that basic validation is
        done by PlatformAgent prior to creating/configuring the driver.

        @param driver_config Driver configuration.

        @raise PlatformDriverException Error in driver configuration.
        """
        pass

    def _configure(self, config):
        """
        Configures this driver. In this base class it basically
        calls validate_driver_configuration and then assigns the given
        config to self._driver_config.

        @param driver_config Driver configuration.
        """

        #
        # NOTE the "pnode" parameter may be not very "standard" but it is the
        # current convenient mechanism that captures the overall definition
        # of the corresponding platform (most of which coming from configuration)
        #



        self.validate_driver_configuration(config)
        self._driver_config = config
        #self._param_dict = deepcopy(self._driver_config.get('attributes',{}))

    def get_config_metadata(self):
        """
        """
        return deepcopy(self._resource_schema)

    def _connect(self, recursion=None):
        """
        To be implemented by subclass.
        Establishes communication with the platform device.

        @raise PlatformConnectionException
        """
        raise NotImplementedError()  #pragma: no cover

    def _disconnect(self, recursion=None):
        """
        To be implemented by subclass.
        Ends communication with the platform device.

        @raise PlatformConnectionException
        """
        raise NotImplementedError()  #pragma: no cover

    def _ping(self):
        """
        To be implemented by subclass.
        Verifies communication with external platform returning "PONG" if
        this verification completes OK.

        @retval "PONG"

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        raise NotImplementedError()  #pragma: no cover

    def get_attribute_values(self, attrs):
        """
        To be implemented by subclass.
        Returns the values for specific attributes since a given time for
        each attribute.

        @param attrs     [(attrName, from_time), ...] desired attributes.
                         from_time Assummed to be in the format basically described by
                         pyon's get_ion_ts function, "a str representing an
                         integer number, the millis in UNIX epoch."

        @retval {attrName : [(attrValue, timestamp), ...], ...}
                dict indexed by attribute name with list of (value, timestamp)
                pairs. Timestamps in same format as from_time.

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        raise NotImplementedError()  #pragma: no cover

    def set_attribute_values(self, attrs):
        """
        To be implemented by subclass.
        Sets values for writable attributes in this platform.

        @param attrs 	[(attrName, attrValue), ...] 	List of attribute values

        @retval {attrName : [(attrValue, timestamp), ...], ...}
                dict with a list of (value,timestamp) pairs for each attribute
                indicated in the input. Returned timestamps indicate the time when the
                value was set. Each timestamp is "a str representing an
                integer number, the millis in UNIX epoch" to
                align with description of pyon's get_ion_ts function.

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        #
        # TODO Any needed alignment with the instrument case?
        #
        raise NotImplementedError()  #pragma: no cover

    def execute(self, cmd, *args, **kwargs):
        """
        Executes the given command.
        Subclasses can override to execute particular commands or delegate to
        its super class. However, note that this base class raises
        NotImplementedError.

        @param cmd     command
        @param args    command's args
        @param kwargs  command's kwargs

        @return  result of the execution

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        raise NotImplementedError()  # pragma: no cover

    def get(self, *args, **kwargs):
        """
        Gets the values of the requested attributes.
        Subclasses can override to get particular attributes and
        delegate to this base implementation to handle common attributes.

        @param args    get's args
        @param kwargs  get's kwargs

        @return  result of the retrieval.

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        raise NotImplementedError()  # pragma: no cover

    def destroy(self):
        """
        Stops all activity done by the driver. Nothing done in this class.
        """
        pass

    def _notify_driver_event(self, driver_event):
        """
        Convenience method for subclasses to send a driver event to
        corresponding platform agent.

        @param driver_event a DriverEvent object.
        """
        log.error("%r: _notify_driver_event: %s", self._platform_id, type(driver_event))
        if isinstance(driver_event, DriverEvent):
            driver_event = str(driver_event)
        self._send_event(driver_event)

    def get_external_checksum(self):
        """
        To be implemented by subclass.
        Returns the checksum of the external platform associated with this
        driver.

        @return SHA1 hash value as string of hexadecimal digits.

        @raise PlatformConnectionException  If the connection to the external
               platform is lost.
        """
        raise NotImplementedError()  #pragma: no cover

    def get_driver_state(self):
        """
        Returns the current FSM state.
        """
        return self._fsm.get_current_state()

    #####################################################################
    # Supporting method for handling connection lost in CONNECT handlers
    #####################################################################

    def _connection_lost(self, cmd, args, kwargs, exc=None):
        """
        Supporting method to be called by any CONNECTED handler right after
        detecting that the connection with the external platform device has
        been lost. It does a regular disconnect() and notifies the agent about
        the lost connection. Note that the call to disconnect() itself may
        throw some additional exception very likely caused by the fact that
        the connection is lost--this exception is just logged out but ignored.

        All parameters are for logging purposes.

        @param cmd     string indicating the command that was attempted
        @param args    args of the command that was attempted
        @param kwargs  kwargs of the command that was attempted
        @param exc     associated exception (if any),

        @return (next_state, result) suitable as the return of the FSM
                handler where the connection lost was detected. The
                next_state will always be PlatformDriverState.DISCONNECTED.
        """
        log.debug("%r: (LC) _connection_lost: cmd=%s, args=%s, kwargs=%s, exc=%s",
                  self._platform_id, cmd, args, kwargs, exc)

        # NOTE I have this import here as a quick way to avoid circular imports
        # (note that platform_agent also imports elements in this module)
        # TODO better to move some basic definitions to separate modules.
        from mi.platform.platform_agent import PlatformAgentEvent

        result = None
        try:
            result = self.disconnect()

        except Exception as e:
            # just log a message
            log.debug("%r: (LC) ignoring exception while calling disconnect upon"
                      " lost connection: %s", self._platform_id, e)

        # in any case, notify the agent about the lost connection and
        # transition to DISCONNECTED:
        self._notify_driver_event(AsyncAgentEvent(PlatformAgentEvent.LOST_CONNECTION))

        next_state = PlatformDriverState.DISCONNECTED

        return next_state, result

    ##############################################################
    # FSM event handlers.
    ##############################################################

    def _common_state_enter(self, *args, **kwargs):
        """
        Common work upon every state entry.
        """
        state = self.get_driver_state()
        log.debug('%r: driver entering state: %s', self._platform_id, state)

        self._notify_driver_event(StateChangeDriverEvent(state))

    def _common_state_exit(self, *args, **kwargs):
        """
        Common work upon every state exit.
        Nothing done in this base class.
        """

    ##############################################################
    # UNCONFIGURED event handlers.
    ##############################################################

    def _handler_unconfigured_configure(self, *args, **kwargs):
        """
        """
        driver_config = kwargs.get('driver_config', None)
        if driver_config is None:
            raise InstrumentException('configure: missing driver_config argument')

        try:
            result = self._configure(driver_config)
            next_state = PlatformDriverState.DISCONNECTED
        except PlatformDriverException as e:
            result = None
            next_state = None
            log.error("Error in platform driver configuration", e)

        return next_state, result

    ##############################################################
    # DISCONNECTED event handlers.
    ##############################################################

    def _handler_disconnected_connect(self, *args, **kwargs):
        """
        """
        recursion = kwargs.get('recursion', None)

        self._connect(recursion=recursion)
        result = next_state = PlatformDriverState.CONNECTED

        return next_state, result

    def _handler_disconnected_disconnect(self, *args, **kwargs):
        """
        We allow the DISCONNECT event in DISCONNECTED state for convenience,
        in particular it facilitates the overall handling of the connection_lost
        event, which is processed by a subsequent call to disconnect from the
        platform agent. The handler here does nothing.
        """
        return None, None

    ###########################################################################
    # CONNECTED event handlers.
    # Except for the explicit disconnect and connection_lost handlers, the
    # CONNECTED handlers (here and in subclasses) should directly catch any
    # PlatformConnectionException to call _connection_lost.
    ###########################################################################

    def _handler_connected_disconnect(self, *args, **kwargs):
        """
        """
        recursion = kwargs.get('recursion', None)

        result = self._disconnect(recursion=recursion)
        next_state = PlatformDriverState.DISCONNECTED

        return next_state, result

    def _handler_connected_connection_lost(self, *args, **kwargs):
        """
        The connection was lost (as opposed to a normal disconnect request).
        Here we do the regular disconnect but also notify the platform agent
        about the lost connection.

        NOTE: this handler in the FSM is provided in case there is a need to
        directly trigger the associated transition along with the associated
        notification to the agent. However, the typical case is that a CONNECTED
        handler dealing with commands will catch any PlatformConnectionException
        to call _connection_lost directly.
        """
        # just use our supporting method:
        return self._connection_lost(PlatformDriverEvent.CONNECTION_LOST, args, kwargs)

    def _handler_connected_ping(self, *args, **kwargs):
        """
        """
        try:
            result = self._ping()
            return None, result

        except PlatformConnectionException as e:
            return self._connection_lost(PlatformDriverEvent.PING, args, kwargs, e)

    def _handler_connected_get(self, *args, **kwargs):
        """
        """
        try:
            result = self.get(*args, **kwargs)
            return None, result

        except PlatformConnectionException as e:
            return self._connection_lost(PlatformDriverEvent.GET, args, kwargs, e)

    def _handler_connected_set(self, *args, **kwargs):
        """
        """
        attrs = kwargs.get('attrs', None)
        if attrs is None:
            raise InstrumentException('set_attribute_values: missing attrs argument')

        try:
            result = self.set_attribute_values(attrs)
            return None, result

        except PlatformConnectionException as e:
            return self._connection_lost(PlatformDriverEvent.SET, args, kwargs, e)

    def _handler_connected_execute(self, *args, **kwargs):
        """
        """

        if len(args) == 0:
            raise InstrumentException('execute_resource: missing resource_cmd argument')

        try:
            result = self.execute(*args, **kwargs)
            return None, result

        except PlatformConnectionException as e:
            return self._connection_lost(PlatformDriverEvent.EXECUTE, args, kwargs, e)

    ##############################################################
    # Platform driver FSM setup
    ##############################################################

    def _construct_fsm(self, states=PlatformDriverState,
                       events=PlatformDriverEvent,
                       enter_event=PlatformDriverEvent.ENTER,
                       exit_event=PlatformDriverEvent.EXIT):
        """
        Constructs the FSM for the driver. The preparations here are mostly
        related with the UNCONFIGURED, DISCONNECTED, and CONNECTED state
        transitions, with some common handlers for the CONNECTED state.
        Subclasses can override to indicate specific parameters and add new
        handlers (typically for the CONNECTED state).
        """
        log.debug("constructing base platform driver FSM")

        self._fsm = ThreadSafeFSM(states, events, enter_event, exit_event)

        for state in PlatformDriverState.list():
            self._fsm.add_handler(state, enter_event, self._common_state_enter)
            self._fsm.add_handler(state, exit_event, self._common_state_exit)

        # UNCONFIGURED state event handlers:
        self._fsm.add_handler(PlatformDriverState.UNCONFIGURED, PlatformDriverEvent.CONFIGURE, self._handler_unconfigured_configure)

        # DISCONNECTED state event handlers:
        self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.CONNECT, self._handler_disconnected_connect)
        self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_disconnected_disconnect)

        # CONNECTED state event handlers:
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_connected_disconnect)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost)

        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.PING, self._handler_connected_ping)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET, self._handler_connected_get)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.SET, self._handler_connected_set)
        self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.EXECUTE, self._handler_connected_execute)
Exemplo n.º 49
0
    def __init__(self, driver_event):
        """
        Protocol constructor.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        InstrumentProtocol.__init__(self, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = 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.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample),
                (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_start_poll),
                (ProtocolEvent.CALIBRATE, self._handler_command_start_calibrate),
                (ProtocolEvent.START_NAFION, self._handler_command_start_nafion_regen),
                (ProtocolEvent.START_ION, self._handler_command_start_ion_regen),
                (ProtocolEvent.ERROR, self._handler_error),
                (ProtocolEvent.POWEROFF, self._handler_command_poweroff),
                (ProtocolEvent.START_MANUAL, self._handler_command_start_manual),
            ],
            ProtocolState.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_SAMPLE, self._handler_autosample_acquire_sample),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.POLL: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.CLEAR, self._handler_error_clear),
            ],
            ProtocolState.CALIBRATE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.REGEN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_REGEN, self._handler_stop_regen),
                (ProtocolEvent.REGEN_COMPLETE, self._handler_regen_complete),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_direct_access_exit),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
            ],
            ProtocolState.MANUAL_OVERRIDE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_MANUAL, self._handler_manual_override_stop),
                (ProtocolEvent.GET_SLAVE_STATES, self._handler_manual_get_slave_states),
            ],
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._slave_protocols = {}
        self.initialize_scheduler()
Exemplo n.º 50
0
    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_AUTOSAMPLE,
                                       self._handler_command_start_autosample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.ACQUIRE_SAMPLE,
                                       self._handler_command_acquire_sample)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.GET, self._handler_command_get)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.SET, self._handler_command_set)
        self._protocol_fsm.add_handler(ProtocolState.COMMAND, ProtocolEvent.START_DIRECT,
                                       self._handler_command_start_direct)

        self._protocol_fsm.add_handler(ProtocolState.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.SCHEDULE_ACQUIRE_SAMPLE,
                                       self._handler_command_acquire_sample)
        self._protocol_fsm.add_handler(ProtocolState.AUTOSAMPLE, ProtocolEvent.STOP_AUTOSAMPLE,
                                       self._handler_autosample_stop_autosample)

        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)

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_driver_dict()
        self._build_command_dict()
        self._build_param_dict()

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

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

        # commands sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._chunker = StringChunker(THSPHProtocol.sieve_function)

        # Set Get Sample Command and Communication Test Command for Series A as default
        self._get_sample_cmd = self.GET_SAMPLE_SERIES_A

        self._direct_commands['Newline'] = self._newline
        self._direct_commands['Test A'] = 'aP*' + self._newline
        self._direct_commands['Test B'] = 'bP*' + self._newline
        self._direct_commands['Test C'] = 'cP*' + self._newline
        self._direct_commands['Sample A'] = self.GET_SAMPLE_SERIES_A + self._newline
        self._direct_commands['Sample B'] = self.GET_SAMPLE_SERIES_B + self._newline
        self._direct_commands['Sample C'] = self.GET_SAMPLE_SERIES_C + self._newline
Exemplo n.º 51
0
    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
Exemplo n.º 52
0
    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.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_command_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT, self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_TURBO, self._handler_command_start_turbo),
            ],
            ProtocolState.SPINNING_UP: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.AT_SPEED, self._handler_spinning_up_at_speed),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.AT_SPEED: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOP_TURBO, self._handler_stop_turbo),
                (ProtocolEvent.CLEAR, self._handler_clear),
                (ProtocolEvent.GET, self._handler_command_get),
            ],
            ProtocolState.SPINNING_DOWN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_STATUS, self._handler_acquire_status),
                (ProtocolEvent.STOPPED, self._handler_spinning_down_stopped),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct),
            ],
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

        # Add build and response handlers for device commands.
        for command in InstrumentCommand.list():
            self._add_build_handler(command, self._generic_build_handler)
            self._add_response_handler(command, self._generic_response_handler)

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._chunker = StringChunker(Protocol.sieve_function)
        self._max_current_count = 0
        self.initialize_scheduler()
class TestUnitCommandInstrumentProtocol(MiUnitTestCase):
    """
    Test cases for instrument protocol class. Functions in this class provide
    instrument protocol unit tests and provide a tutorial on use of
    the protocol interface.
    """

    class TestState(BaseEnum):
        """
        Protocol states for SBE37. Cherry picked from DriverProtocolState
        enum.
        """
        TEST = "TEST"

    class TestEvent(BaseEnum):
        """
        Protocol events for SBE37. Cherry picked from DriverEvent enum.
        """
        ENTER = "ENTER"
        EXIT = "EXIT"
        TEST = "TEST"
        
    def setUp(self):
        """
        """
        self.prompts = [">"]
        self.newline = "\n"
        self.callback_result = None
        self._trigger_count = 0
        self._events = []

        self.protocol = CommandResponseInstrumentProtocol(self.prompts,
                                                          self.newline,
                                                          self.event_callback)
                
        self.protocol_fsm = ThreadSafeFSM(self.TestState, self.TestEvent,
                            self.TestEvent.ENTER, self.TestEvent.EXIT)

        self.protocol_fsm.add_handler(self.TestState.TEST, self.TestEvent.TEST, lambda x : x)

        self.protocol._add_build_handler(self.TestEvent.TEST, self._build_simple_command)
        self.protocol._add_response_handler(self.TestEvent.TEST, self._parse_test_response)
        self.protocol._connection = Mock()
        self.protocol._connection.send = lambda x : self.protocol.add_to_buffer("%s >->" % x)
        self.protocol.get_current_state = Mock(return_value=self.TestState.TEST)
        self.protocol._send_wakeup = lambda: self.protocol.add_to_buffer("wakeup response >->")
        
    def _build_simple_command(self, cmd):
        return "cmd...do it!"

    def _parse_test_response(self, resp, prompt):
        return "c=%s p=%s" % (resp, prompt)
    
    def event_callback(self, event, value=None):
        log.debug("Test event callback: %s" % event)
        self._events.append(event)
        self._trigger_count += 1

    def test_cmd_response(self):
        """
        Test getting a response from a command supplied with prompts and regexes.
        """
        regex1 = re.compile(r'.*(do it).*')
        regex2 = re.compile(r'foobar')
        regex3 = re.compile(r'.*(do) (it).*')
        regex4 = re.compile(r'.*do it.*')
                        
        # Normal case
        result = self.protocol._do_cmd_resp(self.TestEvent.TEST)
        self.assertEqual(result, self._parse_test_response(self._build_simple_command(None)+" >", ">"))
        
        # expected prompt cases
        result = self.protocol._do_cmd_resp(self.TestEvent.TEST, expected_prompt=">")
        self.assertEqual(result, self._parse_test_response(self._build_simple_command(None)+" >", ">"))
        
        result = self.protocol._do_cmd_resp(self.TestEvent.TEST, expected_prompt=">-")
        self.assertEqual(result, self._parse_test_response(self._build_simple_command(None)+" >-", ">-"))

        # Should time out looking for a bad prompt
        self.assertRaises(InstrumentTimeoutException,
                          self.protocol._do_cmd_resp,
                          self.TestEvent.TEST, expected_prompt="-->", timeout=5)

        # regex cases
        result = self.protocol._do_cmd_resp(self.TestEvent.TEST, response_regex=regex1)
        self.assertEqual(result, self._parse_test_response("do it", ""))
        result = self.protocol._do_cmd_resp(self.TestEvent.TEST, response_regex=regex3)
        self.assertEqual(result, self._parse_test_response("doit", ""))
        result = self.protocol._do_cmd_resp(self.TestEvent.TEST, response_regex=regex4)
        self.assertEqual(result, self._parse_test_response("", ""))

        # Should time out looking for a bad regex
        self.assertRaises(InstrumentTimeoutException,
                          self.protocol._do_cmd_resp,
                          self.TestEvent.TEST, response_regex=regex2)
                          
        # combo case
        self.assertRaises(InstrumentProtocolException,
                          self.protocol._do_cmd_resp,
                          self.TestEvent.TEST, expected_prompt=">", response_regex=regex1)
class TestUnitCommandInstrumentProtocol(MiUnitTestCase):
    """
    Test cases for instrument protocol class. Functions in this class provide
    instrument protocol unit tests and provide a tutorial on use of
    the protocol interface.
    """
    class TestState(BaseEnum):
        """
        Protocol states for SBE37. Cherry picked from DriverProtocolState
        enum.
        """
        TEST = "TEST"

    class TestEvent(BaseEnum):
        """
        Protocol events for SBE37. Cherry picked from DriverEvent enum.
        """
        ENTER = "ENTER"
        EXIT = "EXIT"
        TEST = "TEST"

    def setUp(self):
        """
        """
        self.prompts = [">"]
        self.newline = "\n"
        self.callback_result = None
        self._trigger_count = 0
        self._events = []

        self.protocol = CommandResponseInstrumentProtocol(
            self.prompts, self.newline, self.event_callback)

        self.protocol_fsm = ThreadSafeFSM(self.TestState, self.TestEvent,
                                          self.TestEvent.ENTER,
                                          self.TestEvent.EXIT)

        self.protocol_fsm.add_handler(self.TestState.TEST, self.TestEvent.TEST,
                                      lambda x: x)

        self.protocol._add_build_handler(self.TestEvent.TEST,
                                         self._build_simple_command)
        self.protocol._add_response_handler(self.TestEvent.TEST,
                                            self._parse_test_response)
        self.protocol._connection = Mock()
        self.protocol._connection.send = lambda x: self.protocol.add_to_buffer(
            "%s >->" % x)
        self.protocol.get_current_state = Mock(
            return_value=self.TestState.TEST)
        self.protocol._send_wakeup = lambda: self.protocol.add_to_buffer(
            "wakeup response >->")
        self.protocol._wakeup = functools.partial(self.protocol._wakeup,
                                                  delay=0)

    def _build_simple_command(self, cmd):
        return "cmd...do it!"

    def _parse_test_response(self, resp, prompt):
        return "c=%s p=%s" % (resp, prompt)

    def event_callback(self, event, value=None):
        log.debug("Test event callback: %s" % event)
        self._events.append(event)
        self._trigger_count += 1

    def test_cmd_response(self):
        """
        Test getting a response from a command supplied with prompts and regexes.
        """
        regex1 = re.compile(r'.*(do it).*')
        regex2 = re.compile(r'foobar')
        regex3 = re.compile(r'.*(do) (it).*')
        regex4 = re.compile(r'.*do it.*')

        # Normal case
        result = self.protocol._do_cmd_resp(self.TestEvent.TEST)
        self.assertEqual(
            result,
            self._parse_test_response(
                self._build_simple_command(None) + " >", ">"))

        # expected prompt cases
        result = self.protocol._do_cmd_resp(self.TestEvent.TEST,
                                            expected_prompt=">")
        self.assertEqual(
            result,
            self._parse_test_response(
                self._build_simple_command(None) + " >", ">"))

        result = self.protocol._do_cmd_resp(self.TestEvent.TEST,
                                            expected_prompt=">-")
        self.assertEqual(
            result,
            self._parse_test_response(
                self._build_simple_command(None) + " >-", ">-"))

        # Should time out looking for a bad prompt
        self.assertRaises(InstrumentTimeoutException,
                          self.protocol._do_cmd_resp,
                          self.TestEvent.TEST,
                          expected_prompt="-->",
                          timeout=.1)

        # regex cases
        result = self.protocol._do_cmd_resp(self.TestEvent.TEST,
                                            response_regex=regex1)
        self.assertEqual(result, self._parse_test_response("do it", ""))
        result = self.protocol._do_cmd_resp(self.TestEvent.TEST,
                                            response_regex=regex3)
        self.assertEqual(result, self._parse_test_response("doit", ""))
        result = self.protocol._do_cmd_resp(self.TestEvent.TEST,
                                            response_regex=regex4)
        self.assertEqual(result, self._parse_test_response("", ""))

        # Should time out looking for a bad regex
        self.assertRaises(InstrumentTimeoutException,
                          self.protocol._do_cmd_resp,
                          self.TestEvent.TEST,
                          response_regex=regex2,
                          timeout=.1)

        # combo case
        self.assertRaises(InstrumentProtocolException,
                          self.protocol._do_cmd_resp,
                          self.TestEvent.TEST,
                          expected_prompt=">",
                          response_regex=regex1)
Exemplo n.º 55
0
class Protocol(InstrumentProtocol):
    """
    Instrument protocol class
    Subclasses CommandResponseInstrumentProtocol
    """
    __metaclass__ = META_LOGGER

    def __init__(self, driver_event):
        """
        Protocol constructor.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        InstrumentProtocol.__init__(self, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = 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.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT,
                 self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_AUTOSAMPLE,
                 self._handler_command_start_autosample),
                (ProtocolEvent.ACQUIRE_SAMPLE,
                 self._handler_command_start_poll),
                (ProtocolEvent.CALIBRATE,
                 self._handler_command_start_calibrate),
                (ProtocolEvent.START_NAFION,
                 self._handler_command_start_nafion_regen),
                (ProtocolEvent.START_ION,
                 self._handler_command_start_ion_regen),
                (ProtocolEvent.ERROR, self._handler_error),
                (ProtocolEvent.POWEROFF, self._handler_command_poweroff),
                (ProtocolEvent.START_MANUAL,
                 self._handler_command_start_manual),
            ],
            ProtocolState.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_SAMPLE,
                 self._handler_autosample_acquire_sample),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.POLL: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.CLEAR, self._handler_error_clear),
            ],
            ProtocolState.CALIBRATE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.REGEN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_REGEN, self._handler_stop_regen),
                (ProtocolEvent.REGEN_COMPLETE, self._handler_regen_complete),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_direct_access_exit),
                (ProtocolEvent.STOP_DIRECT,
                 self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT,
                 self._handler_direct_access_execute_direct),
            ],
            ProtocolState.MANUAL_OVERRIDE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_MANUAL,
                 self._handler_manual_override_stop),
                (ProtocolEvent.GET_SLAVE_STATES,
                 self._handler_manual_get_slave_states),
            ],
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._slave_protocols = {}
        self.initialize_scheduler()

    def _add_manual_override_handlers(self):
        for slave in self._slave_protocols:
            for event in self._slave_protocols[slave]._cmd_dict._cmd_dict:
                self._protocol_fsm.add_handler(
                    ProtocolState.MANUAL_OVERRIDE, event,
                    self._build_override_handler(slave, event))

    def _build_override_handler(self, slave, event):
        log.debug('Building event handler for protocol: %s event: %s', slave,
                  event)

        def inner():
            return None, self._slave_protocols[slave]._protocol_fsm.on_event(
                event)

        return inner

    def register_slave_protocol(self, name, protocol):
        """
        @param name: slave protocol name
        @param protocol: slave protocol instance
        @return: None
        """
        self._slave_protocols[name] = protocol

    def _slave_protocol_event(self, event, *args, **kwargs):
        """
        Handle an event from a slave protocol.
        @param event: event to be processed
        """
        name = kwargs.get('name')
        if name is not None and name in self._slave_protocols:
            # only react to slave protocol events once we have transitioned out of unknown
            if self.get_current_state(
            ) != ProtocolState.UNKNOWN or event == DriverAsyncEvent.ERROR:
                if event == DriverAsyncEvent.STATE_CHANGE:
                    self._react()
                elif event == DriverAsyncEvent.CONFIG_CHANGE:
                    # do nothing, we handle this ourselves in set_param
                    pass
                else:
                    # pass the event up to the instrument agent
                    log.debug(
                        'Passing event up to the Instrument agent: %r %r %r',
                        event, args, kwargs)
                    self._driver_event(event, *args)

    def _build_param_dict(self):
        """
        Populate the parameter dictionary with parameters.
        For each parameter key, add match string, match lambda function,
        and value formatting function for set commands.
        """
        self._param_dict.add(
            Parameter.SAMPLE_INTERVAL,
            '',
            None,
            int,
            type=ParameterDictType.INT,
            display_name='Autosample Interval',
            description=
            'Interval between sample starts during autosample state',
            range=(7200, 86400),
            units=Units.SECOND)

    def _build_command_dict(self):
        """
        Populate the command dictionary with commands.
        """
        self._cmd_dict.add(Capability.ACQUIRE_SAMPLE,
                           display_name="Acquire Sample")
        self._cmd_dict.add(Capability.START_AUTOSAMPLE,
                           display_name="Start Autosample")
        self._cmd_dict.add(Capability.CALIBRATE,
                           display_name="Acquire Calibration Samples")
        self._cmd_dict.add(Capability.START_ION,
                           display_name="Start Ion Chamber Regeneration")
        self._cmd_dict.add(Capability.START_NAFION,
                           display_name="Start Nafion Regeneration")
        self._cmd_dict.add(Capability.STOP_REGEN,
                           display_name="Stop Current Regeneration")
        self._cmd_dict.add(Capability.STOP_AUTOSAMPLE,
                           display_name="Stop Autosample")
        self._cmd_dict.add(Capability.POWEROFF, display_name='Low Power State')
        self._cmd_dict.add(Capability.GET_SLAVE_STATES,
                           display_name='Get Slave States')
        self._cmd_dict.add(Capability.DISCOVER, display_name='Discover')

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

    def _react(self):
        """
        Determine if an action is necessary based on the states of the slave protocols.

            (MCU STATE, TURBO STATE, RGA STATE) : (TARGET, EVENT)

        The specified event will be sent to the specified target.
        """
        state = self.get_current_state()
        slave_states = self._get_slave_states()

        if MASSP_STATE_ERROR in slave_states:
            return self._error()

        if state == ProtocolState.REGEN and slave_states[
                0] == ProtocolState.COMMAND:
            self._async_raise_fsm_event(ProtocolEvent.REGEN_COMPLETE)

        # these actions are only applicable in POLL, AUTOSAMPLE or CALIBRATE states
        if state not in [
                ProtocolState.POLL, ProtocolState.AUTOSAMPLE,
                ProtocolState.CALIBRATE
        ]:
            return

        mps = mcu.ProtocolState
        tps = turbo.ProtocolState
        rps = rga.ProtocolState
        action_map = {
            # Waiting Turbo (RGA is off)
            (mps.WAITING_TURBO, tps.COMMAND, rps.COMMAND):
            (TURBO, turbo.Capability.START_TURBO),
            (mps.WAITING_TURBO, tps.AT_SPEED, rps.COMMAND):
            (MCU, mcu.Capability.START2),

            # Waiting RGA
            (mps.WAITING_RGA, tps.AT_SPEED, rps.SCAN): (MCU,
                                                        mcu.Capability.SAMPLE),
            (mps.WAITING_RGA, tps.AT_SPEED, rps.COMMAND):
            (RGA, rga.Capability.START_SCAN),
            (mps.WAITING_RGA, tps.COMMAND, rps.SCAN):
            (RGA, rga.Capability.STOP_SCAN),  # this should never happen!
            (mps.WAITING_RGA, tps.COMMAND, rps.COMMAND):
            (MCU, mcu.Capability.STANDBY),  # this should never happen!

            # Stopping
            (mps.STOPPING, tps.AT_SPEED, rps.SCAN): (RGA,
                                                     rga.Capability.STOP_SCAN),
            (mps.STOPPING, tps.AT_SPEED, rps.COMMAND):
            (TURBO, turbo.Capability.STOP_TURBO),
            (mps.STOPPING, tps.COMMAND, rps.SCAN):
            (RGA, rga.Capability.STOP_SCAN),  # this should never happen!
            (mps.STOPPING, tps.COMMAND, rps.COMMAND): (MCU,
                                                       mcu.Capability.STANDBY),
        }

        action = action_map.get(self._get_slave_states())

        if action is not None:
            if not isinstance(action, list):
                action = [action]

            # iterate through the action list, sending the events to the targets
            # if we are in POLL or CALIBRATE and we see a STANDBY event, return this driver to COMMAND.
            for target, command in action:
                if command == mcu.Capability.SAMPLE and state == ProtocolState.CALIBRATE:
                    command = mcu.Capability.CALIBRATE
                if command == mcu.Capability.STANDBY and state in [
                        ProtocolState.CALIBRATE, ProtocolState.POLL
                ]:
                    self._send_event_to_slave(target, command)
                    self._async_raise_fsm_event(ProtocolEvent.STOP)
                else:
                    self._send_event_to_slave(target, command)
        return action

    def _error(self):
        """
        Handle error state in slave protocol
        """
        state = self.get_current_state()
        slave_states = self._get_slave_states()

        # if we are not currently in the error state, make the transition
        if state != ProtocolState.ERROR:
            self._async_raise_fsm_event(ProtocolEvent.ERROR)
        mcu_state, turbo_state, rga_state = slave_states

        # before we do anything else, the RGA must be stopped.
        if rga_state not in [
                rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR
        ]:
            self._send_event_to_slave(RGA, rga.ProtocolEvent.STOP_SCAN)
        # RGA must be in COMMAND or ERROR, the TURBO must be stopped.
        elif turbo_state not in [
                turbo.ProtocolState.COMMAND, turbo.ProtocolState.SPINNING_DOWN
        ]:
            self._send_event_to_slave(TURBO, turbo.ProtocolEvent.STOP_TURBO)
        # Turbo and RGA must be in COMMAND or ERROR, stop the MCU
        elif mcu_state != mcu.ProtocolState.COMMAND:
            self._send_event_to_slave(MCU, mcu.ProtocolEvent.STANDBY)

    def _got_chunk(self, chunk):
        """
        This driver has no chunker...
        """

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        @param events: Events to be filtered
        @return: list of events which are also capabilities
        """
        return [
            x for x in events if Capability.has(x) or mcu.Capability.has(x)
            or turbo.Capability.has(x) or rga.Capability.has(x)
        ]

    def _get_slave_states(self):
        """
        Retrieve the current protocol state from each of the slave protocols and return them as a tuple.
        """
        return (
            self._slave_protocols[MCU].get_current_state(),
            self._slave_protocols[TURBO].get_current_state(),
            self._slave_protocols[RGA].get_current_state(),
        )

    def _send_event_to_all(self, event):
        """
        Send the same event to all slave protocols.
        @return: List of (name, result) for a slave protocols
        """
        return [(name, slave._protocol_fsm.on_event(event))
                for name, slave in self._slave_protocols.items()]

    def _send_event_to_slave(self, name, event):
        """
        Send an event to a specific protocol
        @param name: Name of slave protocol
        @param event: Event to be sent
        """
        slave_protocol = self._slave_protocols.get(name)
        if slave_protocol is None:
            raise InstrumentProtocolException(
                'Attempted to send event to non-existent protocol: %s' % name)
        slave_protocol._async_raise_fsm_event(event)

    def _send_massp_direct_access(self, command):
        """
        Handle a direct access command.  Driver expects direct access commands to specify the target
        using the following format:

        target:command

        It then routes the command to the appropriate slave protocol.
        @param command: Direct access command received
        """
        err_string = 'Invalid command.  Command must be in the following format: "target:command' + NEWLINE + \
                     'Valid targets are: %r' % self._slave_protocols.keys()
        try:
            target, command = command.split(DA_COMMAND_DELIMITER, 1)
            target = target.lower()
        except ValueError:
            target = None

        log.debug('_do_cmd_direct - target: %s command: %r', target, command)

        if target not in self._slave_protocols:
            self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, err_string)
        else:
            self._slave_protocols[target]._protocol_fsm.on_event(
                ProtocolEvent.EXECUTE_DIRECT, command)

    def _set_params(self, *args, **kwargs):
        """
        Set one or more parameters.  This method will determine where the parameter actually resides and
        forward it to the appropriate parameter dictionary based on name.
        @param args: arglist which must contain a parameter dictionary
        @throws InstrumentParameterException
        """
        params = args[0]

        if not isinstance(params, dict):
            raise InstrumentParameterException(
                'Attempted to set parameters with a non-dictionary argument')

        _, old_config = self._handler_command_get([Parameter.ALL])

        temp_dict = {}
        for key in params:
            split_key = key.split('_', 1)
            if len(split_key) == 1:
                raise InstrumentParameterException(
                    'Missing target in MASSP parameter: %s' % key)
            target = split_key[0]
            if target not in self._slave_protocols:
                # this is a master driver parameter, set it here
                if key in self._param_dict.get_keys():
                    log.debug("Setting value for %s to %s", key, params[key])
                    self._param_dict.set_value(key, params[key])
                else:
                    raise InstrumentParameterException(
                        'Invalid key in SET action: %s' % key)
            else:
                temp_dict.setdefault(target, {})[key] = params[key]

        # set parameters for slave protocols
        for name in temp_dict:
            if name in self._slave_protocols:
                self._slave_protocols[name]._set_params(temp_dict[name])
            else:
                # how did we get here?  This should never happen, but raise an exception if it does.
                raise InstrumentParameterException(
                    'Invalid key(s) in SET action: %r' % temp_dict[name])

        _, new_config = self._handler_command_get([Parameter.ALL])

        if not new_config == old_config:
            self._driver_event(DriverAsyncEvent.CONFIG_CHANGE)

    def set_init_params(self, config):
        """
        Set initial parameters.  Parameters are forwarded to the appropriate parameter dictionary based on name.
        @param config: Init param config to be handled
        """
        temp_dict = {}
        self._startup_config = config
        config = config.get(DriverConfigKey.PARAMETERS, {})
        for key in config:
            target, _ = key.split('_', 1)
            if target not in self._slave_protocols:
                # master driver parameter
                log.debug("Setting init value for %s to %s", key, config[key])
                self._param_dict.set_init_value(key, config[key])
            else:
                temp_dict.setdefault(target, {})[key] = config[key]

        for name in temp_dict:
            if name in self._slave_protocols:
                self._slave_protocols[name].set_init_params(
                    {DriverConfigKey.PARAMETERS: temp_dict[name]})
            else:
                # how did we get here?  This should never happen, but raise an exception if it does.
                raise InstrumentParameterException(
                    'Invalid key(s) in INIT PARAMS action: %r' %
                    temp_dict[name])

    def get_config_metadata_dict(self):
        """
        See base class for full description.  This method is overridden to retrieve the parameter
        dictionary from each slave protocol and merge them.
        @return: dictionary containing driver metadata
        """
        log.debug("Getting metadata dict from protocol...")
        return_dict = {
            ConfigMetadataKey.DRIVER: self._driver_dict.generate_dict(),
            ConfigMetadataKey.COMMANDS: self._cmd_dict.generate_dict(),
            ConfigMetadataKey.PARAMETERS: self._param_dict.generate_dict()
        }

        for protocol in self._slave_protocols.values():
            return_dict[ConfigMetadataKey.PARAMETERS].update(
                protocol._param_dict.generate_dict())
            return_dict[ConfigMetadataKey.COMMANDS].update(
                protocol._cmd_dict.generate_dict())

        return return_dict

    def get_resource_capabilities(self, current_state=True):
        """
        Overrides base class to include slave protocol parameters
        @param current_state: Boolean indicating whether we should return only the current state events
        @return: (resource_commands, resource_parameters)
        """
        res_cmds = self._protocol_fsm.get_events(current_state)
        res_cmds = self._filter_capabilities(res_cmds)
        res_params = self._param_dict.get_keys()

        for protocol in self._slave_protocols.values():
            res_params.extend(protocol._param_dict.get_keys())

        return res_cmds, res_params

    def _build_scheduler(self):
        """
        Build a scheduler for periodic status updates
        """
        job_name = ScheduledJob.ACQUIRE_SAMPLE
        config = {
            DriverConfigKey.SCHEDULER: {
                job_name: {
                    DriverSchedulerConfigKey.TRIGGER: {
                        DriverSchedulerConfigKey.TRIGGER_TYPE:
                        TriggerType.INTERVAL,
                        DriverSchedulerConfigKey.SECONDS:
                        self._param_dict.get(Parameter.SAMPLE_INTERVAL)
                    },
                }
            }
        }

        self.set_init_params(config)
        self._add_scheduler_event(ScheduledJob.ACQUIRE_SAMPLE,
                                  ProtocolEvent.ACQUIRE_SAMPLE)

    def _delete_scheduler(self):
        """
        Remove the autosample schedule.
        """
        try:
            self._remove_scheduler(ScheduledJob.ACQUIRE_SAMPLE)
        except KeyError:
            log.info('Failed to remove scheduled job for ACQUIRE_SAMPLE')

    ########################################################################
    # Generic handlers.
    ########################################################################
    def _handler_generic_enter(self, *args, **kwargs):
        """
        Generic enter handler, raise STATE CHANGE
        """
        if self.get_current_state() != ProtocolState.UNKNOWN:
            self._init_params()
        self._driver_event(DriverAsyncEvent.STATE_CHANGE)

    def _handler_generic_exit(self, *args, **kwargs):
        """
        Generic exit handler, do nothing.
        """

    def _handler_stop_generic(self, *args, **kwargs):
        """
        Generic stop method to return to COMMAND (via POLL if appropriate)
        @return next_state, (next_state, None)
        """
        next_state = ProtocolState.COMMAND
        result = []

        self._delete_scheduler()

        # check if we are in autosample AND currently taking a sample, if so, move to POLL
        # otherwise go back to COMMAND.
        if self.get_current_state() == ProtocolState.AUTOSAMPLE:
            if self._get_slave_states() != (ProtocolState.COMMAND,
                                            ProtocolState.COMMAND,
                                            ProtocolState.COMMAND):
                next_state = ProtocolState.POLL

        return next_state, (next_state, result)

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

    def _handler_unknown_discover(self, *args, **kwargs):
        """
        Discover current state
        @return next_state, (next_state, result)
        """
        next_state = ProtocolState.COMMAND
        result = self._send_event_to_all(ProtocolEvent.DISCOVER)
        log.debug('_handler_unknown_discover -- send DISCOVER to all: %r',
                  result)
        target_state = (ProtocolState.COMMAND, ProtocolState.COMMAND,
                        ProtocolState.COMMAND)
        success = False
        # wait for the slave protocols to discover
        for attempt in xrange(5):
            slave_states = self._get_slave_states()
            if slave_states == target_state:
                success = True
                break
            time.sleep(1)
        if not success:
            next_state = ProtocolState.ERROR
        return next_state, (next_state, result)

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

    def _handler_command_get(self, *args, **kwargs):
        """
        Get parameter.  Query this protocol plus all slave protocols.
        @param args: arglist which should contain a list of parameters to get
        @return None, results
        """
        params = args[0]

        if not isinstance(params, list):
            params = [params]

        temp_dict = {}
        result_dict = {}

        # request is for all parameters, send get(ALL) to each protocol then combine the results.
        if Parameter.ALL in params:
            params = [Parameter.ALL]
            _, result = self._handler_get(params, **kwargs)
            result_dict.update(result)
            for protocol in self._slave_protocols.values():
                _, result = protocol._handler_get(params, **kwargs)
                result_dict.update(result)

        # request is for specific parameters.  Determine which protocol should service each,
        # call the appropriate _handler_get and combine the results
        else:
            for key in params:
                log.debug('about to split: %s', key)
                target, _ = key.split('_', 1)
                temp_dict.setdefault(target, []).append(key)
            for key in temp_dict:
                if key == MASTER:
                    _, result = self._handler_get(params, **kwargs)
                else:
                    if key in self._slave_protocols:
                        _, result = self._slave_protocols[key]._handler_get(
                            params, **kwargs)
                    else:
                        raise InstrumentParameterException(
                            'Invalid key(s) in GET action: %r' %
                            temp_dict[key])
                result_dict.update(result)

        return None, result_dict

    def _handler_command_set(self, *args, **kwargs):
        """
        Set parameter, just pass through to _set_params, which knows how to set the params
        in the slave protocols.
        """
        next_state = None
        result = []
        self._set_params(*args, **kwargs)
        return next_state, (next_state, result)

    def _handler_command_start_direct(self):
        """
        Start direct access
        """
        next_state = ProtocolState.DIRECT_ACCESS
        result = []
        return next_state, (next_state, result)

    def _handler_command_start_autosample(self):
        """
        Move my FSM to autosample and start the sample sequence by sending START1 to the MCU.
        Create the scheduler to automatically start the next sample sequence
        """
        next_state = ProtocolState.AUTOSAMPLE
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        self._build_scheduler()
        return next_state, (next_state, result)

    def _handler_command_start_poll(self):
        """
        Move my FSM to poll and start the sample sequence by sending START1 to the MCU
        """
        next_state = ProtocolState.POLL
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return next_state, (next_state, result)

    def _handler_command_start_calibrate(self):
        """
        Move my FSM to calibrate and start the calibrate sequence by sending START1 to the MCU
        """
        next_state = ProtocolState.CALIBRATE
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.START1)
        return next_state, (next_state, result)

    def _handler_command_start_nafion_regen(self):
        """
        Move my FSM to NAFION_REGEN and send NAFION_REGEN to the MCU
        """
        next_state = ProtocolState.REGEN
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.NAFREG)
        return next_state, (next_state, result)

    def _handler_command_start_ion_regen(self):
        """
        Move my FSM to ION_REGEN and send ION_REGEN to the MCU
        """
        next_state = ProtocolState.REGEN
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.IONREG)
        return next_state, (next_state, result)

    def _handler_command_poweroff(self):
        """
        Send POWEROFF to the MCU
        """
        next_state = None
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.POWEROFF)
        return next_state, (next_state, result)

    def _handler_command_start_manual(self):
        """
        Move FSM to MANUAL OVERRIDE state
        """
        next_state = ProtocolState.MANUAL_OVERRIDE
        result = []
        return next_state, (next_state, result)

    ########################################################################
    # Error handlers.
    ########################################################################

    def _handler_error(self):
        next_state = ProtocolState.ERROR
        result = []
        return next_state, (next_state, result)

    def _handler_error_clear(self):
        """
        Send the CLEAR event to any slave protocol in the error state and return this driver to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        for protocol in self._slave_protocols:
            state = protocol.get_current_state()
            if state == MASSP_STATE_ERROR:
                # do this synchronously, to allow each slave protocol to complete the CLEAR action
                # before transitioning states.
                protocol._protocol_fsm.on_event(ProtocolEvent.CLEAR)
        return next_state, (next_state, result)

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

    def _handler_autosample_acquire_sample(self):
        """
        Fire off a sample sequence while in the autosample state.
        @throws InstrumentProtocolException
        """
        next_state = None
        result = []
        slave_states = self._get_slave_states()
        # verify the MCU is not already in a sequence
        if slave_states[0] == ProtocolState.COMMAND:
            result = self._send_event_to_slave(MCU, mcu.Capability.START1)
        else:
            raise InstrumentProtocolException(
                "Attempted to acquire sample while sampling")
        return next_state, (next_state, result)

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

    def _handler_direct_access_enter(self, *args, **kwargs):
        """
        Enter direct access state.  Forward to all slave protocols.
        """
        self._send_event_to_all(ProtocolEvent.START_DIRECT)

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

    def _handler_direct_access_exit(self, *args, **kwargs):
        """
        Exit direct access state.  Check slave protocol states and verify they all
        return to COMMAND, otherwise raise InstrumentProtocolException.
        @throws InstrumentProtocolException
        """
        for attempt in range(DA_EXIT_MAX_RETRIES):
            slave_states = self._get_slave_states()
            if ProtocolState.DIRECT_ACCESS in slave_states:
                log.error(
                    'Slave protocol failed to return to command, attempt %d',
                    attempt)
                time.sleep(1)
            else:
                return

        raise InstrumentProtocolException(
            'Slave protocol never returned to command from DA.')

    def _handler_direct_access_execute_direct(self, data):
        """
        Execute a direct access command.  For MASSP, this means passing the actual command to the
        correct slave protocol.  This is handled by _send_massp_direct_access.
        """
        self._send_massp_direct_access(data)

        # add sent command to list for 'echo' filtering in callback
        self._sent_cmds.append(data)

        return None, (None, [])

    def _handler_direct_access_stop_direct(self):
        next_state = ProtocolState.COMMAND
        result = []
        self._send_event_to_all(ProtocolEvent.STOP_DIRECT)
        return next_state, (next_state, result)

    ########################################################################
    # Regen handlers.
    ########################################################################

    def _handler_stop_regen(self):
        """
        Abort the current regeneration sequence, return to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._send_event_to_slave(MCU, mcu.Capability.STANDBY)
        return next_state, (next_state, result)

    def _handler_regen_complete(self):
        """
        Regeneration sequence is complete, return to COMMAND
        """
        next_state = ProtocolState.COMMAND
        result = []
        self._async_agent_state_change(ResourceAgentState.COMMAND)
        return next_state, (next_state, result)

    def _handler_manual_override_stop(self):
        """
        Exit manual override.  Attempt to bring the slave drivers back to COMMAND.
        """
        next_state = ProtocolState.COMMAND
        result = []
        mcu_state, turbo_state, rga_state = self._get_slave_states()
        if rga_state == rga.ProtocolState.SCAN:
            self._slave_protocols[RGA]._protocol_fsm.on_event(
                rga.Capability.STOP_SCAN)
        if turbo_state == turbo.ProtocolState.AT_SPEED:
            self._slave_protocols[TURBO]._protocol_fsm.on_event(
                turbo.Capability.STOP_TURBO)
        while rga_state not in [rga.ProtocolState.COMMAND, rga.ProtocolState.ERROR] or \
                turbo_state not in [turbo.ProtocolState.COMMAND, turbo.ProtocolState.ERROR]:
            time.sleep(.1)
            mcu_state, turbo_state, rga_state = self._get_slave_states()
        if mcu_state != mcu.ProtocolState.COMMAND:
            self._slave_protocols[MCU]._protocol_fsm.on_event(
                mcu.Capability.STANDBY)

        return next_state, (next_state, result)

    def _handler_manual_get_slave_states(self):
        """
        Get the slave states and return them to the user
        @return: next_state, (next_state, result)
        """
        mcu_state, turbo_state, rga_state = self._get_slave_states()
        return None, (None, {
            MCU: mcu_state,
            RGA: rga_state,
            TURBO: turbo_state
        })
Exemplo n.º 56
0
class Protocol(Pco2wProtocol):
    """
    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.
        """

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

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

        # Build protocol state machine.

        self._protocol_fsm.add_handler(
            ProtocolState.COMMAND, ProtocolEvent.RUN_EXTERNAL_PUMP,
            self._handler_command_run_external_pump)

        # this state would be entered whenever a RUN_EXTERNAL_PUMP event
        # occurred while in the COMMAND state
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.ENTER,
            self._execution_state_enter)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.EXIT,
            self._execution_state_exit)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.EXECUTE,
            self._handler_run_external_pump_execute)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.SUCCESS,
            self._execution_success_to_command_state)
        self._protocol_fsm.add_handler(
            ProtocolState.RUN_EXTERNAL_PUMP, ProtocolEvent.TIMEOUT,
            self._execution_timeout_to_command_state)

        # Add build handlers for device commands.
        ### primarily defined in base class
        self._add_build_handler(InstrumentCommand.PCO2WB_ACQUIRE_SAMPLE_DEV1, self._build_simple_command)
        # Add response handlers for device commands.
        ### primarily defined in base class
        self._add_response_handler(InstrumentCommand.PCO2WB_ACQUIRE_SAMPLE_DEV1, self._parse_response_sample_dev1)

        # Add sample handlers

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

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

        self._engineering_parameters.append(Parameter.EXTERNAL_PUMP_DELAY)

    def _filter_capabilities(self, events):
        """
        Return a list of currently available capabilities.
        """

        return [x for x in events if Capability.has(x)]

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

    def _handler_command_run_external_pump(self):
        """
        Run external pump
        """
        next_state = ProtocolState.RUN_EXTERNAL_PUMP
        result = []

        return next_state, (next_state, result)

    ########################################################################
    # Run external pump handlers.
    ########################################################################

    def _handler_run_external_pump_execute(self, *args, **kwargs):
        """
        Execute run external pump (dev1) command
        """
        next_state = None
        result = []

        try:
            self._take_dev1_sample()

            log.debug('Protocol._handler_run_external_pump_execute(): SUCCESS')

            self._async_raise_fsm_event(ProtocolEvent.SUCCESS)

        except InstrumentTimeoutException:

            log.error('Protocol._handler_run_external_pump_execute(): TIMEOUT')

            self._async_raise_fsm_event(ProtocolEvent.TIMEOUT)

        return next_state, (next_state, result)

    ########################################################################
    # Response handlers.
    ########################################################################

    def _parse_response_sample_dev1(self, response, prompt):
        """
        Parse response to take dev1 sample from instrument
        """

    def _take_dev1_sample(self):
        """
        Run external pump and wait for dev1 sample
        """
        log.debug('Protocol._take_dev1_sample(): Take Dev1 Sample START')

        start_time = time.time()

        dev1_timeout = self._param_dict.get(Parameter.EXTERNAL_PUMP_SETTINGS)

        log.debug('Protocol._take_dev1_sample(): Dev1 Timeout = %s', dev1_timeout)

        ## An exception is raised if timeout is hit.
        self._do_cmd_resp(InstrumentCommand.PCO2WB_ACQUIRE_SAMPLE_DEV1,
                          timeout=dev1_timeout,
                          response_regex=PCO2WB_DEV1_SAMPLE_REGEX_MATCHER)

        sample_time = time.time() - start_time

        log.debug('Protocol._take_dev1_sample(): Dev1 Sample took %s to FINISH', sample_time)

    def _pre_sample_processing(self):
        """
        Run external pump and wait for equilibrium
        """

        self._take_dev1_sample()

        external_pump_delay = self._param_dict.get(Parameter.EXTERNAL_PUMP_DELAY)

        log.debug('Protocol._pre_sample_processing(): Delaying for %d seconds', external_pump_delay)

        time.sleep(external_pump_delay)

    @staticmethod
    def sieve_function(raw_data):
        """
        The method that splits samples
        :param raw_data: data to filter
        """

        return_list = []

        sieve_matchers = [SAMI_REGULAR_STATUS_REGEX_MATCHER,
                          PCO2W_SAMPLE_REGEX_MATCHER,
                          PCO2WB_DEV1_SAMPLE_REGEX_MATCHER,
                          PCO2WB_CONFIGURATION_REGEX_MATCHER,
                          SAMI_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.
        """

        if any([
                self._extract_sample(SamiRegularStatusDataParticle, SAMI_REGULAR_STATUS_REGEX_MATCHER,
                                     chunk, timestamp),
                self._extract_sample(Pco2wConfigurationDataParticle, PCO2WB_CONFIGURATION_REGEX_MATCHER,
                                     chunk, timestamp)]):
            return

        dev1_sample = self._extract_sample(Pco2wbDev1SampleDataParticle, PCO2WB_DEV1_SAMPLE_REGEX_MATCHER, chunk,
                                           timestamp)
        sami_sample = self._extract_sample(Pco2wSamiSampleDataParticle, PCO2W_SAMPLE_REGEX_MATCHER_NORMAL, chunk,
                                           timestamp)
        if sami_sample is None:
            sami_sample = self._extract_sample(Pco2wSamiSampleCalibrationDataParticle, PCO2W_SAMPLE_REGEX_MATCHER_CAL,
                                               chunk, timestamp)

        log.debug('Protocol._got_chunk(): get_current_state() == %s', self.get_current_state())

        if sami_sample:
            self._verify_checksum(chunk, PCO2W_SAMPLE_REGEX_MATCHER)
        elif dev1_sample:
            self._verify_checksum(chunk, PCO2WB_DEV1_SAMPLE_REGEX_MATCHER)

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

    def _build_command_dict(self):
        """
        Populate the command dictionary with command.
        """

        Pco2wProtocol._build_command_dict(self)

        self._cmd_dict.add(Capability.RUN_EXTERNAL_PUMP, display_name="Run External Pump")

    def _build_param_dict(self):
        """
        For each parameter key, add match string, match lambda function,
        and value formatting function for set commands.
        """

        Pco2wProtocol._build_param_dict(self)

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

        configuration_string_regex = self._get_configuration_string_regex()

        self._param_dict.add(Parameter.MODE_BITS, configuration_string_regex,
                             lambda match: int(match.group(4), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=True,
                             direct_access=True,
                             default_value=0x02,
                             visibility=ParameterDictVisibility.IMMUTABLE,
                             display_name='Mode Bits',
                             description='Switch bits for sample scheduling.')

        self._param_dict.add(Parameter.DEVICE1_SAMPLE_INTERVAL, configuration_string_regex,
                             lambda match: int(match.group(8), 16),
                             lambda x: self._int_to_hexstring(x, 6),
                             type=ParameterDictType.INT,
                             startup_param=True,
                             direct_access=True,
                             default_value=0x000E10,
                             visibility=ParameterDictVisibility.IMMUTABLE,
                             display_name='Device 1 Sample Interval',
                             description='',
                             units=Units.SECOND)

        self._param_dict.add(Parameter.DEVICE1_DRIVER_VERSION, configuration_string_regex,
                             lambda match: int(match.group(9), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=True,
                             direct_access=True,
                             default_value=0x01,
                             visibility=ParameterDictVisibility.IMMUTABLE,
                             display_name='Device 1 Driver Version',
                             description='')

        self._param_dict.add(Parameter.DEVICE1_PARAMS_POINTER, configuration_string_regex,
                             lambda match: int(match.group(10), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=True,
                             direct_access=True,
                             default_value=0x0B,
                             visibility=ParameterDictVisibility.IMMUTABLE,
                             display_name='Device 1 Parameter Pointer',
                             description='Pointer to device 1 parameters (offset from position 76).')

        self._param_dict.add(Parameter.EXTERNAL_PUMP_SETTINGS, configuration_string_regex,
                             lambda match: int(match.group(30), 16),
                             lambda x: self._int_to_hexstring(x, 2),
                             type=ParameterDictType.INT,
                             startup_param=True,
                             direct_access=True,
                             default_value=0x1E,
                             range=(0, 0xFF),
                             visibility=ParameterDictVisibility.READ_WRITE,
                             display_name='External Pump Settings',
                             description='Timeout for taking a device 1 sample.',
                             units=Units.SECOND)

        ## Engineering parameter to set delay after running external pump to take a sample, set as startup parameter
        ##   because it is configurable by the user and should be reapplied on application of startup parameters.
        self._param_dict.add(Parameter.EXTERNAL_PUMP_DELAY, r'External pump delay = ([0-9]+)',
                             lambda match: match.group(1),
                             lambda x: int(x),
                             type=ParameterDictType.INT,
                             startup_param=True,
                             direct_access=False,
                             default_value=360,
                             range=(0, 86400),  # up to 1 day
                             visibility=ParameterDictVisibility.READ_WRITE,
                             display_name='External Pump Delay',
                             description='Time to wait before taking a sample after running the external pump.',
                             units=Units.SECOND)

    ########################################################################
    # Overridden base class methods
    ########################################################################

    def _get_specific_configuration_string_parameters(self):
        """
        Overridden by device specific subclasses.
        """

        # An ordered list of parameters, can not use unordered dict
        # PCO2W driver extends the base class (SamiParameter)
        parameter_list = [Parameter.START_TIME_FROM_LAUNCH,
                          Parameter.STOP_TIME_FROM_START,
                          Parameter.MODE_BITS,
                          Parameter.SAMI_SAMPLE_INTERVAL,
                          Parameter.SAMI_DRIVER_VERSION,
                          Parameter.SAMI_PARAMS_POINTER,
                          Parameter.DEVICE1_SAMPLE_INTERVAL,
                          Parameter.DEVICE1_DRIVER_VERSION,
                          Parameter.DEVICE1_PARAMS_POINTER,
                          Parameter.DEVICE2_SAMPLE_INTERVAL,
                          Parameter.DEVICE2_DRIVER_VERSION,
                          Parameter.DEVICE2_PARAMS_POINTER,
                          Parameter.DEVICE3_SAMPLE_INTERVAL,
                          Parameter.DEVICE3_DRIVER_VERSION,
                          Parameter.DEVICE3_PARAMS_POINTER,
                          Parameter.PRESTART_SAMPLE_INTERVAL,
                          Parameter.PRESTART_DRIVER_VERSION,
                          Parameter.PRESTART_PARAMS_POINTER,
                          Parameter.GLOBAL_CONFIGURATION,
                          Parameter.PUMP_PULSE,
                          Parameter.PUMP_DURATION,
                          Parameter.SAMPLES_PER_MEASUREMENT,
                          Parameter.CYCLES_BETWEEN_BLANKS,
                          Parameter.NUMBER_REAGENT_CYCLES,
                          Parameter.NUMBER_BLANK_CYCLES,
                          Parameter.FLUSH_PUMP_INTERVAL,
                          Parameter.PUMP_SETTINGS,
                          Parameter.NUMBER_EXTRA_PUMP_CYCLES,
                          Parameter.EXTERNAL_PUMP_SETTINGS]

        return parameter_list

    def _get_configuration_string_regex(self):
        """
        Get configuration string regex.
        @retval configuration string regex.
        """
        return PCO2WB_CONFIGURATION_REGEX

    def _get_configuration_string_regex_matcher(self):
        """
        Get config string regex matcher.
        @retval configuration string regex matcher
        """
        return PCO2WB_CONFIGURATION_REGEX_MATCHER
Exemplo n.º 57
0
    def __init__(self, driver_event):
        """
        Protocol constructor.
        @param driver_event Driver process event callback.
        """
        # Construct protocol superclass.
        InstrumentProtocol.__init__(self, driver_event)

        # Build protocol state machine.
        self._protocol_fsm = 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.COMMAND: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.START_DIRECT,
                 self._handler_command_start_direct),
                (ProtocolEvent.GET, self._handler_command_get),
                (ProtocolEvent.SET, self._handler_command_set),
                (ProtocolEvent.START_AUTOSAMPLE,
                 self._handler_command_start_autosample),
                (ProtocolEvent.ACQUIRE_SAMPLE,
                 self._handler_command_start_poll),
                (ProtocolEvent.CALIBRATE,
                 self._handler_command_start_calibrate),
                (ProtocolEvent.START_NAFION,
                 self._handler_command_start_nafion_regen),
                (ProtocolEvent.START_ION,
                 self._handler_command_start_ion_regen),
                (ProtocolEvent.ERROR, self._handler_error),
                (ProtocolEvent.POWEROFF, self._handler_command_poweroff),
                (ProtocolEvent.START_MANUAL,
                 self._handler_command_start_manual),
            ],
            ProtocolState.AUTOSAMPLE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.ACQUIRE_SAMPLE,
                 self._handler_autosample_acquire_sample),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.STOP_AUTOSAMPLE, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.POLL: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.ERROR: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.CLEAR, self._handler_error_clear),
            ],
            ProtocolState.CALIBRATE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP, self._handler_stop_generic),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.REGEN: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_REGEN, self._handler_stop_regen),
                (ProtocolEvent.REGEN_COMPLETE, self._handler_regen_complete),
                (ProtocolEvent.ERROR, self._handler_error),
            ],
            ProtocolState.DIRECT_ACCESS: [
                (ProtocolEvent.ENTER, self._handler_direct_access_enter),
                (ProtocolEvent.EXIT, self._handler_direct_access_exit),
                (ProtocolEvent.STOP_DIRECT,
                 self._handler_direct_access_stop_direct),
                (ProtocolEvent.EXECUTE_DIRECT,
                 self._handler_direct_access_execute_direct),
            ],
            ProtocolState.MANUAL_OVERRIDE: [
                (ProtocolEvent.ENTER, self._handler_generic_enter),
                (ProtocolEvent.EXIT, self._handler_generic_exit),
                (ProtocolEvent.STOP_MANUAL,
                 self._handler_manual_override_stop),
                (ProtocolEvent.GET_SLAVE_STATES,
                 self._handler_manual_get_slave_states),
            ],
        }

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

        # Construct the parameter dictionary containing device parameters,
        # current parameter values, and set formatting functions.
        self._build_param_dict()
        self._build_command_dict()
        self._build_driver_dict()

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

        # commands sent sent to device to be filtered in responses for telnet DA
        self._sent_cmds = []

        self._slave_protocols = {}
        self.initialize_scheduler()