Exemple #1
0
void callbackCancelDelayed(CALLBACK *pcallback)
{
    epicsTimerId timer = (epicsTimerId)pcallback->timer;

    if (timer != 0) {
        epicsTimerCancel(timer);
    }
}
static asynStatus
disconnect(void *drvPvt, asynUser *pasynUser)
{
    ttyController_t *tty = (ttyController_t *)drvPvt;

    assert(tty);
    asynPrint(pasynUser, ASYN_TRACE_FLOW,
                                    "%s disconnect\n", tty->serialDeviceName);
    epicsTimerCancel(tty->timer);
    closeConnection(pasynUser,tty);
    return asynSuccess;
}
/*
 * Read from the serial line
 */
static asynStatus readIt(void *drvPvt, asynUser *pasynUser,
    char *data, size_t maxchars,size_t *nbytesTransfered,int *gotEom)
{
    ttyController_t *tty = (ttyController_t *)drvPvt;
    int thisRead;
    int nRead = 0;
    int timerStarted = 0;
    asynStatus status = asynSuccess;

    assert(tty);
    asynPrint(pasynUser, ASYN_TRACE_FLOW,
               "%s read.\n", tty->serialDeviceName);
    if (tty->fd < 0) {
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                                "%s disconnected:", tty->serialDeviceName);
        return asynError;
    }
    if (maxchars <= 0) {
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
            "%s maxchars %d Why <=0?",tty->serialDeviceName,(int)maxchars);
        return asynError;
    }
    if (tty->readTimeout != pasynUser->timeout) {
#ifndef vxWorks
        /*
         * Must set flags if we're transitioning
         * between blocking and non-blocking.
         */
        if ((pasynUser->timeout == 0) || (tty->readTimeout == 0)) {
            int newFlags = (pasynUser->timeout == 0) ? O_NONBLOCK : 0;
            if (fcntl(tty->fd, F_SETFL, newFlags) < 0) {
                epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                            "Can't set %s file flags: %s",
                                    tty->serialDeviceName, strerror(errno));
                closeConnection(pasynUser,tty);
                return asynError;
            }
        }
        /*
         * Set TERMIOS timeout
         */
        if (pasynUser->timeout > 0) {
            int t = (pasynUser->timeout * 10) + 1;
            if (t > 255)
                t = 255;
            tty->termios.c_cc[VMIN] = 0;
            tty->termios.c_cc[VTIME] = t;
        }
        else if (pasynUser->timeout == 0) {
            tty->termios.c_cc[VMIN] = 0;
            tty->termios.c_cc[VTIME] = 0;
        }
        else {
            tty->termios.c_cc[VMIN] = 1;
            tty->termios.c_cc[VTIME] = 0;
        }
            
        if (tcsetattr(tty->fd, TCSANOW, &tty->termios) < 0) {
            epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                              "Can't set \"%s\" c_cc[VTIME]: %s",
                                       tty->serialDeviceName, strerror(errno));
            closeConnection(pasynUser,tty);
            return asynError;
        }
#endif
        tty->readTimeout = pasynUser->timeout;
    }
    tty->timeoutFlag = 0;
    if (gotEom) *gotEom = 0;
    for (;;) {
#ifdef vxWorks
        /*
         * vxWorks has neither poll() nor termios but does have the
         * ability to cancel an operation in progress.  If the read
         * timeout is zero we have to check for characters explicitly
         * since we don't want to start a timer with 0 delay.
         */
        if (tty->readTimeout == 0) {
            int nready;
            ioctl(tty->fd, FIONREAD, (int)&nready);
            if (nready == 0) {
                tty->timeoutFlag = 1;
                break;
            }
        }
#endif
        if (!timerStarted && (tty->readTimeout > 0)) {
            epicsTimerStartDelay(tty->timer, tty->readTimeout);
            timerStarted = 1;
        }
        thisRead = read(tty->fd, data, maxchars);
        if (thisRead > 0) {
            asynPrintIO(pasynUser, ASYN_TRACEIO_DRIVER, data, thisRead,
                       "%s read %d\n", tty->serialDeviceName, thisRead);
            nRead = thisRead;
            tty->nRead += thisRead;
            break;
        }
        else {
            if ((thisRead < 0) && (errno != EWOULDBLOCK)
                               && (errno != EINTR)
                               && (errno != EAGAIN)) {
                epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                                "%s read error: %s",
                                        tty->serialDeviceName, strerror(errno));
                closeConnection(pasynUser,tty);
                status = asynError;
                break;
            }
            if (tty->readTimeout == 0)
                tty->timeoutFlag = 1;
        }
        if (tty->timeoutFlag)
            break;
    }
    if (timerStarted) epicsTimerCancel(tty->timer);
    if (tty->timeoutFlag && (status == asynSuccess))
        status = asynTimeout;
    *nbytesTransfered = nRead;
    /* If there is room add a null byte */
    if (nRead < maxchars)
        data[nRead] = 0;
    else if (gotEom)
        *gotEom = ASYN_EOM_CNT;
    asynPrint(pasynUser, ASYN_TRACE_FLOW, "%s read %lu, return %d\n",
                            tty->serialDeviceName, (unsigned long)*nbytesTransfered, status);
    return status;
}
/*
 * Write to the serial line
 */
