classdef bleGAPDataBlockConfig < comm.internal.ConfigBase
%bleGAPDataBlockConfig Bluetooth LE GAP data block configuration parameters
%   CFGGAP = bleGAPDataBlockConfig creates a default Bluetooth(R) low
%   energy (LE) advertising data (AD) block or scan response data (SRD)
%   block configuration object.
%
%   CFGGAP = bleGAPDataBlockConfig(Name, Value) creates a configuration
%   object, CFGGAP, for Bluetooth LE AD or SRD block with the specified
%   property Name set to the specified Value. You can specify additional
%   name-value pair arguments in any order as (Name1, Value1, ..., NameN,
%   ValueN).
%
%   bleGAPDataBlockConfig properties:
%
%   AdvertisingDataTypes    - Block data advertising data types
%   LEDiscoverability       - LE discoverable mode
%   BREDR                   - BR (Basic Rate) or EDR (Enhanced Data Rate)
%                             support
%   LE                      - Simultaneous LE and BR/EDR support
%   LocalNameShortening     - Local name shortened used
%   LocalName               - UTF-8 encoded user-friendly descriptive name
%   IdentifierType          - Type of 16-bit service or service class
%                             identifiers
%   Identifiers             - List of 16-bit service or service class
%                             identifiers
%   AdvertisingInterval     - Advertising interval
%   TxPowerLevel            - Packet transmit power level in dBm
%   ConnectionIntervalRange - Connection interval range
%
%   % Example 1:
%   % Create a bleGAPDataBlockConfig object for an advertising data block
%   % with advertising data type 'Flags' and 'Tx power level'.
%
%   cfgGAP = bleGAPDataBlockConfig;
%   cfgGAP.AdvertisingDataTypes   = {'Flags'; 'Tx power level'};
%   cfgGAP.LEDiscoverability      = 'Limited';
%   cfgGAP.TxPowerLevel           = 45
%
%   % Example 2:
%   % Create a bleGAPDataBlockConfig object for an advertising data block
%   % with advertising data types 'Advertising interval' and 'Local name'
%   % using Name-Value pairs.
%
%   cfgGAP = bleGAPDataBlockConfig('AdvertisingDataTypes', ...
%                                     {'Advertising interval', ...
%                                      'Local name'});
%   cfgGAP.AdvertisingInterval  = 48;
%   cfgGAP.LocalNameShortening  = true;
%   cfgGAP.LocalName            = 'MathWorks'
%
%   % Example 3:
%   % Create a default bleGAPDataBlockConfig object. The default
%   % configuration assigns an advertising data block with the data type
%   % set to 'Flags'. Add simultaneous support for LE and BR/EDR.
%
%   cfgGAP = bleGAPDataBlockConfig;
%   cfgGAP.LEDiscoverability      = 'Limited and general';
%   cfgGAP.BREDR                  = true;
%   cfgGAP.LE                     = 'Host'
%
%   % Example 4:
%   % Bluetooth LE GAP advertising data blocks end-to-end workflow.
%
%   % Tx side
%   % Create a bleGAPDataBlockConfig object for an advertising data block
%   % with advertising data types 'Advertising interval' and 'Local name'.
%   cfgTx = bleGAPDataBlockConfig('AdvertisingDataTypes', ...
%                                     {'Advertising interval', ...
%                                      'Local name'});
%   cfgTx.AdvertisingInterval  = 48;
%   cfgTx.LocalNameShortening  = true;
%   cfgTx.LocalName            = 'MathWorks'
%
%   % Generate an advertising data (AD) block using configuration 'cfgTx'.
%   dataBlock = bleGAPDataBlock(cfgTx);
%
%   % Rx side
%   % Decode the received advertising data block.
%   [status, cfgRx] = bleGAPDataBlockDecode(dataBlock);
%
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgRx
%   else
%       fprintf('Advertising data block decoding failed due to ::');
%       status
%   end
%
%   See also bleGAPDataBlock, bleGAPDataBlockDecode.

