class SingleConnectionInstrumentDriver(InstrumentDriver): """ Base class for instrument drivers with a single device connection. Provides connenction state logic for single connection drivers. This is the base class for the majority of driver implementation classes. """ def __init__(self, event_callback): """ Constructor for singly connected instrument drivers. @param event_callback Callback to the driver process to send asynchronous driver events back to the agent. """ InstrumentDriver.__init__(self, event_callback) # The only and only instrument connection. # Exists in the connected state. self._connection = None # The one and only instrument protocol. self._protocol = None # Build connection state machine. self._connection_fsm = InstrumentFSM(DriverConnectionState, DriverEvent, DriverEvent.ENTER, DriverEvent.EXIT) # Add handlers for all events. self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.ENTER, self._handler_unconfigured_enter) self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.EXIT, self._handler_unconfigured_exit) self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.INITIALIZE, self._handler_unconfigured_initialize) self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.CONFIGURE, self._handler_unconfigured_configure) self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.ENTER, self._handler_disconnected_enter) self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.EXIT, self._handler_disconnected_exit) self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.INITIALIZE, self._handler_disconnected_initialize) self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.CONFIGURE, self._handler_disconnected_configure) self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.CONNECT, self._handler_disconnected_connect) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.ENTER, self._handler_connected_enter) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.EXIT, self._handler_connected_exit) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.DISCONNECT, self._handler_connected_disconnect) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.DISCOVER, self._handler_connected_protocol_event) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.GET, self._handler_connected_protocol_event) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.SET, self._handler_connected_protocol_event) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.ACQUIRE_SAMPLE, self._handler_connected_protocol_event) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.START_AUTOSAMPLE, self._handler_connected_protocol_event) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.STOP_AUTOSAMPLE, self._handler_connected_protocol_event) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.TEST, self._handler_connected_protocol_event) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.CALIBRATE, self._handler_connected_protocol_event) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.EXECUTE_DIRECT, self._handler_connected_protocol_event) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.START_DIRECT, self._handler_connected_protocol_event) self._connection_fsm.add_handler( DriverConnectionState.CONNECTED, DriverEvent.STOP_DIRECT, self._handler_connected_protocol_event) # Start state machine. self._connection_fsm.start(DriverConnectionState.UNCONFIGURED) ############################################################# # Device connection interface. ############################################################# def initialize(self, *args, **kwargs): """ Initialize driver connection, bringing communications parameters into unconfigured state (no connection object). @raises InstrumentStateException if command not allowed in current state """ # Forward event and argument to the connection FSM. return self._connection_fsm.on_event(DriverEvent.INITIALIZE, *args, **kwargs) def configure(self, *args, **kwargs): """ Configure the driver for communications with the device via port agent / logger (valid but unconnected connection object). @param arg[0] comms config dict. @raises InstrumentStateException if command not allowed in current state @throws InstrumentParameterException if missing comms or invalid config dict. """ # Forward event and argument to the connection FSM. return self._connection_fsm.on_event(DriverEvent.CONFIGURE, *args, **kwargs) def connect(self, *args, **kwargs): """ Establish communications with the device via port agent / logger (connected connection object). @raises InstrumentStateException if command not allowed in current state @throws InstrumentConnectionException if the connection failed. """ # Forward event and argument to the connection FSM. return self._connection_fsm.on_event(DriverEvent.CONNECT, *args, **kwargs) def disconnect(self, *args, **kwargs): """ Disconnect from device via port agent / logger. @raises InstrumentStateException if command not allowed in current state """ # Forward event and argument to the connection FSM. return self._connection_fsm.on_event(DriverEvent.DISCONNECT, *args, **kwargs) ############################################################# # Commande and control interface. ############################################################# def discover(self, *args, **kwargs): """ Determine initial state upon establishing communications. @param timeout=timeout Optional command timeout. @retval Current device state. @raises InstrumentTimeoutException if could not wake device. @raises InstrumentStateException if command not allowed in current state or if device state not recognized. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.DISCOVER, DriverEvent.DISCOVER, *args, **kwargs) def get(self, *args, **kwargs): """ Retrieve device parameters. @param args[0] DriverParameter.ALL or a list of parameters to retrive. @retval parameter : value dict. @raises InstrumentParameterException if missing or invalid get parameters. @raises InstrumentStateException if command not allowed in current state @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.GET, DriverEvent.GET, *args, **kwargs) def set(self, *args, **kwargs): """ Set device parameters. @param args[0] parameter : value dict of parameters to set. @param timeout=timeout Optional command timeout. @raises InstrumentParameterException if missing or invalid set parameters. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if set command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.SET, DriverEvent.SET, *args, **kwargs) def execute_acquire_sample(self, *args, **kwargs): """ Poll for a sample. @param timeout=timeout Optional command timeout. @ retval Device sample dict. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if acquire command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.ACQUIRE_SAMPLE, DriverEvent.ACQUIRE_SAMPLE, *args, **kwargs) def execute_start_autosample(self, *args, **kwargs): """ Switch to autosample mode. @param timeout=timeout Optional command timeout. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.START_AUTOSAMPLE, DriverEvent.START_AUTOSAMPLE, *args, **kwargs) def execute_stop_autosample(self, *args, **kwargs): """ Leave autosample mode. @param timeout=timeout Optional command timeout. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if stop command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.STOP_AUTOSAMPLE, DriverEvent.STOP_AUTOSAMPLE, *args, **kwargs) def execute_test(self, *args, **kwargs): """ Execute device tests. @param timeout=timeout Optional command timeout (for wakeup only -- device specific timeouts for internal test commands). @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if test commands not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.TEST, DriverEvent.TEST, *args, **kwargs) def execute_calibrate(self, *args, **kwargs): """ Execute device calibration. @param timeout=timeout Optional command timeout (for wakeup only -- device specific timeouts for internal calibration commands). @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if test commands not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.CALIBRATE, DriverEvent.CALIBRATE, *args, **kwargs) ######################################################################## # Resource query interface. ######################################################################## def get_current_state(self): """ Return current device state. For single connection devices, return a single connection state if not connected, and protocol state if connected. """ connection_state = self._connection_fsm.get_current_state() if connection_state == DriverConnectionState.CONNECTED: return self._protocol.get_current_state() else: return connection_state ######################################################################## # Unconfigured handlers. ######################################################################## def _handler_unconfigured_enter(self, *args, **kwargs): """ Enter unconfigured state. """ # Send state change event to agent. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_unconfigured_exit(self, *args, **kwargs): """ Exit unconfigured state. """ pass def _handler_unconfigured_initialize(self, *args, **kwargs): """ Initialize handler. We are already in unconfigured state, do nothing. @retval (next_state, result) tuple, (None, None). """ next_state = None result = None return (next_state, result) def _handler_unconfigured_configure(self, *args, **kwargs): """ Configure driver for device comms. @param args[0] Communiations config dictionary. @retval (next_state, result) tuple, (DriverConnectionState.DISCONNECTED, None) if successful, (None, None) otherwise. @raises InstrumentParameterException if missing or invalid param dict. """ next_state = None result = None # Get the required param dict. try: config = args[0] except IndexError: raise InstrumentParameterException( 'Missing comms config parameter.') # Verify dict and construct connection client. try: addr = config['addr'] port = config['port'] if isinstance(addr, str) and isinstance(port, int) and len(addr) > 0: self._connection = LoggerClient(addr, port) next_state = DriverConnectionState.DISCONNECTED else: raise InstrumentParameterException( 'Invalid comms config dict.') except (TypeError, KeyError): raise InstrumentParameterException('Invalid comms config dict.') return (next_state, result) ######################################################################## # Disconnected handlers. ######################################################################## def _handler_disconnected_enter(self, *args, **kwargs): """ Enter disconnected state. """ # Send state change event to agent. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_disconnected_exit(self, *args, **kwargs): """ Exit disconnected state. """ pass def _handler_disconnected_initialize(self, *args, **kwargs): """ Initialize device communications. Causes the connection parameters to be reset. @retval (next_state, result) tuple, (DriverConnectionState.UNCONFIGURED, None). """ next_state = None result = None self._connection = None next_state = DriverConnectionState.UNCONFIGURED return (next_state, result) def _handler_disconnected_configure(self, *args, **kwargs): """ Configure driver for device comms. @param args[0] Communiations config dictionary. @retval (next_state, result) tuple, (None, None). @raises InstrumentParameterException if missing or invalid param dict. """ next_state = None result = None # Get required config param dict. try: config = args[0] except IndexError: raise InstrumentParameterException( 'Missing comms config parameter.') # Verify configuration dict, and update connection if possible. try: addr = config['addr'] port = config['port'] if isinstance(addr, str) and isinstance(port, int) and len(addr) > 0: self._connection = LoggerClient(addr, port) else: raise InstrumentParameterException( 'Invalid comms config dict.') except (TypeError, KeyError): raise InstrumentParameterException('Invalid comms config dict.') return (next_state, result) def _handler_disconnected_connect(self, *args, **kwargs): """ Establish communications with the device via port agent / logger and construct and intialize a protocol FSM for device interaction. @retval (next_state, result) tuple, (DriverConnectionState.CONNECTED, None) if successful. @raises InstrumentConnectionException if the attempt to connect failed. """ next_state = None result = None self._build_protocol() self._connection.init_comms(self._protocol.got_data) self._protocol._connection = self._connection next_state = DriverConnectionState.CONNECTED return (next_state, result) ######################################################################## # Connected handlers. ######################################################################## def _handler_connected_enter(self, *args, **kwargs): """ Enter connected state. """ # Send state change event to agent. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_connected_exit(self, *args, **kwargs): """ Exit connected state. """ pass def _handler_connected_disconnect(self, *args, **kwargs): """ Disconnect to the device via port agent / logger and destroy the protocol FSM. @retval (next_state, result) tuple, (DriverConnectionState.DISCONNECTED, None) if successful. """ next_state = None result = None self._connection.stop_comms() self._protocol = None next_state = DriverConnectionState.DISCONNECTED return (next_state, result) def _handler_connected_connection_lost(self, *args, **kwargs): """ The device connection was lost. Stop comms, destroy protocol FSM and revert to disconnected state. @retval (next_state, result) tuple, (DriverConnectionState.DISCONNECTED, None). """ next_state = None result = None self._connection.stop_comms() self._protocol = None next_state = DriverConnectionState.DISCONNECTED return (next_state, result) def _handler_connected_protocol_event(self, event, *args, **kwargs): """ Forward a driver command event to the protocol FSM. @param args positional arguments to pass on. @param kwargs keyword arguments to pass on. @retval (next_state, result) tuple, (None, protocol result). """ next_state = None result = self._protocol._protocol_fsm.on_event(event, *args, **kwargs) return (next_state, result) ######################################################################## # Helpers. ######################################################################## def _build_protocol(self): """ Construct device specific single connection protocol FSM. Overridden in device specific subclasses. """ pass
class SingleConnectionInstrumentDriver(InstrumentDriver): """ Base class for instrument drivers with a single device connection. Provides connenction state logic for single connection drivers. This is the base class for the majority of driver implementation classes. """ def __init__(self, event_callback): """ Constructor for singly connected instrument drivers. @param event_callback Callback to the driver process to send asynchronous driver events back to the agent. """ InstrumentDriver.__init__(self, event_callback) # The only and only instrument connection. # Exists in the connected state. self._connection = None # The one and only instrument protocol. self._protocol = None # Build connection state machine. self._connection_fsm = InstrumentFSM(DriverConnectionState, DriverEvent, DriverEvent.ENTER, DriverEvent.EXIT) # Add handlers for all events. self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.ENTER, self._handler_unconfigured_enter) self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.EXIT, self._handler_unconfigured_exit) self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.INITIALIZE, self._handler_unconfigured_initialize) self._connection_fsm.add_handler(DriverConnectionState.UNCONFIGURED, DriverEvent.CONFIGURE, self._handler_unconfigured_configure) self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.ENTER, self._handler_disconnected_enter) self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.EXIT, self._handler_disconnected_exit) self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.INITIALIZE, self._handler_disconnected_initialize) self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.CONFIGURE, self._handler_disconnected_configure) self._connection_fsm.add_handler(DriverConnectionState.DISCONNECTED, DriverEvent.CONNECT, self._handler_disconnected_connect) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.ENTER, self._handler_connected_enter) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.EXIT, self._handler_connected_exit) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.DISCONNECT, self._handler_connected_disconnect) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.DISCOVER, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.GET, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.SET, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.ACQUIRE_SAMPLE, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.START_AUTOSAMPLE, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.STOP_AUTOSAMPLE, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.TEST, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.CALIBRATE, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.EXECUTE_DIRECT, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.START_DIRECT, self._handler_connected_protocol_event) self._connection_fsm.add_handler(DriverConnectionState.CONNECTED, DriverEvent.STOP_DIRECT, self._handler_connected_protocol_event) # Start state machine. self._connection_fsm.start(DriverConnectionState.UNCONFIGURED) ############################################################# # Device connection interface. ############################################################# def initialize(self, *args, **kwargs): """ Initialize driver connection, bringing communications parameters into unconfigured state (no connection object). @raises InstrumentStateException if command not allowed in current state """ # Forward event and argument to the connection FSM. return self._connection_fsm.on_event(DriverEvent.INITIALIZE, *args, **kwargs) def configure(self, *args, **kwargs): """ Configure the driver for communications with the device via port agent / logger (valid but unconnected connection object). @param arg[0] comms config dict. @raises InstrumentStateException if command not allowed in current state @throws InstrumentParameterException if missing comms or invalid config dict. """ # Forward event and argument to the connection FSM. return self._connection_fsm.on_event(DriverEvent.CONFIGURE, *args, **kwargs) def connect(self, *args, **kwargs): """ Establish communications with the device via port agent / logger (connected connection object). @raises InstrumentStateException if command not allowed in current state @throws InstrumentConnectionException if the connection failed. """ # Forward event and argument to the connection FSM. return self._connection_fsm.on_event(DriverEvent.CONNECT, *args, **kwargs) def disconnect(self, *args, **kwargs): """ Disconnect from device via port agent / logger. @raises InstrumentStateException if command not allowed in current state """ # Forward event and argument to the connection FSM. return self._connection_fsm.on_event(DriverEvent.DISCONNECT, *args, **kwargs) ############################################################# # Commande and control interface. ############################################################# def discover(self, *args, **kwargs): """ Determine initial state upon establishing communications. @param timeout=timeout Optional command timeout. @retval Current device state. @raises InstrumentTimeoutException if could not wake device. @raises InstrumentStateException if command not allowed in current state or if device state not recognized. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.DISCOVER, DriverEvent.DISCOVER, *args, **kwargs) def get(self, *args, **kwargs): """ Retrieve device parameters. @param args[0] DriverParameter.ALL or a list of parameters to retrive. @retval parameter : value dict. @raises InstrumentParameterException if missing or invalid get parameters. @raises InstrumentStateException if command not allowed in current state @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.GET, DriverEvent.GET, *args, **kwargs) def set(self, *args, **kwargs): """ Set device parameters. @param args[0] parameter : value dict of parameters to set. @param timeout=timeout Optional command timeout. @raises InstrumentParameterException if missing or invalid set parameters. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if set command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.SET, DriverEvent.SET, *args, **kwargs) def execute_acquire_sample(self, *args, **kwargs): """ Poll for a sample. @param timeout=timeout Optional command timeout. @ retval Device sample dict. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if acquire command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.ACQUIRE_SAMPLE, DriverEvent.ACQUIRE_SAMPLE, *args, **kwargs) def execute_start_autosample(self, *args, **kwargs): """ Switch to autosample mode. @param timeout=timeout Optional command timeout. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.START_AUTOSAMPLE, DriverEvent.START_AUTOSAMPLE, *args, **kwargs) def execute_stop_autosample(self, *args, **kwargs): """ Leave autosample mode. @param timeout=timeout Optional command timeout. @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if stop command not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.STOP_AUTOSAMPLE, DriverEvent.STOP_AUTOSAMPLE, *args, **kwargs) def execute_test(self, *args, **kwargs): """ Execute device tests. @param timeout=timeout Optional command timeout (for wakeup only -- device specific timeouts for internal test commands). @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if test commands not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.TEST, DriverEvent.TEST, *args, **kwargs) def execute_calibrate(self, *args, **kwargs): """ Execute device calibration. @param timeout=timeout Optional command timeout (for wakeup only -- device specific timeouts for internal calibration commands). @raises InstrumentTimeoutException if could not wake device or no response. @raises InstrumentProtocolException if test commands not recognized. @raises InstrumentStateException if command not allowed in current state. @raises NotImplementedException if not implemented by subclass. """ # Forward event and argument to the protocol FSM. return self._connection_fsm.on_event(DriverEvent.CALIBRATE, DriverEvent.CALIBRATE, *args, **kwargs) ######################################################################## # Resource query interface. ######################################################################## def get_current_state(self): """ Return current device state. For single connection devices, return a single connection state if not connected, and protocol state if connected. """ connection_state = self._connection_fsm.get_current_state() if connection_state == DriverConnectionState.CONNECTED: return self._protocol.get_current_state() else: return connection_state ######################################################################## # Unconfigured handlers. ######################################################################## def _handler_unconfigured_enter(self, *args, **kwargs): """ Enter unconfigured state. """ # Send state change event to agent. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_unconfigured_exit(self, *args, **kwargs): """ Exit unconfigured state. """ pass def _handler_unconfigured_initialize(self, *args, **kwargs): """ Initialize handler. We are already in unconfigured state, do nothing. @retval (next_state, result) tuple, (None, None). """ next_state = None result = None return (next_state, result) def _handler_unconfigured_configure(self, *args, **kwargs): """ Configure driver for device comms. @param args[0] Communiations config dictionary. @retval (next_state, result) tuple, (DriverConnectionState.DISCONNECTED, None) if successful, (None, None) otherwise. @raises InstrumentParameterException if missing or invalid param dict. """ next_state = None result = None # Get the required param dict. try: config = args[0] except IndexError: raise InstrumentParameterException('Missing comms config parameter.') # Verify dict and construct connection client. try: addr = config['addr'] port = config['port'] if isinstance(addr, str) and isinstance(port, int) and len(addr)>0: self._connection = LoggerClient(addr, port) next_state = DriverConnectionState.DISCONNECTED else: raise InstrumentParameterException('Invalid comms config dict.') except (TypeError, KeyError): raise InstrumentParameterException('Invalid comms config dict.') return (next_state, result) ######################################################################## # Disconnected handlers. ######################################################################## def _handler_disconnected_enter(self, *args, **kwargs): """ Enter disconnected state. """ # Send state change event to agent. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_disconnected_exit(self, *args, **kwargs): """ Exit disconnected state. """ pass def _handler_disconnected_initialize(self, *args, **kwargs): """ Initialize device communications. Causes the connection parameters to be reset. @retval (next_state, result) tuple, (DriverConnectionState.UNCONFIGURED, None). """ next_state = None result = None self._connection = None next_state = DriverConnectionState.UNCONFIGURED return (next_state, result) def _handler_disconnected_configure(self, *args, **kwargs): """ Configure driver for device comms. @param args[0] Communiations config dictionary. @retval (next_state, result) tuple, (None, None). @raises InstrumentParameterException if missing or invalid param dict. """ next_state = None result = None # Get required config param dict. try: config = args[0] except IndexError: raise InstrumentParameterException('Missing comms config parameter.') # Verify configuration dict, and update connection if possible. try: addr = config['addr'] port = config['port'] if isinstance(addr, str) and isinstance(port, int) and len(addr)>0: self._connection = LoggerClient(addr, port) else: raise InstrumentParameterException('Invalid comms config dict.') except (TypeError, KeyError): raise InstrumentParameterException('Invalid comms config dict.') return (next_state, result) def _handler_disconnected_connect(self, *args, **kwargs): """ Establish communications with the device via port agent / logger and construct and intialize a protocol FSM for device interaction. @retval (next_state, result) tuple, (DriverConnectionState.CONNECTED, None) if successful. @raises InstrumentConnectionException if the attempt to connect failed. """ next_state = None result = None self._build_protocol() self._connection.init_comms(self._protocol.got_data) self._protocol._connection = self._connection next_state = DriverConnectionState.CONNECTED return (next_state, result) ######################################################################## # Connected handlers. ######################################################################## def _handler_connected_enter(self, *args, **kwargs): """ Enter connected state. """ # Send state change event to agent. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_connected_exit(self, *args, **kwargs): """ Exit connected state. """ pass def _handler_connected_disconnect(self, *args, **kwargs): """ Disconnect to the device via port agent / logger and destroy the protocol FSM. @retval (next_state, result) tuple, (DriverConnectionState.DISCONNECTED, None) if successful. """ next_state = None result = None self._connection.stop_comms() self._protocol = None next_state = DriverConnectionState.DISCONNECTED return (next_state, result) def _handler_connected_connection_lost(self, *args, **kwargs): """ The device connection was lost. Stop comms, destroy protocol FSM and revert to disconnected state. @retval (next_state, result) tuple, (DriverConnectionState.DISCONNECTED, None). """ next_state = None result = None self._connection.stop_comms() self._protocol = None next_state = DriverConnectionState.DISCONNECTED return (next_state, result) def _handler_connected_protocol_event(self, event, *args, **kwargs): """ Forward a driver command event to the protocol FSM. @param args positional arguments to pass on. @param kwargs keyword arguments to pass on. @retval (next_state, result) tuple, (None, protocol result). """ next_state = None result = self._protocol._protocol_fsm.on_event(event, *args, **kwargs) return (next_state, result) ######################################################################## # Helpers. ######################################################################## def _build_protocol(self): """ Construct device specific single connection protocol FSM. Overridden in device specific subclasses. """ pass
class PlatformDriver(object): """ A platform driver handles a particular platform in a platform network. """ def __init__(self, pnode, evt_recv): """ Creates a PlatformDriver instance. @param pnode Root PlatformNode defining the platform network rooted at this platform. @param evt_recv Listener of events generated by this driver """ assert pnode, "pnode must be given" assert evt_recv, "evt_recv parameter must be given" self._pnode = pnode self._send_event = evt_recv 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 # construct FSM and start it with initial state UNCONFIGURED: self._construct_fsm() self._fsm.start(PlatformDriverState.UNCONFIGURED) 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. """ def configure(self, driver_config): """ Configures this driver. It first calls _validate_driver_configuration. @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 def connect(self): """ To be implemented by subclass. Establishes communication with the platform device. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def disconnect(self): """ 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 """ raise NotImplementedError() #pragma: no cover def get_metadata(self): """ To be implemented by subclass. Returns the metadata associated to the platform. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def get_attribute_values(self, attr_names, from_time): """ To be implemented by subclass. Returns the values for specific attributes since a given time. @param attr_names [attrName, ...] desired attributes @param from_time time from which the values are requested. 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 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 {platform_id: {attrName : [(attrValue, timestamp), ...], ...}} dict with a single entry for the requested platform ID and value as 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;" this is to be aligned with description of pyon's get_ion_ts function. """ raise NotImplementedError() #pragma: no cover def connect_instrument(self, port_id, instrument_id, attributes): """ To be implemented by subclass. Connects an instrument to a port in this platform. @param port_id Port ID @param instrument_id Instrument ID @param attributes Attribute dictionary @retval The resulting configuration for the instrument. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def disconnect_instrument(self, port_id, instrument_id): """ To be implemented by subclass. Disconnects an instrument from a port in this platform. @param port_id Port ID @param instrument_id Instrument ID @retval @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def get_connected_instruments(self, port_id): """ To be implemented by subclass. Retrieves the IDs of the instruments connected to a port. @param port_id Port ID @retval @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def turn_on_port(self, port_id): """ To be implemented by subclass. Turns on a port in this platform. @param port_id Port ID @retval The resulting on/off of the port. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def turn_off_port(self, port_id): """ To be implemented by subclass. Turns off a port in this platform. @param port_id Port ID @retval The resulting on/off of the port. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def destroy(self): """ Stops all activity done by the driver. Nothing done in this class. (previously it stopped resource monitoring). """ 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.debug("platform driver=%r: notify driver_event=%s", self._platform_id, driver_event) assert isinstance(driver_event, DriverEvent) self._send_event(driver_event) def get_checksum(self): """ To be implemented by subclass. Returns the checksum for this platform. @return SHA1 hash value as string of hexadecimal digits. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def get_driver_state(self): """ Returns the current FSM state. """ return self._fsm.get_current_state() ############################################################## # FSM event handlers. ############################################################## def _common_state_enter(self, *args, **kwargs): """ Common work upon every state entry. Nothing done in this base class. @todo determine what should be done, in particular regarding eventual notification of the platform driver state transition. """ state = self.get_driver_state() if log.isEnabledFor(logging.DEBUG): log.debug('%r: driver entering state: %s' % (self._platform_id, state)) # TODO: publish the platform driver FSM state transition? # event_data = { # 'state': state # } # result = self._event_publisher.publish_event( # event_type = ?????, # origin = ?????, # **event_data) # if log.isEnabledFor(logging.DEBUG): # log.debug('%r: PlatformDriver published state change: %s, ' # 'time: %s result: %s', # self._platform_id, state, get_ion_ts(), str(result)) 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))) result = self.connect() next_state = PlatformDriverState.CONNECTED return next_state, result ############################################################## # CONNECTED event handlers. ############################################################## 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))) result = self.disconnect(*args, **kwargs) next_state = PlatformDriverState.DISCONNECTED return next_state, result 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))) result = None try: result = self.ping() next_state = None except PlatformConnectionException as e: next_state = PlatformDriverState.DISCONNECTED log.error("Ping failed", e) return next_state, result def _handler_connected_get_metadata(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))) result = self.get_metadata() next_state = None return next_state, result def _handler_connected_get_attribute_values(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))) attr_names = kwargs.get('attr_names', None) if attr_names is None: raise FSMError('get_attribute_values: missing attr_names argument') from_time = kwargs.get('from_time', None) if from_time is None: raise FSMError('get_attribute_values: missing from_time argument') result = self.get_attribute_values(attr_names, from_time) next_state = None return next_state, result def _handler_connected_set_attribute_values(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))) attrs = kwargs.get('attrs', None) if attrs is None: raise FSMError('set_attribute_values: missing attrs argument') result = self.set_attribute_values(attrs) next_state = None return next_state, result def _handler_connected_connect_instrument(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))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('connect_instrument: missing port_id argument') instrument_id = kwargs.get('instrument_id', None) if instrument_id is None: raise FSMError('connect_instrument: missing instrument_id argument') attributes = kwargs.get('attributes', None) if attributes is None: raise FSMError('connect_instrument: missing attributes argument') result = self.connect_instrument(port_id, instrument_id, attributes) next_state = None return next_state, result def _handler_disconnected_connect_instrument(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))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('disconnect_instrument: missing port_id argument') instrument_id = kwargs.get('instrument_id', None) if instrument_id is None: raise FSMError('disconnect_instrument: missing instrument_id argument') result = self.disconnect_instrument(port_id, instrument_id) next_state = None return next_state, result def _handler_connected_get_connected_instruments(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))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('get_connected_instruments: missing port_id argument') result = self.get_connected_instruments(port_id) next_state = None return next_state, result def _handler_connected_turn_on_port(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))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('turn_on_port: missing port_id argument') result = self.turn_on_port(port_id) next_state = None return next_state, result def _handler_connected_turn_off_port(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))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('turn_off_port: missing port_id argument') result = self.turn_off_port(port_id) next_state = None return next_state, result def _handler_connected_get_checksum(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))) result = self.get_checksum() next_state = None return next_state, result ############################################################## # Platform driver FSM setup ############################################################## def _construct_fsm(self): """ """ log.debug("constructing fsm") self._fsm = InstrumentFSM(PlatformDriverState, PlatformDriverEvent, PlatformDriverEvent.ENTER, PlatformDriverEvent.EXIT) for state in PlatformDriverState.list(): self._fsm.add_handler(state, PlatformDriverEvent.ENTER, self._common_state_enter) self._fsm.add_handler(state, PlatformDriverEvent.EXIT, 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) # 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_disconnect) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.PING, self._handler_connected_ping) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_METADATA, self._handler_connected_get_metadata) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_ATTRIBUTE_VALUES, self._handler_connected_get_attribute_values) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.SET_ATTRIBUTE_VALUES, self._handler_connected_set_attribute_values) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECT_INSTRUMENT, self._handler_connected_connect_instrument) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT_INSTRUMENT, self._handler_disconnected_connect_instrument) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_CONNECTED_INSTRUMENTS, self._handler_connected_get_connected_instruments) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.TURN_ON_PORT, self._handler_connected_turn_on_port) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.TURN_OFF_PORT, self._handler_connected_turn_off_port) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_CHECKSUM, self._handler_connected_get_checksum)
class SBE37Protocol(CommandResponseInstrumentProtocol): """ Instrument protocol class for SBE37 driver. Subclasses CommandResponseInstrumentProtocol """ def __init__(self, prompts, newline, driver_event): """ SBE37Protocol constructor. @param prompts A BaseEnum class containing instrument prompts. @param newline The SBE37 newline. @param driver_event Driver process event callback. """ # Construct protocol superclass. CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event) # Build SBE37 protocol state machine. self._protocol_fsm = InstrumentFSM(SBE37ProtocolState, SBE37ProtocolEvent, SBE37ProtocolEvent.ENTER, SBE37ProtocolEvent.EXIT) # Add event handlers for protocol state machine. self._protocol_fsm.add_handler(SBE37ProtocolState.UNKNOWN, SBE37ProtocolEvent.ENTER, self._handler_unknown_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.UNKNOWN, SBE37ProtocolEvent.EXIT, self._handler_unknown_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.UNKNOWN, SBE37ProtocolEvent.DISCOVER, self._handler_unknown_discover) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.EXIT, self._handler_command_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_acquire_sample) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.TEST, self._handler_command_test) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.START_DIRECT, self._handler_command_start_direct) self._protocol_fsm.add_handler(SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.ENTER, self._handler_autosample_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.EXIT, self._handler_autosample_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample) self._protocol_fsm.add_handler(SBE37ProtocolState.TEST, SBE37ProtocolEvent.ENTER, self._handler_test_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.TEST, SBE37ProtocolEvent.EXIT, self._handler_test_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.TEST, SBE37ProtocolEvent.RUN_TEST, self._handler_test_run_tests) self._protocol_fsm.add_handler(SBE37ProtocolState.TEST, SBE37ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.ENTER, self._handler_direct_access_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.EXIT, self._handler_direct_access_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct) self._protocol_fsm.add_handler(SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct) # Construct the parameter dictionary containing device parameters, # current parameter values, and set formatting functions. self._build_param_dict() # Add build handlers for device commands. self._add_build_handler('ds', self._build_simple_command) self._add_build_handler('dc', self._build_simple_command) self._add_build_handler('ts', self._build_simple_command) self._add_build_handler('startnow', self._build_simple_command) self._add_build_handler('stop', self._build_simple_command) self._add_build_handler('tc', self._build_simple_command) self._add_build_handler('tt', self._build_simple_command) self._add_build_handler('tp', self._build_simple_command) self._add_build_handler('set', self._build_set_command) # Add response handlers for device commands. self._add_response_handler('ds', self._parse_dsdc_response) self._add_response_handler('dc', self._parse_dsdc_response) self._add_response_handler('ts', self._parse_ts_response) self._add_response_handler('set', self._parse_set_response) self._add_response_handler('tc', self._parse_test_response) self._add_response_handler('tt', self._parse_test_response) self._add_response_handler('tp', self._parse_test_response) # Add sample handlers. self._sample_pattern = r'^#? *(-?\d+\.\d+), *(-?\d+\.\d+), *(-?\d+\.\d+)' self._sample_pattern += r'(, *(-?\d+\.\d+))?(, *(-?\d+\.\d+))?' self._sample_pattern += r'(, *(\d+) +([a-zA-Z]+) +(\d+), *(\d+):(\d+):(\d+))?' self._sample_pattern += r'(, *(\d+)-(\d+)-(\d+), *(\d+):(\d+):(\d+))?' self._sample_regex = re.compile(self._sample_pattern) # State state machine in UNKNOWN state. self._protocol_fsm.start(SBE37ProtocolState.UNKNOWN) # commands sent sent to device to be filtered in responses for telnet DA self._sent_cmds = [] ######################################################################## # Unknown handlers. ######################################################################## def _handler_unknown_enter(self, *args, **kwargs): """ Enter unknown state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_unknown_exit(self, *args, **kwargs): """ Exit unknown state. """ pass def _handler_unknown_discover(self, *args, **kwargs): """ Discover current state; can be COMMAND or AUTOSAMPLE. @retval (next_state, result), (SBE37ProtocolState.COMMAND or SBE37State.AUTOSAMPLE, None) if successful. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentStateException if the device response does not correspond to an expected state. """ next_state = None result = None # Wakeup the device with timeout if passed. timeout = kwargs.get('timeout', SBE37_TIMEOUT) prompt = self._wakeup(timeout) prompt = self._wakeup(timeout) # Set the state to change. # Raise if the prompt returned does not match command or autosample. if prompt == SBE37Prompt.COMMAND: next_state = SBE37ProtocolState.COMMAND result = SBE37ProtocolState.COMMAND elif prompt == SBE37Prompt.AUTOSAMPLE: next_state = SBE37ProtocolState.AUTOSAMPLE result = SBE37ProtocolState.AUTOSAMPLE else: raise InstrumentStateException('Unknown state.') return (next_state, result) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentProtocolException if the update commands and not recognized. """ # Command device to update parameters and send a config change event. self._update_params() # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_command_exit(self, *args, **kwargs): """ Exit command state. """ pass def _handler_command_set(self, *args, **kwargs): """ Perform a set command. @param args[0] parameter : value dict. @retval (next_state, result) tuple, (None, None). @throws InstrumentParameterException if missing set parameters, if set parameters not ALL and not a dict, or if paramter can't be properly formatted. @throws InstrumentTimeoutException if device cannot be woken for set command. @throws InstrumentProtocolException if set command could not be built or misunderstood. """ next_state = None result = None # Retrieve required parameter. # Raise if no parameter provided, or not a dict. try: params = args[0] except IndexError: raise InstrumentParameterException('Set command requires a parameter dict.') if not isinstance(params, dict): raise InstrumentParameterException('Set parameters not a dict.') # For each key, val in the dict, issue set command to device. # Raise if the command not understood. else: for (key, val) in params.iteritems(): result = self._do_cmd_resp('set', key, val, **kwargs) self._update_params() return (next_state, result) def _handler_command_acquire_sample(self, *args, **kwargs): """ Acquire sample from SBE37. @retval (next_state, result) tuple, (None, sample dict). @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command could not be built or misunderstood. @throws SampleException if a sample could not be extracted from result. """ next_state = None result = None result = self._do_cmd_resp('ts', *args, **kwargs) return (next_state, result) def _handler_command_start_autosample(self, *args, **kwargs): """ Switch into autosample mode. @retval (next_state, result) tuple, (SBE37ProtocolState.AUTOSAMPLE, None) if successful. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command could not be built or misunderstood. """ next_state = None result = None # Assure the device is transmitting. if not self._param_dict.get(SBE37Parameter.TXREALTIME): self._do_cmd_resp('set', SBE37Parameter.TXREALTIME, True, **kwargs) # Issue start command and switch to autosample if successful. self._do_cmd_no_resp('startnow', *args, **kwargs) next_state = SBE37ProtocolState.AUTOSAMPLE return (next_state, result) def _handler_command_test(self, *args, **kwargs): """ Switch to test state to perform instrument tests. @retval (next_state, result) tuple, (SBE37ProtocolState.TEST, None). """ next_state = None result = None next_state = SBE37ProtocolState.TEST return (next_state, result) def _handler_command_start_direct(self): """ """ next_state = None result = None next_state = SBE37ProtocolState.DIRECT_ACCESS return (next_state, result) ######################################################################## # Autosample handlers. ######################################################################## def _handler_autosample_enter(self, *args, **kwargs): """ Enter autosample state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_autosample_exit(self, *args, **kwargs): """ Exit autosample state. """ pass def _handler_autosample_stop_autosample(self, *args, **kwargs): """ Stop autosample and switch back to command mode. @retval (next_state, result) tuple, (SBE37ProtocolState.COMMAND, None) if successful. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command misunderstood or incorrect prompt received. """ next_state = None result = None # Wake up the device, continuing until autosample prompt seen. timeout = kwargs.get('timeout', SBE37_TIMEOUT) self._wakeup_until(timeout, SBE37Prompt.AUTOSAMPLE) # Issue the stop command. self._do_cmd_resp('stop', *args, **kwargs) # Prompt device until command prompt is seen. self._wakeup_until(timeout, SBE37Prompt.COMMAND) next_state = SBE37ProtocolState.COMMAND return (next_state, result) ######################################################################## # Common handlers. ######################################################################## def _handler_command_autosample_test_get(self, *args, **kwargs): """ Get device parameters from the parameter dict. @param args[0] list of parameters to retrieve, or DriverParameter.ALL. @throws InstrumentParameterException if missing or invalid parameter. """ next_state = None result = None # Retrieve the required parameter, raise if not present. try: params = args[0] except IndexError: raise InstrumentParameterException('Get command requires a parameter list or tuple.') # If all params requested, retrieve config. if params == DriverParameter.ALL: result = self._param_dict.get_config() # If not all params, confirm a list or tuple of params to retrieve. # Raise if not a list or tuple. # Retireve each key in the list, raise if any are invalid. else: if not isinstance(params, (list, tuple)): raise InstrumentParameterException('Get argument not a list or tuple.') result = {} for key in params: try: val = self._param_dict.get(key) result[key] = val except KeyError: raise InstrumentParameterException(('%s is not a valid parameter.' % key)) return (next_state, result) ######################################################################## # Test handlers. ######################################################################## def _handler_test_enter(self, *args, **kwargs): """ Enter test state. Setup the secondary call to run the tests. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) # Forward the test event again to run the test handler and # switch back to command mode afterward. Timer(1, lambda: self._protocol_fsm.on_event(SBE37ProtocolEvent.RUN_TEST)).start() def _handler_test_exit(self, *args, **kwargs): """ Exit test state. """ pass def _handler_test_run_tests(self, *args, **kwargs): """ Run test routines and validate results. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command misunderstood or incorrect prompt received. """ next_state = None result = None tc_pass = False tt_pass = False tp_pass = False tc_result = None tt_result = None tp_result = None test_result = {} try: tc_pass, tc_result = self._do_cmd_resp('tc', timeout=200) tt_pass, tt_result = self._do_cmd_resp('tt', timeout=200) tp_pass, tp_result = self._do_cmd_resp('tp', timeout=200) except Exception as e: test_result['exception'] = e test_result['message'] = 'Error running instrument tests.' finally: test_result['cond_test'] = 'Passed' if tc_pass else 'Failed' test_result['cond_data'] = tc_result test_result['temp_test'] = 'Passed' if tt_pass else 'Failed' test_result['temp_data'] = tt_result test_result['pres_test'] = 'Passed' if tp_pass else 'Failed' test_result['pres_data'] = tp_result test_result['success'] = 'Passed' if (tc_pass and tt_pass and tp_pass) else 'Failed' self._driver_event(DriverAsyncEvent.TEST_RESULT, test_result) next_state = SBE37ProtocolState.COMMAND return (next_state, result) ######################################################################## # Direct access handlers. ######################################################################## def _handler_direct_access_enter(self, *args, **kwargs): """ Enter direct access state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) self._sent_cmds = [] def _handler_direct_access_exit(self, *args, **kwargs): """ Exit direct access state. """ pass def _handler_direct_access_execute_direct(self, data): """ """ next_state = None result = None self._do_cmd_direct(data) # add sent command to list for 'echo' filtering in callback self._sent_cmds.append(data) return (next_state, result) def _handler_direct_access_stop_direct(self): """ @throw InstrumentProtocolException on invalid command """ next_state = None result = None next_state = SBE37ProtocolState.COMMAND return (next_state, result) ######################################################################## # Private helpers. ######################################################################## def _send_wakeup(self): """ Send a newline to attempt to wake the SBE37 device. """ self._connection.send(SBE37_NEWLINE) def _update_params(self, *args, **kwargs): """ Update the parameter dictionary. Wake the device then issue display status and display calibration commands. The parameter dict will match line output and udpate itself. @throws InstrumentTimeoutException if device cannot be timely woken. @throws InstrumentProtocolException if ds/dc misunderstood. """ # Get old param dict config. old_config = self._param_dict.get_config() # Issue display commands and parse results. timeout = kwargs.get('timeout', SBE37_TIMEOUT) self._do_cmd_resp('ds',timeout=timeout) self._do_cmd_resp('dc',timeout=timeout) # Get new param dict config. If it differs from the old config, # tell driver superclass to publish a config change event. new_config = self._param_dict.get_config() if new_config != old_config: self._driver_event(DriverAsyncEvent.CONFIG_CHANGE) def _build_simple_command(self, cmd): """ Build handler for basic SBE37 commands. @param cmd the simple sbe37 command to format. @retval The command to be sent to the device. """ return cmd+SBE37_NEWLINE def _build_set_command(self, cmd, param, val): """ Build handler for set commands. param=val followed by newline. String val constructed by param dict formatting function. @param param the parameter key to set. @param val the parameter value to set. @ retval The set command to be sent to the device. @throws InstrumentProtocolException if the parameter is not valid or if the formatting function could not accept the value passed. """ try: str_val = self._param_dict.format(param, val) set_cmd = '%s=%s' % (param, str_val) set_cmd = set_cmd + SBE37_NEWLINE except KeyError: raise InstrumentParameterException('Unknown driver parameter %s' % param) return set_cmd def _parse_set_response(self, response, prompt): """ Parse handler for set command. @param response command response string. @param prompt prompt following command response. @throws InstrumentProtocolException if set command misunderstood. """ if prompt != SBE37Prompt.COMMAND: raise InstrumentProtocolException('Set command not recognized: %s' % response) def _parse_dsdc_response(self, response, prompt): """ Parse handler for dsdc commands. @param response command response string. @param prompt prompt following command response. @throws InstrumentProtocolException if dsdc command misunderstood. """ if prompt != SBE37Prompt.COMMAND: raise InstrumentProtocolException('dsdc command not recognized: %s.' % response) for line in response.split(SBE37_NEWLINE): self._param_dict.update(line) def _parse_ts_response(self, response, prompt): """ Response handler for ts command. @param response command response string. @param prompt prompt following command response. @retval sample dictionary containig c, t, d values. @throws InstrumentProtocolException if ts command misunderstood. @throws InstrumentSampleException if response did not contain a sample """ if prompt != SBE37Prompt.COMMAND: raise InstrumentProtocolException('ts command not recognized: %s', response) sample = None for line in response.split(SBE37_NEWLINE): sample = self._extract_sample(line, True) if sample: break if not sample: raise SampleException('Response did not contain sample: %s' % repr(response)) return sample def _parse_test_response(self, response, prompt): """ Do minimal checking of test outputs. @param response command response string. @param promnpt prompt following command response. @retval tuple of pass/fail boolean followed by response """ success = False lines = response.split() if len(lines)>2: data = lines[1:-1] bad_count = 0 for item in data: try: float(item) except ValueError: bad_count += 1 if bad_count == 0: success = True return (success, response) def got_data(self, data): """ Callback for receiving new data from the device. """ if self.get_current_state() == SBE37ProtocolState.DIRECT_ACCESS: # direct access mode if len(data) > 0: mi_logger.debug("SBE37Protocol._got_data(): <" + data + ">") if self._driver_event: self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, data) # TODO: what about logging this as an event? return if len(data)>0: # Call the superclass to update line and prompt buffers. CommandResponseInstrumentProtocol.got_data(self, data) # If in streaming mode, process the buffer for samples to publish. cur_state = self.get_current_state() if cur_state == SBE37ProtocolState.AUTOSAMPLE: if SBE37_NEWLINE in self._linebuf: lines = self._linebuf.split(SBE37_NEWLINE) self._linebuf = lines[-1] for line in lines: self._extract_sample(line) def _extract_sample(self, line, publish=True): """ Extract sample from a response line if present and publish to agent. @param line string to match for sample. @param publsih boolean to publish sample (default True). @retval Sample dictionary if present or None. """ sample = None match = self._sample_regex.match(line) if match: sample = {} sample['t'] = [float(match.group(1))] sample['c'] = [float(match.group(2))] sample['p'] = [float(match.group(3))] # Driver timestamp. sample['time'] = [time.time()] sample['stream_name'] = 'ctd_parsed' if self._driver_event: self._driver_event(DriverAsyncEvent.SAMPLE, sample) return sample def _build_param_dict(self): """ Populate the parameter dictionary with SBE37 parameters. For each parameter key, add match stirng, match lambda function, and value formatting function for set commands. """ # Add parameter handlers to parameter dict. self._param_dict.add(SBE37Parameter.OUTPUTSAL, r'(do not )?output salinity with each sample', lambda match : False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE37Parameter.OUTPUTSV, r'(do not )?output sound velocity with each sample', lambda match : False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE37Parameter.NAVG, r'number of samples to average = (\d+)', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE37Parameter.SAMPLENUM, r'samplenumber = (\d+), free = \d+', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE37Parameter.INTERVAL, r'sample interval = (\d+) seconds', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE37Parameter.STORETIME, r'(do not )?store time with each sample', lambda match : False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE37Parameter.TXREALTIME, r'(do not )?transmit real-time data', lambda match : False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE37Parameter.SYNCMODE, r'serial sync mode (enabled|disabled)', lambda match : False if (match.group(1)=='disabled') else True, self._true_false_to_string) self._param_dict.add(SBE37Parameter.SYNCWAIT, r'wait time after serial sync sampling = (\d+) seconds', lambda match : int(match.group(1)), self._int_to_string) self._param_dict.add(SBE37Parameter.TCALDATE, r'temperature: +((\d+)-([a-zA-Z]+)-(\d+))', lambda match : self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE37Parameter.TA0, r' +TA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.TA1, r' +TA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.TA2, r' +TA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.TA3, r' +TA3 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.CCALDATE, r'conductivity: +((\d+)-([a-zA-Z]+)-(\d+))', lambda match : self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE37Parameter.CG, r' +G = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.CH, r' +H = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.CI, r' +I = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.CJ, r' +J = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.WBOTC, r' +WBOTC = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.CTCOR, r' +CTCOR = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.CPCOR, r' +CPCOR = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PCALDATE, r'pressure .+ ((\d+)-([a-zA-Z]+)-(\d+))', lambda match : self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE37Parameter.PA0, r' +PA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PA1, r' +PA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PA2, r' +PA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCA0, r' +PTCA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCA1, r' +PTCA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCA2, r' +PTCA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCB0, r' +PTCSB0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCB1, r' +PTCSB1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCB2, r' +PTCSB2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.POFFSET, r' +POFFSET = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.RCALDATE, r'rtc: +((\d+)-([a-zA-Z]+)-(\d+))', lambda match : self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE37Parameter.RTCA0, r' +RTCA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.RTCA1, r' +RTCA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.RTCA2, r' +RTCA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match : float(match.group(1)), self._float_to_string) ######################################################################## # Static helpers to format set commands. ######################################################################## @staticmethod def _true_false_to_string(v): """ Write a boolean value to string formatted for sbe37 set operations. @param v a boolean value. @retval A yes/no string formatted for sbe37 set operations. @throws InstrumentParameterException if value not a bool. """ if not isinstance(v,bool): raise InstrumentParameterException('Value %s is not a bool.' % str(v)) if v: return 'y' else: return 'n' @staticmethod def _int_to_string(v): """ Write an int value to string formatted for sbe37 set operations. @param v An int val. @retval an int string formatted for sbe37 set operations. @throws InstrumentParameterException if value not an int. """ if not isinstance(v,int): raise InstrumentParameterException('Value %s is not an int.' % str(v)) else: return '%i' % v @staticmethod def _float_to_string(v): """ Write a float value to string formatted for sbe37 set operations. @param v A float val. @retval a float string formatted for sbe37 set operations. @throws InstrumentParameterException if value is not a float. """ if not isinstance(v,float): raise InstrumentParameterException('Value %s is not a float.' % v) else: return '%e' % v @staticmethod def _date_to_string(v): """ Write a date tuple to string formatted for sbe37 set operations. @param v a date tuple: (day,month,year). @retval A date string formatted for sbe37 set operations. @throws InstrumentParameterException if date tuple is not valid. """ if not isinstance(v,(list,tuple)): raise InstrumentParameterException('Value %s is not a list, tuple.' % str(v)) if not len(v)==3: raise InstrumentParameterException('Value %s is not length 3.' % str(v)) months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep', 'Oct','Nov','Dec'] day = v[0] month = v[1] year = v[2] if len(str(year)) > 2: year = int(str(year)[-2:]) if not isinstance(day,int) or day < 1 or day > 31: raise InstrumentParameterException('Value %s is not a day of month.' % str(day)) if not isinstance(month,int) or month < 1 or month > 12: raise InstrumentParameterException('Value %s is not a month.' % str(month)) if not isinstance(year,int) or year < 0 or year > 99: raise InstrumentParameterException('Value %s is not a 0-99 year.' % str(year)) return '%02i-%s-%02i' % (day,months[month-1],year) @staticmethod def _string_to_date(datestr,fmt): """ Extract a date tuple from an sbe37 date string. @param str a string containing date information in sbe37 format. @retval a date tuple. @throws InstrumentParameterException if datestr cannot be formatted to a date. """ if not isinstance(datestr,str): raise InstrumentParameterException('Value %s is not a string.' % str(datestr)) try: date_time = time.strptime(datestr,fmt) date = (date_time[2],date_time[1],date_time[0]) except ValueError: raise InstrumentParameterException('Value %s could not be formatted to a date.' % str(datestr)) return date
class ResourceAgent(BaseResourceAgent): """ A resource agent is an ION process of type "agent" that exposes the standard resource agent service interface. This base class captures the mechanisms common to all resource agents and is subclassed with implementations specific for instrument agents, user agents, etc. """ ############################################################## # Class variables. ############################################################## # ION process type. process_type = "agent" # Override in subclass to publish specific types of events. COMMAND_EVENT_TYPE = "ResourceCommandEvent" # Override in subclass to set specific origin type. ORIGIN_TYPE = "Resource" # Override in subclass to expose agent capabilities. CAPABILITIES = [] ############################################################## # Constructor and ION init/deinit. ############################################################## def __init__(self, *args, **kwargs): """ Initialize superclass and id variables. """ # Base class constructor. super(ResourceAgent, self).__init__(*args, **kwargs) # The ID of the AgentInstance subtype resource object. self.agent_id = None # The ID of the AgentDefinition subtype resource object. self.agent_def_id = None # The ID of the target resource object, e.g. a device id. self.resource_id = None # UUID of the current mutex. self._mutex_id = None # Event publisher. self._event_publisher = None # An example agent parameter. self.aparam_example = None # Set intial state. if 'initial_state' in kwargs: if ResourceAgentState.has(kwargs['initial_state']): self._initial_state = kwargs['initial_state'] else: self._initial_state = ResourceAgentState.UNINITIALIZED # Construct the default state machine. self._construct_fsm() def _on_init(self): """ ION on_init initializer called once the process exists. """ log.debug("Resource Agent initializing. name=%s, resource_id=%s" % (self._proc_name, self.resource_id)) # Create event publisher. self._event_publisher = EventPublisher() # Start state machine. self._fsm.start(self._initial_state) def _on_quit(self): """ ION on_quit called prior to terminating the process. """ pass ############################################################## # Governance interface. ############################################################## def negotiate(self, resource_id="", sap_in=None): """ TBD. """ pass ############################################################## # Capabilities interface. ############################################################## def get_capabilities(self, resource_id="", current_state=True): """ """ agent_cmds = self._fsm.get_events(current_state) agent_cmds = self._filter_capabilities(agent_cmds) agent_params = self.get_agent_parameters() try: [res_cmds, res_params] = self._fsm.on_event(ResourceAgentEvent.GET_RESOURCE_CAPABILITIES, current_state) except FSMStateError: res_cmds = [] res_params = [] caps = [] for item in agent_cmds: cap = IonObject('AgentCapability', name=item, cap_type=CapabilityType.AGT_CMD) caps.append(cap) for item in agent_params: cap = IonObject('AgentCapability', name=item, cap_type=CapabilityType.AGT_PAR) caps.append(cap) for item in res_cmds: cap = IonObject('AgentCapability', name=item, cap_type=CapabilityType.RES_CMD) caps.append(cap) for item in res_params: cap = IonObject('AgentCapability', name=item, cap_type=CapabilityType.RES_PAR) caps.append(cap) return caps def get_agent_parameters(self): """ """ params = [x.lstrip('aparam_') for x in vars(self).keys() if x.startswith('aparam_')] return params def _filter_capabilities(self, events): """ """ return events ############################################################## # Agent interface. ############################################################## def get_agent(self, resource_id='', params=[]): """ """ result = {} for x in params: try: key = 'aparam_' + x val = getattr(self, key) result[x] = val except TypeError: raise BadRequest('Bad agent parameter name: %s' % str(x)) except AttributeError: raise BadRequest('Unknown agent parameter: %s' % x) return result def set_agent(self, resource_id='', params={}): """ """ for (x, val) in params.iteritems(): try: key = 'aparam_' + x getattr(self, key) set_key = 'aparam_set_' + x try: set_func = getattr(self, set_key) except AttributeError: set_func = None else: if not callable(set_func): set_func = None if set_func: set_func(val) else: setattr(self, key, val) except TypeError: raise BadRequest('Bad agent parameter name: %s' % str(x)) except AttributeError: raise BadRequest('Unknown agent parameter: %s' % x) def get_agent_state(self, resource_id=''): """ Return resource agent current common fsm state. """ return self._fsm.get_current_state() def execute_agent(self, resource_id="", command=None): """ """ if not command: iex = BadRequest('Execute argument "command" not set.') self._on_command_error('execute_agent', None, None, None, iex) raise iex # Grab command syntax. id = command.command_id cmd = command.command args = command.args or [] kwargs = command.kwargs or {} if not command.command: iex = BadRequest('Command name not set.') self._on_command_error('execute_agent', cmd, args, kwargs, iex) raise iex # Construct a command result object. cmd_result = IonObject("AgentCommandResult", command_id=id, command=cmd, ts_execute=get_ion_ts(), status=0) try: result = self._fsm.on_event(cmd, *args, **kwargs) cmd_result.result = result self._on_command('execute_agent', cmd, args, kwargs, result) except FSMStateError as ex: iex = Conflict(*(ex.args)) self._on_command_error('execute_agent', cmd, args, kwargs, iex) raise iex except FSMCommandUnknownError as ex: iex = BadRequest(*(ex.args)) self._on_command_error('execute_agent', cmd, args, kwargs, iex) raise iex except IonException as iex: self._on_command_error('execute_agent', cmd, args, kwargs, iex) raise except Exception as ex: iex = ServerError(*(ex.args)) self._on_command_error('execute_agent', cmd, args, kwargs, iex) raise iex return cmd_result def ping_agent(self, resource_id=""): """ """ result = 'ping from %s, time: %s' % (str(self), get_ion_ts()) return result def aparam_set_example(self, val): """ """ if isinstance(val, str): self.aparam_example = val else: raise BadRequest('Invalid type to set agent parameter "example".') ############################################################## # Resource interface. ############################################################## def get_resource(self, resource_id='', params=[]): """ """ try: result = self._fsm.on_event(ResourceAgentEvent.GET_RESOURCE, params) return result except FSMStateError as ex: iex = Conflict(*(ex.args)) self._on_command_error('get_resource', None, [params], None, iex) raise iex except FSMCommandUnknownError as iex: iex = BadRequest(*(ex.args)) self._on_command_error('get_resource', None, [params], None, iex) raise iex except IonException as iex: self._on_command_error('get_resource', None, [params], None, iex) raise except Exception as ex: iex = ServerError(*(ex.args)) self._on_command_error('get_resource', None, [params], None, iex) raise iex def set_resource(self, resource_id='', params={}): """ """ try: result = self._fsm.on_event(ResourceAgentEvent.SET_RESOURCE, params) self._on_command('set_resource', None, [params], None, result) return result except FSMStateError as ex: iex = Conflict(*(ex.args)) self._on_command_error('set_resource', None, [params], None, iex) raise iex except FSMCommandUnknownError as ex: iex = BadRequest(*(ex.args)) self._on_command_error('set_resource', None, [params], None, iex) raise iex except IonException as iex: self._on_command_error('set_resource', None, [params], None, iex) raise iex except Exception as ex: iex = ServerError(*(ex.args)) self._on_command_error('set_resource', None, [params], None, iex) raise iex def get_resource_state(self, resource_id=''): """ """ try: return self._fsm.on_event(ResourceAgentEvent.GET_RESOURCE_STATE) except FSMStateError as ex: iex = Conflict(*(ex.args)) self._on_command_error('get_resource_state', None, None, None, iex) raise iex except FSMCommandUnknownError as ex: iex = BadRequest(*(ex.args)) self._on_command_error('get_resource_state', None, None, None, iex) raise iex except IonException as iex: self._on_command_error('get_resource_state', None, None, None, iex) raise iex except Exception as ex: iex = ServerError(*(ex.args)) self._on_command_error('get_resource_state', None, None, None, iex) raise iex def execute_resource(self, resource_id='', command=None): """ """ if not command: iex = BadRequest('Execute argument "command" not set.') self._on_command_error('execute_resource', None, None, None, iex) raise iex # Grab command syntax. id = command.command_id cmd = command.command args = command.args or [] kwargs = command.kwargs or {} if not command.command: iex = BadRequest('Command name not set.') self._on_command_error('execute_resource', cmd, args, kwargs, iex) raise iex # Construct a command result object. cmd_result = IonObject("AgentCommandResult", command_id=id, command=cmd, ts_execute=get_ion_ts(), status=0) try: result = self._fsm.on_event( ResourceAgentEvent.EXECUTE_RESOURCE, cmd, *args, **kwargs) cmd_result.result = result self._on_command('execute_resource', cmd, args, kwargs, result) except FSMStateError as ex: iex = Conflict(*(ex.args)) self._on_command_error('execute_resource', cmd, args, kwargs, iex) raise iex except FSMCommandUnknownError as ex: iex = BadRequest(*(ex.args)) self._on_command_error('execute_resource', cmd, args, kwargs, iex) raise iex except IonException as iex: self._on_command_error('execute_resource', cmd, args, kwargs, iex) raise iex except Exception as ex: iex = ServerError(*(ex.args)) self._on_command_error('execute_resource', cmd, args, kwargs, iex) raise iex return cmd_result def ping_resource(self, resource_id=''): """ """ try: return self._fsm.on_event(ResourceAgentEvent.PING_RESOURCE) except FSMStateError as ex: iex = Conflict(*(ex.args)) self._on_command_error('ping_resource', None, None, None, iex) raise iex except FSMCommandUnknownError as ex: iex = BadRequest(*(ex.args)) self._on_command_error('ping_resource', None, None, None, iex) raise iex except IonException as iex: self._on_command_error('ping_resource', None, None, None, iex) raise iex except Exception as ex: iex = ServerError(*(ex.args)) self._on_command_error('ping_resource', None, None, None, iex) raise iex ############################################################## # UNINITIALIZED event handlers. ############################################################## def _handler_uninitialized_enter(self, *args, **kwargs): """ """ self._common_state_enter(*args, **kwargs) def _handler_uninitialized_exit(self, *args, **kwargs): """ """ self._common_state_exit(*args, **kwargs) ############################################################## # POWERED_DOWN event handlers. ############################################################## def _handler_powered_down_enter(self, *args, **kwargs): """ """ self._common_state_enter(*args, **kwargs) def _handler_powered_down_exit(self, *args, **kwargs): """ """ self._common_state_exit(*args, **kwargs) ############################################################## # INACTIVE event handlers. ############################################################## def _handler_inactive_enter(self, *args, **kwargs): """ """ self._common_state_enter(*args, **kwargs) def _handler_inactive_exit(self, *args, **kwargs): """ """ self._common_state_exit(*args, **kwargs) ############################################################## # IDLE event handlers. ############################################################## def _handler_idle_enter(self, *args, **kwargs): """ """ self._common_state_enter(*args, **kwargs) def _handler_idle_exit(self, *args, **kwargs): """ """ self._common_state_exit(*args, **kwargs) ############################################################## # STOPPED event handlers. ############################################################## def _handler_stopped_enter(self, *args, **kwargs): """ """ self._common_state_enter(*args, **kwargs) def _handler_stopped_exit(self, *args, **kwargs): """ """ self._common_state_exit(*args, **kwargs) ############################################################## # COMMAND event handlers. ############################################################## def _handler_command_enter(self, *args, **kwargs): """ """ self._common_state_enter(*args, **kwargs) def _handler_command_exit(self, *args, **kwargs): """ """ self._common_state_exit(*args, **kwargs) ############################################################## # STREAMING event handlers. ############################################################## def _handler_streaming_enter(self, *args, **kwargs): """ """ self._common_state_enter(*args, **kwargs) def _handler_streaming_exit(self, *args, **kwargs): """ """ self._common_state_exit(*args, **kwargs) ############################################################## # TEST event handlers. ############################################################## def _handler_test_enter(self, *args, **kwargs): """ """ self._common_state_enter(*args, **kwargs) def _handler_test_exit(self, *args, **kwargs): """ """ self._common_state_exit(*args, **kwargs) ############################################################## # CALIBRATE event handlers. ############################################################## def _handler_calibrate_enter(self, *args, **kwargs): """ """ self._common_state_enter(*args, **kwargs) def _handler_calibrate_exit(self, *args, **kwargs): """ """ self._common_state_exit(*args, **kwargs) ############################################################## # DIRECT_ACCESS event handlers. ############################################################## def _handler_direct_access_enter(self, *args, **kwargs): """ """ self._common_state_enter(*args, **kwargs) def _handler_direct_access_exit(self, *args, **kwargs): """ """ self._common_state_exit(*args, **kwargs) ############################################################## # BUSY event handlers. ############################################################## def _handler_busy_enter(self, *args, **kwargs): """ """ self._common_state_enter(*args, **kwargs) def _handler_busy_exit(self, *args, **kwargs): """ """ self._common_state_exit(*args, **kwargs) ############################################################## # Helpers. ############################################################## def _common_state_enter(self, *args, **kwargs): """ Common work upon every state entry. """ state = self._fsm.get_current_state() log.info('Resource agent %s publsihing state change: %s, time: %s', self.id, state, get_ion_ts()) event_data = { 'state' : state } self._event_publisher.publish_event(event_type='ResourceAgentStateEvent', origin=self.resource_id, **event_data) def _common_state_exit(self, *args, **kwargs): """ Common work upon every state exit. """ pass def _on_command(self, cmd, execute_cmd, args, kwargs, result): log.info('Resource agent %s publishing command event: \ cmd=%s, execute_cmd=%s, args=%s kwargs=%s time=%s', self.id, str(cmd), str(execute_cmd), str(args), str(kwargs), get_ion_ts()) event_data = { 'command': cmd, 'execute_command' : execute_cmd, 'args' : args, 'kwargs' : kwargs, 'result' : result } self._event_publisher.publish_event(event_type='ResourceAgentCommandEvent', origin=self.resource_id, **event_data) def _on_command_error(self, cmd, execute_cmd, args, kwargs, ex): log.info('Resource agent %s publishing command error event: \ cmd=%s, execute_cmd=%s, args=%s, kwargs=%s, \ errtype=%s, errmsg=%s, errno=%i, time=%s', self.id, str(cmd), str(execute_cmd), str(args), str(kwargs), str(type(ex)), ex.message, ex.status_code, get_ion_ts()) if hasattr(ex, 'status_code'): status_code = ex.status_code else: status_code = -1 event_data = { 'command': cmd, 'execute_command' : execute_cmd, 'args' : args, 'kwargs' : kwargs, 'error_type' : str(type(ex)), 'error_msg' : ex.message, 'error_code' : status_code } self._event_publisher.publish_event(event_type='ResourceAgentErrorEvent', origin=self.resource_id, **event_data) def _construct_fsm(self): """ Construct the state machine and register default handlers. Override in subclass to add handlers for resouce-dependent behaviors and state transitions. """ # Instrument agent state machine. self._fsm = InstrumentFSM(ResourceAgentState, ResourceAgentEvent, ResourceAgentEvent.ENTER, ResourceAgentEvent.EXIT) self._fsm.add_handler(ResourceAgentState.UNINITIALIZED, ResourceAgentEvent.ENTER, self._handler_uninitialized_enter) self._fsm.add_handler(ResourceAgentState.UNINITIALIZED, ResourceAgentEvent.EXIT, self._handler_uninitialized_exit) self._fsm.add_handler(ResourceAgentState.POWERED_DOWN, ResourceAgentEvent.ENTER, self._handler_powered_down_enter) self._fsm.add_handler(ResourceAgentState.POWERED_DOWN, ResourceAgentEvent.EXIT, self._handler_powered_down_exit) self._fsm.add_handler(ResourceAgentState.INACTIVE, ResourceAgentEvent.ENTER, self._handler_inactive_enter) self._fsm.add_handler(ResourceAgentState.INACTIVE, ResourceAgentEvent.EXIT, self._handler_inactive_exit) self._fsm.add_handler(ResourceAgentState.IDLE, ResourceAgentEvent.ENTER, self._handler_idle_enter) self._fsm.add_handler(ResourceAgentState.IDLE, ResourceAgentEvent.EXIT, self._handler_idle_exit) self._fsm.add_handler(ResourceAgentState.STOPPED, ResourceAgentEvent.ENTER, self._handler_stopped_enter) self._fsm.add_handler(ResourceAgentState.STOPPED, ResourceAgentEvent.EXIT, self._handler_stopped_exit) self._fsm.add_handler(ResourceAgentState.COMMAND, ResourceAgentEvent.ENTER, self._handler_command_enter) self._fsm.add_handler(ResourceAgentState.COMMAND, ResourceAgentEvent.EXIT, self._handler_command_exit) self._fsm.add_handler(ResourceAgentState.STREAMING, ResourceAgentEvent.ENTER, self._handler_streaming_enter) self._fsm.add_handler(ResourceAgentState.STREAMING, ResourceAgentEvent.EXIT, self._handler_streaming_exit) self._fsm.add_handler(ResourceAgentState.TEST, ResourceAgentEvent.ENTER, self._handler_test_enter) self._fsm.add_handler(ResourceAgentState.TEST, ResourceAgentEvent.EXIT, self._handler_test_exit) self._fsm.add_handler(ResourceAgentState.CALIBRATE, ResourceAgentEvent.ENTER, self._handler_calibrate_enter) self._fsm.add_handler(ResourceAgentState.CALIBRATE, ResourceAgentEvent.EXIT, self._handler_calibrate_exit) self._fsm.add_handler(ResourceAgentState.DIRECT_ACCESS, ResourceAgentEvent.ENTER, self._handler_direct_access_enter) self._fsm.add_handler(ResourceAgentState.DIRECT_ACCESS, ResourceAgentEvent.EXIT, self._handler_direct_access_exit) self._fsm.add_handler(ResourceAgentState.BUSY, ResourceAgentEvent.ENTER, self._handler_busy_enter) self._fsm.add_handler(ResourceAgentState.BUSY, ResourceAgentEvent.EXIT, self._handler_busy_exit)
class SBE37Protocol(CommandResponseInstrumentProtocol): """ Instrument protocol class for SBE37 driver. Subclasses CommandResponseInstrumentProtocol """ def __init__(self, prompts, newline, driver_event): """ SBE37Protocol constructor. @param prompts A BaseEnum class containing instrument prompts. @param newline The SBE37 newline. @param driver_event Driver process event callback. """ # Construct protocol superclass. CommandResponseInstrumentProtocol.__init__(self, prompts, newline, driver_event) # Build SBE37 protocol state machine. self._protocol_fsm = InstrumentFSM(SBE37ProtocolState, SBE37ProtocolEvent, SBE37ProtocolEvent.ENTER, SBE37ProtocolEvent.EXIT) # Add event handlers for protocol state machine. self._protocol_fsm.add_handler(SBE37ProtocolState.UNKNOWN, SBE37ProtocolEvent.ENTER, self._handler_unknown_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.UNKNOWN, SBE37ProtocolEvent.EXIT, self._handler_unknown_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.UNKNOWN, SBE37ProtocolEvent.DISCOVER, self._handler_unknown_discover) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.ENTER, self._handler_command_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.EXIT, self._handler_command_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.ACQUIRE_SAMPLE, self._handler_command_acquire_sample) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.START_AUTOSAMPLE, self._handler_command_start_autosample) self._protocol_fsm.add_handler( SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.SET, self._handler_command_set) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.TEST, self._handler_command_test) self._protocol_fsm.add_handler(SBE37ProtocolState.COMMAND, SBE37ProtocolEvent.START_DIRECT, self._handler_command_start_direct) self._protocol_fsm.add_handler(SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.ENTER, self._handler_autosample_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.EXIT, self._handler_autosample_exit) self._protocol_fsm.add_handler( SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler( SBE37ProtocolState.AUTOSAMPLE, SBE37ProtocolEvent.STOP_AUTOSAMPLE, self._handler_autosample_stop_autosample) self._protocol_fsm.add_handler(SBE37ProtocolState.TEST, SBE37ProtocolEvent.ENTER, self._handler_test_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.TEST, SBE37ProtocolEvent.EXIT, self._handler_test_exit) self._protocol_fsm.add_handler(SBE37ProtocolState.TEST, SBE37ProtocolEvent.RUN_TEST, self._handler_test_run_tests) self._protocol_fsm.add_handler( SBE37ProtocolState.TEST, SBE37ProtocolEvent.GET, self._handler_command_autosample_test_get) self._protocol_fsm.add_handler(SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.ENTER, self._handler_direct_access_enter) self._protocol_fsm.add_handler(SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.EXIT, self._handler_direct_access_exit) self._protocol_fsm.add_handler( SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.EXECUTE_DIRECT, self._handler_direct_access_execute_direct) self._protocol_fsm.add_handler(SBE37ProtocolState.DIRECT_ACCESS, SBE37ProtocolEvent.STOP_DIRECT, self._handler_direct_access_stop_direct) # Construct the parameter dictionary containing device parameters, # current parameter values, and set formatting functions. self._build_param_dict() # Add build handlers for device commands. self._add_build_handler('ds', self._build_simple_command) self._add_build_handler('dc', self._build_simple_command) self._add_build_handler('ts', self._build_simple_command) self._add_build_handler('startnow', self._build_simple_command) self._add_build_handler('stop', self._build_simple_command) self._add_build_handler('tc', self._build_simple_command) self._add_build_handler('tt', self._build_simple_command) self._add_build_handler('tp', self._build_simple_command) self._add_build_handler('set', self._build_set_command) # Add response handlers for device commands. self._add_response_handler('ds', self._parse_dsdc_response) self._add_response_handler('dc', self._parse_dsdc_response) self._add_response_handler('ts', self._parse_ts_response) self._add_response_handler('set', self._parse_set_response) self._add_response_handler('tc', self._parse_test_response) self._add_response_handler('tt', self._parse_test_response) self._add_response_handler('tp', self._parse_test_response) # Add sample handlers. self._sample_pattern = r'^#? *(-?\d+\.\d+), *(-?\d+\.\d+), *(-?\d+\.\d+)' self._sample_pattern += r'(, *(-?\d+\.\d+))?(, *(-?\d+\.\d+))?' self._sample_pattern += r'(, *(\d+) +([a-zA-Z]+) +(\d+), *(\d+):(\d+):(\d+))?' self._sample_pattern += r'(, *(\d+)-(\d+)-(\d+), *(\d+):(\d+):(\d+))?' self._sample_regex = re.compile(self._sample_pattern) # State state machine in UNKNOWN state. self._protocol_fsm.start(SBE37ProtocolState.UNKNOWN) # commands sent sent to device to be filtered in responses for telnet DA self._sent_cmds = [] ######################################################################## # Unknown handlers. ######################################################################## def _handler_unknown_enter(self, *args, **kwargs): """ Enter unknown state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_unknown_exit(self, *args, **kwargs): """ Exit unknown state. """ pass def _handler_unknown_discover(self, *args, **kwargs): """ Discover current state; can be COMMAND or AUTOSAMPLE. @retval (next_state, result), (SBE37ProtocolState.COMMAND or SBE37State.AUTOSAMPLE, None) if successful. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentStateException if the device response does not correspond to an expected state. """ next_state = None result = None # Wakeup the device with timeout if passed. timeout = kwargs.get('timeout', SBE37_TIMEOUT) prompt = self._wakeup(timeout) prompt = self._wakeup(timeout) # Set the state to change. # Raise if the prompt returned does not match command or autosample. if prompt == SBE37Prompt.COMMAND: next_state = SBE37ProtocolState.COMMAND result = SBE37ProtocolState.COMMAND elif prompt == SBE37Prompt.AUTOSAMPLE: next_state = SBE37ProtocolState.AUTOSAMPLE result = SBE37ProtocolState.AUTOSAMPLE else: raise InstrumentStateException('Unknown state.') return (next_state, result) ######################################################################## # Command handlers. ######################################################################## def _handler_command_enter(self, *args, **kwargs): """ Enter command state. @throws InstrumentTimeoutException if the device cannot be woken. @throws InstrumentProtocolException if the update commands and not recognized. """ # Command device to update parameters and send a config change event. self._update_params() # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_command_exit(self, *args, **kwargs): """ Exit command state. """ pass def _handler_command_set(self, *args, **kwargs): """ Perform a set command. @param args[0] parameter : value dict. @retval (next_state, result) tuple, (None, None). @throws InstrumentParameterException if missing set parameters, if set parameters not ALL and not a dict, or if paramter can't be properly formatted. @throws InstrumentTimeoutException if device cannot be woken for set command. @throws InstrumentProtocolException if set command could not be built or misunderstood. """ next_state = None result = None # Retrieve required parameter. # Raise if no parameter provided, or not a dict. try: params = args[0] except IndexError: raise InstrumentParameterException( 'Set command requires a parameter dict.') if not isinstance(params, dict): raise InstrumentParameterException('Set parameters not a dict.') # For each key, val in the dict, issue set command to device. # Raise if the command not understood. else: for (key, val) in params.iteritems(): result = self._do_cmd_resp('set', key, val, **kwargs) self._update_params() return (next_state, result) def _handler_command_acquire_sample(self, *args, **kwargs): """ Acquire sample from SBE37. @retval (next_state, result) tuple, (None, sample dict). @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command could not be built or misunderstood. @throws SampleException if a sample could not be extracted from result. """ next_state = None result = None result = self._do_cmd_resp('ts', *args, **kwargs) return (next_state, result) def _handler_command_start_autosample(self, *args, **kwargs): """ Switch into autosample mode. @retval (next_state, result) tuple, (SBE37ProtocolState.AUTOSAMPLE, None) if successful. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command could not be built or misunderstood. """ next_state = None result = None # Assure the device is transmitting. if not self._param_dict.get(SBE37Parameter.TXREALTIME): self._do_cmd_resp('set', SBE37Parameter.TXREALTIME, True, **kwargs) # Issue start command and switch to autosample if successful. self._do_cmd_no_resp('startnow', *args, **kwargs) next_state = SBE37ProtocolState.AUTOSAMPLE return (next_state, result) def _handler_command_test(self, *args, **kwargs): """ Switch to test state to perform instrument tests. @retval (next_state, result) tuple, (SBE37ProtocolState.TEST, None). """ next_state = None result = None next_state = SBE37ProtocolState.TEST return (next_state, result) def _handler_command_start_direct(self): """ """ next_state = None result = None next_state = SBE37ProtocolState.DIRECT_ACCESS return (next_state, result) ######################################################################## # Autosample handlers. ######################################################################## def _handler_autosample_enter(self, *args, **kwargs): """ Enter autosample state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) def _handler_autosample_exit(self, *args, **kwargs): """ Exit autosample state. """ pass def _handler_autosample_stop_autosample(self, *args, **kwargs): """ Stop autosample and switch back to command mode. @retval (next_state, result) tuple, (SBE37ProtocolState.COMMAND, None) if successful. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command misunderstood or incorrect prompt received. """ next_state = None result = None # Wake up the device, continuing until autosample prompt seen. timeout = kwargs.get('timeout', SBE37_TIMEOUT) self._wakeup_until(timeout, SBE37Prompt.AUTOSAMPLE) # Issue the stop command. self._do_cmd_resp('stop', *args, **kwargs) # Prompt device until command prompt is seen. self._wakeup_until(timeout, SBE37Prompt.COMMAND) next_state = SBE37ProtocolState.COMMAND return (next_state, result) ######################################################################## # Common handlers. ######################################################################## def _handler_command_autosample_test_get(self, *args, **kwargs): """ Get device parameters from the parameter dict. @param args[0] list of parameters to retrieve, or DriverParameter.ALL. @throws InstrumentParameterException if missing or invalid parameter. """ next_state = None result = None # Retrieve the required parameter, raise if not present. try: params = args[0] except IndexError: raise InstrumentParameterException( 'Get command requires a parameter list or tuple.') # If all params requested, retrieve config. if params == DriverParameter.ALL: result = self._param_dict.get_config() # If not all params, confirm a list or tuple of params to retrieve. # Raise if not a list or tuple. # Retireve each key in the list, raise if any are invalid. else: if not isinstance(params, (list, tuple)): raise InstrumentParameterException( 'Get argument not a list or tuple.') result = {} for key in params: try: val = self._param_dict.get(key) result[key] = val except KeyError: raise InstrumentParameterException( ('%s is not a valid parameter.' % key)) return (next_state, result) ######################################################################## # Test handlers. ######################################################################## def _handler_test_enter(self, *args, **kwargs): """ Enter test state. Setup the secondary call to run the tests. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) # Forward the test event again to run the test handler and # switch back to command mode afterward. Timer(1, lambda: self._protocol_fsm.on_event(SBE37ProtocolEvent. RUN_TEST)).start() def _handler_test_exit(self, *args, **kwargs): """ Exit test state. """ pass def _handler_test_run_tests(self, *args, **kwargs): """ Run test routines and validate results. @throws InstrumentTimeoutException if device cannot be woken for command. @throws InstrumentProtocolException if command misunderstood or incorrect prompt received. """ next_state = None result = None tc_pass = False tt_pass = False tp_pass = False tc_result = None tt_result = None tp_result = None test_result = {} try: tc_pass, tc_result = self._do_cmd_resp('tc', timeout=200) tt_pass, tt_result = self._do_cmd_resp('tt', timeout=200) tp_pass, tp_result = self._do_cmd_resp('tp', timeout=200) except Exception as e: test_result['exception'] = e test_result['message'] = 'Error running instrument tests.' finally: test_result['cond_test'] = 'Passed' if tc_pass else 'Failed' test_result['cond_data'] = tc_result test_result['temp_test'] = 'Passed' if tt_pass else 'Failed' test_result['temp_data'] = tt_result test_result['pres_test'] = 'Passed' if tp_pass else 'Failed' test_result['pres_data'] = tp_result test_result['success'] = 'Passed' if (tc_pass and tt_pass and tp_pass) else 'Failed' self._driver_event(DriverAsyncEvent.TEST_RESULT, test_result) next_state = SBE37ProtocolState.COMMAND return (next_state, result) ######################################################################## # Direct access handlers. ######################################################################## def _handler_direct_access_enter(self, *args, **kwargs): """ Enter direct access state. """ # Tell driver superclass to send a state change event. # Superclass will query the state. self._driver_event(DriverAsyncEvent.STATE_CHANGE) self._sent_cmds = [] def _handler_direct_access_exit(self, *args, **kwargs): """ Exit direct access state. """ pass def _handler_direct_access_execute_direct(self, data): """ """ next_state = None result = None self._do_cmd_direct(data) # add sent command to list for 'echo' filtering in callback self._sent_cmds.append(data) return (next_state, result) def _handler_direct_access_stop_direct(self): """ @throw InstrumentProtocolException on invalid command """ next_state = None result = None next_state = SBE37ProtocolState.COMMAND return (next_state, result) ######################################################################## # Private helpers. ######################################################################## def _send_wakeup(self): """ Send a newline to attempt to wake the SBE37 device. """ self._connection.send(SBE37_NEWLINE) def _update_params(self, *args, **kwargs): """ Update the parameter dictionary. Wake the device then issue display status and display calibration commands. The parameter dict will match line output and udpate itself. @throws InstrumentTimeoutException if device cannot be timely woken. @throws InstrumentProtocolException if ds/dc misunderstood. """ # Get old param dict config. old_config = self._param_dict.get_config() # Issue display commands and parse results. timeout = kwargs.get('timeout', SBE37_TIMEOUT) self._do_cmd_resp('ds', timeout=timeout) self._do_cmd_resp('dc', timeout=timeout) # Get new param dict config. If it differs from the old config, # tell driver superclass to publish a config change event. new_config = self._param_dict.get_config() if new_config != old_config: self._driver_event(DriverAsyncEvent.CONFIG_CHANGE) def _build_simple_command(self, cmd): """ Build handler for basic SBE37 commands. @param cmd the simple sbe37 command to format. @retval The command to be sent to the device. """ return cmd + SBE37_NEWLINE def _build_set_command(self, cmd, param, val): """ Build handler for set commands. param=val followed by newline. String val constructed by param dict formatting function. @param param the parameter key to set. @param val the parameter value to set. @ retval The set command to be sent to the device. @throws InstrumentProtocolException if the parameter is not valid or if the formatting function could not accept the value passed. """ try: str_val = self._param_dict.format(param, val) set_cmd = '%s=%s' % (param, str_val) set_cmd = set_cmd + SBE37_NEWLINE except KeyError: raise InstrumentParameterException('Unknown driver parameter %s' % param) return set_cmd def _parse_set_response(self, response, prompt): """ Parse handler for set command. @param response command response string. @param prompt prompt following command response. @throws InstrumentProtocolException if set command misunderstood. """ if prompt != SBE37Prompt.COMMAND: raise InstrumentProtocolException( 'Set command not recognized: %s' % response) def _parse_dsdc_response(self, response, prompt): """ Parse handler for dsdc commands. @param response command response string. @param prompt prompt following command response. @throws InstrumentProtocolException if dsdc command misunderstood. """ if prompt != SBE37Prompt.COMMAND: raise InstrumentProtocolException( 'dsdc command not recognized: %s.' % response) for line in response.split(SBE37_NEWLINE): self._param_dict.update(line) def _parse_ts_response(self, response, prompt): """ Response handler for ts command. @param response command response string. @param prompt prompt following command response. @retval sample dictionary containig c, t, d values. @throws InstrumentProtocolException if ts command misunderstood. @throws InstrumentSampleException if response did not contain a sample """ if prompt != SBE37Prompt.COMMAND: raise InstrumentProtocolException('ts command not recognized: %s', response) sample = None for line in response.split(SBE37_NEWLINE): sample = self._extract_sample(line, True) if sample: break if not sample: raise SampleException('Response did not contain sample: %s' % repr(response)) return sample def _parse_test_response(self, response, prompt): """ Do minimal checking of test outputs. @param response command response string. @param promnpt prompt following command response. @retval tuple of pass/fail boolean followed by response """ success = False lines = response.split() if len(lines) > 2: data = lines[1:-1] bad_count = 0 for item in data: try: float(item) except ValueError: bad_count += 1 if bad_count == 0: success = True return (success, response) def got_data(self, data): """ Callback for receiving new data from the device. """ if self.get_current_state() == SBE37ProtocolState.DIRECT_ACCESS: # direct access mode if len(data) > 0: mi_logger.debug("SBE37Protocol._got_data(): <" + data + ">") if self._driver_event: self._driver_event(DriverAsyncEvent.DIRECT_ACCESS, data) # TODO: what about logging this as an event? return if len(data) > 0: # Call the superclass to update line and prompt buffers. CommandResponseInstrumentProtocol.got_data(self, data) # If in streaming mode, process the buffer for samples to publish. cur_state = self.get_current_state() if cur_state == SBE37ProtocolState.AUTOSAMPLE: if SBE37_NEWLINE in self._linebuf: lines = self._linebuf.split(SBE37_NEWLINE) self._linebuf = lines[-1] for line in lines: self._extract_sample(line) def _extract_sample(self, line, publish=True): """ Extract sample from a response line if present and publish to agent. @param line string to match for sample. @param publsih boolean to publish sample (default True). @retval Sample dictionary if present or None. """ sample = None match = self._sample_regex.match(line) if match: sample = {} sample['t'] = [float(match.group(1))] sample['c'] = [float(match.group(2))] sample['p'] = [float(match.group(3))] # Driver timestamp. sample['time'] = [time.time()] sample['stream_name'] = 'ctd_parsed' if self._driver_event: self._driver_event(DriverAsyncEvent.SAMPLE, sample) return sample def _build_param_dict(self): """ Populate the parameter dictionary with SBE37 parameters. For each parameter key, add match stirng, match lambda function, and value formatting function for set commands. """ # Add parameter handlers to parameter dict. self._param_dict.add(SBE37Parameter.OUTPUTSAL, r'(do not )?output salinity with each sample', lambda match: False if match.group(1) else True, self._true_false_to_string) self._param_dict.add( SBE37Parameter.OUTPUTSV, r'(do not )?output sound velocity with each sample', lambda match: False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE37Parameter.NAVG, r'number of samples to average = (\d+)', lambda match: int(match.group(1)), self._int_to_string) self._param_dict.add(SBE37Parameter.SAMPLENUM, r'samplenumber = (\d+), free = \d+', lambda match: int(match.group(1)), self._int_to_string) self._param_dict.add(SBE37Parameter.INTERVAL, r'sample interval = (\d+) seconds', lambda match: int(match.group(1)), self._int_to_string) self._param_dict.add(SBE37Parameter.STORETIME, r'(do not )?store time with each sample', lambda match: False if match.group(1) else True, self._true_false_to_string) self._param_dict.add(SBE37Parameter.TXREALTIME, r'(do not )?transmit real-time data', lambda match: False if match.group(1) else True, self._true_false_to_string) self._param_dict.add( SBE37Parameter.SYNCMODE, r'serial sync mode (enabled|disabled)', lambda match: False if (match.group(1) == 'disabled') else True, self._true_false_to_string) self._param_dict.add( SBE37Parameter.SYNCWAIT, r'wait time after serial sync sampling = (\d+) seconds', lambda match: int(match.group(1)), self._int_to_string) self._param_dict.add( SBE37Parameter.TCALDATE, r'temperature: +((\d+)-([a-zA-Z]+)-(\d+))', lambda match: self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE37Parameter.TA0, r' +TA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.TA1, r' +TA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.TA2, r' +TA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.TA3, r' +TA3 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add( SBE37Parameter.CCALDATE, r'conductivity: +((\d+)-([a-zA-Z]+)-(\d+))', lambda match: self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE37Parameter.CG, r' +G = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.CH, r' +H = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.CI, r' +I = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.CJ, r' +J = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.WBOTC, r' +WBOTC = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.CTCOR, r' +CTCOR = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.CPCOR, r' +CPCOR = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add( SBE37Parameter.PCALDATE, r'pressure .+ ((\d+)-([a-zA-Z]+)-(\d+))', lambda match: self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE37Parameter.PA0, r' +PA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PA1, r' +PA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PA2, r' +PA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCA0, r' +PTCA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCA1, r' +PTCA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCA2, r' +PTCA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCB0, r' +PTCSB0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCB1, r' +PTCSB1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.PTCB2, r' +PTCSB2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.POFFSET, r' +POFFSET = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add( SBE37Parameter.RCALDATE, r'rtc: +((\d+)-([a-zA-Z]+)-(\d+))', lambda match: self._string_to_date(match.group(1), '%d-%b-%y'), self._date_to_string) self._param_dict.add(SBE37Parameter.RTCA0, r' +RTCA0 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.RTCA1, r' +RTCA1 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) self._param_dict.add(SBE37Parameter.RTCA2, r' +RTCA2 = (-?\d.\d\d\d\d\d\de[-+]\d\d)', lambda match: float(match.group(1)), self._float_to_string) ######################################################################## # Static helpers to format set commands. ######################################################################## @staticmethod def _true_false_to_string(v): """ Write a boolean value to string formatted for sbe37 set operations. @param v a boolean value. @retval A yes/no string formatted for sbe37 set operations. @throws InstrumentParameterException if value not a bool. """ if not isinstance(v, bool): raise InstrumentParameterException('Value %s is not a bool.' % str(v)) if v: return 'y' else: return 'n' @staticmethod def _int_to_string(v): """ Write an int value to string formatted for sbe37 set operations. @param v An int val. @retval an int string formatted for sbe37 set operations. @throws InstrumentParameterException if value not an int. """ if not isinstance(v, int): raise InstrumentParameterException('Value %s is not an int.' % str(v)) else: return '%i' % v @staticmethod def _float_to_string(v): """ Write a float value to string formatted for sbe37 set operations. @param v A float val. @retval a float string formatted for sbe37 set operations. @throws InstrumentParameterException if value is not a float. """ if not isinstance(v, float): raise InstrumentParameterException('Value %s is not a float.' % v) else: return '%e' % v @staticmethod def _date_to_string(v): """ Write a date tuple to string formatted for sbe37 set operations. @param v a date tuple: (day,month,year). @retval A date string formatted for sbe37 set operations. @throws InstrumentParameterException if date tuple is not valid. """ if not isinstance(v, (list, tuple)): raise InstrumentParameterException( 'Value %s is not a list, tuple.' % str(v)) if not len(v) == 3: raise InstrumentParameterException('Value %s is not length 3.' % str(v)) months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] day = v[0] month = v[1] year = v[2] if len(str(year)) > 2: year = int(str(year)[-2:]) if not isinstance(day, int) or day < 1 or day > 31: raise InstrumentParameterException( 'Value %s is not a day of month.' % str(day)) if not isinstance(month, int) or month < 1 or month > 12: raise InstrumentParameterException('Value %s is not a month.' % str(month)) if not isinstance(year, int) or year < 0 or year > 99: raise InstrumentParameterException('Value %s is not a 0-99 year.' % str(year)) return '%02i-%s-%02i' % (day, months[month - 1], year) @staticmethod def _string_to_date(datestr, fmt): """ Extract a date tuple from an sbe37 date string. @param str a string containing date information in sbe37 format. @retval a date tuple. @throws InstrumentParameterException if datestr cannot be formatted to a date. """ if not isinstance(datestr, str): raise InstrumentParameterException('Value %s is not a string.' % str(datestr)) try: date_time = time.strptime(datestr, fmt) date = (date_time[2], date_time[1], date_time[0]) except ValueError: raise InstrumentParameterException( 'Value %s could not be formatted to a date.' % str(datestr)) return date