classdef helperBLEMeshNode < handle
%helperBLEMeshNode Create an object for Bluetooth mesh node
%   MESHNODE = helperBLEMeshNode creates a Bluetooth mesh node object with
%   properties and methods related to mesh node.
%
%   MESHNODE = helperBLEMeshNode(Name, Value) creates a Bluetooth mesh node
%   object 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).
%
%   helperBLEMeshNode properties:
%
%   Identifier      - Unique identification for a node
%   State           - Indicates whether the node is on/off
%   AccessLayer     - Access layer functionality
%   TransportLayer  - Transport layer functionality
%   NetworkLayer    - Network layer functionality
%   LLGAPBearer     - Link Layer state-machine for advertising bearer
%                     functionality

%   Copyright 2019 The MathWorks, Inc.

properties
    %Identifier Unique identification for a node
    %   Specify the identifier as a positive integer. This property
    %   indicates unique identification for the node in a Bluetooth mesh
    %   network. The default value is 1.
    Identifier = 1
    
    %State Indicates whether the node is on/off
    %   Specify the state property as one of 'On' | 'Off'. This property
    %   indicates the state of the node i.e. switched 'On' or 'Off'. The
    %   default value is 'On'.
    State = 'On'
    
    %AccessLayer Access layer functionality
    %   Specify this property as an object of type <a
    %   href="matlab:help('helperBLEMeshAccessLayer')">helperBLEMeshAccessLayer</a>.
    %   This object contains methods and properties related to Bluetooth
    %   mesh access layer.
    AccessLayer
    
    %TransportLayer Transport layer functionality
    %   Specify this property as an object of type <a
    %   href="matlab:help('helperBLEMeshTransportLayer')">helperBLEMeshTransportLayer</a>.
    %   This object contains methods and properties related to Bluetooth
    %   mesh transport layer and this object also supports Bluetooth mesh
    %   friend feature.
    TransportLayer
    
    %NetworkLayer Network layer functionality
    %   Specify this property as an object of type <a
    %   href="matlab:help('helperBLEMeshNetworkLayer')">helperBLEMeshNetworkLayer</a>.
    %   This object contains methods and properties related to Bluetooth
    %   mesh network layer.
    NetworkLayer
    
    %LLGAPBearer Link Layer state-machine for advertising bearer
    %functionality
    %   Specify this property as an object of type <a
    %   href="matlab:help('helperBLEMeshLLGAPBearer')">helperBLEMeshLLGAPBearer</a>.
    %   This object contains methods and properties related to BLE Link
    %   Layer supporting communication over mesh advertising bearer.
    LLGAPBearer
end

properties (Access = private)
    %pFriendState State at the transport layer when friend feature is
    %enabled
    pFriendState = 0
end

properties (Hidden, Constant)
    %AccessAddress Access address for the advertising channel PDUs
    AccessAddress = '8E89BED6'
    %StateValues State values indicating node on/off
    StateValues = {'On'; 'Off'}
end

properties (SetAccess = private)
    %AccessAddrFailedMsgs Number of messages received with invalid access
    %address
    AccessAddrFailedMsgs = 0
end

