Created
February 27, 2014 19:08
-
-
Save peterstrapp/9256895 to your computer and use it in GitHub Desktop.
Thrown together QHY5L-II-C driver
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
QHY5L-II-C driver | |
Copyright (C) 2014 Peter Strapp ([email protected]) | |
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 <memory> | |
#include <time.h> | |
#include <math.h> | |
#include <unistd.h> | |
#include <sys/time.h> | |
#include <libqhyccd/common.h> | |
#include <libqhyccd/interguider.h> | |
#include <opencv/cv.h> | |
#include <opencv/highgui.h> | |
#include "indidevapi.h" | |
#include "eventloop.h" | |
#include "qhy5ii_ccd.h" | |
#define MAX_CCD_TEMP 45 /* Max CCD temperature */ | |
#define MIN_CCD_TEMP -55 /* Min CCD temperature */ | |
#define MAX_X_BIN 16 /* Max Horizontal binning */ | |
#define MAX_Y_BIN 16 /* Max Vertical binning */ | |
#define MAX_PIXELS 4096 /* Max number of pixels in one dimension */ | |
#define POLLMS 1000 /* Polling time (ms) */ | |
#define TEMP_THRESHOLD .25 /* Differential temperature threshold (C)*/ | |
#define MAX_DEVICES 20 /* Max device cameraCount */ | |
static int cameraCount; | |
static QHY5IICCD *cameras[MAX_DEVICES]; | |
//int psw = 1024,psh = 768,psbpp = 8,pschannels = 3; | |
int psw = 640,psh = 480,psbpp = 8,pschannels = 1; | |
static struct { | |
int vid; | |
int pid; | |
const char *name; | |
} deviceTypes[] = { { 0x1618, 0x0921, "QHY5L-II-C" } }; | |
static void cleanup() { | |
for (int i = 0; i < cameraCount; i++) { | |
delete cameras[i]; | |
} | |
} | |
void ISInit() { | |
static bool isInit = false; | |
if (!isInit) { | |
cameras[0] = new QHY5IICCD("QHY5L-II-C"); | |
cameraCount = 1; | |
atexit(cleanup); | |
isInit = true; | |
} | |
} | |
void ISGetProperties(const char *dev) { | |
ISInit(); | |
for (int i = 0; i < cameraCount; i++) { | |
QHY5IICCD *camera = cameras[i]; | |
if (dev == NULL || !strcmp(dev, camera->name)) { | |
camera->ISGetProperties(dev); | |
if (dev != NULL) | |
break; | |
} | |
} | |
} | |
void ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int num) { | |
ISInit(); | |
for (int i = 0; i < cameraCount; i++) { | |
QHY5IICCD *camera = cameras[i]; | |
if (dev == NULL || !strcmp(dev, camera->name)) { | |
camera->ISNewSwitch(dev, name, states, names, num); | |
if (dev != NULL) | |
break; | |
} | |
} | |
} | |
void ISNewText(const char *dev, const char *name, char *texts[], char *names[], int num) { | |
ISInit(); | |
for (int i = 0; i < cameraCount; i++) { | |
QHY5IICCD *camera = cameras[i]; | |
if (dev == NULL || !strcmp(dev, camera->name)) { | |
camera->ISNewText(dev, name, texts, names, num); | |
if (dev != NULL) | |
break; | |
} | |
} | |
} | |
void ISNewNumber(const char *dev, const char *name, double values[], char *names[], int num) { | |
ISInit(); | |
for (int i = 0; i < cameraCount; i++) { | |
QHY5IICCD *camera = cameras[i]; | |
if (dev == NULL || !strcmp(dev, camera->name)) { | |
camera->ISNewNumber(dev, name, values, names, num); | |
if (dev != NULL) | |
break; | |
} | |
} | |
} | |
void ISNewBLOB(const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n) { | |
INDI_UNUSED(dev); | |
INDI_UNUSED(name); | |
INDI_UNUSED(sizes); | |
INDI_UNUSED(blobsizes); | |
INDI_UNUSED(blobs); | |
INDI_UNUSED(formats); | |
INDI_UNUSED(names); | |
INDI_UNUSED(n); | |
} | |
void ISSnoopDevice(XMLEle *root) { | |
INDI_UNUSED(root); | |
} | |
QHY5IICCD::QHY5IICCD(const char *name) { | |
// this->device = device; | |
snprintf(this->name, 32, "%s CCD", name); | |
setDeviceName(this->name); | |
sim = false; | |
} | |
QHY5IICCD::~QHY5IICCD() { | |
} | |
const char * QHY5IICCD::getDefaultName() { | |
return name; | |
} | |
bool QHY5IICCD::initProperties() { | |
INDI::CCD::initProperties(); | |
IUFillSwitch(&ResetS[0], "RESET", "Reset", ISS_OFF); | |
IUFillSwitchVector(&ResetSP, ResetS, 1, getDeviceName(), "FRAME_RESET", "Frame Values", IMAGE_SETTINGS_TAB, IP_WO, ISR_1OFMANY, 0, IPS_IDLE); | |
return true; | |
} | |
void QHY5IICCD::ISGetProperties(const char *dev) { | |
INDI::CCD::ISGetProperties(dev); | |
// Add Debug, Simulator, and Configuration controls | |
addAuxControls(); | |
} | |
bool QHY5IICCD::updateProperties() { | |
INDI::CCD::updateProperties(); | |
if (isConnected()) { | |
defineSwitch(&ResetSP); | |
setupParams(); | |
timerID = SetTimer(POLLMS); | |
} else { | |
deleteProperty(ResetSP.name); | |
rmTimer(timerID); | |
} | |
return true; | |
} | |
bool QHY5IICCD::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) { | |
if (strcmp(dev, getDeviceName()) == 0) { | |
/* Reset */ | |
if (!strcmp(name, ResetSP.name)) { | |
if (IUUpdateSwitch(&ResetSP, states, names, n) < 0) | |
return false; | |
resetFrame(); | |
return true; | |
} | |
} | |
// Nobody has claimed this, so, ignore it | |
return INDI::CCD::ISNewSwitch(dev, name, states, names, n); | |
} | |
bool QHY5IICCD::Connect() { | |
IDMessage(getDeviceName(), "Attempting to find the Generic CCD..."); | |
if (isDebug()) { | |
IDLog("Connecting CCD\n"); | |
IDLog("Attempting to find the camera\n"); | |
} | |
int ret = OpenCameraByID(DEVICETYPE_QHY5LII); | |
SetTransferBit(psbpp); | |
SetSpeed(true); | |
SetUSBTraffic(30); | |
SetGain(20); | |
SetOffset(120); | |
SetResolution(psw,psh); | |
IDMessage(getDeviceName(), "CCD is online. Retrieving basic data."); | |
if (isDebug()) | |
IDLog("CCD is online. Retrieving basic data.\n"); | |
return true; | |
} | |
bool QHY5IICCD::Disconnect() { | |
StopLive(); | |
CloseCamera(); | |
IDMessage(getDeviceName(), "CCD is offline."); | |
return true; | |
} | |
bool QHY5IICCD::setupParams() { | |
if (isDebug()) | |
IDLog("In setupParams\n"); | |
float x_pixel_size, y_pixel_size; | |
int bit_depth = 8; | |
int x_1, y_1, x_2, y_2; | |
/////////////////////////// | |
// 1. Get Pixel size | |
/////////////////////////// | |
x_pixel_size = 3.75; | |
y_pixel_size = 3.75; | |
/////////////////////////// | |
// 2. Get Frame | |
/////////////////////////// | |
x_1 = y_1 = 0; | |
x_2 = psw; | |
y_2 = psh; | |
/////////////////////////// | |
// 3. Get temperature | |
/////////////////////////// | |
TemperatureN[0].value = GetTemp(); | |
IDMessage(getDeviceName(), "The CCD Temperature is %f.\n", TemperatureN[0].value); | |
IDSetNumber(&TemperatureNP, NULL); | |
if (isDebug()) | |
IDLog("The CCD Temperature is %f.\n", TemperatureN[0].value); | |
/////////////////////////// | |
// 4. Get temperature | |
/////////////////////////// | |
bit_depth = psbpp; | |
SetCCDParams(x_2 - x_1, y_2 - y_1, bit_depth, x_pixel_size, y_pixel_size); | |
minDuration = 0.05; | |
int nbuf; | |
nbuf = PrimaryCCD.getXRes() * PrimaryCCD.getYRes() * PrimaryCCD.getBPP() / 8 * pschannels; // this is pixel cameraCount | |
nbuf += 512; // leave a little extra at the end | |
PrimaryCCD.setFrameBufferSize(nbuf); | |
return true; | |
} | |
int QHY5IICCD::SetTemperature(double temperature) | |
{ | |
if (fabs(temperature- TemperatureN[0].value)) | |
return 1; | |
TemperatureRequest = temperature; | |
DEBUGF(INDI::Logger::DBG_SESSION, "Setting CCD temperature to %+06.2f C", temperature); | |
return 0; | |
} | |
bool QHY5IICCD::StartExposure(float duration) | |
{ | |
if (duration < minDuration) | |
{ | |
DEBUGF(INDI::Logger::DBG_WARNING, "Exposure shorter than minimum duration %g s requested. \n Setting exposure time to %g s.", duration, minDuration); | |
duration = minDuration; | |
} | |
if (imageFrameType == CCDChip::BIAS_FRAME) | |
{ | |
duration = minDuration; | |
DEBUGF(INDI::Logger::DBG_SESSION, "Bias Frame (s) : %g\n", minDuration); | |
} | |
IDLog("Starting exposure (%f)...\n", duration); | |
SetExposeTime(duration * 10 *1000); | |
BeginLive(); | |
PrimaryCCD.setExposureDuration(duration); | |
ExposureRequest = duration; | |
gettimeofday(&ExpStart, NULL); | |
DEBUGF(INDI::Logger::DBG_SESSION, "Taking a %g seconds frame...", ExposureRequest); | |
InExposure = true; | |
return true; | |
} | |
bool QHY5IICCD::AbortExposure() { | |
InExposure = false; | |
return true; | |
} | |
bool QHY5IICCD::UpdateCCDFrameType(CCDChip::CCD_FRAME fType) { | |
int err = 0; | |
CCDChip::CCD_FRAME imageFrameType = PrimaryCCD.getFrameType(); | |
if (fType == imageFrameType || sim) | |
return true; | |
switch (imageFrameType) { | |
case CCDChip::BIAS_FRAME: | |
case CCDChip::DARK_FRAME: | |
break; | |
case CCDChip::LIGHT_FRAME: | |
case CCDChip::FLAT_FRAME: | |
break; | |
} | |
PrimaryCCD.setFrameType(fType); | |
return true; | |
} | |
bool QHY5IICCD::UpdateCCDFrame(int x, int y, int w, int h) { | |
/* Add the X and Y offsets */ | |
long x_1 = x; | |
long y_1 = y; | |
long bin_width = x_1 + (w / PrimaryCCD.getBinX()); | |
long bin_height = y_1 + (h / PrimaryCCD.getBinY()); | |
if (bin_width > PrimaryCCD.getXRes() / PrimaryCCD.getBinX()) { | |
IDMessage(getDeviceName(), "Error: invalid width requested %d", w); | |
return false; | |
} else if (bin_height > PrimaryCCD.getYRes() / PrimaryCCD.getBinY()) { | |
IDMessage(getDeviceName(), "Error: invalid height request %d", h); | |
return false; | |
} | |
if (isDebug()) | |
IDLog("The Final image area is (%ld, %ld), (%ld, %ld)\n", x_1, y_1, bin_width, bin_height); | |
// Set UNBINNED coords | |
PrimaryCCD.setFrame(x_1, y_1, w, h); | |
int nbuf; | |
nbuf = (bin_width * bin_height * PrimaryCCD.getBPP() / 8); // this is pixel count | |
nbuf += 512; // leave a little extra at the end | |
PrimaryCCD.setFrameBufferSize(nbuf); | |
if (isDebug()) | |
IDLog("Setting frame buffer size to %d bytes.\n", nbuf); | |
return true; | |
} | |
bool QHY5IICCD::UpdateCCDBin(int binx, int biny) { | |
PrimaryCCD.setBin(binx, biny); | |
return UpdateCCDFrame(PrimaryCCD.getSubX(), PrimaryCCD.getSubY(), PrimaryCCD.getSubW(), PrimaryCCD.getSubH()); | |
} | |
float QHY5IICCD::CalcTimeLeft() { | |
double timesince; | |
double timeleft; | |
struct timeval now; | |
gettimeofday(&now, NULL); | |
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; | |
} | |
int QHY5IICCD::grabImage() { | |
char * image = PrimaryCCD.getFrameBuffer(); | |
int width = PrimaryCCD.getSubW() / PrimaryCCD.getBinX() * PrimaryCCD.getBPP() / 8; | |
int height = PrimaryCCD.getSubH() / PrimaryCCD.getBinY(); | |
int bpp = PrimaryCCD.getBPP(); | |
IDLog("Grabbing Image...\n"); | |
unsigned char ImgData[width*height*pschannels]; | |
GetImageData(width, height, bpp, pschannels, ImgData); | |
IDLog("Transfering image.\n"); | |
// for(int i=0; i< width*height*pschannels; i++) | |
// { | |
// image[i] = ImgData[i]; | |
// } | |
PrimaryCCD.setFrameBuffer((char*)ImgData); | |
PrimaryCCD.setFrameBufferSize(width*height*pschannels, false); | |
PrimaryCCD.setResolution(width, height); | |
PrimaryCCD.setFrame(0, 0, width, height); | |
PrimaryCCD.setNAxis(2); | |
PrimaryCCD.setBPP(bpp); | |
IDLog("Image grabbed.\n"); | |
IDMessage(getDeviceName(), "Download complete."); | |
if (isDebug()) | |
IDLog("Download complete."); | |
ExposureComplete(&PrimaryCCD); | |
return 0; | |
} | |
void QHY5IICCD::addFITSKeywords(fitsfile *fptr, CCDChip *targetChip) { | |
INDI::CCD::addFITSKeywords(fptr, targetChip); | |
int status = 0; | |
fits_update_key_s(fptr, TDOUBLE, "CCD-TEMP", &(TemperatureN[0].value), "CCD Temperature (Celcius)", &status); | |
fits_write_date(fptr, &status); | |
} | |
void QHY5IICCD::resetFrame() { | |
UpdateCCDBin(1, 1); | |
UpdateCCDFrame(0, 0, PrimaryCCD.getXRes(), PrimaryCCD.getYRes()); | |
IUResetSwitch(&ResetSP); | |
ResetSP.s = IPS_IDLE; | |
IDSetSwitch(&ResetSP, "Resetting frame and binning."); | |
return; | |
} | |
void QHY5IICCD::TimerHit() { | |
int timerID = -1; | |
int err = 0; | |
long timeleft; | |
double ccdTemp; | |
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 { | |
// it's real close now, so spin on it | |
while (!sim && timeleft > 0) { | |
int slv; | |
slv = 100000 * timeleft; | |
usleep(slv); | |
} | |
/* We're done exposing */ | |
IDMessage(getDeviceName(), "Exposure done, downloading image..."); | |
if (isDebug()) | |
IDLog("Exposure done, downloading image...\n"); | |
PrimaryCCD.setExposureLeft(0); | |
InExposure = false; | |
/* grab and save image */ | |
grabImage(); | |
} | |
} | |
} else { | |
if (isDebug()) { | |
IDLog("With time left %ld\n", timeleft); | |
IDLog("image not yet ready....\n"); | |
} | |
PrimaryCCD.setExposureLeft(timeleft); | |
} | |
} | |
switch (TemperatureNP.s) { | |
case IPS_IDLE: | |
case IPS_OK: | |
if (fabs(TemperatureN[0].value - ccdTemp) >= TEMP_THRESHOLD) { | |
TemperatureN[0].value = ccdTemp; | |
IDSetNumber(&TemperatureNP, NULL); | |
} | |
break; | |
case IPS_BUSY: | |
if (sim) { | |
ccdTemp = TemperatureRequest; | |
TemperatureN[0].value = ccdTemp; | |
} else { | |
// If we're within threshold, let's make it BUSY ---> OK | |
if (fabs(TemperatureRequest - ccdTemp) <= TEMP_THRESHOLD) { | |
TemperatureNP.s = IPS_OK; | |
IDSetNumber(&TemperatureNP, NULL); | |
} | |
TemperatureN[0].value = ccdTemp; | |
IDSetNumber(&TemperatureNP, NULL); | |
break; | |
case IPS_ALERT: | |
break; | |
} | |
if (timerID == -1) | |
SetTimer(POLLMS); | |
return; | |
} | |
bool QHY5IICCD::GuideNorth(float duration) { | |
GuideControl(0, (long)duration); | |
return true; | |
} | |
bool QHY5IICCD::GuideSouth(float duration) { | |
GuideControl(1, (long)duration); | |
return true; | |
} | |
bool QHY5IICCD::GuideEast(float duration) { | |
GuideControl(2, (long)duration); | |
return true; | |
} | |
bool QHY5IICCD::GuideWest(float duration) { | |
GuideControl(3, (long)duration); | |
return true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment