build.rsudp.raspberryshake.getTR()   A
last analyzed

Complexity

Conditions 4

Size

Total Lines 38
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 38
ccs 15
cts 15
cp 1
rs 9.6
c 0
b 0
f 0
cc 4
nop 1
crap 4
1 1
import numpy as np
2 1
import os, platform
3 1
import socket as s
4 1
import signal
5 1
from obspy import UTCDateTime
6 1
from obspy.core.stream import Stream
7 1
from obspy import read_inventory, read
8 1
from obspy.geodetics.flinnengdahl import FlinnEngdahl
9 1
from obspy.core.trace import Trace
10 1
from rsudp import printM, printW, printE
11 1
from requests.exceptions import HTTPError
12 1
from threading import Thread
13 1
from . import __version__
14
15 1
initd, sockopen = False, False
16 1
qsize = 2048 			# max queue size
17 1
port = 8888				# default listening port
18 1
to = 10					# socket test timeout
19 1
firstaddr = ''			# the first address data is received from
20 1
inv = False				# station inventory
21 1
INVWARN = False			# warning when inventory attachment fails
22 1
region = False
23 1
producer = False 		# flag for producer status
24 1
stn = 'Z0000'			# station name
25 1
net = 'AM'				# network (this will always be AM)
26 1
chns = []				# list of channels
27 1
numchns = 0
28
29 1
tf = None				# transmission frequency in ms
30 1
tr = None				# transmission rate in packets per second
31 1
sps = None				# samples per second
32
33
# conversion units
34
# 		'name',	: ['pretty name', 'unit display']
35 1
UNITS = {'ACC'	: ['Acceleration', 'm/s$^2$'],
36
		 'GRAV'	: ['Earth gravity', ' g'],
37
		 'VEL'	: ['Velocity', 'm/s'],
38
		 'DISP'	: ['Displacement', 'm'],
39
		 'CHAN'	: ['channel-specific', ' Counts']}
40
41 1
g = 9.81	# earth gravity in m/s2
42
43
44
# get an IP to report to the user
45
# from https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib
46 1
def get_ip():
47
	'''
48
	.. |so_ip| raw:: html
49
50
		<a href="https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib" target="_blank">this stackoverflow answer</a>
51
52
53
	Return a reliable network IP to report to the user when there is no data received.
54
	This helps the user set their Raspberry Shake's datacast streams to point to the correct location
55
	if the library raises a "no data received" error.
56
	Solution adapted from |so_ip|.
57
58
	.. code-block:: python
59
60
		>>> get_ip()
61
		'192.168.1.23'
62
63
	:rtype: str
64
	:return: The network IP of the machine that this program is running on
65
	'''
66
67 1
	testsock = s.socket(s.AF_INET, s.SOCK_DGRAM)
68 1
	try:
69
		# doesn't even have to be reachable
70 1
		testsock.connect(('10.255.255.255', 1))
71 1
		IP = testsock.getsockname()[0]
72
	except:
73
		IP = '127.0.0.1'
74
	finally:
75 1
		testsock.close()
76 1
	return IP
77
78 1
ip = get_ip()
79
80
# construct a socket
81 1
socket_type =  s.SOCK_DGRAM
82 1
sock = s.socket(s.AF_INET, socket_type)
83 1
if platform.system() not in 'Windows':
84 1
    sock.setsockopt(s.SOL_SOCKET, s.SO_REUSEADDR, 1)
85
86 1
def handler(signum, frame, ip=ip):
87
	'''
88
	The signal handler for the nodata alarm.
89
90
	:param int signum: signal number
91
	:param int frame: frame number
92
	:param str ip: the IP of the box this program is running on (i.e. the device the Raspberry Shake should send data to)
93
	:raise IOError: on UNIX systems if no data is received
94
	'''
95
	global port
96
	printE('No data received in %s seconds; aborting.' % (to), sender='Init')
97
	printE('Check that the Shake is forwarding data to:', sender='Init', announce=False, spaces=True)
98
	printE('IP address: %s    Port: %s' % (ip, port), sender='Init', announce=False, spaces=True)
99
	printE('and that no firewall exists between the Shake and this computer.', sender='Init', announce=False, spaces=True)
100
	raise IOError('No data received')
