API
 
Loading...
Searching...
No Matches
koolanceCtrl.hpp
Go to the documentation of this file.
1/** \file koolanceCtrl.hpp
2 * \brief The MagAO-X Koolance Controller header file
3 *
4 * \ingroup koolanceCtrl_files
5 */
6
7#ifndef koolanceCtrl_hpp
8#define koolanceCtrl_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/** \defgroup koolanceCtrl
15 * \brief The MagAO-X application to monitor and control a Koolance cooler
16 *
17 * <a href="../handbook/operating/software/apps/koolanceCtrl.html">Application Documentation</a>
18 *
19 * \ingroup apps
20 *
21 */
22
23/** \defgroup koolanceCtrl_files
24 * \ingroup koolanceCtrl
25 */
26
27namespace MagAOX
28{
29namespace app
30{
31
32/// The MagAO-X Koolance Controller
33/** This application will monitor a Koolance v1 or v2 protocol controller. If v2, will also allow
34 * changing of pump and fan settings via INDI.
35 *
36 * \ingroup koolanceCtrl
37 */
38class koolanceCtrl : public MagAOXApp<true>, public tty::usbDevice, public dev::telemeter<koolanceCtrl>
39{
40
41 //Give the test harness access.
42 friend class koolanceCtrl_test;
43
44 friend class dev::telemeter<koolanceCtrl>;
45protected:
46
47 /** \name Configurable Parameters
48 *@{
49 */
50
51 //here add parameters which will be config-able at runtime
52
53 ///@}
54
55 size_t m_protocolChars {0}; ///< Will be set to 43 if protocol 1, and set to 51 if protocol 2.
56
57 bool m_indiSetup {false}; ///< Whether or not INDI has been set up after initial protocol determination.
58
59 float m_liqTemp {0}; ///< The liquid temperature
60 float m_flowRate {0}; ///< The flow rate
61 int m_pumpLvl {0}; ///< The pump power level, 1-10
62 int m_pumpRPM {0}; ///< The pump RPM
63 int m_fanRPM {0}; ///< The fan RPM
64 int m_fanLvl {0}; ///< The fan power level, 0-100
65
66
67
68public:
69 /// Default c'tor.
71
72 /// D'tor, declared and defined for noexcept.
75
76 virtual void setupConfig();
77
78 /// Implementation of loadConfig logic, separated for testing.
79 /** This is called by loadConfig().
80 */
81 int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
82
83 virtual void loadConfig();
84
85 /// Startup function
86 /**
87 *
88 */
89 virtual int appStartup();
90
91 /// Implementation of the FSM for koolanceCtrl.
92 /**
93 * \returns 0 on no critical error
94 * \returns -1 on an error requiring shutdown
95 */
96 virtual int appLogic();
97
98 /// Shutdown the app.
99 /**
100 *
101 */
102 virtual int appShutdown();
103
104 /// Initial connection to controller
105 /** Determine protocol in use based on number of characters in response
106 * and sets up INDI appropriately.
107 *
108 * Calls to this function should be mutexed.
109 *
110 * \returns 0 on success
111 * \returns -1 on an error
112 */
113 int initialConnect();
114
115 /// Get status from controller and updated INDI.
116 /**
117 *
118 * Calls to this function should be mutexed.
119 *
120 * \returns 0 on success
121 * \returns -1 on an error
122 */
123 int getStatus();
124
125 /// Set the pump level
126 /**
127 * Calls to this function should be mutexed.
128 *
129 * \returns 0 on success
130 * \returns -1 on an error
131 */
132 int setPumpLvl(int lvl /**< [in] the new level */);
133
134 /// Set the fan level
135 /**
136 * Calls to this function should be mutexed.
137 *
138 * \returns 0 on success
139 * \returns -1 on an error
140 */
141 int setFanLvl(int lvl /**< [in] the new level */);
142
143 /** \name INDI
144 *
145 *@{
146 */
147protected:
148 //declare our properties
149 pcf::IndiProperty m_indiP_status;
150 pcf::IndiProperty m_indiP_pumplvl;
151 pcf::IndiProperty m_indiP_fanlvl;
152
153public:
156
157 ///@}
158
159 /** \name Telemeter Interface
160 *
161 * @{
162 */
163 int checkRecordTimes();
164
165 int recordTelem( const telem_cooler * );
166
167 int recordCooler(bool force = false);
168
169 ///@}
170
171
172};
173
174koolanceCtrl::koolanceCtrl() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
175{
176 return;
177}
178
186
187int koolanceCtrl::loadConfigImpl( mx::app::appConfigurator & _config )
188{
189 this->m_baudRate = B9600; //default for a Koolance controller. Will be overridden by any config setting.
190
191 int rv = tty::usbDevice::loadConfig(_config);
192
193 if(rv != 0 && rv != TTY_E_NODEVNAMES && rv != TTY_E_DEVNOTFOUND) //Ignore error if not plugged in
194 {
196 }
197
198 return 0;
199}
200
207
209{
211 {
212 log<text_log>( "In appStartup but in state UNINITIALIZED.", logPrio::LOG_CRITICAL );
213 return -1;
214 }
215
216 // set up the INDI properties
217 REG_INDI_NEWPROP_NOCB(m_indiP_status, "status", pcf::IndiProperty::Number);
218 m_indiP_status.add(pcf::IndiElement("liquid_temp"));
219 m_indiP_status.add(pcf::IndiElement("flow_rate"));
220 m_indiP_status.add(pcf::IndiElement("pump_rpm"));
221 m_indiP_status.add(pcf::IndiElement("fan_rpm"));
222
224 {
225 return log<software_error,-1>({__FILE__,__LINE__});
226 }
227
229 return 0;
230}
231
233{
234
235 if( state() == stateCodes::NODEVICE )
236 {
238 if(rv < 0 && rv != TTY_E_DEVNOTFOUND && rv != TTY_E_NODEVNAMES)
239 {
241 if(!stateLogged())
242 {
244 }
245 return -1;
246 }
247
249 {
251
252 if(!stateLogged())
253 {
254 std::stringstream logs;
255 logs << "USB Device " << m_idVendor << ":" << m_idProduct << ":" << m_serial << " not found in udev";
256 log<text_log>(logs.str());
257 }
258 return 0;
259 }
260 else
261 {
262 std::stringstream logs;
263 logs << "USB Device " << m_idVendor << ":" << m_idProduct << ":" << m_serial << " found in udev as " << m_deviceName;
264 log<text_log>(logs.str());
265
267 }
268 }
269
271 {
272 int rv = 0;
273 {
274 elevatedPrivileges ep(this);
275 rv = connect();
276 }
277
278 if(rv < 0)
279 {
281 if(nrv < 0 && nrv != TTY_E_DEVNOTFOUND && nrv != TTY_E_NODEVNAMES)
282 {
285 return -1;
286 }
287
289 {
291
292 if(!stateLogged())
293 {
294 std::stringstream logs;
295 logs << "USB Device " << m_idVendor << ":" << m_idProduct << ":" << m_serial << " no longer found in udev";
296 log<text_log>(logs.str());
297 }
298 return 0;
299 }
300
301 //if connect failed, and there is a device, then we have some other problem.
302 sleep(1); //wait to see if power state updates
303 if(m_powerState == 0) return 0;
304
305 //Ok we can't figure this out, die.
308 return -1;
309
310 }
311 else
312 {
313 std::unique_lock<std::mutex> lock(m_indiMutex);
314 if(initialConnect () == 0)
315 {
317 }
318 else
319 {
320 if(!stateLogged())
321 {
322 log<text_log>("no response from device");
323 }
324 return 0;
325 }
326 }
327
329 {
330 std::stringstream logs;
331 logs << "Connected to koolance system on " << m_deviceName;
332 log<text_log>(logs.str());
334 return 0;
335 }
336
337 }
338
339 if( state() == stateCodes::READY )
340 {
341 { //mutex scope
342 std::unique_lock<std::mutex> lock(m_indiMutex);
343 if(getStatus() < 0)
344 {
346 }
347 }
348
350 {
352 return 0;
353 }
354 return 0;
355 }
356
357 return 0;
358}
359
365
366
367
369{
370 int rv;
371
372 std::vector<unsigned char> com;
373 com.resize(3, '\0');
374 com[0] = 0xCF;
375 com[1] = 0x01;
376 com[2] = 0x08;
377
378 rv = write(m_fileDescrip, com.data(), com.size());
379
380 if(rv < 0)
381 {
382 log<software_error>({__FILE__,__LINE__, errno, "error from write"});
383 ::close(m_fileDescrip);
384 m_fileDescrip = 0;
386
387 return -1;
388 }
389
390 std::vector<unsigned char> resp;
391 resp.resize(51);
392
393 mx::sys::milliSleep(1000); //Sleep for a long time to make sure device responds
394 int readBytes;
395 rv = tty::ttyReadRaw(resp, readBytes, m_fileDescrip, 1000); ///\todo needs to be iodevice
396
397 if(rv < 0)
398 {
399 log<software_error>({__FILE__,__LINE__, errno, "error from read"});
400 ::close(m_fileDescrip);
401 m_fileDescrip = 0;
403
404 return -1;
405 }
406
407 if(readBytes == 43)
408 {
409 log<text_log>("found protocol 1 device");
410 m_protocolChars = 43;
411 }
412 else if(readBytes == 51)
413 {
414 log<text_log>("found protocol 2 device");
415 m_protocolChars = 51;
416 }
417 else
418 {
419 return -1;
420 }
421
422 if(!m_indiSetup)
423 {
424 if(readBytes == 43)
425 {
426 REG_INDI_NEWPROP_NOCB(m_indiP_pumplvl, "pump_level", pcf::IndiProperty::Number);
427 m_indiP_pumplvl.add(pcf::IndiElement("current"));
428
429 REG_INDI_NEWPROP_NOCB(m_indiP_fanlvl, "fan_level", pcf::IndiProperty::Number);
430 m_indiP_fanlvl.add(pcf::IndiElement("current"));
431 }
432 else
433 {
434 createStandardIndiNumber<int>( m_indiP_pumplvl, "pump_level", 1, 10, 1, "%d", "Pump Level", "Lab");
436
437 createStandardIndiNumber<int>( m_indiP_fanlvl, "fan_level", 0, 100, 1, "%d", "Fan Level", "Lab");
439 }
440
441 m_indiSetup = true;
442 }
443
444 return 0;
445}
446
448{
449 int rv;
450
451 std::vector<unsigned char> com;
452 com.resize(3, '\0');
453 com[0] = 0xCF;
454 com[1] = 0x01;
455 com[2] = 0x08;
456
457 rv = write(m_fileDescrip, com.data(), com.size());
458
459 if(rv < 0)
460 {
461 log<software_error>({__FILE__,__LINE__, errno, "error from write"});
462 ::close(m_fileDescrip);
463 m_fileDescrip = 0;
465
466 return -1;
467 }
468
469 std::string resp;
470
471 rv = tty::ttyRead(resp, m_protocolChars, m_fileDescrip, 1000); ///\todo needs to be iodevice
472
473 if(rv < 0)
474 {
475 log<software_error>({__FILE__,__LINE__, errno, "error from read"});
476 ::close(m_fileDescrip);
477 m_fileDescrip = 0;
479
480 return -1;
481 }
482
483 if(resp.size() == m_protocolChars)
484 {
485 m_liqTemp = ((float) (( (unsigned char)resp[2] << 8) + (unsigned char)resp[3]-2000)) / 10.0;
486 m_flowRate = ( (float) ((unsigned char)resp[12] << 8) + (unsigned char)resp[13]) / 10.0;
487 m_fanRPM = ((unsigned char)resp[8] << 8) + (unsigned char)resp[9];
488 m_pumpRPM = ((unsigned char)resp[10] << 8) + (unsigned char)resp[11];
489 m_fanLvl = (unsigned char)resp[15];
490 m_pumpLvl = (unsigned char)resp[17];
491
492 recordCooler();
493// std::cout << std::dec;
494// std::cout << "liq. temp: " << m_liqTemp << " C\n";
495// std::cout << "flow rate: " << m_flowRate << " LPM\n";
496// std::cout << "pump lvl: " << m_pumpLvl << "\n";
497// std::cout << "pump speed:" << m_pumpRPM << " RPM\n";
498// std::cout << "fan lvl: " << m_fanLvl << "\n";
499// std::cout << "fan speed:" << m_fanRPM << " RPM\n";
500
507
508 return 0;
509 }
510 else
511 {
512 log<software_error>({__FILE__,__LINE__, std::string("wrong response size (") + std::to_string(resp.size()) + ") returned"});
513 ::close(m_fileDescrip);
514 m_fileDescrip = 0;
516
517 return -1;
518 }
519}
520
522{
523 if(m_protocolChars == 43) return 0;
524
525 int rv;
526
527 std::vector<unsigned char> com;
528 com.resize(m_protocolChars, '\0');
529 com[0] = 0xCF;
530 com[1] = 0x04;
531
532 com[15] = m_fanLvl;
533 com[17] = lvl;
534
535 //Disable most stuff.
536 for(size_t n = 20; n <m_protocolChars-1; ++n) com[n] = 0xAA;
537
538 //Preserve units
539 if(m_protocolChars > 43)
540 {
541 com[44] = 0;
542 com[45] = 0x0001;
543 com[46] = 0;
544 com[47] = 0;
545 com[48] = 0;
546 com[49] = 0;
547 }
548
549 int checksum = 0;
550 for(size_t n = 0; n <m_protocolChars-1; ++n) checksum += com[n];
551 com[m_protocolChars-1] = checksum % 0x64;
552
553 rv = write(m_fileDescrip, com.data(), com.size());
554
555 if(rv < 0)
556 {
557 log<software_error>({__FILE__,__LINE__, errno, "error from write"});
558 ::close(m_fileDescrip);
559 m_fileDescrip = 0;
561
562 return -1;
563 }
564
565 log<text_log>("set pump level to " + std::to_string(lvl));
566
567 return 0;
568}
569
571{
572 if(m_protocolChars == 43) return 0;
573
574 int rv;
575
576 std::vector<unsigned char> com;
577 com.resize(m_protocolChars, '\0');
578 com[0] = 0xCF;
579 com[1] = 0x04;
580
581 com[15] = lvl;
582 com[17] = m_pumpLvl;
583
584 //Disable most stuff.
585 for(size_t n = 20; n <m_protocolChars-1; ++n) com[n] = 0xAA;
586
587 //Preserve units
588 if(m_protocolChars > 43)
589 {
590 com[44] = 0;
591 com[45] = 0x0001;
592 com[46] = 0;
593 com[47] = 0;
594 com[48] = 0;
595 com[49] = 0;
596 }
597
598 int checksum = 0;
599 for(size_t n = 0; n <m_protocolChars-1; ++n) checksum += com[n];
600 com[m_protocolChars-1] = checksum % 0x64;
601
602 rv = write(m_fileDescrip, com.data(), com.size());
603
604 if(rv < 0)
605 {
606 log<software_error>({__FILE__,__LINE__, errno, "error from write"});
607 ::close(m_fileDescrip);
608 m_fileDescrip = 0;
610
611 return -1;
612 }
613
614 log<text_log>("set fan level to " + std::to_string(lvl));
615
616 return 0;
617}
618
619INDI_NEWCALLBACK_DEFN(koolanceCtrl, m_indiP_pumplvl)(const pcf::IndiProperty &ipRecv)
620{
621 if(ipRecv.getName() != m_indiP_pumplvl.getName())
622 {
623 log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
624 return -1;
625 }
626
627 int lvl = -1;
628
629 if( ipRecv.find("current") )
630 {
631 lvl = ipRecv["current"].get<int>();
632 }
633
634 if( ipRecv.find("target") )
635 {
636 lvl = ipRecv["target"].get<int>();
637 }
638
639 if(lvl < 1 || lvl > 10)
640 {
641 log<software_error>({__FILE__,__LINE__, "Pump level out of range"});
642 return 0;
643 }
644
645 std::unique_lock<std::mutex> lock(m_indiMutex);
646 updateIfChanged(m_indiP_pumplvl, "target", lvl);
647 return setPumpLvl(lvl);
648
649
650}
651
652INDI_NEWCALLBACK_DEFN(koolanceCtrl, m_indiP_fanlvl)(const pcf::IndiProperty &ipRecv)
653{
654 if(ipRecv.getName() != m_indiP_fanlvl.getName())
655 {
656 log<software_error>({__FILE__,__LINE__, "wrong INDI property received."});
657 return -1;
658 }
659
660
661 int lvl = -1;
662
663 if( ipRecv.find("current") )
664 {
665 lvl = ipRecv["current"].get<int>();
666 }
667
668 if( ipRecv.find("target") )
669 {
670 lvl = ipRecv["target"].get<int>();
671 }
672
673 if(lvl < 0 || lvl > 100)
674 {
675 log<software_error>({__FILE__,__LINE__, "Fan level out of range"});
676 return 0;
677 }
678
679 std::unique_lock<std::mutex> lock(m_indiMutex);
680 updateIfChanged(m_indiP_fanlvl, "target", lvl);
681 return setFanLvl(lvl);
682
683 return 0;
684}
685
686inline
691
692inline
694{
695 return recordCooler(true);
696}
697
698inline
700{
701 static float last_liqTemp = std::numeric_limits<float>::max();
702 static float last_flowRate = std::numeric_limits<float>::max();
703 static int last_pumpLvl = std::numeric_limits<int>::max();
704 static int last_pumpRPM = std::numeric_limits<int>::max();
705 static int last_fanRPM = std::numeric_limits<int>::max();
706 static int last_fanLvl = std::numeric_limits<int>::max();
707
710 {
712
719 }
720
721 return 0;
722}
723
724} //namespace app
725} //namespace MagAOX
726
727#endif //koolanceCtrl_hpp
The base-class for MagAO-X applications.
Definition MagAOXApp.hpp:73
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 registerIndiPropertyNew(pcf::IndiProperty &prop, int(*)(void *, const pcf::IndiProperty &))
Register an INDI property which is exposed for others to request a New Property for.
int m_powerState
Current power state, 1=On, 0=Off, -1=Unk.
int stateLogged()
Updates and returns the value of m_stateLogged. Will be 0 on first call after a state change,...
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
std::mutex m_indiMutex
Mutex for locking INDI communications.
The MagAO-X Koolance Controller.
float m_liqTemp
The liquid temperature.
size_t m_protocolChars
Will be set to 43 if protocol 1, and set to 51 if protocol 2.
INDI_NEWCALLBACK_DECL(koolanceCtrl, m_indiP_pumplvl)
~koolanceCtrl() noexcept
D'tor, declared and defined for noexcept.
virtual int appShutdown()
Shutdown the app.
pcf::IndiProperty m_indiP_fanlvl
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
pcf::IndiProperty m_indiP_pumplvl
int m_fanLvl
The fan power level, 0-100.
float m_flowRate
The flow rate.
int recordCooler(bool force=false)
virtual int appStartup()
Startup function.
int m_pumpLvl
The pump power level, 1-10.
virtual int appLogic()
Implementation of the FSM for koolanceCtrl.
int getStatus()
Get status from controller and updated INDI.
int m_pumpRPM
The pump RPM.
int initialConnect()
Initial connection to controller.
int setPumpLvl(int lvl)
Set the pump level.
int recordTelem(const telem_cooler *)
int setFanLvl(int lvl)
Set the fan level.
pcf::IndiProperty m_indiP_status
INDI_NEWCALLBACK_DECL(koolanceCtrl, m_indiP_fanlvl)
bool m_indiSetup
Whether or not INDI has been set up after initial protocol determination.
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
#define INDI_NEWCALLBACK(prop)
Get the name of the static callback wrapper for a new property.
@ NODEVICE
No device exists for the application to control.
@ FAILURE
The application has failed, should be used when m_shutdown is set for an error.
@ READY
The device is ready for operation, but is not operating.
@ CONNECTED
The application has connected to the device or service.
@ UNINITIALIZED
The application is unitialized, the default.
@ NOTCONNECTED
The application is not connected to the device or service.
int ttyReadRaw(std::vector< unsigned char > &vecRead, int &readBytes, int fd, int timeoutRead)
Read from a tty console indicated by a file-descriptor, up to a given number of bytes.
std::string ttyErrorString(int ec)
Get a text explanation of a TTY_E_ error code.
Definition ttyErrors.cpp:15
int ttyRead(std::string &strRead, int bytes, int fd, int timeoutRead)
Read from a tty console indicated by a file-descriptor, until a given number of bytes are read.
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:24
static constexpr logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
A device base class which saves telemetry.
Definition telemeter.hpp:69
int appShutdown()
Perform telemeter application shutdown.
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
Software ERR log entry.
Log entry recording the build-time git state.
A USB device as a TTY device.
Definition usbDevice.hpp:33
std::string m_deviceName
The device path name, e.g. /dev/ttyUSB0.
Definition usbDevice.hpp:40
int m_fileDescrip
The file descriptor.
Definition usbDevice.hpp:42
int connect()
Connect to the device.
int getDeviceName()
Get the device name from udev using the vendor, product, and serial number.
std::string m_idProduct
The product id 4-digit code.
Definition usbDevice.hpp:35
int setupConfig(mx::app::appConfigurator &config)
Setup an application configurator for the USB section.
Definition usbDevice.cpp:24
std::string m_serial
The serial number.
Definition usbDevice.hpp:36
int loadConfig(mx::app::appConfigurator &config)
Load the USB section from an application configurator.
Definition usbDevice.cpp:34
speed_t m_baudRate
The baud rate specification.
Definition usbDevice.hpp:38
std::string m_idVendor
The vendor id 4-digit code.
Definition usbDevice.hpp:34
#define TTY_E_NODEVNAMES
Definition ttyErrors.hpp:28
#define TTY_E_DEVNOTFOUND
Definition ttyErrors.hpp:30