methods
    % Constructor
    function obj = helperBLEMeshNode(varargin)
        % Name-value pairs
        for idx = 1:2:nargin
            obj.(varargin{idx}) = varargin{idx+1};
        end
        % Initialize variable sized properties (for codegen support)
        % Access layer object
        obj.AccessLayer = helperBLEMeshAccessLayer();
        % Transport layer object
        obj.TransportLayer = helperBLEMeshTransportLayer();
        % Network layer object
        obj.NetworkLayer = helperBLEMeshNetworkLayer();
        % Link Layer object
        obj.LLGAPBearer = helperBLEMeshLLGAPBearer();
    end
    
    % For auto completion
    function v = set(obj, prop)
        v = obj.([prop, 'Values']);
    end
    
    % Set identifier
    function set.Identifier(obj, value)
        validateattributes(value, {'numeric'}, {'scalar', 'integer', ...
            '>=', 1, '<=', 1024}, mfilename, 'Identifier');
        obj.Identifier = value;
    end
    
    % Set state
    function set.State(obj, value)
        obj.State = validatestring(value, {'On'; 'Off'}, mfilename, 'State');
    end
    
    % Set access layer object
    function set.AccessLayer(obj, value)
        validateattributes(value, {'helperBLEMeshAccessLayer'}, {'scalar'}, ...
            mfilename, 'AccessLayer');
        obj.AccessLayer = value;
    end
    
    % Set transport layer object
    function set.TransportLayer(obj, value)
        validateattributes(value, {'helperBLEMeshTransportLayer'}, {'scalar'}, ...
            mfilename, 'TransportLayer');
        obj.TransportLayer = value;
    end
    
    % Set network layer object
    function set.NetworkLayer(obj, value)
        validateattributes(value, {'helperBLEMeshNetworkLayer'}, {'scalar'}, ...
            mfilename, 'NetworkLayer');
        obj.NetworkLayer = value;
    end
    
    % Set link layer object
    function set.LLGAPBearer(obj, value)
        validateattributes(value, {'helperBLEMeshLLGAPBearer'}, {'scalar'}, ...
            mfilename, 'LLGAPBearer');
        obj.LLGAPBearer = value;
    end
end

