% Copyright 2014(c) Analog Devices, Inc.
%
%  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.
%      - Neither the name of Analog Devices, Inc. nor the names of its
%        contributors may be used to endorse or promote products derived
%        from this software without specific prior written permission.
%      - The use of this software may or may not infringe the patent rights
%        of one or more patent holders.  This license does not release you
%        from the requirement that you obtain separate licenses from these
%        patent holders to use this software.
%      - Use of the software either in source or binary form or filter designs
%        resulting from the use of this software, must be connected to, run
%        on or loaded to an Analog Devices Inc. component.
%
%  THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
%  INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A
%  PARTICULAR PURPOSE ARE DISCLAIMED.
%
%  IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
%  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, INTELLECTUAL PROPERTY
%  RIGHTS, 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.

classdef libiio_if < handle
    % libiio_if Interface object for for IIO devices

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %% Protected properties
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    properties (Access = protected)
        libname         = 'libiio';
        hname           = 'iio-wrapper.h';
        dev_name        = '';
        data_ch_no      = 0;
        data_ch_size    = 0;
        dev_type        = '';
        iio_ctx         = {};
        iio_dev         = {};
        iio_buffer      = {};
        iio_channel     = {};
        iio_buf_size    = 8192;
        iio_scan_elm_no = 0;
        if_initialized  = 0;
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %% Static private methods
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods (Static, Access = private)
        function out = modInstanceCnt(val)
            % Manages the number of object instances to handle proper DLL unloading
            persistent instance_cnt;
            if isempty(instance_cnt)
                instance_cnt = 0;
            end
            instance_cnt = instance_cnt + val;
            out = instance_cnt;
        end
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %% Protected methods
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods (Access = protected)
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Creates the network context
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function [ret, err_msg, msg_log] = createNetworkContext(obj, ip_address)
            % Initialize the return values
            ret = -1;
            err_msg = '';
            msg_log = [];

            % Create the network context
            obj.iio_ctx = calllib(obj.libname, 'iio_create_network_context', ip_address);

            % Check if the network context is valid
            if (isNull(obj.iio_ctx))
                obj.iio_ctx = {};
                err_msg = 'Could not connect to the IIO server!';
                return;
            end

            % Increase the object's instance count
            libiio_if.modInstanceCnt(1);
            msg_log = [msg_log sprintf('%s: Connected to IP %s\n', class(obj), ip_address)];

            % Set the return code to success
            ret = 0;
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Releases the network context and unload the libiio library
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function releaseContext(obj)
            calllib(obj.libname, 'iio_context_destroy', obj.iio_ctx);
            obj.iio_ctx = {};
            instCnt = libiio_if.modInstanceCnt(-1);
            if(instCnt == 0)
                unloadlibrary(obj.libname);
            end
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Checks the compatibility of the different software modules.
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function [ret, err_msg, msg_log] = checkVersions(obj)
            % Initialize the return values
            ret = -1;
            err_msg = '';
            msg_log = [];

            % Create a set of pointers to read the iiod and dll versions
            data = zeros(1, 10);
            remote_pMajor = libpointer('uint32Ptr', data(1));
            remote_pMinor = libpointer('uint32Ptr', data(2));
            remote_pGitTag = libpointer('int8Ptr', [int8(data(3:end)) 0]);
            local_pMajor = libpointer('uint32Ptr', data(1));
            local_pMinor = libpointer('uint32Ptr', data(2));
            local_pGitTag = libpointer('int8Ptr', [int8(data(3:end)) 0]);

            % get remote libiio version
            calllib(obj.libname, 'iio_context_get_version', obj.iio_ctx, remote_pMajor, remote_pMinor, remote_pGitTag);
            % extract git hash without trailing null char
            remote_githash = remote_pGitTag.Value(1:7);
            remote_version_str = sprintf('Remote libiio version: %d.%d, (git-%s)', remote_pMajor.Value, remote_pMinor.Value, remote_githash);
            msg_log = [msg_log sprintf('%s: %s\n', class(obj), remote_version_str)];

            % get local libiio version
            calllib(obj.libname, 'iio_library_get_version', local_pMajor, local_pMinor, local_pGitTag);
            local_githash = local_pGitTag.Value(1:7);
            local_version_str = sprintf('Local libiio version: %d.%d, (git-%s)', local_pMajor.Value, local_pMinor.Value, local_githash);
            msg_log = [msg_log sprintf('%s: %s\n', class(obj), local_version_str)];

            if(remote_pMajor.Value < local_pMajor.Value)
                err_msg = ['The libiio version running on the device is outdated! ' ...
                    'Run the adi_update_tools.sh script to get libiio up to date.'];
                return;
            elseif(remote_pMajor.Value > local_pMajor.Value)
                err_msg = ['The libiio version on the local host is outdated! ' ...
                    'On Windows, reinstall the dll using the latest installer ' ...
                    'from the Analog Devices wiki.'];
                return;
            end

            % Set the return code to success
            ret = 0;
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Detect if the specified device is present in the system
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function [ret, err_msg, msg_log] = initDevice(obj, dev_name)
            % Initialize the return values
            ret = -1;
            err_msg = '';
            msg_log = [];

            % Store the device name
            obj.dev_name = dev_name;

            % Get the number of devices
            nb_devices = calllib(obj.libname, 'iio_context_get_devices_count', obj.iio_ctx);

            % If no devices are present return with error
            if(nb_devices == 0)
                err_msg = 'No devices were detected in the system!';
                return;
            end
            msg_log = [msg_log sprintf('%s: Found %d devices in the system\n', class(obj), nb_devices)];

            % Detect if the targeted device is installed
            dev_found = 0;
            for i = 0 : nb_devices - 1
                dev = calllib(obj.libname, 'iio_context_get_device', obj.iio_ctx, i);
                name = calllib(obj.libname, 'iio_device_get_name', dev);
                if(strcmp(name, dev_name))
                    obj.iio_dev = dev;
                    dev_found = 1;
                    break;
                end
                clear dev;
            end

            % Check if the target device was detected
            if(dev_found == 0)
                err_msg = 'Could not find target configuration device!';
                return;
            end

            msg_log = [msg_log sprintf('%s: %s was found in the system\n', class(obj), obj.dev_name)];

            % Set the return code to success
            ret = 0;
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Initializes the output data channels
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function [ret, err_msg, msg_log] = initOutputDataChannels(obj, ch_no, ch_size)
            % Initialize the return values
            ret = -1;
            err_msg = '';
            msg_log = [];

            % Save the number of channels and size
            obj.data_ch_no = ch_no;
            obj.data_ch_size = ch_size;

            % Get the number of channels that the device has
            nb_channels = calllib(obj.libname, 'iio_device_get_channels_count', obj.iio_dev);
            if(nb_channels == 0)
                err_msg = 'The selected device does not have any channels!';
                return;
            end

            % Enable the data channels
            if(ch_no ~= 0)
                % Check if the device has output channels. The
                % logic here assumes that a device can have
                % only input or only output channels
                obj.iio_channel{1} = calllib(obj.libname, 'iio_device_get_channel', obj.iio_dev, 0);
                is_output = calllib(obj.libname, 'iio_channel_is_output', obj.iio_channel{1});
                if(is_output == 0)
                    err_msg = 'The selected device does not have output channels!';
                    return;
                end
                % Enable all the channels
                for j = 0 : nb_channels - 1
                    obj.iio_channel{j+1} = calllib(obj.libname, 'iio_device_get_channel', obj.iio_dev, j);
                    calllib(obj.libname, 'iio_channel_enable', obj.iio_channel{j+1});
                    is_scan_element = calllib(obj.libname, 'iio_channel_is_scan_element', obj.iio_channel{j+1});
                    if(is_scan_element == 1)
                        obj.iio_scan_elm_no = obj.iio_scan_elm_no + 1;
                    end
                end
                msg_log = [msg_log sprintf('%s: Found %d output channels for the device %s\n', class(obj), obj.iio_scan_elm_no, obj.dev_name)];

                % Check if the number of channels in the device
                % is greater or equal to the system object
                % input channels
                if(obj.iio_scan_elm_no < ch_no)
                    obj.iio_channel = {};
                    err_msg = 'The selected device does not have enough output channels!';
                    return;
                end

                % Enable the DAC buffer output
                obj.if_initialized = 1;
                ret = writeAttributeString(obj, 'altvoltage0*raw', '0');
                obj.if_initialized = 0;
                if(ret < 0)
                    obj.iio_channel = {};
                    err_msg = 'Could not enable the DAC buffer output!';
                    return;
                end

                % Create the IIO buffer used to write data
                obj.iio_buf_size = obj.data_ch_size * obj.iio_scan_elm_no;
                obj.iio_buffer = calllib(obj.libname, 'iio_device_create_buffer', obj.iio_dev,...
                                         obj.data_ch_size, 1);
            end

            msg_log = [msg_log sprintf('%s: %s output data channels successfully initialized\n', class(obj), obj.dev_name)];

            % Set the return code to success
            ret = 0;
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Initializes the input data channels
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function [ret, err_msg, msg_log] = initInputDataChannels(obj, ch_no, ch_size)
            % Initialize the return values
            ret = -1;
            err_msg = '';
            msg_log = [];

            % Save the number of channels and size
            obj.data_ch_no = ch_no;
            obj.data_ch_size = ch_size;

            % Get the number of channels that the device has
            nb_channels = calllib(obj.libname, 'iio_device_get_channels_count', obj.iio_dev);
            if(nb_channels == 0)
                err_msg = 'The selected device does not have any channels!';
                return;
            end

            % Enable the system object output channels
            if(ch_no ~= 0)
                % Check if the device has input channels. The
                % logic here assumes that a device can have
                % only input or only output channels
                obj.iio_channel{1} = calllib(obj.libname, 'iio_device_get_channel', obj.iio_dev, 0);
                is_output = calllib(obj.libname, 'iio_channel_is_output', obj.iio_channel{1});
                if(is_output == 1)
                    err_msg = 'The selected device does not have input channels!';
                    return;
                end
                msg_log = [msg_log sprintf('%s: Found %d input channels for the device %s\n', class(obj), nb_channels, obj.dev_name)];

                % Check if the number of channels in the device
                % is greater or equal to the system object
                % output channels
                if(nb_channels < ch_no)
                    obj.iio_channel = {};
                    err_msg = 'The selected device does not have enough input channels!';
                    return;
                end

                % Enable the channels
                for j = 0 : ch_no - 1
                    obj.iio_channel{j+1} = calllib(obj.libname, 'iio_device_get_channel', obj.iio_dev, j);
                    calllib(obj.libname, 'iio_channel_enable', obj.iio_channel{j+1});
                end
                for j = ch_no : nb_channels - 1
                    obj.iio_channel{j+1} = calllib(obj.libname, 'iio_device_get_channel', obj.iio_dev, j);
                    calllib(obj.libname, 'iio_channel_disable', obj.iio_channel{j+1});
                end
                % Create the IIO buffer used to read data
                obj.iio_buf_size = obj.data_ch_size * obj.data_ch_no;
                obj.iio_buffer = calllib(obj.libname, 'iio_device_create_buffer', obj.iio_dev, obj.iio_buf_size, 0);
            end

            msg_log = [msg_log sprintf('%s: %s input data channels successfully initialized\n', class(obj), obj.dev_name)];

            % Set the return code to success
            ret = 0;
        end

    end


    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %% Public methods
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    methods
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Constructor
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function obj = libiio_if()
            % Constructor
            obj.if_initialized = 0;
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Destructor
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function delete(obj)
            % Release any resources used by the system object.
            if((obj.if_initialized == 1) && libisloaded(obj.libname))
                if(~isempty(obj.iio_buffer))
                    calllib(obj.libname, 'iio_buffer_destroy', obj.iio_buffer);
                end
                if(~isempty(obj.iio_ctx))
                    calllib(obj.libname, 'iio_context_destroy', obj.iio_ctx);
                end
                obj.iio_buffer = {};
                obj.iio_channel = {};
                obj.iio_dev = {};
                obj.iio_ctx = {};
                instCnt = libiio_if.modInstanceCnt(-1);
                if(instCnt == 0)
                    unloadlibrary(obj.libname);
                end
            end
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Initializes the libiio interface
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function [ret, err_msg, msg_log] = init(obj, ip_address, ...
                                                dev_name, dev_type, ...
                                                data_ch_no, data_ch_size)
            % Initialize the return values
            ret = -1;
            err_msg = '';
            msg_log = [];

            % Save the device type
            obj.dev_type = dev_type;

            % Set the initialization status to fail
            obj.if_initialized = 0;

            % Load the libiio library
            if(~libisloaded(obj.libname))
                try
                    % ignore unknown type warnings due to header parsing limitations
                    warnState = warning('off', 'MATLAB:loadlibrary:TypeNotFound');
                    cleanupObj = onCleanup(@()warning(warnState));
                    [notfound, warnings] = loadlibrary(obj.libname, obj.hname, 'addheader', 'iio.h');
                    cleanupObj = []; % restore the warning state
                catch exception
                    err_msg = exception.message;
                    return;
                end
            end

            if(~libisloaded(obj.libname))
                err_msg = 'Could not load the libiio library!';
                return;
            end

            % Create the network context
            [ret, err_msg, msg_log] = createNetworkContext(obj, ip_address);
            if(ret < 0)
                return;
            end

            % Check the software versions
            [ret, err_msg, msg_log_new] = checkVersions(obj);
            msg_log = [msg_log msg_log_new];
            if(ret < 0)
                releaseContext(obj);
                return;
            end

            % Initialize the device
            [ret, err_msg, msg_log_new] = initDevice(obj, dev_name);
            msg_log = [msg_log msg_log_new];
            if(ret < 0)
                releaseContext(obj);
                return;
            end

            % Initialize the output data channels
            if(strcmp(dev_type, 'OUT'))
                [ret, err_msg, msg_log_new] = initOutputDataChannels(obj, data_ch_no, data_ch_size);
                msg_log = [msg_log msg_log_new];
                if(ret < 0)
                    releaseContext(obj);
                    return;
                end
            end

            % Initialize the input data channels
            if(strcmp(dev_type, 'IN'))
                [ret, err_msg, msg_log_new] = initInputDataChannels(obj, data_ch_no, data_ch_size);
                msg_log = [msg_log msg_log_new];
                if(ret < 0)
                    releaseContext(obj);
                    return;
                end
            end

            % Set the initialization status to success
            obj.if_initialized = 1;
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Implement the data capture flow
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function [ret, data] = readData(obj)
            % Initialize the return values
            ret = -1;
            data = cell(1, obj.data_ch_no);
            for i = 1 : obj.data_ch_no
                data{i} = zeros(obj.data_ch_size, 1);
            end

            % Check if the interface is initialized
            if(obj.if_initialized == 0)
                return;
            end

            % Check if the device type is output
            if(~strcmp(obj.dev_type, 'IN'))
                return;
            end

            % Read the data
            calllib(obj.libname, 'iio_buffer_refill', obj.iio_buffer);
            buffer = calllib(obj.libname, 'iio_buffer_first', obj.iio_buffer, obj.iio_channel{1});
            setdatatype(buffer, 'int16Ptr', obj.iio_buf_size);
            for i = 1 : obj.data_ch_no
                data{i} = double(buffer.Value(i:obj.data_ch_no:end));
            end

            % Set the return code to success
            ret = 0;
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Implement the data transmit flow
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function ret = writeData(obj, data)
            % Initialize the return values
            ret = -1;

            % Check if the interface is initialized
            if(obj.if_initialized == 0)
                return;
            end

            % Check if the device type is input
            if(~strcmp(obj.dev_type, 'OUT'))
                return;
            end

            % Destroy the buffer
            calllib(obj.libname, 'iio_buffer_destroy', obj.iio_buffer);
            obj.iio_buffer = {};

            % Enable the DAC buffer output
            ret = writeAttributeString(obj, 'altvoltage0*raw', '0');
            if(ret < 0)
                obj.iio_channel = {};
                err_msg = 'Could not enable the DAC buffer output!';
                return;
            end

            % Create the IIO buffer used to write data
            obj.iio_buf_size = obj.data_ch_size * obj.iio_scan_elm_no;
            obj.iio_buffer = calllib(obj.libname, 'iio_device_create_buffer', obj.iio_dev,...
                                     obj.data_ch_size, 1);

            % Transmit the data
            buffer = calllib(obj.libname, 'iio_buffer_start', obj.iio_buffer);
            setdatatype(buffer, 'int16Ptr', obj.iio_buf_size);
            for i = 1 : obj.data_ch_no
                buffer.Value(i : obj.iio_scan_elm_no : obj.iio_buf_size) = int16(data{i});
            end
            for i = obj.data_ch_no + 1 : obj.iio_scan_elm_no
                buffer.Value(i : obj.iio_scan_elm_no : obj.iio_buf_size) = 0;
            end
            calllib(obj.libname, 'iio_buffer_push', obj.iio_buffer);

            % Set the return code to success
            ret = 0;
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Find an attribute based on the name. The name can contain wildcard '*' characters
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function [ret, ch, attr] = findAttribute(obj, attr_name)
            % Initialize the return values
            ret = -1;
            ch = 0;
            attr = '';

            % Check if the interface is initialized
            if(obj.if_initialized == 0)
                return;
            end

            % Check if this is a device attribute
            name = calllib(obj.libname, 'iio_device_find_attr', obj.iio_dev, attr_name);
            if(~isempty(name))
                ret = 0;
                return;
            end

            % This is a channel attribute, search for the corresponding channel
            chn_no = calllib(obj.libname, 'iio_device_get_channels_count', obj.iio_dev);
            for k = 0 : chn_no - 1
                ch = calllib(obj.libname, 'iio_device_get_channel', obj.iio_dev, k);
                attr_no = calllib(obj.libname, 'iio_channel_get_attrs_count', ch);
                attr_found = 0;
                for l = 0 : attr_no - 1
                    attr = calllib(obj.libname, 'iio_channel_get_attr', ch, l);
                    name = calllib(obj.libname, 'iio_channel_attr_get_filename', ch, attr);
                    % The attribute to find can contain wildcard '*' characters,
                    % search for all the substrings in the attribute name
                    str_find = strsplit(attr_name, '*');
                    str_find = str_find(find(~strcmp(str_find, '')));
                    has_wildcard = ~isempty(strfind(attr_name, '*'));
                    attr_found = 1;
                    for i = 1 : length(str_find)
                        if(has_wildcard == 0)
                            ret = strcmp(name, str_find{i});
                            if(ret == 0)
                                ret = [];
                            end
                        else
                            ret = strfind(name, str_find{i});
                        end
                        if(isempty(ret))
                            attr_found = 0;
                            break;
                        end
                    end
                    if(attr_found == 1)
                        break;
                    end
                    clear attr;
                end
                % Check if the attribute was found
                if(attr_found == 0)
                    clear ch;
                else
                    ret = 1;
                    break;
                end
            end
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Read an attribute as a double value
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function [ret, val] = readAttributeDouble(obj, attr_name)
            % Find the attribute
            [ret, ch, attr] = findAttribute(obj, attr_name);
            if(ret < 0)
                val = 0;
                return;
            end

            % Create a double pointer to be used for data read
            data = zeros(1, 10);
            pData = libpointer('doublePtr', data(1));

            % Read the attribute value
            if(ret > 0)
                calllib(obj.libname, 'iio_channel_attr_read_double', ch, attr, pData);
                clear ch;
                clear attr;
            else
                calllib(obj.libname, 'iio_device_attr_read_double', obj.iio_dev, attr_name, pData);
            end
            val = pData.Value;
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Read an attribute as a string value
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function [ret, val] = readAttributeString(obj, attr_name)
            % Find the attribute
            [ret, ch, attr] = findAttribute(obj, attr_name);
            if(ret < 0)
                val = '';
                return;
            end

            % Create a pointer to be used for data read
            data = char(ones(1, 512));
            pData = libpointer('stringPtr', data);

            % Read the attribute value
            if(ret > 0)
                [~, ~, ~, val] = calllib(obj.libname, 'iio_channel_attr_read', ch, attr, pData, 512);
                clear ch;
                clear attr;
            else
                [~, ~, ~, val] = calllib(obj.libname, 'iio_device_attr_read', obj.iio_dev, attr_name, pData, 512);
            end
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Write a string double value
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function ret = writeAttributeDouble(obj, attr_name, val)
            % Find the attribute
            [ret, ch, attr] = findAttribute(obj, attr_name);
            if(ret < 0)
                return;
            end

            % Write the attribute
            if(ret > 0)
                calllib(obj.libname, 'iio_channel_attr_write_double', ch, attr, val);
                clear ch;
                clear attr;
            else
                calllib(obj.libname, 'iio_device_attr_write_double', obj.iio_dev, attr_name, val);
            end
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %% Write a string attribute value
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        function ret = writeAttributeString(obj, attr_name, str)
            % Find the attribute
            [ret, ch, attr] = findAttribute(obj, attr_name);
            if(ret < 0)
                return;
            end

            % Write the attribute
            if(ret > 0)
                calllib(obj.libname, 'iio_channel_attr_write', ch, attr, str);
                clear ch;
                clear attr;
            else
                calllib(obj.libname, 'iio_device_attr_write', obj.iio_dev, attr_name, str);
            end
        end
    end
end
