From 3b9d22ccbc11e2ee83e82d62139267bfa3e6978f Mon Sep 17 00:00:00 2001 From: Maps Date: Wed, 21 Aug 2019 10:52:24 +0200 Subject: [PATCH] init_setup: add threshold logging to EPICS --- tools/epics_log.py | 167 ++++++++++++++++++++++++++++++++++++++++++++ tools/init_setup.pl | 41 +++++++++++ 2 files changed, 208 insertions(+) create mode 100755 tools/epics_log.py diff --git a/tools/epics_log.py b/tools/epics_log.py new file mode 100755 index 0000000..f529bdb --- /dev/null +++ b/tools/epics_log.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 + +""" +(c) 2019 Philipp Klaus +if called manually, this script was called like this: +./epics_log.py /local.1/htdocs/mvdconfig/setup/PRESTO_2018_readout.xml --epics-ca-addr-list 192.168.10.46 +""" + +import argparse, sys, os +# external dependencies +import lxml.etree # (OpenSuse package: python3-lxml) +import caproto # (No OpenSuse package / pip install caproto) +#import epics # (No OpenSuse package / pip install pyepics) + + +section_divider = '-' * 20 + +def eprint(*args, **kwargs): + " print to stderr " + print(*args, **kwargs, file=sys.stderr) + +def success(last_words): + eprint(last_words) + print("SUCCESS") + sys.exit(0) + +def failure(last_words): + eprint(last_words) + print("FAILURE") + sys.exit(1) + +def caput(pv, value): + " synchroneously write (put) values to EPICS PVs " + # VARIANT pyepics + #epics.caput(pv, value, wait=True) + # VARIANT caproto (pure python package) + import caproto.sync.client + #caproto.sync.client.write(pv, value, notify=True) + # VARIANT with the caput CLI: + # os.system(f"caput {pv} {value}") + +def caput_many(pvlist, values): + """ + An improved version of epics.caput_many() + with sensible overall connection timeout. + Needed because epics.caput_many() can take ages + if none of a larger number of PVs can be connected. + """ + + connection_timeout = 0.2 + put_timeout = 0.2 + context_timeout = 2 + + # VARIANT caproto threaded client Batch mode + import caproto.threading.client + import time, functools + ctx = caproto.threading.client.Context(timeout=context_timeout) + pvs = ctx.get_pvs(*pvlist) + start = time.time() + while (time.time() - start) < connection_timeout and sum(pv.connected for pv in pvs) < len(pvlist): + time.sleep(0.005) + connected_pvs = [pv for pv in pvs if pv.connected] + disconnected_pvs = [pv for pv in pvs if pv not in connected_pvs] + eprint(section_divider) + eprint('Starting to update PVs NOW') + eprint(f'PVs connected/disconnected/total: {len(connected_pvs)}/{len(disconnected_pvs)}/{len(pvs)}') + eprint(f'caproto Context() object: {ctx}') + results = {pv.name: False for pv in disconnected_pvs} + def convert_result_dict_to_list(): + retlist = [] + for pvname in pvlist: + if pvname in results: + retlist.append(1 if results[pvname] else -1) + else: + retlist.append(-1) # timeout + return retlist + if not connected_pvs: return convert_result_dict_to_list() + def stash_result(name, response): + results[name] = response.status.success == 1 + with caproto.threading.client.Batch() as b: + for pv, value in zip(pvs, values): + if pv not in connected_pvs: continue + eprint(f"{pv.name} -> {value}") + b.write(pv, value, callback=functools.partial(stash_result, pv.name)) + start = time.time() + while (time.time() - start) < put_timeout and len(results) < len(pvlist): + time.sleep(0.005) + eprint(section_divider) + return convert_result_dict_to_list() + + +parser = argparse.ArgumentParser() +parser.add_argument('setup_file') +parser.add_argument('--pv-prefix', default='CBM:MVD:DAQ:PRESTO:') +parser.add_argument('--epics-ca-addr-list', '-a', help="Set to an IP Address, if the IOC to write to cannot be reached via broadcasts.") +args = parser.parse_args() + +eprint(section_divider) +eprint('--- epics_log.py ---') + +if args.epics_ca_addr_list: + eprint(f'Setting EPICS_CA_ADDR_LIST to {args.epics_ca_addr_list}') + os.environ['EPICS_CA_ADDR_LIST'] = args.epics_ca_addr_list + os.environ['EPICS_CA_AUTO_ADDR_LIST'] = 'No' + +eprint("Setup File: %s" % args.setup_file) + +xml_doc = lxml.etree.parse(args.setup_file) + +system = xml_doc.findall("//system")[0] +system_name = system.get('name', 'unknown') # eg. 'PRESTO_2018_readout_sys' +if not 'PRESTO_2018_readout' in system_name: + failure('irrelevant system name:', system_name, 'quitting.') + +# tag name is 'description' (some XML peculiarity...) +setup = xml_doc.findall(".//")[0] + +sensors = xml_doc.findall(".//sensor") + +updates = [] # all PV updates to be processed + +try: + eprint(section_divider) + for sensor in sensors: + name = sensor.get('name') + sensor_settings_xml = sensor.get('config') + assert name.startswith('sensor') + name = name[6:] + + chain = sensor.getparent() # or .find('..') + board = chain.getparent() + full_id = board.get('address', '0000') + sensor.get('id', 'ffff') + eprint(f'Sensor: {name:3s} Full ID: {full_id}') + eprint(f'Sensor Settings XML File: {sensor_settings_xml}') + + folder = os.path.join(os.path.split(args.setup_file)[0], '../config') + abs_path = os.path.abspath(os.path.join(folder, sensor_settings_xml)) + sensor_xml_doc = lxml.etree.parse(abs_path) + fields = sensor_xml_doc.findall('.//field') + foi = 'IVDREF1A', 'IVDREF1B', 'IVDREF1C', 'IVDREF1D', 'IVDREF2' # fields of interest + for field in fields: + fname = field.get('name', '') + if fname in foi: + value = field.get('value') + value = int(value) + eprint(f"{fname} -> {value}") + # now send the new value to EPICS + #caput(f"{args.pv_prefix}SENSOR:{name}:{fname}", value) + pv = f"{args.pv_prefix}SENSOR:{name}:{fname}" + updates.append((pv, value)) + eprint(section_divider) + pvs = [update[0] for update in updates] + values = [update[1] for update in updates] + results = caput_many(pvs, values) + nsuccess = sum(1 for r in results if r == 1) + ntotal = len(results) + eprint(f"Reached {nsuccess} out of {ntotal} PVs.") + if nsuccess != ntotal: + eprint("Couln't reach the IOCs for some PVs:") + for r, pv, value in zip(results, pvs, values): + if r == 1: continue + eprint(f" {r} {pv} {value}") + failure("Unsuccessful") +except Exception as e: + failure(str(e)) + +success('successfully updated EPICS with new setup/sensor settings') diff --git a/tools/init_setup.pl b/tools/init_setup.pl index d9aa6a8..0d6e1e6 100755 --- a/tools/init_setup.pl +++ b/tools/init_setup.pl @@ -83,6 +83,47 @@ my $cts = $dbsys->getDocumentElement->findnodes('cts')->[0]->getAttribute('addre print STDERR "Loading setup $name from file $setupFile\n" if $verbose; +#################################### +## Logging to EPICS IOC and Archiver +#################################### + +# epics_log: Log important DAQ settings (like thresholds) to +# an EPICS IOC and eventually to its Archiver DB. +# Argument: Setup XML file +# Result: "SUCCESS" or "FAILURE" +sub epics_log { + my ($pv) = @_; + if (open(my $F, "-|", "python3 ./epics_log.py $setupFile -a 192.168.10.109 2>/tmp/epics_log.stderr")) { + $result = <$F>; + close $F; + chomp $result; + if ($verbose) { + binmode STDOUT, ':utf8'; + if (open(my $fh, '<:encoding(UTF-8)', "/tmp/epics_log.stderr")) { + while (my $row = <$fh>) { + chomp $row; + print STDERR "$row\n"; + } + } else { + warn "Could not stderr file of epics_log.py script"; + } + } + return $result; + } else { + print STDERR "Cannot run 'epics_log.py'\n"; + return "FAILURE"; + } +} +# Do the logging +my $log_result = epics_log($setupFile); +print STDERR "EPICS LOG Result: $log_result\n"; +print STDERR 'timer: ', Time::HiRes::gettimeofday() - $start, " s\n"; + + +#################################### +## +#################################### + print STDERR "Stop CTS trigger source\n"; execute("trbcmd clearbit 0x$cts 0xa101 0x1"); -- 2.43.0