Instrument Neutral Distributed Interface INDI 2.2.5
camera_bridge_base.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2025 Jasem Mutlaq. All rights reserved.
3
4 INDI Alpaca Camera Bridge - Base 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 "camera_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#include <fitsio.h>
34
35#ifdef _USE_SYSTEM_JSONLIB
36#include <nlohmann/json.hpp>
37#else
38#include <indijson.hpp>
39#endif
40
42
44 : m_Device(device), m_DeviceNumber(deviceNumber)
45{
46 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_SESSION, "Created camera bridge for device %s with number %d",
47 device.getDeviceName(), deviceNumber);
48
49 // Check if the underlying INDI device supports pulse guiding
51 {
52 m_CanPulseGuide = true;
53 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_SESSION, "Device %s supports pulse guiding.",
54 device.getDeviceName());
55 }
56}
57
59{
60 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_SESSION, "Destroyed camera bridge for device %s",
61 m_Device.getDeviceName());
62}
63
64std::string CameraBridge::getDeviceName() const
65{
66 return m_Device.getDeviceName();
67}
68
69std::string CameraBridge::getUniqueID() const
70{
71 // Generate a unique ID based on the device name
72 return "INDI_" + std::string(m_Device.getDeviceName());
73}
74
75void CameraBridge::handleRequest(const std::string &method, const httplib::Request &req, httplib::Response &res)
76{
77 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_DEBUG, "Handling camera request: %s", method.c_str());
78
79 // Common methods
80 if (method == "connected")
81 handleConnected(req, res);
82 else if (method == "name")
83 handleName(req, res);
84 else if (method == "description")
85 handleDescription(req, res);
86 else if (method == "driverinfo")
87 handleDriverInfo(req, res);
88 else if (method == "driverversion")
89 handleDriverVersion(req, res);
90 else if (method == "interfaceversion")
91 handleInterfaceVersion(req, res);
92 // Camera information properties
93 else if (method == "cameraxsize")
94 handleCameraXSize(req, res);
95 else if (method == "cameraysize")
96 handleCameraYSize(req, res);
97 else if (method == "maxbinx")
98 handleMaxBinX(req, res);
99 else if (method == "maxbiny")
100 handleMaxBinY(req, res);
101 else if (method == "canasymmetricbin")
102 handleCanAsymmetricBin(req, res);
103 else if (method == "pixelsizex")
104 handlePixelSizeX(req, res);
105 else if (method == "pixelsizey")
106 handlePixelSizeY(req, res);
107 else if (method == "binx")
108 handleBinX(req, res);
109 else if (method == "biny")
110 handleBinY(req, res);
111 else if (method == "startx")
112 handleStartX(req, res);
113 else if (method == "starty")
114 handleStartY(req, res);
115 else if (method == "numx")
116 handleNumX(req, res);
117 else if (method == "numy")
118 handleNumY(req, res);
119 // Camera capabilities
120 else if (method == "canabortexposure")
121 handleCanAbortExposure(req, res);
122 else if (method == "canstopexposure")
123 handleCanStopExposure(req, res);
124 else if (method == "canpulseguide")
125 handleCanPulseGuide(req, res);
126 else if (method == "cansetccdtemperature")
127 handleCanSetCCDTemperature(req, res);
128 else if (method == "canfastreadout")
129 handleCanFastReadout(req, res);
130 else if (method == "hasshutter")
131 handleHasShutter(req, res);
132 // Temperature control
133 else if (method == "ccdtemperature")
134 handleCCDTemperature(req, res);
135 else if (method == "cooleron")
136 handleCoolerOn(req, res);
137 else if (method == "coolerpower")
138 handleCoolerPower(req, res);
139 else if (method == "cangetcoolerpower")
140 handleCanGetCoolerPower(req, res);
141 else if (method == "setccdtemperature")
142 handleSetCCDTemperature(req, res);
143 // Gain and offset
144 else if (method == "gain")
145 handleGain(req, res);
146 else if (method == "gainmin")
147 handleGainMin(req, res);
148 else if (method == "gainmax")
149 handleGainMax(req, res);
150 else if (method == "gains")
151 handleGains(req, res);
152 else if (method == "offset")
153 handleOffset(req, res);
154 else if (method == "offsetmin")
155 handleOffsetMin(req, res);
156 else if (method == "offsetmax")
157 handleOffsetMax(req, res);
158 else if (method == "offsets")
159 handleOffsets(req, res);
160 // Readout modes and sensor info
161 else if (method == "readoutmode")
162 handleReadoutMode(req, res);
163 else if (method == "readoutmodes")
164 handleReadoutModes(req, res);
165 else if (method == "sensortype")
166 handleSensorType(req, res);
167 else if (method == "bayeroffsetx")
168 handleBayerOffsetX(req, res);
169 else if (method == "bayeroffsety")
170 handleBayerOffsetY(req, res);
171 else if (method == "sensorname")
172 handleSensorName(req, res);
173 // Exposure control
174 else if (method == "startexposure")
175 handleStartExposure(req, res);
176 else if (method == "stopexposure")
177 handleStopExposure(req, res);
178 else if (method == "abortexposure")
179 handleAbortExposure(req, res);
180 else if (method == "imageready")
181 handleImageReady(req, res);
182 else if (method == "camerastate")
183 handleCameraState(req, res);
184 else if (method == "percentcompleted")
185 handlePercentCompleted(req, res);
186 else if (method == "lastexposureduration")
187 handleLastExposureDuration(req, res);
188 else if (method == "lastexposurestarttime")
189 handleLastExposureStartTime(req, res);
190 else if (method == "exposuremin")
191 handleExposureMin(req, res);
192 else if (method == "exposuremax")
193 handleExposureMax(req, res);
194 // Image data
195 else if (method == "imagearray")
196 handleImageArray(req, res);
197 else if (method == "imagearrayvariant")
198 handleImageArrayVariant(req, res);
199 // Guiding
200 else if (method == "ispulseguiding")
201 handleIsPulseGuiding(req, res);
202 else if (method == "pulseguide")
203 handlePulseGuide(req, res);
204 // Additional properties
205 else if (method == "maxadu")
206 handleMaxADU(req, res);
207 else if (method == "electronsperadu")
208 handleElectronsPerADU(req, res);
209 else if (method == "fullwellcapacity")
210 handleFullWellCapacity(req, res);
211 else
212 {
213 // Unknown method
214 json response =
215 {
216 {"ErrorNumber", 1025},
217 {"ErrorMessage", "Method not implemented: " + method}
218 };
219 res.status = 400;
220 res.set_content(response.dump(), "application/json");
221 }
222}
223
225{
226 std::lock_guard<std::mutex> lock(m_Mutex);
227
228 const char *propName = property.getName();
229 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_DEBUG, "Updating property: %s", propName);
230
231 // Update camera state based on property updates
232 if (property.isNameMatch("CCD_INFO"))
233 {
234 // Update camera information
235 INDI::PropertyNumber numberProperty(property);
236 for (auto &num : numberProperty)
237 {
238 if (num.isNameMatch("CCD_MAX_X"))
239 m_CameraXSize = static_cast<int>(num.getValue());
240 else if (num.isNameMatch("CCD_MAX_Y"))
241 m_CameraYSize = static_cast<int>(num.getValue());
242 else if (num.isNameMatch("CCD_PIXEL_SIZE_X"))
243 m_PixelSizeX = num.getValue();
244 else if (num.isNameMatch("CCD_PIXEL_SIZE_Y"))
245 m_PixelSizeY = num.getValue();
246 else if (num.isNameMatch("CCD_BITSPERPIXEL"))
247 m_BitsPerPixel = static_cast<int>(num.getValue());
248 }
250 "Updated camera info: %dx%d, pixel size: %.2fx%.2f, BPP: %d",
251 m_CameraXSize, m_CameraYSize, m_PixelSizeX, m_PixelSizeY, m_BitsPerPixel);
252 }
253 else if (property.isNameMatch("CCD_BINNING"))
254 {
255 // Update binning
256 INDI::PropertyNumber numberProperty(property);
257 for (auto &num : numberProperty)
258 {
259 if (num.isNameMatch("HOR_BIN"))
260 m_BinX = static_cast<int>(num.getValue());
261 else if (num.isNameMatch("VER_BIN"))
262 m_BinY = static_cast<int>(num.getValue());
263 }
264 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_DEBUG, "Updated binning: %dx%d", m_BinX, m_BinY);
265 }
266 else if (property.isNameMatch("CCD_FRAME"))
267 {
268 // Update frame/ROI
269 INDI::PropertyNumber numberProperty(property);
270 for (auto &num : numberProperty)
271 {
272 if (num.isNameMatch("X"))
273 m_StartX = static_cast<int>(num.getValue());
274 else if (num.isNameMatch("Y"))
275 m_StartY = static_cast<int>(num.getValue());
276 else if (num.isNameMatch("WIDTH"))
277 m_NumX = static_cast<int>(num.getValue());
278 else if (num.isNameMatch("HEIGHT"))
279 m_NumY = static_cast<int>(num.getValue());
280 }
282 "Updated frame: (%d,%d) %dx%d", m_StartX, m_StartY, m_NumX, m_NumY);
283 }
284 else if (property.isNameMatch("CCD_CONTROLS"))
285 {
286 // Update gain and offset from CCD_CONTROLS property
287 INDI::PropertyNumber numberProperty(property);
288 for (auto &num : numberProperty)
289 {
290 if (num.isNameMatch("Gain"))
291 {
292 m_Gain = num.getValue();
293 m_GainMin = num.getMin();
294 m_GainMax = num.getMax();
295 m_HasGain = true;
296 m_UsesCCDControlsForGainOffset = true;
298 "Updated gain from CCD_CONTROLS: %.0f (Min: %.0f, Max: %.0f)",
299 m_Gain, m_GainMin, m_GainMax);
300 }
301 else if (num.isNameMatch("Offset"))
302 {
303 m_Offset = num.getValue();
304 m_OffsetMin = num.getMin();
305 m_OffsetMax = num.getMax();
306 m_HasOffset = true;
307 m_UsesCCDControlsForGainOffset = true;
309 "Updated offset from CCD_CONTROLS: %.0f (Min: %.0f, Max: %.0f)",
310 m_Offset, m_OffsetMin, m_OffsetMax);
311 }
312 }
313 }
314 else if (property.isNameMatch("CCD_TEMPERATURE"))
315 {
316 // Update temperature
317 INDI::PropertyNumber numberProperty(property);
318 m_CCDTemperature = numberProperty[0].getValue();
319 m_CanSetCCDTemperature = (property.getPermission() != IP_RO);
320 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_DEBUG, "Updated temperature: %.2f C, CanSetCCDTemperature: %s",
321 m_CCDTemperature, m_CanSetCCDTemperature ? "true" : "false");
322 }
323 else if (property.isNameMatch("CCD_COOLER"))
324 {
325 // Update cooler state
326 INDI::PropertySwitch switchProperty(property);
327 m_CoolerOn = switchProperty[0].getState() == ISS_ON;
328 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_DEBUG, "Updated cooler state: %s", m_CoolerOn ? "ON" : "OFF");
329 }
330 else if (property.isNameMatch("CCD_COOLER_POWER"))
331 {
332 // Update cooler power
333 INDI::PropertyNumber numberProperty(property);
334 m_CoolerPower = numberProperty[0].getValue();
335 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_DEBUG, "Updated cooler power: %.1f%%", m_CoolerPower);
336 }
337 else if (property.isNameMatch("CCD_GAIN"))
338 {
339 // Update gain
340 INDI::PropertyNumber numberProperty(property);
341 m_Gain = numberProperty[0].getValue();
342 m_GainMin = numberProperty[0].getMin();
343 m_GainMax = numberProperty[0].getMax();
344 m_HasGain = true;
345 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_DEBUG, "Updated gain: %.0f (Min: %.0f, Max: %.0f)",
346 m_Gain, m_GainMin, m_GainMax);
347 }
348 else if (property.isNameMatch("CCD_OFFSET"))
349 {
350 // Update offset
351 INDI::PropertyNumber numberProperty(property);
352 m_Offset = numberProperty[0].getValue();
353 m_OffsetMin = numberProperty[0].getMin();
354 m_OffsetMax = numberProperty[0].getMax();
355 m_HasOffset = true;
356 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_DEBUG, "Updated offset: %.0f (Min: %.0f, Max: %.0f)",
357 m_Offset, m_OffsetMin, m_OffsetMax);
358 }
359 else if (property.isNameMatch("CCD_EXPOSURE"))
360 {
361 // Update exposure state
362 INDI::PropertyNumber numberProperty(property);
363 IPState state = numberProperty.getState();
364
365 if (state == IPS_BUSY)
366 {
367 m_IsExposing = true;
368 m_CameraState = 2; // Exposing
369 m_LastExposureDuration = numberProperty[0].getValue();
370 m_ExposureStartTime = std::chrono::steady_clock::now();
371 m_ImageReady = false;
372 }
373 else if (state == IPS_OK)
374 {
375 m_IsExposing = false;
376 // Don't set to Idle yet - waiting for image download
377 m_CameraState = 4; // Downloading
378 // Keep percent at exposure completion level, will be set to 100% when image arrives
379 }
380 else if (state == IPS_ALERT)
381 {
382 m_IsExposing = false;
383 m_CameraState = 5; // Error
384 }
385
386 m_ExposureMin = numberProperty[0].getMin();
387 m_ExposureMax = numberProperty[0].getMax();
388
390 "Updated exposure state: %s, duration: %.3f, min: %.3f, max: %.3f",
391 m_IsExposing ? "EXPOSING" : "IDLE", m_LastExposureDuration, m_ExposureMin, m_ExposureMax);
392 }
393 else if (property.isNameMatch("CCD1"))
394 {
395 // Image data received
396 INDI::PropertyBlob blobProperty(property);
397 if (blobProperty[0].getBlobLen() > 0)
398 {
399 // Extract image data from FITS
400 const uint8_t* fitsData = static_cast<const uint8_t*>(blobProperty[0].getBlob());
401 size_t fitsSize = blobProperty[0].getBlobLen();
402
403 if (extractImageFromFITS(fitsData, fitsSize, m_LastImageData,
404 m_LastImageWidth, m_LastImageHeight,
405 m_LastImageBPP, m_LastImageNAxis))
406 {
407 m_ImageReady = true;
408 m_CameraState = 0; // Idle
409 m_PercentCompleted = 100.0; // Image fully downloaded and ready
411 "Image ready: %dx%d, %d-bit, %d-axis",
412 m_LastImageWidth, m_LastImageHeight, m_LastImageBPP, m_LastImageNAxis);
413 }
414 else
415 {
416 DEBUGDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to extract image from FITS");
417 m_CameraState = 5; // Error
418 }
419 }
420 }
421 else if (property.isNameMatch("CCD_CFA"))
422 {
423 // Update Bayer info
424 INDI::PropertyText textProperty(property);
425 for (auto &text : textProperty)
426 {
427 if (text.isNameMatch("CFA_OFFSET_X"))
428 m_BayerOffsetX = atoi(text.getText());
429 else if (text.isNameMatch("CFA_OFFSET_Y"))
430 m_BayerOffsetY = atoi(text.getText());
431 else if (text.isNameMatch("CFA_TYPE"))
432 {
433 std::string cfaType = text.getText();
434 if (cfaType == "RGGB")
435 m_SensorType = 2;
436 else if (cfaType == "BGGR")
437 m_SensorType = 2;
438 else if (cfaType == "GRBG")
439 m_SensorType = 2;
440 else if (cfaType == "GBRG")
441 m_SensorType = 2;
442 else if (cfaType == "CMYG")
443 m_SensorType = 3;
444 else
445 m_SensorType = 0; // Monochrome
446 }
447 }
449 "Updated Bayer info: type=%d, offset=(%d,%d)",
450 m_SensorType, m_BayerOffsetX, m_BayerOffsetY);
451 }
452}
453
454// Helper methods for sending commands to INDI server
455void CameraBridge::requestNewNumber(const INDI::PropertyNumber &numberProperty)
456{
457 // Forward the request to the DeviceManager
458 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_DEBUG, "Request to send new number property: %s",
459 numberProperty.getName());
460
461 DeviceManager::getInstance()->sendNewNumber(numberProperty);
462}
463
464void CameraBridge::requestNewSwitch(const INDI::PropertySwitch &switchProperty)
465{
466 // Forward the request to the DeviceManager
467 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_DEBUG, "Request to send new switch property: %s",
468 switchProperty.getName());
469
470 DeviceManager::getInstance()->sendNewSwitch(switchProperty);
471}
472
473// Helper method to extract transaction IDs from request
474void CameraBridge::extractTransactionIDs(const httplib::Request &req, uint32_t &clientID, uint32_t &serverID)
475{
476 clientID = 0;
477 serverID = 0;
478
479 // Try to extract ClientTransactionID from query string (GET requests)
480 if (req.has_param("ClientTransactionID"))
481 {
482 try
483 {
484 clientID = static_cast<uint32_t>(std::stoul(req.get_param_value("ClientTransactionID")));
485 }
486 catch (...)
487 {
488 clientID = 0;
489 }
490 }
491 // Try to extract from request body (PUT requests)
492 else if (!req.body.empty())
493 {
494 httplib::Params params;
495 httplib::detail::parse_query_text(req.body, params);
496
497 if (params.count("ClientTransactionID"))
498 {
499 try
500 {
501 clientID = static_cast<uint32_t>(std::stoul(params.find("ClientTransactionID")->second));
502 }
503 catch (...)
504 {
505 clientID = 0;
506 }
507 }
508 }
509
510 // ServerTransactionID is typically incremented for each response
511 // For simplicity, we'll use 0 for now (can be enhanced later)
512 serverID = 0;
513}
514
515// Helper methods for sending standard JSON responses
516void CameraBridge::sendResponse(httplib::Response &res, bool success, const std::string &errorMessage)
517{
518 // Use default transaction IDs (0) for backward compatibility
519 int clientID = 0;
520 int serverID = 0;
521
522 json response =
523 {
524 {"ClientTransactionID", clientID},
525 {"ServerTransactionID", serverID},
526 {"ErrorNumber", success ? 0 : 1},
527 {"ErrorMessage", success ? "" : errorMessage}
528 };
529 res.set_content(response.dump(), "application/json");
530}
531
532// Implementation of template methods
533template <typename T>
534void CameraBridge::sendResponse(httplib::Response &res, const T &value, bool success,
535 const std::string &errorMessage, int clientID, int serverID)
536{
537 json response =
538 {
539 {"Value", value},
540 {"ClientTransactionID", clientID},
541 {"ServerTransactionID", serverID},
542 {"ErrorNumber", success ? 0 : 1},
543 {"ErrorMessage", success ? "" : errorMessage}
544 };
545 res.set_content(response.dump(), "application/json");
546}
547
548// Implementation with request parameter for transaction ID extraction
549template <typename T>
550void CameraBridge::sendResponseValue(httplib::Response &res, const httplib::Request &req, const T &value,
551 bool success, const std::string &errorMessage)
552{
553 uint32_t clientID, serverID;
554 extractTransactionIDs(req, clientID, serverID);
555 sendResponse(res, value, success, errorMessage, clientID, serverID);
556}
557
558// Implementation with request parameter for transaction ID extraction
559void CameraBridge::sendResponseStatus(httplib::Response &res, const httplib::Request &req, bool success,
560 const std::string &errorMessage)
561{
562 uint32_t clientID, serverID;
563 extractTransactionIDs(req, clientID, serverID);
564
565 json response =
566 {
567 {"ClientTransactionID", clientID},
568 {"ServerTransactionID", serverID},
569 {"ErrorNumber", success ? 0 : 1},
570 {"ErrorMessage", success ? "" : errorMessage}
571 };
572 res.set_content(response.dump(), "application/json");
573}
574
575// Explicit template instantiations for common types
576template void CameraBridge::sendResponse<std::string>(httplib::Response &res, const std::string &value, bool success,
577 const std::string &errorMessage, int clientID, int serverID);
578template void CameraBridge::sendResponse<double>(httplib::Response &res, const double &value, bool success,
579 const std::string &errorMessage, int clientID, int serverID);
580template void CameraBridge::sendResponse<int>(httplib::Response &res, const int &value, bool success,
581 const std::string &errorMessage, int clientID, int serverID);
582template void CameraBridge::sendResponse<bool>(httplib::Response &res, const bool &value, bool success,
583 const std::string &errorMessage, int clientID, int serverID);
584template void CameraBridge::sendResponse<json>(httplib::Response &res, const json &value, bool success,
585 const std::string &errorMessage, int clientID, int serverID);
586
587// Explicit template instantiations for request-based methods
588template void CameraBridge::sendResponseValue<std::string>(httplib::Response &res, const httplib::Request &req,
589 const std::string &value, bool success, const std::string &errorMessage);
590template void CameraBridge::sendResponseValue<double>(httplib::Response &res, const httplib::Request &req,
591 const double &value, bool success, const std::string &errorMessage);
592template void CameraBridge::sendResponseValue<int>(httplib::Response &res, const httplib::Request &req,
593 const int &value, bool success, const std::string &errorMessage);
594template void CameraBridge::sendResponseValue<bool>(httplib::Response &res, const httplib::Request &req,
595 const bool &value, bool success, const std::string &errorMessage);
596template void CameraBridge::sendResponseValue<json>(httplib::Response &res, const httplib::Request &req,
597 const json &value, bool success, const std::string &errorMessage);
598
599// Image processing helper functions
600bool CameraBridge::extractImageFromFITS(const uint8_t* fitsData, size_t fitsSize,
601 std::vector<uint8_t> &rawData, int &width, int &height,
602 int &bitsPerPixel, int &naxis)
603{
604 if (!fitsData || fitsSize == 0)
605 return false;
606
607 fitsfile *fptr = nullptr;
608 int status = 0;
609 long naxes[3] = {0, 0, 0};
610 int bitpix = 0;
611
612 // Open FITS file from memory
613 void *memptr = (void *)fitsData;
614 if (fits_open_memfile(&fptr, "", READONLY, &memptr, &fitsSize, 0, nullptr, &status))
615 {
616 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to open FITS from memory: %d", status);
617 return false;
618 }
619
620 // Get image parameters
621 if (fits_get_img_param(fptr, 3, &bitpix, &naxis, naxes, &status))
622 {
623 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to get FITS image parameters: %d", status);
624 fits_close_file(fptr, &status);
625 return false;
626 }
627
628 width = static_cast<int>(naxes[0]);
629 height = static_cast<int>(naxes[1]);
630 bitsPerPixel = abs(bitpix);
631
632 // Calculate total pixels
633 long totalPixels = naxes[0] * naxes[1];
634 if (naxis == 3)
635 totalPixels *= naxes[2];
636
637 // Allocate buffer for raw data
638 size_t bytesPerPixel = bitsPerPixel / 8;
639 rawData.resize(totalPixels * bytesPerPixel);
640
641 // Read image data
642 long firstPixel = 1;
643 int datatype = 0;
644
645 switch (bitsPerPixel)
646 {
647 case 8:
648 datatype = TBYTE;
649 break;
650 case 16:
651 datatype = TUSHORT;
652 break;
653 case 32:
654 datatype = TULONG;
655 break;
656 default:
657 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Unsupported bits per pixel: %d", bitsPerPixel);
658 fits_close_file(fptr, &status);
659 return false;
660 }
661
662 if (fits_read_img(fptr, datatype, firstPixel, totalPixels, nullptr, rawData.data(), nullptr, &status))
663 {
664 DEBUGFDEVICE(m_Device.getDeviceName(), INDI::Logger::DBG_ERROR, "Failed to read FITS image data: %d", status);
665 fits_close_file(fptr, &status);
666 return false;
667 }
668
669 fits_close_file(fptr, &status);
670
671 // Convert coordinate system from FITS (bottom-left origin) to Alpaca (top-left origin)
672 convertCoordinateSystem(rawData, width, height, bytesPerPixel);
673
675 "Extracted FITS image: %dx%d, %d-bit, %d-axis, %zu bytes",
676 width, height, bitsPerPixel, naxis, rawData.size());
677
678 return true;
679}
680
681void CameraBridge::convertCoordinateSystem(std::vector<uint8_t> &imageData, int width, int height, int bytesPerPixel)
682{
683 // FITS uses bottom-left origin, Alpaca uses top-left origin
684 // We need to flip the image vertically
685
686 size_t rowSize = width * bytesPerPixel;
687 std::vector<uint8_t> tempRow(rowSize);
688
689 for (int y = 0; y < height / 2; y++)
690 {
691 uint8_t* topRow = imageData.data() + (y * rowSize);
692 uint8_t* bottomRow = imageData.data() + ((height - 1 - y) * rowSize);
693
694 // Swap rows
695 std::memcpy(tempRow.data(), topRow, rowSize);
696 std::memcpy(topRow, bottomRow, rowSize);
697 std::memcpy(bottomRow, tempRow.data(), rowSize);
698 }
699}
700
701void CameraBridge::formatImageAsJSON(const std::vector<uint8_t> &rawData, int width, int height,
702 int bitsPerPixel, int naxis, nlohmann::json& response)
703{
704 // Create JSON array structure according to ASCOM Alpaca specification
705 // For 2D arrays: array[x][y] where x is width index, y is height index
706 // For 3D arrays: array[x][y][plane] where plane is color component
707
708 json imageArray = json::array();
709
710 if (naxis == 2)
711 {
712 // 2D monochrome image
713 for (int x = 0; x < width; x++)
714 {
715 json column = json::array();
716 for (int y = 0; y < height; y++)
717 {
718 int pixelIndex = (y * width + x);
719 int32_t pixelValue = 0;
720
721 switch (bitsPerPixel)
722 {
723 case 8:
724 pixelValue = static_cast<int32_t>(rawData[pixelIndex]);
725 break;
726 case 16:
727 pixelValue = static_cast<int32_t>(reinterpret_cast<const uint16_t*>(rawData.data())[pixelIndex]);
728 break;
729 case 32:
730 pixelValue = static_cast<int32_t>(reinterpret_cast<const uint32_t*>(rawData.data())[pixelIndex]);
731 break;
732 }
733
734 column.push_back(pixelValue);
735 }
736 imageArray.push_back(column);
737 }
738 }
739 else if (naxis == 3)
740 {
741 // 3D color image - assume RGB with 3 planes
742 int planes = 3;
743 for (int x = 0; x < width; x++)
744 {
745 json column = json::array();
746 for (int y = 0; y < height; y++)
747 {
748 json pixel = json::array();
749 for (int p = 0; p < planes; p++)
750 {
751 int pixelIndex = (p * width * height) + (y * width + x);
752 int32_t pixelValue = 0;
753
754 switch (bitsPerPixel)
755 {
756 case 8:
757 pixelValue = static_cast<int32_t>(rawData[pixelIndex]);
758 break;
759 case 16:
760 pixelValue = static_cast<int32_t>(reinterpret_cast<const uint16_t*>(rawData.data())[pixelIndex]);
761 break;
762 case 32:
763 pixelValue = static_cast<int32_t>(reinterpret_cast<const uint32_t*>(rawData.data())[pixelIndex]);
764 break;
765 }
766
767 pixel.push_back(pixelValue);
768 }
769 column.push_back(pixel);
770 }
771 imageArray.push_back(column);
772 }
773 }
774
775 response = imageArray;
776}
hid_device * device
void handleName(const httplib::Request &req, httplib::Response &res) override
void handleDriverInfo(const httplib::Request &req, httplib::Response &res) override
std::string getDeviceName() const override
std::string getUniqueID() const override
void handleDriverVersion(const httplib::Request &req, httplib::Response &res) override
CameraBridge(INDI::BaseDevice device, int deviceNumber)
void handleConnected(const httplib::Request &req, httplib::Response &res) override
void handleRequest(const std::string &method, const httplib::Request &req, httplib::Response &res) override
void updateProperty(INDI::Property property) override
void handleDescription(const httplib::Request &req, httplib::Response &res) override
void handleInterfaceVersion(const httplib::Request &req, httplib::Response &res) override
void sendNewSwitch(const INDI::PropertySwitch &switchProperty)
static DeviceManager * getInstance()
void sendNewNumber(const INDI::PropertyNumber &numberProperty)
Class to provide basic INDI device functionality.
Definition basedevice.h:52
const char * getDeviceName() const
uint16_t getDriverInterface() const
getDriverInterface returns ORed values of DRIVER_INTERFACE. It presents the device classes supported ...
const char * getName() const
Provides generic container for INDI properties.
bool isNameMatch(const char *otherName) 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
void push_back(basic_json &&val)
add an object to an array
@ ISS_ON
Definition indiapi.h:150
@ 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_OK
Definition indiapi.h:160
#define DEBUGDEVICE(device, priority, msg)
Definition indilogger.h:60
#define DEBUGFDEVICE(device, priority, msg,...)
Definition indilogger.h:61
std::multimap< std::string, std::string > Params
Definition httplib.h:609
basic_json<> json
default specialization
bool has_param(const std::string &key) const
Definition httplib.h:9824
std::string get_param_value(const std::string &key, size_t id=0) const
Definition httplib.h:9829
std::string body
Definition httplib.h:761
void set_content(const char *s, size_t n, const std::string &content_type)
Definition httplib.h:9995