/*

Copyright (c) 2001-2013 Fishcamp Engineering (support@fishcamp.com)

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

		Redistributions of source code must retain the above copyright
		notice, this list of conditions and the following disclaimer.

		Redistributions in binary form must reproduce the above
		copyright notice, this list of conditions and the following
		disclaimer in the documentation and/or other materials
		provided with the distribution.

Modified by: Jasem Mutlaq (2013)

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
======================================================================
*/

#include "fishcamp.h"
#include "indimacros.h"

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdbool.h>

#include <libusb.h>

#define MAXRBUF 512

// globals
struct libusb_context *gCtx;

fc_Camera_Information gCamerasFound[kNumCamsSupported];

bool gFWInitialized; // set when the fcUsb_init is executed the first time.
char gBuffer[513];

UInt16 gRelease;           // set in the 'RawDeviceAdded' routine.  Is actually used
                           // by fishcamp as a camera serial number
int gNumCamerasDiscovered; // total cameras found on the USB interface

bool gReadBlack[kNumCamsSupported];      // set if the host wishes to read the black pixels
int gDataXfrReadMode[kNumCamsSupported]; // the type of data transfers we will be using when taking picts
int gDataFormat[kNumCamsSupported];      // the desired camera data format

fcFindCamState gFindCamState;  // the progress state codes that the fcUsb_FindCameras routine reports
float gFindCamPercentComplete; // 0.0 -> 100.0 progress as a percent of total time.

UInt32 gCurrentIntegrationTime[kNumCamsSupported];
UInt32
    gCurrentGuiderIntegrationTime[kNumCamsSupported]; // currently set exposure time for the guider part of the camera

// add an internal frame buffer to handle time when we do black level line compensation.
// we use the internal frame buffer which is bigger than what the user asked for in order to
// be able to handle the extra black columns.  We then strip it out of the uploaded image
// so the user never sees them
UInt16 *gFrameBuffer;
UInt16 gRoi_left[kNumCamsSupported]; // this is the requested size from the user
UInt16 gRoi_top[kNumCamsSupported];
UInt16 gRoi_right[kNumCamsSupported];
UInt16 gRoi_bottom[kNumCamsSupported];

// for IBIS 1300 image sensor we look at the average of the first row of black pixels to perform the column normalization
// this is where we store the average.  It is computed every time the gain setting on the image sensor is made.
SInt32 gBlackOffsets[1280];

// for the Starfish PRO camera, we will calculate the column and row offsets from information in the overscan pixels
// we will use the vertical overscan to calculate the column offsets and the horizontal overscan to calculate the
// row offsets.  The final number in this vector will be the number that needs to be added to the given row/col
// to normalize the image.  Thus the reason of using SInt32.
SInt32 gProBlackColOffsets[4096]; // offsets.  one for each column
SInt32 gProBlackRowOffsets[4096]; // offsets.  one for each row
bool gProWantColNormalization;    // true if we want column normalization

//CCyUSBDevice*		gUSBDevice;

bool gDoLogging;    // set to TRUE to enable logging to the log file
bool gDoSimulation; // set to TRUE to enable simulation

int gCameraImageFilter[kNumCamsSupported]; // type of image filter for post processing on this camera

UInt16 gBlackPedestal[kNumCamsSupported];

//Location for Drivers
char driverSupportPath[MAXRBUF];

// send data via the designated camera's bulk out endpoint
// valid camNum is 1 -> fcUsb_GetNumCameras()
//
int SendUSB(int camNum, unsigned char *data, int length)
{
    int retVal, transferred;
    struct libusb_device_handle *dev;

    

    retVal = -1;

    dev = gCamerasFound[camNum - 1].dev;
    if (dev != NULL)
        retVal = libusb_bulk_transfer(dev, FC_STARFISH_BULK_OUT_ENDPOINT, data, length, &transferred, 10000);
    else
    {
        Starfish_LogFmt("Error on Sending Libusb Bulk Transfer: no camera handle\n");
        
        return 0;
    }

    if (retVal == 0)
    {
        Starfish_LogFmt("Sent - %d bytes\n", transferred);
        
        return transferred;
    }
    else
    {
        Starfish_LogFmt("Error on Sending Libusb Bulk Transfer: %s\n", libusb_error_name(retVal));
        
        return 0;
    }
}

// receive data via the designated camera's bulk in endpoint
// valid camNum is 1 -> fcUsb_GetNumCameras()
//
int RcvUSB(int camNum, unsigned char *data, int maxBytes)
{
    int retVal, transferred;
    struct libusb_device_handle *dev;

    

    retVal = -1;

    dev = gCamerasFound[camNum - 1].dev;
    if (dev != NULL)
        retVal = libusb_bulk_transfer(dev, FC_STARFISH_BULK_IN_ENDPOINT, data, maxBytes, &transferred, 10000);
    else
    {
        Starfish_LogFmt("Error on Receiving Libusb Bulk Transfer: no camera handle\n");
        
        return 0;
    }

    if (retVal == 0)
    {
        Starfish_LogFmt("RcvUSB - %d bytes\n", transferred);
        
        return transferred;
    }
    else
    {
        Starfish_LogFmt("Error on Receiving Libusb Bulk Transfer: %s\n", libusb_error_name(retVal));
        
        return 0;
    }
}

// routine to check to see if we have a starfish log file on disk
// return TRUE if one exists
//
bool haveStarfishLogFile()
{
    FILE *theLogFile;
    bool retValue;

    // assume the file isn't there
    retValue = false;

    // Open for read (will fail if file does not exist)
    char filename[MAXRBUF];
    snprintf(filename, MAXRBUF, "%s/.indi/starfish_log.txt", getenv("HOME"));
    if ((theLogFile = fopen(filename, "r")) == NULL)
    {
        // no such file
        //		printf("haveStarfishLogFile = FALSE\n");
        retValue = false;
    }
    else
    {
        // the file was there
        //		printf("haveStarfishLogFile = TRUE\n");
        fclose(theLogFile);
        retValue = true;
    }

    return retValue;
}

// routine to create the starfish log file on disk.
// will first check to see if the file already exists.
// will simply return if the file is there already
// otherwise it will create it.
//
void creatStarfishLogFile()
{
    FILE *theLogFile;

    if (!haveStarfishLogFile())
    {
        char filename[MAXRBUF];
        snprintf(filename, MAXRBUF, "%s/.indi/starfish_log.txt", getenv("HOME"));
        if (!(theLogFile = fopen(filename, "w")))
        {
            fprintf(stderr, "Error opening Starfish log file (%s) : %s\n", filename, strerror(errno));
            return;
        }
        fclose(theLogFile);
    }
}

// routine that is called everytime we make a log file entry.
// we don't want the log file to get too big, so we check its size
// and resize it if it was getting too big.  Simple routine
// here is that it checks the size of the file and if too
// big, it simply clears it out starting at zero again.
//
void check4tooLongLogFile()
{
    FILE *theLogFile;
    long length;
    //int result;

    if (haveStarfishLogFile())
    {
        // Open for write
        char filename[MAXRBUF];
        snprintf(filename, MAXRBUF, "%s/.indi/starfish_log.txt", getenv("HOME"));
        theLogFile = fopen(filename, "a+");
        fseek(theLogFile, 0, SEEK_END);
        length = ftell(theLogFile);
        fclose(theLogFile);

        if (length > 8000000)
        {
            // file too big, need to reset it and start over
            //			printf("Re-creating (smaller) Starfish Log File.\n");
            theLogFile = fopen(filename, "w"); // "w" will re-create the file
            fclose(theLogFile);
        }
    }
}

void fc_timestamp(FILE *out)
{
    time_t t;
    struct timeval tv;
    char timestamp[40];

    gettimeofday(&tv, NULL);
    *(unsigned long *)&t = tv.tv_sec;
    strftime(timestamp, sizeof timestamp, "%e %b %G %T", localtime(&t));

    fprintf(out, "%s.%02ld ", timestamp, (long)(tv.tv_usec / 10000));
}

//
// routine to add a log entry to the Starfish log file
//
// The log file is by default placed in \etc\fishcamp\starfish_log.txt
//
// This routine will first check to see if the log file exists and create it if it doesn't
//
// Then the routine will append the passed logString to the end of the file.
//

void Starfish_LogFmt(const char *fmt, ...)
{
    FILE *theLogFile;

    if (!gDoLogging) // only do if logging has been enabled
        return;

    // check to see if we have the starfish log file yet
    // if not create one
    if (!haveStarfishLogFile())
        creatStarfishLogFile();

    // simple way of assuring the log file doesn't get too big
    check4tooLongLogFile();

    char filename[MAXRBUF];
    snprintf(filename, MAXRBUF, "%s/.indi/starfish_log.txt", getenv("HOME"));
    if ((theLogFile = fopen(filename, "a+")) != NULL)
    {
        va_list ap;
        va_start(ap, fmt);
        // get operating system-style date and time.
        fc_timestamp(theLogFile);

        //sprintf( buffer, "- %s", logString );

        //numWritten = fputs( buffer, theLogFile );
        fputs("- ", theLogFile);
        vfprintf(theLogFile, fmt, ap);
        fclose(theLogFile);
        va_end(ap);
    }
    //		else
    //			printf( "Problem opening the file\n" );
}

void Starfish_Log(const char *logString)
{
    Starfish_LogFmt("%s", logString);
}

// utility routine to see if we have already discovered that a certain
// camera was connected to this computer.  Will return TRUE if so.
//
// enter with:
//		- FC vendor ID
//		- RAW camera product ID or -1 if not valid
//		- Final camera product ID or -1 if not valid
//		- camera serial number
//
// the routine will use the desired camera product ID specified.  pass in a -1 if not valid.
//
bool sawThisCameraAlready(UInt16 vendor, UInt16 rawProduct, UInt16 finalProduct, UInt16 release)
{
    int i;
    bool retValue;

    retValue = false;

    for (i = 0; i < kNumCamsSupported; i++)
    {
        if (gCamerasFound[i].camVendor == vendor)
        {
            if ((rawProduct == 0xffff) || (gCamerasFound[i].camRawProduct == rawProduct))
            {
                if ((finalProduct == 0xffff) || (gCamerasFound[i].camFinalProduct == finalProduct))
                {
                    if (gCamerasFound[i].camRelease == release)
                    {
                        retValue = true;
                    }
                }
            }
        }
    }

    return retValue;
}

// utility routine to return the index of the camera in the local 'gCamerasFound' array
// will return 0 -> (kNumCamsSupported - 1) if we have the camera in our data base
// will return -1 if we don't.
//
// enter with:
//		- FC vendor ID
//		- RAW camera product ID or -1 if not valid
//		- Final camera product ID or -1 if not valid
//		- camera serial number
//
// the routine will use the desired camera product ID specified.  pass in a -1 if not valid.
//
int getCameraDBIndex(UInt16 vendor, UInt16 rawProduct, UInt16 finalProduct, UInt16 release)
{
    int i;
    int retValue;

    retValue = -1;

    for (i = 0; i < kNumCamsSupported; i++)
    {
        if (gCamerasFound[i].camVendor == vendor)
        {
            if ((rawProduct == 0xffff) || (gCamerasFound[i].camRawProduct == rawProduct))
            {
                if ((finalProduct == 0xffff) || (gCamerasFound[i].camFinalProduct == finalProduct))
                {
                    if (gCamerasFound[i].camRelease == release)
                    {
                        retValue = i;
                    }
                }
            }
        }
    }

    return retValue;
}

// utility routine to return the DB index of the next available slot in the local 'gCamerasFound' array
// will return 0 -> (kNumCamsSupported - 1) if we have room in our data base
// will return -1 if we don't.
//
//
int getNextFreeDBIndex(void)
{
    int i;
    int retValue;

    retValue = -1;

    for (i = 0; i < kNumCamsSupported; i++)
    {
        if (gCamerasFound[i].camVendor == 0)
        {
            retValue = i;
            break;
        }
    }

    return retValue;
}

// utility debug routine used to print out the camera data base
//
void printOutDiscoveredCamerasDB(void)
{
    int i;
    

    for (i = 0; i < kNumCamsSupported; i++)
    {
        Starfish_LogFmt("Camera DB for discovered camera # - %d\n", i);
        

        Starfish_LogFmt("     gCamerasFound[%d].camVendor       - %04x\n", i, gCamerasFound[i].camVendor);
        

        Starfish_LogFmt("     gCamerasFound[%d].camRawProduct   - %04x\n", i, gCamerasFound[i].camRawProduct);
        

        Starfish_LogFmt("     gCamerasFound[%d].camFinalProduct - %04x\n", i, gCamerasFound[i].camFinalProduct);
        

        Starfish_LogFmt("     gCamerasFound[%d].camRelease      - %04x\n", i, gCamerasFound[i].camRelease);
        
    }
}

void AnchorWrite(struct libusb_device_handle *dev_handle, UInt16 anchorAddress, UInt16 count, UInt8 writeBuffer[])
{
    int retval;

    retval = libusb_control_transfer(dev_handle,                 /*dev_handle*/
                                     LIBUSB_REQUEST_TYPE_VENDOR, //REQ_VENDOR, /*request type*/
                                     0xa0,                       /*request*/
                                     anchorAddress,              /*value*/
                                     0,                          /*index*/
                                     writeBuffer,                /*(unsigned char *)data*/
                                     count,                      /*length*/
                                     5000                        /*timeout*/
    );
    if (retval < 0)
        Starfish_Log("Error in control transfer.\n");
}

FILE *openFile(UInt16 vendor, UInt16 product)
{
	INDI_UNUSED(vendor);
    char name[MAXRBUF];
    char name2[MAXRBUF];
    snprintf(name, MAXRBUF, "%s/gdr_usb.hex", driverSupportPath);
    snprintf(name2, MAXRBUF, "%s/pro_usb.hex", driverSupportPath);
    //char path[256];
    FILE *hexFile;

    Starfish_Log("openFile routine\n");

    if (product == 0x0008)
    {
        Starfish_LogFmt("File to open is \"%s\"\n", name2);

        hexFile = fopen(name2, "r");
    }
    else
    {
        Starfish_LogFmt("File to open is \"%s\"\n", name);

        hexFile = fopen(name, "r");
    }

    return (hexFile);
}

