Passed
Push — master ( b9ba1c...723864 )
by Ian
06:13
created

build.rsudp.raspberryshake   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 703
Duplicated Lines 0 %

Test Coverage

Coverage 76.02%

Importance

Changes 0
Metric Value
wmc 52
eloc 272
dl 0
loc 703
ccs 187
cts 246
cp 0.7602
rs 7.44
c 0
b 0
f 0

17 Functions

Rating   Name   Duplication   Size   Complexity  
A get_ip() 0 31 2
A openSOCK() 0 28 3
B make_trace() 0 48 5
A set_params() 0 26 3
A getTTLCHN() 0 20 1
A getDATA() 0 32 3
A handler() 0 15 1
A update_stream() 0 32 3
A getTIME() 0 27 1
B getCHNS() 0 44 8
A copy() 0 44 2
A getSTREAM() 0 24 1
B initRSlib() 0 69 8
A getTR() 0 38 4
A getCHN() 0 21 1
A getSR() 0 30 1
A get_inventory() 0 60 4

1 Method

Rating   Name   Duplication   Size   Complexity  
A ConsumerThread.__init__() 0 6 1

How to fix   Complexity   

Complexity

Complex classes like build.rsudp.raspberryshake often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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