#!/usr/bin/env python3 import getopt import os import sys import signal from fractions import Fraction import numpy as np from gempa import CAPS class Record: net = "" sta = "" loc = "" cha = "" samples = [] start = CAPS.Time.GMT() numerator = 0 denominator = 1 nSamp = 0 unit = "" usage_info = f"""Usage: {os.path.basename(__file__)} [options] Push data into CAPS. The input and output formats can be easily adjusted. Options: -H, --host Host name of CAPS server to connect to. Default: localhost:18003. -h, --help Display this help message and exit. -i, --input Name of input file. Create a generic signal if not given. -f, --format Format of input data. Supported: slist, unavco. -m, --multiplier Multiplier applied to data samples for generating integers. -n, --network Network code of data to be sent. Read from input if not given. Examples: Generate generic signal assuming AM network code and send to a caps server with default port 18003 {os.path.basename(__file__)} -n AM -H localhost:18003 Read data file in slist format, upscale values by 1000000000 and send in RAW format to the default caps server {os.path.basename(__file__)} -i file.slist -f slist -m 1000000000 Read data file in unavco 1.0 format and send in RAW format. The network code must be specified. {os.path.basename(__file__)} -f unavco -i unavco-pressure-v1-0.txt -n AB Read data file in unavco 1.1 format and send in RAW format {os.path.basename(__file__)} -f unavco -i unavco-tilt-v1-1.txt """ def usage(exitcode=0): sys.stderr.write(usage_info) return exitcode output = CAPS.Plugin("data2caps") def readSlist(filePath, multiplier=1.0, network=None): # supported formats: # TIMESERIES AM_R1108_00_SHZ_R, 8226 samples, 50 sps, 2018-04-10T10:20:03.862000, SLIST, FLOAT, M/S**2 # 0.000134157 # ... data = Record() samples = [] all_data = [] try: with open(filePath, "r", encoding="utf8") as file: all_data = [line.strip() for line in file.readlines()] except FileNotFoundError: print(f"File '{filePath}' not found.", file=sys.stderr) return False if len(all_data) == 0: print(f"No data read from file {filePath}", file=sys.stderr) return False header = all_data[0] dataType = header.split(",")[0].split(" ")[0] if dataType != "TIMESERIES": print( f"Unsupported data type {dataType} in {filePath}. Supported: TIMESERIES", file=sys.stderr, ) return False if network: data.net = network else: data.net = header.split(",")[0].split(" ")[1].split("_")[0] data.sta = header.split(",")[0].split(" ")[1].split("_")[1] data.loc = header.split(",")[0].split(" ")[1].split("_")[2] data.cha = header.split(",")[0].split(" ")[1].split("_")[3] data.nSamp = int(header.split(",")[1].split(" ")[-2]) try: frac = Fraction(header.split(",")[2].split(" ")[-2]).limit_denominator() except Exception: print("Unknown sample rate", file=sys.stderr) return False data.numerator = frac.numerator data.denominator = frac.denominator time = header.split(",")[3].strip(" ") data.start = CAPS.Time.FromString(time, "%FT%T.%f") data.unit = header.split(",")[6].strip(" ") samples = [float(x) for x in all_data[1:]] min_abs = min(abs(x) for x in samples) max_abs = max(abs(x) for x in samples) print( f" + minimum/maximum absolute value found in data: {min_abs}/{max_abs}", file=sys.stderr, ) print(f" + applying multuplier: {multiplier}", file=sys.stderr) for sample in samples: if dataType == "TIMESERIES": data.samples.append(int(round(sample * multiplier, 0))) min_abs = min(abs(x) for x in data.samples) max_abs = max(abs(x) for x in data.samples) print( " + minimum/maximum absolute value after applying multiplier: " f"{min_abs}/{max_abs}", file=sys.stderr, ) return data def readUnavco(filePath, multiplier=1.0, network=None): # supported formats: # version 1.1 # # PBO Tiltmeter Data, format version 1.1 # # http://pboweb.unavco.org/strain_data # # # # B201 coldwt201bwa2007 # # Latitude : +46.3033 degrees (WGS-84) # # Longitude : -122.2648 degrees (WGS-84) # # Elevation : 990 meters (WGS-84) # # Sensor Depth : 24.38 meters # # X Orientation: 316 degrees East of North (magnetic) # # SEED Codes : PB B201 TT LAE # # # # Timestamp (UTC) Tilt X (microradians) # 2023-10-05T16:00:00.58 162.981 data = Record() version = None versionSupported = ["1.0", "1.1"] all_data = [] try: with open(filePath, "r", encoding="utf8") as file: # read header all_data = [line.strip().split(" ") for line in file.readlines()] except FileNotFoundError: print(f"File '{filePath}' not found.", file=sys.stderr) return False if len(all_data) == 0: print(f"No data read from file {filePath}", file=sys.stderr) return False cnt = 0 for line in all_data: if not line[0].startswith("#"): break try: if "version" in line[-2]: version = line[-1] except IndexError: pass cnt += 1 if version not in versionSupported: print( f"Unsupported format version '{version}' of file '{filePath}'. " f"Supported are: {versionSupported}", file=sys.stderr, ) return False print(f" + unavco format version: {version}", file=sys.stderr) # extract meta data header = all_data[:cnt] for line in header: # stream code if set(["seed", "codes"]).issubset(set(x.lower() for x in line)): if network: data.net = network else: if version == "1.0": if not network: print( f"Found version {version}: Must provide network code", file=sys.stderr, ) return False elif version == "1.1": data.net = line[-4] else: print( f"Unsupported format version {version}. Supported are: {version}", file=sys.stderr, ) return False data.sta = line[-3] data.loc = line[-2] data.cha = line[-1] # physical unit if "Timestamp" in line[1]: unit = line[-1].strip("(").strip(")") if "microradians" in unit: if multiplier == 1000.0: unit = "nRad" print(f" + converting data in microradians to {unit}", file=sys.stderr) elif multiplier == 1.0: unit = "nRad" print( f" + converting data from microradians to {unit} for high precision", file=sys.stderr, ) multiplier = 1000 if "hPa" in unit: if multiplier == 1.0: multiplier = 100 unit = "Pa" print(f" + converting data from hPa to {unit}", file=sys.stderr) data.unit = unit if version == "1.0": time1 = CAPS.Time.FromString(all_data[cnt][0] + all_data[cnt][1], "%F%T.%f") time2 = CAPS.Time.FromString( all_data[cnt + 1][0] + all_data[cnt + 1][1], "%F%T.%f" ) elif version == "1.1": time1 = CAPS.Time.FromString(all_data[cnt][0], "%FT%T.%f") time2 = CAPS.Time.FromString(all_data[cnt + 1][0], "%FT%T.%f") else: return False data.start = time1 try: frac = Fraction(1.0 / (time2 - time1).length()).limit_denominator() except Exception: print("Unknown sample rate", file=sys.stderr) return False data.numerator = frac.numerator data.denominator = frac.denominator # extract time series for i in all_data[cnt:]: if version == "1.0": data.samples.append(int(round(float(i[2]) * multiplier, 0))) elif version == "1.1": data.samples.append(int(round(float(i[1]) * multiplier, 0))) else: return False data.nSamp = len(data.samples) return data def signal_handler(sig, frame): # pylint: disable=W0613 print("Caught Ctrl+C!", file=sys.stderr) output.quit() sys.exit(0) def main(): inputFormat = None inputFile = None host = "localhost" port = 18003 multiplier = 1 network = None try: opts, _ = getopt.getopt( sys.argv[1:], "hH:i:f:m:n:", ["help", "host=", "input=", "format=", "multiplier=", "network="], ) except getopt.GetoptError as err: # print help information and exit: print(str(err)) # will print something like "option -a not recognized" return usage(2) addr = None signal.signal(signal.SIGINT, signal_handler) for o, a in opts: if o in ["-h", "--help"]: return usage() if o in ["-H", "--host"]: addr = a elif o in ["-i", "--input"]: inputFile = a elif o in ["-f", "--format"]: inputFormat = a elif o in ["-m", "--multiplier"]: multiplier = float(a) elif o in ["-n", "--network"]: network = a else: assert False, "unhandled option" if addr: if ":" in addr: try: host, port = addr.split(":") except BaseException: print(f"Invalid host address given: {addr}\n", file=sys.stderr) return 1 else: host = addr if port: try: port = int(port) except BaseException: print(f"Invalid port given: {port}", file=sys.stderr) return 1 else: port = 18003 if not host: host = "localhost" output.setHost(host) output.setPort(port) output.setBufferSize(1 << 30) output.enableLogging() res = None print("Summary", file=sys.stderr) print(f" + input file: {inputFile}", file=sys.stderr) print(f" + input format: {inputFormat}", file=sys.stderr) print(f" + caps server: {host}:{port}", file=sys.stderr) if network: print(f" + set network: {network}", file=sys.stderr) # generic signal if not inputFormat and not inputFile: startTime = CAPS.Time.GMT() print("Sending generic test data", file=sys.stderr) x = np.array([1, 2], dtype=np.int32) if not network: network = "AB" print(f"Assuming network '{network}'", file=sys.stderr) res = output.push( network, "HMA", "", "BHZ", startTime, 1, 1, "m", x, CAPS.DT_INT32 ) if res != CAPS.Plugin.Success: print("Failed to send packet", file=sys.stderr) output.close() return 0 if not inputFile: print("Must provide input file", file=sys.stderr) return 1 # data from input file data = None if inputFormat == "slist": data = readSlist(inputFile, multiplier, network) elif inputFormat == "unavco": data = readUnavco(inputFile, multiplier, network) else: return 1 if not data: print(f"Error parsing {inputFile}", file=sys.stderr) return 1 print(f"Sending data to CAPS server {host}:{port}", file=sys.stderr) res = output.push( data.net, data.sta, data.loc, data.cha, data.start, data.numerator, data.denominator, data.unit, data.samples, CAPS.DT_INT32, ) if res != CAPS.Plugin.Success: output.close() print("Failed to send packet", file=sys.stderr) return 1 return 0 if __name__ == "__main__": sys.exit(main())