function v=DaqAIn(daq,channel,range,UnCal)
% v=DaqAIn(DeviceIndex,channel,range,[DoNotCalibrate])
% Analog input.
% USB-1208FS:
% "v" is the measured voltage, in Volts, or NaN if no data were received.
% "DeviceIndex" is a small integer, the array index specifying which HID
%     device in the array returned by PsychHID('Devices') is interface 0
%     of the desired Daq device.
% "channel" (0 to 15) selects any of various single-ended or differential
%     measurements.
%     "channel" Measurement
%      0        0-1 (differential)
%      1        2-3 (differential)
%      2        4-5 (differential)
%      3        6-7 (differential)
%      4        1-0 (differential)
%      5        3-2 (differential)
%      6        5-4 (differential)
%      7        7-6 (differential)
%      8          0 (single-ended)
%      9          1 (single-ended)
%     10          2 (single-ended)
%     11          3 (single-ended)
%     12          4 (single-ended)
%     13          5 (single-ended)
%     14          6 (single-ended)
%     15          7 (single-ended)
% "range" (0 to 7) sets the gain (hence voltage range):
%     for single-ended measurements (channels 8-15), range is always +/- 10 V,
%     and attempts to set ranges other than 0 usually result in saturation, so
%     setting range in this function does not do anything.  For differential
%     measurements (channels 0-7), the map between range as input to this
%     function and the output generated is:
%     0 for Gain 1x (+/-20 V),   1 for Gain 2x (+/-10 V),
%     2 for Gain 4x (+/-5 V),    3 for Gain 5x (+/-4 V),
%     4 for Gain 8x (+/-2.5 V),  5 for Gain 10x (+/-2 V),
%     6 for Gain 16x (+/-1.25 V),  7 for Gain 20x (+/-1 V).
% "DoNotCalibrate" for 1208FS has no effect
%
% USB-1608FS:
% only single-ended inputs are defined, so "channel" is restricted to integers
% from 0 to 7.  Ignore the manual which claims that gains are restricted to only
% four possible values.  Thank the linux people for discovering:
%      0 for Gain 1x (+/- 10 V),      1 for Gain 2x (+/-5 V),
%      2 for Gain 4x (+/- 2.5 V),     3 for Gain 5x (+/- 2 V),
%      4 for Gain 8x (+/- 1.25 V),    5 for Gain 10x (+/- 1V),
%      6 for Gain 16x (+/- 0.625 V),  7 for Gain 32x (+/- 0.3125 V)
%
% "DoNotCalibrate" (0 or 1) if non-zero, function does not use information
% acquired from calibration measurements (see DaqCalibrateAIn).  This should
% probably always be 0 when function is called by a user (hence it defaults to
% 0).  Flag was created for the purpose of acquiring calibration data.
% Otherwise subsequent calibration measurements would be fits of fits.
%
% See also Daq, DaqFunctions, DaqPins, DaqTest, PsychHIDTest,
% DaqDeviceIndex, DaqDIn, DaqDOut, DaqAIn, DaqAOut, DaqAInScan,DaqAOutScan.
%
% 4/15/05 dgp Wrote it.
% 1/21/07 asg changed normalization value from 2^16 to 2^15 to account for 16th bit as a sign bit (not data bit)
%             and modified the "range" value help.
% 6/17/07 mk  Add proper sign handling for negative voltages.
% 12/2x/07-1/x/08  mpr   modified to work with USB-1608FS and changed some
%                           terminology; particularly did away with "sign" name
%                           conflict ("sign" is a Matlab function)
% 6/07/13 mk  Try to make it more robust: Retry on no-date received, proper
%             error handling with error abort on error instead of silent
%             failure returning NaN. Cleanup.

% Perform internal caching of list of HID devices to speedup call:
persistent devices;
if isempty(devices)
    devices = PsychHIDDAQS;
end

if nargin < 4 || isempty(UnCal)
    UnCal=0;
end

if strcmp(devices(daq).product(5:6),'16')
    Is1608=1;
    MaxChannelID = 7;
else
    Is1608=0;
    MaxChannelID = 15;
    % ignore range input for single-ended measurements
    if channel > 7
        range=0;
    end
end

% Receive and flush all stale reports from device or in receive queue:
err = PsychHID('ReceiveReports',daq);
if err.n
    error('DaqAIn ReceiveReports error 0x%s. %s: %s\n',hexstr(err.n),err.name,err.description);