101
102
103 1
def initRSlib(dport=port, rsstn='Z0000', timeout=10):
104
	'''
105
	.. role:: pycode(code)
106
		:language: python
107
108
	Initializes this library (:py:func:`rsudp.raspberryshake`).
109
	Set values for data port, station, network, and port timeout prior to opening the socket.
110
	Calls both :py:func:`rsudp.raspberryshake.openSOCK` and :py:func:`rsudp.raspberryshake.set_params`.
111
112
	.. code-block:: python
113
114
		>>> import rsudp.raspberryshake as rs
115
		>>> rs.initRSlib(dport=8888, rsstn='R3BCF')
116
117
	The library is now initialized:
118
119
	.. code-block:: python
120
121
		>>> rs.initd
122
		True
123
124
	:param int dport: The local port the Raspberry Shake is sending UDP data packets to. Defaults to :pycode:`8888`.
125
	:param str rsstn: The name of the station (something like :pycode:`'RCB43'` or :pycode:`'S0CDE'`)
126
	:param int timeout: The number of seconds for :py:func:`rsudp.raspberryshake.set_params` to wait for data before an error is raised (zero for unlimited wait)
127
128
	:rtype: str
129
	:return: The instrument channel as a string
130
131
	'''
132
	global port, stn, to, initd, port
133
	global producer
134 1
	sender = 'RS lib'
135 1
	printM('Initializing rsudp v %s.' % (__version__), sender)
136 1
	try:						# set port value first
137 1
		if dport == int(dport):
138 1
			port = int(dport)
139
		else:
140
			port = int(dport)
141
			printW('Supplied port value was converted to integer. Non-integer port numbers are invalid.')
142
	except Exception as e:
143
		printE('Details - %s' % e)
144
145 1
	try:						# set station name
146 1
		if len(rsstn) == 5:
147 1
			stn = str(rsstn).upper()
148
		else:
149
			stn = str(rsstn).upper()
150
			printW('Station name does not follow Raspberry Shake naming convention.')
151
	except ValueError as e:
152
		printE('Invalid station name supplied. Details: %s' % e)
153
		printE('reverting to station name Z0000', announce=False, spaces=True)
154
	except Exception as e:
155
		printE('Details - %s' % e)
156
	
157 1
	try:						# set timeout value 
158 1
		to = int(timeout)
159
	except ValueError as e:
160
		printW('You likely supplied a non-integer as the timeout value. Your value was: %s'
161
				% timeout)
162
		printW('Continuing with default timeout of %s sec'
163
				% (to), announce=False, spaces=True)
164
		printW('details: %s' % e, announce=False, spaces=True)
165
	except Exception as e:
166
		printE('Details - %s' % e)
167
168 1
	initd = True				# if initialization goes correctly, set initd to true
169 1
	openSOCK()					# open a socket
170 1
	printM('Waiting for UDP data on port %s...' % (port), sender)
171 1
	set_params()				# get data and set parameters
172
173 1
def openSOCK(host=''):
174
	'''
175
	.. role:: pycode(code)
176
		:language: python
177
178
	Initialize a socket at the port specified by :pycode:`rsudp.raspberryshake.port`.
179
	Called by :py:func:`rsudp.raspberryshake.initRSlib`, must be done before :py:func:`rsudp.raspberryshake.set_params`.
180
181
	:param str host: self-referential location at which to open a listening port (defaults to :pycode:`''` which resolves to :pycode:`'localhost'`)
182
	:raise IOError: if the library is not initialized (:py:func:`rsudp.raspberryshake.initRSlib`) prior to running this function
183
	:raise OSError: if the program cannot bind to the specified port number
184
185
	'''
186
	global sockopen
187 1
	sockopen = False
188 1
	if initd:
189 1
		HP = '%s:%s' % ('localhost',port)
190 1
		printM("Opening socket on %s (HOST:PORT)"
191
				% HP, 'openSOCK')
192 1
		try:
193 1
			sock.bind((host, port))
194 1
			sockopen = True
195
		except Exception as e:
196
			printE('Could not bind to port %s. Is another program using it?' % port)
197
			printE('Detail: %s' % e, announce=False)
198
			raise OSError(e)
199
	else:
200
		raise IOError("Before opening a socket, you must initialize this raspberryshake library by calling initRSlib(dport=XXXXX, rssta='R0E05') first.")
