Exemple #1
0
    def __init__(self, server_url, autoconnect=True):
        # Catch invalid server_url parameters
        if not isinstance(server_url, basestring):
            raise ValueError("Expected string for SeedLink server URL")
        # Allow for sloppy server URLs (e.g. 'geofon.gfz-potsdam.de:18000).
        # (According to RFC 1808 the net_path segment needs to start with '//'
        # and this is expected by the urlparse function, so it is silently
        # added if it was omitted by the user.)
        if "://" not in server_url and not server_url.startswith("//"):
            server_url = "//" + server_url

        parsed_url = urlparse.urlparse(server_url, scheme="seedlink")

        # Check the provided scheme
        if not parsed_url.scheme == "seedlink":
            msg = 'Unsupported scheme %s (expected "seedlink")' % parsed_url.scheme
            raise EasySeedLinkClientException(msg)
        if not parsed_url.hostname:
            msg = "No host name provided"
            raise EasySeedLinkClientException(msg)

        self.server_hostname = parsed_url.hostname
        self.server_port = parsed_url.port or 18000

        self.conn = SeedLinkConnection()
        self.conn.setSLAddress("%s:%d" % (self.server_hostname, self.server_port))

        if autoconnect:
            self.connect()

        # A flag to indicate if the client has entered streaming mode
        self.__streaming_started = False

        self.__capabilities = None
Exemple #2
0
    def __init__(self, loglevel='DEBUG'):
        """
        Creates a new instance of SLClient with the specified logging object
        """
        numeric_level = getattr(logging, loglevel.upper(), None)
        if not isinstance(numeric_level, int):
            raise ValueError('Invalid log level: %s' % loglevel)
        logging.basicConfig(level=numeric_level)

        self.slconn = None
        self.verbose = 0
        self.ppackets = False
        self.streamfile = None
        self.selectors = None
        self.multiselect = None
        self.statefile = None
        self.begin_time = None
        self.end_time = None
        self.infolevel = None
        self.slconn = SeedLinkConnection()
Exemple #3
0
    def __init__(self, server_url, autoconnect=True):
        # Catch invalid server_url parameters
        if not isinstance(server_url, basestring):
            raise ValueError('Expected string for SeedLink server URL')
        # Allow for sloppy server URLs (e.g. 'geofon.gfz-potsdam.de:18000).
        # (According to RFC 1808 the net_path segment needs to start with '//'
        # and this is expected by the urlparse function, so it is silently
        # added if it was omitted by the user.)
        if '://' not in server_url and not server_url.startswith('//'):
            server_url = '//' + server_url

        parsed_url = urlparse.urlparse(server_url, scheme='seedlink')

        # Check the provided scheme
        if not parsed_url.scheme == 'seedlink':
            msg = 'Unsupported scheme %s (expected "seedlink")' % \
                  parsed_url.scheme
            raise EasySeedLinkClientException(msg)
        if not parsed_url.hostname:
            msg = 'No host name provided'
            raise EasySeedLinkClientException(msg)

        self.server_hostname = parsed_url.hostname
        self.server_port = parsed_url.port or 18000

        self.conn = SeedLinkConnection()
        self.conn.setSLAddress('%s:%d' %
                               (self.server_hostname, self.server_port))

        if autoconnect:
            self.connect()

        # A flag to indicate if the client has entered streaming mode
        self.__streaming_started = False

        self.__capabilities = None
Exemple #4
0
    def __init__(self, loglevel='DEBUG'):
        """
        Creates a new instance of SLClient with the specified logging object
        """
        numeric_level = getattr(logging, loglevel.upper(), None)
        if not isinstance(numeric_level, int):
            raise ValueError('Invalid log level: %s' % loglevel)
        logging.basicConfig(level=numeric_level)

        self.slconn = None
        self.verbose = 0
        self.ppackets = False
        self.streamfile = None
        self.selectors = None
        self.multiselect = None
        self.statefile = None
        self.begin_time = None
        self.end_time = None
        self.infolevel = None
        self.slconn = SeedLinkConnection()
Exemple #5
0
    def test_issue777(self):
        """
        Regression tests for Github issue #777
        """
        conn = SeedLinkConnection()

        # Check adding multiple streams (#3)
        conn.addStream('BW', 'RJOB', 'EHZ', seqnum=-1, timestamp=None)
        conn.addStream('BW', 'RJOB', 'EHN', seqnum=-1, timestamp=None)
        self.assertFalse(isinstance(conn.streams[0].getSelectors()[1], list))

        # Check if the correct Exception is raised (#4)
        try:
            conn.negotiateStation(SLNetStation('BW', 'RJOB', None, None, None))
        except Exception as e:
            self.assertTrue(isinstance(e, SeedLinkException))

        # Test if calling addStream() with selectors_str=None still raises (#5)
        try:
            conn.addStream('BW', 'RJOB', None, seqnum=-1, timestamp=None)
        except AttributeError:
            msg = 'Calling addStream with selectors_str=None raised ' + \
                  'AttributeError'
            self.fail(msg)
