//
//  QSICamera.m
//  QSICamera
//
//  Copyright (c) 2011, 2012, Joe Shimkus
//   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.
//  	• 	The name of Joe Shimkus may not be used to endorse or promote
//        products derived from this software without specific prior written
//        permission.
//
//  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 COPYRIGHT OWNER
//  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.
//

#import "QSICameraProtected.h"

#import "QSIAllCommands.h"
#import "QSILittleEndianData.h"

#import "QSI583TestCamera.h"
#import "QSI583cTestCamera.h"

/***********************************************************************************************
************************************************************************************************
* QSICamera Constants
************************************************************************************************
***********************************************************************************************/

/***********************************************************************************************
************************************************************************************************
* QSICamera Support Methods
************************************************************************************************
***********************************************************************************************/

static
int
compareUint16(const void *aFirstValue, const void *aSecondValue)
{
  int result;

  uint16_t  firstValue  = *((uint16_t *) aFirstValue);
  uint16_t  secondValue = *((uint16_t *) aSecondValue);

  if (firstValue == secondValue)
  {
    result = 0;
  }
  else if (firstValue < secondValue)
  {
    result = -1;
  }
  else
  {
    result = 1;
  }

  return (result);
} // end compareUint16()

/***********************************************************************************************
************************************************************************************************
* QSICamera Private Methods
************************************************************************************************
***********************************************************************************************/

//
// QSICamera Private Class Methods
//
@interface QSICamera (QSICameraPrivateClassMethods)
@end // @interface QSICamera (QSICameraPrivateClassMethods)

@implementation QSICamera (QSICameraPrivateClassMethods)
@end // @implementation QSICamera (QSICameraPrivateClassMethods)

//
// QSICamera Private Instance Methods
//
@interface QSICamera (QSICameraPrivateInstanceMethods)
- (QSIStatus) checkConnected;
- (QSIStatus) checkFilterPosition : (uint8_t) aPosition;
- (QSIStatus) executeCommand : (QSICommand *) aCommand;
- (QSIStatus) filter : (QSIFilter * *) aFilter
          atPosition : (uint8_t) aPosition;
@end // @interface QSICamera (QSICameraPrivateInstanceMethods)

@implementation QSICamera (QSICameraPrivateInstanceMethods)
- (QSIStatus) checkConnected
{
  QSIStatus status  = QSISuccess;

  if (! [self isConnected])
  {
    status = QSINotConnected;
  }

  return (status);
} // end -checkConnected

- (QSIStatus) checkFilterPosition : (uint8_t) aPosition
{
  QSIStatus status  = QSISuccess;

  if ((aPosition == 0) || (aPosition > _combinedDetails._details._numberOfFilters))
  {
    status = QSIInvalidParameter;
  }

  return (status);
} // end -checkFilterPosition:

- (QSIStatus) executeCommand : (QSICommand *) aCommand
{
  QSIStatus status  = QSISuccess;

  // Synchronize the execution of the command so that
  // there's only one outstanding command at a time.

  @synchronized(self)
  {
    // Execute the command.
    QSIDebugLog(@"executing %@...", [aCommand className]);

    for (uint8_t attempt = 0; attempt < [aCommand attemptLimit]; attempt ++)
    {
      status = [aCommand execute];

      if (status == QSISuccess)
      {
        break;
      }

      QSIStatusLog(status, @"%@: attempt %d of %d",
                   [aCommand className],
                   attempt + 1,
                   [aCommand attemptLimit]);

      // Purge the comms channel.
      QSIStatus status2;

      status2 = [_commsObject purge];
      if (status2 != QSISuccess)
      {
        status = status2;
        break;
      }
    }
  }

  return (status);
} // end -executeCommand:

- (QSIStatus) filter : (QSIFilter * *) aFilter
          atPosition : (uint8_t) aPosition
{
  QSIStatus status  = QSISuccess;

  status = [self checkFilterPosition:aPosition];

  if (status == QSISuccess)
  {
    *aFilter = [_filters objectAtIndex:aPosition - 1];
    if ((*aFilter) == nil)
    {
      status = QSINoFilter;
    }
  }

  return (status);
} // end -filter:atPosition:
@end // @implementation QSICamera (QSICameraPrivateInstanceMethods)


/***********************************************************************************************
************************************************************************************************
* QSICamera Infrastructure Methods
************************************************************************************************
***********************************************************************************************/

//
// QSICamera Infrastructure Class Methods
//
@implementation QSICamera (QSICameraInfrastructureClassMethods)
@end // @implementation QSICamera (QSICameraInfrastructureClassMethods)

//
// QSICamera Infrastructure Instance Methods
//
@implementation QSICamera (QSICameraInfrastructureInstanceMethods)
- (void) dealloc
{
  // Disconnect, in case we weren't disconnected before ceasing to exist.
  [self disconnect];

  // Free the objects we allocated.
  [_filters release];

  [_advancedDetails release];
  [_autoZero release];
  [_autoZeroControl release];
  [_combinedDetails release];
  
  [_exposureSettings release];

  [_firmwareVersion release];
  [_hardwareVersion release];
  [_readoutBuffer release];

  [_commsObject release];

  [_cameraDefaults release];

  [super dealloc];
} // end -dealloc

- (id) init
{
  [self doesNotRecognizeSelector:_cmd];

  return (nil);
} // end -init
@end // @implementation QSICamera (QSICameraInfrastructureInstanceMethods)

/***********************************************************************************************
************************************************************************************************
* QSICamera Overridden Methods
************************************************************************************************
***********************************************************************************************/

//
// QSICamera Overridden Class Methods
//
@implementation QSICamera (QSICameraOverriddenClassMethods)
@end // @implementation QSICamera (QSICameraOverriddenClassMethods)

//
// QSICamera Overridden Instance Methods
//
@implementation QSICamera (QSICameraOverriddenInstanceMethods)
@end // @implementation QSICamera (QSICameraOverriddenInstanceMethods)


/***********************************************************************************************
************************************************************************************************
* QSICamera Public Methods
************************************************************************************************
***********************************************************************************************/

//
// QSICamera Public Class Methods
//
@implementation QSICamera (QSICameraPublicClassMethods)
+ (Class) cameraClassFromComms : (QSIComms *) aComms
{
  QSIDebugLog(@"looking up camera class");

  Class cameraClass = nil;

  // Figure out if it's a device we know about and, if so, return the class object
  // so that the creation of the camera can proceed.

  // Get a camera.
  QSIDebugLog(@"instantiating camera");

  QSICamera *camera = [[[self alloc] initWithComms:aComms] autorelease];

  QSIStatus status  = QSISuccess;
  if (camera == nil)
  {
    QSIDebugLog(@"instantiation failed");

    status = QSIAllocationFailed;
  }
  else
  {
    QSIDebugLog(@"instantiated camera of %@ class", [camera className]);
  }

  // Connect to the camera.
  if (status == QSISuccess)
  {
    status = [camera connect];

    QSIDebugStatusLog(status, @"camera connect");
  }

  // Check that the model name begins with "QSI" and contains the correct series number.
  NSString  *modelName;

  if (status == QSISuccess)
  {
    status = [camera modelName:&modelName];
  }

  if ((status == QSISuccess) &&
      (([modelName lengthOfBytesUsingEncoding:NSASCIIStringEncoding] < 3) || (! [modelName hasPrefix:@"QSI"])))
  {
    QSILog(@"model name did not begin with QSI prefix");

    status = QSIFailure;
  }

  if ((status == QSISuccess) && (NSLocationInRange(NSNotFound, [modelName rangeOfString:[self seriesNumber]])))
  {
    QSILog(@"model name did not contain expected series number (%d)", [self seriesNumber]);

    status = QSIFailure;
  }

  // Check that the model number is at least 3 characters long.
  // If it is, we use those three characters to form the class name.
  NSString  *modelNumber;

  if (status == QSISuccess)
  {
    status = [camera modelNumber:&modelNumber];
  }

  if ((status == QSISuccess) && ([modelNumber lengthOfBytesUsingEncoding:NSASCIIStringEncoding] < 3))
  {
    QSILog(@"model number too short to be valid");

    status = QSIFailure;
  }

  // Construct the class name and look up the class.
  if (status == QSISuccess)
  {
    // Make an appropriate string in order to look up the class.
    NSMutableString *classString  = [NSMutableString stringWithString:@"QSI"];

    if (classString != nil)
    {
      uint32_t  substringIndex  = 3;

      // If the model number has characters after the number proper it could be a color camera.
      if (([modelNumber lengthOfBytesUsingEncoding:NSASCIIStringEncoding] > 3) &&
          ([modelNumber compare:@"c" options:0 range:NSMakeRange(3, 1)] == NSOrderedSame))
      {
        // A color camera.
        substringIndex = 4;
      }

      [classString appendString:[modelNumber substringToIndex:substringIndex]];
      [classString appendString:@"Camera"];

      cameraClass = [QSIObject classNamed:classString];
    }
  }

  [camera disconnect];

  return (cameraClass);
} // end +cameraClassFromComms:

+ (uint16_t) extendedReadTimeout
{
  return (20000);
} // end +extendedReadTimeout

+ (uint16_t) extendedWriteTimeout
{
  return (20000);
} // end +extendedWriteTimeout

+ (uint16_t) shortReadTimeout
{
  return (250);
} // end +shortReadTimeout

+ (uint16_t) shortWriteTimeout
{
  return (250);
} // end +shortWriteTimeout

+ (uint16_t) standardReadTimeout
{
  return (5000);
} // end +standardReadTimeout

+ (uint16_t) standardWriteTimeout
{
  return (5000);
} // end +standardWriteTimeout

+ (NSArray *) simulatedCameras
{
  NSMutableArray  *simulatedCameras = [[[NSMutableArray alloc] init] autorelease];

  if (simulatedCameras != nil)
  {
    QSICamera *camera;

    camera = [[[QSI583TestCamera alloc] initWithComms:nil] autorelease];
    if (camera != nil)
    {
      [simulatedCameras addObject:camera];
    }

    camera = [[[QSI583cTestCamera alloc] initWithComms:nil] autorelease];
    if (camera != nil)
    {
      [simulatedCameras addObject:camera];
    }
  }

  return (simulatedCameras);
} // end +simulatedCameras
@end // @implementation QSICamera (QSICameraPublicClassMethods)

