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