Instrument Neutral Distributed Interface INDI 2.2.5
telescope_bridge_handlers.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2025 Jasem Mutlaq. All rights reserved.
3
4 INDI Alpaca Telescope Bridge - Handlers Implementation
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2 of the License, or (at your option)
9 any later version.
10
11 This program is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20
21 The full GNU General Public License is included in this distribution in the
22 file called LICENSE.
23*******************************************************************************/
24
25#include "telescope_bridge.h"
26#include "indilogger.h"
27#include "device_manager.h"
28
29#include <httplib.h>
30#include <cmath>
31#include <cstring>
32#include <sstream>
33
34#ifdef _USE_SYSTEM_JSONLIB
35#include <nlohmann/json.hpp>
36#else
37#include <indijson.hpp>
38#endif
39
41
42// Common Alpaca API methods
44{
45 bool isConnected = m_Device.isConnected();
46 json response;
47
48 // Handle PUT request to connect/disconnect
49 if (req.method == "PUT")
50 {
51 try
52 {
53 // Parse form-urlencoded data from request body
54 std::map<std::string, std::string> formData;
56
57 // Check if Connected parameter exists
58 if (formData.find("Connected") == formData.end())
59 {
60 sendResponseValue(res, isConnected, false, "Missing Connected parameter");
61 return;
62 }
63
64 // Convert Connected value to bool
65 bool connected = (formData["Connected"] == "True" || formData["Connected"] == "true" ||
66 formData["Connected"] == "1");
67
68 if (connected && !isConnected)
69 {
70 // Connect the device
71 auto connectSw = m_Device.getSwitch("CONNECTION");
72 if (connectSw)
73 {
74 INDI::PropertySwitch switchProperty(connectSw);
75 switchProperty.reset();
76 switchProperty[0].setState(ISS_ON);
77 // Request to send the switch to the device
78 requestNewSwitch(switchProperty);
79 sendResponseValue(res, true);
80 }
81 else
82 {
83 sendResponseValue(res, false, "Failed to connect device");
84 }
85 }
86 else if (!connected && isConnected)
87 {
88 // Disconnect the device
89 auto connectSw = m_Device.getSwitch("CONNECTION");
90 if (connectSw)
91 {
92 INDI::PropertySwitch switchProperty(connectSw);
93 switchProperty.reset();
94 switchProperty[1].setState(ISS_ON);
95 // Request to send the switch to the device
96 requestNewSwitch(switchProperty);
97 sendResponseValue(res, false);
98 }
99 else
100 {
101 sendResponseValue(res, true, "Failed to disconnect device");
102 }
103 }
104 else
105 {
106 // No change needed
107 sendResponseValue(res, isConnected);
108 }
109 }
110 catch (const std::exception &e)
111 {
112 sendResponseValue(res, isConnected, false, std::string("Invalid request: ") + e.what());
113 }
114 }
115 else
116 {
117 // GET request
118 sendResponseValue(res, isConnected);
119 }
120}
121
123{
124 INDI_UNUSED(req);
125 std::string deviceName = m_Device.getDeviceName();
126 sendResponseValue(res, deviceName);
127}
128
130{
131 INDI_UNUSED(req);
132 std::string description = std::string("INDI Telescope: ") + m_Device.getDeviceName();
133 sendResponseValue(res, description);
134}
135
137{
138 INDI_UNUSED(req);
139 std::string driverInfo = "INDI Alpaca Bridge";
140 sendResponseValue(res, driverInfo);
141}
142
144{
145 INDI_UNUSED(req);
146 std::string version = "1.0";
147 sendResponseValue(res, version);
148}
149
151{
152 INDI_UNUSED(req);
153 sendResponseValue(res, 1);
154}
155
156// Telescope-specific properties
157void TelescopeBridge::handleAlignmentMode(const httplib::Request &req, httplib::Response &res)
158{
159 INDI_UNUSED(req);
160
161 // Check if the telescope has a mount type property
162 auto mountTypeSw = m_Device.getSwitch("TELESCOPE_MOUNT_TYPE");
163 if (mountTypeSw)
164 {
165 INDI::PropertySwitch switchProperty(mountTypeSw);
166
167 // ASCOM AlignmentMode values:
168 // 0 = Alt/Az alignment
169 // 1 = Polar (equatorial) alignment
170 // 2 = German polar alignment
171
172 // Check which mount type is active
173 sendResponseValue(res, switchProperty.findOnSwitchIndex());
174 }
175 else
176 {
177 // If no mount type property is available, check if the telescope has horizontal coordinates
178 bool hasHorizontalCoord = m_Device.getProperty("HORIZONTAL_COORD").isValid();
179 bool hasEquatorialCoord = m_Device.getProperty("EQUATORIAL_EOD_COORD").isValid();
180
181 if (hasHorizontalCoord && !hasEquatorialCoord)
182 {
183 sendResponseValue(res, 0); // Alt/Az alignment
184 }
185 else
186 {
187 // Default to German equatorial alignment
188 sendResponseValue(res, 2);
189 }
190 }
191}
192
193void TelescopeBridge::handleAltitude(const httplib::Request &req, httplib::Response &res)
194{
195 INDI_UNUSED(req);
196 std::lock_guard<std::mutex> lock(m_Mutex);
197 sendResponseValue(res, m_CurrentALT);
198}
199
200void TelescopeBridge::handleAzimuth(const httplib::Request &req, httplib::Response &res)
201{
202 INDI_UNUSED(req);
203 std::lock_guard<std::mutex> lock(m_Mutex);
204 sendResponseValue(res, m_CurrentAZ);
205}
206
207void TelescopeBridge::handleCanPark(const httplib::Request &req, httplib::Response &res)
208{
209 INDI_UNUSED(req);
210 sendResponseValue(res, m_Device.getProperty("TELESCOPE_PARK").isValid());
211}
212
213void TelescopeBridge::handleCanPulseGuide(const httplib::Request &req, httplib::Response &res)
214{
215 INDI_UNUSED(req);
216 bool canPulseGuide = m_Device.getProperty("TELESCOPE_TIMED_GUIDE_NS").isValid() &&
217 m_Device.getProperty("TELESCOPE_TIMED_GUIDE_WE").isValid();
218 sendResponseValue(res, canPulseGuide);
219}
220
221void TelescopeBridge::handleCanSetTracking(const httplib::Request &req, httplib::Response &res)
222{
223 INDI_UNUSED(req);
224 sendResponseValue(res, m_Device.getProperty("TELESCOPE_TRACK_STATE").isValid());
225}
226
227void TelescopeBridge::handleCanSetRightAscensionRate(const httplib::Request &req, httplib::Response &res)
228{
229 INDI_UNUSED(req);
230 sendResponseValue(res, m_Device.getProperty("TELESCOPE_TRACK_RATE").isValid());
231}
232
233void TelescopeBridge::handleCanSetDeclinationRate(const httplib::Request &req, httplib::Response &res)
234{
235 INDI_UNUSED(req);
236 sendResponseValue(res, m_Device.getProperty("TELESCOPE_TRACK_RATE").isValid());
237}
238
239void TelescopeBridge::handleCanSlew(const httplib::Request &req, httplib::Response &res)
240{
241 INDI_UNUSED(req);
242 sendResponseValue(res, m_Device.getProperty("EQUATORIAL_EOD_COORD").isValid());
243}
244
245void TelescopeBridge::handleCanMoveAxis(const httplib::Request &req, httplib::Response &res)
246{
247 INDI_UNUSED(req);
248 sendResponseValue(res, m_Device.getProperty("EQUATORIAL_EOD_COORD").isValid());
249}
250
251void TelescopeBridge::handleCanSlewAsync(const httplib::Request &req, httplib::Response &res)
252{
253 INDI_UNUSED(req);
254 sendResponseValue(res, m_Device.getProperty("EQUATORIAL_EOD_COORD").isValid());
255}
256
257void TelescopeBridge::handleCanSlewAltAzAsync(const httplib::Request &req, httplib::Response &res)
258{
259 INDI_UNUSED(req);
260 sendResponseValue(res, m_Device.getProperty("HORIZONTAL_COORD").isValid());
261}
262
263void TelescopeBridge::handleCanSync(const httplib::Request &req, httplib::Response &res)
264{
265 INDI_UNUSED(req);
266 sendResponseValue(res, m_Device.getProperty("ON_COORD_SET").isValid());
267}
268
269void TelescopeBridge::handleDeclination(const httplib::Request &req, httplib::Response &res)
270{
271 INDI_UNUSED(req);
272 std::lock_guard<std::mutex> lock(m_Mutex);
273 sendResponseValue(res, m_CurrentDEC);
274}
275
276void TelescopeBridge::handleDeclinationRate(const httplib::Request &req, httplib::Response &res)
277{
278 INDI_UNUSED(req);
279 std::lock_guard<std::mutex> lock(m_Mutex);
280 sendResponseValue(res, m_DeclinationRate);
281}
282
283void TelescopeBridge::handleRightAscension(const httplib::Request &req, httplib::Response &res)
284{
285 INDI_UNUSED(req);
286 std::lock_guard<std::mutex> lock(m_Mutex);
287 sendResponseValue(res, m_CurrentRA);
288}
289
290void TelescopeBridge::handleRightAscensionRate(const httplib::Request &req, httplib::Response &res)
291{
292 INDI_UNUSED(req);
293 std::lock_guard<std::mutex> lock(m_Mutex);
294 sendResponseValue(res, m_RightAscensionRate);
295}
296
297void TelescopeBridge::handleSideOfPier(const httplib::Request &req, httplib::Response &res)
298{
299 INDI_UNUSED(req);
300 std::lock_guard<std::mutex> lock(m_Mutex);
301 sendResponseValue(res, m_PierSide);
302}
303
304void TelescopeBridge::handleSlewing(const httplib::Request &req, httplib::Response &res)
305{
306 INDI_UNUSED(req);
307 std::lock_guard<std::mutex> lock(m_Mutex);
308 sendResponseValue(res, m_IsSlewing);
309}
310
311void TelescopeBridge::handleTracking(const httplib::Request &req, httplib::Response &res)
312{
313 INDI_UNUSED(req);
314 std::lock_guard<std::mutex> lock(m_Mutex);
315 sendResponseValue(res, m_IsTracking);
316}
317
318void TelescopeBridge::handleAtPark(const httplib::Request &req, httplib::Response &res)
319{
320 INDI_UNUSED(req);
321 std::lock_guard<std::mutex> lock(m_Mutex);
322 sendResponseValue(res, m_IsParked);
323}
324
325// Telescope-specific actions
326void TelescopeBridge::handleAbortSlew(const httplib::Request &req, httplib::Response &res)
327{
328 INDI_UNUSED(req);
329 bool success = false;
330 auto abortSw = m_Device.getSwitch("TELESCOPE_ABORT_MOTION");
331 if (abortSw)
332 {
333 INDI::PropertySwitch switchProperty(abortSw);
334 switchProperty.reset();
335 switchProperty[0].setState(ISS_ON);
336 // Request to send the switch to the device
337 requestNewSwitch(switchProperty);
338 success = true;
339 }
340
341 sendResponseStatus(res, success, success ? "" : "Failed to abort slew");
342}
343
344void TelescopeBridge::handlePark(const httplib::Request &req, httplib::Response &res)
345{
346 INDI_UNUSED(req);
347 bool success = false;
348 auto parkSw = m_Device.getSwitch("TELESCOPE_PARK");
349 if (parkSw)
350 {
351 INDI::PropertySwitch switchProperty(parkSw);
352 switchProperty.reset();
353 switchProperty[0].setState(ISS_ON);
354 // Request to send the switch to the device
355 requestNewSwitch(switchProperty);
356 success = true;
357 }
358
359 sendResponseStatus(res, success, success ? "" : "Failed to park telescope");
360}
361
362void TelescopeBridge::handleUnpark(const httplib::Request &req, httplib::Response &res)
363{
364 INDI_UNUSED(req);
365 bool success = false;
366 auto parkSw = m_Device.getSwitch("TELESCOPE_PARK");
367 if (parkSw)
368 {
369 INDI::PropertySwitch switchProperty(parkSw);
370 switchProperty.reset();
371 switchProperty[1].setState(ISS_ON);
372 // Request to send the switch to the device
373 requestNewSwitch(switchProperty);
374 success = true;
375 }
376
377 sendResponseStatus(res, success, success ? "" : "Failed to unpark telescope");
378}
379
380void TelescopeBridge::handleSlewToCoordinates(const httplib::Request &req, httplib::Response &res)
381{
382 bool success = false;
383 try
384 {
385 // Parse form-urlencoded data from request body
386 std::map<std::string, std::string> formData;
388
389 // Check if required parameters exist
390 if (formData.find("RightAscension") == formData.end() || formData.find("Declination") == formData.end())
391 {
392 sendResponseStatus(res, false, "Missing RightAscension or Declination parameter");
393 return;
394 }
395
396 // Convert values to double
397 double ra = std::stod(formData["RightAscension"]);
398 double dec = std::stod(formData["Declination"]);
399
400 auto coordSet = m_Device.getSwitch("ON_COORD_SET");
401 auto eqCoord = m_Device.getNumber("EQUATORIAL_EOD_COORD");
402 if (coordSet && eqCoord)
403 {
404 // Set to TRACK mode
405 INDI::PropertySwitch switchProperty(coordSet);
406 switchProperty.reset();
407 switchProperty[0].setState(ISS_ON);
408 // Request to send the switch to the device
409 requestNewSwitch(switchProperty);
410
411 // Set coordinates
412 INDI::PropertyNumber numberProperty(eqCoord);
413 for (auto &num : numberProperty)
414 {
415 if (num.isNameMatch("RA"))
416 num.setValue(ra);
417 else if (num.isNameMatch("DEC"))
418 num.setValue(dec);
419 }
420 // Request to send the coordinates to the device
421 requestNewNumber(numberProperty);
422 success = true;
423
424 // Update target coordinates
425 m_TargetRA = ra;
426 m_TargetDEC = dec;
427 }
428 }
429 catch (const std::exception &e)
430 {
431 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to parse SlewToCoordinates request: %s", e.what());
432 }
433
434 sendResponseStatus(res, success, success ? "" : "Failed to slew to coordinates");
435}
436
437void TelescopeBridge::handleSlewToCoordinatesAsync(const httplib::Request &req, httplib::Response &res)
438{
439 // Same implementation as SlewToCoordinates for now
440 handleSlewToCoordinates(req, res);
441}
442
443void TelescopeBridge::handleSyncToCoordinates(const httplib::Request &req, httplib::Response &res)
444{
445 bool success = false;
446 try
447 {
448 // Parse form-urlencoded data from request body
449 std::map<std::string, std::string> formData;
451
452 // Check if required parameters exist
453 if (formData.find("RightAscension") == formData.end() || formData.find("Declination") == formData.end())
454 {
455 sendResponseStatus(res, false, "Missing RightAscension or Declination parameter");
456 return;
457 }
458
459 // Convert values to double
460 double ra = std::stod(formData["RightAscension"]);
461 double dec = std::stod(formData["Declination"]);
462
463 auto coordSet = m_Device.getSwitch("ON_COORD_SET");
464 auto eqCoord = m_Device.getNumber("EQUATORIAL_EOD_COORD");
465 if (coordSet && eqCoord)
466 {
467 // Set to SYNC mode
468 INDI::PropertySwitch switchProperty(coordSet);
469 switchProperty.reset();
470 switchProperty[1].setState(ISS_ON);
471 // Request to send the switch to the device
472 requestNewSwitch(switchProperty);
473
474 // Set coordinates
475 INDI::PropertyNumber numberProperty(eqCoord);
476 for (auto &num : numberProperty)
477 {
478 if (num.isNameMatch("RA"))
479 num.setValue(ra);
480 else if (num.isNameMatch("DEC"))
481 num.setValue(dec);
482 }
483 // Request to send the coordinates to the device
484 requestNewNumber(numberProperty);
485 success = true;
486 }
487 }
488 catch (const std::exception &e)
489 {
490 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to parse SyncToCoordinates request: %s", e.what());
491 }
492
493 sendResponseStatus(res, success, success ? "" : "Failed to sync to coordinates");
494}
495
496void TelescopeBridge::handlePulseGuide(const httplib::Request &req, httplib::Response &res)
497{
498 bool success = false;
499 try
500 {
501 // Parse form-urlencoded data from request body
502 std::map<std::string, std::string> formData;
504
505 // Check if required parameters exist
506 if (formData.find("Direction") == formData.end() || formData.find("Duration") == formData.end())
507 {
508 sendResponseStatus(res, false, "Missing Direction or Duration parameter");
509 return;
510 }
511
512 // Convert values to integers
513 int direction = std::stoi(formData["Direction"]);
514 int duration = std::stoi(formData["Duration"]);
515
516 // Direction: 0=North, 1=South, 2=East, 3=West
517 if (direction == 0 || direction == 1)
518 {
519 auto guideNS = m_Device.getNumber("TELESCOPE_TIMED_GUIDE_NS");
520 if (guideNS)
521 {
522 INDI::PropertyNumber numberProperty(guideNS);
523 numberProperty[direction].setValue(duration);
524 // Request to send the guide command to the device
525 requestNewNumber(numberProperty);
526 success = true;
527 }
528 }
529 else if (direction == 2 || direction == 3)
530 {
531 auto guideWE = m_Device.getNumber("TELESCOPE_TIMED_GUIDE_WE");
532 if (guideWE)
533 {
534 INDI::PropertyNumber numberProperty(guideWE);
535 numberProperty[direction - 2].setValue(duration);
536 // Request to send the guide command to the device
537 requestNewNumber(numberProperty);
538 success = true;
539 }
540 }
541 }
542 catch (const std::exception &e)
543 {
544 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to parse PulseGuide request: %s", e.what());
545 }
546
547 sendResponseStatus(res, success, success ? "" : "Failed to pulse guide");
548}
549
550void TelescopeBridge::handleMoveAxis(const httplib::Request &req, httplib::Response &res)
551{
552 bool success = false;
553 try
554 {
555 // Parse form-urlencoded data from request body
556 std::map<std::string, std::string> formData;
558
559 // Check if required parameters exist
560 if (formData.find("Axis") == formData.end() || formData.find("Rate") == formData.end())
561 {
562 sendResponseStatus(res, false, "Missing Axis or Rate parameter");
563 return;
564 }
565
566 // Convert values
567 int axis = std::stoi(formData["Axis"]);
568 double rate = std::stod(formData["Rate"]);
569
570 // Get the slew rate property
571 auto slewRateSP = m_Device.getSwitch("TELESCOPE_SLEW_RATE");
572
573 // Try to set an appropriate slew rate based on the magnitude of the rate
574 // Only if we can determine a good conversion
575 if (slewRateSP)
576 {
577 INDI::PropertySwitch slewRateProperty(slewRateSP);
578 int numRates = slewRateProperty.count();
579
580 // Only attempt to set slew rate if we have a non-zero rate
581 if (numRates > 0 && std::fabs(rate) > 0)
582 {
583 // The sidereal rate is approximately 0.004178 degrees/second
584 const double SIDEREAL_RATE_DEG_SEC = 0.004178;
585
586 // Calculate how many times the sidereal rate this is
587 double rateMultiple = std::fabs(rate) / SIDEREAL_RATE_DEG_SEC;
588
589 // Try to find the best matching slew rate
590 int bestRateIndex = -1;
591 double bestRateDiff = std::numeric_limits<double>::max();
592
593 // First, try to extract rate multiples from the labels (e.g., "200x", "400x", etc.)
594 std::vector<double> rateMultiples;
595 rateMultiples.resize(numRates, 0);
596 bool hasExplicitMultiples = false;
597
598 for (int i = 0; i < numRates; i++)
599 {
600 std::string label = slewRateProperty[i].getLabel();
601 // Look for patterns like "200x" or "400x" in the label
602 std::size_t xPos = label.find('x');
603 if (xPos != std::string::npos && xPos > 0)
604 {
605 // Try to extract the number before the 'x'
606 std::string numStr = label.substr(0, xPos);
607 // Remove any non-numeric characters
608 numStr.erase(std::remove_if(numStr.begin(), numStr.end(),
609 [](char c)
610 {
611 return !std::isdigit(c);
612 }),
613 numStr.end());
614
615 if (!numStr.empty())
616 {
617 try
618 {
619 double multiple = std::stod(numStr);
620 rateMultiples[i] = multiple;
621 hasExplicitMultiples = true;
622
623 // Calculate how close this rate is to our target
624 double diff = std::fabs(multiple - rateMultiple);
625 if (diff < bestRateDiff)
626 {
627 bestRateDiff = diff;
628 bestRateIndex = i;
629 }
630
632 "Found slew rate multiple in label: %s = %.0fx",
633 label.c_str(), multiple);
634 }
635 catch (const std::exception &e)
636 {
637 // Failed to convert to number, ignore this label
638 }
639 }
640 }
641 }
642
643 // If we couldn't extract multiples from labels, use our default mapping
644 if (!hasExplicitMultiples)
645 {
646 // Common slew rate multiples for different mounts
647 // Guide: ~1x, Centering: ~8x, Find: ~16x, Max: ~64x or higher
648 // These are approximate and vary by mount
649
650 // Select appropriate slew rate based on the multiple
651 if (rateMultiple <= 2)
652 bestRateIndex = 0; // Guide rate (slowest)
653 else if (rateMultiple <= 10)
654 bestRateIndex = std::min(1, numRates - 1); // Centering rate
655 else if (rateMultiple <= 30)
656 bestRateIndex = std::min(2, numRates - 1); // Find rate
657 else
658 bestRateIndex = numRates - 1; // Max rate (fastest)
659
661 "Using default slew rate mapping for %.1fx sidereal", rateMultiple);
662 }
663
664 // Only set the slew rate if we determined a valid index
665 if (bestRateIndex >= 0)
666 {
667 slewRateProperty.reset();
668 slewRateProperty[bestRateIndex].setState(ISS_ON);
669 requestNewSwitch(slewRateProperty);
670
672 "Setting slew rate to index %d (%s) for rate %.4f deg/sec (%.1fx sidereal)",
673 bestRateIndex, slewRateProperty[bestRateIndex].getLabel(),
674 std::fabs(rate), rateMultiple);
675 }
676 }
677 }
678
679 // Axis: 0=Primary (RA/AZ), 1=Secondary (DEC/ALT)
680 if (axis == 0)
681 {
682 auto motionWE = m_Device.getSwitch("TELESCOPE_MOTION_WE");
683 if (motionWE)
684 {
685 INDI::PropertySwitch switchProperty(motionWE);
686 switchProperty.reset();
687 if (rate > 0)
688 switchProperty[0].setState(ISS_ON); // West
689 else if (rate < 0)
690 switchProperty[1].setState(ISS_ON); // East
691 else
692 {
693 // If rate is 0, stop motion
694 switchProperty[0].setState(ISS_OFF);
695 switchProperty[1].setState(ISS_OFF);
696 }
697 // Request to send the switch to the device
698 requestNewSwitch(switchProperty);
699 success = true;
700 }
701 }
702 else if (axis == 1)
703 {
704 auto motionNS = m_Device.getSwitch("TELESCOPE_MOTION_NS");
705 if (motionNS)
706 {
707 INDI::PropertySwitch switchProperty(motionNS);
708 switchProperty.reset();
709 if (rate > 0)
710 switchProperty[0].setState(ISS_ON); // North
711 else if (rate < 0)
712 switchProperty[1].setState(ISS_ON); // South
713 else
714 {
715 // If rate is 0, stop motion
716 switchProperty[0].setState(ISS_OFF);
717 switchProperty[1].setState(ISS_OFF);
718 }
719 // Request to send the switch to the device
720 requestNewSwitch(switchProperty);
721 success = true;
722 }
723 }
724 }
725 catch (const std::exception &e)
726 {
727 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to parse MoveAxis request: %s", e.what());
728 }
729
730 sendResponseStatus(res, success, success ? "" : "Failed to move axis");
731}
732
733void TelescopeBridge::handleAxisRates(const httplib::Request &req, httplib::Response &res)
734{
735 try
736 {
737 // Check if the Axis parameter exists in the query
738 auto axisIt = req.params.find("Axis");
739 if (axisIt == req.params.end())
740 {
741 sendResponseValue(res, json::array(), false, "Missing Axis parameter");
742 return;
743 }
744
745 // Convert Axis value to int
746 int axis = std::stoi(axisIt->second);
747
748 // Validate axis value (0 = Primary/RA/AZ, 1 = Secondary/DEC/ALT)
749 if (axis < 0 || axis > 1)
750 {
751 sendResponseValue(res, json::array(), false, "Invalid Axis value. Must be 0 (Primary/RA/AZ) or 1 (Secondary/DEC/ALT)");
752 return;
753 }
754
755 // Create a JSON array of rate objects
756 json ratesArray = json::array();
757
758 // 1x to 800x sidereal
759 // hardcoded for now
760 ratesArray.push_back(
761 {
762 {"Minimum", 0.00418}, // degrees/sec
763 {"Maximum", 3.344}
764 });
765
766 // Send the response
767 sendResponseValue(res, ratesArray);
768 }
769 catch (const std::exception &e)
770 {
771 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to process AxisRates request: %s", e.what());
772 sendResponseValue(res, json::array(), false, std::string("Error processing request: ") + e.what());
773 }
774}
775
776void TelescopeBridge::handleSetTracking(const httplib::Request &req, httplib::Response &res)
777{
778 bool success = false;
779 try
780 {
781 // Parse form-urlencoded data from request body
782 std::map<std::string, std::string> formData;
784
785 // Check if required parameters exist
786 if (formData.find("Tracking") == formData.end())
787 {
788 sendResponseStatus(res, false, "Missing Tracking parameter");
789 return;
790 }
791
792 // Convert Tracking value to bool
793 bool tracking = (formData["Tracking"] == "True" || formData["Tracking"] == "true" ||
794 formData["Tracking"] == "1");
795
796 auto trackState = m_Device.getSwitch("TELESCOPE_TRACK_STATE");
797 if (trackState)
798 {
799 INDI::PropertySwitch switchProperty(trackState);
800 switchProperty.reset();
801 switchProperty[tracking ? 0 : 1].setState(ISS_ON);
802 // Request to send the switch to the device
803 requestNewSwitch(switchProperty);
804 success = true;
805 }
806 }
807 catch (const std::exception &e)
808 {
809 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to parse SetTracking request: %s", e.what());
810 }
811
812 sendResponseStatus(res, success, success ? "" : "Failed to set tracking state");
813}
814
815void TelescopeBridge::handleSetRightAscensionRate(const httplib::Request &req, httplib::Response &res)
816{
817 bool success = false;
818 try
819 {
820 // Parse form-urlencoded data from request body
821 std::map<std::string, std::string> formData;
823
824 // Check if required parameters exist
825 if (formData.find("RightAscensionRate") == formData.end())
826 {
827 sendResponseStatus(res, false, "Missing RightAscensionRate parameter");
828 return;
829 }
830
831 // Convert RightAscensionRate value to double
832 double raRate = std::stod(formData["RightAscensionRate"]);
833
834 auto trackRate = m_Device.getNumber("TELESCOPE_TRACK_RATE");
835 if (trackRate)
836 {
837 INDI::PropertyNumber numberProperty(trackRate);
838 for (auto &num : numberProperty)
839 {
840 if (num.isNameMatch("TRACK_RATE_RA"))
841 num.setValue(raRate);
842 }
843 // Request to send the rate to the device
844 requestNewNumber(numberProperty);
845
846 // Update the member variable
847 m_RightAscensionRate = raRate;
848 success = true;
849 }
850 }
851 catch (const std::exception &e)
852 {
853 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to parse SetRightAscensionRate request: %s",
854 e.what());
855 }
856
857 sendResponseStatus(res, success, success ? "" : "Failed to set right ascension rate");
858}
859
860void TelescopeBridge::handleSetDeclinationRate(const httplib::Request &req, httplib::Response &res)
861{
862 bool success = false;
863 try
864 {
865 // Parse form-urlencoded data from request body
866 std::map<std::string, std::string> formData;
868
869 // Check if required parameters exist
870 if (formData.find("DeclinationRate") == formData.end())
871 {
872 sendResponseStatus(res, false, "Missing DeclinationRate parameter");
873 return;
874 }
875
876 // Convert DeclinationRate value to double
877 double decRate = std::stod(formData["DeclinationRate"]);
878
879 auto trackRate = m_Device.getNumber("TELESCOPE_TRACK_RATE");
880 if (trackRate)
881 {
882 INDI::PropertyNumber numberProperty(trackRate);
883 for (auto &num : numberProperty)
884 {
885 if (num.isNameMatch("TRACK_RATE_DE"))
886 num.setValue(decRate);
887 }
888 // Request to send the rate to the device
889 requestNewNumber(numberProperty);
890
891 // Update the member variable
892 m_DeclinationRate = decRate;
893 success = true;
894 }
895 }
896 catch (const std::exception &e)
897 {
898 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to parse SetDeclinationRate request: %s",
899 e.what());
900 }
901
902 sendResponseStatus(res, success, success ? "" : "Failed to set declination rate");
903}
904
905// Equatorial System
906void TelescopeBridge::handleEquatorialSystem(const httplib::Request &req, httplib::Response &res)
907{
908 INDI_UNUSED(req);
909 std::lock_guard<std::mutex> lock(m_Mutex);
910 sendResponseValue(res, 1);
911}
912
913// Site Information
914void TelescopeBridge::handleSiteLatitude(const httplib::Request &req, httplib::Response &res)
915{
916 auto geoCoord = m_Device.getNumber("GEOGRAPHIC_COORD");
917 if (!geoCoord.isValid())
918 {
919 sendResponseValue(res, 0.0, false, "GEOGRAPHIC_COORD property not found");
920 return;
921 }
922
923 // Handle PUT request to set site latitude
924 if (req.method == "PUT")
925 {
926 try
927 {
928 // Parse form-urlencoded data from request body
929 std::map<std::string, std::string> formData;
931
932 // Check if SiteLatitude parameter exists
933 if (formData.find("SiteLatitude") == formData.end())
934 {
935 sendResponseValue(res, 0.0, false, "Missing SiteLatitude parameter");
936 return;
937 }
938
939 // Convert SiteLatitude value to double
940 double latitude = std::stod(formData["SiteLatitude"]);
941
942 // Update the GEOGRAPHIC_COORD property
943 INDI::PropertyNumber numberProperty(geoCoord);
944 for (auto &num : numberProperty)
945 {
946 if (num.isNameMatch("LAT"))
947 num.setValue(latitude);
948 }
949
950 // Request to send the updated property to the device
951 requestNewNumber(numberProperty);
952 sendResponseValue(res, latitude);
953 }
954 catch (const std::exception &e)
955 {
956 sendResponseValue(res, 0.0, false, std::string("Invalid request: ") + e.what());
957 }
958 }
959 else
960 {
961 // GET request - return current latitude
962 double latitude = 0.0;
963 INDI::PropertyNumber numberProperty(geoCoord);
964 for (auto &num : numberProperty)
965 {
966 if (num.isNameMatch("LAT"))
967 {
968 latitude = num.getValue();
969 break;
970 }
971 }
972 sendResponseValue(res, latitude);
973 }
974}
975
976void TelescopeBridge::handleSiteLongitude(const httplib::Request &req, httplib::Response &res)
977{
978 auto geoCoord = m_Device.getNumber("GEOGRAPHIC_COORD");
979 if (!geoCoord.isValid())
980 {
981 sendResponseValue(res, 0.0, false, "GEOGRAPHIC_COORD property not found");
982 return;
983 }
984
985 // Handle PUT request to set site longitude
986 if (req.method == "PUT")
987 {
988 try
989 {
990 // Parse form-urlencoded data from request body
991 std::map<std::string, std::string> formData;
993
994 // Check if SiteLongitude parameter exists
995 if (formData.find("SiteLongitude") == formData.end())
996 {
997 sendResponseValue(res, 0.0, false, "Missing SiteLongitude parameter");
998 return;
999 }
1000
1001 // Convert SiteLongitude value to double
1002 double longitude = std::stod(formData["SiteLongitude"]);
1003
1004 // Update the GEOGRAPHIC_COORD property
1005 INDI::PropertyNumber numberProperty(geoCoord);
1006 for (auto &num : numberProperty)
1007 {
1008 if (num.isNameMatch("LONG"))
1009 num.setValue(longitude);
1010 }
1011
1012 // Request to send the updated property to the device
1013 requestNewNumber(numberProperty);
1014 sendResponseValue(res, longitude);
1015 }
1016 catch (const std::exception &e)
1017 {
1018 sendResponseValue(res, 0.0, false, std::string("Invalid request: ") + e.what());
1019 }
1020 }
1021 else
1022 {
1023 // GET request - return current longitude
1024 double longitude = 0.0;
1025 INDI::PropertyNumber numberProperty(geoCoord);
1026 for (auto &num : numberProperty)
1027 {
1028 if (num.isNameMatch("LONG"))
1029 {
1030 longitude = num.getValue();
1031 break;
1032 }
1033 }
1034 sendResponseValue(res, longitude);
1035 }
1036}
1037
1038void TelescopeBridge::handleSiteElevation(const httplib::Request &req, httplib::Response &res)
1039{
1040 auto geoCoord = m_Device.getNumber("GEOGRAPHIC_COORD");
1041 if (!geoCoord.isValid())
1042 {
1043 sendResponseValue(res, 0.0, false, "GEOGRAPHIC_COORD property not found");
1044 return;
1045 }
1046
1047 // Handle PUT request to set site elevation
1048 if (req.method == "PUT")
1049 {
1050 try
1051 {
1052 // Parse form-urlencoded data from request body
1053 std::map<std::string, std::string> formData;
1055
1056 // Check if SiteElevation parameter exists
1057 if (formData.find("SiteElevation") == formData.end())
1058 {
1059 sendResponseValue(res, 0.0, false, "Missing SiteElevation parameter");
1060 return;
1061 }
1062
1063 // Convert SiteElevation value to double
1064 double elevation = std::stod(formData["SiteElevation"]);
1065
1066 // Update the GEOGRAPHIC_COORD property
1067 INDI::PropertyNumber numberProperty(geoCoord);
1068 for (auto &num : numberProperty)
1069 {
1070 if (num.isNameMatch("ELEV"))
1071 num.setValue(elevation);
1072 }
1073
1074 // Request to send the updated property to the device
1075 requestNewNumber(numberProperty);
1076 sendResponseValue(res, elevation);
1077 }
1078 catch (const std::exception &e)
1079 {
1080 sendResponseValue(res, 0.0, false, std::string("Invalid request: ") + e.what());
1081 }
1082 }
1083 else
1084 {
1085 // GET request - return current elevation
1086 double elevation = 0.0;
1087 INDI::PropertyNumber numberProperty(geoCoord);
1088 for (auto &num : numberProperty)
1089 {
1090 if (num.isNameMatch("ELEV"))
1091 {
1092 elevation = num.getValue();
1093 break;
1094 }
1095 }
1096 sendResponseValue(res, elevation);
1097 }
1098}
static void parseFormUrlEncodedBody(const std::string &body, std::map< std::string, std::string > &params)
bool isConnected() const
INDI::PropertyNumber getNumber(const char *name) const
const char * getDeviceName() const
Property getProperty(const char *name, INDI_PROPERTY_TYPE type=INDI_UNKNOWN) const
Return a property and its type given its name.
INDI::PropertySwitch getSwitch(const char *name) const
void setState(IPState state)
bool isValid() const
void handleConnected(const httplib::Request &req, httplib::Response &res) override
void handleInterfaceVersion(const httplib::Request &req, httplib::Response &res) override
void handleDriverVersion(const httplib::Request &req, httplib::Response &res) override
void handleName(const httplib::Request &req, httplib::Response &res) override
void handleDescription(const httplib::Request &req, httplib::Response &res) override
void handleDriverInfo(const httplib::Request &req, httplib::Response &res) override
a class to store JSON values
void push_back(basic_json &&val)
add an object to an array
double ra
double dec
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
#define INDI_UNUSED(x)
Definition indidevapi.h:132
#define DEBUGFDEVICE(device, priority, msg,...)
Definition indilogger.h:61
basic_json<> json
default specialization
double trackRate
std::string body
Definition httplib.h:761
std::string method
Definition httplib.h:755