Skip to content

Instantly share code, notes, and snippets.

@peterstrapp
Created February 27, 2014 19:08
Show Gist options
  • Save peterstrapp/9256895 to your computer and use it in GitHub Desktop.
Save peterstrapp/9256895 to your computer and use it in GitHub Desktop.
Thrown together QHY5L-II-C driver
/*
QHY5L-II-C driver
Copyright (C) 2014 Peter Strapp (peter@strapp.co.uk)
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