%   Copyright 2018-2022 The MathWorks, Inc.

%#codegen

properties
    %AdvertisingDataTypes Block data advertising data types
    %   Specify the advertising data types as a character vector, a string,
    %   or a cell array containing list of advertising data types.
    %   Acceptable advertising data types are 'Flags' | 'UUIDs' | 'Local
    %   name' | 'Tx power level' | 'Connection interval range' |
    %   'Advertising interval'. The default value is 'Flags'.
    AdvertisingDataTypes

    %LEDiscoverability LE discoverable mode
    %   Specify the LE discoverable mode as one of 'None' | 'General' |
    %   'Limited' | 'Limited and general'. It describes the LE discoverable
    %   mode of the device. Set this property to 'General' when using LE
    %   general discoverable mode. Set this property to 'Limited' when
    %   using LE limited discoverable mode. Set this property to 'Limited
    %   and general' when using LE limited and general discoverable modes.
    %   The default value is 'General'.
    LEDiscoverability

    %BREDR BR (Basic Rate) or EDR (Enhanced Data Rate) support
    %   Specify this property as a scalar logical. A true value indicates
    %   that BR/EDR is supported. This will be used when <a
    %   href="matlab:help('bleGAPDataBlockConfig.AdvertisingDataTypes')">AdvertisingDataTypes</a>
    %   contains 'Flags'. The default value is false.
    BREDR (1, 1) logical  = false

    %LE Simultaneous LE and BR/EDR support
    %   Specify the LE as one of 'None' | 'Host' | 'Controller' | 'Host and
    %   controller'. This property is applicable only when <a
    %   href="matlab:help('bleGAPDataBlockConfig.BREDR')">BREDR</a> is set
    %   to true. Set this property to 'Host' or 'Controller' to specify
    %   that LE and BR/EDR are simultaneously supported at the host only or
    %   at the controller only, respectively. Set this property to 'Host
    %   and Controller' to specify that LE and BR/EDR are simultaneously
    %   supported for both the host and the controller. The default value
    %   is 'None'.
    LE

    %LocalName UTF-8 encoded user-friendly descriptive name
    %   Specify the local name assigned to the device as a character vector
    %   or string scalar consisting of UTF-8 characters. The default value
    %   is 'Bluetooth'.
    LocalName

    %LocalNameShortening Local name shortened used
    %   Specify whether or not local name shortening is used as a scalar
    %   logical. A true value indicates that the name of the device is
    %   shortened. The default value is false.
    LocalNameShortening (1, 1) logical  = false

    %IdentifierType Type of 16-bit service or service class identifiers
    %   Specify the 16-bit identifiers type as one of 'Complete' |
    %   'Incomplete'. If the type is 'Incomplete', then the list of 16-bit
    %   service or service class identifiers (UUIDs) list is
    %   incomplete. The default value is 'Incomplete'.
    IdentifierType

    %Identifiers List of 16-bit service or service class identifiers
    %   Specify the 16-bit identifiers as an n-by-4 character array. Here,
    %   'n' must be in the range [0, 127]. Each row is represented as a
    %   4-element character vector or string scalar denoting a 2-octet
    %   (16-bit) hexadecimal value of a service or service class UUID
    %   (Universally Unique Identifier). These UUIDs are assigned by the
    %   Bluetooth SIG. The default value is '' (empty).
    Identifiers

    %AdvertisingInterval Advertising interval
    %   Specify the advertising interval as an integer in the range [32,
    %   65535]. It is the interval between the start of two consecutive
    %   advertising events. Incremental units are 0.625 steps, so that the
    %   resultant range for [32, 65535] is from 20 ms to 40.959375 s. The
    %   default value is 32.
    AdvertisingInterval = 32

    %TxPowerLevel Packet transmit power level in dBm
    %   Packet transmit power level in dBm, specified as an integer in the
    %   range [-127, 127]. The transmit power level is used to calculate
    %   the pathloss as "pathloss = Tx Power Level - RSSI". The default
    %   value is 0 (dBm).
    TxPowerLevel = 0

    %ConnectionIntervalRange Connection interval range
    %   Connection interval range, specified as a 2-element numeric vector
    %   [MIN, MAX]. MIN specifies the minimum value for the connection
    %   interval, and MAX specifies the maximum value for the connection
    %   interval. MIN must be less than or equal to MAX. MIN and MAX must
    %   be in the range [6,3200]. Incremental units are 1.25 ms steps, so
    %   that the resultant range for [6, 3200] is from 7.5 ms to 4.0 s. The
    %   default value is [6 3200].
    ConnectionIntervalRange = [6 3200]
