otsdaq  v2_03_00
FEVInterface.cc
1 #include "otsdaq-core/FECore/FEVInterface.h"
2 #include "otsdaq-core/CoreSupervisors/CoreSupervisorBase.h"
3 #include "otsdaq-core/FECore/FEVInterfacesManager.h"
4 #include "otsdaq-core/NetworkUtilities/UDPDataStreamerBase.h"
5 
6 #include <iostream>
7 #include <sstream>
8 #include <thread> //for std::thread
9 
10 using namespace ots;
11 
12 //========================================================================================================================
13 FEVInterface::FEVInterface(const std::string& interfaceUID,
14  const ConfigurationTree& theXDAQContextConfigTree,
15  const std::string& configurationPath)
16  : WorkLoop(interfaceUID)
17  , Configurable(theXDAQContextConfigTree, configurationPath)
18  , interfaceUID_(interfaceUID)
19  //, interfaceType_
20  //(theXDAQContextConfigTree_.getBackNode(theConfigurationPath_).getNode("FEInterfacePluginName").getValue<std::string>())
21  //, daqHardwareType_ ("NOT SET")
22  //, firmwareType_ ("NOT SET")
23  , slowControlsWorkLoop_(interfaceUID + "-SlowControls", this)
24 {
25  // NOTE!! be careful to not decorate with __FE_COUT__ because in the constructor the
26  // base class versions of function (e.g. getInterfaceType) are called because the
27  // derived class has not been instantiate yet!
28  __COUT__ << "'" << interfaceUID << "' Constructed." << __E__;
29 }
30 
31 //========================================================================================================================
32 FEVInterface::~FEVInterface(void)
33 {
34  // NOTE:: be careful not to call __FE_COUT__ decoration because it uses the tree and
35  // it may already be destructed partially
36  __COUT__ << FEVInterface::interfaceUID_ << " Destructed." << __E__;
37 }
38 
39 //========================================================================================================================
40 void FEVInterface::configureSlowControls(void)
41 {
42  // START IT HERE
43  if(metricMan && !metricMan->Running() && metricMan->Initialized())
44  metricMan->do_start();
45 
46  ConfigurationTree slowControlsGroupLink =
47  theXDAQContextConfigTree_.getBackNode(theConfigurationPath_)
48  .getNode("LinkToSlowControlsChannelTable");
49 
50  if(slowControlsGroupLink.isDisconnected())
51  {
52  __FE_COUT__
53  << "slowControlsGroupLink is disconnected, so done configuring slow controls."
54  << __E__;
55  return;
56  }
57  __FE_COUT__ << "slowControlsGroupLink is valid! Configuring slow controls..."
58  << __E__;
59 
60  mapOfSlowControlsChannels_.clear();
61  std::vector<std::pair<std::string, ConfigurationTree> > groupLinkChildren =
62  slowControlsGroupLink.getChildren();
63  for(auto& groupLinkChild : groupLinkChildren)
64  {
65  // skip channels that are off
66  if(!(groupLinkChild.second.getNode(TableViewColumnInfo::COL_NAME_STATUS)
67  .getValue<bool>()))
68  continue;
69 
70  __FE_COUT__ << "Channel:" << getInterfaceUID() << "/" << groupLinkChild.first
71  << "\t Type:" << groupLinkChild.second.getNode("ChannelDataType")
72  << __E__;
73 
74  mapOfSlowControlsChannels_.insert(std::pair<std::string, FESlowControlsChannel>(
75  groupLinkChild.first,
77  getInterfaceUID(),
78  groupLinkChild.first,
79  groupLinkChild.second.getNode("ChannelDataType").getValue<std::string>(),
80  universalDataSize_,
81  universalAddressSize_,
82  groupLinkChild.second.getNode("UniversalInterfaceAddress")
83  .getValue<std::string>(),
84  groupLinkChild.second.getNode("UniversalDataBitOffset")
85  .getValue<unsigned int>(),
86  groupLinkChild.second.getNode("ReadAccess").getValue<bool>(),
87  groupLinkChild.second.getNode("WriteAccess").getValue<bool>(),
88  groupLinkChild.second.getNode("MonitoringEnabled").getValue<bool>(),
89  groupLinkChild.second.getNode("RecordChangesOnly").getValue<bool>(),
90  groupLinkChild.second.getNode("DelayBetweenSamplesInSeconds")
91  .getValue<time_t>(),
92  groupLinkChild.second.getNode("LocalSavingEnabled").getValue<bool>(),
93  groupLinkChild.second.getNode("LocalFilePath").getValue<std::string>(),
94  groupLinkChild.second.getNode("RadixFileName").getValue<std::string>(),
95  groupLinkChild.second.getNode("SaveBinaryFile").getValue<bool>(),
96  groupLinkChild.second.getNode("AlarmsEnabled").getValue<bool>(),
97  groupLinkChild.second.getNode("LatchAlarms").getValue<bool>(),
98  groupLinkChild.second.getNode("LowLowThreshold").getValue<std::string>(),
99  groupLinkChild.second.getNode("LowThreshold").getValue<std::string>(),
100  groupLinkChild.second.getNode("HighThreshold").getValue<std::string>(),
101  groupLinkChild.second.getNode("HighHighThreshold")
102  .getValue<std::string>())));
103  }
104 } // end configureSlowControls()
105 
106 //========================================================================================================================
107 bool FEVInterface::slowControlsRunning(void)
108 try
109 {
110  __FE_COUT__ << "slowControlsRunning" << __E__;
111 
112  if(mapOfSlowControlsChannels_.size() == 0)
113  {
114  __FE_COUT__
115  << "No slow controls channels to monitor, exiting slow controls workloop."
116  << __E__;
117  return false;
118  }
119  std::string readVal;
120  readVal.resize(universalDataSize_); // size to data in advance
121 
122  FESlowControlsChannel* channel;
123 
124  const unsigned int txBufferSz = 1500;
125  const unsigned int txBufferFullThreshold = 750;
126  std::string txBuffer;
127  txBuffer.reserve(txBufferSz);
128 
129  ConfigurationTree FEInterfaceNode =
130  theXDAQContextConfigTree_.getBackNode(theConfigurationPath_);
131 
132 
133  //attempt to make Slow Controls transfer socket
134  std::unique_ptr<UDPDataStreamerBase> slowContrlolsTxSocket;
135  std::string slowControlsSupervisorIPAddress = "", slowControlsSelfIPAddress = "";
136  int slowControlsSupervisorPort = 0, slowControlsSelfPort = 0;
137  try
138  {
139  ConfigurationTree slowControlsInterfaceLink =
140  FEInterfaceNode.getNode("LinkToSlowControlsSupervisorTable");
141 
142 
143  if(slowControlsInterfaceLink.isDisconnected())
144  {
145  __FE_SS__ << "slowControlsInterfaceLink is disconnected, so no socket made."
146  << __E__;
147  __FE_SS_THROW__;
148  }
149 
150  slowControlsSelfIPAddress = FEInterfaceNode.getNode("SlowControlsTxSocketIPAddress")
151  .getValue<std::string>();
152  slowControlsSelfPort = FEInterfaceNode.getNode("SlowControlsTxSocketPort").getValue<int>();
153  slowControlsSupervisorIPAddress = slowControlsInterfaceLink.getNode(
154  "IPAddress").getValue<std::string>();
155  slowControlsSupervisorPort = slowControlsInterfaceLink.getNode("Port").getValue<int>();
156  }
157  catch(...)
158  {
159  __FE_COUT__ << "Link to slow controls supervisor is missing, so no socket made."
160  << __E__;
161  }
162 
163  if(slowControlsSupervisorPort && slowControlsSelfPort &&
164  slowControlsSupervisorIPAddress != "" &&
165  slowControlsSelfIPAddress != "")
166  {
167 
168  __FE_COUT__ << "slowControlsInterfaceLink is valid! Create tx socket..." << __E__;
169  slowContrlolsTxSocket.reset(new UDPDataStreamerBase(
170  slowControlsSelfIPAddress,
171  slowControlsSelfPort,
172  slowControlsSupervisorIPAddress,
173  slowControlsSupervisorPort));
174  }
175  else
176  {
177  __FE_COUT__ << "Invalid Slow Controls socket parameters, so no socket made." << __E__;
178  }
179 
180 
181  // check if aggregate saving
182 
183  FILE* fp = 0;
184  bool aggregateFileIsBinaryFormat = false;
185  if(FEInterfaceNode.getNode("SlowControlsLocalAggregateSavingEnabled")
186  .getValue<bool>())
187  {
188  aggregateFileIsBinaryFormat =
189  FEInterfaceNode.getNode("SlowControlsSaveBinaryFile").getValue<bool>();
190 
191  __FE_COUT_INFO__ << "Slow Controls Aggregate Saving turned On BinaryFormat="
192  << aggregateFileIsBinaryFormat << __E__;
193 
194  std::string saveFullFileName =
195  FEInterfaceNode.getNode("SlowControlsLocalFilePath").getValue<std::string>() +
196  "/" +
197  FEInterfaceNode.getNode("SlowControlsRadixFileName").getValue<std::string>() +
198  "-" + FESlowControlsChannel::underscoreString(getInterfaceUID()) + "-" +
199  std::to_string(time(0)) + (aggregateFileIsBinaryFormat ? ".dat" : ".txt");
200 
201  fp = fopen(saveFullFileName.c_str(), aggregateFileIsBinaryFormat ? "ab" : "a");
202  if(!fp)
203  {
204  __FE_COUT_ERR__ << "Failed to open slow controls channel file: "
205  << saveFullFileName << __E__;
206  // continue on, just nothing will be saved
207  }
208  else
209  __FE_COUT_INFO__ << "Slow controls aggregate file opened: "
210  << saveFullFileName << __E__;
211  }
212  else
213  __FE_COUT_INFO__ << "Slow Controls Aggregate Saving turned off." << __E__;
214 
215  time_t timeCounter = 0;
216 
217  while(slowControlsWorkLoop_.getContinueWorkLoop())
218  {
219  sleep(1); // seconds
220  ++timeCounter;
221 
222  if(txBuffer.size())
223  __FE_COUT__ << "txBuffer sz=" << txBuffer.size() << __E__;
224 
225  txBuffer.resize(0); // clear buffer a la txBuffer = "";
226 
227  //__FE_COUT__ << "timeCounter=" << timeCounter << __E__;
228  //__FE_COUT__ << "txBuffer sz=" << txBuffer.size() << __E__;
229 
230  for(auto& slowControlsChannelPair : mapOfSlowControlsChannels_)
231  {
232  channel = &slowControlsChannelPair.second;
233 
234  // skip if no read access
235  if(!channel->readAccess_)
236  continue;
237 
238  // skip if not a sampling moment in time for channel
239  if(timeCounter % channel->delayBetweenSamples_)
240  continue;
241 
242  __FE_COUT__ << "Channel:" << getInterfaceUID() << "/"
243  << slowControlsChannelPair.first << __E__;
244  __FE_COUT__ << "Monitoring..." << __E__;
245 
246  universalRead(channel->getUniversalAddress(), &readVal[0]);
247 
248  // { //print
249  // __FE_SS__ << "0x ";
250  // for(int i=(int)universalAddressSize_-1;i>=0;--i)
251  // ss << std::hex << (int)((readVal[i]>>4)&0xF) <<
252  // (int)((readVal[i])&0xF) << " " << std::dec;
253  // ss << __E__;
254  // __FE_COUT__ << "Sampled.\n" << ss.str();
255  // }
256 
257  // have sample
258  channel->handleSample(readVal, txBuffer, fp, aggregateFileIsBinaryFormat);
259  if(txBuffer.size())
260  __FE_COUT__ << "txBuffer sz=" << txBuffer.size() << __E__;
261 
262  //"Channel:" << getInterfaceUID() << "/"
263  // << slowControlsChannelPair.first << __E__;
264  // Value << readVal
265 
266  // For example,
267  if(metricMan && universalAddressSize_ <= 8)
268  {
269 
270  uint64_t val = 0; // 64 bits!
271  for(size_t ii = 0; ii < universalAddressSize_; ++ii)
272  val += (uint8_t)readVal[ii] << (ii * 4);
273 
274 
275 
276  __FE_COUT__ << "Sending sample to Metric Manager..." << __E__;
277  metricMan->sendMetric(
278  getInterfaceUID() + "/" + slowControlsChannelPair.first,
279  val,
280  "",
281  3,
282  artdaq::MetricMode::LastPoint);
283  }
284 
285  // make sure buffer hasn't exploded somehow
286  if(txBuffer.size() > txBufferSz)
287  {
288  __FE_SS__ << "This should never happen hopefully!" << __E__;
289  __FE_SS_THROW__;
290  }
291 
292  // send early if threshold reached
293  if(slowContrlolsTxSocket && txBuffer.size() > txBufferFullThreshold)
294  {
295  __FE_COUT__ << "Sending now! txBufferFullThreshold="
296  << txBufferFullThreshold << __E__;
297  slowContrlolsTxSocket->send(txBuffer);
298  txBuffer.resize(0); // clear buffer a la txBuffer = "";
299  }
300  }
301 
302  if(txBuffer.size())
303  __FE_COUT__ << "txBuffer sz=" << txBuffer.size() << __E__;
304 
305  // send anything left
306  if(slowContrlolsTxSocket && txBuffer.size())
307  {
308  __FE_COUT__ << "Sending now!" << __E__;
309  slowContrlolsTxSocket->send(txBuffer);
310  }
311 
312  if(fp)
313  fflush(fp); // flush anything in aggregate file for reading ease
314  }
315 
316  if(fp)
317  fclose(fp);
318 
319  return false;
320 } // end slowControlsRunning()
321 catch(const std::runtime_error& e)
322 {
323  __FE_COUT__ << "Error caught during slow controls running thread: " << e.what() << __E__;
324  return false;
325 }
326 catch(...)
327 {
328  __FE_COUT__ << "Unknown error caught during slow controls running thread." << __E__;
329  return false;
330 } // end slowControlsRunning()
331 
332 //========================================================================================================================
333 // SendAsyncErrorToGateway
334 // Static -- thread
335 // Send async error or soft error to gateway
336 // Do this as thread so that workloop can end
337 void FEVInterface::sendAsyncErrorToGateway(FEVInterface* fe,
338  const std::string& errorMessage,
339  bool isSoftError) try
340 {
341  if(isSoftError)
342  __COUT_ERR__ << ":FE:" << fe->getInterfaceType() << ":" << fe->getInterfaceUID()
343  << ":" << fe->theConfigurationRecordName_ << ":"
344  << "Sending FE Async SOFT Running Error... \n"
345  << errorMessage << __E__;
346  else
347  __COUT_ERR__ << ":FE:" << fe->getInterfaceType() << ":" << fe->getInterfaceUID()
348  << ":" << fe->theConfigurationRecordName_ << ":"
349  << "Sending FE Async Running Error... \n"
350  << errorMessage << __E__;
351 
352  XDAQ_CONST_CALL xdaq::ApplicationDescriptor* gatewaySupervisor =
353  fe->VStateMachine::parentSupervisor_->allSupervisorInfo_.getGatewayInfo()
354  .getDescriptor();
355 
356  SOAPParameters parameters;
357  parameters.addParameter("ErrorMessage", errorMessage);
358 
359  xoap::MessageReference replyMessage =
360  fe->VStateMachine::parentSupervisor_->SOAPMessenger::sendWithSOAPReply(
361  gatewaySupervisor, isSoftError ? "AsyncSoftError" : "AsyncError", parameters);
362 
363  std::stringstream replyMessageSStream;
364  replyMessageSStream << SOAPUtilities::translate(replyMessage);
365  __COUT__ << ":FE:" << fe->getInterfaceType() << ":" << fe->getInterfaceUID() << ":"
366  << fe->theConfigurationRecordName_ << ":"
367  << "Received... " << replyMessageSStream.str() << std::endl;
368 
369  if(replyMessageSStream.str().find("Fault") != std::string::npos)
370  {
371  __COUT_ERR__ << ":FE:" << fe->getInterfaceType() << ":" << fe->getInterfaceUID()
372  << ":" << fe->theConfigurationRecordName_ << ":"
373  << "Failure to indicate fault to Gateway..." << __E__;
374  throw;
375  }
376 }
377 catch(const xdaq::exception::Exception& e)
378 {
379  if(isSoftError)
380  __COUT__ << "SOAP message failure indicating front-end asynchronous running SOFT "
381  "error back to Gateway: "
382  << e.what() << __E__;
383  else
384  __COUT__ << "SOAP message failure indicating front-end asynchronous running "
385  "error back to Gateway: "
386  << e.what() << __E__;
387 }
388 catch(...)
389 {
390  if(isSoftError)
391  __COUT__ << "Unknown error encounter indicating front-end asynchronous running "
392  "SOFT error back to Gateway."
393  << __E__;
394  else
395  __COUT__ << "Unknown error encounter indicating front-end asynchronous running "
396  "error back to Gateway."
397  << __E__;
398 } // end SendAsyncErrorToGateway()
399 
400 //========================================================================================================================
401 // override WorkLoop::workLoopThread
402 // return false to stop the workloop from calling the thread again
403 bool FEVInterface::workLoopThread(toolbox::task::WorkLoop* workLoop)
404 {
405  try
406  {
407  continueWorkLoop_ =
408  running(); /* in case users return false, without using continueWorkLoop_*/
409  }
410  catch(...) //
411  {
412  // catch all, then rethrow with local variables needed
413  __FE_SS__;
414 
415  bool isSoftError = false;
416 
417  try
418  {
419  throw;
420  }
421  catch(const __OTS_SOFT_EXCEPTION__& e)
422  {
423  ss << "SOFT Error was caught while configuring: " << e.what() << std::endl;
424  isSoftError = true;
425  }
426  catch(const std::runtime_error& e)
427  {
428  ss << "Caught an error during running at FE Interface '"
429  << Configurable::theConfigurationRecordName_ << "': " << e.what() << __E__;
430  }
431  catch(...)
432  {
433  ss << "Caught an unknown error during running." << __E__;
434  }
435 
436  // At this point, an asynchronous error has occurred
437  // during front-end running...
438  // Send async error to Gateway
439 
440  __FE_COUT_ERR__ << ss.str();
441 
442  std::thread(
443  [](FEVInterface* fe, const std::string errorMessage, bool isSoftError) {
444  FEVInterface::sendAsyncErrorToGateway(fe, errorMessage, isSoftError);
445  },
446  // pass the values
447  this,
448  ss.str(),
449  isSoftError)
450  .detach();
451 
452  return false;
453  }
454 
455  return continueWorkLoop_;
456 } // end workLoopThread()
457 
458 //========================================================================================================================
459 // registerFEMacroFunction
460 // used by user-defined front-end interface implementations of this
461 // virtual interface class to register their macro functions.
462 //
463 // Front-end Macro Functions are then made accessible through the ots Control System
464 // web interfaces. The menu consisting of all enabled FEs macros is assembled
465 // by the FE Supervisor (and its FE Interface Manager).
466 void FEVInterface::registerFEMacroFunction(
467  const std::string& feMacroName,
468  frontEndMacroFunction_t feMacroFunction,
469  const std::vector<std::string>& namesOfInputArgs,
470  const std::vector<std::string>& namesOfOutputArgs,
471  uint8_t requiredUserPermissions,
472  const std::string& allowedCallingFEs)
473 {
474  if(mapOfFEMacroFunctions_.find(feMacroName) != mapOfFEMacroFunctions_.end())
475  {
476  __FE_SS__ << "feMacroName '" << feMacroName << "' already exists! Not allowed."
477  << __E__;
478  __FE_COUT_ERR__ << "\n" << ss.str();
479  __FE_SS_THROW__;
480  }
481 
482  mapOfFEMacroFunctions_.insert(std::pair<std::string, frontEndMacroStruct_t>(
483  feMacroName,
484  frontEndMacroStruct_t(feMacroName,
485  feMacroFunction,
486  namesOfInputArgs,
487  namesOfOutputArgs,
488  requiredUserPermissions,
489  allowedCallingFEs)));
490 }
491 
492 //========================================================================================================================
493 // getFEMacroConstArgument
494 // helper function for getting the value of an argument
495 //
496 // Note: static function
497 const std::string& FEVInterface::getFEMacroConstArgument(frontEndMacroConstArgs_t& args,
498  const std::string& argName)
499 {
500  for(const frontEndMacroArg_t& pair : args)
501  {
502  if(pair.first == argName)
503  {
504  __COUT__ << "argName : " << pair.second << __E__;
505  return pair.second;
506  }
507  }
508  __SS__ << "Requested input argument not found with name '" << argName << "'" << __E__;
509  __SS_THROW__;
510 }
511 
512 //========================================================================================================================
513 // getFEMacroConstArgumentValue
514 // helper function for getting the copy of the value of an argument
515 template<>
516 std::string getFEMacroConstArgumentValue<std::string>(
517  FEVInterface::frontEndMacroConstArgs_t& args, const std::string& argName)
518 {
519  return FEVInterface::getFEMacroConstArgument(args, argName);
520 }
521 
522 //========================================================================================================================
523 // getFEMacroArgumentValue
524 // helper function for getting the copy of the value of an argument
525 template<>
526 std::string getFEMacroArgumentValue<std::string>(FEVInterface::frontEndMacroArgs_t& args,
527  const std::string& argName)
528 {
529  return FEVInterface::getFEMacroArgument(args, argName);
530 }
531 
532 //========================================================================================================================
533 // getFEMacroOutputArgument
534 // helper function for getting the value of an argument
535 //
536 // Note: static function
537 std::string& FEVInterface::getFEMacroArgument(frontEndMacroArgs_t& args,
538  const std::string& argName)
539 {
540  for(std::pair<const std::string /* output arg name */,
541  std::string /* arg output value */>& pair : args)
542  {
543  if(pair.first == argName)
544  return pair.second;
545  }
546  __SS__ << "Requested argument not found with name '" << argName << "'" << __E__;
547  __SS_THROW__;
548 }
549 
550 //========================================================================================================================
551 // runSequenceOfCommands
552 // runs a sequence of write commands from a linked section of the configuration tree
553 // based on these fields:
554 // - WriteAddress, WriteValue, StartingBitPosition, BitFieldSize
555 void FEVInterface::runSequenceOfCommands(const std::string& treeLinkName)
556 {
557  std::map<uint64_t, uint64_t> writeHistory;
558  uint64_t writeAddress, writeValue, bitMask;
559  uint8_t bitPosition;
560 
561  std::string writeBuffer;
562  std::string readBuffer;
563  char msg[1000];
564  bool ignoreError = true;
565 
566  // ignore errors getting sequence of commands through tree (since it is optional)
567  try
568  {
569  ConfigurationTree configSeqLink =
570  theXDAQContextConfigTree_.getNode(theConfigurationPath_)
571  .getNode(treeLinkName);
572 
573  // but throw errors if problems executing the sequence of commands
574  try
575  {
576  if(configSeqLink.isDisconnected())
577  __FE_COUT__ << "Disconnected configure sequence" << __E__;
578  else
579  {
580  __FE_COUT__ << "Handling configure sequence." << __E__;
581  auto childrenMap = configSeqLink.getChildrenMap();
582  for(const auto& child : childrenMap)
583  {
584  // WriteAddress and WriteValue fields
585 
586  writeAddress =
587  child.second.getNode("WriteAddress").getValue<uint64_t>();
588  writeValue = child.second.getNode("WriteValue").getValue<uint64_t>();
589  bitPosition =
590  child.second.getNode("StartingBitPosition").getValue<uint8_t>();
591  bitMask =
592  (1 << child.second.getNode("BitFieldSize").getValue<uint8_t>()) -
593  1;
594 
595  writeValue &= bitMask;
596  writeValue <<= bitPosition;
597  bitMask = ~(bitMask << bitPosition);
598 
599  // place into write history
600  if(writeHistory.find(writeAddress) == writeHistory.end())
601  writeHistory[writeAddress] = 0; // init to 0
602 
603  writeHistory[writeAddress] &= bitMask; // clear incoming bits
604  writeHistory[writeAddress] |= writeValue; // add incoming bits
605 
606  sprintf(msg,
607  "\t Writing %s: \t %ld(0x%lX) \t %ld(0x%lX)",
608  child.first.c_str(),
609  writeAddress,
610  writeAddress,
611  writeHistory[writeAddress],
612  writeHistory[writeAddress]);
613 
614  __FE_COUT__ << msg << __E__;
615 
616  universalWrite((char*)&writeAddress,
617  (char*)&(writeHistory[writeAddress]));
618  }
619  }
620  }
621  catch(...)
622  {
623  ignoreError = false;
624  throw;
625  }
626  }
627  catch(...)
628  {
629  if(!ignoreError)
630  throw;
631  // else ignoring error
632  __FE_COUT__
633  << "Unable to access sequence of commands through configuration tree. "
634  << "Assuming no sequence. " << __E__;
635  }
636 }
637 
638 //========================================================================================================================
639 // runFrontEndMacro
640 // Helper function to run this FEInterface's own front-end macro
641 // and gets the output arguments back.
642 //
643 // Very similar to FEVInterfacesManager::runFEMacro()
644 //
645 // Note: that argsOut are populated for caller, can just pass empty vector.
646 void FEVInterface::runSelfFrontEndMacro(
647  const std::string& feMacroName,
648  // not equivalent to __ARGS__
649  // left non-const value so caller can modify inputArgs as they are being created
650  const std::vector<FEVInterface::frontEndMacroArg_t>& argsIn,
651  std::vector<FEVInterface::frontEndMacroArg_t>& argsOut)
652 {
653  // have pointer to virtual FEInterface, find Macro structure
654  auto FEMacroIt = this->getMapOfFEMacroFunctions().find(feMacroName);
655  if(FEMacroIt == this->getMapOfFEMacroFunctions().end())
656  {
657  __CFG_SS__ << "FE Macro '" << feMacroName << "' of interfaceID '"
658  << getInterfaceUID() << "' was not found!" << __E__;
659  __CFG_COUT_ERR__ << "\n" << ss.str();
660  __CFG_SS_THROW__;
661  }
662  const FEVInterface::frontEndMacroStruct_t& feMacro = FEMacroIt->second;
663 
664  // check for input arg name match
665  for(unsigned int i = 0; i < argsIn.size(); ++i)
666  if(argsIn[i].first != feMacro.namesOfInputArguments_[i])
667  {
668  __CFG_SS__ << "FE Macro '" << feMacro.feMacroName_ << "' of interfaceID '"
669  << getInterfaceUID() << "' was attempted with a mismatch in"
670  << " a name of an input argument. " << argsIn[i].first
671  << " was given. " << feMacro.namesOfInputArguments_[i]
672  << " expected." << __E__;
673  __CFG_COUT_ERR__ << "\n" << ss.str();
674  __CFG_SS_THROW__;
675  }
676 
677  // check namesOfInputArguments_
678  if(feMacro.namesOfInputArguments_.size() != argsIn.size())
679  {
680  __CFG_SS__ << "FE Macro '" << feMacro.feMacroName_ << "' of interfaceID '"
681  << getInterfaceUID() << "' was attempted with a mismatch in"
682  << " number of input arguments. " << argsIn.size() << " were given. "
683  << feMacro.namesOfInputArguments_.size() << " expected." << __E__;
684  __CFG_COUT_ERR__ << "\n" << ss.str();
685  __CFG_SS_THROW__;
686  }
687 
688  __CFG_COUT__ << "# of input args = " << argsIn.size() << __E__;
689  for(auto& argIn : argsIn)
690  __CFG_COUT__ << argIn.first << ": " << argIn.second << __E__;
691 
692  __CFG_COUT__ << "Launching FE Macro '" << feMacro.feMacroName_ << "' ..." << __E__;
693 
694  argsOut.clear();
695  for(unsigned int i = 0; i < feMacro.namesOfOutputArguments_.size(); ++i)
696  argsOut.push_back(FEVInterface::frontEndMacroArg_t(
697  feMacro.namesOfOutputArguments_[i], "DEFAULT"));
698 
699  // run it!
700  (this->*(feMacro.macroFunction_))(feMacro, argsIn, argsOut);
701 
702  __CFG_COUT__ << "FE Macro complete!" << __E__;
703 
704  __CFG_COUT__ << "# of output args = " << argsOut.size() << __E__;
705  for(const auto& arg : argsOut)
706  __CFG_COUT__ << arg.first << ": " << arg.second << __E__;
707 
708 } // end runSelfFrontEndMacro()
709 
710 //========================================================================================================================
711 // runFrontEndMacro
712 // run a front-end macro in the target interface plug-in and gets the output arguments
713 // back
714 void FEVInterface::runFrontEndMacro(
715  const std::string& targetInterfaceID,
716  const std::string& feMacroName,
717  const std::vector<FEVInterface::frontEndMacroArg_t>& inputArgs,
718  std::vector<FEVInterface::frontEndMacroArg_t>& outputArgs) const
719 {
720  __FE_COUTV__(targetInterfaceID);
721  __FE_COUTV__(VStateMachine::parentSupervisor_);
722 
723  std::string inputArgsStr = StringMacros::vectorToString(
724  inputArgs, ";" /*primaryDelimeter*/, "," /*secondaryDelimeter*/);
725 
726  __FE_COUTV__(inputArgsStr);
727 
728  xoap::MessageReference message =
729  SOAPUtilities::makeSOAPMessageReference("FECommunication");
730 
731  SOAPParameters parameters;
732  parameters.addParameter("type", "feMacro");
733  parameters.addParameter("requester", FEVInterface::interfaceUID_);
734  parameters.addParameter("targetInterfaceID", targetInterfaceID);
735  parameters.addParameter("feMacroName", feMacroName);
736  parameters.addParameter("inputArgs", inputArgsStr);
737  SOAPUtilities::addParameters(message, parameters);
738 
739  __FE_COUT__ << "Sending FE communication: " << SOAPUtilities::translate(message)
740  << __E__;
741 
742  xoap::MessageReference replyMessage =
743  VStateMachine::parentSupervisor_->SOAPMessenger::sendWithSOAPReply(
744  VStateMachine::parentSupervisor_->allSupervisorInfo_
745  .getAllMacroMakerTypeSupervisorInfo()
746  .begin()
747  ->second.getDescriptor(),
748  message);
749 
750  __FE_COUT__ << "Response received: " << SOAPUtilities::translate(replyMessage)
751  << __E__;
752 
753  SOAPParameters rxParameters;
754  rxParameters.addParameter("Error");
755  SOAPUtilities::receive(replyMessage, rxParameters);
756 
757  std::string error = rxParameters.getValue("Error");
758 
759  if(error != "")
760  {
761  // error occurred!
762  __FE_SS__ << "Error transmitting request to target interface '"
763  << targetInterfaceID << "' from '" << FEVInterface::interfaceUID_
764  << ".' " << error << __E__;
765  __FE_SS_THROW__;
766  }
767 
768  // extract output args
769  SOAPParameters argsOutParameter;
770  argsOutParameter.addParameter("outputArgs");
771  SOAPUtilities::receive(replyMessage, argsOutParameter);
772 
773  std::string outputArgsStr = argsOutParameter.getValue("outputArgs");
774  std::set<char> pairDelimiter({';'}), nameValueDelimiter({','});
775 
776  std::map<std::string, std::string> mapToReturn;
777  StringMacros::getMapFromString(
778  outputArgsStr, mapToReturn, pairDelimiter, nameValueDelimiter);
779 
780  outputArgs.clear();
781  for(auto& mapPair : mapToReturn)
782  outputArgs.push_back(mapPair);
783 
784 } // end runFrontEndMacro()
785 
786 //========================================================================================================================
787 // receiveFromFrontEnd
788 // specialized template function for T=std::string
789 //
790 // Note: requester can be a wildcard string as defined in StringMacros
791 void FEVInterface::receiveFromFrontEnd(const std::string& requester,
792  std::string& retValue,
793  unsigned int timeoutInSeconds) const
794 {
795  __FE_COUTV__(requester);
796  __FE_COUTV__(parentSupervisor_);
797 
798  std::string data = "0";
799  bool found = false;
800  while(1)
801  {
802  // mutex scope
803  {
804  std::lock_guard<std::mutex> lock(
805  parentInterfaceManager_->frontEndCommunicationReceiveMutex_);
806 
807  auto receiveBuffersForTargetIt =
808  parentInterfaceManager_->frontEndCommunicationReceiveBuffer_.find(
809  FEVInterface::interfaceUID_);
810  if(receiveBuffersForTargetIt !=
811  parentInterfaceManager_->frontEndCommunicationReceiveBuffer_.end())
812  {
813  __FE_COUT__ << "Number of source buffers found for front-end '"
814  << FEVInterface::interfaceUID_
815  << "': " << receiveBuffersForTargetIt->second.size() << __E__;
816 
817  for(auto& buffPair : receiveBuffersForTargetIt->second)
818  __FE_COUTV__(buffPair.first);
819 
820  // match requester to map of buffers
821  std::string sourceBufferId = "";
822  std::queue<std::string /*value*/>& sourceBuffer =
823  StringMacros::getWildCardMatchFromMap(
824  requester, receiveBuffersForTargetIt->second, &sourceBufferId);
825 
826  __FE_COUT__ << "Found source buffer '" << sourceBufferId << "' with size "
827  << sourceBuffer.size() << __E__;
828 
829  if(sourceBuffer.size())
830  {
831  __FE_COUT__ << "Found a value in queue of size "
832  << sourceBuffer.size() << __E__;
833 
834  // remove from receive buffer
835  retValue = sourceBuffer.front();
836  sourceBuffer.pop();
837  return;
838  }
839  else
840  __FE_COUT__ << "Source buffer empty for '" << requester << "'"
841  << __E__;
842  }
843 
844  // else, not found...
845 
846  // if out of time, throw error
847  if(!timeoutInSeconds)
848  {
849  __FE_SS__ << "Timeout (" << timeoutInSeconds
850  << " s) waiting for front-end communication from " << requester
851  << "." << __E__;
852  __FE_SS_THROW__;
853  }
854  // else, there is still hope
855 
856  } // end mutex scope
857 
858  // else try again in a sec
859  __FE_COUT__ << "Waiting for front-end communication from " << requester << " for "
860  << timeoutInSeconds << " more seconds..." << __E__;
861 
862  --timeoutInSeconds;
863  sleep(1); // wait a sec
864  } // end timeout loop
865 
866  // should never get here
867 } // end receiveFromFrontEnd()
868 
869 //========================================================================================================================
870 // receiveFromFrontEnd
871 // specialized template function for T=std::string
872 // Note: if called without template <T> syntax, necessary because types of
873 // std::basic_string<char> cause compiler problems if no string specific function
874 std::string FEVInterface::receiveFromFrontEnd(const std::string& requester,
875  unsigned int timeoutInSeconds) const
876 {
877  std::string retValue;
878  FEVInterface::receiveFromFrontEnd(requester, retValue, timeoutInSeconds);
879  return retValue;
880 } // end receiveFromFrontEnd()
881 
882 //========================================================================================================================
883 // macroStruct_t constructor
884 FEVInterface::macroStruct_t::macroStruct_t(const std::string& macroString)
885 {
886  __COUTV__(macroString);
887 
888  // example macro string:
889  // {"name":"testPublic","sequence":"0:w:1001:writeVal,1:r:1001:","time":"Sat Feb 0
890  // 9 2019 10:42:03 GMT-0600 (Central Standard Time)","notes":"","LSBF":"false"}@
891 
892  std::vector<std::string> extractVec;
893  StringMacros::getVectorFromString(macroString, extractVec, {'"'});
894 
895  __COUTV__(StringMacros::vectorToString(extractVec, " ||| "));
896 
897  enum
898  {
899  MACRONAME_NAME_INDEX = 1,
900  MACRONAME_VALUE_INDEX = 3,
901  SEQUENCE_NAME_INDEX = 5,
902  SEQUENCE_VALUE_INDEX = 7,
903  LSBF_NAME_INDEX = 17,
904  LSFBF_VALUE_INDEX = 19,
905  };
906 
907  // verify fields in sequence (for sanity)
908  if(MACRONAME_NAME_INDEX >= extractVec.size() ||
909  extractVec[MACRONAME_NAME_INDEX] != "name")
910  {
911  __SS__ << "Invalid sequence, 'name' expected in position " << MACRONAME_NAME_INDEX
912  << __E__;
913  __SS_THROW__;
914  }
915  if(SEQUENCE_NAME_INDEX >= extractVec.size() ||
916  extractVec[SEQUENCE_NAME_INDEX] != "sequence")
917  {
918  __SS__ << "Invalid sequence, 'sequence' expected in position "
919  << SEQUENCE_NAME_INDEX << __E__;
920  __SS_THROW__;
921  }
922  if(LSBF_NAME_INDEX >= extractVec.size() || extractVec[LSBF_NAME_INDEX] != "LSBF")
923  {
924  __SS__ << "Invalid sequence, 'LSBF' expected in position " << LSBF_NAME_INDEX
925  << __E__;
926  __SS_THROW__;
927  }
928  macroName_ = extractVec[MACRONAME_VALUE_INDEX];
929  __COUTV__(macroName_);
930  lsbf_ = extractVec[LSFBF_VALUE_INDEX] == "false" ? false : true;
931  __COUTV__(lsbf_);
932  std::string& sequence = extractVec[SEQUENCE_VALUE_INDEX];
933  __COUTV__(sequence);
934 
935  std::vector<std::string> sequenceCommands;
936  StringMacros::getVectorFromString(sequence, sequenceCommands, {','});
937 
938  __COUTV__(StringMacros::vectorToString(sequenceCommands, " ### "));
939 
940  for(auto& command : sequenceCommands)
941  {
942  __COUTV__(command);
943 
944  // Note: the only way to distinguish between variable and data
945  // is hex characters or not (lower and upper case allowed for hex)
946 
947  std::vector<std::string> commandPieces;
948  StringMacros::getVectorFromString(command, commandPieces, {':'});
949 
950  __COUTV__(StringMacros::vectorToString(commandPieces, " ### "));
951 
952  __COUTV__(commandPieces.size());
953 
954  // command format
955  // index | type | address/sleep[ms] | data
956  // d is delay (1+2)
957  // w is write (1+3)
958  // r is read (1+2/3 with arg)
959 
960  // extract input arguments, as the variables in address/data fields
961  // extract output arguments, as the variables in read data fields
962 
963  if(commandPieces.size() < 3 || commandPieces.size() > 4 ||
964  commandPieces[1].size() != 1)
965  {
966  __SS__ << "Invalid command type specified in command string: " << command
967  << __E__;
968  __SS_THROW__;
969  }
970 
971  //==========================
972  // Use lambda to identify variable name in field
973  std::function<bool(const std::string& /*field value*/
974  )>
975  localIsVariable = [/*capture variable*/](const std::string& fieldValue) {
976  // create local message facility subject
977  std::string mfSubject_ = "isVar";
978  __GEN_COUTV__(fieldValue);
979 
980  // return false if all hex characters found
981  for(const auto& c : fieldValue)
982  if(!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
983  (c >= 'A' && c <= 'F')))
984  return true; // is variable name!
985  return false; // else is a valid hex string, so not variable name
986 
987  }; //end local lambda localIsVariable()
988 
989  if(commandPieces[1][0] == 'r' && commandPieces.size() == 4) // read type
990  {
991  __COUT__ << "Read type found." << __E__;
992  // 2: address or optional variable name
993  // 3: optional variable name
994 
995  operations_.push_back(
996  std::make_pair(macroStruct_t::OP_TYPE_READ, readOps_.size()));
997 
998  readOps_.push_back(macroStruct_t::readOp_t());
999  readOps_.back().addressIsVar_ = localIsVariable(commandPieces[2]);
1000  readOps_.back().dataIsVar_ = localIsVariable(commandPieces[3]);
1001 
1002  if(!readOps_.back().addressIsVar_)
1003  {
1004  if(lsbf_) // flip byte order
1005  {
1006  std::string lsbfData = "";
1007 
1008  // always add leading 0 to guarantee do not miss data
1009  commandPieces[2] = "0" + commandPieces[2];
1010  for(unsigned int i = 0; i < commandPieces[2].size() / 2; ++i)
1011  {
1012  __COUTV__(commandPieces[2].size() - 2 * (i + 1));
1013  // add one byte at a time, backwards
1014  lsbfData +=
1015  commandPieces[2][commandPieces[2].size() - 2 * (i + 1)];
1016  lsbfData +=
1017  commandPieces[2][commandPieces[2].size() - 2 * (i + 1) + 1];
1018  __COUTV__(lsbfData);
1019  }
1020  __COUTV__(lsbfData);
1021  StringMacros::getNumber("0x" + lsbfData, readOps_.back().address_);
1022  }
1023  else
1024  StringMacros::getNumber("0x" + commandPieces[2],
1025  readOps_.back().address_);
1026  }
1027  else
1028  {
1029  readOps_.back().addressVarName_ = commandPieces[2];
1030  __COUTV__(readOps_.back().addressVarName_);
1031 
1032  namesOfInputArguments_.emplace(readOps_.back().addressVarName_);
1033  }
1034 
1035  if(readOps_.back().dataIsVar_)
1036  {
1037  readOps_.back().dataVarName_ = commandPieces[3];
1038  __COUTV__(readOps_.back().dataVarName_);
1039 
1040  namesOfOutputArguments_.emplace(readOps_.back().dataVarName_);
1041  }
1042  }
1043  else if(commandPieces[1][0] == 'w' && commandPieces.size() == 4) // write type
1044  {
1045  __COUT__ << "Write type found." << __E__;
1046  // 2: address or optional variable name
1047  // 3: data or optional variable name
1048 
1049  operations_.push_back(
1050  std::make_pair(macroStruct_t::OP_TYPE_WRITE, writeOps_.size()));
1051 
1052  writeOps_.push_back(macroStruct_t::writeOp_t());
1053  writeOps_.back().addressIsVar_ = localIsVariable(commandPieces[2]);
1054  writeOps_.back().dataIsVar_ = localIsVariable(commandPieces[3]);
1055 
1056  if(!writeOps_.back().addressIsVar_)
1057  {
1058  if(lsbf_) // flip byte order
1059  {
1060  std::string lsbfData = "";
1061 
1062  // always add leading 0 to guarantee do not miss data
1063  commandPieces[2] = "0" + commandPieces[2];
1064  for(unsigned int i = 0; i < commandPieces[2].size() / 2; ++i)
1065  {
1066  __COUTV__(commandPieces[2].size() - 2 * (i + 1));
1067  // add one byte at a time, backwards
1068  lsbfData +=
1069  commandPieces[2][commandPieces[2].size() - 2 * (i + 1)];
1070  lsbfData +=
1071  commandPieces[2][commandPieces[2].size() - 2 * (i + 1) + 1];
1072  __COUTV__(lsbfData);
1073  }
1074  __COUTV__(lsbfData);
1075  StringMacros::getNumber("0x" + lsbfData, writeOps_.back().address_);
1076  }
1077  else
1078  StringMacros::getNumber("0x" + commandPieces[2],
1079  writeOps_.back().address_);
1080  }
1081  else
1082  {
1083  writeOps_.back().addressVarName_ = commandPieces[2];
1084  __COUTV__(writeOps_.back().addressVarName_);
1085 
1086  namesOfInputArguments_.emplace(writeOps_.back().addressVarName_);
1087  }
1088 
1089  if(!writeOps_.back().dataIsVar_)
1090  {
1091  if(lsbf_) // flip byte order
1092  {
1093  std::string lsbfData = "";
1094 
1095  // always add leading 0 to guarantee do not miss data
1096  commandPieces[2] = "0" + commandPieces[3];
1097  for(unsigned int i = 0; i < commandPieces[3].size() / 2; ++i)
1098  {
1099  __COUTV__(commandPieces[3].size() - 2 * (i + 1));
1100  // add one byte at a time, backwards
1101  lsbfData +=
1102  commandPieces[3][commandPieces[3].size() - 2 * (i + 1)];
1103  lsbfData +=
1104  commandPieces[3][commandPieces[3].size() - 2 * (i + 1) + 1];
1105  __COUTV__(lsbfData);
1106  }
1107  __COUTV__(lsbfData);
1108  StringMacros::getNumber("0x" + lsbfData, writeOps_.back().data_);
1109  }
1110  else
1111  StringMacros::getNumber("0x" + commandPieces[3],
1112  writeOps_.back().data_);
1113  }
1114  else
1115  {
1116  writeOps_.back().dataVarName_ = commandPieces[3];
1117  __COUTV__(writeOps_.back().dataVarName_);
1118 
1119  namesOfInputArguments_.emplace(writeOps_.back().dataVarName_);
1120  }
1121  }
1122  else if(commandPieces[1][0] == 'd' && commandPieces.size() == 3) // delay type
1123  {
1124  __COUT__ << "Delay type found." << __E__;
1125  // 2: delay[ms] or optional variable name
1126 
1127  operations_.push_back(
1128  std::make_pair(macroStruct_t::OP_TYPE_DELAY, delayOps_.size()));
1129 
1130  delayOps_.push_back(macroStruct_t::delayOp_t());
1131  delayOps_.back().delayIsVar_ = localIsVariable(commandPieces[2]);
1132 
1133  if(!delayOps_.back().delayIsVar_)
1134  StringMacros::getNumber("0x" + commandPieces[2], delayOps_.back().delay_);
1135  else
1136  {
1137  delayOps_.back().delayVarName_ = commandPieces[2];
1138  __COUTV__(delayOps_.back().delayVarName_);
1139 
1140  namesOfInputArguments_.emplace(delayOps_.back().delayVarName_);
1141  }
1142  }
1143  else // invalid type
1144  {
1145  __SS__ << "Invalid command type '" << commandPieces[1][0]
1146  << "' specified with " << commandPieces.size() << " components."
1147  << __E__;
1148  __SS_THROW__;
1149  }
1150 
1151  } // end sequence commands extraction loop
1152 
1153  __COUT__ << operations_.size() << " operations extracted: \n\t" << readOps_.size()
1154  << " reads \n\t" << writeOps_.size() << " writes \n\t" << delayOps_.size()
1155  << " delays" << __E__;
1156 
1157  __COUT__ << "Input arguments: " << __E__;
1158  for(const auto& inputArg : namesOfInputArguments_)
1159  __COUT__ << "\t" << inputArg << __E__;
1160 
1161  __COUT__ << "Output arguments: " << __E__;
1162  for(const auto& outputArg : namesOfOutputArguments_)
1163  __COUT__ << "\t" << outputArg << __E__;
1164 
1165 } // end macroStruct_t constructor
1166 
1167 //========================================================================================================================
1168 // runMacro
1169 void FEVInterface::runMacro(
1171  std::map<std::string /*name*/, uint64_t /*value*/>& variableMap)
1172 {
1173  // Similar to FEVInterface::runSequenceOfCommands()
1174 
1175  __FE_COUT__ << "Running Macro '" << macro.macroName_ << "' of "
1176  << macro.operations_.size() << " operations." << __E__;
1177 
1178  for(auto& op : macro.operations_)
1179  {
1180  if(op.first == macroStruct_t::OP_TYPE_READ)
1181  {
1182  __FE_COUT__ << "Doing read op..." << __E__;
1183  macroStruct_t::readOp_t& readOp = macro.readOps_[op.second];
1184  if(readOp.addressIsVar_)
1185  {
1186  __FE_COUTV__(readOp.addressVarName_);
1187  readOp.address_ = variableMap.at(readOp.addressVarName_);
1188  }
1189 
1190  uint64_t dataValue = 0;
1191 
1192  __FE_COUT__ << std::hex << "Read address: \t 0x" << readOp.address_ << __E__
1193  << std::dec;
1194 
1195  universalRead((char*)&readOp.address_, (char*)&dataValue);
1196 
1197  __FE_COUT__ << std::hex << "Read data: \t 0x" << dataValue << __E__
1198  << std::dec;
1199 
1200  if(readOp.dataIsVar_)
1201  {
1202  __FE_COUTV__(readOp.dataVarName_);
1203  variableMap.at(readOp.dataVarName_) = dataValue;
1204  }
1205 
1206  } // end read op
1207  else if(op.first == macroStruct_t::OP_TYPE_WRITE)
1208  {
1209  __FE_COUT__ << "Doing write op..." << __E__;
1210  macroStruct_t::writeOp_t& writeOp = macro.writeOps_[op.second];
1211  if(writeOp.addressIsVar_)
1212  {
1213  __FE_COUTV__(writeOp.addressVarName_);
1214  writeOp.address_ = variableMap.at(writeOp.addressVarName_);
1215  }
1216  if(writeOp.dataIsVar_)
1217  {
1218  __FE_COUTV__(writeOp.dataVarName_);
1219  writeOp.data_ = variableMap.at(writeOp.dataVarName_);
1220  }
1221 
1222  __FE_COUT__ << std::hex << "Write address: \t 0x" << writeOp.address_ << __E__
1223  << std::dec;
1224  __FE_COUT__ << std::hex << "Write data: \t 0x" << writeOp.data_ << __E__
1225  << std::dec;
1226 
1227  universalWrite((char*)&writeOp.address_, (char*)&writeOp.data_);
1228 
1229  } // end write op
1230  else if(op.first == macroStruct_t::OP_TYPE_DELAY)
1231  {
1232  __FE_COUT__ << "Doing delay op..." << __E__;
1233 
1234  macroStruct_t::delayOp_t& delayOp = macro.delayOps_[op.second];
1235  if(delayOp.delayIsVar_)
1236  {
1237  __FE_COUTV__(delayOp.delayVarName_);
1238  delayOp.delay_ = variableMap.at(delayOp.delayVarName_);
1239  }
1240 
1241  __FE_COUT__ << std::dec << "Delay ms: \t " << delayOp.delay_ << __E__;
1242 
1243  usleep(delayOp.delay_ /*ms*/ * 1000);
1244 
1245  } // end delay op
1246  else // invalid type
1247  {
1248  __FE_SS__ << "Invalid command type '" << op.first << "!'" << __E__;
1249  __FE_SS_THROW__;
1250  }
1251 
1252  } // end operations loop
1253 
1254 } // end runMacro