<commit_before>%
% Copyright (c) 2016-2017 <NAME> <<EMAIL>>
% All rights reserved.
% Distributed under the terms of the MIT License. See the LICENSE file.
%
-module(lorawan_mac_commands).

-export([handle/4, build_fopts/1]).

-include_lib("lorawan_server_api/include/lorawan_application.hrl").

handle(RxQ, Link, FOpts, RxFrame) ->
    FOptsIn = parse_fopts(FOpts),
    case FOptsIn of
        [] -> ok;
        List1 -> lager:debug("~s -> ~w", [lorawan_mac:binary_to_hex(Link#link.devaddr), List1])
    end,
    % process incoming responses
    {MacConfirm, Link2} = handle_rxwin(FOptsIn,
        handle_adr(FOptsIn,
        handle_status(FOptsIn, Link))),
    {Link3, RxFrame2} = auto_adr(RxQ, Link2, RxFrame),
    % process requests
    FOptsOut =
        lists:foldl(
            fun (link_check_req, Acc) -> [send_link_check(RxQ) | Acc];
                (_Else, Acc) -> Acc
            end,
            [], FOptsIn),
    % check for new requests
    {ok, MacConfirm, Link3, build_fopts(Link3, FOptsOut), RxFrame2}.

build_fopts(Link) ->
    build_fopts(Link, []).

build_fopts(Link, FOptsOut0) ->
    FOptsOut = send_adr(Link,
        set_rxwin(Link,
        request_status(Link, FOptsOut0))),
    case FOptsOut of
        [] -> ok;
        List2 -> lager:debug("~s <- ~w", [lorawan_mac:binary_to_hex(Link#link.devaddr), List2])
    end,
    encode_fopts(FOptsOut).


parse_fopts(<<16#02, Rest/binary>>) ->
    [link_check_req | parse_fopts(Rest)];
parse_fopts(<<16#03, _RFU:5, PowerACK:1, DataRateACK:1, ChannelMaskACK:1, Rest/binary>>) ->
    [{link_adr_ans, PowerACK, DataRateACK, ChannelMaskACK} | parse_fopts(Rest)];
parse_fopts(<<16#04, Rest/binary>>) ->
    [duty_cycle_ans | parse_fopts(Rest)];
parse_fopts(<<16#05, _RFU:5, RX1DROffsetACK:1, RX2DataRateACK:1, ChannelACK:1, Rest/binary>>) ->
    [{rx_param_setup_ans, RX1DROffsetACK, RX2DataRateACK, ChannelACK} | parse_fopts(Rest)];
parse_fopts(<<16#06, Battery:8, _RFU:2, Margin:6, Rest/binary>>) ->
    [{dev_status_ans, Battery, Margin} | parse_fopts(Rest)];
parse_fopts(<<16#07, _RFU:6, DataRateRangeOK:1, ChannelFreqOK:1, Rest/binary>>) ->
    [{new_channel_ans, DataRateRangeOK, ChannelFreqOK} | parse_fopts(Rest)];
parse_fopts(<<16#08, Rest/binary>>) ->
    [rx_timing_setup_ans | parse_fopts(Rest)];
parse_fopts(<<>>) ->
    [];
parse_fopts(Unknown) ->
    lager:warning("Unknown command ~p", [Unknown]),
    [].

encode_fopts([{link_check_ans, Margin, GwCnt} | Rest]) ->
    <<16#02, Margin, GwCnt, (encode_fopts(Rest))/binary>>;
encode_fopts([{link_adr_req, DataRate, TXPower, ChMask, ChMaskCntl, NbRep} | Rest]) ->
    <<16#03, DataRate:4, TXPower:4, ChMask:16/little-unsigned-integer, 0:1, ChMaskCntl:3, NbRep:4, (encode_fopts(Rest))/binary>>;
encode_fopts([{duty_cycle_req, MaxDCycle} | Rest]) ->
    <<16#04, MaxDCycle, (encode_fopts(Rest))/binary>>;
encode_fopts([{rx_param_setup_req, RX1DROffset, RX2DataRate, Frequency} | Rest]) ->
    <<16#05, 0:1, RX1DROffset:3, RX2DataRate:4, Frequency:24/little-unsigned-integer, (encode_fopts(Rest))/binary>>;
encode_fopts([dev_status_req | Rest]) ->
    <<16#06, (encode_fopts(Rest))/binary>>;
encode_fopts([{new_channel_req, ChIndex, Freq, MaxDR, MinDR} | Rest]) ->
    <<16#07, ChIndex, Freq:24/little-unsigned-integer, MaxDR:4, MinDR:4, (encode_fopts(Rest))/binary>>;
encode_fopts([{rx_timing_setup_req, Delay} | Rest]) ->
    <<16#08, 0:4, Delay:4, (encode_fopts(Rest))/binary>>;
encode_fopts([]) ->
    <<>>.


handle_adr(FOptsIn, Link) ->
    case find_adr(FOptsIn) of
        {1, 1, 1} ->
            lager:debug("LinkADRReq ~s succeeded", [lorawan_mac:binary_to_hex(Link#link.devaddr)]),
            Link#link{adr_use=Link#link.adr_set, devstat_fcnt=undefined, last_qs=[]};
        {PowerACK, DataRateACK, ChannelMaskACK} ->
            lorawan_utils:throw_warning({node, Link#link.devaddr}, {adr_req_failed, {PowerACK, DataRateACK, ChannelMaskACK}}),
            {TXPower, DataRate, Chans} = Link#link.adr_set,
            % clear the settings that failed
            Link#link{adr_set = {clear_when_zero(PowerACK, TXPower), clear_when_zero(DataRateACK, DataRate),
                clear_when_zero(ChannelMaskACK, Chans)}};
        undefined ->
            Link
    end.

find_adr(FOptsIn) ->
    lists:foldr(
        fun ({link_adr_ans, Power, DataRate, ChannelMask}, undefined) ->
                {Power, DataRate, ChannelMask};
            ({link_adr_ans, _Power, _DataRate, ChannelMask}, {LastPower, LastDataRate, LastChannelMask}) ->
                % all ChannelMasks must be accepted
                % the device processes the DataRate, TXPower and NbTrans from the last message only
                {LastPower, LastDataRate, ChannelMask band LastChannelMask};
            (_Else, Last) -> Last
        end,
        undefined, FOptsIn).

handle_rxwin(FOptsIn, Link) ->
    case find_rxwin(FOptsIn) of
        {1, 1, 1} ->
            lager:debug("RXParamSetupAns ~s succeeded", [lorawan_mac:binary_to_hex(Link#link.devaddr)]),
            {RX1DROffset, _, _} = Link#link.rxwin_set,
            {_, RX2DataRate, Frequency} = lorawan_mac_region:default_rxwin(Link#link.region),
            {true, Link#link{rxwin_use={RX1DROffset, RX2DataRate, Frequency},
                rxwin_set={RX1DROffset, RX2DataRate, Frequency}}};
        {RX1DROffsetACK, RX2DataRateACK, ChannelACK} ->
            lorawan_utils:throw_warning({node, Link#link.devaddr}, {rxwin_setup_failed, {RX1DROffsetACK, RX2DataRateACK, ChannelACK}}),
            {RX1DROffset, RX2DataRate, Frequency} = Link#link.rxwin_set,
            % clear the settings that failed
            {true, Link#link{rxwin_set = {clear_when_zero(RX1DROffset, RX1DROffsetACK),
                clear_when_zero(RX2DataRate, RX2DataRateACK),
                clear_when_zero(Frequency, ChannelACK)}}};
        undefined ->
            {false, Link}
    end.

find_rxwin(FOptsIn) ->
    lists:foldl(
        fun ({rx_param_setup_ans, RX1DROffsetACK, RX2DataRateACK, ChannelACK}, _Prev) -> {RX1DROffsetACK, RX2DataRateACK, ChannelACK};
            (_Else, Last) -> Last
        end,
        undefined, FOptsIn).

handle_status(FOptsIn, Link) ->
    case find_status(FOptsIn) of
        {Battery, Margin} ->
            % compute a maximal D/L SNR
            {_, DataRate, _} = Link#link.adr_use,
            OffUse = case Link#link.rxwin_use of
                {Num2, _, _} when is_number(Num2) -> Num2;
                _Else2 -> 0
            end,
            MaxSNR = lorawan_mac_region:max_downlink_snr(Link#link.region, DataRate, OffUse),
            lager:debug("DevStatus: battery ~B, margin: ~B (max ~.1f)", [Battery, Margin-32, MaxSNR]),
            Link#link{devstat_time=calendar:universal_time(), devstat_fcnt=Link#link.fcntup,
                devstat=append_status({calendar:universal_time(), Battery, Margin-32, MaxSNR}, Link#link.devstat)};
        undefined ->
            Link<commit_msg>Issue #206: Correct margin calculation<commit_after>%
% Copyright (c) 2016-2017 <NAME> <<EMAIL>>
% All rights reserved.
% Distributed under the terms of the MIT License. See the LICENSE file.
%
-module(lorawan_mac_commands).

-export([handle/4, build_fopts/1]).

-include_lib("lorawan_server_api/include/lorawan_application.hrl").

handle(RxQ, Link, FOpts, RxFrame) ->
    FOptsIn = parse_fopts(FOpts),
    case FOptsIn of
        [] -> ok;
        List1 -> lager:debug("~s -> ~w", [lorawan_mac:binary_to_hex(Link#link.devaddr), List1])
    end,
    % process incoming responses
    {MacConfirm, Link2} = handle_rxwin(FOptsIn,
        handle_adr(FOptsIn,
        handle_status(FOptsIn, Link))),
    {Link3, RxFrame2} = auto_adr(RxQ, Link2, RxFrame),
    % process requests
    FOptsOut =
        lists:foldl(
            fun (link_check_req, Acc) -> [send_link_check(RxQ) | Acc];
                (_Else, Acc) -> Acc
            end,
            [], FOptsIn),
    % check for new requests
    {ok, MacConfirm, Link3, build_fopts(Link3, FOptsOut), RxFrame2}.

build_fopts(Link) ->
    build_fopts(Link, []).

build_fopts(Link, FOptsOut0) ->
    FOptsOut = send_adr(Link,
        set_rxwin(Link,
        request_status(Link, FOptsOut0))),
    case FOptsOut of
        [] -> ok;
        List2 -> lager:debug("~s <- ~w", [lorawan_mac:binary_to_hex(Link#link.devaddr), List2])
    end,
    encode_fopts(FOptsOut).


parse_fopts(<<16#02, Rest/binary>>) ->
    [link_check_req | parse_fopts(Rest)];
parse_fopts(<<16#03, _RFU:5, PowerACK:1, DataRateACK:1, ChannelMaskACK:1, Rest/binary>>) ->
    [{link_adr_ans, PowerACK, DataRateACK, ChannelMaskACK} | parse_fopts(Rest)];
parse_fopts(<<16#04, Rest/binary>>) ->
    [duty_cycle_ans | parse_fopts(Rest)];
parse_fopts(<<16#05, _RFU:5, RX1DROffsetACK:1, RX2DataRateACK:1, ChannelACK:1, Rest/binary>>) ->
    [{rx_param_setup_ans, RX1DROffsetACK, RX2DataRateACK, ChannelACK} | parse_fopts(Rest)];
parse_fopts(<<16#06, Battery:8, _RFU:2, Margin:6/signed, Rest/binary>>) ->
    [{dev_status_ans, Battery, Margin} | parse_fopts(Rest)];
parse_fopts(<<16#07, _RFU:6, DataRateRangeOK:1, ChannelFreqOK:1, Rest/binary>>) ->
    [{new_channel_ans, DataRateRangeOK, ChannelFreqOK} | parse_fopts(Rest)];
parse_fopts(<<16#08, Rest/binary>>) ->
    [rx_timing_setup_ans | parse_fopts(Rest)];
parse_fopts(<<>>) ->
    [];
parse_fopts(Unknown) ->
    lager:warning("Unknown command ~p", [Unknown]),
    [].

encode_fopts([{link_check_ans, Margin, GwCnt} | Rest]) ->
    <<16#02, Margin, GwCnt, (encode_fopts(Rest))/binary>>;
encode_fopts([{link_adr_req, DataRate, TXPower, ChMask, ChMaskCntl, NbRep} | Rest]) ->
    <<16#03, DataRate:4, TXPower:4, ChMask:16/little-unsigned-integer, 0:1, ChMaskCntl:3, NbRep:4, (encode_fopts(Rest))/binary>>;
encode_fopts([{duty_cycle_req, MaxDCycle} | Rest]) ->
    <<16#04, MaxDCycle, (encode_fopts(Rest))/binary>>;
encode_fopts([{rx_param_setup_req, RX1DROffset, RX2DataRate, Frequency} | Rest]) ->
    <<16#05, 0:1, RX1DROffset:3, RX2DataRate:4, Frequency:24/little-unsigned-integer, (encode_fopts(Rest))/binary>>;
encode_fopts([dev_status_req | Rest]) ->
    <<16#06, (encode_fopts(Rest))/binary>>;
encode_fopts([{new_channel_req, ChIndex, Freq, MaxDR, MinDR} | Rest]) ->
    <<16#07, ChIndex, Freq:24/little-unsigned-integer, MaxDR:4, MinDR:4, (encode_fopts(Rest))/binary>>;
encode_fopts([{rx_timing_setup_req, Delay} | Rest]) ->
    <<16#08, 0:4, Delay:4, (encode_fopts(Rest))/binary>>;
encode_fopts([]) ->
    <<>>.


handle_adr(FOptsIn, Link) ->
    case find_adr(FOptsIn) of
        {1, 1, 1} ->
            lager:debug("LinkADRReq ~s succeeded", [lorawan_mac:binary_to_hex(Link#link.devaddr)]),
            Link#link{adr_use=Link#link.adr_set, devstat_fcnt=undefined, last_qs=[]};
        {PowerACK, DataRateACK, ChannelMaskACK} ->
            lorawan_utils:throw_warning({node, Link#link.devaddr}, {adr_req_failed, {PowerACK, DataRateACK, ChannelMaskACK}}),
            {TXPower, DataRate, Chans} = Link#link.adr_set,
            % clear the settings that failed
            Link#link{adr_set = {clear_when_zero(PowerACK, TXPower), clear_when_zero(DataRateACK, DataRate),
                clear_when_zero(ChannelMaskACK, Chans)}};
        undefined ->
            Link
    end.

find_adr(FOptsIn) ->
    lists:foldr(
        fun ({link_adr_ans, Power, DataRate, ChannelMask}, undefined) ->
                {Power, DataRate, ChannelMask};
            ({link_adr_ans, _Power, _DataRate, ChannelMask}, {LastPower, LastDataRate, LastChannelMask}) ->
                % all ChannelMasks must be accepted
                % the device processes the DataRate, TXPower and NbTrans from the last message only
                {LastPower, LastDataRate, ChannelMask band LastChannelMask};
            (_Else, Last) -> Last
        end,
        undefined, FOptsIn).

handle_rxwin(FOptsIn, Link) ->
    case find_rxwin(FOptsIn) of
        {1, 1, 1} ->
            lager:debug("RXParamSetupAns ~s succeeded", [lorawan_mac:binary_to_hex(Link#link.devaddr)]),
            {RX1DROffset, _, _} = Link#link.rxwin_set,
            {_, RX2DataRate, Frequency} = lorawan_mac_region:default_rxwin(Link#link.region),
            {true, Link#link{rxwin_use={RX1DROffset, RX2DataRate, Frequency},
                rxwin_set={RX1DROffset, RX2DataRate, Frequency}}};
        {RX1DROffsetACK, RX2DataRateACK, ChannelACK} ->
            lorawan_utils:throw_warning({node, Link#link.devaddr}, {rxwin_setup_failed, {RX1DROffsetACK, RX2DataRateACK, ChannelACK}}),
            {RX1DROffset, RX2DataRate, Frequency} = Link#link.rxwin_set,
            % clear the settings that failed
            {true, Link#link{rxwin_set = {clear_when_zero(RX1DROffset, RX1DROffsetACK),
                clear_when_zero(RX2DataRate, RX2DataRateACK),
                clear_when_zero(Frequency, ChannelACK)}}};
        undefined ->
            {false, Link}
    end.

find_rxwin(FOptsIn) ->
    lists:foldl(
        fun ({rx_param_setup_ans, RX1DROffsetACK, RX2DataRateACK, ChannelACK}, _Prev) -> {RX1DROffsetACK, RX2DataRateACK, ChannelACK};
            (_Else, Last) -> Last
        end,
        undefined, FOptsIn).

handle_status(FOptsIn, Link) ->
    case find_status(FOptsIn) of
        {Battery, Margin} ->
            % compute a maximal D/L SNR
            {_, DataRate, _} = Link#link.adr_use,
            OffUse = case Link#link.rxwin_use of
                {Num2, _, _} when is_number(Num2) -> Num2;
                _Else2 -> 0
            end,
            MaxSNR = lorawan_mac_region:max_downlink_snr(Link#link.region, DataRate, OffUse),
            lager:debug("DevStatus: battery ~B, margin: ~B (max ~.1f)", [Battery, Margin, MaxSNR]),
            Link#link{devstat_time=calendar:universal_time(), devstat_fcnt=Link#link.fcntup,
                devstat=append_status({calendar:universal_time(), Battery, Margin, MaxSNR}, Link#link.devstat)};
        undefined ->
            Link
{'fraction_non_alphanumeric': 0.14521044992743107, 'fraction_numerical': 0.022859216255442672, 'mean_word_length': 3.4198203976908275, 'pattern_counts': {'":': 0, '<': 75, '<?xml version=': 0, '>': 167, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3489633', 'n_tokens_mistral': 5175, 'n_tokens_neox': 4890, 'n_words': 863}