end

properties(Constant, Hidden)
    AdvertisingDataTypes_Values = {...
        'Flags'; ...
        'UUIDs'; ...
        'Local name';...
        'Tx power level'; ...
        'Connection interval range'; ...
        'Advertising interval'}

    IdentifierType_Values = {...
        'Complete'; ...
        'Incomplete'}

    LEDiscoverability_Values = {...
        'None'; ...
        'General'; ...
        'Limited'; ...
        'Limited and general'}

    LE_Values = {...
        'None'; ...
        'Host'; ...
        'Controller'; ...
        'Host and controller'}
end

properties(Hidden)
    %HideDisplay Hide the properties of GAP data block configuration object
    %   This is a hidden property used by <a href="matlab:help('bleGAPDataBlockDecode')">bleGAPDataBlockDecode</a> function to
    %   disable the display of all the properties when GAP data block
    %   decoding is failed.
    HideDisplay (1, 1) logical = false
end

methods
    function obj = bleGAPDataBlockConfig(varargin)
        % For codegen initialize variable sized properties in constructor
        obj@comm.internal.ConfigBase('AdvertisingDataTypes', 'Flags', ...
            'LocalName', 'Bluetooth', 'Identifiers', blanks(0), 'IdentifierType', ...
            'Complete', 'LEDiscoverability', 'General', 'LE', ...
            'None', varargin{:});
    end

    function obj = set.AdvertisingDataTypes(obj, value)
        if iscell(value)
            valTemp = coder.nullcopy(cell(1,numel(value)));
            for i = 1:numel(value)
                valTemp{i} = validatestring(value{i}, ...
                    obj.AdvertisingDataTypes_Values, mfilename, ...
                    'AdvertisingDataTypes');
            end
            % Unique values 
            obj.AdvertisingDataTypes = obj.AdvertisingDataTypes_Values;
            obj.AdvertisingDataTypes = bleGAPDataBlockConfig.getUniqueValues(valTemp);
        else
            if ischar(value)
                value = validatestring(value, ...
                    obj.AdvertisingDataTypes_Values, mfilename, ...
                    'AdvertisingDataTypes');
                obj.AdvertisingDataTypes = obj.AdvertisingDataTypes_Values;
                obj.AdvertisingDataTypes = {char(value)};
            else
                if isscalar(value)
                    value = validatestring(value, ...
                        obj.AdvertisingDataTypes_Values, mfilename, ...
                        'AdvertisingDataTypes');
                        obj.AdvertisingDataTypes = obj.AdvertisingDataTypes_Values;
                        obj.AdvertisingDataTypes = {char(value)};
                else
                    validateattributes(value, {'string'}, {'2d'}, ...
                        mfilename, 'AdvertisingDataTypes');
                    for i = 1:numel(value)
                        value(i) = validatestring(value(i), ...
                            obj.AdvertisingDataTypes_Values, mfilename, ...
                            'AdvertisingDataTypes');
                    end
                    % Unique values
                    obj.AdvertisingDataTypes = obj.AdvertisingDataTypes_Values;
                    obj.AdvertisingDataTypes = bleGAPDataBlockConfig.getUniqueValues(value);
                end
            end
        end
    end

    function obj = set.LEDiscoverability(obj, value)
        value = validatestring(value, ...
            obj.LEDiscoverability_Values, mfilename, 'LEDiscoverability');
        obj.LEDiscoverability = '';
        obj.LEDiscoverability = value;
    end

    function obj = set.LE(obj, value)
        value = validatestring(value, obj.LE_Values, mfilename, 'LE');
        obj.LE = '';
        obj.LE = value;
    end

    function obj = set.LocalName(obj, value)
        if isstring(value)
            validateattributes(value, {'string'}, {'scalar'}, ...
                mfilename, 'LocalName');
            charValue = char(value);
        else
            charValue = value;
        end
        
        if ~isempty(charValue)
            validateattributes(charValue, {'char'}, {'row'}, mfilename, ...
                'LocalName');
        end

        stringLength = numel(charValue);

        % Refer Supplement to Bluetooth core specification CSSv7,
        % Part-A, Section: 1.2 and Section 2
        validateattributes(stringLength, {'numeric'}, {'scalar', ...
            'integer', 'nonnegative', '<', 255}, mfilename, 'LocalName length');

        % Specified in Supplement to Bluetooth core specification CSSv7,
        % Part-A, Section: 1.2
        coder.internal.errorIf(any(double(charValue) > 255), ...
            'bluetooth:bleGAP:GAPInvalidStringOfUTF8');
        
        obj.LocalName = '';
        obj.LocalName = charValue;
    end

    function obj = set.Identifiers(obj, value)
        obj.Identifiers = '';
        if ~isempty(value)
            obj.Identifiers = ...
                bleGAPDataBlockConfig.validateUUIDs(value, 16, 'Identifiers');
        else
            obj.Identifiers = value;
        end
    end

    function obj = set.IdentifierType(obj, value)
        value = validatestring(value, ...
            obj.IdentifierType_Values, mfilename, 'IdentifierType');
        obj.IdentifierType = '';
        obj.IdentifierType = value;
    end

    function obj = set.AdvertisingInterval(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', 'integer', ...
            '>=', 32, '<=', 65535}, mfilename, 'AdvertisingInterval');
        obj.AdvertisingInterval = value;
    end

    function obj = set.TxPowerLevel(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', 'integer', ...
            '>=', -127, '<=', 127}, mfilename, 'TxPowerLevel');
        obj.TxPowerLevel = value;
    end

    function obj = set.ConnectionIntervalRange(obj, value)
        validateattributes(value, {'numeric'}, {'vector', 'integer', ...
            'numel', 2, '>=', 6, '<=', 3200}, mfilename, ...
            'ConnectionIntervalRange');

        % Validate connection interval range
        coder.internal.errorIf((value(1) > value(2)), ...
            'bluetooth:bleGAP:InvalidGAPConnectionIntervalRange');
        obj.ConnectionIntervalRange = value;
    end
