classdef helperBluetoothFullDuplexNode < helperBluetoothBaseNode
%helperBluetoothFullDuplexNode Create an object for Bluetooth node
%   BTNODE = helperBluetoothFullDuplexNode creates an object for Bluetooth
%   node having PHY and baseband layers. This node also had channel to
%   model the path loss and interference with WLAN signals.
%
%   BTNODE = helperBluetoothFullDuplexNode(Name, Value) creates an object
%   for Bluetooth node 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).
%
%   helperBluetoothFullDuplexNode properties:
%
%   NodePosition        - Node position in cartesian coordinates [x y z]
%   NodeID              - Node identifier
%   NodeAddress         - Bluetooth node address
%   Baseband            - Bluetooth baseband layer object
%   PHY                 - Bluetooth PHY layer object
%   Channel             - Bluetooth channel model object

%   Copyright 2020 The MathWorks, Inc.

properties
    %NodeAddress Bluetooth node address
    %   Specify the Bluetooth node address as a 12-element character vector
    %   or string scalar denoting a six octet hexadecimal value. The
    %   default is '0123456789AB'.
    NodeAddress = '0123456789AB'
    
    %Baseband Bluetooth baseband layer object
    %   Specify this property as an object of type <a
    %   href="matlab:help('helperBluetoothBaseband')">helperBluetoothBaseband</a>.
    %   This object contains methods and properties related to Bluetooth
    %   baseband layer.
    Baseband
    
    %PHY Bluetooth PHY layer object
    %   Specify this property as an object of type <a
    %   href="matlab:help('helperBluetoothPHY')">helperBluetoothPHY</a>.
    %   This object contains methods and properties related to Bluetooth PHY.
    PHY
    
    %Channel Bluetooth channel model object
    %   Specify this property as an object of type <a
    %   href="matlab:help('helperBluetoothChannel')">helperBluetoothChannel</a>. This
    %   object contains methods and properties related to Bluetooth channel
    %   model.
    Channel
end

properties (Constant, Hidden)
    %RxBufferSize Maximum number of signals that can be stored in the
    %receiver buffer of the node
    RxBufferSize = 10
end

properties (Hidden)
    %RxBuffer Rx buffer contains the received signals to be processed
    RxBuffer
    
    %RxBufferIdx Index used to identify the current position of the Rx
    %buffer
    RxBufferIdx = 0
    
    %LinkTraffic Traffic on each logical transport associated with this
    %node. This is specified as an nX2 array containing logical transport
    %addresses and their corresponding link traffic as either 1, 2 or 3.
    %   1 - ACL logical transport between Master and Slave
    %   2 - SCO logical transport between Master and Slave
    %   3 - Both ACL and SCO logical transports between Master and Slave
    %   for each logical link along with the LT address.
    LinkTraffic = [1 1]
end

properties (SetAccess = private)
    %TxBuffer Buffer contains the data to be transmitted from the node
    TxBuffer
    
    %VisualizeSpectrum The waveform is visualized through spectrum analyzer
    %when this property is true
    VisualizeSpectrum = false
    
    %ChannelWaveform Waveform after passing through the channel and this is
    %visualized through spectrum analyzer
    ChannelWaveform
end

properties (Access = private)
    %pSignal Bluetooth waveform along with metadata having empty signal
    pSignal
    
    %pSimulationTime Simulation time in microseconds, this will be updated
    %for every call of the runNode with the received elapsed time
    pSimulationTime = 0
end