//
// QSICamera Public Instance Methods
//
@implementation QSICamera (QSICameraPublicInstanceMethods)
- (id) initWithComms : (QSIComms *) aCommsObject
{
  self = [super init];
  if (self != nil)
  {
    _filters = [[NSMutableArray alloc] init];
    if (_filters == nil)
    {
      [self release];
      self = nil;
    }
  }

  if (self != nil)
  {
    _advancedDetails  = [[QSICameraAdvancedDetails alloc] init];
    _advancedSettings = [[QSICameraAdvancedSettingsParameters alloc] init];
    _autoZero         = [[QSICameraAutoZero alloc] init];
    _autoZeroControl  = [[QSICameraAutoZeroControl alloc] init];
    _combinedDetails  = [[QSICameraCombinedDetails alloc] init];
    _exposureSettings = [[QSICameraExposureParameters alloc] init];

    if ((_advancedDetails == nil)   ||
        (_advancedSettings == nil)  ||
        (_autoZero == nil)          ||
        (_autoZeroControl == nil)   ||
        (_combinedDetails == nil)   ||
        (_exposureSettings == nil))
    {
      [self release];
      self = nil;
    }
  }

  if (self != nil)
  {
    _cameraStateCacheInvalid      = yes;

    _colorProfiling               = QSIDefaultColorProfiling;

    _commsObject                  = [aCommsObject retain];
    _currentFilterPosition        = 1;

    _exposureHeight               = 0;
    _exposureLastDuration         = 0;
    _exposureTaken                = no;
    _exposureUseFast              = no;
    _exposureWidth                = 0;

    _firmwareVersion              = nil;
    _hardwareVersion              = nil;
    _isMainCamera                 = yes;

    _imageIsDownloading           = no;
    _imageIsValid                 = no;
    _imageDownloadPending         = no;

    _maxPixelsPerBlock            = QSIDefaultMaxPixelsPerBlock;
    _numberOfPixelsRead           = 0;

    _overscanAdjustment           = 0;
    _overscanLastMean             = 0;

    _readoutBuffer                = nil;

    _testBayerImage               = QSIDefaultTestBayerImage;
  }

  return (self);
} // end -initWithComms:
@end // @implementation QSICamera (QSICameraPublicInstanceMethods)

/***********************************************************************************************
************************************************************************************************
* QSICamera Protected Methods
************************************************************************************************
***********************************************************************************************/

//
// QSICamera Protected Class Methods
//
@implementation QSICamera (QSICameraProtectedClassMethods)
+ (bool) ccdIsKAF
{
  return (no);
}  // end +ccdIsKAF

+ (bool) colorCamera
{
  return (no);
} // end +colorCamera

+ (NSString *) seriesNumber
{
  [self doesNotRecognizeSelector:_cmd];

  return (nil);
} // end +seriesNumber
@end // @implementation QSICamera (QSICameraProtectedClassMethods)

//
// QSICamera Protected Instance Methods
//
@implementation QSICamera (QSICameraProtectedInstanceMethods)
- (QSIStatus) abortRelays
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIAbortRelays  *command;

    command = [[QSIAbortRelays alloc] initUsingComms:_commsObject];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -abortRelays:

- (QSIStatus) activateRelayX : (uint16_t) anX
                        andY : (uint16_t) aY
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIActivateRelay  *command;

    command = [[QSIActivateRelay alloc] initUsingComms:_commsObject
                                                xRelay:anX
                                             andYRelay:aY];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -activateRelayX:andY:

- (void) addFilter : (QSIFilter *) aFilter
{
  [_filters addObject:aFilter];
} // end -addFilter:

- (QSIStatus) adjustZeroByValue : (int16_t) anAdjustment
                         source : (NSMutableData *) aSource
                    destination : (NSMutableData *) aDestination
                      rowLength : (uint32_t) aRowLength
                     rowPadding : (uint32_t) aRowPadding
                  remainingRows : (uint32_t) aRemainingRows
{
  QSIStatus status  = QSISuccess;

  if (_autoZeroControl._autoZeroEnabled)
  {
    QSILittleEndianData *translator = [[QSILittleEndianData alloc] init];

    if (translator == nil)
    {
      status = QSIAllocationFailed;
    }

    if (status == QSISuccess)
    {
      int32_t negativePixelCount;
      int32_t lowPixelValue;
      int32_t saturatedPixelCount;
      int32_t pixelValue;
      int32_t adjustedPixelValue;

      saturatedPixelCount = 0;
      negativePixelCount  = 0;
      lowPixelValue       = 65535;

      uint8_t *source       = [aSource mutableBytes];
      uint8_t *destination  = [aDestination mutableBytes];

      for (; aRemainingRows > 0; aRemainingRows --)
      {
        for (uint32_t index = 0; index < (aRowLength / 2); index ++)
        {
          pixelValue = [translator get16BitsFromSource:source];
          adjustedPixelValue = pixelValue + anAdjustment;

          if (adjustedPixelValue < 0)
          {
            adjustedPixelValue = 0;
            negativePixelCount ++;
          }

          if (adjustedPixelValue < lowPixelValue)
          {
            lowPixelValue = adjustedPixelValue;
          }

          if (adjustedPixelValue > _autoZeroControl._autoZeroMaxADU)
          {
            adjustedPixelValue = _autoZeroControl._autoZeroMaxADU;
            saturatedPixelCount ++;
          }

          [translator set16Bits:destination fromValue:adjustedPixelValue];

          source += 2;
          destination += 2;
        }
      }

      destination += aRowPadding;
    }

    [translator release];
  }

  return (status);
} // end -adjustZeroByValue:source:destination:rowLength:rowPadding:remainingRows:

- (int16_t)   calculateAutoZeroAdjustment : (QSICameraAutoZero *) aCameraAutoZeroData
                           withZeroPixels : (uint16_t *) aZeroPixels
                        andReturnLastMean : (uint16_t *) aLastMean
{
  int16_t autoZeroAdjustment  = 0;

  int32_t median  = 0;
  int32_t mean    = 0;
  int32_t total   = 0;
  int32_t size    = 0;
  int32_t average = 0;

  if (_autoZeroControl._autoZeroEnabled)
  {
    // Sort in preparation for removing start/end pixels.
    size = aCameraAutoZeroData._pixelCount;

    qsort(aZeroPixels, size, sizeof(uint16_t), compareUint16);

    size -= (_autoZeroControl._autoZeroSkipStartPixels + _autoZeroControl._autoZeroSkipEndPixels);
    if (size > 0)
    {
      // Throw away the start pixels.
      if (_autoZeroControl._autoZeroSkipStartPixels != 0)
      {
        for (int32_t index = 0; index < size; index ++)
        {
          aZeroPixels[index] = aZeroPixels[index + _autoZeroControl._autoZeroSkipStartPixels];
        }
      }

      // Calculate the median.
      if ((size % 2) != 0)
      {
        median = aZeroPixels[size / 2];
      }
      else
      {
        int32_t sum;

        sum = aZeroPixels[(size / 2) - 1] + aZeroPixels[(size / 2)];

        median = sum / 2;

        if ((sum % 2) != 0)
        {
          median ++;
        }
      }

      // Calculate the mean.
      total = 0;
      for (int32_t index = 0; index < size; index ++)
      {
        total += aZeroPixels[index];
      }

      mean = total / size;

      average = (_autoZeroControl._autoZeroMedian) ? median : mean;

      *aLastMean = average;

      // If the average is above the saturation threshold, force to saturation.
      // If it's not above the saturation threshold (but is not zero), calculate
      // the normal autozero adjustment.
      // If it's zero, leave the autozero adjustment alone (it was set to zero
      // at the beginning of this method).

      if (average > _autoZeroControl._autoZeroSaturationThreshold)
      {
        autoZeroAdjustment = UINT16_MAX;
      }
      else if (average != 0)
      {
        autoZeroAdjustment = _autoZero._zeroLevel - average;
      }
    }
  }

  return (autoZeroAdjustment);
} // end -calculateAutoZeroAdjustment:withZeroPixels:andReturnLastMean:

- (QSIComms *) commsObject
{
  return (_commsObject);
} // end -commsObject

- (uint16_t) eepromAccessTimeout
{
  return (1000);
} // end -eepromAccessTimeout

- (QSIStatus) fillImageBuffer
{
  QSIStatus status  = QSISuccess;

  status = [self checkConnected];
  if ((status == QSISuccess) && ((_exposureWidth <= 0) || (_exposureHeight <= 0)))
  {
    status = QSIInvalidParameter;
  }

  int32_t totalPixelsToRead;
  int32_t pixelsPerRead;

  if (status == QSISuccess)
  {
    totalPixelsToRead = _exposureWidth * _exposureHeight;

    pixelsPerRead = (_maxPixelsPerBlock / _exposureWidth) * _exposureWidth;
    if (pixelsPerRead == 0)
    {
      pixelsPerRead = _maxPixelsPerBlock;
    }

    _imageIsDownloading = yes;

    _numberOfPixelsRead = 0;
  }

  // While we're transfering the image we can't have any other commands be processed else they'll
  // consume data intended for us.
  @synchronized(self)
  {
    if (status == QSISuccess)
    {
      status = [self transferImage];
    }

    uint16_t  *buffer = [_readoutBuffer mutableBytes];

    if (status == QSISuccess)
    {
      while (_numberOfPixelsRead < totalPixelsToRead)
      {
        if ((_numberOfPixelsRead + pixelsPerRead) > totalPixelsToRead)
        {
          pixelsPerRead = totalPixelsToRead - _numberOfPixelsRead;
        }

        // Each pixel is 2 bytes, hence the multiplication by 2.
        status = [self readImage:(buffer + _numberOfPixelsRead) numberOfBytes:(pixelsPerRead * 2)];
        if (status != QSISuccess)
        {
          break;
        }

        _numberOfPixelsRead += pixelsPerRead;
      }

      _imageIsDownloading = (status != QSISuccess);
    }

    if (status == QSISuccess)
    {
      _autoZero._pixelCount = 0;
      status = [self getAutoZero:_autoZero];
    }

    if ((status == QSISuccess) &&
        ((_autoZero._zeroEnabled) && ((0 < _autoZero._pixelCount) && (_autoZero._pixelCount <= 8192))))
    {
      status = [self readImage:_overscanPixels numberOfBytes:(_autoZero._pixelCount * 2)];

      if (status == QSISuccess)
      {
        _overscanAdjustment = [self calculateAutoZeroAdjustment:_autoZero
                                                 withZeroPixels:_overscanPixels
                                              andReturnLastMean:&_overscanLastMean];
      }
    }
  }

  _imageIsValid = (status == QSISuccess);

  return (status);
} // end -fillImageBuffer

