API
timeSeriesSimulator.hpp
Go to the documentation of this file.
1 /** \file timeSeriesSimulator.hpp
2  * \brief The MagAO-X XXXXXX header file
3  *
4  * \ingroup timeSeriesSimulator_files
5  */
6 
7 #ifndef timeSeriesSimulator_hpp
8 #define timeSeriesSimulator_hpp
9 
10 #include <cmath>
11 #include <iostream>
12 #include <iomanip>
13 #include <sstream>
14 #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
15 #include "../../magaox_git_version.h"
16 
17 /** \defgroup timeSeriesSimulator
18  * \brief The XXXXXX application to do YYYYYYY
19  *
20  * <a href="../handbook/operating/software/apps/XXXXXX.html">Application Documentation</a>
21  *
22  * \ingroup apps
23  *
24  */
25 
26 /** \defgroup timeSeriesSimulator_files
27  * \ingroup timeSeriesSimulator
28  */
29 
30 namespace MagAOX
31 {
32 namespace app
33 {
34 
35 /// The MagAO-X xxxxxxxx
36 /**
37  * \ingroup timeSeriesSimulator
38  */
39 class timeSeriesSimulator : public MagAOXApp<true>
40 {
41 
42 protected:
43  /** \name Configurable Parameters
44  *@{
45  */
46 
47  //here add parameters which will be config-able at runtime
48 
49  ///@}
50  const double PI = 3.141592653589793238463;
51  static const uintmax_t nanos_in_milli = 1000000;
52 
53  pcf::IndiProperty function, duty_cycle, simsensor;
54  // <device>.function.sin switch
55  // <device>.function.cos switch
56  // <device>.function.square switch
57  // <device>.function.constant switch
58  // <device>.duty_cycle.period number
59  // <device>.duty_cycle.amplitude number
60  // <device>.simsensor.value number
61  enum class SimFunction
62  {
63  sin,
64  cos,
65  square,
66  constant
67  };
69  std::vector<std::string> SimFunctionNames = {"sin", "cos", "square", "constant"};
70  double amplitude = 1.0;
71  double period = 5.0;
72  double startTimeSec;
74  {
75  pcf::IndiProperty *property;
76  double targetPos;
77  double startPos;
78  double requestTime;
79  };
80  std::vector<pcf::IndiProperty *> gizmos;
81  pcf::IndiProperty gizmo_presets, gizmo_zero;
82  std::unordered_map<std::string, MotionRequest *> gizmosInMotion;
83  int n_gizmos = 2;
84  double gizmoTimeToTarget = 1;
85 
86 public:
87  /// Default c'tor.
89 
90  /// D'tor, declared and defined for noexcept.
92  {
93  auto it = gizmos.begin();
94 
95  while (it != gizmos.end())
96  {
97  delete *it;
98  ++it;
99  }
100  }
101 
102  virtual void setupConfig();
103 
104  /// Implementation of loadConfig logic, separated for testing.
105  /** This is called by loadConfig().
106  */
107  int loadConfigImpl(mx::app::appConfigurator &_config /**< [in] an application configuration from which to load values*/);
108 
109  virtual void loadConfig();
110 
111  /// Startup function
112  /**
113  *
114  */
115  virtual int appStartup();
116 
117  /// Implementation of the FSM for timeSeriesSimulator.
118  /**
119  * \returns 0 on no critical error
120  * \returns -1 on an error requiring shutdown
121  */
122  virtual int appLogic();
123 
124  /// Shutdown the app.
125  /**
126  *
127  */
128  virtual int appShutdown();
129 
135  void updateVals();
136  void updateSimsensor();
137  void updateGizmos();
138  void requestGizmoTarget(pcf::IndiProperty *gizmoPtr, double targetPos);
139  double lerp(double x0, double y0, double x1, double y1, double xnew);
140 };
141 
142 timeSeriesSimulator::timeSeriesSimulator() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
143 {
144  m_loopPause = nanos_in_milli * 200; // 200 ms sampling rate for signal
145  return;
146 }
147 
149 {
150 }
151 
152 int timeSeriesSimulator::loadConfigImpl(mx::app::appConfigurator &_config)
153 {
154  static_cast<void>(_config);
155  return 0;
156 }
157 
159 {
160  loadConfigImpl(config);
161 }
162 
164 {
165  registerIndiPropertyNew(function,
166  "function",
167  pcf::IndiProperty::Switch,
168  pcf::IndiProperty::ReadWrite,
169  pcf::IndiProperty::Idle,
170  pcf::IndiProperty::OneOfMany,
171  INDI_NEWCALLBACK(function));
172  function.add(pcf::IndiElement("sin"));
173  function["sin"].setSwitchState(pcf::IndiElement::SwitchStateType::Off);
174  function.add(pcf::IndiElement("cos"));
175  function["cos"].setSwitchState(pcf::IndiElement::SwitchStateType::Off);
176  function.add(pcf::IndiElement("square"));
177  function["square"].setSwitchState(pcf::IndiElement::SwitchStateType::On);
178  function.add(pcf::IndiElement("constant"));
179  function["constant"].setSwitchState(pcf::IndiElement::SwitchStateType::Off);
180 
181  REG_INDI_NEWPROP_NOCB(simsensor, "function_out", pcf::IndiProperty::Number);
182  simsensor.add(pcf::IndiElement("value"));
183  simsensor["value"] = 0.0;
184  simsensor.setState(pcf::IndiProperty::Ok);
185 
186  REG_INDI_NEWPROP(duty_cycle, "duty_cycle", pcf::IndiProperty::Number);
187  duty_cycle.add(pcf::IndiElement("period"));
188  duty_cycle["period"] = period;
189  duty_cycle.add(pcf::IndiElement("amplitude"));
190  duty_cycle["amplitude"] = amplitude;
191 
192  for (int i = 0; i < n_gizmos; i++)
193  {
194  std::cerr << "configuring gizmo #" << i << std::endl;
195  gizmos.push_back(new pcf::IndiProperty(pcf::IndiProperty::Number));
196  pcf::IndiProperty *prop = gizmos.back();
197  prop->setDevice(m_configName);
198  std::stringstream propName;
199  propName << "gizmo_" << std::setfill('0') << std::setw(4) << i;
200  prop->setName(propName.str());
201  std::cerr << "gizmo prop name is " << propName.str() << std::endl;
202  prop->setPerm(pcf::IndiProperty::ReadWrite);
203  prop->setState(pcf::IndiProperty::Idle);
204  for (int j = 0; j < 2; j++)
205  {
206  std::cerr << "configuring gizmo element #" << j << std::endl;
207  auto elemName = j == 0 ? "current" : "target";
208  indi::addNumberElement<float>(*prop, elemName, 0, 100, 1, "%f", std::to_string(i));
209  std::cerr << "added " << elemName << " to prop" << std::endl;
210  }
212  std::cerr << "added to gizmos" << std::endl;
213  }
214 
215  startTimeSec = mx::sys::get_curr_time();
216  updateVals();
218  return 0;
219 }
220 
222 {
223  updateVals();
224 
225  return 0;
226 }
227 
229 {
230  return 0;
231 }
232 
233 void timeSeriesSimulator::requestGizmoTarget(pcf::IndiProperty *gizmoPtr, double targetPos)
234 {
235  double currentPos = (*gizmoPtr)["current"].get<double>();
236  (*gizmoPtr)["target"] = targetPos;
237 
238  MotionRequest *theReq;
239  std::string ipName = gizmoPtr->getName();
240  if (gizmosInMotion.count(ipName) == 0)
241  {
242  theReq = new MotionRequest;
243  theReq->property = gizmoPtr;
244  theReq->startPos = currentPos;
245  theReq->targetPos = targetPos;
246  theReq->requestTime = mx::sys::get_curr_time();
247  gizmosInMotion[ipName] = theReq;
248  }
249  else
250  {
251  theReq = gizmosInMotion[ipName];
252  theReq->targetPos = targetPos;
253  theReq->startPos = currentPos;
254  theReq->requestTime = mx::sys::get_curr_time();
255  }
256 }
257 
259 (const pcf::IndiProperty &ipRecv)
260 {
261  std::string ipName = ipRecv.getName();
262  std::cerr << "setProperty cb for gizmos" << std::endl;
263  auto it = gizmos.begin();
264  while (it != gizmos.end())
265  {
266  pcf::IndiProperty *gizmoPtr = *it;
267  pcf::IndiProperty theGizmo = *gizmoPtr;
268  if (ipName == theGizmo.getName())
269  {
270  std::cerr << "Adjusting prop " << ipName << std::endl;
271  if (ipRecv.find("target"))
272  {
273  double currentPos = theGizmo["current"].get<double>();
274  double targetPos = ipRecv["target"].get<double>();
275  std::stringstream msg;
276  msg << "Setting '" << theGizmo.getName() << "' to " << targetPos << " currently " << currentPos;
277  log<software_notice>({__FILE__, __LINE__, msg.str()});
278  std::cerr << msg.str() << std::endl;
279  requestGizmoTarget(gizmoPtr, targetPos);
280  theGizmo.setState(pcf::IndiProperty::Busy);
281  m_indiDriver->sendSetProperty(theGizmo);
282  }
283  }
284  ++it;
285  }
286  return 0;
287 }
288 
289 INDI_NEWCALLBACK_DEFN(timeSeriesSimulator, duty_cycle)
290 (const pcf::IndiProperty &ipRecv)
291 {
293 
294  if (ipRecv.find("period"))
295  {
296  duty_cycle["period"] = ipRecv["period"].get<double>();
297  period = ipRecv["period"].get<double>();
298  std::stringstream msg;
299  msg << "Setting 'period' to " << period;
300  log<software_notice>({__FILE__, __LINE__, msg.str()});
301  }
302  if (ipRecv.find("amplitude"))
303  {
304  duty_cycle["amplitude"] = ipRecv["amplitude"].get<double>();
305  amplitude = ipRecv["amplitude"].get<double>();
306  std::stringstream msg;
307  msg << "Setting 'amplitude' to " << amplitude;
308  log<software_notice>({__FILE__, __LINE__, msg.str()});
309  }
310 
311  duty_cycle.setState(pcf::IndiProperty::Ok);
312  m_indiDriver->sendSetProperty(duty_cycle);
314  return 0;
315 }
316 
317 INDI_NEWCALLBACK_DEFN(timeSeriesSimulator, function)
318 (const pcf::IndiProperty &ipRecv)
319 {
320  std::string currentFunctionName;
321  switch (myFunction)
322  {
323  case SimFunction::sin:
324  currentFunctionName = "sin";
325  break;
326  case SimFunction::cos:
327  currentFunctionName = "sin";
328  break;
329  case SimFunction::square:
330  currentFunctionName = "square";
331  break;
332  case SimFunction::constant:
333  currentFunctionName = "constant";
334  break;
335  }
336 
338 
339  auto updatedSwitches = ipRecv.getElements();
340  for (auto fname : SimFunctionNames)
341  {
342  // Implements SwitchRule OneOfMany behavior such that you can only
343  // switch things On, not Off. Compliant newSwitch messages will only
344  // contain a value for *one* switch element, so we switch that one On
345  // (if requested to be, otherwise ignore) and set all others to Off.
346  if (updatedSwitches.count(fname))
347  {
348  if (ipRecv[fname].getSwitchState() == pcf::IndiElement::SwitchStateType::On)
349  {
350  std::cerr << "Got fname " << fname << std::endl;
351  currentFunctionName = fname;
352  }
353  }
354  }
355  if (currentFunctionName == "sin")
356  {
357  myFunction = SimFunction::sin;
358  log<software_notice>({__FILE__, __LINE__, "Switching sine 'On'"});
359  }
360  else if (currentFunctionName == "cos")
361  {
362  myFunction = SimFunction::cos;
363  log<software_notice>({__FILE__, __LINE__, "Switching cosine 'On'"});
364  }
365  else if (currentFunctionName == "square")
366  {
367  myFunction = SimFunction::square;
368  log<software_notice>({__FILE__, __LINE__, "Switching square wave 'On'"});
369  }
370  else if (currentFunctionName == "constant")
371  {
372  myFunction = SimFunction::constant;
373  log<software_notice>({__FILE__, __LINE__, "Switching constant 'On'"});
374  }
375  for (auto fname : SimFunctionNames)
376  {
377  if (fname == currentFunctionName)
378  {
379  function[fname] = pcf::IndiElement::SwitchStateType::On;
380  }
381  else
382  {
383  function[fname] = pcf::IndiElement::SwitchStateType::Off;
384  }
385  }
386 
387  function.setState(pcf::IndiProperty::Ok);
388  m_indiDriver->sendSetProperty(function);
389 
390  updateVals();
391 
392  return 0;
393 
394 }
395 
397 {
398  double elapsedSeconds = mx::sys::get_curr_time() - startTimeSec;
399  switch (myFunction)
400  {
401  case SimFunction::sin:
402  simsensor["value"] = amplitude * sin(elapsedSeconds * ((2 * PI) / period));
403  break;
404  case SimFunction::cos:
405  simsensor["value"] = amplitude * cos(elapsedSeconds * ((2 * PI) / period));
406  break;
408  simsensor["value"] = amplitude;
409  break;
410  case SimFunction::square:
411  simsensor["value"] = amplitude * ((int)(elapsedSeconds / period) % 2);
412  break;
413  default:
414  break;
415  }
416  if (m_indiDriver)
417  {
418  m_indiDriver->sendSetProperty(simsensor);
419  }
420 }
421 
422 double timeSeriesSimulator::lerp(double x0, double y0, double x1, double y1, double xnew)
423 {
424  return y0 + (xnew - x0) * ((y1 - y0) / (x1 - x0));
425 }
426 
428 {
429  auto it = gizmosInMotion.begin();
430  while (it != gizmosInMotion.end())
431  {
432  pcf::IndiProperty *gizmoProp = it->second->property;
433  MotionRequest *theMotionRequest = it->second;
434  double elapsedSeconds = mx::sys::get_curr_time() - theMotionRequest->requestTime;
435  double currentPos;
436  if (elapsedSeconds < gizmoTimeToTarget)
437  {
438  currentPos = lerp(
439  0,
440  theMotionRequest->startPos,
442  theMotionRequest->targetPos,
443  elapsedSeconds);
444  ++it;
445  }
446  else
447  {
448  currentPos = theMotionRequest->targetPos;
449  it = gizmosInMotion.erase(it);
450  gizmoProp->setState(pcf::IndiProperty::Ok);
451  std::cerr << gizmoProp->getName() << " moved to " << currentPos << std::endl;
452  }
453  (*gizmoProp)["current"] = currentPos;
454  (*gizmoProp)["target"] = theMotionRequest->targetPos;
455 
456  if (m_indiDriver)
457  {
458  m_indiDriver->sendSetProperty(*gizmoProp);
459  }
460  }
461 }
462 
464 {
465  updateSimsensor();
466  updateGizmos();
467 }
468 
469 } //namespace app
470 } //namespace MagAOX
471 
472 #endif //timeSeriesSimulator_hpp
The base-class for MagAO-X applications.
Definition: MagAOXApp.hpp:75
std::string m_configName
The name of the configuration file (minus .conf).
Definition: MagAOXApp.hpp:88
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2082
int registerIndiPropertyNew(pcf::IndiProperty &prop, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is exposed for others to request a New Property for.
unsigned long m_loopPause
The time in nanoseconds to pause the main loop. The appLogic() function of the derived class is calle...
Definition: MagAOXApp.hpp:100
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
Definition: MagAOXApp.hpp:537
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
INDI_NEWCALLBACK_DECL(timeSeriesSimulator, gizmos)
INDI_NEWCALLBACK_DECL(timeSeriesSimulator, duty_cycle)
void requestGizmoTarget(pcf::IndiProperty *gizmoPtr, double targetPos)
INDI_NEWCALLBACK_DECL(timeSeriesSimulator, gizmo_zero)
double lerp(double x0, double y0, double x1, double y1, double xnew)
virtual int appShutdown()
Shutdown the app.
INDI_NEWCALLBACK_DECL(timeSeriesSimulator, gizmo_presets)
~timeSeriesSimulator() noexcept
D'tor, declared and defined for noexcept.
std::vector< std::string > SimFunctionNames
virtual int appStartup()
Startup function.
std::vector< pcf::IndiProperty * > gizmos
virtual int appLogic()
Implementation of the FSM for timeSeriesSimulator.
INDI_NEWCALLBACK_DECL(timeSeriesSimulator, function)
std::unordered_map< std::string, MotionRequest * > gizmosInMotion
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
Definition: indiMacros.hpp:247
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
Definition: indiMacros.hpp:207
#define REG_INDI_NEWPROP(prop, propName, type)
Register a NEW INDI property with the class, using the standard callback name.
Definition: indiMacros.hpp:229
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:51
std::ostream & cerr()
std::stringstream msg
const pcf::IndiProperty & ipRecv
INDI_VALIDATE_CALLBACK_PROPS(function, ipRecv)
INDI_NEWCALLBACK_DEFN(acesxeCtrl, m_indiP_windspeed)(const pcf
Definition: acesxeCtrl.hpp:687
Definition: dm.hpp:24