From 56660d9804fb46e1292191025688646fe892ed20 Mon Sep 17 00:00:00 2001 From: Jasem Mutlaq Date: Fri, 22 Sep 2023 21:13:53 +0300 Subject: [PATCH 1/6] Migrate SVBony to code based on ZWO drivers --- indi-svbony/CMakeLists.txt | 6 +- indi-svbony/svbony_base.cpp | 1341 +++++++++++++++++++++++ indi-svbony/svbony_base.h | 161 +++ indi-svbony/svbony_ccd.cpp | 1936 ++++------------------------------ indi-svbony/svbony_ccd.h | 264 +---- indi-svbony/svbony_helpers.h | 131 +++ 6 files changed, 1876 insertions(+), 1963 deletions(-) create mode 100644 indi-svbony/svbony_base.cpp create mode 100644 indi-svbony/svbony_base.h create mode 100644 indi-svbony/svbony_helpers.h diff --git a/indi-svbony/CMakeLists.txt b/indi-svbony/CMakeLists.txt index 79ba69e07..0264805e0 100644 --- a/indi-svbony/CMakeLists.txt +++ b/indi-svbony/CMakeLists.txt @@ -7,8 +7,8 @@ LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake_modules/") include(GNUInstallDirs) set (SVBONY_VERSION_MAJOR 1) -set (SVBONY_VERSION_MINOR 3) -set (SVBONY_VERSION_PATCH 8) +set (SVBONY_VERSION_MINOR 4) +set (SVBONY_VERSION_PATCH 0) find_package(CFITSIO REQUIRED) find_package(INDI REQUIRED) @@ -22,11 +22,13 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR}) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}) include_directories( ${INDI_INCLUDE_DIR}) include_directories( ${CFITSIO_INCLUDE_DIR}) +include_directories( ${SVBONY_INCLUDE_DIR}) include(CMakeCommon) ############# SVBONY SVBONY CCD ############### set(svbonyccd_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/svbony_base.cpp ${CMAKE_CURRENT_SOURCE_DIR}/svbony_ccd.cpp ) diff --git a/indi-svbony/svbony_base.cpp b/indi-svbony/svbony_base.cpp new file mode 100644 index 000000000..d630f2c77 --- /dev/null +++ b/indi-svbony/svbony_base.cpp @@ -0,0 +1,1341 @@ +/* + SVB Camera Base + + Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com) + Copyright (C) 2018 Leonard Bottleman (leonard@whiteweasel.net) + Copyright (C) 2021 Pawel Soja (kernel32.pl@gmail.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "svbony_base.h" +#include "svbony_helpers.h" + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include + +#define MAX_EXP_RETRIES 3 +#define VERBOSE_EXPOSURE 3 +#define TEMP_TIMER_MS 1000 /* Temperature polling time (ms) */ +#define TEMP_THRESHOLD .25 /* Differential temperature threshold (C)*/ + +#define CONTROL_TAB "Controls" + +static bool warn_roi_height = true; +static bool warn_roi_width = true; + +const char *SVBONYBase::getBayerString() const +{ + return Helpers::toString(mCameraProperty.BayerPattern); +} + +void SVBONYBase::workerStreamVideo(const std::atomic_bool &isAboutToQuit) +{ + SVB_ERROR_CODE ret; + double ExposureRequest = 1.0 / Streamer->getTargetFPS(); + long uSecs = static_cast(ExposureRequest * 950000.0); + + ret = SVBSetControlValue(mCameraInfo.CameraID, SVB_EXPOSURE, uSecs, SVB_FALSE); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to set exposure duration (%s).", Helpers::toString(ret)); + } + + ret = SVBStartVideoCapture(mCameraInfo.CameraID); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to start video capture (%s).", Helpers::toString(ret)); + } + + while (!isAboutToQuit) + { + uint8_t *targetFrame = PrimaryCCD.getFrameBuffer(); + uint32_t totalBytes = PrimaryCCD.getFrameBufferSize(); + int waitMS = static_cast((ExposureRequest * 2000.0) + 500); + + ret = SVBGetVideoData(mCameraInfo.CameraID, targetFrame, totalBytes, waitMS); + if (ret != SVB_SUCCESS) + { + if (ret != SVB_ERROR_TIMEOUT) + { + Streamer->setStream(false); + LOGF_ERROR("Failed to read video data (%s).", Helpers::toString(ret)); + break; + } + + usleep(100); + continue; + } + + if (mCurrentVideoFormat == SVB_IMG_RGB24) + for (uint32_t i = 0; i < totalBytes; i += 3) + std::swap(targetFrame[i], targetFrame[i + 2]); + + Streamer->newFrame(targetFrame, totalBytes); + } + + SVBStopVideoCapture(mCameraInfo.CameraID); +} + +void SVBONYBase::workerExposure(const std::atomic_bool &isAboutToQuit, float duration) +{ + SVB_ERROR_CODE ret; + + ret = SVBStartVideoCapture(mCameraInfo.CameraID); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to start video capture (%s).", Helpers::toString(ret)); + return; + } + + PrimaryCCD.setExposureDuration(duration); + + LOGF_DEBUG("StartExposure->setexp : %.3fs", duration); + ret = SVBSetControlValue(mCameraInfo.CameraID, SVB_EXPOSURE, duration * 1000 * 1000, SVB_FALSE); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to set exposure duration (%s).", Helpers::toString(ret)); + } + + // Try exposure for 3 times + for (int i = 0; i < 3; i++) + { + ret = SVBSendSoftTrigger(mCameraInfo.CameraID); + if (ret == SVB_SUCCESS) + break; + + LOGF_ERROR("Failed to start exposure (%d)", Helpers::toString(ret)); + // Wait 100ms before trying again + usleep(100 * 1000); + } + + INDI::ElapsedTimer exposureTimer; + + if (duration > VERBOSE_EXPOSURE) + LOGF_INFO("Taking a %g seconds frame...", duration); + + auto counter = 0; + SVB_ERROR_CODE status = SVB_ERROR_END; + do + { + float delay = 0.1; + float timeLeft = std::max(duration - exposureTimer.elapsed() / 1000.0, 0.0); + + /* + * Check the status every second until the time left is + * about one second, after which decrease the poll interval + * + * For expsoures with more than a second left try + * to keep the displayed "exposure left" value at + * a full second boundary, which keeps the + * count down neat + */ + if (timeLeft > 1.1) + { + delay = std::max(timeLeft - std::trunc(timeLeft), 0.005f); + timeLeft = std::round(timeLeft); + } + + if (timeLeft > 0) + { + PrimaryCCD.setExposureLeft(timeLeft); + } + else if (isAboutToQuit) + return; + else + { + auto imageBuffer = PrimaryCCD.getFrameBuffer(); + auto size = PrimaryCCD.getFrameBufferSize(); + status = SVBGetVideoData(mCameraInfo.CameraID, imageBuffer, size, 1000); + switch (status) + { + case SVB_SUCCESS: + mExposureRetry = 0; + PrimaryCCD.setExposureLeft(0.0); + if (PrimaryCCD.getExposureDuration() > VERBOSE_EXPOSURE) + LOG_INFO("Exposure done, downloading image..."); + + grabImage(duration); + return; + break; + case SVB_ERROR_TIMEOUT: + counter++; + if (counter > 10) + { + PrimaryCCD.setExposureLeft(0); + PrimaryCCD.setExposureFailed(); + return; + } + break; + default: + PrimaryCCD.setExposureLeft(0); + PrimaryCCD.setExposureFailed(); + return; + } + } + + usleep(delay * 1000 * 1000); + + } + while (status != SVB_SUCCESS); + +} + +/////////////////////////////////////////////////////////////////////// +/// Generic constructor +/////////////////////////////////////////////////////////////////////// +SVBONYBase::SVBONYBase() +{ + setVersion(SVBONY_VERSION_MAJOR, SVBONY_VERSION_MINOR); + mTimerWE.setSingleShot(true); + mTimerNS.setSingleShot(true); +} + +SVBONYBase::~SVBONYBase() +{ + if (isConnected()) + { + Disconnect(); + } +} + +const char *SVBONYBase::getDefaultName() +{ + return "SVBONY CCD"; +} + +void SVBONYBase::ISGetProperties(const char *dev) +{ + INDI::CCD::ISGetProperties(dev); +} + +bool SVBONYBase::initProperties() +{ + INDI::CCD::initProperties(); + + // Add Debug Control. + addDebugControl(); + + CoolerSP[0].fill("COOLER_ON", "ON", ISS_OFF); + CoolerSP[1].fill("COOLER_OFF", "OFF", ISS_ON); + CoolerSP.fill(getDeviceName(), "CCD_COOLER", "Cooler", MAIN_CONTROL_TAB, IP_WO, ISR_1OFMANY, 0, IPS_IDLE); + + CoolerNP[0].fill("CCD_COOLER_VALUE", "Cooling Power (%)", "%+06.2f", 0., 1., .2, 0.0); + CoolerNP.fill(getDeviceName(), "CCD_COOLER_POWER", "Cooling Power", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE); + + ControlNP.fill(getDeviceName(), "CCD_CONTROLS", "Controls", CONTROL_TAB, IP_RW, 60, IPS_IDLE); + ControlSP.fill(getDeviceName(), "CCD_CONTROLS_MODE", "Set Auto", CONTROL_TAB, IP_RW, ISR_NOFMANY, 60, IPS_IDLE); + + FlipSP[FLIP_HORIZONTAL].fill("FLIP_HORIZONTAL", "Horizontal", ISS_OFF); + FlipSP[FLIP_VERTICAL].fill("FLIP_VERTICAL", "Vertical", ISS_OFF); + FlipSP.fill(getDeviceName(), "FLIP", "Flip", CONTROL_TAB, IP_RW, ISR_NOFMANY, 60, IPS_IDLE); + + VideoFormatSP.fill(getDeviceName(), "CCD_VIDEO_FORMAT", "Format", CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); + + IUSaveText(&BayerT[2], getBayerString()); + + ADCDepthNP[0].fill("BITS", "Bits", "%2.0f", 0, 32, 1, 16); + ADCDepthNP.fill(getDeviceName(), "ADC_DEPTH", "ADC Depth", IMAGE_INFO_TAB, IP_RO, 60, IPS_IDLE); + + SDKVersionSP[0].fill("VERSION", "Version", SVBGetSDKVersion()); + SDKVersionSP.fill(getDeviceName(), "SDK", "SDK", INFO_TAB, IP_RO, 60, IPS_IDLE); + + SerialNumberTP[0].fill("SN#", "SN#", mSerialNumber); + SerialNumberTP.fill(getDeviceName(), "Serial Number", "Serial Number", INFO_TAB, IP_RO, 60, IPS_IDLE); + + NicknameTP[0].fill("nickname", "nickname", mNickname); + NicknameTP.fill(getDeviceName(), "NICKNAME", "Nickname", INFO_TAB, IP_RW, 60, IPS_IDLE); + + addAuxControls(); + + return true; +} + +bool SVBONYBase::updateProperties() +{ + INDI::CCD::updateProperties(); + + if (isConnected()) + { + // Let's get parameters now from CCD + setupParams(); + + if (HasCooler()) + { + defineProperty(CoolerNP); + loadConfig(true, CoolerNP.getName()); + defineProperty(CoolerSP); + loadConfig(true, CoolerSP.getName()); + } + // // Even if there is no cooler, we define temperature property as READ ONLY + // else + // { + // TemperatureNP.p = IP_RO; + // defineProperty(&TemperatureNP); + // } + + if (!ControlNP.isEmpty()) + { + defineProperty(ControlNP); + loadConfig(true, ControlNP.getName()); + } + + if (!ControlSP.isEmpty()) + { + defineProperty(ControlSP); + loadConfig(true, ControlSP.getName()); + } + + if (hasFlipControl()) + { + defineProperty(FlipSP); + loadConfig(true, FlipSP.getName()); + } + + if (!VideoFormatSP.isEmpty()) + { + defineProperty(VideoFormatSP); + + // Try to set 16bit RAW by default. + // It can get be overwritten by config value. + // If config fails, we try to set 16 if exists. + if (loadConfig(true, VideoFormatSP.getName()) == false) + { + for (size_t i = 0; i < VideoFormatSP.size(); i++) + { + CaptureFormatSP[i].setState(ISS_OFF); + if (mCameraProperty.SupportedVideoFormat[i] == SVB_IMG_RAW16) + { + setVideoFormat(i); + CaptureFormatSP[i].setState(ISS_ON); + break; + } + } + CaptureFormatSP.apply(); + } + } + + defineProperty(ADCDepthNP); + defineProperty(SDKVersionSP); + if (!mSerialNumber.empty()) + { + defineProperty(SerialNumberTP); + defineProperty(NicknameTP); + } + } + else + { + if (HasCooler()) + { + deleteProperty(CoolerNP.getName()); + deleteProperty(CoolerSP.getName()); + } + + if (!ControlNP.isEmpty()) + deleteProperty(ControlNP.getName()); + + if (!ControlSP.isEmpty()) + deleteProperty(ControlSP.getName()); + + if (hasFlipControl()) + { + deleteProperty(FlipSP.getName()); + } + + if (!VideoFormatSP.isEmpty()) + deleteProperty(VideoFormatSP.getName()); + + deleteProperty(SDKVersionSP.getName()); + if (!mSerialNumber.empty()) + { + deleteProperty(SerialNumberTP.getName()); + deleteProperty(NicknameTP.getName()); + } + deleteProperty(ADCDepthNP.getName()); + } + + return true; +} + +bool SVBONYBase::Connect() +{ + LOGF_DEBUG("Attempting to open %s...", mCameraName.c_str()); + + + auto ret = SVBOpenCamera(mCameraInfo.CameraID); + + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Error Initializing the CCD (%s).", Helpers::toString(ret)); + return false; + } + + ret = SVBGetCameraProperty(mCameraInfo.CameraID, &mCameraProperty); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Error Initializing the CCD (%s).", Helpers::toString(ret)); + return false; + } + + ret = SVBGetCameraPropertyEx(mCameraInfo.CameraID, &mCameraPropertyExtended); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Error Initializing the CCD (%s).", Helpers::toString(ret)); + return false; + } + + ADCDepthNP[0].setValue(mCameraProperty.MaxBitDepth); + + int maxBin = 1; + + for (const auto &supportedBin : mCameraProperty.SupportedBins) + { + if (supportedBin != 0) + maxBin = supportedBin; + else + break; + } + + PrimaryCCD.setMinMaxStep("CCD_EXPOSURE", "CCD_EXPOSURE_VALUE", 0, 3600, 1, false); + PrimaryCCD.setMinMaxStep("CCD_BINNING", "HOR_BIN", 1, maxBin, 1, false); + PrimaryCCD.setMinMaxStep("CCD_BINNING", "VER_BIN", 1, maxBin, 1, false); + + // Log camera capabilities. + LOGF_DEBUG("Camera: %s", mCameraInfo.FriendlyName); + LOGF_DEBUG("ID: %d", mCameraInfo.CameraID); + LOGF_DEBUG("MaxWidth: %d MaxHeight: %d", mCameraProperty.MaxWidth, mCameraProperty.MaxHeight); + LOGF_DEBUG("IsColorCamera: %s", mCameraProperty.IsColorCam ? "True" : "False"); + LOGF_DEBUG("IsCoolerCam: %s", mCameraPropertyExtended.bSupportControlTemp ? "True" : "False"); + LOGF_DEBUG("BitDepth: %d", mCameraProperty.MaxBitDepth); + LOGF_DEBUG("IsTriggerCam: %s", mCameraProperty.IsTriggerCam ? "True" : "False"); + + uint32_t cap = 0; + + if (maxBin > 1) + cap |= CCD_CAN_BIN; + + if (mCameraPropertyExtended.bSupportControlTemp) + cap |= CCD_HAS_COOLER; + + if (mCameraPropertyExtended.bSupportPulseGuide) + cap |= CCD_HAS_ST4_PORT; + + if (mCameraProperty.IsColorCam) + cap |= CCD_HAS_BAYER; + + cap |= CCD_CAN_ABORT; + cap |= CCD_CAN_SUBFRAME; + cap |= CCD_HAS_STREAMING; + +#ifdef HAVE_WEBSOCKET + cap |= CCD_HAS_WEB_SOCKET; +#endif + + SetCCDCapability(cap); + + if (mCameraPropertyExtended.bSupportControlTemp) + { + mTimerTemperature.callOnTimeout(std::bind(&SVBONYBase::temperatureTimerTimeout, this)); + mTimerTemperature.start(TEMP_TIMER_MS); + } + + /* Success! */ + LOG_INFO("Camera is online. Retrieving configuration."); + + return true; +} + +bool SVBONYBase::Disconnect() +{ + // Save all config before shutdown + saveConfig(true); + + LOGF_DEBUG("Closing %s...", mCameraName.c_str()); + + stopGuidePulse(mTimerNS); + stopGuidePulse(mTimerWE); + mTimerTemperature.stop(); + + mWorker.quit(); + Streamer->setStream(false); + + if (isSimulation() == false) + { + SVBStopVideoCapture(mCameraInfo.CameraID); + SVBCloseCamera(mCameraInfo.CameraID); + } + + LOG_INFO("Camera is offline."); + + + setConnected(false, IPS_IDLE); + return true; +} + +void SVBONYBase::setupParams() +{ + int piNumberOfControls = 0; + SVB_ERROR_CODE ret; + + + ret = SVBGetNumOfControls(mCameraInfo.CameraID, &piNumberOfControls); + + if (ret != SVB_SUCCESS) + LOGF_ERROR("Failed to get number of controls (%s).", Helpers::toString(ret)); + + createControls(piNumberOfControls); + + if (HasCooler()) + { + SVB_CONTROL_CAPS pCtrlCaps; + ret = SVBGetControlCaps(mCameraInfo.CameraID, SVB_TARGET_TEMPERATURE, &pCtrlCaps); + if (ret == SVB_SUCCESS) + { + CoolerNP[0].setMinMax(pCtrlCaps.MinValue, pCtrlCaps.MaxValue); + CoolerNP[0].setValue(pCtrlCaps.DefaultValue); + } + } + + // Get Image Format + int x, y, w = 0, h = 0, bin = 0; + ret = SVBGetROIFormat(mCameraInfo.CameraID, &x, &y, &w, &h, &bin); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to get ROI format (%s).", Helpers::toString(ret)); + } + + ret = SVBGetOutputImageType(mCameraInfo.CameraID, &mCurrentVideoFormat); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to get output image type (%s).", Helpers::toString(ret)); + } + + LOGF_DEBUG("CCD ID: %d Width: %d Height: %d Binning: %dx%d Image Type: %d", mCameraInfo.CameraID, w, h, bin, bin, + mCurrentVideoFormat); + + // Get video format and bit depth + int bit_depth = 8; + switch (mCurrentVideoFormat) + { + case SVB_IMG_RAW16: + bit_depth = 16; + break; + + default: + break; + } + + VideoFormatSP.resize(0); + for (const auto &videoFormat : mCameraProperty.SupportedVideoFormat) + { + if (videoFormat == SVB_IMG_END) + break; + + INDI::WidgetSwitch node; + node.fill( + Helpers::toString(videoFormat), + Helpers::toPrettyString(videoFormat), + videoFormat == mCurrentVideoFormat ? ISS_ON : ISS_OFF + ); + + node.setAux(const_cast(&videoFormat)); + VideoFormatSP.push(std::move(node)); + CaptureFormat format = {Helpers::toString(videoFormat), + Helpers::toPrettyString(videoFormat), + static_cast((videoFormat == SVB_IMG_RAW16) ? 16 : 8), + videoFormat == mCurrentVideoFormat + }; + addCaptureFormat(format); + } + + float pixelSize = 2.75; + SVBGetSensorPixelSize(mCameraInfo.CameraID, &pixelSize); + + uint32_t maxWidth = mCameraProperty.MaxWidth; + uint32_t maxHeight = mCameraProperty.MaxHeight; + + SetCCDParams(maxWidth, maxHeight, bit_depth, pixelSize, pixelSize); + + // Let's calculate required buffer + int nbuf = PrimaryCCD.getXRes() * PrimaryCCD.getYRes() * PrimaryCCD.getBPP() / 8; + PrimaryCCD.setFrameBufferSize(nbuf); + + long value = 0; + SVB_BOOL isAuto = SVB_FALSE; + + ret = SVBGetControlValue(mCameraInfo.CameraID, SVB_CURRENT_TEMPERATURE, &value, &isAuto); + if (ret != SVB_SUCCESS) + LOGF_DEBUG("Failed to get temperature (%s).", Helpers::toString(ret)); + else + { + TemperatureN[0].value = value / 10.0; + IDSetNumber(&TemperatureNP, nullptr); + LOGF_INFO("The CCD Temperature is %.3f.", TemperatureN[0].value); + } + + ret = SVBStopVideoCapture(mCameraInfo.CameraID); + if (ret != SVB_SUCCESS) + LOGF_ERROR("Failed to stop video capture (%s).", Helpers::toString(ret)); + + LOGF_DEBUG("setupParams SVBSetROIFormat (%dx%d, bin %d, type %d)", maxWidth, maxHeight, 1, mCurrentVideoFormat); + SVBSetROIFormat(mCameraInfo.CameraID, 0, 0, maxWidth, maxHeight, 1); + + updateRecorderFormat(); + Streamer->setSize(maxWidth, maxHeight); +} + +bool SVBONYBase::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) +{ + SVB_ERROR_CODE ret = SVB_SUCCESS; + + if (dev != nullptr && !strcmp(dev, getDeviceName())) + { + if (ControlNP.isNameMatch(name)) + { + std::vector oldValues; + for (const auto &num : ControlNP) + oldValues.push_back(num.getValue()); + + if (ControlNP.update(values, names, n) == false) + { + ControlNP.setState(IPS_ALERT); + ControlNP.apply(); + return true; + } + + for (size_t i = 0; i < ControlNP.size(); i++) + { + auto numCtrlCap = static_cast(ControlNP[i].getAux()); + + if (std::abs(ControlNP[i].getValue() - oldValues[i]) < 0.01) + continue; + + LOGF_DEBUG("Setting %s=%.2f...", ControlNP[i].getLabel(), ControlNP[i].getValue()); + ret = SVBSetControlValue(mCameraInfo.CameraID, numCtrlCap->ControlType, static_cast(ControlNP[i].getValue()), + SVB_FALSE); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to set %s=%g (%s).", ControlNP[i].getName(), ControlNP[i].getValue(), Helpers::toString(ret)); + for (size_t i = 0; i < ControlNP.size(); i++) + ControlNP[i].setValue(oldValues[i]); + ControlNP.setState(IPS_ALERT); + ControlNP.apply(); + return false; + } + + // If it was set to numCtrlCap->IsAutoSupported value to turn it off + if (numCtrlCap->IsAutoSupported) + { + auto sw = ControlSP.find_if([&numCtrlCap](const INDI::WidgetSwitch & it) -> bool + { + return static_cast(it.getAux())->ControlType == numCtrlCap->ControlType; + }); + + if (sw != ControlSP.end()) + sw->setState(ISS_OFF); + + ControlSP.apply(); + } + } + + ControlNP.setState(IPS_OK); + ControlNP.apply(); + return true; + } + } + + return INDI::CCD::ISNewNumber(dev, name, values, names, n); +} + +bool SVBONYBase::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) +{ + if (dev != nullptr && !strcmp(dev, getDeviceName())) + { + if (ControlSP.isNameMatch(name)) + { + if (ControlSP.update(states, names, n) == false) + { + ControlSP.setState(IPS_ALERT); + ControlSP.apply(); + return true; + } + + for (auto &sw : ControlSP) + { + auto swCtrlCap = static_cast(sw.getAux()); + SVB_BOOL swAuto = (sw.getState() == ISS_ON) ? SVB_TRUE : SVB_FALSE; + + for (auto &num : ControlNP) + { + auto numCtrlCap = static_cast(num.aux0); + + if (swCtrlCap->ControlType != numCtrlCap->ControlType) + continue; + + LOGF_DEBUG("Setting %s=%.2f...", num.label, num.value); + + SVB_ERROR_CODE ret = SVBSetControlValue(mCameraInfo.CameraID, numCtrlCap->ControlType, num.value, swAuto); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to set %s=%g (%s).", num.name, num.value, Helpers::toString(ret)); + ControlNP.setState(IPS_ALERT); + ControlSP.setState(IPS_ALERT); + ControlNP.apply(); + ControlSP.apply(); + return false; + } + numCtrlCap->IsAutoSupported = swAuto; + break; + } + } + + ControlSP.setState(IPS_OK); + ControlSP.apply(); + return true; + } + + if (FlipSP.isNameMatch(name)) + { + if (FlipSP.update(states, names, n) == false) + { + FlipSP.setState(IPS_ALERT); + FlipSP.apply(); + return true; + } + + int flip = 0; + if (FlipSP[FLIP_HORIZONTAL].getState() == ISS_ON) + flip |= SVB_FLIP_HORIZ; + if (FlipSP[FLIP_VERTICAL].getState() == ISS_ON) + flip |= SVB_FLIP_VERT; + + SVB_ERROR_CODE ret = SVBSetControlValue(mCameraInfo.CameraID, SVB_FLIP, flip, SVB_FALSE); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to set SVB_FLIP=%d (%s).", flip, Helpers::toString(ret)); + FlipSP.setState(IPS_ALERT); + FlipSP.apply(); + return false; + } + + FlipSP.setState(IPS_OK); + FlipSP.apply(); + return true; + } + + /* Cooler */ + if (CoolerSP.isNameMatch(name)) + { + if (!CoolerSP.update(states, names, n)) + { + CoolerSP.setState(IPS_ALERT); + CoolerSP.apply(); + return true; + } + + activateCooler(CoolerSP[0].getState() == ISS_ON); + + return true; + } + + if (VideoFormatSP.isNameMatch(name)) + { + if (Streamer->isBusy()) + { + LOG_ERROR("Cannot change format while streaming/recording."); + VideoFormatSP.setState(IPS_ALERT); + VideoFormatSP.apply(); + return true; + } + + const char *targetFormat = IUFindOnSwitchName(states, names, n); + int targetIndex = VideoFormatSP.findWidgetIndexByName(targetFormat); + + if (targetIndex == -1) + { + LOGF_ERROR("Unable to locate format %s.", targetFormat); + VideoFormatSP.setState(IPS_ALERT); + VideoFormatSP.apply(); + return true; + } + + auto result = setVideoFormat(targetIndex); + if (result) + { + VideoFormatSP.reset(); + VideoFormatSP[targetIndex].setState(ISS_ON); + VideoFormatSP.setState(IPS_OK); + VideoFormatSP.apply(); + } + return true; + } + } + + return INDI::CCD::ISNewSwitch(dev, name, states, names, n); +} + +bool SVBONYBase::setVideoFormat(uint8_t index) +{ + if (index == VideoFormatSP.findOnSwitchIndex()) + return true; + + VideoFormatSP.reset(); + VideoFormatSP[index].setState(ISS_ON); + + // When changing video format, reset frame + UpdateCCDFrame(0, 0, PrimaryCCD.getXRes(), PrimaryCCD.getYRes()); + + updateRecorderFormat(); + + VideoFormatSP.setState(IPS_OK); + VideoFormatSP.apply(); + return true; +} + +int SVBONYBase::SetTemperature(double temperature) +{ + // If there difference, for example, is less than 0.1 degrees, let's immediately return OK. + // #PS: how will it warm up? + if (std::abs(temperature - mCurrentTemperature) < TEMP_THRESHOLD) + return 1; + + if (activateCooler(true) == false) + { + LOG_ERROR("Failed to activate cooler."); + return -1; + } + + SVB_ERROR_CODE ret; + + ret = SVBSetControlValue(mCameraInfo.CameraID, SVB_TARGET_TEMPERATURE, std::round(temperature), SVB_TRUE); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to set temperature (%s).", Helpers::toString(ret)); + return -1; + } + + // Otherwise, we set the temperature request and we update the status in TimerHit() function. + mTargetTemperature = temperature; + LOGF_INFO("Setting temperature to %.2f C.", temperature); + return 0; +} + +bool SVBONYBase::activateCooler(bool enable) +{ + SVB_ERROR_CODE ret = SVBSetControlValue(mCameraInfo.CameraID, SVB_COOLER_ENABLE, enable ? SVB_TRUE : SVB_FALSE, SVB_FALSE); + if (ret != SVB_SUCCESS) + { + CoolerSP.setState(IPS_ALERT); + LOGF_ERROR("Failed to activate cooler (%s).", Helpers::toString(ret)); + } + else + { + CoolerSP[0].setState(enable ? ISS_ON : ISS_OFF); + CoolerSP[1].setState(enable ? ISS_OFF : ISS_ON); + CoolerSP.setState(enable ? IPS_BUSY : IPS_IDLE); + } + CoolerSP.apply(); + + return (ret == SVB_SUCCESS); +} + +bool SVBONYBase::StartExposure(float duration) +{ + mExposureRetry = 0; + mWorker.start(std::bind(&SVBONYBase::workerExposure, this, std::placeholders::_1, duration)); + return true; +} + +bool SVBONYBase::AbortExposure() +{ + LOG_DEBUG("Aborting exposure..."); + + mWorker.quit(); + + SVBStopVideoCapture(mCameraInfo.CameraID); + return true; +} + +bool SVBONYBase::StartStreaming() +{ + mWorker.start(std::bind(&SVBONYBase::workerStreamVideo, this, std::placeholders::_1)); + return true; +} + +bool SVBONYBase::StopStreaming() +{ + mWorker.quit(); + return true; +} + +bool SVBONYBase::UpdateCCDFrame(int x, int y, int w, int h) +{ + uint32_t binX = PrimaryCCD.getBinX(); + uint32_t binY = PrimaryCCD.getBinY(); + uint32_t subX = x / binX; + uint32_t subY = y / binY; + uint32_t subW = w / binX; + uint32_t subH = h / binY; + + if (subW > static_cast(PrimaryCCD.getXRes() / binX)) + { + LOGF_INFO("Invalid width request %d", w); + return false; + } + if (subH > static_cast(PrimaryCCD.getYRes() / binY)) + { + LOGF_INFO("Invalid height request %d", h); + return false; + } + + // ZWO rules are this: width%8 = 0, height%2 = 0 + // if this condition is not met, we set it internally to slightly smaller values + + if (warn_roi_width && subW % 8 > 0) + { + LOGF_INFO("Incompatible frame width %dpx. Reducing by %dpx.", subW, subW % 8); + warn_roi_width = false; + } + if (warn_roi_height && subH % 2 > 0) + { + LOGF_INFO("Incompatible frame height %dpx. Reducing by %dpx.", subH, subH % 2); + warn_roi_height = false; + } + + subW -= subW % 8; + subH -= subH % 2; + + LOGF_DEBUG("Frame ROI x:%d y:%d w:%d h:%d", subX, subY, subW, subH); + + SVB_ERROR_CODE ret; + + ret = SVBSetROIFormat(mCameraInfo.CameraID, subX, subY, subW, subH, binX); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to set ROI (%s).", Helpers::toString(ret)); + return false; + } + + mCurrentVideoFormat = getImageType(); + switch (mCurrentVideoFormat) + { + case SVB_IMG_RAW16: + PrimaryCCD.setBPP(16); + break; + + default: + PrimaryCCD.setBPP(8); + break; + } + + SVBSetOutputImageType(mCameraInfo.CameraID, mCurrentVideoFormat); + + // Set UNBINNED coords + PrimaryCCD.setFrame(subX * binX, subY * binY, subW * binX, subH * binY); + + // Total bytes required for image buffer + auto nbuf = (subW * subH * static_cast(PrimaryCCD.getBPP()) / 8) * ((getImageType() == SVB_IMG_RGB24) ? 3 : 1); + + LOGF_DEBUG("Setting frame buffer size to %d bytes.", nbuf); + PrimaryCCD.setFrameBufferSize(nbuf); + + // Always set BINNED size + Streamer->setSize(subW, subH); + + return true; +} + +bool SVBONYBase::UpdateCCDBin(int binx, int biny) +{ + INDI_UNUSED(biny); + + PrimaryCCD.setBin(binx, binx); + + return UpdateCCDFrame(PrimaryCCD.getSubX(), PrimaryCCD.getSubY(), PrimaryCCD.getSubW(), PrimaryCCD.getSubH()); +} + +/* Downloads the image from the CCD. + N.B. No processing is done on the image */ +int SVBONYBase::grabImage(float duration) +{ + SVB_ERROR_CODE ret = SVB_SUCCESS; + + SVB_IMG_TYPE type = getImageType(); + + std::unique_lock guard(ccdBufferLock); + uint8_t *image = PrimaryCCD.getFrameBuffer(); + uint8_t *buffer = image; + + uint16_t subW = PrimaryCCD.getSubW() / PrimaryCCD.getBinX(); + uint16_t subH = PrimaryCCD.getSubH() / PrimaryCCD.getBinY(); + int nChannels = (type == SVB_IMG_RGB24) ? 3 : 1; + size_t nTotalBytes = subW * subH * nChannels * (PrimaryCCD.getBPP() / 8); + + if (type == SVB_IMG_RGB24) + { + buffer = static_cast(malloc(nTotalBytes)); + if (buffer == nullptr) + { + LOGF_ERROR("%s: %d malloc failed (RGB 24).", getDeviceName()); + return -1; + } + } + + ret = SVBGetVideoData(mCameraInfo.CameraID, buffer, nTotalBytes, 1000); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to get data after exposure (%dx%d #%d channels) (%s).", + subW, subH, nChannels, Helpers::toString(ret) + ); + if (type == SVB_IMG_RGB24) + free(buffer); + return -1; + } + + if (type == SVB_IMG_RGB24) + { + uint8_t *dstR = image; + uint8_t *dstG = image + subW * subH; + uint8_t *dstB = image + subW * subH * 2; + + const uint8_t *src = buffer; + const uint8_t *end = buffer + subW * subH * 3; + + while (src != end) + { + *dstB++ = *src++; + *dstG++ = *src++; + *dstR++ = *src++; + } + + free(buffer); + } + guard.unlock(); + + PrimaryCCD.setNAxis(type == SVB_IMG_RGB24 ? 3 : 2); + + // If mono camera or we're sending Luma or RGB, turn off bayering + if (mCameraProperty.IsColorCam == false || type == SVB_IMG_Y8 || type == SVB_IMG_RGB24) + SetCCDCapability(GetCCDCapability() & ~CCD_HAS_BAYER); + else + SetCCDCapability(GetCCDCapability() | CCD_HAS_BAYER); + + if (duration > VERBOSE_EXPOSURE) + LOG_INFO("Download complete."); + + ExposureComplete(&PrimaryCCD); + return 0; +} + +/* The timer call back is used for temperature monitoring */ +void SVBONYBase::temperatureTimerTimeout() +{ + SVB_ERROR_CODE ret; + SVB_BOOL isAuto = SVB_FALSE; + long value = 0; + IPState newState = TemperatureNP.s; + + ret = SVBGetControlValue(mCameraInfo.CameraID, SVB_CURRENT_TEMPERATURE, &value, &isAuto); + + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to get temperature (%s).", Helpers::toString(ret)); + newState = IPS_ALERT; + } + else + { + mCurrentTemperature = value / 10.0; + } + + // Update if there is a change + if ( + std::abs(mCurrentTemperature - TemperatureN[0].value) > 0.05 || + TemperatureNP.s != newState + ) + { + TemperatureNP.s = newState; + TemperatureN[0].value = mCurrentTemperature; + IDSetNumber(&TemperatureNP, nullptr); + } + + if (HasCooler()) + { + ret = SVBGetControlValue(mCameraInfo.CameraID, SVB_COOLER_POWER, &value, &isAuto); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to get perc power information (%s).", Helpers::toString(ret)); + CoolerNP.setState(IPS_ALERT); + } + else + { + CoolerNP[0].setValue(value); + CoolerNP.setState(value > 0 ? IPS_BUSY : IPS_IDLE); + } + CoolerNP.apply(); + } +} + +IPState SVBONYBase::guidePulse(INDI::Timer &timer, float ms, SVB_GUIDE_DIRECTION dir) +{ + timer.stop(); + SVBPulseGuide(mCameraInfo.CameraID, dir, ms); + + LOGF_DEBUG("Starting %s guide for %f ms.", Helpers::toString(dir), ms); + + timer.callOnTimeout([this, dir] + { + LOGF_DEBUG("Stopped %s guide.", Helpers::toString(dir)); + + if (dir == SVB_GUIDE_NORTH || dir == SVB_GUIDE_SOUTH) + GuideComplete(AXIS_DE); + else if (dir == SVB_GUIDE_EAST || dir == SVB_GUIDE_WEST) + GuideComplete(AXIS_RA); + }); + + if (ms < 1) + { + usleep(ms * 1000); + timer.timeout(); + return IPS_OK; + } + + timer.start(ms); + return IPS_BUSY; +} + + +void SVBONYBase::stopGuidePulse(INDI::Timer &timer) +{ + if (timer.isActive()) + { + timer.stop(); + timer.timeout(); + } +} + +IPState SVBONYBase::GuideNorth(uint32_t ms) +{ + return guidePulse(mTimerNS, ms, SVB_GUIDE_NORTH); +} + +IPState SVBONYBase::GuideSouth(uint32_t ms) +{ + return guidePulse(mTimerNS, ms, SVB_GUIDE_SOUTH); +} + +IPState SVBONYBase::GuideEast(uint32_t ms) +{ + return guidePulse(mTimerWE, ms, SVB_GUIDE_EAST); +} + +IPState SVBONYBase::GuideWest(uint32_t ms) +{ + return guidePulse(mTimerWE, ms, SVB_GUIDE_WEST); +} + +void SVBONYBase::createControls(int piNumberOfControls) +{ + ControlNP.resize(0); + ControlSP.resize(0); + + try + { + mControlCaps.resize(piNumberOfControls); + ControlNP.reserve(piNumberOfControls); + ControlSP.reserve(piNumberOfControls); + } + catch(const std::bad_alloc &) + { + IDLog("Failed to allocate memory."); + return; + } + + int i = 0; + for(auto &cap : mControlCaps) + { + SVB_ERROR_CODE ret = SVBGetControlCaps(mCameraInfo.CameraID, i++, &cap); + if (ret != SVB_SUCCESS) + { + LOGF_ERROR("Failed to get control information (%s).", Helpers::toString(ret)); + return; + } + + LOGF_DEBUG("Control #%d: name (%s), Descp (%s), Min (%ld), Max (%ld), Default Value (%ld), IsAutoSupported (%s), " + "isWritale (%s) ", + i, cap.Name, cap.Description, cap.MinValue, cap.MaxValue, + cap.DefaultValue, cap.IsAutoSupported ? "True" : "False", + cap.IsWritable ? "True" : "False"); + + if (cap.IsWritable == SVB_FALSE || cap.ControlType == SVB_TARGET_TEMPERATURE || cap.ControlType == SVB_COOLER_ENABLE + || cap.ControlType == SVB_FLIP) + continue; + + // Update Min/Max exposure as supported by the camera + if (cap.ControlType == SVB_EXPOSURE) + { + double minExp = cap.MinValue / 1000000.0; + double maxExp = cap.MaxValue / 1000000.0; + PrimaryCCD.setMinMaxStep("CCD_EXPOSURE", "CCD_EXPOSURE_VALUE", minExp, maxExp, 1); + continue; + } + + long value = 0; + SVB_BOOL isAuto = SVB_FALSE; + SVBGetControlValue(mCameraInfo.CameraID, cap.ControlType, &value, &isAuto); + + if (cap.IsWritable) + { + LOGF_DEBUG("Adding above control as writable control number %d.", ControlNP.size()); + + // JM 2018-07-04: If Max-Min == 1 then it's boolean value + // So no need to set a custom step value. + double step = 1; + if (cap.MaxValue - cap.MinValue > 1) + step = (cap.MaxValue - cap.MinValue) / 10.0; + + INDI::WidgetNumber node; + node.fill(cap.Name, cap.Name, "%g", cap.MinValue, cap.MaxValue, step, value); + node.setAux(&cap); + ControlNP.push(std::move(node)); + } + + if (cap.IsAutoSupported) + { + LOGF_DEBUG("Adding above control as auto control number %d.", ControlSP.size()); + + char autoName[MAXINDINAME]; + snprintf(autoName, MAXINDINAME, "AUTO_%s", cap.Name); + + INDI::WidgetSwitch node; + node.fill(autoName, cap.Name, isAuto == SVB_TRUE ? ISS_ON : ISS_OFF); + node.setAux(&cap); + ControlSP.push(std::move(node)); + } + } + + // Resize the buffers to free up unused space + ControlNP.shrink_to_fit(); + ControlSP.shrink_to_fit(); +} + +SVB_IMG_TYPE SVBONYBase::getImageType() const +{ + auto sp = VideoFormatSP.findOnSwitch(); + return sp != nullptr ? *static_cast(sp->getAux()) : SVB_IMG_END; +} + +bool SVBONYBase::hasFlipControl() +{ + if (find_if(begin(mControlCaps), end(mControlCaps), [](SVB_CONTROL_CAPS cap) +{ + return cap.ControlType == SVB_FLIP; +}) == end(mControlCaps)) + return false; + else + return true; +} + +void SVBONYBase::updateControls() +{ + for (auto &num : ControlNP) + { + auto numCtrlCap = static_cast(num.getAux()); + long value = 0; + SVB_BOOL isAuto = SVB_FALSE; + SVBGetControlValue(mCameraInfo.CameraID, numCtrlCap->ControlType, &value, &isAuto); + + num.setValue(value); + + auto sw = ControlSP.find_if([&numCtrlCap](const INDI::WidgetSwitch & it) -> bool + { + return static_cast(it.getAux())->ControlType == numCtrlCap->ControlType; + }); + + if (sw != ControlSP.end()) + sw->setState(isAuto == SVB_TRUE ? ISS_ON : ISS_OFF); + } + + ControlNP.apply(); + ControlSP.apply(); +} + +void SVBONYBase::updateRecorderFormat() +{ + mCurrentVideoFormat = getImageType(); + if (mCurrentVideoFormat == SVB_IMG_END) + return; + + Streamer->setPixelFormat( + Helpers::pixelFormat( + mCurrentVideoFormat, + mCameraProperty.BayerPattern, + mCameraProperty.IsColorCam + ), + mCurrentVideoFormat == SVB_IMG_RAW16 ? 16 : 8 + ); +} + +void SVBONYBase::addFITSKeywords(INDI::CCDChip *targetChip, std::vector &fitsKeywords) +{ + INDI::CCD::addFITSKeywords(targetChip, fitsKeywords); + + // e-/ADU + auto np = ControlNP.findWidgetByName("Gain"); + if (np) + { + fitsKeywords.push_back({"GAIN", np->value, 3, "Gain"}); + } + + np = ControlNP.findWidgetByName("Offset"); + if (np) + { + fitsKeywords.push_back({"OFFSET", np->value, 3, "Offset"}); + } +} + +bool SVBONYBase::saveConfigItems(FILE *fp) +{ + INDI::CCD::saveConfigItems(fp); + + if (HasCooler()) + CoolerSP.save(fp); + + if (!ControlNP.isEmpty()) + ControlNP.save(fp); + + if (!ControlSP.isEmpty()) + ControlSP.save(fp); + + if (hasFlipControl()) + FlipSP.save(fp); + + if (!VideoFormatSP.isEmpty()) + VideoFormatSP.save(fp); + + return true; +} + +bool SVBONYBase::SetCaptureFormat(uint8_t index) +{ + return setVideoFormat(index); +} diff --git a/indi-svbony/svbony_base.h b/indi-svbony/svbony_base.h new file mode 100644 index 000000000..73fe73d5e --- /dev/null +++ b/indi-svbony/svbony_base.h @@ -0,0 +1,161 @@ +/* + ASI CCD Driver + + Copyright (C) 2015-2021 Jasem Mutlaq (mutlaqja@ikarustech.com) + Copyright (C) 2018 Leonard Bottleman (leonard@whiteweasel.net) + Copyright (C) 2021 Pawel Soja (kernel32.pl@gmail.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include + +#include "indipropertyswitch.h" +#include "indipropertynumber.h" +#include "indipropertytext.h" +#include "indisinglethreadpool.h" + +#include + +#include +#include + +class SingleWorker; +class SVBONYBase : public INDI::CCD +{ + public: + SVBONYBase(); + ~SVBONYBase() override; + + virtual const char *getDefaultName() override; + + virtual void ISGetProperties(const char *dev) override; + virtual bool initProperties() override; + virtual bool updateProperties() override; + + virtual bool Connect() override; + virtual bool Disconnect() override; + + virtual int SetTemperature(double temperature) override; + virtual bool StartExposure(float duration) override; + virtual bool AbortExposure() override; + + protected: + + virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override; + virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override; + + // Streaming + virtual bool StartStreaming() override; + virtual bool StopStreaming() override; + + virtual bool UpdateCCDFrame(int x, int y, int w, int h) override; + virtual bool UpdateCCDBin(int binx, int biny) override; + + // Guide Port + virtual IPState GuideNorth(uint32_t ms) override; + virtual IPState GuideSouth(uint32_t ms) override; + virtual IPState GuideEast(uint32_t ms) override; + virtual IPState GuideWest(uint32_t ms) override; + + // ASI specific keywords + virtual void addFITSKeywords(INDI::CCDChip *targetChip, std::vector &fitsKeywords) override; + + // Save config + virtual bool saveConfigItems(FILE *fp) override; + + virtual bool SetCaptureFormat(uint8_t index) override; + + /** Get the current Bayer string used */ + const char *getBayerString() const; + + protected: + INDI::SingleThreadPool mWorker; + void workerStreamVideo(const std::atomic_bool &isAboutToQuit); + void workerExposure(const std::atomic_bool &isAboutToQuit, float duration); + + /** Get image from CCD and send it to client */ + int grabImage(float duration); + + protected: + double mTargetTemperature; + double mCurrentTemperature; + INDI::Timer mTimerTemperature; + void temperatureTimerTimeout(); + + /** Timers for NS/WE guiding */ + INDI::Timer mTimerNS; + INDI::Timer mTimerWE; + + IPState guidePulse(INDI::Timer &timer, float ms, SVB_GUIDE_DIRECTION dir); + void stopGuidePulse(INDI::Timer &timer); + + /** Get initial parameters from camera */ + void setupParams(); + + /** Create number and switch controls for camera by querying the API */ + void createControls(int piNumberOfControls); + + /** Update control values from camera */ + void updateControls(); + + /** Return user selected image type */ + SVB_IMG_TYPE getImageType() const; + + /** Update SER recorder video format */ + void updateRecorderFormat(); + + /** Control cooler */ + bool activateCooler(bool enable); + + /** Set Video Format */ + bool setVideoFormat(uint8_t index); + + /** Get if MonoBin is active, thus Bayer is irrelevant */ + bool isMonoBinActive(); + + /** Can the camera flip the image horizontally and vertically */ + bool hasFlipControl(); + + /** Additional Properties to INDI::CCD */ + INDI::PropertyNumber CoolerNP {1}; + INDI::PropertySwitch CoolerSP {2}; + + INDI::PropertyNumber ControlNP {0}; + INDI::PropertySwitch ControlSP {0}; + INDI::PropertySwitch VideoFormatSP {0}; + + INDI::PropertyNumber ADCDepthNP {1}; + INDI::PropertyText SDKVersionSP {1}; + INDI::PropertyText SerialNumberTP {1}; + INDI::PropertyText NicknameTP {1}; + + INDI::PropertySwitch FlipSP {2}; + enum + { + FLIP_HORIZONTAL, + FLIP_VERTICAL + }; + + std::string mCameraName, mCameraID, mSerialNumber, mNickname; + SVB_CAMERA_INFO mCameraInfo; + SVB_CAMERA_PROPERTY mCameraProperty; + SVB_CAMERA_PROPERTY_EX mCameraPropertyExtended; + uint8_t mExposureRetry {0}; + SVB_IMG_TYPE mCurrentVideoFormat; + std::vector mControlCaps; +}; diff --git a/indi-svbony/svbony_ccd.cpp b/indi-svbony/svbony_ccd.cpp index 690d588a2..fe298150a 100644 --- a/indi-svbony/svbony_ccd.cpp +++ b/indi-svbony/svbony_ccd.cpp @@ -1,1812 +1,302 @@ /* - SVBONY CCD - SVBONY CCD Camera driver - Copyright (C) 2020-2021 Blaise-Florentin Collin (thx8411@yahoo.fr) + SVB CCD Driver - Generic CCD skeleton Copyright (C) 2012 Jasem Mutlaq (mutlaqja@ikarustech.com) + Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com) + Copyright (C) 2018 Leonard Bottleman (leonard@whiteweasel.net) + Copyright (C) 2021 Pawel Soja (kernel32.pl@gmail.com) - Multiple device support Copyright (C) 2013 Peter Polakovic (peter.polakovic@cloudmakers.eu) + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "indidevapi.h" -#include "eventloop.h" -#include "stream/streammanager.h" - -#include "libsvbony/SVBCameraSDK.h" - -#include "svbony_ccd.h" - -static class Loader -{ - std::deque> cameras; - public: - Loader() - { - // enumerate cameras - int cameraCount = SVBGetNumOfConnectedCameras(); - if(cameraCount < 1) - { - IDLog("Error, no camera found\n"); - return; - } - - IDLog("Camera(s) found\n"); - - // create SVBONYCCD object for each camera - for(int i = 0; i < cameraCount; i++) - { - cameras.push_back(std::unique_ptr(new SVBONYCCD(i))); - } - } -} loader; - -////////////////////////////////////////////////// -// SVBONY CLASS -// - - -// -SVBONYCCD::SVBONYCCD(int numCamera) -{ - num = numCamera; - - // set driver version - setVersion(SVBONY_VERSION_MAJOR, SVBONY_VERSION_MINOR); - - // Get camera informations - SVB_ERROR_CODE status = SVBGetCameraInfo(&cameraInfo, num); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, can't get camera's informations\n"); - } - - cameraID = cameraInfo.CameraID; - - // Set camera name - snprintf(this->name, 32, "%s %d", cameraInfo.FriendlyName, numCamera); - setDeviceName(this->name); -} - - -// -SVBONYCCD::~SVBONYCCD() -{ -} - - -// -const char *SVBONYCCD::getDefaultName() -{ - return "SVBONY CCD"; -} - - -// -bool SVBONYCCD::initProperties() -{ - // Init parent properties first - INDI::CCD::initProperties(); - - // base capabilities - uint32_t cap = CCD_CAN_ABORT | CCD_CAN_SUBFRAME | CCD_CAN_BIN | CCD_HAS_STREAMING; - - SetCCDCapability(cap); - - addConfigurationControl(); - addDebugControl(); - return true; -} - - -// -void SVBONYCCD::ISGetProperties(const char *dev) -{ - INDI::CCD::ISGetProperties(dev); -} - - -// -bool SVBONYCCD::updateProperties() -{ - INDI::CCD::updateProperties(); - - if (isConnected()) - { - - // cooler enable - defineProperty(&CoolerSP); - defineProperty(&CoolerNP); - - // define controls - defineProperty(&ControlsNP[CCD_GAIN_N]); - defineProperty(&ControlsNP[CCD_CONTRAST_N]); - defineProperty(&ControlsNP[CCD_SHARPNESS_N]); - defineProperty(&ControlsNP[CCD_SATURATION_N]); - defineProperty(&ControlsNP[CCD_WBR_N]); - defineProperty(&ControlsNP[CCD_WBG_N]); - defineProperty(&ControlsNP[CCD_WBB_N]); - defineProperty(&ControlsNP[CCD_GAMMA_N]); - defineProperty(&ControlsNP[CCD_DOFFSET_N]); - - // a switch for automatic correction of dynamic dead pixels - defineProperty(&CorrectDDPSP); - - // define frame rate - defineProperty(&SpeedSP); - - // stretch factor - defineProperty(&StretchSP); - - timerID = SetTimer(getCurrentPollingPeriod()); - } - else - { - rmTimer(timerID); - - // delete cooler enable - deleteProperty(CoolerSP.name); - deleteProperty(CoolerNP.name); - - // delete controls - deleteProperty(ControlsNP[CCD_GAIN_N].name); - deleteProperty(ControlsNP[CCD_CONTRAST_N].name); - deleteProperty(ControlsNP[CCD_SHARPNESS_N].name); - deleteProperty(ControlsNP[CCD_SATURATION_N].name); - deleteProperty(ControlsNP[CCD_WBR_N].name); - deleteProperty(ControlsNP[CCD_WBG_N].name); - deleteProperty(ControlsNP[CCD_WBB_N].name); - deleteProperty(ControlsNP[CCD_GAMMA_N].name); - deleteProperty(ControlsNP[CCD_DOFFSET_N].name); - - // a switch for automatic correction of dynamic dead pixels - deleteProperty(CorrectDDPSP.name); - - // delete frame rate - deleteProperty(SpeedSP.name); - - // stretch factor - deleteProperty(StretchSP.name); - } - - return true; -} - - -// -bool SVBONYCCD::Connect() -{ - // boolean init - streaming = false; - - LOG_INFO("Attempting to find the SVBONY CCD...\n"); - - // init mutex and cond - pthread_mutex_init(&cameraID_mutex, NULL); - pthread_mutex_init(&streaming_mutex, NULL); - pthread_mutex_init(&condMutex, NULL); - pthread_cond_init(&cv, NULL); - - pthread_mutex_lock(&cameraID_mutex); - - // open camera - SVB_ERROR_CODE status = SVBOpenCamera(cameraID); - if (status != SVB_SUCCESS) - { - LOG_ERROR("Error, open camera failed.\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // Get Camera Firmware Version - status = SVBGetCameraFirmwareVersion(cameraID, cameraFirmwareVersion); - if (status == SVB_SUCCESS) - { - LOGF_INFO("Camera Firmware Version:%s", cameraFirmwareVersion); - } - else { - LOG_ERROR("Error, getting Camera Firmware Version failed."); - } - // Get SVBONY Camera SDK Version - SDKVersion = SVBGetSDKVersion(); - LOGF_INFO("SVBONY Camera SDK Version:%s", SDKVersion); - - // wait a bit for the camera to get ready - usleep(0.5 * 1e6); - - // Restore default parameters of SVBONY CCD Camera - status = SVBRestoreDefaultParam(cameraID); - if (status != SVB_SUCCESS) - { - LOGF_ERROR("Error, restore default parameters failed.:%d", status); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // disable suto save param - status = SVBSetAutoSaveParam(cameraID, SVB_FALSE); - if (status != SVB_SUCCESS) - { - LOG_ERROR("Error, disable auto save param failed."); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // get camera properties - status = SVBGetCameraProperty(cameraID, &cameraProperty); - if (status != SVB_SUCCESS) - { - LOG_ERROR("Error, get camera property failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - if (isDebug()) - { - // Output camera properties to log - LOGF_DEBUG("Camera Property:\n WxH= %ldx%ld, Color:%d, BayerPattern:%d, MaxBitDepth:%d, IsTriggerCam:%d", - cameraProperty.MaxWidth, cameraProperty.MaxHeight, - cameraProperty.IsColorCam, - cameraProperty.BayerPattern, - cameraProperty.MaxBitDepth, - cameraProperty.IsTriggerCam); - for (int i = 0; (i < (int)(sizeof(cameraProperty.SupportedBins) / sizeof(cameraProperty.SupportedBins[0]))) && cameraProperty.SupportedBins[i] != 0; i++) - { - LOGF_DEBUG(" Bin %d", cameraProperty.SupportedBins[i]); - } - for (int i = 0; (i < (int)(sizeof(cameraProperty.SupportedVideoFormat) / sizeof(cameraProperty.SupportedVideoFormat[0]))) && cameraProperty.SupportedVideoFormat[i] != SVB_IMG_END; i++) - { - LOGF_DEBUG(" Supported Video Format: %d", cameraProperty.SupportedVideoFormat[i]); - } - } - - // get camera properties ex - status = SVBGetCameraPropertyEx(cameraID, &cameraPropertyEx); - if (status != SVB_SUCCESS) - { - LOG_ERROR("Error, get camera property ex failed"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // output camera properties ex to log - LOGF_DEBUG("Camera Property Ex:\n SupportPulseGuide:%d, SupportControlTemp:%d", - cameraPropertyEx.bSupportPulseGuide, - cameraPropertyEx.bSupportControlTemp); - - // Set CCD Capability - uint32_t cap = GetCCDCapability(); - if (cameraProperty.IsColorCam) - { - cap |= CCD_HAS_BAYER; - } - else - { - cap &= ~CCD_HAS_BAYER; - } - if (cameraPropertyEx.bSupportPulseGuide) - { - cap |= CCD_HAS_ST4_PORT; - } - else - { - cap &= ~CCD_HAS_ST4_PORT; - } - if (cameraPropertyEx.bSupportControlTemp) - { - cap |= CCD_HAS_COOLER; - } - else - { - cap &= ~CCD_HAS_COOLER; - } - SetCCDCapability(cap); - - // get camera pixel size - status = SVBGetSensorPixelSize(cameraID, &pixelSize); - if (status != SVB_SUCCESS) - { - LOG_ERROR("Error, get camera pixel size failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // get num of controls - status = SVBGetNumOfControls(cameraID, &controlsNum); - if (status != SVB_SUCCESS) - { - LOG_ERROR("Error, get camera controls failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // fix for SDK gain error issue - // set exposure time - SVBSetControlValue(cameraID, SVB_EXPOSURE, (long)(1 * 1000000L), SVB_FALSE); - - // read controls and feed UI - for(int i = 0; i < controlsNum; i++) - { - // read control - SVB_CONTROL_CAPS caps; - status = SVBGetControlCaps(cameraID, i, &caps); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, get camera controls caps failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - switch(caps.ControlType) - { - case SVB_EXPOSURE : - // Exposure - minExposure = (double)caps.MinValue / 1000000.0; - maxExposure = (double)caps.MaxValue / 1000000.0; - PrimaryCCD.setMinMaxStep("CCD_EXPOSURE", "CCD_EXPOSURE_VALUE", minExposure, maxExposure, 1, true); - break; - - case SVB_GAIN : - // Gain - IUFillNumber(&ControlsN[CCD_GAIN_N], "GAIN", "Gain", "%.f", caps.MinValue, caps.MaxValue, 10, caps.DefaultValue); - IUFillNumberVector(&ControlsNP[CCD_GAIN_N], &ControlsN[CCD_GAIN_N], 1, getDeviceName(), "CCD_GAIN", "Gain", - MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); - status = SVBSetControlValue(cameraID, SVB_GAIN, caps.DefaultValue, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set gain failed\n"); - } - break; - - case SVB_CONTRAST : - // Contrast - IUFillNumber(&ControlsN[CCD_CONTRAST_N], "CONTRAST", "Contrast", "%.f", caps.MinValue, caps.MaxValue, caps.MaxValue / 10, - caps.DefaultValue); - IUFillNumberVector(&ControlsNP[CCD_CONTRAST_N], &ControlsN[CCD_CONTRAST_N], 1, getDeviceName(), "CCD_CONTRAST", "Contrast", - MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); - status = SVBSetControlValue(cameraID, SVB_CONTRAST, caps.DefaultValue, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set contrast failed\n"); - } - break; - - case SVB_SHARPNESS : - // Sharpness - IUFillNumber(&ControlsN[CCD_SHARPNESS_N], "SHARPNESS", "Sharpness", "%.f", caps.MinValue, caps.MaxValue, caps.MaxValue / 10, - caps.DefaultValue); - IUFillNumberVector(&ControlsNP[CCD_SHARPNESS_N], &ControlsN[CCD_SHARPNESS_N], 1, getDeviceName(), "CCD_SHARPNESS", - "Sharpness", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); - status = SVBSetControlValue(cameraID, SVB_SHARPNESS, caps.DefaultValue, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set sharpness failed\n"); - } - break; - - case SVB_SATURATION : - // Saturation - IUFillNumber(&ControlsN[CCD_SATURATION_N], "SATURATION", "Saturation", "%.f", caps.MinValue, caps.MaxValue, - caps.MaxValue / 10, caps.DefaultValue); - IUFillNumberVector(&ControlsNP[CCD_SATURATION_N], &ControlsN[CCD_SATURATION_N], 1, getDeviceName(), "CCD_SATURATION", - "Saturation", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); - status = SVBSetControlValue(cameraID, SVB_SATURATION, caps.DefaultValue, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set saturation failed\n"); - } - break; - - case SVB_WB_R : - // Red White Balance - IUFillNumber(&ControlsN[CCD_WBR_N], "WBR", "Red White Balance", "%.f", caps.MinValue, caps.MaxValue, caps.MaxValue / 10, - caps.DefaultValue); - IUFillNumberVector(&ControlsNP[CCD_WBR_N], &ControlsN[CCD_WBR_N], 1, getDeviceName(), "CCD_WBR", "Red White Balance", - MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); - status = SVBSetControlValue(cameraID, SVB_WB_R, caps.DefaultValue, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set red WB failed\n"); - } - break; - - case SVB_WB_G : - // Green White Balance - IUFillNumber(&ControlsN[CCD_WBG_N], "WBG", "Green White Balance", "%.f", caps.MinValue, caps.MaxValue, caps.MaxValue / 10, - caps.DefaultValue); - IUFillNumberVector(&ControlsNP[CCD_WBG_N], &ControlsN[CCD_WBG_N], 1, getDeviceName(), "CCD_WBG", "Green White Balance", - MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); - status = SVBSetControlValue(cameraID, SVB_WB_G, caps.DefaultValue, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set green WB failed\n"); - } - break; - - case SVB_WB_B : - // Blue White Balance - IUFillNumber(&ControlsN[CCD_WBB_N], "WBB", "Blue White Balance", "%.f", caps.MinValue, caps.MaxValue, caps.MaxValue / 10, - caps.DefaultValue); - IUFillNumberVector(&ControlsNP[CCD_WBB_N], &ControlsN[CCD_WBB_N], 1, getDeviceName(), "CCD_WBB", "Blue White Balance", - MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); - status = SVBSetControlValue(cameraID, SVB_WB_B, caps.DefaultValue, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set blue WB failed\n"); - } - break; - - case SVB_GAMMA : - // Gamma - IUFillNumber(&ControlsN[CCD_GAMMA_N], "GAMMA", "Gamma", "%.f", caps.MinValue, caps.MaxValue, caps.MaxValue / 10, - caps.DefaultValue); - IUFillNumberVector(&ControlsNP[CCD_GAMMA_N], &ControlsN[CCD_GAMMA_N], 1, getDeviceName(), "CCD_GAMMA", "Gamma", - MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); - status = SVBSetControlValue(cameraID, SVB_GAMMA, caps.DefaultValue, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set gamma failed\n"); - } - break; - - case SVB_BLACK_LEVEL : - // Dark Offset - IUFillNumber(&ControlsN[CCD_DOFFSET_N], "OFFSET", "Offset", "%.f", caps.MinValue, caps.MaxValue, caps.MaxValue / 10, - caps.DefaultValue); - IUFillNumberVector(&ControlsNP[CCD_DOFFSET_N], &ControlsN[CCD_DOFFSET_N], 1, getDeviceName(), "CCD_OFFSET", "Offset", - MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); - status = SVBSetControlValue(cameraID, SVB_BLACK_LEVEL, caps.DefaultValue, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set offset failed\n"); - } - break; - - case SVB_BAD_PIXEL_CORRECTION_ENABLE : - // a switch for automatic correction of dynamic dead pixels - // set the status to disable - IUFillSwitch(&CorrectDDPS[CORRECT_DDP_ENABLE], "CORRECT_DDP_ENABLE", "ENABLE", ISS_OFF); - IUFillSwitch(&CorrectDDPS[CORRECT_DDP_DISABLE], "CORRECT_DDP_DISABLE", "DISABLE", ISS_ON); - IUFillSwitchVector(&CorrectDDPSP, CorrectDDPS, 2, getDeviceName(), "CORRECT_DDP", "Correct Dead pixel", MAIN_CONTROL_TAB, IP_WO, ISR_1OFMANY, 60, IPS_IDLE); - - status = SVBSetControlValue(cameraID, SVB_BAD_PIXEL_CORRECTION_ENABLE, 0, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOGF_ERROR("Error, set a switch for automatic correction of dynamic dead pixels:%d", status); - } - break; - - default : - break; - } - } - - // set frame speed - IUFillSwitch(&SpeedS[SPEED_SLOW], "SPEED_SLOW", "Slow", ISS_OFF); - IUFillSwitch(&SpeedS[SPEED_NORMAL], "SPEED_NORMAL", "Normal", ISS_ON); - IUFillSwitch(&SpeedS[SPEED_FAST], "SPEED_FAST", "Fast", ISS_OFF); - IUFillSwitchVector(&SpeedSP, SpeedS, 3, getDeviceName(), "FRAME_RATE", "Frame rate", MAIN_CONTROL_TAB, IP_RW, ISR_1OFMANY, - 60, IPS_IDLE); - frameSpeed = SPEED_NORMAL; - status = SVBSetControlValue(cameraID, SVB_FRAME_SPEED_MODE, SPEED_NORMAL, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set frame speed failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // set frame format and feed UI - nFrameFormat = 0; - // initialize frameFormatDefinitions from cameraProperty - defaultMaxBitDepth = 0; // max pixel bit depth - for (int i = 0; (i < (int)(sizeof(cameraProperty.SupportedVideoFormat) / sizeof(cameraProperty.SupportedVideoFormat[0]))) && cameraProperty.SupportedVideoFormat[i] != SVB_IMG_END; i++) - { - int svb_img_fmt = cameraProperty.SupportedVideoFormat[i]; - - if (svb_img_fmt != SVB_IMG_RGB24 && svb_img_fmt != SVB_IMG_RGB32) // INDI not support RGB24,RGB32 - { - frameFormatDefinitions[svb_img_fmt].isIndex = i; // Set the index of the ISwitch - - if (HasBayer() == frameFormatDefinitions[svb_img_fmt].isColor) // either HasBayer and color frame format or not HasBayer and grayscale format. - { - // For color CCDs, find the maximum color format bits - // For monochrome CCDs, find the maximum bits in grayscale format. - if (defaultMaxBitDepth < frameFormatDefinitions[svb_img_fmt].isBits) - defaultMaxBitDepth = frameFormatDefinitions[svb_img_fmt].isBits; - } - ++nFrameFormat; // count up number of ISwitch - } - } - - // initialize ISwitchs - if (!(switch2frameFormatDefinitionsIndex = (SVB_IMG_TYPE*)calloc(nFrameFormat, sizeof(int)))) - { - LOG_ERROR("Error, memory allocation for image format switches index\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - defaultFrameFormatIndex = SVB_IMG_END; - for (int i = 0; i < (int)(sizeof(frameFormatDefinitions) / sizeof(FrameFormatDefinition)); i++) - { - FrameFormatDefinition *pFrameFormatDef = &frameFormatDefinitions[i]; - if (pFrameFormatDef->isIndex != -1) - { - if (HasBayer() == pFrameFormatDef->isColor && defaultMaxBitDepth == pFrameFormatDef->isBits) - { - // Switch on the maximum number of bits. For color cameras, the number of bits for color; for monochrome cameras, the number of bits for grayscale. - pFrameFormatDef->isStateDefault = ISS_ON; - defaultFrameFormatIndex = (SVB_IMG_TYPE)i; - } - switch2frameFormatDefinitionsIndex[pFrameFormatDef->isIndex] = (SVB_IMG_TYPE)i; - // Setup Capture Format - CaptureFormat format = - { - pFrameFormatDef->isName, - pFrameFormatDef->isLabel, - (uint8_t)(pFrameFormatDef->isBits), - pFrameFormatDef->isStateDefault == ISS_ON ? true : false - }; - addCaptureFormat(format); - } - } - // Set ISS_ON for default switch cause addCapture cannot set ISS_ON when config.xml 'CCD_CAPTURE_FORMAT' is old format. - if (CaptureFormatSP.findOnSwitchIndex() == -1) - { - FrameFormatDefinition *pFrameFormatDef = &frameFormatDefinitions[defaultFrameFormatIndex]; - CaptureFormatSP[pFrameFormatDef->isIndex].setState(ISS_ON); - CaptureFormatSP.apply(); - } - - if(HasBayer()) - { - IUSaveText(&BayerT[0], "0"); - IUSaveText(&BayerT[1], "0"); - IUSaveText(&BayerT[2], bayerPatternMapping[cameraProperty.BayerPattern]); - } - status = SVBSetOutputImageType(cameraID, defaultFrameFormatIndex); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set frame format failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - bitDepth = defaultMaxBitDepth; - frameFormat = defaultFrameFormatIndex; - LOG_INFO("Camera set frame format mode\n"); - - // set bit stretching and feed UI - IUFillSwitch(&StretchS[STRETCH_OFF], "STRETCH_OFF", "Off", ISS_ON); - IUFillSwitch(&StretchS[STRETCH_X2], "STRETCH_X2", "x2", ISS_OFF); - IUFillSwitch(&StretchS[STRETCH_X4], "STRETCH_X4", "x4", ISS_OFF); - IUFillSwitch(&StretchS[STRETCH_X8], "STRETCH_X8", "x8", ISS_OFF); - IUFillSwitch(&StretchS[STRETCH_X16], "STRETCH_X16", "x16", ISS_OFF); - IUFillSwitchVector(&StretchSP, StretchS, 5, getDeviceName(), "STRETCH_BITS", "12 bits 16 bits stretch", MAIN_CONTROL_TAB, - IP_RW, ISR_1OFMANY, 60, IPS_IDLE); - bitStretch = 0; - - // Cooler Enable - if (HasCooler()) - { - // set initial target temperature - IUFillNumber(&TemperatureN[0], "CCD_TEMPERATURE_VALUE", "Temperature (C)", "%5.2f", -50.0, 50.0, 0., 25.); - - // default target temperature is 0. Setting to 25. - if (SVB_SUCCESS != (status = SVBSetControlValue(cameraID, SVB_TARGET_TEMPERATURE, (long)(25 * 10), SVB_FALSE))) - { - LOGF_INFO("Setting default target temperature failed. (SVB_TARGET_TEMPERATURE:%d)", status); - } - TemperatureRequest = 25; - - // set cooler status to disable - IUFillSwitch(&CoolerS[COOLER_ENABLE], "COOLER_ON", "ON", ISS_OFF); - IUFillSwitch(&CoolerS[COOLER_DISABLE], "COOLER_OFF", "OFF", ISS_ON); - IUFillSwitchVector(&CoolerSP, CoolerS, 2, getDeviceName(), "CCD_COOLER", "Cooler", MAIN_CONTROL_TAB, IP_WO, ISR_1OFMANY, 60, IPS_IDLE); - - // cooler power info - IUFillNumber(&CoolerN[0], "CCD_COOLER_POWER_VALUE", "Cooler power (%)", "%3.f", 0.0, 100.0, 1., 0.); - IUFillNumberVector(&CoolerNP, CoolerN, 1, getDeviceName(), "CCD_COOLER_POWER", "Cooler power", MAIN_CONTROL_TAB, IP_RO, 60, IPS_IDLE); - - } - coolerEnable = COOLER_DISABLE; - - // set camera ROI and BIN - binning = false; - status = SVBSetROIFormat(cameraID, 0, 0, cameraProperty.MaxWidth, cameraProperty.MaxHeight, 1); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set ROI failed"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - int x, y, w, h, bin; - status = SVBGetROIFormat(cameraID, &x, &y, &w, &h, &bin); // Get Actual ROI - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera get ROI failed"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - LOGF_DEBUG("Actual ROI x=%d, y=%d, w=%d, h=%d, bin=%d", x, y, w, h, bin); - SetCCDParams(w, h, bitDepth, pixelSize, pixelSize); - x_offset = x; - y_offset = y; - ROI_width = w; - ROI_height = h; - LOG_INFO("Camera set ROI\n"); - - // set camera soft trigger mode - status = SVBSetCameraMode(cameraID, SVB_MODE_TRIG_SOFT); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera soft trigger mode failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - LOG_INFO("Camera soft trigger mode\n"); - - // start framing - status = SVBStartVideoCapture(cameraID); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, start camera failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - pthread_mutex_unlock(&cameraID_mutex); - - // set CCD up - updateCCDParams(); - - // create streaming thread - terminateThread = false; - pthread_create(&primary_thread, nullptr, &streamVideoHelper, this); - - /* Success! */ - LOG_INFO("CCD is online. Retrieving basic data.\n"); - return true; -} - - -// -bool SVBONYCCD::Disconnect() -{ - // destroy streaming - pthread_mutex_lock(&condMutex); - streaming = true; - terminateThread = true; - pthread_cond_signal(&cv); - pthread_mutex_unlock(&condMutex); - - //pthread_mutex_lock(&cameraID_mutex); // *1 - - // stop camera - SVB_ERROR_CODE status = SVBStopVideoCapture(cameraID); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, stop camera failed\n"); - //pthread_mutex_unlock(&cameraID_mutex); // *1 has been comment outed, so this line comment outed too - return false; - } - - // destroy camera - status = SVBCloseCamera(cameraID); - LOG_INFO("CCD is offline.\n"); - - // free map for frame format Switch to frame format definition array - free(switch2frameFormatDefinitionsIndex); - - //pthread_mutex_unlock(&cameraID_mutex); // *1 has been comment outed, so this line comment outed too - - // destroy mutex, cond and streaming thread - pthread_mutex_destroy(&cameraID_mutex); - pthread_mutex_destroy(&streaming_mutex); - pthread_mutex_destroy(&condMutex); - pthread_cond_destroy(&cv); - - pthread_cancel(primary_thread); - - return true; -} - - -// set CCD parameters -bool SVBONYCCD::updateCCDParams() -{ - // set CCD parameters - PrimaryCCD.setBPP(bitDepth); - - // Let's calculate required buffer - int nbuf = PrimaryCCD.getXRes() * PrimaryCCD.getYRes() * PrimaryCCD.getBPP() / 8; - PrimaryCCD.setFrameBufferSize(nbuf); - - LOGF_INFO("PrimaryCCD buffer size : %d\n", nbuf); - - return true; -} - -/////////////////////////////////////////////////////////////////////////////////////// -/// Set camera temperature -/////////////////////////////////////////////////////////////////////////////////////// -int SVBONYCCD::SetTemperature(double temperature) -{ - /********************************************************** - * We return 0 if setting the temperature will take some time - * If the requested is the same as current temperature, or very - * close, we return 1 and INDI::CCD will mark the temperature status as OK - * If we return 0, INDI::CCD will mark the temperature status as BUSY - **********************************************************/ - SVB_ERROR_CODE ret; - - // If below threshold, do nothing - if (fabs(temperature - TemperatureN[0].value) < TemperatureRampNP[RAMP_THRESHOLD].value) - { - return 1; // The requested temperature is the same as current temperature, or very close - } - - pthread_mutex_lock(&cameraID_mutex); - // Set target temperature - if (SVB_SUCCESS != (ret = SVBSetControlValue(cameraID, SVB_TARGET_TEMPERATURE, (long)(temperature * 10), SVB_FALSE))) - { - LOGF_INFO("Setting target temperature failed. (SVB_TARGET_TEMPERATURE:%d)", ret); - pthread_mutex_unlock(&cameraID_mutex); - return -1; - } - - pthread_mutex_unlock(&cameraID_mutex); - - // Enable Cooler - pthread_mutex_lock(&cameraID_mutex); - if (SVB_SUCCESS != (ret = SVBSetControlValue(cameraID, SVB_COOLER_ENABLE, 1, SVB_FALSE))) - { - LOGF_INFO("Enabling cooler is fail.(SVB_COOLER_ENABLE:%d)", ret); - pthread_mutex_unlock(&cameraID_mutex); - return -1; - } - - pthread_mutex_unlock(&cameraID_mutex); - - CoolerS[COOLER_ENABLE].s = ISS_ON; - CoolerS[COOLER_DISABLE].s = ISS_OFF; - CoolerSP.s = IPS_OK; - IDSetSwitch(&CoolerSP, NULL); - - // Otherwise, we set the temperature request and we update the status in TimerHit() function. - TemperatureRequest = temperature; - LOGF_INFO("Setting CCD temperature to %+06.2f C", temperature); - - return 0; -} - -// -bool SVBONYCCD::StartExposure(float duration) -{ - // checks for time limits - if (duration < minExposure) - { - LOGF_WARN("Exposure shorter than minimum duration %g s requested. \n Setting exposure time to %g s.\n", duration, - minExposure); - duration = minExposure; - } - - if (duration > maxExposure) - { - LOGF_WARN("Exposure greater than minimum duration %g s requested. \n Setting exposure time to %g s.\n", duration, - maxExposure); - duration = maxExposure; - } - - pthread_mutex_lock(&cameraID_mutex); - -#ifdef WORKAROUND_latest_image_can_be_getten_next_time - // Discard unretrieved exposure data - discardVideoData(); -#endif - // set exposure time (s -> us) - SVB_ERROR_CODE status = SVBSetControlValue(cameraID, SVB_EXPOSURE, (long)(duration * 1000000L), SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set exposure failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // soft trigger - status = SVBSendSoftTrigger(cameraID); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, soft trigger failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - pthread_mutex_unlock(&cameraID_mutex); - - PrimaryCCD.setExposureDuration(duration); - ExposureRequest = duration; - - gettimeofday(&ExpStart, nullptr); - LOGF_DEBUG("Taking a %g seconds frame...\n", ExposureRequest); - - InExposure = true; - - return true; -} - -#ifdef WORKAROUND_latest_image_can_be_getten_next_time -// Discard unretrieved exposure data -void SVBONYCCD::discardVideoData() -{ - unsigned char* imageBuffer = PrimaryCCD.getFrameBuffer(); - SVB_ERROR_CODE status = SVBGetVideoData(cameraID, imageBuffer, PrimaryCCD.getFrameBufferSize(), 1000); - LOGF_DEBUG("Discard unretrieved exposure data: SVBGetVideoData:result=%d", status); -} -#endif - -// -bool SVBONYCCD::AbortExposure() -{ - - LOG_INFO("Abort exposure\n"); - - InExposure = false; - - pthread_mutex_lock(&cameraID_mutex); - - // ********* - // TODO - // ********* - - // stop camera - SVB_ERROR_CODE status = SVBStopVideoCapture(cameraID); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, stop camera failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // start camera - status = SVBStartVideoCapture(cameraID); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, start camera failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // ********* - - pthread_mutex_unlock(&cameraID_mutex); - - return true; -} - - -// -bool SVBONYCCD::StartStreaming() -{ - LOG_INFO("framing\n"); - - // stream init - // Check monochrome camera or binning - // if binning, no more bayer - if(!HasBayer() || binning) - { - Streamer->setPixelFormat(INDI_MONO, bitDepth); - } - else - { - Streamer->setPixelFormat(INDI_BAYER_GRBG, bitDepth); - } - Streamer->setSize(PrimaryCCD.getSubW() / PrimaryCCD.getBinX(), PrimaryCCD.getSubH() / PrimaryCCD.getBinY()); - - // streaming exposure time - ExposureRequest = 1.0 / Streamer->getTargetFPS(); - - pthread_mutex_lock(&cameraID_mutex); - - // stop camera - SVB_ERROR_CODE status = SVBStopVideoCapture(cameraID); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, stop camera failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // set exposure time (s -> us) - status = SVBSetControlValue(cameraID, SVB_EXPOSURE, (long)(ExposureRequest * 1000000L), SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set exposure failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // set camera normal mode - status = SVBSetCameraMode(cameraID, SVB_MODE_NORMAL); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera normal mode failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - LOG_INFO("Camera normal mode\n"); - - // set ROI back - status = SVBSetROIFormat(cameraID, x_offset, y_offset, PrimaryCCD.getSubW(), PrimaryCCD.getSubH(), 1); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set subframe failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - LOG_INFO("Subframe set\n"); - - // start camera - status = SVBStartVideoCapture(cameraID); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, start camera failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - pthread_mutex_unlock(&cameraID_mutex); - - pthread_mutex_lock(&condMutex); - streaming = true; - pthread_cond_signal(&cv); - pthread_mutex_unlock(&condMutex); - - LOG_INFO("Streaming started\n"); - - return true; -} - - -// -bool SVBONYCCD::StopStreaming() -{ - LOG_INFO("stop framing\n"); - - pthread_mutex_lock(&cameraID_mutex); - - // stop camera - SVB_ERROR_CODE status = SVBStopVideoCapture(cameraID); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, stop camera failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // set camera back to trigger mode - status = SVBSetCameraMode(cameraID, SVB_MODE_TRIG_SOFT); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera soft trigger mode failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - LOG_INFO("Camera soft trigger mode\n"); - - // set ROI back - status = SVBSetROIFormat(cameraID, x_offset, y_offset, PrimaryCCD.getSubW(), PrimaryCCD.getSubH(), 1); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set subframe failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - LOG_INFO("Subframe set\n"); - - // start camera - status = SVBStartVideoCapture(cameraID); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, start camera failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - pthread_mutex_unlock(&cameraID_mutex); - - pthread_mutex_lock(&condMutex); - streaming = false; - pthread_cond_signal(&cv); - pthread_mutex_unlock(&condMutex); - - LOG_INFO("Streaming stopped\n"); - - return true; -} - - -// -void* SVBONYCCD::streamVideoHelper(void * context) -{ - return static_cast(context)->streamVideo(); -} +#include "svbony_ccd.h" +#include +#include +#include -// -void* SVBONYCCD::streamVideo() +static class Loader { - auto start = std::chrono::high_resolution_clock::now(); - auto finish = std::chrono::high_resolution_clock::now(); - - while (true) - { - pthread_mutex_lock(&condMutex); - - while (!streaming) + INDI::Timer hotPlugTimer; + std::map> cameras; + public: + Loader() { - pthread_cond_wait(&cv, &condMutex); - // ??? - ExposureRequest = 1.0 / Streamer->getTargetFPS(); + load(false); } - pthread_mutex_unlock(&condMutex); - - if (terminateThread) - break; - - unsigned char* imageBuffer = PrimaryCCD.getFrameBuffer(); - - pthread_mutex_lock(&cameraID_mutex); - - // get the frame - // Note. - // The original code continues the same process as when it succeeded, regardless of any errors in SVBGetVideoData. - // The code that assigns the return value to "status" raises a "warning" at compile time, so it should be commented out. - /* SVB_ERROR_CODE status = */ SVBGetVideoData(cameraID, imageBuffer, PrimaryCCD.getFrameBufferSize(), 1000 ); - - pthread_mutex_unlock(&cameraID_mutex); - - finish = std::chrono::high_resolution_clock::now(); - - // stretching 12bits depth to 16bits depth - if(bitDepth == 16 && (bitStretch != 0)) + public: + static size_t getCountOfConnectedCameras() { - u_int16_t* tmp = (u_int16_t*)imageBuffer; - for(int i = 0; i < PrimaryCCD.getFrameBufferSize() / 2; i++) - { - tmp[i] <<= bitStretch; - } + return size_t(std::max(SVBGetNumOfConnectedCameras(), 0)); } - if(binning) + static std::vector getConnectedCameras() { - PrimaryCCD.binFrame(); + auto connectedCameras = getCountOfConnectedCameras(); + std::vector result(connectedCameras); + int i = 0; + for(auto &cameraInfo : result) + SVBGetCameraInfo(&cameraInfo, i++); + return result; } - uint32_t size = PrimaryCCD.getSubW() / PrimaryCCD.getBinX() * PrimaryCCD.getSubH() / PrimaryCCD.getBinY() * bitDepth / 8; - Streamer->newFrame(PrimaryCCD.getFrameBuffer(), size); - - std::chrono::duration elapsed = finish - start; - if (elapsed.count() < ExposureRequest) - usleep(fabs(ExposureRequest - elapsed.count()) * 1e6); - - start = std::chrono::high_resolution_clock::now(); - } - - return nullptr; -} - - -// subframing -bool SVBONYCCD::UpdateCCDFrame(int x, int y, int w, int h) -{ - - if((x + w) > cameraProperty.MaxWidth - || (y + h) > cameraProperty.MaxHeight - || w % 8 != 0 - || h % 2 != 0 - ) - { - LOG_ERROR("Error : Subframe out of range"); - return false; - } - - pthread_mutex_lock(&cameraID_mutex); - - // stop framing - SVB_ERROR_CODE status = SVBStopVideoCapture(cameraID); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, stop camera failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - // change ROI - status = SVBSetROIFormat(cameraID, x, y, w, h, 1); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set subframe failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - LOGF_DEBUG("Given ROI x=%d, y=%d, w=%d, h=%d", x, y, w, h); - int bin; - status = SVBGetROIFormat(cameraID, &x, &y, &w, &h, &bin); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, get actual subframe failed"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - LOGF_DEBUG("Actual ROI x=%d, y=%d, w=%d, h=%d, bin=%d", x, y, w, h, bin); - LOG_INFO("Subframe set"); - - // start framing - status = SVBStartVideoCapture(cameraID); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, start camera failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - - pthread_mutex_unlock(&cameraID_mutex); - - x_offset = x; - y_offset = y; - ROI_width = w; - ROI_height = h; - - return INDI::CCD::UpdateCCDFrame(x, y, w, h); -} - - -// binning -bool SVBONYCCD::UpdateCCDBin(int hor, int ver) -{ - if(hor == 1 && ver == 1) - binning = false; - else - binning = true; - - LOG_INFO("Binning changed"); - - // hardware binning not supported. Using software binning - - return INDI::CCD::UpdateCCDBin(hor, ver); -} - - -// -float SVBONYCCD::CalcTimeLeft() -{ - double timesince; - double timeleft; - struct timeval now; - gettimeofday(&now, nullptr); - - timesince = (double)(now.tv_sec * 1000.0 + now.tv_usec / 1000) - - (double)(ExpStart.tv_sec * 1000.0 + ExpStart.tv_usec / 1000); - timesince = timesince / 1000; - - timeleft = ExposureRequest - timesince; - return timeleft; -} - - -// grab loop -void SVBONYCCD::TimerHit() -{ - int timerID = -1; - double timeleft; - - if (isConnected() == false) - return; // No need to reset timer if we are not connected anymore + public: + void load(bool isHotPlug) + { + auto usedCameras = std::move(cameras); - if (InExposure) - { - timeleft = CalcTimeLeft(); + UniqueName uniqueName(usedCameras); - if (timeleft < 1.0) - { - if (timeleft > 0.25) - { - // a quarter of a second or more - // just set a tighter timer - timerID = SetTimer(250); - } - else + for(const auto &cameraInfo : getConnectedCameras()) { - if (timeleft > 0.07) + int id = cameraInfo.CameraID; + + // camera already created + if (usedCameras.find(id) != usedCameras.end()) { - // use an even tighter timer - timerID = SetTimer((uint32_t)(timeleft * 1000)); + std::swap(cameras[id], usedCameras[id]); + continue; } - else - { - LOGF_DEBUG("Current timeleft:%.2lf sec.", timeleft); - pthread_mutex_lock(&cameraID_mutex); - unsigned char* imageBuffer = PrimaryCCD.getFrameBuffer(); - SVB_ERROR_CODE status = SVBGetVideoData(cameraID, imageBuffer, PrimaryCCD.getFrameBufferSize(), 1000); - pthread_mutex_unlock(&cameraID_mutex); - LOGF_DEBUG("SVBGetVideoData:result=%d", status); - - switch (status) + SVB_SN serialNumber; + std::string serialNumberStr = ""; + if(SVBOpenCamera(cameraInfo.CameraID) == SVB_SUCCESS) + { + if (SVBGetSerialNumber(cameraInfo.CameraID, &serialNumber) == SVB_SUCCESS) { - case SVB_SUCCESS: - // exposing done - PrimaryCCD.setExposureLeft(0); - InExposure = false; - - // stretching 12bits depth to 16bits depth - if(bitDepth == 16 && (bitStretch != 0)) - { - u_int16_t* tmp = (u_int16_t*)imageBuffer; - for(int i = 0; i < PrimaryCCD.getFrameBufferSize() / 2; i++) - { - tmp[i] <<= bitStretch; - } - } - - // binning if needed - if(binning) - PrimaryCCD.binFrame(); - - // exposure done - ExposureComplete(&PrimaryCCD); - break; - - case SVB_ERROR_TIMEOUT: - LOG_DEBUG("Timeout for image data retrieval."); - // set retry timer for SVGGetVideoData - timerID = SetTimer((uint32_t)100); // Time until next image data acquisition: 100 ms - break; - - default: - LOGF_INFO("Error retrieval image data (status:%d)", status); - // Exposure be aborted. Error in SVBGetVideoData - PrimaryCCD.setExposureFailed(); // The exposure will be restarted after calling PrimaryCCD.setExposureFailed(). - PrimaryCCD.setExposureLeft(0); - InExposure = false; - break; + SVBCloseCamera(cameraInfo.CameraID); + char snChars[100]; + auto &sn = serialNumber; + sprintf(snChars, "%02x%02x%02x%02x%02x%02x%02x%02x", sn.id[0], sn.id[1], + sn.id[2], sn.id[3], sn.id[4], sn.id[5], sn.id[6], sn.id[7]); + snChars[16] = 0; + serialNumberStr = std::string(snChars); } } - } - } - else - { - if (isDebug()) - { - IDLog("With time left %.2lf\n", timeleft); - IDLog("image not yet ready....\n"); - } - PrimaryCCD.setExposureLeft(timeleft); + auto camera = new class SVBONYCCD(cameraInfo, uniqueName.make(cameraInfo), serialNumberStr); + cameras[id] = std::shared_ptr(camera); + if (isHotPlug) + camera->ISGetProperties(nullptr); + } } - } - - - if (HasCooler()) - { - SVB_ERROR_CODE ret; - long lValue; - SVB_BOOL bAuto; - // temperature readout - pthread_mutex_lock(&cameraID_mutex); - if (SVB_SUCCESS != (ret = SVBGetControlValue(cameraID, SVB_CURRENT_TEMPERATURE, &lValue, &bAuto))) - { - LOGF_INFO("Error, unable to get temp due to ...(SVB_CURRENT_TEMPERATURE:%d)", ret); - TemperatureNP.s = IPS_ALERT; - } - else + public: + class UniqueName { - TemperatureN[0].value = ((double)lValue) / 10; - IDSetNumber(&TemperatureNP, nullptr); - } - pthread_mutex_unlock(&cameraID_mutex); + std::map used; + public: + UniqueName() = default; + UniqueName(const std::map> &usedCameras) + { + for (const auto &camera : usedCameras) + used[camera.second->getDeviceName()] = true; + } - // read cooler power - pthread_mutex_lock(&cameraID_mutex); - if (SVB_SUCCESS != (ret = SVBGetControlValue(cameraID, SVB_COOLER_POWER, &lValue, &bAuto))) - { - LOGF_INFO("Error, unable to get cooler power due to ...(SVB_COOLER_POWER:%d)", ret); - CoolerNP.s = IPS_ALERT; - } - else - { - CoolerN[0].value = (double)lValue; - CoolerNP.s = IPS_OK; - IDSetNumber(&CoolerNP, nullptr); - } - pthread_mutex_unlock(&cameraID_mutex); - } + std::string make(const SVB_CAMERA_INFO &cameraInfo) + { + std::string cameraName = "SVBONY CCD " + std::string(cameraInfo.FriendlyName + 7); + std::string uniqueName = cameraName; - if (timerID == -1) - SetTimer(getCurrentPollingPeriod()); - return; -} + for (int index = 0; used[uniqueName] == true; ) + uniqueName = cameraName + " " + std::to_string(++index); + used[uniqueName] = true; + return uniqueName; + } + }; +} loader; -// helper : update camera control depending on control type -bool SVBONYCCD::updateControl(int ControlType, SVB_CONTROL_TYPE SVB_Control, double values[], char *names[], int n) +namespace { - IUUpdateNumber(&ControlsNP[ControlType], values, names, n); - pthread_mutex_lock(&cameraID_mutex); - - // set control - SVB_ERROR_CODE status = SVBSetControlValue(cameraID, SVB_Control, ControlsN[ControlType].value, SVB_FALSE); - if(status != SVB_SUCCESS) +// trim from start (in place) +static inline void ltrim(std::string &s) +{ + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { - LOGF_ERROR("Error, camera set control %d failed\n", ControlType); - pthread_mutex_unlock(&cameraID_mutex); - return false; - } - LOGF_INFO("Camera control %d to %.f\n", ControlType, ControlsN[ControlType].value); - - pthread_mutex_unlock(&cameraID_mutex); + return !std::isspace(ch); + })); +} - ControlsNP[ControlType].s = IPS_OK; - IDSetNumber(&ControlsNP[ControlType], nullptr); - return true; +// trim from end (in place) +static inline void rtrim(std::string &s) +{ + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) + { + return !std::isspace(ch); + }).base(), s.end()); } +// trim from both ends (in place) +static inline void trim(std::string &s) +{ + ltrim(s); + rtrim(s); +} -// -bool SVBONYCCD::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) +std::string GetHomeDirectory() { - if (strcmp (dev, getDeviceName())) - return false; + // Check first the HOME environmental variable + const char *HomeDir = getenv("HOME"); - // look for gain settings - if (!strcmp(name, ControlsNP[CCD_GAIN_N].name)) + // ...otherwise get the home directory of the current user. + if (!HomeDir) { - return updateControl(CCD_GAIN_N, SVB_GAIN, values, names, n); + HomeDir = getpwuid(getuid())->pw_dir; } + return (HomeDir ? std::string(HomeDir) : ""); +} - // look for contrast settings - if (!strcmp(name, ControlsNP[CCD_CONTRAST_N].name)) - { - return updateControl(CCD_CONTRAST_N, SVB_CONTRAST, values, names, n); - } +} // namespace - // look for sharpness settings - if (!strcmp(name, ControlsNP[CCD_SHARPNESS_N].name)) - { - return updateControl(CCD_SHARPNESS_N, SVB_SHARPNESS, values, names, n); - } +// Nicknames are stored in an xml-format NICKNAME_FILE in a format like the below. +// Nicknames are assoicated with the serial number of the camera, and are entered/changed +// with NicknameTP. Since the device-name can't be changed once the driver is running, +// changes to nicknames can only take effect at the next INDI startup. +// +// nickname1 +// nickname2 +// nickname3 +// - // look for saturation settings - if (!strcmp(name, ControlsNP[CCD_SATURATION_N].name)) - { - return updateControl(CCD_SATURATION_N, SVB_SATURATION, values, names, n); - } +#define ROOTNODE "Nicknames" +#define ENTRYNODE "Nickname" +#define ATTRIBUTE "SerialNumber" - // look for red WB settings - if (!strcmp(name, ControlsNP[CCD_WBR_N].name)) - { - return updateControl(CCD_WBR_N, SVB_WB_R, values, names, n); - } +void SVBONYCCD::loadNicknames() +{ + const std::string filename = GetHomeDirectory() + NICKNAME_FILE; + mNicknames.clear(); - // look for green WB settings - if (!strcmp(name, ControlsNP[CCD_WBG_N].name)) + LilXML *xmlHandle = newLilXML(); + XMLEle *rootXmlNode = nullptr; + char errorMessage[512] = {0}; + FILE *file = fopen(filename.c_str(), "r"); + if (file) { - return updateControl(CCD_WBG_N, SVB_WB_G, values, names, n); + rootXmlNode = readXMLFile(file, xmlHandle, errorMessage); + fclose(file); } + delLilXML(xmlHandle); - // look for blue WB settings - if (!strcmp(name, ControlsNP[CCD_WBB_N].name)) - { - return updateControl(CCD_WBB_N, SVB_WB_B, values, names, n); - } + if (rootXmlNode == nullptr) + return; - // look for gamma settings - if (!strcmp(name, ControlsNP[CCD_GAMMA_N].name)) + XMLEle *currentXmlNode = nextXMLEle(rootXmlNode, 1); + while (currentXmlNode) { - return updateControl(CCD_GAMMA_N, SVB_GAMMA, values, names, n); + const char *id = findXMLAttValu(currentXmlNode, ATTRIBUTE); + if (id != nullptr) + { + std::string name = pcdataXMLEle(currentXmlNode); + if (!name.empty()) + trim(name); + if (!name.empty()) + mNicknames[id] = name; + } + currentXmlNode = nextXMLEle(rootXmlNode, 0); } - // look for dark offset settings - if (!strcmp(name, ControlsNP[CCD_DOFFSET_N].name)) - { - return updateControl(CCD_DOFFSET_N, SVB_BLACK_LEVEL, values, names, n); - } + delXMLEle(rootXmlNode); +} + +void SVBONYCCD::saveNicknames() +{ + const std::string filename = GetHomeDirectory() + NICKNAME_FILE; + XMLEle *rootXmlNode = nullptr; + XMLEle *oneElement = nullptr; - bool result = INDI::CCD::ISNewNumber(dev, name, values, names, n); + FILE *file = fopen(filename.c_str(), "w"); - // look for ROI settings - if (!strcmp(name, "CCD_FRAME") && result) + rootXmlNode = addXMLEle(nullptr, ROOTNODE); + + for (const auto &kv : mNicknames) { - // Set actural ROI size - PrimaryCCD.setFrame(x_offset, y_offset, ROI_width, ROI_height); + oneElement = addXMLEle(rootXmlNode, ENTRYNODE); + addXMLAtt(oneElement, ATTRIBUTE, kv.first.c_str()); + editXMLEle(oneElement, kv.second.c_str()); } - return result; + prXMLEle(file, rootXmlNode, 0); + fclose(file); + delXMLEle(rootXmlNode); } -// -bool SVBONYCCD::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) +bool SVBONYCCD::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) { - // Make sure the call is for our device - if(!strcmp(dev, getDeviceName())) + if (dev != nullptr && !strcmp(dev, getDeviceName())) { - // Check is the call for capture format - if (CaptureFormatSP.isNameMatch(name)) + if (NicknameTP.isNameMatch(name)) { - // search capture format in frameFormatDefinitions - int tempFormatIndex = -1; // index of matched frameFormatDefinitions. - for (int i = 0; i < (int)nFrameFormat; i++) + NicknameTP.update(texts, names, n); + NicknameTP.setState(IPS_OK); + NicknameTP.apply(); + if (!mSerialNumber.empty()) { - int currentIndex = (int)switch2frameFormatDefinitionsIndex[i]; - - // check to match this frameFormatDefinitions. - for (int j = 0; j < n; j++) + loadNicknames(); // another camera may have updated its nickname. + std::string newNickname = texts[0]; + trim(newNickname); + if (newNickname.empty()) { - if (!strcmp(names[j], frameFormatDefinitions[currentIndex].isName)) - { - tempFormatIndex = currentIndex; // found it. - break; - } + mNicknames.erase(mSerialNumber); + LOGF_INFO("Nickname for %s removed.", mSerialNumber.c_str()); } + else + { + mNicknames[mSerialNumber] = newNickname; + LOGF_INFO("Nickname for %s changed to %s.", mSerialNumber.c_str(), newNickname.c_str()); + } + saveNicknames(); + LOG_INFO("The driver must now be restarted for this change to take effect."); } - if (tempFormatIndex == -1) // If it is not found, abort the process. - { - LOGF_ERROR("Error, %s is not exist in Format switches.", names[0]); - return false; - } - } - - // Check if the call for frame rate switch - if (!strcmp(name, SpeedSP.name)) - { - // Find out which state is requested by the client - const char *actionName = IUFindOnSwitchName(states, names, n); - // If same state as actionName, then we do nothing - int tmpSpeed = IUFindOnSwitchIndex(&SpeedSP); - if (!strcmp(actionName, SpeedS[tmpSpeed].name)) - { - LOGF_INFO("Frame rate is already %s", SpeedS[tmpSpeed].label); - SpeedSP.s = IPS_IDLE; - IDSetSwitch(&SpeedSP, NULL); - return true; - } - - // Otherwise, let us update the switch state - IUUpdateSwitch(&SpeedSP, states, names, n); - tmpSpeed = IUFindOnSwitchIndex(&SpeedSP); - - pthread_mutex_lock(&cameraID_mutex); - - // set new frame rate - SVB_ERROR_CODE status = SVBSetControlValue(cameraID, SVB_FRAME_SPEED_MODE, tmpSpeed, SVB_FALSE); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set frame rate failed\n"); - } - LOGF_INFO("Frame rate is now %s", SpeedS[tmpSpeed].label); - - pthread_mutex_unlock(&cameraID_mutex); - - frameSpeed = tmpSpeed; - - SpeedSP.s = IPS_OK; - IDSetSwitch(&SpeedSP, NULL); - return true; - } - - // Check if the 16 bist stretch factor - if (!strcmp(name, StretchSP.name)) - { - // Find out which state is requested by the client - const char *actionName = IUFindOnSwitchName(states, names, n); - // If same state as actionName, then we do nothing - int tmpStretch = IUFindOnSwitchIndex(&StretchSP); - if (!strcmp(actionName, StretchS[tmpStretch].name)) - { - LOGF_INFO("Stretch factor is already %s", StretchS[tmpStretch].label); - StretchSP.s = IPS_IDLE; - IDSetSwitch(&StretchSP, NULL); - return true; - } - - // Otherwise, let us update the switch state - IUUpdateSwitch(&StretchSP, states, names, n); - tmpStretch = IUFindOnSwitchIndex(&StretchSP); - - LOGF_INFO("Stretch factor is now %s", StretchS[tmpStretch].label); - - bitStretch = tmpStretch; - - StretchSP.s = IPS_OK; - IDSetSwitch(&StretchSP, NULL); - return true; - } - - // Check if the Cooler Enable - if (!strcmp(name, CoolerSP.name)) - { - // Find out which state is requested by the client - const char *actionName = IUFindOnSwitchName(states, names, n); - // If same state as actionName, then we do nothing - int tmpCoolerEnable = IUFindOnSwitchIndex(&CoolerSP); - if (!strcmp(actionName, CoolerS[tmpCoolerEnable].name)) - { - LOGF_INFO("Cooler Enable is already %s", CoolerS[tmpCoolerEnable].label); - CoolerSP.s = IPS_IDLE; - IDSetSwitch(&CoolerSP, NULL); - return true; - } - - // Otherwise, let us update the switch state - IUUpdateSwitch(&CoolerSP, states, names, n); - tmpCoolerEnable = IUFindOnSwitchIndex(&CoolerSP); - - LOGF_INFO("Cooler Power is now %s", CoolerS[tmpCoolerEnable].label); - - coolerEnable = tmpCoolerEnable; - - SVB_ERROR_CODE ret; - // Change cooler state - if (SVB_SUCCESS != (ret = SVBSetControlValue(cameraID, SVB_COOLER_ENABLE, (coolerEnable == COOLER_ENABLE ? 1 : 0), SVB_FALSE))) - { - LOGF_INFO("Enabling cooler is fail.(SVB_COOLER_ENABLE:%d)", ret); - } - - CoolerSP.s = IPS_OK; - IDSetSwitch(&CoolerSP, NULL); - return true; - } - - // a switch for automatic correction of dynamic dead pixels - if (!strcmp(name, CorrectDDPSP.name)) - { - SVB_ERROR_CODE ret; - long tmpCorrectDDPEnable = 0; - SVB_BOOL bAuto; - - // Find out which state is requested by the client - const char *actionName = IUFindOnSwitchName(states, names, n); - // If same state as actionName, then we do nothing - tmpCorrectDDPEnable = IUFindOnSwitchIndex(&CorrectDDPSP); - if (!strcmp(actionName, CorrectDDPS[tmpCorrectDDPEnable].name)) - { - LOGF_INFO("Automatic correction of dynamic dead pixels is already %s", CorrectDDPS[tmpCorrectDDPEnable].label); - CorrectDDPSP.s = IPS_IDLE; - IDSetSwitch(&CorrectDDPSP, NULL); - return true; - } - - // Otherwise, let us update the switch state - IUUpdateSwitch(&CorrectDDPSP, states, names, n); - tmpCorrectDDPEnable = IUFindOnSwitchIndex(&CorrectDDPSP); - - LOGF_INFO("Automatic correction of dynamic dead pixels %s", CorrectDDPS[tmpCorrectDDPEnable].label); - - correctDDPEnable = tmpCorrectDDPEnable; - - // Change switch for automatic correction of dynamic dead pixels - if (SVB_SUCCESS != (ret = SVBSetControlValue(cameraID, SVB_BAD_PIXEL_CORRECTION_ENABLE, (correctDDPEnable == CORRECT_DDP_ENABLE ? 1 : 0), SVB_FALSE))) - { - LOGF_INFO("Setting automatic correction of dynamic dead pixels is fail.(SVB_BAD_PIXEL_CORRECTION_ENABLE:%d)", ret); - } - - CorrectDDPSP.s = IPS_OK; - IDSetSwitch(&CorrectDDPSP, NULL); - - // Get switch for automatic correction of dynamic dead pixels - if (SVB_SUCCESS == (ret = SVBGetControlValue(cameraID, SVB_BAD_PIXEL_CORRECTION_ENABLE, &tmpCorrectDDPEnable, &bAuto))) - { - LOGF_INFO("Automatic correction of dynamic dead pixels:%ld", tmpCorrectDDPEnable); - } else { - LOGF_INFO("Getting automatic correction of dynamic dead pixels is fail.(SVB_BAD_PIXEL_CORRECTION_ENABLE:%d)", ret); + LOG_INFO("Can't apply nickname change--serial number not known."); } - + NicknameTP.apply(); return true; } } - - // If we did not process the switch, let us pass it to the parent class to process it - return INDI::CCD::ISNewSwitch(dev, name, states, names, n); + return INDI::CCD::ISNewText(dev, name, texts, names, n); } -bool SVBONYCCD::SetCaptureFormat(uint8_t index) +/////////////////////////////////////////////////////////////////////// +/// Constructor for multi-camera driver. +/////////////////////////////////////////////////////////////////////// +SVBONYCCD::SVBONYCCD(const SVB_CAMERA_INFO &camInfo, const std::string &cameraName, + const std::string &serialNumber) : SVBONYBase() { - if (index >= nFrameFormat) // if there is no ON switch, set index of default format. - { - LOG_ERROR("Error, No capture format selected."); - return false; - } - SVB_IMG_TYPE newFrameFormat = switch2frameFormatDefinitionsIndex[index]; - - pthread_mutex_lock(&cameraID_mutex); - SVB_ERROR_CODE status = SVBSetOutputImageType(cameraID, newFrameFormat); - pthread_mutex_unlock(&cameraID_mutex); - - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera set frame format failed"); - return false; - } - LOGF_INFO("Capture format is now %s", CaptureFormatSP[index].label); - - frameFormat = newFrameFormat; + mCameraInfo = camInfo; + mSerialNumber = serialNumber; - // pixel depth - bitDepth = frameFormatDefinitions[newFrameFormat].isBits; - PrimaryCCD.setBPP(bitDepth); - - // Change color/grascale mode of CCD - if (HasBayer() != frameFormatDefinitions[newFrameFormat].isColor) + loadNicknames(); + if (!serialNumber.empty()) { - // Set CCD Capability - uint32_t cap = GetCCDCapability(); - if (HasBayer()) - { - cap &= ~CCD_HAS_BAYER; // if color mode exchange to monochrome - } - else + auto nickname = mNicknames[mSerialNumber]; + if (!nickname.empty()) { - cap |= CCD_HAS_BAYER; // if monochrome mode exchange to color + setDeviceName(nickname.c_str()); + mCameraName = nickname; + mNickname = nickname; + LOGF_INFO("Using nickname %s for serial number %s.", nickname.c_str(), mSerialNumber.c_str()); + return; } - SetCCDCapability(cap); - } - // update CCD parameters - updateCCDParams(); - - return true; -} - - -// -bool SVBONYCCD::saveConfigItems(FILE * fp) -{ - // Save CCD Config - INDI::CCD::saveConfigItems(fp); - - // Controls - IUSaveConfigNumber(fp, &ControlsNP[CCD_GAIN_N]); - IUSaveConfigNumber(fp, &ControlsNP[CCD_CONTRAST_N]); - IUSaveConfigNumber(fp, &ControlsNP[CCD_SHARPNESS_N]); - IUSaveConfigNumber(fp, &ControlsNP[CCD_SATURATION_N]); - IUSaveConfigNumber(fp, &ControlsNP[CCD_WBR_N]); - IUSaveConfigNumber(fp, &ControlsNP[CCD_WBG_N]); - IUSaveConfigNumber(fp, &ControlsNP[CCD_WBB_N]); - IUSaveConfigNumber(fp, &ControlsNP[CCD_GAMMA_N]); - IUSaveConfigNumber(fp, &ControlsNP[CCD_DOFFSET_N]); - IUSaveConfigSwitch(fp, &CorrectDDPSP); - - IUSaveConfigSwitch(fp, &SpeedSP); - - // bit stretching - IUSaveConfigSwitch(fp, &StretchSP); - - return true; -} - - -void SVBONYCCD::addFITSKeywords(INDI::CCDChip *targetChip, std::vector &fitsKeywords) -{ - INDI::CCD::addFITSKeywords(targetChip, fitsKeywords); - - // report controls in FITS file - fitsKeywords.push_back({"GAIN", ControlsN[CCD_GAIN_N].value, 3, "Gain"}); - fitsKeywords.push_back({"CONTRAST", ControlsN[CCD_CONTRAST_N].value, 3, "Contrast"}); - fitsKeywords.push_back({"SHARPNESS", ControlsN[CCD_SHARPNESS_N].value, 3, "Sharpness"}); - - // Add items for color camera - if(HasBayer()) - { - fitsKeywords.push_back({"SATURATION", ControlsN[CCD_SATURATION_N].value, 3, "Saturation"}); - fitsKeywords.push_back({"RED WHITE BALANCE", ControlsN[CCD_WBR_N].value, 3, "Red White Balance"}); - fitsKeywords.push_back({"GREEN WHITE BALANCE", ControlsN[CCD_WBG_N].value, 3, "Green White Balance"}); - fitsKeywords.push_back({"BLUE WHITE BALANCE", ControlsN[CCD_WBB_N].value, 3, "Blue White Balance"}); - } - - fitsKeywords.push_back({"GAMMA", ControlsN[CCD_GAMMA_N].value, 3, "Gamma"}); - fitsKeywords.push_back({"FRAME SPEED", frameSpeed, "Frame Speed"}); - fitsKeywords.push_back({"OFFSET", ControlsN[CCD_DOFFSET_N].value, 3, "Offset"}); - fitsKeywords.push_back({"16 BITS STRETCH FACTOR (BIT SHIFT)", bitStretch, "Stretch factor"}); -} - - -// -IPState SVBONYCCD::GuideNorth(uint32_t ms) -{ - pthread_mutex_lock(&cameraID_mutex); - - SVB_ERROR_CODE status = SVBPulseGuide(cameraID, SVB_GUIDE_NORTH, ms); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera guide North failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return IPS_ALERT; - } - LOG_INFO("Guiding North\n"); - - pthread_mutex_unlock(&cameraID_mutex); - - return IPS_OK; -} - - -// -IPState SVBONYCCD::GuideSouth(uint32_t ms) -{ - pthread_mutex_lock(&cameraID_mutex); - - SVB_ERROR_CODE status = SVBPulseGuide(cameraID, SVB_GUIDE_SOUTH, ms); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera guide South failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return IPS_ALERT; - } - LOG_INFO("Guiding South\n"); - - pthread_mutex_unlock(&cameraID_mutex); - - return IPS_OK; -} - - -// -IPState SVBONYCCD::GuideEast(uint32_t ms) -{ - pthread_mutex_lock(&cameraID_mutex); - - SVB_ERROR_CODE status = SVBPulseGuide(cameraID, SVB_GUIDE_EAST, ms); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera guide East failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return IPS_ALERT; - } - LOG_INFO("Guiding East\n"); - - pthread_mutex_unlock(&cameraID_mutex); - return IPS_OK; -} - - -// -IPState SVBONYCCD::GuideWest(uint32_t ms) -{ - pthread_mutex_lock(&cameraID_mutex); - - SVB_ERROR_CODE status = SVBPulseGuide(cameraID, SVB_GUIDE_WEST, ms); - if(status != SVB_SUCCESS) - { - LOG_ERROR("Error, camera guide West failed\n"); - pthread_mutex_unlock(&cameraID_mutex); - return IPS_ALERT; } - LOG_INFO("Guiding North\n"); - pthread_mutex_unlock(&cameraID_mutex); - return IPS_OK; + setDeviceName(cameraName.c_str()); + mCameraName = cameraName; } diff --git a/indi-svbony/svbony_ccd.h b/indi-svbony/svbony_ccd.h index 05f68f0fd..29936c204 100644 --- a/indi-svbony/svbony_ccd.h +++ b/indi-svbony/svbony_ccd.h @@ -1,252 +1,40 @@ /* - SVBONY CCD - SVBONY CCD Camera driver - Copyright (C) 2020 Blaise-Florentin Collin (thx8411@yahoo.fr) + ASI CCD Driver - Generic CCD skeleton Copyright (C) 2012 Jasem Mutlaq (mutlaqja@ikarustech.com) + Copyright (C) 2015-2021 Jasem Mutlaq (mutlaqja@ikarustech.com) + Copyright (C) 2018 Leonard Bottleman (leonard@whiteweasel.net) + Copyright (C) 2021 Pawel Soja (kernel32.pl@gmail.com) - Multiple device support Copyright (C) 2013 Peter Polakovic (peter.polakovic@cloudmakers.eu) + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ +#pragma once -#ifndef SVBONY_CCD_H -#define SVBONY_CCD_H +#include "svbony_base.h" -#include -#include - -#include "libsvbony/SVBCameraSDK.h" - -// WORKAROUND for bug #655 -// If defined following symbol, get buffered image data before calling StartExposure() -//#define WORKAROUND_latest_image_can_be_getten_next_time - -using namespace std; - - -///////////////////////////////////////////////// -// SVBONYCCD CLASS -// - -class SVBONYCCD : public INDI::CCD +class SVBONYCCD : public SVBONYBase { public: - SVBONYCCD(int numCamera); - virtual ~SVBONYCCD(); - - // INDI BASE - const char *getDefaultName() override; - - void ISGetProperties(const char *dev) override; - - bool initProperties() override; - bool updateProperties() override; - - bool Connect() override; - bool Disconnect() override; - - int SetTemperature(double temperature) override; - bool StartExposure(float duration) override; - bool AbortExposure() override; - - // streaming - virtual bool StartStreaming() override; - virtual bool StopStreaming() override; - static void* streamVideoHelper(void *context); - void* streamVideo(); - - // subframe - virtual bool UpdateCCDFrame(int x, int y, int w, int h) override; - - // binning - virtual bool UpdateCCDBin(int hor, int ver) override; - - // handle UI settings - virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override; - virtual bool ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n) override; - - // Guide Port - virtual IPState GuideNorth(uint32_t ms) override; - virtual IPState GuideSouth(uint32_t ms) override; - virtual IPState GuideEast(uint32_t ms) override; - virtual IPState GuideWest(uint32_t ms) override; - + explicit SVBONYCCD(const SVB_CAMERA_INFO &camInfo, const std::string &cameraName, + const std::string &serialNumber); protected: - // INDI periodic grab query - void TimerHit() override; + virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override; private: - // camera # - int num; - // camera name - char name[32]; - // camera infos - SVB_CAMERA_INFO cameraInfo; - // camera API handler - int cameraID; - // camera property - SVB_CAMERA_PROPERTY cameraProperty; - // number of camera control - int controlsNum; - // camera propertyEx - SVB_CAMERA_PROPERTY_EX cameraPropertyEx; - // exposure limits - double minExposure; - double maxExposure; - // pixel size - float pixelSize; - - // Camera Firmware version number - char cameraFirmwareVersion[65]; - // SVBONY Camera SDK version number - const char* SDKVersion; - - // hCamera mutex protection - pthread_mutex_t cameraID_mutex; - - // binning ? - bool binning; - // bit per pixel - int bitDepth; - // stretch factor x2, x4, x8, x16 (bit shift) - int bitStretch; - ISwitch StretchS[5]; - ISwitchVectorProperty StretchSP; - enum { STRETCH_OFF, STRETCH_X2, STRETCH_X4, STRETCH_X8, STRETCH_X16 }; - - // ROI offsets - int x_offset; - int y_offset; - // ROI actual size - int ROI_width; - int ROI_height; - - // streaming ? - bool streaming; - // streaming mutex and thread control - pthread_mutex_t streaming_mutex; - pthread_t primary_thread; - bool terminateThread; - - // for cooling control - double TemperatureRequest; - - // controls settings - enum - { - CCD_GAIN_N, - CCD_CONTRAST_N, - CCD_SHARPNESS_N, - CCD_SATURATION_N, - CCD_WBR_N, - CCD_WBG_N, - CCD_WBB_N, - CCD_GAMMA_N, - CCD_DOFFSET_N - }; - INumber ControlsN[9]; - INumberVectorProperty ControlsNP[9]; - // control helper - bool updateControl(int ControlType, SVB_CONTROL_TYPE SVB_Control, double values[], char *names[], int n); - - // frame speed - ISwitch SpeedS[3]; - ISwitchVectorProperty SpeedSP; - enum { SPEED_SLOW, SPEED_NORMAL, SPEED_FAST}; - int frameSpeed; - - // cooler enable - ISwitch CoolerS[2]; - ISwitchVectorProperty CoolerSP; - enum { COOLER_ENABLE = 0, COOLER_DISABLE = 1 }; - int coolerEnable; // 0:Enable, 1:Disable - - // cooler power - INumber CoolerN[1]; - INumberVectorProperty CoolerNP; - - // a switch for automatic correction of dynamic dead pixels - ISwitch CorrectDDPS[2]; - ISwitchVectorProperty CorrectDDPSP; - enum { CORRECT_DDP_ENABLE = 0, CORRECT_DDP_DISABLE = 1 }; - int correctDDPEnable; // 0:Enable, 1:Disable - - // output frame format - // the camera is able to output RGB24, but not supported by INDI - // -> ignored - // NOTE : SV305M PRO doesn't support RAW8 and RAW16, only Y8 and Y16 - size_t nFrameFormat; // number of frame format types without SVB_IMG_RGB24 - SVB_IMG_TYPE defaultFrameFormatIndex; // Index of Default ISwitch in frameFormatDefinions array. The index is the same as SVB_IMG_TYPE. - int defaultMaxBitDepth; // Maximum bit depth in camera. - typedef struct frameFormatDefinition - { - const char* isName; // Name of ISwitch - const char* isLabel; // label of ISwitch - int isBits; // bit depth - bool isColor; // true:color, false:grayscale - int isIndex; // index for ISwitch - ISState isStateDefault; // default ISState - } FrameFormatDefinition; - FrameFormatDefinition frameFormatDefinitions[SVB_IMG_RGB24] = - { - { "FORMAT_RAW8", "RAW 8 bits", 8, true, -1, ISS_OFF }, - { "FORMAT_RAW10", "RAW 10 bits", 10, true, -1, ISS_OFF }, - { "FORMAT_RAW12", "RAW 12 bits", 12, true, -1, ISS_OFF }, - { "FORMAT_RAW14", "RAW 14 bits", 14, true, -1, ISS_OFF }, - { "FORMAT_RAW16", "RAW 16 bits", 16, true, -1, ISS_OFF }, - { "FORMAT_Y8", "Y 8 bits", 8, false, -1, ISS_OFF }, - { "FORMAT_Y10", "Y 10 bits", 10, false, -1, ISS_OFF }, - { "FORMAT_Y12", "Y 12 bits", 12, false, -1, ISS_OFF }, - { "FORMAT_Y14", "Y 14 bits", 14, false, -1, ISS_OFF }, - { "FORMAT_Y16", "Y 16 bits", 16, false, -1, ISS_OFF } - }; - SVB_IMG_TYPE *switch2frameFormatDefinitionsIndex; - SVB_IMG_TYPE frameFormat; // currenct Frame format - const char* bayerPatternMapping[4] = {"RGGB", "BGGR", "GRBG", "GBRG"}; - - virtual bool SetCaptureFormat(uint8_t index) override; - - // exposure timing - int timerID; - struct timeval ExpStart; - float ExposureRequest; - float CalcTimeLeft(); - - // update CCD Params - bool updateCCDParams(); - - // Discard unretrieved exposure data - void discardVideoData(); - - // save settings - virtual bool saveConfigItems(FILE *fp) override; - - // add FITS fields - virtual void addFITSKeywords(INDI::CCDChip *targetChip, std::vector &fitsKeywords) override; - - // INDI Callbacks - friend void ::ISGetProperties(const char *dev); - friend void ::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int num); - friend void ::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int num); - friend void ::ISNewNumber(const char *dev, const char *name, double values[], char *names[], int num); - friend void ::ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n); - - // Threading - streaming mutex - pthread_cond_t cv; - pthread_mutex_t condMutex; + void loadNicknames(); + void saveNicknames(); + const std::string NICKNAME_FILE = "/.indi/SVBONYNicknames.xml"; + std::map mNicknames; }; - -#endif // SVBONY_CCD_H diff --git a/indi-svbony/svbony_helpers.h b/indi-svbony/svbony_helpers.h new file mode 100644 index 000000000..d2ee9513f --- /dev/null +++ b/indi-svbony/svbony_helpers.h @@ -0,0 +1,131 @@ +/* + ASI CCD Driver + + Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com) + Copyright (C) 2018 Leonard Bottleman (leonard@whiteweasel.net) + Copyright (C) 2021 Pawel Soja (kernel32.pl@gmail.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#pragma once +#include +#include + +namespace Helpers +{ + +const char *toString(SVB_GUIDE_DIRECTION dir) +{ + switch (dir) + { + case SVB_GUIDE_NORTH: return "North"; + case SVB_GUIDE_SOUTH: return "South"; + case SVB_GUIDE_EAST: return "East"; + case SVB_GUIDE_WEST: return "West"; + default: return "Unknown"; + } +} + +const char *toString(SVB_BAYER_PATTERN pattern) +{ + switch (pattern) + { + case SVB_BAYER_BG: return "BGGR"; + case SVB_BAYER_GR: return "GRBG"; + case SVB_BAYER_GB: return "GBRG"; + default: return "RGGB"; + } +} + + +const char *toString(SVB_ERROR_CODE code) +{ + switch (code) + { + case SVB_SUCCESS: return "SVB_SUCCESS"; + case SVB_ERROR_INVALID_INDEX: return "SVB_ERROR_INVALID_INDEX"; + case SVB_ERROR_INVALID_ID: return "SVB_ERROR_INVALID_ID"; + case SVB_ERROR_INVALID_CONTROL_TYPE: return "SVB_ERROR_INVALID_CONTROL_TYPE"; + case SVB_ERROR_CAMERA_CLOSED: return "SVB_ERROR_CAMERA_CLOSED"; + case SVB_ERROR_CAMERA_REMOVED: return "SVB_ERROR_CAMERA_REMOVED"; + case SVB_ERROR_INVALID_PATH: return "SVB_ERROR_INVALID_PATH"; + case SVB_ERROR_INVALID_FILEFORMAT: return "SVB_ERROR_INVALID_FILEFORMAT"; + case SVB_ERROR_INVALID_SIZE: return "SVB_ERROR_INVALID_SIZE"; + case SVB_ERROR_INVALID_IMGTYPE: return "SVB_ERROR_INVALID_IMGTYPE"; + case SVB_ERROR_OUTOF_BOUNDARY: return "SVB_ERROR_OUTOF_BOUNDARY"; + case SVB_ERROR_TIMEOUT: return "SVB_ERROR_TIMEOUT"; + case SVB_ERROR_INVALID_SEQUENCE: return "SVB_ERROR_INVALID_SEQUENCE"; + case SVB_ERROR_BUFFER_TOO_SMALL: return "SVB_ERROR_BUFFER_TOO_SMALL"; + case SVB_ERROR_VIDEO_MODE_ACTIVE: return "SVB_ERROR_VIDEO_MODE_ACTIVE"; + case SVB_ERROR_EXPOSURE_IN_PROGRESS: return "SVB_ERROR_EXPOSURE_IN_PROGRESS"; + case SVB_ERROR_GENERAL_ERROR: return "SVB_ERROR_GENERAL_ERROR"; + case SVB_ERROR_INVALID_DIRECTION: return "SVB_ERROR_INVALID_DIRECTION"; + case SVB_ERROR_UNKNOW_SENSOR_TYPE: return "SVB_ERROR_UNKNOW_SENSOR_TYPE"; + case SVB_ERROR_INVALID_MODE: return "SVB_ERROR_INVALID_MODE"; + case SVB_ERROR_END: return "SVB_ERROR_END"; + } + return "UNKNOWN"; +} + +const char *toString(SVB_IMG_TYPE type) +{ + switch (type) + { + case SVB_IMG_RAW8: return "SVB_IMG_RAW8"; + case SVB_IMG_RGB24: return "SVB_IMG_RGB24"; + case SVB_IMG_RAW16: return "SVB_IMG_RAW16"; + case SVB_IMG_Y8: return "SVB_IMG_Y8"; + case SVB_IMG_END: return "SVB_IMG_END"; + default: return "UNKNOWN"; + } +} + +const char *toPrettyString(SVB_IMG_TYPE type) +{ + switch (type) + { + case SVB_IMG_RAW8: return "Raw 8 bit"; + case SVB_IMG_RGB24: return "RGB 24"; + case SVB_IMG_RAW16: return "Raw 16 bit"; + case SVB_IMG_Y8: return "Luma"; + case SVB_IMG_END: return "END"; + default: return "UNKNOWN"; + } +} + +INDI_PIXEL_FORMAT pixelFormat(SVB_IMG_TYPE type, SVB_BAYER_PATTERN pattern, bool isColor) +{ + if (isColor == false) + return INDI_MONO; + + switch (type) + { + case SVB_IMG_RGB24: return INDI_RGB; + case SVB_IMG_Y8: return INDI_MONO; + default:; // see below + } + + switch (pattern) + { + case SVB_BAYER_RG: return INDI_BAYER_RGGB; + case SVB_BAYER_BG: return INDI_BAYER_BGGR; + case SVB_BAYER_GR: return INDI_BAYER_GRBG; + case SVB_BAYER_GB: return INDI_BAYER_GBRG; + } + + return INDI_MONO; +} + +} From fb9435fcfc5f08c542243138dedf9bc99bc8fb7c Mon Sep 17 00:00:00 2001 From: Jasem Mutlaq Date: Fri, 22 Sep 2023 21:30:22 +0300 Subject: [PATCH 2/6] Add copyright --- indi-svbony/svbony_ccd.cpp | 1 + indi-svbony/svbony_ccd.h | 1 + 2 files changed, 2 insertions(+) diff --git a/indi-svbony/svbony_ccd.cpp b/indi-svbony/svbony_ccd.cpp index fe298150a..8b50c4036 100644 --- a/indi-svbony/svbony_ccd.cpp +++ b/indi-svbony/svbony_ccd.cpp @@ -4,6 +4,7 @@ Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com) Copyright (C) 2018 Leonard Bottleman (leonard@whiteweasel.net) Copyright (C) 2021 Pawel Soja (kernel32.pl@gmail.com) + Copyright (C) 2020 Blaise-Florentin Collin (thx8411@yahoo.fr) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/indi-svbony/svbony_ccd.h b/indi-svbony/svbony_ccd.h index 29936c204..0d3742a67 100644 --- a/indi-svbony/svbony_ccd.h +++ b/indi-svbony/svbony_ccd.h @@ -4,6 +4,7 @@ Copyright (C) 2015-2021 Jasem Mutlaq (mutlaqja@ikarustech.com) Copyright (C) 2018 Leonard Bottleman (leonard@whiteweasel.net) Copyright (C) 2021 Pawel Soja (kernel32.pl@gmail.com) + Copyright (C) 2020 Blaise-Florentin Collin (thx8411@yahoo.fr) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public From fd5d3bc21c66228abd8843ce06c422e52f5d4eb4 Mon Sep 17 00:00:00 2001 From: Jasem Mutlaq Date: Fri, 22 Sep 2023 22:05:38 +0300 Subject: [PATCH 3/6] fix macos library --- libasi/mac/libASICamera2.bin | Bin 5305920 -> 5305920 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/libasi/mac/libASICamera2.bin b/libasi/mac/libASICamera2.bin index a52b559da25c351ea355279157f3feb38bcba9e6..332aa28cffa7a4adb8bc2c831b918db2354a8038 100644 GIT binary patch delta 423 zcmZwCNlpR*07TJlqyZI6(C!uyoF|+IP@G%EdEyA3fh(AlYr_I>p$Sp8Bwhd$4`5sv z6G^;)3rmeVD|ze6uht)YRk^hz=6TtP-1yzJVHhzn zm}!(g0;xs|-1yzoUWd09)E;*$pPizNXGN=O=|Ntz`rEdptkh(x7L+Qk+}Tyki5@+|wdv9O3YJ)of delta 427 zcmaLSxk>{86ouhTOfs&=QD-#9#C;q0eT`<+xUZ$12-*b%UqLuNK#DwuLe#VYTZ>c{ zDJ-l~2!fTR$0A_mg|E5I;m?0O`}1;dTShADSa*SF+~@O!#gOuOsjKsg%j>gr(T=3Q z?AOdg$aT!}^UVooznvHJrVzU=#0>FEg#;uhAu**=s-#+Kq*m$#QZJS?NTW1~Esiux zi-e_BBGM*N`Q2=n4(XIG>6RYpl|Jd0fm|Urn0z-*A00OnjCyMOFEdjNx}Sd&bES}L znS@IW5yQj?F-nXP Date: Thu, 28 Sep 2023 15:11:24 +0300 Subject: [PATCH 4/6] Fix bayer issue --- indi-svbony/svbony_base.cpp | 17 ++++++++++++----- indi-svbony/svbony_base.h | 5 +++-- indi-svbony/svbony_ccd.cpp | 4 ++-- indi-svbony/svbony_ccd.h | 4 ++-- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/indi-svbony/svbony_base.cpp b/indi-svbony/svbony_base.cpp index d630f2c77..113504c17 100644 --- a/indi-svbony/svbony_base.cpp +++ b/indi-svbony/svbony_base.cpp @@ -1,9 +1,10 @@ /* - SVB Camera Base + SVBony Camera Driver - Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com) + Copyright (C) 2023 Jasem Mutlaq (mutlaqja@ikarustech.com) Copyright (C) 2018 Leonard Bottleman (leonard@whiteweasel.net) Copyright (C) 2021 Pawel Soja (kernel32.pl@gmail.com) + Copyright (C) 2020 Blaise-Florentin Collin (thx8411@yahoo.fr) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -252,8 +253,6 @@ bool SVBONYBase::initProperties() VideoFormatSP.fill(getDeviceName(), "CCD_VIDEO_FORMAT", "Format", CONTROL_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); - IUSaveText(&BayerT[2], getBayerString()); - ADCDepthNP[0].fill("BITS", "Bits", "%2.0f", 0, 32, 1, 16); ADCDepthNP.fill(getDeviceName(), "ADC_DEPTH", "ADC Depth", IMAGE_INFO_TAB, IP_RO, 60, IPS_IDLE); @@ -381,7 +380,6 @@ bool SVBONYBase::Connect() { LOGF_DEBUG("Attempting to open %s...", mCameraName.c_str()); - auto ret = SVBOpenCamera(mCameraInfo.CameraID); if (ret != SVB_SUCCESS) @@ -1038,7 +1036,16 @@ int SVBONYBase::grabImage(float duration) if (mCameraProperty.IsColorCam == false || type == SVB_IMG_Y8 || type == SVB_IMG_RGB24) SetCCDCapability(GetCCDCapability() & ~CCD_HAS_BAYER); else + { SetCCDCapability(GetCCDCapability() | CCD_HAS_BAYER); + auto bayerString = getBayerString(); + // Send if different + if (strcmp(bayerString, BayerT[2].text)) + { + IUSaveText(&BayerT[2], bayerString); + IDSetText(&BayerTP, nullptr); + } + } if (duration > VERBOSE_EXPOSURE) LOG_INFO("Download complete."); diff --git a/indi-svbony/svbony_base.h b/indi-svbony/svbony_base.h index 73fe73d5e..ee92984eb 100644 --- a/indi-svbony/svbony_base.h +++ b/indi-svbony/svbony_base.h @@ -1,9 +1,10 @@ /* - ASI CCD Driver + SVBony Camera Driver - Copyright (C) 2015-2021 Jasem Mutlaq (mutlaqja@ikarustech.com) + Copyright (C) 2023 Jasem Mutlaq (mutlaqja@ikarustech.com) Copyright (C) 2018 Leonard Bottleman (leonard@whiteweasel.net) Copyright (C) 2021 Pawel Soja (kernel32.pl@gmail.com) + Copyright (C) 2020 Blaise-Florentin Collin (thx8411@yahoo.fr) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/indi-svbony/svbony_ccd.cpp b/indi-svbony/svbony_ccd.cpp index 8b50c4036..b22446aa9 100644 --- a/indi-svbony/svbony_ccd.cpp +++ b/indi-svbony/svbony_ccd.cpp @@ -1,7 +1,7 @@ /* - SVB CCD Driver + SVBony Camera Driver - Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com) + Copyright (C) 2023 Jasem Mutlaq (mutlaqja@ikarustech.com) Copyright (C) 2018 Leonard Bottleman (leonard@whiteweasel.net) Copyright (C) 2021 Pawel Soja (kernel32.pl@gmail.com) Copyright (C) 2020 Blaise-Florentin Collin (thx8411@yahoo.fr) diff --git a/indi-svbony/svbony_ccd.h b/indi-svbony/svbony_ccd.h index 0d3742a67..4e68bb333 100644 --- a/indi-svbony/svbony_ccd.h +++ b/indi-svbony/svbony_ccd.h @@ -1,7 +1,7 @@ /* - ASI CCD Driver + SVBony Camera Driver - Copyright (C) 2015-2021 Jasem Mutlaq (mutlaqja@ikarustech.com) + Copyright (C) 2023 Jasem Mutlaq (mutlaqja@ikarustech.com) Copyright (C) 2018 Leonard Bottleman (leonard@whiteweasel.net) Copyright (C) 2021 Pawel Soja (kernel32.pl@gmail.com) Copyright (C) 2020 Blaise-Florentin Collin (thx8411@yahoo.fr) From a20e532d7b43962c35d17e84a866b4d98090c708 Mon Sep 17 00:00:00 2001 From: Jasem Mutlaq Date: Thu, 28 Sep 2023 15:19:12 +0300 Subject: [PATCH 5/6] SVBONY Camera has the ability to automatically save/load configuration files. Loading a settings file that has been modified by another application may result in unintended behavior. Therefore, when using the SVBONY Camera, you should set the settings to default, disable automatic saving/loading of the configuration file, and initialize the parameters again according to the configuration file of the INDI SVBONY CCD Driver. Proposed by @jctk --- indi-svbony/svbony_base.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/indi-svbony/svbony_base.cpp b/indi-svbony/svbony_base.cpp index 113504c17..f4c4f4de0 100644 --- a/indi-svbony/svbony_base.cpp +++ b/indi-svbony/svbony_base.cpp @@ -388,6 +388,20 @@ bool SVBONYBase::Connect() return false; } + // Restore settings + ret = SVBRestoreDefaultParam(mCameraInfo.CameraID); + if (ret != SVB_SUCCESS) + { + LOGF_WARN("Error Initializing the CCD (%s).", Helpers::toString(ret)); + } + + ret = SVBSetAutoSaveParam(mCameraInfo.CameraID, SVB_FALSE); + if (ret != SVB_SUCCESS) + { + LOGF_WARN("Error Initializing the CCD (%s).", Helpers::toString(ret)); + } + + // Get Camera Property ret = SVBGetCameraProperty(mCameraInfo.CameraID, &mCameraProperty); if (ret != SVB_SUCCESS) { From 8c6801dd3944b63143ba45b3b3d55f959fc251b0 Mon Sep 17 00:00:00 2001 From: Jasem Mutlaq Date: Thu, 28 Sep 2023 16:12:15 +0300 Subject: [PATCH 6/6] Try to discard frame after abort --- indi-svbony/svbony_base.cpp | 21 ++++++++++++++++----- indi-svbony/svbony_base.h | 3 ++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/indi-svbony/svbony_base.cpp b/indi-svbony/svbony_base.cpp index f4c4f4de0..e01ead44b 100644 --- a/indi-svbony/svbony_base.cpp +++ b/indi-svbony/svbony_base.cpp @@ -139,6 +139,13 @@ void SVBONYBase::workerExposure(const std::atomic_bool &isAboutToQuit, float dur SVB_ERROR_CODE status = SVB_ERROR_END; do { + if (isAboutToQuit) + { + // Discard from buffer but do not send + grabImage(0, false); + return; + } + float delay = 0.1; float timeLeft = std::max(duration - exposureTimer.elapsed() / 1000.0, 0.0); @@ -161,8 +168,6 @@ void SVBONYBase::workerExposure(const std::atomic_bool &isAboutToQuit, float dur { PrimaryCCD.setExposureLeft(timeLeft); } - else if (isAboutToQuit) - return; else { auto imageBuffer = PrimaryCCD.getFrameBuffer(); @@ -988,7 +993,7 @@ bool SVBONYBase::UpdateCCDBin(int binx, int biny) /* Downloads the image from the CCD. N.B. No processing is done on the image */ -int SVBONYBase::grabImage(float duration) +int SVBONYBase::grabImage(float duration, bool send) { SVB_ERROR_CODE ret = SVB_SUCCESS; @@ -1044,10 +1049,17 @@ int SVBONYBase::grabImage(float duration) } guard.unlock(); + if (send) + sendImage(type, duration); + return 0; +} + +void SVBONYBase::sendImage(SVB_IMG_TYPE type, float duration) +{ PrimaryCCD.setNAxis(type == SVB_IMG_RGB24 ? 3 : 2); // If mono camera or we're sending Luma or RGB, turn off bayering - if (mCameraProperty.IsColorCam == false || type == SVB_IMG_Y8 || type == SVB_IMG_RGB24) + if (mCameraProperty.IsColorCam == false || type >= SVB_IMG_Y8) SetCCDCapability(GetCCDCapability() & ~CCD_HAS_BAYER); else { @@ -1065,7 +1077,6 @@ int SVBONYBase::grabImage(float duration) LOG_INFO("Download complete."); ExposureComplete(&PrimaryCCD); - return 0; } /* The timer call back is used for temperature monitoring */ diff --git a/indi-svbony/svbony_base.h b/indi-svbony/svbony_base.h index ee92984eb..9353b9179 100644 --- a/indi-svbony/svbony_base.h +++ b/indi-svbony/svbony_base.h @@ -90,7 +90,8 @@ class SVBONYBase : public INDI::CCD void workerExposure(const std::atomic_bool &isAboutToQuit, float duration); /** Get image from CCD and send it to client */ - int grabImage(float duration); + int grabImage(float duration, bool send = true); + void sendImage(SVB_IMG_TYPE type, float duration); protected: double mTargetTemperature;