| 1 | /*
 | 
|---|
| 2 |    INDI Developers Manual
 | 
|---|
| 3 |    Tutorial #3
 | 
|---|
| 4 |    
 | 
|---|
| 5 |    "Simple CCD Simulator"
 | 
|---|
| 6 |    
 | 
|---|
| 7 |    In this tutorial, we employ a BLOB property to send a sample FITS file to the client.
 | 
|---|
| 8 |    
 | 
|---|
| 9 |    Refer to README, which contains instruction on how to build this driver, and use it 
 | 
|---|
| 10 |    with an INDI-compatible client.
 | 
|---|
| 11 | 
 | 
|---|
| 12 | */
 | 
|---|
| 13 | 
 | 
|---|
| 14 | /** \file tutorial_three.c
 | 
|---|
| 15 |     \brief Simulate a CCD camera by using INDI BLOBs to send FITS data to the client.
 | 
|---|
| 16 |     \author Jasem Mutlaq
 | 
|---|
| 17 | */
 | 
|---|
| 18 | 
 | 
|---|
| 19 | #include <stdio.h>
 | 
|---|
| 20 | #include <stdlib.h>
 | 
|---|
| 21 | #include <string.h>
 | 
|---|
| 22 | #include <stdarg.h>
 | 
|---|
| 23 | #include <math.h>
 | 
|---|
| 24 | #include <unistd.h>
 | 
|---|
| 25 | #include <time.h>
 | 
|---|
| 26 | #include <fcntl.h>
 | 
|---|
| 27 | #include <signal.h>
 | 
|---|
| 28 | #include <errno.h>
 | 
|---|
| 29 | #include <sys/stat.h>
 | 
|---|
| 30 | #include <sys/types.h>
 | 
|---|
| 31 | #include <sys/socket.h>
 | 
|---|
| 32 | #include <netinet/in.h>
 | 
|---|
| 33 | #include <netdb.h>
 | 
|---|
| 34 | #include <zlib.h>
 | 
|---|
| 35 | 
 | 
|---|
| 36 | /* INDI Core headers */
 | 
|---|
| 37 | #include "indidevapi.h"
 | 
|---|
| 38 | #include "indicom.h"
 | 
|---|
| 39 | #include "eventloop.h"
 | 
|---|
| 40 | #include "base64.h"
 | 
|---|
| 41 | 
 | 
|---|
| 42 | #define mydev           "CCD Simulator"
 | 
|---|
| 43 | #define COMM_GROUP      "Main Control"
 | 
|---|
| 44 | 
 | 
|---|
| 45 | /* Handy macro */
 | 
|---|
| 46 | #define currentTemp     TemperatureN[0].value
 | 
|---|
| 47 | 
 | 
|---|
| 48 | void uploadFile(const char* filename);
 | 
|---|
| 49 | void ISPoll(void * p);
 | 
|---|
| 50 | 
 | 
|---|
| 51 | static double targetTemp        = 0;
 | 
|---|
| 52 | 
 | 
|---|
| 53 | /*INDI controls */
 | 
|---|
| 54 | 
 | 
|---|
| 55 | /* Connect/Disconnect */
 | 
|---|
| 56 | static ISwitch PowerS[]                 = {{"CONNECT" , "Connect" , ISS_OFF, 0, 0},{"DISCONNECT", "Disconnect", ISS_ON, 0, 0}};
 | 
|---|
| 57 | static ISwitchVectorProperty PowerSP    = { mydev, "CONNECTION" , "Connection", COMM_GROUP, IP_RW, ISR_1OFMANY, 60, IPS_IDLE, PowerS, NARRAY(PowerS), "", 0};
 | 
|---|
| 58 | 
 | 
|---|
| 59 | /* Exposure time */
 | 
|---|
| 60 | static INumber ExposeTimeN[]    = {{ "CCD_EXPOSURE_VALUE", "Duration (s)", "%5.2f", 0., 36000., .5, 1., 0, 0, 0}};
 | 
|---|
| 61 | static INumberVectorProperty ExposeTimeNP = { mydev, "CCD_EXPOSURE", "Expose", COMM_GROUP, IP_RW, 60, IPS_IDLE, ExposeTimeN, NARRAY(ExposeTimeN), "", 0};
 | 