static asynStatus writeIt(void *drvPvt, asynUser *pasynUser,
    const char *data, size_t numchars,size_t *nbytesTransfered)
{
    ttyController_t *tty = (ttyController_t *)drvPvt;
    int thisWrite;
    int nleft = numchars;
    int timerStarted = 0;
    asynStatus status = asynSuccess;

    assert(tty);
    asynPrint(pasynUser, ASYN_TRACE_FLOW,
                            "%s write.\n", tty->serialDeviceName);
    asynPrintIO(pasynUser, ASYN_TRACEIO_DRIVER, data, numchars,
                            "%s write %lu\n", tty->serialDeviceName, (unsigned long)numchars);
    if (tty->fd < 0) {
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                                "%s disconnected:", tty->serialDeviceName);
        return asynError;
    }
    if (numchars == 0) {
        *nbytesTransfered = 0;
        return asynSuccess;
    }
    if (tty->writeTimeout != pasynUser->timeout) {
#ifndef vxWorks
        /*
         * Must set flags if we're transitioning
         * between blocking and non-blocking.
         */
        if ((pasynUser->timeout == 0) || (tty->writeTimeout == 0)) {
            int newFlags = (pasynUser->timeout == 0) ? O_NONBLOCK : 0;
            if (fcntl(tty->fd, F_SETFL, newFlags) < 0) {
                epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                            "Can't set %s file flags: %s",
                                    tty->serialDeviceName, strerror(errno));
                closeConnection(pasynUser,tty);
                return asynError;
            }
        }
#endif
        tty->writeTimeout = pasynUser->timeout;
    }
    tty->timeoutFlag = 0;
    nleft = numchars;
#ifdef vxWorks
    if (tty->writeTimeout >= 0)
#else
    if (tty->writeTimeout > 0)
#endif
        {
        epicsTimerStartDelay(tty->timer, tty->writeTimeout);
        timerStarted = 1;
        }
    for (;;) {
        thisWrite = write(tty->fd, (char *)data, nleft);
        if (thisWrite > 0) {
            tty->nWritten += thisWrite;
            nleft -= thisWrite;
            if (nleft == 0)
                break;
            data += thisWrite;
        }
        if (tty->timeoutFlag || (tty->writeTimeout == 0)) {
            status = asynTimeout;
            break;
        }
        if ((thisWrite < 0) && (errno != EWOULDBLOCK)
                            && (errno != EINTR)
                            && (errno != EAGAIN)) {
            epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                                "%s write error: %s",
                                        tty->serialDeviceName, strerror(errno));
            closeConnection(pasynUser,tty);
            status = asynError;
            break;
        }
    }
    if (timerStarted) epicsTimerCancel(tty->timer);
    *nbytesTransfered = numchars - nleft;
    asynPrint(pasynUser, ASYN_TRACE_FLOW, "wrote %lu to %s, return %s\n",
                                            (unsigned long)*nbytesTransfered,
                                            tty->serialDeviceName,
                                            pasynManager->strStatus(status));
    return status;
}
/*
 * Read from the serial line
 */
