tdaq-develop-2025-02-12
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 {
90  unsigned int numberOfChannels = 0;
91  __COUT__ << "slowControlsHandler" << __E__;
92 
93  if(!slowControlsLink.isDisconnected())
94  // if(1)
95  {
96  std::vector<std::pair<std::string, ConfigurationTree>> channelChildren =
97  slowControlsLink.getChildren();
98 
99  // first do single bit binary fields
100  bool first = true;
101  for(auto& channel : channelChildren)
102  {
103  if(channel.second.getNode(channelColNames_.colChannelDataType_)
104  .getValue<std::string>() != "1b")
105  continue; // skip non-binary fields
106 
107  if(first) // if first, output header
108  {
109  first = false;
110  OUT << "file \"dbt/soft_bi.dbt\" {" << __E__;
111  PUSHTAB;
112  OUT << "pattern { Subsystem, loc, pvar, ZNAM, ONAM, ZSV, OSV, "
113  "COSV, DESC }"
114  << __E__;
115  PUSHTAB;
116  }
117 
118  ++numberOfChannels;
119 
120  std::string pvName = channel.first;
121  std::string comment =
122  channel.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT)
123  .getValue<std::string>();
124 
125  // output channel
126  OUT << "{ \"" << subsystem << "\", \"" << location << "\", \"" << pvName
127  << "\", \""
128  << "NOT_OK"
129  << "\", \""
130  << "OK"
131  << "\", \""
132  << "MAJOR"
133  << "\", \""
134  << "NO_ALARM"
135  << "\", \""
136  << ""
137  << "\", \"" << comment << "\" }" << __E__;
138 
139  } // end binary channel loop
140  if(!first) // if there was data, then pop tabs
141  {
142  POPTAB;
143  POPTAB;
144  out << "}" << __E__;
145  }
146 
147  // then do 'analog' fields
148  first = true;
149  for(auto& channel : channelChildren)
150  {
151  if(channel.second.getNode(channelColNames_.colChannelDataType_)
152  .getValue<std::string>() == "1b")
153  continue; // skip non-binary fields
154 
155  if(first) // if first, output header
156  {
157  first = false;
158  OUT << "file \"dbt/subst_ai.dbt\" {" << __E__;
159  PUSHTAB;
160  OUT << "pattern { Subsystem, loc, pvar, PREC, EGU, LOLO, LOW, "
161  "HIGH, HIHI, MDEL, ADEL, INP, SCAN, DTYP, DESC }"
162  << __E__;
163  PUSHTAB;
164  }
165 
166  ++numberOfChannels;
167 
168  std::string pvName = channel.first;
169  std::string comment =
170  channel.second.getNode(TableViewColumnInfo::COL_NAME_COMMENT)
171  .getValue<std::string>();
172  std::string precision = "0";
173  std::string units = channel.second.getNode(channelColNames_.colUnits_)
174  .getValue<std::string>();
175  // channel.second.getNode(channelColNames_.colChannelDataType_)
176  // .getValue<std::string>();
177  std::string low_alarm_lmt =
178  channel.second.getNode(channelColNames_.colLowLowThreshold_)
179  .getValueWithDefault<std::string>("-1000");
180  std::string low_warn_lmt =
181  channel.second.getNode(channelColNames_.colLowThreshold_)
182  .getValueWithDefault<std::string>("-100");
183  std::string high_warn_lmt =
184  channel.second.getNode(channelColNames_.colHighThreshold_)
185  .getValueWithDefault<std::string>("100");
186  std::string high_alarm_lmt =
187  channel.second.getNode(channelColNames_.colHighHighThreshold_)
188  .getValueWithDefault<std::string>("1000");
189  if(channelList != nullptr)
190  {
191  std::vector<std::string> pvSettings;
192  pvSettings.push_back(comment);
193  pvSettings.push_back(low_warn_lmt);
194  pvSettings.push_back(high_warn_lmt);
195  pvSettings.push_back(low_alarm_lmt);
196  pvSettings.push_back(high_alarm_lmt);
197  pvSettings.push_back(precision);
198  pvSettings.push_back(units);
199  channelList->push_back(std::make_pair(
200  "Mu2e:" + subsystem + ":" + location + ":" + pvName, pvSettings));
201  }
202 
203  // output channel
204  OUT << "{ \"" << subsystem << "\", \"" << location << "\", \"" << pvName
205  << "\", \"" << precision // PREC
206  << "\", \"" << units << "\", \"" << low_alarm_lmt << "\", \""
207  << low_warn_lmt << "\", \"" << high_warn_lmt << "\", \"" << high_alarm_lmt
208  << "\", \""
209  << ""
210  << "\", \"" << // MDEL
211  ""
212  << "\", \"" << // ADEL
213  ""
214  << "\", \"" << // INP
215  ""
216  << "\", \"" << // SCAN
217  ""
218  << "\", \"" << // DTYP
219  comment << "\" }" << __E__;
220 
221  } // end binary channel loop
222  if(!first) // if there was data, then pop tabs
223  {
224  POPTAB;
225  POPTAB;
226  out << "}" << __E__;
227  }
228  }
229  else
230  __COUT__ << "Disconnected EventBuilder Slow Controls metric channels link, so "
231  "assuming "
232  "no slow controls channels."
233  << __E__;
234 
235  return numberOfChannels;
236 } // end localSlowControlsHandler
237 
238 //==============================================================================
241  ConfigurationManager* configManager,
242  std::vector<std::pair<std::string /*channelName*/, std::vector<std::string>>>*
243  channelList /*= 0*/) const
244 {
245  /*
246  the file will look something like this:
247 
248  file name.template {
249  pattern { var1, var2, var3, ... }
250  { sub1_for_set1, sub2_for_set1, sub3_for_set1, ... }
251  { sub1_for_set2, sub2_for_set2, sub3_for_set2, ... }
252  { sub1_for_set3, sub2_for_set3, sub3_for_set3, ... }
253 
254  ...
255  }
256 
257  # for comment lines
258 
259  file "soft_ai.dbt" -- for floating point ("analog") data
260 
261  file "soft_bi.dbt" -- for binary values (on/off, good/bad, etc)
262 
263  file "soft_stringin.dbt" -- for string values (e.g. "states")
264 
265  Subsystem names:
266  https://docs.google.com/spreadsheets/d/1SO8R3O5Xm37X0JdaBiVmbg9p9aXy1Gk13uqiWFCchBo/edit#gid=1775059019
267  DTC maps to: CRV, Tracker, EMCal, STM, TEM
268 
269  Example lines:
270 
271  file "soft_ai.dbt" {
272  pattern { Subsystem, loc, var, PREC, EGU, LOLO, LOW, HIGH, HIHI, MDEL, ADEL,
273  DESC } { "TDAQ", "DataLogger", "RunNumber", "0", "", "-1e23", "-1e23", "1e23",
274  "1e23", "", "", "DataLogger run number" } { "TDAQ", "DataLogger", "AvgEvtSize",
275  "0", "MB/evt", "-1e23", "-1e23", "1e23", "1e23", "", "", "Datalogger avg event
276  size" }
277  }
278 
279  file "soft_bi.dbt" {
280  pattern { Subsystem, loc, pvar, ZNAM, ONAM, ZSV, OSV, COSV, DESC }
281  { "Computer", "daq01", "voltages_ok", "NOT_OK", "OK", "MAJOR",
282  "NO_ALARM", "", "voltages_ok daq01" } { "Computer", "daq02", "voltages_ok",
283  "NOT_OK", "OK", "MAJOR", "NO_ALARM", "", "voltages_ok daq02" }
284  }
285  */
286 
287  std::string filename = setFilePath();
288 
289  __COUT__ << "EPICS PV file: " << filename << __E__;
290 
291  std::string previousConfigFileContents;
292  {
293  std::FILE* fp = std::fopen(filename.c_str(), "rb");
294  if(fp)
295  {
296  std::fseek(fp, 0, SEEK_END);
297  previousConfigFileContents.resize(std::ftell(fp));
298  std::rewind(fp);
299  std::fread(
300  &previousConfigFileContents[0], 1, previousConfigFileContents.size(), fp);
301  std::fclose(fp);
302  }
303  else
304  __COUT_WARN__ << "Could not open EPICS IOC config file at " << filename
305  << __E__;
306 
307  } // done reading
308 
310  // generate xdaq run parameter file
311 
312  std::stringstream out;
313  unsigned int numberOfParameters =
314  slowControlsHandlerConfig(out, configManager, channelList);
315  __COUTV__(numberOfParameters);
316 
317  // check if need to restart EPICS ioc
318  // if dbg string has changed, then mark ioc configuration dirty
319  if(previousConfigFileContents != out.str())
320  {
321  __COUT__ << "Configuration has changed! Marking dirty flag..." << __E__;
322 
323  // only write files if first app in context AND channelList is not passed, i.e. init() is only time we write!
324  // if(isFirstAppInContext_ && channelList == nullptr)
325  if(channelList == nullptr)
326  {
327  std::fstream fout;
328  fout.open(filename, std::fstream::out | std::fstream::trunc);
329  if(fout.fail())
330  {
331  __SS__ << "Failed to open EPICS PV file: " << filename << __E__;
332  __SS_THROW__;
333  }
334 
335  fout << out.str();
336  fout.close();
337 
338  std::string csvFilename = filename.substr(0, filename.length() - 3) + "csv";
339  fout.open(csvFilename, std::fstream::out | std::fstream::trunc);
340  if(fout.fail())
341  {
342  __SS__ << "Failed to open CSV EPICS PV file: " << filename << __E__;
343  __SS_THROW__;
344  }
345 
346  std::string csvOut = out.str();
347  // if (csvOut.find("file \"dbt/subst_ai.dbt\" {\n") != std::string::npos) csvOut = csvOut.replace(csvOut.find("file \"dbt/subst_ai.dbt\" {\n"), 26,
348  // "");
349  csvOut.erase(0, csvOut.find("\n") + 1);
350  if(csvOut.find("pattern {") != std::string::npos)
351  csvOut = csvOut.replace(csvOut.find("pattern {"), 10, "");
352  while(csvOut.find("{") != std::string::npos)
353  csvOut = csvOut.replace(csvOut.find("{"), 1, "");
354  while(csvOut.find("}") != std::string::npos)
355  csvOut = csvOut.replace(csvOut.find("}"), 1, "");
356  while(csvOut.find("\"") != std::string::npos)
357  csvOut = csvOut.replace(csvOut.find("\""), 1, "");
358  while(csvOut.find(" ") != std::string::npos)
359  csvOut = csvOut.replace(csvOut.find(" "), 1, "");
360  while(csvOut.find("\t") != std::string::npos)
361  csvOut = csvOut.replace(csvOut.find("\t"), 1, "");
362 
363  fout << csvOut.substr(0, csvOut.length() - 1);
364  fout.close();
365 
366  std::FILE* fp = fopen(EPICS_DIRTY_FILE_PATH.c_str(), "w");
367  if(fp)
368  {
369  fprintf(fp, "1"); // set dirty flag
370  fclose(fp);
371  }
372  else
373  __COUT_WARN__ << "Could not open dirty file: " << EPICS_DIRTY_FILE_PATH
374  << __E__;
375  }
376 
377  // Indicate that PV list has changed
378  // if otsdaq_epics plugin is listening, then write PV data to archive db: SQL insert or modify of ROW for PV
379  __COUT__ << "outputEpicsPVFile() return true" << __E__;
380  return true;
381  } // end handling of previous contents
382  __COUT__ << "outputEpicsPVFile() return false" << __E__;
383  return false;
384 } // end outputEpicsPVFile()
bool isDisconnected(void) const
std::vector< std::pair< std::string, ConfigurationTree > > getChildren(std::map< std::string, std::string > filterMap=std::map< std::string, std::string >(), bool byPriority=false, bool onlyStatusTrue=false) const
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.