classdef bluetoothLEConnectionConfig < comm.internal.ConfigBase
%bluetoothLEConnectionConfig Bluetooth LE connection configuration
%parameters between Central and Peripheral roles
%   CFGCONN = bluetoothLEConnectionConfig creates a default configuration
%   object, CFGCONN, for a connection between Bluetooth(R) low energy (LE)
%   Central and Peripheral roles.
%
%   CFGCONN = bluetoothLEConnectionConfig(Name=Value) creates a Bluetooth
%   LE connection configuration object, CFGCONN, 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).
%
%   bluetoothLEConnectionConfig properties:
%
%   ConnectionInterval   - Connection interval in seconds
%   AccessAddress        - Unique connection address
%   UsedChannels         - List of used (good) data channels
%   Algorithm            - Type of channel selection algorithm
%   HopIncrement         - Hop increment count
%   CRCInitialization    - CRC initialization
%   SupervisionTimeout   - Connection supervision timeout in seconds
%   PHYMode              - Generating or decoding physical layer (PHY) mode
%   InstantOffset        - Offset added to connection event counter
%   ConnectionOffset     - Offset for starting the connection events
%   ActivePeriod         - Active communication period in each connection
%                          event in seconds
%
%   bluetoothLEConnectionConfig methods:
%
%   configureConnection  - Configure the connection Bluetooth LE Central
%                          and Peripheral nodes
%
%   % Example 1:
%   % Create a configuration object to form connection between Bluetooth LE
%   % Central and Peripheral nodes.
%   cfgConnection = bluetoothLEConnectionConfig(ConnectionInterval=0.030, ...
%                                               ActivePeriod=0.015, ...
%                                               PHYMode="LE125K")
%
%   % Example 2:
%   % Check if the Communications Toolbox(TM) Wireless Network Simulation
%   % Library support package is installed. If the support package is not
%   % installed, MATLAB(R) returns an error with a link to download and install
%   % the support package.
%   wirelessnetworkSupportPackageCheck
%
%   % Initialize the wireless network simulator
%   networkSimulator = wirelessNetworkSimulator.init;
%
%   %  Create the Bluetooth LE piconet with a "central" and a "peripheral"
%   %  node.
%
%   % Create a Bluetooth LE node with "central" role.
%   centralNode = bluetoothLENode("central");
%   % Create a Bluetooth LE node with "peripheral" role.
%   peripheralNode = bluetoothLENode("peripheral");
%
%   % Create a configuration object to form connection between Central and
%   % Peripheral nodes.
%   cfgConnection = bluetoothLEConnectionConfig(ConnectionInterval=0.040, ...
%                                               Algorithm=2, ...
%                                               PHYMode="LE2M")
%   % Configure the connection between "central" and "peripheral" nodes.
%   configureConnection(cfgConnection, centralNode, peripheralNode);
%   disp(centralNode);
%   disp(peripheralNode);
%
%   % Example 3:
%   % Check if the Communications Toolbox(TM) Wireless Network Simulation
%   % Library support package is installed. If the support package is not
%   % installed, MATLAB(R) returns an error with a link to download and install
%   % the support package.
%   wirelessnetworkSupportPackageCheck
%
%   % Initialize the wireless network simulator
%   networkSimulator = wirelessNetworkSimulator.init;
%
%   %  Create the Bluetooth LE piconet with a "central" and two
%   %  "peripheral" nodes.
%
%   % Create a Bluetooth LE node with "central" role.
%   centralNode = bluetoothLENode("central");
%   % Create a Bluetooth LE node with "peripheral" role.
%   peripheralNode1 = bluetoothLENode("peripheral");
%
%   % Create a configuration object to form connection between Central and
%   % Peripheral node 1.
%   cfgConnection = bluetoothLEConnectionConfig(ConnectionInterval=0.040, ...
%                                               ConnectionOffset=0, ...
%                                               ActivePeriod=0.020, ...
%                                               AccessAddress="5DA44270")
%   % Configure the connection between "central" and "peripheral" node 1.
%   configureConnection(cfgConnection, centralNode, peripheralNode1);
%   disp(centralNode);
%   disp(peripheralNode1);
%
%   % Create a Bluetooth LE node with "peripheral" role.
%   peripheralNode2 = bluetoothLENode("peripheral");
%
%   % Update the connection configuration object to form connection
%   % between Central and Peripheral node 2.
%   cfgConnection.AccessAddress = "5DA44271";
%   cfgConnection.ConnectionOffset = 0.020; % in seconds
%   cfgConnection.ActivePeriod = 0.020 % in seconds
%   % Configure the connection between "central" and "peripheral" node 2.
%   configureConnection(cfgConnection, centralNode, peripheralNode2);
%   disp(centralNode);
%   disp(peripheralNode2);
%
%   % Add application traffic from the Central to the Peripheral nodes
%   traffic1 = networkTrafficOnOff(DataRate=200,PacketSize=30);
%   addTrafficSource(centralNode,traffic1,DestinationNode=peripheralNode1);
%   traffic2 = networkTrafficOnOff(DataRate=200,PacketSize=20);
%   addTrafficSource(centralNode,traffic2,DestinationNode=peripheralNode2);
%
%   % Add the nodes to the simulator
%   addNodes(networkSimulator,[centralNode peripheralNode1 peripheralNode2]);
%
%   % Set the simulation time in seconds and run the simulation
%   run(networkSimulator,0.1);
%
%   % Retrieve statistics corresponding to the Central node
%   centralStats = statistics(centralNode);
%
%   See also bluetoothLECISConfig, bluetoothLEBIGConfig,
%   bluetoothMeshProfileConfig, bluetoothMeshFriendshipConfig,
%   bluetoothLENode.

