Instrument Neutral Distributed Interface INDI 2.2.5
indidome.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 INDI Dome Base Class
3 Copyright(c) 2014 Jasem Mutlaq. All rights reserved.
4
5 The code used calculate dome target AZ and ZD is written by Ferran Casarramona, and adapted from code from Markus Wildi.
6 The transformations are based on the paper Matrix Method for Coordinates Transformation written by Toshimi Taki (http://www.asahi-net.or.jp/~zs3t-tk).
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License version 2 as published by the Free Software Foundation.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*******************************************************************************/
22
23#include "indidome.h"
24
25#include "indicom.h"
26#include "indicontroller.h"
27#include "inditimer.h"
30
31#include <libnova/julian_day.h>
32#include <libnova/sidereal_time.h>
33#include <libnova/transform.h>
34
35#include <cerrno>
36#include <cmath>
37#include <cstring>
38#include <wordexp.h>
39#include <pwd.h>
40#include <unistd.h>
41#include <limits>
42
43#define DOME_SLAVING_TAB "Slaving"
44#define DOME_COORD_THRESHOLD \
45 0.1 /* Only send debug messages if the differences between old and new values of Az/Alt excceds this value */
46
47namespace INDI
48{
49
53Dome::Dome() : ParkDataFileName(GetHomeDirectory() + "/.indi/ParkData.xml")
54{
55 controller = new Controller(this);
56
58
62
63 capability = 0;
64
67
69 ParkdataXmlRoot = nullptr;
70
71 m_MountUpdateTimer.callOnTimeout(std::bind(&Dome::UpdateMountCoords, this));
72}
73
78{
79 delXMLEle(ParkdataXmlRoot);
80
81 delete controller;
82 delete serialConnection;
83 delete tcpConnection;
84}
85
89std::string Dome::GetHomeDirectory() const
90{
91 // Check first the HOME environmental variable
92 const char * HomeDir = getenv("HOME");
93
94 // ...otherwise get the home directory of the current user.
95 if (!HomeDir)
96 {
97 HomeDir = getpwuid(getuid())->pw_dir;
98 }
99 return (HomeDir ? std::string(HomeDir) : "");
100}
101
106{
107 DefaultDevice::initProperties(); // let the base class flesh in what it wants
108
109 // Presets
110 PresetNP[0].fill("Preset 1", "", "%6.2f", 0, 360.0, 1.0, 0);
111 PresetNP[1].fill("Preset 2", "", "%6.2f", 0, 360.0, 1.0, 0);
112 PresetNP[2].fill("Preset 3", "", "%6.2f", 0, 360.0, 1.0, 0);
113 PresetNP.fill(getDeviceName(), "Presets", "", "Presets", IP_RW, 0, IPS_IDLE);
114
115 //Preset GOTO
116 PresetGotoSP[0].fill("Preset 1", "", ISS_OFF);
117 PresetGotoSP[1].fill("Preset 2", "", ISS_OFF);
118 PresetGotoSP[2].fill("Preset 3", "", ISS_OFF);
119 PresetGotoSP.fill(getDeviceName(), "Goto", "", "Presets", IP_RW, ISR_1OFMANY, 0, IPS_IDLE);
120
121 // Active Devices
122 ActiveDeviceTP[ACTIVE_MOUNT].fill("ACTIVE_TELESCOPE", "Telescope", "Telescope Simulator");
123 ActiveDeviceTP[ACTIVE_INPUT].fill("ACTIVE_INPUT", "Input", "");
124 ActiveDeviceTP[ACTIVE_OUTPUT].fill("ACTIVE_OUTPUT", "Output", "");
125 ActiveDeviceTP.fill(getDeviceName(), "ACTIVE_DEVICES", "Snoop devices", OPTIONS_TAB, IP_RW, 60, IPS_IDLE);
126 if (ActiveDeviceTP.load())
128
129 // Use locking if telescope is unparked
130 // @INDI_STANDARD_PROPERTY@
131 MountPolicySP[MOUNT_IGNORED].fill("MOUNT_IGNORED", "Mount ignored", ISS_ON);
132 MountPolicySP[MOUNT_LOCKS].fill("MOUNT_LOCKS", "Mount locks", ISS_OFF);
133 MountPolicySP.fill(getDeviceName(), "MOUNT_POLICY", "Mount Policy", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
135
136 // Shutter Policy
137 // @INDI_STANDARD_PROPERTY@
138 ShutterParkPolicySP[SHUTTER_CLOSE_ON_PARK].fill("SHUTTER_CLOSE_ON_PARK", "Close On Park", ISS_OFF);
139 ShutterParkPolicySP[SHUTTER_OPEN_ON_UNPARK].fill("SHUTTER_OPEN_ON_UNPARK", "Open On UnPark", ISS_OFF);
140 ShutterParkPolicySP.fill(getDeviceName(), "DOME_SHUTTER_PARK_POLICY", "Shutter", OPTIONS_TAB, IP_RW, ISR_NOFMANY, 60,
141 IPS_IDLE);
143
144 // Measurements
145 // @INDI_STANDARD_PROPERTY@
146 DomeMeasurementsNP[DM_DOME_RADIUS].fill("DM_DOME_RADIUS", "Radius (m)", "%6.2f", 0.0, 50.0, 1.0, 0.0);
147 DomeMeasurementsNP[DM_SHUTTER_WIDTH].fill("DM_SHUTTER_WIDTH", "Shutter width (m)", "%6.2f", 0.0, 10.0, 1.0, 0.0);
148 DomeMeasurementsNP[DM_NORTH_DISPLACEMENT].fill("DM_NORTH_DISPLACEMENT", "N displacement (m)", "%6.2f", -10.0, 10.0, 1.0,
149 0.0);
150 DomeMeasurementsNP[DM_EAST_DISPLACEMENT].fill("DM_EAST_DISPLACEMENT", "E displacement (m)", "%6.2f", -10.0, 10.0, 1.0, 0.0);
151 DomeMeasurementsNP[DM_UP_DISPLACEMENT].fill("DM_UP_DISPLACEMENT", "Up displacement (m)", "%6.2f", -10, 10.0, 1.0, 0.0);
152 DomeMeasurementsNP[DM_OTA_OFFSET].fill("DM_OTA_OFFSET", "OTA offset (m)", "%6.2f", -10.0, 10.0, 1.0, 0.0);
153 DomeMeasurementsNP.fill(getDeviceName(), "DOME_MEASUREMENTS", "Measurements", DOME_SLAVING_TAB, IP_RW, 60, IPS_OK);
155
156 // @INDI_STANDARD_PROPERTY@
157 OTASideSP[DM_OTA_SIDE_EAST].fill("DM_OTA_SIDE_EAST", "East", ISS_OFF);
158 OTASideSP[DM_OTA_SIDE_WEST].fill("DM_OTA_SIDE_WEST", "West", ISS_OFF);
159 OTASideSP[DM_OTA_SIDE_MOUNT].fill("DM_OTA_SIDE_MOUNT", "Mount", ISS_ON);
160 OTASideSP[DM_OTA_SIDE_HA].fill("DM_OTA_SIDE_HA", "Hour Angle", ISS_OFF);
161 OTASideSP[DM_OTA_SIDE_IGNORE].fill("DM_OTA_SIDE_IGNORE", "Ignore", ISS_OFF);
162 OTASideSP.fill(getDeviceName(), "DM_OTA_SIDE", "Meridian side", DOME_SLAVING_TAB, IP_RW, ISR_ATMOST1, 60, IPS_OK);
163
164 // @INDI_STANDARD_PROPERTY@
165 DomeAutoSyncSP[INDI_ENABLED].fill("DOME_AUTOSYNC_ENABLE", "Enable", ISS_OFF);
166 DomeAutoSyncSP[INDI_DISABLED].fill("DOME_AUTOSYNC_DISABLE", "Disable", ISS_ON);
167 DomeAutoSyncSP.fill(getDeviceName(), "DOME_AUTOSYNC", "Slaving", DOME_SLAVING_TAB, IP_RW, ISR_1OFMANY, 60, IPS_OK);
168
169 // @INDI_STANDARD_PROPERTY@
170 DomeSpeedNP[0].fill("DOME_SPEED_VALUE", "RPM", "%6.2f", 0.0, 10, 0.1, 1.0);
171 DomeSpeedNP.fill(getDeviceName(), "DOME_SPEED", "Speed", MAIN_CONTROL_TAB, IP_RW, 60, IPS_OK);
172
173 // @INDI_STANDARD_PROPERTY@
174 DomeSyncNP[0].fill("DOME_SYNC_VALUE", "Az", "%.2f", 0.0, 360, 10, 0.0);
175 DomeSyncNP.fill(getDeviceName(), "DOME_SYNC", "Sync", MAIN_CONTROL_TAB, IP_RW, 60, IPS_OK);
176
177 // @INDI_STANDARD_PROPERTY@
178 DomeMotionSP[0].fill("DOME_CW", "Dome CW", ISS_OFF);
179 DomeMotionSP[1].fill("DOME_CCW", "Dome CCW", ISS_OFF);
180 DomeMotionSP.fill(getDeviceName(), "DOME_MOTION", "Motion", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_OK);
181
182 // Driver can define those to clients if there is support
183 // @INDI_STANDARD_PROPERTY@
184 DomeAbsPosNP[0].fill("DOME_ABSOLUTE_POSITION", "Degrees", "%6.2f", 0.0, 360.0, 1.0, 0.0);
185 DomeAbsPosNP.fill(getDeviceName(), "ABS_DOME_POSITION", "Absolute Position", MAIN_CONTROL_TAB, IP_RW, 60, IPS_OK);
186
187 // @INDI_STANDARD_PROPERTY@
188 DomeRelPosNP[0].fill("DOME_RELATIVE_POSITION", "Degrees", "%6.2f", -180, 180.0, 10.0, 0.0);
189 DomeRelPosNP.fill(getDeviceName(), "REL_DOME_POSITION", "Relative Position", MAIN_CONTROL_TAB, IP_RW, 60, IPS_OK);
190
191 // @INDI_STANDARD_PROPERTY@
192 AbortSP[0].fill("ABORT", "Abort", ISS_OFF);
193 AbortSP.fill(getDeviceName(), "DOME_ABORT_MOTION", "Abort Motion", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
194
195 // @INDI_STANDARD_PROPERTY@
196 DomeParamNP[0].fill("AUTOSYNC_THRESHOLD", "Autosync threshold (deg)", "%6.2f", 0.0, 360.0, 1.0, 0.5);
197 DomeParamNP.fill(getDeviceName(), "DOME_PARAMS", "Params", DOME_SLAVING_TAB, IP_RW, 60, IPS_OK);
199
200 // @INDI_STANDARD_PROPERTY@
201 ParkSP[0].fill("PARK", "Park(ed)", ISS_OFF);
202 ParkSP[1].fill("UNPARK", "UnPark(ed)", ISS_OFF);
203 ParkSP.fill(getDeviceName(), "DOME_PARK", "Parking", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_OK);
204
205 // Backlash Compensation
206 // @INDI_STANDARD_PROPERTY@
207 DomeBacklashSP[INDI_ENABLED].fill("INDI_ENABLED", "Enabled", ISS_OFF);
208 DomeBacklashSP[INDI_DISABLED].fill("INDI_DISABLED", "Disabled", ISS_ON);
209 DomeBacklashSP.fill(getDeviceName(), "DOME_BACKLASH_TOGGLE", "Backlash", OPTIONS_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
210
211 // Backlash Compensation Value
212 // @INDI_STANDARD_PROPERTY@
213 DomeBacklashNP[0].fill("DOME_BACKLASH_VALUE", "Steps", "%.f", 0, 1e6, 100, 0);
214 DomeBacklashNP.fill(getDeviceName(), "DOME_BACKLASH_STEPS", "Backlash", OPTIONS_TAB, IP_RW, 60, IPS_OK);
216
217 // @INDI_STANDARD_PROPERTY@
218 DomeShutterSP[0].fill("SHUTTER_OPEN", "Open", ISS_OFF);
219 DomeShutterSP[1].fill("SHUTTER_CLOSE", "Close", ISS_ON);
220 DomeShutterSP.fill(getDeviceName(), "DOME_SHUTTER", "Shutter", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 60, IPS_OK);
221
222 // @INDI_STANDARD_PROPERTY@
223 ParkOptionSP[0].fill("PARK_CURRENT", "Current", ISS_OFF);
224 ParkOptionSP[1].fill("PARK_DEFAULT", "Default", ISS_OFF);
225 ParkOptionSP[2].fill("PARK_WRITE_DATA", "Write Data", ISS_OFF);
226 ParkOptionSP.fill(getDeviceName(), "DOME_PARK_OPTION", "Park Options", SITE_TAB, IP_RW, ISR_ATMOST1, 60, IPS_IDLE);
227
229
230 controller->mapController("Dome CW", "CW/Open", Controller::CONTROLLER_BUTTON, "BUTTON_1");
231 controller->mapController("Dome CCW", "CCW/Close", Controller::CONTROLLER_BUTTON, "BUTTON_2");
232
233 controller->initProperties();
234
235 const auto scope = ActiveDeviceTP[0].getText();
236 IDSnoopDevice(scope, "EQUATORIAL_EOD_COORD");
237 IDSnoopDevice(scope, "GEOGRAPHIC_COORD");
238 IDSnoopDevice(scope, "TELESCOPE_PARK");
239 if (CanAbsMove())
240 IDSnoopDevice(scope, "TELESCOPE_PIER_SIDE");
241
243
244 if (domeConnection & CONNECTION_SERIAL)
245 {
248 {
249 return callHandshake();
250 });
252 }
253
254 if (domeConnection & CONNECTION_TCP)
255 {
256 tcpConnection = new Connection::TCP(this);
258 {
259 return callHandshake();
260 });
262 }
263
264 return true;
265}
266
270void Dome::ISGetProperties(const char * dev)
271{
273
276
277 controller->ISGetProperties(dev);
278 return;
279}
280
285{
286 if (isConnected())
287 {
288 if (HasShutter())
289 {
292 }
293
294 // Now we add our Dome specific stuff
296
297 if (HasVariableSpeed())
298 {
300 }
301 if (CanRelMove())
303 if (CanAbsMove())
305 if (CanAbort())
307 if (CanAbsMove())
308 {
311
316 }
317 if (CanSync())
319
320 if (CanPark())
321 {
323 if (parkDataType != PARK_NONE)
324 {
327 }
328 }
329
330 if (HasBacklash())
331 {
334 }
335 }
336 else
337 {
338 if (HasShutter())
339 {
342 }
343
345
346 if (HasVariableSpeed())
347 {
349 }
350 if (CanRelMove())
352 if (CanAbsMove())
354 if (CanAbort())
356 if (CanAbsMove())
357 {
360
365 }
366
367 if (CanSync())
369
370 if (CanPark())
371 {
373 if (parkDataType != PARK_NONE)
374 {
377 }
378 }
379
380 if (HasBacklash())
381 {
384 }
385 }
386
387 controller->updateProperties();
388 return true;
389}
390
394bool Dome::ISNewNumber(const char * dev, const char * name, double values[], char * names[], int n)
395{
396 // first check if it's for our device
397 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
398 {
399 if (PresetNP.isNameMatch(name))
400 {
401 PresetNP.update(values, names, n);
403 PresetNP.apply(nullptr);
405 return true;
406 }
407 // Dome Sync
408 else if (DomeSyncNP.isNameMatch(name))
409 {
410 if (Sync(values[0]))
411 {
412 DomeSyncNP.update(values, names, n);
414 DomeAbsPosNP[0].setValue(values[0]);
416 }
417 else
418 {
420 }
421
423 return true;
424 }
425 else if (DomeParamNP.isNameMatch(name))
426 {
427 DomeParamNP.update(values, names, n);
430 // If slaving is enabled, report the differential threshold update.
431 if (DomeAutoSyncSP[INDI_ENABLED].getState() == ISS_ON)
432 LOGF_INFO("Dome slaving differential threshold updated to %.2f degrees.", DomeParamNP[0].getValue());
434 return true;
435 }
436 else if (DomeSpeedNP.isNameMatch(name))
437 {
438 auto newSpeed = values[0];
439 Dome::SetSpeed(newSpeed);
440 return true;
441 }
442 else if (DomeAbsPosNP.isNameMatch(name))
443 {
444 auto newPos = values[0];
445 Dome::MoveAbs(newPos);
446 return true;
447 }
448 else if (DomeRelPosNP.isNameMatch(name))
449 {
450 auto newPos = values[0];
451 Dome::MoveRel(newPos);
452 return true;
453 }
454 else if (DomeMeasurementsNP.isNameMatch(name))
455 {
456 DomeMeasurementsNP.update(values, names, n);
460 return true;
461 }
462 else if (ParkPositionNP.isNameMatch(name))
463 {
464 ParkPositionNP.update(values, names, n);
466 Axis1ParkPosition = ParkPositionNP[AXIS_RA].getValue();
468 return true;
469 }
471 // Backlash value
473 else if (DomeBacklashNP.isNameMatch(name))
474 {
475 if (DomeBacklashSP[INDI_ENABLED].getState() != ISS_ON)
476 {
478 LOG_WARN("Dome backlash must be enabled first.");
479 }
480 else
481 {
482 auto steps = static_cast<uint32_t>(values[0]);
483 if (SetBacklash(steps))
484 {
485 DomeBacklashNP[0].setValue(values[0]);
487 }
488 else
490 }
493 return true;
494 }
495 }
496
497 return DefaultDevice::ISNewNumber(dev, name, values, names, n);
498}
499
503bool Dome::ISNewSwitch(const char * dev, const char * name, ISState * states, char * names[], int n)
504{
505 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
506 {
508 // GOTO Presets
510 if (PresetGotoSP.isNameMatch(name))
511 {
513 {
514 LOG_ERROR("Please unpark before issuing any motion commands.");
517 return false;
518 }
519
520 PresetGotoSP.update(states, names, n);
521 auto index = PresetGotoSP.findOnSwitchIndex();
522 IPState rc = Dome::MoveAbs(PresetNP[index].getValue());
523 if (rc == IPS_OK || rc == IPS_BUSY)
524 {
526 LOGF_INFO("Moving to Preset %d (%.2f degrees).", index + 1, PresetNP[index].getValue());
528 return true;
529 }
530
533 return false;
534 }
536 // Dome Auto Sync
538 else if (DomeAutoSyncSP.isNameMatch(name))
539 {
540 DomeAutoSyncSP.update(states, names, n);
542
543 if (DomeAutoSyncSP[0].getState() == ISS_ON)
544 {
545 LOGF_WARN("Dome will now be synced to mount azimuth position if the differential threshold exceeds %.2f degrees.",
546 DomeParamNP[0].getValue());
549 m_MountUpdateTimer.start(HORZ_UPDATE_TIMER);
550 }
551 else
552 {
553 LOG_WARN("Dome is no longer synced to mount azimuth position.");
555 m_MountUpdateTimer.stop();
557 Dome::Abort();
558 }
559
560 return true;
561 }
563 // OTA Side
565 else if (OTASideSP.isNameMatch(name))
566 {
567 OTASideSP.update(states, names, n);
569
570 if (OTASideSP[DM_OTA_SIDE_EAST].getState() == ISS_ON)
571 {
572 LOG_WARN("Dome shall be synced assuming OTA situated east of the pier");
573 }
574 else if (OTASideSP[DM_OTA_SIDE_WEST].getState() == ISS_ON)
575 {
576 LOG_WARN("Dome shall be synced assuming OTA situated west of the pier");
577 }
578 else if (OTASideSP[DM_OTA_SIDE_MOUNT].getState() == ISS_ON)
579 {
580 LOG_WARN("Dome shall be synced from pier side as reported by the mount.");
581 }
582 else if (OTASideSP[DM_OTA_SIDE_HA].getState() == ISS_ON)
583 {
584 LOG_WARN("Dome shall be synced for OTA pier side derived from Hour Angle.");
585 }
586 else if (OTASideSP[DM_OTA_SIDE_IGNORE].getState() == ISS_ON)
587 {
588 LOG_WARN("Dome shall be synced by ignoring pier side as in a fork mount.");
589 }
590
594 return true;
595 }
597 // Dome Motion
599 else if (DomeMotionSP.isNameMatch(name))
600 {
601 // Check if any switch is ON
602 for (int i = 0; i < n; i++)
603 {
604 if (states[i] == ISS_ON)
605 {
606 if (DomeMotionSP[DOME_CW].isNameMatch(names[i]))
608 else
610
611 return true;
612 }
613 }
614
615 // All switches are off, so let's turn off last motion
616 auto current_direction = DomeMotionSP.findOnSwitchIndex();
617 // Shouldn't happen
618 if (current_direction < 0)
619 {
622 return false;
623 }
624
625 Dome::Move(static_cast<DomeDirection>(current_direction), MOTION_STOP);
626
627 return true;
628 }
630 // Abort Motion
632 else if (AbortSP.isNameMatch(name))
633 {
634 Dome::Abort();
635 return true;
636 }
638 // Shutter
640 else if (DomeShutterSP.isNameMatch(name))
641 {
642 // Check if any switch is ON
643 for (int i = 0; i < n; i++)
644 {
645 if (states[i] == ISS_ON)
646 {
647 // Open
648 if (DomeShutterSP[0].isNameMatch(names[i]))
649 {
651 }
652 else
653 {
655 }
656 }
657 }
658 }
660 // Parking Switch
662 else if (ParkSP.isNameMatch(name))
663 {
664 // Check if any switch is ON
665 for (int i = 0; i < n; i++)
666 {
667 if (states[i] == ISS_ON)
668 {
669 if (ParkSP[0].isNameMatch(names[i]))
670 {
672 return false;
673
674 return (Dome::Park() != IPS_ALERT);
675 }
676 else
677 {
679 return false;
680
681 return (Dome::UnPark() != IPS_ALERT);
682 }
683 }
684 }
685 }
687 // Parking Option
689 else if (ParkOptionSP.isNameMatch(name))
690 {
691 ParkOptionSP.update(states, names, n);
692 auto sp = ParkOptionSP.findOnSwitch();
693 if (!sp)
694 return false;
695
696 bool rc = false;
697
699
700 if (sp->isNameMatch("PARK_CURRENT"))
701 {
702 rc = SetCurrentPark();
703 }
704 else if (sp->isNameMatch("PARK_DEFAULT"))
705 {
706 rc = SetDefaultPark();
707 }
708 else if (sp->isNameMatch( "PARK_WRITE_DATA"))
709 {
710 rc = WriteParkData();
711 if (rc)
712 LOG_INFO("Saved Park Status/Position.");
713 else
714 LOG_WARN( "Can not save Park Status/Position.");
715 }
716
719 return true;
720 }
722 // Auto Park
724#if 0
725 else if (!strcmp(name, AutoParkSP.name))
726 {
727 IUUpdateSwitch(&AutoParkSP, states, names, n);
728
729 AutoParkSP.s = IPS_OK;
730
731 if (AutoParkS[0].s == ISS_ON)
732 LOG_WARN( "Warning: Auto park is enabled. If weather conditions are in the "
733 "danger zone, the dome will be automatically parked. Only enable this "
734 "option is parking the dome at any time will not cause damage to any "
735 "equipment.");
736 else
737 DEBUG(Logger::DBG_SESSION, "Auto park is disabled.");
738
739 IDSetSwitch(&AutoParkSP, nullptr);
740
741 return true;
742 }
743#endif
745 // Telescope Parking Policy
747 else if (MountPolicySP.isNameMatch(name))
748 {
749 MountPolicySP.update(states, names, n);
751 if (MountPolicySP[MOUNT_IGNORED].getState() == ISS_ON)
752 LOG_INFO("Mount Policy set to: Mount ignored. Dome can park regardless of mount parking state.");
753 else
754 LOG_WARN("Mount Policy set to: Mount locks. This prevents the dome from parking when mount is unparked.");
757 triggerSnoop(ActiveDeviceTP[0].getText(), "TELESCOPE_PARK");
758 return true;
759 }
761 // Shutter Parking Policy
763 else if (ShutterParkPolicySP.isNameMatch(name))
764 {
765 ShutterParkPolicySP.update(states, names, n);
769 return true;
770 }
772 // Backlash enable/disable
774 else if (DomeBacklashSP.isNameMatch(name))
775 {
776 auto prevIndex = DomeBacklashSP.findOnSwitchIndex();
777 DomeBacklashSP.update(states, names, n);
779
780 if (SetBacklashEnabled(enabled))
781 {
782 DomeBacklashSP.update(states, names, n);
784 LOGF_INFO("Dome backlash is %s.", (enabled ? "enabled" : "disabled"));
785 }
786 else
787 {
789 DomeBacklashSP[prevIndex].setState(ISS_ON);
791 LOG_ERROR("Failed to set trigger Dome backlash.");
792 }
793
796 return true;
797 }
798 }
799
800 controller->ISNewSwitch(dev, name, states, names, n);
801
802 // Nobody has claimed this, so, ignore it
803 return DefaultDevice::ISNewSwitch(dev, name, states, names, n);
804}
805
809bool Dome::ISNewText(const char * dev, const char * name, char * texts[], char * names[], int n)
810{
811 if (dev != nullptr && strcmp(dev, getDeviceName()) == 0)
812 {
813 if (ActiveDeviceTP.isNameMatch(name))
814 {
816 ActiveDeviceTP.update(texts, names, n);
818
819 auto mount = ActiveDeviceTP[ACTIVE_MOUNT].getText();
820 IDSnoopDevice(mount, "EQUATORIAL_EOD_COORD");
821 IDSnoopDevice(mount, "TARGET_EOD_COORD");
822 IDSnoopDevice(mount, "GEOGRAPHIC_COORD");
823 IDSnoopDevice(mount, "TELESCOPE_PARK");
824 if (CanAbsMove())
825 IDSnoopDevice(mount, "TELESCOPE_PIER_SIDE");
826
829 return true;
830 }
831 }
832
833 controller->ISNewText(dev, name, texts, names, n);
834
835 return DefaultDevice::ISNewText(dev, name, texts, names, n);
836}
837
842{
843 XMLEle * ep = nullptr;
844 const char * propName = findXMLAttValu(root, "name");
845 auto deviceName = std::string(findXMLAttValu(root, "device"));
846
847 // Check TARGET
848 if (!strcmp("TARGET_EOD_COORD", propName) && deviceName == ActiveDeviceTP[ACTIVE_MOUNT].getText())
849 {
850 int rc_ra = -1, rc_de = -1;
851 double ra = std::numeric_limits<double>::quiet_NaN(), de = std::numeric_limits<double>::quiet_NaN();
852
853 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
854 {
855 const char * elemName = findXMLAttValu(ep, "name");
856
857 LOGF_DEBUG("Snooped Target RA-DEC: %s", pcdataXMLEle(ep));
858 if (!strcmp(elemName, "RA"))
859 rc_ra = f_scansexa(pcdataXMLEle(ep), &ra);
860 else if (!strcmp(elemName, "DEC"))
861 rc_de = f_scansexa(pcdataXMLEle(ep), &de);
862 }
863 // Dont start moving the dome till the mount has initialized all the variables
864 if (HaveRaDec && CanAbsMove())
865 {
866 if (rc_ra == 0 && rc_de == 0 && !std::isnan(ra) && !std::isnan(de))
867 {
868 // Ignore empty/uninitialized target coordinates (e.g. default RA=0, DEC=0 broadcast on startup
869 // before any actual GOTO has been commanded). Without this guard the dome would spuriously
870 // slew to the position corresponding to RA=0/DEC=0 and then have to correct itself once the
871 // real mount coordinates arrive, mirroring the same protection already present in the
872 // EQUATORIAL_EOD_COORD handler.
873 if (ra == 0 && de == 0)
874 return true;
875
876 // everything parsed ok, so lets start the dome to moving
877 // If this slew involves a meridian flip, then the slaving calcs will end up using
878 // the wrong OTA side. Lets set things up so our slaving code will calculate the side
879 // for the target slew instead of using mount pier side info
880 // and see if we can get there at the same time as the mount
881 // TODO: see what happens in a meridian flip with OTASide
884 LOGF_DEBUG("Calling Update mount to anticipate goto target: %g - DEC: %g",
886 UseHourAngle = true;
888 UseHourAngle = false;
889 }
890 }
891
892 return true;
893 }
894 // Check EOD
895 if (!strcmp("EQUATORIAL_EOD_COORD", propName) && deviceName == ActiveDeviceTP[ACTIVE_MOUNT].getText())
896 {
897 int rc_ra = -1, rc_de = -1;
898 double ra = std::numeric_limits<double>::quiet_NaN(), de = std::numeric_limits<double>::quiet_NaN();
899
900 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
901 {
902 const char * elemName = findXMLAttValu(ep, "name");
903
904 if (!strcmp(elemName, "RA"))
905 rc_ra = f_scansexa(pcdataXMLEle(ep), &ra);
906 else if (!strcmp(elemName, "DEC"))
907 rc_de = f_scansexa(pcdataXMLEle(ep), &de);
908 }
909
910 if (rc_ra == 0 && rc_de == 0 && !std::isnan(ra) && !std::isnan(de))
911 {
912 // Do not spam log
913 if (std::fabs(mountEquatorialCoords.rightascension - ra) > 0.01
914 || std::fabs(mountEquatorialCoords.declination - de) > 0.01)
915 {
916 // Ignore empty coords.
917 if (ra == 0 && de == 0)
918 return true;
919
920 char RAStr[64] = {0}, DEStr[64] = {0};
921 fs_sexa(RAStr, ra, 2, 3600);
922 fs_sexa(DEStr, de, 2, 3600);
923
924 LOGF_DEBUG("Snooped RA %s DEC %s", RAStr, DEStr);
925 }
926
929 }
930
932 crackIPState(findXMLAttValu(root, "state"), &m_MountState);
933
934 // If the diff > 0.1 then the mount is in motion, so let's wait until it settles before moving the doom
937 {
940 //LOGF_DEBUG("Snooped RA: %g - DEC: %g", mountEquatorialCoords.rightascension, mountEquatorialCoords.declination);
941 // a mount still initializing will emit 0 and 0 on the first go
942 // we dont want to process 0/0
944 HaveRaDec = true;
945 }
946 // else mount stable, i.e. tracking, so let's update mount coords and check if we need to move
947 else if (m_MountState == IPS_OK || m_MountState == IPS_IDLE)
949
950 return true;
951 }
952
953 // Check Geographic coords
954 if (!strcmp("GEOGRAPHIC_COORD", propName) && deviceName == ActiveDeviceTP[ACTIVE_MOUNT].getText())
955 {
956 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
957 {
958 const char * elemName = findXMLAttValu(ep, "name");
959 if (!strcmp(elemName, "LONG"))
960 {
961 double indiLong;
962 f_scansexa(pcdataXMLEle(ep), &indiLong);
963 if (indiLong > 180)
964 indiLong -= 360;
965 observer.longitude = indiLong;
966 HaveLatLong = true;
967 }
968 else if (!strcmp(elemName, "LAT"))
970 }
971
972 LOGF_DEBUG("Snooped LONG: %g - LAT: %g", observer.longitude, observer.latitude);
973
975
976 return true;
977 }
978
979 // Check Telescope Park status
980 if (!strcmp("TELESCOPE_PARK", propName) && deviceName == ActiveDeviceTP[ACTIVE_MOUNT].getText())
981 {
982 if (!strcmp(findXMLAttValu(root, "state"), "Ok"))
983 {
984 bool prevState = IsLocked;
985 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
986 {
987 const char * elemName = findXMLAttValu(ep, "name");
988
989 if ((!strcmp(elemName, "PARK") && !strcmp(pcdataXMLEle(ep), "On")))
990 IsMountParked = true;
991 else if ((!strcmp(elemName, "UNPARK") && !strcmp(pcdataXMLEle(ep), "On")))
992 IsMountParked = false;
993
994 if (IsLocked && !strcmp(elemName, "PARK") && !strcmp(pcdataXMLEle(ep), "On"))
995 IsLocked = false;
996 else if (!IsLocked && !strcmp(elemName, "UNPARK") && !strcmp(pcdataXMLEle(ep), "On"))
997 IsLocked = true;
998 }
999 if (prevState != IsLocked && MountPolicySP[1].getState() == ISS_ON)
1000 LOGF_INFO("Telescope status changed. Lock is set to: %s", IsLocked ? "locked" : "unlocked");
1001 }
1002 return true;
1003 }
1004
1005 // Weather Status
1006 // JM 2020-07-16: Weather handling moved to Watchdog driver
1007#if 0
1008 if (!strcmp("WEATHER_STATUS", propName))
1009 {
1010 weatherState = IPS_ALERT;
1011 crackIPState(findXMLAttValu(root, "state"), &weatherState);
1012
1013 if (weatherState == IPS_ALERT)
1014 {
1015 if (CanPark())
1016 {
1017 if (!isParked())
1018 {
1019 if (AutoParkS[0].s == ISS_ON)
1020 {
1021 LOG_WARN("Weather conditions in the danger zone! Parking dome...");
1022 Dome::Park();
1023 }
1024 else
1025 {
1026 LOG_WARN("Weather conditions in the danger zone! AutoPark is disabled. Please park the dome!");
1027 }
1028 }
1029 }
1030 else
1031 LOG_WARN("Weather conditions in the danger zone! Close the dome immediately!");
1032
1033 return true;
1034 }
1035 }
1036#endif
1037 if (!strcmp("TELESCOPE_PIER_SIDE", propName) && deviceName == ActiveDeviceTP[ACTIVE_MOUNT].getText())
1038 {
1039 // crack the message
1040 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
1041 {
1042 const char * elemName = findXMLAttValu(ep, "name");
1043
1044 if (!strcmp(elemName, "PIER_EAST") && !strcmp(pcdataXMLEle(ep), "On"))
1045 mountOTASide = -1;
1046 else if (!strcmp(elemName, "PIER_WEST") && !strcmp(pcdataXMLEle(ep), "On"))
1047 mountOTASide = 1;
1048 }
1049
1050 return true;
1051 }
1052
1053 controller->ISSnoopDevice(root);
1054
1055 return DefaultDevice::ISSnoopDevice(root);
1056}
1057
1061bool Dome::SetBacklash(int32_t steps)
1062{
1063 INDI_UNUSED(steps);
1064 LOG_ERROR("Dome does not support backlash compensation.");
1065 return false;
1066}
1067
1072{
1073 // If disabled, set the Domeer backlash to zero.
1074 if (enabled)
1075 return SetBacklash(static_cast<int32_t>(DomeBacklashNP[0].getValue()));
1076 else
1077 return SetBacklash(0);
1078}
1079
1084{
1086
1087 ActiveDeviceTP.save(fp);
1088 MountPolicySP.save(fp);
1089 PresetNP.save(fp);
1090 DomeParamNP.save(fp);
1092 DomeAutoSyncSP.save(fp);
1093 OTASideSP.save(fp);
1094
1095 if (HasBacklash())
1096 {
1097 DomeBacklashSP.save(fp);
1098 DomeBacklashNP.save(fp);
1099 }
1100
1101 if (HasShutter())
1102 {
1104 }
1105
1106 controller->saveConfigItems(fp);
1107
1108 return true;
1109}
1110
1114void Dome::triggerSnoop(const char * driverName, const char * snoopedProp)
1115{
1116 LOGF_DEBUG("Active Snoop, driver: %s, property: %s", driverName, snoopedProp);
1117 IDSnoopDevice(driverName, snoopedProp);
1118}
1119
1124{
1125 return MountPolicySP[1].getState() == ISS_ON && IsLocked;
1126}
1127
1131void Dome::buttonHelper(const char * button_n, ISState state, void * context)
1132{
1133 static_cast<Dome *>(context)->processButton(button_n, state);
1134}
1135
1139void Dome::processButton(const char * button_n, ISState state)
1140{
1141 //ignore OFF
1142 if (state == ISS_OFF)
1143 return;
1144
1145 // Dome In
1146 if (!strcmp(button_n, "Dome CW"))
1147 {
1150 else
1152 }
1153 else if (!strcmp(button_n, "Dome CCW"))
1154 {
1157 else
1159 }
1160 else if (!strcmp(button_n, "Dome Abort"))
1161 {
1162 Dome::Abort();
1163 }
1164}
1165
1170{
1171 return m_MountState;
1172}
1173
1178{
1179 switch (value)
1180 {
1181 case SHUTTER_OPENED:
1185 break;
1186
1187 case SHUTTER_CLOSED:
1191 break;
1192
1193
1194 case SHUTTER_MOVING:
1196 break;
1197
1198 case SHUTTER_ERROR:
1200 LOG_WARN("Shutter failure.");
1201 break;
1202
1203 case SHUTTER_UNKNOWN:
1206 LOG_WARN("Unknown shutter status.");
1207 break;
1208 }
1209
1211 m_ShutterState = value;
1212}
1213
1218{
1219 switch (value)
1220 {
1221 case DOME_IDLE:
1223 {
1227 }
1229 {
1232 }
1234 {
1237 }
1238 break;
1239
1240 case DOME_SYNCED:
1242 {
1246 }
1248 {
1251 }
1253 {
1256 }
1257 break;
1258
1259 case DOME_PARKED:
1261 {
1265 }
1267 {
1270 }
1272 {
1275 }
1276 ParkSP.reset();
1279 ParkSP.apply();
1280 IsParked = true;
1281 break;
1282
1283 case DOME_PARKING:
1284 ParkSP.reset();
1287 ParkSP.apply();
1288 break;
1289
1290 case DOME_UNPARKING:
1291 ParkSP.reset();
1294 ParkSP.apply();
1295 break;
1296
1297 case DOME_UNPARKED:
1298 ParkSP.reset();
1301 ParkSP.apply();
1302 IsParked = false;
1303 break;
1304
1305 case DOME_UNKNOWN:
1306 ParkSP.reset();
1308 IsParked = false;
1309 ParkSP.apply();
1310 break;
1311
1312 case DOME_ERROR:
1314 ParkSP.apply();
1315 break;
1316
1317 case DOME_MOVING:
1318 break;
1319 }
1320
1321 m_DomeState = value;
1322}
1323
1335bool Dome::GetTargetAz(double &Az, double &Alt, double &minAz, double &maxAz)
1336{
1337 point3D MountCenter, OptCenter, OptVector, DomeIntersect;
1338 double hourAngle;
1339 double mu1, mu2;
1340
1341 if (HaveLatLong == false)
1342 {
1343 triggerSnoop(ActiveDeviceTP[0].getText(), "GEOGRAPHIC_COORD");
1344 LOG_WARN( "Geographic coordinates are not yet defined, triggering snoop...");
1345 return false;
1346 }
1347
1348 double JD = ln_get_julian_from_sys();
1350 char lstStr[64], latStr[64], lonStr[64];
1351 fs_sexa(lstStr, LST, 2, 3600);
1352 fs_sexa(latStr, observer.latitude, 2, 3600);
1353 fs_sexa(lonStr, observer.longitude, 2, 3600);
1354
1355 // Condensed Initial Coords Log
1356 LOGF_DEBUG("Coords - LST: %s, Lat: %s, Lon: %s", lstStr, latStr, lonStr);
1357
1358 MountCenter.x = DomeMeasurementsNP[DM_EAST_DISPLACEMENT].getValue(); // Positive to East
1359 MountCenter.y = DomeMeasurementsNP[DM_NORTH_DISPLACEMENT].getValue(); // Positive to North
1360 MountCenter.z = DomeMeasurementsNP[DM_UP_DISPLACEMENT].getValue(); // Positive Up
1361
1362 // Get hour angle in hours
1364 char raStr[64], decStr[64], haStr[64];
1366 fs_sexa(decStr, mountEquatorialCoords.declination, 2, 3600);
1367 fs_sexa(haStr, hourAngle, 2, 3600);
1368
1369 // To be sure mountHoriztonalCoords is up to date before potentially using it.
1371 char mountAzStr[64], mountAltStr[64];
1372 fs_sexa(mountAzStr, mountHoriztonalCoords.azimuth, 2, 3600);
1373 fs_sexa(mountAltStr, mountHoriztonalCoords.altitude, 2, 3600);
1374
1375 // Condensed Mount Coords Log
1376 LOGF_DEBUG("Mount - RA: %s, Dec: %s, HA: %s --> Az: %s, Alt: %s", raStr, decStr, haStr, mountAzStr, mountAltStr);
1377
1378 // Near the celestial pole, HA calculated from RA can be unreliable for determining pier side.
1379 // Use Azimuth to infer a more reliable effective HA for the OpticalCenter calculation.
1380 double effectiveHourAngle = hourAngle; // Use original HA by default
1381 const double poleThresholdDec = 89.0; // Degrees
1382 bool nearPole = std::abs(mountEquatorialCoords.declination) > poleThresholdDec;
1383
1384 // Define a threshold for azimuth deviation when near the pole
1385 const double POLE_AZIMUTH_DEVIATION_THRESHOLD = 30.0; // Degrees
1386
1387 // Lambda function to perform the core calculations
1388 auto performCalculations = [&](double currentEffectiveHourAngle)
1389 {
1390 // Side of the telescope with respect of the mount, 1: west, -1: east, 0: use the mid point
1391 int OTASide = 0;
1392 int otaSideSelection = OTASideSP.findOnSwitchIndex(); // Get selected mode
1393
1394 if (OTASideSP.getState() == IPS_OK)
1395 {
1396 if(otaSideSelection == DM_OTA_SIDE_HA || (UseHourAngle && otaSideSelection == DM_OTA_SIDE_MOUNT))
1397 {
1398 // Note if the telescope points West (HA > 0), OTA is at east of the pier, and vice-versa.
1399 double haForSide = nearPole ? currentEffectiveHourAngle : hourAngle; // Use effective HA near pole
1400
1401 // Determine side based on HA. HA=0 means pointing East (OTA West), HA=12 means pointing West (OTA East)
1402 if (haForSide > 0.1 && haForSide < 11.9) // Pointing West
1403 OTASide = -1; // OTA East
1404 else // Pointing East (HA near 0 or 12)
1405 OTASide = 1; // OTA West
1406 }
1407 else if(otaSideSelection == DM_OTA_SIDE_EAST)
1408 OTASide = -1;
1409 else if(otaSideSelection == DM_OTA_SIDE_WEST)
1410 OTASide = 1;
1411 else if(otaSideSelection == DM_OTA_SIDE_MOUNT)
1412 OTASide = mountOTASide;
1413 // DM_OTA_SIDE_IGNORE results in OTASide = 0
1414 }
1415
1416 double otaOffset = OTASide * DomeMeasurementsNP[DM_OTA_OFFSET].getValue();
1417 OpticalCenter(MountCenter, otaOffset, observer.latitude, currentEffectiveHourAngle, OptCenter);
1418
1419 // Get optical axis point. This and the previous form the optical axis line
1421
1422 // Condensed Geometry Log
1423 char effHaStr[64];
1424 fs_sexa(effHaStr, currentEffectiveHourAngle, 2, 3600);
1425 LOGF_DEBUG("Geom - Mount(E:%.2f,N:%.2f,Up:%.2f) OTA(SideSel:%d, SideUsed:%d, Off:%.2f, EffHA:%s) -> OptCenter(X:%.3f,Y:%.3f,Z:%.3f) OptVec(X:%.3f,Y:%.3f,Z:%.3f)",
1426 MountCenter.x, MountCenter.y, MountCenter.z,
1427 otaSideSelection, OTASide, DomeMeasurementsNP[DM_OTA_OFFSET].getValue(), effHaStr,
1428 OptCenter.x, OptCenter.y, OptCenter.z,
1429 OptVector.x, OptVector.y, OptVector.z);
1430
1431 if (Intersection(OptCenter, OptVector, DomeMeasurementsNP[DM_DOME_RADIUS].getValue(), mu1, mu2))
1432 {
1433 // If telescope is pointing over the horizon, the solution is mu1, else is mu2
1434 if (mu1 < 0)
1435 {
1436 LOGF_DEBUG("Intersection mu1 < 0, using mu2 (%.3f)", mu2); // Less critical
1437 mu1 = mu2;
1438 }
1439
1440 DomeIntersect.x = OptCenter.x + mu1 * (OptVector.x );
1441 DomeIntersect.y = OptCenter.y + mu1 * (OptVector.y );
1442 DomeIntersect.z = OptCenter.z + mu1 * (OptVector.z );
1443 LOGF_DEBUG("Intersection - Point(X:%.3f, Y:%.3f, Z:%.3f) Dist: %.3f", DomeIntersect.x, DomeIntersect.y, DomeIntersect.z,
1444 mu1);
1445
1446 // Calculate Azimuth using atan2(x, y) for robustness.
1447 Az = atan2(DomeIntersect.x, DomeIntersect.y) * 180.0 / M_PI;
1448
1449 // Normalize Az to [0, 360) range
1450 if (Az < 0)
1451 {
1452 Az += 360.0;
1453 }
1454 if (Az >= 360.0)
1455 {
1456 Az = 0.0;
1457 }
1458
1459 if ((std::abs(DomeIntersect.x) > 0.00001) || (std::abs(DomeIntersect.y) > 0.00001))
1460 Alt = 180 * atan(DomeIntersect.z / sqrt((DomeIntersect.x * DomeIntersect.x) + (DomeIntersect.y * DomeIntersect.y))) / M_PI;
1461 else
1462 Alt = 90; // Dome Zenith
1463
1464 double RadiusAtAlt = DomeMeasurementsNP[DM_DOME_RADIUS].getValue() * cos(M_PI * Alt / 180);
1465
1466 if (DomeMeasurementsNP[DM_SHUTTER_WIDTH].getValue() < (2 * RadiusAtAlt))
1467 {
1468 double HalfApertureChordAngle = 180 * asin(DomeMeasurementsNP[DM_SHUTTER_WIDTH].getValue() / (2 * RadiusAtAlt)) / M_PI;
1469 minAz = range360(Az - HalfApertureChordAngle);
1470 maxAz = range360(Az + HalfApertureChordAngle);
1471 }
1472 else
1473 {
1474 minAz = 0;
1475 maxAz = 360;
1476 }
1477 return true;
1478 }
1479 return false;
1480 };
1481
1482 // Initial calculation attempt
1483 bool success = performCalculations(effectiveHourAngle);
1484
1485 // If near pole and initial calculation deviates significantly, try the other effectiveHourAngle
1486 if (nearPole && success)
1487 {
1488 double initialAz = Az;
1489 double diff = std::abs(initialAz - mountHoriztonalCoords.azimuth);
1490 if (diff > 180.0) // Handle wrap-around for angular difference
1491 diff = 360.0 - diff;
1492
1493 if (diff > POLE_AZIMUTH_DEVIATION_THRESHOLD)
1494 {
1495 LOGF_DEBUG("Near pole, initial Az (%.2f) deviates from mount Az (%.2f) by %.2f degrees. Attempting alternative effectiveHourAngle.",
1496 initialAz, mountHoriztonalCoords.azimuth, diff);
1497
1498 // Flip effectiveHourAngle
1499 effectiveHourAngle = (effectiveHourAngle == 0.0) ? 12.0 : 0.0;
1500 success = performCalculations(effectiveHourAngle);
1501
1502 if (success)
1503 {
1504 LOGF_DEBUG("Re-calculated Az (%.2f) with alternative effectiveHourAngle (%.2f).", Az, effectiveHourAngle);
1505 }
1506 else
1507 {
1508 LOG_WARN("Re-calculation with alternative effectiveHourAngle failed.");
1509 }
1510 }
1511 }
1512
1513 if (success)
1514 {
1515 // Final Condensed Log
1516 char currentAzStr[64], targetAzStr[64], targetAltStr[64], minAzStr[64], maxAzStr[64];
1517 fs_sexa(currentAzStr, DomeAbsPosNP[0].getValue(), 2, 3600);
1518 fs_sexa(targetAzStr, Az, 2, 3600);
1519 fs_sexa(targetAltStr, Alt, 2, 3600);
1520 fs_sexa(minAzStr, minAz, 2, 3600);
1521 fs_sexa(maxAzStr, maxAz, 2, 3600);
1522 LOGF_DEBUG("Result - Current Az:%s , Target Az: %s, Alt: %s --> Range Min: %s, Max: %s", currentAzStr, targetAzStr,
1523 targetAltStr, minAzStr, maxAzStr);
1524 return true;
1525 }
1526 else
1527 {
1528 LOG_WARN("Optical axis does not intersect dome sphere.");
1529 return false;
1530 }
1531}
1532
1533bool Dome::Intersection(point3D p1, point3D dp, double r, double &mu1, double &mu2)
1534{
1535 double a, b, c;
1536 double bb4ac;
1537
1538 a = dp.x * dp.x + dp.y * dp.y + dp.z * dp.z;
1539 b = 2 * (dp.x * p1.x + dp.y * p1.y + dp.z * p1.z);
1540 c = 0.0;
1541 c = c + p1.x * p1.x + p1.y * p1.y + p1.z * p1.z;
1542 c = c - r * r;
1543 bb4ac = b * b - 4 * a * c;
1544 if ((fabs(a) < 0.0000001) || (bb4ac < 0))
1545 {
1546 mu1 = 0;
1547 mu2 = 0;
1548 return false;
1549 }
1550
1551 mu1 = (-b + sqrt(bb4ac)) / (2 * a);
1552 mu2 = (-b - sqrt(bb4ac)) / (2 * a);
1553
1554 return true;
1555}
1556
1557bool Dome::OpticalCenter(point3D MountCenter, double dOpticalAxis, double Lat, double Ah, point3D &OP)
1558{
1559 double q, f;
1560 double cosf, sinf, cosq, sinq;
1561
1562 // Note: this transformation is a circle rotated around X axis -(90 - Lat) degrees
1563 q = M_PI * (90 - Lat) / 180;
1564 f = -M_PI * (180 + Ah * 15) / 180;
1565
1566 cosf = cos(f);
1567 sinf = sin(f);
1568 cosq = cos(q);
1569 sinq = sin(q);
1570
1571 // The sign of dOpticalAxis determines de side of the tube
1572 OP.x = (dOpticalAxis * cosf + MountCenter.x);
1573 OP.y = (dOpticalAxis * sinf * cosq + MountCenter.y);
1574 OP.z = (dOpticalAxis * sinf * sinq + MountCenter.z);
1575
1576 return true;
1577}
1578
1579bool Dome::OpticalVector(double Az, double Alt, point3D &OV)
1580{
1581 double q, f;
1582
1583 q = M_PI * Alt / 180;
1584 f = M_PI * Az / 180;
1585 OV.x = cos(q) * sin(f);
1586 OV.y = cos(q) * cos(f);
1587 OV.z = sin(q);
1588
1589 return true;
1590}
1591
1592double Dome::Csc(double x)
1593{
1594 return 1.0 / sin(x);
1595}
1596
1597double Dome::Sec(double x)
1598{
1599 return 1.0 / cos(x);
1600}
1601
1602bool Dome::CheckHorizon(double HA, double dec, double lat)
1603{
1604 double sinh_value;
1605
1606 sinh_value = cos(lat) * cos(HA) * cos(dec) + sin(lat) * sin(dec);
1607
1608 if (sinh_value >= 0.0)
1609 return true;
1610
1611 return false;
1612}
1613
1615{
1616 // If not initialized yet, return.
1618 return;
1619
1620 // Dont do this if we haven't had co-ordinates from the mount yet
1621 if (!HaveLatLong)
1622 return;
1623 if (!HaveRaDec)
1624 return;
1625
1627
1628 // Control debug flooding
1631 {
1634 LOGF_DEBUG("Updated telescope Az: %g - Alt: %g", prev_az, prev_alt);
1635 }
1636
1637 // Check if we need to move if mount is unparked.
1638 if (IsMountParked == false)
1640}
1641
1643{
1645 && DomeAutoSyncSP[0].getState() == ISS_ON)
1646 {
1647 if (CanPark())
1648 {
1649 if (isParked() == true)
1650 {
1651 if (AutoSyncWarning == false)
1652 {
1653 LOG_WARN("Cannot perform autosync with dome parked. Please unpark to enable autosync operation.");
1654 AutoSyncWarning = true;
1655 }
1656 return;
1657 }
1658 }
1659
1660 AutoSyncWarning = false;
1661 double targetAz = 0, targetAlt = 0, minAz = 0, maxAz = 0;
1662 bool res;
1663 res = GetTargetAz(targetAz, targetAlt, minAz, maxAz);
1664 if (!res)
1665 {
1666 LOGF_DEBUG("GetTargetAz failed %g", targetAz);
1667 return;
1668 }
1669
1670 double diff = std::abs(targetAz - DomeAbsPosNP[0].getValue());
1671 if (diff > 180.0)
1672 diff = 360.0 - diff;
1673 if (diff > DomeParamNP[0].getValue())
1674 {
1675 IPState ret = Dome::MoveAbs(targetAz);
1676 if (ret == IPS_OK)
1677 LOGF_DEBUG("Dome synced to position %.2f degrees.", targetAz);
1678 else if (ret == IPS_BUSY)
1679 LOGF_DEBUG("Dome is syncing to position %.2f degrees...", targetAz);
1680 else
1681 LOG_ERROR("Dome failed to sync to new requested position.");
1682
1685 }
1686 }
1687}
1688
1689void Dome::SetDomeCapability(uint32_t cap)
1690{
1691 capability = cap;
1692
1693 if (CanAbort())
1694 controller->mapController("Dome Abort", "Dome Abort", Controller::CONTROLLER_BUTTON, "BUTTON_3");
1695}
1696
1698{
1699 switch (status)
1700 {
1701 case SHUTTER_OPENED:
1702 return "Shutter is open.";
1703 case SHUTTER_CLOSED:
1704 return "Shutter is closed.";
1705 case SHUTTER_MOVING:
1706 return "Shutter is moving.";
1707 case SHUTTER_ERROR:
1708 return "Shutter has errors.";
1709 case SHUTTER_UNKNOWN:
1710 default:
1711 return "Shutter status is unknown.";
1712 }
1713}
1714
1716{
1718
1719 switch (parkDataType)
1720 {
1721 case PARK_NONE:
1722 DomeMotionSP[DOME_CW].setLabel("Open");
1723 DomeMotionSP[DOME_CCW].setLabel("Close");
1724 break;
1725
1726 case PARK_AZ:
1727 ParkPositionNP[AXIS_AZ].fill("PARK_AZ", "AZ D:M:S", "%10.6m", 0.0, 360.0, 0.0, 0);
1728 ParkPositionNP.fill(getDeviceName(), "DOME_PARK_POSITION", "Park Position", SITE_TAB, IP_RW, 60, IPS_IDLE);
1729 break;
1730
1731 case PARK_AZ_ENCODER:
1732 ParkPositionNP[AXIS_AZ].fill("PARK_AZ", "AZ Encoder", "%.0f", 0, 16777215, 1, 0);
1733 ParkPositionNP.fill(getDeviceName(), "DOME_PARK_POSITION", "Park Position", SITE_TAB, IP_RW, 60, IPS_IDLE);
1734 break;
1735
1736 default:
1737 break;
1738 }
1739}
1740
1741void Dome::SyncParkStatus(bool isparked)
1742{
1743 IsParked = isparked;
1744
1746
1747 if (IsParked)
1748 {
1750 LOG_INFO("Dome is parked.");
1751 }
1752 else
1753 {
1755 LOG_INFO("Dome is unparked.");
1756 }
1757}
1758
1759void Dome::SetParked(bool isparked)
1760{
1761 SyncParkStatus(isparked);
1762 WriteParkData();
1763}
1764
1766{
1767 return IsParked;
1768}
1769
1771{
1772 const char * loadres = LoadParkData();
1773 if (loadres)
1774 {
1775 LOGF_INFO("InitPark: No Park data in file %s: %s", ParkDataFileName.c_str(), loadres);
1776 SyncParkStatus(false);
1777 return false;
1778 }
1779
1780 SyncParkStatus(isParked());
1781
1782 if (parkDataType != PARK_NONE)
1783 {
1784 LOGF_DEBUG("InitPark Axis1 %.2f", Axis1ParkPosition);
1785 ParkPositionNP[AXIS_AZ].setValue(Axis1ParkPosition);
1787
1788 // If parked, store the position as current azimuth angle or encoder ticks
1789 if (isParked() && CanAbsMove())
1790 {
1791 DomeAbsPosNP[0].setValue(ParkPositionNP[AXIS_AZ].getValue());
1793 }
1794 }
1795
1796 return true;
1797}
1798
1799const char * Dome::LoadParkXML()
1800{
1801 wordexp_t wexp;
1802 FILE * fp;
1803 LilXML * lp;
1804 static char errmsg[XML_ERROR_SIZE];
1805
1806 XMLEle * parkxml;
1807 XMLAtt * ap;
1808 bool devicefound = false;
1809
1810 ParkDeviceName = getDeviceName();
1811 ParkstatusXml = nullptr;
1812 ParkdeviceXml = nullptr;
1813 ParkpositionXml = nullptr;
1814 ParkpositionAxis1Xml = nullptr;
1815
1816 if (wordexp(ParkDataFileName.c_str(), &wexp, 0))
1817 {
1818 wordfree(&wexp);
1819 return "Badly formed filename.";
1820 }
1821
1822 if (!(fp = fopen(wexp.we_wordv[0], "r")))
1823 {
1824 wordfree(&wexp);
1825 return strerror(errno);
1826 }
1827 wordfree(&wexp);
1828
1829 lp = newLilXML();
1830
1831 if (ParkdataXmlRoot)
1832 delXMLEle(ParkdataXmlRoot);
1833
1834 ParkdataXmlRoot = readXMLFile(fp, lp, errmsg);
1835 fclose(fp);
1836
1837 delLilXML(lp);
1838 if (!ParkdataXmlRoot)
1839 return errmsg;
1840
1841 if (!strcmp(tagXMLEle(nextXMLEle(ParkdataXmlRoot, 1)), "parkdata"))
1842 return "Not a park data file";
1843
1844 parkxml = nextXMLEle(ParkdataXmlRoot, 1);
1845
1846 while (parkxml)
1847 {
1848 if (strcmp(tagXMLEle(parkxml), "device"))
1849 {
1850 parkxml = nextXMLEle(ParkdataXmlRoot, 0);
1851 continue;
1852 }
1853 ap = findXMLAtt(parkxml, "name");
1854 if (ap && (!strcmp(valuXMLAtt(ap), ParkDeviceName)))
1855 {
1856 devicefound = true;
1857 break;
1858 }
1859 parkxml = nextXMLEle(ParkdataXmlRoot, 0);
1860 }
1861
1862 if (!devicefound)
1863 return "No park data found for this device";
1864
1865 ParkdeviceXml = parkxml;
1866 ParkstatusXml = findXMLEle(parkxml, "parkstatus");
1867
1868 if (parkDataType != PARK_NONE)
1869 {
1870 ParkpositionXml = findXMLEle(parkxml, "parkposition");
1871 ParkpositionAxis1Xml = findXMLEle(ParkpositionXml, "axis1position");
1872
1873 if (ParkpositionAxis1Xml == nullptr)
1874 {
1875 return "Park position invalid or missing.";
1876 }
1877 }
1878 else if (ParkstatusXml == nullptr)
1879 return "Park status invalid or missing.";
1880
1881 return nullptr;
1882}
1883
1885{
1886 IsParked = false;
1887
1888 const char * result = LoadParkXML();
1889 if (result != nullptr)
1890 return result;
1891
1892 if (!strcmp(pcdataXMLEle(ParkstatusXml), "true"))
1893 IsParked = true;
1894
1895 if (parkDataType == PARK_NONE)
1896 return nullptr;
1897
1898 double axis1Pos = std::numeric_limits<double>::quiet_NaN();
1899
1900 int rc = sscanf(pcdataXMLEle(ParkpositionAxis1Xml), "%lf", &axis1Pos);
1901 if (rc != 1)
1902 {
1903 return "Unable to parse Park Position Axis 1.";
1904 }
1905
1906 if (std::isnan(axis1Pos) == false)
1907 {
1908 Axis1ParkPosition = axis1Pos;
1909 return nullptr;
1910 }
1911
1912 return "Failed to parse Park Position.";
1913}
1914
1916{
1917 // We need to refresh parking data in case other devices parking states were updated since we
1918 // read the data the first time.
1919 if (LoadParkXML() != nullptr)
1920 LOG_DEBUG("Failed to refresh parking data.");
1921
1922 wordexp_t wexp;
1923 FILE * fp;
1924 char pcdata[30];
1925 ParkDeviceName = getDeviceName();
1926
1927 if (wordexp(ParkDataFileName.c_str(), &wexp, 0))
1928 {
1929 wordfree(&wexp);
1930 LOGF_INFO("WriteParkData: can not write file %s: Badly formed filename.",
1931 ParkDataFileName.c_str());
1932 return false;
1933 }
1934
1935 if (!(fp = fopen(wexp.we_wordv[0], "w")))
1936 {
1937 wordfree(&wexp);
1938 LOGF_INFO("WriteParkData: can not write file %s: %s", ParkDataFileName.c_str(),
1939 strerror(errno));
1940 return false;
1941 }
1942
1943 if (!ParkdataXmlRoot)
1944 ParkdataXmlRoot = addXMLEle(nullptr, "parkdata");
1945
1946 if (!ParkdeviceXml)
1947 {
1948 ParkdeviceXml = addXMLEle(ParkdataXmlRoot, "device");
1949 addXMLAtt(ParkdeviceXml, "name", ParkDeviceName);
1950 }
1951
1952 if (!ParkstatusXml)
1953 ParkstatusXml = addXMLEle(ParkdeviceXml, "parkstatus");
1954 if (parkDataType != PARK_NONE)
1955 {
1956 if (!ParkpositionXml)
1957 ParkpositionXml = addXMLEle(ParkdeviceXml, "parkposition");
1958 if (!ParkpositionAxis1Xml)
1959 ParkpositionAxis1Xml = addXMLEle(ParkpositionXml, "axis1position");
1960 }
1961
1962 editXMLEle(ParkstatusXml, (IsParked ? "true" : "false"));
1963
1964 if (parkDataType != PARK_NONE)
1965 {
1966 snprintf(pcdata, sizeof(pcdata), "%lf", Axis1ParkPosition);
1967 editXMLEle(ParkpositionAxis1Xml, pcdata);
1968 }
1969
1970 prXMLEle(fp, ParkdataXmlRoot, 0);
1971 fclose(fp);
1972 wordfree(&wexp);
1973
1974 return true;
1975}
1976
1978{
1979 return Axis1ParkPosition;
1980}
1981
1983{
1984 return Axis1DefaultParkPosition;
1985}
1986
1987void Dome::SetAxis1Park(double value)
1988{
1989 Axis1ParkPosition = value;
1990 ParkPositionNP[AXIS_RA].setValue(value);
1992}
1993
1995{
1996 Axis1DefaultParkPosition = value;
1997}
1998
2000{
2001 // Check if it is already parked.
2002 if (CanPark())
2003 {
2004 if (parkDataType != PARK_NONE && isParked())
2005 {
2006 LOG_WARN( "Please unpark the dome before issuing any motion commands.");
2007 return IPS_ALERT;
2008 }
2009 }
2010
2013 {
2014 LOG_WARN( "Please stop dome before issuing any further motion commands.");
2015 return IPS_ALERT;
2016 }
2017
2018 auto current_direction = DomeMotionSP.findOnSwitchIndex();
2019
2020 // if same move requested, return
2021 if (DomeMotionSP.getState() == IPS_BUSY && current_direction == dir && operation == MOTION_START)
2022 return IPS_BUSY;
2023
2024 DomeMotionSP.setState(Move(dir, operation));
2025
2027 {
2028 m_DomeState = (operation == MOTION_START) ? DOME_MOVING : DOME_IDLE;
2030 if (operation == MOTION_START)
2032 }
2033
2035
2036 return DomeMotionSP.getState();
2037}
2038
2040{
2041 if (CanRelMove() == false)
2042 {
2043 LOG_ERROR( "Dome does not support relative motion.");
2044 return IPS_ALERT;
2045 }
2046
2047 if (m_DomeState == DOME_PARKED)
2048 {
2049 LOG_ERROR( "Please unpark before issuing any motion commands.");
2052 return IPS_ALERT;
2053 }
2054
2056 {
2057 LOG_WARN( "Please stop dome before issuing any further motion commands.");
2060 return IPS_ALERT;
2061 }
2062
2063 IPState rc;
2064
2065 if ((rc = MoveRel(azDiff)) == IPS_OK)
2066 {
2069 DomeRelPosNP[0].setValue(azDiff);
2070 LOGF_INFO("Dome moved %.2f degrees %s.", azDiff, (azDiff > 0) ? "clockwise" : "counter clockwise");
2072 if (CanAbsMove())
2073 {
2076 }
2077 return IPS_OK;
2078 }
2079 else if (rc == IPS_BUSY)
2080 {
2082 DomeRelPosNP[0].setValue(azDiff);
2084 LOGF_INFO("Dome moved %.2f degrees %s.", azDiff, (azDiff > 0) ? "clockwise" : "counter clockwise");
2086 if (CanAbsMove())
2087 {
2090 }
2091
2094 DomeMotionSP[DOME_CW].setState((azDiff > 0) ? ISS_ON : ISS_OFF);
2095 DomeMotionSP[DOME_CCW].setState((azDiff < 0) ? ISS_ON : ISS_OFF);
2097 return IPS_BUSY;
2098 }
2099
2102 LOG_WARN("Dome failed to move to new requested position.");
2104 return IPS_ALERT;
2105}
2106
2108{
2109 if (CanAbsMove() == false)
2110 {
2111 LOG_ERROR("Dome does not support MoveAbs(). MoveAbs() must be implemented in the child class.");
2112 return IPS_ALERT;
2113 }
2114
2115 if (m_DomeState == DOME_PARKED)
2116 {
2117 LOG_ERROR( "Please unpark before issuing any motion commands.");
2120 return IPS_ALERT;
2121 }
2122
2124 {
2125 LOG_WARN( "Please stop dome before issuing any further motion commands.");
2126 return IPS_ALERT;
2127 }
2128
2129 IPState rc;
2130
2131 if (az < DomeAbsPosNP[0].getMin() || az > DomeAbsPosNP[0].getMax())
2132 {
2133 LOGF_ERROR( "Error: requested azimuth angle %.2f is out of range.", az);
2136 return IPS_ALERT;
2137 }
2138
2139 if ((rc = MoveAbs(az)) == IPS_OK)
2140 {
2143 DomeAbsPosNP[0].setValue(az);
2144 LOGF_INFO("Dome moved to position %.2f degrees azimuth.", az);
2146
2147 return IPS_OK;
2148 }
2149 else if (rc == IPS_BUSY)
2150 {
2153 LOGF_INFO("Dome is moving to position %.2f degrees azimuth...", az);
2155
2158 DomeMotionSP[DOME_CW].setState((az > DomeAbsPosNP[0].getValue()) ? ISS_ON : ISS_OFF);
2159 DomeMotionSP[DOME_CCW].setState((az < DomeAbsPosNP[0].getValue()) ? ISS_ON : ISS_OFF);
2161
2162 return IPS_BUSY;
2163 }
2164
2167 LOG_INFO("Dome failed to move to new requested position.");
2169 return IPS_ALERT;
2170}
2171
2172bool Dome::Sync(double az)
2173{
2174 INDI_UNUSED(az);
2175 LOG_WARN("Syncing is not supported.");
2176 return false;
2177}
2178
2180{
2181 if (CanAbort() == false)
2182 {
2183 LOG_ERROR( "Dome does not support abort.");
2184 return false;
2185 }
2186
2187 AbortSP.reset();
2188
2189 if (Abort())
2190 {
2192
2194 {
2195 ParkSP.reset();
2197 {
2198 DEBUG(Logger::DBG_SESSION, "Parking aborted.");
2199 // If parking was aborted then it was UNPARKED before
2201 }
2202 else
2203 {
2204 DEBUG(Logger::DBG_SESSION, "UnParking aborted.");
2205 // If unparking aborted then it was PARKED before
2207 }
2208
2210 ParkSP.apply();
2211 }
2212
2214 }
2215 else
2216 {
2218
2219 // If alert was aborted during parking or unparking, the parking state is unknown
2221 {
2222 ParkSP.reset();
2224 ParkSP.apply();
2225 }
2226 }
2227
2228 AbortSP.apply();
2229
2230 return (AbortSP.getState() == IPS_OK);
2231}
2232
2233bool Dome::SetSpeed(double speed)
2234{
2235 if (HasVariableSpeed() == false)
2236 {
2237 LOG_ERROR( "Dome does not support variable speed.");
2238 return false;
2239 }
2240
2241 if (SetSpeed(speed))
2242 {
2244 DomeSpeedNP[0].setValue(speed);
2245 }
2246 else
2248
2250
2251 return (DomeSpeedNP.getState() == IPS_OK);
2252}
2253
2255{
2256 if (HasShutter() == false)
2257 {
2258 LOG_ERROR( "Dome does not have shutter control.");
2259 return IPS_ALERT;
2260 }
2261
2262 auto currentStatus = DomeShutterSP.findOnSwitchIndex();
2263
2264 // No change of status, let's return
2265 if (DomeShutterSP.getState() == IPS_BUSY && currentStatus == operation)
2266 {
2268 return DomeShutterSP.getState();
2269 }
2270
2272
2273 if (DomeShutterSP.getState() == IPS_OK)
2274 {
2275 LOGF_INFO("Shutter is %s.", (operation == SHUTTER_OPEN ? "open" : "closed"));
2278 return DomeShutterSP.getState();
2279 }
2280 else if (DomeShutterSP.getState() == IPS_BUSY)
2281 {
2283 DomeShutterSP[operation].setState(ISS_ON);
2284 LOGF_INFO("Shutter is %s...", (operation == 0 ? "opening" : "closing"));
2287 return DomeShutterSP.getState();
2288 }
2289
2290 LOGF_INFO("Shutter failed to %s.", (operation == 0 ? "open" : "close"));
2292 return IPS_ALERT;
2293}
2294
2296{
2297 // Not really supposed to get this at all, but just in case.
2298 if (CanPark() == false)
2299 {
2300 LOG_ERROR( "Dome does not support parking.");
2301 return IPS_ALERT;
2302 }
2303
2304 // No need to park if parked already.
2305 if (m_DomeState == DOME_PARKED)
2306 {
2307 ParkSP.reset();
2309 LOG_INFO("Dome already parked.");
2310 ParkSP.apply();
2311 return IPS_OK;
2312 }
2313
2314 // Check if dome is locked due to Mount Policy
2315 if (isLocked())
2316 {
2317 ParkSP.reset();
2318 ParkSP[1].s = ISS_ON;
2320 ParkSP.apply();
2321 LOG_INFO("Cannot Park Dome when mount is locking. See: Mount Policy in options tab.");
2322 return IPS_ALERT;
2323 }
2324
2325 // Now ask child driver to start the actual parking process.
2326 ParkSP.setState(Park());
2327
2328 // IPS_OK is when it is immediately parked so realisticly this does not happen
2329 // unless dome is physically parked but just needed a state change.
2330 if (ParkSP.getState() == IPS_OK)
2331 SetParked(true);
2332 // Dome is moving to parking position
2333 else if (ParkSP.getState() == IPS_BUSY)
2334 {
2336
2337 if (CanAbsMove())
2339
2340 ParkSP.reset();
2342 }
2343 else
2344 ParkSP.apply();
2345
2346 return ParkSP.getState();
2347}
2348
2350{
2351 if (CanPark() == false)
2352 {
2353 LOG_ERROR( "Dome does not support parking.");
2354 return IPS_ALERT;
2355 }
2356
2357 if (m_DomeState != DOME_PARKED)
2358 {
2359 ParkSP.reset();
2361 LOG_INFO("Dome already unparked.");
2362 ParkSP.apply();
2363 return IPS_OK;
2364 }
2365
2367
2368 if (ParkSP.getState() == IPS_OK)
2369 SetParked(false);
2370 else if (ParkSP.getState() == IPS_BUSY)
2372 else
2373 ParkSP.apply();
2374
2375 return ParkSP.getState();
2376}
2377
2379{
2380 LOG_WARN( "Parking is not supported.");
2381 return false;
2382}
2383
2385{
2386 LOG_WARN( "Parking is not supported.");
2387 return false;
2388}
2389
2391{
2392 return false;
2393}
2394
2395bool Dome::callHandshake()
2396{
2397 if (domeConnection > 0)
2398 {
2401 else if (getActiveConnection() == tcpConnection)
2403 }
2404
2405 return Handshake();
2406}
2407
2409{
2410 return domeConnection;
2411}
2412
2413void Dome::setDomeConnection(const uint8_t &value)
2414{
2416
2417 if (value == 0 || (mask & value) == 0)
2418 {
2419 LOGF_ERROR( "Invalid connection mode %d", value);
2420 return;
2421 }
2422
2423 domeConnection = value;
2424}
2425
2426}
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The Serial class manages connection with serial devices including Bluetooth. Serial communication is ...
The TCP class manages connection with devices over the network via TCP/IP. Upon successful connection...
int getPortFD() const
bool isConnected() const
const char * getDeviceName() const
INDI::PropertyText getText(const char *name) const
The Controller class provides functionality to access a controller (e.g. joystick) input and send it ...
void setButtonCallback(buttonFunc buttonCallback)
setButtonCallback Sets the callback function when a new button input is detected.
virtual bool ISSnoopDevice(XMLEle *root)
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
void mapController(const char *propertyName, const char *propertyLabel, ControllerType type, const char *initialValue)
mapController adds a new property to the joystick's settings.
virtual bool initProperties()
virtual bool saveConfigItems(FILE *fp)
virtual void ISGetProperties(const char *dev)
virtual bool updateProperties()
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
virtual void ISGetProperties(const char *dev)
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISSnoopDevice(XMLEle *root)
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
void setDriverInterface(uint32_t value)
setInterface Set driver interface. By default the driver interface is set to GENERAL_DEVICE....
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
Connection::Interface * getActiveConnection()
void addDebugControl()
Add Debug control to the driver.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
Class to provide general functionality of a Dome device.
Definition indidome.h:75
bool CanPark()
Definition indidome.h:242
INDI::PropertySwitch PresetGotoSP
Definition indidome.h:574
double prev_alt
Definition indidome.h:602
INDI::IHorizontalCoordinates mountHoriztonalCoords
Definition indidome.h:621
Connection::TCP * tcpConnection
Definition indidome.h:608
DomeState m_DomeState
Definition indidome.h:611
virtual IPState UnPark()
UnPark dome. The action of the Unpark command is dome specific, but it may include opening the shutte...
uint32_t capability
Definition indidome.h:599
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...
INDI::PropertySwitch ShutterParkPolicySP
Definition indidome.h:566
@ SHUTTER_OPEN_ON_UNPARK
Definition indidome.h:570
@ SHUTTER_CLOSE_ON_PARK
Definition indidome.h:569
INDI::PropertySwitch ParkOptionSP
Definition indidome.h:552
const char * GetShutterStatusString(ShutterState status)
getShutterStatusString
INDI::IEquatorialCoordinates mountEquatorialCoords
Definition indidome.h:622
bool CheckHorizon(double HA, double dec, double lat)
CheckHorizon Returns true if telescope points above horizon.
INDI::PropertyNumber DomeRelPosNP
Definition indidome.h:538
bool isParked()
isParked is dome currently parked?
bool HaveRaDec
Definition indidome.h:624
INDI::PropertyNumber DomeParamNP
Definition indidome.h:542
bool OpticalCenter(point3D MountCenter, double dOpticalAxis, double Lat, double Ah, point3D &OP)
OpticalCenter This function calculates the distance from the optical axis to the Dome center.
@ DM_OTA_SIDE_EAST
Definition indidome.h:583
@ DM_OTA_SIDE_MOUNT
Definition indidome.h:585
@ DM_OTA_SIDE_IGNORE
Definition indidome.h:587
@ DM_OTA_SIDE_WEST
Definition indidome.h:584
@ DM_OTA_SIDE_HA
Definition indidome.h:586
DomeState
Dome status.
Definition indidome.h:130
@ DOME_UNPARKING
Definition indidome.h:135
@ DOME_UNPARKED
Definition indidome.h:137
INDI::PropertyNumber ParkPositionNP
Definition indidome.h:550
virtual bool SetSpeed(double rpm)
SetSpeed Set Dome speed. This does not initiate motion, it sets the speed for the next motion command...
virtual bool Sync(double az)
Sync sets the dome current azimuth as the supplied azimuth position.
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
Definition indidome.cpp:270
ShutterState m_ShutterState
Definition indidome.h:612
double prev_dec
Definition indidome.h:602
bool CanRelMove()
Definition indidome.h:234
void SetAxis1Park(double value)
SetRAPark Set current AZ parking position. The data park file (stored in ~/.indi/ParkData....
double prev_ra
Definition indidome.h:602
void UpdateMountCoords()
updateCoords updates the horizontal coordinates (Az & Alt) of the mount from the snooped RA,...
IPState getMountState() const
void SetAxis1ParkDefault(double steps)
SetAxis1Park Set default AZ parking position.
ShutterOperation
Shutter operation command.
Definition indidome.h:114
@ SHUTTER_CLOSE
Definition indidome.h:116
INDI::PropertySwitch AbortSP
Definition indidome.h:540
INDI::PropertyNumber DomeSpeedNP
Definition indidome.h:532
DomeMotionCommand
Definition indidome.h:97
@ MOTION_STOP
Definition indidome.h:99
@ MOTION_START
Definition indidome.h:98
double Csc(double x)
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
Definition indidome.cpp:105
double prev_az
Definition indidome.h:602
INDI::PropertyNumber DomeMeasurementsNP
Definition indidome.h:576
virtual void UpdateAutoSync()
UpdateAutoSync This function calculates the target dome azimuth from the mount's target coordinates g...
INDI::PropertyNumber DomeBacklashNP
Definition indidome.h:597
virtual IPState MoveRel(double azDiff)
Move the Dome to an relative position.
double GetAxis1Park()
bool CanSync()
Definition indidome.h:250
bool HasShutter()
Definition indidome.h:258
@ SHUTTER_ERROR
Definition indidome.h:151
@ SHUTTER_MOVING
Definition indidome.h:149
@ SHUTTER_UNKNOWN
Definition indidome.h:150
@ SHUTTER_OPENED
Definition indidome.h:147
@ SHUTTER_CLOSED
Definition indidome.h:148
bool isLocked()
isLocked, is the dome currently locked?
virtual void ActiveDevicesUpdated()
Definition indidome.h:527
INDI::PropertySwitch ParkSP
Definition indidome.h:548
int mountOTASide
Definition indidome.h:590
bool HasVariableSpeed()
Definition indidome.h:266
virtual bool SetBacklash(int32_t steps)
SetBacklash Set the dome backlash compensation value.
double GetAxis1ParkDefault()
virtual IPState Park()
Goto Park Position. The park position is an absolute azimuth value.
const char * LoadParkData()
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
IPState m_MountState
Definition indidome.h:613
virtual bool SetDefaultPark()
SetDefaultPark Set default coordinates/encoders value as the desired parking position.
bool Intersection(point3D p1, point3D p2, double r, double &mu1, double &mu2)
Intersection Calculate the intersection of a ray and a sphere. The line segment is defined from p1 to...
virtual bool SetBacklashEnabled(bool enabled)
SetBacklashEnabled Enables or disables the dome backlash compensation.
double Sec(double x)
virtual IPState MoveAbs(double az)
Move the Dome to an absolute azimuth.
bool CanAbort()
Definition indidome.h:218
INDI::PropertySwitch DomeMotionSP
Definition indidome.h:534
IGeographicCoordinates observer
Definition indidome.h:616
virtual bool ISSnoopDevice(XMLEle *root) override
Process a snoop event from INDI server. This function is called when a snooped property is updated in...
Definition indidome.cpp:841
static void buttonHelper(const char *button_n, ISState state, void *context)
@ CONNECTION_SERIAL
Definition indidome.h:172
@ CONNECTION_TCP
Definition indidome.h:173
@ CONNECTION_NONE
Definition indidome.h:171
INDI::PropertySwitch DomeBacklashSP
Definition indidome.h:594
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Dome Presets in the configuration file
virtual bool SetCurrentPark()
SetCurrentPark Set current coordinates/encoders value as the desired parking position.
bool CanAbsMove()
Definition indidome.h:226
@ PARK_AZ_ENCODER
Definition indidome.h:107
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
Definition indidome.cpp:809
void setShutterState(const ShutterState &value)
@ ACTIVE_OUTPUT
Definition indidome.h:559
bool OpticalVector(double Az, double Alt, point3D &OV)
OpticalVector This function calculates a second point for determining the optical axis.
virtual ~Dome()
Definition indidome.cpp:77
bool HasBacklash()
Definition indidome.h:274
void setDomeState(const DomeState &value)
@ MOUNT_IGNORED
Definition indidome.h:122
bool InitPark()
InitPark Loads parking data (stored in ~/.indi/ParkData.xml) that contains parking status and parking...
Connection::Serial * serialConnection
Definition indidome.h:607
INDI::PropertySwitch MountPolicySP
Definition indidome.h:563
void SetParkDataType(DomeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
bool WriteParkData()
INDI::PropertySwitch DomeAutoSyncSP
Definition indidome.h:591
INDI::PropertySwitch DomeShutterSP
Definition indidome.h:546
DomeParkData parkDataType
Definition indidome.h:600
virtual bool Handshake()
perform handshake with device to check communication
@ DM_NORTH_DISPLACEMENT
Definition indidome.h:85
@ DM_EAST_DISPLACEMENT
Definition indidome.h:86
@ DM_SHUTTER_WIDTH
Definition indidome.h:84
@ DM_DOME_RADIUS
Definition indidome.h:83
@ DM_OTA_OFFSET
Definition indidome.h:88
@ DM_UP_DISPLACEMENT
Definition indidome.h:87
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
bool HaveLatLong
Definition indidome.h:618
virtual IPState ControlShutter(ShutterOperation operation)
Open or Close shutter.
INDI::PropertyText ActiveDeviceTP
Definition indidome.h:554
INDI::PropertySwitch OTASideSP
Definition indidome.h:578
bool GetTargetAz(double &Az, double &Alt, double &minAz, double &maxAz)
GetTargetAz.
INDI::PropertyNumber DomeSyncNP
Definition indidome.h:544
uint8_t getDomeConnection() const
virtual IPState Move(DomeDirection dir, DomeMotionCommand operation)
Move the Dome in a particular direction.
INDI::PropertyNumber PresetNP
Definition indidome.h:573
virtual bool Abort()
Abort all dome motion.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
void setLabel(const char *label)
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
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)
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
INDI::PropertyViewText * getText() const
void callOnTimeout(const std::function< void()> &callback)
Definition inditimer.cpp:76
void start()
Starts or restarts the timer with the timeout specified in interval.
Definition inditimer.cpp:82
void stop()
Stops the timer.
Definition inditimer.cpp:97
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * SITE_TAB
SITE_TAB Where all site information setting are located.
const char * OPTIONS_TAB
OPTIONS_TAB Where all the driver's options are located. Those may include auxiliary controls,...
#define M_PI
Definition dsp.h:52
int errno
double ra
double dec
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
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_NOFMANY
Definition indiapi.h:173
@ ISR_ATMOST1
Definition indiapi.h:172
@ AXIS_RA
@ AXIS_AZ
int f_scansexa(const char *str0, double *dp)
convert sexagesimal string str AxBxC to double. x can be anything non-numeric. Any missing A,...
Definition indicom.c:194
double range360(double r)
range360 Limits an angle to be between 0-360 degrees.
Definition indicom.c:1321
int fs_sexa(char *out, double a, int w, int fracbase)
Converts a sexagesimal number to a string. sprint the variable a in sexagesimal format into out[].
Definition indicom.c:130
double get_local_hour_angle(double sideral_time, double ra)
get_local_hour_angle Returns local hour angle of an object
Definition indicom.c:1379
Implementations for common driver routines.
double get_local_sidereal_time(double longitude)
get_local_sidereal_time Returns local sidereal time given longitude and system clock.
int crackIPState(const char *str, IPState *ip)
Extract property state (Idle, OK, Busy, Alert) from the supplied string.
Definition indidevapi.c:793
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define DOME_COORD_THRESHOLD
Definition indidome.cpp:44
#define DOME_SLAVING_TAB
Definition indidome.cpp:43
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
void IDSnoopDevice(const char *snooped_device, const char *snooped_property)
Function a Driver calls to snoop on another Device. Snooped messages will then arrive via ISSnoopDevi...
Definition indidriver.c:143
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_DEBUG(txt)
Definition indilogger.h:75
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");.
Definition indilogger.h:56
#define LOGF_WARN(fmt,...)
Definition indilogger.h:81
#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
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition lilxml.cpp:644
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition lilxml.cpp:555
XMLAtt * findXMLAtt(XMLEle *ep, const char *name)
Find an XML attribute within an XML element.
Definition lilxml.cpp:524
LilXML * newLilXML()
Create a new lilxml parser.
Definition lilxml.cpp:150
char * tagXMLEle(XMLEle *ep)
Return the tag of an XML element.
Definition lilxml.cpp:600
XMLEle * addXMLEle(XMLEle *parent, const char *tag)
add an element with the given tag to the given element. parent can be NULL to make a new root.
Definition lilxml.cpp:670
XMLEle * findXMLEle(XMLEle *ep, const char *tag)
Find an XML element within an XML element.
Definition lilxml.cpp:537
char * valuXMLAtt(XMLAtt *ap)
Return the value of an XML attribute.
Definition lilxml.cpp:624
void editXMLEle(XMLEle *ep, const char *pcdata)
set the pcdata of the given element
Definition lilxml.cpp:698
void prXMLEle(FILE *fp, XMLEle *ep, int level)
Print an XML element.
Definition lilxml.cpp:844
void delXMLEle(XMLEle *ep)
delXMLEle Delete XML element.
Definition lilxml.cpp:167
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition lilxml.cpp:606
void delLilXML(LilXML *lp)
Delete a lilxml parser.
Definition lilxml.cpp:159
XMLAtt * addXMLAtt(XMLEle *ep, const char *name, const char *valu)
Add an XML attribute to an existing XML element.
Definition lilxml.cpp:706
XMLEle * readXMLFile(FILE *fp, LilXML *lp, char ynot[])
Handy wrapper to read one xml file.
Definition lilxml.cpp:653
#define XML_ERROR_SIZE
Definition lilxml.h:73
Namespace to encapsulate INDI client, drivers, and mediator classes.
void EquatorialToHorizontal(IEquatorialCoordinates *object, IGeographicCoordinates *observer, double JD, IHorizontalCoordinates *position)
EquatorialToHorizontal Calculate horizontal coordinates from equatorial coordinates.
Definition libastro.cpp:141
uint16_t type
Definition pwc-ioctl.h:0
double y
Definition indidome.h:34
double x
Definition indidome.h:34
double z
Definition indidome.h:34