From 9ecec99901317646daa61d6c2c793e9f940900f3 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Tue, 22 Sep 2020 12:33:46 +0200 Subject: [PATCH] first tests --- alarms-dicts.txt | 31 ++++++ alarms.py | 44 +++++++++ alarms.txt | 34 +++++++ chans.txt | 18 ++++ config.py | 19 ++++ execute.py | 16 ++++ logger.py | 26 +++++ pvcollection.py | 57 +++++++++++ sani.py | 241 +++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 486 insertions(+) create mode 100644 alarms-dicts.txt create mode 100644 alarms.py create mode 100644 alarms.txt create mode 100644 chans.txt create mode 100644 config.py create mode 100644 execute.py create mode 100644 logger.py create mode 100644 pvcollection.py create mode 100755 sani.py diff --git a/alarms-dicts.txt b/alarms-dicts.txt new file mode 100644 index 0000000..ba9893b --- /dev/null +++ b/alarms-dicts.txt @@ -0,0 +1,31 @@ +status = { + 0: "NO ALARM", + 1: "READ", + 2: "WRITE", + 3: "HIHI", + 4: "HIGH", + 5: "LOLO", + 6: "LOW", + 7: "STATE", + 8: "COS", + 9: "COMM", + 10: "TIMEOUT", + 11: "HW LIMIT", + 12: "CALC", + 13: "SCAN", + 14: "LINK", + 15: "SOFT", + 16: "BAD SUB", + 17: "UDF", + 18: "DISABLE", + 19: "SIMM", + 20: "READ ACCESS", + 21: "WRITE ACCESS" +} + +severity = { + 0: "NO ALARM", + 1: "MINOR", + 2: "MAJOR", + 3: "INVALID" +} diff --git a/alarms.py b/alarms.py new file mode 100644 index 0000000..d46eca8 --- /dev/null +++ b/alarms.py @@ -0,0 +1,44 @@ + +STATUS = { + 0: "no alarm", + 1: "read", + 2: "write", + 3: "hihi", + 4: "high", + 5: "lolo", + 6: "low", + 7: "state", + 8: "cos", + 9: "comm", + 10: "timeout", + 11: "hw limit", + 12: "calc", + 13: "scan", + 14: "link", + 15: "soft", + 16: "bad sub", + 17: "udf", + 18: "disable", + 19: "simm", + 20: "read access", + 21: "write access" +} + +SEVERITY = { + 0: "no alarm", + 1: "minor", + 2: "major", + 3: "invalid" +} + + + +def message(status_code, severity_code): + status = STATUS.get(status_code, "unknown") + if status_code == severity_code == 0: + return status + severity = SEVERITY.get(severity_code, "unknown") + return f"{status} ({severity})" + + + diff --git a/alarms.txt b/alarms.txt new file mode 100644 index 0000000..5673216 --- /dev/null +++ b/alarms.txt @@ -0,0 +1,34 @@ +/usr/local/epics/base/include/alarm.h + +Severity ist "wie schlimm ist es?" +0 = NO_ALARM +1 = MINOR +2 = MAJOR +3 = INVALID (das heisst dem Wert darf nicht geglaubt werden, besonders nicht für Feedbacks.) + +Status ist "Was für ein Alarm ist das?" +0 = NO_ALARM +1 = READ (Lesefehler, oft mit INVALID Severity) +2 = WRITE (Schreibfehler, oft mit INVALID Severity) +3 = HIHI (Wert ist sehr hoch, oft mit MAJOR Severity) +4 = HIGH (Wert ist hoch, oft mit MINOR Severity) +5 = LOLO (Wert ist sehr tief, oft mit MAJOR Severity) +6 = LOW (Wert ist tief, oft mit MINOR Severity) +7 = STATE (Wert ist ein Alarmzustand (z.B. OFF)) +8 = COS (=Change of State, Zustand hat sich geändert = Signalflanke) +9 = COMM (Communikationsfehler, oft mit INVALID Severity) +10 = TIMEOUT +11 = HW_LIMIT +12 = CALC (Calculation gibt NaN oder Inf oder so) +13 = SCAN (Record konnte 5 mal nicht gescannt werden weil busy) +14 = LINK (Record hat Alarm geerbt, weil der Link ein MS hat, oder Link ist ungültig) +15 = SOFT +16 = BAD_SUB (Subroutine Record hat keine Subroutine) +17 = UDF (Undefined, Record hat keinen Wert, typischerweise noch nie prozessiert seit dem Start, normalerweise mit Severity INVALID) +18 = DISABLE (Record wurde disabled) +19 = SIMM (Record is in Simulationsmodus) +20 = READ_ACCESS +21 = WRITE_ACCESS + + + diff --git a/chans.txt b/chans.txt new file mode 100644 index 0000000..10f0446 --- /dev/null +++ b/chans.txt @@ -0,0 +1,18 @@ +SIN-TIMAST-TMA:Bunch-2-Appl-Freq-RB # rep rate + +# vacuum gauges +SATOP21-VMFR165-A010:PRESSURE + SATOP21-VMCC165-A010:PRESSURE + + +# slits +SATOP21-OAPU161:MOTOR_W.RBV + + +CRAP:CRAP0 +CRAP:CRAP1 +CRAP:CRAP2 +CRAP:CRAP3 + +SAROP21-OKB:MODE +SATOP11-VVPG092-A010:PLC_OPEN diff --git a/config.py b/config.py new file mode 100644 index 0000000..4910071 --- /dev/null +++ b/config.py @@ -0,0 +1,19 @@ + +def load(fname): + chans = set() + for line in read_lines(fname): + line = remove_comments(line).strip() + if not line: + continue + chans.add(line) + return sorted(chans) + +def read_lines(fname): + with open(fname, "r") as f: + yield from f + +def remove_comments(line, comment_char="#"): + return line.split(comment_char)[0] + + + diff --git a/execute.py b/execute.py new file mode 100644 index 0000000..b0df236 --- /dev/null +++ b/execute.py @@ -0,0 +1,16 @@ +from concurrent.futures import ThreadPoolExecutor + + +def parallel(func, targets, names): + with ThreadPoolExecutor() as executor: + results = executor.map(func, targets) +# return list(results) + return dict(zip(names, results)) + + +def serial(func, targets): + return [func(t) for t in targets] +# return {t: func(t) for t in targets} + + + diff --git a/logger.py b/logger.py new file mode 100644 index 0000000..b23e20f --- /dev/null +++ b/logger.py @@ -0,0 +1,26 @@ +import logging as log + + +LEVELS = ( +# log.CRITICAL, + log.ERROR, + log.WARNING, + log.INFO, + log.DEBUG, + log.NOTSET +) + + +def set_log_level(verbosity): + ntotal = len(LEVELS) - 1 + index = min(verbosity, ntotal) + level = LEVELS[index] + log.basicConfig(level=level, format="%(asctime)s %(levelname)s %(message)s") + + + + + +#from logger import log, set_log_level + +#set_log_level(clargs.verbose) diff --git a/pvcollection.py b/pvcollection.py new file mode 100644 index 0000000..743fbb3 --- /dev/null +++ b/pvcollection.py @@ -0,0 +1,57 @@ +import epics + +from config import load +from execute import parallel +#from execute import serial as parallel + + +class PVCollection: + + def __init__(self, chans): + self.chans = chans + self.pvs = [PV(ch) for ch in chans] + + @classmethod + def from_file(cls, fname): + chans = load(fname) + return cls(chans) + + def connected(self): + return self._run_all(lambda pv: pv.wait_for_connection(0.01)) + + def status(self): + return self._run_all(lambda pv: pv.status) + + def severity(self): + return self._run_all(lambda pv: pv.severity) + + def value(self): + return self._run_all(lambda pv: pv.value) + + def _run_all(self, func): + return parallel(func, self.pvs) + + + +class PV(epics.PV): + + @property + def status(self): + if not self.connected: + return -1 + return super().status + + @property + def severity(self): + if not self.connected: + return -1 + return super().severity + + @property + def value(self): + if not self.connected: + return None + return super().value + + + diff --git a/sani.py b/sani.py new file mode 100755 index 0000000..1c23904 --- /dev/null +++ b/sani.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python + + +import argparse + +parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + +parser.add_argument("filename") + +#parser.add_argument("-v", "--verbose", action="count", default=0) +parser.add_argument("-s", "--show", action="store_true") +parser.add_argument("-t", "--timeout", type=float, default=2) + +clargs = parser.parse_args() + + + + + + + + +import enum + +import epics +import pandas as pd + +from pvcollection import PVCollection +from alarms import message +from config import load +from execute import parallel + + + + + + + + + + + + +MSG_NOT_CONNECTED = "did not connect" +MSG_SUCCESS = "OK" + + + +def v1a(): + cs = load(clargs.filename) + pvs = [epics.PV(c) for c in cs] + for pv in pvs: + c = pv.pvname + connected = pv.wait_for_connection(clargs.timeout) + if not connected: + msg = MSG_NOT_CONNECTED + elif pv.status != 0 or pv.severity != 0: + msg = message(pv.status, pv.severity) + else: + if not clargs.show: + continue + msg = MSG_SUCCESS +# print(c, msg) + + +def v1b(): + chans = load(clargs.filename) + pvs = [epics.PV(ch) for ch in chans] + connected = parallel(lambda pv: pv.wait_for_connection(clargs.timeout), pvs) + + for ch, pv, con in zip(chans, pvs, connected): + if not con: + msg = MSG_NOT_CONNECTED + elif pv.status != 0 or pv.severity != 0: + msg = message(pv.status, pv.severity) + else: + if not clargs.show: + continue + msg = MSG_SUCCESS +# print(ch, msg) + + + + + +def v1c(): + chans = load(clargs.filename) + length = maxstrlen(chans) + pvs = [epics.PV(ch) for ch in chans] + connected = parallel(lambda pv: pv.wait_for_connection(clargs.timeout), pvs) + + data = {} + for ch, pv, con in zip(chans, pvs, connected): + if not con: + status = severity = -1 + value = None + msg = MSG_NOT_CONNECTED + else: + status = pv.status + severity = pv.severity + if status == 0 and severity == 0: + if not clargs.show: + continue + msg = MSG_SUCCESS + else: + msg = message(status, severity) + value = pv.value + +# print(ch.ljust(length), msg) + data[ch] = (con, status, severity, value) + + columns = ("connected", "status", "severity", "value") + df = pd.DataFrame.from_dict(data, columns=columns, orient="index") +# print() +# print(df) +# print() +# print(df.dtypes) + + + +def maxstrlen(seq): + return max(strlen(i) for i in seq) + +def strlen(val): + return len(str(val)) + + + +#cs = load(clargs.filename) +#pvs = [epics.PV(c) for c in cs] +#data = [] +#for pv in pvs: +# c = pv.pvname +# connected = pv.wait_for_connection(clargs.timeout) + +# if not connected: +# print(c, "didn't connect") +# data.append((connected, -1, -1, None)) +# continue +# if pv.status != 0 or pv.severity != 0: +# print(c, message(pv.status, pv.severity)) + +# data.append((connected, pv.status, pv.severity, pv.value)) + +#df = pd.DataFrame(data, index=cs, columns=["connected", "status", "severity", "value"]) + +#print(df) +#print(df.dtypes) + + + + + + + +def v2a(): + pvc = PVCollection.from_file(clargs.filename) + + data = { + "connected": pvc.connected(), + "status": pvc.status(), + "severity": pvc.severity(), + "value": pvc.value() + } + + df = pd.DataFrame(data, index=pvc.chans) + +# print(df) +# print(df.dtypes) + + + + + + + + +def get_data(pv): + connected = pv.wait_for_connection(clargs.timeout) + + if not connected: + value = None + status = severity = -1 + msg = MSG_NOT_CONNECTED + else: + value = pv.value #TODO: not needed if shot not ok + status = pv.status + severity = pv.severity + if status == 0 and severity == 0: + msg = MSG_SUCCESS + else: + msg = message(status, severity) + + data = { + "connected": connected, + "value": value, + "status": status, + "severity": severity + } + + if clargs.show: + print(pv.pvname, msg) + return data#, msg + + + + + +def v3(): + chans = load(clargs.filename) + pvs = (epics.PV(ch) for ch in chans) # putting PV constructors into threads has weird effects + data = parallel(get_data, pvs, chans) + df = pd.DataFrame(data).T + return df + + + + + + + + +res = v3() +print(res) +#for m in res: +# print(m) + + + + + + + + + + + + + + +