static asynStatus readIt(void *drvPvt, asynUser *pasynUser,
    char *data, size_t maxchars,size_t *nbytesTransfered,int *gotEom)
{
    ttyController_t *tty = (ttyController_t *)drvPvt;
    int thisRead;
    int nRead = 0;
    int timerStarted = 0;
    COMMTIMEOUTS ctimeout;
    BOOL ret;
    DWORD error;
    asynStatus status = asynSuccess;

    assert(tty);
    asynPrint(pasynUser, ASYN_TRACE_FLOW,
               "%s read.\n", tty->serialDeviceName);
    if (tty->commHandle == INVALID_HANDLE_VALUE) {
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                                "%s disconnected:", tty->serialDeviceName);
        return asynError;
    }
    if (maxchars <= 0) {
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
            "%s maxchars %d Why <=0?",tty->serialDeviceName,(int)maxchars);
        return asynError;
    }
    if (tty->readTimeout != pasynUser->timeout) {
        if (pasynUser->timeout >= 0) {
            ctimeout.ReadIntervalTimeout          = (int)(pasynUser->timeout*1000.); 
            ctimeout.ReadTotalTimeoutMultiplier   = 1; 
            ctimeout.ReadTotalTimeoutConstant     = (int)(pasynUser->timeout*1000.); 
            ctimeout.WriteTotalTimeoutMultiplier  = 1; 
            ctimeout.WriteTotalTimeoutConstant    = (int)(pasynUser->timeout*1000.); 

            ret = SetCommTimeouts(tty->commHandle, &ctimeout);
            if (ret == 0) {
                error = GetLastError();
                epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                              "Can't set \"%s\" timeout: %f, error=%d",
                              tty->serialDeviceName, pasynUser->timeout, error);
                return asynError;
            }
            tty->readTimeout = pasynUser->timeout;
        }
    }
    tty->timeoutFlag = 0;
    if (gotEom) *gotEom = 0;
    for (;;) {
        if (!timerStarted && (tty->readTimeout > 0)) {
            epicsTimerStartDelay(tty->timer, tty->readTimeout);
            timerStarted = 1;
        }
        ret = ReadFile(
                        tty->commHandle,  // handle of file to read
                        data,             // pointer to buffer that receives data
                        1,                // number of bytes to read
                        &thisRead,        // pointer to number of bytes read
                        NULL              // pointer to structure for data
                        );
        if (ret == 0) {
            error = GetLastError();
            epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                          "%s read error: %d",
                          tty->serialDeviceName, error);
            status = asynError;
            break;
        }
        if (thisRead > 0) {
            asynPrintIO(pasynUser, ASYN_TRACEIO_DRIVER, data, thisRead,
                       "%s read %d\n", tty->serialDeviceName, thisRead);
            nRead = thisRead;
            tty->nRead += thisRead;
            break;
        }
        if (tty->timeoutFlag)
            break;
        if (tty->readTimeout == 0) /* Timeout of 0 means return immediately */
            break;
    }
    if (timerStarted) epicsTimerCancel(tty->timer);
    if (tty->timeoutFlag && (status == asynSuccess))
        status = asynTimeout;
    *nbytesTransfered = nRead;
    /* If there is room add a null byte */
    if (nRead < (int)maxchars)
        data[nRead] = 0;
    else if (gotEom)
        *gotEom = ASYN_EOM_CNT;
    asynPrint(pasynUser, ASYN_TRACE_FLOW, "%s read %d, return %d\n",
                            tty->serialDeviceName, *nbytesTransfered, status);
    return status;
}
/*
 * Write to the serial line
 */