Exemple #6
0
class SLClient(object):
    """
    Basic class to create and use a connection to a SeedLink server using a
    SeedLinkConnection object.

    A new SeedLink application can be created by sub-classing SLClient and
    overriding at least the packetHandler method of SLClient.

    :var slconn: SeedLinkConnection object for communicating with the
        SeedLinkConnection over a socket.
    :type slconn: SeedLinkConnection
    :var verbose: Verbosity level, 0 is lowest.
    :type verbose: int
    :var ppackets: Flag to indicate show detailed packet information.
    :type  ppackets: boolean
    :var streamfile: Name of file containing stream list for multi-station
        mode.
    :type  streamfile: str
    :var selectors: Selectors for uni-station or default selectors for
        multi-station.
    :type  selectors: str
    :var multiselect: Selectors for multi-station.
    :type  multiselect: str
    :var statefile: Name of file for reading (if exists) and storing state.
    :type  statefile: str
    :var begin_time: Beginning of time window for read start in past.
    :type  begin_time :str
    :var end_time: End of time window for reading windowed data.
    :type  end_time: str
    :var infolevel: INFO LEVEL for info request only.
    :type  infolevel: str
    """
    VERSION = "1.2.0X00"
    VERSION_YEAR = "2011"
    VERSION_DATE = "24Nov" + VERSION_YEAR
    COPYRIGHT_YEAR = VERSION_YEAR
    PROGRAM_NAME = "SLClient v" + VERSION
    VERSION_INFO = PROGRAM_NAME + " (" + VERSION_DATE + ")"
    BANNER = ["SLClient comes with ABSOLUTELY NO WARRANTY"]

    def __init__(self, loglevel='DEBUG'):
        """
        Creates a new instance of SLClient with the specified logging object
        """
        numeric_level = getattr(logging, loglevel.upper(), None)
        if not isinstance(numeric_level, int):
            raise ValueError('Invalid log level: %s' % loglevel)
        logging.basicConfig(level=numeric_level)

        self.slconn = None
        self.verbose = 0
        self.ppackets = False
        self.streamfile = None
        self.selectors = None
        self.multiselect = None
        self.statefile = None
        self.begin_time = None
        self.end_time = None
        self.infolevel = None

        ## for-while
        for line in SLClient.BANNER:
            print line
        self.slconn = SeedLinkConnection()

    def parseCmdLineArgs(self, args):
        """
        Parses the commmand line arguments.

        :type args: list
        :param args: main method arguments.
        :return: -1 on error, 1 if version or help argument found, 0 otherwise.
        """
        if len(args) < 2:
            self.printUsage(False)
            return 1
        optind = 1
        while optind < len(args):
            if args[optind] == "-V":
                print(self.VERSION_INFO, sys.stderr)
                return 1
            elif args[optind] == "-h":
                self.printUsage(False)
                return 1
            elif args[optind].startswith("-v"):
                self.verbose += len(args[optind]) - 1
            elif args[optind] == "-p":
                self.ppackets = True
            elif args[optind] == "-nt":
                optind += 1
                self.slconn.setNetTimout(int(args[optind]))
            elif args[optind] == "-nd":
                optind += 1
                self.slconn.setNetDelay(int(args[optind]))
            elif args[optind] == "-k":
                optind += 1
                self.slconn.setKeepAlive(int(args[optind]))
            elif args[optind] == "-l":
                optind += 1
                self.streamfile = args[optind]
            elif args[optind] == "-s":
                optind += 1
                self.selectors = args[optind]
            elif args[optind] == "-S":
                optind += 1
                self.multiselect = args[optind]
            elif args[optind] == "-x":
                optind += 1
                self.statefile = args[optind]
            elif args[optind] == "-t":
                optind += 1
                self.begin_time = args[optind]
            elif args[optind] == "-e":
                optind += 1
                self.end_time = args[optind]
            elif args[optind] == "-i":
                optind += 1
                self.infolevel = args[optind]
            elif args[optind].startswith("-"):
                print("Unknown option: " + args[optind], sys.stderr)
                return -1
            elif self.slconn.getSLAddress() is None:
                self.slconn.setSLAddress(args[optind])
            else:
                print("Unknown option: " + args[optind], sys.stderr)
                return -1
            optind += 1
        return 0

    def initialize(self):
        """
        Initializes this SLClient.
        """
        if self.slconn.getSLAddress() is None:
            message = "no SeedLink server specified"
            raise SeedLinkException(message)

        if self.verbose >= 2:
            self.ppackets = True
        if self.slconn.getSLAddress().startswith(":"):
            self.slconn.setSLAddress("127.0.0.1" + self.slconn.getSLAddress())
        if self.streamfile is not None:
            self.slconn.readStreamList(self.streamfile, self.selectors)
        if self.multiselect is not None:
            self.slconn.parseStreamlist(self.multiselect, self.selectors)
        else:
            if self.streamfile is None:
                self.slconn.setUniParams(self.selectors, -1, None)
        if self.statefile is not None:
            self.slconn.setStateFile(self.statefile)
        else:
            if self.begin_time is not None:
                self.slconn.setBeginTime(self.begin_time)
            if self.end_time is not None:
                self.slconn.setEndTime(self.end_time)

    def run(self):
        """
        Start this SLClient.
        """
        if self.infolevel is not None:
            self.slconn.requestInfo(self.infolevel)
        # Loop with the connection manager
        count = 1
        slpack = self.slconn.collect()
        while slpack is not None:
            if (slpack == SLPacket.SLTERMINATE):
                break
            try:
                # do something with packet
                terminate = self.packetHandler(count, slpack)
                if terminate:
                    break
            except SeedLinkException as sle:
                print(self.__class__.__name__ + ": " + sle.value)
            if count >= sys.maxint:
                count = 1
                print "DEBUG INFO: " + self.__class__.__name__ + ":",
                print "Packet count reset to 1"
            else:
                count += 1
            slpack = self.slconn.collect()

        # Close the SeedLinkConnection
        self.slconn.close()

    def packetHandler(self, count, slpack):
        """
        Processes each packet received from the SeedLinkConnection.

        This method should be overridden when sub-classing SLClient.

        :type count: int
        :param count:  Packet counter.
        :type slpack: :class:`~obspy.seedlink.SLPacket`
        :param slpack: packet to process.
        :return: Boolean true if connection to SeedLink server should be
            closed and session terminated, false otherwise.
        """
        # check if not a complete packet
        if slpack is None or (slpack == SLPacket.SLNOPACKET) or \
                (slpack == SLPacket.SLERROR):
            return False

        # get basic packet info
        seqnum = slpack.getSequenceNumber()
        type = slpack.getType()

        # process INFO packets here
        if (type == SLPacket.TYPE_SLINF):
            return False
        if (type == SLPacket.TYPE_SLINFT):
            print "Complete INFO:\n" + self.slconn.getInfoString()
            if self.infolevel is not None:
                return True
            else:
                return False

        # can send an in-line INFO request here
        try:
            #if (count % 100 == 0 and not self.slconn.state.expect_info):
            if (count % 100 == 0):
                infostr = "ID"
                self.slconn.requestInfo(infostr)
        except SeedLinkException as sle:
            print(self.__class__.__name__ + ": " + sle.value)

        # if here, must be a data blockette
        print self.__class__.__name__ + ": packet seqnum:",
        print str(seqnum) + ": blockette type: " + str(type)
        if not self.ppackets:
            return False

        # process packet data
        trace = slpack.getTrace()
        if trace is not None:
            print self.__class__.__name__ + ": blockette contains a trace: "
            print trace.id, trace.stats['starttime'],
            print " dt:" + str(1.0 / trace.stats['sampling_rate']),
            print " npts:" + str(trace.stats['npts']),
            print " sampletype:" + str(trace.stats['sampletype']),
            print " dataquality:" + str(trace.stats['dataquality'])
            if self.verbose >= 3:
                print self.__class__.__name__ + ":"
                print "blockette contains a trace: " + str(trace.stats)
        else:
            print self.__class__.__name__ + ": blockette contains no trace"
        return False

    def printUsage(self, concise=True):
        """
        Prints the usage message for this class.
        """
        print("\nUsage: python %s [options] <[host]:port>" % \
              (self.__class__.__name__))
        if concise:
            usage = "Use '-h' for detailed help"
        else:
            usage = USAGE
        print(usage)

    @classmethod
    def main(cls, args):
        """
        Main method - creates and runs an SLClient using the specified
        command line arguments
        """
        slClient = None
        try:
            slClient = SLClient()
            rval = slClient.parseCmdLineArgs(args)
            if (rval != 0):
                sys.exit(rval)
            slClient.initialize()
            slClient.run()
        except Exception as e:
            logger.critical(e)
            traceback.print_exc()
