classdef bluetoothLEPeriodicAdvConfig < comm.internal.ConfigBase
    %bluetoothLEPeriodicAdvConfig Bluetooth LE periodic advertisements
    %configuration
    %   CFGPA = bluetoothLEPeriodicAdvConfig creates a default Bluetooth(R) low
    %   energy (LE) periodic advertisements (PA) configuration object, CFGPA,
    %   between the Broadcaster and Observer nodes.
    %
    %   CFGPA = bluetoothLEPeriodicAdvConfig(Name=Value) creates a Bluetooth LE
    %   PA configuration object, CFGPA, with the specified property Name set to
    %   the specified Value. You can specify additional name-value arguments in
    %   any order as (Name1=Value1, ..., NameN=ValueN).
    %
    %   bluetoothLEPeriodicAdvConfig properties:
    %
    %   PeriodicAdvInterval - Periodic advertising interval in seconds
    %   PeriodicAdvOffset   - Offset for starting the periodic advertising train (PAT)
    %   PHYMode             - Generating or decoding physical layer (PHY) mode
    %   UsedChannels        - List of used (good) data channels
    %
    %   bluetoothLEPeriodicAdvConfig properties (read-only):
    %
    %   AccessAddress - Unique address for PAT between the Broadcaster and
    %                   Observer nodes
    %
    %   bluetoothLEPeriodicAdvConfig methods:
    %
    %   configurePeriodicAdv - Configure PAT between the Broadcaster and
    %                          Observer nodes
    %
    %   % Example 1:
    %   % Create a configuration object to form PAT
    %   % between Bluetooth LE Broadcaster and Observer nodes
    %   cfgPeriodicAdv = bluetoothLEPeriodicAdvConfig(PeriodicAdvInterval=1);
    %
    %   % 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 Broadcaster node and an
    %   %  Observer node
    %
    %   % Create a Bluetooth LE node with "broadcaster" role
    %   broadcasterNode = bluetoothLENode("broadcaster");
    %   % Create a Bluetooth LE node with "observer" role
    %   observerNode = bluetoothLENode("observer");
    %
    %   % Create a configuration object to form PAT between the Broadcaster and Observer nodes
    %   cfgPeriodicAdv = bluetoothLEPeriodicAdvConfig(PeriodicAdvInterval=60, ...
    %                                                 UsedChannels=10:30, ...
    %                                                 PHYMode="LE2M")
    %
    %   % Configure the PAT between the Broadcaster and Observer nodes
    %   cfgPeriodicAdvUpdated = configurePeriodicAdv(cfgPeriodicAdv,broadcasterNode,observerNode)
    %
    %   % Configure and add application traffic at the Broadcaster node
    %   traffic = networkTrafficOnOff(DataRate=1,PacketSize=15);
    %   addTrafficSource(broadcasterNode,traffic);
    %
    %   % Add nodes to the wireless network simulator
    %   addNodes(networkSimulator,[broadcasterNode observerNode]);
    %
    %   % Set the simulation time in seconds and run the simulation
    %   run(networkSimulator,600);
    %
    %   % Retrieve statistics corresponding to the Broadcaster and Observer
    %   % nodes
    %   broadcasterNodeStats = statistics(broadcasterNode);
    %   observerNodeStats = statistics(observerNode);
    %
    %   See also bluetoothLEConnectionConfig, bluetoothLECISConfig,
    %   bluetoothLEBIGConfig, bluetoothMeshProfileConfig,
    %   bluetoothMeshFriendshipConfig, bluetoothLENode.

    %   Copyright 2023 The MathWorks, Inc.

    properties
        %PeriodicAdvInterval Periodic advertising interval in seconds
        %   Specify the periodic advertising interval as a scalar in the range
        %   [0.0075, 81.91875]. Units are in seconds. This property specifies the
        %   interval between the start of two consecutive periodic advertising
        %   events. Specify this value as an integer multiple of 1.25 milliseconds.
        %   The default value is 0.010 seconds. For more information about periodic
        %   advertising interval, see Bluetooth Core Specification v5.3 | Vol 6,
        %   PART B, Section 4.4.2.2.3.
        PeriodicAdvInterval = 0.010

        %PeriodicAdvOffset Offset for starting the periodic advertising train (PAT)
        %   Specify the periodic advertising offset as a scalar in the range [0,
        %   (<a href="matlab:help('bluetoothLEPeriodicAdvConfig.PeriodicAdvInterval')">PeriodicAdvInterval</a>-(MaxPeriodicPDUDuration+T_IFS)).
        %   The MaxPeriodicPDUDuration value specifies the time taken by the
        %   Broadcaster to transmit a packet containing maximum advertising
        %   payload. The T_IFS value specifies the interframe spacing time. This
        %   property specifies the sleep time before starting the PAT between the
        %   Broadcaster and Observer nodes. This property specifies the
        %   synchronization packet window offset by considering the reference point
        %   as the simulation start time. Units are in seconds. The default value
        %   is 0 seconds. For more information about synchronization packet window
        %   offset, see Bluetooth Core Specification v5.3 | Vol 6, PART B, Section
        %   2.3.4.6.
        PeriodicAdvOffset (1,1) {mustBeNumeric, mustBeFinite, mustBeNonnegative} = 0

        %PHYMode Generating or decoding physical layer (PHY) mode
        %   Specify the PHY mode as "LE1M","LE2M","LE500K" or "LE125K". This
        %   property specifies the generating or decoding mode that the PHY uses
        %   based on the data rate. The default value is "LE1M".
        PHYMode = string.empty

        %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 property specifies the
        %   indices of the assigned good data channels that the periodic
        %   advertising train uses. To ensure that at least two channels are set as
        %   used (good) channels, specify the vector length greater than 1. The
        %   default value is a row vector containing all the channel indices in the
        %   range [0:36].
        UsedChannels
    end

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

    properties (SetAccess=protected)
        %AccessAddress Unique periodic advertising train (PAT) access address
        %   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 PAT between Broadcaster and Observer
        %   nodes. The default value is "5DA44270".
        AccessAddress = "5DA44270"
    end

    properties (SetAccess=protected,Hidden)
        %BroadcasterName Name of the Broadcaster node associated with this PAT
        BroadcasterName = blanks(0)

        %ObserverName Name(s) of the Observer node associated with this PAT
        ObserverName = blanks(0)

        %BroadcasterID ID of the Broadcaster node associated with this PAT
        BroadcasterID

        %ObserverID ID(s) of the Observer node associated with this PAT
        ObserverID
    end

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

        % Set periodic advertising interval
        function obj = set.PeriodicAdvInterval(obj,value)
            validateattributes(value,{'numeric'},{'scalar','nonnegative','>=',7.5e-3, ...
                '<=',81.91875},mfilename,"PeriodicAdvInterval");
            coder.internal.errorIf((mod(value,1.25e-3)~=0), ...
                "bluetooth:bluetoothLENode:InvalidPeriodicAdvertisingInterval",sprintf('%f',value));
            obj.PeriodicAdvInterval = value; % In seconds
        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.UsedChannels = value;
        end

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

        function updatedPeriodicAdvCfg = configurePeriodicAdv(periodicAdvCfg,broadcasterNode,observerNode)
            %configurePeriodicAdv Configure periodic advertisements between Bluetooth LE Broadcaster and Observer
            %
            %   UPDATEDPERIODICADVCFG = configurePeriodicAdv(PERIODICADVCFG,BROADCASTERNODE,OBSERVERNODE)
            %   configures periodic advertisements between the Broadcaster, BROADCASTERNODE, and the Observer, OBSERVERNODE, by
            %   sharing the common periodic advertising synchronization configuration, PERIODICADVCFG, between the nodes.
            %
            %   PERIODICADVCFG is an object of type <a href="matlab:help('bluetoothLEPeriodicAdvConfig')">bluetoothLEPeriodicAdvConfig</a>.
            %
            %   BROADCASTERNODE is a scalar or a vector of objects corresponding to the
            %   Broadcaster(s) in the LE periodic advertisements. Each object is of
            %   type bluetoothLENode with the Role set to "broadcaster".
            %   If you specify this property as a vector, the object function creates a
            %   periodic advertising train (PAT) at each specified Broadcaster and
            %   configures the specified Observer to listen for all of these PAT's. If
            %   you specify this input as a vector, the OBSERVERNODE input must be a
            %   scalar.
            %
            %   OBSERVERNODE is a scalar or a vector of objects corresponding to
            %   Observer(s) in the LE periodic advertisements. Each object is of type
            %   bluetoothLENode with the Role set to "observer".
            %   If you specify this property as a vector, the object function creates a
            %   PAT for the specified Broadcaster and configures all the specified
            %   observers to listen for the PAT from the Broadcaster. If you specify
            %   this input as a vector, the BROADCASTERNODE input must be a scalar.
            %
            %   UPDATEDPERIODICADVCFG is a scalar or a vector of objects of
            %   type <a href="matlab:help('bluetoothLEPeriodicAdvConfig')">bluetoothLEPeriodicAdvConfig</a>. This value returns the configured
            %   periodic advertising information.

            % Validate nodes and their roles
            validateattributes(broadcasterNode,{'bluetoothLENode'},{},mfilename,"broadcasterNode",2);
            validateattributes(observerNode,{'bluetoothLENode'},{},mfilename,"observerNode",3);
            coder.internal.errorIf(any(arrayfun(@(x) ~strcmp(x.Role,"broadcaster"),broadcasterNode,UniformOutput=true)),"bluetooth:bluetoothLENode:PATInvalidRole","Second","Broadcaster","broadcaster");
            coder.internal.errorIf(any(arrayfun(@(x) ~strcmp(x.Role,"observer"),observerNode,UniformOutput=true)),"bluetooth:bluetoothLENode:PATInvalidRole","Third","Observer","observer");
            coder.internal.errorIf((~isscalar(broadcasterNode)&&~isscalar(observerNode)),"bluetooth:bluetoothLENode:PATInvalidSignature");

            % Validate single PAT per Broadcaster
            for bIdx=1:numel(broadcasterNode)
                broadcaster = broadcasterNode(bIdx);
                if broadcaster.NumPeriodicAdvs>0
                    coder.internal.error("bluetooth:bluetoothLENode:SinglePATPerBroadcaster",broadcaster.PeriodicAdvConfig.BroadcasterName,strjoin(broadcaster.PeriodicAdvConfig.ObserverName, ', '));
                end
            end

            % Validate PeriodicAdvOffset value to be less than
            % (PeriodicAdvInterval-(MaxPeriodicPDUDuration+T_IFS))
            secToUsec = 1e6;
            maxPeriodicAdvPDUDuration = bluetoothPacketDuration(periodicAdvCfg.PHYMode,"Disabled",255);
            T_IFS = 150;
            validateattributes(round(periodicAdvCfg.PeriodicAdvOffset*secToUsec)/secToUsec,{'numeric'},{'scalar','>=',0, ...
                '<=',((round(periodicAdvCfg.PeriodicAdvInterval*secToUsec)-(maxPeriodicAdvPDUDuration+T_IFS)))/secToUsec}, ...
                mfilename,"PeriodicAdvOffset");

            % Validate same PeriodicAdvInterval for all PAT at Observer
            for oIdx=1:numel(observerNode)
                observer = observerNode(oIdx);
                if observer.NumPeriodicAdvs>0
                    existingInterval = observer.PeriodicAdvConfig(1).PeriodicAdvInterval;
                    coder.internal.errorIf(round(existingInterval*secToUsec)~=round(periodicAdvCfg.PeriodicAdvInterval*secToUsec), ...
                        "bluetooth:bluetoothLENode:MustHaveSamePeriodicAdvertisingInterval",sprintf('%f',existingInterval));

                    % Validate PeriodicAdvOffset for every new PAT
                    existingOffset = observer.PeriodicAdvConfig(observer.NumPeriodicAdvs).PeriodicAdvOffset;
                    newOffset = periodicAdvCfg.PeriodicAdvOffset;
                    coder.internal.errorIf(round(newOffset*secToUsec)<=(round(existingOffset*secToUsec)+maxPeriodicAdvPDUDuration+T_IFS), ...
                        "bluetooth:bluetoothLENode:InvalidNextPATOffset",sprintf('%f',(round(existingOffset*secToUsec)+maxPeriodicAdvPDUDuration+T_IFS)/secToUsec));
                end
            end

            % Add PAT between Broadcaster and Observer nodes
            if isscalar(broadcasterNode) && isscalar(observerNode)
                periodicAdvCfg.BroadcasterName = broadcasterNode.Name;
                periodicAdvCfg.BroadcasterID = broadcasterNode.ID;
                periodicAdvCfg.ObserverName = observerNode.Name;
                periodicAdvCfg.ObserverID = observerNode.ID;
                periodicAdvCfg.AccessAddress = ble.internal.generateAccessAddress;

                addPeriodicAdvertisements(broadcasterNode,periodicAdvCfg);
                addPeriodicAdvertisements(observerNode,periodicAdvCfg);

            % Add multiple PAT's of Broadcasters at single Observer node
            elseif ~isscalar(broadcasterNode) && isscalar(observerNode)
                periodicAdvCfg = repmat(periodicAdvCfg,1,numel(broadcasterNode));
                for bIdx = 1:numel(broadcasterNode)
                    periodicAdvCfg(bIdx).BroadcasterName = broadcasterNode(bIdx).Name;
                    periodicAdvCfg(bIdx).BroadcasterID = broadcasterNode(bIdx).ID;
                    periodicAdvCfg(bIdx).ObserverName = observerNode.Name;
                    periodicAdvCfg(bIdx).ObserverID = observerNode.ID;
                    periodicAdvCfg(bIdx).AccessAddress = ble.internal.generateAccessAddress;
                    if bIdx>1
                        periodicAdvCfg(bIdx).PeriodicAdvOffset = periodicAdvCfg(bIdx-1).PeriodicAdvOffset+...
                            round((maxPeriodicAdvPDUDuration+T_IFS)/secToUsec,6);
                    end

                    addPeriodicAdvertisements(broadcasterNode(bIdx),periodicAdvCfg(bIdx));
                    addPeriodicAdvertisements(observerNode,periodicAdvCfg(bIdx));
                end

            % Add PAT of Broadcaster at multiple Observer nodes
            elseif ~isscalar(observerNode) && isscalar(broadcasterNode)
                periodicAdvCfg.BroadcasterID = broadcasterNode.ID;
                periodicAdvCfg.ObserverID = arrayfun(@(x) x.ID,observerNode);
                periodicAdvCfg.BroadcasterName = broadcasterNode.Name;
                periodicAdvCfg.ObserverName = arrayfun(@(x) x.Name,observerNode);
                periodicAdvCfg.AccessAddress = ble.internal.generateAccessAddress;

                addPeriodicAdvertisements(broadcasterNode, periodicAdvCfg);
                for oIdx = 1:numel(observerNode)
                    periodicAdvCfg.ObserverID = observerNode(oIdx).ID;
                    periodicAdvCfg.ObserverName = observerNode(oIdx).Name;
                    addPeriodicAdvertisements(observerNode(oIdx), periodicAdvCfg);
                end
            end
            updatedPeriodicAdvCfg = periodicAdvCfg;
        end
    end
end