end

err = PsychHID('ReceiveReportsStop',daq);
if err.n
    error('DaqAIn ReceiveReportsStop error 0x%s. %s: %s\n',hexstr(err.n),err.name,err.description);
end

[reports,err] = PsychHID('GiveMeReports',daq);
if err.n
    error('DaqAIn GiveMeReports error 0x%s. %s: %s\n',hexstr(err.n),err.name,err.description);
end

if ~ismember(channel,0:MaxChannelID)
    error('DaqAIn: "channel" must be an integer 0 to %d.',MaxChannelID);
end

if ~ismember(range,0:7)
    error('DaqAIn: "range" must be an integer 0 to 7.');
end

% Send analog measurement request to device:
err = PsychHID('SetReport',daq,2,16,uint8([16 channel range]));
if err.n
    error('DaqAIn SetReport error 0x%s. %s: %s\n',hexstr(err.n),err.name,err.description);
end

% Enable reception of data from device and retry reception until a valid
% non-empty report of 3 Bytes size arrives:
report = [];
while isempty(report) || length(report)~=3
    err = PsychHID('ReceiveReports',daq);
    if err.n
        error('DaqAIn ReceiveReports-II error 0x%s. %s: %s\n',hexstr(err.n),err.name,err.description);
    end
    [report,err] = PsychHID('GetReport',daq,1,16,3); % Get report
    if err.n
        error('DaqAIn GetReport error 0x%s. %s: %s\n',hexstr(err.n),err.name,err.description);
    end
end

% Ok, got a non-empty report of length 3 as expected. Stop reception:
err = PsychHID('ReceiveReportsStop',daq);
if err.n
    error('DaqAIn ReceiveReportsStop-II error 0x%s. %s: %s\n',hexstr(err.n),err.name,err.description);
end

if Is1608
    vmax = [10 5 2.5 2 1.25 1 0.625 0.3125];

    RawValue = double(report(3))*256 + double(report(2));
    v = vmax(range+1)*(RawValue/32768-1);
    if UnCal
        return;
    else
        DaqPrefsDir = DaqtoolboxConfigDir;
        if exist([DaqPrefsDir filesep 'DaqPrefs.mat'],'file')
            DaqVars = load([DaqPrefsDir filesep 'DaqPrefs']);
            if isfield(DaqVars,'CalData')
                CalData = DaqVars.CalData;
                GoodIndices = find(CalData(:,1) == channel & CalData(:,2) == range);
                if ~isempty(GoodIndices)
                    TheDays = CalData(GoodIndices,end);
                    ThisDay = datenum(date);
                    [DaysSinceLast,BestIndex] = min(ThisDay-TheDays);
                    AllThatDay = find(TheDays == TheDays(BestIndex));
                    MostRecentPolyFit = CalData(GoodIndices(AllThatDay(end)),3:5);

                    if DaysSinceLast > 30
                        warning('Psychtoolbox:Daq:outdatedCalibration', 'Calibration of this channel has not been performed since %s!!', ...
                            datestr(TheDays(BestIndex)));
                    end

                    v = polyval(MostRecentPolyFit,v);
                    return;
                end
            end % if isfield(DaqVars,'CalData')
        end % if exist([DaqPrefsDir filesep 'DaqPrefs.mat'],'file')
    end % if UnCal

    warning('Psychtoolbox:Daq:missingCalibration', ['It looks like this channel has not yet been calibrated. In my\n' ...
        'tests, uncalibrated values could be off by as much as 15%%!']);
else
    % Mapping table value -> voltage for differential gains:
    vmax=[20 10 5 4 2.5 2 1.25 1];

    RawReturn = double(report(2:3))*[1; 256];
    if channel < 8
        % combined two-bytes of report make a 2's complement 12-bit value
        DigitalValue = bitshift(RawReturn,-4);
        if bitget(DigitalValue,12)
            DigitalValue = -bitcmp(DigitalValue,12)-1;
        end
    else
        % range needs to be zero above during call to PsychHID('SetReport',... but
        % must be 1 to get scale below.
        range=1;
        % combined two-bytes of report make a 2's complement 11-bit value
        if RawReturn > 32752
            DigitalValue = -2048;
        elseif RawReturn > 32736
            DigitalValue = 2047;
        else
            DigitalValue = bitand(4095,bitshift(RawReturn,-3))-2048;
        end
    end

    v=vmax(range+1)*DigitalValue/2047;
end

return;