|---|
| 62 |  
 | 
|---|
| 63 | /* Temperature control */
 | 
|---|
| 64 |  static INumber TemperatureN[]    = { {"CCD_TEMPERATURE_VALUE", "Temperature", "%+06.2f", -30., 40., 1., 0., 0, 0, 0}};
 | 
|---|
| 65 |  static INumberVectorProperty TemperatureNP = { mydev, "CCD_TEMPERATURE", "Temperature (C)", COMM_GROUP, IP_RW, 60, IPS_IDLE, TemperatureN, NARRAY(TemperatureN), "", 0};
 | 
|---|
| 66 | 
 | 
|---|
| 67 | /* BLOB for sending image */
 | 
|---|
| 68 | static IBLOB imageB = {"CCD1", "Feed", "", 0, 0, 0, 0, 0, 0, 0};
 | 
|---|
| 69 | static IBLOBVectorProperty imageBP = {mydev, "Video", "Video", COMM_GROUP,
 | 
|---|
| 70 |   IP_RO, 0, IPS_IDLE, &imageB, 1, "", 0};
 | 
|---|
| 71 | 
 | 
|---|
| 72 | void ISGetProperties (const char *dev)
 | 
|---|
| 73 | { 
 | 
|---|
| 74 | 
 | 
|---|
| 75 |   if (dev && strcmp (mydev, dev))
 | 
|---|
| 76 |     return;
 | 
|---|
| 77 | 
 | 
|---|
| 78 |   /* COMM_GROUP */
 | 
|---|
| 79 |   IDDefSwitch(&PowerSP, NULL);
 | 
|---|
| 80 |   IDDefNumber(&ExposeTimeNP, NULL);
 | 
|---|
| 81 |   IDDefNumber(&TemperatureNP, NULL);
 | 
|---|
| 82 |   IDDefBLOB(&imageBP, NULL);
 | 
|---|
| 83 | 
 | 
|---|
| 84 |   IEAddTimer(1000, ISPoll, NULL);
 | 
|---|
| 85 |   
 | 
|---|
| 86 | }
 | 
|---|
| 87 | 
 | 
|---|
| 88 | /* Note that we must define ISNewBLOB and ISSnoopDevice even if we don't use them, otherwise, the driver will NOT compile */
 | 
|---|
| 89 | void ISNewBLOB (const char *dev, const char *name, int sizes[], int blobsizes[], char *blobs[], char *formats[], char *names[], int n) {}
 | 
|---|
| 90 | void ISSnoopDevice (XMLEle *root) {}
 | 
|---|
| 91 | 
 | 
|---|
| 92 |   
 | 
|---|
| 93 | void ISNewSwitch (const char *dev, const char *name, ISState *states, char *names[], int n)
 | 
|---|
| 94 | {
 | 
|---|
| 95 |         /* ignore if not ours */
 | 
|---|
| 96 |         if (dev && strcmp (dev, mydev))
 | 
|---|
| 97 |             return;
 | 
|---|
| 98 |             
 | 
|---|
| 99 |         /* Connection */
 | 
|---|
| 100 |         if (!strcmp (name, PowerSP.name))
 | 
|---|
| 101 |         {
 | 
|---|
| 102 |           IUUpdateSwitch(&PowerSP, states, names, n);
 | 
|---|
| 103 |           PowerSP.s = IPS_OK;
 | 
|---|
| 104 |           if (PowerS[0].s == ISS_ON)
 | 
|---|
| 105 |                 IDSetSwitch(&PowerSP, "CCD Simulator is online.");
 | 
|---|
| 106 |           else
 | 
|---|
| 107 |                 IDSetSwitch(&PowerSP, "CCD Simulator is offline.");
 | 
|---|
| 108 |           return;
 | 
|---|
| 109 |         }
 | 
|---|
| 110 |         
 | 
|---|
| 111 | }
 | 
|---|
| 112 | 
 | 
|---|
| 113 | void ISNewText (const char *dev, const char *name, char *texts[], char *names[], int n)
 | 