methods
    % Constructor
    function obj = helperBluetoothFullDuplexNode(varargin)
        % Number of bits for an octet
        octetLen = 8;
        % Initialize
        obj.PHY = helperBluetoothPHY;
        obj.Channel = helperBluetoothChannel;
        obj.Baseband = helperBluetoothBaseband;
        obj.pSignal = struct('Waveform', complex(zeros(helperBluetoothPHY.WaveformMaxSize, 1)), ...
                             'PacketType', blanks(0), ...
                             'NumSamples', 0, ...
                             'SampleRate', 0, ...
                             'SamplesPerSymbol', 0, ...
                             'SourceID', 0, ...
                             'Bandwidth', 0, ...
                             'NodePosition', [0 0 0], ...
                             'CenterFrequency', 0, ...
                             'StartTime', 0, ...
                             'EndTime', 0, ...
                             'Payload', zeros(helperBluetoothBaseband.MaxPayloadSize * octetLen, 1), ...
                             'PayloadLength', 0);
        % Initialize the transmitted buffer with empty signal
        obj.TxBuffer = obj.pSignal;
        obj.RxBuffer = cell(1, obj.RxBufferSize);
        obj.ChannelWaveform = complex(zeros(helperBluetoothPHY.WaveformMaxSize, 1));
        % Name-value pairs
        for idx = 1:2:nargin
            obj.(varargin{idx}) = varargin{idx+1};
        end
    end

    % Auto-completion
    function v = set(obj, prop)
        v = obj.([prop, 'Values']);
    end
    
    % Set node address
    function set.NodeAddress(obj, value)
        ble.internal.validateHex(value, 12, 'NodeAddress');
        obj.NodeAddress = char(value);
        obj.PHY.WaveformConfig.DeviceAddress = obj.NodeAddress; %#ok<MCSUP>
    end
    
    % Set baseband object
    function set.Baseband(obj, value)
        validateattributes(value, {'helperBluetoothBaseband'}, {'scalar'}, ...
            mfilename, 'Baseband');
        obj.Baseband = value;
    end
    
    % Set PHY object
    function set.PHY(obj, value)
        validateattributes(value, {'helperBluetoothPHY'}, {'scalar'}, ...
            mfilename, 'PHY');
        obj.PHY = value;
    end
    
    % Set Bluetooth channel model object
    function set.Channel(obj, value)
        validateattributes(value, {'helperBluetoothChannel'}, {'scalar'}, ...
            mfilename, 'Channel');
        obj.Channel = value;
    end
end