%   Copyright 2021-2023 The MathWorks, Inc.

properties
    %ConnectionInterval Connection interval
    %   Specify the connection interval as a scalar in the range [0.0075, 4].
    %   Units are in seconds. This property specifies the interval between the
    %   start of two consecutive connection events. Specify this value as an
    %   integer multiple of 1.25 milliseconds. If the connection has connected
    %   isochronous stream (CIS) events, specify this value as a multiple of
    %   (NumPeripherals*1.25), where "NumPeripherals" represents the total
    %   number of simulated peripheral nodes. The default value is 0.020
    %   seconds. For more information about connection interval, see Bluetooth
    %   Core Specification v5.3 | Vol 6, PART B, Section 4.5.1.
    ConnectionInterval = 0.020

    %AccessAddress Unique connection address
    %   Specify the access address as an 8-element character vector or a
    %   string scalar denoting a 4-octet hexadecimal value. This property
    %   specifies a unique 32-bit address for the link layer connection
    %   between Central and Peripheral roles. The default value is
    %   "5DA44270".
    AccessAddress = "5DA44270"

    %UsedChannels List of used (good) data channels
    %   Specify the list of used data channels as an integer vector with
    %   element values in the range [0, 36]. This value specifies the
    %   indices of the assigned data channels. To ensure that at least two
    %   channels are set as used (good) channels, specify the vector length
    %   greater than 1. This property indicates the set of good channels
    %   used by the connection. The default value is a row vector
    %   containing all the channel indices [0:36].
    UsedChannels

    %Algorithm Type of channel selection algorithm
    %   Specify the channel selection algorithm as 1 or 2 representing
    %   "Algorithm #1" or "Algorithm #2", respectively. This property
    %   specifies the channel selection algorithm used for hopping of this
    %   connection. The default value is 1.
    Algorithm = 1

    %HopIncrement Hop increment count
    %   Specify the hop increment as an integer in the range [5, 16]. This
    %   property specifies the hop increment count to hop between data
    %   channels. The object uses this property as an input to "Algorithm
    %   #1" for channel selection. The default value is 5.
    HopIncrement (1,1) {mustBeNumeric, mustBeInteger, ...
        mustBeGreaterThanOrEqual(HopIncrement,5), ...
        mustBeLessThanOrEqual(HopIncrement,16)} = 5

    %CRCInitialization CRC initialization
    %   Specify CRC initialization as a 6-element character vector or
    %   string scalar denoting a 3-octet hexadecimal value. The object uses
    %   this value to initialize CRC calculation. The default value is
    %   "012345".
    CRCInitialization = "012345"

    %SupervisionTimeout Connection supervision timeout in seconds
    %   Specify the connection supervision timeout as a scalar in the range
    %   [0.1, 32]. Units are in seconds. This property indicates the
    %   timeout for a connection if no valid packet is received within this
    %   time. The supervision timeout must be a multiple of 10
    %   milliseconds. The default value is 1 second.
    SupervisionTimeout = 1

    %PHYMode Generating or decoding physical layer (PHY) mode
    %   Specify the PHY mode as "LE1M","LE2M","LE500K" or "LE125K". This
    %   property indicates the generating or decoding mode used by PHY
    %   based on the data rate. The default value is "LE1M".
    PHYMode

    %InstantOffset Offset added to connection event counter
    %   Specify instant offset as an integer in the range [6, 65535]. This
    %   property indicates the offset added to the connection event count.
    %   The object updates the channel map after InstantOffset connection
    %   events from the current connection event. The default value is 6.
    InstantOffset (1,1) {mustBeNumeric, mustBeInteger, ...
        mustBeGreaterThanOrEqual(InstantOffset,6), ...
        mustBeLessThanOrEqual(InstantOffset,65535)} = 6

    %ConnectionOffset Offset for starting the connection event
    %   Specify the connection offset as a scalar in the range [0, <a
    %   href="matlab:help('bluetoothLEConnectionConfig.ConnectionInterval')">ConnectionInterval</a>].
    %   This property specifies the sleep time before starting the connection
    %   events. If the connection has CIS events, specify this value as
    %   (ConnectionOffsetOld+(ConnectionInterval/NumPeripherals)). For the
    %   first connection, the "ConnectionOffsetOld" value is 0, and for
    %   subsequent connections, it denotes the "ConnectionOffset" value of the
    %   previous connection. The "NumPeripherals" value specifies the total
    %   number of simulated peripheral nodes. Units are in seconds. The default
    %   value is 0 seconds.
    ConnectionOffset = 0

    %ActivePeriod Active communication period in each connection event in
    %seconds
    %   Specify the active period as a scalar in the range [0 <a
    %   href="matlab:help('bluetoothLEConnectionConfig.ConnectionInterval')">ConnectionInterval</a>].
    %   This property indicates the active period of communication in each
    %   connection event. Units are in seconds. The default value is 0.020
    %   seconds.
    ActivePeriod = 0.020