201
202 1
def set_params():
203
	'''
204
	.. role:: pycode(code)
205
		:language: python
206
207
	Read a data packet off the port.
208
	Called by :py:func:`rsudp.raspberryshake.initRSlib`,
209
	must be done after :py:func:`rsudp.raspberryshake.openSOCK`
210
	but before :py:func:`rsudp.raspberryshake.getDATA`.
211
	Will wait :pycode:`rsudp.raspberryshake.to` seconds for data before raising a no data exception
212
	(only available with UNIX socket types).
213
214
	'''
215
	global to, firstaddr
216 1
	if os.name not in 'nt': 	# signal alarm not available on windows
217 1
		signal.signal(signal.SIGALRM, handler)
218 1
		signal.alarm(to)		# alarm time set with timeout value
219 1
	data, (firstaddr, connport) = sock.recvfrom(2048)
220 1
	if os.name not in 'nt':
221 1
		signal.alarm(0)			# once data has been received, turn alarm completely off
222 1
	to = 0						# otherwise it erroneously triggers after keyboardinterrupt
223 1
	getTR(getCHNS()[0])
224 1
	getSR(tf, data)
225 1
	getTTLCHN()
226 1
	printM('Available channels: %s' % chns, 'Init')
227 1
	get_inventory()
228
229 1
def getDATA():
230
	'''
231
	Read a data packet off the port.
232
233
	In this example, we get a Shake 1Dv7 data packet:
234
235
	.. code-block:: python
236
237
		>>> import rsudp.raspberryshake as rs
238
		>>> rs.initRSlib(dport=8888, rsstn='R3BCF')
239
		>>> d = rs.getDATA()
240
		>>> d
241
		b"{'EHZ', 1582315130.292, 14168, 14927, 16112, 17537, 18052, 17477,
242
		15418, 13716, 15604, 17825, 19637, 20985, 17325, 10439, 11510, 17678,
243
		20027, 20207, 18481, 15916, 13836, 13073, 14462, 17628, 19388}"
244
245
246
	:rtype: bytes
247
	:return: Returns a data packet as an encoded bytes object.
248
249
	:raise IOError: if no socket is open (:py:func:`rsudp.raspberryshake.openSOCK`) prior to running this function
250
	:raise IOError: if the library is not initialized (:py:func:`rsudp.raspberryshake.initRSlib`) prior to running this function
251
252
	'''
253
	global to, firstaddr
254 1
	if sockopen:
255 1
		return sock.recv(4096)
256
	else:
257
		if initd:
258
			raise IOError("No socket is open. Please open a socket using this library's openSOCK() function.")
259
		else:
260
			raise IOError('No socket is open. Please initialize the library using initRSlib() then open a socket using openSOCK().')
261
	
262 1
def getCHN(DP):
263
	'''
264
	Extract the channel information from the data packet.
265
	Requires :py:func:`rsudp.raspberryshake.getDATA` packet as argument.
266
267
	In this example, we get the channel code from a Shake 1Dv7 data packet:
268
269
	.. code-block:: python
270
271
		>>> import rsudp.raspberryshake as rs
272
		>>> rs.initRSlib(dport=8888, rsstn='R3BCF')
273
		>>> d = rs.getDATA()
274
		>>> rs.getCHN(d)
275
		'EHZ'
276
277
	:param DP: The Raspberry Shake UDP data packet (:py:func:`rsudp.raspberryshake.getDATA`) to parse channel information from
278
	:type DP: bytes
279
	:rtype: str
280
	:return: Returns the instrument channel as a string.
281
	'''
282 1
	return str(DP.decode('utf-8').split(",")[0][1:]).strip("\'")
283
	
284 1
def getTIME(DP):
285
	'''
286
	Extract the timestamp from the data packet.
287
	Timestamp is seconds since 1970-01-01 00:00:00Z,
288
	which can be passed directly to an :py:class:`obspy.core.utcdatetime.UTCDateTime` object:
289
290
	In this example, we get the timestamp of a Shake 1Dv7 data packet and convert it to a UTCDateTime:
291
292
	.. code-block:: python
293
294
		>>> import rsudp.raspberryshake as rs
295
		>>> rs.initRSlib(dport=8888, rsstn='R3BCF')
296
		>>> from obspy import UTCDateTime
297
		>>> d = rs.getDATA()
298
		>>> t = rs.getTIME(d)
299
		>>> t
300
		1582315130.292
301
		>>> dt = obspy.UTCDateTime(t, precision=3)
302
		>>> dt
303
		UTCDateTime(2020, 2, 21, 19, 58, 50, 292000)
304
305
	:param DP: The Raspberry Shake UDP data packet (:py:func:`rsudp.raspberryshake.getDATA`) to parse time information from
306
	:type DP: bytes
307
	:rtype: float
308
	:return: Timestamp in decimal seconds since 1970-01-01 00:00:00Z
309
	'''
