Instrument Neutral Distributed Interface INDI 2.2.5
rti_dome.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2026 Jasem Mutlaq. All rights reserved.
3
4 RTI Dome INDI Driver
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License version 2 as published by the Free Software Foundation.
9 .
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14 .
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*******************************************************************************/
20
21#include "rti_dome.h"
22
23#include "indicom.h"
26
27#include <cmath>
28#include <cstring>
29#include <memory>
30#include <termios.h>
31#include <unistd.h>
32
33static std::unique_ptr<RTIDome> rtidome(new RTIDome());
34
39{
41
48
49 // RTI dome supports both Serial and TCP connections (it has an Ethernet module)
51}
52
57{
59
60 // Firmware versions
61 FirmwareTP[FIRMWARE_ROTATION].fill("FIRMWARE_ROTATION", "Rotation", "N/A");
62 FirmwareTP[FIRMWARE_SHUTTER].fill("FIRMWARE_SHUTTER", "Shutter", "N/A");
63 FirmwareTP.fill(getDeviceName(), "FIRMWARE", "Firmware", INFO_TAB, IP_RO, 60, IPS_IDLE);
64
65 // Dome & Shutter status text
66 StatusTP[STATUS_DOME].fill("STATUS_DOME", "Dome", "N/A");
67 StatusTP[STATUS_SHUTTER].fill("STATUS_SHUTTER", "Shutter", "N/A");
68 StatusTP.fill(getDeviceName(), "STATUS", "Status", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE);
69
70 // Motor settings
71 SettingsNP[SETTINGS_STEPS_PER_ROTATION].fill("SETTINGS_STEPS_PER_ROTATION", "Steps/Rotation", "%.f",
72 1, 0xFFFFFF, 1, 0);
73 SettingsNP[SETTINGS_STEP_RATE].fill("SETTINGS_STEP_RATE", "Step Rate (steps/s)", "%.f",
74 1, 50000, 1, 0);
75 SettingsNP[SETTINGS_ACCELERATION].fill("SETTINGS_ACCELERATION", "Acceleration", "%.f",
76 1, 50000, 1, 0);
77 SettingsNP.fill(getDeviceName(), "SETTINGS", "Settings", SETTINGS_TAB, IP_RW, 60, IPS_IDLE);
78
79 // Home position
80 HomePositionNP[0].fill("HOME_POSITION", "Az (deg)", "%.2f", 0, 360, 1, 0);
81 HomePositionNP.fill(getDeviceName(), "HOME_POSITION", "Home Position", SETTINGS_TAB, IP_RW, 60, IPS_IDLE);
82
83 // Find Home
84 HomeSP[HOME_FIND].fill("HOME_FIND", "Find", ISS_OFF);
85 HomeSP.fill(getDeviceName(), "HOME", "Home", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
86
87 // Rain action
88 RainActionSP[RAIN_DO_NOTHING].fill("RAIN_DO_NOTHING", "Do Nothing", ISS_ON);
89 RainActionSP[RAIN_GOTO_HOME].fill("RAIN_GOTO_HOME", "Go Home", ISS_OFF);
90 RainActionSP[RAIN_GOTO_PARK].fill("RAIN_GOTO_PARK", "Go Park", ISS_OFF);
91 RainActionSP.fill(getDeviceName(), "RAIN_ACTION", "Rain Action", SETTINGS_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
92
93 // Motor direction
94 ReversedSP[REVERSED_NORMAL].fill("REVERSED_NORMAL", "Normal", ISS_ON);
95 ReversedSP[REVERSED_REVERSED].fill("REVERSED_REVERSED", "Reversed", ISS_OFF);
96 ReversedSP.fill(getDeviceName(), "MOTOR_REVERSED", "Motor Direction", SETTINGS_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
97
98 // Shutter voltage
99 ShutterVoltsNP[SHUTTER_VOLTAGE_CURRENT].fill("SHUTTER_VOLTAGE_CURRENT", "Current (V)", "%.2f", 0, 20, 0, 0);
100 ShutterVoltsNP[SHUTTER_VOLTAGE_CUTOFF].fill("SHUTTER_VOLTAGE_CUTOFF", "Cutoff (V)", "%.2f", 0, 20, 0, 0);
101 ShutterVoltsNP.fill(getDeviceName(), "SHUTTER_VOLTAGE", "Shutter Voltage", INFO_TAB, IP_RW, 60, IPS_IDLE);
102
103 // RTI dome USB serial is 115200 8N1 by default
105
106 // RTI dome defaults to 192.168.1.9:2323 when no DHCP server is available
107 tcpConnection->setDefaultHost("192.168.1.9");
109
110 // Default to TCP (network) connection since that's the most common usage
112
115
116 return true;
117}
118
122bool RTIDome::setupInitialParameters()
123{
124 if (InitPark())
125 {
127 }
128 else
129 {
130 SetAxis1Park(0);
132 }
133
134 // Get rotation firmware only (shutter firmware is fetched later, after
135 // we confirm the shutter controller is reachable via H#/o#).
136 if (getFirmwareVersion())
137 FirmwareTP[FIRMWARE_ROTATION].setText(FirmwareTP[FIRMWARE_ROTATION].getText());
138
139 FirmwareTP.setState(IPS_OK);
140 FirmwareTP.apply();
141
142 // Get current dome azimuth
143 double az = 0;
144 if (getDomeAzimuth(az))
145 {
146 DomeAbsPosNP[0].setValue(az);
148 }
149
150 // Get steps per rotation, step rate, acceleration
151 uint32_t steps = 0, rate = 0, accel = 0;
152 bool settingsOk = true;
153 if (getStepsPerRotation(steps))
154 SettingsNP[SETTINGS_STEPS_PER_ROTATION].setValue(steps);
155 else
156 settingsOk = false;
157 if (getStepRate(rate))
158 SettingsNP[SETTINGS_STEP_RATE].setValue(rate);
159 else
160 settingsOk = false;
161 if (getAcceleration(accel))
162 SettingsNP[SETTINGS_ACCELERATION].setValue(accel);
163 else
164 settingsOk = false;
165
166 SettingsNP.setState(settingsOk ? IPS_OK : IPS_ALERT);
167 SettingsNP.apply();
168
169 // Get home position
170 double homeAz = 0;
171 if (getHomePosition(homeAz))
172 {
173 HomePositionNP[0].setValue(homeAz);
174 HomePositionNP.setState(IPS_OK);
175 }
176 HomePositionNP.apply();
177
178 // Get motor reversed status
179 bool rev = false;
180 if (getMotorReversed(rev))
181 {
182 ReversedSP.reset();
183 ReversedSP[rev ? REVERSED_REVERSED : REVERSED_NORMAL].setState(ISS_ON);
184 ReversedSP.setState(IPS_OK);
185 ReversedSP.apply();
186 }
187
188 // Get rain action
189 int action = 0;
190 if (getRainAction(action))
191 {
192 RainActionSP.reset();
193 if (action >= 0 && action <= 2)
194 RainActionSP[action].setState(ISS_ON);
195 RainActionSP.setState(IPS_OK);
196 RainActionSP.apply();
197 }
198
199 // Ping the shutter XBee radio, wait briefly, then check if it responded.
200 // If the shutter controller is absent (no XBee link), m_bShutterPresent stays
201 // false and we will never try to send M# — avoiding constant 3-second timeouts.
202 sendShutterHello();
203 usleep(250000); // 250 ms — same delay as the X2 plugin
204 getShutterPresent(m_bShutterPresent);
205 if (m_bShutterPresent)
206 {
207 LOG_INFO("Shutter controller detected.");
208 // Fetch shutter firmware and initial state only when actually present
209 getShutterFirmwareVersion();
210 FirmwareTP.apply();
211
212 int shutterStatus = 0;
213 if (queryShutterState(shutterStatus))
214 {
215 // Map raw state to INDI ShutterState
217 switch (shutterStatus)
218 {
219 case 0:
220 ss = SHUTTER_OPENED;
221 break;
222 case 1:
223 ss = SHUTTER_CLOSED;
224 break;
225 case 2:
226 case 3:
227 ss = SHUTTER_MOVING;
228 break;
229 default:
230 ss = SHUTTER_UNKNOWN;
231 break;
232 }
233 setShutterState(ss);
234 }
235
236 // Get shutter voltage
237 double voltage = 0, cutoff = 0;
238 if (getShutterVoltage(voltage, cutoff))
239 {
240 ShutterVoltsNP[SHUTTER_VOLTAGE_CURRENT].setValue(voltage);
241 ShutterVoltsNP[SHUTTER_VOLTAGE_CUTOFF].setValue(cutoff);
242 ShutterVoltsNP.setState(IPS_OK);
243 ShutterVoltsNP.apply();
244 }
245 }
246 else
247 {
248 LOG_INFO("No shutter controller detected. Shutter commands will be skipped.");
249 }
250
251 return true;
252}
253
258{
259 return getFirmwareVersion();
260}
261
266{
267 return "RTI Dome";
268}
269
274{
276
277 if (isConnected())
278 {
279 setupInitialParameters();
280
281 defineProperty(FirmwareTP);
282 defineProperty(StatusTP);
283 defineProperty(HomeSP);
284 defineProperty(HomePositionNP);
285 defineProperty(SettingsNP);
286 defineProperty(RainActionSP);
287 defineProperty(ReversedSP);
288 defineProperty(ShutterVoltsNP);
289 }
290 else
291 {
292 deleteProperty(FirmwareTP);
293 deleteProperty(StatusTP);
294 deleteProperty(HomeSP);
295 deleteProperty(HomePositionNP);
296 deleteProperty(SettingsNP);
297 deleteProperty(RainActionSP);
298 deleteProperty(ReversedSP);
299 deleteProperty(ShutterVoltsNP);
300 }
301
302 return true;
303}
304
308bool RTIDome::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
309{
310 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
311 {
313 // Home
315 if (HomeSP.isNameMatch(name))
316 {
317 HomeSP.update(states, names, n);
318 if (HomeSP[HOME_FIND].getState() == ISS_ON)
319 {
320 if (findHome())
321 {
322 HomeSP.setState(IPS_BUSY);
323 LOG_INFO("Dome finding home position...");
324 }
325 else
326 {
327 HomeSP.reset();
328 HomeSP.setState(IPS_ALERT);
329 LOG_ERROR("Failed to start home search.");
330 }
331 }
332 HomeSP.apply();
333 return true;
334 }
335
337 // Rain Action
339 if (RainActionSP.isNameMatch(name))
340 {
341 RainActionSP.update(states, names, n);
342 int action = RainActionSP.findOnSwitchIndex();
343 if (setRainAction(action))
344 {
345 RainActionSP.setState(IPS_OK);
346 LOGF_INFO("Rain action set to: %s", RainActionSP.findOnSwitch()->getLabel());
347 }
348 else
349 {
350 RainActionSP.setState(IPS_ALERT);
351 LOG_ERROR("Failed to set rain action.");
352 }
353 RainActionSP.apply();
354 return true;
355 }
356
358 // Motor Direction
360 if (ReversedSP.isNameMatch(name))
361 {
362 ReversedSP.update(states, names, n);
363 bool rev = (ReversedSP.findOnSwitchIndex() == REVERSED_REVERSED);
364 if (setMotorReversed(rev))
365 {
366 ReversedSP.setState(IPS_OK);
367 LOGF_INFO("Motor direction set to: %s", rev ? "Reversed" : "Normal");
368 }
369 else
370 {
371 ReversedSP.setState(IPS_ALERT);
372 LOG_ERROR("Failed to set motor direction.");
373 }
374 ReversedSP.apply();
375 return true;
376 }
377 }
378
379 return INDI::Dome::ISNewSwitch(dev, name, states, names, n);
380}
381
385bool RTIDome::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
386{
387 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
388 {
390 // Settings
392 if (SettingsNP.isNameMatch(name))
393 {
394 bool allSet = true;
395 for (int i = 0; i < n; i++)
396 {
397 if (SettingsNP[SETTINGS_STEPS_PER_ROTATION].isNameMatch(names[i]))
398 {
399 if (setStepsPerRotation(static_cast<uint32_t>(values[i])))
400 SettingsNP[SETTINGS_STEPS_PER_ROTATION].setValue(values[i]);
401 else
402 {
403 allSet = false;
404 LOG_ERROR("Failed to set steps per rotation.");
405 }
406 }
407 else if (SettingsNP[SETTINGS_STEP_RATE].isNameMatch(names[i]))
408 {
409 if (setStepRate(static_cast<uint32_t>(values[i])))
410 SettingsNP[SETTINGS_STEP_RATE].setValue(values[i]);
411 else
412 {
413 allSet = false;
414 LOG_ERROR("Failed to set step rate.");
415 }
416 }
417 else if (SettingsNP[SETTINGS_ACCELERATION].isNameMatch(names[i]))
418 {
419 if (setAcceleration(static_cast<uint32_t>(values[i])))
420 SettingsNP[SETTINGS_ACCELERATION].setValue(values[i]);
421 else
422 {
423 allSet = false;
424 LOG_ERROR("Failed to set acceleration.");
425 }
426 }
427 }
428 SettingsNP.setState(allSet ? IPS_OK : IPS_ALERT);
429 SettingsNP.apply();
430 return true;
431 }
432
434 // Home Position
436 if (HomePositionNP.isNameMatch(name))
437 {
438 if (setHomePosition(values[0]))
439 {
440 HomePositionNP[0].setValue(values[0]);
441 HomePositionNP.setState(IPS_OK);
442 LOGF_INFO("Home position set to %.2f degrees.", values[0]);
443 }
444 else
445 {
446 HomePositionNP.setState(IPS_ALERT);
447 LOG_ERROR("Failed to set home position.");
448 }
449 HomePositionNP.apply();
450 return true;
451 }
452
454 // Shutter Voltage Cutoff
456 if (ShutterVoltsNP.isNameMatch(name))
457 {
458 // Only the cutoff value is settable; current is read-only
459 double cutoff = values[SHUTTER_VOLTAGE_CUTOFF];
460 char cmd[DRIVER_LEN] = {0};
461 snprintf(cmd, DRIVER_LEN, "K%d", static_cast<int>(cutoff * 100));
462 char res[DRIVER_LEN] = {0};
463 if (sendCommand(cmd, res))
464 {
465 // Response is KVVVV,vvvv — re-parse
466 double voltage = 0, newCutoff = 0;
467 if (getShutterVoltage(voltage, newCutoff))
468 {
469 ShutterVoltsNP[SHUTTER_VOLTAGE_CURRENT].setValue(voltage);
470 ShutterVoltsNP[SHUTTER_VOLTAGE_CUTOFF].setValue(newCutoff);
471 }
472 ShutterVoltsNP.setState(IPS_OK);
473 }
474 else
475 {
476 ShutterVoltsNP.setState(IPS_ALERT);
477 LOG_ERROR("Failed to set shutter voltage cutoff.");
478 }
479 ShutterVoltsNP.apply();
480 return true;
481 }
482 }
483
484 return INDI::Dome::ISNewNumber(dev, name, values, names, n);
485}
486
491{
492 if (!isConnected())
493 return;
494
495 // Poll dome azimuth
496 double az = 0;
497 if (getDomeAzimuth(az))
498 {
499 if (std::abs(az - DomeAbsPosNP[0].getValue()) > DOME_AZ_THRESHOLD)
500 {
501 DomeAbsPosNP[0].setValue(az);
503 }
504 }
505
506 // Poll slewing status
507 int slewStatus = 0;
508 if (getSlewingStatus(slewStatus))
509 {
510 const char *slewText = (slewStatus == 1) ? "Slewing CW" : (slewStatus == -1) ? "Slewing CCW" : "Idle";
511
512 if (strcmp(StatusTP[STATUS_DOME].getText(), slewText) != 0)
513 {
514 StatusTP[STATUS_DOME].setText(slewText);
515 StatusTP.setState(IPS_OK);
516 StatusTP.apply();
517 }
518
519 // If dome was moving / parking and is now idle
520 if (slewStatus == 0)
521 {
522 if (getDomeState() == DOME_PARKING)
523 {
524 SetParked(true);
525 LOG_INFO("Dome parked.");
526 }
527 else if (getDomeState() == DOME_MOVING)
528 {
530 }
531
532 // If home search was in progress
533 if (HomeSP.getState() == IPS_BUSY)
534 {
535 int homedStatus = 0;
536 if (getHomedStatus(homedStatus) && homedStatus >= 1)
537 {
538 HomeSP.reset();
539 HomeSP.setState(IPS_OK);
540 HomeSP.apply();
541 LOG_INFO("Dome found home position.");
542 }
543 }
544 }
545 }
546
547 // Periodically re-check shutter presence (every ~10 ticks ≈ 10 s at 1 s poll)
548 // This handles the case where the shutter XBee link comes up after the driver
549 // was already connected, or drops out during a session.
550 ++m_nShutterPollCounter;
551 if (m_nShutterPollCounter >= 10)
552 {
553 m_nShutterPollCounter = 0;
554 bool wasPresent = m_bShutterPresent;
555 sendShutterHello();
556 getShutterPresent(m_bShutterPresent);
557 if (m_bShutterPresent && !wasPresent)
558 LOG_INFO("Shutter controller detected.");
559 else if (!m_bShutterPresent && wasPresent)
560 LOG_WARN("Shutter controller lost.");
561 }
562
563 // Only poll shutter state when the shutter controller is reachable.
564 // Without this guard, every M# would block for DRIVER_TIMEOUT seconds
565 // when the XBee link is absent, causing visible lag in the timer loop.
566 if (m_bShutterPresent)
567 {
568 int shutterStatus = 0;
569 if (queryShutterState(shutterStatus))
570 {
571 // Full 11-state mapping from the RTI firmware enum:
572 // 0=OPEN, 1=CLOSED, 2=OPENING, 3=CLOSING,
573 // 4=BOTTOM_OPEN, 5=BOTTOM_CLOSED, 6=BOTTOM_OPENING, 7=BOTTOM_CLOSING,
574 // 8=SHUTTER_ERROR, 9=FINISHING_OPEN, 10=FINISHING_CLOSE
575 const char *shutterText = nullptr;
576 ShutterState newShutterState = getShutterState();
577
578 switch (shutterStatus)
579 {
580 case 0:
581 shutterText = "Open";
582 newShutterState = SHUTTER_OPENED;
583 break;
584 case 1:
585 shutterText = "Closed";
586 newShutterState = SHUTTER_CLOSED;
587 break;
588 case 2:
589 case 6:
590 case 9:
591 shutterText = "Opening";
592 newShutterState = SHUTTER_MOVING;
593 break;
594 case 3:
595 case 7:
596 case 10:
597 shutterText = "Closing";
598 newShutterState = SHUTTER_MOVING;
599 break;
600 case 4:
601 shutterText = "Bottom Open";
602 newShutterState = SHUTTER_OPENED;
603 break;
604 case 5:
605 shutterText = "Bottom Closed";
606 newShutterState = SHUTTER_CLOSED;
607 break;
608 case 8:
609 shutterText = "Error";
610 newShutterState = SHUTTER_UNKNOWN;
611 break;
612 default:
613 shutterText = "Unknown";
614 newShutterState = SHUTTER_UNKNOWN;
615 break;
616 }
617
618 if (strcmp(StatusTP[STATUS_SHUTTER].getText(), shutterText) != 0)
619 {
620 StatusTP[STATUS_SHUTTER].setText(shutterText);
621 StatusTP.setState(IPS_OK);
622 StatusTP.apply();
623 }
624
625 if (newShutterState != getShutterState())
626 setShutterState(newShutterState);
627 }
628 }
629
631}
632
637{
638 if (gotoAzimuth(az))
639 return IPS_BUSY;
640 return IPS_ALERT;
641}
642
647{
648 m_TargetAz = DomeAbsPosNP[0].getValue() + azDiff;
649
650 if (m_TargetAz < DomeAbsPosNP[0].getMin())
651 m_TargetAz += DomeAbsPosNP[0].getMax();
652 if (m_TargetAz > DomeAbsPosNP[0].getMax())
653 m_TargetAz -= DomeAbsPosNP[0].getMax();
654
655 return MoveAbs(m_TargetAz);
656}
657
661bool RTIDome::Sync(double az)
662{
663 return syncAzimuth(az);
664}
665
670{
671 if (operation == MOTION_STOP)
672 {
673 if (stopDome())
674 return IPS_OK;
675 }
676 else
677 {
678 // For manual jog: move a small fixed amount in the requested direction
679 double jogDeg = (dir == DOME_CW) ? 5.0 : -5.0;
680 return MoveRel(jogDeg);
681 }
682 return IPS_ALERT;
683}
684
689{
690 m_TargetAz = GetAxis1Park();
691 return MoveAbs(m_TargetAz);
692}
693
698{
699 SetParked(false);
700 return IPS_OK;
701}
702
707{
708 // Refuse immediately if the shutter XBee link is not established
709 if (!m_bShutterPresent)
710 {
711 LOG_ERROR("Cannot control shutter: shutter controller not detected.");
712 return IPS_ALERT;
713 }
714
715 if (operation == SHUTTER_OPEN)
716 {
717 if (openShutter())
718 return IPS_BUSY;
719 }
720 else if (operation == SHUTTER_CLOSE)
721 {
722 if (closeShutter())
723 return IPS_BUSY;
724 }
725 return IPS_ALERT;
726}
727
732{
733 if (!stopDome())
734 return false;
735
738
739 if (ParkSP.getState() == IPS_BUSY)
740 SetParked(false);
741
742 if (HomeSP.getState() == IPS_BUSY)
743 {
744 HomeSP.reset();
745 HomeSP.setState(IPS_IDLE);
746 HomeSP.apply();
747 }
748
749 return true;
750}
751
756{
757 SetAxis1Park(DomeAbsPosNP[0].getValue());
758 return true;
759}
760
765{
766 SetAxis1Park(0);
767 return true;
768}
769
773bool RTIDome::getFirmwareVersion()
774{
775 char res[DRIVER_LEN] = {0};
776 if (!sendCommand("v", res))
777 return false;
778
779 // Response: v2.645# — strip the leading 'v'
780 if (res[0] == 'v' || res[0] == 'V')
781 {
782 FirmwareTP[FIRMWARE_ROTATION].setText(res + 1);
783 return true;
784 }
785 return false;
786}
787
791bool RTIDome::getShutterFirmwareVersion()
792{
793 char res[DRIVER_LEN] = {0};
794 if (!sendCommand("V", res))
795 return false;
796
797 // Response: V2.645# — strip the leading 'V'
798 if (res[0] == 'v' || res[0] == 'V')
799 {
800 FirmwareTP[FIRMWARE_SHUTTER].setText(res + 1);
801 return true;
802 }
803 return false;
804}
805
809bool RTIDome::getDomeAzimuth(double &az)
810{
811 char res[DRIVER_LEN] = {0};
812 if (!sendCommand("g", res))
813 return false;
814
815 // Response: g321.50# — strip the leading 'g'
816 if (res[0] != 'g')
817 return false;
818
819 az = std::atof(res + 1);
820 return true;
821}
822
826bool RTIDome::getSlewingStatus(int &status)
827{
828 char res[DRIVER_LEN] = {0};
829 if (!sendCommand("m", res))
830 return false;
831
832 // Response: m0#, m1#, m-1#
833 if (res[0] != 'm')
834 return false;
835
836 status = std::atoi(res + 1);
837 return true;
838}
839
843bool RTIDome::getHomedStatus(int &status)
844{
845 char res[DRIVER_LEN] = {0};
846 if (!sendCommand("z", res))
847 return false;
848
849 // Response: z0#
850 if (res[0] != 'z')
851 return false;
852
853 status = std::atoi(res + 1);
854 return true;
855}
856
860bool RTIDome::queryShutterState(int &status)
861{
862 char res[DRIVER_LEN] = {0};
863 if (!sendCommand("M", res))
864 return false;
865
866 // Response: M0#
867 if (res[0] != 'M')
868 return false;
869
870 status = std::atoi(res + 1);
871 return true;
872}
873
877bool RTIDome::getStepsPerRotation(uint32_t &steps)
878{
879 char res[DRIVER_LEN] = {0};
880 if (!sendCommand("t", res))
881 return false;
882
883 // Response: t440640#
884 if (res[0] != 't')
885 return false;
886
887 steps = static_cast<uint32_t>(std::stoul(res + 1));
888 return true;
889}
890
894bool RTIDome::getStepRate(uint32_t &rate)
895{
896 char res[DRIVER_LEN] = {0};
897 if (!sendCommand("r", res))
898 return false;
899
900 // Response: r8000#
901 if (res[0] != 'r')
902 return false;
903
904 rate = static_cast<uint32_t>(std::stoul(res + 1));
905 return true;
906}
907
911bool RTIDome::getAcceleration(uint32_t &accel)
912{
913 char res[DRIVER_LEN] = {0};
914 if (!sendCommand("e", res))
915 return false;
916
917 // Response: e7000#
918 if (res[0] != 'e')
919 return false;
920
921 accel = static_cast<uint32_t>(std::stoul(res + 1));
922 return true;
923}
924
928bool RTIDome::getHomePosition(double &az)
929{
930 char res[DRIVER_LEN] = {0};
931 if (!sendCommand("i", res))
932 return false;
933
934 // Response: i180.00#
935 if (res[0] != 'i')
936 return false;
937
938 az = std::atof(res + 1);
939 return true;
940}
941
945bool RTIDome::getParkPosition(double &az)
946{
947 char res[DRIVER_LEN] = {0};
948 if (!sendCommand("l", res))
949 return false;
950
951 // Response: l321.50#
952 if (res[0] != 'l')
953 return false;
954
955 az = std::atof(res + 1);
956 return true;
957}
958
962bool RTIDome::getMotorReversed(bool &reversed)
963{
964 char res[DRIVER_LEN] = {0};
965 if (!sendCommand("y", res))
966 return false;
967
968 // Response: y0# or y1#
969 if (res[0] != 'y')
970 return false;
971
972 reversed = (res[1] == '1');
973 return true;
974}
975
979bool RTIDome::getRainAction(int &action)
980{
981 char res[DRIVER_LEN] = {0};
982 if (!sendCommand("n", res))
983 return false;
984
985 // Response: n2#
986 if (res[0] != 'n')
987 return false;
988
989 action = std::atoi(res + 1);
990 return true;
991}
992
996bool RTIDome::getRainStatus(bool &raining)
997{
998 char res[DRIVER_LEN] = {0};
999 if (!sendCommand("F", res))
1000 return false;
1001
1002 // Response: F0# or F1#
1003 if (res[0] != 'F')
1004 return false;
1005
1006 raining = (res[1] == '1');
1007 return true;
1008}
1009
1013bool RTIDome::getShutterVoltage(double &voltage, double &cutoff)
1014{
1015 char res[DRIVER_LEN] = {0};
1016 if (!sendCommand("K", res))
1017 return false;
1018
1019 // Response: K1319,1150# — VVVV,vvvv; divide by 100 for float
1020 if (res[0] != 'K')
1021 return false;
1022
1023 int raw_v = 0, raw_c = 0;
1024 if (sscanf(res + 1, "%d,%d", &raw_v, &raw_c) != 2)
1025 return false;
1026
1027 voltage = raw_v / 100.0;
1028 cutoff = raw_c / 100.0;
1029 return true;
1030}
1031
1035bool RTIDome::gotoAzimuth(double az)
1036{
1037 char cmd[DRIVER_LEN] = {0};
1038 snprintf(cmd, DRIVER_LEN, "g%.2f", az);
1039 char res[DRIVER_LEN] = {0};
1040 if (!sendCommand(cmd, res))
1041 return false;
1042 return (res[0] == 'g');
1043}
1044
1048bool RTIDome::syncAzimuth(double az)
1049{
1050 char cmd[DRIVER_LEN] = {0};
1051 snprintf(cmd, DRIVER_LEN, "s%.2f", az);
1052 char res[DRIVER_LEN] = {0};
1053 if (!sendCommand(cmd, res))
1054 return false;
1055 return (res[0] == 's');
1056}
1057
1061bool RTIDome::stopDome()
1062{
1063 char res[DRIVER_LEN] = {0};
1064 if (!sendCommand("a", res))
1065 return false;
1066 return (res[0] == 'a');
1067}
1068
1072bool RTIDome::findHome()
1073{
1074 char res[DRIVER_LEN] = {0};
1075 if (!sendCommand("h", res))
1076 return false;
1077 return (res[0] == 'h');
1078}
1079
1083bool RTIDome::setHomePosition(double az)
1084{
1085 char cmd[DRIVER_LEN] = {0};
1086 snprintf(cmd, DRIVER_LEN, "i%.2f", az);
1087 char res[DRIVER_LEN] = {0};
1088 if (!sendCommand(cmd, res))
1089 return false;
1090 return (res[0] == 'i');
1091}
1092
1096bool RTIDome::setParkPosition(double az)
1097{
1098 char cmd[DRIVER_LEN] = {0};
1099 snprintf(cmd, DRIVER_LEN, "l%.2f", az);
1100 char res[DRIVER_LEN] = {0};
1101 if (!sendCommand(cmd, res))
1102 return false;
1103 return (res[0] == 'l');
1104}
1105
1109bool RTIDome::setStepsPerRotation(uint32_t steps)
1110{
1111 char cmd[DRIVER_LEN] = {0};
1112 snprintf(cmd, DRIVER_LEN, "t%u", steps);
1113 char res[DRIVER_LEN] = {0};
1114 if (!sendCommand(cmd, res))
1115 return false;
1116 return (res[0] == 't');
1117}
1118
1122bool RTIDome::setStepRate(uint32_t rate)
1123{
1124 char cmd[DRIVER_LEN] = {0};
1125 snprintf(cmd, DRIVER_LEN, "r%u", rate);
1126 char res[DRIVER_LEN] = {0};
1127 if (!sendCommand(cmd, res))
1128 return false;
1129 return (res[0] == 'r');
1130}
1131
1135bool RTIDome::setAcceleration(uint32_t accel)
1136{
1137 char cmd[DRIVER_LEN] = {0};
1138 snprintf(cmd, DRIVER_LEN, "e%u", accel);
1139 char res[DRIVER_LEN] = {0};
1140 if (!sendCommand(cmd, res))
1141 return false;
1142 return (res[0] == 'e');
1143}
1144
1148bool RTIDome::setMotorReversed(bool reversed)
1149{
1150 char cmd[DRIVER_LEN] = {0};
1151 snprintf(cmd, DRIVER_LEN, "y%d", reversed ? 1 : 0);
1152 char res[DRIVER_LEN] = {0};
1153 if (!sendCommand(cmd, res))
1154 return false;
1155 return (res[0] == 'y');
1156}
1157
1161bool RTIDome::setRainAction(int action)
1162{
1163 char cmd[DRIVER_LEN] = {0};
1164 snprintf(cmd, DRIVER_LEN, "n%d", action);
1165 char res[DRIVER_LEN] = {0};
1166 if (!sendCommand(cmd, res))
1167 return false;
1168 return (res[0] == 'n');
1169}
1170
1174bool RTIDome::openShutter()
1175{
1176 char res[DRIVER_LEN] = {0};
1177 if (!sendCommand("O", res))
1178 return false;
1179 // Response: O# normally; OL# if low voltage, OR# if raining
1180 if (res[0] != 'O')
1181 return false;
1182 if (strlen(res) > 1 && res[1] == 'L')
1183 {
1184 LOG_WARN("Shutter refused to open: low voltage.");
1185 return false;
1186 }
1187 if (strlen(res) > 1 && res[1] == 'R')
1188 {
1189 LOG_WARN("Shutter refused to open: rain detected.");
1190 return false;
1191 }
1192 return true;
1193}
1194
1198bool RTIDome::closeShutter()
1199{
1200 char res[DRIVER_LEN] = {0};
1201 if (!sendCommand("C", res))
1202 return false;
1203 return (res[0] == 'C');
1204}
1205
1215bool RTIDome::sendShutterHello()
1216{
1217 char res[DRIVER_LEN] = {0};
1218 return sendCommand("H", res);
1219}
1220
1226bool RTIDome::getShutterPresent(bool &present)
1227{
1228 char res[DRIVER_LEN] = {0};
1229 if (!sendCommand("o", res))
1230 {
1231 present = false;
1232 return false;
1233 }
1234
1235 // Response: o0# or o1#
1236 if (res[0] != 'o')
1237 {
1238 present = false;
1239 return false;
1240 }
1241
1242 present = (res[1] == '1');
1243 return true;
1244}
1245
1249bool RTIDome::sendCommand(const char *cmd, char *res)
1250{
1251 int nbytes_written = 0, nbytes_read = 0, rc = -1;
1252
1253 tcflush(PortFD, TCIOFLUSH);
1254
1255 // Build full command string: cmd + '#'
1256 char formatted_command[DRIVER_LEN] = {0};
1257 snprintf(formatted_command, DRIVER_LEN, "%s#", cmd);
1258
1259 LOGF_DEBUG("CMD <%s>", formatted_command);
1260
1261 rc = tty_write_string(PortFD, formatted_command, &nbytes_written);
1262 if (rc != TTY_OK)
1263 {
1264 char errstr[MAXRBUF] = {0};
1265 tty_error_msg(rc, errstr, MAXRBUF);
1266 LOGF_ERROR("Serial write error: %s.", errstr);
1267 return false;
1268 }
1269
1270 if (res == nullptr)
1271 return true;
1272
1273 // Read until '#' stop character
1274 rc = tty_nread_section(PortFD, res, DRIVER_LEN, DRIVER_STOP_CHAR, DRIVER_TIMEOUT, &nbytes_read);
1275 if (rc != TTY_OK)
1276 {
1277 char errstr[MAXRBUF] = {0};
1278 tty_error_msg(rc, errstr, MAXRBUF);
1279 LOGF_ERROR("Serial read error: %s.", errstr);
1280 return false;
1281 }
1282
1283 // Strip trailing '#'
1284 if (nbytes_read > 0 && res[nbytes_read - 1] == DRIVER_STOP_CHAR)
1285 res[nbytes_read - 1] = '\0';
1286
1287 LOGF_DEBUG("RES <%s>", res);
1288
1289 tcflush(PortFD, TCIOFLUSH);
1290
1291 return true;
1292}
1293
1297void RTIDome::hexDump(char *buf, const char *data, int size)
1298{
1299 for (int i = 0; i < size; i++)
1300 sprintf(buf + 3 * i, "%02X ", static_cast<uint8_t>(data[i]));
1301
1302 if (size > 0)
1303 buf[3 * size - 1] = '\0';
1304}
void setDefaultBaudRate(BaudRate newRate)
setDefaultBaudRate Set default baud rate. The default baud rate is 9600 unless otherwise changed by t...
void setDefaultHost(const char *addressHost)
void setDefaultPort(uint32_t addressPort)
bool isConnected() const
INDI::PropertyText getText(const char *name) const
void setActiveConnection(Connection::Interface *existingConnection)
setActiveConnection Switch the active connection to the passed connection plugin
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
Connection::TCP * tcpConnection
Definition indidome.h:608
void SetParked(bool isparked)
SetParked Change the mount parking status. The data park file (stored in ~/.indi/ParkData....
void SetDomeCapability(uint32_t cap)
SetDomeCapability set the dome capabilities. All capabilities must be initialized.
void setDomeConnection(const uint8_t &value)
setDomeConnection Set Dome connection mode. Child class should call this in the constructor before Do...
ShutterState m_ShutterState
Definition indidome.h:612
void SetAxis1Park(double value)
SetRAPark Set current AZ parking position. The data park file (stored in ~/.indi/ParkData....
void SetAxis1ParkDefault(double steps)
SetAxis1Park Set default AZ parking position.
ShutterOperation
Shutter operation command.
Definition indidome.h:114
@ SHUTTER_CLOSE
Definition indidome.h:116
DomeMotionCommand
Definition indidome.h:97
@ MOTION_STOP
Definition indidome.h:99
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition indidome.cpp:105
@ DOME_CAN_PARK
Definition indidome.h:159
@ DOME_CAN_ABS_MOVE
Definition indidome.h:157
@ DOME_HAS_SHUTTER
Definition indidome.h:161
@ DOME_CAN_REL_MOVE
Definition indidome.h:158
@ DOME_CAN_ABORT
Definition indidome.h:156
@ DOME_CAN_SYNC
Definition indidome.h:160
double GetAxis1Park()
@ SHUTTER_MOVING
Definition indidome.h:149
@ SHUTTER_UNKNOWN
Definition indidome.h:150
@ SHUTTER_OPENED
Definition indidome.h:147
@ SHUTTER_CLOSED
Definition indidome.h:148
INDI::PropertySwitch ParkSP
Definition indidome.h:548
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition indidome.cpp:284
INDI::PropertyNumber DomeAbsPosNP
Definition indidome.h:536
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition indidome.cpp:503
@ CONNECTION_SERIAL
Definition indidome.h:172
@ CONNECTION_TCP
Definition indidome.h:173
void setShutterState(const ShutterState &value)
void setDomeState(const DomeState &value)
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
Connection::Serial * serialConnection
Definition indidome.h:607
void SetParkDataType(DomeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
ShutterState getShutterState() const
Definition indidome.h:291
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition indidome.cpp:394
DomeState getDomeState() const
Definition indidome.h:285
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool isNameMatch(const char *otherName) const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
INDI::WidgetViewSwitch * findOnSwitch() const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
virtual IPState ControlShutter(ShutterOperation operation) override
Open or Close shutter.
Definition rti_dome.cpp:706
virtual bool Handshake() override
perform handshake with device to check communication
Definition rti_dome.cpp:257
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Definition rti_dome.cpp:385
virtual IPState UnPark() override
UnPark dome. The action of the Unpark command is dome specific, but it may include opening the shutte...
Definition rti_dome.cpp:697
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
Definition rti_dome.cpp:308
virtual IPState Park() override
Goto Park Position. The park position is an absolute azimuth value.
Definition rti_dome.cpp:688
virtual IPState MoveRel(double azDiff) override
Move the Dome to an relative position.
Definition rti_dome.cpp:646
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition rti_dome.cpp:56
virtual bool SetDefaultPark() override
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
Definition rti_dome.cpp:764
virtual const char * getDefaultName() override
Definition rti_dome.cpp:265
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
Definition rti_dome.cpp:273
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
Definition rti_dome.cpp:490
virtual bool Abort() override
Abort all dome motion.
Definition rti_dome.cpp:731
virtual bool SetCurrentPark() override
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
Definition rti_dome.cpp:755
virtual bool Sync(double az) override
Sync sets the dome current azimuth as the supplied azimuth position.
Definition rti_dome.cpp:661
virtual IPState Move(DomeDirection dir, DomeMotionCommand operation) override
Move the Dome in a particular direction.
Definition rti_dome.cpp:669
virtual IPState MoveAbs(double az) override
Move the Dome to an absolute azimuth.
Definition rti_dome.cpp:636
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
IPState
Property state.
Definition indiapi.h:158
@ IPS_BUSY
Definition indiapi.h:161
@ IPS_ALERT
Definition indiapi.h:162
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
@ ISR_ATMOST1
Definition indiapi.h:172
int tty_write_string(int fd, const char *buf, int *nbytes_written)
Writes a null terminated string to fd.
Definition indicom.c:473
void tty_error_msg(int err_code, char *err_msg, int err_msg_len)
Retrieve the tty error message.
Definition indicom.c:1243
int tty_nread_section(int fd, char *buf, int nsize, char stop_char, int timeout, int *nbytes_read)
read buffer from terminal with a delimiter
Definition indicom.c:759
Implementations for common driver routines.
@ TTY_OK
Definition indicom.h:54
#define MAXRBUF
Definition indidriver.c:52
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_WARN(txt)
Definition indilogger.h:73
#define LOGF_DEBUG(fmt,...)
Definition indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition indilogger.h:80
#define LOG_INFO(txt)
Definition indilogger.h:74
#define getDeviceName
uint8_t cmd[4]
Definition pwc-ioctl.h:2
const char * getLabel() const