end

properties(Constant, Hidden)
    PHYMode_Values = ble.internal.networkUtilities.PHYMode_Values
end

properties(SetAccess = protected, Hidden)
    %CentralName Name of the Central node associated with this connection
    CentralName = blanks(0)

    %PeripheralName Name of the Peripheral node associated with this
    %connection
    PeripheralName = blanks(0)

    %CentralID ID of the Central node associated with this connection
    CentralID

    %PeripheralID ID of the Peripheral node associated with this connection
    PeripheralID

    %ChannelMap Channel map indicating good (1) and bad (0) data channels
    ChannelMap = ones(1, 37)
end

methods
    function obj = bluetoothLEConnectionConfig(varargin)
        % For codegen initialize variable sized properties in constructor
        obj@comm.internal.ConfigBase("UsedChannels",0:36, ...
                                    "PHYMode","LE1M",varargin{:});
    end

    % Set connection interval
    function obj = set.ConnectionInterval(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', '>=', 7.5e-3, ...
            '<=', 4}, mfilename, "ConnectionInterval");
        coder.internal.errorIf((mod(value, 1.25e-3) ~= 0), ...
            "bluetooth:bluetoothLENode:InvalidConnectionInterval", sprintf('%f', value));
        obj.ConnectionInterval = value; % in seconds
    end

    % Set access address
    function obj = set.AccessAddress(obj, value)
        ble.internal.validateHex(value, 8, "AccessAddress");
        obj.AccessAddress = string(value);
    end

    % Set used channels list
    function obj = set.UsedChannels(obj, value)
        validateattributes(value, {'numeric'}, {'vector', 'nonnegative', ...
            'integer', '<=', 36}, mfilename, "UsedChannels");
        value = unique(value);
        % At least two channels must be set as used (good) channels as
        % specified in Bluetooth Core Specification v5.3 | Vol 6, Part B,
        % Section 4.5.8.1
        coder.internal.errorIf((numel(value) < 2), ...
            "bluetooth:bleLL:InvalidUsedChannels");
        obj.ChannelMap = zeros(1, 37); %#ok<*MCSUP> 
        obj.ChannelMap(value+1) = 1;
        obj.UsedChannels = value;
    end

    % Set channel selection algorithm
    function obj = set.Algorithm(obj, value)
        % Accept either 1 or 2
        coder.internal.errorIf(~ismember(value, [1 2]), ...
            "bluetooth:bleLL:InvalidChSelAlgorithm");
        obj.Algorithm = value;
    end

    % Set CRC initialization
    function obj = set.CRCInitialization(obj, value)
        ble.internal.validateHex(value, 6, "CRCInitialization");
        obj.CRCInitialization = string(value);
    end

    % Set connection supervision timeout
    function obj = set.SupervisionTimeout(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', ...
            '>=', 0.1, '<=', 32}, mfilename, "SupervisionTimeout");
        coder.internal.errorIf((mod(value, 10e-3) ~= 0), ...
            "bluetooth:bluetoothLENode:InvalidSupervisionTimeout", sprintf('%f', value));
        obj.SupervisionTimeout = value;
    end

    % Set PHY mode
    function obj = set.PHYMode(obj, value)
        value = validatestring(value, obj.PHYMode_Values, mfilename, "PHYMode");
        obj.PHYMode = "";
        obj.PHYMode = string(value);
    end

    % Set connection offset
    function obj = set.ConnectionOffset(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', '>=', 0, ...
            '<=', obj.ConnectionInterval}, mfilename, "ConnectionOffset");
        obj.ConnectionOffset = value;
    end

    % Set active period
    function obj = set.ActivePeriod(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', '>=', 0, ...
            '<=', obj.ConnectionInterval}, mfilename, "ActivePeriod");
        obj.ActivePeriod = value;
    end

    function [updatedConnectionConfig, updatedCISConfig] = configureConnection(connectionConfig, centralNode, peripheralNode, varargin)
        %configureConnection Configure connection between Bluetooth LE Central and
        %Peripheral nodes
        %
        %   [UPDATEDCONNECTIONCONFIG, UPDATEDCISCONFIG] = configureConnection(CONNECTIONCONFIG, CENTRALNODE, PERIPHERALNODE)
        %   configures the connection between the Central, CENTRALNODE, and the Peripheral, PERIPHERALNODE, nodes
        %   by sharing the same connection configuration between these two nodes. The CONNECTIONCONFIG object
        %   specifies the connection parameters.
        %
        %   [UPDATEDCONNECTIONCONFIG, UPDATEDCISCONFIG] = configureConnection(CONNECTIONCONFIG, CENTRALNODE, PERIPHERALNODE, CISConfig=cisConfig)
        %   configures the connected isochronous stream (CIS) connection between the Central, CENTRALNODE, and the Peripheral, PERIPHERALNODE,
        %   nodes by sharing the same asynchronous connection-oriented (ACL) and CIS logical transport connection configuration between the two nodes.
        %
        %   CONNECTIONCONFIG is an object of type <a
        %   href="matlab:help('bluetoothLEConnectionConfig')">bluetoothLEConnectionConfig</a>.
        %
        %   CISCONFIG is an optional Name-Value argument specified as an object or
        %   an array of objects of type <a href="matlab:help('bluetoothLECISConfig')">bluetoothLECISConfig</a>.
        %   If you specify this input, the object function enables the
        %   establishment of a CIS stream between the Central and Peripheral nodes.
        %   To configure multiple CIS connections, specify this input as an array.
        %   Note that a Central node can configure up to 31 CIS connections.
        %
        %   CENTRALNODE is an object of type bluetoothLENode with the Role
        %   property set as "central".
        %
        %   PERIPHERALNODE is an object of type bluetoothLENode with the
        %   Role property set as "peripheral".
        %
        %   UPDATEDCONNECTIONCONFIG is an object of type <a href="matlab:help('bluetoothLEConnectionConfig')">bluetoothLEConnectionConfig</a>.
        %   This value specifies the updated connection configuration.
        %
        %   UPDATEDCISCONFIG is an object of type <a href="matlab:help('bluetoothLECISConfig')">bluetoothLECISConfig</a>.
        %   This value specifies the updated CIS connection configuration.

        % Validate the provided configuration
        narginchk(3, 5);
        if nargin > 3 % CIS configuration is provided for this connection
            validatestring(varargin{1}, {'CISConfig'}, mfilename, "name-value-arguments");
            validateattributes(varargin{2}, {'bluetoothLECISConfig'}, {'nonempty'}, mfilename, "CISConfig");
            cisConfig = varargin{2};
            cisConfig = reshape(cisConfig, 1, []); % Convert to row vector
        else
            cisConfig = [];
        end
        validateConnectionConfiguration(connectionConfig, centralNode, peripheralNode, cisConfig);

        % Store IDs and names of the connected Central and Peripheral nodes
        connectionConfig.CentralID = centralNode.ID;
        connectionConfig.PeripheralID = peripheralNode.ID;
        connectionConfig.CentralName = centralNode.Name;
        connectionConfig.PeripheralName = peripheralNode.Name;

        if ~isempty(cisConfig) % CIS configuration is provided for this connection
            % Check if cisConfig is an array for multiple CIS connections
            currentCISConnections = centralNode.NumCISConnections;
            lastCISConfig = [];
            if numel(cisConfig) > 1
                for idx = 1:numel(cisConfig)
                    if idx > 1
                        lastCISConfig = cisConfig(idx-1);
                    else
                        if currentCISConnections ~= 0
                            lastCISConfig = centralNode.CISConfig(currentCISConnections);
                        end
                    end
                    cisConfig(idx) = setupCIS(connectionConfig, cisConfig(idx), centralNode, peripheralNode, currentCISConnections, lastCISConfig);
                    currentCISConnections = currentCISConnections + 1;
                end
            else % Set up a single CIS connection
                if currentCISConnections ~= 0
                    lastCISConfig = centralNode.CISConfig(currentCISConnections);
                end
                cisConfig = setupCIS(connectionConfig, cisConfig, centralNode, peripheralNode, currentCISConnections, lastCISConfig);
            end

            % Add the connection at the Central node
            addConnection(centralNode, connectionConfig, cisConfig);
            % Add the connection at the Peripheral node
            addConnection(peripheralNode, connectionConfig, cisConfig);
        else
            % Add the connection at the Central node
            addConnection(centralNode, connectionConfig);
            % Add the connection at the Peripheral node
            addConnection(peripheralNode, connectionConfig);
        end
        updatedConnectionConfig = connectionConfig;
        updatedCISConfig = cisConfig;
    end
