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/// 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 */
67template <class derivedT>
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 */
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 */
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
171private:
172 /// Access the derived class.
173 derivedT &derived()
174 {
175 return *static_cast<derivedT *>(this);
176 }
177};
178
179template <class derivedT>
183
184template <class derivedT>
185template <typename telT>
186int 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
197template <class derivedT>
198template <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
210template <class derivedT>
211int 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
222template <class derivedT>
223int 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
240template <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
267template <class derivedT>
269{
270 return derived().checkRecordTimes();
271}
272
273template <class derivedT>
275{
276 return 0;
277}
278
279template <class derivedT>
280template <class telT, class... telTs>
281int 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
289template <class derivedT>
290template <class telT, class... telTs>
291int 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
302template <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
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.
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.
logger::logManager< derivedT, logFileRaw > logManagerT
The log manager type.
Definition telemeter.hpp:71
derivedT & derived()
Access the derived class.
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.