API
 
Loading...
Searching...
No Matches
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
30namespace MagAOX
31{
32namespace app
33{
34
35/// The MagAO-X xxxxxxxx
36/**
37 * \ingroup timeSeriesSimulator
38 */
39class timeSeriesSimulator : public MagAOXApp<true>
40{
41
42protected:
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,
67 };
69 std::vector<std::string> SimFunctionNames = {"sin", "cos", "square", "constant"};
70 double amplitude = 1.0;
71 double period = 5.0;
74 {
75 pcf::IndiProperty *property;
76 double targetPos;
77 double startPos;
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;
85
86public:
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
142timeSeriesSimulator::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
151
152int timeSeriesSimulator::loadConfigImpl(mx::app::appConfigurator &_config)
153{
154 static_cast<void>(_config);
155 return 0;
156}
157
162
164{
166 "function",
167 pcf::IndiProperty::Switch,
168 pcf::IndiProperty::ReadWrite,
169 pcf::IndiProperty::Idle,
170 pcf::IndiProperty::OneOfMany,
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
233void timeSeriesSimulator::requestGizmoTarget(pcf::IndiProperty *gizmoPtr, double targetPos)
234{
235 double currentPos = (*gizmoPtr)["current"].get<double>();
236 (*gizmoPtr)["target"] = targetPos;
237
239 std::string ipName = gizmoPtr->getName();
240 if (gizmosInMotion.count(ipName) == 0)
241 {
242 theReq = new MotionRequest;
244 theReq->startPos = currentPos;
245 theReq->targetPos = targetPos;
246 theReq->requestTime = mx::sys::get_curr_time();
248 }
249 else
250 {
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
289INDI_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
317INDI_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;
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
422double 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;
434 double elapsedSeconds = mx::sys::get_curr_time() - theMotionRequest->requestTime;
435 double currentPos;
437 {
439 0,
440 theMotionRequest->startPos,
442 theMotionRequest->targetPos,
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
468
469} //namespace app
470} //namespace MagAOX
471
472#endif //timeSeriesSimulator_hpp
The base-class for MagAO-X applications.
Definition MagAOXApp.hpp:73
std::string m_configName
The name of the configuration file (minus .conf).
Definition MagAOXApp.hpp:83
stateCodes::stateCodeT state()
Get the current state code.
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.
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
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 INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
#define REG_INDI_NEWPROP(prop, propName, type)
Register a NEW INDI property with the class, using the standard callback name.
@ READY
The device is ready for operation, but is not operating.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
std::stringstream msg
const pcf::IndiProperty & ipRecv
Definition dm.hpp:24