310 1
	return float(DP.split(b",")[1])
311
312 1
def getSTREAM(DP):
313
	'''
314
	Get the samples in a data packet as a list object.
315
	Requires :py:func:`rsudp.raspberryshake.getDATA` packet as argument.
316
317
	In this example, we get a list of samples from a Shake 1Dv7 data packet:
318
319
	.. code-block:: python
320
321
		>>> import rsudp.raspberryshake as rs
322
		>>> rs.initRSlib(dport=8888, rsstn='R3BCF')
323
		>>> d = rs.getDATA()
324
		>>> s = rs.getSTREAM(d)
325
		>>> s
326
		[14168, 14927, 16112, 17537, 18052, 17477, 15418, 13716, 15604,
327
		 17825, 19637, 20985, 17325, 10439, 11510, 17678, 20027, 20207,
328
		 18481, 15916, 13836, 13073, 14462, 17628, 19388]
329
330
	:param DP: The Raspberry Shake UDP data packet (:py:func:`rsudp.raspberryshake.getDATA`) to parse stream information from
331
	:type DP: bytes
332
	:rtype: list
333
	:return: List of data samples in the packet
334
	'''
335 1
	return list(map(int, DP.decode('utf-8').replace('}','').split(',')[2:]))
336
337 1
def getTR(chn):				# DP transmission rate in msecs
338
	'''
339
	Get the transmission rate in milliseconds between consecutive packets from the same channel.
340
	Must wait to receive a second packet from the same channel.
341
	Requires a :py:func:`rsudp.raspberryshake.getCHN` or a channel name string as argument.
342
343
	In this example, we calculate the transmission frequency of a Shake 1Dv7:
344
345
	.. code-block:: python
346
347
		>>> import rsudp.raspberryshake as rs
348
		>>> rs.initRSlib(dport=8888, rsstn='R3BCF')
349
		>>> d = rs.getDATA()
350
		>>> tr = rs.getTR(rs.getCHN(d))
351
		>>> tr
352
		250
353
354
	:param chn: The seismic instrument channel (:py:func:`rsudp.raspberryshake.getCHN`) to calculate transmission rate information from
355
	:type chn: str
356
	:rtype: int
357
	:return: Transmission rate in milliseconds between consecutive packets from a specific channel
358
	'''
359
	global tf, tr
360 1
	timeP1, timeP2 = 0.0, 0.0
361 1
	done = False
362 1
	while not done:
363 1
		DP = getDATA()
364 1
		CHAN = getCHN(DP)
365 1
		if CHAN == chn:
366 1
			if timeP1 == 0.0:
367 1
				timeP1 = getTIME(DP)
368
			else:
369 1
				timeP2 = getTIME(DP)
370 1
				done = True
371 1
	TR = timeP2*1000 - timeP1*1000
372 1
	tf = int(TR)
373 1
	tr = int(1000 / TR)
374 1
	return tf
375
376 1
def getSR(TR, DP):
377
	'''
378
	Get the sample rate in samples per second.
379
	Requires an integer transmission frequency and a data packet as arguments.
380
381
	In this example, we calculate the number of samples per second from a Shake 1Dv7:
382
383
	.. code-block:: python
384
385
		>>> import rsudp.raspberryshake as rs
386
		>>> rs.initRSlib(dport=8888, rsstn='R3BCF')
387
		>>> d = rs.getDATA()
388
		>>> tr = rs.getTR(rs.getCHN(d))
389
		>>> tr
390
		250
391
		>>> sps = rs.getSR(tr, d)
392
		>>> sps
393
		100
394
395
396
	:param TR: The transmission frequency (:py:func:`rsudp.raspberryshake.getTR`) in milliseconds between packets
397
	:type TR: int
398
	:param DP: The Raspberry Shake UDP data packet (:py:func:`rsudp.raspberryshake.getDATA`) calculate sample rate information from
399
	:type DP: bytes
400
	:rtype: int
401
	:return: The sample rate in samples per second from a specific channel
402
	'''
