34#ifdef _USE_SYSTEM_JSONLIB
35#include <nlohmann/json.hpp>
41#ifndef INDI_ALPACA_CCD_NO_INSTANCE
62 if (force || m_ConfigHost.empty())
63 ServerAddressTP[0].setText(host);
68 if (force || m_ConfigPort.empty())
69 ServerAddressTP[1].setText(port);
88 m_ConfigHost = configHost;
90 m_ConfigPort = configPort;
95 ServerAddressTP[0].
fill(
"HOST",
"Host", m_ConfigHost.c_str());
96 ServerAddressTP[1].
fill(
"PORT",
"Port", m_ConfigPort.c_str());
100 DeviceNumberNP[0].
fill(
"DEVICE_NUMBER",
"Device Number",
"%.0f", 0, 10, 1, 0);
102 DeviceNumberNP.
load();
105 ConnectionSettingsNP[0].
fill(
"TIMEOUT",
"Timeout (sec)",
"%.0f", 1, 30, 1, 5);
106 ConnectionSettingsNP[1].
fill(
"RETRIES",
"Max Retries",
"%.0f", 1, 10, 1, 3);
107 ConnectionSettingsNP[2].
fill(
"RETRY_DELAY",
"Retry Delay (ms)",
"%.0f", 100, 5000, 100, 1000);
109 ConnectionSettingsNP.
load();
112 GainNP[0].
fill(
"GAIN",
"Gain",
"%.0f", 0, 1000, 1, 0);
116 OffsetNP[0].
fill(
"OFFSET",
"Offset",
"%.0f", 0, 10000, 1, 0);
120 CoolerPowerNP[0].
fill(
"CCD_COOLER_VALUE",
"Power (%)",
"%.0f", 0, 100, 1, 0);
124 FastReadoutSP[FAST_READOUT_ENABLE].
fill(
"FAST_READOUT_ENABLE",
"Enable",
ISS_OFF);
125 FastReadoutSP[FAST_READOUT_DISABLE].
fill(
"FAST_READOUT_DISABLE",
"Disable",
ISS_ON);
130 DeviceInfoTP[0].
fill(
"DESCRIPTION",
"Description",
"");
131 DeviceInfoTP[1].
fill(
"DRIVER_INFO",
"Driver Info",
"");
132 DeviceInfoTP[2].
fill(
"DRIVER_VERSION",
"Driver Version",
"");
133 DeviceInfoTP[3].
fill(
"NAME",
"Name",
"");
137 CameraStateTP[0].
fill(
"STATE",
"State",
"Idle");
160 if (ServerAddressTP[0].isEmpty() || ServerAddressTP[1].isEmpty())
162 LOG_ERROR(
"Server address or port is not set.");
167 httpClient = std::make_unique<httplib::Client>(ServerAddressTP[0].
getText(), std::stoi(ServerAddressTP[1].
getText()));
168 httpClient->set_connection_timeout(
static_cast<int>(ConnectionSettingsNP[0].getValue()));
169 httpClient->set_read_timeout(
static_cast<int>(ConnectionSettingsNP[0].getValue()));
173 bool success = sendAlpacaGET(
"/connected", response);
177 LOG_ERROR(
"Failed to connect to Alpaca camera. Please check server address and port.");
183 success = sendAlpacaPUT(
"/connected", body, response);
187 LOG_ERROR(
"Failed to set connected state on Alpaca camera.");
191 LOG_INFO(
"Successfully connected to Alpaca camera.");
197 success = sendAlpacaGET(
"/gain", response);
201 LOGF_INFO(
"Camera supports Gain: Yes (current value: %.0f)", response[
"Value"].get<double>());
205 LOG_INFO(
"Camera does not support Gain (failed to get gain value).");
210 success = sendAlpacaGET(
"/offset", response);
214 LOGF_INFO(
"Camera supports Offset: Yes (current value: %.0f)", response[
"Value"].get<double>());
218 LOG_INFO(
"Camera does not support Offset (failed to get offset value).");
223 success = sendAlpacaGET(
"/cansetccdtemperature", response);
224 if (success && response[
"Value"].get<bool>())
227 LOG_INFO(
"Camera supports CCD temperature control.");
231 cap &=
~INDI::CCD::CCD_HAS_COOLER;
232 LOG_INFO(
"Camera does not support CCD temperature control.");
236 success = sendAlpacaGET(
"/canpulseguide", response);
239 m_CanPulseGuide = response[
"Value"].
get<
bool>();
243 LOG_INFO(
"Camera supports pulse guiding.");
247 LOG_INFO(
"Camera does not support pulse guiding.");
252 LOG_WARN(
"Failed to query CanPulseGuide, assuming no pulse guide support.");
253 m_CanPulseGuide =
false;
257 success = sendAlpacaGET(
"/canabortexposure", response);
258 m_CanAbortExposure = success && response[
"Value"].
get<
bool>();
260 success = sendAlpacaGET(
"/canstopexposure", response);
263 m_CanStopExposure = response[
"Value"].
get<
bool>();
264 if (m_CanStopExposure)
266 LOG_INFO(
"Camera supports stopping exposure.");
270 LOG_INFO(
"Camera does not support stopping exposure.");
275 LOG_WARN(
"Failed to query CanStopExposure, assuming no stop exposure support.");
276 m_CanStopExposure =
false;
279 if (m_CanAbortExposure || m_CanStopExposure)
282 LOG_INFO(
"Camera supports aborting exposure.");
286 cap &=
~INDI::CCD::CCD_CAN_ABORT;
287 LOG_INFO(
"Camera does not support aborting exposure.");
291 success = sendAlpacaGET(
"/hasshutter", response);
292 if (success && response[
"Value"].get<bool>())
295 LOG_INFO(
"Camera has a mechanical shutter.");
299 cap &=
~INDI::CCD::CCD_HAS_SHUTTER;
300 LOG_INFO(
"Camera does not have a mechanical shutter.");
304 success = sendAlpacaGET(
"/cangetcoolerpower", response);
305 m_HasCoolerPower = success && response[
"Value"].
get<
bool>();
306 LOGF_INFO(
"Camera supports reading cooler power: %s", m_HasCoolerPower ?
"Yes" :
"No");
309 success = sendAlpacaGET(
"/canasymmetricbin", response);
310 m_CanAsymmetricBin = success && response[
"Value"].
get<
bool>();
311 LOGF_INFO(
"Camera supports asymmetric binning: %s", m_CanAsymmetricBin ?
"Yes" :
"No");
314 success = sendAlpacaGET(
"/canfastreadout", response);
315 m_HasFastReadout = success && response[
"Value"].
get<
bool>();
316 LOGF_INFO(
"Camera supports fast readout mode: %s", m_HasFastReadout ?
"Yes" :
"No");
319 success = sendAlpacaGET(
"/sensortype", response);
322 m_SensorType = response[
"Value"].
get<uint8_t>();
323 std::string sensorTypeStr = getSensorTypeString(m_SensorType);
324 LOGF_INFO(
"Camera sensor type: %d (%s)", m_SensorType, sensorTypeStr.c_str());
327 bool isColorCamWithBayer = (m_SensorType >= 2);
329 if (isColorCamWithBayer)
332 LOG_INFO(
"Camera has Bayer color sensor - enabling Bayer capability.");
334 else if (m_SensorType == 1)
336 LOG_INFO(
"Camera has color sensor (non-Bayer).");
340 LOG_INFO(
"Camera has monochrome sensor.");
345 LOG_WARN(
"Failed to query sensor type, assuming monochrome sensor.");
350 std::string sensorTypeStr = getSensorTypeString(m_SensorType);
351 LOGF_INFO(
"Setting up capture formats for sensor type: %d (%s)", m_SensorType, sensorTypeStr.c_str());
358 switch (m_SensorType)
378 LOG_WARN(
"Unknown sensor type, defaulting to mono formats.");
388 mTimerTemperature.
callOnTimeout(std::bind(&AlpacaCCD::temperatureTimerTimeout,
this));
389 mTimerTemperature.
start(TEMP_TIMER_MS);
395void AlpacaCCD::updateCameraCapabilities()
399 LOG_WARN(
"Not connected to Alpaca camera, cannot update camera capabilities.");
407 success = sendAlpacaGET(
"/cameraxsize", response);
408 if (success) m_CameraXSize = response[
"Value"].
get<
int>();
410 success = sendAlpacaGET(
"/cameraysize", response);
411 if (success) m_CameraYSize = response[
"Value"].
get<
int>();
413 success = sendAlpacaGET(
"/pixelsizex", response);
414 if (success) m_PixelSizeX = response[
"Value"].
get<
double>();
416 success = sendAlpacaGET(
"/pixelsizey", response);
417 if (success) m_PixelSizeY = response[
"Value"].
get<
double>();
423 double maxBinX = 1, maxBinY = 1;
424 success = sendAlpacaGET(
"/maxbinx", response);
425 if (success) maxBinX = response[
"Value"].
get<
double>();
427 success = sendAlpacaGET(
"/maxbiny", response);
428 if (success) maxBinY = response[
"Value"].
get<
double>();
434 double expMin = 0.01, expMax = 3600, expStep = 1.0;
435 success = sendAlpacaGET(
"/exposuremin", response);
436 if (success) expMin = response[
"Value"].
get<
double>();
438 success = sendAlpacaGET(
"/exposuremax", response);
439 if (success) expMax = response[
"Value"].
get<
double>();
441 success = sendAlpacaGET(
"/exposureresolution", response);
442 if (success) expStep = response[
"Value"].
get<
double>();
446 success = sendAlpacaGET(
"/description", response);
447 if (success) m_Description = response[
"Value"].
get<std::string>();
449 success = sendAlpacaGET(
"/driverinfo", response);
450 if (success) m_DriverInfo = response[
"Value"].
get<std::string>();
452 success = sendAlpacaGET(
"/driverversion", response);
453 if (success) m_DriverVersion = response[
"Value"].
get<std::string>();
455 success = sendAlpacaGET(
"/name", response);
456 if (success) m_CameraName = response[
"Value"].
get<std::string>();
462 success = sendAlpacaGET(
"/gains", response);
463 if (success && response.
contains(
"Value") && response[
"Value"].is_array() && !response[
"Value"].empty())
466 auto gains = response[
"Value"];
467 GainSP.
resize(gains.size());
468 for (
size_t i = 0; i < gains.size(); ++i)
470 std::string gainName = gains[i].get<std::string>();
472 snprintf(nameBuf,
sizeof(nameBuf),
"GAIN_%zu", i);
473 GainSP[i].
fill(nameBuf, gainName.c_str(),
ISS_OFF);
479 m_GainIsList =
false;
481 success = sendAlpacaGET(
"/gainmin", response);
482 if (success) m_GainMin = response[
"Value"].
get<
double>();
484 success = sendAlpacaGET(
"/gainmax", response);
485 if (success) m_GainMax = response[
"Value"].
get<
double>();
492 success = sendAlpacaGET(
"/offsets", response);
493 if (success && response.
contains(
"Value") && response[
"Value"].is_array() && !response[
"Value"].empty())
495 m_OffsetIsList =
true;
496 auto offsets = response[
"Value"];
497 OffsetSP.
resize(offsets.size());
498 for (
size_t i = 0; i < offsets.size(); ++i)
500 std::string offsetName = offsets[i].get<std::string>();
502 snprintf(nameBuf,
sizeof(nameBuf),
"OFFSET_%zu", i);
503 OffsetSP[i].
fill(nameBuf, offsetName.c_str(),
ISS_OFF);
509 m_OffsetIsList =
false;
513 success = sendAlpacaGET(
"/maxadu", response);
514 if (success) m_MaxADU = response[
"Value"].
get<uint32_t>();
516 success = sendAlpacaGET(
"/electronsperadu", response);
517 if (success) m_ElectronsPerADU = response[
"Value"].
get<
double>();
519 success = sendAlpacaGET(
"/fullwellcapacity", response);
520 if (success) m_FullWellCapacity = response[
"Value"].
get<
double>();
522 if (m_SensorType >= 2)
524 if (sendAlpacaGET(
"/bayeroffsetx", response))
525 m_BayerOffsetX = response[
"Value"].get<uint8_t>();
527 if (sendAlpacaGET(
"/bayeroffsety", response))
528 m_BayerOffsetY = response[
"Value"].get<uint8_t>();
538 mTimerTemperature.
stop();
544 bool success = sendAlpacaPUT(
"/connected", body, response);
548 LOG_ERROR(
"Failed to set disconnected state on Alpaca camera.");
553 LOG_INFO(
"Disconnected from Alpaca camera.");
560 return "Alpaca Camera";
567 ServerAddressTP.
update(texts, names, n);
569 ServerAddressTP.
apply();
584 ConnectionSettingsNP.
update(values, names, n);
586 ConnectionSettingsNP.
apply();
588 LOG_INFO(
"Connection settings updated.");
595 DeviceNumberNP.
update(values, names, n);
598 LOG_INFO(
"Alpaca device number updated.");
603 DeviceNumberNP.
apply();
612 return sendAlpacaPUT(
"/gain", body, response);
622 return sendAlpacaPUT(
"/offset", body, response);
642 bool coolerOn = states[0] ==
ISS_ON;
645 return sendAlpacaPUT(
"/cooleron", body, response);
651 if (ReadoutModeSP.
update(states, names, n) ==
false)
654 ReadoutModeSP.
apply();
664 bool success = sendAlpacaPUT(
"/readoutmode", body, response);
668 m_CurrentReadoutModeIndex = index;
670 ReadoutModeSP.
apply();
671 LOGF_DEBUG(
"Readout mode set to index %d: %s", index, ReadoutModeSP[index].getLabel());
676 LOG_ERROR(
"Failed to set readout mode.");
680 ReadoutModeSP.
apply();
685 if (GainSP.
update(states, names, n) ==
false)
695 if (index != -1 && sendAlpacaPUT(
"/gain", body, response))
698 LOGF_INFO(
"Gain set to %s", GainSP[index].getLabel());
708 else if (m_OffsetIsList && OffsetSP.
isNameMatch(name))
710 if (OffsetSP.
update(states, names, n) ==
false)
720 if (index != -1 && sendAlpacaPUT(
"/offset", body, response))
723 LOGF_INFO(
"Offset set to %s", OffsetSP[index].getLabel());
737 bool enable = states[0] ==
ISS_ON;
740 return sendAlpacaPUT(
"/fastreadout", body, response);
752 updateCameraCapabilities();
764 DeviceInfoTP[0].setText(m_Description.c_str());
765 DeviceInfoTP[1].setText(m_DriverInfo.c_str());
766 DeviceInfoTP[2].setText(m_DriverVersion.c_str());
767 DeviceInfoTP[3].setText(m_CameraName.c_str());
769 DeviceInfoTP.
apply();
774 success = sendAlpacaGET(
"/gain", response);
780 int index = response[
"Value"].
get<
int>();
781 if (index >= 0 &&
static_cast<size_t>(index) < GainSP.
count())
795 GainNP[0].setValue(response[
"Value"].get<double>());
805 GainNP[0].setMin(m_GainMin);
806 GainNP[0].setMax(m_GainMax);
813 success = sendAlpacaGET(
"/offset", response);
819 int index = response[
"Value"].
get<
int>();
820 if (index >= 0 &&
static_cast<size_t>(index) < OffsetSP.
count())
834 OffsetNP[0].setValue(response[
"Value"].get<double>());
848 if (m_HasCoolerPower)
852 if (m_HasFastReadout)
854 success = sendAlpacaGET(
"/fastreadout", response);
857 bool fastReadout = response[
"Value"].
get<
bool>();
858 FastReadoutSP.
reset();
859 FastReadoutSP[fastReadout ? FAST_READOUT_ENABLE : FAST_READOUT_DISABLE].
setState(
ISS_ON);
866 updateReadoutModes();
868 if (m_SensorType >= 2)
870 char offsetX[2], offsetY[2];
871 snprintf(offsetX,
sizeof(offsetX),
"%d", m_BayerOffsetX);
872 snprintf(offsetY,
sizeof(offsetY),
"%d", m_BayerOffsetY);
881 LOG_INFO(
"Alpaca camera is disconnected.");
912 ServerAddressTP.
save(fp);
913 DeviceNumberNP.
save(fp);
914 ConnectionSettingsNP.
save(fp);
915 if (m_HasGain && !m_GainIsList)
919 if (m_HasOffset && !m_OffsetIsList)
931 LOG_ERROR(
"Not connected to Alpaca camera.");
936 mWorker.
start(std::bind(&AlpacaCCD::workerExposure,
this, std::placeholders::_1, duration));
950 bool success =
false;
952 if (m_CanAbortExposure)
953 success = sendAlpacaPUT(
"/abortexposure", {}, response);
954 else if (m_CanStopExposure)
955 success = sendAlpacaPUT(
"/stopexposure", {}, response);
957 LOG_WARN(
"Camera does not support aborting or stopping exposures.");
963 else if (m_CanAbortExposure || m_CanStopExposure)
965 LOG_ERROR(
"Failed to send abort/stop command to Alpaca camera.");
975 LOG_ERROR(
"Not connected to Alpaca camera.");
980 if (std::abs(
temperature - m_CurrentTemperature) < TEMP_THRESHOLD)
988 bool success = sendAlpacaPUT(
"/cooleron", body, response);
1006 bool success = sendAlpacaPUT(
"/setccdtemperature", body, response);
1022 LOG_ERROR(
"Not connected to Alpaca camera.");
1030 body = {{
"StartX", x}};
1031 success = sendAlpacaPUT(
"/startx", body, response);
1032 if (!success)
return false;
1034 body = {{
"StartY", y}};
1035 success = sendAlpacaPUT(
"/starty", body, response);
1036 if (!success)
return false;
1039 success = sendAlpacaPUT(
"/numx", body, response);
1040 if (!success)
return false;
1043 return sendAlpacaPUT(
"/numy", body, response);
1050 LOG_ERROR(
"Not connected to Alpaca camera.");
1054 if (!m_CanAsymmetricBin && hor != ver)
1056 LOG_WARN(
"Camera does not support asymmetric binning, using the horizontal value for both axes.");
1064 body = {{
"BinX", hor}};
1065 success = sendAlpacaPUT(
"/binx", body, response);
1066 if (!success)
return false;
1068 body = {{
"BinY", ver}};
1069 success = sendAlpacaPUT(
"/biny", body, response);
1080 LOG_ERROR(
"Not connected to Alpaca camera.");
1087 LOGF_ERROR(
"Invalid capture format index: %d", index);
1094 uint8_t bpp = format.bitsPerPixel;
1101 LOGF_DEBUG(
"Capture format set to %s (%d-bit)", format.label.c_str(), bpp);
1110 LOG_ERROR(
"Not connected to Alpaca camera.");
1114 if (!m_CanPulseGuide)
1116 LOG_ERROR(
"Camera does not support pulse guiding.");
1122 int alpacaDirection;
1127 alpacaDirection = 0;
1130 alpacaDirection = 1;
1133 alpacaDirection = 2;
1136 alpacaDirection = 3;
1143 body = {{
"Direction", alpacaDirection}, {
"Duration", duration}};
1144 return sendAlpacaPUT(
"/pulseguide", body, response);
1157 m_PulseGuiding =
true;
1170 m_PulseGuiding =
true;
1183 m_PulseGuiding =
true;
1196 m_PulseGuiding =
true;
1203std::string AlpacaCCD::getAlpacaURL(
const std::string& endpoint)
1205 return "/api/v1/camera/" + std::to_string(
static_cast<int>(DeviceNumberNP[0].getValue())) + endpoint;
1211std::string AlpacaCCD::getSensorTypeString(uint8_t sensorType)
1216 return "Monochrome";
1220 return "RGGB Bayer";
1222 return "CMYG Bayer";
1224 return "CMYG2 Bayer";
1226 return "LRGB Truesense";
1235bool AlpacaCCD::sendAlpacaGET(
const std::string& endpoint,
nlohmann::json& response)
1239 LOG_ERROR(
"HTTP client not initialized.");
1243 std::string url = getAlpacaURL(endpoint);
1244 url +=
"?ClientID=" + std::to_string(getpid()) +
"&ClientTransactionID=" + std::to_string(
getTransactionId());
1247 httpClient->set_read_timeout(5, 0);
1248 auto result = httpClient->Get(url.c_str());
1252 LOGF_ERROR(
"HTTP GET failed for %s: %s", endpoint.c_str(),
1257 if (result->status != 200)
1259 LOGF_ERROR(
"HTTP GET %s returned status %d", endpoint.c_str(), result->status);
1265 response = nlohmann::json::parse(result->body);
1268 if (response.
contains(
"ErrorNumber") && response[
"ErrorNumber"].get<
int>() != 0)
1270 LOGF_ERROR(
"Alpaca error in %s: %d - %s", endpoint.c_str(),
1271 response[
"ErrorNumber"].get<
int>(),
1272 response[
"ErrorMessage"].get<std::string>().c_str());
1281 LOGF_ERROR(
"JSON parse error for %s: %s", endpoint.c_str(), e.
what());
1293 LOG_ERROR(
"HTTP client not initialized.");
1297 std::string url = getAlpacaURL(endpoint);
1300 auto result = httpClient->Put(url, form_data,
"application/x-www-form-urlencoded");
1304 LOGF_ERROR(
"HTTP PUT failed for %s: %s", endpoint.c_str(),
1309 if (result->status != 200)
1311 LOGF_ERROR(
"HTTP PUT %s returned status %d", endpoint.c_str(), result->status);
1317 response = nlohmann::json::parse(result->body);
1320 if (response.
contains(
"ErrorNumber") && response[
"ErrorNumber"].get<
int>() != 0)
1328 LOGF_ERROR(
"Alpaca error in %s: %d - %s", endpoint.c_str(),
1329 response[
"ErrorNumber"].get<
int>(),
1330 response[
"ErrorMessage"].get<std::string>().c_str());
1339 LOGF_ERROR(
"JSON parse error for %s: %s", endpoint.c_str(), e.
what());
1347bool AlpacaCCD::alpacaGetImageReady()
1350 if (!sendAlpacaGET(
"/imageready", response))
1354 return response[
"Value"].
get<
bool>();
1360bool AlpacaCCD::alpacaGetImageArrayImageBytes(uint8_t** buffer,
size_t* buffer_size, ImageBytesMetadata* metadata)
1364 LOG_ERROR(
"HTTP client not initialized.");
1371 {
"Accept",
"application/imagebytes"}
1374 std::string url = getAlpacaURL(
"/imagearray");
1375 url +=
"?ClientID=" + std::to_string(getpid()) +
"&ClientTransactionID=" + std::to_string(
getTransactionId());
1377 auto result = httpClient->Get(url.c_str(), headers);
1385 if (result->status != 200)
1387 LOGF_ERROR(
"Image array request returned status %d", result->status);
1392 auto content_type = result->get_header_value(
"Content-Type");
1393 if (content_type.find(
"application/imagebytes") == std::string::npos)
1395 LOGF_DEBUG(
"Server returned Content-Type: %s, falling back to JSON", content_type.c_str());
1400 if (result->body.size() <
sizeof(ImageBytesMetadata))
1402 LOGF_ERROR(
"Response too small for ImageBytes metadata: %zu bytes", result->body.size());
1407 std::memcpy(metadata, result->body.data(),
sizeof(ImageBytesMetadata));
1415 size_t bytes_per_element;
1416 switch (metadata->TransmissionElementType)
1419 bytes_per_element = 1;
1423 bytes_per_element = 2;
1428 bytes_per_element = 4;
1433 bytes_per_element = 8;
1436 LOGF_ERROR(
"Unsupported transmission element type: %d", metadata->TransmissionElementType);
1440 uint32_t planes = (metadata->Rank == 3) ? metadata->Dimension3 : 1;
1441 size_t expected_data_size = metadata->Dimension1 * metadata->Dimension2 * planes * bytes_per_element;
1442 size_t actual_data_size = result->body.size() - metadata->DataStart;
1444 if (actual_data_size != expected_data_size)
1446 LOGF_ERROR(
"Image data size mismatch: expected %zu bytes, got %zu bytes",
1447 expected_data_size, actual_data_size);
1452 *buffer_size = expected_data_size;
1457 LOG_ERROR(
"Failed to allocate image buffer");
1462 std::memcpy(*buffer, result->body.data() + metadata->DataStart, *buffer_size);
1464 LOGF_DEBUG(
"ImageBytes: %dx%dx%d, type %d->%d, %zu bytes",
1465 metadata->Dimension1, metadata->Dimension2, planes,
1466 metadata->ImageElementType, metadata->TransmissionElementType, *buffer_size);
1473 return ++m_ClientTransactionID;
1479 std::string form_data;
1480 for (
auto & [key, value] : request.
items())
1482 if (!form_data.empty()) form_data +=
"&";
1484 if (value.is_string())
1486 form_data += key +
"=" + value.get<std::string>();
1488 else if (value.is_number_integer())
1490 form_data += key +
"=" + std::to_string(value.get<
int>());
1492 else if (value.is_number_float())
1494 form_data += key +
"=" + std::to_string(value.get<
double>());
1496 else if (value.is_boolean())
1498 form_data += key +
"=" + (value.get<
bool>() ?
"true" :
"false");
1503 if (!form_data.empty()) form_data +=
"&";
1504 form_data +=
"ClientID=" + std::to_string(getpid());
1527 if (metadata->
Rank < 2 || metadata->
Rank > 3)
1529 LOGF_ERROR(
"Invalid image rank: %d (must be 2 or 3)", metadata->
Rank);
1551bool AlpacaCCD::alpacaGetImageArrayJSON(ImageMetadata& meta, uint8_t** buffer,
size_t* buffer_size)
1555 if (!sendAlpacaGET(
"/imagearray", response))
1557 LOG_ERROR(
"Failed to get image array via JSON");
1561 if (!response.
contains(
"Value") || !response[
"Value"].is_array())
1563 LOG_ERROR(
"Invalid JSON image array response");
1567 auto imageArray = response[
"Value"];
1570 if (imageArray.empty() || !imageArray[0].is_array())
1572 LOG_ERROR(
"Invalid image array structure");
1576 meta.width = imageArray.size();
1577 meta.height = imageArray[0].size();
1582 if (!imageArray[0][0].is_number())
1584 if (imageArray[0][0].is_array())
1587 meta.planes = imageArray[0][0].size();
1591 LOG_ERROR(
"Unsupported image array element type");
1597 size_t pixel_count = meta.width * meta.height * (meta.planes > 0 ? meta.planes : 1);
1598 *buffer_size = pixel_count *
sizeof(int32_t);
1603 LOG_ERROR(
"Failed to allocate image buffer");
1607 int32_t* int_buffer =
reinterpret_cast<int32_t*
>(*buffer);
1614 for (uint32_t x = 0; x < meta.width; x++)
1616 for (uint32_t y = 0; y < meta.height; y++)
1618 int_buffer[index++] = imageArray[x][y].get<int32_t>();
1625 for (uint32_t x = 0; x < meta.width; x++)
1627 for (uint32_t y = 0; y < meta.height; y++)
1629 for (uint32_t p = 0; p < meta.planes; p++)
1631 int_buffer[index++] = imageArray[x][y][p].get<int32_t>();
1640 meta.max_adu = m_MaxADU;
1641 meta.sensor_type = m_SensorType;
1642 if (meta.sensor_type >= 2)
1648 LOG_INFO(
"Downloaded image via JSON fallback");
1655bool AlpacaCCD::downloadImage()
1657 uint8_t* image_buffer =
nullptr;
1658 size_t buffer_size = 0;
1659 bool success =
false;
1661 LOG_DEBUG(
"Starting image download...");
1664 ImageBytesMetadata imagebytes_meta;
1665 if (alpacaGetImageArrayImageBytes(&image_buffer, &buffer_size, &imagebytes_meta))
1667 LOGF_DEBUG(
"ImageBytes metadata: %dx%dx%d, rank=%d, image_type=%d, transmission_type=%d",
1668 imagebytes_meta.Dimension1, imagebytes_meta.Dimension2,
1669 (imagebytes_meta.Rank == 3) ? imagebytes_meta.Dimension3 : 1,
1671 LOGF_DEBUG(
"Raw buffer size: %zu bytes", buffer_size);
1674 m_CurrentImage.width = imagebytes_meta.Dimension1;
1675 m_CurrentImage.height = imagebytes_meta.Dimension2;
1676 m_CurrentImage.planes = (imagebytes_meta.Rank == 3) ? imagebytes_meta.Dimension3 : 0;
1677 m_CurrentImage.rank = imagebytes_meta.Rank;
1680 switch (imagebytes_meta.TransmissionElementType)
1683 m_CurrentImage.type = 1;
1684 LOG_DEBUG(
"Transmission type: Byte (8-bit) -> Internal type 1");
1688 m_CurrentImage.type = 2;
1689 LOGF_DEBUG(
"Transmission type: %s (16-bit) -> Internal type 2",
1690 (imagebytes_meta.TransmissionElementType == 1) ?
"Int16" :
"UInt16");
1695 m_CurrentImage.type = 3;
1696 LOGF_DEBUG(
"Transmission type: %s (32-bit) -> Internal type 3",
1697 (imagebytes_meta.TransmissionElementType == 2) ?
"Int32" :
1703 m_CurrentImage.type = 4;
1704 LOGF_DEBUG(
"Transmission type: %s (64-bit) -> Internal type 4",
1705 (imagebytes_meta.TransmissionElementType == 3) ?
"Double" :
1709 m_CurrentImage.type = 3;
1710 LOGF_WARN(
"Unknown transmission type %d, defaulting to Internal type 3",
1711 imagebytes_meta.TransmissionElementType);
1716 m_CurrentImage.max_adu = m_MaxADU;
1717 m_CurrentImage.sensor_type = m_SensorType;
1718 if (m_CurrentImage.sensor_type >= 2)
1722 LOGF_DEBUG(
"Bayer offsets: X=%d, Y=%d", m_CurrentImage.bayer_offset_x, m_CurrentImage.bayer_offset_y);
1725 LOGF_DEBUG(
"Final image metadata: %dx%d, planes=%d, rank=%d, type=%d",
1726 m_CurrentImage.width, m_CurrentImage.height, m_CurrentImage.planes,
1727 m_CurrentImage.rank, m_CurrentImage.type);
1732 success = processImageBytesData(image_buffer, buffer_size, imagebytes_meta);
1737 LOG_DEBUG(
"ImageBytes not supported, falling back to JSON ImageArray");
1738 if (alpacaGetImageArrayJSON(m_CurrentImage, &image_buffer, &buffer_size))
1740 LOGF_DEBUG(
"JSON image metadata: %dx%d, planes=%d, rank=%d, type=%d",
1741 m_CurrentImage.width, m_CurrentImage.height, m_CurrentImage.planes,
1742 m_CurrentImage.rank, m_CurrentImage.type);
1743 LOGF_DEBUG(
"JSON buffer size: %zu bytes", buffer_size);
1746 translateCoordinates(image_buffer, m_CurrentImage);
1749 if (m_CurrentImage.planes == 0 || m_CurrentImage.planes == 1)
1751 success = processMonoImage(image_buffer);
1755 success = processColorImage(image_buffer);
1760 LOG_ERROR(
"Failed to download image via both ImageBytes and JSON methods");
1771 m_ExposureInProgress =
false;
1772 LOGF_DEBUG(
"Image download completed: %s", success ?
"SUCCESS" :
"FAILED");
1779void AlpacaCCD::updateReadoutModes()
1783 LOG_WARN(
"Not connected to Alpaca camera, cannot update readout modes.");
1790 if (sendAlpacaGET(
"/readoutmodes", response))
1792 if (response.
contains(
"Value") && response[
"Value"].is_array())
1794 auto readoutModes = response[
"Value"];
1795 if (!readoutModes.empty())
1798 ReadoutModeSP.
resize(readoutModes.size());
1799 for (
size_t i = 0; i < readoutModes.size(); ++i)
1801 std::string modeName = readoutModes[i].get<std::string>();
1803 snprintf(nameBuf,
sizeof(nameBuf),
"MODE_%zu", i);
1804 ReadoutModeSP[i].
fill(nameBuf, modeName.c_str(),
ISS_OFF);
1810 if (sendAlpacaGET(
"/readoutmode", response))
1812 m_CurrentReadoutModeIndex = response[
"Value"].
get<
int>();
1813 if (m_CurrentReadoutModeIndex >= 0 &&
static_cast<size_t>(m_CurrentReadoutModeIndex) < ReadoutModeSP.
count())
1820 LOG_WARN(
"Failed to get current readout mode, defaulting to first mode.");
1822 m_CurrentReadoutModeIndex = 0;
1826 ReadoutModeSP.
apply();
1832 LOG_WARN(
"Failed to get readout modes from Alpaca camera.");
1839void AlpacaCCD::updateStatus()
1846 updateCameraState();
1852 if (!sendAlpacaGET(
"/ispulseguiding", response) || !response[
"Value"].get<bool>())
1854 m_PulseGuiding =
false;
1863void AlpacaCCD::workerExposure(
const std::atomic_bool &isAboutToQuit,
float duration)
1867 LOGF_DEBUG(
"StartExposure->setexp : %.3fs", duration);
1873 {
"Duration", duration},
1877 bool success = sendAlpacaPUT(
"/startexposure", body, response);
1880 LOG_ERROR(
"Failed to start exposure on Alpaca camera.");
1886 LOGF_INFO(
"Taking a %.3f seconds frame...", duration);
1892 float timeLeft = std::max(duration - exposureTimer.
elapsed() / 1000.0, 0.0);
1898 delay = std::max(timeLeft - std::trunc(timeLeft), 0.005f);
1899 timeLeft = std::round(timeLeft);
1912 usleep(delay * 1000 * 1000);
1914 while (exposureTimer.
elapsed() / 1000.0 < duration);
1918 LOG_INFO(
"Exposure time complete, waiting for image...");
1920 if (m_HasLastExposureDuration)
1922 if (sendAlpacaGET(
"/lastexposureduration", response))
1924 double actualDuration = response[
"Value"].
get<
double>();
1925 LOGF_DEBUG(
"Last exposure duration: %.3fs (requested %.3fs)", actualDuration, duration);
1928 m_HasLastExposureDuration =
false;
1932 int maxWaitTime = 30;
1935 while (waitCount < maxWaitTime * 10)
1940 if (alpacaGetImageReady())
1942 LOG_INFO(
"Image ready, downloading...");
1948 if (m_HasPercentCompleted && waitCount % 10 == 0)
1950 if (sendAlpacaGET(
"/percentcompleted", response))
1951 LOGF_DEBUG(
"Percent completed: %d%%", response[
"Value"].get<int>());
1953 m_HasPercentCompleted =
false;
1960 LOG_ERROR(
"Timeout waiting for image to be ready.");
1967void AlpacaCCD::updateCoolerStatus()
1976 success = sendAlpacaGET(
"/cooleron", response);
1979 bool coolerOn = response[
"Value"].
get<
bool>();
1987 LOG_WARN(
"Failed to get cooler status.");
1991 if (m_HasCoolerPower)
1993 success = sendAlpacaGET(
"/coolerpower", response);
1996 CoolerPowerNP[0].setValue(response[
"Value"].get<double>());
1998 CoolerPowerNP.
apply();
2002 LOG_WARN(
"Failed to get cooler power.");
2010void AlpacaCCD::updateCameraState()
2016 if (sendAlpacaGET(
"/camerastate", response))
2018 int state = response[
"Value"].
get<
int>();
2019 std::string previousState = CameraStateTP[0].
getText();
2023 CameraStateTP[0].setText(
"Idle");
2027 CameraStateTP[0].setText(
"Waiting");
2031 CameraStateTP[0].setText(
"Exposing");
2035 CameraStateTP[0].setText(
"Reading");
2039 CameraStateTP[0].setText(
"Downloading");
2043 CameraStateTP[0].setText(
"Error");
2047 CameraStateTP[0].setText(
"Unknown");
2051 if (previousState != CameraStateTP[0].
getText())
2053 CameraStateTP.
apply();
2058 LOG_WARN(
"Failed to get camera state.");
2065bool AlpacaCCD::processImageBytesData(uint8_t* buffer,
size_t buffer_size,
const ImageBytesMetadata& metadata)
2070 LOGF_DEBUG(
"Processing ImageBytes data: %dx%d, rank=%d, transmission_type=%d",
2071 metadata.Dimension1, metadata.Dimension2, metadata.Rank, metadata.TransmissionElementType);
2076 uint32_t width = metadata.Dimension1;
2077 uint32_t height = metadata.Dimension2;
2078 uint32_t planes = (metadata.Rank == 3) ? metadata.Dimension3 : 1;
2079 size_t pixel_count = width * height * planes;
2081 LOGF_DEBUG(
"Image dimensions: %dx%d, planes=%d, pixel_count=%zu", width, height, planes, pixel_count);
2084 size_t bytes_per_element;
2085 switch (metadata.TransmissionElementType)
2088 bytes_per_element = 1;
2092 bytes_per_element = 2;
2097 bytes_per_element = 4;
2102 bytes_per_element = 8;
2105 LOGF_ERROR(
"Unsupported transmission element type: %d", metadata.TransmissionElementType);
2109 LOGF_DEBUG(
"Bytes per element: %zu", bytes_per_element);
2112 size_t expected_size = pixel_count * bytes_per_element;
2113 if (buffer_size != expected_size)
2115 LOGF_ERROR(
"Buffer size mismatch: expected %zu, got %zu", expected_size, buffer_size);
2122 std::vector<uint8_t> src_copy(buffer, buffer + buffer_size);
2125 size_t indi_buffer_size = width * height *
sizeof(uint16_t);
2130 LOG_ERROR(
"Failed to allocate INDI buffer");
2134 LOGF_DEBUG(
"Allocated INDI buffer: %zu bytes (%dx%d * 2 bytes/pixel)",
2135 indi_buffer_size, width, height);
2141 if (metadata.Rank == 2)
2145 convertImageBytesToINDI2D(src_copy.data(), indi_buffer, width, height, metadata.TransmissionElementType);
2147 else if (metadata.Rank == 3)
2151 LOGF_DEBUG(
"Converting 3D image data (averaging %d planes to grayscale)", planes);
2152 convertImageBytesToINDI3D(src_copy.data(), indi_buffer, width, height, planes, metadata.TransmissionElementType);
2156 LOGF_ERROR(
"Unsupported image rank: %d", metadata.Rank);
2164 primary_chip->
setFrame(0, 0, width, height);
2165 primary_chip->
setFrameBuffer(
reinterpret_cast<uint8_t*
>(indi_buffer));
2168 LOGF_DEBUG(
"Set INDI frame buffer: %dx%d, size=%zu bytes", width, height, indi_buffer_size);
2171 LOG_DEBUG(
"Image processing completed successfully");
2178bool AlpacaCCD::processMonoImage(uint8_t* buffer)
2186 uint16_t* indi_buffer =
nullptr;
2187 size_t pixel_count = m_CurrentImage.width * m_CurrentImage.height;
2188 size_t indi_buffer_size = pixel_count *
sizeof(uint16_t);
2192 switch (m_CurrentImage.type)
2195 for (
size_t i = 0; i < pixel_count; i++)
2197 indi_buffer[i] =
static_cast<uint16_t
>(
buffer[i]) << 8;
2202 memcpy(indi_buffer, buffer, pixel_count *
sizeof(uint16_t));
2207 uint32_t* src32 =
reinterpret_cast<uint32_t*
>(
buffer);
2208 for (
size_t i = 0; i < pixel_count; i++)
2210 indi_buffer[i] =
static_cast<uint16_t
>(src32[i] >> 16);
2215 LOGF_ERROR(
"Unsupported Alpaca image type: %d", m_CurrentImage.type);
2221 LOG_ERROR(
"Failed to convert image data");
2226 primary_chip->
setFrame(0, 0, m_CurrentImage.width, m_CurrentImage.height);
2227 primary_chip->
setFrameBuffer(
reinterpret_cast<uint8_t*
>(indi_buffer));
2238bool AlpacaCCD::processColorImage(uint8_t* )
2241 LOG_WARN(
"Color image processing not yet implemented.");
2248void AlpacaCCD::translateCoordinates(uint8_t* buffer,
const ImageMetadata& meta)
2253 size_t bytes_per_pixel = (meta.type == 1) ? 1 : (meta.
type == 2) ? 2 : (meta.
type == 3) ? 4 : 8;
2254 size_t row_size = meta.width * bytes_per_pixel;
2257 uint32_t planes = (meta.planes > 0) ? meta.planes : 1;
2259 for (uint32_t plane = 0; plane < planes; plane++)
2261 uint8_t* plane_start =
buffer + (plane * meta.width * meta.height * bytes_per_pixel);
2264 for (uint32_t y = 0; y < meta.height / 2; y++)
2266 uint8_t* top_row = plane_start + (y * row_size);
2267 uint8_t* bottom_row = plane_start + ((meta.height - 1 - y) * row_size);
2270 for (
size_t i = 0; i < row_size; i++)
2272 uint8_t temp = top_row[i];
2273 top_row[i] = bottom_row[i];
2274 bottom_row[i] = temp;
2283void AlpacaCCD::temperatureTimerTimeout()
2293 success = sendAlpacaGET(
"/ccdtemperature", response);
2296 m_CurrentTemperature = response[
"Value"].
get<
double>();
2300 && std::abs(m_CurrentTemperature - m_TargetTemperature) <= TEMP_THRESHOLD)
2305 LOG_WARN(
"Failed to get temperature from Alpaca camera.");
2310 if (std::abs(m_CurrentTemperature -
TemperatureNP[0].getValue()) > 0.05 ||
2319 updateCoolerStatus();
2325void AlpacaCCD::convertImageBytesToINDI2D(uint8_t* src_buffer, uint16_t* dst_buffer, uint32_t width, uint32_t height,
2326 int32_t transmission_type)
2334 switch (transmission_type)
2338 uint8_t* src = src_buffer;
2339 for (uint32_t x = 0; x < width; x++)
2341 for (uint32_t y = 0; y < height; y++)
2343 dst_buffer[y * width + x] =
static_cast<uint16_t
>(src[x * height + y]) << 8;
2350 int16_t* src =
reinterpret_cast<int16_t*
>(src_buffer);
2351 for (uint32_t x = 0; x < width; x++)
2353 for (uint32_t y = 0; y < height; y++)
2355 int16_t val = src[x * height + y];
2356 dst_buffer[y * width + x] =
static_cast<uint16_t
>(val + 32768);
2363 uint16_t* src =
reinterpret_cast<uint16_t*
>(src_buffer);
2364 for (uint32_t x = 0; x < width; x++)
2366 for (uint32_t y = 0; y < height; y++)
2368 dst_buffer[y * width + x] = src[x * height + y];
2375 int32_t* src =
reinterpret_cast<int32_t*
>(src_buffer);
2376 for (uint32_t x = 0; x < width; x++)
2378 for (uint32_t y = 0; y < height; y++)
2380 int32_t val = src[x * height + y];
2381 dst_buffer[y * width + x] =
static_cast<uint16_t
>(val);
2388 uint32_t* src =
reinterpret_cast<uint32_t*
>(src_buffer);
2389 for (uint32_t x = 0; x < width; x++)
2391 for (uint32_t y = 0; y < height; y++)
2393 uint32_t val = src[x * height + y];
2394 dst_buffer[y * width + x] =
static_cast<uint16_t
>(val);
2401 float* src =
reinterpret_cast<float*
>(src_buffer);
2402 for (uint32_t x = 0; x < width; x++)
2404 for (uint32_t y = 0; y < height; y++)
2406 float val = src[x * height + y];
2407 dst_buffer[y * width + x] =
static_cast<uint16_t
>(val * 65535.0f);
2413 LOGF_ERROR(
"Unsupported transmission type for 2D conversion: %d", transmission_type);
2414 memset(dst_buffer, 0, width * height *
sizeof(uint16_t));
2419void AlpacaCCD::convertImageBytesToINDI3D(uint8_t* src_buffer, uint16_t* dst_buffer, uint32_t width, uint32_t height,
2421 int32_t transmission_type)
2426 switch (transmission_type)
2430 uint8_t* src = src_buffer;
2431 for (uint32_t x = 0; x < width; x++)
2433 for (uint32_t y = 0; y < height; y++)
2436 for (uint32_t p = 0; p < planes; p++)
2438 sum += src[(x * height + y) * planes + p];
2440 uint16_t avg =
static_cast<uint16_t
>((sum / planes)) << 8;
2441 dst_buffer[y * width + x] = avg;
2448 int16_t* src =
reinterpret_cast<int16_t*
>(src_buffer);
2449 for (uint32_t x = 0; x < width; x++)
2451 for (uint32_t y = 0; y < height; y++)
2454 for (uint32_t p = 0; p < planes; p++)
2456 sum += src[(x * height + y) * planes + p];
2458 int16_t avg =
static_cast<int16_t
>(sum / planes);
2459 dst_buffer[y * width + x] =
static_cast<uint16_t
>(avg + 32768);
2466 uint16_t* src =
reinterpret_cast<uint16_t*
>(src_buffer);
2467 for (uint32_t x = 0; x < width; x++)
2469 for (uint32_t y = 0; y < height; y++)
2472 for (uint32_t p = 0; p < planes; p++)
2474 sum += src[(x * height + y) * planes + p];
2476 dst_buffer[y * width + x] =
static_cast<uint16_t
>(sum / planes);
2483 int32_t* src =
reinterpret_cast<int32_t*
>(src_buffer);
2484 for (uint32_t x = 0; x < width; x++)
2486 for (uint32_t y = 0; y < height; y++)
2489 for (uint32_t p = 0; p < planes; p++)
2491 sum += src[(x * height + y) * planes + p];
2493 int32_t avg =
static_cast<int32_t
>(sum / planes);
2494 dst_buffer[y * width + x] =
static_cast<uint16_t
>(avg >> 16);
2501 uint32_t* src =
reinterpret_cast<uint32_t*
>(src_buffer);
2502 for (uint32_t x = 0; x < width; x++)
2504 for (uint32_t y = 0; y < height; y++)
2507 for (uint32_t p = 0; p < planes; p++)
2509 sum += src[(x * height + y) * planes + p];
2511 uint32_t avg =
static_cast<uint32_t
>(sum / planes);
2512 dst_buffer[y * width + x] =
static_cast<uint16_t
>(avg >> 16);
2519 float* src =
reinterpret_cast<float*
>(src_buffer);
2520 for (uint32_t x = 0; x < width; x++)
2522 for (uint32_t y = 0; y < height; y++)
2525 for (uint32_t p = 0; p < planes; p++)
2527 sum += src[(x * height + y) * planes + p];
2529 float avg = sum / planes;
2530 dst_buffer[y * width + x] =
static_cast<uint16_t
>(avg * 65535.0f);
2536 LOGF_ERROR(
"Unsupported transmission type for 3D conversion: %d", transmission_type);
2537 memset(dst_buffer, 0, width * height *
sizeof(uint16_t));
2542void AlpacaCCD::addFITSKeywords(
INDI::CCDChip * targetChip, std::vector<INDI::FITSRecord> &fitsKeywords)
2550 fitsKeywords.push_back({
"MAXADU",
static_cast<int>(m_CurrentImage.max_adu),
"Maximum ADU value"});
2553 switch (m_CurrentImage.sensor_type)
2556 fitsKeywords.push_back({
"COLORTYP",
"MONOCHROME",
"Sensor color type"});
2559 fitsKeywords.push_back({
"COLORTYP",
"COLOR",
"Sensor color type"});
2562 fitsKeywords.push_back({
"COLORTYP",
"RGGB",
"Bayer matrix pattern"});
2563 fitsKeywords.push_back({
"XBAYROFF",
static_cast<int>(m_CurrentImage.bayer_offset_x),
"Bayer X offset"});
2564 fitsKeywords.push_back({
"YBAYROFF",
static_cast<int>(m_CurrentImage.bayer_offset_y),
"Bayer Y offset"});
2567 fitsKeywords.push_back({
"COLORTYP",
"CMYG",
"Bayer matrix pattern"});
2568 fitsKeywords.push_back({
"XBAYROFF",
static_cast<int>(m_CurrentImage.bayer_offset_x),
"Bayer X offset"});
2569 fitsKeywords.push_back({
"YBAYROFF",
static_cast<int>(m_CurrentImage.bayer_offset_y),
"Bayer Y offset"});
2572 LOGF_WARN(
"Unknown sensor type: %d", m_CurrentImage.sensor_type);
2577 fitsKeywords.push_back({
"ALPACA",
"TRUE",
"Image from ASCOM Alpaca camera"});
2578 fitsKeywords.push_back({
"ALPTYPE",
static_cast<int>(m_CurrentImage.type),
"Alpaca data type code"});
2587 fitsKeywords.push_back({
"GAIN", GainSP[index].
getLabel(),
"Camera Gain setting"});
2591 fitsKeywords.push_back({
"GAIN", GainNP[0].getValue(), 3,
"Camera Gain setting"});
2600 fitsKeywords.push_back({
"OFFSET", OffsetSP[index].
getLabel(),
"Camera Offset setting"});
2604 fitsKeywords.push_back({
"OFFSET", OffsetNP[0].getValue(), 3,
"Camera Offset setting"});
2609 if (m_ElectronsPerADU > 0)
2611 fitsKeywords.push_back({
"EGAIN", m_ElectronsPerADU, 3,
"Electrons per ADU"});
2613 if (m_FullWellCapacity > 0)
2615 fitsKeywords.push_back({
"FULLWELL", m_FullWellCapacity, 3,
"Full well capacity in electrons"});
virtual bool SetCaptureFormat(uint8_t index) override
SetCaptureFormat Set Active Capture format.
virtual bool UpdateCCDFrame(int x, int y, int w, int h) override
CCD calls this function when CCD Frame dimension needs to be updated in the hardware....
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual bool AbortExposure() override
Abort ongoing exposure.
const char * getDefaultName() override
virtual IPState GuideNorth(uint32_t ms) override
Guide northward for ms milliseconds.
virtual IPState GuideEast(uint32_t ms) override
Guide easward for ms milliseconds.
void TimerHit() override
Callback function to be called once SetTimer duration elapses.
uint32_t getTransactionId()
void setDefaultServerAddress(const char *host, const char *port, bool force=false)
bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool parseImageBytesMetadata(ImageBytesMetadata *metadata, size_t body_size)
virtual int SetTemperature(double temperature) override
Set CCD temperature.
virtual bool StartExposure(float duration) override
Start exposing primary CCD chip.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual IPState GuideSouth(uint32_t ms) override
Guide southward for ms milliseconds.
bool Connect() override
Connect to the device. INDI::DefaultDevice implementation connects to appropriate connection interfac...
void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
virtual bool UpdateCCDBin(int hor, int ver) override
CCD calls this function when CCD Binning needs to be updated in the hardware. Derived classes should ...
bool Disconnect() override
Disconnect from device.
virtual std::string buildAlpacaFormData(const nlohmann::json &request)
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save configuration items in XML file.
virtual IPState GuideWest(uint32_t ms) override
Guide westward for ms milliseconds.
INDI::PropertyText getText(const char *name) const
bool isDeviceNameMatch(const char *otherName) const
Check that the device name matches the argument.
The CCDChip class provides functionality of a CCD Chip within a CCD.
void setFrameBuffer(uint8_t *buffer)
setFrameBuffer Set raw frame buffer pointer.
int getSubX() const
getSubX Get the starting left coordinates (X) of the frame.
int getSubY() const
getSubY Get the starting top coordinates (Y) of the frame.
void setExposureDuration(double duration)
setExposureDuration Set desired CCD frame exposure duration for next exposure. You must call this fun...
int getSubH() const
getSubH Get the height of the frame
int getBPP() const
getBPP Get CCD Chip depth (bits per pixel).
CCD_FRAME getFrameType() const
isInterlaced
int getBinY() const
getBinY Get vertical binning of the CCD chip.
void setFrame(uint32_t subx, uint32_t suby, uint32_t subw, uint32_t subh)
setFrame Set desired frame resolutoin for an exposure.
void setMinMaxStep(const char *property, const char *element, double min, double max, double step, bool sendToClient=true)
setMinMaxStep for a number property element
int getXRes() const
getXRes Get the horizontal resolution in pixels of the CCD Chip.
int getBinX() const
getBinX Get horizontal binning of the CCD chip.
void setImageExtension(const char *ext)
setImageExtension Set image extension
uint8_t * getFrameBuffer()
getFrameBuffer Get raw frame buffer of the CCD chip.
void setExposureFailed()
setExposureFailed Alert the client that the exposure failed.
void setExposureLeft(double duration)
setExposureLeft Update exposure time left. Inform the client of the new exposure time left value.
int getSubW() const
getSubW Get the width of the frame
void setFrameBufferSize(uint32_t nbuf, bool allocMem=true)
setFrameBufferSize Set desired frame buffer size. The function will allocate memory accordingly....
int getYRes() const
Get the vertical resolution in pixels of the CCD Chip.
void setBPP(uint8_t bpp)
setBPP Set depth of CCD chip.
uint32_t GetCCDCapability() const
GetCCDCapability returns the CCD capabilities.
virtual void addFITSKeywords(CCDChip *targetChip, std::vector< FITSRecord > &fitsKeywords)
Generate FITS keywords that will be added to FIST/XISF file.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
virtual bool ExposureComplete(CCDChip *targetChip)
Uploads target Chip exposed buffer as FITS to the client. Derived classes should class this function ...
virtual void ISGetProperties(const char *dev) override
define the driver's properties to the client. Usually, only a minimum set of properties are defined t...
INDI::PropertyNumber TemperatureNP
TemperatureNP Camera Temperature in Celcius.
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
INDI::PropertyText BayerTP
BayerTP Bayer pattern offset and type.
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual void addCaptureFormat(const CaptureFormat &format)
addCaptureFormat Add a supported camera native capture format (e.g. Mono, Bayer8.....
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save configuration items in XML file.
virtual void GuideComplete(INDI_EQ_AXIS axis) override
GuideComplete Signal guide pulse completion.
void SetCCDCapability(uint32_t cap)
SetCCDCapability Set the CCD capabilities. All fields must be initialized.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
virtual void SetCCDParams(int x, int y, int bpp, float xf, float yf)
Setup CCD parameters for primary CCD. Child classes call this function to update CCD parameters.
std::vector< CaptureFormat > m_CaptureFormats
bool updateProperty(PropertyType &property, ValueType *values, char *names[], int n, std::function< bool()> updater, bool saveConfig=false)
Generic helper function to update an INDI property based on an external operation.
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
uint32_t getCurrentPollingPeriod() const
getCurrentPollingPeriod Return the current polling period.
void addAuxControls()
Add Debug, Simulation, and Configuration options to the driver.
int SetTimer(uint32_t ms)
Set a timer to call the function TimerHit after ms milliseconds.
The ElapsedTimer class provides a fast way to calculate elapsed times.
int64_t elapsed() const
Returns the number of milliseconds since this ElapsedTimer was last started.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
const char * getLabel() const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
int findOnSwitchIndex() const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
INDI::PropertyViewText * getText() const
void quit()
Sets the 'isAboutToClose' flag to 'true' and waits for the end of running function.
void start(const std::function< void(const std::atomic_bool &isAboutToClose)> &functionToRun)
Reserves a thread and uses it to run functionToRun. A running function can check the 'isAboutToClose'...
void callOnTimeout(const std::function< void()> &callback)
void start()
Starts or restarts the timer with the timeout specified in interval.
void stop()
Stops the timer.
a class to store JSON values
iteration_proxy< iterator > items() noexcept
helper to access iterator member functions in range-based for
bool contains(const typename object_t::key_type &key) const
check the existence of an element in a JSON object
auto get() const noexcept(noexcept(std::declval< const basic_json_t & >().template get_impl< ValueType >(detail::priority_tag< 4 > {}))) -> decltype(std::declval< const basic_json_t & >().template get_impl< ValueType >(detail::priority_tag< 4 > {}))
get a (pointer) value (explicit)
general exception of the basic_json class
const char * what() const noexcept override
returns the explanatory string
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
std::unique_ptr< AlpacaCCD > alpaca_ccd(new AlpacaCCD())
int32_t TransmissionElementType
Implementations for common driver routines.
int IUGetConfigText(const char *dev, const char *property, const char *member, char *value, int len)
IUGetConfigText Opens configuration file and reads single text property.
#define LOGF_INFO(fmt,...)
#define LOGF_WARN(fmt,...)
#define LOGF_DEBUG(fmt,...)
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
#define LOGF_ERROR(fmt,...)
std::vector< uint8_t > buffer
Namespace to encapsulate INDI client, drivers, and mediator classes.
std::string to_string(Error error)
std::unordered_multimap< std::string, std::string, detail::case_ignore::hash, detail::case_ignore::equal_to > Headers