Exemple #7
0
class EasySeedLinkClient(object):
    """
    An easy-to-use SeedLink client.

    This class is meant to be used as a base class, with a subclass
    implementing one or more of the callbacks (most usefully the
    :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.on_data` callback).
    See the :ref:`ObsPy Tutorial <seedlink-tutorial>` for a detailed example.

    .. rubric:: Example

    .. code-block:: python

        # Subclass the client class
        class MyClient(EasySeedLinkClient):
            # Implement the on_data callback
            def on_data(self, trace):
                print('Received trace:')
                print(trace)

        # Connect to a SeedLink server
        client = MyClient('geofon.gfz-potsdam.de:18000')

        # Retrieve INFO:STREAMS
        streams_xml = client.get_info('STREAMS')
        print(streams_xml)

        # Select a stream and start receiving data
        client.select_stream('BW', 'RJOB', 'EHZ')
        client.run()

    .. rubric:: Implementation

    The EasySeedLinkClient uses the
    :class:`~obspy.seedlink.client.seedlinkconnection.SeedLinkConnection`
    object. (It is not based on
    :class:`~obspy.seedlink.slclient.SLClient`.)

    :type server_url: str
    :param server_url: The SeedLink server URL
    :type autoconnect: bool
    :param autoconnect: Connect to the server when the client object is
                        created; default is True.

    .. warning::

        The SeedLink connection only fails on connection errors if the
        connection was started explicitly, either when ``autoconnect`` is
        ``True`` or by calling
        :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.connect`
        explicitly. Otherwise the client might get stuck in an infinite
        reconnect loop if there are connection problems (e.g. connect, timeout,
        reconnect, timeout, ...). This might be intended behavior in some
        situations.
    """

    def __init__(self, server_url, autoconnect=True):
        # Catch invalid server_url parameters
        if not isinstance(server_url, basestring):
            raise ValueError("Expected string for SeedLink server URL")
        # Allow for sloppy server URLs (e.g. 'geofon.gfz-potsdam.de:18000).
        # (According to RFC 1808 the net_path segment needs to start with '//'
        # and this is expected by the urlparse function, so it is silently
        # added if it was omitted by the user.)
        if "://" not in server_url and not server_url.startswith("//"):
            server_url = "//" + server_url

        parsed_url = urlparse.urlparse(server_url, scheme="seedlink")

        # Check the provided scheme
        if not parsed_url.scheme == "seedlink":
            msg = 'Unsupported scheme %s (expected "seedlink")' % parsed_url.scheme
            raise EasySeedLinkClientException(msg)
        if not parsed_url.hostname:
            msg = "No host name provided"
            raise EasySeedLinkClientException(msg)

        self.server_hostname = parsed_url.hostname
        self.server_port = parsed_url.port or 18000

        self.conn = SeedLinkConnection()
        self.conn.setSLAddress("%s:%d" % (self.server_hostname, self.server_port))

        if autoconnect:
            self.connect()

        # A flag to indicate if the client has entered streaming mode
        self.__streaming_started = False

        self.__capabilities = None

    def connect(self):
        """
        Connect to the SeedLink server.
        """
        # XXX Check if already connected?
        self.conn.connect()
        self.conn.state.state = SLState.SL_UP

    def get_info(self, level):
        """
        Send a SeedLink ``INFO`` command and retrieve response.

        Available info levels depend on the server implementation. Usually one
        of ``ID``, ``CAPABILITIES``, ``STATIONS``, ``STREAMS``, ``GAPS``,
        ``CONNNECTIONS``, ``ALL``.

        As a convenience, the server's ``CAPABILITIES`` can be accessed through
        the client's
        :attr:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.capabilities`
        attribute.

        .. note::

            This is a synchronous call. While the client waits for the
            response, other packets the server might potentially send will
            be disregarded.

        :type level: str
        :param level: The INFO level to retrieve (sent as ``INFO:LEVEL``)
        """
        if self.__streaming_started:
            msg = "Method not available after SeedLink connection has " + "entered streaming mode."
            raise EasySeedLinkClientException(msg)

        # Send the INFO request
        self.conn.requestInfo(level)

        # Wait for full response
        while True:
            data = self.conn.collect()

            if data == SLPacket.SLTERMINATE:
                msg = "SeedLink connection terminated while expecting " + "INFO response"
                raise EasySeedLinkClientException(msg)
            elif data == SLPacket.SLERROR:
                msg = "Unknown error occured while expecting INFO response"
                raise EasySeedLinkClientException(msg)

            # Wait for the terminated INFO response
            packet_type = data.getType()
            if packet_type == SLPacket.TYPE_SLINFT:
                return self.conn.getInfoString()

    @property
    def capabilities(self):
        """
        The server's capabilities, parsed from ``INFO:CAPABILITIES`` (cached).
        """
        if self.__capabilities is None:
            self.__capabilities = []

            capabilities_xml = self.get_info("CAPABILITIES")

            # The INFO response should be encoded in UTF-8. However, if the
            # encoding is given in the XML header (e.g. by IRIS Ringserver),
            # lxml accepts byte input only (and raises a ValueError otherwise.)
            #
            # Example XML header with encoding:
            #     <?xml version="1.0" encoding="utf-8"?>
            try:
                root = lxml.etree.fromstring(capabilities_xml)
            except ValueError:
                root = lxml.etree.fromstring(capabilities_xml.encode("UTF-8"))

            nodes = root.findall("capability")

            for node in nodes:
                self.__capabilities.append(node.attrib["name"].lower())

        return self.__capabilities

    def has_capability(self, capability):
        """
        Check if the SeedLink server has a certain capability.

        The capabilities are fetched using an ``INFO:CAPABILITIES`` request.

        :type capability: str
        :param capability: The capability to check for

        :rtype: bool
        :return: Whether the server has the given capability
        """
        return capability.lower() in self.capabilities

    def has_info_capability(self, capability):
        """
        A shortcut for checking for ``INFO`` capabilities.

        Calling this is equivalent to calling
        :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.has_capability`
        with ``'info:' + capability``.

        .. rubric:: Example

        .. code-block:: python

            # Check if the server has the INFO:STREAMS capability
            client.has_info_capability('STREAMS')

        :type capability: str
        :param capability: The ``INFO`` capability to check for

        :rtype: bool
        :return: Whether the server has the given ``INFO`` capability
        """
        return self.has_capability("info:" + capability)

    def _send_and_recv(self, bytes_, stop_on=[b"END"]):
        """
        Send a command to the server and read the response.

        The response is read until a packet is received that ends with one of
        the provided stop words.

        .. warning::

            If the server doesn't send one of the stop words, this never
            returns!

        :type bytes_: str (Python 2) or bytes (Python 3)
        :param bytes_: The bytes to send to the server
        :type stop_on: list
        :param stop_on: A list of strings that indicate the end of the server
                        response.

        :rtype: str (Python 2) or bytes (Python 3)
        :return: The server's response
        """
        if not bytes_.endswith(b"\r"):
            bytes_ += b"\r"
        if not type(stop_on) is list:
            stop_on = [stop_on]
        for i, stopword in enumerate(stop_on):
            if not type(stopword) == bytes:
                stop_on[i] = stopword.encode()

        self.conn.socket.send(bytes_)

        response = bytearray()
        while True:
            bytes_read = self.conn.socket.recv(SeedLinkConnection.DFT_READBUF_SIZE)
            response += bytes_read
            for stopword in stop_on:
                if response.endswith(stopword):
                    # Collapse the bytearray
                    return bytes(response)

    def _get_CAT(self):
        """
        Send the CAT command to a server and receive the answer.

        This can potentially be used for older SeedLink servers that don't
        support the ``INFO:STREAMS`` command yet.
        """
        # Quick hack, but works so far
        ringserver_error = "CAT command not implemented\r\n"

        response = self._send_and_recv("CAT", ["END", ringserver_error])

        if response == ringserver_error:
            raise EasySeedLinkClientException(ringserver_error.strip())

        return response

    def select_stream(self, net, station, selector=None):
        """
        Select a stream for data transfer.

        This method can be called once or multiple times as needed. A
        subsequent call to the
        :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.run` method
        starts the streaming process.

        .. note::
            Selecting a stream always puts the SeedLink connection in
            *multi-station mode*, even if only a single stream is selected.
            *Uni-station mode* is not supported.

        :type net: str
        :param net: The network id
        :type station: str
        :param station: The station id
        :type selectors: str
        :param selector: a valid SeedLink selector, e.g. ``EHZ`` or ``EH?``
        """
        if not self.has_capability("multistation"):
            msg = "SeedLink server does not support multi-station mode"
            raise EasySeedLinkClientException(msg)

        if self.__streaming_started:
            msg = "Adding streams is not supported after the SeedLink " + "connection has entered streaming mode."
            raise EasySeedLinkClientException(msg)

        self.conn.addStream(net, station, selector, seqnum=-1, timestamp=None)

    def run(self):
        """
        Start streaming data from the SeedLink server.

        Streams need to be selected using
        :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.select_stream`
        before this is called.

        This method enters an infinite loop, calling the client's callbacks
        when events occur.
        """
        # Note: This somewhat resembles the run() method in SLClient.

        # Check if any streams have been specified (otherwise this will result
        # in an infinite reconnect loop in the SeedLinkConnection)
        if not len(self.conn.streams):
            msg = "No streams specified. Use select_stream() to select " + "a stream."
            raise EasySeedLinkClientException(msg)

        self.__streaming_started = True

        # Start the collection loop
        while True:
            data = self.conn.collect()

            if data == SLPacket.SLTERMINATE:
                self.on_terminate()
                break
            elif data == SLPacket.SLERROR:
                self.on_seedlink_error()
                continue

            # At this point the received data should be a SeedLink packet
            # XXX In SLClient there is a check for data == None, but I think
            #     there is no way that self.conn.collect() can ever return None
            assert isinstance(data, SLPacket)

            packet_type = data.getType()

            # Ignore in-stream INFO packets (not supported)
            if packet_type not in (SLPacket.TYPE_SLINF, SLPacket.TYPE_SLINFT):
                # The packet should be a data packet
                trace = data.getTrace()
                # Pass the trace to the on_data callback
                self.on_data(trace)

    def close(self):
        """
        Close the SeedLink connection.

        .. note::

            Closing  the connection is not threadsafe yet. Client code must
            ensure that
            :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.run` and
            :meth:`SeedLinkConnection.terminate()
            <obspy.seedlink.client.seedlinkconnection.SeedLinkConnection.terminate>`
            are not being called after the connection has been closed.

            See the corresponding `GitHub issue
            <https://github.com/obspy/obspy/pull/876#issuecomment-60537414>`_
            for details.
        """
        self.conn.disconnect()

    def on_terminate(self):
        """
        Callback for handling connection termination.

        A termination event can either be triggered by the SeedLink server
        explicitly terminating the connection (by sending an ``END`` packet in
        streaming mode) or by the
        :meth:`~obspy.seedlink.client.seedlinkconnection.SeedLinkConnection.terminate`
        method of the
        :class:`~obspy.seedlink.client.seedlinkconnection.SeedLinkConnection`
        object being called.
        """
        pass

    def on_seedlink_error(self):
        """
        Callback for handling SeedLink errors.

        This handler is called when an ``ERROR`` response is received. The
        error generally corresponds to the last command that was sent to the
        server. However, with the current implementation of the SeedLink
        connection, no further information about the error is available.
        """
        pass

    def on_data(self, trace):
        """
        Callback for handling the reception of waveform data.

        Override this for data streaming.

        :type trace: :class:`~obspy.core.trace.Trace`
        :param trace: The trace received from the server
        """
        pass