403
	global sps
404 1
	sps = int((DP.count(b",") - 1) * 1000 / TR)
405 1
	return sps
406
	
407 1
def getCHNS():
408
	'''
409
	Get a list of channels sent to the port.
410
411
	In this example, we list channels from a Boom:
412
413
	.. code-block:: python
414
415
		>>> import rsudp.raspberryshake as rs
416
		>>> rs.initRSlib(dport=8888, rsstn='R940D')
417
		>>> rs.getCHNS()
418
		['EHZ', 'HDF']
419
420
421
	:rtype: list
422
	:return: The list of channels being sent to the port (from the single IP address sending data)
423
	'''
424
	global chns
425 1
	chdict = {'EHZ': False, 'EHN': False, 'EHE': False,
426
			  'ENZ': False, 'ENN': False, 'ENE': False, 'HDF': False}
427 1
	firstCHN = ''
428 1
	done = False
429 1
	sim = 0
430 1
	while not done:
431 1
		DP = getDATA()
432 1
		if firstCHN == '':
433 1
			firstCHN = getCHN(DP)
434 1
			chns.append(firstCHN)
435 1
			continue
436 1
		nextCHN = getCHN(DP)
437 1
		if firstCHN == nextCHN:
438 1
			if sim > 1:
439 1
				done = True
440 1
				continue
441 1
			sim += 1
442
		else:
443 1
			chns.append(nextCHN)
444 1
	for ch in chns:
445 1
		chdict[ch] = True
446 1
	chns = []
447 1
	for ch in chdict:
448 1
		if chdict[ch] == True:
449 1
			chns.append(ch)
450 1
	return chns
451
452 1
def getTTLCHN():
453
	'''
454
	Calculate total number of channels received,
455
	by counting the number of channels returned by :py:func:`rsudp.raspberryshake.getCHNS`.
456
457
	In this example, we get the number of channels from a Shake & Boom:
458
459
	.. code-block:: python
460
461
		>>> import rsudp.raspberryshake as rs
462
		>>> rs.initRSlib(dport=8888, rsstn='R940D')
463
		>>> rs.getTTLCHN()
464
		2
465
466
	:rtype: int
467
	:return: The number of channels being sent to the port (from the single IP address sending data)
468
	'''
469
	global numchns
470 1
	numchns = len(getCHNS())
471 1
	return numchns
472
473
474 1
def get_inventory(sender='get_inventory'):
475
	'''
476
	.. role:: pycode(code)
477
		:language: python
478
479
	Downloads the station inventory from the Raspberry Shake FDSN and stores
480
	it as an :py:class:`obspy.core.inventory.inventory.Inventory` object which is available globally.
481
482
	In this example, we get the R940D station inventory from the Raspberry Shake FDSN:
483
484
	.. code-block:: python
485
486
		>>> import rsudp.raspberryshake as rs
487
		>>> rs.initRSlib(dport=8888, rsstn='R940D')
488
		>>> inv = rs.get_inventory()
489
		>>> print(inv)
490
		Inventory created at 2020-02-21T20:37:34.246777Z
491
			Sending institution: SeisComP3 (gempa testbed)
492
			Contains:
493
				Networks (1):
494
					AM
495
				Stations (1):
496
					AM.R940D (Raspberry Shake Citizen Science Station)
497
				Channels (2):
498
					AM.R940D.00.EHZ, AM.R940D.00.HDF
499
500
501
	:param sender: `(optional)` The name of the function calling the :py:func:`rsudp.printM` logging function
502
	:type str: str or None
503
	:rtype: obspy.core.inventory.inventory.Inventory or bool
504
	:return: The inventory of the Raspberry Shake station in the :pycode:`rsudp.raspberryshake.stn` variable.
505
	'''
506
	global inv, stn, region
507 1
	sender = 'get_inventory'
508 1
	if 'Z0000' in stn:
509
		printW('No station name given, continuing without inventory.',
510
				sender)
511
		inv = False
512
	else:
513 1
		try:
514 1
			printM('Fetching inventory for station %s.%s from Raspberry Shake FDSN.'
515
					% (net, stn), sender)
