import os
from datetime import datetime
import requests
import logging
import numpy as np
import pandas as pd
import hax
from hax.utils import human_to_utc_datetime, utc_timestamp
log = logging.getLogger('hax.slow_control')
sc_variables = None
[docs]def get_sc_api_key():
"""Return the slow control API key, if we know it"""
if 'sc_api_key' in hax.config:
return hax.config['sc_api_key']
elif 'SC_API_KEY' in os.environ:
return os.environ['SC_API_KEY']
else:
raise ValueError(
'Please set the SC_API_KEY environment variable or the hax.sc_api_key option '
'to access the slow control web API.')
[docs]def init_sc_interface():
"""Initialize the slow control interface access and list of variables"""
global sc_variables
sc_variables = pd.read_csv(hax.config['sc_variable_list'])
# lowercase all the descriptions, so queries by description are case
# insensitive
sc_variables['Description'] = [x.lower() if not isinstance(
x, float) else '' for x in sc_variables['Description'].values]
[docs]class UnknownSlowControlMonikerException(Exception):
pass
[docs]class AmbiguousSlowControlMonikerException(Exception):
pass
[docs]def get_sc_name(name, column='Historian_name'):
"""Return slow control historian name of name. You can pass
a historian name, sc name, pid identifier, or description. For a full table, see hax.
"""
# Find out what variable we need to query. Try all possible slow control
# abbreviations/codes/etc
for key in ['Historian_name', 'SC_Name', 'Pid_identifier', 'Description']:
if key == 'Description':
# For descriptions, we do an even fuzzier matching: look for descriptions which contain the passed string
# We lowered all descriptions to become case-insensitive
mask = np.array([name.lower() in x for x in sc_variables[key]])
q = sc_variables[mask]
else:
q = sc_variables[sc_variables[key] == name]
if len(q) == 1:
return q.iloc[0][column]
elif len(q) > 1:
raise AmbiguousSlowControlMonikerException("'%s' has multiple mathching %ss: %s" %
(name, key, str(q[key].values)))
raise UnknownSlowControlMonikerException(
"Don't known any slow control moniker matching %s" % name)
[docs]def get_pmt_data_last_measured(run):
"""
Retrieve PMT information for a run from the historian database
:param run: run number/name to return data for.
:return: pandas DataFrame of the values, with index the time in UTC.
"""
# End time
end = hax.runs.datasets.query('number == %d' % hax.runs.get_run_number(run)).iloc[0].end
params = {
"EndDateUnix": int(utc_timestamp(end)),
"username": hax.config['sc_api_username'],
"api_key": get_sc_api_key(),
}
r = requests.get(hax.config['sc_api_url'].replace('GetSCData', 'getLastMeasuredPMTValues'), params=params)
# If there is an error, raise here instead of giving weird error later
r.raise_for_status()
response = r.json()
answer = {}
for x in range(254):
tagname = get_sc_name('PMT %03d' % x)
for entry in response:
if tagname == entry['tagname']:
answer[x] = entry['value']
return answer
[docs]def get_sc_data(names, run=None, start=None, end=None, url=None):
"""
Retrieve the data from the historian database (hax.slow_control.get is just a synonym of this function)
:param names: name or list of names of slow control variables; see get_historian_name.
:param run: run number/name to return data for. If passed, start/end is ignored.
:param start: String indicating start of time range, in arbitrary format (thanks to parsedatetime)
:param end: String indicating end of time range, in arbitrary format
:return: pandas Series of the values, with index the time in UTC. If you requested multiple names, pandas DataFrame
"""
c = hax.config
if isinstance(names, (list, tuple)):
# Get multiple values, return in a single dataframe. I hope the variables all have the same time resolution,
# otherwise you get NaNs...
df = pd.DataFrame([get_sc_data(name, run=run, start=start, end=end) for name in names]).T
df.columns = names
return df
name = names
try:
name = get_sc_name(name)
except UnknownSlowControlMonikerException:
log.warning("Slow control moniker %s not known, trying to query the API anyway..." % name)
# Find out the start and end time
if run is not None:
q = hax.runs.datasets.query('number == %d' % hax.runs.get_run_number(run)).iloc[0]
start = q.start
end = q.end
else:
start = human_to_utc_datetime(start)
end = human_to_utc_datetime(end)
params = {
"name": name,
"QueryType": "lab",
"StartDateUnix": int(utc_timestamp(start)),
"EndDateUnix": int(utc_timestamp(end)),
"username": c['sc_api_username'],
"api_key": get_sc_api_key(),
}
dates = []
values = []
if url is None:
url = c['sc_api_url']
r = requests.get(url,
params=params)
# If there is an error, raise here instead of giving weird error later
r.raise_for_status()
for entry in r.json():
if not isinstance(entry, dict):
continue
dates.append(datetime.utcfromtimestamp(entry['timestampseconds']))
values.append(entry['value'])
return pd.Series(values, index=dates)
# Alias for convenience
get = get_sc_data