methods
    % Run node
    function [channelIndex, txMessage] = run(obj, elapsedTime, rxMessage)
        % Initialize
        txMessage = zeros(1, 0);
        channelIndex = -1;
        
        % Return, if node is switched off
        if strcmp(obj.State, 'Off')
            return;
        end
        
        % Validate access address for the received advertising channel
        % PDU
        rxMessage = validateAdvAccessAddress(obj, rxMessage);
        
        % Tx path
        if isempty(rxMessage)
            % Run access layer
            [accessMessage, sourceAddress, destinationAddress, ttl] = run(obj.AccessLayer, elapsedTime, [], '', '');
            if ~isempty(accessMessage)

                % Push access message into transport layer queue
                pushAccessMessage(obj.TransportLayer, accessMessage, sourceAddress, ...
                    destinationAddress, ttl);
            end
            
            % Run transport layer
            [transportPDU, ctl, src, dst, ttl] = run(obj.TransportLayer, [], -1, '', '');
            if ~isempty(transportPDU) && (ctl ~= -1)
  
                % Push transport PDU into Network Layer queue
                pushUpperLayerPDU(obj.NetworkLayer, src, dst, ttl, transportPDU, ctl);
                if ~strcmp(obj.TransportLayer.Feature, 'None')
                    obj.LLGAPBearer.pAdvertising = true;
                end
            end
            if strcmp(obj.TransportLayer.Feature, 'Low power')
                % Fetch friend state for Low Power node
                obj.pFriendState = obj.TransportLayer.LowPower.pState;
            end
            
            % Run network layer
            [~, networkPDU] = run(obj.NetworkLayer, elapsedTime, rxMessage);
            if ~isempty(networkPDU)
                % GAP data block with Advertising Data type 'Mesh Message'
                gapData = helperBLEMeshGAPDataBlock(networkPDU);
                
                % Push GAP data into BLE Link Layer queue
                pushGAPData(obj.LLGAPBearer, gapData);
            end
            
            % Run Link Layer (friend feature enabled)
            if strcmp(obj.TransportLayer.Feature, 'Low power')
                % Pass Low Power node state to Link Layer
                [~, txMessage, channelIndex, ~] = run(obj.LLGAPBearer, elapsedTime, rxMessage, obj.TransportLayer.LowPower.pState);
                
                % Update the advertising flag
                obj.TransportLayer.LowPower.FriendTimer.Advertising = obj.LLGAPBearer.pAdvertising;
            % Run Link Layer (friend feature disabled)
            else
                [~, txMessage, channelIndex, ~] = run(obj.LLGAPBearer, elapsedTime, rxMessage);
            end

        % Rx path
        else
            % Run Link Layer - decode Link Layer advertising channel PDU
            % Friend feature enabled
            if strcmp(obj.TransportLayer.Feature, 'Low power')
                % Pass Low Power node state to Link Layer
                [~, txMessage, channelIndex, ~] = run(obj.LLGAPBearer, elapsedTime, rxMessage, obj.TransportLayer.LowPower.pState);
                
                % Update the advertising flag
                obj.TransportLayer.LowPower.FriendTimer.Advertising = obj.LLGAPBearer.pAdvertising;
            % Friend feature disabled
            else
                [~, txMessage, channelIndex, ~] = run(obj.LLGAPBearer, elapsedTime, rxMessage);
            end
            % Decode GAP advertising data block of type 'Mesh Message'
            if (obj.LLGAPBearer.pGAPDataRxFlag)
                % Convert into decimal bytes
                gapData = obj.LLGAPBearer.pGAPDataRx;
                
                % Decode network PDU
                networkPDURx = helperBLEMeshGAPDataBlockDecode(gapData);
            else
                networkPDURx = zeros(1, 0);
            end
            
            % Run network layer - decode network PDU
            [~, networkPDU] = run(obj.NetworkLayer, elapsedTime, networkPDURx);
            if ~isempty(networkPDU)
                % GAP data block with Advertising Data type 'Mesh Message'
                gapData = helperBLEMeshGAPDataBlock(networkPDU);
                
                % Push GAP data into Link Layer queue
                pushGAPData(obj.LLGAPBearer, gapData);
            end
            % Message is destined to this node
            if obj.NetworkLayer.pMessageDestinedToNode
                lowerTransportPDU = obj.NetworkLayer.DecodedLowerTransportPDU;
                ctl = obj.NetworkLayer.pDecodedCTL;
                src = obj.NetworkLayer.pDecodedSRC;
                dst = obj.NetworkLayer.pDecodedDST;
                
            % Message is not destined to this node
            else
                lowerTransportPDU = zeros(1, 0);
                ctl = -1;
                src = blanks(0);
                dst = blanks(0);
            end
            
            % Run transport layer
            [transportPDU, ctl, src, dst, ttl] = ...
                run(obj.TransportLayer, lowerTransportPDU, ctl, src, dst);
            if ~isempty(transportPDU) && (ctl ~= -1)
                % Push transport PDU into Network Layer queue
                pushUpperLayerPDU(obj.NetworkLayer, src, dst, ttl, transportPDU, ctl);
                if ~strcmp(obj.TransportLayer.Feature, 'None')
                    obj.LLGAPBearer.pAdvertising = true;
                end
            end
            if strcmp(obj.TransportLayer.Feature, 'Low power')
                % Fetch friend state for Low Power node
                obj.pFriendState = obj.TransportLayer.LowPower.pState;
            end
            % Access message is destined to one of the element addresses
            if obj.TransportLayer.pAccessMessageReceived
                accessMessage = obj.TransportLayer.pDecodedAccessMessage;
                src = obj.TransportLayer.pDecodedSRC;
                dst = obj.TransportLayer.pDecodedDST;
            % Access message is not destined to the elements in this node
            else
                accessMessage = zeros(1, 0);
                src = blanks(0);
                dst = blanks(0);
            end
            
            % Run access layer
            [accessMessage, sourceAddress, destinationAddress, ttl] = run(obj.AccessLayer, elapsedTime, accessMessage, src, dst);
            if ~isempty(accessMessage)
                % Push access message into transport layer queue
                pushAccessMessage(obj.TransportLayer, accessMessage, sourceAddress, ...
                    destinationAddress, ttl);
            end
        end
        % Prepend access address for advertising channel PDUs
        if ~isempty(txMessage)
            txMessage = helperBLEPrependAccessAddress(txMessage, obj.AccessAddress);
        end
    end
end
methods (Access = private)
    function advPDU = validateAdvAccessAddress(obj, rxPDU)
        advPDU = zeros(1, 0);
        accessAddressLen = 32;
        if isempty(rxPDU) || numel(rxPDU) < accessAddressLen
            return;
        end
        rxAccessAddress = dec2hex(bi2de(rxPDU(1:accessAddressLen)'), 8);
        % Access address matched
        if strcmp(rxAccessAddress, obj.AccessAddress)
            advPDU = rxPDU(accessAddressLen+1:end);
        % Access address mismatched
        else
            obj.AccessAddrFailedMsgs = obj.AccessAddrFailedMsgs + 1;
        end
    end
end
end