Exemple #8
0
class SLClient(object):
    """
    Basic class to create and use a connection to a SeedLink server using a
    SeedLinkConnection object.

    A new SeedLink application can be created by sub-classing SLClient and
    overriding at least the packetHandler method of SLClient.

    :var slconn: SeedLinkConnection object for communicating with the
        SeedLinkConnection over a socket.
    :type slconn: SeedLinkConnection
    :var verbose: Verbosity level, 0 is lowest.
    :type verbose: int
    :var ppackets: Flag to indicate show detailed packet information.
    :type  ppackets: boolean
    :var streamfile: Name of file containing stream list for multi-station
        mode.
    :type  streamfile: str
    :var selectors: Selectors for uni-station or default selectors for
        multi-station.
    :type  selectors: str
    :var multiselect: Selectors for multi-station.
    :type  multiselect: str
    :var statefile: Name of file for reading (if exists) and storing state.
    :type  statefile: str
    :var begin_time: Beginning of time window for read start in past.
    :type  begin_time :str
    :var end_time: End of time window for reading windowed data.
    :type  end_time: str
    :var infolevel: INFO LEVEL for info request only.
    :type  infolevel: str
    """
    VERSION = "1.2.0X00"
    VERSION_YEAR = "2011"
    VERSION_DATE = "24Nov" + VERSION_YEAR
    COPYRIGHT_YEAR = VERSION_YEAR
    PROGRAM_NAME = "SLClient v" + VERSION
    VERSION_INFO = PROGRAM_NAME + " (" + VERSION_DATE + ")"
    BANNER = ["SLClient comes with ABSOLUTELY NO WARRANTY"]

    def __init__(self, loglevel='DEBUG'):
        """
        Creates a new instance of SLClient with the specified logging object
        """
        numeric_level = getattr(logging, loglevel.upper(), None)
        if not isinstance(numeric_level, int):
            raise ValueError('Invalid log level: %s' % loglevel)
        logging.basicConfig(level=numeric_level)

        self.slconn = None
        self.verbose = 0
        self.ppackets = False
        self.streamfile = None
        self.selectors = None
        self.multiselect = None
        self.statefile = None
        self.begin_time = None
        self.end_time = None
        self.infolevel = None

        ## for-while
        for line in SLClient.BANNER:
            print line
        self.slconn = SeedLinkConnection()

    def parseCmdLineArgs(self, args):
        """
        Parses the commmand line arguments.

        :type args: list
        :param args: main method arguments.
        :return: -1 on error, 1 if version or help argument found, 0 otherwise.
        """
        if len(args) < 2:
            self.printUsage(False)
            return 1
        optind = 1
        while optind < len(args):
            if args[optind] == "-V":
                print(self.VERSION_INFO, sys.stderr)
                return 1
            elif args[optind] == "-h":
                self.printUsage(False)
                return 1
            elif args[optind].startswith("-v"):
                self.verbose += len(args[optind]) - 1
            elif args[optind] == "-p":
                self.ppackets = True
            elif args[optind] == "-nt":
                optind += 1
                self.slconn.setNetTimout(int(args[optind]))
            elif args[optind] == "-nd":
                optind += 1
                self.slconn.setNetDelay(int(args[optind]))
            elif args[optind] == "-k":
                optind += 1
                self.slconn.setKeepAlive(int(args[optind]))
            elif args[optind] == "-l":
                optind += 1
                self.streamfile = args[optind]
            elif args[optind] == "-s":
                optind += 1
                self.selectors = args[optind]
            elif args[optind] == "-S":
                optind += 1
                self.multiselect = args[optind]
            elif args[optind] == "-x":
                optind += 1
                self.statefile = args[optind]
            elif args[optind] == "-t":
                optind += 1
                self.begin_time = args[optind]
            elif args[optind] == "-e":
                optind += 1
                self.end_time = args[optind]
            elif args[optind] == "-i":
                optind += 1
                self.infolevel = args[optind]
            elif args[optind].startswith("-"):
                print("Unknown option: " + args[optind], sys.stderr)
                return -1
            elif self.slconn.getSLAddress() is None:
                self.slconn.setSLAddress(args[optind])
            else:
                print("Unknown option: " + args[optind], sys.stderr)
                return -1
            optind += 1
        return 0

    def initialize(self):
        """
        Initializes this SLClient.
        """
        if self.slconn.getSLAddress() is None:
            message = "no SeedLink server specified"
            raise SeedLinkException(message)

        if self.verbose >= 2:
            self.ppackets = True
        if self.slconn.getSLAddress().startswith(":"):
            self.slconn.setSLAddress("127.0.0.1" + self.slconn.getSLAddress())
        if self.streamfile is not None:
            self.slconn.readStreamList(self.streamfile, self.selectors)
        if self.multiselect is not None:
            self.slconn.parseStreamlist(self.multiselect, self.selectors)
        else:
            if self.streamfile is None:
                self.slconn.setUniParams(self.selectors, -1, None)
        if self.statefile is not None:
            self.slconn.setStateFile(self.statefile)
        else:
            if self.begin_time is not None:
                self.slconn.setBeginTime(self.begin_time)
            if self.end_time is not None:
                self.slconn.setEndTime(self.end_time)

    def run(self):
        """
        Start this SLClient.
        """
        if self.infolevel is not None:
            self.slconn.requestInfo(self.infolevel)
        # Loop with the connection manager
        count = 1
        slpack = self.slconn.collect()
        while slpack is not None:
            if (slpack == SLPacket.SLTERMINATE):
                break
            try:
                # do something with packet
                terminate = self.packetHandler(count, slpack)
                if terminate:
                    break
            except SeedLinkException as sle:
                print(self.__class__.__name__ + ": " + sle.value)
            if count >= sys.maxint:
                count = 1
                print "DEBUG INFO: " + self.__class__.__name__ + ":",
                print "Packet count reset to 1"
            else:
                count += 1
            slpack = self.slconn.collect()

        # Close the SeedLinkConnection
        self.slconn.close()

    def packetHandler(self, count, slpack):
        """
        Processes each packet received from the SeedLinkConnection.

        This method should be overridden when sub-classing SLClient.

        :type count: int
        :param count:  Packet counter.
        :type slpack: :class:`~obspy.seedlink.SLPacket`
        :param slpack: packet to process.
        :return: Boolean true if connection to SeedLink server should be
            closed and session terminated, false otherwise.
        """
        # check if not a complete packet
        if slpack is None or (slpack == SLPacket.SLNOPACKET) or \
                (slpack == SLPacket.SLERROR):
            return False

        # get basic packet info
        seqnum = slpack.getSequenceNumber()
        type = slpack.getType()

        # process INFO packets here
        if (type == SLPacket.TYPE_SLINF):
            return False
        if (type == SLPacket.TYPE_SLINFT):
            print "Complete INFO:\n" + self.slconn.getInfoString()
            if self.infolevel is not None:
                return True
            else:
                return False

        # can send an in-line INFO request here
        try:
            #if (count % 100 == 0 and not self.slconn.state.expect_info):
            if (count % 100 == 0):
                infostr = "ID"
                self.slconn.requestInfo(infostr)
        except SeedLinkException as sle:
            print(self.__class__.__name__ + ": " + sle.value)

        # if here, must be a data blockette
        print self.__class__.__name__ + ": packet seqnum:",
        print str(seqnum) + ": blockette type: " + str(type)
        if not self.ppackets:
            return False

        # process packet data
        trace = slpack.getTrace()
        if trace is not None:
            print self.__class__.__name__ + ": blockette contains a trace: "
            print trace.id, trace.stats['starttime'],
            print " dt:" + str(1.0 / trace.stats['sampling_rate']),
            print " npts:" + str(trace.stats['npts']),
            print " sampletype:" + str(trace.stats['sampletype']),
            print " dataquality:" + str(trace.stats['dataquality'])
            if self.verbose >= 3:
                print self.__class__.__name__ + ":"
                print "blockette contains a trace: " + str(trace.stats)
        else:
            print self.__class__.__name__ + ": blockette contains no trace"
        return False

    def printUsage(self, concise):
        """
        Prints the usage message for this class.
        """
        print("\nUsage: python %s [options] <[host]:port>" % \
              (self.__class__.__name__))
        if concise:
            print("Use '-h' for detailed help")
            return
        print
        print(" ## General program options ##")
        print(" -V             report program version")
        print(" -h             show this usage message")
        print(" -v             be more verbose, multiple flags can be used")
        print(" -p             print details of data packets")
        print(" -nd delay      network re-connect delay (seconds), default 30")
        print(
            " -nt timeout    network timeout (seconds), re-establish connection if no"
        )
        print(
            "                  data/keepalives are received in this time, default 600"
        )
        print(
            " -k interval    send keepalive (heartbeat) packets this often (seconds)"
        )
        print(
            " -x statefile   save/restore stream state information to this file"
        )
        print(
            " -t begintime   sets a beginning time for the initiation of data transmission (year,month,day,hour,minute,second)"
        )
        print(
            " -e endtime     sets an end time for windowed data transmission  (year,month,day,hour,minute,second)"
        )
        print(
            " -i infolevel   request this INFO level, write response to std out, and exit "
        )
        print(
            "                  infolevel is one of: ID, STATIONS, STREAMS, GAPS, CONNECTIONS, ALL "
        )
        print
        print(" ## Data stream selection ##")
        print(
            " -l listfile    read a stream list from this file for multi-station mode"
        )
        print(
            " -s selectors   selectors for uni-station or default for multi-station"
        )
        print(
            " -S streams     select streams for multi-station (requires SeedLink >= 2.5)"
        )
        print("   'streams' = 'stream1[:selectors1],stream2[:selectors2],...'")
        print("        'stream' is in NET_STA format, for example:")
        print("        -S \"IU_KONO:BHE BHN,GE_WLF,MN_AQU:HH?.D\"")
        print("")
        print(
            " <[host]:port>  Address of the SeedLink server in host:port format"
        )
        print(
            "                  if host is omitted (i.e. ':18000'), localhost is assumed"
        )

    @classmethod
    def main(cls, args):
        """
        Main method - creates and runs an SLClient using the specified
        command line arguments
        """
        slClient = None
        try:
            slClient = SLClient()
            rval = slClient.parseCmdLineArgs(args)
            if (rval != 0):
                sys.exit(rval)
            slClient.initialize()
            slClient.run()
        except Exception as e:
            logger.critical(e)
            traceback.print_exc()