/*
	Description of Intel hex records.

Position	Description
1		Record Marker: The first character of the line is always a colon (ASCII 0x3A) to identify 
						the line as an Intel HEX file
2 - 3	Record Length: This field contains the number of data bytes in the register represented 
						as a 2-digit hexidecimal number. This is the total number of data bytes, 
						not including the checksum byte nor the first 9 characters of the line.
4 - 7	Address: This field contains the address where the data should be loaded into the chip. 
						This is a value from 0 to 65,535 represented as a 4-digit hexidecimal value.
8 - 9	Record Type: This field indicates the type of record for this line. The possible values 
						are: 00=Register contains normal data. 01=End of File. 02=Extended address.
10 - ?	Data Bytes: The following bytes are the actual data that will be burned into the EPROM. The 
						data is represented as 2-digit hexidecimal values.
Last 2 characters	Checksum: The last two characters of the line are a checksum for the line. The 
						checksum value is calculated by taking the twos complement of the sum of all 
						the preceeding data bytes, excluding the checksum byte itself and the colon 
						at the beginning of the line.
*/
int hexRead(INTEL_HEX_RECORD *record, FILE *hexFile, UInt16 vendor, UInt16 product)
{ // Read the next hex record from the file into the structure
	INDI_UNUSED(vendor);
    // **** Need to impliment checksum checking ****
    if (gDoSimulation)
        return 0;

    char c;
    UInt16 i;
    UInt16 newChecksum;
    int n, c1, check, len;
    

    c = getc(hexFile);

    if (c != ':')
    {
        Starfish_LogFmt("Line does not start with colon (%d)\n", c);
        
        return (-1);
    }
    n = fscanf(hexFile, "%2lX%4lX%2lX", &record->Length, &record->Address, &record->Type);
    if (n != 3)
    {
        Starfish_LogFmt("Could not read line preamble %d\n", c);
        
        return (-1);
    }

    len = record->Length;
    if (len > MAX_INTEL_HEX_RECORD_LENGTH)
    {
        Starfish_LogFmt("length is more than can fit %d, %d\n", len, MAX_INTEL_HEX_RECORD_LENGTH);
        
        return (-1);
    }
    for (i = 0; i < len; i++)
    {
        n = fscanf(hexFile, "%2X", &c1);
        if (n != 1)
        {
            if (i != record->Length)
            {
                Starfish_LogFmt("Line finished at wrong time %d, %ld\n", i, record->Length);

                
                return (-1);
            }
        }
        record->Data[i] = c1;
    }
    n = fscanf(hexFile, "%2X\n", &check);
    if (n != 1)
    {
        Starfish_LogFmt("Check not found\n");
        
        return (-1);
    }

    // final operation.  stuff the serial number of the RAW camera into the firmware file of the final camera
    //	printf("     record->Length  = %ld\n", record->Length);
    //	printf("     record->Address = %ld\n", record->Address);
    //	printf("     record->Type    = %ld\n", record->Type);
    if ((record->Length == 16) && (record->Address == 0x0700) && (record->Type == 00))
    {
        Starfish_Log("setting configured productID\n");
        record->Data[10] = (product & 0xFF) | 0x01; // even# was RAW device, odd will be CONFIGURED device
        record->Data[11] = (product >> 8) & 0xFF;

        Starfish_Log("setting serial number\n");
        record->Data[12] = gRelease & 0xFF;
        record->Data[13] = (gRelease >> 8) & 0xFF;

        // compute new checksum
        newChecksum = 0x10 + 0x07;
        for (i = 0; i < len; i++)
        {
            newChecksum = newChecksum + record->Data[i];
        }
        newChecksum = ~newChecksum;
        newChecksum = newChecksum + 1;
        newChecksum = newChecksum & 0xff;

        //		printf("     old check       = %ld\n", check);
        check = newChecksum;
        //		printf("     new check       = %ld\n", check);
    }

    return (0);
}

// routine used to load the USB chip's firmware.
// called from RawDeviceAdded,
//
int DownloadToAnchorDevice(struct libusb_device_handle *dev, UInt16 vendor, UInt16 product)
{
    UInt8 writeVal;
    int kr;
    FILE *hexFile;
    INTEL_HEX_RECORD anchorCode;

    Starfish_Log("DownloadToAnchorDevice routine\n");

    if (gDoSimulation)
        return 0;

    hexFile = openFile(vendor, product);
    if (hexFile == NULL)
    {
        Starfish_Log("DownloadToAnchorDevice could not open file.\n");
        return -1;
    }

    // Assert reset
    writeVal = 1;
    AnchorWrite(dev, k8051_USBCS, 1, &writeVal);

    // Download code
    while (1)
    {
        kr = hexRead(&anchorCode, hexFile, vendor, product);
        if (anchorCode.Type != 0)
            break;

        if (kr == 0)
            AnchorWrite(dev, anchorCode.Address, anchorCode.Length, anchorCode.Data);
    }

    // De-assert reset
    writeVal = 0;
    AnchorWrite(dev, k8051_USBCS, 1, &writeVal);

    return 0;
}

// Routine to search the 'fishcamp' directory for the latest version of the
// MicroBlaze SREC file.  The following file name format is assumed
//		"\etc\fishcamp\Guider_mono_revXX_intel.srec";
//
// where revXX is an incrementing rev number.
//
// will return the rev number of the latest file found or -1 if no file found
//
int GetLatestSrecFileRevNumber(void)
{
    int returnNumber;
    //int tempInt;
    bool done;
    //char fileName[200];
    FILE *srecFile;

    Starfish_Log("GetLatestSrecFileRevNumber\n");

    returnNumber = 16; // rev 16 is the latest version released
    done         = false;
    while (!done)
    {
        // generate the next file name to look for
        char fileName[MAXRBUF];
        snprintf(fileName, MAXRBUF, "%s/Guider_mono_rev%02d_intel.srec", driverSupportPath, returnNumber);
        Starfish_LogFmt("  Looking for file - \"%s\"\n", fileName);

        // try to open the file
        srecFile = fopen(fileName, "r");
        if (srecFile == NULL)
        {
            // file didn't exist so we must be done looking
            Starfish_Log("  Done looking\n");
            done = true;
        }
        else
        {
            // file was there so keep looking
            Starfish_Log("  Found SREC file\n");

            fclose(srecFile);
            returnNumber++;
        }
    }

    returnNumber--;

    if (returnNumber == 15)
    {
        Starfish_Log("  Could not find an SREC file\n");
        returnNumber = -1;
    }
    else
    {
        char fileName[MAXRBUF];
        snprintf(fileName, MAXRBUF, "%s/Guider_mono_rev%02d_intel.srec", driverSupportPath, returnNumber);
        Starfish_LogFmt("  Most recent SREC file is - \"%s\"\n", fileName);
    }

    return (returnNumber);
}

// routine to open the file containing the program to be executed by
// the camera's microBlaze processor.  File is S-records
//
FILE *openSrecFile(UInt16 vendor, UInt16 rawProduct)
{
	INDI_UNUSED(vendor);
    char name[MAXRBUF];
    char name1[MAXRBUF];
    char name2[MAXRBUF];
    snprintf(name, MAXRBUF, "%s/Guider_mono_rev16_intel.srec", driverSupportPath);
    snprintf(name1, MAXRBUF, "%s/ibis_rev1_intel.srec", driverSupportPath);          // ibis 1300
    snprintf(name2, MAXRBUF, "%s/StarfishPRO4M_rev1_intel.srec", driverSupportPath); // StarfishPRO4M
    int fileRevNumber;
    //char fileName[200];

    FILE *srecFile;

    Starfish_Log("openSrecFile routine\n");

    if (rawProduct == starfish_ibis13_raw_deviceID)
    {
        Starfish_LogFmt("File to open is \"%s\"\n", name1);

        srecFile = fopen(name1, "r");
    }
    else
    {
        if (rawProduct == starfish_pro4m_raw_deviceID)
        {
            Starfish_LogFmt("File to open is \"%s\"\n", name2);

            srecFile = fopen(name2, "r");
        }

        else
        {
            // look for the most recent SREC file
            fileRevNumber = GetLatestSrecFileRevNumber();

            if (fileRevNumber != -1)
            {
                char fileName[MAXRBUF];
                snprintf(fileName, MAXRBUF, "%s/Guider_mono_rev16_intel.srec", driverSupportPath);

                Starfish_LogFmt("File to open is \"%s\"\n", fileName);

                srecFile = fopen(fileName, "r");
            }
            else
            {
                srecFile = NULL;
                Starfish_Log("  Could not open the file\n");
            }
        }
    }

    return (srecFile);
}

char *srecRead(UInt8 *record, FILE *srecFile)
{
    // Read the next srecord from the file into the buffer
    int maxLineSize;

    maxLineSize = 100;
    return (fgets((char *)record, maxLineSize, srecFile));
}

// will download the application program to the MicroBlaze processor in the camera.
// At boot-up, the camera is looking for srecords.  After the download the processor
// will jump execution to the downloaded program.
//
//
// valid camNum is 1 -> fcUsb_GetNumCameras()
//

void DownloadtToMicroBlaze(int camNum)

{
    FILE *mySrecFile;
    char *lineReadResult;
    bool done;
    static char srecBuffer[513];
    int /*index,*/ retVal, msgSize, transferred;
    UInt16 vendor, rawProduct, finalProduct, release;
    INDI_UNUSED(finalProduct);
    INDI_UNUSED(release);
    struct libusb_device_handle *USBdev;
    

    Starfish_Log("DownloadtToMicroBlaze routine\n");

    if (gDoSimulation)
        return;

    vendor       = gCamerasFound[camNum - 1].camVendor;
    rawProduct   = gCamerasFound[camNum - 1].camRawProduct;
    finalProduct = gCamerasFound[camNum - 1].camFinalProduct;
    release      = gCamerasFound[camNum - 1].camRelease;
    USBdev       = gCamerasFound[camNum - 1].dev;

    mySrecFile = openSrecFile(vendor, rawProduct);

    if (mySrecFile != NULL)
    {
        Starfish_LogFmt("SrecFile Opened and writing\n");
        
        done = false;
        while (!done)
        {
            lineReadResult = srecRead((UInt8 *)&srecBuffer[0], mySrecFile);
            if (lineReadResult == 0)
                done = true;
            else
            {
                msgSize = strlen(srecBuffer);
                retVal  = libusb_bulk_transfer(USBdev, FC_STARFISH_BULK_OUT_ENDPOINT, (unsigned char *)&srecBuffer[0],
                                              msgSize, &transferred, 10000);
                if (retVal != 0)
                {
                    Starfish_LogFmt("Error on Libusb Bulk Transfer: %s\n", libusb_error_name(retVal));
                    
                }
                else
                {
                    //For some reason, without this it fails to send all the lines to the camera.
                    usleep(100);
                }
            }
        }
        Starfish_LogFmt("Closing Srecfile\n");
        
        fclose(mySrecFile);
    }
    else
    {
        Starfish_LogFmt("Error opening SrecFile!\n");
        
    }
}

// Notification.  Here when we discover that the guider camera is first plugged in.
//
//   - reads, VendorID, ProductID, and ReleaseID
//   - calls 'DownloadToAnchorDevice' to load the USB chip's firmware
//
//void RawDeviceAdded(void)
//{
//    UInt16		vendor;
//    UInt16		product;
//    UInt16		release;
//	int			index;
//	int			kr;
// 	char		buffer[200];
//
//
//
//    Starfish_Log("RawDeviceAdded routine\n");
//
//
//	vendor  = gUSBDevice->VendorID;
//	product = gUSBDevice->ProductID;
//	release = gUSBDevice->BcdDevice;
//
//	Starfish_LogFmt("     vendor  = %08x\n", vendor);
//	Starfish_Log( buffer );
//	Starfish_LogFmt("     product = %08x\n", product);
//	Starfish_Log( buffer );
//	Starfish_LogFmt("     release = %08x\n", release);
//	Starfish_Log( buffer );
//
//	// put the new RAW camera in our data base
//	if (!sawThisCameraAlready (vendor, product, 0xffff, release))
//		{
//		index = getNextFreeDBIndex();
//
//		gCamerasFound[index].camVendor       = vendor;
//		gCamerasFound[index].camRawProduct   = product;
//		gCamerasFound[index].camFinalProduct = 0;
//		gCamerasFound[index].camRelease      = release;
////		gCamerasFound[index].camUsbIntfc     = 0;
//
//		Starfish_LogFmt("RawDeviceAdded - added RAW camera to DB index at index = %d\n", index);
//		Starfish_Log( buffer );
//		}
//
//	// the ReleaseNumber is used by fishcamp as a camera serial number.  Store it away
//	gRelease = release;
//
//    ConfigureAnchorDevice();
//
//    kr = DownloadToAnchorDevice(vendor, product);
//    if (0 != kr)
//		{
//        Starfish_LogFmt("unable to download to device: %08x\n", kr);
//		Starfish_Log( buffer );
//        }
//
//}

//Notfication.  here when we discover our newly programmed guider device
//  - calls 'FindInterfaces'
//  - calls 'DownloadtToMicroBlaze' to load final camera software
//
//void NewDeviceAdded(void)
//{
//    int			kr;
//	int			kr2;
//    SInt32		score;
//    UInt16		vendor;
//    UInt16		product;
//    UInt16		release;
//	int			index;
// 	char		buffer[200];
//
//
//
//
//    Starfish_Log("NewDeviceAdded routine\n");
//
//	vendor  = gUSBDevice->VendorID;
//	product = gUSBDevice->ProductID;
//	release = gUSBDevice->BcdDevice;
//
//	Starfish_LogFmt("     vendor  = %08x\n", vendor);
//	Starfish_Log( buffer );
//	Starfish_LogFmt("     product = %08x\n", product);
//	Starfish_Log( buffer );
//	Starfish_LogFmt("     release = %08x\n", release);
//	Starfish_Log( buffer );
//
//	// put the new initialized camera in our data base
//	// at this point only the RAW version of the camera will be in the data base, if at all.  It
//	// won't be in it at all if we re-started the application after previously been running
//	// and the camera is already initialized from before.
//	// by convention, the RAW productID will be an even number, the initialized version will be odd.
//	// we use this knowledge to look for any previous entries of the RAW version of this camera.
//	if (!sawThisCameraAlready (vendor, product & 0xfffe, 0xffff, release))
//		{
//		// here if we haven't seen this camera in its RAW state
//
//		// check to see if it was already addded in its initialized state.  add it if not.
//		if (getCameraDBIndex (vendor, 0xffff, product, release) == -1)
//			{
//			index = getNextFreeDBIndex();
//
//			gCamerasFound[index].camVendor       = vendor;
//			gCamerasFound[index].camRawProduct   = 0;
//			gCamerasFound[index].camFinalProduct = product;
//			gCamerasFound[index].camRelease      = release;
////			gCamerasFound[index].camUsbIntfc     = 0;
//
//			Starfish_LogFmt("NewDeviceAdded - added FINAL camera to DB at index = %d\n", index);
//			Starfish_Log( buffer );
//			}
//		else
//			Starfish_Log("NewDeviceAdded -  FINAL camera already in DB\n");
//		}
//	else
//		{
//		index =  getCameraDBIndex (vendor, product & 0xfffe, 0xffff, release);
//
//		gCamerasFound[index].camVendor       = vendor;
//		// don't touch the following field.  we already saw this camera added as a RAW device
////		gCamerasFound[index].camRawProduct   = 0;
//		gCamerasFound[index].camFinalProduct = product;
//		gCamerasFound[index].camRelease      = release;
////		gCamerasFound[index].camUsbIntfc     = 0;
//
//		Starfish_LogFmt("NewDeviceAdded - updating RAW camera at DB index = %d\n", index);
//		Starfish_Log( buffer );
//		}
//
//
//
//	// the ReleaseNumber is used by fishcamp as a camera serial number.  Store it away
//	gRelease = release;
//
//    ConfigureAnchorDevice();
//
//	FindInterfaces();
//
//	// one last thing....
//	// need to load the MicroBlaze executable program before doing any camera stuff.
//
//	if (gCamerasFound[index].camRawProduct != 0)	// if the camera was discovered in the RAW state.  we will know this cause it will be non-zero
//		{
//		Starfish_Log("Calling - DownloadtToMicroBlaze \n");
//		DownloadtToMicroBlaze(gCamerasFound[index].camVendor, gCamerasFound[index].camRawProduct, gCamerasFound[index].camFinalProduct, gCamerasFound[index].camRelease);
//		}
//	else
//		{
//		Starfish_Log("didn't need to call - DownloadtToMicroBlaze \n");
//		}
//
////	printOutDiscoveredCamerasDB();
//}