methods
    function nextInvokeTime = runNode(obj, elapsedTime)
    %runNode Runs the Bluetooth node
    %
    %   NEXTINVOKETIME = runNode(OBJ, ELAPSEDTIME) runs the functionality
    %   of Bluetooth node (having baseband and PHY layers) by updating the
    %   elapsed time and returns the time to run the node again.
    %
    %   NEXTINVOKETIME is the time (in microseconds) after which the RUN
    %   function must be invoked again.
    %
    %   OBJ is an object of type helperBluetoothFullDuplexNode.
    %
    %   ELAPSEDTIME is the time elapsed in microseconds between two
    %   successive calls of this function.
    
        % Reset the flag to visualize the waveform through spectrum
        % analyzer
        obj.VisualizeSpectrum = false;
        
        % Update the simulation time in microseconds
        obj.pSimulationTime = obj.pSimulationTime + elapsedTime;
    
        % Rx buffer has data to be processed 
        if obj.RxBufferIdx ~= 0
            % Process the data in the Rx buffer
            for idx = 1:obj.RxBufferIdx
                % Get the data from the Rx buffer and process the data
                nextInvokeTime = run(obj, elapsedTime, obj.RxBuffer{idx});
                % Set the elapsed time as 0, because all the data in the Rx
                % buffer should be processed at the same timestamp
                elapsedTime = 0;
            end
            obj.RxBufferIdx = 0;
        % Rx buffer has no data to process
        else
            % Update the elapsed time to all the layers
            nextInvokeTime = run(obj, elapsedTime, obj.pSignal);
        end
    end
    
    function pushData(obj, aclPacketType, scoPacketType)
    %pushData Push random payload into ACL and SCO logical transport queues
    %based on the respective packet type
    %
    %   pushData(OBJ, ACLPACKETTYPE, SCOPACKETTYPE) pushes random payload,
    %   into the ACL and SCO logical transport queues based on the
    %   respective packet type and the availability of the respective
    %   queue.
    %
    %   OBJ is an object of type helperBluetoothFullDuplexNode.
    %
    %   ACLPACKETTYPE is character vector or string scalar, specifies ACL
    %   packet type.
    %
    %   SCOPACKETTYPE is character vector or string scalar, specifies SCO
    %   packet type.
    
        % Baseband object
        baseband = obj.Baseband;
        
        % Get the available ACL logical transport addresses for holding
        % data in the respective queues
        aclAddrs = aclLinkAddress(baseband);
        
        % Get the size of ACL data based on the ACL packet type
        aclSize = baseband.ACLPacketInfo(strcmpi(baseband.ACLPacketTypes, aclPacketType), 2); % in bytes
        
        % Push ACL data into the respective logical transports
        for idx = 1:numel(aclAddrs)
            addrIdx = (obj.LinkTraffic(:, 1) == aclAddrs(idx));
            if ((obj.LinkTraffic(addrIdx, 2) == 1) || (obj.LinkTraffic(addrIdx, 2) == 3))
                aclPayload = randi([0 1], aclSize*8, 1);
                pushACLData(baseband, aclAddrs(idx), aclPayload);
            end
        end
        
        % Get the available SCO logical transport addresses for holding
        % data in the respective queues
        scoAddrs = scoLinkAddress(baseband);
        
        % Push SCO data into the respective logical transports
        for idx = 1:numel(scoAddrs)
            addrIdx = (obj.LinkTraffic(:, 1) == scoAddrs(idx));
            if (obj.LinkTraffic(addrIdx, 2) == 2) || (obj.LinkTraffic(addrIdx, 2) == 3)
                scoTypeIdx = 0;
                for scoIdx = 1:numel(scoPacketType)/2
                    if scoPacketType{scoIdx, 1} == scoAddrs(idx)
                        scoTypeIdx = scoIdx;
                    end
                end
                scoType = scoPacketType{scoTypeIdx, 2};
                % Get the size of SCO data based on the SCO packet type
                scoSize = 0;
                switch scoType
                    case 'HV1'
                        scoSize = 10; % in bytes
                    case 'HV2'
                        scoSize = 20; % in bytes
                    case 'HV3'
                        scoSize = 30; % in bytes
                end
                
                scoPayload = randi([0 1], scoSize*8, 1);
                pushSCOData(baseband, scoPacketType{scoTypeIdx, 1}, scoPayload);
            end
        end
    end
end