|---|
| 114 | {
 | 
|---|
| 115 |        /* ignore if not ours */ 
 | 
|---|
| 116 |        if (dev && strcmp (mydev, dev))
 | 
|---|
| 117 |          return;
 | 
|---|
| 118 | 
 | 
|---|
| 119 |         /* suppress warning */
 | 
|---|
| 120 |         n=n; dev=dev; name=name; names=names; texts=texts;
 | 
|---|
| 121 |         
 | 
|---|
| 122 | }
 | 
|---|
| 123 | 
 | 
|---|
| 124 | 
 | 
|---|
| 125 | void ISNewNumber (const char *dev, const char *name, double values[], char *names[], int n)
 | 
|---|
| 126 | {
 | 
|---|
| 127 | 
 | 
|---|
| 128 |     /* Exposure time */
 | 
|---|
| 129 |     if (!strcmp (ExposeTimeNP.name, name))
 | 
|---|
| 130 |     {
 | 
|---|
| 131 | 
 | 
|---|
| 132 |      /* Let's make sure that our simulator is online */
 | 
|---|
| 133 |      if (PowerS[0].s != ISS_ON)
 | 
|---|
| 134 |      {
 | 
|---|
| 135 |        ExposeTimeNP.s = IPS_IDLE;
 | 
|---|
| 136 |        IDSetNumber(&ExposeTimeNP, "CCD Simulator is offline.");
 | 
|---|
| 137 |        return;
 | 
|---|
| 138 |      }
 | 
|---|
| 139 | 
 | 
|---|
| 140 |       IDLog("Sending BLOB FITS...\n");
 | 
|---|
| 141 | 
 | 
|---|
| 142 |       ExposeTimeNP.s = IPS_BUSY;
 | 
|---|
| 143 |       ExposeTimeN[0].value = values[0];
 | 
|---|
| 144 |  
 | 
|---|
| 145 |       return;
 | 
|---|
| 146 |     } 
 | 
|---|
| 147 | 
 | 
|---|
| 148 |     /* Temperature */
 | 
|---|
| 149 |     if (!strcmp (TemperatureNP.name, name))
 | 
|---|
| 150 |     {
 | 
|---|
| 151 | 
 | 
|---|
| 152 |      /* Let's make sure that our simulator is online */
 | 
|---|
| 153 |      if (PowerS[0].s != ISS_ON)
 | 
|---|
| 154 |      {
 | 
|---|
| 155 |        TemperatureNP.s = IPS_IDLE;
 | 
|---|
| 156 |        IDSetNumber(&TemperatureNP, "CCD Simulator is offline");
 | 
|---|
| 157 |        return;
 | 
|---|
| 158 |      }
 | 
|---|
| 159 | 
 | 
|---|
| 160 |       targetTemp = values[0];
 | 
|---|
| 161 | 
 | 
|---|
| 162 |       /* If the requested temperature is the current CCD chip temperature, then return */
 | 
|---|
| 163 |       if (targetTemp == TemperatureN[0].value)
 | 
|---|
| 164 |       {
 | 
|---|
| 165 |         TemperatureNP.s = IPS_OK;
 | 
|---|
| 166 |         IDSetNumber(&TemperatureNP, NULL);
 | 
|---|
| 167 |         return;
 | 
|---|
| 168 |       }
 | 
|---|
| 169 |       
 | 
|---|
| 170 |       /* Otherwise, set property state to busy */
 | 
|---|
| 171 |       TemperatureNP.s = IPS_BUSY;
 | 
|---|
| 172 |       IDSetNumber(&TemperatureNP, "Setting CCD temperature to %g", targetTemp);
 | 
|---|
| 173 | 
 | 
|---|
| 174 |       return;
 | 
|---|
| 175 |     }
 | 
|---|
| 176 | 
 | 
|---|
| 177 | }
 | 
|---|
| 178 | 
 | 
|---|
| 179 | /* Open a data file, compress its content, and send it via our BLOB property */
 | 
|---|
| 180 | void uploadFile(const char* filename)
 | 