end

methods (Access = protected)
    function flag = isInactiveProperty(obj, prop)
        % Controls conditional display of properties
        flag = true;

        % Disable display of all the properties
        if obj.HideDisplay
            return;
        end

        switch prop
            case 'AdvertisingDataTypes'
                flag = false;

            case {'LEDiscoverability', 'BREDR'}
                if any(strcmp(obj.AdvertisingDataTypes, 'Flags'))
                    flag = false;
                end

            case 'LE'
                if (any(strcmp(obj.AdvertisingDataTypes, 'Flags'))) && ...
                    (obj.BREDR)
                    flag = false;
                end

            case {'LocalName', 'LocalNameShortening'}
                if any(strcmp(obj.AdvertisingDataTypes, 'Local name'))
                    flag =  false;
                end

            case {'Identifiers', 'IdentifierType'}
                if any(strcmp(obj.AdvertisingDataTypes, 'UUIDs'))
                    flag =  false;
                end

            case 'AdvertisingInterval'
                if any(strcmp(obj.AdvertisingDataTypes, ...
                    'Advertising interval'))
                    flag = false;
                end

            case 'TxPowerLevel'
                if any(strcmp(obj.AdvertisingDataTypes, 'Tx power level'))
                    flag = false;
                end

            case 'ConnectionIntervalRange'
                if any(strcmp(obj.AdvertisingDataTypes, ...
                    'Connection interval range'))
                    flag = false;
                end
        end
    end