516 1
			url = 'https://fdsnws.raspberryshakedata.com/fdsnws/station/1/query?network=%s&station=%s&level=resp&nodata=404&format=xml' % (
517
				   net, stn)#, str(UTCDateTime.now()-timedelta(seconds=14400)))
518 1
			inv = read_inventory(url)
519 1
			region = FlinnEngdahl().get_region(inv[0][-1].longitude, inv[0][-1].latitude)
520 1
			printM('Inventory fetch successful. Station region is %s' % (region), sender)
521
		except (IndexError, HTTPError):
522
			printW('No inventory found for %s. Are you forwarding your Shake data?' % stn, sender)
523
			printW('Deconvolution will only be available if data forwarding is on.', sender, spaces=True)
524
			printW('Access the config page of the web front end for details.', sender, spaces=True)
525
			printW('More info at https://manual.raspberryshake.org/quickstart.html', sender, spaces=True)
526
			inv = False
527
			region = False
528
		except Exception as e:
529
			printE('Inventory fetch failed!', sender)
530
			printE('Error detail: %s' % e, sender, spaces=True)
531
			inv = False
532
			region = False
533 1
	return inv
534
535
536 1
def make_trace(d):
537
	'''
538
	Makes a trace and assigns it some values using a data packet.
539
540
	In this example, we make a trace object with some RS 1Dv7 data:
541
542
	.. code-block:: python
543
544
		>>> import rsudp.raspberryshake as rs
545
		>>> rs.initRSlib(dport=8888, rsstn='R3BCF')
546
		>>> d = rs.getDATA()
547
		>>> t = rs.make_trace(d)
548
		>>> print(t)
549
		AM.R3BCF.00.EHZ | 2020-02-21T19:58:50.292000Z - 2020-02-21T19:58:50.532000Z | 100.0 Hz, 25 samples
550
551
	:param d: The Raspberry Shake UDP data packet (:py:func:`rsudp.raspberryshake.getDATA`) to parse Trace information from
552
	:type d: bytes
553
	:rtype: obspy.core.trace.Trace
554
	:return: A fully formed Trace object to build a Stream with
555
	'''
556
	global INVWARN
557 1
	ch = getCHN(d)						# channel
558 1
	if ch:
559 1
		t = getTIME(d)				# unix epoch time since 1970-01-01 00:00:00Z; "timestamp" in obspy
560 1
		st = getSTREAM(d)				# samples in data packet in list [] format
561 1
		tr = Trace(data=np.ma.MaskedArray(st, dtype=np.int32))	# create empty trace
562 1
		tr.stats.network = net			# assign values
563 1
		tr.stats.location = '00'
564 1
		tr.stats.station = stn
565 1
		tr.stats.channel = ch
566 1
		tr.stats.sampling_rate = sps
567 1
		tr.stats.starttime = UTCDateTime(t, precision=3)
568 1
		if inv:
569 1
			try:
570 1
				tr.stats.response = inv.get_response(tr.id, tr.stats.starttime)
571
			except Exception as e:
572
				if not INVWARN:
573
					INVWARN = True
574
					printE(e, sender='make_trace')
575
					printE('Could not attach inventory response.', sender='make_trace')
576
					printE('Are you sure you set the station name correctly?', spaces=True, sender='make_trace')
577
					printE('This could indicate a mismatch in the number of data channels', spaces=True, sender='make_trace')
578
					printE('between the inventory and the stream. For example,', spaces=True, sender='make_trace')
579
					printE('if you are receiving RS4D data, please make sure', spaces=True, sender='make_trace')
580
					printE('the inventory you download has 4 channels.', spaces=True, sender='make_trace')
581
				else:
582
					pass
583 1
		return tr
584
585
586
# Then make repeated calls to this, to continue adding trace data to the stream
587 1
def update_stream(stream, d, **kwargs):
588
	'''
589
	Returns an updated Stream object with new data, merged down to one trace per available channel.
590
	Most sub-consumers call this each time they receive data packets in order to keep their obspy stream current.
591
592
	In this example, we make a stream object with some RS 1Dv7 data:
593
594
	.. code-block:: python
595
596
		>>> import rsudp.raspberryshake as rs
597
		>>> from obspy.core.stream import Stream
598
		>>> rs.initRSlib(dport=8888, rsstn='R3BCF')
599
		>>> s = Stream()
600
		>>> d = rs.getDATA()
601
		>>> t = rs.make_trace(d)
602
		>>> s = rs.update_stream(s, d)
603
		>>> print(s)
604
		1 Trace(s) in Stream:
605
		AM.R3BCF.00.EHZ | 2020-02-21T19:58:50.292000Z - 2020-02-21T19:58:50.532000Z | 100.0 Hz, 25 samples
606
607
608
	:param obspy.core.stream.Stream stream: The stream to update
609
	:param d: The Raspberry Shake UDP data packet (:py:func:`rsudp.raspberryshake.getDATA`) to parse Stream information from
610
	:type d: bytes
611
	:rtype: obspy.core.stream.Stream
612
	:return: A seismic data stream
613
	'''