methods (Access = private)
    function nextInvokeTime = run(obj, elapsedTime, btSignal)
    %run Runs the node with the received signal and returns the next invoke
    %time in microseconds
    
        % Initialize
        obj.TxBuffer.NumSamples = 0;
        
        % Baseband object
        baseband = obj.Baseband;
        % PHY object
        phy = obj.PHY;
        % Channel object
        channel = obj.Channel;
        
        % Discard, if the waveform is received from the same node
        if obj.NodeID == btSignal.SourceID
            btSignal.NumSamples = 0;
        end
        
        % Pass the received Bluetooth waveform through channel to apply
        % path loss and interference
        if (btSignal.NumSamples ~= 0)
            % Pass the received signal through the channel
            btSignal = run(channel, btSignal, phy.Mode);
            
            % Update the waveform start time
            btSignal.StartTime = obj.pSimulationTime;
            
            % Update the waveform to visualize through spectrum analyzer
            obj.VisualizeSpectrum = true;
            obj.ChannelWaveform = btSignal.Waveform(1:btSignal.NumSamples);
        end
        
        % Received a Bluetooth waveform and the node is listening for input
        % waveforms
        if (btSignal.NumSamples ~= 0) && (baseband.State == 2)
            % Invoke the PHY module
            [nextPHYTime, btWaveform] = run(phy, elapsedTime, btSignal);
            
            % PHY has decoded the waveform and returned the baseband data
            if phy.Decoded
                % Pass the decoded baseband data to baseband layer
                baseband.RxData = phy.DecodedBasebandData;
            else
                % Reset the baseband data
                baseband.RxData.PacketType = blanks(0);
            end
            
            % Update baseband reception slots
            if phy.UpdateReceptionSlots
                % Update the number of reception slots at baseband layer
                updateReceptionSlots(baseband, phy.RecvPktType)
            end
            
            % Invoke the baseband layer
            nextBBTime = run(baseband, elapsedTime);
            
            % Update the PHY with request from the baseband layer
            if baseband.SendRequest
                if isempty(baseband.TxData.PacketType)
                    updatePHY(phy, baseband.State, ...
                        baseband.ChannelIndex, baseband.WhitenInit);
                else
                    updatePHY(phy, baseband.State, ...
                        baseband.ChannelIndex, baseband.WhitenInit, ...
                        baseband.TxData);
                    
                    % Invoke the PHY module
                    [nextPHYTime, btWaveform] = run(phy, 0);
                end
                % Update the channel index to the channel model object
                channel.ChannelIndex = baseband.ChannelIndex;
            end
        else
            % Reset the baseband Rx data
            baseband.RxData.PacketType = blanks(0);
            
            % Invoke the baseband layer
            nextBBTime = run(baseband, elapsedTime);
            
            % Update the PHY with request from the baseband layer
            if baseband.SendRequest
                if isempty(baseband.TxData.PacketType)
                    updatePHY(phy, baseband.State, ...
                        baseband.ChannelIndex, baseband.WhitenInit);
                else
                    updatePHY(phy, baseband.State, ...
                        baseband.ChannelIndex, baseband.WhitenInit, ...
                        baseband.TxData);
                end
                % Update the channel index to the channel model object
                channel.ChannelIndex = baseband.ChannelIndex;
            end
            
            % Invoke the PHY module
            [nextPHYTime, btWaveform] = run(phy, elapsedTime);
            
            % PHY has decoded the waveform and returned the baseband data
            if phy.Decoded
                % Pass the decoded baseband data to baseband layer
                baseband.RxData = phy.DecodedBasebandData;
                nextBBTime = run(baseband, 0);
            else
                % Reset the baseband Rx data
                baseband.RxData.PacketType = blanks(0);
            end
            
            % Update baseband reception slots
            if phy.UpdateReceptionSlots
                % Update the number of reception slots at baseband layer
                updateReceptionSlots(baseband, phy.RecvPktType)
            end
        end
        
        % Update the transmitted waveform along with the metadata
        if ~isempty(btWaveform)
            obj.TxBuffer.PacketType = phy.WaveformConfig.PacketType;
            obj.TxBuffer.NumSamples = phy.pDuration * phy.SamplesPerSymbol;
            obj.TxBuffer.Waveform(1:obj.TxBuffer.NumSamples) = btWaveform(1:obj.TxBuffer.NumSamples);
            obj.TxBuffer.SampleRate = phy.SamplesPerSymbol * phy.SymbolRate;
            obj.TxBuffer.NodePosition = obj.NodePosition;
            obj.TxBuffer.Bandwidth = phy.ChannelBandwidth;
            obj.TxBuffer.CenterFrequency = centerFrequency(obj.PHY);
            obj.TxBuffer.SourceID = obj.NodeID;
            obj.TxBuffer.SamplesPerSymbol = phy.SamplesPerSymbol;
            obj.TxBuffer.PayloadLength = baseband.TxData.PayloadLength;
            obj.TxBuffer.Payload = baseband.TxData.Payload;
        end
        
        % Update the next invoke time as minimum of next invoke times of
        % all the modules
        invokeTimes = [nextPHYTime nextBBTime];
        nextInvokeTime = min(invokeTimes(invokeTimes ~= -1));
    end
end
end

% LocalWords:  PHY WLAN ACL SCO