static asynStatus writeIt(void *drvPvt, asynUser *pasynUser,
    const char *data, size_t numchars,size_t *nbytesTransfered)
{
    ttyController_t *tty = (ttyController_t *)drvPvt;
    int thisWrite;
    int nleft = (int)numchars;
    int timerStarted = 0;
    BOOL ret;
    DWORD error;
    asynStatus status = asynSuccess;

    assert(tty);
    asynPrint(pasynUser, ASYN_TRACE_FLOW,
                            "%s write.\n", tty->serialDeviceName);
    asynPrintIO(pasynUser, ASYN_TRACEIO_DRIVER, data, numchars,
                            "%s write %d\n", tty->serialDeviceName, numchars);
    if (tty->commHandle == INVALID_HANDLE_VALUE) {
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                                "%s disconnected:", tty->serialDeviceName);
        return asynError;
    }
    if (numchars == 0) {
        *nbytesTransfered = 0;
        return asynSuccess;
    }
    if (tty->writeTimeout != pasynUser->timeout) {
        tty->writeTimeout = pasynUser->timeout;
    }
    tty->timeoutFlag = 0;
    nleft = (int)numchars;
    if (tty->writeTimeout > 0)
        {
        epicsTimerStartDelay(tty->timer, tty->writeTimeout);
        timerStarted = 1;
        }
    for (;;) {
        ret = WriteFile(tty->commHandle,     // handle to file to write to
                        data,     // pointer to data to write to file
                        nleft,     // number of bytes to write
                        &thisWrite, // pointer to number of bytes written
                        NULL     // pointer to structure for overlapped I/O
                        );
        if (ret == 0) {
            error = GetLastError();
            epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
                                "%s write error: %d",
                                        tty->serialDeviceName, error);
            closeConnection(pasynUser,tty);
            status = asynError;
            break;
        }
        tty->nWritten += thisWrite;
        nleft -= thisWrite;
        if (nleft == 0)
            break;
        data += thisWrite;
        if (tty->timeoutFlag || (tty->writeTimeout == 0)) {
            status = asynTimeout;
            break;
        }
    }
    if (timerStarted) epicsTimerCancel(tty->timer);
    *nbytesTransfered = numchars - nleft;
    asynPrint(pasynUser, ASYN_TRACE_FLOW, "wrote %lu to %s, return %s\n",
                                            (unsigned long)*nbytesTransfered,
                                            tty->serialDeviceName,
                                            pasynManager->strStatus(status));
    return status;
}
Exemple #7
0
asynStatus mar345::acquireFrame()
{
    asynStatus status=asynSuccess;
    epicsTimeStamp startTime, currentTime;
    int eraseMode;
    epicsEventWaitStatus waitStatus;
    int imageCounter;
    int arrayCallbacks;
    double acquireTime;
    double timeRemaining;
    int size, res;
    int shutterMode, useShutter;
    char tempFileName[MAX_FILENAME_LEN];
    char fullFileName[MAX_FILENAME_LEN];
    //const char *functionName = "acquireframe";

    /* Get current values of some parameters */
    getDoubleParam(ADAcquireTime, &acquireTime);
    getIntegerParam(ADShutterMode, &shutterMode);
    getIntegerParam(mar345Size, &size);
    getIntegerParam(mar345Res, &res);
    getIntegerParam(NDArrayCallbacks, &arrayCallbacks);
    getIntegerParam(mar345EraseMode, &eraseMode);
    if (shutterMode == ADShutterModeNone) useShutter=0; else useShutter=1;

    epicsTimeGetCurrent(&this->acqStartTime);

    createFileName(MAX_FILENAME_LEN, tempFileName);
    /* We need to append the extension */
    epicsSnprintf(fullFileName, sizeof(fullFileName), "%s.mar%d", tempFileName, imageSizes[res][size]);

    /* Erase before exposure if set */
    if (eraseMode == mar345EraseBefore) {
        status = this->erase();
        if (status) return(status);
    }
    
    /* Set the the start time for the TimeRemaining counter */
    epicsTimeGetCurrent(&startTime);
    timeRemaining = acquireTime;
    if (useShutter) setShutter(1);

    /* Wait for the exposure time using epicsEventWaitWithTimeout, 
     * so we can abort */
    epicsTimerStartDelay(this->timerId, acquireTime);
    setIntegerParam(ADStatus, mar345StatusExpose);
    callParamCallbacks();
    while(1) {
        if (epicsEventTryWait(this->abortEventId) == epicsEventWaitOK) {
            status = asynError;
            break;
        }
        this->unlock();
        waitStatus = epicsEventWaitWithTimeout(this->stopEventId, MAR345_POLL_DELAY);
        this->lock();
        if (waitStatus == epicsEventWaitOK) {
            /* The acquisition was stopped before the time was complete */
            epicsTimerCancel(this->timerId);
            break;
        }
        epicsTimeGetCurrent(&currentTime);
        timeRemaining = acquireTime - 
            epicsTimeDiffInSeconds(&currentTime, &startTime);
        if (timeRemaining < 0.) timeRemaining = 0.;
        setDoubleParam(ADTimeRemaining, timeRemaining);
        callParamCallbacks();
    }
    setDoubleParam(ADTimeRemaining, 0.0);
    if (useShutter) setShutter(0);
    setIntegerParam(ADStatus, mar345StatusIdle);
    callParamCallbacks();
    // If the exposure was aborted return error
    if (status) return asynError;
    setIntegerParam(ADStatus, mar345StatusScan);
    callParamCallbacks();
    epicsSnprintf(this->toServer, sizeof(this->toServer), "COMMAND SCAN %s", fullFileName);
    setStringParam(NDFullFileName, fullFileName);
    callParamCallbacks();
    writeServer(this->toServer);
    status = waitForCompletion("SCAN_DATA    Ended o.k.", MAR345_COMMAND_TIMEOUT);
    if (status) {
        return asynError;
    }
    getIntegerParam(NDArrayCounter, &imageCounter);
    imageCounter++;
    setIntegerParam(NDArrayCounter, imageCounter);
    /* Call the callbacks to update any changes */
    callParamCallbacks();

    /* If arrayCallbacks is set then read the file back in */
    if (arrayCallbacks) {
        getImageData();
    }

    /* Erase after scanning if set */
    if (eraseMode == mar345EraseAfter) status = this->erase();

    return status;
}
/** This thread controls acquisition, reads SFRM files to get the image data, and
  * does the callbacks to send it to higher layers */
