You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
8.6 KiB
Python
261 lines
8.6 KiB
Python
import os, sys, tempfile
|
|
import datetime, time, re
|
|
from seiscomp import mseedlite as mseed
|
|
|
|
def _timeparse(t, format):
|
|
"""Parse a time string that might contain fractions of a second.
|
|
|
|
Fractional seconds are supported using a fragile, miserable hack.
|
|
Given a time string like '02:03:04.234234' and a format string of
|
|
'%H:%M:%S', time.strptime() will raise a ValueError with this
|
|
message: 'unconverted data remains: .234234'. If %S is in the
|
|
format string and the ValueError matches as above, a datetime
|
|
object will be created from the part that matches and the
|
|
microseconds in the time string.
|
|
"""
|
|
try:
|
|
return datetime.datetime(*time.strptime(t, format)[0:6]).time()
|
|
except ValueError as msg:
|
|
if "%S" in format:
|
|
msg = str(msg)
|
|
mat = re.match(r"unconverted data remains:"
|
|
" \.([0-9]{1,6})$", msg)
|
|
if mat is not None:
|
|
# fractional seconds are present - this is the style
|
|
# used by datetime's isoformat() method
|
|
frac = "." + mat.group(1)
|
|
t = t[:-len(frac)]
|
|
t = datetime.datetime(*time.strptime(t, format)[0:6])
|
|
microsecond = int(float(frac)*1e6)
|
|
return t.replace(microsecond=microsecond)
|
|
else:
|
|
mat = re.match(r"unconverted data remains:"
|
|
" \,([0-9]{3,3})$", msg)
|
|
if mat is not None:
|
|
# fractional seconds are present - this is the style
|
|
# used by the logging module
|
|
frac = "." + mat.group(1)
|
|
t = t[:-len(frac)]
|
|
t = datetime.datetime(*time.strptime(t, format)[0:6])
|
|
microsecond = int(float(frac)*1e6)
|
|
return t.replace(microsecond=microsecond)
|
|
|
|
raise
|
|
|
|
def timeparse(t):
|
|
return _timeparse(t, "%Y/%m/%d %H:%M:%S")
|
|
|
|
|
|
class Input(mseed.Input):
|
|
|
|
def __init__(self, server, streams,
|
|
stime=None, etime=None, timeout=None, verbose=0):
|
|
|
|
# XXX Add the possibility for supplying stime and etime as
|
|
# individual times for each stream.
|
|
|
|
"""
|
|
'streams' must be a list containing tuples of (net,sta,loc,cha)
|
|
"""
|
|
|
|
import subprocess
|
|
|
|
streams = [ "%-3s %5s %s%3s.D" % s for s in streams ]
|
|
streams.sort()
|
|
|
|
self.tmp = tempfile.NamedTemporaryFile(mode="w", prefix="slinktool.")
|
|
self.tmp.write("\n".join(streams)+"\n")
|
|
self.tmp.flush()
|
|
if verbose:
|
|
sys.stderr.write("\n".join(streams)+"\n")
|
|
|
|
slinktool = os.getenv("SLINKTOOL")
|
|
if not slinktool:
|
|
slinktool = "slinktool"
|
|
args = [slinktool, "-l", self.tmp.name, "-o", "-"]
|
|
if stime:
|
|
args.append("-tw")
|
|
tw = "%d,%d,%d,%d,%d,%d:" % (stime.year,stime.month,stime.day,stime.hour,stime.minute,stime.second)
|
|
if etime:
|
|
rw += "%d,%d,%d,%d,%d,%d" % (etime.year,etime.month,etime.day,etime.hour,etime.minute,etime.second)
|
|
args.append(tw)
|
|
if verbose: args.append("-v")
|
|
|
|
if timeout:
|
|
try: assert int(timeout) > 0
|
|
except: raise TypeError("illegal timeout parameter")
|
|
args += ["-nt", "%d" % int(timeout)]
|
|
|
|
args.append(server)
|
|
# start 'slinktool' as sub-process
|
|
self.popen = subprocess.Popen(args, stdout=subprocess.PIPE, shell=False)
|
|
infile = self.popen.stdout
|
|
|
|
mseed.Input.__init__(self, infile)
|
|
|
|
def __del__(self):
|
|
"""
|
|
Shut down SeedLink connections and close input.
|
|
"""
|
|
sys.stderr.write("shutting down slinktool\n")
|
|
sys.stderr.flush()
|
|
|
|
slinktool_pid = self.popen.pid
|
|
# It would of course be much better to send SIGTERM,
|
|
# but somehow slinktool often appears to ignore it.
|
|
# XXX Need to figure out why, and perhaps fix it (not critical).
|
|
self.popen.kill()
|
|
self.popen.communicate()
|
|
# mseed.Input.__del__(self) # closes the input file
|
|
|
|
|
|
|
|
class Input2(mseed.Input):
|
|
|
|
def __init__(self, server, streams, stime=None, etime=None, verbose=0):
|
|
|
|
"""
|
|
XXX information not uptodate!!! XXX
|
|
|
|
'streams' must be a dict containing tuples of (stime, etime),
|
|
with the key being the stream_id and stime and etime being
|
|
the starting and end time of the time window, respectively.
|
|
The times must be seis.Time objects. For instance
|
|
|
|
stime = seis.Time(...)
|
|
etime = seis.Time(...)
|
|
streams["GE.KBS.00.BHZ.D"] = (stime, etime)
|
|
|
|
It is more efficient to request the same time interval for
|
|
all streams. Wildcards for the channels are allowed. If
|
|
stime is None, only new data are retrieved as they come in.
|
|
"""
|
|
|
|
streams = [ "%-3s %5s %s%3s.D" % tuple(s.split(".")[:4])
|
|
for s in streams ]
|
|
streams.sort()
|
|
|
|
self.tmp = tempfile.NamedTemporaryFile(mode="w", prefix="slinktool.")
|
|
self.tmp.write("\n".join(streams)+"\n")
|
|
sys.stderr.write("\n".join(streams)+"\n")
|
|
self.tmp.flush()
|
|
|
|
cmd = "slinktool -l %s -o -" % self.tmp.name
|
|
if stime:
|
|
assert isinstance(stime, seis.Time)
|
|
cmd += " -tw %d,%d,%d,%d,%d,%d:" % stime.asDate
|
|
if etime:
|
|
assert isinstance(etime, seis.Time)
|
|
cmd += "%d,%d,%d,%d,%d,%d" % etime.asDate
|
|
cmd = cmd + "%s '%s'" % (verbose*" -v", server)
|
|
|
|
infile = os.popen(cmd)
|
|
|
|
mseed.Input.__init__(self, infile)
|
|
|
|
|
|
def available(server="localhost:18000",
|
|
time_window=None, stream_ids=None, verbose=0):
|
|
|
|
"""
|
|
Connects to server and returns a dictionary of lists of available
|
|
time windows as tuples of (start_time, end_time) for each available
|
|
stream. The stream set can be limited by specifying a list of
|
|
stream_ids in the format usual format, i.e. net.sta.loc.cha.type,
|
|
e.g. "GE.KBS.00.BHZ.D".
|
|
Note that often the returned lists contain only one time tuple,
|
|
corresponding to one contiguous time window available.
|
|
|
|
NEW:
|
|
The search for available data can be limited to a time window by
|
|
specifying the "time_window" parameter, which must be a tuple
|
|
containing the starting and end time as seis.Time objects.
|
|
"""
|
|
|
|
import re
|
|
|
|
if time_window:
|
|
stime, etime = time_window
|
|
assert stime <= etime
|
|
else:
|
|
stime, etime = None, None
|
|
|
|
cmd = "slinktool -Q %s %s " % (verbose*"-v ", server)
|
|
infile = os.popen(cmd)
|
|
windows = {}
|
|
|
|
# parse the output of "slinktool -Q"
|
|
# It is assumed that the lines consist of the fields
|
|
# net,sta,[loc,], cha, type, date1, time1, "-", date2, time2
|
|
# Since the location code (loc) may or may not be present, we
|
|
# determine the position of the dash "-" to determine where the
|
|
# other fields are.
|
|
regex = re.compile("^[A-Z][A-Z]\ [A-Z].*[12][0-9]{3}(/[0-9]{2}){2}.*$")
|
|
for line in infile:
|
|
if regex.match(line): # line containing a time window, a bit crude
|
|
|
|
line = line.split()
|
|
try:
|
|
dash = line.index("-")
|
|
except ValueError:
|
|
continue
|
|
|
|
if dash==7: # location code is present
|
|
loc = line[2]
|
|
else: loc = ""
|
|
|
|
net, sta, cha, typ = line[0], line[1], line[dash-4], line[dash-3]
|
|
|
|
stream_id = "%s.%s.%s.%s.%s" % (net, sta, loc, cha, typ)
|
|
|
|
if stream_ids and stream_id not in stream_ids:
|
|
continue
|
|
|
|
t1 = seis.Time("%s %s" % (line[dash-2], line[dash-1]))
|
|
t2 = seis.Time("%s %s" % (line[dash+1], line[dash+2]))
|
|
|
|
if stime and t2<stime or etime and t1>etime:
|
|
continue # non-overlapping time windows
|
|
|
|
if stime and t1<stime:
|
|
t1 = stime
|
|
if etime and t2>etime:
|
|
t2 = etime
|
|
|
|
if not stream_id in windows:
|
|
windows[stream_id] = []
|
|
|
|
windows[stream_id].append((t1,t2))
|
|
|
|
elif verbose:
|
|
# probably some diagnostic output
|
|
sys.stdout.write("%s\n" % line.strip())
|
|
|
|
return windows
|
|
|
|
|
|
def server_version(host, port=18000):
|
|
|
|
import socket
|
|
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
try:
|
|
s.connect((host, port))
|
|
except:
|
|
return None
|
|
s.send("HELLO\n")
|
|
data = s.recv(1024)
|
|
s.close()
|
|
if data[:8] != "SeedLink":
|
|
return None
|
|
|
|
return data[10:13]
|
|
|
|
|
|
def server_running(host, port=18000):
|
|
|
|
if server_version(host, port):
|
|
return True
|
|
|
|
return False
|