Line data Source code
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 : #ifdef XWCTEST_NAMESPACE
20 : namespace 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 : */
73 : template <class derivedT>
74 : struct telemeter
75 : {
76 : typedef XWC_DEFAULT_VERBOSITY verboseT;
77 :
78 : /// The log manager type.
79 : typedef logger::logManager<derivedT, logFileRaw<verboseT>> logManagerT;
80 :
81 : logManagerT m_tel;
82 :
83 : double m_maxInterval{10.0}; ///< The maximum interval, in seconds, between telemetry records. Default is 10.0 seconds.
84 :
85 : telemeter();
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 : */
129 : int appStartup();
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 : */
146 : int appShutdown();
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 :
179 : private:
180 : /// Access the derived class.
181 20 : derivedT &derived()
182 : {
183 20 : return *static_cast<derivedT *>(this);
184 : }
185 : };
186 :
187 : template <class derivedT>
188 271 : telemeter<derivedT>::telemeter()
189 : {
190 271 : }
191 :
192 : template <class derivedT>
193 : template <typename telT>
194 14 : int telemeter<derivedT>::telem(const typename telT::messageT &msg)
195 : {
196 :
197 14 : m_tel.template log<telT>(msg, logPrio::LOG_TELEM);
198 :
199 : // Set timestamp
200 14 : clock_gettime(CLOCK_REALTIME, &telT::lastRecord);
201 :
202 14 : return 0;
203 : }
204 :
205 : /* I think this shouldn't be defined. Delete after 11/27/2025
206 : template <class derivedT>
207 : template <typename telT>
208 : int 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 :
219 : template <class derivedT>
220 5 : int telemeter<derivedT>::setupConfig(mx::app::appConfigurator &config)
221 : {
222 5 : m_tel.m_configSection = "telemeter";
223 :
224 5 : m_tel.setupConfig(config);
225 :
226 65 : 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 5 : return 0;
229 : }
230 :
231 : template <class derivedT>
232 5 : int telemeter<derivedT>::loadConfig(mx::app::appConfigurator &config)
233 : {
234 5 : m_tel.m_logLevel = logPrio::LOG_TELEM;
235 :
236 : // Setup default log path
237 5 : std::string tmpstr = mx::sys::getEnv( MAGAOX_env_telem );
238 5 : if( tmpstr == "" )
239 : {
240 5 : tmpstr = MAGAOX_telRelPath;
241 : }
242 5 : m_tel.logPath(std::string(derived().basePath()) + "/" + tmpstr);
243 :
244 10 : m_tel.logExt("bintel");
245 :
246 5 : m_tel.logName(derived().m_configName);
247 :
248 5 : m_tel.loadConfig(config);
249 :
250 5 : config(m_maxInterval, "telemeter.maxInterval");
251 :
252 5 : return 0;
253 5 : }
254 :
255 : template <class derivedT>
256 3 : int telemeter<derivedT>::appStartup()
257 : {
258 : //----------------------------------------//
259 : // Begin the telemetry system
260 : //----------------------------------------//
261 :
262 3 : 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 3 : int w = 0;
272 25 : while (m_tel.logThreadRunning() == false && w < 20)
273 : {
274 : // Sleep for 100 msec
275 22 : std::this_thread::sleep_for(std::chrono::duration<unsigned long, std::nano>(100000000));
276 22 : ++w;
277 : }
278 :
279 3 : if (m_tel.logThreadRunning() == false)
280 : {
281 1 : derivedT::template log<software_critical>({__FILE__, __LINE__, "telemetry thread not running. exiting."});
282 1 : return -1;
283 : }
284 :
285 2 : return 0;
286 : }
287 :
288 : template <class derivedT>
289 3 : int telemeter<derivedT>::appLogic()
290 : {
291 3 : if( m_tel.logThreadRunning() == false )
292 : {
293 1 : derived().state( stateCodes::FAILURE );
294 :
295 : // Directly ouput the error b/c all other outputs are via the log thread
296 1 : std::cerr << "\nCRITICAL: telemetry thread not running. Exiting.\n\n";
297 :
298 1 : derived().m_shutdown = 1;
299 :
300 1 : return -1;
301 : }
302 :
303 2 : return derived().checkRecordTimes();
304 : }
305 :
306 : template <class derivedT>
307 2 : int telemeter<derivedT>::appShutdown()
308 : {
309 2 : return 0;
310 : }
311 :
312 : template <class derivedT>
313 : template <class telT, class... telTs>
314 2 : int telemeter<derivedT>::checkRecordTimes(const telT &tel, telTs... tels)
315 : {
316 : timespec ts;
317 :
318 2 : clock_gettime(CLOCK_REALTIME, &ts);
319 4 : return checkRecordTimes(ts, tel, tels...);
320 : }
321 :
322 : template <class derivedT>
323 : template <class telT, class... telTs>
324 4 : int 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 4 : 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 2 : derived().recordTelem(&tel);
330 : }
331 :
332 4 : return checkRecordTimes(ts, tels...);
333 : }
334 :
335 : template <class derivedT>
336 2 : int telemeter<derivedT>::checkRecordTimes(timespec &ts)
337 : {
338 : static_cast<void>(ts); // be unused
339 :
340 2 : 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
|