- (QSIStatus) getAdvancedDetails : (QSICameraAdvancedDetails *) anAdvancedDetails
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIGetAdvancedDetails *command;

    command = [[QSIGetAdvancedDetails alloc] initUsingComms:_commsObject
                                         andAdvancedDetails:anAdvancedDetails];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Update the filters.
    if (status == QSISuccess)
    {
      [self removeAllFilters];

      [_filters addObjectsFromArray:[command filters]];
    }

    // Release the command.
    [command release];
  }

  // KAF-based cameras incorrectly report shutter priority.
  // Override what those cameras report.
  if ((status == QSISuccess) && ([[self class] ccdIsKAF]))
  {
    anAdvancedDetails._shutterPriorityIndex = shutterPriorityMechanical;
  }

  return (status);
} // end -getAdvancedDetails:

- (QSIStatus) getAltMode1 : (uint8_t *) anAltMode1
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIGetAltMode1  *command;

    command = [[QSIGetAltMode1 alloc] initUsingComms:_commsObject];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Return the output.
    if (status == QSISuccess)
    {
      *anAltMode1 = [command altMode1];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -getAltMode1:

- (QSIStatus) getAutoZero : (QSICameraAutoZero *) anAutoZero
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIGetAutoZero  *command;

    command = [[QSIGetAutoZero alloc] initUsingComms:_commsObject
                                  andAutoZeroResults:anAutoZero];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -getAutoZero:

- (QSIStatus) getCCDSpecs : (QSICameraCCDSpecs *) aCCDSpecs
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIGetCCDSpecs  *command;

    command = [[QSIGetCCDSpecs alloc] initUsingComms:_commsObject forResults:aCCDSpecs];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -getCCDSpecs:

- (QSIStatus) getCombinedDetails : (QSICameraCombinedDetails *) aCombinedDetails
{
  QSIStatus status  = QSISuccess;

  status = [self getCCDSpecs:aCombinedDetails._ccdSpecs];
  if (status == QSISuccess)
  {
    status = [self getDetails:aCombinedDetails._details];
  }

  return (status);
} // end -getCombinedDetails:

- (QSIStatus) getDetails : (QSICameraDetails *) aDetails
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIGetDetails *command;

    command = [[QSIGetDetails alloc] initUsingComms:_commsObject andDetailsResults:aDetails];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -getDetails:

- (QSIStatus) getDeviceState : (QSICameraDeviceState *) aDeviceState
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIGetDeviceState *command;

    command = [[QSIGetDeviceState alloc] initUsingComms:_commsObject andStateResults:aDeviceState];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -getDeviceState:

- (QSIStatus) getEEPROMData : (uint8_t *) aData
                fromAddress : (uint16_t) anAddress
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIGetEEPROM  *command;

    command = [[QSIGetEEPROM alloc] initUsingComms:_commsObject andAddress:anAddress];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Return the output.
    if (status == QSISuccess)
    {
      *aData = [command data];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -getEEPROMData:fromAddress:

- (QSIStatus) getFilterPosition : (uint8_t *) aPosition
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIGetFilterPosition  *command;

    command = [[QSIGetFilterPosition alloc] initUsingComms:_commsObject];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Return the output.
    if (status == QSISuccess)
    {
      *aPosition = [command filterPosition];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -getFilterPosition:

- (QSIStatus) getHardwareVersion : (NSString * *) aHardwareVersion
              andFirmwareVersion : (NSString * *) aFirmwareVersion
{
  QSIStatus status  = QSISuccess;

  *aHardwareVersion = nil;
  *aFirmwareVersion = nil;

  uint8_t temp[9];

  status = [_commsObject setReadTimeout:[self eepromAccessTimeout] andWriteTimeout:[self eepromAccessTimeout]];
  if (status == QSISuccess)
  {
    status = [self getEEPROMData:temp fromAddress:0x0020];
  }

  if (status == QSISuccess)
  {
    for (uint32_t index = 1; index < ((sizeof(temp) / sizeof(temp[0])) - 1); index ++)
    {
      [self getEEPROMData:(temp + index) fromAddress:(0x0018 + index)];
    }

    temp[((sizeof(temp) / sizeof(temp[0])) - 1)] = '\0';

    *aHardwareVersion = [NSString stringWithFormat:@"%s", temp];
    if ((*aHardwareVersion) == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  if (status == QSISuccess)
  {
    for (uint32_t index = 0; index < ((sizeof(temp) / sizeof(temp[0])) - 1); index ++)
    {
      [self getEEPROMData:(temp + index) fromAddress:(0x0020 + index)];
    }

    temp[((sizeof(temp) / sizeof(temp[0])) - 1)] = '\0';

    *aFirmwareVersion = [NSString stringWithFormat:@"%s", temp];
    if ((*aFirmwareVersion) == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  status = QSISuccess;

  if ((*aHardwareVersion) == nil)
  {
    *aHardwareVersion = [NSString stringWithFormat:@"00.00.00"];
    if ((*aHardwareVersion) == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  if ((status == QSISuccess) && ((*aFirmwareVersion) == nil))
  {
    *aFirmwareVersion = [NSString stringWithFormat:@"00.00.00"];
    if ((*aFirmwareVersion) == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  return (status);
} // end -getHardwareVersion:andFirmwareVersion:

- (QSIStatus) getLastExposure : (double *) aLastExposure
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIGetLastExposure  *command;

    command = [[QSIGetLastExposure alloc] initUsingComms:_commsObject];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Return the output.
    if (status == QSISuccess)
    {
      *aLastExposure = [command exposure];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -getLastExposure:

- (QSIStatus) getSetPoint : (double *) aSetPoint
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIGetSetPoint  *command;

    command = [[QSIGetSetPoint alloc] initUsingComms:_commsObject];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Return the output.
    if (status == QSISuccess)
    {
      *aSetPoint = [command setPoint];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -getSetPoint:

- (QSIStatus) getTemperature : (QSICameraTemperature *) aResults
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIGetTemperature *command;

    command = [[QSIGetTemperature alloc] initUsingComms:_commsObject andTemperatureResults:aResults];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -getTemperature:

- (QSIStatus) hasFastExposure : (bool *) aHasFastExposure;
{
  QSIStatus status  = QSISuccess;

  QSICameraExposureParameters *parameters = [[QSICameraExposureParameters alloc] init];
  
  if (parameters == nil)
  {
    status = QSIAllocationFailed;
  }
  
  if (status == QSISuccess)
  {
    parameters._probeForImplemented = yes;
    
    status = [self startExposureExtended:parameters];
  }

  [parameters release];
  
  *aHasFastExposure = (status == QSISuccess);

  status = QSISuccess;

  return (status);
} // end -hasFastExposure:

- (QSIStatus) initialize
{
  QSIStatus status  = QSISuccess;

  // Establish appropriate timeouts.
  status = [_commsObject setReadTimeout:2000 andWriteTimeout:[_commsObject standardWriteTimeout]];

  // Use a 'can stop exposure' command to synchronize with the camera as it requires
  // no additional processing.
  //
  // Don't worry if the command has errors.
  QSICommand  *command  = nil;

  if (status == QSISuccess)
  {
    command = [[QSICanStopExposure alloc] initUsingComms:_commsObject];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  if (status == QSISuccess)
  {
    [self executeCommand:command];
    [self executeCommand:command];
  }

  // Release the command.
  [command release];
  command = nil;

  // Set the timeouts to the standard values.
  if (status == QSISuccess)
  {
    status = [_commsObject setToStandardTimeouts];
  }

  // Purge the comms channel.
  // Ignore the status of this as well.
  if (status == QSISuccess)
  {
    [_commsObject purge];
  }

  // Now, initialize the camera.
  if (status == QSISuccess)
  {
    command = [[QSIInitializeCamera alloc] initUsingComms:_commsObject];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  if (status == QSISuccess)
  {
    status = [self executeCommand:command];
  }

  // Release the command.
  [command release];

  return (status);
} // end -initialize

- (QSIStatus) isRelayDone : (bool *) aIsDone
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIIsRelayDone  *command;

    command = [[QSIIsRelayDone alloc] initUsingComms:_commsObject];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Return the output.
    if (status == QSISuccess)
    {
      *aIsDone = [command relayDone];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -isRelayDone:

- (QSIStatus) readImage : (void *) aBuffer
          numberOfBytes : (uint32_t) aNumberOfBytes
{
  return ([_commsObject readToBuffer:aBuffer numberOfBytes:&aNumberOfBytes]);
} // end -readImage:numberOfBytes:

- (void) removeAllFilters
{
  [_filters removeAllObjects];
} // end -removeAllFilters

- (QSIStatus) sendAdvancedSettings : (QSICameraAdvancedSettingsParameters *) aParameters
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSISendAdvancedSettings *command;

    command = [[QSISendAdvancedSettings alloc] initUsingComms:_commsObject andSettingsParameters:aParameters];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -sendAdvancedSettings:

- (QSIStatus) setAltMode1 : (uint8_t) anAltMode1
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSISetAltMode1  *command;

    command = [[QSISetAltMode1 alloc] initUsingComms:_commsObject andAltMode1:anAltMode1];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -setAltMode1:

- (QSIStatus) setDefaults
{
  QSIStatus status  = QSISuccess;

  if (_cameraDefaults == nil)
  {
    _cameraDefaults = [[QSICameraDefaults alloc] initForCameraWithModelNumer:_combinedDetails._details._modelNumber
                                                             andSerialNumber:_combinedDetails._details._serialNumber];
    if (_cameraDefaults == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setAntiBlooming:[_cameraDefaults antiBloom]];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setCoolerOn:[_cameraDefaults cooler]];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setCCDTargetTemperature:[_cameraDefaults coolerSetPoint]];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setFanMode:[_cameraDefaults fanMode]];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setGain:[_cameraDefaults gain]];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setLEDEnabled:[_cameraDefaults ledAlert]];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setPreExposureFlush:[_cameraDefaults preExposureFlush]];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setReadoutSpeed:[_cameraDefaults readoutSpeed]];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setShutterPriority:[_cameraDefaults shutterPriority]];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setSoundEnabled:[_cameraDefaults soundAlert]];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  return (status);
} // end -setDefaults

- (QSIStatus) setFilterWheelPosition : (uint8_t) aPosition
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSISetFilterWheel *command;

    command = [[QSISetFilterWheel alloc] initUsingComms:_commsObject andFilterPosition:aPosition];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -setFilterWheelPosition:

- (QSIStatus) setShutter : (bool) aOpen
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSISetShutter *command;

    command = [[QSISetShutter alloc] initUsingComms:_commsObject andShutterOpen:aOpen];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -setShutter:

- (QSIStatus) setTemperature : (QSICameraTemperatureParameters *) aParameters
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSISetTemperature *command;

    command = [[QSISetTemperature alloc] initUsingComms:_commsObject andTemperatureParameters:aParameters];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -setTemperature:

- (QSIStatus) startExposure : (QSICameraExposureParameters *) aParameters
{
  QSIStatus status  = QSISuccess;

  // Establish the extended timeouts.
  status = [_commsObject setToExtendedTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSIStartExposure  *command;

    command = [[QSIStartExposure alloc] initUsingComms:_commsObject andExposureParameters:aParameters];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -startExposure:

- (QSIStatus) startExposureExtended : (QSICameraExposureParameters *) aParameters
{
  QSIStatus status  = QSISuccess;

  // If we're just probing set the timeouts to the short timeout, else set them to the extended timeouts.
  if (aParameters._probeForImplemented)
  {
    status = [_commsObject setToShortTimeouts];
  }
  else
  {
    status = [_commsObject setToExtendedTimeouts];
  }

  if (status == QSISuccess)
  {
    // Create the command.
    QSIStartExposureExtended  *command;

    command = [[QSIStartExposureExtended alloc] initUsingComms:_commsObject andExposureParameters:aParameters];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -startExposureExtended:

- (QSIStatus) transferImage
{
  QSIStatus status  = QSISuccess;

  // Establish the standard timeouts.
  status = [_commsObject setToStandardTimeouts];
  if (status == QSISuccess)
  {
    // Create the command.
    QSITransferImage  *command;

    command = [[QSITransferImage alloc] initUsingComms:_commsObject];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -transferImage:

- (QSIStatus) updateCCDSpecs
{
  return ([self getCCDSpecs:_combinedDetails._ccdSpecs]);
} // end -updateCCDSpecs

- (QSIStatus) setExposureAntiBlooming : (in QSICameraAntiBloom) anAntiBlooming
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if ((status == QSISuccess) && (! _advancedDetails._antiBloomingEnabled))
  {
    status = QSINotSupported;
  }

  if (status == QSISuccess)
  {
    QSICameraAntiBloom  oldValue  = _advancedSettings._antiBloomingIndex;

    _advancedSettings._antiBloomingIndex = anAntiBlooming;

    status = [self sendAdvancedSettings:_advancedSettings];
    if (status != QSISuccess)
    {
      _advancedSettings._antiBloomingIndex = oldValue;
    }

    _advancedDetails._antiBloomingIndex = _advancedSettings._antiBloomingIndex;
  }

  return (status);
} // end -setExposureAntiBlooming:

- (QSIStatus) setExposureGain : (in QSICameraGain) aGain
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if ((status == QSISuccess) && (! _advancedDetails._cameraGainEnabled))
  {
    status = QSINotSupported;
  }

  if (status == QSISuccess)
  {
    QSICameraGain oldValue  = _advancedSettings._cameraGainIndex;

    _advancedSettings._cameraGainIndex = aGain;

    status = [self sendAdvancedSettings:_advancedSettings];
    if (status != QSISuccess)
    {
      _advancedSettings._cameraGainIndex = oldValue;
    }

    _advancedDetails._cameraGainIndex = _advancedSettings._cameraGainIndex;

    // If we successfully set the gain, update the cached CCD specs
    // as the electrons/ADU will have changed.
    if (status == QSISuccess)
    {
      status = [self updateCCDSpecs];
    }
  }

  return (status);
} // end -setExposureGain:

- (QSIStatus) setExposurePreExposureFlush : (in QSICameraPreExposureFlush) aPreExposureFlush
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if ((status == QSISuccess) && (! _advancedDetails._preExposureFlushEnabled))
  {
    status = QSINotSupported;
  }

  if (status == QSISuccess)
  {
    QSICameraPreExposureFlush oldValue  = _advancedSettings._preExposureFlushIndex;

    _advancedSettings._preExposureFlushIndex = aPreExposureFlush;

    status = [self sendAdvancedSettings:_advancedSettings];
    if (status != QSISuccess)
    {
      _advancedSettings._preExposureFlushIndex = oldValue;
    }

    _advancedDetails._preExposureFlushIndex = _advancedSettings._preExposureFlushIndex;
  }

  return (status);
} // end -setExposurePreExposureFlush:

- (QSIStatus) setExposureReadoutSpeed : (in QSICameraReadoutSpeed) aReadoutSpeed
{
  QSIStatus status   = QSISuccess;

  bool  canSet  = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self canSetReadoutSpeed:&canSet];
  }

  if ((status == QSISuccess) && (! canSet))
  {
    status = QSINotSupported;
  }

  if (status == QSISuccess)
  {
    bool  oldValue  = _advancedSettings._optimizeReadoutSpeed;

    _advancedSettings._optimizeReadoutSpeed = (aReadoutSpeed == readoutSpeedFast);

    status = [self sendAdvancedSettings:_advancedSettings];
    if (status != QSISuccess)
    {
      _advancedSettings._optimizeReadoutSpeed = oldValue;
    }

    _advancedDetails._optimizeReadoutSpeed = _advancedSettings._optimizeReadoutSpeed;
  }

  return (status);
} // end -setExposureReadoutSpeed:

- (QSIStatus) setExposureShutterPriority : (in QSICameraShutterPriority) aShutterPriority
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if ((status == QSISuccess) && (! _advancedDetails._shutterPriorityEnabled))
  {
    status = QSINotSupported;
  }

  if (status == QSISuccess)
  {
    QSICameraShutterPriority  oldValue  = _advancedSettings._shutterPriorityIndex;

    _advancedSettings._shutterPriorityIndex = aShutterPriority;

    status = [self sendAdvancedSettings:_advancedSettings];
    if (status != QSISuccess)
    {
      _advancedSettings._shutterPriorityIndex = oldValue;
    }

    _advancedDetails._shutterPriorityIndex = _advancedSettings._shutterPriorityIndex;
  }

  return (status);
} // end -setExposureShutterPriority:

@end // @implementation QSICamera (QSICameraProtectedInstanceMethods)

/***********************************************************************************************
************************************************************************************************
* The QSICamera
************************************************************************************************
***********************************************************************************************/

@implementation QSICamera
//
// QSIComms protocol
//
- (QSIStatus) disconnect
{
  QSIStatus status  = QSISuccess;

  status = [_commsObject disconnect];
  if (status == QSISuccess)
  {
    [_firmwareVersion release];
    _firmwareVersion = nil;

    [_hardwareVersion release];
    _hardwareVersion = nil;

    [_readoutBuffer release];
    _readoutBuffer = nil;
  }

  return (status);
} // end -disconnect

- (bool) isConnected
{
  return ([_commsObject isConnected]);
} // end -isConnected

//
// QSICamera protocol
//

- (QSIStatus) abortExposure
{
  QSIStatus status   = QSISuccess;

  bool  supported = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    // See if the camera supports aborting an exposure.
    status = [self canAbortExposure:&supported];
  }

  if ((status == QSISuccess) && (! supported))
  {
    status = QSINotSupported;
  }

  if (status == QSISuccess)
  {
    // Establish the standard timeouts.
    status = [_commsObject setToStandardTimeouts];
  }

  if (status == QSISuccess)
  {
    // Create the command.
    QSIAbortExposure  *command;

    command = [[QSIAbortExposure alloc] initUsingComms:_commsObject];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      _imageDownloadPending = no;
      _imageIsValid         = no;

      status = [self executeCommand:command];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -abortExposure

- (QSIStatus) advancedDetails : (out QSICameraAdvancedDetails * *) anAdvancedDetails
{
  QSIStatus status  = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *anAdvancedDetails = [[_advancedDetails copy] autorelease];
    if ((*anAdvancedDetails) == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  return (status);
} // end -advancedDetails:

- (QSIStatus) advancedSettings : (out QSICameraAdvancedSettingsParameters * *) anAdvancedSettings
{
  QSIStatus status  = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *anAdvancedSettings = [[_advancedSettings copy] autorelease];
    if ((*anAdvancedSettings) == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  return (status);
} // end -advancedSettings:

- (QSIStatus) autoZeroControl : (out QSICameraAutoZeroControl * *) anAutoZeroControl
{
  QSIStatus status   = QSISuccess;

  *anAutoZeroControl = [[_autoZeroControl copy] autorelease];
  if ((*anAutoZeroControl) == nil)
  {
    status = QSIAllocationFailed;
  }

  return (status);
} // end -autoZeroControl:

- (QSIStatus) cameraState : (out QSICameraState *) aCameraState
{
  QSIStatus status   = QSISuccess;

  QSICameraDeviceState  *deviceState  = nil;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    deviceState = [[QSICameraDeviceState alloc] init];
    if (deviceState == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  if (status == QSISuccess)
  {
    status = [self getDeviceState:deviceState];
  }

  if (status == QSISuccess)
  {
    switch (deviceState._cameraState)
    {
      case deviceStateDownloadingImage:
        *aCameraState = stateDownloading;
        break;

      case deviceStateError:
        *aCameraState = stateError;
        break;

      case deviceStateExposing:
        *aCameraState = stateExposing;
        break;

      case deviceStateFilterWheelMoving:
      case deviceStateFlushing:
      case deviceStateWaitingOnExternalTrigger:
        *aCameraState = stateWaiting;
        break;

      case deviceStateIdle:
        *aCameraState = stateIdle;
        break;

      case deviceStateReading:
        *aCameraState = stateReading;
        break;

      default:
        *aCameraState = stateIdle;
        break;
    }
  }

  [deviceState release];

  return (status);
} // end -cameraState:

- (QSIStatus) canAbortExposure : (out bool *) aCanAbortExposure
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    // Establish the standard timeouts.
    status = [_commsObject setToStandardTimeouts];
  }

  if (status == QSISuccess)
  {
    // Create the command.
    QSICanAbortExposure *command;

    command = [[QSICanAbortExposure alloc] initUsingComms:_commsObject];
    if (command == nil)
    {
      status = QSIAllocationFailed;
    }

    // Execute the command.
    if (status == QSISuccess)
    {
      status = [self executeCommand:command];
    }

    // Return the output.
    if (status == QSISuccess)
    {
      *aCanAbortExposure = [command canAbortExposure];
    }

    // Release the command.
    [command release];
  }

  return (status);
} // end -canAbortExposure:

- (QSIStatus) canGetCoolerPower : (out bool *) aCanGetCoolerPower
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aCanGetCoolerPower = yes;
  }

  return (status);
} // end -canGetCoolerPower:

- (QSIStatus) canPulseGuide : (out bool *) aCanPulseGuide
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aCanPulseGuide = _combinedDetails._details._hasRelays;
  }

  return (status);
} // end -canPulseGuide:

- (QSIStatus) canSetReadoutSpeed : (out bool *) aCanSetReadoutSpeed
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aCanSetReadoutSpeed = _advancedDetails._optimizationsEnabled;
  }

  return (status);
} // end -canSetReadoutSpeed:

- (QSIStatus) canSetTemperature : (out bool *) aCanSetTemperature
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aCanSetTemperature = _combinedDetails._details._hasTemperatureRegulator;
  }

  return (status);
} // end -canSetTemperature:

- (QSIStatus) ccdHeight : (out uint16_t *) aHeight
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aHeight = _combinedDetails._details._arrayRows;
  }

  return (status);
} // end -ccdHeight:

- (QSIStatus) ccdTargetTemperature : (out double *) aTargetTemperature
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self getSetPoint:aTargetTemperature];
  }

  return (status);
} // end -ccdTargetTemperature:

- (QSIStatus) ccdTemperature : (out double *) aCCDTemperature
{
  QSIStatus status   = QSISuccess;

  QSICameraTemperature  *temperature  = nil;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    temperature = [[QSICameraTemperature alloc] init];
    if (temperature == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  if (status == QSISuccess)
  {
    status = [self getTemperature:temperature];
  }

  if (status == QSISuccess)
  {
    *aCCDTemperature = temperature._coolerTemperature;
  }

  [temperature release];

  return (status);
} // end -ccdTemperature:

- (QSIStatus) ccdWidth : (out uint16_t *) aWidth
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aWidth = _combinedDetails._details._arrayColumns;
  }

  return (status);
} // end -ccdWidth:

- (bool) colorCamera
{
  return ([[self class] colorCamera]);
} // end -colorCamera

- (QSIStatus) columnBinning : (out uint16_t *) aBinning
{
  QSIStatus status   = QSISuccess;

  *aBinning = _exposureSettings._columnBinning;

  return (status);
} // end -columnBinning:

- (QSIStatus) combinedDetails : (out QSICameraCombinedDetails * *) aCombinedDetails
{
  QSIStatus status  = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aCombinedDetails = [[_combinedDetails copy] autorelease];
    if ((*aCombinedDetails) == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  return (status);
} // end -combinedDetails:

- (QSIStatus) connect
{
  QSIStatus status  = QSISuccess;

  if (! [self isConnected])
  {
    status = [_commsObject connect];
    if (status == QSISuccess)
    {
      status = [self initialize];

      QSICameraAdvancedDetails            *advancedDetails;
      QSICameraCombinedDetails            *combinedDetails;
      QSICameraExposureParameters         *exposureSettings;
      QSICameraAdvancedSettingsParameters *advancedSettings;

      advancedDetails   = _advancedDetails;
      combinedDetails   = _combinedDetails;
      exposureSettings  = _exposureSettings;
      advancedSettings  = _advancedSettings;

      if (status == QSISuccess)
      {
        status = [self getCombinedDetails:combinedDetails];
      }

      if (status == QSISuccess)
      {
        exposureSettings._width          = combinedDetails._details._arrayColumns;
        exposureSettings._height         = combinedDetails._details._arrayRows;
        exposureSettings._columnBinning  = 1;
        exposureSettings._rowBinning     = 1;
      }

      if (status == QSISuccess)
      {
        status = [self getAdvancedDetails:advancedDetails];
      }

      if (status == QSISuccess)
      {
        advancedSettings._antiBloomingIndex      = advancedDetails._antiBloomingIndex;
        advancedSettings._cameraGainIndex        = advancedDetails._cameraGainIndex;
        advancedSettings._fanModeIndex           = advancedDetails._fanModeIndex;
        advancedSettings._ledIndicatorOn         = advancedDetails._ledIndicatorDefault;
        advancedSettings._optimizeReadoutSpeed   = advancedDetails._optimizeReadoutSpeed;
        advancedSettings._preExposureFlushIndex  = advancedDetails._preExposureFlushIndex;
        advancedSettings._showDownloadProgress   = advancedDetails._showDownloadProgressDefault;
        advancedSettings._shutterPriorityIndex   = advancedDetails._shutterPriorityIndex;
        advancedSettings._soundOn                = advancedDetails._soundOnDefault;
      }

      if (status == QSISuccess)
      {
        status = [self sendAdvancedSettings:advancedSettings];
      }

      if (status == QSISuccess)
      {
        NSMutableData *newReadoutBuffer;

        newReadoutBuffer = [[NSMutableData alloc] initWithCapacity:((combinedDetails._details._arrayColumns *
                                                                     combinedDetails._details._arrayRows) *
                                                                    sizeof(uint16_t))];
        if (newReadoutBuffer != nil)
        {
          [_readoutBuffer release];
          _readoutBuffer = newReadoutBuffer;
        }

        if (newReadoutBuffer == nil)
        {
          status = QSINoMemory;
        }
      }

      if ((status == QSISuccess) && (combinedDetails._details._hasFilterWheel))
      {
        status = [self setFilterPosition:1];
      }

      if (status == QSISuccess)
      {
        NSString  *newHardwareVersion;
        NSString  *newFirmwareVersion;

        status = [self getHardwareVersion:&newHardwareVersion andFirmwareVersion:&newFirmwareVersion];
        if (status == QSISuccess)
        {
          [_hardwareVersion release];
          _hardwareVersion = [newHardwareVersion retain];

          [_firmwareVersion release];
          _firmwareVersion = [newFirmwareVersion retain];
        }
      }

      if (status == QSISuccess)
      {
        // Get whether to use fast exposure.  If this fails we default to not using it.
        if ([self hasFastExposure:&_exposureUseFast] != QSISuccess)
        {
          _exposureUseFast = no;
        }
      }

      if (status == QSISuccess)
      {
        status = [self setDefaults];
      }

      if (status != QSISuccess)
      {
        QSIStatus status2;

        status2 = [self disconnect];
        if (status2 != QSISuccess)
        {
          status = status2;
        }
      }
    }
  }

  return (status);
} // end -connect

- (QSIStatus) coolerPower : (out double *) aCoolerPower
{
  QSIStatus status   = QSISuccess;

  bool  supported = no;
  bool  coolerOn;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self canGetCoolerPower:&supported];
  }

  if ((status == QSISuccess) && (! supported))
  {
    status = QSINotSupported;
  }

  if (status == QSISuccess)
  {
    status = [self isCoolerOn:&coolerOn];
  }

  QSICameraTemperature  *temperature  = nil;
  if (status == QSISuccess)
  {
    temperature = [[QSICameraTemperature alloc] init];
    if (temperature == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  if (status == QSISuccess)
  {
    if (! coolerOn)
    {
      temperature._coolerPower = 0;
    }
    else
    {
      status = [self getTemperature:temperature];
    }
  }

  if (status == QSISuccess)
  {
    *aCoolerPower = temperature._coolerPower;
  }

  [temperature release];

  return (status);
} // end -coolerPower:

- (QSIStatus) electronsPerADU : (out double *) anElectronsPerADU
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    if ((_advancedDetails._cameraGainEnabled) && (_advancedDetails._cameraGainIndex == gainLow))
    {
      *anElectronsPerADU = _combinedDetails._ccdSpecs._electronsPerADULow;
    }
    else
    {
      *anElectronsPerADU = _combinedDetails._ccdSpecs._electronsPerADUHigh;
    }
  }

  return (status);
} // end -electronsPerADU:

- (QSIStatus) exposeForDuration : (in uint32_t) aDuration
                withShutterOpen : (in bool) aShutterOpen
{
  QSIStatus status   = QSISuccess;

  QSICameraCCDSpecs           *ccdSpecs;
  QSICameraDetails            *details;
  QSICameraExposureParameters *exposure;

  ccdSpecs  = _combinedDetails._ccdSpecs;
  details   = _combinedDetails._details;
  exposure  = _exposureSettings;

  status = [self checkConnected];
  if ((status == QSISuccess) &&
      (((exposure._startingColumn + exposure._width) * exposure._columnBinning) > details._arrayColumns))
  {
    QSILog(@"exposure width exceeds allowed maximum");

    status = QSIInvalidParameter;
  }

  if ((status == QSISuccess) &&
      (((exposure._startingRow + exposure._height) * exposure._rowBinning) > details._arrayRows))
  {
    QSILog(@"exposure height exceeds allowed maximum");

    status = QSIInvalidParameter;
  }

  if ((status == QSISuccess) &&
      ((exposure._columnBinning > details._maxHorizontalBinning) || (exposure._rowBinning > details._maxVerticalBinning)))
  {
    QSILog(@"horizontal/vertical binning exceeds allowed maximum");

    status = QSIInvalidParameter;
  }

  if ((status == QSISuccess) && (exposure._columnBinning != exposure._rowBinning) && (! details._supportsAsymmetricBinning))
  {
    QSILog(@"asymmetrical binning not supported");

    status = QSIInvalidParameter;
  }

  if ((status == QSISuccess) && (aDuration != 0) &&
      (((ccdSpecs._minExposure * 1000) > aDuration) || (aDuration > (ccdSpecs._maxExposure * 1000))))
  {
    QSILog(@"exposure duration outside acceptable range and not zero");

    status = QSIInvalidParameter;
  }

  if (status == QSISuccess)
  {
    _exposureHeight = exposure._height;
    _exposureWidth  = exposure._width;

    exposure._openShutter = aShutterOpen;

    // For ASCOM compliance, if there is no shutter the shutten open parameter is ignored.
    if (! details._hasShutter)
    {
      exposure._openShutter = yes;
    }

    exposure._useExternalTrigger   = no;
    exposure._strobeShutterOutput  = no;
    exposure._repeatCount          = 0;
    exposure._probeForImplemented  = no;

    _exposureLastDuration = aDuration;

    if (_exposureUseFast)
    {
      exposure._duration     = aDuration / 10;         // 10 ms units
      exposure._durationUsec = (aDuration % 10) * 10;  // 100 usec units

      status = [self startExposureExtended:exposure];
    }
    else
    {
      exposure._duration = aDuration / 10;             // 10 ms units
      if ((aDuration % 10) >= 5)
      {
        exposure._duration ++;
      }

      exposure._durationUsec = 0;

      status = [self startExposure:exposure];
    }
  }

  if (status == QSISuccess)
  {
    _imageDownloadPending = yes;
    _exposureTaken        = yes;
    _imageIsValid         = no;
  }

  return (status);
} // end -exposeForDuration:withShutterOpen:

