API
 
Loading...
Searching...
No Matches
flowRPM.hpp
Go to the documentation of this file.
1/** \file flowRPM.hpp
2 * \brief The MagAO-X flowRPM application header.
3 *
4 * \ingroup flowRPM_files
5 */
6
7#ifndef flowRPM_hpp
8#define flowRPM_hpp
9
10#include <cerrno>
11#include <cmath>
12#include <fstream>
13#include <sstream>
14
15#include "../../libMagAOX/libMagAOX.hpp" // Note this is included on command line to trigger pch
16#include "../../magaox_git_version.h"
17
18/** \defgroup flowRPM
19 * \brief The MagAO-X application to convert a file-based fan reading into flow telemetry.
20 *
21 * <a href="../handbook/operating/software/apps/flowRPM.html">Application Documentation</a>
22 *
23 * \ingroup apps
24 *
25 */
26
27/** \defgroup flowRPM_files
28 * \ingroup flowRPM
29 */
30
31namespace MagAOX
32{
33namespace app
34{
35
36/// The MagAO-X flow-from-RPM monitor.
37/**
38 * \ingroup flowRPM
39 */
40class flowRPM : public MagAOXApp<true>, public dev::telemeter<flowRPM>
41{
42 // Give the test harness access.
43 friend class flowRPM_test;
44
45 friend class dev::telemeter<flowRPM>;
46
47 public:
48 /// Status returned by the file parser.
63
64 /// Parsed result of the current file contents.
66 {
67 parseStatus m_status{ parseStatus::fileReadError }; ///< Outcome of the parse attempt.
68 double m_flowRate{ -999.0 }; ///< Displayed flow rate in LPM or the bad-value sentinel.
69 double m_age{ -999.0 }; ///< Displayed age in seconds or the bad-value sentinel.
70 timespec m_sourceTs{ 0, 0 }; ///< Parsed source timestamp when available.
71 };
72
73 /// The telemeter base type.
75
76 protected:
77 /** \name Configurable Parameters - Data
78 *
79 * @{
80 */
81
82 /// Path to the file written by the systemd producer.
83 std::string m_inputPath{ "/tmp/fac_flow.txt" };
84
85 /// Maximum allowed source age in seconds before the reading is treated as stale.
86 double m_maxAge{ 60.0 };
87
88 /// Expected fan descriptor in the pipe-delimited record.
89 std::string m_fanDescriptor{ "CHA_FAN1" };
90
91 /// Bad-value sentinel published on parse or availability failures.
92 double m_badValue{ -999.0 };
93
94 /// Minimum interval between repeated logs for the same persistent error.
95 double m_errorLogInterval{ 60.0 };
96
97 ///@}
98
99 /** \name Runtime State - Data
100 *
101 * @{
102 */
103
104 /// Current published flow rate in LPM or the bad-value sentinel.
105 double m_flowRate{ -999.0 };
106
107 /// Current published age in seconds or the bad-value sentinel.
108 double m_age{ -999.0 };
109
110 /// Timestamp parsed from the input file for the currently displayed value.
111 timespec m_sourceTs{ 0, 0 };
112
113 /// Whether the current displayed value is valid.
114 bool m_haveValidReading{ false };
115
116 /// Last flow value written to telemetry.
117 double m_lastTelemFlowRate{ std::numeric_limits<double>::quiet_NaN() };
118
119 /// Last validity state written to telemetry.
120 bool m_lastTelemValid{ false };
121
122 /// Time of the most recent error log emission.
123 timespec m_lastErrorLogTs{ 0, 0 };
124
125 /// Key for the most recently logged error class.
126 std::string m_lastErrorKey;
127
128 /// Read-only status property exposing flow rate and age.
129 pcf::IndiProperty m_indiP_status;
130
131 ///@}
132
133 public:
134 /// Default c'tor.
135 flowRPM();
136
137 /// D'tor, declared and defined for noexcept.
139
140 /// Set up the application configuration.
141 virtual void setupConfig();
142
143 /// Implementation of loadConfig logic, separated for testing.
144 virtual int
145 loadConfigImpl( mx::app::appConfigurator &_config /**< [in] application configuration from which to load */ );
146
147 /// Load the application configuration.
148 virtual void loadConfig();
149
150 /// Perform application startup.
151 virtual int appStartup();
152
153 /// Implementation of the FSM for flowRPM.
154 virtual int appLogic();
155
156 /// Shut the application down.
157 virtual int appShutdown();
158
159 /** \name Parser Helpers
160 *
161 * @{
162 */
163
164 /// Parse a source timestamp line into a timespec.
165 int parseTimestamp( timespec &ts, /**< [out] parsed timestamp */
166 const std::string &line /**< [in] source timestamp line */
167 ) const;
168
169 /// Parse the sensor record line into a flow rate in LPM.
170 parseStatus parseRecordLine( double &flowRate, /**< [out] parsed flow rate in LPM */
171 const std::string &line /**< [in] sensor record line */
172 ) const;
173
174 /// Parse the two-line file contents using a supplied current time.
175 int parseFileContents( parseResult &result, /**< [out] parse result */
176 const std::string &contents, /**< [in] raw file contents */
177 const timespec &now /**< [in] current time for age calculation */
178 ) const;
179
180 /// Read the configured file and parse its current contents.
181 virtual int readAndParse( parseResult &result, /**< [out] parse result */
182 const timespec &now /**< [in] current time for age calculation */
183 ) const;
184
185 /// Get the string key used for a parse status in log rate limiting.
186 static std::string statusKey( parseStatus status /**< [in] parse status to stringify */ );
187
188 /// Get the configured input path.
189 const std::string &inputPath() const;
190
191 /// Get the configured maximum reading age in seconds.
192 double maxAge() const;
193
194 /// Get the configured fan descriptor.
195 const std::string &fanDescriptor() const;
196
197 /// Get the configured bad-value sentinel.
198 double badValue() const;
199
200 /// Get the configured repeated-error log interval.
201 double errorLogInterval() const;
202
203 /// Get the currently published flow rate.
204 double flowRate() const;
205
206 /// Get the currently published age.
207 double age() const;
208
209 /// Get whether the current published value is valid.
210 bool haveValidReading() const;
211
212 ///@}
213
214 /** \name Telemeter
215 *
216 * @{
217 */
218
219 /// Check whether telemetry records need to be forced.
220 int checkRecordTimes();
221
222 /// Record telemetry when requested by the telemeter helper.
223 int recordTelem( const logger::telem_flowrpm * /**< [in] telemetry tag used for overload resolution */ );
224
225 /// Record the currently displayed flow state to telemetry.
226 virtual int recordFlow( bool force = false /**< [in] force a telemetry record even if unchanged */ );
227
228 /// Reconcile a newly parsed result against the currently displayed state.
229 parseResult reconcileResult( const parseResult &result, /**< [in] newly parsed file result */
230 const timespec &now /**< [in] current time */
231 ) const;
232
233 /// Publish a parse result to INDI and runtime state.
234 virtual int publishResult( const parseResult &result /**< [in] parse result to publish */ );
235
236 /// Determine whether a repeated error should be logged now.
237 bool shouldLogError( const std::string &key, /**< [in] error key under consideration */
238 const timespec &now /**< [in] current time */
239 );
240
241 ///@}
242};
243
244namespace flowRPMDetail
245{
246
247/// Trim leading and trailing ASCII whitespace from a token.
248inline std::string trimToken( const std::string &token )
249{
250 const std::string::size_type first = token.find_first_not_of( " \t\r" );
251
252 if( first == std::string::npos )
253 {
254 return "";
255 }
256
257 const std::string::size_type last = token.find_last_not_of( " \t\r" );
258 return token.substr( first, last - first + 1 );
259}
260
261/// Convert a timespec to fractional seconds.
262inline double timespecToDouble( const timespec &ts )
263{
264 return static_cast<double>( ts.tv_sec ) + 1e-9 * static_cast<double>( ts.tv_nsec );
265}
266
267/// Measure elapsed seconds between two timestamps.
268inline double elapsedSeconds( const timespec &start, const timespec &end )
269{
271}
272
273/// Split a pipe-delimited sensor line into trimmed fields.
274inline std::vector<std::string> splitPipeDelimited( const std::string &line )
275{
276 std::vector<std::string> fields;
277 std::stringstream ss( line );
278 std::string field;
279
280 while( std::getline( ss, field, '|' ) )
281 {
282 fields.push_back( trimToken( field ) );
283 }
284
285 return fields;
286}
287
288/// Determine whether the IPMI sensor status token represents a valid flow reading.
289inline bool isValidStatusField( const std::string &statusField )
290{
291 return statusField == "'OK'" || statusField == "'At or Below (<=) Lower Non-Recoverable Threshold'";
292}
293
294/// Split file contents into non-empty logical lines.
295inline std::vector<std::string> splitLogicalLines( const std::string &contents )
296{
297 std::vector<std::string> lines;
298 std::stringstream ss( contents );
299 std::string line;
300
301 while( std::getline( ss, line ) )
302 {
303 if( !line.empty() && line.back() == '\r' )
304 {
305 line.pop_back();
306 }
307
308 if( trimToken( line ).empty() )
309 {
310 continue;
311 }
312
313 lines.push_back( line );
314 }
315
316 return lines;
317}
318
319} // namespace flowRPMDetail
320
325
326inline flowRPM::~flowRPM() noexcept
327{
328}
329
331{
332 config.add( "input.path",
333 "",
334 "input.path",
335 argType::Required,
336 "input",
337 "path",
338 false,
339 "string",
340 "Path to the file containing the two-line flow RPM record." );
341 config.add( "input.maxAge",
342 "",
343 "input.maxAge",
344 argType::Required,
345 "input",
346 "maxAge",
347 false,
348 "double",
349 "Maximum source age in seconds before the reading is treated as stale. Default is 60." );
350 config.add( "input.fanDescriptor",
351 "",
352 "input.fanDescriptor",
353 argType::Required,
354 "input",
355 "fanDescriptor",
356 false,
357 "string",
358 "Expected descriptor token in the source file. Default is CHA_FAN1." );
359 config.add( "input.badValue",
360 "",
361 "input.badValue",
362 argType::Required,
363 "input",
364 "badValue",
365 false,
366 "double",
367 "Bad-value sentinel published when the file can not be read or parsed. Default is -999." );
368 config.add( "input.errorLogInterval",
369 "",
370 "input.errorLogInterval",
371 argType::Required,
372 "input",
373 "errorLogInterval",
374 false,
375 "double",
376 "Minimum interval in seconds between repeated logs of the same persistent error. Default is 60." );
377
378 TELEMETER_SETUP_CONFIG( config );
379}
380
381inline int flowRPM::loadConfigImpl( mx::app::appConfigurator &_config )
382{
383 _config( m_inputPath, "input.path" );
384 _config( m_maxAge, "input.maxAge" );
385 _config( m_fanDescriptor, "input.fanDescriptor" );
386 _config( m_badValue, "input.badValue" );
387 _config( m_errorLogInterval, "input.errorLogInterval" );
388
390
391 return 0;
392}
393
395{
396 if( loadConfigImpl( config ) < 0 )
397 {
398 m_shutdown = 1;
399 }
400}
401
403{
404 createROIndiNumber( m_indiP_status, "status", "Flow Status" );
405 indi::addNumberElement<double>( m_indiP_status, "flow_rate", -1e9, 1e9, 0.0, "%0.3f", "Flow Rate [LPM]" );
406 indi::addNumberElement<double>( m_indiP_status, "age", -1e9, 1e9, 0.0, "%0.3f", "Age [s]" );
407 m_indiP_status["flow_rate"] = m_badValue;
408 m_indiP_status["age"] = m_badValue;
410
412
414
415 return 0;
416}
417
419{
420 timespec now;
423
425
426 if( readAndParse( result, now ) < 0 )
427 {
428 return log<software_error, -1>( { __FILE__, __LINE__, "unexpected failure while reading flow file" } );
429 }
430
432
433 const bool wasValid = m_haveValidReading;
434 const bool isValid = ( displayResult.m_status == parseStatus::success );
435
436 if( publishResult( displayResult ) < 0 )
437 {
438 return log<software_error, -1>( { __FILE__, __LINE__, "failed to publish flow result" } );
439 }
440
441 if( isValid )
442 {
443 if( !wasValid )
444 {
445 log<text_log>( "Recovered valid flow reading from " + m_inputPath + ".", logPrio::LOG_NOTICE );
446 }
447
448 m_lastErrorKey.clear();
449 }
450 else
451 {
452 const std::string key = statusKey( result.m_status );
453
454 if( shouldLogError( key, now ) )
455 {
456 log<software_error>( { __FILE__, __LINE__, "flowRPM " + key + " for " + m_inputPath } );
457 }
458 }
459
460 if( recordFlow() < 0 )
461 {
462 return log<software_error, -1>( { __FILE__, __LINE__, "error recording flow telemetry" } );
463 }
464
466
467 return 0;
468}
469
471{
473
474 return 0;
475}
476
477inline int flowRPM::parseTimestamp( timespec &ts, const std::string &line ) const
478{
479 std::stringstream ss( line );
480 long long sec = 0;
481 long long nsec = 0;
482
483 if( !( ss >> sec >> nsec ) )
484 {
485 return -1;
486 }
487
488 char trailing = '\0';
489 if( ss >> trailing )
490 {
491 return -1;
492 }
493
494 if( nsec < 0 || nsec >= 1000000000LL )
495 {
496 return -1;
497 }
498
499 ts.tv_sec = static_cast<time_t>( sec );
500 ts.tv_nsec = static_cast<long>( nsec );
501
502 return 0;
503}
504
505inline flowRPM::parseStatus flowRPM::parseRecordLine( double &flowRate, const std::string &line ) const
506{
507 const std::vector<std::string> fields = flowRPMDetail::splitPipeDelimited( line );
508
509 if( fields.size() != 6 )
510 {
512 }
513
514 if( fields[1] != m_fanDescriptor )
515 {
517 }
518
519 if( fields[4] != "RPM" )
520 {
522 }
523
525 {
527 }
528
529 char *end = nullptr;
530 errno = 0;
531 double value = std::strtod( fields[3].c_str(), &end );
532
533 if( errno != 0 || end == fields[3].c_str() || *end != '\0' )
534 {
536 }
537
538 flowRate = value / 1000.0;
539
541}
542
543inline int flowRPM::parseFileContents( parseResult &result, const std::string &contents, const timespec &now ) const
544{
546 result.m_flowRate = m_badValue;
547 result.m_age = m_badValue;
548 result.m_sourceTs = { 0, 0 };
549
550 const std::vector<std::string> lines = flowRPMDetail::splitLogicalLines( contents );
551
552 if( lines.empty() )
553 {
555 return 0;
556 }
557
558 if( parseTimestamp( result.m_sourceTs, lines[0] ) < 0 )
559 {
561 return 0;
562 }
563
564 if( lines.size() < 2 )
565 {
567 return 0;
568 }
569
570 if( lines.size() != 2 )
571 {
573 result.m_age = std::max( 0.0, flowRPMDetail::elapsedSeconds( result.m_sourceTs, now ) );
574 return 0;
575 }
576
577 double parsedFlowRate = m_badValue;
578
579 result.m_status = parseRecordLine( parsedFlowRate, lines[1] );
580 result.m_age = std::max( 0.0, flowRPMDetail::elapsedSeconds( result.m_sourceTs, now ) );
581
582 if( result.m_status != parseStatus::success )
583 {
584 return 0;
585 }
586
587 if( result.m_age > m_maxAge )
588 {
590 return 0;
591 }
592
593 result.m_flowRate = parsedFlowRate;
594 return 0;
595}
596
597inline int flowRPM::readAndParse( parseResult &result, const timespec &now ) const
598{
599 std::ifstream ifs( m_inputPath.c_str() );
600
602 result.m_flowRate = m_badValue;
603 result.m_age = m_badValue;
604 result.m_sourceTs = { 0, 0 };
605
606 if( !ifs )
607 {
608 return 0;
609 }
610
611 std::stringstream buffer;
612 buffer << ifs.rdbuf();
613
614 return parseFileContents( result, buffer.str(), now );
615}
616
617inline std::string flowRPM::statusKey( parseStatus status )
618{
619 switch( status )
620 {
622 return "success";
624 return "open_failed";
626 return "missing_timestamp";
628 return "timestamp_parse_failed";
630 return "missing_record";
632 return "record_parse_failed";
634 return "wrong_descriptor";
636 return "wrong_units";
638 return "bad_status";
640 return "bad_value";
642 return "stale";
643 default:
644 return "unknown";
645 }
646}
647
648inline const std::string &flowRPM::inputPath() const
649{
650 return m_inputPath;
651}
652
653inline double flowRPM::maxAge() const
654{
655 return m_maxAge;
656}
657
658inline const std::string &flowRPM::fanDescriptor() const
659{
660 return m_fanDescriptor;
661}
662
663inline double flowRPM::badValue() const
664{
665 return m_badValue;
666}
667
668inline double flowRPM::errorLogInterval() const
669{
670 return m_errorLogInterval;
671}
672
673inline double flowRPM::flowRate() const
674{
675 return m_flowRate;
676}
677
678inline double flowRPM::age() const
679{
680 return m_age;
681}
682
683inline bool flowRPM::haveValidReading() const
684{
685 return m_haveValidReading;
686}
687
692
694{
695 return recordFlow( true );
696}
697
710
711inline flowRPM::parseResult flowRPM::reconcileResult( const parseResult &result, const timespec &now ) const
712{
714
715 if( result.m_status == parseStatus::success )
716 {
717 return displayResult;
718 }
719
720 if( !m_haveValidReading )
721 {
722 return displayResult;
723 }
724
725 const double lastGoodAge = std::max( 0.0, flowRPMDetail::elapsedSeconds( m_sourceTs, now ) );
726
727 if( lastGoodAge > m_maxAge )
728 {
730 displayResult.m_flowRate = m_badValue;
732 displayResult.m_sourceTs = m_sourceTs;
733 return displayResult;
734 }
735
737 displayResult.m_flowRate = m_flowRate;
739 displayResult.m_sourceTs = m_sourceTs;
740
741 return displayResult;
742}
743
744inline int flowRPM::publishResult( const parseResult &result )
745{
746 m_flowRate = result.m_flowRate;
747 m_age = result.m_age;
748 m_sourceTs = result.m_sourceTs;
750
752 std::vector<std::string>( { "flow_rate", "age" } ),
753 std::vector<double>( { m_flowRate, m_age } ),
755
756 return 0;
757}
758
759inline bool flowRPM::shouldLogError( const std::string &key, const timespec &now )
760{
761 if( key != m_lastErrorKey )
762 {
763 m_lastErrorKey = key;
765 return true;
766 }
767
769 {
771 return true;
772 }
773
774 return false;
775}
776
777} // namespace app
778} // namespace MagAOX
779
780#endif // flowRPM_hpp
The base-class for XWCTk applications.
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI property element value if it has changed.
stateCodes::stateCodeT state()
Get the current state code.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
int createROIndiNumber(pcf::IndiProperty &prop, const std::string &propName, const std::string &propLabel="", const std::string &propGroup="")
Create a ReadOnly INDI Number property.
int registerIndiPropertyReadOnly(pcf::IndiProperty &prop)
Register an INDI property which is read only.
The MagAO-X flow-from-RPM monitor.
Definition flowRPM.hpp:41
timespec m_lastErrorLogTs
Time of the most recent error log emission.
Definition flowRPM.hpp:123
parseStatus m_status
Outcome of the parse attempt.
Definition flowRPM.hpp:67
double m_age
Current published age in seconds or the bad-value sentinel.
Definition flowRPM.hpp:108
parseStatus
Status returned by the file parser.
Definition flowRPM.hpp:50
dev::telemeter< flowRPM > telemeterT
The telemeter base type.
Definition flowRPM.hpp:74
bool m_lastTelemValid
Last validity state written to telemetry.
Definition flowRPM.hpp:120
double m_flowRate
Displayed flow rate in LPM or the bad-value sentinel.
Definition flowRPM.hpp:68
virtual int readAndParse(parseResult &result, const timespec &now) const
Read the configured file and parse its current contents.
Definition flowRPM.hpp:597
const std::string & fanDescriptor() const
Get the configured fan descriptor.
Definition flowRPM.hpp:658
int recordTelem(const logger::telem_flowrpm *)
Record telemetry when requested by the telemeter helper.
Definition flowRPM.hpp:693
~flowRPM() noexcept
D'tor, declared and defined for noexcept.
Definition flowRPM.hpp:326
parseResult reconcileResult(const parseResult &result, const timespec &now) const
Reconcile a newly parsed result against the currently displayed state.
Definition flowRPM.hpp:711
double m_flowRate
Current published flow rate in LPM or the bad-value sentinel.
Definition flowRPM.hpp:105
double m_errorLogInterval
Minimum interval between repeated logs for the same persistent error.
Definition flowRPM.hpp:95
virtual void setupConfig()
Set up the application configuration.
Definition flowRPM.hpp:330
double m_age
Displayed age in seconds or the bad-value sentinel.
Definition flowRPM.hpp:69
std::string m_lastErrorKey
Key for the most recently logged error class.
Definition flowRPM.hpp:126
double badValue() const
Get the configured bad-value sentinel.
Definition flowRPM.hpp:663
int parseTimestamp(timespec &ts, const std::string &line) const
Parse a source timestamp line into a timespec.
Definition flowRPM.hpp:477
virtual void loadConfig()
Load the application configuration.
Definition flowRPM.hpp:394
const std::string & inputPath() const
Get the configured input path.
Definition flowRPM.hpp:648
parseStatus parseRecordLine(double &flowRate, const std::string &line) const
Parse the sensor record line into a flow rate in LPM.
Definition flowRPM.hpp:505
friend class flowRPM_test
Definition flowRPM.hpp:43
bool m_haveValidReading
Whether the current displayed value is valid.
Definition flowRPM.hpp:114
static std::string statusKey(parseStatus status)
Get the string key used for a parse status in log rate limiting.
Definition flowRPM.hpp:617
timespec m_sourceTs
Timestamp parsed from the input file for the currently displayed value.
Definition flowRPM.hpp:111
double flowRate() const
Get the currently published flow rate.
Definition flowRPM.hpp:673
std::string m_inputPath
Path to the file written by the systemd producer.
Definition flowRPM.hpp:83
double age() const
Get the currently published age.
Definition flowRPM.hpp:678
virtual int appLogic()
Implementation of the FSM for flowRPM.
Definition flowRPM.hpp:418
pcf::IndiProperty m_indiP_status
Read-only status property exposing flow rate and age.
Definition flowRPM.hpp:129
double m_badValue
Bad-value sentinel published on parse or availability failures.
Definition flowRPM.hpp:92
double m_maxAge
Maximum allowed source age in seconds before the reading is treated as stale.
Definition flowRPM.hpp:86
timespec m_sourceTs
Parsed source timestamp when available.
Definition flowRPM.hpp:70
std::string m_fanDescriptor
Expected fan descriptor in the pipe-delimited record.
Definition flowRPM.hpp:89
virtual int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
Definition flowRPM.hpp:381
bool haveValidReading() const
Get whether the current published value is valid.
Definition flowRPM.hpp:683
virtual int recordFlow(bool force=false)
Record the currently displayed flow state to telemetry.
Definition flowRPM.hpp:698
virtual int publishResult(const parseResult &result)
Publish a parse result to INDI and runtime state.
Definition flowRPM.hpp:744
flowRPM()
Default c'tor.
Definition flowRPM.hpp:321
virtual int appStartup()
Perform application startup.
Definition flowRPM.hpp:402
virtual int appShutdown()
Shut the application down.
Definition flowRPM.hpp:470
double m_lastTelemFlowRate
Last flow value written to telemetry.
Definition flowRPM.hpp:117
bool shouldLogError(const std::string &key, const timespec &now)
Determine whether a repeated error should be logged now.
Definition flowRPM.hpp:759
double maxAge() const
Get the configured maximum reading age in seconds.
Definition flowRPM.hpp:653
int parseFileContents(parseResult &result, const std::string &contents, const timespec &now) const
Parse the two-line file contents using a supplied current time.
Definition flowRPM.hpp:543
int checkRecordTimes()
Check whether telemetry records need to be forced.
Definition flowRPM.hpp:688
double errorLogInterval() const
Get the configured repeated-error log interval.
Definition flowRPM.hpp:668
Parsed result of the current file contents.
Definition flowRPM.hpp:66
@ READY
The device is ready for operation, but is not operating.
#define INDI_ALERT
Definition indiUtils.hpp:30
#define INDI_OK
Definition indiUtils.hpp:28
std::vector< std::string > splitPipeDelimited(const std::string &line)
Split a pipe-delimited sensor line into trimmed fields.
Definition flowRPM.hpp:274
double elapsedSeconds(const timespec &start, const timespec &end)
Measure elapsed seconds between two timestamps.
Definition flowRPM.hpp:268
std::vector< std::string > splitLogicalLines(const std::string &contents)
Split file contents into non-empty logical lines.
Definition flowRPM.hpp:295
double timespecToDouble(const timespec &ts)
Convert a timespec to fractional seconds.
Definition flowRPM.hpp:262
std::string trimToken(const std::string &token)
Trim leading and trailing ASCII whitespace from a token.
Definition flowRPM.hpp:248
bool isValidStatusField(const std::string &statusField)
Determine whether the IPMI sensor status token represents a valid flow reading.
Definition flowRPM.hpp:289
Definition dm.hpp:19
static constexpr logPrioT LOG_NOTICE
A normal but significant condition.
A device base class which saves telemetry.
Definition telemeter.hpp:75
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
Software ERR log entry.
The type of the input message.
Log entry recording the displayed flow value and source age.
#define TELEMETER_APP_LOGIC
Call telemeter::appLogic with error checking.
#define TELEMETER_LOAD_CONFIG(cfig)
Call telemeter::loadConfig with error checking.
#define TELEMETER_APP_STARTUP
Call telemeter::appStartup with error checking.
#define TELEMETER_SETUP_CONFIG(cfig)
Call telemeter::setupConfig with error checking.
#define TELEMETER_APP_SHUTDOWN
Call telemeter::appShutdown with error checking.