// given a pointer to a command parameter struct, this routine will
// calculate the correct cksum field
UInt16 fcUsb_GetUsbCmdCksum(UInt16 *cmdParams)
{
    UInt16 theCksum;
    UInt16 *ptr16;
    UInt16 theLength;
    int i;

    ptr16 = cmdParams;

    theCksum = *ptr16++; // start with the header

    theCksum += *ptr16++; // add in the command field

    theLength = *ptr16++;
    theCksum += theLength;

    for (i = 0; i < (theLength - 8); i += 2)
    {
        theCksum += *ptr16++;
    }

    return theCksum;
}

// helper routine for fcImage_doFullFrameRowLevelNormalization.
// will calculate the average level of the pixels in the
// black cols of the image sensor
//
float fcImage_calcFullFrameAllColAvg(UInt16 *frameBufferPtr, int imageWidth, int imageHeight)
{
    float floatPixel;
    float retValue;
    int row, col;
    //bool evenRow;
    UInt16 *inputPtr;
    UInt16 aPixel;

    retValue = 0.0;

    // we will average all of the pixels in cols 0 -> 14
    for (row = 0; row < imageHeight; row++)
    {
        inputPtr = frameBufferPtr;
        inputPtr = inputPtr + (row * imageWidth);

        for (col = 0; col < 14; col++)
        {
            // get the next pixel
            aPixel = *inputPtr++;

            floatPixel = (float)aPixel;
            retValue += floatPixel;
        }
    }

    //divide by the number of pixels in the black col
    retValue = retValue / (14.0 * (float)imageHeight);

    return retValue;
}

// helper routine for fcImage_doFullFrameRowLevelNormalization.
// will calculate the average level of the pixels in the
// defined ROW's black columns
float fcImage_calcFullFrameRowAvgForRow(UInt16 *frameBufferPtr, int imageWidth, int imageHeight, int theRow)
{
	INDI_UNUSED(imageHeight);
    float floatPixel;
    float retValue;
    int /*row,*/ col;
    //bool evenRow;
    UInt16 *inputPtr;
    UInt16 aPixel;

    UInt16 lowPixel;
    UInt16 hiPixel;

    retValue = 0.0;

    lowPixel = 0xffff;
    hiPixel  = 0;

    // we will average all of the pixels in cols 0 -> 14
    inputPtr = frameBufferPtr;
    inputPtr = inputPtr + (theRow * imageWidth);

    for (col = 0; col < 14; col++)
    {
        // get the next pixel
        aPixel = *inputPtr++;

        // find lowest and highest pixel
        if (lowPixel > aPixel)
            lowPixel = aPixel;

        if (hiPixel < aPixel)
            hiPixel = aPixel;

        floatPixel = (float)aPixel;
        retValue += floatPixel;
    }

    // throw out lowest and highest
    //	floatPixel = (float) lowPixel;
    //	retValue  -= floatPixel;
    //	floatPixel = (float) hiPixel;
    //	retValue  -= floatPixel;

    //divide by the number of pixels in the black col
    retValue = retValue / 14.0;
    //	retValue = retValue / 12.0;

    return retValue;
}

// routine to perform line level normalization on the RAW camera image
// Used to get rid of the camera's read noise associated with ROWs
// enter with pointer to 16 bit image
//
void fcImage_doFullFrameRowLevelNormalization(UInt16 *frameBufferPtr, int imageWidth, int imageHeight)
{
    //float reference;
    int row, col;
    UInt16 *inputPtr;
    UInt16 aPixel;
    float rowAvg = 0;
    float thisRowAvg;
    //float minRowAvg;
    //float colAvg;
    float frameAvg;
    float rowOffset;
    //float colOffset;
    float floatPixel;

    if (gDoSimulation)
    {
        srand(time(NULL));
        int i = 0, j = 0;

        for (i = 0; i < imageHeight; i++)
            for (j = 0; j < imageWidth; j++)
                frameBufferPtr[i * imageWidth + j] = rand() % 65535;

        return;
    }

    // make sure we are dealing with 16 bit pixels
    //	printf("fcImage_doFullFrameRowLevelNormalization\n");

    // calculate the average of all the black pixels
    frameAvg = fcImage_calcFullFrameAllColAvg(frameBufferPtr, imageWidth, imageHeight);

    for (row = 0; row < imageHeight; row++)
    {
        if (row > 0)
            rowAvg = fcImage_calcFullFrameRowAvgForRow(frameBufferPtr, imageWidth, imageHeight, (row - 1));
        //		rowAvg  = [self calcFullFrameRowMedianForRow: (row - 1)];

        thisRowAvg = fcImage_calcFullFrameRowAvgForRow(frameBufferPtr, imageWidth, imageHeight, row);
        //		thisRowAvg = [self calcFullFrameRowMedianForRow: row];

        if (row == 0)
            rowOffset = frameAvg - thisRowAvg;
        else
            rowOffset = rowAvg - thisRowAvg; // 11-23-07

        inputPtr = frameBufferPtr;
        inputPtr = inputPtr + (row * imageWidth);
        for (col = 0; col < imageWidth; col++)
        //		for (col = 0; col < (imageWidth / 2); col++)
        {
            // get the next pixel
            aPixel = *inputPtr;

            floatPixel = (float)aPixel;

            floatPixel += rowOffset;

            if (floatPixel > 65535.0)
                floatPixel = 65535.0;

            if (floatPixel < 0.0)
                floatPixel = 0.0;

            // put corrected value back
            *inputPtr++ = (UInt16)floatPixel;
        }
    }
}

// routine to strip the black columns form the image read from the camera.  the camera's image is
// assumed to be in gFrameBuffer and the pointer passed to this routine is where we will put the
// stripped image;
void fcImage_StripBlackCols(int camNum, UInt16 *frameBuffer)
{
    //float reference;
    int row, col;
    int imageHeight, imageWidth;
    UInt16 *inputPtr;
    UInt16 *outputPtr;
    //UInt16 aPixel;
    //float rowAvg;
    //float thisRowAvg;
    //float minRowAvg;
    //float colAvg;
    //float frameAvg;
    //float rowOffset;
    //float colOffset;
    //float floatPixel;

    imageHeight = (gRoi_bottom[camNum - 1] - gRoi_top[camNum - 1]) + 1;
    imageWidth  = (gRoi_right[camNum - 1] - gRoi_left[camNum - 1]) + 1;

    for (row = 0; row < imageHeight; row++)
    {
        inputPtr = gFrameBuffer;
        //		if (row < (imageHeight / 2))
        inputPtr = inputPtr + (row * (imageWidth + 16)) + 16;
        //		else
        //			inputPtr = inputPtr + (row * (imageWidth + 16));
        outputPtr = frameBuffer;
        outputPtr = outputPtr + (row * imageWidth);

        for (col = 0; col < imageWidth; col++)
        {
            // transfer pixel
            *outputPtr++ = *inputPtr++;
        }
    }
}

// the following three routines are used to touch up the images from the IBIS1300 sensor
// it normalizes the offsets in the columns.
// clear out the black row vector for the IBIS1300 image sensor
void fcImage_IBIS_clearBlackRowAverage(void)
{
    int i;

    for (i = 0; i < 1280; i++)
        gBlackOffsets[i] = 0;
}

// accumulate another image's first black row from the IBIS image sensor
void fcImage_IBIS_accumulateBlackRowAverage(void)
{
    UInt16 *inputPtr;
    UInt16 aPixel;
    SInt32 bigPixel;
    int col;

    // we will average all of the pixels in the first row
    inputPtr = gFrameBuffer;

    for (col = 0; col < 1280; col++)
    {
        // get the next pixel
        aPixel = *inputPtr++;

        bigPixel           = (SInt32)aPixel;
        gBlackOffsets[col] = gBlackOffsets[col] + bigPixel;
    }
}

// here after we accumulated all of the images first black row.  now divide by the num of images taken
void fcImage_IBIS_divideBlackRowAverage(void)
{
    int i;

    for (i = 0; i < 1280; i++)
        gBlackOffsets[i] = gBlackOffsets[i] / 4;
}

// helper routine for fcImage_IBIS_doFullFrameColLevelNormalization.
// will calculate the average level of the pixels in the
// first black row of the image sensor
//
float fcImage_IBIS_calcFirstBlackRowAverage(UInt16 *frameBufferPtr, int imageWidth, int imageHeight)
{
	INDI_UNUSED(frameBufferPtr);
	INDI_UNUSED(imageHeight);
    float floatPixel;
    float retValue;
    int /*row,*/ col;
    //bool evenRow;
    //UInt16 *inputPtr;
    SInt32 aPixel;

    retValue = 0.0;

    // we will average all of the pixels in the first row

    for (col = 0; col < imageWidth; col++)
    {
        // get the next pixel
        aPixel = gBlackOffsets[col];

        floatPixel = (float)aPixel;
        retValue += floatPixel;
    }

    //divide by the number of pixels in the black row
    retValue = retValue / (float)imageWidth;

    return retValue;
}

// routine which will subtract out the offset pedestal from the image.  It looks at the
// first row of black pixels to determine the average of the row.  Then it subtracts out
// that number from each pixel in the image.
void fcImage_IBIS_subtractPedestal(UInt16 *frameBufferPtr, int imageWidth, int imageHeight)
{
    //float reference;
    int row, col;
    UInt16 *inputPtr;
    UInt16 aPixel;
    SInt32 bigPixel;
    SInt32 thePedestal;
    //float rowAvg;
    //float thisColBlack;
    //float minRowAvg;
    //float colAvg;
    float frameAvg;
    //float rowOffset;
    //float colOffset;
    //float floatPixel;

    // calculate the average of all the black pixels in the first row
    frameAvg    = fcImage_IBIS_calcFirstBlackRowAverage(frameBufferPtr, imageWidth, imageHeight);
    thePedestal = (SInt32)frameAvg;

    // don't touch the black row.  Start at row '1'
    inputPtr = frameBufferPtr;
    inputPtr = inputPtr + imageWidth;
    for (col = 0; col < imageWidth; col++)
    {
        for (row = 1; row < imageHeight; row++)
        {
            aPixel   = *inputPtr;
            bigPixel = (SInt32)aPixel;
            bigPixel = bigPixel - thePedestal;

            if (bigPixel > 65535)
                bigPixel = 65535;

            if (bigPixel < 0)
                bigPixel = 0;

            // put corrected value back
            *inputPtr = (UInt16)bigPixel;

            // point to next
            inputPtr++;
        }
    }
}

// this routine is called everytime the gain setting is changed on the IBIS1300 image sensor.  I takes 8 frames
// and stores the average of the first black row of pixels in the sensor.  The resulting vector is then used
// to normalize the columns everytime a new image is readout.
void fcImage_IBIS_computeAvgColOffsets(int camNum)
{
    UInt32 savedIntegrationTime;
    bool done;
    UInt16 state;
    int i;

    // clear out the vector
    fcImage_IBIS_clearBlackRowAverage();

    // remember the integration time that the user wants
    savedIntegrationTime = gCurrentIntegrationTime[camNum - 1];

    // set a short integration time
    fcUsb_cmd_setIntegrationTime(camNum, 10); // 10 ms

    // take an initial picture
    fcUsb_cmd_startExposure(camNum);

    // wait till the picture is done
    done = false;
    while (!done)
    {
        state = fcUsb_cmd_getState(camNum);
        if (state == 0)
            done = true;
    }

    for (i = 0; i < 4; i++)
    {
        // take a picture
        fcUsb_cmd_startExposure(camNum);

        // wait till the picture is done
        done = false;
        while (!done)
        {
            state = fcUsb_cmd_getState(camNum);
            if (state == 0)
                done = true;
        }

        // read in the image into our local frame buffer
        fcUsb_cmd_getRawFrame(camNum, 1024, 1280, gFrameBuffer);

        // add to the average
        fcImage_IBIS_accumulateBlackRowAverage();
    }

    // divide the vector by the number of picts taken
    fcImage_IBIS_divideBlackRowAverage();

    // put the users' desired integration time back
    fcUsb_cmd_setIntegrationTime(camNum, savedIntegrationTime);
}

// routine to perform column level normalization on the RAW camera image
// Used to get rid of the camera's fixed pattern noise associated with COLs
// enter with pointer to 16 bit image
//
void fcImage_IBIS_doFullFrameColLevelNormalization(UInt16 *frameBufferPtr, int imageWidth, int imageHeight)
{
    //float reference;
    int row, col;
    UInt16 *inputPtr;
    UInt16 aPixel;
    //float rowAvg;
    SInt32 thisColBlack;
    //float minRowAvg;
    //float colAvg;
    float frameAvg;
    //float rowOffset;
    SInt32 colOffset;
    //float floatPixel;
    SInt32 blackAvg;
    SInt32 bigPixel;
    //SInt32 theOffset;

    // make sure we are dealing with 16 bit pixels

    //	printf("fcImage_IBIS_doFullFrameColLevelNormalization\n");

    // calculate the average of all the black pixels
    frameAvg = fcImage_IBIS_calcFirstBlackRowAverage(frameBufferPtr, imageWidth, imageHeight);
    blackAvg = (SInt32)frameAvg;

    for (col = 0; col < imageWidth; col++)
    {
        // first get this cols black pixel from the first row of the image
        thisColBlack = gBlackOffsets[col];

        colOffset = blackAvg - thisColBlack;

        for (row = 1; row < imageHeight; row++)
        {
            // get the pixel for this row/col
            inputPtr = frameBufferPtr;
            inputPtr = inputPtr + (row * imageWidth) + col;
            aPixel   = *inputPtr;
            bigPixel = (SInt32)aPixel;

            // normalize
            bigPixel = bigPixel + colOffset;

            if (bigPixel > 65535)
                bigPixel = 65535;

            if (bigPixel < 0)
                bigPixel = 0;

            // put corrected value back
            *inputPtr = (UInt16)bigPixel;
        }
    }
}

// routine to compute the column level offsets in the image.
// We do this by examining the vertical overscan region in the image
// Computing the average in the particular column.
void fcImage_PRO_calcColOffsets(UInt16 *frameBufferPtr, int imageWidth, int imageHeight)
{
	INDI_UNUSED(imageHeight);
    //float reference;
    int row, col;
    UInt16 *inputPtr;
    UInt16 aPixel;
    //float rowAvg;
    //float thisRowAvg;
    //float minRowAvg;
    //float colAvg;
    //float frameAvg;
    //float rowOffset;
    //float colOffset;
    //float floatPixel;
    int startRow, endRow;
    SInt32 colAverage;

    //	printf("fcImage_PRO_calcColOffsets\n");
    Starfish_Log("fcImage_PRO_calcColOffsets\n");

    // clear out any old results
    for (col = 0; col < 4096; col++)
        gProBlackColOffsets[col] = 0;

    // define the start row and end row of the vertical overscan region of the image
    startRow = 2100;
    endRow   = 2300;

    // accumulate the average
    for (col = 0; col < imageWidth; col++)
    {
        for (row = startRow; row < endRow; row++)
        {
            inputPtr = frameBufferPtr;
            inputPtr = inputPtr + (row * imageWidth) + col;

            // get the next pixel
            aPixel = *inputPtr;

            // add up
            gProBlackColOffsets[col] += (SInt32)aPixel;
        }

        // divide by the number of rows in the overscan area
        gProBlackColOffsets[col] = gProBlackColOffsets[col] / (SInt32)(endRow - startRow);
    }

    // now calculate the average of all the columns
    colAverage = 0;
    for (col = 0; col < imageWidth; col++)
    {
        colAverage += gProBlackColOffsets[col];
    }

    colAverage = colAverage / (SInt32)imageWidth;

    // normalize the offsets to the column average
    for (col = 0; col < imageWidth; col++)
    {
        gProBlackColOffsets[col] = gProBlackColOffsets[col] - colAverage;
    }
}

