/* SV305 CCD SVBONY SV305 Camera driver Copyright (C) 2020-2021 Blaise-Florentin Collin (thx8411@yahoo.fr) Generic CCD skeleton Copyright (C) 2012 Jasem Mutlaq (mutlaqja@ikarustech.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 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 #include #include #include #include #include #include "config.h" #include "indidevapi.h" #include "eventloop.h" #include "stream/streammanager.h" #include "libsv305/SVBCameraSDK.h" #include "sv305_ccd.h" // streaming mutex static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; static pthread_mutex_t condMutex = PTHREAD_MUTEX_INITIALIZER; 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 Sv305CCD object for each camera for(int i = 0; i < cameraCount; i++) { cameras.push_back(std::unique_ptr(new Sv305CCD(i))); } } } loader; ////////////////////////////////////////////////// // SV305 CLASS // // Sv305CCD::Sv305CCD(int numCamera) { num = numCamera; // set driver version setVersion(SV305_VERSION_MAJOR, SV305_VERSION_MINOR); // Get camera informations 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); // mutex init pthread_mutex_init(&cameraID_mutex, NULL); pthread_mutex_init(&streaming_mutex, NULL); } // Sv305CCD::~Sv305CCD() { // mutex destroy pthread_mutex_destroy(&cameraID_mutex); pthread_mutex_destroy(&streaming_mutex); } // const char *Sv305CCD::getDefaultName() { return "SVBONY SV305"; } // bool Sv305CCD::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; // SV305 is a color camera if(strcmp(cameraInfo.FriendlyName, "SVBONY SV305")==0) { cap|= CCD_HAS_BAYER; } // SV305 Pro is a color camera and has an ST4 port if(strcmp(cameraInfo.FriendlyName, "SVBONY SV305PRO")==0) { cap|= CCD_HAS_BAYER; cap|= CCD_HAS_ST4_PORT; } // SV305M Pro is a mono camera and has an ST4 port if(strcmp(cameraInfo.FriendlyName, "SVBONY SV305M PRO")==0) { cap|= CCD_HAS_ST4_PORT; } SetCCDCapability(cap); addConfigurationControl(); addDebugControl(); return true; } // void Sv305CCD::ISGetProperties(const char *dev) { INDI::CCD::ISGetProperties(dev); } // bool Sv305CCD::updateProperties() { INDI::CCD::updateProperties(); if (isConnected()) { // 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]); // define frame format defineProperty(&FormatSP); defineProperty(&SpeedSP); // stretch factor defineProperty(&StretchSP); timerID = SetTimer(getCurrentPollingPeriod()); } else { rmTimer(timerID); // 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); // delete frame format deleteProperty(FormatSP.name); deleteProperty(SpeedSP.name); // stretch factor deleteProperty(StretchSP.name); } return true; } // bool Sv305CCD::Connect() { // boolean init streaming = false; LOG_INFO("Attempting to find the SVBONY SV305 CCD...\n"); pthread_mutex_lock(&cameraID_mutex); // open camera status = SVBOpenCamera(cameraID); if (status != SVB_SUCCESS) { LOG_ERROR("Error, open camera failed.\n"); pthread_mutex_unlock(&cameraID_mutex); return false; } // wait a bit for the camera to get ready usleep(0.5 * 1e6); // 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; } // // TEST // SDK issue on SV305M Pro ? /* // 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; } */ pixelSize=4.3; // END // // 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 , (double)(1 * 1000000), SVB_FALSE); // read controls and feed UI for(int i=0; i 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); // set exposure time (s -> us) status = SVBSetControlValue(cameraID, SVB_EXPOSURE , (double)(duration * 1000000), 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; } // bool Sv305CCD::AbortExposure() { LOG_INFO("Abort exposure\n"); InExposure = false; pthread_mutex_lock(&cameraID_mutex); // ********* // TODO // ********* // stop camera 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 Sv305CCD::StartStreaming() { LOG_INFO("framing\n"); // stream init Streamer->setPixelFormat(INDI_BAYER_GRBG, bitDepth); Streamer->setSize(PrimaryCCD.getXRes(), PrimaryCCD.getYRes()); // streaming exposure time ExposureRequest = 1.0 / Streamer->getTargetFPS(); pthread_mutex_lock(&cameraID_mutex); // set exposure time (s -> us) status = SVBSetControlValue(cameraID, SVB_EXPOSURE , (double)(ExposureRequest * 1000000), 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 soft trigger mode failed\n"); pthread_mutex_unlock(&cameraID_mutex); return false; } LOG_INFO("Camera soft trigger mode\n"); pthread_mutex_unlock(&cameraID_mutex); pthread_mutex_lock(&condMutex); streaming = true; pthread_mutex_unlock(&condMutex); pthread_cond_signal(&cv); LOG_INFO("Streaming started\n"); return true; } // bool Sv305CCD::StopStreaming() { LOG_INFO("stop framing\n"); pthread_mutex_lock(&cameraID_mutex); // 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"); pthread_mutex_unlock(&cameraID_mutex); pthread_mutex_lock(&condMutex); streaming = false; pthread_mutex_unlock(&condMutex); pthread_cond_signal(&cv); LOG_INFO("Streaming stopped\n"); return true; } // void* Sv305CCD::streamVideoHelper(void * context) { return static_cast(context)->streamVideo(); } // void* Sv305CCD::streamVideo() { auto start = std::chrono::high_resolution_clock::now(); auto finish = std::chrono::high_resolution_clock::now(); while (true) { pthread_mutex_lock(&condMutex); while (!streaming) { pthread_cond_wait(&cv, &condMutex); // ??? ExposureRequest = 1.0 / Streamer->getTargetFPS(); } if (terminateThread) break; pthread_mutex_unlock(&condMutex); unsigned char* imageBuffer = PrimaryCCD.getFrameBuffer(); pthread_mutex_lock(&cameraID_mutex); // get the frame status = SVBGetVideoData(cameraID, imageBuffer, PrimaryCCD.getFrameBufferSize(), 100000 ); pthread_mutex_unlock(&cameraID_mutex); finish = std::chrono::high_resolution_clock::now(); // stretching 12bits depth to 16bits depth if(bitDepth==16 && (bitStretch != 0)) { u_int16_t* tmp=(u_int16_t*)imageBuffer; for(int i=0; inewFrame(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 Sv305CCD::UpdateCCDFrame(int x, int y, int w, int h) { if((x + w) > cameraProperty.MaxWidth || (y + h) > cameraProperty.MaxHeight) { LOG_ERROR("Error : Subframe out of range"); return false; } pthread_mutex_lock(&cameraID_mutex); 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; } LOG_INFO("Subframe set\n"); pthread_mutex_unlock(&cameraID_mutex); // update streamer long bin_width = w / PrimaryCCD.getBinX(); long bin_height = h / PrimaryCCD.getBinY(); bin_width = bin_width - (bin_width % 2); bin_height = bin_height - (bin_height % 2); if (Streamer->isBusy()) { LOG_WARN("Cannot change binning while streaming/recording.\n"); } else { Streamer->setSize(bin_width, bin_height); } LOG_INFO("Subframe changed\n"); return INDI::CCD::UpdateCCDFrame(x, y, w, h); } // binning bool Sv305CCD::UpdateCCDBin(int hor, int ver) { if(hor == 1 && ver == 1) binning = false; else binning = true; // update streamer uint32_t bin_width = PrimaryCCD.getSubW() / hor; uint32_t bin_height = PrimaryCCD.getSubH() / ver; if (Streamer->isBusy()) { LOG_WARN("Cannot change binning while streaming/recording.\n"); } else { Streamer->setSize(bin_width, bin_height); } LOG_INFO("Binning changed"); // hardware binning not supported. Using software binning return INDI::CCD::UpdateCCDBin(hor, ver); } // float Sv305CCD::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 Sv305CCD::TimerHit() { int timerID = -1; long timeleft; if (isConnected() == false) return; // No need to reset timer if we are not connected anymore if (InExposure) { timeleft = CalcTimeLeft(); if (timeleft < 1.0) { if (timeleft > 0.25) { // a quarter of a second or more // just set a tighter timer timerID = SetTimer(250); } else { if (timeleft > 0.07) { // use an even tighter timer timerID = SetTimer(50); } else { pthread_mutex_lock(&cameraID_mutex); unsigned char* imageBuffer = PrimaryCCD.getFrameBuffer(); status = SVBGetVideoData(cameraID, imageBuffer, PrimaryCCD.getFrameBufferSize(), 100 ); while(status != SVB_SUCCESS) { pthread_mutex_unlock(&cameraID_mutex); usleep(100000); pthread_mutex_lock(&cameraID_mutex); status = SVBGetVideoData(cameraID, imageBuffer, PrimaryCCD.getFrameBufferSize(), 100 ); LOG_DEBUG("Wait..."); } pthread_mutex_unlock(&cameraID_mutex); // 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