|---|
| 181 | {
 | 
|---|
| 182 |    FILE * fitsFile;
 | 
|---|
| 183 |    unsigned char *fitsData, *compressedData;
 | 
|---|
| 184 |    int r=0;
 | 
|---|
| 185 |    unsigned int i =0, nr = 0;
 | 
|---|
| 186 |    uLongf compressedBytes=0;
 | 
|---|
| 187 |    uLong  totalBytes;
 | 
|---|
| 188 |    struct stat stat_p; 
 | 
|---|
| 189 |  
 | 
|---|
| 190 |    /* #1 Let's get the file size */
 | 
|---|
| 191 |    if ( -1 ==  stat (filename, &stat_p))
 | 
|---|
| 192 |    { 
 | 
|---|
| 193 |      IDLog(" Error occoured attempting to stat file.\n"); 
 | 
|---|
| 194 |      return; 
 | 
|---|
| 195 |    }
 | 
|---|
| 196 |    
 | 
|---|
| 197 |    /* #2 Allocate memory for raw and compressed data */
 | 
|---|
| 198 |    totalBytes     = stat_p.st_size;
 | 
|---|
| 199 |    fitsData       = (unsigned char *) malloc (sizeof(unsigned char) * totalBytes);
 | 
|---|
| 200 |    compressedData = (unsigned char *) malloc (sizeof(unsigned char) * totalBytes + totalBytes / 64 + 16 + 3);
 | 
|---|
| 201 |    
 | 
|---|
| 202 |    if (fitsData == NULL || compressedData == NULL)
 | 
|---|
| 203 |    {
 | 
|---|
| 204 |      IDLog("Error! low memory. Unable to initialize fits buffers.\n");
 | 
|---|
| 205 |      return;
 | 
|---|
| 206 |    }
 | 
|---|
| 207 |    
 | 
|---|
| 208 |    /* #3 Open the FITS file */
 | 
|---|
| 209 |    fitsFile = fopen(filename, "r");
 | 
|---|
| 210 |    
 | 
|---|
| 211 |    if (fitsFile == NULL)
 | 
|---|
| 212 |     return;
 | 
|---|
| 213 |    
 | 
|---|
| 214 |    /* #4 Read file from disk */ 
 | 
|---|
| 215 |    for (i=0; i < totalBytes; i+= nr)
 | 
|---|
| 216 |    {
 | 
|---|
| 217 |       nr = fread(fitsData + i, 1, totalBytes - i, fitsFile);
 | 
|---|
| 218 |      
 | 
|---|
| 219 |      if (nr <= 0)
 | 
|---|
| 220 |      {
 | 
|---|
| 221 |         IDLog("Error reading temporary FITS file.\n");
 | 
|---|
| 222 |         return;
 | 
|---|
| 223 |      }
 | 
|---|
| 224 |    }
 | 
|---|
| 225 |    
 | 
|---|
| 226 |    compressedBytes = sizeof(char) * totalBytes + totalBytes / 64 + 16 + 3;
 | 
|---|
| 227 |     
 | 
|---|
| 228 |    /* #5 Compress raw data */ 
 | 
|---|
| 229 |    r = compress2(compressedData, &compressedBytes, fitsData, totalBytes, 9);
 | 
|---|
| 230 |    if (r != Z_OK)
 | 
|---|
| 231 |    {
 | 
|---|
| 232 |         /* this should NEVER happen */
 | 
|---|
| 233 |         IDLog("internal error - compression failed: %d\n", r);
 | 
|---|
| 234 |         return;
 | 
|---|
| 235 |    }
 | 
|---|
| 236 |    
 | 
|---|
| 237 |    /* #6 Send it */
 | 
|---|
| 238 |    imageB.blob = compressedData;
 | 
|---|
| 239 |    imageB.bloblen = compressedBytes;
 | 
|---|
| 240 |    imageB.size = totalBytes;
 | 
|---|
| 241 |    strcpy(imageB.format, ".fits.z");
 | 
|---|
| 242 | 
 | 
|---|
| 243 |    /* #7 Set BLOB state to Ok */
 | 
|---|
| 244 |    imageBP.s = IPS_OK;
 | 
|---|
| 245 |    IDSetBLOB (&imageBP, NULL);
 | 
|---|
| 246 | 
 | 
|---|
| 247 |    /* #8 Set Exposure status to Ok */
 | 
|---|
| 248 |    ExposeTimeNP.s = IPS_OK;
 | 
|---|
| 249 |    IDSetNumber(&ExposeTimeNP, "Sending FITS...");
 | 
|---|
| 250 |    
 | 
|---|
| 251 |    /* #9 Let's never forget to free our memory */
 | 
|---|
| 252 |    free (fitsData);   
 | 
|---|
| 253 |    free (compressedData);
 | 
|---|
| 254 |    
 | 
|---|
| 255 | }
 | 
