#if 0 FLI CCD INDI Interface for Apogee PPI Copyright (C) 2003 Jasem Mutlaq (mutlaqja@ikarustech.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 #endif #include #include #include #include #include #include #include #include #include "apogee_ppi.h" #include "lilxml.h" #include "base64.h" static void ISPoll(void *); extern char* me; /* argv[0] */ ApogeeCam *MainCam = NULL; /* Main and only camera */ /* send client definitions of all properties */ void ISInit() { if (MainCam == NULL) { MainCam = new ApogeeCam(); IEAddTimer (POLLMS, ISPoll, NULL); } } void ISGetProperties (const char *dev) { if (dev && strcmp (mydev, dev)) return; ISInit(); MainCam->ISGetProperties(dev); } void ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n) { /* ignore if not ours */ if (dev && strcmp (dev, mydev)) return; ISInit(); MainCam->ISNewSwitch(dev, name, states, names, n); } void ISNewText (const char *dev, const char *name, char *texts[], char *names[], int n) { /* ignore if not ours */ if (dev && strcmp (mydev, dev)) return; ISInit(); MainCam->ISNewText(dev, name, texts, names, n); } void ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n) { /* ignore if not ours */ if (dev && strcmp (dev, mydev)) return; ISInit(); MainCam->ISNewNumber(dev, name, values, names, n); } void ISNewBLOB (const char */*dev*/, const char */*name*/, int */*sizes[]*/, char **/*blobs[]*/, char **/*formats[]*/, char **/*names[]*/, int /*n*/) { } void ISPoll(void *) { MainCam->ISPoll(); IEAddTimer (POLLMS, ISPoll, NULL); } ApogeeCam::ApogeeCam() { ApogeeModelS = NULL; initProperties(); } ApogeeCam::~ApogeeCam() { } void ApogeeCam::initProperties() { fillSwitch(&PowerS[0], "CONNECT", "Connect", ISS_OFF); fillSwitch(&PowerS[1], "DISCONNECT", "Disconnect", ISS_ON); fillSwitchVector(&PowerSP, PowerS, NARRAY(PowerS), mydev, "CONNECTION", "Connection", COMM_GROUP, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); fillSwitch(&FrameTypeS[0], "FRAME_LIGHT", "Light", ISS_ON); fillSwitch(&FrameTypeS[1], "FRAME_BIAS", "Bias", ISS_OFF); fillSwitch(&FrameTypeS[2], "FRAME_DARK", "Dark", ISS_OFF); fillSwitch(&FrameTypeS[3], "FRAME_FLAT", "Flat Field", ISS_OFF); fillSwitchVector(&FrameTypeSP, FrameTypeS, NARRAY(FrameTypeS), mydev, "CCD_FRAME_TYPE", "Frame Type", EXPOSE_GROUP, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); fillNumber(&FrameN[0], "X", "X", "%.0f", 0., MAX_PIXELS, 1., 0.); fillNumber(&FrameN[1], "Y", "Y", "%.0f", 0., MAX_PIXELS, 1., 0.); fillNumber(&FrameN[2], "WIDTH", "Width", "%.0f", 0., MAX_PIXELS, 1., 0.); fillNumber(&FrameN[3], "HEIGHT", "Height", "%.0f", 0., MAX_PIXELS, 1., 0.); fillNumberVector(&FrameNP, FrameN, NARRAY(FrameN), mydev, "CCD_FRAME", "Frame", IMAGE_GROUP, IP_RW, 60, IPS_IDLE); fillNumber(&BinningN[0], "HOR_BIN", "X", "%0.f", 1., MAXHBIN, 1., 1.); fillNumber(&BinningN[1], "VER_BIN", "Y", "%0.f", 1., MAXVBIN, 1., 1.); fillNumberVector(&BinningNP, BinningN, NARRAY(BinningN), mydev, "CCD_BINNING", "Binning", IMAGE_GROUP, IP_RW, 60, IPS_IDLE); fillNumber(&ExposeTimeN[0], "DURATION", "Duration (s)", "%5.2f", 0., 36000., 0.5, 1.); fillNumberVector(&ExposeTimeNP, ExposeTimeN, NARRAY(ExposeTimeN), mydev, "CCD_EXPOSE_DURATION", "Expose", EXPOSE_GROUP, IP_RW, 60, IPS_IDLE); fillNumber(&TemperatureN[0], "TEMPERATURE", "Temperature", "%+06.2f", MIN_CCD_TEMP, MAX_CCD_TEMP, 0.2, 0.); fillNumberVector(&TemperatureNP, TemperatureN, NARRAY(TemperatureN), mydev, "CCD_TEMPERATURE", "Expose", EXPOSE_GROUP, IP_RW, 60, IPS_IDLE); strcpy(imageB.name, "CCD1"); strcpy(imageB.label, "Feed"); strcpy(imageB.format, ""); imageB.blob = 0; imageB.bloblen = 0; imageB.size = 0; imageB.bvp = 0; imageB.aux0 = 0; imageB.aux1 = 0; imageB.aux2 = 0; strcpy(imageBP.device, mydev); strcpy(imageBP.name, "Video"); strcpy(imageBP.label, "Video"); strcpy(imageBP.group, COMM_GROUP); strcpy(imageBP.timestamp, ""); imageBP.p = IP_RO; imageBP.timeout = 0; imageBP.s = IPS_IDLE; imageBP.bp = &imageB; imageBP.nbp = 1; imageBP.aux = 0; //loadXMLModel(); } bool ApogeeCam::loadXMLModel() { LilXML *XMLParser = newLilXML(); XMLEle *root = NULL, *camera = NULL; XMLAtt *modelName; FILE *modelSpecFile = NULL; char errmsg[1024]; int ncams = 0; //IDLog("Top dir is "TOP_DATADIR, NULL); modelSpecFile = fopen(TOP_DATADIR"/apogee_caminfo.xml", "r"); //modelSpecFile = fopen("/opt/trinity/share/apps/kstars/apogee_caminfo.xml", "r"); if (modelSpecFile == NULL) { IDLog("Error: Unable to open file apogee_caminfo.xml\n"); IDMessage(mydev, "Error: Unable to open file apogee_caminfo.xml"); return false; } root = readXMLFile(modelSpecFile, XMLParser, errmsg); if (root == NULL) { IDLog("Error: Unable to process apogee_caminfo.xml. %s\n", errmsg); IDMessage(mydev, "Error: Unable to process apogee_caminfo.xml. %s\n", errmsg); fclose(modelSpecFile); delLilXML(XMLParser); return false; } for (camera = nextXMLEle (root, 1); camera != NULL; camera = nextXMLEle (root, 0)) { modelName = findXMLAtt(camera, "model"); if (modelName == NULL) continue; ApogeeModelS = (ApogeeModelS == NULL) ? (ISwitch *) malloc (sizeof(ISwitch)) : (ISwitch *) realloc(ApogeeModelS, sizeof(ISwitch) * (ncams + 1)); snprintf(ApogeeModelS[ncams].name, MAXINDINAME, "Model%d", ncams); strcpy(ApogeeModelS[ncams].label, valuXMLAtt(modelName)); ApogeeModelS[ncams].s = (ncams == 0) ? ISS_ON : ISS_OFF; ApogeeModelS[ncams].svp = NULL; ApogeeModelS[ncams].aux = NULL; ncams++; } fclose(modelSpecFile); delLilXML(XMLParser); if (ncams > 0) { fillSwitchVector(&ApogeeModelSP, ApogeeModelS, ncams, mydev, "Model", "", COMM_GROUP, IP_RW, ISR_1OFMANY, 60, IPS_IDLE); return true; } return false; } void ApogeeCam::ISGetProperties(const char */*dev*/) { /* COMM_GROUP */ IDDefSwitch(&PowerSP, NULL); if (loadXMLModel()) IDDefSwitch(&ApogeeModelSP, NULL); else IDMessage(mydev, "Error: Unable to read camera specifications. Driver is disabled."); IDDefBLOB(&imageBP, NULL); /* Expose */ IDDefSwitch(&FrameTypeSP, NULL); IDDefNumber(&ExposeTimeNP, NULL); IDDefNumber(&TemperatureNP, NULL); /* Image Group */ IDDefNumber(&FrameNP, NULL); IDDefNumber(&BinningNP, NULL); IDLog("Apogee Driver Debug Enabled\n"); } void ApogeeCam::ISNewSwitch (const char */*dev*/, const char *name, ISState *states, char *names[], int n) { /* Connection */ if (!strcmp (name, PowerSP.name)) { IUResetSwitches(&PowerSP); IUUpdateSwitches(&PowerSP, states, names, n); connectCCD(); return; } /* Frame Type */ if (!strcmp(FrameTypeSP.name, name)) { if (checkPowerS(&FrameTypeSP)) return; IUResetSwitches(&FrameTypeSP); IUUpdateSwitches(&FrameTypeSP, states, names, n); FrameTypeSP.s = IPS_OK; IDSetSwitch(&FrameTypeSP, NULL); return; } /* Apogee Model */ if (!strcmp(ApogeeModelSP.name, name)) { IUResetSwitches(&ApogeeModelSP); IUUpdateSwitches(&ApogeeModelSP, states, names, n); ApogeeModelSP.s = IPS_OK; IDSetSwitch(&ApogeeModelSP, NULL); return; } } void ApogeeCam::ISNewText (const char */*dev*/, const char */*name*/, char **/*texts[]*/, char **/*names[]*/, int /*n*/) { } void ApogeeCam::ISNewNumber (const char */*dev*/, const char *name, double values[], char *names[], int n) { /* Exposure time */ if (!strcmp (ExposeTimeNP.name, name)) { if (checkPowerN(&ExposeTimeNP)) return; if (ExposeTimeNP.s == IPS_BUSY) { cam->Reset(); ExposeTimeNP.s = IPS_IDLE; ExposeTimeN[0].value = 0; IDSetNumber(&ExposeTimeNP, "Exposure cancelled."); IDLog("Exposure Cancelled.\n"); return; } ExposeTimeNP.s = IPS_IDLE; IUUpdateNumbers(&ExposeTimeNP, values, names, n); IDLog("Exposure Time is: %g\n", ExposeTimeN[0].value); handleExposure(NULL); return; } if (!strcmp(TemperatureNP.name, name)) { if (checkPowerN(&TemperatureNP)) return; TemperatureNP.s = IPS_IDLE; if (values[0] < MIN_CCD_TEMP || values[0] > MAX_CCD_TEMP) { IDSetNumber(&TemperatureNP, "Error: valid range of temperature is from %d to %d", MIN_CCD_TEMP, MAX_CCD_TEMP); return; } targetTemp = values[0]; cam->write_CoolerMode(0); cam->write_CoolerMode(1); cam->write_CoolerSetPoint(targetTemp); TemperatureNP.s = IPS_BUSY; IDSetNumber(&TemperatureNP, "Setting CCD temperature to %+06.2f C", values[0]); IDLog("Setting CCD temperature to %+06.2f C\n", values[0]); return; } if (!strcmp(FrameNP.name, name)) { if (checkPowerN(&FrameNP)) return; FrameNP.s = IPS_OK; IUUpdateNumbers(&FrameNP, values, names, n); cam->m_StartX = (int) FrameN[0].value; cam->m_StartY = (int) FrameN[1].value; cam->m_NumX = (int) FrameN[2].value; cam->m_NumY = (int) FrameN[3].value; IDSetNumber(&FrameNP, NULL); } /* end FrameNP */ if (!strcmp(BinningNP.name, name)) { if (checkPowerN(&BinningNP)) return; BinningNP.s = IPS_OK; IUUpdateNumbers(&BinningNP, values, names, n); cam->m_BinX = (int) BinningN[0].value; cam->m_BinY = (int) BinningN[1].value; IDLog("Binning is: %.0f x %.0f\n", BinningN[0].value, BinningN[1].value); return; } } void ApogeeCam::ISPoll() { static int mtc=5; int readStatus=0; double ccdTemp; if (!isCCDConnected()) return; switch (ExposeTimeNP.s) { case IPS_IDLE: case IPS_OK: break; case IPS_BUSY: readStatus = cam->read_Status(); if (readStatus < 0) { IDLog("Error in exposure! Read status: %d\n", readStatus); ExposeTimeNP.s = IPS_ALERT; ExposeTimeN[0].value = 0; IDSetNumber(&ExposeTimeNP, "Error in exposure procedure. Read states: %d", readStatus); return; } else if (readStatus == Camera_Status_ImageReady) { ExposeTimeN[0].value = 0; ExposeTimeNP.s = IPS_OK; IDSetNumber(&ExposeTimeNP, "Exposure done, downloading image..."); IDLog("Exposure done, downloading image...\n"); /* grab and save image */ grabImage(); return; } ExposeTimeN[0].value --; IDSetNumber(&ExposeTimeNP, NULL); break; case IPS_ALERT: break; } switch (TemperatureNP.s) { case IPS_IDLE: case IPS_OK: mtc--; if (mtc == 0) { TemperatureN[0].value = cam->read_Temperature(); IDSetNumber(&TemperatureNP, NULL); mtc = 5; } break; case IPS_BUSY: ccdTemp = cam->read_Temperature(); if (fabs(targetTemp - ccdTemp) <= TEMP_THRESHOLD) TemperatureNP.s = IPS_OK; mtc = 1; TemperatureN[0].value = ccdTemp; IDSetNumber(&TemperatureNP, NULL); break; case IPS_ALERT: break; } } /* Downloads the image from the CCD row by row and store them in a raw file. N.B. No processing is done on the image */ void ApogeeCam::grabImage() { long err; int img_size, fd; char errmsg[1024]; char filename[] = "/tmp/fitsXXXXXX"; IDLog("In grab Image\n"); if ((fd = mkstemp(filename)) < 0) { IDMessage(mydev, "Error making temporary filename."); IDLog("Error making temporary filename.\n"); return; } close(fd); img_size = APGFrame.width * APGFrame.height * sizeof(unsigned short); IDLog("Allocating memory buffer. Width: %d - Height: %d\n", APGFrame.width, APGFrame.height); APGFrame.img = (unsigned short *) malloc (img_size); if (APGFrame.img == NULL) { IDMessage(mydev, "Not enough memory to store image."); IDLog("Not enough memory to store image.\n"); return; } IDLog("Getting frame buffer from camera...\n"); if (!cam->GetImage( APGFrame.img , APGFrame.width, APGFrame.height )) { free(APGFrame.img); IDMessage(mydev, "GetImage() failed."); IDLog("GetImage() failed."); return; } IDLog("Done with getting frame buffer, writing FITS file\n"); err = writeFITS(filename, errmsg); if (err) { free(APGFrame.img); IDMessage(mydev, errmsg, NULL); return; } free(APGFrame.img); IDLog("All good, returning\n"); } int ApogeeCam::writeFITS(char *filename, char errmsg[]) { FITS_FILE* ofp; int bpp, bpsl, width, height; long nbytes; FITS_HDU_LIST *hdu; IDLog("in write FITS, opening filename %s\n", filename); ofp = fits_open (filename, "w"); if (!ofp) { sprintf(errmsg, "Error: cannot open file for writing."); return (-1); } width = APGFrame.width; height = APGFrame.height; bpp = sizeof(unsigned short); /* Bytes per Pixel */ bpsl = bpp * APGFrame.width; /* Bytes per Line */ nbytes = 0; IDLog("Creating FITS header\n"); hdu = create_fits_header (ofp, width, height, bpp); if (hdu == NULL) { sprintf(errmsg, "Error: creating FITS header failed."); return (-1); } if (fits_write_header (ofp, hdu) < 0) { sprintf(errmsg, "Error: writing to FITS header failed."); return (-1); } IDLog("Converting to BIG Endian\n"); for (int i=0; i < height; i++) for (int j=0 ; j < width; j++) APGFrame.img[width * i + j] = getBigEndian( (APGFrame.img[width * i + j]) ); IDLog("Writing frame to disk\n"); for (int i= 0; i < height ; i++) { fwrite(APGFrame.img + (i * width), 2, width, ofp->fp); nbytes += bpsl; } IDLog("Calculating nbytes\n"); nbytes = nbytes % FITS_RECORD_SIZE; if (nbytes) { while (nbytes++ < FITS_RECORD_SIZE) putc (0, ofp->fp); } if (ferror (ofp->fp)) { sprintf(errmsg, "Error: write error occured"); return (-1); } IDLog("Closing ofp\n"); fits_close (ofp); /* Success */ ExposeTimeNP.s = IPS_OK; IDSetNumber(&ExposeTimeNP, NULL); IDLog("Loading FITS image...\n"); IDLog("Uploading filename\n"); uploadFile(filename); IDLog("Uploading done, returning\n"); return 0; } void ApogeeCam::uploadFile(char * filename) { FILE * fitsFile; unsigned char *fitsData, *compressedData; int r=0; unsigned int i =0, nr = 0; uLongf compressedBytes=0; uLong totalBytes; struct stat stat_p; IDLog("in upload file, will stat file now\n"); if ( -1 == stat (filename, &stat_p)) { IDLog(" Error occoured attempting to stat %s\n", filename); return; } totalBytes = stat_p.st_size; fitsData = (unsigned char *) malloc (sizeof(unsigned char) * totalBytes); compressedData = (unsigned char *) malloc (sizeof(unsigned char) * totalBytes + totalBytes / 64 + 16 + 3); if (fitsData == NULL || compressedData == NULL) { IDLog("Error! low memory. Unable to initialize fits buffers.\n"); return; } IDLog("opening file\n"); fitsFile = fopen(filename, "r"); if (fitsFile == NULL) return; IDLog("Reading file from disk\n"); /* #1 Read file from disk */ for (i=0; i < totalBytes; i+= nr) { nr = fread(fitsData + i, 1, totalBytes - i, fitsFile); if (nr <= 0) { IDLog("Error reading temporary FITS file.\n"); return; } } compressedBytes = sizeof(char) * totalBytes + totalBytes / 64 + 16 + 3; IDLog("Compressing data\n"); /* #2 Compress it */ r = compress2(compressedData, &compressedBytes, fitsData, totalBytes, 9); if (r != Z_OK) { /* this should NEVER happen */ IDLog("internal error - compression failed: %d\n", r); return; } IDLog("Sending blob. bloblen %ld - size %ld\n", compressedBytes, totalBytes); /* #3 Send it */ imageB.blob = compressedData; imageB.bloblen = compressedBytes; imageB.size = totalBytes; strcpy(imageB.format, ".fits.z"); imageBP.s = IPS_OK; IDSetBLOB (&imageBP, NULL); free (fitsData); free (compressedData); } /* Initiates the exposure procedure */ void ApogeeCam::handleExposure(void */*p*/) { int curFrame = getOnSwitch(&FrameTypeSP); switch (curFrame) { /* Light frame */ case LIGHT_FRAME: if (!cam->Expose( (int) ExposeTimeN[0].value, true )) { ExposeTimeNP.s = IPS_IDLE; IDSetNumber(&ExposeTimeNP, "Light Camera exposure failed."); IDLog("Light Camera exposure failed.\n"); return; } break; /* BIAS frame is the same as DARK but with minimum period. i.e. readout from camera electronics. */ case BIAS_FRAME: if (!cam->Expose( 0.05 , false )) { ExposeTimeNP.s = IPS_IDLE; IDSetNumber(&ExposeTimeNP, "Bias Camera exposure failed."); IDLog("Bias Camera exposure failed.\n"); return; } break; /* Dark frame */ case DARK_FRAME: if (!cam->Expose( (int) ExposeTimeN[0].value , false )) { ExposeTimeNP.s = IPS_IDLE; IDSetNumber(&ExposeTimeNP, "Dark Camera exposure failed."); IDLog("Dark Camera exposure failed.\n"); return; } break; case FLAT_FRAME: if (!cam->Expose( (int) ExposeTimeN[0].value , true )) { ExposeTimeNP.s = IPS_IDLE; IDSetNumber(&ExposeTimeNP, "Flat Camera exposure failed."); IDLog("Flat Camera exposure failed.\n"); return; } break; } APGFrame.frameType = curFrame; APGFrame.width = (int) FrameN[2].value; APGFrame.height = (int) FrameN[3].value; APGFrame.expose = (int) ExposeTimeN[0].value; APGFrame.temperature = TemperatureN[0].value; APGFrame.binX = (int) BinningN[0].value; APGFrame.binY = (int) BinningN[1].value; ExposeTimeNP.s = IPS_BUSY; IDSetNumber(&ExposeTimeNP, "Taking a %g seconds frame...", ExposeTimeN[0].value); IDLog("Taking a frame. Width: %d - Height: %d - expose %d - temperature %g - binX %d - binY %d\n", APGFrame.width, APGFrame.height, APGFrame.expose, APGFrame.temperature, APGFrame.binX, APGFrame.binY); } /* Retrieves basic data from the CCD upon connection like temperature, array size, firmware..etc */ void ApogeeCam::getBasicData() { // Maximum resolution FrameN[2].max = cam->m_NumX; FrameN[3].max = cam->m_NumY; IUUpdateMinMax(&FrameNP); // Maximum Bin BinningN[0].max = cam->m_MaxBinX; BinningN[1].max = cam->m_MaxBinX; IUUpdateMinMax(&BinningNP); FrameN[0].value = 0; FrameN[1].value = 0; FrameN[2].min = 0; FrameN[2].max = cam->m_ImgColumns; FrameN[2].value = cam->m_ImgColumns; FrameN[3].min = 0; FrameN[3].max = cam->m_ImgRows; FrameN[3].value = cam->m_ImgRows; IUUpdateMinMax(&FrameNP); IDSetNumber(&FrameNP, NULL); // Current Temperature TemperatureN[0].value = cam->read_Temperature(); IDSetNumber(&TemperatureNP, NULL); } int ApogeeCam::getOnSwitch(ISwitchVectorProperty *sp) { int i=0; for (i=0; i < sp->nsp ; i++) { /*IDLog("Switch %s is %s\n", sp->sp[i].name, sp->sp[i].s == ISS_ON ? "On" : "Off");*/ if (sp->sp[i].s == ISS_ON) return i; } return -1; } int ApogeeCam::checkPowerS(ISwitchVectorProperty *sp) { if (PowerSP.s != IPS_OK) { if (!strcmp(sp->label, "")) IDMessage (mydev, "Cannot change property %s while the CCD is offline.", sp->name); else IDMessage (mydev, "Cannot change property %s while the CCD is offline.", sp->label); sp->s = IPS_IDLE; IDSetSwitch(sp, NULL); return -1; } return 0; } int ApogeeCam::checkPowerN(INumberVectorProperty *np) { if (PowerSP.s != IPS_OK) { if (!strcmp(np->label, "")) IDMessage (mydev, "Cannot change property %s while the CCD is offline.", np->name); else IDMessage (mydev, "Cannot change property %s while the CCD is offline.", np->label); np->s = IPS_IDLE; IDSetNumber(np, NULL); return -1; } return 0; } int ApogeeCam::checkPowerT(ITextVectorProperty *tp) { if (PowerSP.s != IPS_OK) { if (!strcmp(tp->label, "")) IDMessage (mydev, "Cannot change property %s while the CCD is offline.", tp->name); else IDMessage (mydev, "Cannot change property %s while the CCD is offline.", tp->label); tp->s = IPS_IDLE; IDSetText(tp, NULL); return -1; } return 0; } void ApogeeCam::connectCCD() { /* USB by default {USB, SERIAL, PARALLEL, INET} */ switch (PowerS[0].s) { case ISS_ON: if (initCamera()) { /* Sucess! */ PowerS[0].s = ISS_ON; PowerS[1].s = ISS_OFF; PowerSP.s = IPS_OK; IDSetSwitch(&PowerSP, "CCD is online. Retrieving basic data."); IDLog("CCD is online. Retrieving basic data.\n"); getBasicData(); } else { PowerSP.s = IPS_IDLE; PowerS[0].s = ISS_OFF; PowerS[1].s = ISS_ON; IDSetSwitch(&PowerSP, "Error: no cameras were detected."); IDLog("Error: no cameras were detected.\n"); return; } break; case ISS_OFF: PowerS[0].s = ISS_OFF; PowerS[1].s = ISS_ON; PowerSP.s = IPS_IDLE; IDSetSwitch(&PowerSP, "CCD is offline."); break; } } bool ApogeeCam::initCamera() { LilXML *XMLParser = newLilXML(); XMLEle *root = NULL, *camera = NULL, *ele = NULL; XMLEle *system = NULL, *geometry = NULL, *temp = NULL, *ccd = NULL; XMLAtt *ap; FILE *spFile = NULL; char errmsg[1024]; spFile = fopen(TOP_DATADIR"/apogee_caminfo.xml", "r"); //spFile = fopen("/opt/trinity/share/apps/kstars/apogee_caminfo.xml", "r"); if (spFile == NULL) { IDLog("Error: Unable to open file apogee_caminfo.xml\n"); IDMessage(mydev, "Error: Unable to open file apogee_caminfo.xml"); return false; } root = readXMLFile(spFile, XMLParser, errmsg); if (root == NULL) { IDLog("Error: Unable to process apogee_caminfo.xml. %s\n", errmsg); IDMessage(mydev, "Error: Unable to process apogee_caminfo.xml. %s\n", errmsg); fclose(spFile); delLilXML(XMLParser); return false; } fclose(spFile); // Let's locate which camera to load the configuration for camera = findXMLEle(root, "Apogee_Camera"); if (camera == NULL) { IDLog("Error: Unable to find Apogee_Camera element.\n"); IDMessage(mydev, "Error: Unable to find Apogee_Camera element."); delLilXML(XMLParser); return false; } IDLog("Looking for %s - len %d\n", ApogeeModelS[getOnSwitch(&ApogeeModelSP)].label, strlen(ApogeeModelS[getOnSwitch(&ApogeeModelSP)].label)); ap = findXMLAtt(camera, "model"); if (!ap) { IDLog("Error: Unable to find attribute model.\n"); IDMessage(mydev, "Error: Unable to find attribute model."); return false; } if (strcmp(valuXMLAtt(ap), ApogeeModelS[getOnSwitch(&ApogeeModelSP)].label)) { IDLog("Camera %s not found in XML file\n", ApogeeModelS[getOnSwitch(&ApogeeModelSP)].label); IDMessage(mydev, "Camera %s not found in XML file\n", ApogeeModelS[getOnSwitch(&ApogeeModelSP)].label); delLilXML(XMLParser); return false; } // Let's get the subsections now system = findXMLEle(camera, "System"); geometry = findXMLEle(camera, "Geometry"); temp = findXMLEle(camera, "Temp"); ccd = findXMLEle(camera, "CCD"); if (system == NULL) { IDLog("Error: Unable to find System element in camera.\n"); IDMessage(mydev, "Error: Unable to find System element in camera."); delLilXML(XMLParser); return false; } if (geometry == NULL) { IDLog("Error: Unable to find Geometry element in camera.\n"); IDMessage(mydev, "Error: Unable to find Geometry element in camera."); delLilXML(XMLParser); return false; } if (temp == NULL) { IDLog("Error: Unable to find Temp element in camera.\n"); IDMessage(mydev, "Error: Unable to find Temp element in camera."); delLilXML(XMLParser); return false; } if (ccd == NULL) { IDLog("Error: Unable to find CCD element in camera.\n"); IDMessage(mydev, "Error: Unable to find CCD element in camera."); delLilXML(XMLParser); return false; } cam = new CCameraIO(); if (cam == NULL) { IDLog("Error: Failed to create CCameraIO object.\n"); IDMessage(mydev, "Error: Failed to create CCameraIO object."); delLilXML(XMLParser); return false; } int bAddr = 0x378; int val = 0; bAddr = hextoi(valuXMLAtt(findXMLAtt(system, "Base"))) & 0xFFF; // Rows ap = findXMLAtt(geometry, "Rows"); if (!ap) { IDLog("Error: Unable to find attribute Rows.\n"); IDMessage(mydev, "Error: Unable to find attribute Rows."); delLilXML(XMLParser); return false; } cam->m_Rows = hextoi(valuXMLAtt(ap)); // Columns ap = findXMLAtt(geometry, "Columns"); if (!ap) { IDLog("Error: Unable to find attribute Columns.\n"); IDMessage(mydev, "Error: Unable to find attribute Columns."); delLilXML(XMLParser); return false; } cam->m_Columns = hextoi(valuXMLAtt(ap)); // pp_repeat ele = findXMLEle(system, "PP_Repeat"); if (!ele) { IDLog("Error: Unable to find element PP_Repeat.\n"); IDMessage(mydev, "Error: Unable to find element PP_Repeat."); delLilXML(XMLParser); return false; } val = hextoi(pcdataXMLEle(ele)); if (val > 0 && val <= 1000) cam->m_PPRepeat = val; // Initiate driver if (!cam->InitDriver(0)) { IDLog("Error: Failed to Init Driver.\n"); IDMessage(mydev, "Error: Failed to Init Driver."); delLilXML(XMLParser); return false; } cam->Reset(); // Cable length ele = findXMLEle(system, "Cable"); if (!ele) { IDLog("Error: Unable to find element Cable.\n"); IDMessage(mydev, "Error: Unable to find element Cable."); delLilXML(XMLParser); return false; } if (!strcmp("Long", pcdataXMLEle(ele))) { cam->write_LongCable( true ); IDLog("Cable is long\n"); } else { cam->write_LongCable( false ); IDLog("Cable is short\n"); } if (!cam->read_Present()) { IDLog("Error: read_Present() failed.\n"); IDMessage(mydev, "Error: read_Present() failed."); delLilXML(XMLParser); return false; } // Default settings cam->write_UseTrigger( false ); cam->write_ForceShutterOpen( false ); // High priority ele = findXMLEle(system, "High_Priority"); if (ele) { if (!strcmp(pcdataXMLEle(ele), "True")) cam->m_HighPriority = true; else cam->m_HighPriority = false; } // Data bits ele = findXMLEle(system, "Data_Bits"); if (ele) { val = hextoi(pcdataXMLEle(ele)); if (val >= 8 && val <= 18) cam->m_DataBits = val; } // Sensor ele = findXMLEle(system, "Sensor"); if (ele) { if (!strcmp(pcdataXMLEle(ele), "CCD")) cam->m_SensorType = Camera_SensorType_CCD; else cam->m_SensorType = Camera_SensorType_CMOS; } // Mode ele = findXMLEle(system, "Mode"); if (ele) { val = hextoi(pcdataXMLEle(ele)) & 0xF; cam->write_Mode( val ); IDLog("Mode %d\n", val); } else cam->write_Mode( 0 ); // Test ele = findXMLEle(system, "Test"); if (ele) { val = hextoi(pcdataXMLEle(ele)) & 0xF; cam->write_TestBits( val ); IDLog("Test bits %d\n", val); } else cam->write_TestBits( 0 ); // Test2 ele = findXMLEle(system, "Test2"); if (ele) { val = hextoi(pcdataXMLEle(ele)) & 0xF; cam->write_Test2Bits( val ); IDLog("Test 2 bits %d\n", val); } else cam->write_Test2Bits( 0 ); // Shutter Speed ele = findXMLEle(system, "Shutter_Speed"); if (ele) { cam->m_MaxExposure = 10485.75; if (!strcmp(pcdataXMLEle(ele), "Normal")) { cam->m_FastShutter = false; cam->m_MinExposure = 0.01; IDLog("Shutter speed normal\n"); } else if ( (!strcmp(pcdataXMLEle(ele), "Fast")) || (!strcmp(pcdataXMLEle(ele), "Dual")) ) { cam->m_FastShutter = true; cam->m_MinExposure = 0.001; IDLog("Shutter speed fast\n"); } } // Shutter Bits ele = findXMLEle(system, "Shutter_Bits"); if (ele) { val = hextoi(pcdataXMLEle(ele)); cam->m_FastShutterBits_Mode = val & 0x0F; cam->m_FastShutterBits_Test = ( val & 0xF0 ) >> 4; IDLog("Shutter bits %d\n", val); } // Max X Bin ele = findXMLEle(system, "MaxBinX"); if (ele) { val = hextoi(pcdataXMLEle(ele)); if (val >= 1 && val <= MAXHBIN) cam->m_MaxBinX = val; } // Max Y Bin ele = findXMLEle(system, "MaxBinY"); if (ele) { val = hextoi(pcdataXMLEle(ele)); if (val >= 1 && val <= MAXVBIN) cam->m_MaxBinY = val; } // Guider Relays ele = findXMLEle(system, "Guider_Relays"); if (ele) { if (!strcmp(pcdataXMLEle(ele), "True")) cam->m_GuiderRelays = true; else cam->m_GuiderRelays = false; } // Timeout ele = findXMLEle(system, "Timeout"); if (ele) { double dval = atof(pcdataXMLEle(ele)); if (dval >= 0.0 && dval <= 10000.0) cam->m_Timeout = dval; } // BIC ele = findXMLEle(geometry, "BIC"); if (ele) { val = hextoi(pcdataXMLEle(ele)); if (val >= 1 && val <= MAXCOLUMNS) cam->m_BIC = val; } // BIR ele = findXMLEle(geometry, "BIR"); if (ele) { val = hextoi(pcdataXMLEle(ele)); if (val >= 1 && val <= MAXROWS) cam->m_BIR = val; } // SKIPC ele = findXMLEle(geometry, "SKIPC"); if (ele) { val = hextoi(pcdataXMLEle(ele)); if (val >= 1 && val <= MAXCOLUMNS) cam->m_SkipC = val; } // SKIPR ele = findXMLEle(geometry, "SKIPR"); if (ele) { val = hextoi(pcdataXMLEle(ele)); if (val >= 1 && val <= MAXROWS) cam->m_SkipR = val; } // IMG COlS ele = findXMLEle(geometry, "ImgCols"); if (ele) { val = hextoi(pcdataXMLEle(ele)); if (val >= 1 && val <= MAXTOTALCOLUMNS) cam->m_ImgColumns = val; } else cam->m_ImgColumns = cam->m_Columns - cam->m_BIC - cam->m_SkipC; // IMG ROWS ele = findXMLEle(geometry, "ImgRows"); if (ele) { val = hextoi(pcdataXMLEle(ele)); if (val >= 1 && val <= MAXTOTALROWS) cam->m_ImgRows = val; } else cam->m_ImgRows = cam->m_Rows - cam->m_BIR - cam->m_SkipR; // Hor Flush ele = findXMLEle(geometry, "HFlush"); if (ele) { val = hextoi(pcdataXMLEle(ele)); if (val >= 1 && val <= MAXHBIN) cam->m_HFlush = val; } // Ver Flush ele = findXMLEle(geometry, "VFlush"); if (ele) { val = hextoi(pcdataXMLEle(ele)); if (val >= 1 && val <= MAXVBIN) cam->m_VFlush = val; } // Default to full frame cam->m_NumX = cam->m_ImgColumns; cam->m_NumY = cam->m_ImgRows; // Temp Control ap = findXMLAtt(temp, "Control"); if (ap) { if (!strcmp(valuXMLAtt(ap), "True")) cam->m_TempControl = true; else cam->m_TempControl = false; } // Calibration ap = findXMLAtt(temp, "Cal"); if (ap) { val = hextoi(valuXMLAtt(ap)); if (val >= 1 && val <= 255) cam->m_TempCalibration = val; } // Scale ap = findXMLAtt(temp, "Scale"); if (ap) { double dval = atof(valuXMLAtt(ap)); if (dval >= 1.0 && dval <= 10.0) cam->m_TempScale = dval; } // Target ap = findXMLAtt(temp, "Target"); if (ap) { double dval = atof(valuXMLAtt(ap)); if (dval >= -60.0 && dval <= 40.0) cam->write_CoolerSetPoint( dval ); else cam->write_CoolerSetPoint( -10.0 ); // Default IDLog("Target: %g\n", dval); } // Sensor ap = findXMLAtt(ccd, "Sensor"); if (ap) { strncpy (cam->m_Sensor, valuXMLAtt(ap), 255); IDLog("Sensor: %s\n", cam->m_Sensor); } // Color ele = findXMLEle(ccd, "Color"); if (ele) { if (!strcmp(pcdataXMLEle(ele), "True")) { cam->m_Color = true; IDLog("Color: true\n"); } else { cam->m_Color = false; IDLog("Color: false\n"); } } // Noise ele = findXMLEle(ccd, "Noise"); if (ele) cam->m_Noise = atof( pcdataXMLEle(ele) ); // Noise ele = findXMLEle(ccd, "Gain"); if (ele) cam->m_Gain = atof( pcdataXMLEle(ele) ); // Pixel X Size ele = findXMLEle(ccd, "PixelXSize"); if (ele) { cam->m_PixelXSize = atof( pcdataXMLEle(ele) ); IDLog("Pixel X Size: %g\n", cam->m_PixelXSize); } // Pixel Y Size ele = findXMLEle(ccd, "PixelYSize"); if (ele) { cam->m_PixelYSize = atof( pcdataXMLEle(ele) ); IDLog("Pixel Y Size: %g\n", cam->m_PixelYSize); } // Log all values IDLog("Cam Row: %d - Cam Cols: %d - PP_Repeat %d\n",cam->m_Rows, cam->m_Columns, cam->m_PPRepeat); IDLog("High_Priority %s - Data_Bits %d - Sensor %s\n", cam->m_HighPriority ? "true" : "false", cam->m_DataBits, (cam->m_SensorType == Camera_SensorType_CCD) ? "CCD" : "CMOS"); IDLog("Max X Bin: %d - Max Y Bin: %d - Guider Relays: %s\n", cam->m_MaxBinX, cam->m_MaxBinY, cam->m_GuiderRelays ? "true" : "false"); IDLog("BIC: %d - BIR: %d - SKIPC: %d - SKIPR: %d - ImgRows: %d - ImgCols %d\n", cam->m_BIC, cam->m_BIR, cam->m_SkipC, cam->m_SkipR, cam->m_ImgRows, cam->m_ImgColumns); IDLog("HFlush: %d - VFlush: %d - Control: %s - Cal: %d - Scale: %g\n", cam->m_HFlush, cam->m_VFlush, cam->m_TempControl ? "true" : "false", cam->m_TempCalibration, cam->m_TempScale); delLilXML(XMLParser); return true; } /* isCCDConnected: return 1 if we have a connection, 0 otherwise */ int ApogeeCam::isCCDConnected(void) { return ((PowerS[0].s == ISS_ON) ? 1 : 0); } FITS_HDU_LIST * ApogeeCam::create_fits_header (FITS_FILE *ofp, uint width, uint height, uint bpp) { FITS_HDU_LIST *hdulist; char temp_s[FITS_CARD_SIZE], expose_s[FITS_CARD_SIZE], binning_s[FITS_CARD_SIZE], frame_s[FITS_CARD_SIZE], pixel_s[FITS_CARD_SIZE]; char obsDate[FITS_CARD_SIZE]; snprintf(obsDate, FITS_CARD_SIZE, "DATE-OBS= '%s' /Observation Date UTC", timestamp()); hdulist = fits_add_hdu (ofp); if (hdulist == NULL) return (NULL); hdulist->used.simple = 1; hdulist->bitpix = 16; hdulist->naxis = 2; hdulist->naxisn[0] = width; hdulist->naxisn[1] = height; hdulist->naxisn[2] = bpp; hdulist->used.datamin = 1; hdulist->datamin = min(); hdulist->used.datamax = 1; hdulist->datamax = max(); hdulist->used.bzero = 1; hdulist->bzero = 0.0; hdulist->used.bscale = 1; hdulist->bscale = 1.0; snprintf(temp_s, FITS_CARD_SIZE, "CCD-TEMP= %g / degrees celcius", APGFrame.temperature); snprintf(expose_s, FITS_CARD_SIZE, "EXPOSURE= %d / milliseconds", APGFrame.expose); snprintf(binning_s, FITS_CARD_SIZE, "BINNING = '(%d x %d)'", APGFrame.binX, APGFrame.binY); snprintf(pixel_s, FITS_CARD_SIZE, "PIX-SIZ = '%0.f x %0.f microns square'", cam->m_PixelXSize, cam->m_PixelYSize); switch (APGFrame.frameType) { case LIGHT_FRAME: strcpy(frame_s, "FRAME = 'Light'"); break; case BIAS_FRAME: strcpy(frame_s, "FRAME = 'Bias'"); break; case FLAT_FRAME: strcpy(frame_s, "FRAME = 'Flat Field'"); break; case DARK_FRAME: strcpy(frame_s, "FRAME = 'Dark'"); break; } fits_add_card (hdulist, frame_s); fits_add_card (hdulist, temp_s); fits_add_card (hdulist, expose_s); fits_add_card (hdulist, pixel_s); fits_add_card (hdulist, "INSTRUME= 'Apogee CCD'"); fits_add_card (hdulist, obsDate); return (hdulist); } // Convert a string to a decimal or hexadecimal integer // Code taken from Dave Mills Apogee driver unsigned short ApogeeCam::hextoi(char *instr) { unsigned short val, tot = 0; bool IsHEX = false; long n = strlen( instr ); if ( n > 1 ) { // Look for hex format e.g. 8Fh, A3H or 0x5D if ( instr[ n - 1 ] == 'h' || instr[ n - 1 ] == 'H' ) IsHEX = true; else if ( *instr == '0' && *(instr+1) == 'x' ) { IsHEX = true; instr += 2; } } if ( IsHEX ) { while (instr && *instr && isxdigit(*instr)) { val = *instr++ - '0'; if (9 < val) val -= 7; tot <<= 4; tot |= (val & 0x0f); } } else tot = atoi( instr ); return tot; } double ApogeeCam::min() { double lmin = APGFrame.img[0]; int ind=0, i, j; for (i= 0; i < APGFrame.height ; i++) for (j= 0; j < APGFrame.width; j++) { ind = (i * APGFrame.width) + j; if (APGFrame.img[ind] < lmin) lmin = APGFrame.img[ind]; } return lmin; } double ApogeeCam::max() { double lmax = APGFrame.img[0]; int ind=0, i, j; for (i= 0; i < APGFrame.height ; i++) for (j= 0; j < APGFrame.width; j++) { ind = (i * APGFrame.width) + j; if (APGFrame.img[ind] > lmax) lmax = APGFrame.img[ind]; } return lmax; }