otsdaq  3.03.00
SlowControlsTableBase.cc
1 #include "otsdaq/TablePlugins/SlowControlsTableBase/SlowControlsTableBase.h"
2 #include "otsdaq/TablePlugins/XDAQContextTable/XDAQContextTable.h"
3 
4 #include <fstream> // std::fstream
5 
6 using namespace ots;
7 
8 #undef __MF_SUBJECT__
9 #define __MF_SUBJECT__ "SlowControlsTableBase"
10 
11 //==============================================================================
18  std::string* accumulatedExceptions /* =0 */)
19  : TableBase(tableName, accumulatedExceptions)
20 {
21  // December 2021 started seeing an issue where traceTID is found to be cleared to 0
22  // which crashes TRACE if __COUT__ is used in a Table plugin constructor
23  // This check and re-initialization seems to cover up the issue for now.
24  // Why it is cleared to 0 after the constructor sets it to -1 is still unknown.
25  // Note: it seems to only happen on the first alphabetially ARTDAQ Configure Table plugin.
26  if(traceTID == 0)
27  {
28  std::cout << "SlowControlsTableBase Before traceTID=" << traceTID << __E__;
29  char buf[40];
30  traceInit(trace_name(TRACE_NAME, __TRACE_FILE__, buf, sizeof(buf)), 0);
31  std::cout << "SlowControlsTableBase After traceTID=" << traceTID << __E__;
32  __COUT__ << "SlowControlsTableBase TRACE reinit and Constructed." << __E__;
33  }
34 } // end constuctor()
35 
36 //==============================================================================
40 {
41  __SS__ << "Should not call void constructor, table type is lost!" << __E__;
42  __SS_THROW__;
43 } // end illegal default constructor()
44 
45 //==============================================================================
46 SlowControlsTableBase::~SlowControlsTableBase(void) {} // end destructor()
47 
48 //==============================================================================
49 void SlowControlsTableBase::getSlowControlsChannelList(
50  std::vector<std::pair<std::string /*channelName*/, std::vector<std::string>>>&
51  channelList) const
52 {
53  outputEpicsPVFile(lastConfigManager_, &channelList);
54 } // end getSlowControlsChannelList()
55 
56 //==============================================================================
58 {
59  __COUT__ << "channelListHasChanged()" << __E__;
62 
63  if(lastConfigManager_ == nullptr)
64  {
65  __SS__ << "Illegal call to get status of channel list, no config manager has "
66  "been initialized!"
67  << __E__;
68  __SS_THROW__;
69  }
70 
71  // if here, lastConfigManager_ pointer is defined
72  bool changed = outputEpicsPVFile(lastConfigManager_);
73  __COUT__ << "slowControlsChannelListHasChanged(): return " << std::boolalpha
74  << std::to_string(changed) << __E__;
75  return changed;
76 } // end slowControlsChannelListHasChanged()
77 
78 //==============================================================================
79 unsigned int SlowControlsTableBase::slowControlsHandler(
80  std::stringstream& out,
81  std::string& tabStr,
82  std::string& commentStr,
83  std::string& subsystem,
84  std::string& location,
85  ConfigurationTree slowControlsLink,
86  std::vector<std::pair<std::string /*channelName*/, std::vector<std::string>>>*
87  channelList /*= 0*/
88 ) const
89 try
90 {
91  unsigned int numberOfChannels = 0;
92  __COUTS__(2) << "slowControlsHandler \n" << StringMacros::stackTrace() << __E__;
93 
94  if(!slowControlsLink.isDisconnected())
95  // if(1)
96  {
97  std::vector<std::pair<std::string, ConfigurationTree>> channelChildren =
98  slowControlsLink.getChildren();
99 
100  __COUTVS__(2, channelChildren.size());
101  // first do single bit binary fields
102  bool first = true;
103  for(auto& channel : channelChildren)
104  {
105  if(channel.second.getNode(channelColNames_.colChannelDataType_)
106  .getValue<std::string>() != "1b")
107  continue; // skip non-binary fields
108 
109  if(first) // if first, output header
110  {
111  first = false;
112  OUT << "file \"dbt/soft_bi.dbt\" {" << __E__;
113  PUSHTAB;
114  OUT << "pattern { Subsystem, loc, pvar, ZNAM, ONAM, ZSV, OSV, "
115  "COSV, DESC }"
116  << __E__;
117  PUSHTAB;
118  }
119 
120  ++numberOfChannels;
121 
122  std::string pvName = channel.first;
123  std::string comment =
124  channel.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT)
125  .getValue<std::string>();
126 
127  // output channel
128  OUT << "{ \"" << subsystem << "\", \"" << location << "\", \"" << pvName
129  << "\", \""
130  << "NOT_OK"
131  << "\", \""
132  << "OK"
133  << "\", \""
134  << "MAJOR"
135  << "\", \""
136  << "NO_ALARM"
137  << "\", \""
138  << ""
139  << "\", \"" << comment << "\" }" << __E__;
140 
141  } // end binary channel loop
142  if(!first) // if there was data, then pop tabs
143  {
144  POPTAB;
145  POPTAB;
146  out << "}" << __E__;
147  }
148 
149  // then do 'analog' fields
150  first = true;
151  for(auto& channel : channelChildren)
152  {
153  __COUTVS__(2, channel.first);
154  if(channel.second.getNode(channelColNames_.colChannelDataType_)
155  .getValue<std::string>() == "1b")
156  continue; // skip non-binary fields
157 
158  if(first) // if first, output header
159  {
160  first = false;
161  OUT << "file \"dbt/subst_ai.dbt\" {" << __E__;
162  PUSHTAB;
163  OUT << "pattern { Subsystem, loc, pvar, PREC, EGU, LOLO, LOW, "
164  "HIGH, HIHI, MDEL, ADEL, INP, SCAN, DTYP, DESC }"
165  << __E__;
166  PUSHTAB;
167  }
168 
169  ++numberOfChannels;
170 
171  std::string experimentName = "";
172  try
173  {
174  experimentName = __ENV__("OTS_EPICS_OWNER");
175  }
176  catch(...)
177  {
178  experimentName = __ENV__("OTS_OWNER");
179  }
180  __COUT__ << "experimentName has metricParameterValue: " << experimentName
181  << __E__;
182 
183  std::string pvName = channel.first;
184  std::string comment =
185  channel.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT)
186  .getValue<std::string>();
187  std::string precision = "0";
188  std::string units = channel.second.getNode(channelColNames_.colUnits_)
189  .getValue<std::string>();
190  // channel.second.getNode(channelColNames_.colChannelDataType_)
191  // .getValue<std::string>();
192  std::string low_alarm_lmt =
193  channel.second.getNode(channelColNames_.colLowLowThreshold_)
194  .getValueWithDefault<std::string>("-1000");
195  std::string low_warn_lmt =
196  channel.second.getNode(channelColNames_.colLowThreshold_)
197  .getValueWithDefault<std::string>("-100");
198  std::string high_warn_lmt =
199  channel.second.getNode(channelColNames_.colHighThreshold_)
200  .getValueWithDefault<std::string>("100");
201  std::string high_alarm_lmt =
202  channel.second.getNode(channelColNames_.colHighHighThreshold_)
203  .getValueWithDefault<std::string>("1000");
204  if(channelList != nullptr)
205  {
206  std::vector<std::string> pvSettings;
207  pvSettings.push_back(comment);
208  pvSettings.push_back(low_warn_lmt);
209  pvSettings.push_back(high_warn_lmt);
210  pvSettings.push_back(low_alarm_lmt);
211  pvSettings.push_back(high_alarm_lmt);
212  pvSettings.push_back(precision);
213  pvSettings.push_back(units);
214  channelList->push_back(std::make_pair(
215  experimentName + ":" + subsystem + ":" + location + ":" + pvName,
216  pvSettings));
217  }
218 
219  // output channel
220  OUT << "{ \"" << subsystem << "\", \"" << location << "\", \"" << pvName
221  << "\", \"" << precision // PREC
222  << "\", \"" << units << "\", \"" << low_alarm_lmt << "\", \""
223  << low_warn_lmt << "\", \"" << high_warn_lmt << "\", \"" << high_alarm_lmt
224  << "\", \""
225  << ""
226  << "\", \"" << // MDEL
227  ""
228  << "\", \"" << // ADEL
229  ""
230  << "\", \"" << // INP
231  ""
232  << "\", \"" << // SCAN
233  ""
234  << "\", \"" << // DTYP
235  comment << "\" }" << __E__;
236 
237  } // end binary channel loop
238  if(!first) // if there was data, then pop tabs
239  {
240  POPTAB;
241  POPTAB;
242  out << "}" << __E__;
243  }
244  }
245  else
246  __COUT__ << "Disconnected EventBuilder Slow Controls metric channels link, so "
247  "assuming "
248  "no slow controls channels."
249  << __E__;
250 
251  return numberOfChannels;
252 } // end localSlowControlsHandler
253 catch(const std::runtime_error& e)
254 {
255  __COUTV__(e.what());
256  throw;
257 }
258 
259 //==============================================================================
262  ConfigurationManager* configManager,
263  std::vector<std::pair<std::string /*channelName*/, std::vector<std::string>>>*
264  channelList /*= 0*/) const
265 {
266  /*
267  the file will look something like this:
268 
269  file name.template {
270  pattern { var1, var2, var3, ... }
271  { sub1_for_set1, sub2_for_set1, sub3_for_set1, ... }
272  { sub1_for_set2, sub2_for_set2, sub3_for_set2, ... }
273  { sub1_for_set3, sub2_for_set3, sub3_for_set3, ... }
274 
275  ...
276  }
277 
278  # for comment lines
279 
280  file "soft_ai.dbt" -- for floating point ("analog") data
281 
282  file "soft_bi.dbt" -- for binary values (on/off, good/bad, etc)
283 
284  file "soft_stringin.dbt" -- for string values (e.g. "states")
285 
286  Subsystem names:
287  https://docs.google.com/spreadsheets/d/1SO8R3O5Xm37X0JdaBiVmbg9p9aXy1Gk13uqiWFCchBo/edit#gid=1775059019
288  DTC maps to: CRV, Tracker, EMCal, STM, TEM
289 
290  Example lines:
291 
292  file "soft_ai.dbt" {
293  pattern { Subsystem, loc, var, PREC, EGU, LOLO, LOW, HIGH, HIHI, MDEL, ADEL,
294  DESC } { "TDAQ", "DataLogger", "RunNumber", "0", "", "-1e23", "-1e23", "1e23",
295  "1e23", "", "", "DataLogger run number" } { "TDAQ", "DataLogger", "AvgEvtSize",
296  "0", "MB/evt", "-1e23", "-1e23", "1e23", "1e23", "", "", "Datalogger avg event
297  size" }
298  }
299 
300  file "soft_bi.dbt" {
301  pattern { Subsystem, loc, pvar, ZNAM, ONAM, ZSV, OSV, COSV, DESC }
302  { "Computer", "daq01", "voltages_ok", "NOT_OK", "OK", "MAJOR",
303  "NO_ALARM", "", "voltages_ok daq01" } { "Computer", "daq02", "voltages_ok",
304  "NOT_OK", "OK", "MAJOR", "NO_ALARM", "", "voltages_ok daq02" }
305  }
306  */
307 
308  std::string filename = setFilePath();
309 
310  __COUT__ << "EPICS PV file: " << filename << __E__;
311 
312  std::string previousConfigFileContents;
313  {
314  std::FILE* fp = std::fopen(filename.c_str(), "rb");
315  if(fp)
316  {
317  std::fseek(fp, 0, SEEK_END);
318  previousConfigFileContents.resize(std::ftell(fp));
319  std::rewind(fp);
320  std::fread(
321  &previousConfigFileContents[0], 1, previousConfigFileContents.size(), fp);
322  std::fclose(fp);
323  }
324  else
325  __COUT_WARN__ << "Could not open EPICS IOC config file at " << filename
326  << __E__;
327 
328  } // done reading
329 
331  // generate xdaq run parameter file
332 
333  std::stringstream out;
334  unsigned int numberOfParameters =
335  slowControlsHandlerConfig(out, configManager, channelList);
336  __COUTVS__(2, numberOfParameters);
337 
338  // check if need to restart EPICS ioc
339  // if dbg string has changed, then mark ioc configuration dirty
340  if(previousConfigFileContents != out.str())
341  {
342  __COUT__ << "Configuration has changed! Marking dirty flag..." << __E__;
343 
344  // only write files if first app in context AND channelList is not passed, i.e. init() is only time we write!
345  // if(isFirstAppInContext_ && channelList == nullptr)
346  if(channelList == nullptr)
347  {
348  std::fstream fout;
349  fout.open(filename, std::fstream::out | std::fstream::trunc);
350  if(fout.fail())
351  {
352  __SS__ << "Failed to open EPICS PV file: " << filename << __E__;
353  __SS_THROW__;
354  }
355 
356  fout << out.str();
357  fout.close();
358 
359  std::string csvFilename = filename.substr(0, filename.length() - 3) + "csv";
360  fout.open(csvFilename, std::fstream::out | std::fstream::trunc);
361  if(fout.fail())
362  {
363  __SS__ << "Failed to open CSV EPICS PV file: " << filename << __E__;
364  __SS_THROW__;
365  }
366 
367  std::string csvOut = out.str();
368  // if (csvOut.find("file \"dbt/subst_ai.dbt\" {\n") != std::string::npos) csvOut = csvOut.replace(csvOut.find("file \"dbt/subst_ai.dbt\" {\n"), 26,
369  // "");
370  csvOut.erase(0, csvOut.find("\n") + 1);
371  if(csvOut.find("pattern {") != std::string::npos)
372  csvOut = csvOut.replace(csvOut.find("pattern {"), 10, "");
373  while(csvOut.find("{") != std::string::npos)
374  csvOut = csvOut.replace(csvOut.find("{"), 1, "");
375  while(csvOut.find("}") != std::string::npos)
376  csvOut = csvOut.replace(csvOut.find("}"), 1, "");
377  while(csvOut.find("\"") != std::string::npos)
378  csvOut = csvOut.replace(csvOut.find("\""), 1, "");
379  while(csvOut.find(" ") != std::string::npos)
380  csvOut = csvOut.replace(csvOut.find(" "), 1, "");
381  while(csvOut.find("\t") != std::string::npos)
382  csvOut = csvOut.replace(csvOut.find("\t"), 1, "");
383 
384  fout << csvOut.substr(0, csvOut.length() - 1);
385  fout.close();
386 
387  std::FILE* fp = fopen(EPICS_DIRTY_FILE_PATH.c_str(), "w");
388  if(fp)
389  {
390  fprintf(fp, "1"); // set dirty flag
391  fclose(fp);
392  }
393  else
394  __COUT_WARN__ << "Could not open dirty file: " << EPICS_DIRTY_FILE_PATH
395  << __E__;
396  }
397 
398  // Indicate that PV list has changed
399  // if otsdaq_epics plugin is listening, then write PV data to archive db: SQL insert or modify of ROW for PV
400  __COUT__ << "outputEpicsPVFile() return true" << __E__;
401  return true;
402  } // end handling of previous contents
403  __COUT__ << "outputEpicsPVFile() return false" << __E__;
404  return false;
405 } // end outputEpicsPVFile()
virtual bool outputEpicsPVFile(ConfigurationManager *configManager, std::vector< std::pair< std::string, std::vector< std::string >>> *channelList=0) const
use table name to have different file names! (instead of DEFINES like in DTC)
bool channelListHasChanged_
for managing if PV list has changed
bool isFirstAppInContext_
for managing if PV list has changed
virtual bool slowControlsChannelListHasChanged(void) const
Getters.
static std::string stackTrace(void)