Exemple #9
0
class EasySeedLinkClient(object):
    """
    An easy-to-use SeedLink client.

    This class is meant to be used as a base class, with a subclass
    implementing one or more of the callbacks (most usefully the
    :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.on_data` callback).
    See the :ref:`ObsPy Tutorial <seedlink-tutorial>` for a detailed example.

    .. rubric:: Example

    .. code-block:: python

        # Subclass the client class
        class MyClient(EasySeedLinkClient):
            # Implement the on_data callback
            def on_data(self, trace):
                print('Received trace:')
                print(trace)

        # Connect to a SeedLink server
        client = MyClient('geofon.gfz-potsdam.de:18000')

        # Retrieve INFO:STREAMS
        streams_xml = client.get_info('STREAMS')
        print(streams_xml)

        # Select a stream and start receiving data
        client.select_stream('BW', 'RJOB', 'EHZ')
        client.run()

    .. rubric:: Implementation

    The EasySeedLinkClient uses the
    :class:`~obspy.seedlink.client.seedlinkconnection.SeedLinkConnection`
    object. (It is not based on
    :class:`~obspy.seedlink.slclient.SLClient`.)

    :type server_url: str
    :param server_url: The SeedLink server URL
    :type autoconnect: bool
    :param autoconnect: Connect to the server when the client object is
                        created; default is True.

    .. warning::

        The SeedLink connection only fails on connection errors if the
        connection was started explicitly, either when ``autoconnect`` is
        ``True`` or by calling
        :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.connect`
        explicitly. Otherwise the client might get stuck in an infinite
        reconnect loop if there are connection problems (e.g. connect, timeout,
        reconnect, timeout, ...). This might be intended behavior in some
        situations.
    """

    def __init__(self, server_url, autoconnect=True):
        # Catch invalid server_url parameters
        if not isinstance(server_url, basestring):
            raise ValueError('Expected string for SeedLink server URL')
        # Allow for sloppy server URLs (e.g. 'geofon.gfz-potsdam.de:18000).
        # (According to RFC 1808 the net_path segment needs to start with '//'
        # and this is expected by the urlparse function, so it is silently
        # added if it was omitted by the user.)
        if '://' not in server_url and not server_url.startswith('//'):
            server_url = '//' + server_url

        parsed_url = urlparse.urlparse(server_url, scheme='seedlink')

        # Check the provided scheme
        if not parsed_url.scheme == 'seedlink':
            msg = 'Unsupported scheme %s (expected "seedlink")' % \
                  parsed_url.scheme
            raise EasySeedLinkClientException(msg)
        if not parsed_url.hostname:
            msg = 'No host name provided'
            raise EasySeedLinkClientException(msg)

        self.server_hostname = parsed_url.hostname
        self.server_port = parsed_url.port or 18000

        self.conn = SeedLinkConnection()
        self.conn.setSLAddress('%s:%d' %
                               (self.server_hostname, self.server_port))

        if autoconnect:
            self.connect()

        # A flag to indicate if the client has entered streaming mode
        self.__streaming_started = False

        self.__capabilities = None

    def connect(self):
        """
        Connect to the SeedLink server.
        """
        # XXX Check if already connected?
        self.conn.connect()
        self.conn.state.state = SLState.SL_UP

    def get_info(self, level):
        """
        Send a SeedLink ``INFO`` command and retrieve response.

        Available info levels depend on the server implementation. Usually one
        of ``ID``, ``CAPABILITIES``, ``STATIONS``, ``STREAMS``, ``GAPS``,
        ``CONNNECTIONS``, ``ALL``.

        As a convenience, the server's ``CAPABILITIES`` can be accessed through
        the client's
        :attr:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.capabilities`
        attribute.

        .. note::

            This is a synchronous call. While the client waits for the
            response, other packets the server might potentially send will
            be disregarded.

        :type level: str
        :param level: The INFO level to retrieve (sent as ``INFO:LEVEL``)
        """
        if self.__streaming_started:
            msg = 'Method not available after SeedLink connection has ' + \
                  'entered streaming mode.'
            raise EasySeedLinkClientException(msg)

        # Send the INFO request
        self.conn.requestInfo(level)

        # Wait for full response
        while True:
            data = self.conn.collect()

            if data == SLPacket.SLTERMINATE:
                msg = 'SeedLink connection terminated while expecting ' + \
                      'INFO response'
                raise EasySeedLinkClientException(msg)
            elif data == SLPacket.SLERROR:
                msg = 'Unknown error occured while expecting INFO response'
                raise EasySeedLinkClientException(msg)

            # Wait for the terminated INFO response
            packet_type = data.getType()
            if packet_type == SLPacket.TYPE_SLINFT:
                return self.conn.getInfoString()

    @property
    def capabilities(self):
        """
        The server's capabilities, parsed from ``INFO:CAPABILITIES`` (cached).
        """
        if self.__capabilities is None:
            self.__capabilities = []

            capabilities_xml = self.get_info('CAPABILITIES')

            # The INFO response should be encoded in UTF-8. However, if the
            # encoding is given in the XML header (e.g. by IRIS Ringserver),
            # lxml accepts byte input only (and raises a ValueError otherwise.)
            #
            # Example XML header with encoding:
            #     <?xml version="1.0" encoding="utf-8"?>
            try:
                root = lxml.etree.fromstring(capabilities_xml)
            except ValueError:
                root = lxml.etree.fromstring(capabilities_xml.encode('UTF-8'))

            nodes = root.findall('capability')

            for node in nodes:
                self.__capabilities.append(node.attrib['name'].lower())

        return self.__capabilities

    def has_capability(self, capability):
        """
        Check if the SeedLink server has a certain capability.

        The capabilities are fetched using an ``INFO:CAPABILITIES`` request.

        :type capability: str
        :param capability: The capability to check for

        :rtype: bool
        :return: Whether the server has the given capability
        """
        return capability.lower() in self.capabilities

    def has_info_capability(self, capability):
        """
        A shortcut for checking for ``INFO`` capabilities.

        Calling this is equivalent to calling
        :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.has_capability`
        with ``'info:' + capability``.

        .. rubric:: Example

        .. code-block:: python

            # Check if the server has the INFO:STREAMS capability
            client.has_info_capability('STREAMS')

        :type capability: str
        :param capability: The ``INFO`` capability to check for

        :rtype: bool
        :return: Whether the server has the given ``INFO`` capability
        """
        return self.has_capability('info:' + capability)

    def _send_and_recv(self, bytes_, stop_on=[b'END']):
        """
        Send a command to the server and read the response.

        The response is read until a packet is received that ends with one of
        the provided stop words.

        .. warning::

            If the server doesn't send one of the stop words, this never
            returns!

        :type bytes_: str (Python 2) or bytes (Python 3)
        :param bytes_: The bytes to send to the server
        :type stop_on: list
        :param stop_on: A list of strings that indicate the end of the server
                        response.

        :rtype: str (Python 2) or bytes (Python 3)
        :return: The server's response
        """
        if not bytes_.endswith(b'\r'):
            bytes_ += b"\r"
        if not type(stop_on) is list:
            stop_on = [stop_on]
        for i, stopword in enumerate(stop_on):
            if not type(stopword) == bytes:
                stop_on[i] = stopword.encode()

        self.conn.socket.send(bytes_)

        response = bytearray()
        while True:
            bytes_read = self.conn.socket.recv(
                SeedLinkConnection.DFT_READBUF_SIZE)
            response += bytes_read
            for stopword in stop_on:
                if response.endswith(stopword):
                    # Collapse the bytearray
                    return bytes(response)

    def _get_CAT(self):
        """
        Send the CAT command to a server and receive the answer.

        This can potentially be used for older SeedLink servers that don't
        support the ``INFO:STREAMS`` command yet.
        """
        # Quick hack, but works so far
        ringserver_error = 'CAT command not implemented\r\n'

        response = self._send_and_recv('CAT', ['END', ringserver_error])

        if response == ringserver_error:
            raise EasySeedLinkClientException(ringserver_error.strip())

        return response

    def select_stream(self, net, station, selector=None):
        """
        Select a stream for data transfer.

        This method can be called once or multiple times as needed. A
        subsequent call to the
        :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.run` method
        starts the streaming process.

        .. note::
            Selecting a stream always puts the SeedLink connection in
            *multi-station mode*, even if only a single stream is selected.
            *Uni-station mode* is not supported.

        :type net: str
        :param net: The network id
        :type station: str
        :param station: The station id
        :type selectors: str
        :param selector: a valid SeedLink selector, e.g. ``EHZ`` or ``EH?``
        """
        if not self.has_capability('multistation'):
            msg = 'SeedLink server does not support multi-station mode'
            raise EasySeedLinkClientException(msg)

        if self.__streaming_started:
            msg = 'Adding streams is not supported after the SeedLink ' + \
                  'connection has entered streaming mode.'
            raise EasySeedLinkClientException(msg)

        self.conn.addStream(net, station, selector, seqnum=-1, timestamp=None)

    def run(self):
        """
        Start streaming data from the SeedLink server.

        Streams need to be selected using
        :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.select_stream`
        before this is called.

        This method enters an infinite loop, calling the client's callbacks
        when events occur.
        """
        # Note: This somewhat resembles the run() method in SLClient.

        # Check if any streams have been specified (otherwise this will result
        # in an infinite reconnect loop in the SeedLinkConnection)
        if not len(self.conn.streams):
            msg = 'No streams specified. Use select_stream() to select ' + \
                  'a stream.'
            raise EasySeedLinkClientException(msg)

        self.__streaming_started = True

        # Start the collection loop
        while True:
            data = self.conn.collect()

            if data == SLPacket.SLTERMINATE:
                self.on_terminate()
                break
            elif data == SLPacket.SLERROR:
                self.on_seedlink_error()
                continue

            # At this point the received data should be a SeedLink packet
            # XXX In SLClient there is a check for data == None, but I think
            #     there is no way that self.conn.collect() can ever return None
            assert(isinstance(data, SLPacket))

            packet_type = data.getType()

            # Ignore in-stream INFO packets (not supported)
            if packet_type not in (SLPacket.TYPE_SLINF, SLPacket.TYPE_SLINFT):
                # The packet should be a data packet
                trace = data.getTrace()
                # Pass the trace to the on_data callback
                self.on_data(trace)

    def close(self):
        """
        Close the SeedLink connection.

        .. note::

            Closing  the connection is not threadsafe yet. Client code must
            ensure that
            :meth:`~obspy.seedlink.easyseedlink.EasySeedLinkClient.run` and
            :meth:`SeedLinkConnection.terminate()
            <obspy.seedlink.client.seedlinkconnection.SeedLinkConnection.terminate>`
            are not being called after the connection has been closed.

            See the corresponding `GitHub issue
            <https://github.com/obspy/obspy/pull/876#issuecomment-60537414>`_
            for details.
        """
        self.conn.disconnect()

    def on_terminate(self):
        """
        Callback for handling connection termination.

        A termination event can either be triggered by the SeedLink server
        explicitly terminating the connection (by sending an ``END`` packet in
        streaming mode) or by the
        :meth:`~obspy.seedlink.client.seedlinkconnection.SeedLinkConnection.terminate`
        method of the
        :class:`~obspy.seedlink.client.seedlinkconnection.SeedLinkConnection`
        object being called.
        """
        pass

    def on_seedlink_error(self):
        """
        Callback for handling SeedLink errors.

        This handler is called when an ``ERROR`` response is received. The
        error generally corresponds to the last command that was sent to the
        server. However, with the current implementation of the SeedLink
        connection, no further information about the error is available.
        """
        pass

    def on_data(self, trace):
        """
        Callback for handling the reception of waveform data.

        Override this for data streaming.

        :type trace: :class:`~obspy.core.trace.Trace`
        :param trace: The trace received from the server
        """
        pass
