API
zaberLowLevel.hpp
Go to the documentation of this file.
1 /** \file zaberLowLevel.hpp
2  * \brief The MagAO-X Low-Level Zaber Controller
3  *
4  * \ingroup zaberLowLevel_files
5  */
6 
7 #ifndef zaberLowLevel_hpp
8 #define zaberLowLevel_hpp
9 
10 #include <iostream>
11 
12 
13 #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
14 #include "../../magaox_git_version.h"
15 
16 typedef MagAOX::app::MagAOXApp<true> MagAOXAppT; //This needs to be before zaberStage.hpp for logging to work.
17 
18 #include "zaberUtils.hpp"
19 #include "zaberStage.hpp"
20 #include "za_serial.h"
21 
22 #define ZC_CONNECTED (0)
23 #define ZC_ERROR (-1)
24 #define ZC_NOT_CONNECTED (10)
25 
26 /** \defgroup zaberLowLevel low-level zaber controller
27  * \brief The low-level interface to a set of chained Zaber stages
28  *
29  * <a href="../handbook/operating/software/apps/zaberLowLevel.html">Application Documentation</a>
30  *
31  * \ingroup apps
32  *
33  */
34 
35 /** \defgroup zaberLowLevel_files zaber low-level files
36  * \ingroup zaberLowLevel
37  */
38 
39 namespace MagAOX
40 {
41 namespace app
42 {
43 
44 class zaberLowLevel : public MagAOXAppT, public tty::usbDevice
45 {
46 
47  //Give the test harness access.
48  friend class zaberLowLevel_test;
49 
50 
51 protected:
52 
53  int m_numStages {0};
54 
55  z_port m_port {0};
56 
57  std::vector<zaberStage<zaberLowLevel>> m_stages;
58 
59  std::unordered_map<int, size_t> m_stageAddress;
60  std::unordered_map<std::string, size_t> m_stageSerial;
61  std::unordered_map<std::string, size_t> m_stageName;
62 
63 public:
64  /// Default c'tor.
65  zaberLowLevel();
66 
67  /// D'tor, declared and defined for noexcept.
68  ~zaberLowLevel() noexcept
69  {}
70 
71  virtual void setupConfig();
72 
73  virtual void loadConfig();
74 
75  int connect();
76 
77  int loadStages( std:: string & serialRes );
78 
79  /// Startup functions
80  /** Sets up the INDI vars.
81  *
82  */
83  virtual int appStartup();
84 
85  /// Implementation of the FSM for zaberLowLevel.
86  virtual int appLogic();
87 
88  /// Implementation of the on-power-off FSM logic
89  virtual int onPowerOff();
90 
91  /// Implementation of the while-powered-off FSM
92  virtual int whilePowerOff();
93 
94  /// Do any needed shutdown tasks. Currently nothing in this app.
95  virtual int appShutdown();
96 
97 protected:
98  ///Current state of the stage.
99  pcf::IndiProperty m_indiP_curr_state;
100 
101  ///Maximum raw position of the stage.
102  pcf::IndiProperty m_indiP_max_pos;
103 
104  ///Current raw position of the stage.
105  pcf::IndiProperty m_indiP_curr_pos;
106 
107  ///Current temperature of the stage.
108  pcf::IndiProperty m_indiP_temp;
109 
110  ///Current temperature of the stage.
111  pcf::IndiProperty m_indiP_warn;
112 
113  ///Target raw position of the stage.
114  pcf::IndiProperty m_indiP_tgt_pos;
115 
116  ///Target relative position of the stage.
117  pcf::IndiProperty m_indiP_tgt_relpos;
118 
119  ///Command a stage to home.
120  pcf::IndiProperty m_indiP_req_home;
121 
122  ///Command a stage to safely halt.
123  pcf::IndiProperty m_indiP_req_halt;
124 
125  ///Command a stage to safely immediately halt.
126  pcf::IndiProperty m_indiP_req_ehalt;
127 
128 public:
134 
135 };
136 
137 zaberLowLevel::zaberLowLevel() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
138 {
139  m_powerMgtEnabled = true;
140 
141  return;
142 }
143 
145 {
147 
148 
149 }
150 
152 {
153 
154  this->m_baudRate = B115200; //default for Zaber stages. Will be overridden by any config setting.
155 
156  int rv = tty::usbDevice::loadConfig(config);
157 
158  if(rv != 0 && rv != TTY_E_NODEVNAMES && rv != TTY_E_DEVNOTFOUND) //Ignore error if not plugged in
159  {
160  log<software_error>( {__FILE__, __LINE__, rv, tty::ttyErrorString(rv)});
161  }
162 
163  std::vector<std::string> sections;
164 
165  config.unusedSections(sections);
166 
167  if(sections.size() == 0)
168  {
169  log<software_error>({__FILE__, __LINE__, "No stages found"});
170  return;
171  }
172 
173  for(size_t n=0; n<sections.size(); ++n)
174  {
175  if(config.isSetUnused(mx::app::iniFile::makeKey(sections[n], "serial" )))
176  {
177  m_stages.push_back(zaberStage<zaberLowLevel>(this));
178 
179  size_t idx = m_stages.size()-1;
180 
181  m_stages[idx].name(sections[n]);
182 
183 
184  //Get serial number from config.
185  std::string tmp = m_stages[idx].serial(); //get default
186  config.configUnused( tmp , mx::app::iniFile::makeKey(sections[n], "serial" ) );
187  m_stages[idx].serial(tmp);
188 
189  m_stageName.insert( {m_stages[idx].name(), idx});
190  m_stageSerial.insert( {m_stages[idx].serial(), idx});
191  }
192  }
193 }
194 
196 {
197  if(m_port > 0)
198  {
199  int rv = za_disconnect(m_port);
200  if(rv < 0)
201  {
202  log<text_log>("Error disconnecting from zaber system.", logPrio::LOG_ERROR);
203  }
204  m_port = 0;
205  }
206 
207 
208  if(m_port <= 0)
209  {
210 
211  int zrv;
212 
213  {//scope for elPriv
214  elevatedPrivileges elPriv(this);
215  zrv = za_connect(&m_port, m_deviceName.c_str());
216  }
217 
218  if(zrv != Z_SUCCESS)
219  {
220  if(m_port > 0)
221  {
223  m_port = 0;
224  }
225 
226  if(!stateLogged())
227  {
228  log<software_error>({__FILE__, __LINE__, "can not connect to zaber stage(s)"});
229  }
230 
231  return ZC_NOT_CONNECTED; //We aren't connected.
232  }
233  }
234 
235  if(m_port <= 0)
236  {
237  //state(stateCodes::ERROR); //Should not get this here. Probably means no device.
238  log<text_log>("can not connect to zaber stage(s): no port", logPrio::LOG_WARNING);
239  return ZC_NOT_CONNECTED; //We aren't connected.
240  }
241 
242  log<text_log>("DRAINING", logPrio::LOG_DEBUG);
243 
244  int rv = za_drain(m_port);
245 
246  if(rv != Z_SUCCESS)
247  {
248  log<software_error>({__FILE__,__LINE__, rv, "error from za_drain"});
250  return ZC_ERROR;
251  }
252 
253  char buffer[256];
254 
255  //===== First renumber so they are unique.
256  log<text_log>("Sending: / renumber", logPrio::LOG_DEBUG);
257  std::string renum = "/ renumber";
258  int nwr = za_send(m_port, renum.c_str(), renum.size());
259 
260  if(nwr == Z_ERROR_SYSTEM_ERROR)
261  {
262  log<text_log>("Error sending renumber query to stages", logPrio::LOG_ERROR);
264  return ZC_ERROR;
265  }
266 
267  //===== Drain the result
268  log<text_log>("DRAINING", logPrio::LOG_DEBUG);
269 
270  rv = za_drain(m_port);
271 
272  if(rv != Z_SUCCESS)
273  {
274  log<software_error>({__FILE__,__LINE__, rv, "error from za_drain"});
276  return ZC_ERROR;
277  }
278 
279  //======= Now find the stages
280  log<text_log>("Sending: / get system.serial", logPrio::LOG_DEBUG);
281  std::string gss = "/ get system.serial";
282  nwr = za_send(m_port, gss.c_str(), gss.size() );
283 
284  if(nwr == Z_ERROR_SYSTEM_ERROR)
285  {
286  log<text_log>("Error sending system.serial query to stages", logPrio::LOG_ERROR);
288  return ZC_ERROR;
289  }
290 
291  std::string serialRes;
292  while(1)
293  {
294  int nrd = za_receive(m_port, buffer, sizeof(buffer));
295  if(nrd >= 0 )
296  {
297  buffer[nrd] = '\0';
298  log<text_log>(std::string("Received: ")+buffer, logPrio::LOG_DEBUG);
299  serialRes += buffer;
300  }
301  else if( nrd != Z_ERROR_TIMEOUT)
302  {
303  log<text_log>("Error receiving from stages", logPrio::LOG_ERROR);
305  return ZC_ERROR;
306  }
307  else
308  {
309  log<text_log>("TIMEOUT", logPrio::LOG_DEBUG);
310  break; //Timeout ok.
311  }
312  }
313 
314  return loadStages( serialRes );
315 }
316 
317 int zaberLowLevel::loadStages( std::string & serialRes )
318 {
319  std::vector<int> addresses;
320  std::vector<std::string> serials;
321 
322  int rv = parseSystemSerial( addresses, serials, serialRes );
323  if( rv < 0)
324  {
325  log<software_error>({__FILE__, __LINE__, errno, rv, "error in parseSystemSerial"});
327  return ZC_ERROR;
328  }
329  else
330  {
331  log<text_log>("Found " + std::to_string(addresses.size()) + " stages.");
332  m_stageAddress.clear(); //We clear this map before re-populating.
333  for(size_t n=0;n<addresses.size(); ++n)
334  {
335  if( m_stageSerial.count( serials[n] ) == 1)
336  {
337  m_stages[m_stageSerial[serials[n]]].deviceAddress(addresses[n]);
338 
339  m_stageAddress.insert({ addresses[n], m_stageSerial[serials[n]]});
340  log<text_log>("stage @" + std::to_string(addresses[n]) + " with s/n " + serials[n] + " corresponds to " + m_stages[m_stageSerial[serials[n]]].name());
341  }
342  else
343  {
344  log<text_log>("Unkown stage @" + std::to_string(addresses[n]) + " with s/n " + serials[n], logPrio::LOG_WARNING);
345  }
346  }
347 
348  for(size_t n=0; n < m_stages.size(); ++n)
349  {
350  if(m_stages[n].deviceAddress() < 1)
351  {
352  log<text_log>("stage " + m_stages[n].name() + " with with s/n " + serials[n] + " not found in system.", logPrio::LOG_ERROR);
353  state(state(), true);
354  }
355  }
356  }
357 
358  return ZC_CONNECTED;
359 }
360 
361 
363 {
365  {
366  log<text_log>( "In appStartup but in state UNINITIALIZED.", logPrio::LOG_CRITICAL );
367  return -1;
368  }
369 
370  if( m_stages.size() == 0 )
371  {
372  log<text_log>( "No stages configured.", logPrio::LOG_CRITICAL);
373  return -1;
374  }
375 
376  REG_INDI_NEWPROP_NOCB(m_indiP_curr_state, "curr_state", pcf::IndiProperty::Text);
377  for(size_t n=0; n< m_stages.size(); ++n)
378  {
379  m_indiP_curr_state.add (pcf::IndiElement(m_stages[n].name()));
380  }
381 
382  REG_INDI_NEWPROP_NOCB(m_indiP_max_pos, "max_pos", pcf::IndiProperty::Text);
383  for(size_t n=0; n< m_stages.size(); ++n)
384  {
385  m_indiP_max_pos.add (pcf::IndiElement(m_stages[n].name()));
386  m_indiP_max_pos[m_stages[n].name()] = -1;
387  }
388 
389  REG_INDI_NEWPROP_NOCB(m_indiP_curr_pos, "curr_pos", pcf::IndiProperty::Number);
390  for(size_t n=0; n< m_stages.size(); ++n)
391  {
392  m_indiP_curr_pos.add (pcf::IndiElement(m_stages[n].name()));
393  }
394 
395  REG_INDI_NEWPROP_NOCB(m_indiP_temp, "temp", pcf::IndiProperty::Number);
396  for(size_t n=0; n< m_stages.size(); ++n)
397  {
398  m_indiP_temp.add (pcf::IndiElement(m_stages[n].name()));
399  }
400 
401  REG_INDI_NEWPROP_NOCB(m_indiP_warn, "warning", pcf::IndiProperty::Switch);
402  m_indiP_warn.setRule(pcf::IndiProperty::AnyOfMany);
403  for(size_t n=0; n< m_stages.size(); ++n)
404  {
405  m_indiP_warn.add (pcf::IndiElement(m_stages[n].name()));
406  m_indiP_warn[m_stages[n].name()].setSwitchState(pcf::IndiElement::Off);
407  }
408 
409  REG_INDI_NEWPROP(m_indiP_tgt_pos, "tgt_pos", pcf::IndiProperty::Number);
410  for(size_t n=0; n< m_stages.size(); ++n)
411  {
412  m_indiP_tgt_pos.add (pcf::IndiElement(m_stages[n].name()));
413  }
414 
415  /*--> Kill this */
416  REG_INDI_NEWPROP(m_indiP_tgt_relpos, "tgt_relpos", pcf::IndiProperty::Number);
417  for(size_t n=0; n< m_stages.size(); ++n)
418  {
419  m_indiP_tgt_relpos.add (pcf::IndiElement(m_stages[n].name()));
420  }
421 
422  /*--> Make a switch */
423  REG_INDI_NEWPROP(m_indiP_req_home, "req_home", pcf::IndiProperty::Number);
424  for(size_t n=0; n< m_stages.size(); ++n)
425  {
426  m_indiP_req_home.add (pcf::IndiElement(m_stages[n].name()));
427  }
428 
429  /*--> Make a switch */
430  REG_INDI_NEWPROP(m_indiP_req_halt, "req_halt", pcf::IndiProperty::Number);
431  for(size_t n=0; n< m_stages.size(); ++n)
432  {
433  m_indiP_req_halt.add (pcf::IndiElement(m_stages[n].name()));
434  }
435 
436  /*--> Make a switch */
437  REG_INDI_NEWPROP(m_indiP_req_ehalt, "req_ehalt", pcf::IndiProperty::Number);
438  for(size_t n=0; n< m_stages.size(); ++n)
439  {
440  m_indiP_req_ehalt.add (pcf::IndiElement(m_stages[n].name()));
441  }
442 
443  return 0;
444 }
445 
447 {
448  if( state() == stateCodes::INITIALIZED )
449  {
450  log<text_log>( "In appLogic but in state INITIALIZED.", logPrio::LOG_CRITICAL );
451  return -1;
452  }
453 
454  if( state() == stateCodes::POWERON)
455  {
457  for(size_t i=0; i < m_stages.size();++i)
458  {
459  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("NODEVICE"));
460  }
461  }
462 
463  if( state() == stateCodes::NODEVICE )
464  {
466  if(rv < 0 && rv != TTY_E_DEVNOTFOUND && rv != TTY_E_NODEVNAMES)
467  {
468  if( powerState() != 1 || powerStateTarget() != 1 ) return 0; //means we're powering off
469 
471  if(!stateLogged())
472  {
473  log<software_critical>({__FILE__, __LINE__, rv, tty::ttyErrorString(rv)});
474  }
475  return -1;
476  }
477 
478  if(rv == TTY_E_DEVNOTFOUND || rv == TTY_E_NODEVNAMES)
479  {
481 
482  if(!stateLogged())
483  {
484  std::stringstream logs;
485  logs << "USB Device " << m_idVendor << ":" << m_idProduct << ":" << m_serial << " not found in udev";
486  log<text_log>(logs.str());
487  }
488  return 0;
489  }
490  else
491  {
492  std::stringstream logs;
493  logs << "USB Device " << m_idVendor << ":" << m_idProduct << ":" << m_serial << " found in udev as " << m_deviceName;
494  log<text_log>(logs.str());
495 
497 
498  for(size_t i=0; i < m_stages.size();++i)
499  {
500  if(m_stages[i].deviceAddress() < 1) continue;
501  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("NOTCONNECTED"));
502  }
503 
504  return 0; //we return to give the stage time to initialize the connection if this is a USB-FTDI power on/plug-in event.
505  }
506 
507  }
508 
510  {
511  std::lock_guard<std::mutex> guard(m_indiMutex);
512 
513  int rv = connect();
514 
515  if( rv == ZC_CONNECTED)
516  {
518  for(size_t i=0; i < m_stages.size();++i)
519  {
520  if(m_stages[i].deviceAddress() < 1) continue;
521  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("CONNECTED"));
522  }
523 
524  if(!stateLogged())
525  {
526  log<text_log>("Connected to stage(s) on " + m_deviceName);
527  }
528  }
529  else if(rv == ZC_NOT_CONNECTED)
530  {
531  return 0;
532  }
533  else
534  {
535 
536  }
537  }
538 
539  if( state() == stateCodes::CONNECTED )
540  {
541  for(size_t i=0; i < m_stages.size();++i)
542  {
543  if(m_stages[i].deviceAddress() < 1)
544  {
545  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("NODEVICE"));
546  continue; //Skip configured but not found stage
547  }
548  std::lock_guard<std::mutex> guard(m_indiMutex); //Inside loop so INDI requests can steal it
549 
550  m_stages[i].getMaxPos(m_port);
551  std::cerr << i << " " << m_stages[i].name() << " " << m_stages[i].maxPos() << "\n";
552  updateIfChanged(m_indiP_max_pos, m_stages[i].name(), m_stages[i].maxPos());
553 
554  //Get warnings so first pass through has correct state for home/not-homed
555  if(m_stages[i].getWarnings(m_port) < 0)
556  {
557  if( powerState() != 1 || powerStateTarget() != 1 ) return 0; //means we're powering off
558  log<software_error>({__FILE__, __LINE__});
560  return 0;
561  }
562 
563  }
565 
566  return 0;
567  }
568 
569  if( state() == stateCodes::READY )
570  {
571 
572  //Here we check complete stage state.
573  for(size_t i=0; i < m_stages.size();++i)
574  {
575  if(m_stages[i].deviceAddress() < 1) continue; //Skip configured but not found stage
576 
577  std::lock_guard<std::mutex> guard(m_indiMutex); //Inside loop so INDI requests can steal it
578 
579  m_stages[i].updatePos(m_port);
580 
581  updateIfChanged(m_indiP_curr_pos, m_stages[i].name(), m_stages[i].rawPos());
582 
583  if(m_stages[i].rawPos() == m_stages[i].tgtPos())
584  {
585  updateIfChanged(m_indiP_tgt_pos, m_stages[i].name(), std::string(""));
586  }
587  else
588  {
589  updateIfChanged(m_indiP_tgt_pos, m_stages[i].name(), m_stages[i].tgtPos());
590  }
591 
592 
593  if(m_stages[i].deviceStatus() == 'B')
594  {
595  if(m_stages[i].homing())
596  {
597  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("HOMING"));
598  }
599  else
600  {
601  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("OPERATING"));
602  }
603  }
604  else if(m_stages[i].deviceStatus() == 'I')
605  {
606  if(m_stages[i].homing())
607  {
608  std::cerr << __FILE__ << " " << __LINE__ << "\n";
609  return 0;
610  }
611  if(m_stages[i].warnWR())
612  {
613  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("NOTHOMED"));
614  }
615  else
616  {
617  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("READY"));
618  }
619  }
620  else updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("NODEVICE"));
621 
622  if(m_stages[i].warn())
623  {
624  updateIfChanged(m_indiP_warn, m_stages[i].name(), pcf::IndiElement::On);
625  }
626  else
627  {
628  updateIfChanged(m_indiP_warn, m_stages[i].name(), pcf::IndiElement::Off);
629  }
630 
631  m_stages[i].updateTemp(m_port);
632  updateIfChanged(m_indiP_temp, m_stages[i].name(), m_stages[i].temp());
633 
634  if(m_stages[i].getWarnings(m_port) < 0)
635  {
636  if( powerState() != 1 || powerStateTarget() != 1 ) return 0; //means we're powering off
637  log<software_error>({__FILE__, __LINE__});
639  return 0;
640  }
641 
642  }
643  }
644 
645  if( state() == stateCodes::ERROR )
646  {
648  if(rv < 0 && rv != TTY_E_DEVNOTFOUND && rv != TTY_E_NODEVNAMES)
649  {
650  if( powerState() != 1 || powerStateTarget() != 1 ) return 0; //means we're powering off
652  for(size_t i=0; i < m_stages.size();++i)
653  {
654  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("FAILURE"));
655  }
656  if(!stateLogged())
657  {
658  log<software_critical>({__FILE__, __LINE__, rv, tty::ttyErrorString(rv)});
659  }
660  return rv;
661  }
662 
663  if(rv == TTY_E_DEVNOTFOUND || rv == TTY_E_NODEVNAMES)
664  {
665  if( powerState() != 1 || powerStateTarget() != 1 ) return 0; //means we're powering off
667  for(size_t i=0; i < m_stages.size();++i)
668  {
669  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("NODEVICE"));
670  }
671 
672  if(!stateLogged())
673  {
674  std::stringstream logs;
675  logs << "USB Device " << m_idVendor << ":" << m_idProduct << ":" << m_serial << " not found in udev";
676  log<text_log>(logs.str());
677  }
678  return 0;
679  }
680 
681  if( powerState() != 1 || powerStateTarget() != 1 ) return 0; //means we're powering off
683  for(size_t i=0; i < m_stages.size();++i)
684  {
685  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("FAILURE"));
686  }
687 
688  log<software_critical>({__FILE__, __LINE__});
689  log<text_log>("Error NOT due to loss of USB connection. I can't fix it myself.", logPrio::LOG_CRITICAL);
690  }
691 
692 
693 
694  if( powerState() != 1 || powerStateTarget() != 1 ) return 0; //means we're powering off
695 
696  if( state() == stateCodes::FAILURE )
697  {
698  return -1;
699  }
700 
701  return 0;
702 }
703 
704 inline
706 {
707  int rv = za_disconnect(m_port);
708  if(rv < 0)
709  {
710  log<text_log>("Error disconnecting from zaber system.", logPrio::LOG_ERROR);
711  }
712 
713  m_port = 0;
714 
715  std::lock_guard<std::mutex> lock(m_indiMutex);
716 
717  for(size_t i=0; i < m_stages.size();++i)
718  {
719  updateIfChanged(m_indiP_tgt_pos, m_stages[i].name(), std::string(""));
720  updateIfChanged(m_indiP_tgt_relpos, m_stages[i].name(), std::string(""));
721  updateIfChanged(m_indiP_temp, m_stages[i].name(), std::string(""));
722 
723  m_stages[i].onPowerOff();
724 
725  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("POWEROFF"));
726 
727  updateIfChanged(m_indiP_warn, m_stages[i].name(), pcf::IndiElement::Off);
728  }
729  return 0;
730 }
731 
732 inline
734 {
735  return 0;
736 }
737 
738 inline
740 {
741  for(size_t i=0; i < m_stages.size();++i)
742  {
743  if(m_stages[i].deviceAddress() < 1) continue;
744  updateIfChanged(m_indiP_curr_state, m_stages[i].name(), std::string("NODEVICE"));
745  }
746 
747  return 0;
748 }
749 
750 INDI_NEWCALLBACK_DEFN(zaberLowLevel, m_indiP_tgt_pos)(const pcf::IndiProperty &ipRecv)
751 {
752  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_tgt_pos, ipRecv);
753 
754  for(size_t n=0; n < m_stages.size(); ++n)
755  {
756  if( ipRecv.find(m_stages[n].name()) )
757  {
758  long tgt = ipRecv[m_stages[n].name()].get<long>();
759  if(tgt >= 0)
760  {
761  if(m_stages[n].deviceAddress() < 1)
762  {
763  return log<software_error,-1>({__FILE__, __LINE__, "stage " + m_stages[n].name() + " with with s/n " + m_stages[n].serial() + " not found in system."});
764  }
765 
766  std::lock_guard<std::mutex> guard(m_indiMutex);
767  updateIfChanged(m_indiP_curr_state, m_stages[n].name(), std::string("OPERATING"));
768  return m_stages[n].moveAbs(m_port, tgt);
769  }
770  }
771  }
772 
773  return 0;
774 }
775 
776 INDI_NEWCALLBACK_DEFN(zaberLowLevel, m_indiP_tgt_relpos)(const pcf::IndiProperty &ipRecv)
777 {
778  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_tgt_relpos, ipRecv);
779 
780  for(size_t n=0; n < m_stages.size(); ++n)
781  {
782  if( ipRecv.find(m_stages[n].name()) )
783  {
784  long tgt = ipRecv[m_stages[n].name()].get<long>();
785  tgt += m_stages[n].rawPos();
786  if(tgt >= 0)
787  {
788  if(m_stages[n].deviceAddress() < 1)
789  {
790  return log<software_error,-1>({__FILE__, __LINE__, "stage " + m_stages[n].name() + " with with s/n " + m_stages[n].serial() + " not found in system."});
791  }
792  std::lock_guard<std::mutex> guard(m_indiMutex);
793 
794  updateIfChanged(m_indiP_curr_state, m_stages[n].name(), std::string("OPERATING"));
795  return m_stages[n].moveAbs(m_port, tgt);
796  }
797  }
798  }
799 
800  return 0;
801 }
802 
803 INDI_NEWCALLBACK_DEFN(zaberLowLevel, m_indiP_req_home)(const pcf::IndiProperty &ipRecv)
804 {
805  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_req_home, ipRecv);
806 
807  for(size_t n=0; n < m_stages.size(); ++n)
808  {
809  if( ipRecv.find(m_stages[n].name()) )
810  {
811  int tgt = ipRecv[m_stages[n].name()].get<int>();
812  if(tgt > 0)
813  {
814  if(m_stages[n].deviceAddress() < 1)
815  {
816  return log<software_error,-1>({__FILE__, __LINE__, "stage " + m_stages[n].name() + " with with s/n " + m_stages[n].serial() + " not found in system."});
817  }
818  std::lock_guard<std::mutex> guard(m_indiMutex);
819  return m_stages[n].home(m_port);
820  }
821  }
822  }
823 
824  return 0;
825 }
826 
827 INDI_NEWCALLBACK_DEFN(zaberLowLevel, m_indiP_req_halt)(const pcf::IndiProperty &ipRecv)
828 {
829  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_req_halt, ipRecv);
830 
831 
832  for(size_t n=0; n < m_stages.size(); ++n)
833  {
834  if( ipRecv.find(m_stages[n].name()) )
835  {
836  int tgt = ipRecv[m_stages[n].name()].get<int>();
837  if(tgt > 0)
838  {
839  if(m_stages[n].deviceAddress() < 1)
840  {
841  return log<software_error,-1>({__FILE__, __LINE__, "stage " + m_stages[n].name() + " with with s/n " + m_stages[n].serial() + " not found in system."});
842  }
843 
844  std::lock_guard<std::mutex> guard(m_indiMutex);
845  return m_stages[n].stop(m_port);
846  }
847  }
848  }
849 
850  return 0;
851 }
852 
853 INDI_NEWCALLBACK_DEFN(zaberLowLevel, m_indiP_req_ehalt)(const pcf::IndiProperty &ipRecv)
854 {
855  INDI_VALIDATE_CALLBACK_PROPS(m_indiP_req_ehalt, ipRecv);
856 
857  for(size_t n=0; n < m_stages.size(); ++n)
858  {
859  if( ipRecv.find(m_stages[n].name()) )
860  {
861  int tgt = ipRecv[m_stages[n].name()].get<int>();
862  if(tgt > 0)
863  {
864  if(m_stages[n].deviceAddress() < 1)
865  {
866  return log<software_error,-1>({__FILE__, __LINE__, "stage " + m_stages[n].name() + " with with s/n " + m_stages[n].serial() + " not found in system."});
867  }
868 
869  std::lock_guard<std::mutex> guard(m_indiMutex);
870  return m_stages[n].estop(m_port);
871  }
872  }
873  }
874 
875  return 0;
876 }
877 
878 } //namespace app
879 } //namespace MagAOX
880 
881 #endif //zaberLowLevel_hpp
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.
Definition: MagAOXApp.hpp:3120
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2297
int powerState()
Returns the current power state.
Definition: MagAOXApp.hpp:3414
int powerStateTarget()
Returns the target power state.
Definition: MagAOXApp.hpp:3423
int stateLogged()
Updates and returns the value of m_stateLogged. Will be 0 on first call after a state change,...
Definition: MagAOXApp.hpp:2361
std::mutex m_indiMutex
Mutex for locking INDI communications.
Definition: MagAOXApp.hpp:545
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_req_ehalt)
pcf::IndiProperty m_indiP_req_home
Command a stage to home.
pcf::IndiProperty m_indiP_warn
Current temperature of the stage.
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_req_halt)
virtual int appLogic()
Implementation of the FSM for zaberLowLevel.
pcf::IndiProperty m_indiP_max_pos
Maximum raw position of the stage.
pcf::IndiProperty m_indiP_req_halt
Command a stage to safely halt.
virtual int onPowerOff()
Implementation of the on-power-off FSM logic.
pcf::IndiProperty m_indiP_tgt_pos
Target raw position of the stage.
pcf::IndiProperty m_indiP_temp
Current temperature of the stage.
std::unordered_map< std::string, size_t > m_stageSerial
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_tgt_relpos)
std::unordered_map< int, size_t > m_stageAddress
pcf::IndiProperty m_indiP_curr_state
Current state of the stage.
int loadStages(std::string &serialRes)
virtual int whilePowerOff()
Implementation of the while-powered-off FSM.
pcf::IndiProperty m_indiP_curr_pos
Current raw position of the stage.
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_tgt_pos)
virtual int appStartup()
Startup functions.
virtual int appShutdown()
Do any needed shutdown tasks. Currently nothing in this app.
std::vector< zaberStage< zaberLowLevel > > m_stages
pcf::IndiProperty m_indiP_tgt_relpos
Target relative position of the stage.
~zaberLowLevel() noexcept
D'tor, declared and defined for noexcept.
std::unordered_map< std::string, size_t > m_stageName
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_req_home)
pcf::IndiProperty m_indiP_req_ehalt
Command a stage to safely immediately halt.
A class to manage the details of one stage in a Zaber system.
Definition: zaberStage.hpp:29
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
Definition: indiMacros.hpp:248
#define REG_INDI_NEWPROP(prop, propName, type)
Register a NEW INDI property with the class, using the standard callback name.
Definition: indiMacros.hpp:230
@ NODEVICE
No device exists for the application to control.
Definition: stateCodes.hpp:46
@ FAILURE
The application has failed, should be used when m_shutdown is set for an error.
Definition: stateCodes.hpp:42
@ ERROR
The application has encountered an error, from which it is recovering (with or without intervention)
Definition: stateCodes.hpp:43
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:56
@ CONNECTED
The application has connected to the device or service.
Definition: stateCodes.hpp:50
@ UNINITIALIZED
The application is unitialized, the default.
Definition: stateCodes.hpp:44
@ INITIALIZED
The application has been initialized, set just before calling appStartup().
Definition: stateCodes.hpp:45
@ NOTCONNECTED
The application is not connected to the device or service.
Definition: stateCodes.hpp:49
@ POWERON
The device power is on.
Definition: stateCodes.hpp:48
std::string ttyErrorString(int ec)
Get a text explanation of a TTY_E_ error code.
Definition: ttyErrors.cpp:15
int parseSystemSerial(std::vector< int > &address, std::vector< std::string > &serial, const std::string &response)
Parse the system.serial query.
Definition: zaberUtils.hpp:30
std::ostream & cerr()
INDI_VALIDATE_CALLBACK_PROPS(function, ipRecv)
const pcf::IndiProperty & ipRecv
Definition: MagAOXApp.hpp:3434
updateIfChanged(m_indiP_angle, "target", m_angle)
INDI_NEWCALLBACK_DEFN(acesxeCtrl, m_indiP_windspeed)(const pcf
Definition: acesxeCtrl.hpp:687
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition: dm.hpp:24
constexpr static logPrioT LOG_DEBUG
Used for debugging.
Definition: logPriority.hpp:52
constexpr static logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
Definition: logPriority.hpp:37
constexpr static logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
Definition: logPriority.hpp:40
constexpr static logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
Definition: logPriority.hpp:43
Software ERR log entry.
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 getDeviceName()
Get the device name from udev using the vendor, product, and serial number.
Definition: usbDevice.cpp:103
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
@ Z_ERROR_SYSTEM_ERROR
Definition: z_common.h:67
@ Z_SUCCESS
Definition: z_common.h:65
@ Z_ERROR_TIMEOUT
Definition: z_common.h:78
int za_connect(z_port *port, const char *port_name)
Definition: za_serial.c:53
int za_receive(z_port port, char *destination, int length)
Definition: za_serial.c:151
int za_send(z_port port, const char *command, size_t sMaxSz)
Definition: za_serial.c:112
int za_drain(z_port port)
Definition: za_serial.c:235
int za_disconnect(z_port port)
Definition: za_serial.c:99
Provides a set of functions for interacting with Zaber devices in the ASCII protocol.
#define ZC_NOT_CONNECTED
#define ZC_ERROR
#define ZC_CONNECTED
MagAOX::app::MagAOXApp< true > MagAOXAppT
A class with details of a single zaber stage.
utilties for working with zaber stages