|---|
| 256 | 
 | 
|---|
| 257 | /* Poll device status and update accordingly */
 | 
|---|
| 258 | void ISPoll(void * p)
 | 
|---|
| 259 | {
 | 
|---|
| 260 | 
 | 
|---|
| 261 |    /* We need to check the status of properties that we want to watch. */
 | 
|---|
| 262 | 
 | 
|---|
| 263 |    /* #1 CCD Exposure */
 | 
|---|
| 264 |    switch (ExposeTimeNP.s)
 | 
|---|
| 265 |    {
 | 
|---|
| 266 |      case IPS_IDLE:
 | 
|---|
| 267 |      case IPS_OK:
 | 
|---|
| 268 |      case IPS_ALERT:
 | 
|---|
| 269 |         break;
 | 
|---|
| 270 | 
 | 
|---|
| 271 |      /* If an exposure state is busy, decrement the exposure value until it's zero (done exposing). */
 | 
|---|
| 272 |      case IPS_BUSY:
 | 
|---|
| 273 |         ExposeTimeN[0].value--;
 | 
|---|
| 274 | 
 | 
|---|
| 275 |         /* Are we done yet? */
 | 
|---|
| 276 |         if (ExposeTimeN[0].value == 0)
 | 
|---|
| 277 |         {
 | 
|---|
| 278 |           /* Let's set the state of OK and report that to the client */
 | 
|---|
| 279 |           ExposeTimeNP.s = IPS_OK;
 | 
|---|
| 280 |  
 | 
|---|
| 281 |           /* Upload a sample FITS file */
 | 
|---|
| 282 |           uploadFile("ngc1316o.fits");
 | 
|---|
| 283 | 
 | 
|---|
| 284 |           IDSetNumber(&ExposeTimeNP, NULL);
 | 
|---|
| 285 |           break;
 | 
|---|
| 286 |         }
 | 
|---|
| 287 | 
 | 
|---|
| 288 |         IDSetNumber(&ExposeTimeNP, NULL);
 | 
|---|
| 289 |         break;
 | 
|---|
| 290 |    } 
 | 
|---|
| 291 | 
 | 
|---|
| 292 |    /* #2 CCD Temperature */
 | 
|---|
| 293 |    switch (TemperatureNP.s)
 | 
|---|
| 294 |    {
 | 
|---|
| 295 |      case IPS_IDLE:
 | 
|---|
| 296 |      case IPS_OK:
 | 
|---|
| 297 |      case IPS_ALERT:
 | 
|---|
| 298 |         break;
 | 
|---|
| 299 | 
 | 
|---|
| 300 |      case IPS_BUSY:
 | 
|---|
| 301 |      /* If target temperature is higher, then increase current CCD temperature */  
 | 
|---|
| 302 |      if (currentTemp < targetTemp)
 | 
|---|
| 303 |         currentTemp++;
 | 
|---|
| 304 |      /* If target temperature is lower, then decrese current CCD temperature */
 | 
|---|
| 305 |      else if (currentTemp > targetTemp)
 | 
|---|
| 306 |        currentTemp--;
 | 
|---|
| 307 |      /* If they're equal, stop updating */
 | 
|---|
| 308 |      else
 | 
|---|
| 309 |      {
 | 
|---|
| 310 |        TemperatureNP.s = IPS_OK;
 | 
|---|
| 311 |        IDSetNumber(&TemperatureNP, "Target temperature reached.");
 | 
|---|
| 312 | 
 | 
|---|
| 313 |        break;
 | 
|---|
| 314 |      }
 | 
|---|
| 315 | 
 | 
|---|
| 316 |      IDSetNumber(&TemperatureNP, NULL);
 | 
|---|
| 317 |      break;
 | 
|---|
| 318 |    }
 | 
|---|
| 319 | 
 | 
|---|
| 320 |    /* Keep polling alive */
 | 
|---|
| 321 |    IEAddTimer (1000, ISPoll, NULL);
 | 
|---|
| 322 | 
 | 
|---|
| 323 | }
 | 
|---|