- (QSIStatus) exposeUsingRequest : (in QSICameraExposureRequest *) anExposureRequest
{
  QSIStatus status  = QSISuccess;

  // QSINotSupported is tolerated.

  // Use the internal 'exposure' methods to avoid setting the automatic camera defaults.
  status = [self setExposureAntiBlooming:anExposureRequest._antiBlooming];
  if (status == QSINotSupported)
  {
    status = QSISuccess;
  }

  if (status == QSISuccess)
  {
    status = [self setExposureGain:anExposureRequest._cameraGain];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setExposurePreExposureFlush:anExposureRequest._preExposureFlush];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setExposureReadoutSpeed:anExposureRequest._readoutSpeed];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setExposureShutterPriority:anExposureRequest._shutterPriority];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setColumnBinning:anExposureRequest._columnBinning];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setExposureHeight:anExposureRequest._height];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setRowBinning:anExposureRequest._rowBinning];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setExposureStartingColumn:anExposureRequest._startingColumn];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setExposureStartingRow:anExposureRequest._startingRow];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    status = [self setExposureWidth:anExposureRequest._width];
    if (status == QSINotSupported)
    {
      status = QSISuccess;
    }
  }

  if (status == QSISuccess)
  {
    QSICameraDeviceState  *deviceState  = [[QSICameraDeviceState alloc] init];
    if (deviceState == nil)
    {
      status = QSIAllocationFailed;
    }

    if (status == QSISuccess)
    {
      status = [self setFilterPosition:anExposureRequest._filterPosition];
      if (status == QSISuccess)
      {
        // Wait for the filter to finish moving.

        do
        {
          status = [self getDeviceState:deviceState];
        } while ((status == QSISuccess) && (deviceState._cameraState == deviceStateFilterWheelMoving));
      }
      else if ((status == QSINotSupported) || (status == QSINoFilter))
      {
        status = QSISuccess;
      }
    }

    [deviceState release];
  }

  // If everything got set ok, time to do the exposure.
  if (status == QSISuccess)
  {
    status = [self exposeForDuration:anExposureRequest._duration
                     withShutterOpen:anExposureRequest._openShutter];
  }

  return (status);
} // end -exposeUsingRequest:

- (QSIStatus) exposureHeight : (out uint16_t *) aHeight
{
  QSIStatus status   = QSISuccess;

  *aHeight = _exposureSettings._height;

  return (status);
} // end -exposureHeight:

- (QSIStatus) exposureLastDuration : (out uint32_t *) aDuration
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if ((status == QSISuccess) && (! _exposureTaken))
  {
    status = QSINoExposure;
  }

  if (status == QSISuccess)
  {
    *aDuration = _exposureLastDuration;
  }

  return (status);
} // end -exposureLastDuration:

- (QSIStatus) exposureSettings : (out QSICameraExposureParameters * *) anExposureSettings
{
  QSIStatus status   = QSISuccess;

  *anExposureSettings = [[_exposureSettings copy] autorelease];
  if ((*anExposureSettings) == nil)
  {
    status = QSIAllocationFailed;
  }

  return (status);
} // end -exposureSettings:

- (QSIStatus) exposureStartingColumn : (out uint16_t *) aColumn
{
  QSIStatus status   = QSISuccess;

  *aColumn = _exposureSettings._startingColumn;

  return (status);
} // end -exposureStartingColumn:

- (QSIStatus) exposureStartingRow : (out uint16_t *) aRow
{
  QSIStatus status   = QSISuccess;

  *aRow = _exposureSettings._startingRow;

  return (status);
} // end -exposureStartingRow:

- (QSIStatus) exposureWidth : (out uint16_t *) aWidth
{
  QSIStatus status   = QSISuccess;

  *aWidth = _exposureSettings._width;

  return (status);
} // end -exposureWidth:

- (QSIStatus) fanMode : (out QSICameraFanMode *) aFanMode
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aFanMode = _advancedSettings._fanModeIndex;
  }

  return (status);
} // end -fanMode:

- (QSIStatus) filterCount : (out uint8_t *) aFilterCount
{
  QSIStatus status  = QSISuccess;

  bool  hasFilters  = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self hasFilterWheel:&hasFilters];
  }

  if (status == QSISuccess)
  {
    if (hasFilters)
    {
      *aFilterCount = _combinedDetails._details._numberOfFilters;
    }
    else
    {
      *aFilterCount = 0;
    }
  }

  return (status);
} // end -filterCount:

- (QSIStatus) filterPosition : (out uint8_t *) aFilterPosition
{
  QSIStatus status   = QSISuccess;

  bool  hasFilters  = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self hasFilterWheel:&hasFilters];
  }

  if ((status == QSISuccess) && (! hasFilters))
  {
    status = QSINoFilter;
  }

  QSICameraDeviceState  *deviceState  = nil;
  if (status == QSISuccess)
  {
    deviceState = [[QSICameraDeviceState alloc] init];
    if (deviceState == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  if (status == QSISuccess)
  {
    status = [self getDeviceState:deviceState];
  }

  if (status == QSISuccess)
  {
    if (deviceState._filterState)
    {
      // The filter wheel is moving.
      *aFilterPosition = -1;
    }
    else
    {
      status = [self getFilterPosition:aFilterPosition];
    }
  }

  [deviceState release];

  return (status);
} // end -filterPosition:

- (QSIStatus) filterWheelConnected : (out bool *) aFilterWheelConnected
{
  QSIStatus status   = QSISuccess;

  *aFilterWheelConnected = ([self isConnected]) && (_combinedDetails._details._hasFilterWheel);

  return (status);
} // end -filterWheelConnected:

- (QSIStatus) focusOffset : (out int32_t *) aFocusOffset
                forFilter : (in uint8_t) aFilterPosition
{
  QSIStatus status   = QSISuccess;

  bool  hasFilters  = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self hasFilterWheel:&hasFilters];
  }

  if ((status == QSISuccess) && (! hasFilters))
  {
    status = QSINoFilter;
  }

  if (status == QSISuccess)
  {
    QSIFilter *filter;

    status = [self filter:&filter atPosition:aFilterPosition];
    if (status == QSISuccess)
    {
      *aFocusOffset = [filter focusOffset];
    }
  }

  return (status);
} // end -focusOffset:forFilter:

- (QSIStatus) fullWellCapacity : (out double *) aFullWellCapacity
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aFullWellCapacity = _combinedDetails._ccdSpecs._fullWell;
  }

  return (status);
} // end -fullWellCapacity:

- (QSIStatus) getName : (out NSString * *) aName
            forFilter : (in uint8_t) aFilterPosition
{
  QSIStatus status   = QSISuccess;

  bool  hasFilters  = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self hasFilterWheel:&hasFilters];
  }

  if ((status == QSISuccess) && (! hasFilters))
  {
    status = QSINoFilter;
  }

  if (status == QSISuccess)
  {
    QSIFilter *filter;

    status = [self filter:&filter atPosition:aFilterPosition];
    if (status == QSISuccess)
    {
      *aName = [filter name];
    }
  }

  return (status);
} // end -getName:forFilter:

- (QSIStatus) hasFilterWheel : (out bool *) aHasFilterWheel
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aHasFilterWheel = _combinedDetails._details._hasFilterWheel;
  }

  return (status);
} // end -hasFilterWheel:

- (QSIStatus) hasShutter : (out bool *) aHasShutter
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aHasShutter = _combinedDetails._details._hasShutter;
  }

  return (status);
} // end -hasShutter:

- (QSIStatus) heatsinkTemperature : (out double *) aHeatsinkTemperature
{
  QSIStatus status   = QSISuccess;

  QSICameraTemperature  *temperature  = nil;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    temperature = [[QSICameraTemperature alloc] init];
    if (temperature == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  if (status == QSISuccess)
  {
    status = [self getTemperature:temperature];
  }

  if (status == QSISuccess)
  {
    *aHeatsinkTemperature = temperature._ambientTemperature;
  }

  [temperature release];

  return (status);
} // end -heatsinkTemperature:

- (QSIStatus) imageArray : (inout NSMutableData * *) anImageArray
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if ((status == QSISuccess) && (! _imageIsValid))
  {
    status = QSINoImage;
  }

  if (status == QSISuccess)
  {
    if (_autoZeroControl._autoZeroEnabled)
    {
      status = [self adjustZeroByValue:_overscanAdjustment
                                source:_readoutBuffer
                           destination:*anImageArray
                             rowLength:(_exposureWidth * 2)
                            rowPadding:0
                         remainingRows:_exposureHeight];
    }
    else
    {
      memcpy([*anImageArray mutableBytes], [_readoutBuffer bytes], (_exposureWidth * 2) * _exposureHeight);
    }
  }

  return (status);
} // end -imageArray:

- (QSIStatus) imageArrayWidth : (out uint16_t *) aWidth
                       height : (out uint16_t *) aHeight
               andElementSize : (out uint16_t *) anElementSize
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if ((status == QSISuccess) && (! _imageIsValid))
  {
    status = QSINoImage;
  }

  if (status == QSISuccess)
  {
    *aWidth         = _exposureWidth;
    *aHeight        = _exposureHeight;
    *anElementSize  = 2;
  }

  return (status);
} // end -imageArrayWidth:height:andElementSize:

- (QSIStatus) imageIsReady : (out bool *) anImageIsReady
{
  QSIStatus status   = QSISuccess;

  // Assume an image is ready; if this changes, we're done.
  *anImageIsReady = yes;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *anImageIsReady = _exposureTaken;
  }

  if ((status == QSISuccess) && (*anImageIsReady))
  {
    QSICameraDeviceState  *deviceState  = [[QSICameraDeviceState alloc] init];

    if (deviceState == nil)
    {
      status = QSIAllocationFailed;
    }

    if (status == QSISuccess)
    {
      status = [self getDeviceState:deviceState];
    }

    if (status == QSISuccess)
    {
      *anImageIsReady = (deviceState._cameraState == deviceStateIdle);
    }

    [deviceState release];

    if ((status == QSISuccess) && (_imageDownloadPending) && (*anImageIsReady))
    {
      _imageDownloadPending = no;

      status = [self fillImageBuffer];
    }
  }

  return (status);
} // end -imageIsReady:

- (QSIStatus) isCoolerOn : (out bool *) aIsCoolerOn
{
  QSIStatus status   = QSISuccess;

  QSICameraTemperature  *temperature  = nil;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    temperature = [[QSICameraTemperature alloc] init];
    if (temperature == nil)
    {
      status = QSIAllocationFailed;
    }
  }

  if (status == QSISuccess)
  {
    status = [self getTemperature:temperature];
  }

  if (status == QSISuccess)
  {
    *aIsCoolerOn = (temperature._coolerState != coolerOff);
  }

  [temperature release];

  return (status);
} // end -isCoolerOn:

- (QSIStatus) isLEDEnabled : (out bool *) anLEDEnabled
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *anLEDEnabled = _advancedSettings._ledIndicatorOn;
  }

  return (status);
} // end -isLEDEnabled:

- (QSIStatus) isMainCamera : (out bool *) aMainCamera
{
  QSIStatus status   = QSISuccess;

  *aMainCamera = _isMainCamera;

  return (status);
} // end -isMainCamera:

- (QSIStatus) isPulseGuiding : (out bool *) anIsPulseGuiding
{
  QSIStatus status   = QSISuccess;

  *anIsPulseGuiding = no;

  bool  canGuide  = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self canPulseGuide:&canGuide];
  }

  if ((status == QSISuccess) && (canGuide))
  {
    bool  isDone;

    status = [self isRelayDone:&isDone];
    if (status == QSISuccess)
    {
      *anIsPulseGuiding = (! isDone);
    }
  }

  return (status);
} // end -isPulseGuiding:

- (QSIStatus) isSoundEnabled : (out bool *) aSoundEnabled
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aSoundEnabled = _advancedSettings._soundOn;
  }

  return (status);
} // end -isSoundEnabled:

- (QSIStatus) maxADU : (out uint32_t *) aMaxADU
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aMaxADU = _combinedDetails._ccdSpecs._maxADU;
  }

  return (status);
} // end -maxADU:

- (QSIStatus) maxColumnBinning : (out uint16_t *) aBinning
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aBinning = _combinedDetails._details._maxHorizontalBinning;
  }

  return (status);
} // end -maxColumnBinning:

- (QSIStatus) maxPixelsPerBlock : (out int32_t *) aMaxPixelsPerBlock
{
  QSIStatus status  = QSISuccess;

  *aMaxPixelsPerBlock = _maxPixelsPerBlock;

  return (status);
} // end -maxPixelsPerBlock:

- (QSIStatus) maxRowBinning : (out uint16_t *) aBinning
{
  QSIStatus status   = QSISuccess;

  bool  supportsAsymmetricBinning;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self supportsAsymmetricBinning:&supportsAsymmetricBinning];
  }

  if (status == QSISuccess)
  {
    if (supportsAsymmetricBinning)
    {
      *aBinning = _combinedDetails._details._maxVerticalBinning;
    }
    else
    {
      *aBinning = _combinedDetails._details._maxHorizontalBinning;
    }
  }

  return (status);
} // end -maxRowBinning:

- (QSIStatus) modelName : (out NSString * *) aModelName
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aModelName = _combinedDetails._details._modelName;
  }

  return (status);
} // end -modelName:

- (QSIStatus) modelNumber : (out NSString * *) aModelNumber
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aModelNumber = _combinedDetails._details._modelNumber;
  }

  return (status);
} // end -modelNumber:

- (QSIStatus) pixelHeight : (out double *) aHeight
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aHeight = _combinedDetails._details._pixelHeight / 100.0;
  }

  return (status);
} // end -pixelHeight:

- (QSIStatus) pixelWidth : (out double *) aWidth
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aWidth = _combinedDetails._details._pixelWidth / 100.0;
  }

  return (status);
} // end -pixelWidth:

- (QSIStatus) powerOfTwoBinning : (out bool *) aPowerOfTwoBinning
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aPowerOfTwoBinning = _combinedDetails._details._powerOfTwoBinning;
  }

  return (status);
} // end -powerOfTwoBinning:

- (QSIStatus) pulseGuide : (in QSICameraGuiding) aGuideDirection
            withDuration : (in uint16_t) aDuration
{
  QSIStatus status   = QSISuccess;

  bool  canGuide  = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self canPulseGuide:&canGuide];
  }

  if ((status == QSISuccess) && (! canGuide))
  {
    status = QSINotSupported;
  }

  if (status == QSISuccess)
  {
    // Convert milliseconds to 10ms units.
    aDuration /= 10;

    uint16_t  xDuration = 0;
    uint16_t  yDuration = 0;

    switch (aGuideDirection)
    {
      case guidingNorth:
        yDuration = aDuration;
        break;

      case guidingSouth:
        yDuration = -aDuration;
        break;

      case guidingEast:
        xDuration = aDuration;
        break;

      case guidingWest:
        xDuration = -aDuration;
        break;

      default:
        // Must be abort.
        break;
    }

    bool  relayDone;
    status = [self isRelayDone:&relayDone];
    if ((status == QSISuccess) && ((! relayDone) || ((xDuration == 0) && (yDuration == 0))))
    {
      status = [self abortRelays];
    }

    if (status == QSISuccess)
    {
      status = [self activateRelayX:xDuration andY:yDuration];
    }
  }

  return (status);
} // end -pulseGuide:withDuration:

- (QSIStatus) readoutSpeed : (out QSICameraReadoutSpeed *) aReadoutSpeed
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aReadoutSpeed = (_advancedDetails._optimizeReadoutSpeed) ? readoutSpeedFast : readoutSpeedHighQuality;
  }

  return (status);
} // end -readoutSpeed:

- (QSIStatus) rowBinning : (out uint16_t *) aBinning
{
  QSIStatus status   = QSISuccess;

  *aBinning = _exposureSettings._rowBinning;

  return (status);
} // end -rowBinning:

- (QSIStatus) serialNumber : (out NSString * *) aSerialNumber
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aSerialNumber = _combinedDetails._details._serialNumber;
  }

  return (status);
} // end -serialNumber:

- (QSIStatus) setAntiBlooming : (in QSICameraAntiBloom) anAntiBlooming
{
  QSIStatus status   = QSISuccess;

  status = [self setExposureAntiBlooming:anAntiBlooming];

  if (status == QSISuccess)
  {
    [_cameraDefaults setAntiBloom:anAntiBlooming];
  }

  return (status);
} // end -setAntiBlooming:

- (QSIStatus) setCCDTargetTemperature : (in double) aTargetTemperature
{
  QSIStatus status   = QSISuccess;

  bool  canSet  = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self canSetTemperature:&canSet];
  }

  if ((status == QSISuccess) && (! canSet))
  {
    status = QSINotSupported;
  }

  if ((status == QSISuccess) && ((aTargetTemperature <= -100) || (aTargetTemperature >= 100)))
  {
    status = QSIInvalidParameter;
  }

  bool  coolerOn;
  if (status == QSISuccess)
  {
    status = [self isCoolerOn:&coolerOn];
  }

  QSICameraTemperatureParameters  *temperature  = nil;
  
  if (status == QSISuccess)
  {
    temperature = [[QSICameraTemperatureParameters alloc] init];
    if (temperature == nil)
    {
      status = QSIAllocationFailed;
    }
  }
  
  if (status == QSISuccess)
  {
    temperature._coolerOn     = coolerOn;
    temperature._goToAmbient  = no;
    temperature._setPoint     = aTargetTemperature;
    
    status = [self setTemperature:temperature];
  }
  
  [temperature release];

  if (status == QSISuccess)
  {
    [_cameraDefaults setCoolerSetPoint:aTargetTemperature];
  }

  return (status);
} // end -setCCDTargetTemperature:

- (QSIStatus) setColumnBinning : (in uint16_t) aBinning
{
  QSIStatus status   = QSISuccess;

  if ((0 < aBinning) && (aBinning <= _combinedDetails._details._maxHorizontalBinning))
  {
    _exposureSettings._columnBinning = aBinning;
  }
  else
  {
    status = QSIInvalidParameter;
  }

  return (status);
} // end -setColumnBinning:

- (QSIStatus) setCoolerOn : (in bool) aCoolerOn
{
  QSIStatus status   = QSISuccess;

  double  setPoint;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self getSetPoint:&setPoint];
  }

  QSICameraTemperatureParameters  *temperature  = nil;

  if (status == QSISuccess)
  {
    temperature = [[QSICameraTemperatureParameters alloc] init];
    if (temperature == nil)
    {
      status = QSIAllocationFailed;
    }
  }
  
  if (status == QSISuccess)
  {
    temperature._coolerOn     = aCoolerOn;
    temperature._goToAmbient  = no;
    temperature._setPoint     = setPoint;

    status = [self setTemperature:temperature];
  }
  
  [temperature release];

  if (status == QSISuccess)
  {
    [_cameraDefaults setCooler:aCoolerOn];
  }

  return (status);
} // end -setCoolerOn:

- (QSIStatus) setExposureHeight : (in uint16_t) aHeight
{
  QSIStatus status   = QSISuccess;

  _exposureSettings._height = aHeight;

  return (status);
} // end -setExposureHeight:

- (QSIStatus) setExposureStartingColumn : (in uint16_t) aColumn
{
  QSIStatus status   = QSISuccess;

  _exposureSettings._startingColumn = aColumn;

  return (status);
} // end -setExposureStartingColumn

- (QSIStatus) setExposureStartingRow : (in uint16_t) aRow
{
  QSIStatus status   = QSISuccess;

  _exposureSettings._startingRow = aRow;

  return (status);
} // end -setExposureStartingRow

- (QSIStatus) setExposureWidth : (in uint16_t) aWidth
{
  QSIStatus status   = QSISuccess;

  _exposureSettings._width = aWidth;

  return (status);
} // end -setExposureWidth:

- (QSIStatus) setFanMode : (in QSICameraFanMode) aFanMode
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if ((status == QSISuccess) && (! _advancedDetails._fanModeEnabled))
  {
    status = QSINotSupported;
  }

  if (status == QSISuccess)
  {
    QSICameraFanMode  oldMode = _advancedSettings._fanModeIndex;

    _advancedSettings._fanModeIndex = aFanMode;

    status = [self sendAdvancedSettings:_advancedSettings];
    if (status != QSISuccess)
    {
      _advancedSettings._fanModeIndex = oldMode;
    }

    _advancedDetails._fanModeIndex = _advancedSettings._fanModeIndex;
  }

  if (status == QSISuccess)
  {
    [_cameraDefaults setFanMode:aFanMode];
  }

  return (status);
} // end -setFanMode:

- (QSIStatus) setFilterPosition : (in uint8_t) aFilterPosition
{
  QSIStatus status   = QSISuccess;

  bool  hasFilters  = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self hasFilterWheel:&hasFilters];
  }

  if ((status == QSISuccess) && (! hasFilters))
  {
    status = QSINoFilter;
  }

  if (status == QSISuccess)
  {
    status = [self checkFilterPosition:aFilterPosition];
  }

  if (status == QSISuccess)
  {
    status = [self setFilterWheelPosition:aFilterPosition];
  }

  if (status == QSISuccess)
  {
    _currentFilterPosition = aFilterPosition;
  }

  return (status);
} // end -setFilterPosition:

- (QSIStatus) setFocusOffset : (in int32_t) aFocusOffset
                   forFilter : (in uint8_t) aFilterPosition
{
  QSIStatus status   = QSISuccess;

  bool  hasFilters  = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self hasFilterWheel:&hasFilters];
  }

  if ((status == QSISuccess) && (! hasFilters))
  {
    status = QSINoFilter;
  }

  if (status == QSISuccess)
  {
    QSIFilter *filter;

    status = [self filter:&filter atPosition:aFilterPosition];
    if (status == QSISuccess)
    {
      [filter setFocusOffset:aFocusOffset];
    }
  }

  return (status);
} // end -setFocusOffset:forFilter:

- (QSIStatus) setGain : (in QSICameraGain) aGain
{
  QSIStatus status   = QSISuccess;

  status = [self setExposureGain:aGain];

  if (status == QSISuccess)
  {
    [_cameraDefaults setGain:aGain];
  }

  return (status);
} // end -setGain:

- (QSIStatus) setLEDEnabled : (in bool) anLEDEnabled
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if ((status == QSISuccess) && (! _advancedDetails._ledIndicatorEnabled))
  {
    status = QSINotSupported;
  }

  if (status == QSISuccess)
  {
    bool  oldValue  = _advancedSettings._ledIndicatorOn;

    _advancedSettings._ledIndicatorOn = anLEDEnabled;

    status = [self sendAdvancedSettings:_advancedSettings];
    if (status != QSISuccess)
    {
      _advancedSettings._ledIndicatorOn = oldValue;
    }

    _advancedDetails._ledIndicatorDefault = _advancedSettings._ledIndicatorOn;
  }

  if (status == QSISuccess)
  {
    [_cameraDefaults setLEDAlert:anLEDEnabled];
  }

  return (status);
} // end -setLEDEnabled:

- (QSIStatus) setMainCamera : (in bool) aMainCamera
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = QSIConnected;
  }
  else if (status == QSINotConnected)
  {
    _isMainCamera = aMainCamera;

    status = QSISuccess;
  }

  return (status);
} // end -setMainCamera:

- (QSIStatus) setMaxPixelsPerBlock : (in int32_t) aMaxPixelsPerBlock
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    if (aMaxPixelsPerBlock > (_combinedDetails._details._arrayColumns * _combinedDetails._details._arrayRows))
    {
      aMaxPixelsPerBlock = _combinedDetails._details._arrayColumns * _combinedDetails._details._arrayRows;
    }
    else if (aMaxPixelsPerBlock < QSIMinMaxPixelsPerBlock)
    {
      aMaxPixelsPerBlock = QSIMinMaxPixelsPerBlock;
    }

    _maxPixelsPerBlock = aMaxPixelsPerBlock;
  }

  return (status);
} // end -setMaxPixelsPerBlock:

- (QSIStatus) setName : (in NSString *) aName
            forFilter : (in uint8_t) aFilterPosition
{
  QSIStatus status   = QSISuccess;

  bool  hasFilters  = no;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    status = [self hasFilterWheel:&hasFilters];
  }

  if ((status == QSISuccess) && (! hasFilters))
  {
    status = QSINoFilter;
  }

  if (status == QSISuccess)
  {
    QSIFilter *filter;

    status = [self filter:&filter atPosition:aFilterPosition];
    if (status == QSISuccess)
    {
      [filter setName:aName];
    }
  }

  return (status);
} // end -setName:forFilter:

- (QSIStatus) setPreExposureFlush : (in QSICameraPreExposureFlush) aPreExposureFlush
{
  QSIStatus status   = QSISuccess;

  status = [self setExposurePreExposureFlush:aPreExposureFlush];

  if (status == QSISuccess)
  {
    [_cameraDefaults setPreExposureFlush:aPreExposureFlush];
  }

  return (status);
} // end -setPreExposureFlush:

- (QSIStatus) setReadoutSpeed : (in QSICameraReadoutSpeed) aReadoutSpeed
{
  QSIStatus status   = QSISuccess;

  status = [self setExposureReadoutSpeed:aReadoutSpeed];

  if (status == QSISuccess)
  {
    [_cameraDefaults setReadoutSpeed:aReadoutSpeed];
  }

  return (status);
} // end -setReadoutSpeed:

- (QSIStatus) setRowBinning : (in uint16_t) aBinning
{
  QSIStatus status   = QSISuccess;

  if ((0 < aBinning) && (aBinning <= _combinedDetails._details._maxVerticalBinning))
  {
    _exposureSettings._rowBinning = aBinning;
  }
  else
  {
    status = QSIInvalidParameter;
  }

  return (status);
} // end -setRowBinning:

- (QSIStatus) setShutterPriority : (in QSICameraShutterPriority) aShutterPriority
{
  QSIStatus status   = QSISuccess;

  status = [self setExposureShutterPriority:aShutterPriority];

  if (status == QSISuccess)
  {
    [_cameraDefaults  setShutterPriority:aShutterPriority];
  }

  return (status);
} // end -setShutterPriority:

- (QSIStatus) setSoundEnabled : (in bool) aSoundEnabled
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if ((status == QSISuccess) && (! _advancedDetails._soundOnEnabled))
  {
    status = QSINotSupported;
  }

  if (status == QSISuccess)
  {
    bool  oldValue  = _advancedSettings._soundOn;

    _advancedSettings._soundOn = aSoundEnabled;

    status = [self sendAdvancedSettings:_advancedSettings];
    if (status != QSISuccess)
    {
      _advancedSettings._soundOn = oldValue;
    }

    _advancedDetails._soundOnDefault = _advancedSettings._soundOn;
  }

  if (status == QSISuccess)
  {
    [_cameraDefaults  setSoundAlert:aSoundEnabled];
  }

  return (status);
} // end -setSoundEnabled:

- (QSIStatus) supportsAsymmetricBinning : (out bool *) aSupportsAsymmetricBinning
{
  QSIStatus status   = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aSupportsAsymmetricBinning = _combinedDetails._details._supportsAsymmetricBinning;
  }

  return (status);
} // end -supportsAsymmetricBinning:

- (QSIStatus) temperature : (out QSICameraTemperature * *) aTemperature
{
  QSIStatus status  = QSISuccess;

  status = [self checkConnected];
  if (status == QSISuccess)
  {
    *aTemperature = [[[QSICameraTemperature alloc] init] autorelease];
    if ((*aTemperature) == nil)
    {
      status = QSIAllocationFailed;
    }
  }
  
  if (status == QSISuccess)
  {
    status = [self getTemperature:*aTemperature];
  }

  return (status);
} // end -temperature:
@end // @implementation QSICamera