end

methods (Access = protected)
    function flag = isInactiveProperty(obj, prop)
        flag = false;
        if strcmpi(prop, "HopIncrement")
            flag = ~(obj.Algorithm == 1);
        end
    end
end

methods (Access = protected)
    function validateConnectionConfiguration(connectionConfig, centralNode, peripheralNode, cisConfig)
        %validateConnectionConfiguration Validate the provided configuration (ACL
        %and CIS)

        % Validate nodes and their roles
        validateattributes(centralNode, {'bluetoothLENode'}, {'scalar'}, ...
            mfilename, "centralNode", 2);
        validateattributes(peripheralNode, {'bluetoothLENode'}, {'scalar'}, ...
            mfilename, "peripheralNode", 3);
        validatestring(centralNode.Role, {'central'}, mfilename, "centralNode.Role");
        validatestring(peripheralNode.Role, {'peripheral'}, mfilename, "peripheralNode.Role");

        % Supervision timeout must be greater than (2 * connection interval)
        secToUsec = 1e6;
        coder.internal.errorIf(round(connectionConfig.SupervisionTimeout*secToUsec) <= 2*round(connectionConfig.ConnectionInterval*secToUsec), ...
            "bluetooth:bluetoothLENode:InvalidSupervisionTimeoutAndConnectionInterval", ...
            sprintf('%f', connectionConfig.SupervisionTimeout), sprintf('%f', 2*connectionConfig.ConnectionInterval));

        % Peripheral must have single connection only
        coder.internal.errorIf(peripheralNode.NumConnections >= 1, ...
            "bluetooth:bluetoothLENode:MustHaveOneConnection", ...
            peripheralNode.ConnectionConfig.PeripheralName, ...
            peripheralNode.ConnectionConfig.CentralName);

        % Validate active period and connection offset of the connection (ACL) to
        % be less than the connection interval
        validateattributes(connectionConfig.ActivePeriod, {'numeric'}, {'scalar', '>=', 0, ...
            '<=', connectionConfig.ConnectionInterval}, mfilename, "ActivePeriod");
        validateattributes(connectionConfig.ConnectionOffset, {'numeric'}, {'scalar', '>=', 0, ...
            '<=', connectionConfig.ConnectionInterval}, mfilename, "ConnectionOffset");

        % See whether CIS configuration is provided for the connection
        cisConfigured = ~isempty(cisConfig);
        numConnections = centralNode.NumConnections;
        numCISConnections = centralNode.NumCISConnections;
        if cisConfigured % Validate CIS config
            coder.internal.errorIf((numCISConnections + numel(cisConfig)) > 31, "bluetooth:bluetoothLENode:Maximum31CISSupported");

            % Verify all the CIS connections to have same values of ISOInterval,
            % CISArrangement, FlushTimeout
            isoInterval = arrayfun(@(cfg) cfg.ISOInterval, cisConfig);
            arrangement = arrayfun(@(cfg) cfg.CISArrangement, cisConfig);
            if numCISConnections > 0 % Append value of previously configured CIS connection
                isoInterval = [centralNode.CISConfig(1).ISOInterval isoInterval];
                arrangement = [centralNode.CISConfig(1).CISArrangement arrangement];
            end
            existingArrangement = arrangement(1);
            if ~isscalar(cisConfig) || numCISConnections > 0
                flushTimeout = arrayfun(@(cfg) cfg.FlushTimeout, cisConfig, 'UniformOutput', false);
                if numCISConnections > 0
                    flushTimeout = [{centralNode.CISConfig(1).FlushTimeout} flushTimeout];
                end
                isoInterval = unique(isoInterval);
                coder.internal.errorIf(~isscalar(isoInterval), "bluetooth:bluetoothLENode:MustHaveSameCISParams", "ISOInterval", sprintf('%f', isoInterval(1)));
                coder.internal.errorIf(~all(strcmp(arrangement, existingArrangement)), "bluetooth:bluetoothLENode:MustHaveSameCISParams", "CISArrangement", existingArrangement);
                coder.internal.errorIf(~isequal(flushTimeout{:}), "bluetooth:bluetoothLENode:MustHaveSameCISParams", "FlushTimeout", num2str(flushTimeout{1}));

                % Verify SubInterval
                is_empty_si = unique(arrayfun(@(x) isempty(x.SubInterval), cisConfig));
                coder.internal.errorIf(~isscalar(is_empty_si), "bluetooth:bluetoothLENode:SubIntervalMustbeEmptyOrNonEmpty");
                coder.internal.errorIf(is_empty_si && strcmp(existingArrangement, "interleaved"),"bluetooth:bluetoothLENode:InvalidSubIntervalInterleaved");
                coder.internal.errorIf((strcmp(existingArrangement, "interleaved") && ...
                    ~(all([cisConfig.SubInterval] == cisConfig(1).SubInterval))), ...
                    "bluetooth:bluetoothLENode:SubIntervalMustbeSameInterleaved", sprintf('%f', cisConfig(1).SubInterval));
            end
        end

        % Validate connections at Central
        if numConnections > 0
            % All the connections at the Central node must have the same connection
            % interval
            existingInterval = centralNode.ConnectionConfig(1).ConnectionInterval;
            coder.internal.errorIf(round(existingInterval*secToUsec) ~= round(connectionConfig.ConnectionInterval*secToUsec), ...
                "bluetooth:bluetoothLENode:MustHaveSameConnectionInterval", ...
                sprintf('%f', existingInterval));

            % Validate CIS connections at Central
            if numCISConnections
                % Verify to have same values of ACL ActivePeriod in the given and existing configuration
                existingAP = centralNode.ConnectionConfig(1).ActivePeriod;
                coder.internal.errorIf(round(existingAP*secToUsec) ~= round(connectionConfig.ActivePeriod*secToUsec), "bluetooth:bluetoothLENode:MustHaveSameActivePeriod", sprintf('%f', existingAP));

                % For the second ACL connection validate ConnectionOffset because the value
                % of ISOInterval is dependent on the ConnectionInterval, ConnectionOffset.
                if numConnections == 1
                    maxCIS = 31;
                    Offsets = round((connectionConfig.ConnectionInterval./(numConnections+1:maxCIS+1)).*secToUsec);
                    validACLOffsets = Offsets(mod(Offsets, 1250) == 0); % ISOInterval to be multiples of 1.25 ms
                    coder.internal.errorIf(~ismember(round(connectionConfig.ConnectionOffset*secToUsec),validACLOffsets), ...
                        "bluetooth:bluetoothLENode:ACLCISInvalidConnectionOffset");
                else % Validate connection offset for the rest of the connections
                    numPeripherals = existingInterval/centralNode.ConnectionConfig(2).ConnectionOffset;
                    if numConnections < numPeripherals
                        expectedOffset = centralNode.ConnectionConfig(2).ConnectionOffset*(numConnections);
                        coder.internal.errorIf(round(connectionConfig.ConnectionOffset*secToUsec) ~= round(expectedOffset*secToUsec), "bluetooth:bluetoothLENode:ACLCISInvalidNextConnectionOffset", sprintf('%f', expectedOffset));
                    else
                        coder.internal.error("bluetooth:bluetoothLENode:ACLCISInsufficientConnectionInterval", sprintf('%d', numPeripherals), sprintf('%f', centralNode.ConnectionConfig(2).ConnectionOffset*(numPeripherals+1)));
                    end
                end
            end
        end

        % Verify ISOInterval to be enough to support ACL and CISs
        if cisConfigured
            aclAP = connectionConfig.ActivePeriod;
            coder.internal.errorIf(round(aclAP*secToUsec) == round(connectionConfig.ConnectionInterval*secToUsec),"bluetooth:bluetoothLENode:NoActivePeriodForCIS", ...
                sprintf('%f', aclAP), sprintf('%f', connectionConfig.ConnectionInterval));
            if numCISConnections > 0
                cisConfig = [centralNode.CISConfig cisConfig];
            end
            numCIS = numel(cisConfig);

            % Get SubeventLength of all the CIS connections at Central to validate
            % against the SubInterval
            seLengths = zeros(1,numCIS);
            subIntervals = zeros(1,numCIS);
            for idx = 1:numCIS
                seLengths(idx) = subeventLength(cisConfig(idx),connectionConfig.PHYMode);
                if isempty(cisConfig(idx).SubInterval)
                    subIntervals(idx) = seLengths(idx);
                else
                    subIntervals(idx) = cisConfig(idx).SubInterval;
                    coder.internal.errorIf((strcmp(existingArrangement, "sequential") && cisConfig(idx).SubInterval < seLengths(idx)), ...
                        "bluetooth:bluetoothLENode:InsufficientSubIntervalSequential", sprintf('%f', cisConfig(idx).SubInterval), sprintf('%f', seLengths(idx)));
                end
                coder.internal.errorIf(cisConfig(idx).NumSubevents<cisConfig(idx).BurstNumber,"bluetooth:bluetoothLENode:InvalidNumSubevents",cisConfig(idx).NumSubevents,max(cisConfig(idx).BurstNumber));
            end

            % Get active period required for the CIS connections
            if strcmp(existingArrangement, "sequential")
                numSubevents = arrayfun(@(cfg) cfg.NumSubevents, cisConfig);
                cisAP = sum(subIntervals.*numSubevents);
            else
                coder.internal.errorIf(round(subIntervals(1)*secToUsec) < sum(round(seLengths.*secToUsec)), "bluetooth:bluetoothLENode:InsufficientSubIntervalInterleaved", ...
                    sprintf('%f', subIntervals(1)), sprintf('%f', sum(seLengths)));
                maxSubeventCountWithinCIG = max(arrayfun(@(cfg) cfg.NumSubevents, cisConfig));
                cisAP = subIntervals(1)*maxSubeventCountWithinCIG;
            end
            coder.internal.errorIf(round(isoInterval*secToUsec) < round((aclAP+cisAP)*secToUsec), "bluetooth:bluetoothLENode:NoActivePeriodForACLCIS", sprintf('%f', aclAP), sprintf('%f', cisAP));
        end
    end

    function cisConfig = setupCIS(obj, cisConfig, centralNode, peripheralNode, currentCISCount, prevCISConfig)
        %setupCIS Establish a new CIS connection between the Central and Peripheral
        %nodes according to the provided cisConfig.

        % Assign a new CIS ID and access address
        currentCISCount = currentCISCount + 1;
        cisAccessAddress = ble.internal.generateAccessAddress;

        % Calculate subevent length of the CIS
        subEventLength = subeventLength(cisConfig, obj.PHYMode);
        subEventLength = max(400e-6, subEventLength);
        if isempty(cisConfig.SubInterval)
            cisConfig.SubInterval = subEventLength; % Subinterval should be minimum 400 us
        end

        % Calculate CISOffset
        if isempty(prevCISConfig)
            cisOffset = obj.ActivePeriod;
        else
            prevCISOffset = prevCISConfig.CISOffset;
            if strcmp(prevCISConfig.CISArrangement, "sequential")
                prevCISSpacing = prevCISConfig.NumSubevents*prevCISConfig.SubInterval;
            else
                prevCISSpacing = prevCISConfig.SubeventLength;
            end
            cisOffset = prevCISOffset + prevCISSpacing ;
        end

        % Set these read-only parameters in the CIS config
        cisConfig = setDerivedParams(cisConfig, ...
            CISID=currentCISCount, AccessAddress=cisAccessAddress, SubeventLength=subEventLength, ...
            CentralName=centralNode.Name, PeripheralName=peripheralNode.Name, ...
            CentralID=centralNode.ID, PeripheralID=peripheralNode.ID, CISOffset=cisOffset);
    end
end
end

% LocalWords:  CRC PHY
