From cb3623e4a90d05418cf9f54c5220aaf56524ddc8 Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Tue, 6 Dec 2022 15:43:13 +0100 Subject: [PATCH 1/6] Add quality check for clock quality (LCQ) --- parameters.yaml | 14 ++++++++------ survBot.py | 44 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/parameters.yaml b/parameters.yaml index 916992c..ce6486a 100644 --- a/parameters.yaml +++ b/parameters.yaml @@ -1,10 +1,10 @@ # Parameters file for Surveillance Bot -datapath: "/data/SDS/" # SC3 Datapath -networks: ["1Y", "HA"] -stations: "*" -locations: "*" -channels: ["EX1", "EX2", "EX3", "VEI"] # Specify SOH channels, currently supported EX[1-3] and VEI -channel_names: ["Temperature (°C)", "230V/12V Status (V)", "Router/Charger State (V)", "Logger Voltage (V)"] # names for plotting (optional) +datapath: "/data/SDS/" # SC3 Datapath +networks: ["1Y", "HA"] # select networks, list or str +stations: "*" # select stations, list or str +locations: "*" # select locations, list or str +channels: ["EX1", "EX2", "EX3", "VEI", "LCQ"] # Specify SOH channels, currently supported EX[1-3], VEI, LCQ +channel_names: ["Clock Quality (%)", "Temperature (°C)", "230V/12V Status (V)", "Router/Charger State (V)", "Logger Voltage (V)"] # names for plotting (optional) stations_blacklist: ["TEST", "EREA"] networks_blacklist: [] interval: 60 # Perform checks every x seconds @@ -43,6 +43,8 @@ THRESHOLDS: low_volt: 12 # min voltage for low voltage warning high_volt: 14.8 # max voltage for over voltage warning unclassified: 5 # min voltage samples not classified for warning + clockquality_warn: 90 # clock quality ranges from 0 % to 100 % with 100 % being the best level + clockquality_fail: 70 # add links to html table with specified key as column and value as relative link, interpretable string parameters: # nw (e.g. 1Y), st (e.g. GR01A), nwst_id (e.g. 1Y.GR01A) diff --git a/survBot.py b/survBot.py index 62fd9c3..763a93c 100755 --- a/survBot.py +++ b/survBot.py @@ -61,7 +61,7 @@ def fancy_timestr(dt, thresh=600, modif='+'): class SurveillanceBot(object): def __init__(self, parameter_path, outpath_html=None): - self.keys = ['last active', '230V', '12V', 'router', 'charger', 'voltage', 'temp', 'other'] + self.keys = ['last active', '230V', '12V', 'router', 'charger', 'voltage', 'clock', 'temp', 'other'] self.parameter_path = parameter_path self.update_parameters() self.starttime = UTCDateTime() @@ -668,6 +668,7 @@ class StationQC(object): self.pb_temp_analysis() self.pb_power_analysis() self.pb_rout_charge_analysis() + self.clock_quality_analysis() def return_print_analysis(self): items = [self.nwst_id] @@ -696,6 +697,47 @@ class StationQC(object): def get_last_occurrence(self, trace, indices): return self.get_time(trace, indices[-1]) + def clock_quality_analysis(self, channel='LCQ'): + """ Analyse clock quality """ + key = 'clock' + st = self.stream.select(channel=channel) + trace = self.get_trace(st, key) + if not trace: return + clockQuality = trace.data + clockQuality_warn_level = self.parameters.get('THRESHOLDS').get('clockquality_warn') + clockQuality_fail_level = self.parameters.get('THRESHOLDS').get('clockquality_fail') + + if self.verbosity > 1: + self.print(40 * '-') + self.print('Performing Clock Quality check', flush=False) + + clockQuality_warn = np.where(clockQuality < clockQuality_warn_level)[0] + clockQuality_fail = np.where(clockQuality < clockQuality_fail_level)[0] + + if len(clockQuality_warn) == 0 and len(clockQuality_fail) == 0: + self.status_ok(key, detailed_message=f'ClockQuality={(clockQuality[-1])}') + return + + n_qc_warn = 0 + n_qc_fail = 0 + + warn_message = f'Trace {trace.get_id()}:' + if len(clockQuality_warn) > 0: + # try calculate number of warn peaks from gaps between indices + n_qc_warn = len(np.where(np.diff(clockQuality_warn) > 1)[0]) + 1 + detailed_message = warn_message + f' {n_qc_warn}x Qlock Quality less then {clockQuality_warn_level}' \ + + self.get_last_occurrence_timestring(trace, clockQuality_warn) + self.warn(key, detailed_message=detailed_message, count=n_qc_warn, + last_occurrence=self.get_last_occurrence(trace, clockQuality_warn)) + + if len(clockQuality_fail) > 0: + # try calculate number of fail peaks from gaps between indices + n_qc_fail = len(np.where(np.diff(clockQuality_fail) > 1)[0]) + 1 + detailed_message = warn_message + f' {n_qc_fail}x Qlock Quality less then {clockQuality_fail_level}V ' \ + + self.get_last_occurrence_timestring(trace, clockQuality_fail) + self.error(key, detailed_message=detailed_message, count=n_qc_fail, + last_occurrence=self.get_last_occurrence(trace, clockQuality_fail)) + def voltage_analysis(self, channel='VEI'): """ Analyse voltage channel for over/undervoltage """ key = 'voltage' From 69412dc5fe6ba733315790413e4883cf37e0b147 Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Tue, 6 Dec 2022 15:51:24 +0100 Subject: [PATCH 2/6] use function calc_occurences --- survBot.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/survBot.py b/survBot.py index 77c1a1d..6ab6fc5 100755 --- a/survBot.py +++ b/survBot.py @@ -721,13 +721,10 @@ class StationQC(object): self.status_ok(key, detailed_message=f'ClockQuality={(clockQuality[-1])}') return - n_qc_warn = 0 - n_qc_fail = 0 - warn_message = f'Trace {trace.get_id()}:' if len(clockQuality_warn) > 0: # try calculate number of warn peaks from gaps between indices - n_qc_warn = len(np.where(np.diff(clockQuality_warn) > 1)[0]) + 1 + n_qc_warn = self.calc_occurrences(clockQuality_warn) detailed_message = warn_message + f' {n_qc_warn}x Qlock Quality less then {clockQuality_warn_level}' \ + self.get_last_occurrence_timestring(trace, clockQuality_warn) self.warn(key, detailed_message=detailed_message, count=n_qc_warn, @@ -735,7 +732,7 @@ class StationQC(object): if len(clockQuality_fail) > 0: # try calculate number of fail peaks from gaps between indices - n_qc_fail = len(np.where(np.diff(clockQuality_fail) > 1)[0]) + 1 + n_qc_fail = self.calc_occurrences(clockQuality_fail) detailed_message = warn_message + f' {n_qc_fail}x Qlock Quality less then {clockQuality_fail_level}V ' \ + self.get_last_occurrence_timestring(trace, clockQuality_fail) self.error(key, detailed_message=detailed_message, count=n_qc_fail, From ac9f83d8561a5275871b96d9ca34e4b27463d4bc Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Tue, 6 Dec 2022 16:46:19 +0100 Subject: [PATCH 3/6] fixed paramter file --- parameters.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/parameters.yaml b/parameters.yaml index 0c5ff74..690d8a5 100644 --- a/parameters.yaml +++ b/parameters.yaml @@ -4,7 +4,6 @@ networks: ["1Y", "HA"] # select networks, list or str stations: "*" # select stations, list or str locations: "*" # select locations, list or str channels: ["EX1", "EX2", "EX3", "VEI", "LCQ"] # Specify SOH channels, currently supported EX[1-3], VEI, LCQ -channel_names: ["Clock Quality (%)", "Temperature (°C)", "230V/12V Status (V)", "Router/Charger State (V)", "Logger Voltage (V)"] # names for plotting (optional) stations_blacklist: ["TEST", "EREA"] networks_blacklist: [] interval: 60 # Perform checks every x seconds @@ -42,7 +41,7 @@ THRESHOLDS: pb_thresh: 0.2 # Threshold for PowBox Voltage check +/- (V) max_temp: 50 # max temperature for temperature warning low_volt: 12 # min voltage for low voltage warning - high_volt: 14.8 # max voltage for over voltage warning + high_volt: 14.8 # max voltage for over voltage warning unclassified: 5 # min voltage samples not classified for warning clockquality_warn: 90 # clock quality ranges from 0 % to 100 % with 100 % being the best level clockquality_fail: 70 @@ -63,9 +62,10 @@ EMAIL: sender: "webmaster@geophysik.ruhr-uni-bochum.de" # mail sender # names for plotting of the above defined parameter "channels" in the same order -channel_names: ["Temperature (°C)", "230V/12V Status (V)", "Router/Charger State (V)", "Logger Voltage (V)"] +channel_names: ["Clock Quality (%)", "Temperature (°C)", "230V/12V Status (V)", "Router/Charger State (V)", "Logger Voltage (V)"] # names for plotting (optional) # specify y-ticks (and ylims) giving, (ymin, ymax, step) for each of the above channels (0: default) CHANNEL_TICKS: + - [0, 100, 20] - [-10, 50, 10] - [1, 5, 1] - [1, 5, 1] From 541815d81ffd62922dec8d11cd23f01c84691f3e Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Tue, 6 Dec 2022 15:43:13 +0100 Subject: [PATCH 4/6] Add quality check for clock quality (LCQ) --- parameters.yaml | 6 ++++-- survBot.py | 44 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/parameters.yaml b/parameters.yaml index 0fdf7c6..5208559 100644 --- a/parameters.yaml +++ b/parameters.yaml @@ -3,7 +3,7 @@ datapath: "/data/SDS/" # SC3 Datapath networks: ["1Y", "HA"] # select networks, list or str stations: "*" # select stations, list or str locations: "*" # select locations, list or str -channels: ["EX1", "EX2", "EX3", "VEI"] # Specify SOH channels, currently supported EX[1-3] and VEI +channels: ["EX1", "EX2", "EX3", "VEI", "LCQ"] # Specify SOH channels, currently supported EX[1-3], VEI, LCQ stations_blacklist: ["TEST", "EREA"] # exclude these stations networks_blacklist: [] # exclude these networks interval: 60 # Perform checks every x seconds @@ -43,6 +43,8 @@ THRESHOLDS: low_volt: 12 # min voltage for low voltage warning high_volt: 14.8 # max voltage for over voltage warning unclassified: 5 # min voltage samples not classified for warning + clockquality_warn: 90 # clock quality ranges from 0 % to 100 % with 100 % being the best level + clockquality_fail: 70 # ---------------------------------------- OPTIONAL PARAMETERS --------------------------------------------------------- @@ -62,7 +64,7 @@ EMAIL: networks_blacklist: [] # do not send emails for specific network # names for plotting of the above defined parameter "channels" in the same order -channel_names: ["Temperature (°C)", "230V/12V Status (V)", "Router/Charger State (V)", "Logger Voltage (V)"] +channel_names: ["Clock Quality (%)", "Temperature (°C)", "230V/12V Status (V)", "Router/Charger State (V)", "Logger Voltage (V)"] # names for plotting (optional) # specify y-ticks (and ylims) giving, (ymin, ymax, step) for each of the above channels (0: default) CHANNEL_TICKS: - [-10, 50, 10] diff --git a/survBot.py b/survBot.py index 10dfabf..0de3f7f 100755 --- a/survBot.py +++ b/survBot.py @@ -61,7 +61,7 @@ def fancy_timestr(dt, thresh=600, modif='+'): class SurveillanceBot(object): def __init__(self, parameter_path, outpath_html=None): - self.keys = ['last active', '230V', '12V', 'router', 'charger', 'voltage', 'temp', 'other'] + self.keys = ['last active', '230V', '12V', 'router', 'charger', 'voltage', 'clock', 'temp', 'other'] self.parameter_path = parameter_path self.update_parameters() self.starttime = UTCDateTime() @@ -685,6 +685,7 @@ class StationQC(object): self.pb_temp_analysis() self.pb_power_analysis() self.pb_rout_charge_analysis() + self.clock_quality_analysis() def return_print_analysis(self): items = [self.nwst_id] @@ -713,6 +714,47 @@ class StationQC(object): def get_last_occurrence(self, trace, indices): return self.get_time(trace, indices[-1]) + def clock_quality_analysis(self, channel='LCQ'): + """ Analyse clock quality """ + key = 'clock' + st = self.stream.select(channel=channel) + trace = self.get_trace(st, key) + if not trace: return + clockQuality = trace.data + clockQuality_warn_level = self.parameters.get('THRESHOLDS').get('clockquality_warn') + clockQuality_fail_level = self.parameters.get('THRESHOLDS').get('clockquality_fail') + + if self.verbosity > 1: + self.print(40 * '-') + self.print('Performing Clock Quality check', flush=False) + + clockQuality_warn = np.where(clockQuality < clockQuality_warn_level)[0] + clockQuality_fail = np.where(clockQuality < clockQuality_fail_level)[0] + + if len(clockQuality_warn) == 0 and len(clockQuality_fail) == 0: + self.status_ok(key, detailed_message=f'ClockQuality={(clockQuality[-1])}') + return + + n_qc_warn = 0 + n_qc_fail = 0 + + warn_message = f'Trace {trace.get_id()}:' + if len(clockQuality_warn) > 0: + # try calculate number of warn peaks from gaps between indices + n_qc_warn = len(np.where(np.diff(clockQuality_warn) > 1)[0]) + 1 + detailed_message = warn_message + f' {n_qc_warn}x Qlock Quality less then {clockQuality_warn_level}' \ + + self.get_last_occurrence_timestring(trace, clockQuality_warn) + self.warn(key, detailed_message=detailed_message, count=n_qc_warn, + last_occurrence=self.get_last_occurrence(trace, clockQuality_warn)) + + if len(clockQuality_fail) > 0: + # try calculate number of fail peaks from gaps between indices + n_qc_fail = len(np.where(np.diff(clockQuality_fail) > 1)[0]) + 1 + detailed_message = warn_message + f' {n_qc_fail}x Qlock Quality less then {clockQuality_fail_level}V ' \ + + self.get_last_occurrence_timestring(trace, clockQuality_fail) + self.error(key, detailed_message=detailed_message, count=n_qc_fail, + last_occurrence=self.get_last_occurrence(trace, clockQuality_fail)) + def voltage_analysis(self, channel='VEI'): """ Analyse voltage channel for over/undervoltage """ key = 'voltage' From 735abac249359b45c33f3cdcab047ccded905960 Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Tue, 6 Dec 2022 15:51:24 +0100 Subject: [PATCH 5/6] use function calc_occurences --- survBot.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/survBot.py b/survBot.py index 0de3f7f..e6e6ebf 100755 --- a/survBot.py +++ b/survBot.py @@ -735,13 +735,10 @@ class StationQC(object): self.status_ok(key, detailed_message=f'ClockQuality={(clockQuality[-1])}') return - n_qc_warn = 0 - n_qc_fail = 0 - warn_message = f'Trace {trace.get_id()}:' if len(clockQuality_warn) > 0: # try calculate number of warn peaks from gaps between indices - n_qc_warn = len(np.where(np.diff(clockQuality_warn) > 1)[0]) + 1 + n_qc_warn = self.calc_occurrences(clockQuality_warn) detailed_message = warn_message + f' {n_qc_warn}x Qlock Quality less then {clockQuality_warn_level}' \ + self.get_last_occurrence_timestring(trace, clockQuality_warn) self.warn(key, detailed_message=detailed_message, count=n_qc_warn, @@ -749,7 +746,7 @@ class StationQC(object): if len(clockQuality_fail) > 0: # try calculate number of fail peaks from gaps between indices - n_qc_fail = len(np.where(np.diff(clockQuality_fail) > 1)[0]) + 1 + n_qc_fail = self.calc_occurrences(clockQuality_fail) detailed_message = warn_message + f' {n_qc_fail}x Qlock Quality less then {clockQuality_fail_level}V ' \ + self.get_last_occurrence_timestring(trace, clockQuality_fail) self.error(key, detailed_message=detailed_message, count=n_qc_fail, From f2e322230ef06d18a2f2d62faec336b30692bbd0 Mon Sep 17 00:00:00 2001 From: "Kasper D. Fischer" Date: Tue, 6 Dec 2022 16:46:19 +0100 Subject: [PATCH 6/6] fixed paramter file --- parameters.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/parameters.yaml b/parameters.yaml index 5208559..3123f07 100644 --- a/parameters.yaml +++ b/parameters.yaml @@ -41,7 +41,7 @@ THRESHOLDS: pb_thresh: 0.2 # Threshold for PowBox Voltage check +/- (V) max_temp: 50 # max temperature for temperature warning low_volt: 12 # min voltage for low voltage warning - high_volt: 14.8 # max voltage for over voltage warning + high_volt: 14.8 # max voltage for over voltage warning unclassified: 5 # min voltage samples not classified for warning clockquality_warn: 90 # clock quality ranges from 0 % to 100 % with 100 % being the best level clockquality_fail: 70 @@ -67,6 +67,7 @@ EMAIL: channel_names: ["Clock Quality (%)", "Temperature (°C)", "230V/12V Status (V)", "Router/Charger State (V)", "Logger Voltage (V)"] # names for plotting (optional) # specify y-ticks (and ylims) giving, (ymin, ymax, step) for each of the above channels (0: default) CHANNEL_TICKS: + - [0, 100, 20] - [-10, 50, 10] - [1, 5, 1] - [1, 5, 1]