API
 
Loading...
Searching...
No Matches
xt1121Ctrl.hpp
Go to the documentation of this file.
1/** \file xt1121Ctrl.hpp
2 * \brief The MagAO-X Acromag XT 1121digital I/O controller.
3 *
4 * \author Jared R. Males (jaredmales@gmail.com)
5 *
6 * \ingroup xt1121Ctrl_files
7 */
8
9#ifndef xt1121Ctrl_hpp
10#define xt1121Ctrl_hpp
11
12#include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
13#include "../../magaox_git_version.h"
14
15#include "xtChannels.hpp"
16
17namespace MagAOX
18{
19namespace app
20{
21
22/** \defgroup xt1121Ctrl Acromag xt1121Controller
23 * \brief Control of an Acromag xt1121digital I/O module
24 *
25 * <a href="../handbook/operating/software/apps/xt1121Ctrl.html">Application Documentation</a>
26 *
27 * \ingroup apps
28 *
29 */
30
31/** \defgroup xt1121Ctrl_files Acromag xt1121Controller Files
32 * \ingroup xt1121Ctrl
33 */
34
35/** MagAO-X application to control an Acromag xt1121digital i/o module
36 *
37 * \ingroup xt1121Ctrl
38 *
39 */
40class xt1121Ctrl : public MagAOXApp<>, public xt1121Channels
41{
42
43 protected:
44 /** \name configurable parameters
45 *@{
46 */
47
48 std::string m_address; ///< The I.P. address of the device
49
50 uint16_t m_port{ 502 }; ///< The port to use. Default is 502 for modbus.
51
52 ///@}
53
54 std::mutex m_modbusMutex; ///< Protects m_mb lifetime and all modbus socket I/O.
55
56 std::atomic<bool> m_callbacksEnabled{ true }; ///< Gates INDI callbacks during shutdown/teardown.
57
58 modbus *m_mb{ nullptr }; ///< The modbus protocol communication object
59
60 /// Close and delete the current modbus object under m_modbusMutex.
61 void closeModbus();
62
63 public:
64 /// Default c'tor
65 xt1121Ctrl();
66
67 /// Destructor
69
70 /// Setup the configuration system (called by MagAOXApp::setup())
71 virtual void setupConfig();
72
73 /// load the configuration system results (called by MagAOXApp::setup())
74 virtual void loadConfig();
75
76 /// Startup functions
77 /** Sets up the INDI vars.
78 *
79 */
80 virtual int appStartup();
81
82 /// Implementation of the FSM for the Siglent SDG
83 virtual int appLogic();
84
85 /// Implementation of the on-power-off FSM logic
86 virtual int onPowerOff();
87
88 /// Implementation of the while-powered-off FSM
90
91 /// Do any needed shutdown tasks. Currently nothing in this app.
92 virtual int appShutdown();
93
94 /// Get the current state of the outlets.
95 /**
96 * \returns 0 on success
97 * \returns -1 on error
98 */
99 int getState();
100
101 // INDI:
102 protected:
103 // declare our properties
120
121 public:
122 /// Callback worker to actually set or clear a channel and send it to the device
123 /** Contains the target/current logic, and calls the xtChannels::setRegisters
124 * function, and then the modbus write_registers.
125 *
126 * \returns 0 on success
127 * \returns -1 on error
128 */
129 int channelSetCallback( size_t chNo, ///< [in] The channel number to set
130 pcf::IndiProperty &ipToSet, ///< [in] The corresponding local INDI property
131 const pcf::IndiProperty &ipRecv ///< [in] The received INDI property
132 );
133
150};
151
153{
154 m_powerMgtEnabled = true;
155 m_powerOnWait = 2; // set default.
156 return;
157}
158
160{
161 m_callbacksEnabled = false;
162 closeModbus();
163
164 return;
165}
166
168{
169 config.add( "device.address",
170 "",
171 "device.address",
172 argType::Required,
173 "device",
174 "address",
175 true,
176 "string",
177 "The device I.P. address." );
178
179 config.add( "device.port",
180 "",
181 "device.port",
182 argType::Required,
183 "device",
184 "port",
185 true,
186 "int",
187 "The device port. Default is 502." );
188
189 config.add( "device.inputOnly",
190 "",
191 "device.inputOnly",
192 argType::Required,
193 "device",
194 "inputOnly",
195 false,
196 "vector<int>",
197 "List of channels which are input-only." );
198}
199
201{
202 config( m_address, "device.address" );
203 config( m_port, "device.port" );
204
205 std::vector<int> ino;
206 config( ino, "device.inputOnly" );
207
208 for( size_t i = 0; i < ino.size(); ++i )
209 {
210 if( setInputOnly( ino[i] ) != 0 )
211 {
212 log<text_log>( "Error setting channel " + std::to_string( i ) + " to input only.", logPrio::LOG_ERROR );
213 }
214 }
215}
216
218{
219 // set up the INDI properties
220 REG_INDI_NEWPROP( m_indiP_ch00, "ch00", pcf::IndiProperty::Number );
221 m_indiP_ch00.add( pcf::IndiElement( "current" ) );
222 m_indiP_ch00["current"].set( -1 );
223 m_indiP_ch00.add( pcf::IndiElement( "target" ) );
224
225 REG_INDI_NEWPROP( m_indiP_ch01, "ch01", pcf::IndiProperty::Number );
226 m_indiP_ch01.add( pcf::IndiElement( "current" ) );
227 m_indiP_ch01["current"].set( -1 );
228 m_indiP_ch01.add( pcf::IndiElement( "target" ) );
229
230 REG_INDI_NEWPROP( m_indiP_ch02, "ch02", pcf::IndiProperty::Number );
231 m_indiP_ch02.add( pcf::IndiElement( "current" ) );
232 m_indiP_ch02["current"].set( -1 );
233 m_indiP_ch02.add( pcf::IndiElement( "target" ) );
234
235 REG_INDI_NEWPROP( m_indiP_ch03, "ch03", pcf::IndiProperty::Number );
236 m_indiP_ch03.add( pcf::IndiElement( "current" ) );
237 m_indiP_ch03["current"].set( -1 );
238 m_indiP_ch03.add( pcf::IndiElement( "target" ) );
239
240 REG_INDI_NEWPROP( m_indiP_ch04, "ch04", pcf::IndiProperty::Number );
241 m_indiP_ch04.add( pcf::IndiElement( "current" ) );
242 m_indiP_ch04["current"].set( -1 );
243 m_indiP_ch04.add( pcf::IndiElement( "target" ) );
244
245 REG_INDI_NEWPROP( m_indiP_ch05, "ch05", pcf::IndiProperty::Number );
246 m_indiP_ch05.add( pcf::IndiElement( "current" ) );
247 m_indiP_ch05["current"].set( -1 );
248 m_indiP_ch05.add( pcf::IndiElement( "target" ) );
249
250 REG_INDI_NEWPROP( m_indiP_ch06, "ch06", pcf::IndiProperty::Number );
251 m_indiP_ch06.add( pcf::IndiElement( "current" ) );
252 m_indiP_ch06["current"].set( -1 );
253 m_indiP_ch06.add( pcf::IndiElement( "target" ) );
254
255 REG_INDI_NEWPROP( m_indiP_ch07, "ch07", pcf::IndiProperty::Number );
256 m_indiP_ch07.add( pcf::IndiElement( "current" ) );
257 m_indiP_ch07["current"].set( -1 );
258 m_indiP_ch07.add( pcf::IndiElement( "target" ) );
259
260 REG_INDI_NEWPROP( m_indiP_ch08, "ch08", pcf::IndiProperty::Number );
261 m_indiP_ch08.add( pcf::IndiElement( "current" ) );
262 m_indiP_ch08["current"].set( -1 );
263 m_indiP_ch08.add( pcf::IndiElement( "target" ) );
264
265 REG_INDI_NEWPROP( m_indiP_ch09, "ch09", pcf::IndiProperty::Number );
266 m_indiP_ch09.add( pcf::IndiElement( "current" ) );
267 m_indiP_ch09["current"].set( -1 );
268 m_indiP_ch09.add( pcf::IndiElement( "target" ) );
269
270 REG_INDI_NEWPROP( m_indiP_ch10, "ch10", pcf::IndiProperty::Number );
271 m_indiP_ch10.add( pcf::IndiElement( "current" ) );
272 m_indiP_ch10["current"].set( -1 );
273 m_indiP_ch10.add( pcf::IndiElement( "target" ) );
274
275 REG_INDI_NEWPROP( m_indiP_ch11, "ch11", pcf::IndiProperty::Number );
276 m_indiP_ch11.add( pcf::IndiElement( "current" ) );
277 m_indiP_ch11["current"].set( -1 );
278 m_indiP_ch11.add( pcf::IndiElement( "target" ) );
279
280 REG_INDI_NEWPROP( m_indiP_ch12, "ch12", pcf::IndiProperty::Number );
281 m_indiP_ch12.add( pcf::IndiElement( "current" ) );
282 m_indiP_ch12["current"].set( -1 );
283 m_indiP_ch12.add( pcf::IndiElement( "target" ) );
284
285 REG_INDI_NEWPROP( m_indiP_ch13, "ch13", pcf::IndiProperty::Number );
286 m_indiP_ch13.add( pcf::IndiElement( "current" ) );
287 m_indiP_ch13["current"].set( -1 );
288 m_indiP_ch13.add( pcf::IndiElement( "target" ) );
289
290 REG_INDI_NEWPROP( m_indiP_ch14, "ch14", pcf::IndiProperty::Number );
291 m_indiP_ch14.add( pcf::IndiElement( "current" ) );
292 m_indiP_ch14["current"].set( -1 );
293 m_indiP_ch14.add( pcf::IndiElement( "target" ) );
294
295 REG_INDI_NEWPROP( m_indiP_ch15, "ch15", pcf::IndiProperty::Number );
296 m_indiP_ch15.add( pcf::IndiElement( "current" ) );
297 m_indiP_ch15["current"].set( -1 );
298 m_indiP_ch15.add( pcf::IndiElement( "target" ) );
299
300 return 0;
301}
302
304{
305 if( state() == stateCodes::POWERON )
306 {
307 if( !powerOnWaitElapsed() )
308 {
309 return 0;
310 }
311
313 }
314
316 {
317 // Might have gotten here because of a power off.
318 if( m_powerState == 0 )
319 {
320 return 0;
321 }
322
323 closeModbus();
324
325 modbus *newMb = new( std::nothrow ) modbus( m_address, m_port );
326
327 if( newMb == nullptr )
328 {
329 return log<software_critical, -1>( { __FILE__, __LINE__, "allocation failure" } );
330 }
331
332 newMb->modbus_set_slave_id( 1 );
333
334 if( newMb->modbus_connect() == false )
335 {
336 if( !stateLogged() )
337 {
338 log<text_log>( "connect failed at " + m_address + ":" + std::to_string( m_port ) );
339 }
340 delete newMb;
341 return 0;
342 }
343
344 if( !newMb->modbus_set_timeouts( 2, 0 ) )
345 {
346 log<text_log>( "failed setting modbus socket timeouts", logPrio::LOG_WARNING );
347 }
348
349 { // mutex scope
350 std::lock_guard<std::mutex> lock( m_modbusMutex );
351 m_mb = newMb;
352 }
353
355 log<text_log>( "connected to " + m_address + ":" + std::to_string( m_port ) );
356 }
357
359 {
360 if( getState() == 0 )
361 {
363 return 0;
364 }
365 else
366 {
369 }
370 }
371
373 {
374 if( getState() < 0 )
375 {
376 if( m_powerState == 0 )
377 {
378 return 0;
379 }
380
382 return 0;
383 }
384
385 return 0;
386 }
387
388 // Fall through check?
389
390 return 0;
391}
392
394{
395 std::lock_guard<std::mutex> lock( m_indiMutex );
396
397 updateIfChanged( m_indiP_ch00, "current", -1 );
398 updateIfChanged( m_indiP_ch00, "target", -1 );
399
400 updateIfChanged( m_indiP_ch01, "current", -1 );
401 updateIfChanged( m_indiP_ch01, "target", -1 );
402
403 updateIfChanged( m_indiP_ch02, "current", -1 );
404 updateIfChanged( m_indiP_ch02, "target", -1 );
405
406 updateIfChanged( m_indiP_ch03, "current", -1 );
407 updateIfChanged( m_indiP_ch03, "target", -1 );
408
409 updateIfChanged( m_indiP_ch04, "current", -1 );
410 updateIfChanged( m_indiP_ch04, "target", -1 );
411
412 updateIfChanged( m_indiP_ch05, "current", -1 );
413 updateIfChanged( m_indiP_ch05, "target", -1 );
414
415 updateIfChanged( m_indiP_ch06, "current", -1 );
416 updateIfChanged( m_indiP_ch06, "target", -1 );
417
418 updateIfChanged( m_indiP_ch07, "current", -1 );
419 updateIfChanged( m_indiP_ch07, "target", -1 );
420
421 updateIfChanged( m_indiP_ch08, "current", -1 );
422 updateIfChanged( m_indiP_ch08, "target", -1 );
423
424 updateIfChanged( m_indiP_ch09, "current", -1 );
425 updateIfChanged( m_indiP_ch09, "target", -1 );
426
427 updateIfChanged( m_indiP_ch10, "current", -1 );
428 updateIfChanged( m_indiP_ch10, "target", -1 );
429
430 updateIfChanged( m_indiP_ch11, "current", -1 );
431 updateIfChanged( m_indiP_ch11, "target", -1 );
432
433 updateIfChanged( m_indiP_ch12, "current", -1 );
434 updateIfChanged( m_indiP_ch12, "target", -1 );
435
436 updateIfChanged( m_indiP_ch13, "current", -1 );
437 updateIfChanged( m_indiP_ch13, "target", -1 );
438
439 updateIfChanged( m_indiP_ch14, "current", -1 );
440 updateIfChanged( m_indiP_ch14, "target", -1 );
441
442 updateIfChanged( m_indiP_ch15, "current", -1 );
443 updateIfChanged( m_indiP_ch15, "target", -1 );
444
445 return 0;
446}
447
449{
450 return 0;
451}
452
454{
455 m_callbacksEnabled = false;
456 closeModbus();
457 return 0;
458}
459
461{
462 std::lock_guard<std::mutex> lock( m_modbusMutex );
463
464 if( !m_mb )
465 {
466 return;
467 }
468
469 try
470 {
472 }
473 catch( ... )
474 {
475 }
476
477 delete m_mb;
478 m_mb = nullptr;
479}
480
482{
483 if( shutdown() != 0 )
484 {
485 return 0;
486 }
487
489
490 { // mutex scope
491 std::lock_guard<std::mutex> lock( m_modbusMutex );
492
493 if( m_mb == nullptr )
494 {
495 return -1;
496 }
497
498 try
499 {
501 }
502 catch( std::exception &e )
503 {
504 if( m_powerState == 0 )
505 {
506 return 0; // due to power off
507 }
508
509 return log<software_error, -1>( { __FILE__, __LINE__, std::string( "Exception caught: " ) + e.what() } );
510 }
511 }
512
513 std::lock_guard<std::mutex> lock( m_indiMutex );
514 if( readRegisters( input_regs ) != 0 )
515 {
516 return log<software_error, -1>( { __FILE__, __LINE__ } );
517 }
518
519 updateIfChanged( m_indiP_ch00, "current", channel( 0 ) );
520 updateIfChanged( m_indiP_ch01, "current", channel( 1 ) );
521 updateIfChanged( m_indiP_ch02, "current", channel( 2 ) );
522 updateIfChanged( m_indiP_ch03, "current", channel( 3 ) );
523 updateIfChanged( m_indiP_ch04, "current", channel( 4 ) );
524 updateIfChanged( m_indiP_ch05, "current", channel( 5 ) );
525 updateIfChanged( m_indiP_ch06, "current", channel( 6 ) );
526 updateIfChanged( m_indiP_ch07, "current", channel( 7 ) );
527 updateIfChanged( m_indiP_ch08, "current", channel( 8 ) );
528 updateIfChanged( m_indiP_ch09, "current", channel( 9 ) );
529 updateIfChanged( m_indiP_ch10, "current", channel( 10 ) );
530 updateIfChanged( m_indiP_ch11, "current", channel( 11 ) );
531 updateIfChanged( m_indiP_ch12, "current", channel( 12 ) );
532 updateIfChanged( m_indiP_ch13, "current", channel( 13 ) );
533 updateIfChanged( m_indiP_ch14, "current", channel( 14 ) );
534 updateIfChanged( m_indiP_ch15, "current", channel( 15 ) );
535
536 return 0;
537}
538
539int xt1121Ctrl::channelSetCallback( size_t chNo, pcf::IndiProperty &ipToSet, const pcf::IndiProperty &ipRecv )
540{
541 if( !m_callbacksEnabled || shutdown() != 0 )
542 {
543 return 0;
544 }
545
546 int current = -1, target = -1;
547
548 if( ipRecv.find( "current" ) )
549 {
550 current = ipRecv["current"].get<unsigned>();
551 }
552
553 if( ipRecv.find( "target" ) )
554 {
555 target = ipRecv["target"].get<unsigned>();
556 }
557
558 if( target == -1 )
559 {
560 target = current;
561 }
562
563 if( target < 0 )
564 {
565 return 0;
566 }
567
569
570 { // mutex scope
571 std::lock_guard<std::mutex> lock( m_indiMutex );
572
573 if( target == 0 )
574 {
576 }
577 else
578 {
579 setChannel( chNo );
580 }
581
582 target = channel( chNo ); // This checks for inputOnly
583
584 updateIfChanged( ipToSet, "target", target );
585
586 if( setRegisters( input_regs ) != 0 )
587 {
588 return log<software_error, -1>( { __FILE__, __LINE__ } );
589 }
590 }
591
592 { // mutex scope
593 std::lock_guard<std::mutex> lock( m_modbusMutex );
594
595 if( m_mb == nullptr )
596 {
597 return 0;
598 }
599
600 try
601 {
603 }
604 catch( std::exception &e )
605 {
606 if( m_powerState == 0 )
607 {
608 return 0; // due to power off
609 }
610
611 return log<software_error, -1>( { __FILE__, __LINE__, std::string( "Exception caught: " ) + e.what() } );
612 }
613 }
614
615 log<text_log>( "Set channel " + std::to_string( chNo ) + " to " + std::to_string( target ) );
616
617 return 0;
618}
619
621{
623
625
626 return -1;
627}
628
630{
632
634
635 return -1;
636}
637
639{
641
643
644 return -1;
645}
646
648{
650
652
653 return -1;
654}
655
657{
659
661
662 return -1;
663}
664
666{
668
670
671 return -1;
672}
673
675{
677
679
680 return -1;
681}
682
684{
686
688
689 return -1;
690}
691
693{
695
697
698 return -1;
699}
700
702{
704
706
707 return -1;
708}
709
711{
713
715
716 return -1;
717}
718
720{
722
724
725 return -1;
726}
727
729{
731
733
734 return -1;
735}
736
738{
740
742
743 return -1;
744}
745
747{
749
751
752 return -1;
753}
754
756{
758
760
761 return -1;
762}
763
764} // namespace app
765} // namespace MagAOX
766#endif
The base-class for XWCTk applications.
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI property element value if it has changed.
stateCodes::stateCodeT state()
Get the current state code.
int shutdown()
Get the value of the shutdown flag.
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.
bool powerOnWaitElapsed()
This method tests whether the power on wait time has elapsed.
unsigned long m_powerOnWait
Default time in sec to wait for device to boot after power on.
std::mutex m_indiMutex
Mutex for locking INDI communications.
virtual void setupConfig()
Setup the configuration system (called by MagAOXApp::setup())
pcf::IndiProperty m_indiP_ch11
virtual void loadConfig()
load the configuration system results (called by MagAOXApp::setup())
virtual int appLogic()
Implementation of the FSM for the Siglent SDG.
pcf::IndiProperty m_indiP_ch02
pcf::IndiProperty m_indiP_ch14
virtual int appStartup()
Startup functions.
~xt1121Ctrl() noexcept
Destructor.
virtual int appShutdown()
Do any needed shutdown tasks. Currently nothing in this app.
pcf::IndiProperty m_indiP_ch07
std::string m_address
The I.P. address of the device.
void closeModbus()
Close and delete the current modbus object under m_modbusMutex.
uint16_t m_port
The port to use. Default is 502 for modbus.
pcf::IndiProperty m_indiP_ch10
int getState()
Get the current state of the outlets.
pcf::IndiProperty m_indiP_ch00
pcf::IndiProperty m_indiP_ch08
pcf::IndiProperty m_indiP_ch06
std::mutex m_modbusMutex
Protects m_mb lifetime and all modbus socket I/O.
pcf::IndiProperty m_indiP_ch15
pcf::IndiProperty m_indiP_ch01
pcf::IndiProperty m_indiP_ch05
xt1121Ctrl()
Default c'tor.
std::atomic< bool > m_callbacksEnabled
Gates INDI callbacks during shutdown/teardown.
pcf::IndiProperty m_indiP_ch09
virtual int whilePowerOff()
Implementation of the while-powered-off FSM.
modbus * m_mb
The modbus protocol communication object.
virtual int onPowerOff()
Implementation of the on-power-off FSM logic.
pcf::IndiProperty m_indiP_ch12
pcf::IndiProperty m_indiP_ch03
pcf::IndiProperty m_indiP_ch13
pcf::IndiProperty m_indiP_ch04
int channelSetCallback(size_t chNo, pcf::IndiProperty &ipToSet, const pcf::IndiProperty &ipRecv)
Callback worker to actually set or clear a channel and send it to the device.
Modbus Operator Class.
Definition modbus.hpp:53
void modbus_read_input_registers(int address, int amount, uint16_t *buffer)
Definition modbus.cpp:225
void modbus_close()
Definition modbus.cpp:94
void modbus_write_registers(int address, int amount, uint16_t *value)
Definition modbus.cpp:400
#define protected
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define REG_INDI_NEWPROP(prop, propName, type)
Register a NEW INDI property with the class, using the standard callback name.
#define INDI_NEWCALLBACK_DECL(class, prop)
Declare the callback for a new property request, and declare and define the static wrapper.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
const pcf::IndiProperty & ipRecv
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:19
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.
@ OPERATING
The device is operating, other than homing.
@ 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.
Software CRITICAL log entry.
Software ERR log entry.
Utility class for managing Acromag xt12XX digital I/O channels.
int setRegisters(uint16_t registers[numRegisters])
Set registers based on current channel states.
int setChannel(size_t chNo)
Set a channel to true.
static constexpr int numRegisters
The number of registers needed for the number of channels.
int channel(size_t chNo)
Gets the current state of a channel.
int setInputOnly(size_t chNo)
Set a channel to be input only.
int clearChannel(size_t chNo)
Set a channel to false.
int readRegisters(uint16_t registers[numRegisters])
Read channel states from the registers.
Utility class for managing Acromag xt12XX digital I/O channels.