Instrument Neutral Distributed Interface INDI 2.2.5
camera_bridge_handlers.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2025 Jasem Mutlaq. All rights reserved.
3
4 INDI Alpaca Camera Bridge - API Handlers
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 "camera_bridge.h"
26#include "indilogger.h"
27#include <httplib.h>
28#include <chrono>
29#include <iomanip>
30#include <sstream>
31#include <map>
32
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 std::lock_guard<std::mutex> lock(m_Mutex);
46
47 if (req.method == "GET")
48 {
49 // Return connection status
50 bool connected = m_Device.isConnected();
51 sendResponseValue(res, req, connected);
52 }
53 else if (req.method == "PUT")
54 {
55 // Acknowledge connection status request, but do not control connection from bridge
56 sendResponseStatus(res, req, true, "");
57 }
58}
59
61{
62 // Transaction IDs extracted in sendResponseValue
63 std::lock_guard<std::mutex> lock(m_Mutex);
64 sendResponseValue(res, req, std::string(m_Device.getDeviceName()));
65}
66
68{
69 // Transaction IDs extracted in sendResponseValue
70 std::lock_guard<std::mutex> lock(m_Mutex);
71 std::string description = "INDI Camera Bridge for " + std::string(m_Device.getDeviceName());
72 sendResponseValue(res, req, description);
73}
74
76{
77 // Transaction IDs extracted in sendResponseValue
78 std::lock_guard<std::mutex> lock(m_Mutex);
79 std::string driverInfo = "INDI Alpaca Camera Bridge v1.0";
80 sendResponseValue(res, req, driverInfo);
81}
82
84{
85 // Transaction IDs extracted in sendResponseValue
86 std::lock_guard<std::mutex> lock(m_Mutex);
87 std::string version = "1.0.0";
88 sendResponseValue(res, req, version);
89}
90
92{
93 // Transaction IDs extracted in sendResponseValue
94 std::lock_guard<std::mutex> lock(m_Mutex);
95 int interfaceVersion = 3; // Alpaca Camera Interface v3
96 sendResponseValue(res, req, interfaceVersion);
97}
98
99// Camera Information Properties
100void CameraBridge::handleCameraXSize(const httplib::Request &req, httplib::Response &res)
101{
102 // Transaction IDs extracted in sendResponseValue
103 std::lock_guard<std::mutex> lock(m_Mutex);
104 sendResponseValue(res, req, m_CameraXSize);
105}
106
107void CameraBridge::handleCameraYSize(const httplib::Request &req, httplib::Response &res)
108{
109 // Transaction IDs extracted in sendResponseValue
110 std::lock_guard<std::mutex> lock(m_Mutex);
111 sendResponseValue(res, req, m_CameraYSize);
112}
113
114void CameraBridge::handleMaxBinX(const httplib::Request &req, httplib::Response &res)
115{
116 // Transaction IDs extracted in sendResponseValue
117 std::lock_guard<std::mutex> lock(m_Mutex);
118 sendResponseValue(res, req, m_MaxBinX);
119}
120
121void CameraBridge::handleMaxBinY(const httplib::Request &req, httplib::Response &res)
122{
123 // Transaction IDs extracted in sendResponseValue
124 std::lock_guard<std::mutex> lock(m_Mutex);
125 sendResponseValue(res, req, m_MaxBinY);
126}
127
128void CameraBridge::handleCanAsymmetricBin(const httplib::Request &req, httplib::Response &res)
129{
130 // Transaction IDs extracted in sendResponseValue
131 std::lock_guard<std::mutex> lock(m_Mutex);
132 sendResponseValue(res, req, m_CanAsymmetricBin);
133}
134
135void CameraBridge::handlePixelSizeX(const httplib::Request &req, httplib::Response &res)
136{
137 // Transaction IDs extracted in sendResponseValue
138 std::lock_guard<std::mutex> lock(m_Mutex);
139 sendResponseValue(res, req, m_PixelSizeX);
140}
141
142void CameraBridge::handlePixelSizeY(const httplib::Request &req, httplib::Response &res)
143{
144 // Transaction IDs extracted in sendResponseValue
145 std::lock_guard<std::mutex> lock(m_Mutex);
146 sendResponseValue(res, req, m_PixelSizeY);
147}
148
149void CameraBridge::handleBinX(const httplib::Request &req, httplib::Response &res)
150{
151 std::lock_guard<std::mutex> lock(m_Mutex);
152
153 if (req.method == "GET")
154 {
155 sendResponseValue(res, req, m_BinX);
156 }
157 else if (req.method == "PUT")
158 {
159 httplib::Params params;
160 httplib::detail::parse_query_text(req.body, params);
161
162 if (params.count("BinX"))
163 {
164 int newBinX = std::stoi(params.find("BinX")->second);
165 INDI::Property binningProperty = m_Device.getProperty("CCD_BINNING");
166 if (binningProperty.isValid() && binningProperty.getType() == INDI_NUMBER)
167 {
168 INDI::PropertyNumber numberProperty(binningProperty);
169 auto horBinElement = numberProperty.findWidgetByName("HOR_BIN");
170 if (horBinElement)
171 {
172 horBinElement->setValue(newBinX);
173 requestNewNumber(numberProperty);
174 sendResponseStatus(res, req, true, "");
175 return;
176 }
177 }
178 sendResponseStatus(res, req, false, "Failed to set BinX: CCD_BINNING property not found or invalid.");
179 }
180 else
181 {
182 sendResponseStatus(res, req, false, "Missing 'BinX' parameter in request body");
183 }
184 }
185}
186
187void CameraBridge::handleBinY(const httplib::Request &req, httplib::Response &res)
188{
189 std::lock_guard<std::mutex> lock(m_Mutex);
190
191 if (req.method == "GET")
192 {
193 sendResponseValue(res, req, m_BinY);
194 }
195 else if (req.method == "PUT")
196 {
197 httplib::Params params;
198 httplib::detail::parse_query_text(req.body, params);
199
200 if (params.count("BinY"))
201 {
202 int newBinY = std::stoi(params.find("BinY")->second);
203 INDI::Property binningProperty = m_Device.getProperty("CCD_BINNING");
204 if (binningProperty.isValid() && binningProperty.getType() == INDI_NUMBER)
205 {
206 INDI::PropertyNumber numberProperty(binningProperty);
207 auto verBinElement = numberProperty.findWidgetByName("VER_BIN");
208 if (verBinElement)
209 {
210 verBinElement->setValue(newBinY);
211 requestNewNumber(numberProperty);
212 sendResponseStatus(res, req, true, "");
213 return;
214 }
215 }
216 sendResponseStatus(res, req, false, "Failed to set BinY: CCD_BINNING property not found or invalid.");
217 }
218 else
219 {
220 sendResponseStatus(res, req, false, "Missing 'BinY' parameter in request body");
221 }
222 }
223}
224
225void CameraBridge::handleStartX(const httplib::Request &req, httplib::Response &res)
226{
227 std::lock_guard<std::mutex> lock(m_Mutex);
228
229 if (req.method == "GET")
230 {
231 sendResponseValue(res, req, m_StartX);
232 }
233 else if (req.method == "PUT")
234 {
235 httplib::Params params;
236 httplib::detail::parse_query_text(req.body, params);
237
238 if (params.count("StartX"))
239 {
240 int newStartX = std::stoi(params.find("StartX")->second);
241 INDI::Property frameProperty = m_Device.getProperty("CCD_FRAME");
242 if (frameProperty.isValid() && frameProperty.getType() == INDI_NUMBER)
243 {
244 INDI::PropertyNumber numberProperty(frameProperty);
245 auto xElement = numberProperty.findWidgetByName("X");
246 if (xElement)
247 {
248 xElement->setValue(newStartX);
249 requestNewNumber(numberProperty);
250 sendResponseStatus(res, req, true, "");
251 return;
252 }
253 }
254 sendResponseStatus(res, req, false, "Failed to set StartX: CCD_FRAME property not found or invalid.");
255 }
256 else
257 {
258 sendResponseStatus(res, req, false, "Missing 'StartX' parameter in request body");
259 }
260 }
261}
262
263void CameraBridge::handleStartY(const httplib::Request &req, httplib::Response &res)
264{
265 std::lock_guard<std::mutex> lock(m_Mutex);
266
267 if (req.method == "GET")
268 {
269 sendResponseValue(res, req, m_StartY);
270 }
271 else if (req.method == "PUT")
272 {
273 httplib::Params params;
274 httplib::detail::parse_query_text(req.body, params);
275
276 if (params.count("StartY"))
277 {
278 int newStartY = std::stoi(params.find("StartY")->second);
279 INDI::Property frameProperty = m_Device.getProperty("CCD_FRAME");
280 if (frameProperty.isValid() && frameProperty.getType() == INDI_NUMBER)
281 {
282 INDI::PropertyNumber numberProperty(frameProperty);
283 auto yElement = numberProperty.findWidgetByName("Y");
284 if (yElement)
285 {
286 yElement->setValue(newStartY);
287 requestNewNumber(numberProperty);
288 sendResponseStatus(res, req, true, "");
289 return;
290 }
291 }
292 sendResponseStatus(res, req, false, "Failed to set StartY: CCD_FRAME property not found or invalid.");
293 }
294 else
295 {
296 sendResponseStatus(res, req, false, "Missing 'StartY' parameter in request body");
297 }
298 }
299}
300
301void CameraBridge::handleNumX(const httplib::Request &req, httplib::Response &res)
302{
303 std::lock_guard<std::mutex> lock(m_Mutex);
304
305 if (req.method == "GET")
306 {
307 sendResponseValue(res, req, m_NumX);
308 }
309 else if (req.method == "PUT")
310 {
311 httplib::Params params;
312 httplib::detail::parse_query_text(req.body, params);
313
314 if (params.count("NumX"))
315 {
316 int newNumX = std::stoi(params.find("NumX")->second);
317 INDI::Property frameProperty = m_Device.getProperty("CCD_FRAME");
318 if (frameProperty.isValid() && frameProperty.getType() == INDI_NUMBER)
319 {
320 INDI::PropertyNumber numberProperty(frameProperty);
321 auto widthElement = numberProperty.findWidgetByName("WIDTH");
322 if (widthElement)
323 {
324 widthElement->setValue(newNumX);
325 requestNewNumber(numberProperty);
326 sendResponseStatus(res, req, true, "");
327 return;
328 }
329 }
330 sendResponseStatus(res, req, false, "Failed to set NumX: CCD_FRAME property not found or invalid.");
331 }
332 else
333 {
334 sendResponseStatus(res, req, false, "Missing 'NumX' parameter in request body");
335 }
336 }
337}
338
339void CameraBridge::handleNumY(const httplib::Request &req, httplib::Response &res)
340{
341 std::lock_guard<std::mutex> lock(m_Mutex);
342
343 if (req.method == "GET")
344 {
345 sendResponseValue(res, req, m_NumY);
346 }
347 else if (req.method == "PUT")
348 {
349 httplib::Params params;
350 httplib::detail::parse_query_text(req.body, params);
351
352 if (params.count("NumY"))
353 {
354 int newNumY = std::stoi(params.find("NumY")->second);
355 INDI::Property frameProperty = m_Device.getProperty("CCD_FRAME");
356 if (frameProperty.isValid() && frameProperty.getType() == INDI_NUMBER)
357 {
358 INDI::PropertyNumber numberProperty(frameProperty);
359 auto heightElement = numberProperty.findWidgetByName("HEIGHT");
360 if (heightElement)
361 {
362 heightElement->setValue(newNumY);
363 requestNewNumber(numberProperty);
364 sendResponseStatus(res, req, true, "");
365 return;
366 }
367 }
368 sendResponseStatus(res, req, false, "Failed to set NumY: CCD_FRAME property not found or invalid.");
369 }
370 else
371 {
372 sendResponseStatus(res, req, false, "Missing 'NumY' parameter in request body");
373 }
374 }
375}
376
377// Camera Capabilities
378void CameraBridge::handleCanAbortExposure(const httplib::Request &req, httplib::Response &res)
379{
380 // Transaction IDs extracted in sendResponseValue
381 std::lock_guard<std::mutex> lock(m_Mutex);
382 sendResponseValue(res, req, m_CanAbortExposure);
383}
384
385void CameraBridge::handleCanStopExposure(const httplib::Request &req, httplib::Response &res)
386{
387 // Transaction IDs extracted in sendResponseValue
388 std::lock_guard<std::mutex> lock(m_Mutex);
389 sendResponseValue(res, req, m_CanStopExposure);
390}
391
392void CameraBridge::handleCanPulseGuide(const httplib::Request &req, httplib::Response &res)
393{
394 // Transaction IDs extracted in sendResponseValue
395 std::lock_guard<std::mutex> lock(m_Mutex);
396 sendResponseValue(res, req, m_CanPulseGuide);
397}
398
399void CameraBridge::handleCanSetCCDTemperature(const httplib::Request &req, httplib::Response &res)
400{
401 // Transaction IDs extracted in sendResponseValue
402 std::lock_guard<std::mutex> lock(m_Mutex);
403 sendResponseValue(res, req, m_CanSetCCDTemperature);
404}
405
406void CameraBridge::handleCanFastReadout(const httplib::Request &req, httplib::Response &res)
407{
408 // Transaction IDs extracted in sendResponseValue
409 std::lock_guard<std::mutex> lock(m_Mutex);
410 // INDI cameras typically don't support fast readout mode
411 sendResponseValue(res, req, false);
412}
413
414void CameraBridge::handleHasShutter(const httplib::Request &req, httplib::Response &res)
415{
416 // Transaction IDs extracted in sendResponseValue
417 std::lock_guard<std::mutex> lock(m_Mutex);
418 sendResponseValue(res, req, m_HasShutter);
419}
420
421// Temperature Control
422void CameraBridge::handleCCDTemperature(const httplib::Request &req, httplib::Response &res)
423{
424 // Transaction IDs extracted in sendResponseValue
425 std::lock_guard<std::mutex> lock(m_Mutex);
426 sendResponseValue(res, req, m_CCDTemperature);
427}
428
429void CameraBridge::handleCoolerOn(const httplib::Request &req, httplib::Response &res)
430{
431 std::lock_guard<std::mutex> lock(m_Mutex);
432
433 if (req.method == "GET")
434 {
435 sendResponseValue(res, req, m_CoolerOn);
436 }
437 else if (req.method == "PUT")
438 {
439 httplib::Params params;
440 httplib::detail::parse_query_text(req.body, params);
441
442 if (params.count("CoolerOn"))
443 {
444 bool newCoolerOn = (params.find("CoolerOn")->second == "true");
445 INDI::Property coolerProperty = m_Device.getProperty("CCD_COOLER");
446 if (coolerProperty.isValid() && coolerProperty.getType() == INDI_SWITCH)
447 {
448 INDI::PropertySwitch switchProperty(coolerProperty);
449 if (newCoolerOn)
450 {
451 switchProperty.findWidgetByName("COOLER_ON")->setState(ISS_ON);
452 switchProperty.findWidgetByName("COOLER_OFF")->setState(ISS_OFF);
453 }
454 else
455 {
456 switchProperty.findWidgetByName("COOLER_ON")->setState(ISS_OFF);
457 switchProperty.findWidgetByName("COOLER_OFF")->setState(ISS_ON);
458 }
459 requestNewSwitch(switchProperty);
460 sendResponseStatus(res, req, true, "");
461 return;
462 }
463 sendResponseStatus(res, req, false, "Failed to set CoolerOn: CCD_COOLER property not found or invalid.");
464 }
465 else
466 {
467 sendResponseStatus(res, req, false, "Missing 'CoolerOn' parameter in request body");
468 }
469 }
470}
471
472void CameraBridge::handleCoolerPower(const httplib::Request &req, httplib::Response &res)
473{
474 // Transaction IDs extracted in sendResponseValue
475 std::lock_guard<std::mutex> lock(m_Mutex);
476 sendResponseValue(res, req, m_CoolerPower);
477}
478
479void CameraBridge::handleCanGetCoolerPower(const httplib::Request &req, httplib::Response &res)
480{
481 // Transaction IDs extracted in sendResponseValue
482 std::lock_guard<std::mutex> lock(m_Mutex);
483 // Return true if CCD_COOLER_POWER property exists
484 INDI::Property coolerPowerProperty = m_Device.getProperty("CCD_COOLER_POWER");
485 bool canGet = coolerPowerProperty.isValid();
486 sendResponseValue(res, req, canGet);
487}
488
489void CameraBridge::handleSetCCDTemperature(const httplib::Request &req, httplib::Response &res)
490{
491 std::lock_guard<std::mutex> lock(m_Mutex);
492
493 if (req.method == "GET")
494 {
495 // Return the target temperature setpoint
496 sendResponseValue(res, req, m_TargetCCDTemperature);
497 }
498 else if (req.method == "PUT")
499 {
500 httplib::Params params;
501 httplib::detail::parse_query_text(req.body, params);
502
503 if (params.count("SetCCDTemperature"))
504 {
505 double newTemperature = std::stod(params.find("SetCCDTemperature")->second);
506 m_TargetCCDTemperature = newTemperature; // Track the target
507
508 INDI::Property temperatureProperty = m_Device.getProperty("CCD_TEMPERATURE");
509 if (temperatureProperty.isValid() && temperatureProperty.getType() == INDI_NUMBER)
510 {
511 INDI::PropertyNumber numberProperty(temperatureProperty);
512 numberProperty[0].setValue(newTemperature);
513 requestNewNumber(numberProperty);
514 sendResponseStatus(res, req, true, "");
515 return;
516 }
517 sendResponseStatus(res, req, false, "Failed to set CCDTemperature: CCD_TEMPERATURE property not found or invalid.");
518 }
519 else
520 {
521 sendResponseStatus(res, req, false, "Missing 'SetCCDTemperature' parameter in request body");
522 }
523 }
524 else
525 {
526 sendResponseStatus(res, req, false, "Method not supported");
527 }
528}
529
530// Gain and Offset
531void CameraBridge::handleGain(const httplib::Request &req, httplib::Response &res)
532{
533 std::lock_guard<std::mutex> lock(m_Mutex);
534
535 if (!m_HasGain)
536 {
537 sendResponseStatus(res, req, false, "Gain not supported");
538 return;
539 }
540
541 if (req.method == "GET")
542 {
543 sendResponseValue(res, req, static_cast<int>(m_Gain));
544 }
545 else if (req.method == "PUT")
546 {
547 httplib::Params params;
548 httplib::detail::parse_query_text(req.body, params);
549
550 if (params.count("Gain"))
551 {
552 double newGain = std::stod(params.find("Gain")->second);
553
554 if (m_UsesCCDControlsForGainOffset)
555 {
556 INDI::Property controlsProperty = m_Device.getProperty("CCD_CONTROLS");
557 if (controlsProperty.isValid() && controlsProperty.getType() == INDI_NUMBER)
558 {
559 INDI::PropertyNumber numberProperty(controlsProperty);
560 auto gainElement = numberProperty.findWidgetByName("Gain");
561 if (gainElement)
562 {
563 gainElement->setValue(newGain);
564 requestNewNumber(numberProperty);
565 sendResponseStatus(res, req, true, "");
566 return;
567 }
568 }
569 sendResponseStatus(res, req, false, "Failed to set Gain via CCD_CONTROLS");
570 }
571 else
572 {
573 INDI::Property gainProperty = m_Device.getProperty("CCD_GAIN");
574 if (gainProperty.isValid() && gainProperty.getType() == INDI_NUMBER)
575 {
576 INDI::PropertyNumber numberProperty(gainProperty);
577 numberProperty[0].setValue(newGain);
578 requestNewNumber(numberProperty);
579 sendResponseStatus(res, req, true, "");
580 return;
581 }
582 sendResponseStatus(res, req, false, "Failed to set Gain via CCD_GAIN");
583 }
584 }
585 else
586 {
587 sendResponseStatus(res, req, false, "Missing 'Gain' parameter in request body");
588 }
589 }
590}
591
592void CameraBridge::handleGainMin(const httplib::Request &req, httplib::Response &res)
593{
594 // Transaction IDs extracted in sendResponseValue
595 std::lock_guard<std::mutex> lock(m_Mutex);
596
597 if (m_HasGain)
598 sendResponseValue(res, req, static_cast<int>(m_GainMin));
599 else
600 sendResponseStatus(res, req, false, "Gain not supported");
601}
602
603void CameraBridge::handleGainMax(const httplib::Request &req, httplib::Response &res)
604{
605 // Transaction IDs extracted in sendResponseValue
606 std::lock_guard<std::mutex> lock(m_Mutex);
607
608 if (m_HasGain)
609 sendResponseValue(res, req, static_cast<int>(m_GainMax));
610 else
611 sendResponseStatus(res, req, false, "Gain not supported");
612}
613
614void CameraBridge::handleGains(const httplib::Request &req, httplib::Response &res)
615{
616 // Transaction IDs extracted in sendResponseValue
617 std::lock_guard<std::mutex> lock(m_Mutex);
618
619 if (m_HasGain)
620 {
621 json gainsArray = json::array();
622 for (const auto& gain : m_Gains)
623 gainsArray.push_back(gain);
624 sendResponseValue(res, req, gainsArray);
625 }
626 else
627 {
628 sendResponseStatus(res, req, false, "Gain not supported");
629 }
630}
631
632void CameraBridge::handleOffset(const httplib::Request &req, httplib::Response &res)
633{
634 std::lock_guard<std::mutex> lock(m_Mutex);
635
636 if (!m_HasOffset)
637 {
638 sendResponseStatus(res, req, false, "Offset not supported");
639 return;
640 }
641
642 if (req.method == "GET")
643 {
644 sendResponseValue(res, req, static_cast<int>(m_Offset));
645 }
646 else if (req.method == "PUT")
647 {
648 httplib::Params params;
649 httplib::detail::parse_query_text(req.body, params);
650
651 if (params.count("Offset"))
652 {
653 double newOffset = std::stod(params.find("Offset")->second);
654
655 if (m_UsesCCDControlsForGainOffset)
656 {
657 INDI::Property controlsProperty = m_Device.getProperty("CCD_CONTROLS");
658 if (controlsProperty.isValid() && controlsProperty.getType() == INDI_NUMBER)
659 {
660 INDI::PropertyNumber numberProperty(controlsProperty);
661 auto offsetElement = numberProperty.findWidgetByName("Offset");
662 if (offsetElement)
663 {
664 offsetElement->setValue(newOffset);
665 requestNewNumber(numberProperty);
666 sendResponseStatus(res, req, true, "");
667 return;
668 }
669 }
670 sendResponseStatus(res, req, false, "Failed to set Offset via CCD_CONTROLS");
671 }
672 else
673 {
674 INDI::Property offsetProperty = m_Device.getProperty("CCD_OFFSET");
675 if (offsetProperty.isValid() && offsetProperty.getType() == INDI_NUMBER)
676 {
677 INDI::PropertyNumber numberProperty(offsetProperty);
678 numberProperty[0].setValue(newOffset);
679 requestNewNumber(numberProperty);
680 sendResponseStatus(res, req, true, "");
681 return;
682 }
683 sendResponseStatus(res, req, false, "Failed to set Offset via CCD_OFFSET");
684 }
685 }
686 else
687 {
688 sendResponseStatus(res, req, false, "Missing 'Offset' parameter in request body");
689 }
690 }
691}
692
693void CameraBridge::handleOffsetMin(const httplib::Request &req, httplib::Response &res)
694{
695 // Transaction IDs extracted in sendResponseValue
696 std::lock_guard<std::mutex> lock(m_Mutex);
697
698 if (m_HasOffset)
699 sendResponseValue(res, req, static_cast<int>(m_OffsetMin));
700 else
701 sendResponseStatus(res, req, false, "Offset not supported");
702}
703
704void CameraBridge::handleOffsetMax(const httplib::Request &req, httplib::Response &res)
705{
706 // Transaction IDs extracted in sendResponseValue
707 std::lock_guard<std::mutex> lock(m_Mutex);
708
709 if (m_HasOffset)
710 sendResponseValue(res, req, static_cast<int>(m_OffsetMax));
711 else
712 sendResponseStatus(res, req, false, "Offset not supported");
713}
714
715void CameraBridge::handleOffsets(const httplib::Request &req, httplib::Response &res)
716{
717 // Transaction IDs extracted in sendResponseValue
718 std::lock_guard<std::mutex> lock(m_Mutex);
719
720 if (m_HasOffset)
721 {
722 json offsetsArray = json::array();
723 for (const auto& offset : m_Offsets)
724 offsetsArray.push_back(offset);
725 sendResponseValue(res, req, offsetsArray);
726 }
727 else
728 {
729 sendResponseStatus(res, req, false, "Offset not supported");
730 }
731}
732
733// Readout Modes and Sensor Info
734void CameraBridge::handleReadoutMode(const httplib::Request &req, httplib::Response &res)
735{
736 std::lock_guard<std::mutex> lock(m_Mutex);
737
738 if (req.method == "GET")
739 {
740 sendResponseValue(res, req, m_ReadoutMode);
741 }
742 else if (req.method == "PUT")
743 {
744 httplib::Params params;
745 httplib::detail::parse_query_text(req.body, params);
746
747 if (params.count("ReadoutMode"))
748 {
749 int newReadoutModeIndex = std::stoi(params.find("ReadoutMode")->second);
750
751 if (newReadoutModeIndex >= 0 && static_cast<size_t>(newReadoutModeIndex) < m_ReadoutModes.size())
752 {
753 std::string newReadoutModeName = m_ReadoutModes[newReadoutModeIndex];
754 INDI::Property readoutModeProperty = m_Device.getProperty("CCD_READOUT_MODE");
755 if (readoutModeProperty.isValid() && readoutModeProperty.getType() == INDI_SWITCH)
756 {
757 INDI::PropertySwitch switchProperty(readoutModeProperty);
758 for (auto &sw : switchProperty)
759 {
760 if (sw.isNameMatch(newReadoutModeName.c_str()))
761 sw.setState(ISS_ON);
762 else
763 sw.setState(ISS_OFF);
764 }
765 requestNewSwitch(switchProperty);
766 sendResponseStatus(res, req, true, "");
767 return;
768 }
769 sendResponseStatus(res, req, false, "Failed to set ReadoutMode: CCD_READOUT_MODE property not found or invalid.");
770 }
771 else
772 {
773 sendResponseStatus(res, req, false, "Invalid ReadoutMode index.");
774 }
775 }
776 else
777 {
778 sendResponseStatus(res, req, false, "Missing 'ReadoutMode' parameter in request body");
779 }
780 }
781}
782
783void CameraBridge::handleReadoutModes(const httplib::Request &req, httplib::Response &res)
784{
785 // Transaction IDs extracted in sendResponseValue
786 std::lock_guard<std::mutex> lock(m_Mutex);
787
788 json modesArray = json::array();
789 for (const auto& mode : m_ReadoutModes)
790 modesArray.push_back(mode);
791 sendResponseValue(res, req, modesArray);
792}
793
794void CameraBridge::handleSensorType(const httplib::Request &req, httplib::Response &res)
795{
796 // Transaction IDs extracted in sendResponseValue
797 std::lock_guard<std::mutex> lock(m_Mutex);
798 sendResponseValue(res, req, m_SensorType);
799}
800
801void CameraBridge::handleBayerOffsetX(const httplib::Request &req, httplib::Response &res)
802{
803 // Transaction IDs extracted in sendResponseValue
804 std::lock_guard<std::mutex> lock(m_Mutex);
805 sendResponseValue(res, req, m_BayerOffsetX);
806}
807
808void CameraBridge::handleBayerOffsetY(const httplib::Request &req, httplib::Response &res)
809{
810 // Transaction IDs extracted in sendResponseValue
811 std::lock_guard<std::mutex> lock(m_Mutex);
812 sendResponseValue(res, req, m_BayerOffsetY);
813}
814
815void CameraBridge::handleSensorName(const httplib::Request &req, httplib::Response &res)
816{
817 // Transaction IDs extracted in sendResponseValue
818 std::lock_guard<std::mutex> lock(m_Mutex);
819 sendResponseValue(res, req, std::string());
820}
821
822// Exposure Control
823void CameraBridge::handleStartExposure(const httplib::Request &req, httplib::Response &res)
824{
825 if (req.method == "PUT")
826 {
827 httplib::Params params;
828 httplib::detail::parse_query_text(req.body, params);
829
830 // Accept both "Duration" (ASCOM standard) and "ExposureDuration" (backward compatibility)
831 double exposureDuration = 0.0;
832 if (params.count("Duration"))
833 {
834 exposureDuration = std::stod(params.find("Duration")->second);
835 }
836 else if (params.count("ExposureDuration"))
837 {
838 exposureDuration = std::stod(params.find("ExposureDuration")->second);
839 }
840 else
841 {
842 sendResponseStatus(res, req, false, "Missing 'Duration' parameter in request body");
843 return;
844 }
845
846 // Handle Light parameter for frame type (light vs dark frame)
847 bool isLightFrame = true; // Default to light frame
848 if (params.count("Light"))
849 {
850 std::string lightParam = params.find("Light")->second;
851 isLightFrame = (lightParam == "true" || lightParam == "True" || lightParam == "1");
852 }
853
854 // Set frame type if supported
855 INDI::Property frameTypeProperty = m_Device.getProperty("CCD_FRAME_TYPE");
856 if (frameTypeProperty.isValid() && frameTypeProperty.getType() == INDI_SWITCH)
857 {
858 INDI::PropertySwitch switchProperty(frameTypeProperty);
859 for (auto &sw : switchProperty)
860 {
861 if (isLightFrame && sw.isNameMatch("FRAME_LIGHT"))
862 sw.setState(ISS_ON);
863 else if (!isLightFrame && sw.isNameMatch("FRAME_DARK"))
864 sw.setState(ISS_ON);
865 else
866 sw.setState(ISS_OFF);
867 }
868 requestNewSwitch(switchProperty);
869 }
870
871 // Start exposure
872 INDI::Property exposureProperty = m_Device.getProperty("CCD_EXPOSURE");
873 if (exposureProperty.isValid() && exposureProperty.getType() == INDI_NUMBER)
874 {
875 INDI::PropertyNumber numberProperty(exposureProperty);
876 numberProperty[0].setValue(exposureDuration);
877 requestNewNumber(numberProperty);
878 sendResponseStatus(res, req, true, "");
879 return;
880 }
881 sendResponseStatus(res, req, false, "Failed to start exposure: CCD_EXPOSURE property not found or invalid.");
882 }
883 else
884 {
885 sendResponseStatus(res, req, false, "Method not supported");
886 }
887}
888
889void CameraBridge::handleStopExposure(const httplib::Request &req, httplib::Response &res)
890{
891 if (req.method == "PUT")
892 {
893 INDI::Property abortProperty = m_Device.getProperty("CCD_ABORT_EXPOSURE");
894 if (abortProperty.isValid() && abortProperty.getType() == INDI_SWITCH)
895 {
896 INDI::PropertySwitch switchProperty(abortProperty);
897 auto abortElement = switchProperty.findWidgetByName("ABORT");
898 if (abortElement)
899 {
900 abortElement->setState(ISS_ON);
901 requestNewSwitch(switchProperty);
902 sendResponseStatus(res, req, true, "");
903 return;
904 }
905 }
906 sendResponseStatus(res, req, false, "Failed to stop exposure: CCD_ABORT_EXPOSURE property not found or invalid.");
907 }
908 else
909 {
910 sendResponseStatus(res, req, false, "Method not supported");
911 }
912}
913
914void CameraBridge::handleAbortExposure(const httplib::Request &req, httplib::Response &res)
915{
916 if (req.method == "PUT")
917 {
918 INDI::Property abortProperty = m_Device.getProperty("CCD_ABORT_EXPOSURE");
919 if (abortProperty.isValid() && abortProperty.getType() == INDI_SWITCH)
920 {
921 INDI::PropertySwitch switchProperty(abortProperty);
922 auto abortElement = switchProperty.findWidgetByName("ABORT");
923 if (abortElement)
924 {
925 abortElement->setState(ISS_ON);
926 requestNewSwitch(switchProperty);
927 sendResponseStatus(res, req, true, "");
928 return;
929 }
930 }
931 sendResponseStatus(res, req, false, "Failed to abort exposure: CCD_ABORT_EXPOSURE property not found or invalid.");
932 }
933 else
934 {
935 sendResponseStatus(res, req, false, "Method not supported");
936 }
937}
938
939void CameraBridge::handleImageReady(const httplib::Request &req, httplib::Response &res)
940{
941 // Transaction IDs extracted in sendResponseValue
942 std::lock_guard<std::mutex> lock(m_Mutex);
943 sendResponseValue(res, req, m_ImageReady);
944}
945
946void CameraBridge::handleCameraState(const httplib::Request &req, httplib::Response &res)
947{
948 // Transaction IDs extracted in sendResponseValue
949 std::lock_guard<std::mutex> lock(m_Mutex);
950 sendResponseValue(res, req, m_CameraState);
951}
952
953void CameraBridge::handlePercentCompleted(const httplib::Request &req, httplib::Response &res)
954{
955 // Transaction IDs extracted in sendResponseValue
956 std::lock_guard<std::mutex> lock(m_Mutex);
957
958 // Calculate percentage if exposing
959 if (m_IsExposing && m_LastExposureDuration > 0)
960 {
961 auto now = std::chrono::steady_clock::now();
962 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_ExposureStartTime).count() / 1000.0;
963 double percentage = std::min(100.0, (elapsed / m_LastExposureDuration) * 100.0);
964 sendResponseValue(res, req, percentage);
965 }
966 else
967 {
968 sendResponseValue(res, req, m_PercentCompleted);
969 }
970}
971
972void CameraBridge::handleLastExposureDuration(const httplib::Request &req, httplib::Response &res)
973{
974 // Transaction IDs extracted in sendResponseValue
975 std::lock_guard<std::mutex> lock(m_Mutex);
976 sendResponseValue(res, req, m_LastExposureDuration);
977}
978
979void CameraBridge::handleLastExposureStartTime(const httplib::Request &req, httplib::Response &res)
980{
981 // Transaction IDs extracted in sendResponseValue
982 std::lock_guard<std::mutex> lock(m_Mutex);
983 sendResponseValue(res, req, m_LastExposureStartTime);
984}
985
986void CameraBridge::handleExposureMin(const httplib::Request &req, httplib::Response &res)
987{
988 // Transaction IDs extracted in sendResponseValue
989 std::lock_guard<std::mutex> lock(m_Mutex);
990 sendResponseValue(res, req, m_ExposureMin);
991}
992
993void CameraBridge::handleExposureMax(const httplib::Request &req, httplib::Response &res)
994{
995 // Transaction IDs extracted in sendResponseValue
996 std::lock_guard<std::mutex> lock(m_Mutex);
997 sendResponseValue(res, req, m_ExposureMax);
998}
999
1000// Image Data
1001void CameraBridge::handleImageArray(const httplib::Request &req, httplib::Response &res)
1002{
1003 // Transaction IDs extracted in sendResponseValue
1004 std::lock_guard<std::mutex> lock(m_Mutex);
1005
1006 if (!m_ImageReady || m_LastImageData.empty())
1007 {
1008 sendResponseStatus(res, req, false, "No image available");
1009 return;
1010 }
1011
1012 // Format image as JSON array
1013 json imageArray;
1014 formatImageAsJSON(m_LastImageData, m_LastImageWidth, m_LastImageHeight,
1015 m_LastImageBPP, m_LastImageNAxis, imageArray);
1016
1017 // Even though the actual data is UInt16, we must report it as Int32
1018 int elementType = 2; // Int32
1019
1020 // Create response with metadata (Type and Rank are required by NINA)
1021 uint32_t clientID, serverID;
1022 extractTransactionIDs(req, clientID, serverID);
1023
1024 json response =
1025 {
1026 {"Value", imageArray},
1027 {"Type", elementType},
1028 {"Rank", m_LastImageNAxis},
1029 {"ClientTransactionID", clientID},
1030 {"ServerTransactionID", serverID},
1031 {"ErrorNumber", 0},
1032 {"ErrorMessage", ""}
1033 };
1034
1035 res.set_content(response.dump(), "application/json");
1036}
1037
1038void CameraBridge::handleImageArrayVariant(const httplib::Request &req, httplib::Response &res)
1039{
1040 // Transaction IDs extracted in sendResponseValue
1041 std::lock_guard<std::mutex> lock(m_Mutex);
1042
1043 if (!m_ImageReady || m_LastImageData.empty())
1044 {
1045 sendResponseStatus(res, req, false, "No image available");
1046 return;
1047 }
1048
1049 // Format image as JSON array
1050 json imageArray;
1051 formatImageAsJSON(m_LastImageData, m_LastImageWidth, m_LastImageHeight,
1052 m_LastImageBPP, m_LastImageNAxis, imageArray);
1053
1054 // Even though the actual data is UInt16, we must report it as Int32
1055 int elementType = 2; // Int32
1056
1057 // Create response with metadata
1058 uint32_t clientID, serverID;
1059 extractTransactionIDs(req, clientID, serverID);
1060
1061 json response =
1062 {
1063 {"Value", imageArray},
1064 {"Type", elementType},
1065 {"Rank", m_LastImageNAxis},
1066 {"ClientTransactionID", clientID},
1067 {"ServerTransactionID", serverID},
1068 {"ErrorNumber", 0},
1069 {"ErrorMessage", ""}
1070 };
1071
1072 res.set_content(response.dump(), "application/json");
1073}
1074
1075// Guiding
1076void CameraBridge::handleIsPulseGuiding(const httplib::Request &req, httplib::Response &res)
1077{
1078 // Transaction IDs extracted in sendResponseValue
1079 std::lock_guard<std::mutex> lock(m_Mutex);
1080 sendResponseValue(res, req, m_IsPulseGuiding);
1081}
1082
1083void CameraBridge::handlePulseGuide(const httplib::Request &req, httplib::Response &res)
1084{
1085 if (req.method == "PUT")
1086 {
1087 httplib::Params params;
1088 httplib::detail::parse_query_text(req.body, params);
1089
1090 if (params.count("Direction") && params.count("Duration"))
1091 {
1092 int direction = std::stoi(params.find("Direction")->second);
1093 double duration = std::stod(params.find("Duration")->second); // Duration in milliseconds
1094
1095 INDI::Property guideNSProperty = m_Device.getProperty("TELESCOPE_TIMED_GUIDE_NS");
1096 INDI::Property guideWEProperty = m_Device.getProperty("TELESCOPE_TIMED_GUIDE_WE");
1097
1098 if (!guideNSProperty.isValid() && !guideWEProperty.isValid())
1099 {
1100 sendResponseStatus(res, req, false,
1101 "Pulse guiding properties (TELESCOPE_TIMED_GUIDE_NS, TELESCOPE_TIMED_GUIDE_WE) not found.");
1102 return;
1103 }
1104
1105 if (direction == 0) // North (ASCOM Alpaca standard)
1106 {
1107 if (guideNSProperty.isValid() && guideNSProperty.getType() == INDI_NUMBER)
1108 {
1109 INDI::PropertyNumber numberProperty(guideNSProperty);
1110 auto northElement = numberProperty.findWidgetByName("TIMED_GUIDE_N");
1111 if (northElement)
1112 {
1113 northElement->setValue(duration);
1114 requestNewNumber(numberProperty);
1115 sendResponseStatus(res, req, true, "");
1116 return;
1117 }
1118 }
1119 sendResponseStatus(res, req, false, "Failed to pulse guide North.");
1120 }
1121 else if (direction == 1) // South (ASCOM Alpaca standard)
1122 {
1123 if (guideNSProperty.isValid() && guideNSProperty.getType() == INDI_NUMBER)
1124 {
1125 INDI::PropertyNumber numberProperty(guideNSProperty);
1126 auto southElement = numberProperty.findWidgetByName("TIMED_GUIDE_S");
1127 if (southElement)
1128 {
1129 southElement->setValue(duration);
1130 requestNewNumber(numberProperty);
1131 sendResponseStatus(res, req, true, "");
1132 return;
1133 }
1134 }
1135 sendResponseStatus(res, req, false, "Failed to pulse guide South.");
1136 }
1137 else if (direction == 2) // East (ASCOM Alpaca standard)
1138 {
1139 if (guideWEProperty.isValid() && guideWEProperty.getType() == INDI_NUMBER)
1140 {
1141 INDI::PropertyNumber numberProperty(guideWEProperty);
1142 auto eastElement = numberProperty.findWidgetByName("TIMED_GUIDE_E");
1143 if (eastElement)
1144 {
1145 eastElement->setValue(duration);
1146 requestNewNumber(numberProperty);
1147 sendResponseStatus(res, req, true, "");
1148 return;
1149 }
1150 }
1151 sendResponseStatus(res, req, false, "Failed to pulse guide East.");
1152 }
1153 else if (direction == 3) // West (ASCOM Alpaca standard)
1154 {
1155 if (guideWEProperty.isValid() && guideWEProperty.getType() == INDI_NUMBER)
1156 {
1157 INDI::PropertyNumber numberProperty(guideWEProperty);
1158 auto westElement = numberProperty.findWidgetByName("TIMED_GUIDE_W");
1159 if (westElement)
1160 {
1161 westElement->setValue(duration);
1162 requestNewNumber(numberProperty);
1163 sendResponseStatus(res, req, true, "");
1164 return;
1165 }
1166 }
1167 sendResponseStatus(res, req, false, "Failed to pulse guide West.");
1168 }
1169 else
1170 {
1171 sendResponseStatus(res, req, false, "Invalid 'Direction' parameter.");
1172 }
1173 }
1174 else
1175 {
1176 sendResponseStatus(res, req, false, "Missing 'Direction' or 'Duration' parameter in request body");
1177 }
1178 }
1179 else
1180 {
1181 sendResponseStatus(res, req, false, "Method not supported");
1182 }
1183}
1184
1185// Additional Properties
1186void CameraBridge::handleMaxADU(const httplib::Request &req, httplib::Response &res)
1187{
1188 // Transaction IDs extracted in sendResponseValue
1189 std::lock_guard<std::mutex> lock(m_Mutex);
1190 sendResponseValue(res, req, m_MaxADU);
1191}
1192
1193void CameraBridge::handleElectronsPerADU(const httplib::Request &req, httplib::Response &res)
1194{
1195 // Transaction IDs extracted in sendResponseValue
1196 std::lock_guard<std::mutex> lock(m_Mutex);
1197 sendResponseValue(res, req, m_ElectronsPerADU);
1198}
1199
1200void CameraBridge::handleFullWellCapacity(const httplib::Request &req, httplib::Response &res)
1201{
1202 // Transaction IDs extracted in sendResponseValue
1203 std::lock_guard<std::mutex> lock(m_Mutex);
1204 sendResponseValue(res, req, m_FullWellCapacity);
1205}
void handleName(const httplib::Request &req, httplib::Response &res) override
void handleDriverInfo(const httplib::Request &req, httplib::Response &res) override
void handleDriverVersion(const httplib::Request &req, httplib::Response &res) override
void handleConnected(const httplib::Request &req, httplib::Response &res) override
void handleDescription(const httplib::Request &req, httplib::Response &res) override
void handleInterfaceVersion(const httplib::Request &req, httplib::Response &res) override
bool isConnected() 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.
Provides generic container for INDI properties.
bool isValid() const
INDI_PROPERTY_TYPE getType() const
a class to store JSON values
string_t dump(const int indent=-1, const char indent_char=' ', const bool ensure_ascii=false, const error_handler_t error_handler=error_handler_t::strict) const
serialization
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
@ INDI_NUMBER
Definition indidriver.c:57
@ INDI_SWITCH
Definition indidriver.c:58
std::multimap< std::string, std::string > Params
Definition httplib.h:609
basic_json<> json
default specialization
std::string body
Definition httplib.h:761
std::string method
Definition httplib.h:755
void set_content(const char *s, size_t n, const std::string &content_type)
Definition httplib.h:9995