Line data Source code
1 : /** \file rhusbMon.hpp
2 : * \brief The MagAO-X RH USB monitor
3 : *
4 : * \ingroup rhusbMon_files
5 : */
6 :
7 : #ifndef rhusbMon_hpp
8 : #define rhusbMon_hpp
9 :
10 :
11 : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
12 : #include "../../magaox_git_version.h"
13 :
14 : #include "rhusbMonParsers.hpp"
15 :
16 : /** \defgroup rhusbMon
17 : * \brief Application to monitor an Omega RH USB probe.
18 : *
19 : * <a href="../handbook/operating/software/apps/rhusbMon.html">Application Documentation</a>
20 : *
21 : * \ingroup apps
22 : *
23 : */
24 :
25 : /** \defgroup rhusbMon_files
26 : * \ingroup rhusbMon
27 : */
28 :
29 : namespace MagAOX
30 : {
31 : namespace app
32 : {
33 :
34 : /// The MagAO-X RH-USB monitoring class
35 : /** Interacts with the Omega RH-USB probe used for DM chamber humidity monitoring.
36 : *
37 : * \todo need a test mode (compile-time) which adds a way (INDI?) to initiate testing of parameter limits.
38 : *
39 : * \ingroup rhusbMon
40 : */
41 : class rhusbMon : public MagAOXApp<true>, public tty::usbDevice, public dev::ioDevice, public dev::telemeter<rhusbMon>
42 : {
43 :
44 : //Give the test harness access.
45 : friend class rhusbMon_test;
46 :
47 : //Let telemeter work.
48 : friend class dev::telemeter<rhusbMon>;
49 :
50 : protected:
51 :
52 : /** \name Configurable Parameters
53 : *@{
54 : */
55 : float m_warnTemp {30}; ///< This is abnormally high if the system is working, but still safe.
56 : float m_alertTemp {35}; ///< This is the actual limit, shut down should occur.
57 : float m_emergTemp {40}; ///< Must shutdown immediately.
58 :
59 : float m_warnHumid {18}; ///< This is abnormally high if the system is working, but still safe.
60 : float m_alertHumid {20}; ///< This is the actual limit, shut down should occur.
61 : float m_emergHumid {22}; ///< Must shutdown immediately.
62 :
63 : ///@}
64 :
65 : float m_temp {-999};
66 : float m_rh {-999};
67 :
68 : pcf::IndiProperty m_indiP_temp;
69 : pcf::IndiProperty m_indiP_rh;
70 :
71 :
72 : public:
73 : /// Default c'tor.
74 : rhusbMon();
75 :
76 : /// D'tor, declared and defined for noexcept.
77 0 : ~rhusbMon() noexcept
78 0 : {}
79 :
80 : virtual void setupConfig();
81 :
82 : /// Implementation of loadConfig logic, separated for testing.
83 : /** This is called by loadConfig().
84 : */
85 : int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
86 :
87 : virtual void loadConfig();
88 :
89 : /// Startup function
90 : /**
91 : *
92 : */
93 : virtual int appStartup();
94 :
95 : /// Implementation of the FSM for rhusbMon.
96 : /**
97 : * \returns 0 on no critical error
98 : * \returns -1 on an error requiring shutdown
99 : */
100 : virtual int appLogic();
101 :
102 : /// Shutdown the app.
103 : /**
104 : *
105 : */
106 : virtual int appShutdown();
107 :
108 : /// Connect to the probe
109 : /** Search for the USB device in udev and attempt ot open it.
110 : * The result is reported via the FSM state (NODEVICE, NOTCONNECTED, CONNECTED).
111 : *
112 : * \returns -1 on an error attempting to read udev
113 : * \returns 0 if device not found, or connection does not work, or if connected.
114 : */
115 : int connect();
116 :
117 : /// Read current values from the RH-USB probe
118 : /** Issues the 'C' and 'H' commands to get temperature and humidity.
119 : *
120 : * \returns -1 on error writing or reading, or on a parsing error
121 : *
122 : * \see \parseC( float &, const std::string)
123 : * \see \parseH( float &, const std::string)
124 : */
125 : int readProbe();
126 :
127 : /** \name Telemeter Interface
128 : *
129 : * @{
130 : */
131 : int checkRecordTimes();
132 :
133 : int recordTelem( const telem_rhusb * );
134 :
135 : protected:
136 :
137 : int recordRH( bool force = false );
138 :
139 : ///@}
140 :
141 : };
142 :
143 0 : rhusbMon::rhusbMon() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
144 : {
145 0 : dev::ioDevice::m_readTimeout = 2000;
146 0 : dev::ioDevice::m_writeTimeout = 1000;
147 :
148 0 : return;
149 0 : }
150 :
151 0 : void rhusbMon::setupConfig()
152 : {
153 0 : config.add("temp.warning", "", "temp.warning", argType::Required, "temp", "warning", false, "float", "Temperature at which to issue a warning. Default is 30.");
154 0 : config.add("temp.alert", "", "temp.alert", argType::Required, "temp", "alert", false, "float", "Temperature at which to issue an alert. Default is 35.");
155 0 : config.add("temp.emergency", "", "temp.emergency", argType::Required, "temp", "emergency", false, "float", "Temperature at which to issue an emergency. Default is 40.");
156 :
157 0 : config.add("humid.warning", "", "humid.warning", argType::Required, "humid", "warning", false, "float", "Humidity at which to issue a warning. Default is 18.");
158 0 : config.add("humid.alert", "", "humid.alert", argType::Required, "humid", "alert", false, "float", "Humidity at which to issue an alert. Default is 20.");
159 0 : config.add("humid.emergency", "", "humid.emergency", argType::Required, "humid", "emergency", false, "float", "Humidity at which to issue an emergency. Default is 22.");
160 :
161 0 : tty::usbDevice::setupConfig(config);
162 0 : dev::ioDevice::setupConfig(config);
163 :
164 0 : dev::telemeter<rhusbMon>::setupConfig(config);
165 0 : }
166 :
167 0 : int rhusbMon::loadConfigImpl( mx::app::appConfigurator & _config )
168 : {
169 :
170 0 : _config(m_warnTemp, "temp.warning");
171 0 : _config(m_alertTemp, "temp.alert");
172 0 : _config(m_emergTemp, "temp.emergency");
173 :
174 0 : _config(m_warnHumid, "humid.warning");
175 0 : _config(m_alertHumid, "humid.alert");
176 0 : _config(m_emergHumid, "humid.emergency");
177 :
178 0 : tty::usbDevice::loadConfig(_config);
179 0 : dev::ioDevice::loadConfig(_config);
180 :
181 0 : dev::telemeter<rhusbMon>::loadConfig(_config);
182 :
183 0 : return 0;
184 : }
185 :
186 0 : void rhusbMon::loadConfig()
187 : {
188 0 : loadConfigImpl(config);
189 0 : }
190 :
191 0 : int rhusbMon::appStartup()
192 : {
193 0 : createROIndiNumber( m_indiP_temp, "temperature", "Temperature [C]");
194 0 : indi::addNumberElement<float>( m_indiP_temp, "current", -20., 120., 0, "%0.1f");
195 0 : m_indiP_temp["current"] = -999;
196 0 : registerIndiPropertyReadOnly(m_indiP_temp);
197 :
198 0 : createROIndiNumber( m_indiP_rh, "humidity", "Relative Humidity [%]");
199 0 : indi::addNumberElement<float>( m_indiP_rh, "current", 0., 100., 0, "%0.1f");
200 0 : m_indiP_rh["current"] = -999;
201 0 : registerIndiPropertyReadOnly(m_indiP_rh);
202 :
203 :
204 0 : if(dev::telemeter<rhusbMon>::appStartup() < 0)
205 : {
206 0 : return log<software_error,-1>({__FILE__,__LINE__});
207 : }
208 :
209 0 : return connect();
210 : }
211 :
212 :
213 :
214 0 : int rhusbMon::appLogic()
215 : {
216 0 : if(state() == stateCodes::NODEVICE || state() == stateCodes::NOTCONNECTED || state() == stateCodes::ERROR)
217 : {
218 0 : int rv = connect();
219 0 : if(rv < 0) return log<software_error,-1>({__FILE__, __LINE__});
220 : }
221 :
222 0 : if(state() == stateCodes::CONNECTED || state() == stateCodes::OPERATING)
223 : {
224 0 : int rv = readProbe();
225 0 : if(rv == 0)
226 : {
227 0 : state(stateCodes::OPERATING);
228 : }
229 : else
230 : {
231 0 : state(stateCodes::ERROR);
232 0 : return log<software_error,0>({__FILE__, __LINE__});
233 : }
234 : }
235 :
236 0 : pcf::IndiProperty::PropertyStateType rhState = pcf::IndiProperty::Ok;
237 : //Check warning and alert values
238 0 : if(m_rh > m_emergHumid)
239 : {
240 0 : log<text_log>("RH > " + std::to_string(m_emergHumid) + "% : " + std::to_string(m_rh) + "%! Shutdown immediately!", logPrio::LOG_EMERGENCY);
241 0 : rhState = pcf::IndiProperty::Alert;
242 : }
243 0 : else if(m_rh > m_alertHumid)
244 : {
245 0 : log<text_log>("RH > " + std::to_string(m_alertHumid) + "% : " + std::to_string(m_rh) + "%. Fix or shutdown.", logPrio::LOG_ALERT);
246 0 : rhState = pcf::IndiProperty::Alert;
247 : }
248 0 : else if(m_rh > m_warnHumid)
249 : {
250 0 : log<text_log>("RH > " + std::to_string(m_warnHumid) + "% : " + std::to_string(m_rh) + "%.", logPrio::LOG_WARNING);
251 0 : rhState = pcf::IndiProperty::Alert;
252 : }
253 :
254 0 : pcf::IndiProperty::PropertyStateType tState = pcf::IndiProperty::Ok;
255 : //Check warning and alert values
256 0 : if(m_temp > m_emergTemp)
257 : {
258 0 : log<text_log>("Temp > " + std::to_string(m_emergTemp) + "C : " + std::to_string(m_temp) + "C! Shutdown immediately!", logPrio::LOG_EMERGENCY);
259 0 : tState = pcf::IndiProperty::Alert;
260 : }
261 0 : else if(m_temp > m_alertTemp)
262 : {
263 0 : log<text_log>("Temp > " + std::to_string(m_alertTemp) + "C : " + std::to_string(m_temp) + "C. Fix or shutdown.", logPrio::LOG_ALERT);
264 0 : tState = pcf::IndiProperty::Alert;
265 : }
266 0 : else if(m_temp > m_warnTemp)
267 : {
268 0 : log<text_log>("Temp > " + std::to_string(m_warnTemp) + "C : " + std::to_string(m_temp) + "C.", logPrio::LOG_WARNING);
269 0 : tState = pcf::IndiProperty::Alert;
270 : }
271 :
272 : //Scope for mutex
273 : {
274 0 : std::unique_lock<std::mutex> lock(m_indiMutex);
275 0 : updateIfChanged(m_indiP_temp, "current", m_temp, tState);
276 :
277 0 : m_indiP_rh["current"].set<float>(-999); //Force the update to get a new timestamp
278 0 : updateIfChanged(m_indiP_rh, "current", m_rh, rhState); ///\todo updateIfChanged should have a force flag
279 0 : }
280 :
281 0 : if(telemeter<rhusbMon>::appLogic() < 0)
282 : {
283 0 : return log<software_error,0>({__FILE__, __LINE__});
284 : }
285 :
286 0 : return 0;
287 : }
288 :
289 0 : int rhusbMon::appShutdown()
290 : {
291 0 : return 0;
292 : }
293 :
294 0 : int rhusbMon::connect()
295 : {
296 0 : int rv = tty::usbDevice::getDeviceName();
297 0 : if(rv < 0 && rv != TTY_E_DEVNOTFOUND && rv != TTY_E_NODEVNAMES)
298 : {
299 : //There is no device reason for this to error. Something is wrong.
300 0 : state(stateCodes::FAILURE);
301 0 : return log<software_critical, -1>({__FILE__, __LINE__, rv, tty::ttyErrorString(rv)});
302 : }
303 :
304 0 : if(rv == TTY_E_DEVNOTFOUND || rv == TTY_E_NODEVNAMES)
305 : {
306 0 : state(stateCodes::NODEVICE);
307 :
308 0 : if(!stateLogged())
309 : {
310 0 : std::stringstream logs;
311 0 : logs << "USB Device " << m_idVendor << ":" << m_idProduct << ":" << m_serial << " not found in udev";
312 0 : log<text_log>(logs.str());
313 0 : }
314 0 : return 0;
315 : }
316 : else
317 : {
318 0 : state(stateCodes::NOTCONNECTED);
319 0 : if(!stateLogged())
320 : {
321 0 : std::stringstream logs;
322 0 : logs << "USB Device " << m_idVendor << ":" << m_idProduct << ":" << m_serial << " found in udev as " << m_deviceName;
323 0 : log<text_log>(logs.str());
324 0 : }
325 :
326 : //scope for elevated priv
327 : {
328 0 : elevatedPrivileges elPriv(this);
329 0 : rv = tty::usbDevice::connect();
330 0 : }
331 :
332 0 : if(rv == TTY_E_NOERROR)
333 : {
334 0 : state(stateCodes::CONNECTED);
335 0 : if(!stateLogged())
336 : {
337 0 : std::stringstream logs;
338 0 : logs << "Connected to " << m_idVendor << ":" << m_idProduct << ":" << m_serial << " @ " << m_deviceName;
339 0 : log<text_log>(logs.str());
340 0 : }
341 : }
342 : else
343 : {
344 : //There is no power or other reason this should happen. It means something is wrong and needs to be corrected.
345 0 : state(stateCodes::FAILURE);
346 0 : return log<software_critical, -1>({__FILE__,__LINE__, errno, rv, "Error opening connection: " + tty::ttyErrorString(rv)});
347 : }
348 : }
349 :
350 0 : return 0;
351 : }
352 :
353 0 : int rhusbMon::readProbe()
354 : {
355 0 : std::string strRead;
356 :
357 0 : int rv = tty::ttyWriteRead( strRead, "C\r", "\r\n>", false, m_fileDescrip, dev::ioDevice::m_writeTimeout, dev::ioDevice::m_readTimeout);
358 0 : if(rv != TTY_E_NOERROR)
359 : {
360 0 : return log<software_error,-1>({__FILE__, __LINE__, 0, rv, "Error reading temp: " + tty::ttyErrorString(rv)});
361 : }
362 :
363 0 : rv = RH::parseC(m_temp, strRead);
364 0 : if(rv != 0)
365 : {
366 0 : if( rv == -1 )
367 : {
368 0 : return log<software_error, -1>({__FILE__, __LINE__, "Error parsing temp, no EOT"});
369 : }
370 0 : else if (rv == -2 )
371 : {
372 0 : return log<software_error, -1>({__FILE__, __LINE__, "Error parsing temp, no value"});
373 : }
374 0 : else if (rv == -3)
375 : {
376 0 : return log<software_error, -1>({__FILE__, __LINE__, "Error parsing temp, does not begin with digit"});
377 : }
378 : else
379 : {
380 0 : return log<software_error, -1>({__FILE__, __LINE__, "Error parsing temp."});
381 : }
382 : }
383 :
384 0 : std::cout << m_temp << "\n";
385 :
386 0 : rv = tty::ttyWriteRead( strRead, "H\r", "\r\n>", false, m_fileDescrip, dev::ioDevice::m_writeTimeout, dev::ioDevice::m_readTimeout);
387 0 : if(rv != TTY_E_NOERROR)
388 : {
389 0 : return log<software_error,-1>({__FILE__, __LINE__, 0, rv, "Error reading RH: " + tty::ttyErrorString(rv)});
390 : }
391 :
392 0 : rv = RH::parseH(m_rh, strRead);
393 0 : if(rv != 0)
394 : {
395 0 : if( rv == -1 )
396 : {
397 0 : return log<software_error, -1>({__FILE__, __LINE__, "Error parsing humid, no EOT"});
398 : }
399 0 : else if (rv == -2 )
400 : {
401 0 : return log<software_error, -1>({__FILE__, __LINE__, "Error parsing humid, no value"});
402 : }
403 0 : else if (rv == -3)
404 : {
405 0 : return log<software_error, -1>({__FILE__, __LINE__, "Error parsing temp, does not begin with digit"});
406 : }
407 : else
408 : {
409 0 : return log<software_error, -1>({__FILE__, __LINE__, "Error parsing humid."});
410 : }
411 : }
412 :
413 0 : std::cout << m_rh << "\n";
414 :
415 0 : return 0;
416 0 : }
417 :
418 0 : int rhusbMon::checkRecordTimes()
419 : {
420 0 : return dev::telemeter<rhusbMon>::checkRecordTimes(telem_rhusb());
421 : }
422 :
423 0 : int rhusbMon::recordTelem( const telem_rhusb * )
424 : {
425 0 : return recordRH(true);
426 : }
427 :
428 : inline
429 0 : int rhusbMon::recordRH(bool force)
430 : {
431 : static float lastTemp = -99;
432 : static float lastRH = -99;
433 :
434 0 : if(force || m_temp != lastTemp || m_rh != lastRH)
435 : {
436 0 : telem<telem_rhusb>({m_temp, m_rh});
437 : }
438 :
439 0 : lastTemp = m_temp;
440 0 : lastRH = m_rh;
441 :
442 0 : return 0;
443 :
444 : }
445 :
446 : } //namespace app
447 : } //namespace MagAOX
448 :
449 : #endif //rhusbMon_hpp
|