// routine to perform column level normalization on the RAW camera image
// Used to get rid of the sensor's fixed pattern noise associated with COLs
// enter with pointer to 16 bit image
//
void fcImage_PRO_doFullFrameColLevelNormalization(UInt16 *frameBufferPtr, int imageWidth, int imageHeight)
{
    //float reference;
    int row, col;
    UInt16 *inputPtr;
    UInt16 aPixel;
    //float rowAvg;
    //float thisRowAvg;
    //float minRowAvg;
    //float colAvg;
    //float frameAvg;
    //float rowOffset;
    float colOffset;
    float floatPixel;

    //	printf("fcImage_PRO_doFullFrameColLevelNormalization\n");
    Starfish_Log("fcImage_PRO_doFullFrameColLevelNormalization\n");

    // calculate the average of all the black pixels in the vertical overscan area
    //	fcImage_PRO_calcColOffsets(frameBufferPtr, imageWidth, imageHeight);

    for (row = 0; row < imageHeight; row++)
    {
        inputPtr = frameBufferPtr;
        inputPtr = inputPtr + (row * imageWidth);
        for (col = 0; col < imageWidth; col++)
        {
            // get the next pixel
            aPixel = *inputPtr;

            // get the offset for this column
            colOffset = (float)gProBlackColOffsets[col];

            floatPixel = (float)aPixel;

            floatPixel -= colOffset;

            if (floatPixel > 65535.0)
                floatPixel = 65535.0;

            if (floatPixel < 0.0)
                floatPixel = 0.0;

            // put corrected value back
            *inputPtr++ = (UInt16)floatPixel;
        }
    }
}

// this routine is used internally to calibrate the PRO series cameras.  We do this each time
// the fcPROP_NUMSAMPLES property is changed or if the sensor's temperature changes by more than 1 degree C.
//
// the primary job this routine performs is taking a bias frame and measuring the vertical overscan region
// so that we know how to do column level normalization.  We only can collect this information during a
// bias frame since a long exposure light frame will have the scene bleed into the vertical overscan region
// and corrupt the measurement.
//
void fcImage_PRO_calibrateProCamera(int camNum, UInt16 *frameBufferPtr, int imageWidth, int imageHeight)
{
    UInt32 savedIntegrationTime;
    bool done;
    UInt16 state;
    int i;
    bool savedWantNorm;

    Starfish_Log("fcImage_PRO_calibrateProCamera\n");

    if (gDoSimulation)
        return;

    // remember the current integration time setting so that we can put it back when we are done.
    savedIntegrationTime = gCurrentIntegrationTime[camNum - 1];
    savedWantNorm        = gProWantColNormalization;

    gProWantColNormalization = false;

    // set a short integration time.  bias frame = 1ms exposure
    fcUsb_cmd_setIntegrationTime(camNum, 1); // 1 ms

    for (i = 0; i < 2; i++)
    {
        // take a picture
        fcUsb_cmd_startExposure(camNum);

        // wait till the picture is done
        done = false;
        while (!done)
        {
            state = fcUsb_cmd_getState(camNum);
            if (state == 0)
                done = true;
        }

        usleep(1000000);

        // read in the image into our local frame buffer
        fcUsb_cmd_getRawFrame(camNum, imageHeight, imageWidth, frameBufferPtr);
    }

    // calculate the offsets to use for all subsequent images
    fcImage_PRO_calcColOffsets(frameBufferPtr, imageWidth, imageHeight);

    // put the users' desired integration time back
    fcUsb_cmd_setIntegrationTime(camNum, savedIntegrationTime);

    gProWantColNormalization = savedWantNorm;
}

// routine to perform a 3x3 kernel filter on the image buffer
//
void fcImage_do_3x3_kernel(UInt16 imageHeight, UInt16 imageWidth, UInt16 *frameBuffer)
{
    //float reference;
    int row, col;
    UInt16 *inputPtr;
    UInt16 *outputPtr;
    UInt16 aPixel;
    UInt32 accumPixel;
    UInt16 *tempBuffer;
    size_t size;

    // this routine will work 'in place'.  We will first allocate a temporary image buffer
    // we copy the image to it and then fill the original buffer with the filtered image
    //
    size       = imageWidth * imageHeight * 2; // 2 bytes/pixel
    tempBuffer = (UInt16 *)malloc(size);

    if (tempBuffer != NULL)
    {
        // copy the image buffer to my local storage
        memcpy(tempBuffer, frameBuffer, size);

        // Start at row '1'
        for (row = 1; row < (imageHeight - 1); row++)
        {
            for (col = 1; col < (imageWidth - 1); col++)
            {
                inputPtr  = tempBuffer;
                inputPtr  = inputPtr + (row * imageWidth) + col;
                outputPtr = frameBuffer;
                outputPtr = outputPtr + (row * imageWidth) + col;

                accumPixel = 0;

                inputPtr   = inputPtr - imageWidth - 1; // 1
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;

                inputPtr++; // 2
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;

                inputPtr++; // 3
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;

                inputPtr   = inputPtr + imageWidth - 2; // 4
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;

                inputPtr++; // 5
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;

                inputPtr++; // 6
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;

                inputPtr   = inputPtr + imageWidth - 2; // 7
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;

                inputPtr++; // 8
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;

                inputPtr++; // 9
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;

                // divide by the kernel size
                accumPixel = accumPixel / 9;

                // put filtered value back
                *outputPtr = (UInt16)accumPixel;
            }
        }

        free(tempBuffer);
    }
}

// routine to perform a 5x5 kernel filter on the image buffer
//
void fcImage_do_5x5_kernel(UInt16 imageHeight, UInt16 imageWidth, UInt16 *frameBuffer)
{
    //float reference;
    int row, col;
    UInt16 *inputPtr;
    UInt16 *outputPtr;
    UInt16 aPixel;
    UInt32 accumPixel;
    UInt16 *tempBuffer;
    size_t size;
    int x, y;

    // this routine will work 'in place'.  We will first allocate a temporary image buffer
    // we copy the image to it and then fill the original buffer with the filtered image
    //
    size       = imageWidth * imageHeight * 2; // 2 bytes/pixel
    tempBuffer = (UInt16 *)malloc(size);

    if (tempBuffer != NULL)
    {
        // copy the image buffer to my local storage
        memcpy(tempBuffer, frameBuffer, size);

        // Start at row '2'
        for (row = 2; row < (imageHeight - 2); row++)
        {
            for (col = 2; col < (imageWidth - 2); col++)
            {
                inputPtr  = tempBuffer;
                inputPtr  = inputPtr + (row * imageWidth) + col;
                outputPtr = frameBuffer;
                outputPtr = outputPtr + (row * imageWidth) + col;

                accumPixel = 0;

                inputPtr = inputPtr - (3 * imageWidth) + 2;

                for (y = 0; y < 5; y++)
                {
                    inputPtr   = inputPtr + imageWidth - 4;
                    aPixel     = *inputPtr;
                    accumPixel = accumPixel + (UInt32)aPixel;

                    for (x = 0; x < 4; x++)
                    {
                        inputPtr++;
                        aPixel     = *inputPtr;
                        accumPixel = accumPixel + (UInt32)aPixel;
                    }
                }

                // divide by the kernel size
                accumPixel = accumPixel / 25;

                // put filtered value back
                *outputPtr = (UInt16)accumPixel;
            }
        }

        free(tempBuffer);
    }
}

// routine to perform hot pixel removal filter on the image buffer
//
// algorithm looks for the center pixel ina 3x3 grid being more than 20%
// brighter than the brightest of the neigboring pixels.  If it is
// then it will replace it with the average of the neighboring pixels.
//
void fcImage_do_hotPixel_kernel(UInt16 imageHeight, UInt16 imageWidth, UInt16 *frameBuffer)
{
    float floatBrightPixel;
    float floatCenterPixel;
    int row, col;
    UInt16 *inputPtr;
    UInt16 *outputPtr;
    UInt16 aPixel;
    UInt32 accumPixel;
    UInt16 *tempBuffer;
    size_t size;
    UInt16 brightestNeighbor;
    UInt16 thisPixel;

    // this routine will work 'in place'.  We will first allocate a temporary image buffer
    // we copy the image to it and then fill the original buffer with the filtered image
    //
    size       = imageWidth * imageHeight * 2; // 2 bytes/pixel
    tempBuffer = (UInt16 *)malloc(size);

    if (tempBuffer != NULL)
    {

        // copy the image buffer to my local storage
        memcpy(tempBuffer, frameBuffer, size);

        // Start at row '1'
        for (row = 1; row < (imageHeight - 1); row++)
        {
            for (col = 1; col < (imageWidth - 1); col++)
            {
                inputPtr  = tempBuffer;
                inputPtr  = inputPtr + (row * imageWidth) + col;
                outputPtr = frameBuffer;
                outputPtr = outputPtr + (row * imageWidth) + col;

                accumPixel        = 0;
                brightestNeighbor = 0;

                inputPtr   = inputPtr - imageWidth - 1; // 1
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;
                if (brightestNeighbor < aPixel)
                    brightestNeighbor = aPixel;

                inputPtr++; // 2
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;
                if (brightestNeighbor < aPixel)
                    brightestNeighbor = aPixel;

                inputPtr++; // 3
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;
                if (brightestNeighbor < aPixel)
                    brightestNeighbor = aPixel;

                inputPtr   = inputPtr + imageWidth - 2; // 4
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;
                if (brightestNeighbor < aPixel)
                    brightestNeighbor = aPixel;

                inputPtr++; // 5 - center pixel
                aPixel    = *inputPtr;
                thisPixel = aPixel;

                inputPtr++; // 6
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;
                if (brightestNeighbor < aPixel)
                    brightestNeighbor = aPixel;

                inputPtr   = inputPtr + imageWidth - 2; // 7
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;
                if (brightestNeighbor < aPixel)
                    brightestNeighbor = aPixel;

                inputPtr++; // 8
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;
                if (brightestNeighbor < aPixel)
                    brightestNeighbor = aPixel;

                inputPtr++; // 9
                aPixel     = *inputPtr;
                accumPixel = accumPixel + (UInt32)aPixel;
                if (brightestNeighbor < aPixel)
                    brightestNeighbor = aPixel;

                // divide by the number of surrounding pixels
                accumPixel = accumPixel / 8;

                floatBrightPixel = (float)brightestNeighbor;
                floatBrightPixel = floatBrightPixel * 1.2;

                floatCenterPixel = (float)thisPixel;

                if (floatCenterPixel > floatBrightPixel)
                {
                    // substitute average
                    *outputPtr = (UInt16)accumPixel;
                }
            }
        }

        free(tempBuffer);
    }

    //	Starfish_LogFmt("fcImage_do_hotPixel_kernel numHotPixels = %d\n", numHotPixels);
}

// This is the framework initialization routine and needs to be called once upon application startup
void fcUsb_init(void)
{
    int i;
    size_t size;
    UInt16 rows, cols;

//On OS X, Prefer embedded App location if it exists
#ifdef __APPLE__
    if (getenv("INDIPREFIX") != NULL)
        snprintf(driverSupportPath, MAXRBUF, "%s/Contents/Resources", getenv("INDIPREFIX"));
    else
        strncpy(driverSupportPath, "/usr/local/lib/indi", MAXRBUF);
    strncat(driverSupportPath, "/DriverSupport/fishcamp", MAXRBUF);
#else
    snprintf(driverSupportPath, MAXRBUF, "/lib/firmware");
#endif

    gDoLogging    = true; // default to do logging
    gDoSimulation = false;

    Starfish_Log("fcUsb_init routine\n");

    libusb_init(&gCtx);

#if LIBUSB_API_VERSION >= 0x01000106
    libusb_set_option(gCtx, LIBUSB_OPTION_LOG_LEVEL, 3);
#else
    libusb_set_debug(gCtx, 3);
#endif

    if (!gFWInitialized)
    {
        for (i = 0; i < kNumCamsSupported; i++)
        {
            gCamerasFound[i].camVendor       = 0;
            gCamerasFound[i].camRawProduct   = 0;
            gCamerasFound[i].camFinalProduct = 0;
            gCamerasFound[i].camRelease      = 0;
            gCamerasFound[i].dev             = 0;

            gReadBlack[i]       = false;             // set if the host wishes to read the black pixels
            gDataXfrReadMode[i] = fc_classicDataXfr; // the type of data transfers we will be using when taking picts
            gDataFormat[i]      = fc_16b_data;       // the desired camera data format

            gCurrentIntegrationTime[i] = 50;

            gRoi_left[i]   = 0;
            gRoi_top[i]    = 0;
            gRoi_right[i]  = 1279;
            gRoi_bottom[i] = 1023;

            gCameraImageFilter[i] = fc_filter_none;

            gBlackPedestal[i] = 0;
        }

        gRelease              = 0;
        gNumCamerasDiscovered = 0;

        gFindCamState           = fcFindCam_notYetStarted;
        gFindCamPercentComplete = 0.0;

        // allocate memory for the internal frame buffer
        rows = 2520;
        cols = 3364;
        //		cols += 16;					// room for black cols
        size         = rows * cols * 2; // 2 bytes/pixel
        gFrameBuffer = (UInt16 *)malloc(size);

        gProWantColNormalization = true;

        gFWInitialized = true;
        Starfish_Log("fcCamFw initialized\n");
    }
}

// Call this routine to enable / disable entrie in the log file
// by default, logging is turned off.  The log file will be created
// in C:\Program Files\fishcamp\starfish_log.txt
//
// loggingState = true to turn logging on
//
void fcUsb_setLogging(bool loggingState)
{
    gDoLogging = loggingState;
}

void fcUsb_setSimulation(bool simState)
{
    gDoSimulation = simState;
}

// This is the framework close routine and needs to be called just before application termination
void fcUsb_close(void)
{
    int i;

    free(gFrameBuffer);

    for (i = 0; i < kNumCamsSupported; i++)
    {
        gCamerasFound[i].camVendor       = 0;
        gCamerasFound[i].camRawProduct   = 0;
        gCamerasFound[i].camFinalProduct = 0;
        gCamerasFound[i].camRelease      = 0;

        if (gCamerasFound[i].dev != 0)
        {
            fcUsb_CloseCamera(i + 1);
            gCamerasFound[i].dev = 0;
        }
    }

    libusb_exit(gCtx);
}