Exemple #10
0
class SLClient(object):
    """
    Basic class to create and use a connection to a SeedLink server using a
    SeedLinkConnection object.

    A new SeedLink application can be created by sub-classing SLClient and
    overriding at least the packetHandler method of SLClient.

    :var slconn: SeedLinkConnection object for communicating with the
        SeedLinkConnection over a socket.
    :type slconn: SeedLinkConnection
    :var verbose: Verbosity level, 0 is lowest.
    :type verbose: int
    :var ppackets: Flag to indicate show detailed packet information.
    :type  ppackets: boolean
    :var streamfile: Name of file containing stream list for multi-station
        mode.
    :type  streamfile: str
    :var selectors: Selectors for uni-station or default selectors for
        multi-station.
    :type  selectors: str
    :var multiselect: Selectors for multi-station.
    :type  multiselect: str
    :var statefile: Name of file for reading (if exists) and storing state.
    :type  statefile: str
    :var begin_time: Beginning of time window for read start in past.
    :type  begin_time :str
    :var end_time: End of time window for reading windowed data.
    :type  end_time: str
    :var infolevel: INFO LEVEL for info request only.
    :type  infolevel: str
    """
    VERSION = "1.2.0X00"
    VERSION_YEAR = "2011"
    VERSION_DATE = "24Nov" + VERSION_YEAR
    COPYRIGHT_YEAR = VERSION_YEAR
    PROGRAM_NAME = "SLClient v" + VERSION
    VERSION_INFO = PROGRAM_NAME + " (" + VERSION_DATE + ")"

    def __init__(self, loglevel='DEBUG'):
        """
        Creates a new instance of SLClient with the specified logging object
        """
        numeric_level = getattr(logging, loglevel.upper(), None)
        if not isinstance(numeric_level, int):
            raise ValueError('Invalid log level: %s' % loglevel)
        logging.basicConfig(level=numeric_level)

        self.slconn = None
        self.verbose = 0
        self.ppackets = False
        self.streamfile = None
        self.selectors = None
        self.multiselect = None
        self.statefile = None
        self.begin_time = None
        self.end_time = None
        self.infolevel = None
        self.slconn = SeedLinkConnection()

    def parseCmdLineArgs(self, args):
        """
        Parses the commmand line arguments.

        :type args: list
        :param args: main method arguments.
        :return: -1 on error, 1 if version or help argument found, 0 otherwise.
        """
        if len(args) < 2:
            self.printUsage(False)
            return 1
        optind = 1
        while optind < len(args):
            if args[optind] == "-V":
                print(self.VERSION_INFO, sys.stderr)
                return 1
            elif args[optind] == "-h":
                self.printUsage(False)
                return 1
            elif args[optind].startswith("-v"):
                self.verbose += len(args[optind]) - 1
            elif args[optind] == "-p":
                self.ppackets = True
            elif args[optind] == "-nt":
                optind += 1
                self.slconn.setNetTimout(int(args[optind]))
            elif args[optind] == "-nd":
                optind += 1
                self.slconn.setNetDelay(int(args[optind]))
            elif args[optind] == "-k":
                optind += 1
                self.slconn.setKeepAlive(int(args[optind]))
            elif args[optind] == "-l":
                optind += 1
                self.streamfile = args[optind]
            elif args[optind] == "-s":
                optind += 1
                self.selectors = args[optind]
            elif args[optind] == "-S":
                optind += 1
                self.multiselect = args[optind]
            elif args[optind] == "-x":
                optind += 1
                self.statefile = args[optind]
            elif args[optind] == "-t":
                optind += 1
                self.begin_time = args[optind]
            elif args[optind] == "-e":
                optind += 1
                self.end_time = args[optind]
            elif args[optind] == "-i":
                optind += 1
                self.infolevel = args[optind]
            elif args[optind].startswith("-"):
                print("Unknown option: " + args[optind], sys.stderr)
                return -1
            elif self.slconn.getSLAddress() is None:
                self.slconn.setSLAddress(args[optind])
            else:
                print("Unknown option: " + args[optind], sys.stderr)
                return -1
            optind += 1
        return 0

    def initialize(self):
        """
        Initializes this SLClient.
        """
        if self.slconn.getSLAddress() is None:
            message = "no SeedLink server specified"
            raise SeedLinkException(message)

        if self.verbose >= 2:
            self.ppackets = True
        if self.slconn.getSLAddress().startswith(":"):
            self.slconn.setSLAddress("127.0.0.1" + self.slconn.getSLAddress())
        if self.streamfile is not None:
            self.slconn.readStreamList(self.streamfile, self.selectors)
        if self.multiselect is not None:
            self.slconn.parseStreamlist(self.multiselect, self.selectors)
        else:
            if self.streamfile is None:
                self.slconn.setUniParams(self.selectors, -1, None)
        if self.statefile is not None:
            self.slconn.setStateFile(self.statefile)
        else:
            if self.begin_time is not None:
                self.slconn.setBeginTime(self.begin_time)
            if self.end_time is not None:
                self.slconn.setEndTime(self.end_time)

    def run(self):
        """
        Start this SLClient.
        """
        if self.infolevel is not None:
            self.slconn.requestInfo(self.infolevel)
        # Loop with the connection manager
        count = 1
        slpack = self.slconn.collect()
        while slpack is not None:
            if (slpack == SLPacket.SLTERMINATE):
                break
            try:
                # do something with packet
                terminate = self.packetHandler(count, slpack)
                if terminate:
                    break
            except SeedLinkException as sle:
                print(self.__class__.__name__ + ": " + sle.value)
            if count >= sys.maxint:
                count = 1
                print "DEBUG INFO: " + self.__class__.__name__ + ":",
                print "Packet count reset to 1"
            else:
                count += 1
            slpack = self.slconn.collect()

        # Close the SeedLinkConnection
        self.slconn.close()

    def packetHandler(self, count, slpack):
        """
        Processes each packet received from the SeedLinkConnection.

        This method should be overridden when sub-classing SLClient.

        :type count: int
        :param count:  Packet counter.
        :type slpack: :class:`~obspy.seedlink.SLPacket`
        :param slpack: packet to process.
        :return: Boolean true if connection to SeedLink server should be
            closed and session terminated, false otherwise.
        """
        # check if not a complete packet
        if slpack is None or (slpack == SLPacket.SLNOPACKET) or \
                (slpack == SLPacket.SLERROR):
            return False

        # get basic packet info
        seqnum = slpack.getSequenceNumber()
        type = slpack.getType()

        # process INFO packets here
        if (type == SLPacket.TYPE_SLINF):
            return False
        if (type == SLPacket.TYPE_SLINFT):
            print "Complete INFO:\n" + self.slconn.getInfoString()
            if self.infolevel is not None:
                return True
            else:
                return False

        # can send an in-line INFO request here
        try:
            #if (count % 100 == 0 and not self.slconn.state.expect_info):
            if (count % 100 == 0):
                infostr = "ID"
                self.slconn.requestInfo(infostr)
        except SeedLinkException as sle:
            print(self.__class__.__name__ + ": " + sle.value)

        # if here, must be a data blockette
        print self.__class__.__name__ + ": packet seqnum:",
        print str(seqnum) + ": blockette type: " + str(type)
        if not self.ppackets:
            return False

        # process packet data
        trace = slpack.getTrace()
        if trace is not None:
            print self.__class__.__name__ + ": blockette contains a trace: "
            print trace.id, trace.stats['starttime'],
            print " dt:" + str(1.0 / trace.stats['sampling_rate']),
            print " npts:" + str(trace.stats['npts']),
            print " sampletype:" + str(trace.stats['sampletype']),
            print " dataquality:" + str(trace.stats['dataquality'])
            if self.verbose >= 3:
                print self.__class__.__name__ + ":"
                print "blockette contains a trace: " + str(trace.stats)
        else:
            print self.__class__.__name__ + ": blockette contains no trace"
        return False

    def printUsage(self, concise=True):
        """
        Prints the usage message for this class.
        """
        print("\nUsage: python %s [options] <[host]:port>" %
              (self.__class__.__name__))
        if concise:
            usage = "Use '-h' for detailed help"
        else:
            usage = USAGE
        print(usage)

    @classmethod
    def main(cls, args):
        """
        Main method - creates and runs an SLClient using the specified
        command line arguments
        """
        slClient = None
        try:
            slClient = SLClient()
            rval = slClient.parseCmdLineArgs(args)
            if (rval != 0):
                sys.exit(rval)
            slClient.initialize()
            slClient.run()
        except Exception as e:
            logger.critical(e)
            traceback.print_exc()