Instrument Neutral Distributed Interface INDI 2.2.5
alpaca_dome.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2026 Philippe Bazart, Jérémie Klein. All rights reserved.
3 With partial reuse of code by Jasem Mutlaq, originally written for the
4 Alpaca CCD INDI driver.
5
6 ASCOM Alpaca Dome INDI Driver
7
8 This program is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by the Free
10 Software Foundation; either version 2 of the License, or (at your option)
11 any later version.
12
13 This program is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22
23 The full GNU General Public License is included in this distribution in the
24 file called LICENSE.
25*******************************************************************************/
26
27#include "alpaca_dome.h"
28
29#include <httplib.h>
30#include <string.h>
31#include <chrono>
32#include <thread>
33
34#ifdef _USE_SYSTEM_JSONLIB
35#include <nlohmann/json.hpp>
36#else
37#include <indijson.hpp>
38#endif
39
40// We declare an auto pointer to AlpacaDome
41std::unique_ptr<AlpacaDome> alpaca_dome(new AlpacaDome());
42
49
51
53{
54 if (!isConnected())
55 return;
56
57 updateStatus();
59}
60
62
63void AlpacaDome::ISGetProperties(const char *dev)
64{
66
67 // Always define these properties
71}
72
74
75bool AlpacaDome::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
76{
77 if (dev != nullptr && isDeviceNameMatch(dev))
78 {
79 if (OperationSP.isNameMatch(name))
80 {
81 const char *requestedOperation = IUFindOnSwitchName(states, names, n);
82 if (OperationSP[OPERATION_FIND_HOME].isNameMatch(requestedOperation))
83 {
84 IPState rc = FindHome();
85 if (rc != IPS_ALERT)
86 {
90 }
92 }
93 else if (OperationSP[OPERATION_CALIBRATE].isNameMatch(requestedOperation))
94 {
95 IPState rc = Calibrate();
96 if (rc != IPS_ALERT)
97 {
101 }
103 }
104
106 return true;
107 }
108 }
109
110 return INDI::Dome::ISNewSwitch(dev, name, states, names, n);
111}
112
114
115bool AlpacaDome::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
116{
117 if (dev != nullptr && isDeviceNameMatch(dev))
118 {
120 {
121 ConnectionSettingsNP.update(values, names, n);
124 // Save configuration after update
125 saveConfig();
126 LOG_INFO("Connection settings updated.");
127 return true;
128 }
129 else if (DeviceNumberNP.isNameMatch(name))
130 {
131 DeviceNumberNP.update(values, names, n);
134 // Save configuration after update
135 saveConfig();
136 LOG_INFO("Alpaca device number updated.");
137 return true;
138 }
139 }
140
141 return INDI::Dome::ISNewNumber(dev, name, values, names, n);
142}
143
145
146bool AlpacaDome::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
147{
148 if (dev != nullptr && isDeviceNameMatch(dev) && ServerAddressTP.isNameMatch(name))
149 {
150 ServerAddressTP.update(texts, names, n);
153 // Save configuration after update
154 saveConfig();
155 return true;
156 }
157
158 return INDI::Dome::ISNewText(dev, name, texts, names, n);
159}
160
162
164{
165 return "Alpaca Dome";
166}
167
169
171{
173
174 // Try to load server address from saved config (mirrors TCP plugin pattern).
175 // m_ConfigHost/m_ConfigPort are empty if no config was previously saved.
176 char configHost[MAXINDINAME] = {0};
177 char configPort[MAXINDINAME] = {0};
178 if (IUGetConfigText(getDeviceName(), "SERVER_ADDRESS", "HOST", configHost, MAXINDINAME) == 0)
179 m_ConfigHost = configHost;
180 if (IUGetConfigText(getDeviceName(), "SERVER_ADDRESS", "PORT", configPort, MAXINDINAME) == 0)
181 m_ConfigPort = configPort;
182
183 // Setup server address properties
184 ServerAddressTP[HOST].fill("HOST", "Host", m_ConfigHost.c_str());
185 ServerAddressTP[PORT].fill("PORT", "Port", m_ConfigPort.c_str());
186 ServerAddressTP.fill(getDeviceName(), "SERVER_ADDRESS", "Server", CONNECTION_TAB, IP_RW, 60, IPS_IDLE);
188
189 // Setup device number property
190 DeviceNumberNP[0].fill("DEVICE_NUMBER", "Device Number", "%.0f", 0, 10, 1, 0);
191 DeviceNumberNP.fill(getDeviceName(), "DEVICE_NUMBER", "Alpaca Device", CONNECTION_TAB, IP_RW, 60, IPS_IDLE);
193
194 // Setup connection settings properties
195 ConnectionSettingsNP[TIMEOUT].fill("TIMEOUT", "Connection timeout (ms)", "%.0f", 1, 10000, 100, 5000);
196 ConnectionSettingsNP[RW_TIMEOUT].fill("RW_TIMEOUT", "Read/Write timeout (ms)", "%.0f", 1, 5000, 100, 1000);
197 ConnectionSettingsNP.fill(getDeviceName(), "CONNECTION_SETTINGS", "Connection", CONNECTION_TAB, IP_RW, 60, IPS_IDLE);
199
200 // Device and driver Info property
201 InfoTP[INFO_DEVICE_NAME].fill("INFO_DEVICE_NAME", "Name", "");
202 InfoTP[INFO_DESCRIPTION].fill("INFO_DESCRIPTION", "Description", "");
203 InfoTP[INFO_DRIVER_INFO].fill("INFO_DRIVER_INFO", "Driver", "");
204 InfoTP[INFO_DRIVER_VERSION].fill("INFO_DRIVER_VERSION", "Driver Version", "");
205 InfoTP[INFO_INTERFACE_VERSION].fill("INFO_INTERFACE_VERSION", "ASCOM Dome Interface Version", "");
206 InfoTP.fill(getDeviceName(), "DEVICE_INFO", "Alpaca Device Info", INFO_TAB, IP_RO, 60, IPS_IDLE);
207
208 // Configuring operations about positionning and home definition
209 OperationSP[OPERATION_FIND_HOME].fill("OPERATION_FIND_HOME", "Find Home", ISS_OFF);
210 OperationSP[OPERATION_CALIBRATE].fill("OPERATION_CALIBRATE", "Calibrate", ISS_OFF);
211 OperationSP.fill(getDeviceName(), "OPERATION", "Operation", MAIN_CONTROL_TAB, IP_RW, ISR_ATMOST1, 0, IPS_IDLE);
212
215
216 // Default server address.
217 setDefaultServerAddress("alpaca.local", "7843");
218
219 return true;
220}
221
223
225{
227
228 if (isConnected())
229 {
230 // State properties
231 if (CanFindHome() || CanCalibrate())
232 {
234 }
235
236 // Device Info properties
238 InfoTP[INFO_DEVICE_NAME].setText(m_DeviceName.c_str());
239 InfoTP[INFO_DESCRIPTION].setText(m_Description.c_str());
240 InfoTP[INFO_DRIVER_INFO].setText(m_DriverInfo.c_str());
241 InfoTP[INFO_DRIVER_VERSION].setText(m_DriverVersion.c_str());
242 InfoTP[INFO_INTERFACE_VERSION].setText(std::to_string(m_InterfaceVersion).c_str());
244 InfoTP.apply();
245
246 LOG_INFO("Alpaca dome is ready for operation.");
247 }
248 else
249 {
252
253 LOG_INFO("Alpaca dome is disconnected.");
254 }
255
256 return true;
257}
258
260
262{
264
268
269 return true;
270}
271
273
275{
276 if (ServerAddressTP[HOST].isEmpty() || ServerAddressTP[PORT].isEmpty())
277 {
278 LOG_ERROR("Server address or port is not set.");
279 return false;
280 }
281
282 // Initialize HTTP client
283 int connectionTimeout = static_cast<int>(ConnectionSettingsNP[TIMEOUT].getValue());
284 int timeout = static_cast<int>(ConnectionSettingsNP[RW_TIMEOUT].getValue());
285 httpClient = std::make_unique<httplib::Client>(ServerAddressTP[HOST].getText(), std::stoi(ServerAddressTP[PORT].getText()));
286 httpClient->set_connection_timeout(std::chrono::milliseconds(connectionTimeout));
287 httpClient->set_read_timeout(std::chrono::milliseconds(timeout));
288 httpClient->set_write_timeout(std::chrono::milliseconds(timeout));
289
290 // Test connection by getting camera status
291 bool connected = false;
292 if (!alpacaGetBool("/connected", connected))
293 {
294 LOG_ERROR("Failed to connect to Alpaca dome. Please check server address and port.");
295 return false;
296 }
297
298 // Connect to the device if not already connected
299 if (!connected)
300 {
301 if (!alpacaConnect())
302 return false;
303 }
304
305 LOG_INFO("Successfully connected to Alpaca dome.");
306
307 // Get device info
308 alpacaGetString("/name", m_DeviceName);
309 alpacaGetString("/description", m_Description);
310 alpacaGetString("/driverinfo", m_DriverInfo);
311 alpacaGetString("/driverversion", m_DriverVersion);
312 alpacaGetInt("/interfaceversion", m_InterfaceVersion);
313
314 // Update dome capabilities
315 alpacaGetBool("/canfindhome", m_CanFindHome);
316 alpacaGetBool("/canpark", m_CanPark);
317 alpacaGetBool("/cansetaltitude", m_CanSetAltitude);
318 alpacaGetBool("/cansetazimuth", m_CanSetAzimuth);
319 alpacaGetBool("/cansetpark", m_CanSetPark);
320 alpacaGetBool("/cansetshutter", m_CanSetShutter);
321 alpacaGetBool("/canslave", m_CanSlave);
322 alpacaGetBool("/cansyncazimuth", m_CanSyncAzimuth);
323 uint32_t cap = INDI::Dome::DOME_CAN_ABORT;
324 if (m_CanPark)
326 if (m_CanSetShutter)
328 if (m_CanSetAzimuth)
330 if (m_CanSyncAzimuth)
333
334 // Setting parked flag once on connection
335 if (m_CanPark)
336 {
337 bool slewing, parked;
338 if (alpacaGetBool("/slewing", slewing, false) && alpacaGetBool("/atpark", parked, true))
339 SetParked(!slewing && parked);
340 }
341
343 return true;
344}
345
347
349{
350 if (isConnected())
351 {
352 if (!alpacaDisconnect())
353 {
354 LOG_ERROR("Failed to set disconnected state on Alpaca dome.");
355 return false;
356 }
357 }
358
359 // Reset HTTP client object and close any existing connections
360 httpClient.reset();
361
362 LOG_INFO("Disconnected from Alpaca dome.");
363 return true;
364}
365
367
368bool AlpacaDome::SetSpeed(double rpm)
369{
370 INDI_UNUSED(rpm);
371 return false;
372}
373
375
377{
378 if (CanSetAzimuth() == false)
379 {
380 LOG_ERROR( "Dome does not support azimuth slewing.");
381 return IPS_ALERT;
382 }
383
384 double targetAz = range360(DomeAbsPosNP[0].getValue() + azDiff);
385 return MoveAbs(targetAz);
386}
387
389
391{
392 if (CanSetAzimuth() == false)
393 {
394 LOG_ERROR( "Dome does not support azimuth slewing.");
395 return IPS_ALERT;
396 }
397
398 if (!alpacaDomeSlewToAzimuth(range360(az)))
399 return IPS_ALERT;
400 return IPS_BUSY;
401}
402
404
406{
407 if (CanSetAzimuth() == false)
408 {
409 LOG_ERROR( "Dome does not support azimuth slewing.");
410 return IPS_ALERT;
411 }
412
413 double targetAz = DomeAbsPosNP[0].getValue();
414
415 if (operation == MOTION_START)
416 {
417 targetAz = DomeAbsPosNP[0].getValue();
418 if (dir == DOME_CW)
419 targetAz += 5;
420 else
421 targetAz -= 5;
422 targetAz = range360(targetAz);
423 }
424
425 if (MoveAbs(targetAz) == IPS_ALERT)
426 return IPS_ALERT;
427
428 return ((operation == MOTION_START) ? IPS_BUSY : IPS_OK);
429}
430
432
433bool AlpacaDome::Sync(double az)
434{
435 if (CanSync() == false)
436 {
437 LOG_ERROR( "Dome does not support syncing.");
438 return false;
439 }
440
441 double targetAz = range360(az);
442 bool success = alpacaDomeSyncToAzimuth(targetAz);
443 if (success)
444 {
446 LOGF_INFO( "Dome successfully synchronized to azimuth angle %.2f.", targetAz);
447 }
448 else
449 {
451 LOGF_ERROR( "Error: cannot synchronize dome to azimuth angle %.2f.", targetAz);
452 }
453
455 return success;
456}
457
459
461{
462 if (alpacaDomeAbortSlew())
463 {
465 {
466 if (OperationSP[OPERATION_FIND_HOME].getState() == ISS_ON)
467 {
468 LOG_WARN("Finding home operation is aborted.");
469 }
470 else if (OperationSP[OPERATION_CALIBRATE].getState() == ISS_ON)
471 {
472 LOG_WARN("Calibrating operation is aborted.");
473 }
477 }
478 else if (getShutterState() == SHUTTER_MOVING)
479 {
480 LOG_WARN("Shutter motion aborted!");
483 }
484 else
485 {
486 LOG_WARN("Dome motion aborted.");
487 }
488
489 return true;
490 }
491
492 return false;
493}
494
496
498{
500 {
502 return IPS_ALERT;
503 LOG_INFO("Close shutter on park");
504 }
505
506 if (alpacaDomePark())
507 {
508 LOG_INFO("Parking dome...");
510 return IPS_BUSY;
511 }
512
513 LOG_ERROR("Failed to park dome");
514 return IPS_ALERT;
515}
516
518
520{
522 {
524 return IPS_ALERT;
525 LOG_INFO("Open shutter on unpark");
526 }
527
528 LOG_INFO("Unparking dome...");
529 IPState result = MoveAbs(DomeAbsPosNP[0].getValue());
530 if (result != IPS_ALERT)
532
533 return result;
534}
535
537
539{
540 if (operation == SHUTTER_OPEN)
541 {
542 LOG_INFO("Opening dome...");
543 if (alpacaDomeOpenShutter())
544 {
545 LOG_INFO("Dome is opening...");
547 return IPS_BUSY;
548 }
549
550 return IPS_ALERT;
551 }
552 else if (operation == SHUTTER_CLOSE)
553 {
554 if (isLocked())
555 {
556 LOG_WARN("Cannot close dome when mount is locking. See: Telescope parking policy, in options tab");
557 return IPS_ALERT;
558 }
559
560 LOG_INFO("Closing dome...");
561 if (alpacaDomeCloseShutter())
562 {
563 LOG_INFO("Dome is closing...");
565 return IPS_BUSY;
566 }
567
568 return IPS_ALERT;
569 }
570
571 return IPS_OK;
572}
573
575
577{
578 if (CanSetPark() == false)
579 {
580 LOG_ERROR( "Dome does not support setting park position.");
581 return false;
582 }
583
584 if (alpacaDomeSetPark())
585 {
586 LOG_INFO( "Dome park position successfully set to current azimuth");
587 return true;
588 }
589
590 LOG_ERROR( "Error: cannot set park position to current azimuth");
591 return false;
592}
593
595
597{
598 return SetCurrentPark();
599}
600
602
604{
605 if (CanFindHome() == false)
606 {
607 LOG_ERROR( "Dome does not support homing.");
608 return IPS_ALERT;
609 }
610
611 if (alpacaDomeFindHome())
612 {
613 LOG_INFO( "Dome is moving to home position...");
615 return IPS_BUSY;
616 }
617
618 LOG_ERROR( "Error: cannot move the dome to the home position");
619 return IPS_ALERT;
620}
621
623
625{
626 LOG_ERROR( "Calibration is not supported by standard basic Alpaca dome.");
627 return IPS_ALERT;
628}
629
631
632std::string AlpacaDome::getAlpacaURL(const std::string& endpoint)
633{
634 return "/api/v1/dome/" + std::to_string(static_cast<int>(DeviceNumberNP[0].getValue())) + endpoint;
635}
636
638
640{
641 // Convert JSON to form data for Alpaca compatibility
642 std::string form_data;
643 for (auto & [key, value] : request.items())
644 {
645 if (!form_data.empty()) form_data += "&";
646
647 if (value.is_string())
648 {
649 form_data += key + "=" + value.get<std::string>();
650 }
651 else if (value.is_number_integer())
652 {
653 form_data += key + "=" + std::to_string(value.get<int>());
654 }
655 else if (value.is_number_float())
656 {
657 form_data += key + "=" + std::to_string(value.get<double>());
658 }
659 else if (value.is_boolean())
660 {
661 form_data += key + "=" + (value.get<bool>() ? "true" : "false");
662 }
663 }
664
665 // Add ClientID and ClientTransactionID to form data
666 if (!form_data.empty()) form_data += "&";
667 form_data += "ClientID=" + std::to_string(getpid());
668 form_data += "&ClientTransactionID=" + std::to_string(getTransactionId());
669
670 return form_data;
671}
672
674
675void AlpacaDome::setDefaultServerAddress(const char *host, const char *port, bool force)
676{
677 // Only apply defaults if no value was loaded from saved config (mirrors TCP plugin pattern).
678 // m_ConfigHost/m_ConfigPort are empty when no config file entry exists for this device,
679 // meaning this is either a first run or the user has never saved settings.
680 if (host != nullptr)
681 {
682 if (force || m_ConfigHost.empty())
683 ServerAddressTP[HOST].setText(host);
684 }
685
686 if (port != nullptr)
687 {
688 if (force || m_ConfigPort.empty())
689 ServerAddressTP[PORT].setText(port);
690 }
691}
692
694
696{
697 return ++m_ClientTransactionID;
698}
699
701
702void AlpacaDome::updateStatus()
703{
704 if (!isConnected())
705 {
708 return;
709 }
710
711 // Request dome current state.
712 AlapacaDomeState value;
713 if (alpacaGetDeviceState(value))
714 {
715 // Shutter state update
716 if (HasShutter())
717 updateShutterStatus(value);
718
719 // Dome state update
720 updateDomeStatus(value);
721 }
722}
723
725
726void AlpacaDome::updateDomeStatus(AlapacaDomeState &state)
727{
728 if (getDomeState() == DOME_MOVING && !state.slewing)
729 {
731 bool isCalibrating = (OperationSP.getState() == IPS_BUSY && OperationSP[OPERATION_CALIBRATE].getState() == ISS_ON);
732
733 if (isHoming && state.atHome)
734 {
735 LOG_INFO("Dome has found home position.");
740 }
741 else if (isCalibrating)
742 {
743 LOG_INFO("Dome calibration complete.");
748 }
749 else
750 {
751 LOGF_INFO("Dome reached requested azimuth: %.3f Degrees", state.azimuth);
753 }
754 }
755 else if (getDomeState() == DOME_PARKING && !state.slewing && state.atPark)
756 {
757 SetParked(true);
758 }
759 else if (getDomeState() == DOME_UNPARKING && !state.slewing)
760 {
761 SetParked(false);
762 }
763 else if (getDomeState() == DOME_MOVING && state.slewing && DomeAbsPosNP.getState() != IPS_BUSY)
764 {
766 }
767 DomeAbsPosNP[0].setValue(state.azimuth);
769}
770
772
773void AlpacaDome::updateShutterStatus(AlapacaDomeState &state)
774{
775 // Alpaca shutter states
776 // 0 = Open
777 // 1 = Closed
778 // 2 = Opening
779 // 3 = Closing
780 // 4 = Error
781 ShutterState previousState = getShutterState();
782 switch(state.shutterStatus)
783 {
784 case 0: // Open
785 if (previousState != SHUTTER_OPENED)
786 {
787 LOG_INFO("Shutter is fully open.");
789 }
790 break;
791
792 case 1: // Closed
793 if (previousState != SHUTTER_CLOSED)
794 {
795 LOG_INFO("Shutter is fully closed.");
797 }
798 break;
799
800 case 2: // Opening
801 if (previousState != SHUTTER_MOVING)
802 {
803 LOG_INFO("Shutter is opening...");
805 }
806 break;
807
808 case 3: // Closing
809 if (previousState != SHUTTER_MOVING)
810 {
811 LOG_INFO("Shutter is closing...");
813 }
814 break;
815
816 case 4: // Error
817 if (previousState != SHUTTER_ERROR)
818 {
819 LOG_ERROR("Shutter is in error state.");
821 }
822 break;
823
824 default:
825 if (previousState != SHUTTER_UNKNOWN)
827 break;
828 }
829}
830
832
833bool AlpacaDome::sendAlpacaGET(const std::string &endpoint, nlohmann::json &response)
834{
835 if (!httpClient)
836 {
837 LOG_ERROR("HTTP client not initialized.");
838 return false;
839 }
840
841 std::string url = getAlpacaURL(endpoint);
842 url += "?ClientID=" + std::to_string(getpid()) + "&ClientTransactionID=" + std::to_string(getTransactionId());
843
844 auto result = httpClient->Get(url.c_str());
845
846 if (!result)
847 {
848 LOGF_ERROR("HTTP GET failed for %s: %s", endpoint.c_str(),
849 httplib::to_string(result.error()).c_str());
850 return false;
851 }
852
853 if (result->status != 200)
854 {
855 LOGF_ERROR("HTTP GET %s returned status %d", endpoint.c_str(), result->status);
856 return false;
857 }
858
859 try
860 {
861 response = nlohmann::json::parse(result->body);
862
863 // Check for Alpaca errors
864 if (response.contains("ErrorNumber") && response["ErrorNumber"].get<int>() != 0)
865 {
866 LOGF_ERROR("Alpaca error in %s: %d - %s", endpoint.c_str(),
867 response["ErrorNumber"].get<int>(),
868 response["ErrorMessage"].get<std::string>().c_str());
869 return false;
870 }
871
872 return true;
873 }
874 catch (const nlohmann::json::exception& e)
875 {
876 LOGF_ERROR("JSON parse error for %s: %s", endpoint.c_str(), e.what());
877 return false;
878 }
879}
880
882
883bool AlpacaDome::sendAlpacaPUT(const std::string &endpoint, const nlohmann::json &request, nlohmann::json &response)
884{
885 if (!httpClient)
886 {
887 LOG_ERROR("HTTP client not initialized.");
888 return false;
889 }
890
891 std::string url = getAlpacaURL(endpoint);
892 std::string form_data = buildAlpacaFormData(request);
893
894 auto result = httpClient->Put(url, form_data, "application/x-www-form-urlencoded");
895
896 if (!result)
897 {
898 LOGF_ERROR("HTTP PUT failed for %s: %s", endpoint.c_str(),
899 httplib::to_string(result.error()).c_str());
900 return false;
901 }
902
903 if (result->status != 200)
904 {
905 LOGF_ERROR("HTTP PUT %s returned status %d", endpoint.c_str(), result->status);
906 return false;
907 }
908
909 try
910 {
911 response = nlohmann::json::parse(result->body);
912
913 // Check for Alpaca errors
914 if (response.contains("ErrorNumber") && response["ErrorNumber"].get<int>() != 0)
915 {
916
917 // JM 2025.08.29: For some reason, I always get error 1025 when setting temperature
918 // even though value is in range. Ignore this error for now.
919 // if (response["ErrorNumber"].get<int>() == 1025)
920 // return true;
921
922 LOGF_ERROR("Alpaca error in %s: %d - %s", endpoint.c_str(),
923 response["ErrorNumber"].get<int>(),
924 response["ErrorMessage"].get<std::string>().c_str());
925 return false;
926 }
927
928 return true;
929
930 }
931 catch (const nlohmann::json::exception& e)
932 {
933 LOGF_ERROR("JSON parse error for %s: %s", endpoint.c_str(), e.what());
934 return false;
935 }
936}
937
939
940bool AlpacaDome::alpacaConnect()
941{
942 nlohmann::json response;
943
944 // IDome Version 3 and later
945 if (m_InterfaceVersion >= 3)
946 {
947 if (!sendAlpacaPUT("/connect", {}, response))
948 return false;
949
950 bool connecting = true;
951 int retryDelay = 100;
952
953 // Need waiting for connection to be fully established (by checking /connecting)
954 while (connecting && retryDelay < 4000)
955 {
956 if (!alpacaGetBool("/connecting", connecting))
957 return false;
958 if (connecting)
959 {
960 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelay));
961 retryDelay *= 2;
962 }
963 }
964 }
965 else
966 {
967 if (!sendAlpacaPUT("/connected", {{"Connected", true}}, response))
968 return false;
969
970 bool connected = false;
971 int retryDelay = 100;
972
973 // Need waiting for connection to be fully established (by checking /connecting)
974 while (!connected && retryDelay < 4000)
975 {
976 if (!alpacaGetBool("/connected", connected))
977 return false;
978 if (!connected)
979 {
980 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelay));
981 retryDelay *= 2;
982 }
983 }
984 }
985
986 return true;
987}
988
990
991bool AlpacaDome::alpacaDisconnect()
992{
993 nlohmann::json response;
994
995 // IDome Version 3 and later
996 if (m_InterfaceVersion >= 3)
997 {
998 if (!sendAlpacaPUT("/disconnect", {}, response))
999 return false;
1000 }
1001 else
1002 {
1003 if (!sendAlpacaPUT("/connected", {{"Connected", false}}, response))
1004 return false;
1005 }
1006
1007 return true;
1008}
1009
1011
1012bool AlpacaDome::alpacaGetBool(const std::string &endpoint, bool& value, bool defaultValue)
1013{
1014 nlohmann::json response;
1015 if (!sendAlpacaGET(endpoint, response))
1016 return false;
1017 if (response.contains("Value") && response["Value"].is_boolean())
1018 value = response["Value"].get<bool>();
1019 else
1020 value = defaultValue;
1021 return true;
1022}
1023
1025
1026bool AlpacaDome::alpacaGetInt(const std::string &endpoint, int &value, int defaultValue)
1027{
1028 nlohmann::json response;
1029 if (!sendAlpacaGET(endpoint, response))
1030 return false;
1031 if (response.contains("Value") && response["Value"].is_number_integer())
1032 value = response["Value"].get<int>();
1033 else
1034 value = defaultValue;
1035 return true;
1036}
1037
1039
1040bool AlpacaDome::alpacaGetDouble(const std::string &endpoint, double &value, double defaultValue)
1041{
1042 nlohmann::json response;
1043 if (!sendAlpacaGET(endpoint, response))
1044 return false;
1045 if (response.contains("Value") && response["Value"].is_number_float())
1046 value = response["Value"].get<double>();
1047 else
1048 value = defaultValue;
1049 return true;
1050}
1051
1053
1054bool AlpacaDome::alpacaGetString(const std::string &endpoint, std::string &value, const std::string& defaultValue)
1055{
1056 nlohmann::json response;
1057 if (!sendAlpacaGET(endpoint, response))
1058 return false;
1059 if (response.contains("Value") && response["Value"].is_string())
1060 value = response["Value"].get<std::string>();
1061 else
1062 value = defaultValue;
1063 return true;
1064}
1065
1067
1068bool AlpacaDome::alpacaGetDeviceState(AlapacaDomeState &value, double defaultAz, double defaultAlt)
1069{
1070 value.atPark = value.atHome = value.slewing = false;
1071 value.azimuth = defaultAz;
1072 value.altitude = defaultAlt;
1073 value.shutterStatus = -1;
1074
1075 // IDome Version 3 and later
1076 if (m_InterfaceVersion >= 3)
1077 {
1078 nlohmann::json response;
1079
1080 if (!sendAlpacaGET("/devicestate", response))
1081 return false;
1082
1083 if (!response.contains("Value") || !response["Value"].is_array())
1084 {
1085 LOG_ERROR("Error: an array of objects is expected in Value attribute of response");
1086 return false;
1087 }
1088
1089 for (auto &item : response["Value"])
1090 {
1091 if (!item.contains("Name") || !item["Name"].is_string())
1092 {
1093 LOG_ERROR("Error: missing string element Name in Value array item, as returned by /devicestate API method");
1094 continue;
1095 }
1096 if (!item.contains("Value"))
1097 {
1098 LOG_ERROR("Error: missing element Value in Value array item, as returned by /devicestate API method");
1099 continue;
1100 }
1101
1102 const std::string &key = item["Name"].get<std::string>();
1103 if (key == "Altitude")
1104 value.altitude = item["Value"].get<double>();
1105 else if (key == "AtHome")
1106 value.atHome = item["Value"].get<bool>();
1107 else if (key == "AtPark")
1108 value.atPark = item["Value"].get<bool>();
1109 else if (key == "Azimuth")
1110 value.azimuth = item["Value"].get<double>();
1111 else if (key == "ShutterStatus")
1112 value.shutterStatus = item["Value"].get<int>();
1113 else if (key == "Slewing")
1114 value.slewing = item["Value"].get<bool>();
1115 }
1116 }
1117 else
1118 {
1119 if (CanSetAzimuth() || CanAbsMove() || CanRelMove())
1120 alpacaGetBool("/slewing", value.slewing, false);
1121 if (CanFindHome())
1122 alpacaGetBool("/athome", value.atHome, false);
1123 if (CanSetPark())
1124 alpacaGetBool("/atpark", value.atPark, false);
1125 if (CanSetAzimuth())
1126 alpacaGetDouble("/azimuth", value.azimuth, defaultAz);
1127 if (CanSetAltitude())
1128 alpacaGetDouble("/altitude", value.altitude, defaultAlt);
1129 if (HasShutter())
1130 alpacaGetInt("/shutterstatus", value.shutterStatus, -1);
1131 }
1132
1133 return true;
1134}
1135
1137
1138bool AlpacaDome::alpacaDomeAbortSlew()
1139{
1140 nlohmann::json response;
1141 return sendAlpacaPUT("/abortslew", {}, response);
1142}
1143
1145
1146bool AlpacaDome::alpacaDomeCloseShutter()
1147{
1148 nlohmann::json response;
1149 return sendAlpacaPUT("/closeshutter", {}, response);
1150}
1151
1153
1154bool AlpacaDome::alpacaDomeFindHome()
1155{
1156 nlohmann::json response;
1157 return sendAlpacaPUT("/findhome", {}, response);
1158}
1159
1161
1162bool AlpacaDome::alpacaDomeOpenShutter()
1163{
1164 nlohmann::json response;
1165 return sendAlpacaPUT("/openshutter", {}, response);
1166}
1167
1169
1170bool AlpacaDome::alpacaDomePark()
1171{
1172 nlohmann::json response;
1173 return sendAlpacaPUT("/park", {}, response);
1174}
1175
1177
1178bool AlpacaDome::alpacaDomeSetPark()
1179{
1180 nlohmann::json response;
1181 return sendAlpacaPUT("/setpark", {}, response);
1182}
1183
1185
1186bool AlpacaDome::alpacaDomeSlewToAltitude(double alt)
1187{
1188 nlohmann::json response;
1189 nlohmann::json request = {{"Altitude", alt}};
1190 return sendAlpacaPUT("/slewtoaltitude", request, response);
1191}
1192
1194
1195bool AlpacaDome::alpacaDomeSlewToAzimuth(double az)
1196{
1197 nlohmann::json response;
1198 nlohmann::json request = {{"Azimuth", az}};
1199 return sendAlpacaPUT("/slewtoazimuth", request, response);
1200}
1201
1203
1204bool AlpacaDome::alpacaDomeSyncToAzimuth(double az)
1205{
1206 nlohmann::json response;
1207 nlohmann::json request = {{"Azimuth", az}};
1208 return sendAlpacaPUT("/synctoazimuth", request, response);
1209}
std::unique_ptr< AlpacaDome > alpaca_dome(new AlpacaDome())
INDI::PropertySwitch OperationSP
bool CanFindHome()
Definition alpaca_dome.h:90
virtual bool saveConfigItems(FILE *fp) override
Save all dome parameters in the configuration file. This overridden method adds the HTTP connection i...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool Abort() override
Abort all dome motion (spinning and shutter).
virtual IPState FindHome()
Request the dome for moving and finding its Home position. The method is asynchronous and the dome sh...
bool CanSetPark()
virtual bool SetCurrentPark() override
Set parking position of the dome with the current position.
virtual IPState Park() override
Park the dome.
virtual bool SetSpeed(double rpm) override
Set Dome speed. This does not initiate motion, it sets the speed for the next motion command....
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual IPState MoveAbs(double az) override
Move the Dome to an absolute azimuth.
@ OPERATION_CALIBRATE
@ OPERATION_FIND_HOME
virtual bool Disconnect() override
Disconnect from the remote dome device.
virtual IPState MoveRel(double azDiff) override
Move the Dome to an relative 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...
virtual uint32_t getTransactionId()
INDI::PropertyText ServerAddressTP
Additional properties.
bool CanCalibrate()
Definition alpaca_dome.h:98
virtual IPState Move(DomeDirection dir, DomeMotionCommand operation) override
Move the Dome in a particular direction.
INDI::PropertyNumber DeviceNumberNP
virtual IPState ControlShutter(ShutterOperation operation) override
Open or Close the shutter.
@ INFO_INTERFACE_VERSION
@ INFO_DRIVER_VERSION
virtual bool updateProperties() override
Called whenever there is a change in the CONNECTION status of the driver. This will enable the driver...
INDI::PropertyText InfoTP
bool CanSetAzimuth()
bool CanSetAltitude()
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual std::string buildAlpacaFormData(const nlohmann::json &request)
INDI::PropertyNumber ConnectionSettingsNP
virtual bool initProperties() override
Initialize properties state and value for Alpaca dome device.
virtual const char * getDefaultName() override
virtual void setDefaultServerAddress(const char *host, const char *port, bool force=false)
virtual void TimerHit() override
Callback function to be called once SetTimer duration elapses.
virtual IPState Calibrate()
Request the dome for moving in order to calibrate itself. The calibration is an asynchronous process ...
virtual bool Sync(double az) override
Set the dome current azimuth with the supplied azimuth position.
virtual bool Connect() override
Connect to the remote dome device. For Alpaca drivers, this method tries to connect to the device usi...
virtual IPState UnPark() override
UnPark the dome. The action of the Unpark command is dome specific, but it may include opening the sh...
virtual bool SetDefaultPark() override
Set parking position of the dome with the current position.
virtual std::string getAlpacaURL(const std::string &endpoint)
bool isConnected() const
INDI::PropertyText getText(const char *name) const
bool isDeviceNameMatch(const char *otherName) const
Check that the device name matches the argument.
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
void setVersion(uint16_t vMajor, uint16_t vMinor)
Set driver version information to be defined in DRIVER_INFO property as vMajor.vMinor.
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.
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
@ DOME_UNPARKING
Definition indidome.h:135
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
bool CanRelMove()
Definition indidome.h:234
ShutterOperation
Shutter operation command.
Definition indidome.h:114
@ SHUTTER_CLOSE
Definition indidome.h:116
DomeMotionCommand
Definition indidome.h:97
@ MOTION_START
Definition indidome.h:98
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
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 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_NONE
Definition indidome.h:171
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Saves the Device Port and Dome Presets in the configuration file
bool CanAbsMove()
Definition indidome.h:226
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)
void setDomeState(const DomeState &value)
void SetParkDataType(DomeParkData type)
setParkDataType Sets the type of parking data stored in the park data file and presented to the user.
INDI::PropertySwitch DomeShutterSP
Definition indidome.h:546
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
INDI::PropertyNumber DomeSyncNP
Definition indidome.h:544
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.
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)
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)
a class to store JSON values
iteration_proxy< iterator > items() noexcept
helper to access iterator member functions in range-based for
bool contains(const typename object_t::key_type &key) const
check the existence of an element in a JSON object
auto get() const noexcept(noexcept(std::declval< const basic_json_t & >().template get_impl< ValueType >(detail::priority_tag< 4 > {}))) -> decltype(std::declval< const basic_json_t & >().template get_impl< ValueType >(detail::priority_tag< 4 > {}))
get a (pointer) value (explicit)
general exception of the basic_json class
const char * what() const noexcept override
returns the explanatory string
const char * CONNECTION_TAB
CONNECTION_TAB Where all device connection settings (serial, usb, ethernet) are defined and controlle...
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
const char * INFO_TAB
INFO_TAB Where all the properties for general information 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_ATMOST1
Definition indiapi.h:172
#define MAXINDINAME
Definition indiapi.h:189
double range360(double r)
range360 Limits an angle to be between 0-360 degrees.
Definition indicom.c:1321
const char * IUFindOnSwitchName(ISState *states, char *names[], int n)
Returns the name of the first ON switch it finds in the supplied arguments.
Definition indidevapi.c:355
#define INDI_UNUSED(x)
Definition indidevapi.h:132
int IUGetConfigText(const char *dev, const char *property, const char *member, char *value, int len)
IUGetConfigText Opens configuration file and reads single text property.
Definition indidriver.c:908
#define LOGF_INFO(fmt,...)
Definition indilogger.h:82
#define LOG_WARN(txt)
Definition indilogger.h:73
#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
std::string to_string(Error error)
Definition httplib.h:9275