// the prefered way of finding and opening a communications link to any of our supported cameras
// This routine will call fcUsb_OpenCameraDriver and fcUsb_CloseCameraDriver routines to do its job
//
// will return the actual number of cameras found.
//
// be carefull, this routine can take a long time (> 5 secs) to execute
//
// Change added 2/28/07 to allow more time between looking for the RAW camera and
// looking for the final cmaera.  We will look up till 10 seconds for this to happen
// before giving up.
//
//int fcUsb_FindCameras(void)
//{
//	int			result;
//	int			i;
//	GUID		guid;
//	bool		done;
//	int			d;
//	char		buffer[200];
//
//
//	Starfish_Log("fcUsb_FindCameras routine\n");
//
//	gFindCamState			= fcFindCam_looking4supported;
//	gFindCamPercentComplete = 20.0;
//
//
//	// create the GUID used in the driver's .INF file that was used
//	// to match the driver to the starfish camera
//	FcStringToGUID("B0C944D5-FBAF-478e-8E70-59F80C297347", &guid);
//
//	// get a reference to the CyAPI library that handles communication
//	// to any of our cameras.
//	gUSBDevice = new CCyUSBDevice(NULL, guid);	// Create an instance of CCyUSBDevice
//														// Does not register for PnP events
//
//	// Look for any starfish cameras
//	gNumCamerasDiscovered = gUSBDevice->DeviceCount();
//	Starfish_LogFmt("fcCamFw gNumCamerasDiscovered = %d\n", gNumCamerasDiscovered);
//	Starfish_Log (buffer );
//
//	gFindCamState			= fcFindCam_initializingUSB;
//	gFindCamPercentComplete = 50.0;
//
//	if (gNumCamerasDiscovered > 0)
//		{
//		// find any RAW starfish cameras
//		d = 0;
//		done = false;
//		do {
//			gUSBDevice->Open(d);  // Open automatically calls Close( ) if necessary
//
//			if ((gUSBDevice->VendorID == 0x1887) && (gUSBDevice->ProductID == 0x0002))
//				{
//				// we found one of our RAW starfish cameras
//				// we need to download the 'gdr_usb.hex' file to the USB controller RAM and then execute it
//				RawDeviceAdded();
//				}
//
//
//			d++;
//			if (d >= gNumCamerasDiscovered)
//				{
//				done = true;
//				}
//
//			}  while (!done);
//		}
//
//	printOutDiscoveredCamerasDB();
//
//	gUSBDevice->~CCyUSBDevice();
//
//
//
//	Sleep(1000);	// 2/28/08  used to wait a fixed 3 seconds
//
//
//
//	// found any RAW starfish cameras and renumerated them to the programmed state of the USB controller
//
//	gFindCamState			= fcFindCam_initializingIP;
//	gFindCamPercentComplete = 80.0;
//
//
//	// get a reference to the CyAPI library that handles communication
//	// to any of our cameras.
//	gUSBDevice = new CCyUSBDevice(NULL, guid);	// Create an instance of CCyUSBDevice
//														// Does not register for PnP events
//
//	// Look for any starfish cameras
//	// we will look every second for up till 10 seconds.
//	// used to just look once  2/28/08
//	i = 0;
//	done = false;
//	while (!done)
//		{
//		gNumCamerasDiscovered = gUSBDevice->DeviceCount();
//		Starfish_LogFmt("fcCamFw gNumCamerasDiscovered = %d\n", gNumCamerasDiscovered);
//		Starfish_Log( buffer );
//
//		i++;
//		if ((gNumCamerasDiscovered > 0) || (i > 10))
//			done = true;
//
//		Sleep(1000);
//		}
//
//	if (gNumCamerasDiscovered > 0)
//		{
//		// find any RAW starfish cameras
//		d = 0;
//		done = false;
//		do {
//			gUSBDevice->Open(d);  // Open automatically calls Close( ) if necessary
//
//			// make sure device is reset
//			gUSBDevice->Reset();
//			gUSBDevice->Open(d);
//
//			if ((gUSBDevice->VendorID == 0x1887) && (gUSBDevice->ProductID == 0x0003))
//				{
//				// we found one of our RAW starfish cameras
//				// we need to download the 'gdr_usb.hex' file to the USB controller RAM and then execute it
//				NewDeviceAdded();
//				}
//
//
//			d++;
//			if (d >= gNumCamerasDiscovered)
//				{
//				done = true;
//				}
//
//			}  while (!done);
//
//		}
//
//	printOutDiscoveredCamerasDB();
//
//
//
//	gFindCamState			= fcFindCam_finished;
//	gFindCamPercentComplete = 100.0;
//
//
//
//	if (gNumCamerasDiscovered > 0)
//		{
//		// set some default timeouts
//		gUSBDevice->BulkInEndPt->TimeOut  = 20000;		// increased to 20000 from 10000   6_13_10
//		gUSBDevice->BulkOutEndPt->TimeOut = 20000;		// increased to 20000 from 10000   6_13_10
//
//		// see results
////		printOutDiscoveredCamerasDB();
//
//		// set some camera defaults
//		for (i = 0; i < gNumCamerasDiscovered; i++)
//			{
//			if (gCamerasFound[i].camFinalProduct == starfish_mono_rev2_final_deviceID)	// is this a Starfish?
//				{
//				Starfish_LogFmt("Found Starfish - SN%04d\n", gCamerasFound[i].camRelease);
//				Starfish_Log( buffer );
//				fcUsb_setStarfishDefaultRegs(i+1);
//				}
//			}
//
//		}
//
//	return gNumCamerasDiscovered;
//}

// will return the actual number of cameras found.
// This routine will call fcUsb_OpenCameraDriver and fcUsb_CloseCameraDriver routines to do its job on any RAW cameras found.
// It will return negative one if a raw camera is found and needs to be run again.
//
//int fcUsb_FindCameras(struct libusb_context *ctx)
int fcUsb_FindCameras(void)
{
    struct libusb_config_descriptor *cdesc;
    struct libusb_device **devs_list;
    //struct libusb_interface_descriptor *idesc;
    //struct libusb_endpoint_descriptor *edesc;
    struct libusb_device_descriptor descriptor;
    libusb_device *device;
    int cnt;
    //int ret;
    int i;
	//int j, k, l;
    int retValue, err;
    INDI_UNUSED(err);

    UInt16 vendor;
    UInt16 product;
    UInt16 release;
    int index;
    //int kr;
    

    Starfish_Log("fcUsb_FindCameras routine\n");

    gNumCamerasDiscovered   = 0;
    gFindCamState           = fcFindCam_looking4supported;
    gFindCamPercentComplete = 20.0;

    if (gDoSimulation)
    {
        gFindCamState           = fcFindCam_finished;
        gFindCamPercentComplete = 100.0;

        gCamerasFound[0].camVendor       = fishcamp_USB_VendorID;
        gCamerasFound[0].camRawProduct   = starfish_mono_rev2_raw_deviceID;
        gCamerasFound[0].camFinalProduct = starfish_mono_rev2_final_deviceID;
        gCamerasFound[0].camRelease      = 12345;
        gCamerasFound[0].dev             = 0;

        gNumCamerasDiscovered = 1;

        return gNumCamerasDiscovered;
    }

    if ((cnt = libusb_get_device_list(gCtx, &devs_list)) < 0)
    {
        Starfish_LogFmt("  libusb_get_device_list failed with 0x%x error code\n", cnt);
        
        return (EXIT_FAILURE);
    }

    if (cnt == 0)
    {
        Starfish_LogFmt("  No device match or lack of permissions.\n");
        
        return (EXIT_SUCCESS);
    }

    Starfish_LogFmt("There are %i devices\n", cnt);
    
    for (i = 0; i < cnt; i++)
    {
        Starfish_LogFmt("|-- device number = %i\n", i);
        
        device = devs_list[i];
        err    = libusb_get_device_descriptor(device, &descriptor);

        vendor  = descriptor.idVendor;
        product = descriptor.idProduct;
        release = descriptor.bcdDevice;

        Starfish_LogFmt("|---- vendor  = %08x\n", vendor);
        
        Starfish_LogFmt("|---- product = %08x\n", product);
        
        Starfish_LogFmt("|---- release = %08x\n", release);
        

        if ((vendor == fishcamp_USB_VendorID) && (product == starfish_mono_rev2_raw_deviceID))
        {
            // we found one of our RAW starfish cameras
            // put the new RAW camera in our data base
            if (!sawThisCameraAlready(vendor, product, 0xffff, release))
            {
                index = getNextFreeDBIndex();

                gCamerasFound[index].camVendor       = vendor;
                gCamerasFound[index].camRawProduct   = product;
                gCamerasFound[index].camFinalProduct = 0;
                gCamerasFound[index].camRelease      = release;
                gCamerasFound[index].dev             = 0;

                Starfish_LogFmt("|---- RawDeviceAdded - added RAW camera to DB index at index = %d\n", index);
                

                //This is to initialize the Raw cameras that haven't been seen before.  Find Cameras must be run again afterwards.
                int rc = fcUsb_OpenCamera(i + 1);
                Starfish_LogFmt("fcUsb_OpenCamera to initialize it: opening cam #%d, returns %d\n", i + 1, rc);
                
                rc = fcUsb_CloseCamera(i + 1);
                Starfish_LogFmt("fcUsb_CloseCamera to initialize it: opening cam #%d, returns %d\n", i + 1, rc);
                
                return -1;
            }
        }

        if ((vendor == fishcamp_USB_VendorID) && (product == starfish_mono_rev2_final_deviceID))
        {
            // put the new initialized camera in our data base
            // at this point only the RAW version of the camera will be in the data base, if at all.  It
            // won't be in it at all if we re-started the application after previously been running
            // and the camera is already initialized from before.
            // by convention, the RAW productID will be an even number, the initialized version will be odd.
            // we use this knowledge to look for any previous entries of the RAW version of this camera.
            if (!sawThisCameraAlready(vendor, product & 0xfffe, 0xffff, release))
            {
                // here if we haven't seen this camera in its RAW state

                // check to see if it was already addded in its initialized state.  add it if not.
                if (getCameraDBIndex(vendor, 0xffff, product, release) == -1)
                {
                    index = getNextFreeDBIndex();

                    gCamerasFound[index].camVendor       = vendor;
                    gCamerasFound[index].camRawProduct   = product & 0xfffe;
                    gCamerasFound[index].camFinalProduct = product;
                    gCamerasFound[index].camRelease      = release;
                    gCamerasFound[index].dev             = 0;

                    Starfish_LogFmt("|---- NewDeviceAdded - added FINAL camera to DB at index = %d\n", index);
                    
                }
                else
                    Starfish_Log("|---- NewDeviceAdded -  FINAL camera already in DB\n");
            }
            else
            {
                index = getCameraDBIndex(vendor, product & 0xfffe, 0xffff, release);

                gCamerasFound[index].camVendor = vendor;
                // don't touch the following field.  we already saw this camera added as a RAW device
                //				gCamerasFound[index].camRawProduct   = 0;
                gCamerasFound[index].camFinalProduct = product;
                gCamerasFound[index].camRelease      = release;
                gCamerasFound[index].dev             = 0;

                Starfish_LogFmt("|---- NewDeviceAdded - updating RAW camera at DB index = %d\n", index);
                
            }
        }

        retValue = libusb_get_active_config_descriptor(devs_list[i], &cdesc);

        if (retValue == LIBUSB_SUCCESS)
        {
            Starfish_LogFmt("|---- bLength : 0x%.2x\n", cdesc->bLength);
            
            Starfish_LogFmt("|---- bDescriptorType : 0x%.2x\n", cdesc->bDescriptorType);
            
            Starfish_LogFmt("|---- wTotalLength : 0x%.2x\n", cdesc->wTotalLength);
            
            Starfish_LogFmt("|---- bNumInterfaces : 0x%.2x\n", cdesc->bNumInterfaces);
            
            Starfish_LogFmt("|---- bConfigurationValue : 0x%.2x\n", cdesc->bConfigurationValue);
            
            Starfish_LogFmt("|---- iConfiguration : 0x%.2x\n", cdesc->iConfiguration);
            
            Starfish_LogFmt("|---- bmAttributes : 0x%.2x\n", cdesc->bmAttributes);
            
            Starfish_LogFmt("|---- MaxPower : 0x%.2x\n", cdesc->MaxPower);
            

            //			for (j = 0 ; j < cdesc->bNumInterfaces ; j++)
            //				{
            //				for (k = 0 ; k < cdesc->interface[j].num_altsetting ; k++)
            //					{
            //					idesc = &cdesc->interface[j].altsetting[k];
            //					printf("|---- INTERFACE :\n");
            //					printf("|------ Interface %i%i\n", j, k);
            //					printf("|------ bLength 0x%.2x\n", idesc->bLength);
            //					printf("|------ bDescriptorType 0x%.2x\n", idesc->bDescriptorType);
            //					printf("|------ bInterfaceNumber 0x%.2x\n", idesc->bInterfaceNumber);
            //					printf("|------ bAlternateSetting 0x%.2x\n", idesc->bAlternateSetting);
            //					printf("|------ bNumEndpoints 0x%.2x\n", idesc->bNumEndpoints);
            //					printf("|------ bInterfaceClass 0x%.2x\n", idesc->bInterfaceClass);
            //					printf("|------ bInterfaceSubClass 0x%.2x\n", idesc->bInterfaceSubClass);
            //					printf("|------ bInterfaceProtocol 0x%.2x\n", idesc->bInterfaceProtocol);
            //					printf("|------ iInterface 0x%.2x\n|\n", idesc->iInterface);
            //					for (l = 0 ; l < idesc->bNumEndpoints ; l++)
            //						{
            //						edesc = &idesc->endpoint[l];
            //						printf("|------ ENDPOINT DESCRIPTOR :\n");
            //						printf("|-------- bLength 0x%.2x\n", edesc->bLength);
            //						printf("|-------- bDescriptorType 0x%.2x\n", edesc->bDescriptorType);
            //						printf("|-------- bEndpointAddress 0x%.2x\n", edesc->bEndpointAddress);
            //						printf("|-------- bmAttributes 0x%.2x\n", edesc->bmAttributes);
            //						printf("|-------- wMaxPacketSize 0x%.4x\n", edesc->wMaxPacketSize);
            //						printf("|-------- bInterval 0x%.2x\n", edesc->bInterval);
            //						printf("|-------- bRefresh 0x%.2x\n", edesc->bRefresh);
            //						printf("|-------- bSynchAddress 0x%.2x\n|\n", edesc->bSynchAddress);
            //						}	// for l
            //					}	// for k
            //				}	// for j
        } // if (retValue == LIBUSB_SUCCESS)
        else
        {
            Starfish_LogFmt("libusb_get_active_config_descriptor failed: %s\n", libusb_error_name(retValue));

            
            return (EXIT_FAILURE);
        }

    } // for i

    libusb_free_config_descriptor(cdesc);

    printOutDiscoveredCamerasDB();

    gFindCamState           = fcFindCam_finished;
    gFindCamPercentComplete = 100.0;

    gNumCamerasDiscovered = getNextFreeDBIndex();
    return gNumCamerasDiscovered;
}

// call this routine to know how what state the fcUsb_FindCameras routine is currently executing
// will return a result of fcFindCamState type
//
int fcUsb_GetFindCamsState(void)
{
    return gFindCamState;
}

// call this routine to know how long it will take for the fcUsb_FindCameras routine to complete.
//
float fcUsb_GetFindCamsPercentComplete(void)
{
    return gFindCamPercentComplete;
}