end

methods (Static, Access = private)
    % Get unique strings from the given list of strings
    function uniqueValues = getUniqueValues(values)
        validIndices = coder.nullcopy(zeros(42,1));
        allIndices = zeros(numel(values), 1);
        cnt = 1;

        for i = 1:numel(values)
            if ~any(i == allIndices)
                validIndices(cnt) = i;
                cnt = cnt + 1;
            end
            for j = 1:numel(values)
                if strcmpi(values{i}, values{j})
                    allIndices(j) = j;
                end
            end
        end

        uniqueValues = cell(cnt-1, 1);
        for i = 1:numel(uniqueValues)
            j = validIndices(i);
            uniqueValues{i} = char(values{j});
        end
        if (cnt-1 < numel(values))
            coder.internal.warning('bluetooth:bleGAP:GAPDuplicateValues');
        end
    end

    % Validate given UUIDs list and returns the valid list
    function uuidsList = validateUUIDs(value, numOfBits, propName)
        numHexDigits = numOfBits/4;
        if isstring(value)
            validateattributes(value, {'string'}, {}, mfilename, propName);
            charValues = char(zeros(size(value, 1), numHexDigits));
            for i=1:size(value, 1)
                validateattributes(strlength(value(i)), {'numeric'}, {'scalar', ...
                    '<=', numHexDigits, '>=', numHexDigits}, mfilename, ...
                    ['Each of the ' propName ' length']);
                charValues(i, :) = char(value(i));
            end
        else
            validateattributes(value, {'char'}, {'ncols', numHexDigits}, ...
                mfilename, propName);
            charValues = value;
        end
        for i=1:size(charValues, 1)
            ble.internal.validateHex(charValues(i, :), numHexDigits, propName);
        end
        
        if (numOfBits == 16)
            validateattributes(size(charValues, 1), {'numeric'}, {'scalar', ...
                'integer', '<=', 127}, mfilename, 'Total 16-bit UUIDs');
        end
        
        % GAP and GATT service UUIDs should not be included in a Service
        % UUIDs AD type. Refer Supplement to Bluetooth Core Specification,
        % CSSv7, Part A, Section 1.1.
        gattGAPUUIDs = ['1800'; '2800'; '2801'; '2802'; '2803'];
        
        maxUUIDs = char(zeros(size(charValues, 1), numHexDigits));
        cnt = 1;
        invalidWarn = false;
        
        for i = 1:size(charValues, 1)
            if (numOfBits == 16)
                % GAP and GATT service UUIDs should not be included in a
                % Service UUIDs AD type. Refer Supplement to Bluetooth Core
                % Specification, CSSv7, Part A, Section 1.1.
                if any(strcmpi(string(charValues(i, :)), gattGAPUUIDs))
                    if ~invalidWarn
                        coder.internal.warning(...
                            'bluetooth:bleGAP:GAPInvalidUUIDsList');
                        invalidWarn = true;
                    end
                    continue;
                end
            end
            
            % Remove duplicate UUIDs
            if ~any(strcmpi(string(charValues(i, :)), maxUUIDs))
                maxUUIDs(cnt, :) = charValues(i, :);
                cnt = cnt + 1;
            end
        end
        if (cnt-1 > 0)
            uuidsList = maxUUIDs(1:cnt-1, :);
            if (cnt-1 < size(charValues, 1))
                coder.internal.warning('bluetooth:bleGAP:GAPDuplicateValues');
            end
        else
            uuidsList = blanks(0);
        end
    end
end
end
