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