// need to open the camera before using it.
// valid camNum is 1 -> fcUsb_GetNumCameras()
//
int fcUsb_OpenCamera(int camNum)
{
    //int result;
    //int i;
    //bool done;
    //int d;
    
    UInt16 vendor;
    UInt16 product;
    UInt16 release;
    struct libusb_device_handle *usb_handle;
    int kr;
    int tries;
    int retval;

    Starfish_Log("fcUsb_OpenCamera routine\n");

    if (gDoSimulation)
    {
        Starfish_LogFmt("Found Starfish - SN%04d\n", gCamerasFound[camNum - 1].camRelease);
        
        fcUsb_setStarfishDefaultRegs(camNum);
        return 0;
    }

    if (gCamerasFound[camNum - 1].dev == 0)
    {
        // this camera wasn't being used yet
        vendor = gCamerasFound[camNum - 1].camVendor;

        if (vendor != 0)
        {
            // this camera has a DB entry
            product = gCamerasFound[camNum - 1].camFinalProduct;
            if (product == 0)
            {
                // we have a RAW starfish camera being opened
                product = gCamerasFound[camNum - 1].camRawProduct;

                Starfish_LogFmt("Opening raw USB device with vendor: %08x prodcut: %08x\n", vendor, product);
                
                usb_handle                    = libusb_open_device_with_vid_pid(gCtx, vendor, product);
                gCamerasFound[camNum - 1].dev = usb_handle;
                if (usb_handle == NULL)
                {
                    Starfish_Log("Unable to open the raw USB device\n");
                    return -1;
                }

                // the ReleaseNumber is used by fishcamp as a camera serial number.  Store it away
                // make sure we store the camera serial number in gRelease.
                // the DownloadToAnchorDevice routine will make sure the newly renumerated
                // camera will have the same serial number.
                release  = gCamerasFound[camNum - 1].camRelease;
                gRelease = release;

                kr = DownloadToAnchorDevice(usb_handle, vendor, product);
                if (0 != kr)
                {
                    Starfish_LogFmt("unable to download to device: %08x\n", kr);
                    
                    libusb_close(usb_handle);
                    return -1;
                }

                libusb_close(usb_handle);
                usb_handle                    = NULL;
                gCamerasFound[camNum - 1].dev = usb_handle;

                // found a RAW starfish cameras and renumerated it to the programmed state of the USB controller
                // the RAW camera has been renumerated.  Wait for it to brecome ready
                // takes about three seconds for the device to reset and my laptop to find it again. Wait for 10.
                product = product + 1;
                for (tries = 0; tries < 25; tries++)
                {
                    usb_handle = libusb_open_device_with_vid_pid(gCtx, vendor, product);
                    if (usb_handle)
                    {
                        gCamerasFound[camNum - 1].camFinalProduct = product;
                        break;
                    }
                    usleep(400 * 1000);
                }

                // let the device settle -- sometimes we're able to open it before it's ready to accept the S-Record download
                usleep(1000 * 1000);

                gCamerasFound[camNum - 1].dev = usb_handle;
                if (usb_handle == NULL)
                {
                    Starfish_Log("Unable to open the final USB device\n");
                }
                else
                {
                    retval = libusb_claim_interface(usb_handle, 0);
                    if (retval < 0)
                        Starfish_Log("Couldn't claim interface 0\n");

                    // one last thing....
                    // need to load the MicroBlaze executable program before doing any camera stuff.

                    if (gCamerasFound[camNum - 1].camRawProduct !=
                        0) // if the camera was discovered in the RAW state.  we will know this cause it will be non-zero
                    {
                        Starfish_Log("Calling - DownloadtToMicroBlaze \n");
                        DownloadtToMicroBlaze(camNum);
                    }
                    else
                    {
                        Starfish_Log("didn't need to call - DownloadtToMicroBlaze \n");
                    }

                    //				    download_srec(usb_handle, argv[1], FC_STARFISH_BULK_OUT_ENDPOINT);
                }
            } //(product == 0)	// we have a RAW starfish camera being opened
            else
            {
                // we have a FINAL starfish camera being opened
                Starfish_Log("we have a FINAL starfish camera being opened.\n");

                usb_handle = libusb_open_device_with_vid_pid(gCtx, vendor, product);

                gCamerasFound[camNum - 1].dev = usb_handle;
                if (usb_handle == NULL)
                {
                    Starfish_Log("Unable to open the final USB device\n");
                }
                else
                {
                    retval = libusb_claim_interface(usb_handle, 0);
                    if (retval < 0)
                        Starfish_Log("Couldn't claim interface 0\n");

                    if (gCamerasFound[camNum - 1].camFinalProduct ==
                        starfish_mono_rev2_final_deviceID) // is this a Starfish?
                    {
                        Starfish_LogFmt("Found Starfish - SN%04d\n", gCamerasFound[camNum - 1].camRelease);
                        
                        fcUsb_setStarfishDefaultRegs(camNum);
                    }
                }
            } // we have a FINAL starfish camera being opened
        }     // if - this camera has a DB entry
    }         // this camera wasn't being used yet

    //	printOutDiscoveredCamerasDB();

    return 0;
}

// call this routine after you are finished making calls to this camera
// This routine will free the designated camera for other applications to use
// valid camNum is 1 -> fcUsb_GetNumCameras()
//
int fcUsb_CloseCamera(int camNum)
{
    if (gDoSimulation)
        return 0;

    if (gCamerasFound[camNum - 1].dev)
    {
        libusb_release_interface(gCamerasFound[camNum - 1].dev, 0);
        libusb_reset_device(gCamerasFound[camNum - 1].dev);
        libusb_close(gCamerasFound[camNum - 1].dev);

        gCamerasFound[camNum - 1].dev = NULL;

        return 0;
    }

    return -1;
}

// call this routine to know how many of our supported cameras are available for use.
//
int fcUsb_GetNumCameras(void)
{
    return gNumCamerasDiscovered;
}

bool fcUsb_haveCamera(void)
{
    bool haveCamera;

    haveCamera = true;

    // do we have at least one camera?
    if (gNumCamerasDiscovered == 0)
        haveCamera = false;

    return haveCamera;
}

// return the numeric serial number of the camera specified.
// valid camNum is 1 -> fcUsb_GetNumCameras()
//
int fcUsb_GetCameraSerialNum(int camNum)
{
    return gCamerasFound[camNum - 1].camRelease;
}

// return the numeric vendorID of the camera specified.
// valid camNum is 1 -> fcUsb_GetNumCameras()
//
int fcUsb_GetCameraVendorID(int camNum)
{
    return gCamerasFound[camNum - 1].camVendor;
}

// return the numeric productID of the camera specified.
// valid camNum is 1 -> fcUsb_GetNumCameras()
//
int fcUsb_GetCameraProductID(int camNum)
{
    return gCamerasFound[camNum - 1].camFinalProduct;
}