void BISDetector::BISTask()
{
    int status = asynSuccess;
    int imageCounter;
        int numImages, numImagesCounter;
    int imageMode;
    int acquire;
    NDArray *pImage;
    double acquireTime, timeRemaining;
    ADShutterMode_t shutterMode;
    int frameType;
    int numDarks;
    double readSFRMTimeout;
    epicsTimeStamp startTime, currentTime;
    const char *functionName = "BISTask";
    char fullFileName[MAX_FILENAME_LEN];
    char statusMessage[MAX_MESSAGE_SIZE];
    size_t dims[2];
    int itemp;
    int arrayCallbacks;
    
    this->lock();

    /* Loop forever */
    while (1) {
        /* Is acquisition active? */
        getIntegerParam(ADAcquire, &acquire);
        
        /* If we are not acquiring then wait for a semaphore that is given when acquisition is started */
        if (!acquire) {
            setStringParam(ADStatusMessage, "Waiting for acquire command");
            setIntegerParam(ADStatus, ADStatusIdle);
            callParamCallbacks();
            /* Release the lock while we wait for an event that says acquire has started, then lock again */
            this->unlock();
            asynPrint(this->pasynUserSelf, ASYN_TRACE_FLOW, 
                "%s:%s: waiting for acquire to start\n", driverName, functionName);
            status = epicsEventWait(this->startEventId);
            this->lock();
            setIntegerParam(ADNumImagesCounter, 0);
        }
        
        /* Get current values of some parameters */
        getIntegerParam(ADFrameType, &frameType);
        /* Get the exposure parameters */
        getDoubleParam(ADAcquireTime, &acquireTime);
        getIntegerParam(ADShutterMode, &itemp);  shutterMode = (ADShutterMode_t)itemp;
        getDoubleParam(BISSFRMTimeout, &readSFRMTimeout);
        
        setIntegerParam(ADStatus, ADStatusAcquire);

        /* Create the full filename */
        createFileName(sizeof(fullFileName), fullFileName);
        
        setStringParam(ADStatusMessage, "Starting exposure");
        /* Call the callbacks to update any changes */
        setStringParam(NDFullFileName, fullFileName);
        callParamCallbacks();
        switch (frameType) {
            case BISFrameNormal:
                epicsSnprintf(this->toBIS, sizeof(this->toBIS), 
                    "[Scan /Filename=%s /scantime=%f /Rescan=0]", fullFileName, acquireTime);
                break;
            case BISFrameDark:
                getIntegerParam(BISNumDarks, &numDarks);
                epicsSnprintf(this->toBIS, sizeof(this->toBIS), 
                    "[Dark /AddTime=%f /Repetitions=%d]", acquireTime, numDarks);
                break;
            case BISFrameRaw:
                epicsSnprintf(this->toBIS, sizeof(this->toBIS), 
                    "[Scan /Filename=%s /scantime=%f /Rescan=0 /DarkFlood=0]", fullFileName, acquireTime);
                break;
            case BISFrameDoubleCorrelation:
                epicsSnprintf(this->toBIS, sizeof(this->toBIS), 
                    "[Scan /Filename=%s /scantime=%f /Rescan=1]", fullFileName, acquireTime);
                break;
        }
        /* Send the acquire command to BIS */
        writeBIS(2.0);

        setStringParam(ADStatusMessage, "Waiting for Acquisition");
        callParamCallbacks();
        /* Set the the start time for the TimeRemaining counter */
        epicsTimeGetCurrent(&startTime);
        timeRemaining = acquireTime;

        /* BIS will control the shutter if we are using the hardware shutter signal.
         * If we are using the EPICS shutter then tell it to open */
        if (shutterMode == ADShutterModeEPICS) ADDriver::setShutter(1);

        /* Wait for the exposure time using epicsEventWaitWithTimeout, 
         * so we can abort. */
        epicsTimerStartDelay(this->timerId, acquireTime);
        while(1) {
            this->unlock();
            status = epicsEventWaitWithTimeout(this->stopEventId, BIS_POLL_DELAY);
            this->lock();
            if (status == epicsEventWaitOK) {
                /* The acquisition was stopped before the time was complete */
                epicsTimerCancel(this->timerId);
                break;
            }
            epicsTimeGetCurrent(&currentTime);
            timeRemaining = acquireTime -  epicsTimeDiffInSeconds(&currentTime, &startTime);
            if (timeRemaining < 0.) timeRemaining = 0.;
            setDoubleParam(ADTimeRemaining, timeRemaining);
            callParamCallbacks();
        }
        if (shutterMode == ADShutterModeEPICS) ADDriver::setShutter(0);
        setDoubleParam(ADTimeRemaining, 0.0);
        callParamCallbacks();
        this->unlock();
        status = epicsEventWaitWithTimeout(this->readoutEventId, 5.0);
        this->lock();
        /* If there was an error jump to bottom of loop */
        if (status != epicsEventWaitOK) {
            setIntegerParam(ADAcquire, 0);
            asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
                "%s:%s: error waiting for readout to complete\n",
                driverName, functionName);
            goto done;
        }
        getIntegerParam(NDArrayCallbacks, &arrayCallbacks);
        getIntegerParam(NDArrayCounter, &imageCounter);
        imageCounter++;
        setIntegerParam(NDArrayCounter, imageCounter);
        getIntegerParam(ADNumImagesCounter, &numImagesCounter);
        numImagesCounter++;
        setIntegerParam(ADNumImagesCounter, numImagesCounter);
        callParamCallbacks();

        if (arrayCallbacks && frameType != BISFrameDark) {
            /* Get an image buffer from the pool */
            getIntegerParam(ADSizeX, &itemp); dims[0] = itemp;
            getIntegerParam(ADSizeY, &itemp); dims[1] = itemp;
            pImage = this->pNDArrayPool->alloc(2, dims, NDInt32, 0, NULL);
            epicsSnprintf(statusMessage, sizeof(statusMessage), "Reading from File %s", fullFileName);
            setStringParam(ADStatusMessage, statusMessage);
            callParamCallbacks();
            status = readSFRM(fullFileName, &startTime, acquireTime + readSFRMTimeout, pImage); 
            /* If there was an error jump to bottom of loop */
            if (status) {
                setIntegerParam(ADAcquire, 0);
                pImage->release();
                goto done;
            } 

            /* Put the frame number and time stamp into the buffer */
            pImage->uniqueId = imageCounter;
            pImage->timeStamp = startTime.secPastEpoch + startTime.nsec / 1.e9;
            updateTimeStamp(&pImage->epicsTS);

            /* Get any attributes that have been defined for this driver */        
            this->getAttributes(pImage->pAttributeList);

            /* Call the NDArray callback */
            /* Must release the lock here, or we can get into a deadlock, because we can
             * block on the plugin lock, and the plugin can be calling us */
            this->unlock();
            asynPrint(this->pasynUserSelf, ASYN_TRACE_FLOW, 
                 "%s:%s: calling NDArray callback\n", driverName, functionName);
            doCallbacksGenericPointer(pImage, NDArrayData, 0);
            this->lock();
            /* Free the image buffer */
            pImage->release();
        }
        getIntegerParam(ADImageMode, &imageMode);
        if (imageMode == ADImageMultiple) {
            getIntegerParam(ADNumImages, &numImages);
            if (numImagesCounter >= numImages) setIntegerParam(ADAcquire, 0);
        }    
        if (imageMode == ADImageSingle) setIntegerParam(ADAcquire, 0);
        done:
        callParamCallbacks();
    }
}