614 1
	while True:
615 1
		try:
616 1
			return stream.append(make_trace(d)).merge(**kwargs)
617
		except TypeError:
618
			pass
619
620
621 1
def copy(orig):
622
	"""
623
	True-copy a stream by creating a new stream and copying old attributes to it.
624
	This is necessary because the old stream accumulates *something* that causes
625
	CPU usage to increase over time as more data is added. This is a bug in obspy
626
	that I intend to find--or at the very least report--but until then this hack
627
	works fine and is plenty fast enough.
628
629
	In this example, we make a stream object with some RS 1Dv7 data and then copy it to a new stream:
630
631
	.. code-block:: python
632
633
		>>> import rsudp.raspberryshake as rs
634
		>>> from obspy.core.stream import Stream
635
		>>> rs.initRSlib(dport=8888, rsstn='R3BCF')
636
		>>> s = Stream()
637
		>>> d = rs.getDATA()
638
		>>> t = rs.make_trace(d)
639
		>>> s = rs.update_stream(s, d)
640
		>>> s
641
		1 Trace(s) in Stream:
642
		AM.R3BCF.00.EHZ | 2020-02-21T19:58:50.292000Z - 2020-02-21T19:58:50.532000Z | 100.0 Hz, 25 samples
643
		>>> s = rs.copy(s)
644
		>>> s
645
		1 Trace(s) in Stream:
646
		AM.R3BCF.00.EHZ | 2020-02-21T19:58:50.292000Z - 2020-02-21T19:58:50.532000Z | 100.0 Hz, 25 samples
647
648
649
	:param obspy.core.stream.Stream orig: The data stream to copy information from
650
	:rtype: obspy.core.stream.Stream
651
	:return: A low-memory copy of the passed data stream
652
653
	"""
654 1
	stream = Stream()
655 1
	for t in range(len(orig)):
656 1
		trace = Trace(data=orig[t].data)
657 1
		trace.stats.network = orig[t].stats.network
658 1
		trace.stats.location = orig[t].stats.location
659 1
		trace.stats.station = orig[t].stats.station
660 1
		trace.stats.channel = orig[t].stats.channel
661 1
		trace.stats.sampling_rate = orig[t].stats.sampling_rate
662 1
		trace.stats.starttime = orig[t].stats.starttime
663 1
		stream.append(trace).merge(fill_value=None)
664 1
	return stream.copy()
665
666
667 1
class ConsumerThread(Thread):
668
	'''
669
	The default consumer thread setup.
670
	Import this consumer and easily create your own consumer modules!
671
	This class modifies the :py:class:`threading.Thread` object to
672
	include some settings that all rsudp consumers need,
673
	some of which the :py:class:`rsudp.p_producer.Producer`
674
	needs in order to function.
675
676
	Currently, the modifications that this module makes to
677
	:py:class:`threading.Thread` objects are:
678
679
	.. code-block:: python
680
681
		self.sender = 'ConsumerThread'  # module name used in logging
682
		self.alarm = False              # the Producer reads this to set the ``ALARM`` state
683
		self.alarm_reset = False        # the Producer reads this to set the ``RESET`` state
684
		self.alive = True               # this is used to keep the main ``for`` loop running
685
686
	For more information on creating your own consumer threads,
687
	see :ref:`add_your_own`.
688
689
	'''
690 1
	def __init__(self):
691 1
		super().__init__()
692 1
		self.sender = 'ConsumerThread'	# used in logging
693 1
		self.alarm = False				# the producer reads this
694 1
		self.alarm_reset = False		# the producer reads this
695 1
		self.alive = True				# this is used to keep the main for loop running
696
697
698
if __name__ == '__main__':
699
	pass
700