// send the nop command to the starfish camera
//
int fcUsb_cmd_nop(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    Starfish_Log("fcUsb_cmd_nop\n");

    if (gDoSimulation)
        return 0;

    myParameters.header  = 'fc';
    myParameters.command = fcNOP;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// send the rst command to the starfish camera
//
int fcUsb_cmd_rst(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    Starfish_Log("fcUsb_cmd_rst\n");

    if (gDoSimulation)
        return 0;

    myParameters.header  = 'fc';
    myParameters.command = fcRST;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// send the fcGETINFO command to the starfish camera
// read the return information
//
int fcUsb_cmd_getinfo(int camNum, fc_camInfo *camInfo)
{
    UInt32 msgSize;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    fc_no_param myParameters;
    UInt16 *wordPtr1;
    UInt16 *wordPtr2;
    size_t i;
    
    int maxBytes;

    Starfish_Log("fcUsb_cmd_getinfo\n");

    if (gDoSimulation)
    {
        camInfo->boardRevision = 1;
        camInfo->boardVersion  = 2;
        camInfo->fpgaRevision  = 1;
        camInfo->fpgaVersion   = 2;
        camInfo->pixelHeight   = 7;
        camInfo->pixelWidth    = 7;
        camInfo->width         = 1280;
        camInfo->height        = 1024;
    }
    else
    {
        // send the command to the camera
        myParameters.header  = 'fc';
        myParameters.command = fcGETINFO;
        myParameters.length  = sizeof(myParameters);
        myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

        msgSize = sizeof(myParameters);
        SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

        // get the response to the command
        maxBytes     = 512;
        numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

        // we have the ACK message in gBuffer.  It is proceeded by two words before the fc_camInfo
        // data.  copy the real data to the user's data structure.
        wordPtr1 = (UInt16 *)&gBuffer[4];
        wordPtr2 = (UInt16 *)camInfo;

        for (i = 0; i < sizeof(fc_camInfo); i += 2)
            *wordPtr2++ = *wordPtr1++;
    }

    // bug fix.  The starfish puts out garbled text for the camera name string
	strcpy((char*)camInfo->camNameStr, "Starfish Mono");

    // also need to fix up the serial number
    sprintf((char*)camInfo->camSerialStr, "%04x-%04x-%04x-%04x",
		gCamerasFound[camNum - 1].camVendor,
		gCamerasFound[camNum - 1].camRawProduct,
        gCamerasFound[camNum - 1].camFinalProduct,
		gCamerasFound[camNum - 1].camRelease);

    // JM (2013-12-13) camInfo->pixelWidth returns 1312 (0x520) which is wrong
    camInfo->pixelWidth  = 52;
    camInfo->pixelHeight = 52;

    // print out the information returned
    Starfish_Log("fcUsb_cmd_getinfo:\n");
    Starfish_LogFmt("     boardVersion  - 0x%02x\n", camInfo->boardVersion);
    Starfish_LogFmt("     boardRevision - 0x%02x\n", camInfo->boardRevision);
    Starfish_LogFmt("     fpgaVersion   - 0x%02x\n", camInfo->fpgaVersion);
    Starfish_LogFmt("     fpgaRevision  - 0x%02x\n", camInfo->fpgaRevision);
    Starfish_LogFmt("     width         - 0x%02x\n", camInfo->width);
    Starfish_LogFmt("     height        - 0x%02x\n", camInfo->height);
    Starfish_LogFmt("     pixelWidth    - 0x%02x\n", camInfo->pixelWidth);
    Starfish_LogFmt("     pixelHeight   - 0x%02x\n", camInfo->pixelHeight);
    Starfish_LogFmt("     camSerialStr  - %s\n", camInfo->camSerialStr);
    Starfish_LogFmt("     camNameStr    - %s\n", camInfo->camNameStr);

    return 0;
}

// call to set a low level register in the camera
//
// for Starfish camera:
//		- call to set a low level register in the micron image sensor
//		- enter with the micron address register, and register data value
//			refer to the micron image sensor documentation for details
//			on available registers and bit definitions
//
// for IBIS1300
//		- call to set the serial configuration word on the camera
//		- enter with regAddress = 0, dataValue
//
// valid camNum is 1 -> fcUsb_GetNumCameras()
//
int fcUsb_cmd_setRegister(int camNum, UInt16 regAddress, UInt16 dataValue)
{
    UInt32 msgSize;
    fc_setReg_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    // print out the information
    //	printf("fcUsb_cmd_setRegister:\n");
    //	printf("     address - 0x%04x\n", regAddress);
    //	printf("     data    - 0x%04x\n", dataValue);

    Starfish_Log("fcUsb_cmd_setRegister\n");

    if (gDoSimulation)
        return 0;

    myParameters.header          = 'fc';
    myParameters.command         = fcSETREG;
    myParameters.length          = sizeof(myParameters);
    myParameters.registerAddress = regAddress;
    myParameters.dataValue       = dataValue;
    myParameters.cksum           = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// call to get a low level register from the micron image sensor
//

UInt16 fcUsb_cmd_getRegister(int camNum, UInt16 regAddress)
{
    UInt32 msgSize;
    fc_getReg_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 retValue;
    fc_regInfo myRegInfo;
    //
    int maxBytes;

    Starfish_Log("fcUsb_cmd_getRegister\n");

    if (gDoSimulation)
        return 0;

    myParameters.header          = 'fc';
    myParameters.command         = fcGETREG;
    myParameters.length          = sizeof(myParameters);
    myParameters.registerAddress = regAddress;
    myParameters.cksum           = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);

    //	Starfish_LogFmt("     myParameters.header           - 0x%04x\n", myParameters.header);
    //	Starfish_LogFmt("     myParameters.command          - 0x%04x\n", myParameters.command);
    //	Starfish_LogFmt("     myParameters.length           - 0x%04x\n", myParameters.length);
    //	Starfish_LogFmt("     myParameters.registerAddress  - 0x%04x\n", myParameters.registerAddress);
    //	Starfish_LogFmt("     myParameters.cksum            - 0x%04x\n", myParameters.cksum);
    //	Starfish_LogFmt("     msgSize                       - 0x%08x\n", msgSize);

    //	Starfish_LogFmt("     gUSBDevice                    - 0x%08x\n", gUSBDevice);
    //	Starfish_LogFmt("     BulkOutEndPt                  - 0x%08x\n", gUSBDevice->BulkOutEndPt);

    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    //	Starfish_LogFmt("     msgSize                       - 0x%08x\n", msgSize);

    // get the ACK to the command
    maxBytes = 512;

    //	Starfish_LogFmt("     numBytesRead                  - 0x%08x\n", numBytesRead);

    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    // copy the Rx buffer to my local storage
    memcpy(&myRegInfo, &gBuffer, sizeof(myRegInfo));

    //	// print out the information returned
    //	Starfish_LogFmt("fcUsb_cmd_getRegister:\n");
    //	Starfish_LogFmt("     address       - 0x%04x\n", regAddress);
    //	Starfish_LogFmt("     header        - 0x%04x\n", myRegInfo.header);
    //	Starfish_LogFmt("     command       - 0x%04x\n", myRegInfo.command);
    //	Starfish_LogFmt("     dataValue     - 0x%04x\n", myRegInfo.dataValue);
    //	Starfish_LogFmt("     numBytesRead  - 0x%08x\n", numBytesRead);

    return myRegInfo.dataValue;
}

// call to set the integration time register.  only 22 LSB significant  (69 minutes with 1ms resolution)
//
int fcUsb_cmd_setIntegrationTime(int camNum, UInt32 theTime)
{
    UInt32 msgSize;
    fc_setIntTime_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    UInt16 aWord;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_setIntegrationTime\n");

    if (gDoSimulation)
        return 0;

    // store the time away so we can decide if we want to read the black columns
    gCurrentIntegrationTime[camNum - 1] = theTime;

    //This is a nice idea, but there are 2 problems with it.
    //First, with the Starfish, for some reason the images fail to download when full frame images are requested.
    //I believe this is due to the fact that it is requesting images that are larger than it can do.
    //Second, if the region of interest is smaller than full frame, the images will download properly, but
    //then the black column positions will make no sense because the region of interest is not adjacent to black columns then.
    /**
	// special handling for the starfish guide camera
	if (gCamerasFound[camNum - 1].camFinalProduct == starfish_mono_rev2_final_deviceID)
		{
		// tell the camera the new setting of the read black cols mode bit.  We will read the
		// black cols anytime the integration time is less than 2 seconds.
		fcUsb_cmd_setReadMode(camNum, gDataXfrReadMode[camNum - 1], gDataFormat[camNum - 1]);
		// resend the ROI params.  the fcUsb_cmd_setReadMode routine will have set the gReadBlack variable
		// so the next routine will trick the camera into using a wider ROI.
		fcUsb_cmd_setRoi(camNum, gRoi_left[camNum - 1], gRoi_top[camNum - 1], gRoi_right[camNum - 1], gRoi_bottom[camNum - 1]);
		}
	**/

    myParameters.header  = 'fc';
    myParameters.command = fcSETINTTIME;
    myParameters.length  = sizeof(myParameters);
    aWord                = theTime >> 16;
    aWord                = aWord & 0x0ffff;
    myParameters.timeHi  = aWord;
    aWord                = theTime & 0x0ffff;
    myParameters.timeLo  = aWord;
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// command to set the integration time period of the guider portion of
// the IBIS1300 image sensor.  This command is not recognized by the Starfish camera
//
int fcUsb_cmd_setGuiderIntegrationTime(int camNum, UInt32 theTime)
{
    UInt32 msgSize;
    fc_setIntTime_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    UInt16 aWord;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_setGuiderIntegrationTime\n");

    if (gDoSimulation)
        return 0;

    // store the time away so we can decide if we want to read the black columns
    gCurrentIntegrationTime[camNum - 1] = theTime;

    // only the ibis1300 camera supports this right now
    if (gCamerasFound[camNum - 1].camFinalProduct == starfish_ibis13_final_deviceID)
    {
        // store the time away so we can decide if we want to read the black columns
        gCurrentGuiderIntegrationTime[camNum - 1] = theTime;

        myParameters.header  = 'fc';
        myParameters.command = fcSETGUIDEINTTIME;
        myParameters.length  = sizeof(myParameters);
        aWord                = theTime >> 16;
        aWord                = aWord & 0x0ffff;
        myParameters.timeHi  = aWord;
        aWord                = theTime & 0x0ffff;
        myParameters.timeLo  = aWord;
        myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

        msgSize = sizeof(myParameters);
        SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

        // get the ACK to the command
        maxBytes     = 512;
        numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);
    }

    return 0;
}

// send the 'start exposure' command to the camera
//
int fcUsb_cmd_startExposure(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    Starfish_Log("fcUsb_cmd_startExposure\n");

    if (gDoSimulation)
        return 0;

    myParameters.header  = 'fc';
    myParameters.command = fcSTARTEXP;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// send the 'start exposure' command to the guider portion of the
// IBIS1300 image sensor.    This command is not recognized by the Starfish camera
//
int fcUsb_cmd_startGuiderExposure(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    Starfish_Log("fcUsb_cmd_startGuiderExposure\n");

    if (gDoSimulation)
        return 0;

    // only the ibis1300 camera supports this right now
    if (gCamerasFound[camNum - 1].camFinalProduct == starfish_ibis13_final_deviceID)
    {
        myParameters.header  = 'fc';
        myParameters.command = fcSTARTGUIDEEXP;
        myParameters.length  = sizeof(myParameters);
        myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

        msgSize = sizeof(myParameters);
        SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

        // get the ACK to the command
        maxBytes     = 512;
        numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);
    }

    return 0;
}

// send the 'abort exposure' command to the camera
//
int fcUsb_cmd_abortExposure(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    Starfish_Log("fcUsb_cmd_abortExposure\n");

    if (gDoSimulation)
        return 0;

    myParameters.header  = 'fc';
    myParameters.command = fcABORTEXP;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// send the 'abort Guider exposure' command to the camera
// This command is not recognized by the Starfish camera
//
// valid camNum is 1 -> fcUsb_GetNumCameras()
//
int fcUsb_cmd_abortGuiderExposure(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    Starfish_Log("fcUsb_cmd_abortGuiderExposure\n");

    if (gDoSimulation)
        return 0;

    // only the ibis1300 camera supports this right now
    if (gCamerasFound[camNum - 1].camFinalProduct == starfish_ibis13_final_deviceID)
    {
        myParameters.header  = 'fc';
        myParameters.command = fcABORTGUIDEEXP;
        myParameters.length  = sizeof(myParameters);
        myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

        msgSize = sizeof(myParameters);
        SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

        // get the ACK to the command
        maxBytes     = 512;
        numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);
    }

    return 0;
}

// send a command to get the current camera state
//
UInt16 fcUsb_cmd_getState(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 retValue;
    fc_regInfo myRegInfo;
    int maxBytes;

    if (gDoSimulation)
        return 0;

    //	printf("fcUsb_cmd_getState:\n");
    //	Starfish_Log("fcUsb_cmd_getState\n");

    myParameters.header  = 'fc';
    myParameters.command = fcGETSTATE;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    //	printf("fcUsb_cmd_getState -  about to read:\n");

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    // copy the Rx buffer to my local storage
    memcpy(&myRegInfo, &gBuffer, sizeof(myRegInfo));

    // print out the information returned
    //	printf("fcUsb_cmd_getState:\n");
    //	printf("     dataValue  - 0x%02x\n", myRegInfo.dataValue);

    usleep(10000);

    return myRegInfo.dataValue;
}

// send a command to get the current camera state of the guider sensor
// This command is not recognized by the Starfish camera
//
// valid camNum is 1 -> fcUsb_GetNumCameras()
// return values are:
//	0 - idle
//	1 - integrating
//	2 - processing image
//
UInt16 fcUsb_cmd_getGuiderState(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 retValue;
    fc_regInfo myRegInfo;
    int maxBytes;

    //	printf("fcUsb_cmd_getGuiderState:\n");
    Starfish_Log("fcUsb_cmd_getGuiderState\n");

    if (gDoSimulation)
        return 0;

    // only the ibis1300 camera supports this right now
    if (gCamerasFound[camNum - 1].camFinalProduct == starfish_ibis13_final_deviceID)
    {
        myParameters.header  = 'fc';
        myParameters.command = fcGETGUIDESTATE;
        myParameters.length  = sizeof(myParameters);
        myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

        msgSize = sizeof(myParameters);
        SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

        //		printf("fcUsb_cmd_getGuiderState -  about to read:\n");

        // get the ACK to the command
        maxBytes     = 512;
        numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

        // copy the Rx buffer to my local storage
        memcpy(&myRegInfo, &gBuffer, sizeof(myRegInfo));

        // print out the information returned
        //		printf("fcUsb_cmd_getGuiderState:\n");
        //		printf("     dataValue  - 0x%02x\n", myRegInfo.dataValue);

        usleep(10000000);

        return myRegInfo.dataValue;
    }
    else
    {
        return 0; // idle state if not supported
    }
}

// turn on/off the frame grabber's test pattern generator
// 0 = off, 1 = on
//
int fcUsb_cmd_setFrameGrabberTestPattern(int camNum, UInt16 state)
{
    UInt32 msgSize;
    fc_setFgTp_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 retValue;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_setFrameGrabberTestPattern\n");

    if (gDoSimulation)
        return 0;

    myParameters.header  = 'fc';
    myParameters.command = fcSETFGTP;
    myParameters.length  = sizeof(myParameters);
    myParameters.state   = state;
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// here to read a specific scan line from the camera's frame grabber buffer
//
int fcUsb_cmd_rdScanLine(int camNum, UInt16 lineNum, UInt16 Xmin, UInt16 Xmax, UInt16 *lineBuffer)
{
    UInt32 msgSize;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    fc_rdScanLine_param myParameters;
    UInt16 dataOdd;
    UInt16 dataEven;
    UInt16 *wordPtr1;
    UInt16 *wordPtr2;
    int i;
    int scanLineSize;
    fc_scanLineInfo myScanLineInfo;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_rdScanLine\n");

    // send the command to the camera
    myParameters.header  = 'fc';
    myParameters.command = fcRDSCANLINE;
    myParameters.LineNum = lineNum;
    myParameters.padZero = 0;
    myParameters.Xmin    = Xmin;
    myParameters.Xmax    = Xmax;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the response to the command
    maxBytes     = (((Xmax - Xmin) + 1) * 2) + 12; // bigger is OK
    maxBytes     = 2048;
    maxBytes     = sizeof(myScanLineInfo);
    maxBytes     = 4608;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&myScanLineInfo, maxBytes);

    // we have the ACK message in myScanLineInfo.  It is proceeded by several words before the fc_scanLineInfo
    // data.  copy the real data to the user's data structure.
    wordPtr1 = &myScanLineInfo.lineBuffer[0];
    wordPtr2 = lineBuffer;

    scanLineSize = Xmax - Xmin + 1;
    for (i = 0; i < scanLineSize; i = i + 2)
    {
        //		*wordPtr2++ = *wordPtr1++;
        dataOdd  = *wordPtr1++;
        dataEven = *wordPtr1++;

        //		*wordPtr2++ = dataEven;
        //		*wordPtr2++ = dataOdd;
        *wordPtr2++ = dataOdd;
        *wordPtr2++ = dataEven;
    }

    return 0;
}

// here to specify a new ROI to the sensor
// X = 0 -> 2047

// Y = 0 -> 1535
//
int fcUsb_cmd_setRoi(int camNum, UInt16 left, UInt16 top, UInt16 right, UInt16 bottom)
{
    UInt32 msgSize;
    fc_setRoi_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 regVal;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_setRoi\n");

    // store user requested number away
    gRoi_left[camNum - 1]   = left;
    gRoi_top[camNum - 1]    = top;
    gRoi_right[camNum - 1]  = right;
    gRoi_bottom[camNum - 1] = bottom;

    if (gDoSimulation)
        return 0;

    myParameters.header  = 'fc';
    myParameters.command = fcSETROI;
    myParameters.length  = sizeof(myParameters);
    myParameters.left    = left;
    myParameters.top     = top;
    myParameters.right   = right;
    myParameters.bottom  = bottom;

    // if we need to read the black cols, increase the ROI width appropriately
    if (gReadBlack[camNum - 1])
        myParameters.right = right + 16;

    myParameters.cksum = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    //	regVal = top + 20;	// default first row is 20
    //	regVal = regVal & 0x7fe;	// 11 bits, must be even
    //	fcUsb_cmd_setRegister(0x01, regVal);
    //	regVal = left + 32;	// default first row is 32
    //	regVal = regVal & 0xffe;	// 12 bits, must be even
    //	fcUsb_cmd_setRegister(0x02, regVal);
    //	regVal = bottom - top;
    //	regVal = regVal & 0x7ff;	// 11 bits, must be odd
    //	regVal = regVal | 0x0001;
    //	fcUsb_cmd_setRegister(0x03, regVal);
    //	regVal = right - left;
    //	regVal = regVal & 0x7ff;	// 11 bits, must be odd
    //	regVal = regVal | 0x0001;
    //	fcUsb_cmd_setRegister(0x04, regVal);

    // test
    //	regVal = fcUsb_cmd_getRegister(camNum, 0x01);
    //	regVal = fcUsb_cmd_getRegister(camNum, 0x02);
    //	regVal = fcUsb_cmd_getRegister(camNum, 0x03);
    //	regVal = fcUsb_cmd_getRegister(camNum, 0x04);

    return 0;
}

// set the binning mode of the camera.  Valid binModes are 1, 2, 3
//
int fcUsb_cmd_setBin(int camNum, UInt16 binMode)
{
    UInt32 msgSize;
    fc_setBin_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 regVal;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_setBin\n");

    if (gDoSimulation)
        return 0;

    myParameters.header  = 'fc';
    myParameters.command = fcSETBIN;
    myParameters.length  = sizeof(myParameters);
    myParameters.binMode = binMode;
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    // test
    //	regVal = fcUsb_cmd_getRegister(camNum, 0x22);
    //	regVal = fcUsb_cmd_getRegister(camNum, 0x23);

    return 0;
}

// turn on one of the relays on the camera.  whichRelay is one of enum fc_relay
//
int fcUsb_cmd_setRelay(int camNum, int whichRelay)
{
    UInt32 msgSize;
    fc_setClrRelay_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 regVal;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_setRelay\n");

    if (gDoSimulation)
        return 0;

    myParameters.header   = 'fc';
    myParameters.command  = fcSETRELAY;
    myParameters.length   = sizeof(myParameters);
    myParameters.relayNum = whichRelay;
    myParameters.cksum    = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// turn off one of the relays on the camera.  whichRelay is one of enum fc_relay
//
int fcUsb_cmd_clearRelay(int camNum, int whichRelay)
{
    UInt32 msgSize;
    fc_setClrRelay_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 regVal;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_clearRelay\n");

    if (gDoSimulation)
        return 0;

    myParameters.header   = 'fc';
    myParameters.command  = fcCLRRELAY;
    myParameters.length   = sizeof(myParameters);
    myParameters.relayNum = whichRelay;
    myParameters.cksum    = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// generate a pulse on one of the relays on the camera.  whichRelay is one of enum fc_relay
// pulse width parameters are in mS.  you can specify the hi and lo period of the pulse.
// if 'repeats' is true then the pulse will loop.
//
int fcUsb_cmd_pulseRelay(int camNum, int whichRelay, int onMs, int offMs, bool repeats)
{
    UInt32 msgSize;
    fc_pulseRelay_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 regVal;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_pulseRelay\n");

    if (gDoSimulation)
        return 0;

    myParameters.header         = 'fc';
    myParameters.command        = fcPULSERELAY;
    myParameters.length         = sizeof(myParameters);
    myParameters.relayNum       = whichRelay;
    myParameters.highPulseWidth = onMs & 0x7fff;  // only 15 bits
    myParameters.lowPulseWidth  = offMs & 0x7fff; // only 15 bits
    if (repeats)
        myParameters.repeats = 1;
    else
        myParameters.repeats = 0;
    myParameters.cksum = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// tell the camera what temperature setpoint to use for the TEC controller
// this will also turn on cooling to the camera
//
int fcUsb_cmd_setTemperature(int camNum, SInt16 theTemp)
{
    UInt32 msgSize;
    fc_setTemp_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    if (gDoSimulation)
        return 0;

    Starfish_Log("fcUsb_cmd_setTemperature\n");

    myParameters.header  = 'fc';
    myParameters.command = fcSETTEMP;
    myParameters.length  = sizeof(myParameters);
    myParameters.theTemp = theTemp;
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// get the temperature of the image sensor
//
SInt16 fcUsb_cmd_getTemperature(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 regVal;
    fc_tempInfo myTemperatureInfo;
    float theCurTemperature;
    
    int maxBytes;

    Starfish_Log("fcUsb_cmd_getTemperature\n");

    if (gDoSimulation)
        return 25;

    myParameters.header  = 'fc';
    myParameters.command = fcGETTEMP;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    // copy the Rx buffer to my local storage
    memcpy(&myTemperatureInfo, &gBuffer, sizeof(myTemperatureInfo));

    // put the current temperature in the log file
    theCurTemperature = (float)myTemperatureInfo.tempValue;
    theCurTemperature = theCurTemperature / 100.0;
    Starfish_LogFmt("     Got temperature - %2.1f degrees C\n", theCurTemperature);
    

    return myTemperatureInfo.tempValue;
}

UInt16 fcUsb_cmd_getTECPowerLevel(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 regVal;
    fc_tempInfo myTemperatureInfo;
    
    UInt16 theCurPower;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_getTECPowerLevel\n");

    if (gDoSimulation)
        return 50;

    myParameters.header  = 'fc';
    myParameters.command = fcGETTEMP;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    // copy the Rx buffer to my local storage
    memcpy(&myTemperatureInfo, &gBuffer, sizeof(myTemperatureInfo));

    // put the current power in the log file
    theCurPower = myTemperatureInfo.TECPwrValue;
    Starfish_LogFmt("     Got power level - %d percent\n", theCurPower);
    

    return myTemperatureInfo.TECPwrValue;
}

bool fcUsb_cmd_getTECInPowerOK(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    //UInt16 regVal;
    fc_tempInfo myTemperatureInfo;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_getTECInPowerOK\n");

    if (gDoSimulation)
        return true;

    myParameters.header  = 'fc';
    myParameters.command = fcGETTEMP;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    // copy the Rx buffer to my local storage
    memcpy(&myTemperatureInfo, &gBuffer, sizeof(myTemperatureInfo));

    if (myTemperatureInfo.TECInPwrOK == 0)
        return false;
    else
        return true;
}

// command camera to turn off the TEC cooler
//
int fcUsb_cmd_turnOffCooler(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    Starfish_Log("fcUsb_cmd_turnOffCooler\n");

    if (gDoSimulation)
        return 0;

    myParameters.header  = 'fc';
    myParameters.command = fcTURNOFFTEC;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// here to read an entire frame in RAW format
//
int fcUsb_cmd_getRawFrame(int camNum, UInt16 numRows, UInt16 numCols, UInt16 *frameBuffer)
{
    UInt32 msgSize;
    UInt32 numBytesRead = 0;
    fc_no_param myParameters;
    //UInt16 dataOdd;
    //UInt16 dataEven;
    //UInt16 *wordPtr1;
    //UInt16 *wordPtr2;
    //int i;
    //char errorString[513];
    int maxBytes;
    

    Starfish_Log("fcUsb_cmd_getRawFrame\n");

    if (gDoSimulation)
        return 0;

    // send the command to the camera
    myParameters.header  = 'fc';
    myParameters.command = fcGETRAWFRAME;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);

    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the response to the command

    if (gCamerasFound[camNum - 1].camFinalProduct == starfish_pro4m_final_deviceID)
    {
        maxBytes     = numRows * numCols * 2; // 2 bytes / pixel
        numBytesRead = RcvUSB(camNum, (unsigned char *)&frameBuffer, maxBytes);

        Starfish_LogFmt("   read - %ld bytes\n", numBytesRead);
        

        if (gProWantColNormalization)
            fcImage_PRO_doFullFrameColLevelNormalization(frameBuffer, numCols, numRows);
    }
    else
    {
        if (gCamerasFound[camNum - 1].camFinalProduct == starfish_ibis13_final_deviceID)
        {
            maxBytes     = numRows * numCols * 2; // 2 bytes / pixel
            numBytesRead = RcvUSB(camNum, (unsigned char *)&frameBuffer, maxBytes);

            fcImage_IBIS_doFullFrameColLevelNormalization(frameBuffer, numCols, numRows);
            fcImage_IBIS_subtractPedestal(frameBuffer, numCols, numRows);
        }
        else
        {
            // if we are doing black level compensation, then we read into our internal buffer
            // then strip out the balck cols after we are done with them
            if (gReadBlack[camNum - 1])
            {
                maxBytes     = numRows * (numCols + 16) * 2; // 2 bytes / pixel
                numBytesRead = RcvUSB(camNum, (unsigned char *)gFrameBuffer, maxBytes);
            }
            else
            {
                maxBytes     = numRows * numCols * 2; // 2 bytes / pixel
                numBytesRead = RcvUSB(camNum, (unsigned char *)frameBuffer, maxBytes);
            }
            Starfish_LogFmt("   fcUsb_cmd_getRawFrame - numBytesRead - %i\n", (unsigned int)numBytesRead);
            
            if (gReadBlack[camNum - 1] && numBytesRead != 0)
            {
                fcImage_doFullFrameRowLevelNormalization(gFrameBuffer, (numCols + 16), numRows);
                fcImage_StripBlackCols(camNum, frameBuffer);
            }
        } // if Starfish
    }

    if (gCameraImageFilter[camNum - 1] == fc_filter_3x3)
    {
        // perform 3x3 kernel filter
        fcImage_do_3x3_kernel(numRows, numCols, frameBuffer);
    }

    if (gCameraImageFilter[camNum - 1] == fc_filter_5x5)
    {
        // perform 5x5 kernel filter
        fcImage_do_5x5_kernel(numRows, numCols, frameBuffer);
    }

    if (gCameraImageFilter[camNum - 1] == fc_filter_hotPixel)
    {
        // perform hot pixel removal filter
        fcImage_do_hotPixel_kernel(numRows, numCols, frameBuffer);
    }

    return (numBytesRead);
}

// here to set the analog gain on the camera.
//
// theGain the gain number desired.
//
// Valid gains are between 0 and 15.
//		For the IBIS camera this corresponds to 1.28 to 17.53 as per the sensor data sheet
//
int fcUsb_cmd_setCameraGain(int camNum, UInt16 theGain)
{
    UInt32 msgSize;
    fc_setGain_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    Starfish_Log("fcUsb_cmd_setCameraGain\n");

    if (gDoSimulation)
        return 0;

    myParameters.header  = 'fc';
    myParameters.command = fcSETGAIN;
    myParameters.length  = sizeof(myParameters);
    myParameters.theGain = theGain;
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    // if this is the IBIS1300 camera, then we need to get new black row averages
    if (gCamerasFound[camNum - 1].camFinalProduct == starfish_ibis13_final_deviceID)
        fcImage_IBIS_computeAvgColOffsets(camNum);

    gBlackPedestal[camNum - 1] = fcUsb_cmd_getBlackPedestal(camNum);

    return 0;
}

// here to set the analog offset on the camera.
//
// theOffset the offset number desired.
//
// For the IBIS camera valid offsets are between 0 and 15.
//
int fcUsb_cmd_setCameraOffset(int camNum, UInt16 theOffset)
{
    UInt32 msgSize;
    fc_setOffset_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    Starfish_Log("fcUsb_cmd_setCameraOffset\n");

    if (gDoSimulation)
        return 0;

    myParameters.header    = 'fc';
    myParameters.command   = fcSETOFFSET;
    myParameters.length    = sizeof(myParameters);
    myParameters.theOffset = theOffset;
    myParameters.cksum     = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    gBlackPedestal[camNum - 1] = fcUsb_cmd_getBlackPedestal(camNum);

    return 0;
}

// here to define some image readout modes of the camera.  The state of these bits will be
// used during the call to fcUsb_cmd_startExposure.  When an exposure is started and subsequent
// image readout is begun, the camera will assume an implied fcUsb_cmd_getRawFrame command
// when the 'DataXfrReadMode' is a '1' or '2' and begin uploading pixel data to the host as the image
// is being read from the sensor.  When the 'ReadBlack' bit is set, the first black rows and cols
// of pixels will also be read from the sensor.
// DataFormat can be one of 8, 10, 12, 14, 16
//     8 - packed into a single byte
//     others - packed into a 16 bit word
//
int fcUsb_cmd_setReadMode(int camNum, int DataXfrReadMode, int DataFormat)
{
    UInt32 msgSize;
    fc_setReadMode_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    bool DoOffsetCorrection;
    bool ReadBlack;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_setReadMode\n");

    if (gDoSimulation)
        return 0;

    if (gCamerasFound[camNum - 1].camFinalProduct == starfish_ibis13_final_deviceID)
    {
        ReadBlack          = false;
        DoOffsetCorrection = false;

        // remember settings
        gReadBlack[camNum - 1]       = ReadBlack;       // set if the host wishes to read the black pixels
        gDataXfrReadMode[camNum - 1] = DataXfrReadMode; // the type of data transfers we will be using when taking picts
        gDataFormat[camNum - 1]      = DataFormat;      // the desired camera data format

        myParameters.header  = 'fc';
        myParameters.command = fcSETREADMODE;
        myParameters.length  = sizeof(myParameters);

        if (ReadBlack)
            myParameters.ReadBlack = -1;
        else
            myParameters.ReadBlack = 0;

        myParameters.DataXfrReadMode = DataXfrReadMode;
        myParameters.DataFormat      = DataFormat;

        if (DoOffsetCorrection)
            myParameters.AutoOffsetCorrection = -1;
        else
            myParameters.AutoOffsetCorrection = 0;
    }
    else
    {
        // the following two parameters are now managed automatically
        if (gCurrentIntegrationTime[camNum - 1] <= 2000)
            ReadBlack =
                false; //Note: The readblack function for the Fishcamp Starfish is disabled because it doesn't currently seem to work properly.  See the note above.
        else
            ReadBlack = false;

        DoOffsetCorrection = true;

        // remember settings
        gReadBlack[camNum - 1]       = ReadBlack;       // set if the host wishes to read the black pixels
        gDataXfrReadMode[camNum - 1] = DataXfrReadMode; // the type of data transfers we will be using when taking picts
        gDataFormat[camNum - 1]      = DataFormat;      // the desired camera data format

        myParameters.header  = 'fc';
        myParameters.command = fcSETREADMODE;
        myParameters.length  = sizeof(myParameters);

        if (ReadBlack)
            myParameters.ReadBlack = -1;
        else
            myParameters.ReadBlack = 0;

        myParameters.DataXfrReadMode = DataXfrReadMode;
        myParameters.DataFormat      = DataFormat;

        if (DoOffsetCorrection)
            myParameters.AutoOffsetCorrection = -1;
        else
            myParameters.AutoOffsetCorrection = 0;
    }

    // send the constructed command
    myParameters.cksum = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    return 0;
}

// set some register defaults for the starfish camera
void fcUsb_setStarfishDefaultRegs(int camNum)
{
    UInt16 regValue;

    Starfish_Log("fcUsb_setStarfishDefaultRegs\n");

    if (gDoSimulation)
        return;

    // Turn off auto black level compensation in the micron image sensor
    regValue = fcUsb_cmd_getRegister(camNum, 0x5F);
    regValue = regValue | 0x8080;
    fcUsb_cmd_setRegister(camNum, 0x5F, regValue);
    regValue = fcUsb_cmd_getRegister(camNum, 0x62);
    regValue = regValue | 0x0001;
    fcUsb_cmd_setRegister(camNum, 0x62, regValue);

    // setup some minimum offset in the 4 bayer channels.
    // Don't want to be too close to all zeros for pixel data
    fcUsb_cmd_setRegister(camNum, 0x60, 0x08);
    fcUsb_cmd_setRegister(camNum, 0x61, 0x08);
    fcUsb_cmd_setRegister(camNum, 0x63, 0x08);
    fcUsb_cmd_setRegister(camNum, 0x64, 0x08);
}

// here to set the filter type used for image processing
// The specified filter will be performed on any images
// transferred from the camera.
//
// 'theImageFilter' is one of fc_imageFilter
//
void fcUsb_cmd_setImageFilter(int camNum, int theImageFilter)
{
    gCameraImageFilter[camNum - 1] = theImageFilter;
}

// call to get the black level pedestal of the image sensor.  This is usefull if you wish
// to subtract out the pedestal from the image returned from the camera.
//
// the return value will be scalled according to the currently set fc_dataFormat. See the
// fcUsb_cmd_setReadMode routine for information on the pixel format
//
UInt16 fcUsb_cmd_getBlackPedestal(int camNum)
{
    UInt32 msgSize;
    fc_no_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    fc_blackPedestal myPedestalInfo;
    
    UInt16 retValue;
    int maxBytes;

    Starfish_Log("fcUsb_cmd_getBlackPedestal\n");

    if (gDoSimulation)
        return 0;

    myParameters.header  = 'fc';
    myParameters.command = fcGETPEDESTAL;
    myParameters.length  = sizeof(myParameters);
    myParameters.cksum   = fcUsb_GetUsbCmdCksum(&myParameters.header);

    msgSize = sizeof(myParameters);
    SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

    // get the ACK to the command
    maxBytes     = 512;
    numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

    // copy the Rx buffer to my local storage
    memcpy(&myPedestalInfo, &gBuffer, sizeof(myPedestalInfo));
    retValue = myPedestalInfo.dataValue;

    // put the current power in the log file
    Starfish_LogFmt("     Got pedestal - 0x%04x\n", retValue);
    

    return retValue;
}

// call this routine to set a camera property.  Various cameras support different properties
// the following properties are defined:
//
//	1) fcPROP_NUMSAMPLES - enter with 'propertyValue' set to the number desired.  Valid
//                         numbers are binary numbers starting with 4 (4, 8, 16, 32, 64, 128)
//
//  2) fcPROP_PATTERNENABLE - enter with 'propertyValue' set to '1' to enable the frame grabber
//                         pattern generator.  '0' to get true sensor data
//
//  3) fcPROP_PIXCAPTURE - enter with 'propertyValue' set to '0' for normal CDS pixel sampling,
//                         '1' for pixel level only and '2' for reset level only.
//
//	4) fcPROP_SHUTTERMODE - enter with 'propertyValue' set to '0' for AUTOMATIC shutter,
//                         '1' for MANUAL shutter.
//
//  5) fcPROP_SHUTTERPRIORITY - enter with 'propertyValue' set to '0' for MECHANICAL shutter priority,
//                         '1' for ELECTRONIC shutter priority
//
//  6) fcPROP_MOVESHUTTERFLAG = enter with 'propertyValue' set to '0' to CLOSE the mechanical shutter,
//                         '1' to OPEN the mechanical shutter
//
//  7) fcPROP_COLNORM - enter with 'propertyValue' set to '0' for no column level normalization
//                         '1' for column level normalization.
//
//  8) fcPROP_SERVOOPENPOS - enter with 'propertyValue' set to servo pulse period used for the OPEN position
//
//  9) fcPROP_SERVOCLOSEDPOS - enter with 'propertyValue' set to servo pulse period used for the CLOSED position
//
//
void fcUsb_cmd_setCameraProperty(int camNum, int propertyType, int propertyValue)
{
    UInt32 msgSize;
    fc_setProperty_param myParameters;
    UInt32 numBytesRead;
    INDI_UNUSED(numBytesRead);
    int maxBytes;

    // print out the information
    //	printf("fcUsb_cmd_setCameraProperty:\n");
    //	printf("     propertyType  - 0x%04x\n", propertyType);
    //	printf("     propertyValue - 0x%04x\n", propertyValue);

    Starfish_Log("fcUsb_cmd_setCameraProperty\n");

    if (gDoSimulation)
        return;

    if (propertyType == fcPROP_COLNORM)
    {
        // this property is handled by the PC driver
        if (propertyValue == 0)
            gProWantColNormalization = false;
        else
            gProWantColNormalization = true;
    }
    else
    {
        // this one needs to be sent to the camera
        myParameters.header        = 'fc';
        myParameters.command       = fcSETPROPERTY;
        myParameters.length        = sizeof(myParameters);
        myParameters.propertyType  = propertyType;
        myParameters.propertyValue = (UInt16)propertyValue;
        myParameters.cksum         = fcUsb_GetUsbCmdCksum(&myParameters.header);

        msgSize = sizeof(myParameters);
        SendUSB(camNum, (unsigned char *)&myParameters, (int)msgSize);

        // get the ACK to the command
        maxBytes     = 512;
        numBytesRead = RcvUSB(camNum, (unsigned char *)&gBuffer, maxBytes);

        // if the property was to change the number of samples, we need to re-calibrate the camera.
        //	if (propertyType == fcPROP_NUMSAMPLES)
        //	{
        //  	fcImage_PRO_calibrateProCamera(camNum, gFrameBuffer, 2304, 2305);
        //	}
    }
}
