/
slclient.py
343 lines (310 loc) · 12.6 KB
/
slclient.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Module 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 packet_handler method of SLClient.
Part of Python implementation of libslink of Chad Trabant and
JSeedLink of Anthony Lomax
:copyright:
The ObsPy Development Team (devs@obspy.org) & Anthony Lomax
:license:
GNU Lesser General Public License, Version 3
(https://www.gnu.org/copyleft/lesser.html)
"""
import sys
from obspy.core.util.decorator import deprecated_keywords
from .client.seedlinkconnection import SeedLinkConnection
from .seedlinkexception import SeedLinkException
from .slpacket import SLPacket
USAGE = """
## General program options ##
-V report program version
-h show this usage message
-v be more verbose, multiple flags can be used
-p print details of data packets
-nd delay network re-connect delay (seconds), default 30
-nt timeout network timeout (seconds), re-establish connection if no
data/keepalives are received in this time, default 600
-k interval send keepalive (heartbeat) packets this often (seconds)
-x statefile save/restore stream state information to this file
-t begintime sets a beginning time for the initiation of data transmission
(year,month,day,hour,minute,second)
-e endtime sets an end time for windowed data transmission
(year,month,day,hour,minute,second)
-i infolevel request this INFO level, write response to std out, and exit
infolevel is one of: ID, STATIONS, STREAMS, GAPS, CONNECTIONS,
ALL
## Data stream selection ##
-l listfile read a stream list from this file for multi-station mode
-s selectors selectors for uni-station or default for multi-station
-S streams select streams for multi-station (requires SeedLink >= 2.5)
'streams' = 'stream1[:selectors1],stream2[:selectors2],...'
'stream' is in NET_STA format, for example:
-S \"IU_KONO:BHE BHN,GE_WLF,MN_AQU:HH?.D\"
<[host]:port> Address of the SeedLink server in host:port format
if host is omitted (i.e. ':18000'), localhost is assumed
"""
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 packet_handler 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: bool
: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
:type timeout: float
:param timeout: Timeout in seconds, passed on to the underlying
SeedLinkConnection.
"""
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 + ")"
@deprecated_keywords({"loglevel": None})
def __init__(self, loglevel=None, timeout=None):
"""
Creates a new instance of SLClient with the specified logging object
"""
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.timeout = timeout
self.slconn = SeedLinkConnection(timeout=timeout)
def parse_cmd_line_args(self, args):
"""
Parses the command 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.print_usage(False)
return 1
optind = 1
while optind < len(args):
if args[optind] == "-V":
print(self.VERSION_INFO, file=sys.stderr)
return 1
elif args[optind] == "-h":
self.print_usage(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.set_net_timeout(int(args[optind]))
elif args[optind] == "-nd":
optind += 1
self.slconn.set_net_delay(int(args[optind]))
elif args[optind] == "-k":
optind += 1
self.slconn.set_keep_alive(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], file=sys.stderr)
return -1
elif self.slconn.get_sl_address() is None:
self.slconn.set_sl_address(args[optind])
else:
print("Unknown option: " + args[optind], file=sys.stderr)
return -1
optind += 1
return 0
def initialize(self):
"""
Initializes this SLClient.
"""
if self.slconn.get_sl_address() is None:
message = "no SeedLink server specified"
raise SeedLinkException(message)
if self.verbose >= 2:
self.ppackets = True
if self.slconn.get_sl_address().startswith(":"):
self.slconn.set_sl_address("127.0.0.1" +
self.slconn.get_sl_address())
if self.streamfile is not None:
self.slconn.read_stream_list(self.streamfile, self.selectors)
if self.multiselect is not None:
self.slconn.parse_stream_list(self.multiselect, self.selectors)
else:
if self.streamfile is None:
self.slconn.set_uni_params(self.selectors, -1, None)
if self.statefile is not None:
self.slconn.set_state_file(self.statefile)
else:
if self.begin_time is not None:
self.slconn.set_begin_time(self.begin_time)
if self.end_time is not None:
self.slconn.set_end_time(self.end_time)
def run(self, packet_handler=None):
"""
Start this SLClient.
:type packet_handler: callable
:param packet_handler: Custom packet handler funtion to override
`self.packet_handler` for this seedlink request. The function will
be repeatedly called with two arguments: the current packet counter
(`int`) and the currently served seedlink packet
(:class:`~obspy.clients.seedlink.slclient.SLPacket`). The
function should return `True` to abort the request or `False` to
continue the request.
"""
if packet_handler is None:
packet_handler = self.packet_handler
if self.infolevel is not None:
self.slconn.request_info(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 = packet_handler(count, slpack)
if terminate:
break
except SeedLinkException as sle:
print(self.__class__.__name__ + ": " + sle.value)
if count >= sys.maxsize:
count = 1
print("DEBUG INFO: " + self.__class__.__name__ + ":", end=' ')
print("Packet count reset to 1")
else:
count += 1
slpack = self.slconn.collect()
# Close the SeedLinkConnection
self.slconn.close()
def packet_handler(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.clients.seedlink.slpacket.SLPacket`
:param slpack: packet to process.
:rtype: bool
:return: 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.get_sequence_number()
type = slpack.get_type()
# process INFO packets here
if (type == SLPacket.TYPE_SLINF):
return False
if (type == SLPacket.TYPE_SLINFT):
print("Complete INFO:\n" + self.slconn.get_info_string())
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.request_info(infostr)
except SeedLinkException as sle:
print(self.__class__.__name__ + ": " + sle.value)
# if here, must be a data blockette
print(self.__class__.__name__ + ": packet seqnum:", end=' ')
print(str(seqnum) + ": blockette type: " + str(type))
if not self.ppackets:
return False
# process packet data
trace = slpack.get_trace()
if trace is not None:
print(self.__class__.__name__ + ": blockette contains a trace: ")
print(trace.id, trace.stats['starttime'], end=' ')
print(" dt:" + str(1.0 / trace.stats['sampling_rate']), end=' ')
print(" npts:" + str(trace.stats['npts']), end=' ')
print(" sampletype:" + str(trace.stats['sampletype']), end=' ')
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 print_usage(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
"""
sl_client = SLClient()
rval = sl_client.parse_cmd_line_args(args)
if (rval != 0):
sys.exit(rval)
sl_client.initialize()
sl_client.run()
if __name__ == '__main__':
SLClient.main(sys.argv)