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 Parameter error if missing or invalid param dict.
        """
        next_state = None
        result = None

        # Get required config param dict.
        try:
            config = args[0]

        except IndexError:
            raise ParameterError("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 ParameterError("Invalid comms config dict.")

        except (TypeError, KeyError):
            raise ParameterError("Invalid comms config dict.")

        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 Parameter error if missing or invalid param dict.        
        """
        next_state = None
        result = None

        # Get the required param dict.
        try:
            config = args[0]

        except IndexError:
            raise ParameterError("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 ParameterError("Invalid comms config dict.")

        except (TypeError, KeyError):
            raise ParameterError("Invalid comms config dict.")

        return (next_state, result)
Esempio n. 3
0
    def configure(self, config, *args, **kwargs):
        """
        """
        mi_logger.info('Configuring for device comms.')        

        method = config['method']
                
        if method == 'ethernet':
            device_addr = config['device_addr']
            device_port = config['device_port']
            server_addr = config['server_addr']
            server_port = config['server_port']
            self._logger = EthernetDeviceLogger(device_addr, device_port,
                                            server_port)
            self._logger_client = LoggerClient(server_addr, server_port)

        elif method == 'serial':
            # The config dict does not have a valid connection method.
            raise InstrumentConnectionException()
                
        else:
            # The config dict does not have a valid connection method.
            raise InstrumentConnectionException()
class SingleConnectionInstrumentDriver(InstrumentDriver):
    """
    Base class for instrument drivers with a single device connection.
    Provides connenction state logic for single connection drivers.
    """

    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 StateError 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 StateError if command not allowed in current state        
        @throws ParameterError 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 StateError if command not allowed in current state
        @throws ConnectionError 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 StateError 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 TimeoutError if could not wake device.
        @raises StateError if command not allowed in current state or if
        device state not recognized.
        @raises NotImplementedError 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 ParameterError if missing or invalid get parameters.
        @raises StateError if command not allowed in current state
        @raises NotImplementedError 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 ParameterError if missing or invalid set parameters.
        @riases TimeoutError if could not wake device or no response.
        @raises ProtocolError if set command not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError 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.
        @riases TimeoutError if could not wake device or no response.
        @raises ProtocolError if acquire command not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError 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.        
        @riases TimeoutError if could not wake device or no response.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError 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.        
        @riases TimeoutError if could not wake device or no response.
        @raises ProtocolError if stop command not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError 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).
        @riases TimeoutError if could not wake device or no response.
        @raises ProtocolError if test commands not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError 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).
        @riases TimeoutError if could not wake device or no response.
        @raises ProtocolError if test commands not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError 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)

    def execute_start_direct_access(self, *args, **kwargs):
        """
        Switch to direct access mode.
        @raises TimeoutError if could not wake device or no response.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                
        """
        return self._connection_fsm.on_event(DriverEvent.START_DIRECT, DriverEvent.START_DIRECT, *args, **kwargs)

    def execute_direct_access(self, *args, **kwargs):
        """
        output direct access data to device.
        @raises TimeoutError if could not wake device or no response.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                
        """
        return self._connection_fsm.on_event(DriverEvent.EXECUTE_DIRECT, DriverEvent.EXECUTE_DIRECT, *args, **kwargs)

    def execute_stop_direct_access(self, *args, **kwargs):
        """
        Leave direct access mode.
        @raises TimeoutError if could not wake device or no response.
        @raises ProtocolError if stop command not recognized.
        @raises StateError if command not allowed in current state.
        @raises NotImplementedError if not implemented by subclass.                
        """
        return self._connection_fsm.on_event(DriverEvent.STOP_DIRECT, DriverEvent.STOP_DIRECT, *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 Parameter error if missing or invalid param dict.        
        """
        next_state = None
        result = None

        # Get the required param dict.
        try:
            config = args[0]

        except IndexError:
            raise ParameterError("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 ParameterError("Invalid comms config dict.")

        except (TypeError, KeyError):
            raise ParameterError("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 Parameter error if missing or invalid param dict.
        """
        next_state = None
        result = None

        # Get required config param dict.
        try:
            config = args[0]

        except IndexError:
            raise ParameterError("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 ParameterError("Invalid comms config dict.")

        except (TypeError, KeyError):
            raise ParameterError("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 ConnectionError 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
Esempio n. 5
0
class InstrumentProtocol(object):
    """The base class for an instrument protocol
    
    The classes derived from this class will carry out the specific
    interactions between a specific device and the instrument driver. At this
    layer of interaction, there are no conflicts or transactions as that is
    handled at the layer above this. Think of this as encapsulating the
    transport layer of the communications.
    """
    
    implements(IInstrumentConnection)
    
    def __init__(self, evt_callback=None):
        """Set instrument connect at creation
        
        @param connection An InstrumetnConnection object
        """
        self._logger = None
        self._logger_client = None
        self._logger_popen = None
        self._fsm = None
        
        self.send_event = evt_callback
        """The driver callback where we an publish events. Should be a link
        to a function. Currently a dict with keys in EventKey enum."""
        
    ########################################################################
    # Protocol connection interface.
    ########################################################################

    """
    @todo Move this into the driver state machine?
    """
    
    def initialize(self, *args, **kwargs):
        """
        """
        mi_logger.info('Initializing device comms.')        
        self._logger = None
        self._logger_client = None
    
    def configure(self, config, *args, **kwargs):
        """
        """
        mi_logger.info('Configuring for device comms.')        

        method = config['method']
                
        if method == 'ethernet':
            device_addr = config['device_addr']
            device_port = config['device_port']
            server_addr = config['server_addr']
            server_port = config['server_port']
            self._logger = EthernetDeviceLogger(device_addr, device_port,
                                            server_port)
            self._logger_client = LoggerClient(server_addr, server_port)

        elif method == 'serial':
            # The config dict does not have a valid connection method.
            raise InstrumentConnectionException()
                
        else:
            # The config dict does not have a valid connection method.
            raise InstrumentConnectionException()
    
    def connect(self, *args, **kwargs):
        """Connect via the instrument connection object
        
        @param args connection arguments
        @throws InstrumentConnectionException
        """
        mi_logger.info('Connecting to device.')
        
        logger_pid = self._logger.get_pid()
        mi_logger.info('Found logger pid: %s.', str(logger_pid))
        if not logger_pid:
            self._logger_popen = self._logger.launch_process()
            time.sleep(0.2)
            try:
                retval = os.wait()
                mi_logger.debug('os.wait returned %s' % str(retval))
            except Exception as e:
                mi_logger.debug('os.wait() threw %s: %s' %
                               (e.__class__.__name__, str(e)))
            mi_logger.debug('popen wait returned %s', str(self._logger_popen.wait()))
            time.sleep(1)         
            self.attach()
        else:
            # There was a pidfile for the device.
            raise InstrumentConnectionException()

        return logger_pid
        
    def disconnect(self, *args, **kwargs):
        """Disconnect via the instrument connection object
        
        @throws InstrumentConnectionException
        """
        mi_logger.info('Disconnecting from device.')
        self.detach()
        self._logger.stop()
    
    def attach(self, *args, **kwargs):
        """
        """
        mi_logger.info('Attaching to device.')        
        self._logger_client.init_comms(self._got_data)
    
    def detach(self, *args, **kwargs):
        """
        """
        mi_logger.info('Detaching from device.')
        self._logger_client.stop_comms()
        
    def reset(self, *args, **kwargs):
        """Reset via the instrument connection object"""
        # Call logger reset here.
        pass
        
    ########################################################################
    # Protocol command interface.
    ########################################################################
        
    def get(self, *args, **kwargs):
        """Get some parameters
        
        @param params A list of parameters to fetch. These must be in the
        fetchable parameter list
        @retval results A dict of the parameters that were queried
        @throws InstrumentProtocolException Confusion dealing with the
        physical device
        @throws InstrumentStateException Unable to handle current or future
        state properly
        @throws InstrumentTimeoutException Timeout
        """
        pass
    
    def set(self, *args, **kwargs):
        """Get some parameters
        
        @throws InstrumentProtocolException Confusion dealing with the
        physical device
        @throws InstrumentStateException Unable to handle current or future
        state properly
        @throws InstrumentTimeoutException Timeout
        """
        pass

    def execute(self, *args, **kwargs):
        """Execute a command
        
        @param command A single command as a list with the command ID followed
        by the parameters for that command
        @throws InstrumentProtocolException Confusion dealing with the
        physical device
        @throws InstrumentStateException Unable to handle current or future
        state properly
        @throws InstrumentTimeoutException Timeout
        """
        pass
    
    def execute_direct(self, *args, **kwargs):
        """
        """
        pass
            
    ########################################################################
    # TBD.
    ########################################################################
    
    
    def get_capabilities(self):
        """
        """
        pass

    ########################################################################
    # Helper methods
    ########################################################################
    def _got_data(self, data):
       """
       Called by the logger whenever there is data available
       """
       pass

    def announce_to_driver(self, type, error_code=None, msg=None):
        """
        Announce an event to the driver via the callback
        
        @param type The DriverAnnouncement enum type of the event
        @param args Any arguments involved
        @param msg A message to be included
        @todo Clean this up, promote to InstrumentProtocol?
        """
        assert type != None
        event = {EventKey:type}
        
        if error_code:
            event.update({EventKey.ERROR_CODE:error_code})
        if msg:
            event.update({EventKey.MESSAGE:msg})
            
        self.send_event(event)