API
 
Loading...
Searching...
No Matches
picoMotorCtrl.hpp
Go to the documentation of this file.
1/** \file picoMotorCtrl.hpp
2 * \brief The MagAO-X Pico Motor Controller header file
3 *
4 * \ingroup picoMotorCtrl_files
5 */
6
7#ifndef picoMotorCtrl_hpp
8#define picoMotorCtrl_hpp
9
10#include <map>
11
12#include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
13#include "../../magaox_git_version.h"
14
15/** \defgroup picoMotorCtrl
16 * \brief The Pico Motor Controller application.
17 *
18 * Controls a multi-channel Newport pico motor controller. Each motor gets its own thread.
19 *
20 * <a href="../handbook/operating/software/apps/picoMotorCtrl.html">Application Documentation</a>
21 *
22 * \ingroup apps
23 *
24 */
25
26/** \defgroup picoMotorCtrl_files
27 * \ingroup picoMotorCtrl
28 */
29
30
31
32namespace MagAOX
33{
34namespace app
35{
36
37int splitResponse( int & address,
38 std::string & response,
39 const std::string & fullResponse
40 )
41{
42 size_t carrot = fullResponse.find('>');
43
44 if(carrot == std::string::npos)
45 {
46 address = 1;
47 response = fullResponse;
48 return 0;
49 }
50
51 if(carrot == 0)
52 {
53 address = 0;
54 response = "";
55 return -1;
56 }
57
58 if(carrot == fullResponse.size()-1)
59 {
60 address = 0;
61 response = "";
62 return -2;
63 }
64
65 try
66 {
67 address = std::stoi( fullResponse.substr(0,carrot));
68 response = fullResponse.substr(carrot+1);
69 }
70 catch(...)
71 {
72 address = 0;
73 response = "";
74 return -3;
75 }
76
77 return 0;
78
79}
80
81/** MagAO-X application to control a multi-channel Newport Picomotor Controller.
82 *
83 * \todo need to recognize signals in tty polls and not return errors, etc.
84 * \todo need to implement an onDisconnect() to update values to unknown indicators.
85 * \todo need a frequency-dependent max amp facility.
86 * \todo convert to ioDevice
87 * \todo need telnet device, with optional username/password.
88 *
89 */
90class picoMotorCtrl : public MagAOXApp<>, public dev::ioDevice, public dev::telemeter<picoMotorCtrl>
91{
92
93 friend class dev::telemeter<picoMotorCtrl>;
94
96
97 typedef long posT;
98
100 {
101 picoMotorCtrl * m_parent {nullptr}; ///< A pointer to this for thread starting.
102
103 std::string m_name; ///< The name of this channel, from the config section
104
105 int m_address {1}; ///< The controller address, default is 1
106
107 int m_channel {-1}; ///< The number of this channel, where the motor is plugged in
108
109 int m_type {3}; ///< The motor type of this channel, default is 3
110
111 std::vector<std::string> m_presetNames;
112 std::vector<posT> m_presetPositions;
113
114 posT m_currCounts {0}; ///< The current counts, the cumulative position
115
116 bool m_doMove {false}; ///< Flag indicating that a move is requested.
117 bool m_moving {false}; ///< Flag to indicate that we are actually moving
118
119 pcf::IndiProperty m_property;
120 pcf::IndiProperty m_indiP_presetName;
121
122 std::thread * m_thread {nullptr}; ///< Thread for managing this channel. A pointer to allow copying, but must be deleted in d'tor of parent.
123
124 bool m_threadInit {true}; ///< Thread initialization flag.
125
126 pid_t m_threadID {0}; ///< The ID of the thread.
127
128 pcf::IndiProperty m_threadProp; ///< The property to hold the thread details.
129
130 motorChannel( picoMotorCtrl * p /**< [in] The parent point to set */) : m_parent(p)
131 {
132 m_thread = new std::thread;
133 }
134
135 motorChannel( picoMotorCtrl * p, ///< [in] The parent point to set
136 const std::string & n, ///< [in] The name of this channel
137 int add, ///< [in] The controller address
138 int ch, ///< [in] The number of this channel
139 int type ///< [in] The motor type of this channel
140 ) : m_parent(p), m_name(n), m_address(add), m_channel(ch), m_type(type)
141 {
142 m_thread = new std::thread;
143 }
144
145 };
146
147 typedef std::map<std::string, motorChannel> channelMapT;
148
149 /** \name Configurable Parameters
150 * @{
151 */
152
153 std::string m_deviceAddr; ///< The device address
154 std::string m_devicePort {"23"}; ///< The device port
155
156 int m_nChannels {4}; ///< The number of motor channels total on the hardware. Number of attached motors inferred from config.
157
158 ///@}
159
160 std::vector<int> m_addresses; ///< The unique controller addresses.
161
162 channelMapT m_channels; ///< Map of motor names to channel.
163
164 tty::telnetConn m_telnetConn; ///< The telnet connection manager
165
166 ///Mutex for locking telnet communications.
167 std::mutex m_telnetMutex;
168
169 public:
170
171 /// Default c'tor.
173
174 /// D'tor, declared and defined for noexcept.
176
177 /// Setup the configuration system (called by MagAOXApp::setup())
178 virtual void setupConfig();
179
180 /// Implementation of loadConfig logic, separated for testing.
181 /** This is called by loadConfig().
182 */
183 int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
184
185 /// load the configuration system results (called by MagAOXApp::setup())
186 virtual void loadConfig();
187
188 /// Startup functions
189 /** Setsup the INDI vars.
190 *
191 */
192 virtual int appStartup();
193
194 /// Implementation of the FSM
195 /**
196 * \returns 0 on no critical error
197 * \returns -1 on an error requiring shutdown
198 */
199 virtual int appLogic();
200
201 /// Implementation of the on-power-off FSM logic
202 virtual int onPowerOff();
203
204 /// Implementation of the while-powered-off FSM
206
207 /// Do any needed shutdown tasks.
208 virtual int appShutdown();
209
210 /// Read the current channel counts from disk at startup
211 /** Reads the counts from the file with the specified name in this apps sys directory.
212 * Returns the file contents as a posT.
213 */
214 posT readChannelCounts(const std::string & chName);
215
216 int writeChannelCounts( const std::string & chName,
217 posT counts
218 );
219
220 /// Channel thread starter function
221 static void channelThreadStart( motorChannel * mc /**< [in] the channel to start controlling */);
222
223 /// Channel thread execution function
224 /** Runs until m_shutdown is true.
225 */
227
228/** \name INDI
229 * @{
230 */
232
233 //declare our properties
234 std::vector<pcf::IndiProperty> m_indiP_counts;
235
236
237public:
238 /// The static callback function to be registered for relative position requests
239 /** Dispatches to the handler, which then signals the relavent thread.
240 *
241 * \returns 0 on success.
242 * \returns -1 on error.
243 */
244 static int st_newCallBack_picopos( void * app, ///< [in] a pointer to this, will be static_cast-ed to this
245 const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
246 );
247
248 /// The handler function for relative position requests, called by the static callback
249 /** Signals the relavent thread.
250 *
251 * \returns 0 on success.
252 * \returns -1 on error.
253 */
254 int newCallBack_picopos( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
255
256 /// The static callback function to be registered for position presets
257 /** Dispatches to the handler, which then signals the relavent thread.
258 *
259 * \returns 0 on success.
260 * \returns -1 on error.
261 */
262 static int st_newCallBack_presetName( void * app, ///< [in] a pointer to this, will be static_cast-ed to this
263 const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
264 );
265
266 /// The handler function for position presets, called by the static callback
267 /** Signals the relavent thread.
268 *
269 * \returns 0 on success.
270 * \returns -1 on error.
271 */
272 int newCallBack_presetName( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
273 ///@}
274
275 /** \name Telemeter Interface
276 *
277 * @{
278 */
279 int checkRecordTimes();
280
282
283 int recordPico( bool force = false );
284 ///@}
285
286};
287
294
296{
297 //Wait for each channel thread to exit, then delete it.
298 for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
299 {
300 if(it->second.m_thread != nullptr)
301 {
302 if(it->second.m_thread->joinable()) it->second.m_thread->join();
303 delete it->second.m_thread;
304 }
305 }
306}
307
308
310{
311 config.add("device.address", "", "device.address", argType::Required, "device", "address", false, "string", "The controller IP address.");
312 config.add("device.nChannels", "", "device.nChannels", argType::Required, "device", "nChannels", false, "int", "Number of motoro channels. Default is 4.");
313
315
316 TELEMETER_SETUP_CONFIG( config );
317
318}
319
320#define PICOMOTORCTRL_E_NOMOTORS (-5)
321#define PICOMOTORCTRL_E_BADCHANNEL (-6)
322#define PICOMOTORCTRL_E_DUPMOTOR (-7)
323#define PICOMOTORCTRL_E_INDIREG (-20)
324
325int picoMotorCtrl::loadConfigImpl( mx::app::appConfigurator & _config )
326{
327 //Standard config parsing
328 _config(m_deviceAddr, "device.address");
329 _config(m_nChannels, "device.nChannels");
330
331
332 // Parse the unused config options to look for motors
333 std::vector<std::string> sections;
334
335 _config.unusedSections(sections);
336
337 if( sections.size() == 0 )
338 {
339 log<text_log>("No motors found in config.", logPrio::LOG_CRITICAL);
340
342 }
343
344 //Now see if any unused sections have a channel keyword
345 for(size_t i=0;i<sections.size(); ++i)
346 {
347 int channel = -1;
348 _config.configUnused(channel, mx::app::iniFile::makeKey(sections[i], "channel" ) );
349 if( channel == -1 )
350 {
351 //not a channel
352 continue;
353 }
354
356 {
357 log<text_log>("Bad channel specificiation: " + sections[i] + " channel: " + std::to_string(channel), logPrio::LOG_CRITICAL);
358
360 }
361
362 int address = 1;
363 _config.configUnused(address, mx::app::iniFile::makeKey(sections[i], "address" ) );
364
365 if(address < 1)
366 {
367 log<text_log>("Bad channel specificiation: " + sections[i] + " address: " + std::to_string(address), logPrio::LOG_CRITICAL);
368
370 }
371
372 int type = 3;
373 _config.configUnused(type, mx::app::iniFile::makeKey(sections[i], "type" ) );
374
375 if(address < 1)
376 {
377 log<text_log>("Bad motor type specificiation: " + sections[i] + " type: " + std::to_string(type), logPrio::LOG_CRITICAL);
378
380 }
381
382 //Ok, valid channel. Insert into map and check for duplicates.
383 std::pair<channelMapT::iterator, bool> insert = m_channels.insert(std::pair<std::string, motorChannel>(sections[i], motorChannel(this,sections[i], address, channel, type)));
384
385 if(insert.second == false)
386 {
387 log<text_log>("Duplicate motor specificiation: " + sections[i] + " " + std::to_string(channel), logPrio::LOG_CRITICAL);
389 }
390 else
391 {
392 _config.configUnused(insert.first->second.m_presetNames, mx::app::iniFile::makeKey(sections[i], "names" ));
393 _config.configUnused(insert.first->second.m_presetPositions, mx::app::iniFile::makeKey(sections[i], "positions" ));
394 }
395
396 ///\todo extend to include address
397 log<pico_channel>({sections[i], (uint8_t) channel});
398
399 bool found = false;
400 for(size_t n = 0; n < m_addresses.size(); ++n)
401 {
402 if(address == m_addresses[n])
403 {
404 found = true;
405 break;
406 }
407 }
408
409 if(!found)
410 {
411 m_addresses.push_back(address);
412 }
413 }
414
415 TELEMETER_LOAD_CONFIG( config );
416
417 return 0;
418}
419
421{
422 if( loadConfigImpl(config) < 0)
423 {
424 log<text_log>("Error during config", logPrio::LOG_CRITICAL);
425 m_shutdown = true;
426 }
427
428 if(dev::ioDevice::loadConfig(config) < 0)
429 {
430 log<text_log>("Error during ioDevice config", logPrio::LOG_CRITICAL);
431 m_shutdown = true;
432 }
433
434
435
436}
437
439{
440 ///\todo read state from disk to get current counts.
441
442 for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
443 {
444 it->second.m_currCounts = readChannelCounts(it->second.m_name);
445
446
447 createStandardIndiNumber( it->second.m_property, it->first+"_pos", std::numeric_limits<posT>::lowest(), std::numeric_limits<posT>::max(), static_cast<posT>(1), "%d", "Position", it->first);
448 it->second.m_property["current"].set(it->second.m_currCounts);
449 it->second.m_property["target"].set(it->second.m_currCounts);
450 it->second.m_property.setState(INDI_IDLE);
451
452 if( registerIndiPropertyNew( it->second.m_property, st_newCallBack_picopos) < 0)
453 {
454 #ifndef PICOMOTORCTRL_TEST_NOLOG
456 #endif
458 }
459
460 if(it->second.m_presetNames.size() > 0)
461 {
462 if(createStandardIndiSelectionSw( it->second.m_indiP_presetName, it->first, it->second.m_presetNames) < 0)
463 {
465 return -1;
466 }
467 if( registerIndiPropertyNew( it->second.m_indiP_presetName, st_newCallBack_presetName) < 0)
468 {
470 return -1;
471 }
472 }
473
474 //Here we start each channel thread, with 0 R/T prio.
475 threadStart( *it->second.m_thread, it->second.m_threadInit, it->second.m_threadID, it->second.m_threadProp, 0, "", it->second.m_name, &it->second, channelThreadStart);
476 }
477
478 //Install empty signal handler for USR1, which is used to interrupt sleeps in the channel threads.
479 struct sigaction act;
480 sigset_t set;
481
482 act.sa_sigaction = &sigUsr1Handler;
483 act.sa_flags = SA_SIGINFO;
484 sigemptyset(&set);
485 act.sa_mask = set;
486
487 errno = 0;
488 if( sigaction(SIGUSR1, &act, 0) < 0 )
489 {
490 std::string logss = "Setting handler for SIGUSR1 failed. Errno says: ";
491 logss += strerror(errno);
492
494
495 return -1;
496 }
497
499
500 return 0;
501}
502
504{
505 if( state() == stateCodes::POWERON)
506 {
507 if(!powerOnWaitElapsed()) return 0;
508
510 }
511
513 {
515
516 if(rv == 0)
517 {
520 }
521 else
522 {
523 if(powerState() != 1 || powerStateTarget() != 1) return 0;
524
525 if(!stateLogged())
526 {
527 log<text_log>("Failed to connect on " + m_deviceAddr + ":" + m_devicePort);
528 }
529
530 return 0;
531 }
532
533 }
534
536 {
537
538 std::unique_lock<std::mutex> lock(m_telnetMutex);
539
540 //First check the address 1 controller
541 int rv = m_telnetConn.write("*IDN?\r\n", m_writeTimeout);
542 if(rv != TTY_E_NOERROR)
543 {
544 if(powerState() != 1 || powerStateTarget() != 1) return 0;
547 return 0;
548 }
549
550 rv = m_telnetConn.read("\r\n", m_readTimeout, true);
551 if(rv != TTY_E_NOERROR)
552 {
553 if(powerState() != 1 || powerStateTarget() != 1) return 0;
556 return 0;
557 }
558
559 if(m_telnetConn.m_strRead.find("New_Focus") != std::string::npos)
560 {
561 log<text_log>("Connected to " + m_telnetConn.m_strRead + " at address 1");
562 }
563 else
564 {
565 if(powerState() != 1 || powerStateTarget() != 1) return 0;
566 log<software_error>({__FILE__, __LINE__, "wrong response to IDN query at address 1"});
568 return 0;
569 }
570
571 //Now do a controller scan
572 rv = m_telnetConn.write("SC1\r\n", m_writeTimeout); //Will adjust addresses.
573 if(rv != TTY_E_NOERROR)
574 {
575 if(powerState() != 1 || powerStateTarget() != 1) return 0;
578 return 0;
579 }
580
581 //And now do a motor scan
582 rv = m_telnetConn.write("MC\r\n", m_writeTimeout);
583 if(rv != TTY_E_NOERROR)
584 {
585 if(powerState() != 1 || powerStateTarget() != 1) return 0;
588 return 0;
589 }
590
591 sleep(2); //Give time for controller scan to finish
592
593 for(size_t n = 0; n < m_addresses.size(); ++n)
594 {
595 if(m_addresses[n] == 1) continue; //already done.
596
597 std::string addprefix = std::to_string(m_addresses[n]) + ">";
598
599 int rv = m_telnetConn.write(addprefix + "*IDN?\r\n", m_writeTimeout);
600 if(rv != TTY_E_NOERROR)
601 {
602 if(powerState() != 1 || powerStateTarget() != 1) return 0;
605 return 0;
606 }
607
608 rv = m_telnetConn.read("\r\n", m_readTimeout, true);
609 if(rv != TTY_E_NOERROR)
610 {
611 if(powerState() != 1 || powerStateTarget() != 1) return 0;
614 return 0;
615 }
616
617 int add;
618 std::string resp;
619
621 if(rv != 0)
622 {
623 if(powerState() != 1 || powerStateTarget() != 1) return 0;
624 log<software_error>({__FILE__, __LINE__, "splitResponse returned " + std::to_string(rv)});
626 return 0;
627 }
628
629 if(add != m_addresses[n])
630 {
631 if(powerState() != 1 || powerStateTarget() != 1) return 0;
632 log<software_error>({__FILE__, __LINE__, "address did not match in response"});
634 return 0;
635 }
636
637 if(resp.find("New_Focus") != std::string::npos)
638 {
639 log<text_log>("Connected to " + resp + " at address " + std::to_string(m_addresses[n]));
640 }
641 else
642 {
643 if(powerState() != 1 || powerStateTarget() != 1) return 0;
644 log<software_error>({__FILE__, __LINE__, "wrong response to IDN query at address " + std::to_string(m_addresses[n])});
646 return 0;
647 }
648
649 //Now do a motor scan
651 if(rv != TTY_E_NOERROR)
652 {
653 if(powerState() != 1 || powerStateTarget() != 1) return 0;
656 return 0;
657 }
658 }
659
660 sleep(2); //This is to give time for motor scans to finish
661
662 //Now check for each motor attached
663 for(auto it=m_channels.begin(); it!=m_channels.end();++it)
664 {
665 std::string query = std::to_string(it->second.m_address) + ">" + std::to_string(it->second.m_channel) + "QM?";
666
668 if(rv != TTY_E_NOERROR)
669 {
670 if(powerState() != 1 || powerStateTarget() != 1) return 0;
673 return 0;
674 }
675
676 rv = m_telnetConn.read("\r\n", m_readTimeout, true);
677 if(rv != TTY_E_NOERROR)
678 {
679 if(powerState() != 1 || powerStateTarget() != 1) return 0;
682 return 0;
683 }
684
685 int add;
686 std::string resp;
687
689 if(rv != 0)
690 {
691 if(powerState() != 1 || powerStateTarget() != 1) return 0;
692 log<software_error>({__FILE__, __LINE__, "splitResponse returned " + std::to_string(rv)});
694 return 0;
695 }
696
697 if(add != it->second.m_address)
698 {
699 if(powerState() != 1 || powerStateTarget() != 1) return 0;
700 log<software_error>({__FILE__, __LINE__, "address did not match in response"});
702 return 0;
703 }
704
705 int moType = std::stoi(resp);
706 if(moType == 0)
707 {
708 if(powerState() != 1 || powerStateTarget() != 1) return 0;
709 log<text_log>("No motor connected on channel " + std::to_string(it->second.m_address) + "." + std::to_string(it->second.m_channel) + " [" + it->second.m_name + "]", logPrio::LOG_CRITICAL);
711 return -1;
712 }
713 else if (moType != it->second.m_type)
714 {
715 if(powerState() != 1 || powerStateTarget() != 1) return 0;
716 std::string msg = "Wrong motor type connected on channel " + std::to_string(it->second.m_address) + ".";
717 msg += std::to_string(it->second.m_channel) + " [" + it->second.m_name + "] ";
718 msg += "expected " + std::to_string(it->second.m_type) + " ";
719 msg += "got " + std::to_string(moType);
720
723 return -1;
724 }
725 }
726
728
729 return 0;
730 }
731
733 {
734 //check connection
735 {
736 std::unique_lock<std::mutex> lock(m_telnetMutex);
737
738 int rv = m_telnetConn.write("*IDN?\r\n", m_writeTimeout);
739 if(rv != TTY_E_NOERROR)
740 {
741 if(powerState() != 1 || powerStateTarget() != 1) return 0;
744 return 0;
745 }
746
747 rv = m_telnetConn.read("\r\n", m_readTimeout, true);
748 if(rv != TTY_E_NOERROR)
749 {
750 if(powerState() != 1 || powerStateTarget() != 1) return 0;
753 return 0;
754 }
755
756 if(m_telnetConn.m_strRead.find("New_Focus") == std::string::npos)
757 {
758 if(powerState() != 1 || powerStateTarget() != 1) return 0;
759
760 log<software_error>({__FILE__, __LINE__, "wrong response to IDN query"});
762 return 0;
763 }
764 }
765
766 //Now check state of motors
767 bool anymoving = false;
768
769 //This is where we'd check for moving
770 for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
771 {
772 std::unique_lock<std::mutex> lock(m_telnetMutex);
773
774 std::string query = std::to_string(it->second.m_address) + ">" + std::to_string(it->second.m_channel) + "MD?";
775
776 int rv = m_telnetConn.write(query + "\r\n", m_writeTimeout);
777 if(rv != TTY_E_NOERROR)
778 {
779 if(powerState() != 1 || powerStateTarget() != 1) return 0;
782 return 0;
783 }
784
785 rv = m_telnetConn.read("\r\n", m_readTimeout, true);
786 if(rv != TTY_E_NOERROR)
787 {
788 if(powerState() != 1 || powerStateTarget() != 1) return 0;
791 return 0;
792 }
793
794 int add;
795 std::string resp;
796
798 if(rv != 0)
799 {
800 if(powerState() != 1 || powerStateTarget() != 1) return 0;
801 log<software_error>({__FILE__, __LINE__, "splitResponse returned " + std::to_string(rv)});
803 return 0;
804 }
805
806 if(add != it->second.m_address)
807 {
808 if(powerState() != 1 || powerStateTarget() != 1) return 0;
809 log<software_error>({__FILE__, __LINE__, "address did not match in response"});
811 return 0;
812 }
813
814 //The check for moving here. With power off detection
815 if(std::stoi(resp) == 0)
816 {
817 anymoving = true;
818 it->second.m_moving = true;
819 }
820 else
821 {
822 it->second.m_moving = false;
823 }
824
825 if(it->second.m_moving == false && it->second.m_doMove == true)
826 {
827 it->second.m_currCounts = it->second.m_property["target"].get<long>();
828 log<text_log>("moved " + it->second.m_name + " to " + std::to_string(it->second.m_currCounts) + " counts");
829 it->second.m_doMove = false;
830 recordPico(true);
831 }
832 }
833
834 if(anymoving == false) state(stateCodes::READY);
836
837 for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
838 {
839 std::unique_lock<std::mutex> lock(m_indiMutex);
840 if(it->second.m_moving) updateIfChanged(it->second.m_property, "current", it->second.m_currCounts, INDI_BUSY);
841 else updateIfChanged(it->second.m_property, "current", it->second.m_currCounts, INDI_IDLE);
842
843 for(size_t n=0; n < it->second.m_presetNames.size(); ++n)
844 {
845 bool changed = false;
846 if( it->second.m_currCounts == it->second.m_presetPositions[n])
847 {
848 if(it->second.m_indiP_presetName[it->second.m_presetNames[n]] == pcf::IndiElement::Off) changed = true;
849 it->second.m_indiP_presetName[it->second.m_presetNames[n]] = pcf::IndiElement::On;
850 }
851 else
852 {
853 if(it->second.m_indiP_presetName[it->second.m_presetNames[n]] == pcf::IndiElement::On) changed = true;
854 it->second.m_indiP_presetName[it->second.m_presetNames[n]] = pcf::IndiElement::Off;
855 }
856
857 if(changed) m_indiDriver->sendSetProperty(it->second.m_indiP_presetName);
858 }
859
860 if(writeChannelCounts(it->second.m_name, it->second.m_currCounts) < 0)
861 {
863 }
864 }
865
867
868 return 0;
869 }
870
871
872 return 0;
873}
874
876{
877 return 0;
878}
879
881{
882 return 0;
883}
884
886{
887 //Shutdown and join the threads
888 for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
889 {
890 if(it->second.m_thread->joinable())
891 {
892 pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
893 try
894 {
895 it->second.m_thread->join(); //this will throw if it was already joined
896 }
897 catch(...)
898 {
899 }
900 }
901 }
902
904
905 return 0;
906}
907
909{
910 std::string statusDir = sysPath;
911 statusDir += "/";
913
914 std::string fileName = statusDir + "/" + chName;
915
916 std::ifstream posIn;
917 posIn.open( fileName );
918
919 if(!posIn.good())
920 {
921 log<text_log>("no position file for " + chName + " found. initializing to 0.");
922 return 0;
923 }
924
925 long pos;
926 posIn >> pos;
927
928 posIn.close();
929
930 log<text_log>("initializing " + chName + " to " + std::to_string(pos));
931
932 return pos;
933}
934
936 posT counts
937 )
938{
939 std::string statusDir = sysPath;
940 statusDir += "/";
942
943 std::string fileName = statusDir + "/" + chName;
944
946
947 std::ofstream posOut;
948 posOut.open( fileName );
949
950 if(!posOut.good())
951 {
952 log<text_log>("could not open counts file for " + chName + " -- can not store position.", logPrio::LOG_ERROR);
953 return -1;
954 }
955
956 posOut << counts;
957
958 posOut.close();
959
960 return 0;
961}
962
964{
965 mc->m_parent->channelThreadExec(mc);
966}
967
969{
970 //Get the thread PID immediately so the caller can return.
971 mc->m_threadID = syscall(SYS_gettid);
972
973 //Wait for initialization to complete.
974 while( mc->m_threadInit == true && m_shutdown == 0)
975 {
976 sleep(1);
977 }
978
979 //Now begin checking for state change request.
980 while(!m_shutdown)
981 {
982 //If told to move and not moving, start a move
983 if(mc->m_doMove && !mc->m_moving && (state() == stateCodes::READY || state() == stateCodes::OPERATING))
984 {
985 long dr = mc->m_property["target"].get<long>() - mc->m_currCounts;
986
987 recordPico(true);
988 std::unique_lock<std::mutex> lock(m_telnetMutex);
990 mc->m_moving = true;
991 log<text_log>("moving " + mc->m_name + " by " + std::to_string(dr) + " counts");
992
993 std::string comm = std::to_string(mc->m_address) + ">" + std::to_string(mc->m_channel) + "PR" + std::to_string(dr);
994
995 int rv = m_telnetConn.write(comm + "\r\n", m_writeTimeout);
996 if(rv != TTY_E_NOERROR)
997 {
998 if(powerState() != 1 || powerStateTarget() != 1) //about to get POWEROFF
999 {
1000 sleep(1);
1001 continue;
1002 }
1005 }
1006 }
1007 else if( !(state() == stateCodes::READY || state() == stateCodes::OPERATING))
1008 {
1009 mc->m_doMove = false; //In case a move is requested when not able to move
1010 }
1011
1012 sleep(1);
1013 }
1014
1015
1016}
1017
1018
1020 const pcf::IndiProperty &ipRecv
1021 )
1022{
1023 return static_cast<picoMotorCtrl*>(app)->newCallBack_picopos(ipRecv);
1024}
1025
1026int picoMotorCtrl::newCallBack_picopos( const pcf::IndiProperty &ipRecv )
1027{
1028
1029 //Search for the channel
1030 std::string propName = ipRecv.getName();
1031 size_t nend = propName.rfind("_pos");
1032
1033 if(nend == std::string::npos)
1034 {
1035 log<software_error>({__FILE__, __LINE__, "Channel without _pos received"});
1036 return -1;
1037 }
1038
1039 std::string chName = propName.substr(0, nend);
1040 channelMapT::iterator it = m_channels.find(chName);
1041
1042 if(it == m_channels.end())
1043 {
1044 log<software_error>({__FILE__, __LINE__, "Unknown channel name received"});
1045 return -1;
1046 }
1047
1048 if(it->second.m_doMove == true)
1049 {
1050 log<text_log>("channel " + it->second.m_name + " is already moving", logPrio::LOG_WARNING);
1051 return 0;
1052 }
1053
1054 //Set the target element, and the doMove flag, and then signal the thread.
1055 {//scope for mutex
1056 std::unique_lock<std::mutex> lock(m_indiMutex);
1057
1058 long counts; //not actually used
1059 if(indiTargetUpdate( it->second.m_property, counts, ipRecv, true) < 0)
1060 {
1061 return log<software_error,-1>({__FILE__,__LINE__});
1062 }
1063 }
1064
1065 it->second.m_doMove= true;
1066
1067 pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
1068
1069 return 0;
1070}
1071
1073 const pcf::IndiProperty &ipRecv
1074 )
1075{
1076 return static_cast<picoMotorCtrl*>(app)->newCallBack_presetName (ipRecv);
1077}
1078
1079int picoMotorCtrl::newCallBack_presetName( const pcf::IndiProperty &ipRecv )
1080{
1081 channelMapT::iterator it = m_channels.find(ipRecv.getName());
1082
1083 if(it == m_channels.end())
1084 {
1085 log<software_error>({__FILE__, __LINE__, "Unknown channel name received"});
1086 return -1;
1087 }
1088
1089 if(it->second.m_doMove == true)
1090 {
1091 log<text_log>("channel " + it->second.m_name + " is already moving", logPrio::LOG_WARNING);
1092 return 0;
1093 }
1094
1095 long counts = -1e10;
1096
1097 size_t i;
1098 for(i=0; i< it->second.m_presetNames.size(); ++i)
1099 {
1100 if(!ipRecv.find(it->second.m_presetNames[i])) continue;
1101
1102 if(ipRecv[it->second.m_presetNames[i]].getSwitchState() == pcf::IndiElement::On)
1103 {
1104 if(counts != -1e10)
1105 {
1106 log<text_log>("More than one preset selected", logPrio::LOG_ERROR);
1107 return -1;
1108 }
1109
1110 counts = it->second.m_presetPositions[i];
1111 std::cerr << "selected: " << it->second.m_presetNames[i] << " " << counts << "\n";
1112 }
1113 }
1114
1115 //Set the target element, and the doMove flag, and then signal the thread.
1116 {//scope for mutex
1117 std::unique_lock<std::mutex> lock(m_indiMutex);
1118
1119 it->second.m_property["target"].set(counts);
1120 }
1121
1122 it->second.m_doMove= true;
1123
1124 pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
1125
1126 return 0;
1127}
1128
1133
1135{
1136 return recordPico(true);
1137}
1138
1140{
1141 static std::vector<int64_t> lastpos(m_nChannels, std::numeric_limits<long>::max());
1142
1143 bool changed = false;
1144 for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
1145 {
1146 if(it->second.m_currCounts != lastpos[it->second.m_channel-1]) changed = true;
1147 }
1148
1149 if( changed || force )
1150 {
1151 for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
1152 {
1153 lastpos[it->second.m_channel-1] = it->second.m_currCounts;
1154 }
1155
1157 }
1158
1159 return 0;
1160}
1161
1162} //namespace app
1163} //namespace MagAOX
1164
1165#endif //picoMotorCtrl_hpp
Internal class to manage setuid privilege escalation with RAII.
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.
std::string m_configName
The name of the configuration file (minus .conf).
Definition MagAOXApp.hpp:83
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 createStandardIndiNumber(pcf::IndiProperty &prop, const std::string &name, const T &min, const T &max, const T &step, const std::string &format, const std::string &label="", const std::string &group="")
Create a standard R/W INDI Number property with target and current elements.
int powerState()
Returns the current power state.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
int powerStateTarget()
Returns the target power state.
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.
bool powerOnWaitElapsed()
This method tests whether the power on wait time has elapsed.
std::mutex m_indiMutex
Mutex for locking INDI communications.
int createStandardIndiSelectionSw(pcf::IndiProperty &prop, const std::string &name, const std::vector< std::string > &elements, const std::vector< std::string > &elementLabels, const std::string &label="", const std::string &group="")
Create a standard R/W INDI selection (one of many) switch with vector of elements and element labels.
int threadStart(std::thread &thrd, bool &thrdInit, pid_t &tpid, pcf::IndiProperty &thProp, int thrdPrio, const std::string &cpuset, const std::string &thrdName, thisPtr *thrdThis, Function &&thrdStart)
Start a thread, using this class's privileges to set priority, etc.
std::string sysPath
The path to the system directory, for PID file, etc.
Definition MagAOXApp.hpp:91
int indiTargetUpdate(pcf::IndiProperty &localProperty, T &localTarget, const pcf::IndiProperty &remoteProperty, bool setBusy=true)
Get the target element value from an new property.
int newCallBack_picopos(const pcf::IndiProperty &ipRecv)
The handler function for relative position requests, called by the static callback.
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
int m_nChannels
The number of motor channels total on the hardware. Number of attached motors inferred from config.
virtual int onPowerOff()
Implementation of the on-power-off FSM logic.
tty::telnetConn m_telnetConn
The telnet connection manager.
static int st_newCallBack_presetName(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for position presets.
std::map< std::string, motorChannel > channelMapT
~picoMotorCtrl() noexcept
D'tor, declared and defined for noexcept.
static void channelThreadStart(motorChannel *mc)
Channel thread starter function.
posT readChannelCounts(const std::string &chName)
Read the current channel counts from disk at startup.
std::string m_deviceAddr
The device address.
virtual int appStartup()
Startup functions.
virtual void loadConfig()
load the configuration system results (called by MagAOXApp::setup())
virtual int appLogic()
Implementation of the FSM.
int recordPico(bool force=false)
dev::telemeter< picoMotorCtrl > telemeterT
virtual int whilePowerOff()
Implementation of the while-powered-off FSM.
std::vector< int > m_addresses
The unique controller addresses.
int recordTelem(const telem_pico *)
static int st_newCallBack_picopos(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for relative position requests.
std::string m_devicePort
The device port.
virtual int appShutdown()
Do any needed shutdown tasks.
std::mutex m_telnetMutex
Mutex for locking telnet communications.
int writeChannelCounts(const std::string &chName, posT counts)
virtual void setupConfig()
Setup the configuration system (called by MagAOXApp::setup())
void channelThreadExec(motorChannel *mc)
Channel thread execution function.
int newCallBack_presetName(const pcf::IndiProperty &ipRecv)
The handler function for position presets, called by the static callback.
channelMapT m_channels
Map of motor names to channel.
std::vector< pcf::IndiProperty > m_indiP_counts
@ OPERATING
The device is operating, other than homing.
@ FAILURE
The application has failed, should be used when m_shutdown is set for an error.
@ ERROR
The application has encountered an error, from which it is recovering (with or without intervention)
@ READY
The device is ready for operation, but is not operating.
@ CONNECTED
The application has connected to the device or service.
@ NOTCONNECTED
The application is not connected to the device or service.
@ POWERON
The device power is on.
std::string ttyErrorString(int ec)
Get a text explanation of a TTY_E_ error code.
Definition ttyErrors.cpp:15
#define INDI_IDLE
Definition indiUtils.hpp:28
#define INDI_BUSY
Definition indiUtils.hpp:30
std::stringstream msg
int splitResponse(int &address, std::string &response, const std::string &fullResponse)
void sigUsr1Handler(int signum, siginfo_t *siginf, void *ucont)
Empty signal handler. SIGUSR1 is used to interrupt sleep in various threads.
Definition MagAOXApp.cpp:42
const pcf::IndiProperty & ipRecv
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)
static constexpr logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
static constexpr logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
#define PICOMOTORCTRL_E_INDIREG
#define PICOMOTORCTRL_E_NOMOTORS
#define PICOMOTORCTRL_E_BADCHANNEL
#define PICOMOTORCTRL_E_DUPMOTOR
An input/output capable device.
Definition ioDevice.hpp:27
unsigned m_writeTimeout
The write timeout [msec].
Definition ioDevice.hpp:29
int loadConfig(mx::app::appConfigurator &config)
Load the device section from an application configurator.
Definition ioDevice.cpp:28
int setupConfig(mx::app::appConfigurator &config)
Setup an application configurator for the device section.
Definition ioDevice.cpp:20
unsigned m_readTimeout
The read timeout [msec].
Definition ioDevice.hpp:28
A device base class which saves telemetry.
Definition telemeter.hpp:69
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
picoMotorCtrl * m_parent
A pointer to this for thread starting.
int m_address
The controller address, default is 1.
int m_channel
The number of this channel, where the motor is plugged in.
std::vector< std::string > m_presetNames
bool m_doMove
Flag indicating that a move is requested.
pcf::IndiProperty m_threadProp
The property to hold the thread details.
posT m_currCounts
The current counts, the cumulative position.
bool m_threadInit
Thread initialization flag.
motorChannel(picoMotorCtrl *p, const std::string &n, int add, int ch, int type)
std::string m_name
The name of this channel, from the config section.
bool m_moving
Flag to indicate that we are actually moving.
std::thread * m_thread
Thread for managing this channel. A pointer to allow copying, but must be deleted in d'tor of parent.
int m_type
The motor type of this channel, default is 3.
Software ERR log entry.
Log entry recording CPU temperatures.
A Telnet connection manager, wrapping libtelnet.
int write(const std::string &buffWrite, int timeoutWrite)
Write to a telnet connection.
std::string m_strRead
The accumulated string read from the device.
int noLogin()
Set flags as if we're logged in, used when device doesn't require it.
std::string m_prompt
The device's prompt, used for detecting end of transmission.
int read(const std::string &eot, int timeoutRead, bool clear=true)
Read from a telnet connection, until end-of-transmission string is read.
int connect(const std::string &host, const std::string &port)
Connect to the device.
#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.
#define TTY_E_NOERROR
Definition ttyErrors.hpp:15