API
telemeter.hpp
Go to the documentation of this file.
1 /** \file telemeter.hpp
2  * \author Jared R. Males
3  * \brief Configuration and control of a telemetry logger
4  *
5  * \ingroup app_files
6  *
7  */
8 
9 #ifndef app_telemeter_hpp
10 #define app_telemeter_hpp
11 
12 namespace MagAOX
13 {
14 namespace app
15 {
16 namespace dev
17 {
18 
19 /// A device base class which saves telemetry.
20 /**
21  * CRTP class `derivedT` has the following requirements:
22  * - Must be a MagAOXApp
23  * - Must include the following friend declaration:
24  * \code
25  * friend class dev::telemeter<DERIVEDNAME>; //replace DERIVEDNAME with derivedT class name
26  * \endcode
27  * - Must include the following typedef:
28  * \code
29  * typedef dev::telemeter<DERIVEDNAME> telemeterT; //replace DERIVEDNAME with derivedT class name
30  * \endcode
31  * - Must implement the following interface:
32  * \code
33  * int checkRecordTimes()
34  * {
35  * // Must call this variadic template function with each relevant telemetry type exactly like this
36  * return telemeterT::checkRecordTimes( telem_type1(), telem_type2(), ..., telem_typeN());
37  * }
38  * \endcode
39  * where there is one constructor-call argument for each telemetry log type recorded by this device. The resultant
40  * objects are not used, rather the types are just used for variadic template resolution.
41  *
42  * - Must provide one overload of the following function for each telemetry type:
43  * \code
44  * int recordTelem( const telem_type1 * )
45  * {
46  * //DO NOT USE telem_type1
47  * return m_tel<telem_type1>( { message entered here } );
48  * }
49  * \endcode
50  * You MUST NOT use the pointer argument, it is for type resolution only -- you
51  * should fill in the telemetry log message using internal values. Note that calls to this function should result
52  * in a telemetry log entry every time -- it is called when the minimum interval has elapsed since the last entry.
53  *
54  * - Must call this class's setupConfig(), loadConfig(), appStartup(), appLogic(), and appShutdown()
55  * in the corresponding function of `derivedT`, with error checking.
56  * For convenience the following macros are defined to provide error checking:
57  * \code
58  * TELEMETER_SETUP_CONFIG( cfig )
59  * TELEMETER_LOAD_CONFIG( cfig )
60  * TELEMETER_APP_STARTUP
61  * TELEMETER_APP_LOGIC
62  * TELEMETER_APP_SHUTDOWN
63  * \endcode
64  *
65  * \ingroup appdev
66  */
67 template <class derivedT>
68 struct telemeter
69 {
70  /// The log manager type.
72 
74 
75  double m_maxInterval{10.0}; ///< The maximum interval, in seconds, between telemetry records. Default is 10.0 seconds.
76 
78 
79  /// Make a telemetry recording
80  /** Wrapper for logManager::log, which updates telT::lastRecord.
81  *
82  * \tparam logT the log entry type
83  * \tparam retval the value returned by this method.
84  *
85  */
86  template <typename telT>
87  int telem(const typename telT::messageT &msg /**< [in] the data to log */);
88 
89  /// Make a telemetry recording, for an empty record
90  /** Wrapper for logManager::log, which updates telT::lastRecord.
91  *
92  * \tparam logT the log entry type
93  * \tparam retval the value returned by this method.
94  *
95  */
96  template <typename telT>
97  int telem();
98 
99  /// Setup an application configurator for the device section
100  /**
101  * \returns 0 on success.
102  * \returns -1 on error (nothing implemented yet)
103  */
104  int setupConfig(appConfigurator &config /**< [in] an application configuration to setup */);
105 
106  /// Load the device section from an application configurator
107  /**
108  *
109  * \returns 0 on success
110  * \returns -1 on error (nothing implemented yet)
111  */
112  int loadConfig(appConfigurator &config /**< [in] an application configuration from which to load values */);
113 
114  /// Starts the telemetry log thread.
115  /**
116  * This should be called from `derivedT::appStartup`
117  *
118  * \returns 0 on success
119  * \returns -1 on error
120  */
121  int appStartup();
122 
123  /// Perform `telemeter` application logic
124  /** This calls `derivedT::checkRecordTimes()`, and should be called from `derivedT::appLogic`, but only
125  * when the FSM is in states where telemetry logging makes sense.
126  *
127  * \returns 0 on success
128  * \returns -1 on error
129  */
130  int appLogic();
131 
132  /// Perform `telemeter` application shutdown
133  /** This currently does nothing.
134  *
135  * \returns 0 on success
136  * \returns -1 on error
137  */
138  int appShutdown();
139 
140  /// Check the time of the last record for each telemetry type and make an entry if needed
141  /** This must be called from `derivedT::checkRecordTimes()`, with one template parameter
142  * for ach telemetry log type being recorded.
143  *
144  * \returns 0 on succcess
145  * \returns -1 on error
146  */
147  template <class telT, class... telTs>
148  int checkRecordTimes(const telT &tel, ///< [in] [unused] object of the telemetry type to record
149  telTs... tels ///< [in] [unused] objects of the additional telemetry types to record
150  );
151 
152  /// Worker function to actually perform the record time checking logic
153  /** Recursively calls itself until the variadic template list is exhausted.
154  *
155  * \returns 0 on succcess
156  * \returns -1 on error
157  */
158  template <class telT, class... telTs>
159  int checkRecordTimes(timespec &ts, ///<[in] [unused] the timestamp that records are compared to
160  const telT &tel, ///< [in] [unused] objects of the telemetry type to record
161  telTs... tels ///< [in] [unused] objects of the additional telemetry types to record
162  );
163 
164  /// Empty function called at the end of the template list
165  /**
166  * \returns 0 on succcess
167  * \returns -1 on error
168  */
169  int checkRecordTimes(timespec &ts /**<[in] [unused] the timestamp that records are compared to */);
170 
171 private:
172  /// Access the derived class.
173  derivedT &derived()
174  {
175  return *static_cast<derivedT *>(this);
176  }
177 };
178 
179 template <class derivedT>
181 {
182 }
183 
184 template <class derivedT>
185 template <typename telT>
186 int telemeter<derivedT>::telem(const typename telT::messageT &msg)
187 {
188 
189  m_tel.template log<telT>(msg, logPrio::LOG_TELEM);
190 
191  // Set timestamp
192  clock_gettime(CLOCK_REALTIME, &telT::lastRecord);
193 
194  return 0;
195 }
196 
197 template <class derivedT>
198 template <typename telT>
200 {
201 
202  m_tel.template log<telT>(logPrio::LOG_TELEM);
203 
204  // Set timestamp
205  clock_gettime(CLOCK_REALTIME, &telT::lastRecord);
206 
207  return 0;
208 }
209 
210 template <class derivedT>
211 int telemeter<derivedT>::setupConfig(mx::app::appConfigurator &config)
212 {
213  m_tel.m_configSection = "telemeter";
214 
215  m_tel.setupConfig(config);
216 
217  config.add("telemeter.maxInterval", "", "telemeter.maxInterval", argType::Required, "telemeter", "maxInterval", false, "double", "The maximum interval, in seconds, between telemetry records. Default is 10.0 seconds.");
218 
219  return 0;
220 }
221 
222 template <class derivedT>
223 int telemeter<derivedT>::loadConfig(mx::app::appConfigurator &config)
224 {
225  m_tel.m_logLevel = logPrio::LOG_TELEM;
226 
227  m_tel.logPath(std::string(derived().MagAOXPath) + "/" + MAGAOX_telRelPath);
228 
229  m_tel.logExt("bintel");
230 
231  m_tel.logName(derived().m_configName);
232 
233  m_tel.loadConfig(config);
234 
235  config(m_maxInterval, "telemeter.maxInterval");
236 
237  return 0;
238 }
239 
240 template <class derivedT>
242 {
243  //----------------------------------------//
244  // Begin the telemetry system
245  //----------------------------------------//
246 
247  m_tel.logThreadStart();
248 
249  // Give up to 2 secs to make sure log thread has time to get started and try to open a file.
250  int w = 0;
251  while (m_tel.logThreadRunning() == false && w < 20)
252  {
253  // Sleep for 100 msec
254  std::this_thread::sleep_for(std::chrono::duration<unsigned long, std::nano>(100000000));
255  ++w;
256  }
257 
258  if (m_tel.logThreadRunning() == false)
259  {
260  derivedT::template log<software_critical>({__FILE__, __LINE__, "telemetry thread not running. exiting."});
261  return -1;
262  }
263 
264  return 0;
265 }
266 
267 template <class derivedT>
269 {
270  return derived().checkRecordTimes();
271 }
272 
273 template <class derivedT>
275 {
276  return 0;
277 }
278 
279 template <class derivedT>
280 template <class telT, class... telTs>
281 int telemeter<derivedT>::checkRecordTimes(const telT &tel, telTs... tels)
282 {
283  timespec ts;
284 
285  clock_gettime(CLOCK_REALTIME, &ts);
286  return checkRecordTimes(ts, tel, tels...);
287 }
288 
289 template <class derivedT>
290 template <class telT, class... telTs>
291 int telemeter<derivedT>::checkRecordTimes(timespec &ts, const telT &tel, telTs... tels)
292 {
293  // Check if it's been more than maxInterval seconds since the last record. This is corrected for the pause of the main loop.
294  if (((double)ts.tv_sec - ((double)ts.tv_nsec) / 1e9) - ((double)telT::lastRecord.tv_sec - ((double)telT::lastRecord.tv_nsec) / 1e9) > m_maxInterval - ((double)derived().m_loopPause) / 1e9)
295  {
296  derived().recordTelem(&tel);
297  }
298 
299  return checkRecordTimes(ts, tels...);
300 }
301 
302 template <class derivedT>
304 {
305  static_cast<void>(ts); // be unused
306 
307  return 0;
308 }
309 
310 /// Call telemeter::setupConfig with error checking
311 /**
312  * \param cfig the application configurator
313  */
314 #define TELEMETER_SETUP_CONFIG( cfig ) \
315  if (telemeterT::setupConfig( cfig) < 0) \
316  { \
317  log<software_error>({__FILE__, __LINE__, "Error from telemeterT::setupConfig"}); \
318  m_shutdown = true; \
319  }
320 
321 /// Call telemeter::loadConfig with error checking
322 /** This must be inside a function that returns int, e.g. the standard loadConfigImpl.
323  * \param cfig the application configurator
324  */
325 #define TELEMETER_LOAD_CONFIG( cfig ) \
326  if (telemeterT::loadConfig(cfig) < 0) \
327  { \
328  return log<software_error, -1>({__FILE__, __LINE__, "Error from telemeterT::loadConfig"}); \
329  }
330 
331 /// Call telemeter::appStartup with error checking
332 #define TELEMETER_APP_STARTUP \
333  if (telemeterT::appStartup() < 0) \
334  { \
335  return log<software_error, -1>({__FILE__, __LINE__}); \
336  }
337 
338 /// Call telemeter::appLogic with error checking
339 #define TELEMETER_APP_LOGIC \
340  if (telemeterT::appLogic() < 0) \
341  { \
342  return log<software_error, -1>({__FILE__, __LINE__}); \
343  }
344 
345 /// Call telemeter::appShutdown with error checking
346 #define TELEMETER_APP_SHUTDOWN \
347  if (telemeterT::appShutdown() < 0) \
348  { \
349  log<software_error>({__FILE__, __LINE__, "error from telemeterT::appShutdown"}); \
350  }
351 
352 } // namespace dev
353 } // namespace tty
354 } // namespace MagAOX
355 
356 #endif // tty_telemeter_hpp
#define MAGAOX_telRelPath
The relative path to the telemetry directory.
Definition: paths.hpp:57
std::stringstream msg
Definition: dm.hpp:24
constexpr static logPrioT LOG_TELEM
A telemetry recording.
Definition: logPriority.hpp:58
A device base class which saves telemetry.
Definition: telemeter.hpp:69
double m_maxInterval
The maximum interval, in seconds, between telemetry records. Default is 10.0 seconds.
Definition: telemeter.hpp:75
int telem()
Make a telemetry recording, for an empty record.
Definition: telemeter.hpp:199
int appShutdown()
Perform telemeter application shutdown.
Definition: telemeter.hpp:274
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
Definition: telemeter.hpp:223
int telem(const typename telT::messageT &msg)
Make a telemetry recording.
Definition: telemeter.hpp:186
int appLogic()
Perform telemeter application logic.
Definition: telemeter.hpp:268
logger::logManager< derivedT, logFileRaw > logManagerT
The log manager type.
Definition: telemeter.hpp:71
int checkRecordTimes(timespec &ts)
Empty function called at the end of the template list.
Definition: telemeter.hpp:303
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
Definition: telemeter.hpp:211
int appStartup()
Starts the telemetry log thread.
Definition: telemeter.hpp:241
int checkRecordTimes(timespec &ts, const telT &tel, telTs... tels)
Worker function to actually perform the record time checking logic.
Definition: telemeter.hpp:291
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
Definition: telemeter.hpp:281
derivedT & derived()
Access the derived class.
Definition: telemeter.hpp:173